security
Claude-powered SAST and SCA security scan skill. Invoked automatically as the Security Scan step (Step 7) of /aod.build (after Design Quality Gate, before Code Simplification) or standalone via /security. Analyzes all code files and dependency manifests changed on the feature branch relative to main for OWASP Top 10 vulnerability patterns and known CVE findings. Produces a human-readable security-scan.md report and machine-readable .security/ compliance artifacts (scan-log.jsonl, vulnerabilities.jsonl, SARIF 2.1.0, CycloneDX 1.5 SBOM). Blocks build progression on CRITICAL/HIGH findings with an explicit acknowledgment gate. Use --no-security in /aod.build to skip. Invoke /security directly for standalone analysis outside the build pipeline.
Best use case
security is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Claude-powered SAST and SCA security scan skill. Invoked automatically as the Security Scan step (Step 7) of /aod.build (after Design Quality Gate, before Code Simplification) or standalone via /security. Analyzes all code files and dependency manifests changed on the feature branch relative to main for OWASP Top 10 vulnerability patterns and known CVE findings. Produces a human-readable security-scan.md report and machine-readable .security/ compliance artifacts (scan-log.jsonl, vulnerabilities.jsonl, SARIF 2.1.0, CycloneDX 1.5 SBOM). Blocks build progression on CRITICAL/HIGH findings with an explicit acknowledgment gate. Use --no-security in /aod.build to skip. Invoke /security directly for standalone analysis outside the build pipeline.
Teams using security should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/security/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How security Compares
| Feature / Agent | security | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Claude-powered SAST and SCA security scan skill. Invoked automatically as the Security Scan step (Step 7) of /aod.build (after Design Quality Gate, before Code Simplification) or standalone via /security. Analyzes all code files and dependency manifests changed on the feature branch relative to main for OWASP Top 10 vulnerability patterns and known CVE findings. Produces a human-readable security-scan.md report and machine-readable .security/ compliance artifacts (scan-log.jsonl, vulnerabilities.jsonl, SARIF 2.1.0, CycloneDX 1.5 SBOM). Blocks build progression on CRITICAL/HIGH findings with an explicit acknowledgment gate. Use --no-security in /aod.build to skip. Invoke /security directly for standalone analysis outside the build pipeline.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# /security — SAST/SCA Security Scan
**Purpose**: Analyze code files and dependency manifests changed on the feature branch for OWASP Top 10 vulnerabilities and known CVE patterns. Write permanent audit artifacts. Block on CRITICAL/HIGH findings until acknowledged or fixed.
**When invoked from /aod.build**: Called as the Security Scan step (Step 7) via the Skill tool. `--no-security` flag in `/aod.build` bypasses this skill entirely.
**When invoked standalone**: Run directly as `/security` from any project directory. `security-scan.md` is written to `specs/{NNN}-*/` if inside an AOD project, or to the current working directory otherwise.
---
## Step 0: Parse Arguments
If invoked standalone, parse optional flags from arguments:
1. **`--no-security`**: If present (standalone invocation context), output "Security scan skipped (--no-security)" and exit cleanly. This flag is normally handled by `/aod.build` Step 0 before invoking this skill; if present here in standalone mode, honor it.
2. **Feature number extraction**: Detect current feature number from branch name (`git branch --show-current` → extract leading digits) or from `specs/*/` directory listing. Used for output path resolution.
3. **Output path resolution**:
- If inside an AOD project (`specs/{NNN}-*/` exists): write `security-scan.md` to `specs/{NNN}-*/security-scan.md`
- Otherwise: write `security-scan.md` to current working directory
---
## Step 1: File Detection
Detect all files changed on the feature branch relative to `main`.
### 1a: SAST File Detection (FR-004)
Run: `git diff --name-only main...HEAD`
Filter the output to **code files** by including only these extensions:
`.py .js .ts .jsx .tsx .sh .go .rs .java .rb .swift .kt .php .cs .cpp .c .h`
Exclude these patterns from the result:
- Generated files: `*.lock`, `*.min.js`, `*.min.css`, `*.map`, `*.generated.*`
- Directories: `vendor/`, `dist/`, `build/`, `node_modules/`, `.aod/`, `docs/`
Store result as `code_files` list. Count as `code_file_count`.
**Zero-file path**: If `code_file_count` is 0, set `sast_status = "SKIPPED"` with note `"SAST: No code files changed — skipping static analysis"`. Proceed to Step 1b.
### 1b: SCA Manifest Detection (FR-005)
From the same `git diff --name-only main...HEAD` output, filter to **dependency manifest filenames**:
`requirements.txt`, `pyproject.toml`, `setup.py`, `package.json`, `package-lock.json`, `yarn.lock`, `Gemfile`, `Gemfile.lock`, `go.mod`, `go.sum`, `pom.xml`, `build.gradle`, `Cargo.toml`, `Cargo.lock`
Store result as `manifest_files` list. Count as `manifest_count`.
**Zero-manifest path**: If `manifest_count` is 0, set `sca_status = "SKIPPED"` with note `"SCA: No dependency manifests changed — skipping dependency audit"`.
---
## Step 2: Pre-Scan Checks
### 2a: Large-Diff Warning (FR-006)
If `code_file_count` > 50:
```
Warning: {code_file_count} code files changed on this branch.
SAST analysis of large diffs may take several minutes.
(A) Continue with SAST analysis
(B) Skip SAST for this run
```
Use AskUserQuestion with these options. If user selects (B): set `sast_status = "SKIPPED"` with reason `"user-skip-large-diff"`.
### 2b: .security/ Directory Initialization (FR-020)
Check if `.security/` exists at repo root:
```bash
if [ ! -d ".security" ]; then
mkdir -p .security/reports
# Write README.md (see content below)
fi
```
Auto-generate `.security/README.md` with content:
```markdown
# .security/ — Compliance Evidence Store
This directory contains machine-readable security audit artifacts generated by the `/security` skill
(invoked automatically from `/aod.build` Security Scan step).
**Do NOT gitignore this directory.** These files serve as compliance evidence for security reviews,
PCI-DSS 4.0 requirements, and PR audit trails.
## Contents
| File | Format | Purpose |
|------|--------|---------|
| `scan-log.jsonl` | JSONL (append-only) | Immutable execution history with chain_hash integrity |
| `vulnerabilities.jsonl` | JSONL (append-only) | Vulnerability lifecycle events (DETECTED, RISK_ACCEPTED, EXPIRY_REOPENED, REMEDIATED) |
| `exceptions.jsonl` | JSONL (append-only) | Risk acceptance decisions with expiry dates |
| `reports/{commit-sha}.sarif` | SARIF 2.1.0 JSON | Machine-readable findings (importable to GitHub Security tab, Defect Dojo, SonarQube) |
| `reports/sca-{YYYY-MM-DD}.cdx.json` | CycloneDX 1.5 JSON | Software Bill of Materials for dependency scans |
## Verification
Chain hash integrity check (scan-log.jsonl):
Re-compute SHA256(entry_json_without_chain_hash) for the first entry.
For subsequent entries: SHA256(previous_chain_hash + current_entry_json_without_chain_hash).
All JSON keys sorted alphabetically, no trailing whitespace.
```
Check `.gitignore` for `.security/` exclusion:
```bash
if grep -q "\.security" .gitignore 2>/dev/null; then
echo "WARNING: .security/ appears to be gitignored. This directory must NOT be excluded from version control — it contains compliance evidence. Please remove the .security/ entry from .gitignore."
fi
```
### 2c: Expired Acceptance Check (FR-018)
If `.security/exceptions.jsonl` exists:
Read all entries. For each entry, check whether `acceptance_expiry` has passed using cross-platform epoch comparison (see utility functions below).
Collect all expired entries as `reopened_exceptions` list.
If `reopened_exceptions` is non-empty:
1. Display **"REOPENED EXCEPTIONS"** section (before any new findings):
```
REOPENED EXCEPTIONS
===================
The following previously acknowledged findings have expired and require re-acknowledgment:
{for each expired entry:}
vuln_id: {vuln_id}
Severity: {finding_severity}
Summary: {finding_summary}
File: {file from original finding — if available}
Accepted: {ts_accepted} by {accepted_by}
Expired: {acceptance_expiry}
Justification: {original justification}
```
2. Append `EXPIRY_REOPENED` event to `.security/vulnerabilities.jsonl` for each expired entry (see Step 6 for format). This MUST happen before the acknowledgment prompt.
3. Route each reopened exception through the severity gate in Step 5 (same blocking logic as new CRITICAL/HIGH findings).
---
## Step 3: SAST Analysis
Skip if `sast_status = "SKIPPED"`.
### 3a: File Batching (FR-009)
Group `code_files` into batches of 5–10 files per analysis invocation:
```
batch_1 = code_files[0:8]
batch_2 = code_files[8:16]
... etc.
```
Maintain `all_sast_findings = []` across all batches.
### 3b: OWASP P0 Pattern Detection Per Batch (FR-007)
For each batch, analyze the files against these OWASP P0 patterns:
**A01: Broken Access Control**
- Open Redirect: `redirect(user_input)` or `redirect(request.args.get(...))` without allowlist validation
- Path Traversal: `open(user_input)`, `os.path.join(base, user_input)` without sanitization
**A02: Cryptographic Failures**
- Hardcoded Secrets: credential literals in non-test files (`password = "..."`, `api_key = "..."`, `secret = "..."`, `token = "..."`)
- Weak Crypto: `hashlib.md5(password)`, `sha1(pwd)` used for password hashing (excludes file checksum use)
- Insecure Random: `random.random()`, `Math.random()` used for token generation, session IDs, or nonces
**A03: Injection**
- SQL Injection: string concatenation into SQL/DB query with user input (`"SELECT" + var`, f-string with request param)
- Command Injection: `os.system(var)`, `subprocess.call(f"cmd {var}")`, `exec(user_input)`
- Template Injection: `render_template_string(user_input)` or equivalent
**A05: Security Misconfiguration**
- Debug Mode: `DEBUG = True` in non-test config files, `app.run(debug=True)`
- Permissive CORS: `Access-Control-Allow-Origin: *` with `Allow-Credentials: true`
- Verbose Errors: `traceback.format_exc()` returned in HTTP response body
- Default Credentials: `admin/admin`, `root/root`, `user/password` literals in config files
**A07: Identification & Authentication Failures (partial)**
- Insecure Cookie: `set_cookie(...)` without `secure=True, httponly=True`
- Plaintext Credentials: password stored without hashing, credentials written to log
For each finding detected, collect:
```json
{
"type": "SAST",
"owasp_category": "A03: Injection",
"file": "relative/path/from/repo/root.py",
"line": 42,
"description": "SQL injection via string concatenation with user input",
"recommendation": "Use parameterized queries or an ORM. Never concatenate user input directly into SQL strings."
}
```
Append to `all_sast_findings`.
### 3c: CVSS Category Rubric (FR-008)
After collecting raw findings, assign CVSS scores using this fixed lookup table. Label ALL SAST scores **"estimated"**. Set `kev_flag: "N/A"` for all SAST findings.
| OWASP Category | Default Score | Default Severity | Escalation | Reduction |
|---|---|---|---|---|
| A03: SQL Injection | 8.8 | HIGH | No auth required → 9.8 CRITICAL | Server-side validation present → 6.5 MEDIUM |
| A03: Command Injection | 9.0 | CRITICAL | Remote exec confirmed → 9.8 CRITICAL | — |
| A02: Hardcoded Secret | 7.5 | HIGH | Production credential → 9.1 CRITICAL | Dev-only env file → 5.5 MEDIUM |
| A02: Weak Crypto (passwords) | 7.4 | HIGH | — | Read-only checksum use → 3.7 LOW |
| A02: Insecure Random | 6.5 | MEDIUM | Auth/session use → 8.0 HIGH | — |
| A01: Open Redirect | 6.1 | MEDIUM | Authenticated user targeted → 7.4 HIGH | — |
| A01: Path Traversal | 7.5 | HIGH | Unauthenticated endpoint → 9.1 CRITICAL | — |
| A05: Debug Mode | 5.3 | MEDIUM | Production config → 7.5 HIGH | — |
| A05: Permissive CORS * | 7.5 | HIGH | — | Non-credentialed endpoint → 5.4 MEDIUM |
| A05: Default Credentials | 9.8 | CRITICAL | — | — |
| A07: Missing secure/httponly cookie | 5.4 | MEDIUM | Session/auth cookie → 7.1 HIGH | — |
| A07: Plaintext credential storage | 8.0 | HIGH | — | — |
Apply escalation/reduction conditions based on code context detected during analysis.
### 3d: EPSS Qualitative Context (FR-010)
Assign qualitative EPSS context per vulnerability class (not a per-finding numeric score):
| Vulnerability Class | EPSS Context |
|---|---|
| SQL Injection | "SQL injection vulnerabilities are actively exploited in 8–15% of exposed systems within 30 days of disclosure" |
| Command Injection | "Command injection vulnerabilities are among the most exploited — exploitation rates exceed 20% within 30 days in exposed systems" |
| Hardcoded Secrets | "Hardcoded credentials are exploited in nearly all cases once discovered via source code access or repository scanning" |
| Weak Crypto (passwords) | "Weak password hashing enables offline cracking attacks; exploitation probability is high once credential databases are obtained" |
| Insecure Random | "Insecure random number generation for security-sensitive functions has a moderate exploitation rate depending on the use case" |
| Open Redirect | "Open redirects are commonly exploited in phishing campaigns; exploitation is straightforward for any attacker with knowledge of the endpoint" |
| Path Traversal | "Path traversal vulnerabilities are actively exploited in 5–12% of exposed systems; automated scanners commonly detect and exploit them" |
| Debug Mode | "Debug mode exposure is commonly exploited in reconnaissance and subsequent attacks; exploitation probability is high if internet-exposed" |
| Permissive CORS | "CORS misconfiguration is exploited in targeted attacks against authenticated users; exploitation requires user interaction" |
| Default Credentials | "Default credentials are exploited immediately upon discovery; automated scanners and credential stuffing tools target these actively" |
| Insecure Cookie | "Missing cookie security flags enable session hijacking; exploitation requires network position or XSS as a prerequisite" |
| Plaintext Credentials | "Plaintext credential storage is exploited whenever storage is accessed; exploitation probability is effectively 100% once storage is read" |
### 3e: Finding Deduplication
After aggregating findings across all batches:
Compute `vuln_id` for each finding using the fingerprint function (see Utility Functions).
If the same `vuln_id` appears multiple times (same vulnerability detected in multiple batches), retain only one instance.
Store deduplicated result as `sast_findings`.
**Error handling**: If any analysis step encounters an error (timeout, context limit, encoding error):
```
Security scan encountered an error: {error_message}
(A) Retry scan
(B) Skip and complete build
(C) Abort build
```
Use AskUserQuestion. If Skip: write `security-scan.md` with error type and timestamp; append `scan-log.jsonl` entry with `status: "ERROR"`. Build continues. If Abort: halt.
---
## Step 4: SCA Analysis
Skip if `sca_status = "SKIPPED"`.
### 4a: Manifest Analysis (FR-011)
For each file in `manifest_files`, analyze:
1. **Known CVE patterns**: Packages with known vulnerability patterns from Claude training data (cutoff: August 2025)
2. **Unsafe version ranges**: `*`, `latest`, `>=0.0.0` in production manifests
3. **Abandoned packages**: Packages where Claude has training knowledge of abandonment or takeover
4. **Supply chain risks**: Known typosquatting targets, known package takeover victims
For each finding, collect raw fields:
```json
{
"type": "SCA",
"package_name": "string",
"package_version": "string (declared version or constraint)",
"cve_id": "string | null",
"manifest_source": "requirements.txt",
"description": "string (with training cutoff note)",
"recommendation": "string"
}
```
### 4b: SCA Finding Format (FR-012)
For each SCA finding, populate all required fields:
- `severity`: assigned based on CVSS score (CRITICAL ≥9.0, HIGH 7.0–8.9, MEDIUM 4.0–6.9, LOW <4.0)
- `cve_id`: when known from training data; `null` otherwise
- `cvss_score`: NVD v3.1 base score from training data; labeled **"known official CVSS"**
- `epss_context`: qualitative context + note `"(verify current score at first.org/epss)"`
- `kev_flag`: `"YES"` or `"NO"` from training data + note `"(verify at cisa.gov/known-exploited-vulnerabilities)"`
- `kev_verification_url`: `"https://www.cisa.gov/known-exploited-vulnerabilities-catalog"`
- `purl`: ecosystem-appropriate format (see purl formats below)
**purl formats by ecosystem**:
```
Python: pkg:pypi/{name}@{version}
npm: pkg:npm/{name}@{version}
npm scoped: pkg:npm/%40{scope}/{name}@{version}
Go: pkg:golang/{module_path}@{version}
Java: pkg:maven/{group_id}/{artifact_id}@{version}
Rust: pkg:cargo/{name}@{version}
Ruby: pkg:gem/{name}@{version}
```
For `package.json` version ranges (e.g., `^4.17.21`): record the declared constraint in `version` field; add property `aod:version_constraint` noting "resolved version may differ — declare exact version in lock file for definitive audit".
### 4c: KEV=YES Auto-Elevation (FR-015)
Any SCA finding with `kev_flag: "YES"` MUST be elevated to CRITICAL severity regardless of its CVSS base score:
```
if kev_flag == "YES":
severity = "CRITICAL"
cvss_note = "(elevated to CRITICAL per KEV rule — actively exploited)"
```
This elevation propagates to the severity gate in Step 5: KEV=YES findings show only (A) Fix now and (C) Abort.
### 4d: CycloneDX SBOM Component Collection
For each package identified in `manifest_files`, create an `SBOMComponent` record:
```json
{
"type": "library",
"name": "package-name",
"version": "declared-version-or-constraint",
"purl": "pkg:pypi/package-name@1.2.3",
"properties": [
{"name": "aod:manifest_source", "value": "requirements.txt"},
{"name": "aod:version_constraint", "value": "^1.2.3 (resolved version may differ)"}
]
}
```
Store as `sbom_components` list. Used by Step 6 (artifact writing) to generate the CycloneDX SBOM.
### 4e: SCA Training-Cutoff Disclaimer (FR-013)
Every SCA section in `security-scan.md` output MUST include verbatim:
> SCA findings are based on Claude training knowledge (cutoff: August 2025). Supplement with real-time CVE scanning (npm audit, pip-audit, Snyk) for production workloads.
**Error handling**: Same retry/skip/abort pattern as Step 3 error handling.
---
## Step 5: Severity Gate
### 5a: Aggregate All Findings
Combine `sast_findings` + `sca_findings` (after KEV elevation) into `all_findings`.
Group:
- `blocking_findings` = findings with `severity` in `["CRITICAL", "HIGH"]`
- `non_blocking_findings` = findings with `severity` in `["MEDIUM", "LOW", "INFO"]`
Also include `reopened_exceptions` (from Step 2c) in `blocking_findings` — they go through the same gate.
### 5b: Display Non-Blocking Findings
For MEDIUM findings, display with note: "Review recommended before deploy"
For LOW/INFO findings, display in report without note.
Non-blocking findings do not halt the build.
### 5c: Clean-Scan Path (US1 AC2, SC-006)
If `blocking_findings` is empty AND there are no `reopened_exceptions`:
Output:
```
Security Scan: PASSED — no issues found (AI-powered analysis; supplement with dedicated SAST tools for production-critical systems)
```
Set `scan_status = "PASSED"`. Auto-proceed to Step 6 (artifact writing).
### 5d: Blocking Acknowledgment Gate (FR-014, FR-015)
For each finding in `blocking_findings` (one at a time):
**Check KEV status**:
- If `kev_flag == "YES"`:
```
{severity} Finding: {owasp_category or cve_id}
File: {file}:{line}
CVSS: {cvss_score} {severity} ({cvss_type})
KEV: YES (actively exploited — verify at cisa.gov/known-exploited-vulnerabilities-catalog)
EPSS: {epss_context}
Summary: {description}
Fix: {recommendation}
(A) Fix now — halt this session; re-run /aod.build to resume at security scan
(C) Abort
Note: Acknowledge option unavailable for KEV=YES findings — actively exploited vulnerability
```
- If `kev_flag != "YES"`:
```
{severity} Finding: {owasp_category or cve_id}
File: {file}:{line}
CVSS: {cvss_score} {severity} ({cvss_type})
KEV: {kev_flag}
EPSS: {epss_context}
Summary: {description}
Fix: {recommendation}
(A) Fix now — halt this session; re-run /aod.build to resume at security scan
(B) Acknowledge and proceed — requires justification (minimum 20 characters)
(C) Abort
```
Use AskUserQuestion.
**On (A) Fix now**: Halt build session. Developer fixes the issue and re-runs `/aod.build`.
**On (B) Acknowledge and proceed**:
- Prompt: "Enter justification (minimum 20 characters):"
- Validate: `len(justification.strip()) >= 20` — if too short, re-prompt
- Get `accepted_by` via: `git config user.email`
- Compute `acceptance_expiry`:
- CRITICAL: 30 days from now (ISO 8601 UTC)
- HIGH: 90 days from now (ISO 8601 UTC)
- Append `RiskAcceptance` entry to `.security/exceptions.jsonl` (see Step 6)
- Record finding as acknowledged; add to `acknowledged_findings` list
- Continue to next blocking finding
**On (C) Abort**: Halt build session entirely.
### 5e: Post-Gate Status
After processing all blocking findings:
- If all were acknowledged: `scan_status = "FINDINGS"` (build proceeds)
- If any were not acknowledged (user selected A or C): build halted
---
## Step 6: Artifact Writing
Write all compliance artifacts. This step executes after the severity gate completes (all blocking findings acknowledged or no blocking findings).
### 6a: security-scan.md (FR-026)
Always write `specs/{NNN}-*/security-scan.md` (or CWD for standalone). Content:
```markdown
# Security Scan Report
**Feature**: {NNN} — {feature_name or branch_name}
**Branch**: {branch}
**Commit**: {commit_sha}
**Scan ID**: {scan_id}
**Timestamp**: {ts_started} UTC
**Status**: {PASSED | FINDINGS | SKIPPED | ERROR}
---
## Summary
| Category | Count |
|---|---|
| Files scanned (SAST) | {code_file_count} |
| Manifests audited (SCA) | {manifest_count} |
| CRITICAL findings | {count} |
| HIGH findings | {count} |
| MEDIUM findings | {count} |
| LOW findings | {count} |
| INFO findings | {count} |
{If --no-security: "Skipped (--no-security) — {timestamp}"}
---
## Findings
{if no findings: "No security findings detected."}
### CRITICAL
{for each CRITICAL finding:}
#### {vuln_id}: {owasp_category or cve_id}
- **Type**: {SAST | SCA}
- **File**: `{file}:{line}`
- **CVSS**: {cvss_score} CRITICAL ({cvss_type})
- **EPSS**: {epss_context}
- **KEV**: {kev_flag}{if YES: " — verify at cisa.gov/known-exploited-vulnerabilities-catalog"}
- **Description**: {description}
- **Recommendation**: {recommendation}
{if acknowledged: "- **Acknowledged**: Yes — Justification: {justification} | Accepted by: {accepted_by} | Expiry: {acceptance_expiry}"}
### HIGH
{same format}
### MEDIUM
{same format, with note: "(Review recommended before deploy)"}
### LOW / INFO
{same format}
---
## Acknowledgment Decisions
{if any acknowledged findings:}
| vuln_id | Severity | Justification | Accepted By | Expiry |
|---|---|---|---|---|
{rows}
{if no acknowledged findings: "No acknowledgment decisions made in this scan."}
---
## Artifacts
- Scan log: `.security/scan-log.jsonl`
- Vulnerability events: `.security/vulnerabilities.jsonl`
- Risk acceptances: `.security/exceptions.jsonl`
- SARIF report: `.security/reports/{commit_sha}.sarif`
{if sca ran: "- CycloneDX SBOM: `.security/reports/sca-{date}.cdx.json`"}
---
*Security Scan: AI-powered analysis; supplement with dedicated SAST tools for production-critical systems.*
{if sca ran: "*SCA findings are based on Claude training knowledge (cutoff: August 2025). Supplement with real-time CVE scanning (npm audit, pip-audit, Snyk) for production workloads.*"}
```
### 6b: scan-log.jsonl (FR-021)
Append exactly one `ScanLogEntry` to `.security/scan-log.jsonl`:
```json
{"scan_id": "{uuid_v4}", "ts": "{ISO_8601_UTC}", "branch": "{branch}", "commit_sha": "{12_char_sha}", "status": "{PASSED|FINDINGS|SKIPPED|ERROR}", "files_scanned": {integer}, "manifests_audited": {integer}, "finding_counts": {"CRITICAL": 0, "HIGH": 0, "MEDIUM": 0, "LOW": 0, "INFO": 0}, "chain_hash": "{sha256_hex}"}
```
Compute `chain_hash` using the chain hash utility function (see Utility Functions).
Append-only. Never modify existing entries.
### 6c: vulnerabilities.jsonl (FR-022)
Append `VulnerabilityEvent` entries to `.security/vulnerabilities.jsonl`:
**DETECTED** (for each new finding where no existing event exists for `vuln_id`, OR most recent event is `REMEDIATED`):
```json
{"vuln_id": "...", "event_type": "DETECTED", "ts": "{ISO_8601_UTC}", "scan_id": "{scan_id}", "detail": {"severity": "{severity}", "file": "{file}", "previous_expiry": null, "original_acceptor": null}}
```
**Deduplication rule**: If the most recent event for a `vuln_id` is `RISK_ACCEPTED`, do NOT append a new `DETECTED` event — surface the existing acceptance status instead.
**RISK_ACCEPTED** (for each acknowledged finding):
```json
{"vuln_id": "...", "event_type": "RISK_ACCEPTED", "ts": "{ISO_8601_UTC}", "scan_id": "{scan_id}", "detail": {"severity": "{severity}", "file": "{file}", "previous_expiry": null, "original_acceptor": null}}
```
**REMEDIATED** (for each `vuln_id` in previous `vulnerabilities.jsonl` that is absent from current scan findings):
```json
{"vuln_id": "...", "event_type": "REMEDIATED", "ts": "{ISO_8601_UTC}", "scan_id": "{scan_id}", "detail": {"severity": "{previous_severity}", "file": "{file}", "previous_expiry": null, "original_acceptor": null}}
```
**EXPIRY_REOPENED** (for each expired exception, written in Step 2c before acknowledgment prompt):
```json
{"vuln_id": "...", "event_type": "EXPIRY_REOPENED", "ts": "{ISO_8601_UTC}", "scan_id": "{scan_id}", "detail": {"severity": "{finding_severity}", "file": "{file_from_exception}", "previous_expiry": "{expired_acceptance_expiry}", "original_acceptor": "{accepted_by_from_exception}"}}
```
Never truncate or rewrite this file.
### 6d: SARIF 2.1.0 Writer (FR-024)
Write `.security/reports/{commit_sha}.sarif`:
```json
{
"version": "2.1.0",
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
"runs": [
{
"tool": {
"driver": {
"name": "aod-security",
"version": "1.0.0",
"informationUri": "https://github.com/anthropics/agentic-oriented-development",
"rules": [
{
"id": "{owasp_category_id}",
"name": "{owasp_category_name}",
"shortDescription": {"text": "{category description}"},
"helpUri": "https://owasp.org/Top10/"
}
]
}
},
"results": [
{
"ruleId": "{owasp_category_id}",
"message": {"text": "{description}"},
"locations": [
{
"physicalLocation": {
"artifactLocation": {"uri": "{relative_file_path}"},
"region": {"startLine": {line_number}}
}
}
],
"partialFingerprints": {
"primaryLocationLineHash": "{deterministic_hash_of_file_line_description}"
},
"level": "{error|warning|note}"
}
]
}
]
}
```
Level mapping: CRITICAL/HIGH → `"error"`, MEDIUM → `"warning"`, LOW/INFO → `"note"`
One `rules[]` entry per distinct OWASP category detected. One `results[]` entry per finding.
**Schema validation**: Before writing, verify the JSON structure is valid SARIF 2.1.0 (check required fields: `version`, `runs[0].tool.driver.name`, `runs[0].results[]`). If validation fails: write `security-scan.md` with validation error note; skip SARIF write.
### 6e: CycloneDX SBOM Writer (FR-025)
Write `.security/reports/sca-{YYYY-MM-DD}.cdx.json` when `manifest_count > 0`:
```json
{
"bomFormat": "CycloneDX",
"specVersion": "1.5",
"serialNumber": "urn:uuid:{uuid_v4}",
"version": 1,
"metadata": {
"timestamp": "{ISO_8601_UTC}",
"tools": [{"vendor": "aod-security", "name": "aod-security", "version": "1.0.0"}],
"component": {"type": "application", "name": "{repo_name}", "version": "{commit_sha}"}
},
"components": [
{
"type": "library",
"name": "{package_name}",
"version": "{declared_version_or_constraint}",
"purl": "{ecosystem_purl}",
"properties": [
{"name": "aod:manifest_source", "value": "{manifest_filename}"},
{"name": "aod:version_constraint", "value": "{constraint_note_or_null}"}
]
}
]
}
```
### 6f: exceptions.jsonl Appender (FR-017)
For each acknowledged finding, append one `RiskAcceptance` entry to `.security/exceptions.jsonl`:
```json
{"vuln_id": "...", "finding_severity": "HIGH", "finding_summary": "{one-line description}", "justification": "{user justification}", "accepted_by": "{git_user_email}", "ts_accepted": "{ISO_8601_UTC}", "acceptance_expiry": "{ISO_8601_UTC — 30d CRITICAL / 90d HIGH}"}
```
Append-only. One entry per acknowledgment decision.
---
## Step 7: Commit Strategy (FR-030)
### Commit 1 (always, when scan ran)
Stage: `scan-log.jsonl` + `.security/reports/{commit_sha}.sarif` + `.security/reports/sca-{date}.cdx.json` (if written) + `specs/{NNN}-*/security-scan.md` + `.security/README.md` (if first run)
Commit message:
```
security({NNN}): run security scan [{commit_sha}]
```
### Commit 2 (only when findings acknowledged)
Stage: `.security/vulnerabilities.jsonl` + `.security/exceptions.jsonl`
Commit message:
```
security({NNN}): record risk acceptances
```
**Commit 2 failure handling (FR-031)**:
If Commit 2 fails after Commit 1 succeeds:
1. Catch the error
2. Notify: "Scan evidence committed (Commit 1) but risk acceptances were not recorded (Commit 2 failed: {error})"
3. Retry Commit 2 once
4. If retry fails: display manual recovery steps:
```
Manual recovery required:
git add .security/vulnerabilities.jsonl .security/exceptions.jsonl
git commit -m "security({NNN}): record risk acceptances (manual recovery)"
```
5. Build MUST NOT proceed silently — the developer must acknowledge the manual recovery requirement
---
## Utility Functions
### vuln_id Fingerprint (FR-023)
**SAST findings**:
```
input = owasp_category + file + str(line_number) + description[:50]
hash = SHA256(input)
vuln_id = "{REPO-SLUG}-VULN-" + hash[:12]
```
Where `REPO-SLUG` is the repository directory name (lowercase, hyphens for spaces).
**SCA findings** (when CVE known):
```
input = cve_id + package_name + package_version
hash = SHA256(input)
vuln_id = "{REPO-SLUG}-VULN-" + hash[:12]
```
**SCA findings** (no CVE):
```
input = "SCA:" + package_name + package_version + description[:50]
hash = SHA256(input)
vuln_id = "{REPO-SLUG}-VULN-" + hash[:12]
```
### chain_hash Computation (FR-021, SC-008)
```
# Serialize entry JSON without the chain_hash field:
# - Sort all keys alphabetically
# - No trailing whitespace or newlines within values
# First entry:
chain_hash = SHA256(serialized_entry_json_without_chain_hash)
# Subsequent entries:
chain_hash = SHA256(previous_entry_chain_hash + serialized_entry_json_without_chain_hash)
```
To compute SHA256 as an AI agent: use Python `hashlib.sha256(input.encode()).hexdigest()` or equivalent. Keys must be sorted alphabetically, JSON compact (no spaces), no trailing newline in the input string.
### Cross-Platform Date Comparison (FR-019)
For expiry checks (bash 3.2 compatible, macOS + Linux):
```bash
OS=$(uname -s)
acceptance_expiry="2026-06-01T12:00:00Z"
if [ "$OS" = "Darwin" ]; then
expiry_epoch=$(date -j -f "%Y-%m-%dT%H:%M:%SZ" "$acceptance_expiry" +%s)
else
expiry_epoch=$(date -d "$acceptance_expiry" +%s)
fi
now_epoch=$(date +%s)
if [ "$now_epoch" -gt "$expiry_epoch" ]; then
echo "EXPIRED"
fi
```
No `declare -A`. No `${var^}`. No `readarray`. Use `case` instead of associative arrays. Use `tr` for case modification. Use `while read` for line-by-line processing.
For computing future expiry dates (ISO 8601 UTC):
```bash
OS=$(uname -s)
if [ "$OS" = "Darwin" ]; then
# macOS: -v+30d for CRITICAL, -v+90d for HIGH
expiry_30d=$(date -u -v+30d "+%Y-%m-%dT%H:%M:%SZ")
expiry_90d=$(date -u -v+90d "+%Y-%m-%dT%H:%M:%SZ")
else
# Linux:
expiry_30d=$(date -u -d "+30 days" "+%Y-%m-%dT%H:%M:%SZ")
expiry_90d=$(date -u -d "+90 days" "+%Y-%m-%dT%H:%M:%SZ")
fi
```Related Skills
~aod-status
On-demand backlog snapshot and lifecycle stage summary. Regenerates BACKLOG.md from GitHub Issues and displays item counts per stage. Use this skill when you need to check backlog status, view stage counts, regenerate BACKLOG.md, or get a lifecycle overview.
~aod-spec
Validates specification completeness and quality by checking for mandatory sections, [NEEDS CLARIFICATION] markers, testable criteria, and clear scope boundaries. Use this skill when you need to check if spec is complete, validate specifications, review spec.md, or check specification quality. Ensures specifications are ready for architecture and implementation phases.
~aod-score
Re-score an existing idea's ICE rating when circumstances change. Use this skill when you need to re-evaluate ideas, update ICE scores, change idea priority, or re-assess deferred ideas.
~aod-run
Full lifecycle orchestrator that chains all 6 AOD stages (Discover, Define, Plan, Build, Deliver, Document) with disk-persisted state for session resilience and governance gates at every boundary. Use this skill when you need to run the full lifecycle, orchestrate stages, resume orchestration, or check orchestration status.
~aod-project-plan
Validates architecture documentation completeness by checking for technology stack, API specifications, database schema, security architecture, and alignment with feature specification. Use this skill when you need to check if plan.md is complete before implementation, validate architecture documentation, or review technical plans for completeness.
~aod-plan
Plan stage orchestrator that runs all three Plan sub-steps (spec → project-plan → tasks) in sequence with governance gates. Stops on rejection, continues through approvals. Use this skill when you need to run the full Plan stage, navigate planning sub-steps, or resume after a rejection.
~aod-kickstart
POC kickstart skill that transforms a project idea into a sequenced consumer guide with 6-10 seed features. Use when a developer invokes /aod.kickstart to generate a consumer guide, when starting a new project and needing a structured backlog plan, or when converting a project idea into seed features for the AOD lifecycle. Three-stage workflow: Idea Intake, Stack Selection, Guide Generation.
~aod-discover
Unified discovery skill with 4 entry points: /aod.discover (full flow: capture + score + validate), /aod.discover --seed (fast-track pre-vetted ideas with auto defaults), /aod.idea (capture + score only), /aod.validate (PM validation for existing idea). Use this skill when you need to capture ideas, run discovery, validate ideas with PM, generate user stories, log feature requests, or add items to the ideas backlog.
~aod-deliver
Structured delivery retrospective for the AOD Lifecycle's Deliver stage. Validates Definition of Done, captures delivery metrics (estimated vs. actual duration), logs surprises, feeds new ideas back into discovery via GitHub Issues, and creates Institutional Knowledge entries. Use this skill when you need to close a feature, run a delivery retrospective, capture lessons learned, or complete the AOD lifecycle.
~aod-define
Internal skill invoked by /aod.define to generate industry-standard PRD content using proven frameworks from Google, Amazon, and Intercom. Do NOT invoke directly — use /aod.define instead, which wraps this skill with Triad governance and sign-offs.
~aod-build
Generate standardized checkpoint reports for multi-phase implementation projects. Use this skill when pausing implementation at strategic milestones (phase completion, user story completion, critical features) to create comprehensive progress reports with task breakdowns, metrics, knowledge base entries, and resume instructions.
~aod-bugfix
One-shot governed bug fix loop: diagnose → plan → implement → verify → document. TRIGGER when: user reports a bug, pastes an error message/stack trace/failing test, or asks to fix a bug. Runs 5 Whys root cause analysis, presents confirmation gate before any code changes, implements fix, verifies with tests, and generates KB entry for review.