Resolve Rule Variables
Given a product and a list of rule IDs that have just been selected for a control mapping, determine which XCCDF variables each rule depends on and interactively collect a value selection for each.
This skill is a sub-skill called by other skills (e.g., assess-nist-control) after rule selection. It can also be invoked directly when an author wants to inspect or change variable selections for rules already in a control file.
Arguments: $ARGUMENTS — format: <product> <rule_id> [<rule_id> ...] [--cis-vars <var=key> [...]]
Examples:
- Used as a sub-skill after rule selection in
assess-nist-control Phase 3
/resolve-rule-variables rhel9 accounts_password_pam_dcredit accounts_password_pam_minlen
/resolve-rule-variables rhel9 accounts_tmout --cis-vars var_accounts_tmout=600
The --cis-vars flag passes pre-existing variable selections from CIS mappings (or other sources) as
default suggestions. Each entry is var_name=key matching the .var file option key.
Phase 1: Load Variable Mapping
Parse arguments: Extract product and rule_ids from $ARGUMENTS. Also extract any --cis-vars entries (format: var_name=key).
Check the mapping file:
ls build/$PRODUCT/rule_variable_mapping.json
If the file does not exist:
Note: build/<product>/rule_variable_mapping.json not found. Build the product first to enable automatic variable detection:
./build_product <product> -d
Proceeding without variable resolution. Add variable selections manually as var_name=key entries in the rules list if needed.
Stop and return no selections if the file is missing.
Look up variables for each rule:
python3 -c "
import json, sys, os, glob
product = sys.argv[1]
rule_ids = sys.argv[2:]
with open(f'build/{product}/rule_variable_mapping.json') as f:
rule_var_map = json.load(f)
# Collect all variables across the selected rules, deduplicating
seen_vars = {}
for rule_id in rule_ids:
for var_name in rule_var_map.get(rule_id, []):
if var_name not in seen_vars:
seen_vars[var_name] = []
seen_vars[var_name].append(rule_id)
# For each variable, find the .var file and read options
result = {}
for var_name, used_by in seen_vars.items():
var_files = (glob.glob(f'linux_os/**/{var_name}.var', recursive=True) +
glob.glob(f'shared/**/{var_name}.var', recursive=True) +
glob.glob(f'applications/**/{var_name}.var', recursive=True))
if not var_files:
result[var_name] = {'title': var_name, 'type': 'unknown', 'options': {}, 'used_by': used_by}
continue
import yaml
with open(var_files[0]) as f:
var_data = yaml.safe_load(f)
result[var_name] = {
'title': var_data.get('title', var_name),
'type': var_data.get('type', 'string'),
'options': var_data.get('options', {}),
'used_by': used_by,
}
print(json.dumps(result, indent=2))
" "$PRODUCT" $RULE_IDS
If no variables are found for any of the rules, output an empty {} and stop — no variable selections needed. Report: "No variable dependencies found for the selected rules."
Phase 2: Present Variable Selection
For each variable found in Phase 1 (process in alphabetical order):
Determine the default key: The default key is the key named "default" in the options dict. If no key is named "default", look for the key whose value matches the majority/common case. Note the default value for display.
Check for a CIS pre-selection: If --cis-vars included {var_name}=<key>, pre-mark that option as the suggested default in the question description.
Ask via AskUserQuestion:
Question: "Select value for \{var_name}` — {title}"`
Description on the question itself: "Used by: {rule_id1}, {rule_id2}..."
Options (limit to 4 total including the "Use default" option; if more keys exist, show the most semantically meaningful ones):
For each key in options (up to 3 non-default keys):
label: the key string (e.g., 1, 15_min, never)
description: "actual value: {value}" — add "(CIS suggested)" if this key matches the --cis-vars selection, or "(Default)" if this key is the default key
Always add a final option:
label: "Use default (omit variable)"
description: "actual value: {default_value} — variable omitted from rules list, scanner uses the .var file default automatically"
Example for var_password_pam_dcredit with options: {"0": "0", 1: -1, 2: -2, default: -1}:
label: "1" description: "actual value: -1 (CIS suggested)"
label: "2" description: "actual value: -2"
label: "0" description: "actual value: 0"
label: "Use default (omit variable)" description: "actual value: -1 — variable omitted from rules list, scanner uses the .var file default automatically"
→ If key 1 chosen: written as var_password_pam_dcredit=1
→ If "Use default" chosen: variable NOT added to the rules list
Record the selection:
- If a key was chosen: add
{var_name}={key} to the output selections list
- If "Use default" was chosen: do not add anything for this variable
Phase 3: Report Selections
After all variables are processed, display a summary:
### Variable Selections
| Variable | Key | Actual Value | Status |
|----------|-----|--------------|--------|
| var_password_pam_dcredit | 1 | -1 | selected |
| var_accounts_tmout | default | 900 | omitted (using .var default) |
Return the selections as a list of var_name=key entries. These are ready to be included in the control file's rules: list alongside the rule IDs.
Important Notes
- Key, not value: The
rules: list stores the key from the .var file's options dict — not the actual value the key resolves to. var_password_pam_dcredit=1 means key 1, which resolves to actual value -1.
- Deduplicate shared variables: If two rules both use
var_accounts_tmout, ask for the value once and apply it once to the rules list.
- Variable is mandatory if a rule uses it: Do not finalize a mapping without a variable selection when
rule_variable_mapping.json shows the rule requires one. A rule written without its variable produces an incomplete control mapping.
- Planned migration: Variable selections are currently written inline in control files alongside rule IDs. The long-term plan is to extract all variable selections into a dedicated per-product file so authors can review and customize values in one place. Until that migration lands, continue writing
var_name=key inline.
Error Handling
rule_variable_mapping.json missing: Stop and report — build the product first.
.var file not found for a variable: Ask the author to enter the key manually via AskUserQuestion with an "Other" option. Show the variable name and note the .var file was not found.
- Rule not in
rule_variable_mapping.json: The rule either has no XCCDF variables or was not present in the last build. Treat as having no variables — no warning.
- Options dict is empty: Show the variable name as informational. Offer "Use default (omit variable)" as the only option.
1---2name: resolve-rule-variables3description: Resolve XCCDF variable selections for a set of rules. Looks up which variables each rule depends on, reads their .var files, and guides the author to select a value key for each variable. Returns a list of var_name=key selections ready to add to a control file's rules list.4---56# Resolve Rule Variables78Given a product and a list of rule IDs that have just been selected for a control mapping, determine which XCCDF variables each rule depends on and interactively collect a value selection for each.910This skill is a sub-skill called by other skills (e.g., `assess-nist-control`) after rule selection. It can also be invoked directly when an author wants to inspect or change variable selections for rules already in a control file.1112**Arguments**: `$ARGUMENTS` — format: `<product> <rule_id> [<rule_id> ...] [--cis-vars <var=key> [...]]`1314Examples:15- Used as a sub-skill after rule selection in `assess-nist-control` Phase 316- `/resolve-rule-variables rhel9 accounts_password_pam_dcredit accounts_password_pam_minlen`17- `/resolve-rule-variables rhel9 accounts_tmout --cis-vars var_accounts_tmout=600`1819The `--cis-vars` flag passes pre-existing variable selections from CIS mappings (or other sources) as20default suggestions. Each entry is `var_name=key` matching the `.var` file option key.2122## Phase 1: Load Variable Mapping23241. **Parse arguments**: Extract `product` and `rule_ids` from `$ARGUMENTS`. Also extract any `--cis-vars` entries (format: `var_name=key`).25262. **Check the mapping file**:27 ```bash28 ls build/$PRODUCT/rule_variable_mapping.json29 ```30 If the file does not exist:31 > **Note**: `build/<product>/rule_variable_mapping.json` not found. Build the product first to enable automatic variable detection:32 > ```33 > ./build_product <product> -d34 > ```35 > Proceeding without variable resolution. Add variable selections manually as `var_name=key` entries in the rules list if needed.3637 Stop and return no selections if the file is missing.38393. **Look up variables for each rule**:40 ```bash41 python3 -c "42 import json, sys, os, glob4344 product = sys.argv[1]45 rule_ids = sys.argv[2:]4647 with open(f'build/{product}/rule_variable_mapping.json') as f:48 rule_var_map = json.load(f)4950 # Collect all variables across the selected rules, deduplicating51 seen_vars = {}52 for rule_id in rule_ids:53 for var_name in rule_var_map.get(rule_id, []):54 if var_name not in seen_vars:55 seen_vars[var_name] = []56 seen_vars[var_name].append(rule_id)5758 # For each variable, find the .var file and read options59 result = {}60 for var_name, used_by in seen_vars.items():61 var_files = (glob.glob(f'linux_os/**/{var_name}.var', recursive=True) +62 glob.glob(f'shared/**/{var_name}.var', recursive=True) +63 glob.glob(f'applications/**/{var_name}.var', recursive=True))64 if not var_files:65 result[var_name] = {'title': var_name, 'type': 'unknown', 'options': {}, 'used_by': used_by}66 continue67 import yaml68 with open(var_files[0]) as f:69 var_data = yaml.safe_load(f)70 result[var_name] = {71 'title': var_data.get('title', var_name),72 'type': var_data.get('type', 'string'),73 'options': var_data.get('options', {}),74 'used_by': used_by,75 }7677 print(json.dumps(result, indent=2))78 " "$PRODUCT" $RULE_IDS79 ```8081 If no variables are found for any of the rules, output an empty `{}` and stop — no variable selections needed. Report: "No variable dependencies found for the selected rules."8283## Phase 2: Present Variable Selection8485For each variable found in Phase 1 (process in alphabetical order):86871. **Determine the default key**: The default key is the key named `"default"` in the options dict. If no key is named `"default"`, look for the key whose value matches the majority/common case. Note the default value for display.88892. **Check for a CIS pre-selection**: If `--cis-vars` included `{var_name}=<key>`, pre-mark that option as the suggested default in the question description.90913. **Ask via `AskUserQuestion`**:92 - Question: `"Select value for \`{var_name}\` — {title}"`93 - Description on the question itself: `"Used by: {rule_id1}, {rule_id2}..."`94 - Options (limit to 4 total including the "Use default" option; if more keys exist, show the most semantically meaningful ones):9596 For each key in `options` (up to 3 non-default keys):97 - `label`: the key string (e.g., `1`, `15_min`, `never`)98 - `description`: `"actual value: {value}"` — add `"(CIS suggested)"` if this key matches the `--cis-vars` selection, or `"(Default)"` if this key is the default key99100 Always add a final option:101 - `label`: `"Use default (omit variable)"`102 - `description`: `"actual value: {default_value} — variable omitted from rules list, scanner uses the .var file default automatically"`103104 **Example** for `var_password_pam_dcredit` with `options: {"0": "0", 1: -1, 2: -2, default: -1}`:105 ```106 label: "1" description: "actual value: -1 (CIS suggested)"107 label: "2" description: "actual value: -2"108 label: "0" description: "actual value: 0"109 label: "Use default (omit variable)" description: "actual value: -1 — variable omitted from rules list, scanner uses the .var file default automatically"110 ```111 → If key `1` chosen: written as `var_password_pam_dcredit=1`112 → If "Use default" chosen: variable NOT added to the rules list1131144. **Record the selection**:115 - If a key was chosen: add `{var_name}={key}` to the output selections list116 - If "Use default" was chosen: do not add anything for this variable117118## Phase 3: Report Selections119120After all variables are processed, display a summary:121122```123### Variable Selections124125| Variable | Key | Actual Value | Status |126|----------|-----|--------------|--------|127| var_password_pam_dcredit | 1 | -1 | selected |128| var_accounts_tmout | default | 900 | omitted (using .var default) |129```130131Return the selections as a list of `var_name=key` entries. These are ready to be included in the control file's `rules:` list alongside the rule IDs.132133## Important Notes134135- **Key, not value**: The `rules:` list stores the **key** from the `.var` file's `options` dict — not the actual value the key resolves to. `var_password_pam_dcredit=1` means key `1`, which resolves to actual value `-1`.136- **Deduplicate shared variables**: If two rules both use `var_accounts_tmout`, ask for the value once and apply it once to the rules list.137- **Variable is mandatory if a rule uses it**: Do not finalize a mapping without a variable selection when `rule_variable_mapping.json` shows the rule requires one. A rule written without its variable produces an incomplete control mapping.138- **Planned migration**: Variable selections are currently written inline in control files alongside rule IDs. The long-term plan is to extract all variable selections into a dedicated per-product file so authors can review and customize values in one place. Until that migration lands, continue writing `var_name=key` inline.139140## Error Handling141142- **`rule_variable_mapping.json` missing**: Stop and report — build the product first.143- **`.var` file not found for a variable**: Ask the author to enter the key manually via `AskUserQuestion` with an "Other" option. Show the variable name and note the `.var` file was not found.144- **Rule not in `rule_variable_mapping.json`**: The rule either has no XCCDF variables or was not present in the last build. Treat as having no variables — no warning.145- **Options dict is empty**: Show the variable name as informational. Offer "Use default (omit variable)" as the only option.