This file is the agent-agnostic counterpart to CLAUDE.md. It tells any AI coding agent (Codex, Cursor, Gemini CLI, Aider, Kimi, Claude Code, etc.) how to drive redink's report-generation pipeline.
Claude Code users: you can ignore this file — Claude Code reads
CLAUDE.md+.claude/agents/+.claude/commands/directly and dispatches sub-agents. AGENTS.md exists so the same workflow runs on agents that don't have sub-agent spawning.
A penetration tester has finished the exploitation phase of an engagement. They've left you a folder of PoC screenshots and configuration files. Your job is to turn that into a compliance-grade report.
You'll do this by:
- Reading the engagement context (framework, scope, firm details, narrative summary)
- For each PoC folder, composing one validated finding entry (with verified CWE / CVE / CVSS / EPSS)
- Running the Python assembler to render everything into a docx (or JSON for OWASP OPTRS)
You are an external auditor. Write terse, present-tense, third-person, factual prose. No hedge fluff, no LLM tells, no marketing copy.
| File | What it gives you |
|---|---|
framework.yaml |
The active compliance framework (one of 17 presets) |
frameworks/<active>/manifest.yaml |
Section defaults, metadata, hard rules, official sources |
report-recipe.yaml |
Which of the 28 sections to include this engagement (overrides the framework default) |
sections/<id>/section.md |
Per-section template + frontmatter for the enabled sections |
sections/14-per-finding-details/schema.json |
Universal per-finding schema |
sections/14-per-finding-details/EXAMPLE.json |
A sample populated finding |
brand-guidelines.md |
Firm name, team, contact, methodology, tools, standards |
targets.yaml |
Engagement scopes — hosts, IPs, services |
engagement-summary.md |
Three narrative sections (Overview · Findings · Key Observations) |
poc/web/<NN_FolderName>/ |
Web-scope evidence — folder name becomes the vulnerability title |
poc/server/<NN_FolderName>/ |
Network/server-scope evidence |
scans/* (optional) |
Nessus / Burp / nmap output for cross-reference |
If brand-guidelines.md, targets.yaml, or engagement-summary.md still contain <...> placeholders, stop and tell the user what to fill — do not generate a partial report.
| Path | What |
|---|---|
jsons/NNN_<slug>.json |
One per-finding entry per PoC folder, conforming to the universal schema |
output/<framework>-report.docx |
Final assembled report (or .json for OWASP OPTRS) |
output/qa-report.md |
Self-audit log of citation re-validations |
These are non-negotiable. The recipient (a compliance evaluator) verifies them independently after submission. Get any of these wrong and the entry is rejected.
The PoC folder name (minus the NN_ prefix) is the vulnerability title. Preserve typos, trailing spaces, mixed case. Do not rewrite or clean up.
For every CWE you cite, fetch https://cwe.mitre.org/data/definitions/<n>.html. Read the Vulnerability Mapping Notes → Usage value verbatim. Only ALLOWED or ALLOWED-WITH-REVIEW are acceptable.
Known-bad CWE list (re-verify at MITRE before submission — list can change):
- PROHIBITED — never cite:
CWE-1187 - DISCOURAGED — replace with a more specific child:
CWE-200,CWE-269,CWE-284,CWE-285,CWE-287,CWE-693 - Category pages — not for vulnerability mapping:
CWE-254,CWE-264,CWE-388
If your candidate CWE is in any of these lists, walk MITRE's Children / CanFollow relationships and pick a more specific child. Read each candidate's Description + Vulnerability Mapping Notes before settling.
In the entry's cwe_cve_audit field, include the exact-quote Usage value (e.g., "Usage: ALLOWED").
For every CVE you cite, fetch https://nvd.nist.gov/vuln/detail/CVE-YYYY-NNNNN. Confirm the affected version range matches the target version. Record the exact-quote version-range string in cwe_cve_audit.
If the target version is explicitly patched/unaffected in the NVD record, do not cite that CVE — find the right one.
Use the metric values at https://www.first.org/cvss/v3.1/specification-document. Show per-metric reasoning + ISC / Impact / Exploitability / Base math in cvss_audit.
Don't copy NVD's score blindly — re-derive. If you disagree with NVD, document why.
For every CVE, fetch https://api.first.org/data/v1/epss?cve=CVE-YYYY-NNNNN. Format: EPSS X.YY% (lookup YYYY-MM-DD). CWE-only entries: EPSS N/A. If multiple CVEs, report the highest EPSS.
Copy paths character-for-character from disk. Never normalise spaces, never re-encode.
If the finding is "outdated library / component / version with known vulnerabilities," at least one verified CVE must be cited.
Enforcement — after writing the entry, do a self-audit pass: re-fetch each cited source (CWE, CVE, EPSS) from a clean shell and string-compare against the audit-trail excerpts you wrote. Mismatches mean you fabricated or drifted; fix the entry before submission.
- Read
framework.yaml. Note the active framework name. - Load
frameworks/<active>/manifest.yaml. Notesections,finding_id_format,hard_rules,explicitly_excluded_sections(if present). - Read
report-recipe.yaml. The enabled sections (in this file's order) are what gets rendered. - If
report-recipe.yamlis missing, copy the framework'ssectionsarray as the default recipe and ask the user if they want to customise (otherwise proceed).
brand-guidelines.md— firm detailstargets.yaml— engagement scopeengagement-summary.md— exec-summary narrativessections/<id>/section.mdfor each enabled section — frontmatter tells you what inputs each needssections/14-per-finding-details/schema.json+EXAMPLE.json— finding shape + tone calibrationpoc/web/*/andpoc/server/*/— list every folder, list every image insidescans/*if present — optional cross-reference
If any <...> placeholders remain in brand-guidelines.md, targets.yaml, or engagement-summary.md, stop and tell the user.
Only the questions a senior pentester would actually ask. Be specific. Quote folder names exactly as they appear. Examples:
- "I see
poc/server/02_LDAP_Anonymous_Bindandpoc/server/04_LDAP_Anon. Same finding or two distinct ones?" - "
poc/web/05_SQLi_login/has 8 screenshots but no DB-extract evidence. Did SQLi succeed (Critical) or only confirmed via error-based detection (High)?" - "
scans/nessus.htmlflags CVE-2024-1086 on the host, but I see no matching folder. Skip it, or draft a finding from the Nessus data alone?"
Do NOT ask things you can answer by reading the input files yourself. Wait for answers before proceeding.
Order findings by severity (Critical → Informational), then scope (Network → Web), then alphabetical by folder name. Assign IDs using the format from frameworks/<active>/manifest.yaml#finding_id_format (e.g. 001, FINDING-001, F-001, CHK-001, PCI-001).
For each PoC folder, produce one jsons/<id>_<slug>.json file conforming to the universal schema. Per-field guidance:
s_no/id/finding_id— assigned in step 4name_of_vulnerability/title— verbatim folder name (strip theNN_prefix only)severity— from CVSS-derived rubric inbrand-guidelines.md §8(or framework override; IRAP omits this entirely)vulnerable_location—"<host> @ <ip> — <service description>"(pull host/IP fromtargets.yaml)vulnerable_path_port_url— exact URL/port/pathvulnerable_parameter— HTTP param / form field / CLI arg / config key /nullcve_cwe— comma-separatedCWE-N, CVE-YYYY-NNNNN, validated values onlycvss_epss_score—"CVSS v3.1 Base X.Y (vector) — Severity; EPSS X.YY% (lookup YYYY-MM-DD)"description— ≤ 80 words. What weakness, where, worst-case impact.poc_intro— optional, ≤ 40 words;nullif step 1 is self-explanatorypoc_steps— array of{step_number, caption, image_path}; caption ≤ 30 wordsrecommendations— 2–4 actionable bullets ≤ 25 words eachreferences— CWE link + NVD link + vendor advisory + EPSS link, newline-separatedadditional_observations— optional chain notes, ornullcwe_cve_audit— one sentence per cited source with the exact-quote excerpt from your fetchcvss_audit— per-metric reasoning + ISC/Impact/Exploitability/Base math
Validate against the schema before writing. No empty strings — use null (where the schema allows) or "N/A" / "None" for required-but-inapplicable text.
After every JSON is written, do another pass: for each entry, re-fetch CWE / CVE / EPSS from clean URLs and string-compare against cwe_cve_audit. If anything drifted (you wrote ALLOWED but MITRE actually says DISCOURAGED; you wrote EPSS 12.3% but the API now returns 94.5%), fix the entry.
Write a self-audit log to output/qa-report.md listing every re-validation + any hard issues + any advisory issues.
python3 scripts/assemble_docx.pyThe Python assembler (no LLM needed for this step):
- Reads
framework.yaml+report-recipe.yaml - Iterates the enabled sections in recipe order
- Reads each
sections/<id>/section.mdtemplate - Pulls data from
brand-guidelines.md,targets.yaml,engagement-summary.md,jsons/*.json - Renders to
output/<framework>-report.docx(or.jsonfor OWASP OPTRS) - Embeds screenshots from PoC folders
Requires python-docx + pyyaml:
pip install python-docx pyyamlOpen output/<framework>-report.docx. Verify:
- Every enabled section is present and populated
- Every finding has its screenshots embedded
- The source-audit footer is visible on every finding (small grey italic text)
- No
<...>placeholders survived
If any blank fields or <...> placeholders appear in the rendered docx, the human auditor signs off — not you.
- Process meta — caps, rules, internal categories, "I'll analyze", "Let me walk through"
- Hedging — "might", "could potentially", "may possibly". State what is.
- LLM tells — "Here is a comprehensive...", "In summary, the vulnerability..."
- Marketing language — "robust", "comprehensive", "cutting-edge", "leverage", "synergy"
- Vendor blame — stick to the weakness, not who's at fault
Bad: "It appears that the application might be vulnerable to a potential SQL injection issue which could possibly allow attackers to leverage the database in malicious ways."
Good: "The login form's username parameter is concatenated into a SQL query without parameterization. Authenticated database read is possible via UNION-based extraction (see PoC step 3)."
If the user wants to customise which sections to include, run:
python3 scripts/recipe.pyThis is a pure Python interactive picker — walks every section, asks Y/N, writes report-recipe.yaml. No LLM involvement needed.
Claude Code users can also use the /redink-recipe slash command, which is the LLM-mediated equivalent.
sections/<id>/section.md — 28 modular section templates
frameworks/<name>/manifest.yaml — 17 framework presets (section recipes)
.claude/ — Claude Code-specific orchestration (ignore on other agents)
scripts/assemble_docx.py — Python assembler (cross-agent)
scripts/recipe.py — Python interactive picker (cross-agent)
brand-guidelines.md — firm details
targets.yaml — engagement scope
engagement-summary.md — exec summary narrative
framework.yaml — active framework
report-recipe.yaml — active section selection
poc/{web,server}/ — drop PoC folders here
jsons/ — generated per-finding entries
output/ — final report + qa-report.md
See README.md for the human-readable user flow, CLAUDE.md for the Claude-Code-specific version of these instructions, and DISCLAIMER.md for responsible-use framing.