Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 27 additions & 14 deletions .opencode/skills/exploit-development/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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)."
Comment thread
pruiz marked this conversation as resolved.

And include justification in `# Exploitation Result`.

Expand Down Expand Up @@ -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,
Expand Down
27 changes: 24 additions & 3 deletions .opencode/skills/finding-format/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment thread
pruiz marked this conversation as resolved.
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

Expand All @@ -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: []
Expand Down Expand Up @@ -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

Expand Down
2 changes: 0 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
10 changes: 10 additions & 0 deletions prompts/phase-1-recon.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
4 changes: 4 additions & 0 deletions templates/finding.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: []
Expand Down
8 changes: 8 additions & 0 deletions tests/test_check_frontmatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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: []
Expand Down Expand Up @@ -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}
Expand Down
68 changes: 68 additions & 0 deletions tools/check-frontmatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"title",
"status",
"severity",
"cvss_v4",
"confidence",
"category",
"cwe",
Expand All @@ -76,6 +77,12 @@
"updated_at",
]

REQUIRED_CVSS_V4_FIELDS = [
"vector",
"score",
"justification",
]

REQUIRED_VALIDATION_FIELDS = [
"status",
"methods",
Expand All @@ -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<title>.+?)\n(?P<body>.*?)(?=^# |\Z)", re.MULTILINE | re.DOTALL)
CVSS_V4_VECTOR_RE = re.compile(r"^CVSS:4\.0/")

REQUIRED_EXPLOITED_SECTIONS = [
"Root cause analysis",
Expand Down Expand Up @@ -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] = []

Expand Down Expand Up @@ -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}")
Expand Down
Loading