Adding a new control involves defining it and then mapping it to the components, personas, and risks it affects. For this example, let's add a hypothetical controlNewControl.
First, derive the new control ID from the control title (control + camelCase descriptor — e.g., "Red Teaming" → controlRedTeaming) and declare it in controls.schema.json. This registers the new control with the framework. (The issue-template flow derives this ID automatically; when authoring YAML/schema directly via PR, apply the same convention by hand.)
- File to edit:
schemas/controls.schema.json - Action: Find the
enumlist underdefinitions.control.properties.idand add your new control ID alphabetically.
// In schemas/controls.schema.json
"id": {
"type": "string",
"enum": [
...
"controlApplicationAccessManagement",
"controlNewControl", // Add your new control ID here
"controlIncidentResponseManagement",
...
]
},Next, define the control's properties in the main controls.yaml data file. This is where you describe what the control is and map it to other parts of the framework.
Title convention: Control titles must be short noun phrases (2-6 words) that name the defensive capability or security measure. See the Control Titles Style Guide for rules and examples.
- File to edit:
controls.yaml - Action: Add a new entry to the
controlslist. When filling out the properties, you must select valid IDs from the other schema files.
Persona model for controls: List personas that are in a position to implement the control — parties who can take action to deploy or enforce it. This is distinct from the risk persona model, where personas are parties who bear the consequences of the risk. For example,
personaGovernanceappears on controls (governance teams define and enforce policies) but not on risks. Conversely,personaEndUserappears on most risks (users bear consequences) but rarely on controls.
Note on universal controls: For controls that apply broadly (e.g., governance or assurance tasks), you can use the string
"all"forcomponentsandrisks. For controls that don't apply to any specific component, use"none".
Optional Metadata Fields: You can also add optional metadata fields like
mappings(framework cross-references),lifecycleStage,impactType, andactorAccessto provide additional context. These fields support both specific arrays (e.g.,lifecycleStage: [planning, deployment]) and universal values (e.g.,lifecycleStage: all). See Metadata Fields Guide for details.
# Example of a specific control
- id: controlNewControl
title: A New and Important Control
description:
- >
A clear and concise description of what this control does, how it works,
and why it is an effective safeguard.
category: controlsModel
personas:
- personaModelProvider
- personaApplicationDeveloper
components:
- componentTheModel
- componentOutputHandling
risks:
- riskInsecureModelOutput # Mapped to Insecure Model Output
- riskPromptInjection # Mapped to Prompt Injection
# Example of a universal (governance) control
- id: controlRedTeaming
title: Red Teaming
description:
- >
Drive security and privacy improvements through self-driven adversarial attacks
on AI infrastructure and products.
category: controlsAssurance
personas:
- personaModelProvider
- personaApplicationDeveloper
components: all # This control applies to all components
risks: all # This control applies to all risksTo ensure the framework remains fully connected, every risk that your new control mitigates must be updated to include a reference to that control. (This step is not necessary if you set risks: all in the previous step).
Controls that apply to all risks should use the keyword all:
- id: controlRedTeaming
risks: all # Universal - applies to all risks implicitlyWhen a control has risks: all, it is a universal control. This means:
- The control applies implicitly to ALL risks in the framework
- Risks should NOT explicitly list this control in their
controlsfield - The universal application is automatic
Example of INCORRECT usage:
# ❌ WRONG - Don't do this!
# In risks.yaml
- id: DataPoisoning
controls:
- controlRedTeaming # ❌ This is a universal control - don't list it!Example of CORRECT usage:
# ✅ CORRECT
# In risks.yaml
- id: DataPoisoning
controls:
- controlTrainingDataSanitization # ✅ Only list specific controls
# controlRedTeaming applies implicitly (don't list it)Universal Controls in the Framework:
- controlRedTeaming
- controlVulnerabilityManagement
- controlThreatDetection
- controlIncidentResponseManagement
- controlInternalPoliciesAndEducation
- controlProductGovernance
- controlRiskGovernance
These controls apply to all risks by default and should never be explicitly listed in risks.yaml.
- File to edit:
risks.yaml - Action: For each risk ID you listed in the previous step (e.g.,
riskInsecureModelOutput,riskPromptInjection), find its definition inrisks.yamland add your newcontrolNewControlID to itscontrolslist.
# In yaml/risks.yaml, under the riskInsecureModelOutput risk definition
- id: riskInsecureModelOutput
# other properties
controls:
- controlOutputValidationAndSanitization
- controlNewControl # Add your new control hereBefore committing, validate that your control-risk cross-references are consistent:
# Manual validation (recommended during development)
python scripts/hooks/validate_control_risk_references.py --force
# Format YAML files (auto-runs in pre-commit but useful for preview)
npx prettier --write risk-map/yaml/controls.yaml risk-map/yaml/risks.yaml
# The pre-commit hook will also run all validations automatically when you commit
git add risk-map/yaml/controls.yaml risk-map/yaml/risks.yaml
git commit -m "Add new control with proper risk relationships"The validation will check:
- ✅ All controls that list risks in
controls.yamlare referenced back by those risks inrisks.yaml - ✅ All risks that reference controls in
risks.yamlhave those controls listing them incontrols.yaml - ✅ No isolated entries (controls with empty risk lists, risks with empty control lists)
Note: When you commit changes to controls.yaml, the pre-commit hook automatically generates:
- Updated control graph at
./risk-map/diagrams/controls-graph.md - Updated risk graph at
./risk-map/diagrams/controls-to-risk-graph.md - All 4 control table formats in
./risk-map/tables/(full, summary, xref-risks, xref-components)
All files are automatically staged for your commit.
Example of consistent cross-references:
# controls.yaml
controls:
- id: CTRL-001
risks: # Control addresses these risks
- RISK-001
- RISK-002
# risks.yaml
risks:
- id: RISK-001
controls:
- CTRL-001 # Risk acknowledges this control
- id: RISK-002
controls:
- CTRL-001 # Bidirectional consistency ✅Once validated, follow the General Content Contribution Workflow to create your pull request.
Related:
- Control Titles Style Guide - Title naming convention and reviewer checklist
- Validation Tools - Detailed validation commands
- Troubleshooting - Help with validation errors
- Best Practices - Development workflow tips