with one click
create-rule
Create a new security rule with all required components
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Menu
Create a new security rule with all required components
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Based on SOC occupation classification
| name | create-rule |
| description | Create a new security rule with all required components |
Create a new security rule for ComplianceAsCode. This skill handles both templated and non-templated rules.
Rule ID: $ARGUMENTS
This skill uses mcp__content-agent__* tools when available (preferred — deterministic, structured results). When the MCP server is not configured, fall back to filesystem-based alternatives noted as Fallback in each step. See .claude/skills/shared/mcp_fallbacks.md for detailed fallback procedures. The skill must complete successfully either way.
Validate rule ID format:
sshd_max_auth_tries, accounts_password_minlenCheck if rule already exists:
Use mcp__content-agent__search_rules with query=$ARGUMENTS to check if a rule with this ID already exists.
Fallback: Use Glob to search for **/$ARGUMENTS/rule.yml.
Determine rule location:
sshd_* goes under linux_os/guide/services/ssh/ssh_server/)Use AskUserQuestion to ask the user:
Question: "What type of rule do you want to create?"
Options:
If user chose templated rule:
List available templates using mcp__content-agent__list_templates to get all available templates with their descriptions.
Fallback: Run ls shared/templates/ to list available template directories.
Present available templates from the list obtained in step 1 and help the user select the right one based on their use case.
Ask for template selection using AskUserQuestion
Get template parameter schema using mcp__content-agent__get_template_schema with template_name=<selected_template> to get the full parameter schema, supported languages, and documentation.
Fallback: Read shared/templates/<template_name>/template.yml or template.py for parameter definitions. Check existing rules using this template for usage examples.
Collect template variables:
Collect the following information using AskUserQuestion or prompts:
Title: Short descriptive title (displayed in scan results)
Description: Detailed description using Jinja2 templating
{{{ sshd_config_file() }}}, {{{ describe_mount(...) }}}Rationale: Why this rule is important for security
Severity: One of low, medium, high, unknown. Default value would be medium.
CCE identifiers: Ask which RHEL products need CCEs (rhel8, rhel9, rhel10)
cce@rhel9: CCE-XXXXX-Xshared/references/cce-redhat-avail.txtrule.yml, remove it from cce-redhat-avail.txt to prevent reuse# Read first available CCE
head -1 shared/references/cce-redhat-avail.txt
# After using a CCE, remove it from the file
sed -i '1d' shared/references/cce-redhat-avail.txt
nist: NIST SP 800-53 controls (e.g., AC-6(2),CM-6(a))srg: DISA SRG IDs (e.g., SRG-OS-000480-GPOS-00227)stigid and cis references are filled in automatically by placing the rule into an appropriate control filePlatform (optional): Platform applicability
platform: machine (not for containers)platform: package[openssh-server]OCIL (optional): OCIL check description
{{{ complete_ocil_entry_sshd_option(...) }}}Use mcp__content-agent__generate_rule_from_template with:
template_name: The selected template nameparameters: Template variables collected from the userrule_id: $ARGUMENTSproduct: The target productThis generates the rule directory and rule.yml with the template configuration automatically.
Fallback: Create the rule directory and rule.yml manually using Write tool. Include the template: key with name: and vars: based on the selected template.
Use mcp__content-agent__generate_rule_boilerplate with:
rule_id: $ARGUMENTStitle: The rule titledescription: The rule descriptionseverity: The severity levelproduct: The target productrationale: The rationale (optional)location: The guide path (optional, e.g., linux_os/guide/services/ssh)Fallback: Create the directory structure and all files manually using Write tool, following the skeleton templates below.
This generates the rule directory structure with skeleton files:
<location>/$ARGUMENTS/
├── rule.yml
├── oval/
│ └── shared.xml
├── bash/
│ └── shared.sh
├── ansible/
│ └── shared.yml
└── tests/
├── correct.pass.sh
└── wrong.fail.sh
After MCP generates the boilerplate, customize the generated files:
oval/shared.xml:
<def-group>
<definition class="compliance" id="{{{ rule_id }}}" version="1">
{{{ oval_metadata("{DESCRIPTION}", rule_title=rule_title) }}}
<criteria>
<!-- Add OVAL criteria here -->
</criteria>
</definition>
<!-- Add OVAL tests, objects, and states here -->
</def-group>
Note: rule_id and rule_title are automatically populated during build from the rule.yml.
bash/shared.sh:
# platform = multi_platform_all
# reboot = false
# strategy = configure
# complexity = low
# disruption = low
# Remediation script for $ARGUMENTS
# TODO: Implement remediation
ansible/shared.yml:
# platform = multi_platform_all
# reboot = false
# strategy = configure
# complexity = low
# disruption = low
- name: "{TITLE}"
# TODO: Implement remediation
ansible.builtin.debug:
msg: "Remediation not yet implemented"
tests/correct.pass.sh:
#!/bin/bash
# packages = {REQUIRED_PACKAGES}
# Set up compliant state
# TODO: Configure system to be compliant
tests/wrong.fail.sh:
#!/bin/bash
# packages = {REQUIRED_PACKAGES}
# Set up non-compliant state
# TODO: Configure system to be non-compliant
Every rule must belong to a component. Components are defined in components/*.yml and map rules to their associated packages and groups.
Based on the rule's purpose and location, suggest likely components:
# List all available components
ls components/*.yml | sed 's|components/||;s|\.yml||' | sort
Finding the right component: Search existing component files for rules with a similar prefix to identify the likely component:
# Find which component contains rules with a similar prefix
grep -l '<rule_prefix>' components/*.yml
# Check if suggested component exists
cat components/<component_name>.yml
Use AskUserQuestion with options:
If user selects an existing component:
Read the component file:
cat components/<component_name>.yml
Add the rule ID to the rules: list in alphabetical order:
rules:
- existing_rule_1
- existing_rule_2
- $ARGUMENTS # Add new rule here
- existing_rule_3
If using a template, verify the template is listed in templates: section. If not, add it:
templates:
- existing_template
- {TEMPLATE_NAME} # Add if not present
If no suitable component exists:
Ask for component details:
my-service)Create the component file components/<component_name>.yml:
groups:
- <group_from_rule_hierarchy>
name: <component_name>
packages:
- <package_name>
rules:
- $ARGUMENTS
templates:
- {TEMPLATE_NAME} # If templated rule
Verify the new component:
python3 -c "import yaml; yaml.safe_load(open('components/<component_name>.yml'))"
groups: # Rule groups this component covers (from guide hierarchy)
- service_name
- service_name_server
name: component-name # Matches filename without .yml
packages: # Packages associated with this component
- package-name
- package-name-server
rules: # Rule IDs belonging to this component (alphabetical)
- rule_id_1
- rule_id_2
templates: # Templates used by rules in this component
- template_name
Most profiles in the project reference control files rather than listing rules directly. When adding a rule to a profile that uses a control file, add the rule to the control file instead.
Use mcp__content-agent__list_profiles with product=<product> to list all available profiles for each target product.
Fallback: Run ls products/<product>/profiles/*.profile to list profiles.
Use AskUserQuestion to ask which profile(s) to add the rule to. Allow multiple selection.
For each selected profile, read it and check if it references a control file:
grep -E "^\s+-\s+\w+:all" products/<product>/profiles/<profile>.profile
Control file reference patterns:
cis_rhel9:all or cis_rhel9:all:l2_server → Control file: products/rhel9/controls/cis_rhel9.ymlstig_rhel9:all → Control file: products/rhel9/controls/stig_rhel9.ymlhipaa:all → Control file: controls/hipaa.yml (shared across products)How to find the control file:
cis_rhel9 from cis_rhel9:all:l2_server)products/<product>/controls/<control_id>.yml firstcontrols/<control_id>.ymlIf the profile references a control file:
Read the control file to understand its structure
Ask user for control placement:
5.2.15 for SSH settings)RHEL-09-123456)Determine the level(s) for the rule:
l1_server, l2_server, l1_workstation, l2_workstationhigh, medium, lowAdd a new control entry to the control file:
For CIS control files:
- id: 5.2.15
title: Ensure SSH MaxAuthTries is set to 4 or less (Automated)
levels:
- l1_server
- l1_workstation
status: automated
rules:
- $ARGUMENTS
For STIG control files:
- id: RHEL-09-123456
levels:
- medium
title: RHEL 9 must limit the number of SSH authentication attempts.
rules:
- $ARGUMENTS
status: automated
Insert in the correct position (controls are typically ordered by ID)
If the profile lists rules directly (no control file reference):
selections: listProduct-specific (preferred for product-specific benchmarks):
products/rhel8/controls/*.ymlproducts/rhel9/controls/*.ymlproducts/rhel10/controls/*.ymlShared (for cross-product policies):
controls/*.yml| Profile | Control File |
|---|---|
cis.profile (rhel9) | products/rhel9/controls/cis_rhel9.yml |
stig.profile (rhel9) | products/rhel9/controls/stig_rhel9.yml |
hipaa.profile | controls/hipaa.yml |
pci-dss.profile | controls/pcidss_4.yml |
ospp.profile | controls/ospp.yml |
anssi_bp28_*.profile | controls/anssi.yml |
When adding to CIS or STIG control files, reference the corresponding security policy document to find the correct control ID:
security_policies/CIS_Red_Hat_Enterprise_Linux_9_Benchmark_v2.0.0.mdsecurity_policies/Red Hat Enterprise Linux 9 STIG V2R5 - STIG-A-View.mdIMPORTANT: Whenever a rule is added to a profile (whether directly or via control file), the profile stability test data must also be updated.
Profile stability test data is located in tests/data/profile_stability/<product>/<profile>.profile and contains a sorted list of rule IDs, one per line.
Check if stability test file exists:
ls tests/data/profile_stability/<product>/<profile>.profile
If the file exists, add the rule ID in alphabetical order:
# Read current file, add new rule, sort, and write back
(cat tests/data/profile_stability/<product>/<profile>.profile; echo "$ARGUMENTS") | sort -u > /tmp/profile_stability_tmp
mv /tmp/profile_stability_tmp tests/data/profile_stability/<product>/<profile>.profile
If the file doesn't exist, this may be a new profile or a product without stability tests. Check if the product directory exists:
ls tests/data/profile_stability/<product>/
Profile stability test file format:
rule_id_1
rule_id_2
rule_id_3
...
Rules are listed one per line, sorted alphabetically, with no duplicates.
Discover products with stability tests:
ls tests/data/profile_stability/
Verify created files:
ls -la <rule_directory>/
cat <rule_directory>/rule.yml
Validate rule YAML using mcp__content-agent__validate_rule_yaml with the content of the generated rule.yml. This validates syntax, structure, and reference formats.
Fallback: Validate against the JSON schema:
python3 -c "
import json, yaml, sys
from jsonschema import validate, ValidationError
schema = json.load(open('shared/schemas/rule.json'))
data = yaml.safe_load(open(sys.argv[1]))
try:
validate(instance=data, schema=schema)
print('Valid')
except ValidationError as e:
print(f'Invalid: {e.message}')
" <path_to_rule.yml>
For control files, use mcp__content-agent__validate_control_file with the control file path.
Fallback: Validate against the control schema:
python3 -c "
import json, yaml, sys
from jsonschema import validate, ValidationError
schema = json.load(open('shared/schemas/control.json'))
data = yaml.safe_load(open(sys.argv[1]))
try:
validate(instance=data, schema=schema)
print('Valid')
except ValidationError as e:
print(f'Invalid: {e.message}')
" <path_to_control.yml>
Verify CCE was removed from available list (if CCE was allocated):
grep "<allocated_cce>" shared/references/cce-redhat-avail.txt # should return nothing
Verify rule is in component:
grep "$ARGUMENTS" components/<component>.yml
Verify profile stability test data (if profile was modified):
grep "$ARGUMENTS" tests/data/profile_stability/<product>/<profile>.profile
Report to user:
stigid and cis references will be automatically populated from the control file/test-rule $ARGUMENTS to test"/test-rule $ARGUMENTS"/build-product <product> to build and /run-tests to validate"ssg/jinja.py) avoid conflicts with YAML/XML curly braces:
{{{ expr }}} (triple braces), NOT {{ expr }}{{% stmt %}}, NOT {% stmt %}{{# comment #}}, NOT {# comment #}{{{ full_name }}}, {{{ describe_service_enable(service="auditd") }}}, {{% if product in ["rhel9"] %}}, {{% set var="value" %}}cce-redhat-avail.txttests/data/profile_stability/<product>/<profile>.profileAssess a pending NIST 800-53 control with OSCAL enrichment, CIS reverse lookup, and Linux hardening prioritization. Guides authors through understanding, automatability analysis, rule mapping, and validation.
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.
Open a pull request in the browser with prefilled title, description, and labels
Create a new product in ComplianceAsCode project
Create a template for checks and remediations
Create Automatus test scenarios to test the given rule.