diff --git a/.opencode/skills/exploit-development/SKILL.md b/.opencode/skills/exploit-development/SKILL.md index 466bbd6..58db462 100644 --- a/.opencode/skills/exploit-development/SKILL.md +++ b/.opencode/skills/exploit-development/SKILL.md @@ -218,24 +218,32 @@ Good PoC: if "DB_PASSWORD" in line: print(f"\n[!] IMPACT: Database password exposed: {line}") -## Severity adjustment rules +## CVSSv4 scoring -### When to upgrade +After exploitation, recompute the CVSSv4 vector with the metrics now justified by demonstrated impact. -| From | To | Condition | -|---|---|---| -| INFO | LOW | Any concrete exploitation demonstrated | -| LOW | MEDIUM | Impact affects confidentiality, integrity, or availability meaningfully | -| MEDIUM | HIGH | Code execution, sensitive data exfiltration, or privilege escalation demonstrated | -| HIGH | CRITICAL | Unauthenticated RCE, full database access, or cross-tenant compromise demonstrated | +Typical metric refinements after a working PoC: -### When to downgrade +- `AC` / `AT` may drop once attack complexity and prerequisites are proven. +- `PR` / `UI` may drop if no authentication or interaction is actually required. +- `VC` / `VI` / `VA` (and `SC` / `SI` / `SA`) rise when concrete confidentiality, integrity, or availability impact is demonstrated on the vulnerable or subsequent system. +- `E` (threat) rises from `P`/`U` to `A` only when a working PoC exists. -| From | To | Condition | -|---|---|---| -| CRITICAL | HIGH | Exploitation requires authentication or specific preconditions | -| HIGH | MEDIUM | Impact is limited to non-sensitive data or requires unlikely conditions | -| MEDIUM | LOW | Exploitation achieves only DoS or minimal information disclosure | +If exploitation is marked `NOT_FEASIBLE`, do not invent demonstrated-impact metrics. Preserve or refine the vector only according to evidence already proven by validation and the failed exploitation attempt. + +## Severity adjustment + +Once the CVSSv4 vector and score are decided, update the finding severity to match the score band. + +| CVSSv4 base+threat score | Severity | +|--------------------------|----------| +| 9.0 - 10.0 | CRITICAL | +| 7.0 - 8.9 | HIGH | +| 4.0 - 6.9 | MEDIUM | +| 0.1 - 3.9 | LOW | +| 0.0 | INFO | + +Do not pick a severity label without recomputing or preserving a justified vector. ### Documentation requirement @@ -244,6 +252,10 @@ Always record: exploitation: severity_before: "MEDIUM" severity_after: "HIGH" + cvss_v4: + vector: "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:A" + score: 9.3 + justification: "Unauthenticated RCE demonstrated against a default install (see PoC and recording)." And include justification in `# Exploitation Result`. @@ -352,6 +364,7 @@ Before finishing: `# Recording` section, - CWE id(s) are assigned in finding frontmatter, - impact narrative is clear and concrete, +- CVSSv4 vector, score, and justification are recorded in finding frontmatter, - severity was assessed and adjusted if warranted, - finding frontmatter `exploitation:` block is updated, - finding `# Exploitation Result` section is updated, diff --git a/.opencode/skills/finding-format/SKILL.md b/.opencode/skills/finding-format/SKILL.md index 4cc192d..b0dba8b 100644 --- a/.opencode/skills/finding-format/SKILL.md +++ b/.opencode/skills/finding-format/SKILL.md @@ -55,16 +55,26 @@ Rules: - Use four digits for the numeric id. - Use lowercase slugs. - Use hyphens instead of spaces. -- Keep the slug short but meaningful. +- Derive the slug from a short vulnerability-style title. +- Do not necessarily derive it directly from the raw finding description. +- Keep the slug concise: favor 8 words or fewer. +- Include the product surface when it helps locate the finding mentally. +- Avoid code terms, function names, endpoint internals, and long causal chains. - Do not reuse ids. - Do not change ids after creation. -Examples: +Good examples: CC-0001-missing-owner-check.md CC-0002-stack-buffer-overflow-in-parser.md CC-0003-unsafe-yaml-deserialization.md CC-0004-command-injection-via-backup-name.md + CC-0005-path-traversal-in-admin-export.md + +Bad examples: + +- `CC-0020-username-comparison-in-uh-auth-check-uses-non-constant-time-strcmp-leaking-realm-usernames-via-timing.md` +- `CC-0021-unbounded-crypt-3-invocations-on-every-basic-auth-attempt-enable-single-threaded-daemon-dos.md` ## Required frontmatter @@ -77,6 +87,10 @@ Minimum fields: title: "Short vulnerability title" status: "PENDING" severity: "MEDIUM" + cvss_v4: + vector: "" + score: 0.0 + justification: "" confidence: "LOW" category: "Unclassified" cwe: [] @@ -148,7 +162,14 @@ Use only: - `LOW` - `INFO` -Severity should reflect realistic impact, not just theoretical bug class. +Severity must reflect realistic impact, not just theoretical bug class. +When `cvss_v4.score` is populated, `severity` must match the score band +defined by the exploit-development skill. + +`cvss_v4.vector`, `cvss_v4.score`, and `cvss_v4.justification` are +required frontmatter fields. They may stay empty or zero for early +`PENDING` findings, but must be populated when status is `CONFIRMED` or +`EXPLOITED`. ## Confidence values diff --git a/AGENTS.md b/AGENTS.md index a8957af..e50d0e8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -250,8 +250,6 @@ Use these severity levels: - `LOW` - `INFO` -Do not over-focus on CVSS in the PoC. Prefer clear technical impact. - ## Status values Use only these status values: diff --git a/prompts/phase-1-recon.md b/prompts/phase-1-recon.md index 6050346..47daabf 100644 --- a/prompts/phase-1-recon.md +++ b/prompts/phase-1-recon.md @@ -61,6 +61,16 @@ Document: - interesting files for Phase 2, - validation strategy. +Recursively scan `src/` for high-signal documentation such as `README*`, `SECURITY*`, `THREAT_MODEL*`, `CONTRIBUTING*`, `docs/`, and similar. Also inspect `CHANGELOG*`, `HISTORY*`, and `NEWS*`, but prefer top-level or component-relevant files. + +If the repository has dozens of changelog/history/news files, do not process them exhaustively. Summarize the pattern, prioritize files near the primary target or security-relevant components, and record that scope decision. + +Review external public context for prior security advisories, CVE references, historical security fixes, release notes, and recurring bug classes affecting this project or closely related upstream components. Prefer project advisories, GitHub Security Advisories, NVD/CVE entries, issue trackers, release notes, and distribution advisories. + +Use external context only as reconnaissance input: distill affected components, historical bug patterns, trust boundaries, and fixed attack surfaces into the notes. Do not treat external claims as proof that the current source tree is affected; verify everything against `src/` before creating findings. + +Distill declared threat model, past CVEs, trust boundaries, and third-party components into the relevant notes; treat author claims as input to verify, not facts. + ### File risk index Create `itemdb/notes/file-risk-index.yml` using the schema in `templates/file-risk-index.yml`. diff --git a/templates/finding.md b/templates/finding.md index 8ad50fe..81c5e8d 100644 --- a/templates/finding.md +++ b/templates/finding.md @@ -3,6 +3,10 @@ id: "CC-0000" title: "Short vulnerability title" status: "PENDING" severity: "MEDIUM" +cvss_v4: + vector: "" + score: 0.0 + justification: "" confidence: "LOW" category: "Unclassified" cwe: [] diff --git a/tests/test_check_frontmatter.py b/tests/test_check_frontmatter.py index 7f842f0..3579687 100644 --- a/tests/test_check_frontmatter.py +++ b/tests/test_check_frontmatter.py @@ -8,6 +8,10 @@ title: "Valid finding" status: "PENDING" severity: "MEDIUM" +cvss_v4: + vector: "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N" + score: 5.0 + justification: "Unit-test fixture with medium impact." confidence: "LOW" category: "Test" cwe: [] @@ -68,6 +72,10 @@ def test_validate_frontmatter_status_directory_mismatch(tmp_path): title: "Exploited finding" status: "EXPLOITED" severity: "HIGH" +cvss_v4: + vector: "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:L/VA:N/SC:N/SI:N/SA:N/E:A" + score: 8.0 + justification: "Unit-test fixture with demonstrated high impact." confidence: "CONFIRMED" category: "Test" cwe: {cwe} diff --git a/tools/check-frontmatter.py b/tools/check-frontmatter.py index 2a44755..b6a7df3 100755 --- a/tools/check-frontmatter.py +++ b/tools/check-frontmatter.py @@ -58,6 +58,7 @@ "title", "status", "severity", + "cvss_v4", "confidence", "category", "cwe", @@ -76,6 +77,12 @@ "updated_at", ] +REQUIRED_CVSS_V4_FIELDS = [ + "vector", + "score", + "justification", +] + REQUIRED_VALIDATION_FIELDS = [ "status", "methods", @@ -97,6 +104,7 @@ FINDING_ID_RE = re.compile(r"^CC-\d{4,}$") FINDING_FILENAME_RE = re.compile(r"^CC-\d{4}-[a-z0-9]+[-_a-z0-9]*\.md$", re.IGNORECASE) SECTION_RE = re.compile(r"^# (?P.+?)\n(?P<body>.*?)(?=^# |\Z)", re.MULTILINE | re.DOTALL) +CVSS_V4_VECTOR_RE = re.compile(r"^CVSS:4\.0/") REQUIRED_EXPLOITED_SECTIONS = [ "Root cause analysis", @@ -151,6 +159,64 @@ def has_remediation_code(value: str) -> bool: return "```diff" in value or "```patch" in value or "```c" in value or "```" in value +def severity_from_cvss_v4_score(score: float) -> str: + if score == 0.0: + return "INFO" + if 0.1 <= score <= 3.9: + return "LOW" + if 4.0 <= score <= 6.9: + return "MEDIUM" + if 7.0 <= score <= 8.9: + return "HIGH" + if 9.0 <= score <= 10.0: + return "CRITICAL" + return "" + + +def validate_cvss_v4(data: Dict[str, object], status: object, severity: object) -> List[str]: + errors: List[str] = [] + cvss_v4 = data.get("cvss_v4") + + if not isinstance(cvss_v4, dict): + return ["cvss_v4 must be an object"] + + for field in REQUIRED_CVSS_V4_FIELDS: + if field not in cvss_v4: + errors.append(f"missing cvss_v4 field: cvss_v4.{field}") + + vector = cvss_v4.get("vector") + score = cvss_v4.get("score") + justification = cvss_v4.get("justification") + + if vector is not None and not isinstance(vector, str): + errors.append("cvss_v4.vector must be a string") + if justification is not None and not isinstance(justification, str): + errors.append("cvss_v4.justification must be a string") + if score is not None and not isinstance(score, (int, float)): + errors.append("cvss_v4.score must be a number") + + if status in ("CONFIRMED", "EXPLOITED"): + if not isinstance(vector, str) or is_placeholder(vector): + errors.append(f"{status} status requires populated cvss_v4.vector") + elif not CVSS_V4_VECTOR_RE.match(vector): + errors.append("cvss_v4.vector must start with 'CVSS:4.0/'") + + if not isinstance(justification, str) or is_placeholder(justification): + errors.append(f"{status} status requires populated cvss_v4.justification") + + if isinstance(score, (int, float)): + expected_severity = severity_from_cvss_v4_score(float(score)) + if not expected_severity: + errors.append("cvss_v4.score must be between 0.0 and 10.0") + elif severity in SEVERITIES and severity != expected_severity: + errors.append( + f"severity {severity!r} does not match cvss_v4.score {score!r} " + f"(expected {expected_severity!r})" + ) + + return errors + + def validate_finding(path: Path) -> List[str]: errors: List[str] = [] @@ -185,6 +251,8 @@ def validate_finding(path: Path) -> List[str]: if severity not in SEVERITIES: errors.append(f"invalid severity: {severity!r}") + errors.extend(validate_cvss_v4(data, status, severity)) + confidence = data.get("confidence") if confidence not in CONFIDENCES: errors.append(f"invalid confidence: {confidence!r}")