map-generate-controls
Skill0 starsChanged 18 days agoApache-2.0View on GitHub
Generate security controls from mitigations x capabilities, organized by Category x Scope matrix. Produces map-controls-generated.json with rich metadata for downstream framework mapping. Consumes validated.json.
We split this skill into the rules it gives an agent
39 rules. About 0 are common, 10 are distinctive, and 29 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈10
Distinctive · estimate
This author's own choices. Worth a look.
29
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 17
Every string this skill emits (control name, description, security_rationale, mapped-objective rationale) MUST be defensible from the input …
- Distinctive: line 31
If you are not 100% certain a specific claim holds, omit it or hedge to the broad objective level — describe the *category* of mechanism …
- Very specific: line 102
If assessment_gaps[] exists in validated.json:
- Distinctive: line 104
Check if any existing mitigation addresses this gap
- Very specific: line 105
If not, flag as unaddressed_gap — must generate controls for it
- Very specific: line 116
Read the mitigation_goal — defines WHAT needs to be controlled
- Very specific: line 117
Read request_parameters[] — the API parameters that enforce the control
- Very specific: line 118
Look up the corresponding capability in capabilities{} — defines HOW the service supports it
- Distinctive: line 120
Write the security_rationale — 1-2 short sentences naming the mitigation goal and the mechanism family chosen to enforce it.
- Very specific: line 131
When assigning condition_keys and actions to a control, verify that each condition key actually applies to the specified actions.
- Distinctive: line 136
Before assigning a condition key to a control:
- Very specific: line 137
Look up the key in capabilities.iam.condition_keys[]
- Very specific: line 138
Read its applies_to_actions[] list
- Very specific: line 140
If the action is NOT in applies_to_actions[], do NOT use that condition key for that action.
- Distinctive: line 141
Instead, find an alternative enforcement mechanism (resource policy, Config rule, etc.)
- Distinctive: line 161
Before assigning mechanisms, classify each mitigation's posture and verify mechanism
- Very specific: line 239
Persist the fallback log: Every entry appended to classification_fallback_log[] MUST be written into map-controls-generated.json as a …
- Very specific: line 247
Look up P in validated.json.api_surface.operations[].parameters[] and read gateable_by[], sar_condition_key, enum, mutability:
- Very specific: line 269
Do NOT pick VPCE Policy as the mechanism for a control whose parameters_controlled[] references a request-body field of a Create/Update …
- Very specific: line 287
If feasibility holds, emit a control in map-controls-generated.json.controls[] with:
- Very specific: line 292
If feasibility FAILS, append to map-controls-generated.json.coverage_gaps[]:
- Very specific: line 335
Use the spelling "preventative" in prose throughout (the posture vocabulary already does).
- Distinctive: line 385
If a new mechanism is proposed that is not in this table, map-generate-controls MUST add a row explicitly — never infer scope ad-hoc.
- Distinctive: line 393
Every control MUST include these fields:
- Very specific: line 421
Do NOT: use service-prefixed variants (S3-RES-PRO-01, ATH-ORG-PRV-01, LAMBDA-ACC-DET-03).
- Very specific: line 469
Use mechanism types: SSM Automation, Lambda Remediation, EventBridge + Step Functions.
- Very specific: line 481
Check if any of its actions[] appear in a technique's api_surface_evidence[]
- Very specific: line 482
If so, add that technique's ID to attack_techniques[] on the control
- Very specific: line 488
Every control MUST have a non-empty attack_techniques[] — if no direct action match exists, use mechanism alignment to identify relevant …
- Very specific: line 506
When two candidate controls would share that identity, emit one control and union the source mitigation_ids[] (and the other traceability …
- Very specific: line 563
After generating all controls, verify that EVERY mitigation in existing_mitigations[] has at least one control referencing its ID in …
- Very specific: line 587
Any other: Read mitigation_goal and request_parameters[] to determine domain, then generate.
- Very specific: line 599
Keep the resolver's mitigation_references[{alias,uuid}] as canonical provenance.
- Very specific: line 603
Run python3 tools/validate/audit_threat_model.py --slug <slug> after writing outputs.
- Very specific: line 609
After generating all controls and verifying mitigation coverage, tag each control with a canonical mitigation_intent and security_domain …
- Very specific: line 623
If no pattern matches, set mitigation_intent = "unclassified" and security_domain = null — the mapping skill will flag these for manual …
- Very specific: line 635
When a control traces to multiple mitigations with different intents (e.g., a consolidated Config rule), use the intent of the primary …
- Distinctive: line 642
After intent tagging, thread the source mitigation's AWS Threat Model identity and MITRE ATT&CK techniques onto each control.
- Distinctive: line 660
Run the resolver over the AWS Threat Model input and thread onto each control (additive fields):
≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Service Approval — Map: Generate Controls
Using the mitigation inventory and verified service capabilities, generate security controls organized by the Category x Scope matrix. Each control traces back to mitigation(s) and includes the metadata downstream skills (framework-mapping, generate) need without re-deriving the reasoning chain.
Authoring rule — state only what is verified
Every string this skill emits (control name, description,
security_rationale, mapped-objective rationale) MUST be defensible
from the input data:
- API operations and parameters MUST appear in
validated.json.api_surface.operations[]. - IAM action strings MUST come from the AWS Service Authorization
Reference IAM action list (resolved via the operation→action map at
data/botocore-iam-alias.json) orvalidated.json.capabilities.iam.permission_only_actions[]. - Condition keys MUST appear in
validated.json.capabilities.iam.condition_keys[]or be cited from a specific public AWS doc URL.
If you are not 100% certain a specific claim holds, omit it or hedge to
the broad objective level — describe the category of mechanism (SCP,
AWS Config rule, KMS key policy, etc.) and what objective the control
serves, and stop there. Do NOT assert that an action list is complete,
that a particular condition key fires under specific runtime context not
present in the input data, or that the control causally prevents a named
attack without an explicit ATT&CK technique cross-reference. Causal-
filler and absolute-overreach phrases ("ensures", "guarantees",
"prevents all", "fully mitigates", "completely blocks", bare "prevents" /
"blocks" / "blocking", "cannot", absolute "only", "denies … untrusted",
"confused-deputy" as a claim) are banned in description and
security_rationale — prefer "supports", "enforces at request time",
"constrains to the listed actions", or similar. The
tools/validate/lint_control_prose.py lint flags these phrases plus an
over-length budget on each prose field, and it also scans the rendered
.md files so a render-time re-wording cannot slip a stronger claim past
the source JSON.
Input: .service-approval/<slug>/03-validate/validated.json
Output: .service-approval/<slug>/04-map/map-controls-generated.json
Schema: schemas/map-controls-generated.schema.json — encodes shape, id pattern ^CTRL-(ORG|ACC|RES)-(PRO|PRV|DET|COR)-\d{3}$, scope/category enums, mitigation_ids[] pattern ^M-.+, per-control required fields.
This is one of 4 focused map sub-skills. Each writes to a separate intermediate state file.
Prerequisites
test -f .service-approval/<slug>/03-validate/validated.json && echo "validated: OK" || echo "ERROR: run validator first"
mkdir -p .service-approval/<slug>/04-mapLoad validated.json and extract:
python3 -c "
import json
vj = json.load(open('.service-approval/<slug>/03-validate/validated.json'))
print(f'Assets: {len(vj[\"assets\"])}')
print(f'Operations: {len(vj[\"api_surface\"][\"operations\"])}')
print(f'Mitigations: {len(vj[\"existing_mitigations\"])}')
print(f'Condition keys: {len(vj[\"capabilities\"][\"iam\"][\"condition_keys\"])}')
gaps = vj.get('assessment_gaps', [])
print(f'Assessment gaps: {len(gaps)}')
print(f'SCP: {vj[\"capabilities\"][\"organization_policies\"][\"scp\"][\"supported\"]}')
print(f'RCP: {vj[\"capabilities\"][\"organization_policies\"].get(\"rcp\", {}).get(\"supported\", False)}')
"Phase 1: Build Mitigation Index
For each mitigation in existing_mitigations[], build a rich index:
{
"id": "M-<Resource>.0",
"statement": "...",
"mitigation_goal": "...",
"request_parameters": ["$.encryptionKeyArn", "$.executionRoleArn"],
"resource_type": "AWS::<Service>::<Resource>",
"verified": true,
"verification_note": "..."
}Group mitigations by resource_type for efficient lookup during control generation.
Cross-reference with assessment gaps
If assessment_gaps[] exists in validated.json:
- For each gap with
status=REQUIREMENT GAPorCUSTOMER ACTION REQUIRED:- Check if any existing mitigation addresses this gap
- If not, flag as
unaddressed_gap— must generate controls for it
Phase 2: Generate Controls
For each mitigation, generate controls using the Category x Scope matrix.
2.1 Control generation logic
For each mitigation:
- Read the
mitigation_goal— defines WHAT needs to be controlled - Read
request_parameters[]— the API parameters that enforce the control - Look up the corresponding capability in
capabilities{}— defines HOW the service supports it - Generate one or more controls that enforce the mitigation goal using available capabilities
- Write the security_rationale — 1-2 short sentences naming the mitigation goal and the mechanism family chosen to enforce it. Stay at the objective level: name the control category (SCP, Config rule, KMS key policy) and the parameter/action it constrains. Do NOT claim the control prevents specific attacks unless an ATT&CK technique is cross-referenced in the same control. The rationale is read by map-framework-mapping; broad, verified statements are more useful than confident-sounding speculation.
CRITICAL — Condition key action applicability:
When assigning condition_keys and actions to a control, verify that each condition key
actually applies to the specified actions. The SAR page defines which condition keys work with
which actions — a condition key used in an SCP/IAM Deny on an action it doesn't apply to will
be silently ignored by IAM, making the control ineffective.
Before assigning a condition key to a control:
- Look up the key in
capabilities.iam.condition_keys[] - Read its
applies_to_actions[]list - Confirm that EVERY action in the control's
actions[]appears inapplies_to_actions[] - If the action is NOT in
applies_to_actions[], do NOT use that condition key for that action. Instead, find an alternative enforcement mechanism (resource policy, Config rule, etc.)
Common error: using a resource-type-specific condition key (e.g., one that applies to Gateway operations) on a different resource type's actions (e.g., Runtime). Each resource type may have different applicable condition keys.
2.2 Control sources to enumerate
- IAM controls — from
capabilities.iam(condition keys, resource policies, permission boundaries, SCPs) - Encryption controls — from
capabilities.encryption(KMS CMK, SSE, TLS, key rotation) - Network controls — from
capabilities.network(VPC, endpoints, PrivateLink, security groups) - Logging controls — from
capabilities.logging(CloudTrail, CloudWatch, data events) - Configuration controls — from
capabilities.config(Config rules, conformance packs) - Organization policy controls — from
capabilities.organization_policies(SCPs, RCPs, declarative policies) - Tagging controls — tag policies, required tags
- Availability controls — backup, multi-AZ, lifecycle
- AI-specific controls — guardrails, prompt validation (if applicable)
2.2.1 Rule C1 — Posture × feasibility filter (REQUIRED)
Before assigning mechanisms, classify each mitigation's posture and verify mechanism
feasibility against the parameter's gateable_by and mutability fields in
validated.json.api_surface.operations[].parameters[]. A critical mitigation produces
controls at multiple postures (defense-in-depth) — one control per feasible posture.
Posture values:
preventative-request— AWS IAM evaluates the condition at request time (SCP, RCP, IAM policy, trust policy, permission boundary, KMS key policy, resource-based policy, VPCE policy). Requires"sar"in the parameter'sgateable_by[].preventative-proactive— IaC/CI pipeline evaluates the template before any AWS call (CFN Guard, CFN Hooks, Checkov, OPA, Terraformvalidation, CDK runtime validation). Requires"api"ingateable_by[].reactive-detective— AWS Config or Access Analyzer flags existing state (Config rule Lambda, managed Config rule, Access Analyzer). Requires"api"ingateable_by[].reactive-corrective— remediation after a detective finding (SSM Automation, Step Functions, Lambda remediator). Requires"api"ingateable_by[]ANDmutability != "create_only".
Posture classification (three-pass derivation from mitigation_goal, theme nouns, and title/statement fallback):
Mitigation goals vary in style across sources. Some are verb-led ("Enforce KMS encryption"); others are noun-phrase themes ("Encryption at Rest — CMK", "Network Perimeter — Approved Networks"). Run all three passes and take the union before falling back.
posture_set = []
haystack_goal = mitigation.mitigation_goal
# ── Pass 1 — Verb pass against mitigation_goal ──────────────────────────
if haystack_goal matches /prevent|block|disallow|deny|mandate/: posture_set += [preventative-proactive, preventative-request]
if haystack_goal matches /enforce|require|ensure.*(?:at creation|configured|set)/:
posture_set += [preventative-proactive, preventative-request]
if haystack_goal matches /encrypt|cmk|kms key|customer-managed key/: posture_set += [preventative-proactive, preventative-request]
if haystack_goal matches /protect|safeguard|guard against/: posture_set += [preventative-proactive, preventative-request]
if haystack_goal matches /enable.*(?:logging|telemetry|monitoring)|configure.*(?:at creation|upfront)|ensure.*(?:available|enabled|active)/:
posture_set += [preventative-proactive]
if haystack_goal matches /detect|monitor|audit|flag|observe|alert|visibility|anomaly|forensic/:
posture_set += [reactive-detective]
if haystack_goal matches /remediat|rollback|restore|auto-repair|revert/: posture_set += [reactive-corrective]
if haystack_goal matches /\bcorrect\b.*(?:state|configuration|drift|violation)/
AND NOT /correct (?:classification|tagging|labeling)/: posture_set += [reactive-corrective]
# ── Pass 2 — Noun-phrase themes against mitigation_goal ─────────────────
# Noun-phrase goals like "Identity Perimeter" or "Encryption at Rest" have
# no verb to match; their posture is implied by the theme.
if haystack_goal matches /perimeter|boundary|allow.?list|deny.?list/: posture_set += [preventative-proactive, preventative-request]
if haystack_goal matches /at rest|in transit|encryption/: posture_set += [preventative-proactive, preventative-request]
if haystack_goal matches /logging|telemetry|observability|audit trail/: posture_set += [preventative-proactive, reactive-detective]
if haystack_goal matches /tagging|governance/: posture_set += [preventative-proactive]
if haystack_goal matches /remediation|recovery|rollback/: posture_set += [reactive-corrective]
# ── Pass 3 — Title + statement fallback (only if passes 1 & 2 empty) ────
# Some mitigations compress the actionable signal into the title or the
# statement text rather than the goal field. Re-run BOTH pass 1 and pass 2
# regexes against (title + " " + statement) as a last resort.
if posture_set is empty AND (mitigation.title or mitigation.statement):
haystack_fallback = (mitigation.title or "") + " " + (mitigation.statement or "")
<re-run pass 1 AND pass 2 patterns against haystack_fallback>
# Critical mitigations get ALL FOUR postures where feasible (defense-in-depth)
if criticality == "critical":
posture_set = [preventative-request, preventative-proactive, reactive-detective, reactive-corrective]
# Final fallback when all three passes return empty
if posture_set is empty:
posture_set = [preventative-proactive]
append to `classification_fallback_log` (emit in map-controls-generated.json):
{"mitigation_id": M.id, "reason": "no keyword match in goal/title/statement"}Over-generation is safe. The feasibility filter (next section) drops
postures that have no gateable parameter — so a noun-phrase match that
fires multiple postures does not pollute the output. A mitigation goal
like "Audit data at rest" legitimately warrants BOTH reactive-detective
(from audit) and preventative-proactive|request (from at rest); the
filter resolves which layer(s) are actually buildable.
Persist the fallback log. Every entry appended to
classification_fallback_log[] MUST be written into
map-controls-generated.json as a top-level key (not just printed to
stdout). Downstream consumers and validators rely on the JSON record to
verify that the fallback only fires on genuinely ambiguous goals.
Feasibility filter per posture (per parameter path P in mitigation.request_parameters):
Look up P in validated.json.api_surface.operations[].parameters[] and read
gateable_by[], sar_condition_key, enum, mutability:
| Posture | Feasibility requirement | Mechanism family |
preventative-request | "sar" in gateable_by (requires sar_condition_key) | SCP, RCP, IAM identity policy, trust policy, permission boundary, resource-based policy, KMS key policy, VPCE policy |
preventative-proactive | "api" in gateable_by | CFN Guard, CFN Hook, Checkov, OPA, Terraform validation, CDK runtime validation |
reactive-detective | "api" in gateable_by | AWS Config rule (managed or custom Lambda), Access Analyzer |
reactive-corrective | "api" in gateable_by AND mutability != "create_only" | SSM Automation runbook, Step Functions workflow, Lambda remediator |
When VPCE Policy is NOT a valid preventative-request mechanism:
VPC endpoint policies appear in the preventative-request mechanism family
above, but they have a narrower preventive scope than the other policies
in that row. A VPCE policy gates who can use the VPC endpoint to reach a
service (Action × Principal × ResourceArn at the endpoint boundary). It
does not see request-body parameters such as
lambda:CreateFunction.VpcConfig.SubnetIds,
s3:PutObject.ServerSideEncryption, or any nested config field of the
caller's request payload. The endpoint-policy evaluation context is the
endpoint itself, not the API request body.
Do NOT pick VPCE Policy as the mechanism for a control whose
parameters_controlled[] references a request-body field of a Create/Update
operation (subnet selection, encryption-key parameter, KMS key ARN on
create, etc.). A VPCE policy cannot enforce those at request time. The
correct mechanism family for "<service> must be created with parameter X
set to Y" is SCP / IAM identity policy / IAM permission boundary
(when a SAR condition key for the parameter exists) and CFN Guard /
CFN Hook / OPA (template-time, preventative-proactive).
VPCE Policy is appropriate when the control gates endpoint use:
restricting which principals/actions can flow through the endpoint, or
enforcing aws:PrincipalOrgID on traffic that egresses the VPC. If the
mitigation request_parameters[] is an endpoint-level concern (e.g.,
"only org members may invoke any action via this VPCE"), VPCE Policy is
correct and preventative-request applies; otherwise, route the control
to the mechanisms above.
Emit one control per feasible posture. For each posture in posture_set:
- If feasibility holds, emit a control in
map-controls-generated.json.controls[]with:posture: one of the four values abovemechanism: selected from the mechanism family (canonical snake_case via the mechanism→scope table in §2.3)source: derived from the parameter'sgateable_by("sar","api", or"both")
- If feasibility FAILS, append to
map-controls-generated.json.coverage_gaps[]:{"mitigation_id": "M-X.0", "posture": "reactive-corrective", "reason": "parameter is create_only — cannot remediate post-create"}
A single critical mitigation can produce up to 4 controls (one per posture). A low-priority non-critical mitigation typically produces 1-2.
Downstream contract: The posture field is propagated through map-assemble into
mapping-results.json.controls[].posture, then baked into each generated control file's
_metadata.posture. Validator CHECK-15 enforces posture ↔ directory consistency at
write time — a reactive-detective control that lands in preventive/scp-policy.json
will fail the hook.
2.3 Control Matrix — Category x Scope
Assign each control a primary category (X-axis) and a scope level (Y-axis):
Categories (X-axis) — canonical definitions. Each category is defined by
what it does (purpose) and the AWS implementation primitives that realise it.
This is the single authoritative definition of the four categories; the
customer-facing controls-catalog.md renders the same purpose text.
| Category | Code | Purpose | Implementation primitives |
| Proactive | PRO | Validate resource configuration before creation and block non-compliant resources from being created | CloudFormation Hooks (via Control Tower), CloudFormation Guard (CI/CD), OPA rules (CI/CD) |
| Preventative | PRV | Stop non-compliant actions — hard guardrails that deny API calls regardless of the caller's IAM permissions | SCPs (org), RCPs (resource), Declarative Policies (service), IAM permissions boundaries (entity), VPC endpoint policies |
| Detective | DET | Identify non-compliance after deployment | AWS Config rules, Security Hub controls, GuardDuty findings, CloudTrail, Amazon Inspector |
| Corrective (Responsive) | COR | Remediate after detection | SSM Automation runbooks, Config auto-remediation (→ SSM), Lambda remediation, Security Hub automated response, EventBridge → remediation workflows |
Category ↔ posture correspondence. This pipeline carries two vocabularies for
the same distinction: the four control categories above (PRO/PRV/DET/COR, the
schema enum on controls[].category) and the four postures on
controls[].posture (§2.2.1). They map one-to-one:
| Category | Posture |
| PRO (Proactive) | preventative-proactive |
| PRV (Preventative) | preventative-request |
| DET (Detective) | reactive-detective |
| COR (Corrective/Responsive) | reactive-corrective |
Use the spelling "preventative" in prose throughout (the posture vocabulary
already does); the schema code stays PRV. "Responsive" is the short label used
for the COR column and the scope-matrix axis; "Corrective" is its category name.
The Examples-oriented restatement below keeps the previous phrasing for quick reference — the purpose + primitives table above is authoritative:
| Category | Code | Definition | Examples |
| Proactive | PRO | Prevents non-compliant resources from being created | CFN Guard, SCP deny, Config proactive rules, OPA |
| Preventative | PRV | Blocks unauthorized actions at runtime | IAM policies, resource policies, SCPs, VPC SGs, KMS key policies |
| Detective | DET | Identifies non-compliant or suspicious state after the fact | Config rules, CloudTrail analysis, CloudWatch alarms |
| Corrective (Responsive) | COR | Automatically remediates non-compliant state | SSM runbooks, Lambda remediation, EventBridge workflows |
Scope Levels (Y-axis):
| Scope | Code | Definition | Examples |
| Organisation | ORG | Applied across the AWS Organization | SCPs, tag policies, Config org rules |
| Account | ACC | Applied within a single AWS account | IAM policies, permission boundaries, Config rules |
| Resource | RES | Applied directly to an individual resource | Resource-based policies, KMS key policies |
Deterministic mechanism → scope mapping (REQUIRED for stability across runs):
To prevent iteration-to-iteration drift, every control's scope MUST be derived from
its mechanism using this table. Do NOT choose scope by thematic heading (e.g., "Resource
× Proactive" in generate-preventive) — the mechanism alone determines scope.
| Mechanism | Scope | Rationale |
| SCP (Service Control Policy) | ORG | Attached at the Organization / OU level |
| RCP (Resource Control Policy) | ORG | Organization policy type |
| Tag policy | ORG | Organizations-native |
| Config org rule / conformance pack (org) | ORG | Deployed via Organizations |
| CloudFormation Guard rule | ACC | Enforced at account's CFN deploy (or per-account Hook) |
| CloudFormation Hook | ACC | Registered per-account |
| OPA / Rego policy | ACC | Evaluated per-account at CI / deploy |
| Checkov custom policy | ACC | Evaluated per-account at CI / deploy |
| IAM identity-based policy (user/role) | ACC | Principals live at the account level |
| IAM permission boundary | ACC | Attached to an account-level principal |
| AWS Config rule (account) | ACC | Deployed per-account |
| EventBridge rule (default bus) | ACC | Default event bus is account-scoped |
| CloudWatch alarm / metric filter | ACC | Per-account resources |
| SSM runbook / automation document | ACC | Document is account-scoped |
| Lambda remediator | ACC | Function lives in an account |
| Step Functions workflow | ACC | State machine lives in an account |
| Resource-based policy (S3 bucket policy, SNS topic policy, Lambda resource policy, SQS queue policy) | RES | Attached to a specific resource ARN |
| KMS key policy | RES | Attached to a specific key |
| VPC endpoint policy | RES | Attached to a specific endpoint |
If a new mechanism is proposed that is not in this table, map-generate-controls MUST add a row explicitly — never infer scope ad-hoc. This makes control ID prefixes stable across runs, which is required for downstream generate skills to produce consistent artifact IDs.
Each control MUST have exactly one category AND one scope level.
2.4 Control output schema
Every control MUST include these fields:
{
"id": "CTRL-ORG-PRV-001",
"name": "Enforce CMK encryption on <Resource> creation via SCP",
"description": "Constrains <Resource> creation to require encryption at request time via SCP",
"posture": "preventative-request",
"source": "sar",
"scope": "ORG",
"category": "PRV",
"mechanism": "SCP",
"mitigation_ids": ["M-<Resource>.0", "M-<Resource>.3"],
"parameters_controlled": ["$.encryptionKeyArn"],
"condition_keys": ["<prefix>:encryptionKeyArn"],
"actions": ["<prefix>:Create<Resource>", "<prefix>:Update<Resource>"],
"resource_types": ["AWS::<Service>::<Resource>"],
"assessment_gap_ids": [],
"security_rationale": "Supports encryption-at-rest by gating the create call on the customer-managed key parameter at request time.",
"attack_techniques": ["T1530", "T1486"],
"framework_keywords": ["encryption", "key management", "data protection", "policy enforcement"]
}Field requirements:
id: Format MUST match the regex^CTRL-(ORG|ACC|RES)-(PRO|PRV|DET|COR)-\d{3}$— literalCTRL-prefix, scope token, category token, three-digit sequence. Example:CTRL-ORG-PRV-001,CTRL-ACC-DET-012,CTRL-RES-PRO-003. Do NOT use service-prefixed variants (S3-RES-PRO-01,ATH-ORG-PRV-01,LAMBDA-ACC-DET-03). The service identity lives in_metadata.serviceandvalidated.service— duplicating it inside the control ID creates drift that failsvalidate_state.pyCHECK-S11 andvalidate_controls.pyCHECK-13 body scans. Service-agnostic uniformity lets every downstream check be a literal string match rather than "strip prefix, then compare". Validator CHECK-S11 enforces this regex enum; non-conforming IDs fail state validation.posture: One ofpreventative-request,preventative-proactive,reactive-detective,reactive-corrective. Derived from the classification in §2.2.1. Propagated downstream tomapping-results.json.controls[].postureby map-assemble, then to each generated artifact's_metadata.postureso CHECK-15 can enforce posture ↔ directory consistency.source: One ofsar,api,both— derived from the parameter'sgateable_by[]. Indicates which data source authoritatively drives the mechanism's contents.scope: Abbreviated code:ORG,ACC,REScategory: Abbreviated code:PRV,PRO,DET,CORmechanism: AWS enforcement mechanism (SCP, CloudFormation Hook, AWS Config Rule, VPC Endpoint Policy, IAM Permission Boundary, Resource Policy, IAM Trust Policy, CloudTrail, CloudWatch Alarm, CloudWatch Logs, Kinesis Data Streams, SSM Automation, EventBridge + Step Functions, Tag Policy, OPA Policy, CloudFormation Guard, Compensating Control Documentation)mitigation_ids: Non-empty array — every control MUST trace to at least one mitigationparameters_controlled: API parameters this control enforces (from mitigationrequest_parameters[])condition_keys: IAM condition keys used (empty array for non-IAM controls)actions: IAM actions restricted/monitored (empty array for non-IAM controls)resource_types: AWS resource types affected (CFN format)assessment_gap_ids: Assessment gaps addressed (empty array if none)security_rationale: ONE broad sentence stating the control's intent at objective level — name the kind of setting it governs and the objective it supports. Do not assert a causal guarantee, enumerate verbatim actions, or list condition keys here (those live in the structuredactions[]/condition_keys[]fields the generate phase consumes). State only what is verified; a broad true sentence beats a precise uncertain one. Banned causal-filler words (ensures,guarantees,prevents all, bareprevents/blocks,cannot, absoluteonly, …) are rejected bylint_control_prose.py. Prefer "supports / constrains / enforces at request time". This field is the PRIMARY context for downstream framework-mapping — write it broad but accurate.attack_techniques: ATT&CK technique IDs this control mitigates (empty array ifattack_surfacenot present in validated.json). Populated by cross-referencing the control'sactions[],condition_keys[], andmechanismagainstattack_surface.applicable_techniques[].api_surface_evidence[]. A control mitigates a technique if it restricts/monitors any action listed in that technique's evidence.framework_keywords: 4-8 keywords for framework objective matching. Include BOTH domain keywords (encryption, access, network) AND mechanism keywords (deny, monitor, audit, enforce, restrict).
2.5 Responsive control generation
For every detective control, evaluate whether a responsive control is appropriate. Generate responsive controls for at minimum:
- Encryption drift: auto-remediate resources created without CMK encryption
- Authentication drift: alert + optional auto-remediate gateways with AuthorizerType=NONE
- Tag compliance: auto-tag resources missing mandatory tags
- VPC configuration drift: alert on resources created without VPC networking
Link each responsive control to the same mitigation IDs as its corresponding detective control.
Use mechanism types: SSM Automation, Lambda Remediation, EventBridge + Step Functions.
2.6 Assessment gap coverage
For every REQUIREMENT GAP and CUSTOMER ACTION REQUIRED from assessment_gaps[]:
- Generate at least one control that addresses the gap
- Link via
assessment_gap_ids[]on the control
2.7 ATT&CK technique cross-reference (mandatory)
validated.json always contains attack_surface.applicable_techniques[] (MITRE ATT&CK
analysis is mandatory for every service onboarding). For each control:
- Check if any of its
actions[]appear in a technique'sapi_surface_evidence[] - If so, add that technique's ID to
attack_techniques[]on the control - Also check mechanism alignment:
- SCP/IAM controls → relevant to T1078, T1098, T1548 (identity techniques)
- Encryption controls → relevant to T1530, T1486 (data techniques)
- Network controls → relevant to T1190, T1133 (access techniques)
- Logging controls → relevant to T1562, T1070 (evasion techniques)
- Every control MUST have a non-empty
attack_techniques[]— if no direct action match exists, use mechanism alignment to identify relevant techniques
2.7.1 Canonical control identity — avoid emitting structural duplicates
Per-resource mitigation variants (e.g. CapacityProvider / EventSourceMapping / Layer / CodeSigningConfig twins of a Function control) often produce structurally identical controls that differ only by ID — same name, scope, category, mechanism, resource_types, actions, and posture. These inflate the control count and the coverage numbers without adding coverage.
Two controls have the same canonical identity when they share:
(normalized name, scope, category, mechanism,
sorted(resource_types), sorted(actions), posture)When two candidate controls would share that identity, emit one control and union
the source mitigation_ids[] (and the other traceability lists) onto it — do not emit a
second ID. If two controls differ only by which resource type they target but enforce
the same action via the same mechanism, collapse them to one control and keep
resource_types[] as a list so traceability survives; controls that gate different
actions or use a different mechanism stay distinct.
A deterministic safety net runs in map-assemble Phase 0 (dedup_controls()):
it collapses any remaining duplicates by this same canonical key before the rest of the
pipeline consumes the controls, and tools/validate/validate_control_dedup.py fails the
map gate if two controls in mapping-results.json still share the key. Generating
cleanly here keeps the catalog honest before the safety net even runs.
2.8 Compensating control documentation
Some capabilities are NOT available for the service (e.g., RCP not supported, no delegated admin, ABAC partial). For each unavailable capability referenced in assessment_gaps or where controls cannot be implemented:
- Generate a control with
mechanism: "Compensating Control Documentation" - Describe what compensating measures should be taken
- Include in the controls array like any other control
2.8.1 Doc reference for non-AWS Threat Model controls (doc_reference)
Controls that are NOT sourced from an AWS Threat Model mitigation (trace_source: "assessment_gap" —
the compensating / assessment-gap controls from §2.6 and §2.8) do not carry an AWS Threat Model
citation, so the framework-mapping report cites an AWS documentation source for them
instead. Attach a doc_reference to every such control:
"doc_reference": {
"url": "https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_rcps.html#rcp-supported-services",
"title": "AWS Organizations — List of AWS services that support RCPs"
}Rules:
- The
urlMUST be a realhttps://docs.aws.amazon.com/...page, MCP-verified (awsknowledge/aws-documentation) and logged tomcp-calls.log— never a training-data guess (see "Verify, never hardcode"). - Make it as specific as possible so a reviewer can cross-check it: deep-link to the
exact page and, where the page has one, the
#sectionanchor for the relevant list or section (e.g. the RCP supported-services list, the declarative-policy supported-services list) rather than a service landing page. - Pick the page that documents why the compensating control exists — typically the AWS doc that establishes the capability gap the control compensates for (e.g. the RCP or declarative-policy supported-services list when the service is absent from it).
- AWS Threat Model-sourced controls (
trace_source: "threat_model") do NOT get adoc_reference— they cite their AWS Threat Model mitigation.doc_referenceis optional in the schema and present only on non-AWS Threat Model controls.
Phase 3: Mitigation Coverage Completeness Check (MANDATORY)
CRITICAL — Do NOT write output until this check passes.
After generating all controls, verify that EVERY mitigation in existing_mitigations[] has at
least one control referencing its ID in mitigation_ids[]:
all_mit_ids = set(m['id'] for m in existing_mitigations)
covered_mit_ids = set()
for ctrl in controls:
covered_mit_ids.update(ctrl.get('mitigation_ids', []))
uncovered = all_mit_ids - covered_mit_ids
if uncovered:
print(f"UNCOVERED MITIGATIONS: {sorted(uncovered)}")
# MUST generate controls for each — see guidance belowFor each uncovered mitigation, generate feasible controls using this guidance or record the explicit source-specific disposition described below:
- Availability mitigations (multi-AZ, redundancy): CloudFormation Guard rule + Config rule checking subnet AZ diversity. Distinct from "has VPC subnets."
- Authentication configuration mitigations (JWT, OIDC): Guard rule validating full auth config (discovery URL, audiences, clients, scopes). Distinct from "authorizer type != NONE."
- Network perimeter mitigations (approved networks): VPC endpoint policies + IAM conditions restricting by source VPC/endpoint.
- Data lifecycle mitigations (retention, deletion): Config rules checking resource config.
- Any other: Read
mitigation_goalandrequest_parameters[]to determine domain, then generate.
Re-run accounting after gap filling. Every source mitigation needs controls or an explicit disposition; do not invent automated controls to reach zero gaps.
For manual guidance, not-applicable decisions, or unresolved coverage, write
mitigation_dispositions[] in map-controls-generated.json. Each entry requires
mitigation_id (internal M-* ID), mitigation_alias, mitigation_uuid (source),
status (manual_guidance, not_applicable, or unresolved), and a specific
rationale. manual_guidance also requires concrete guidance. The source pair
must match the saved model. Unresolved entries remain visible gaps.
Keep the resolver's mitigation_references[{alias,uuid}] as canonical provenance.
Legacy alias/UUID arrays must have matching positions; primary UUID must belong
to primary alias. Preserve repair metadata. Assembly repairs unique known pairs
and records what changed; ambiguous source identities are reported, not guessed.
Run python3 tools/validate/audit_threat_model.py --slug <slug> after writing outputs.
Phase 3b: Mitigation Intent Tagging (MANDATORY)
After generating all controls and verifying mitigation coverage, tag each control with
a canonical mitigation_intent and security_domain derived from its source mitigation
statement. This step is deterministic — no LLM reasoning needed.
Input: data/mitigation-intent-patterns.json — controlled vocabulary of ~20 intent
patterns with match_statements[] (case-insensitive substring/regex patterns matched
against the mitigation statement field).
Algorithm:
- For each control, find its source mitigation(s) via
mitigation_ids[] - For each source mitigation, get the
statementtext fromvalidated.json.existing_mitigations[] - Match the statement against
data/mitigation-intent-patterns.jsonpatterns (first match wins) - Set
mitigation_intent= matched pattern'sintent_id - Set
security_domain= matched pattern'ssecurity_domain - If no pattern matches, set
mitigation_intent="unclassified"andsecurity_domain=null— the mapping skill will flag these for manual review
Example:
- Mitigation statement: "An AWS Lambda function should be encrypted with an AWS KMS Customer Managed Key (CMK)"
- Matches pattern:
"encrypted with an AWS KMS Customer Managed Key" - Result:
mitigation_intent: "cmk_encryption",security_domain: "data_protection_at_rest"
Why this matters: The mitigation_intent field constrains downstream framework mapping.
A control tagged cmk_encryption can only map to objectives in the encryption/data-protection
domains — it CANNOT be mapped to IAM, logging, or network objectives regardless of keyword overlap.
When a control traces to multiple mitigations with different intents (e.g., a consolidated
Config rule), use the intent of the primary mitigation (first in mitigation_ids[] array).
Phase 3c: AWS Threat Model Traceability Threading (MANDATORY)
After intent tagging, thread the source mitigation's AWS Threat Model identity and MITRE ATT&CK techniques onto each control. This step is deterministic — the MITRE techniques are recovered from the AWS Threat Model source, never invented or fetched from an external service.
Why. An AWS Threat Model mitigation carries an alias (e.g. Mitigation.LAMBDA.Function.1) and a
UUID; the MITRE ATT&CK technique IDs live on the threat objects the mitigation addresses.
Threading these onto controls lets the final framework-mapping.md cite the exact AWS Threat Model
mitigation and MITRE technique behind every mapped row.
Resolver: scripts/threat_model_trace.py walks the golden-source chain
mitigation.mappings[type=="THREAT"].id → threats[].id → threat.mappings[type=="FRAMEWORK",
framework.name=="MITRE ATT&CK"].id (also accepting legacy frameworkName) to build, per mitigation, {alias, statement,
mitre_techniques[], threat_aliases[]}.
Algorithm:
- For each control, collect the
aliasof each source mitigation (fromvalidated.json.existing_mitigations[], matched bymitigation_ids[]) and stamp it asmitigation_aliases[]on the control. - Run the resolver over the AWS Threat Model input and thread onto each control (additive fields):
Each control gainsimport importlib.util, json spec = importlib.util.spec_from_file_location("threat_model_trace", "scripts/threat_model_trace.py") ct = importlib.util.module_from_spec(spec); spec.loader.exec_module(ct) threat_model = json.load(open("<the AWS Threat Model input json>")) # the intake threat-model file ct.thread_controls(controls, threat_model)mitigation_alias,mitigation_aliases[],mitigation_uuid,mitigation_uuids[],mitre_techniques[], andtrace_source: "threat_model". - No-AWS Threat Model / synthetic controls. A control derived from an assessment gap (no real
AWS Threat Model mitigation behind it) has no resolvable alias — the resolver marks it
trace_source: "assessment_gap"with an emptymitre_techniques[]. This is expected, not an error.
Provenance note: mitre_techniques[] here is resolved deterministically from the AWS Threat Model
source and is authoritative for downstream citation. It is distinct from the heuristic
attack_techniques[] (§2.7), which is derived by action/mechanism alignment; keep both.
Phase 4: Write Output
Write .service-approval/<slug>/04-map/map-controls-generated.json:
{
"schema_version": "1.0",
"service": "<service-name>",
"controls": [
{
"id": "CTRL-ORG-PRV-001",
"name": "...",
"description": "...",
"posture": "preventative-request",
"source": "sar",
"scope": "ORG",
"category": "PRV",
"mechanism": "SCP",
"mitigation_ids": ["M-<Resource>.0"],
"parameters_controlled": ["$.encryptionKeyArn"],
"condition_keys": ["<prefix>:encryptionKeyArn"],
"actions": ["<prefix>:Create<Resource>"],
"resource_types": ["AWS::<Service>::<Resource>"],
"assessment_gap_ids": [],
"security_rationale": "...",
"framework_keywords": ["encryption", "key management"],
"mitigation_intent": "cmk_encryption",
"security_domain": "data_protection_at_rest"
}
],
"control_summary": {
"total_controls": 60,
"by_scope": { "ORG": 14, "ACC": 27, "RES": 19 },
"by_category": { "PRV": 20, "PRO": 10, "DET": 18, "COR": 12 },
"by_mechanism": { "SCP": 10, "AWS Config Rule": 12 }
},
"mitigation_coverage": {
"total_mitigations": 37,
"covered": 37,
"uncovered": 0,
"uncovered_ids": []
},
"assessment_gap_coverage": {
"total_gaps": 13,
"covered": 10,
"uncovered": 3,
"uncovered_ids": ["GAP-3", "GAP-7", "GAP-11"]
},
"coverage_gaps": [
{
"mitigation_id": "M-<Resource>.2",
"posture": "reactive-corrective",
"reason": "Parameter $.encryptionKey is create_only — cannot remediate post-create"
}
]
}The coverage_gaps[] array is produced by Rule C1's feasibility filter — one entry per
(mitigation, posture) pair where no feasible mechanism exists. map-assemble forwards
these into mapping-results.json.coverage_gaps[].
Validate Output
Output validation runs automatically via PostToolUse hook. The JSON Schema (see Schema above) hard-fails on shape / id-pattern / scope-category enum / required-field drift. validate_state.py::check_controls_generated adds the cross-file and cross-field checks the schema cannot express: duplicate control IDs, scope/category mismatch between id and fields, mitigation coverage against validated.json, and control_summary.total_controls vs actual length.
Print Summary
Map (Generate Controls) complete:
Controls: {total} ({by_scope}, {by_category})
Mitigations covered: {covered}/{total} (100%)
Assessment gaps covered: {covered}/{total}
Output: .service-approval/<slug>/04-map/map-controls-generated.jsonIn this repository24 other files it gives its agents.
- AGENTS.mdAGENTS.md
- .github/copilot-instructions.mdCopilot instructions
- assessSkill
- generate-detectiveSkill
- generate-preventive-iamSkill
- generate-preventive-rcpSkill
- generate-preventive-recommendSkill
- generate-preventive-rootcauseSkill
- generate-preventive-scpSkill
- generate-preventiveSkill
- generate-v3Skill
- intakeSkill
- map-assembleSkill
- map-framework-mappingSkill
- map-parse-frameworkSkill
- mapSkill
- research-api-surfaceSkill
- research-attack-surfaceSkill
- research-capabilitiesSkill
- research-mitigationsSkill
- research-v3Skill
- service-approval-v3Skill
- summarize-v3Skill
- validate-v3Skill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
Reviews can't be read right now.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

