| name | verify-remediation |
| description | Use when the user provides a previous secure-code-audit report and a patched version of the scanned repository, and asks to verify that findings have been resolved. Performs a targeted re-audit of each original finding against the patched code using the same frameworks, criteria, and evidence standards as the original secure-code-audit, then emits a structured verification report. |
| user-invocable | true |
Verify Remediation
Take a previous *-security-audit.{json,md} report and a patched version
of the scanned repository, and verify whether each original finding has
been resolved. Apply the same assessment criteria used by the
secure-code-audit skill — same frameworks (OWASP ASVS v5.0, OWASP K8s
Top 10, CIS K8s Benchmark, DISA STIG, SLSA, OpenSSF Scorecard, PEACH),
same evidence standards (file:line citations, CWE IDs, CVSS scores),
and the same severity thresholds.
This skill does not perform a full re-audit of the entire codebase. It
is a targeted verification: for each finding in the original report, it
checks whether the specific vulnerability has been addressed in the
patched code. It also checks for regressions — new vulnerabilities
introduced by the patch itself.
When to Use
- User provides an audit report and says the repo has been patched
- User asks to "verify remediations", "check fixes", or "confirm findings
are resolved"
- User asks to "re-scan" a repo against a previous report
- User provides a branch, PR, or commit and asks if it fixes the findings
Inputs
$ARGUMENTS contains:
-
The original audit report — one of:
- Path to a
*-security-audit.json file (preferred)
- Path to a
*-security-audit.md file (parsed for finding data)
- A
<product>/<repo> shorthand resolved to
analysis-results/findings/<product>/<repo>/<repo>-security-audit.json
- A
<slug>-findings/<repo> shorthand resolved to
progress-tracker/processed-results/<slug>-findings/<repo>/<repo>-security-audit.json
-
The patched repository — one of:
- A GitHub or GitLab URL (optionally with
@branch or @commit suffix)
- A local path to a checked-out repository
- A GitHub PR or GitLab MR number/URL (the PR/MR head ref is
checked out — the fix does not need to be merged first)
- The keyword
latest — uses the repo's default branch (assumes
patches have been merged)
Merging is never a prerequisite: verification is a direct comparison of
the code at each finding location against the original audit commit, so
an open PR/MR, an unmerged fix branch, or even a local working copy can
all be verified as-is. Only the latest keyword assumes a merge.
Examples:
/verify-remediation findings/openstack-operator/sg-core/sg-core-security-audit.json https://github.com/openstack-k8s-operators/sg-core@fix-branch
/verify-remediation sg-core-findings/sg-core latest
/verify-remediation path/to/report.json /tmp/patched-repo
/verify-remediation openstack-operator/sg-core https://github.com/openstack-k8s-operators/sg-core/pull/42
/verify-remediation openstack-operator/sg-core https://gitlab.example.com/group/sg-core/-/merge_requests/17
- Full-sweep mode —
$ARGUMENTS starts with full-sweep:
answer "what has been fixed thus far?" across the whole findings
tree without re-auditing repos where nothing can have changed. See
Full-Sweep Mode below. Remaining tokens pass through to
build_verify_sweep.py (--include-branches, --no-network,
--force, --roots …), plus two sweep-loop controls handled by the
skill itself: --tier T1[,T2…] limits execution to the named tiers,
--limit N caps the number of repos verified this run.
/verify-remediation full-sweep
/verify-remediation full-sweep --tier T1 --limit 20
Procedure
Phase 1 — Ingest the Original Report
1a. Load the report
Read the original audit report and extract:
- All findings:
id, title, severity, cwes, cvss, locations,
description, remediation, evidence, attack_pattern, category
- Metadata:
repository, commit, date, scope, framework,
harness_version
- PEACH isolation review (if present):
applicable, interfaces
If the report is JSON, parse it directly. If Markdown, extract finding
blocks by parsing the structured tables and sections.
Record the original commit SHA — this is the baseline the findings
were identified against. Every verification verdict must reference both the
original commit and the patched commit.
1b. Categorize findings by verification strategy
Each finding requires a different verification approach depending on its
nature. Categorize each finding:
| Finding Type | Verification Strategy | Examples |
|---|
| Code-level vulnerability | Check that the vulnerable code at locations[].path:lines has been modified to address the root cause described in description | Injection, SSRF, confused deputy, missing auth checks, hardcoded secrets |
| Configuration hardening | Check that the manifest/config at the specified path now meets the required standard | Missing securityContext, overly permissive RBAC, missing NetworkPolicy, PSA labels |
| Supply chain | Check that dependencies/actions are now pinned, signed, or updated | Unpinned GitHub Actions, floating base image tags, vulnerable dependencies in go.mod |
| Missing control | Check that the recommended control now exists | Missing SECURITY.md, missing Dependabot config, missing audit logging |
| PEACH isolation | Check that the boundary hardening gap has been addressed | Missing per-tenant auth, shared credentials, missing network segmentation |
Phase 2 — Obtain the Patched Code
2a. Clone or access the patched repository
Clone with full history (no --depth 1) so that the commit log is
available for attribution in Phase 3. The original audit commit must be
reachable from the patched HEAD.
Based on the input:
-
GitHub URL: Clone the specified branch/commit
git clone --branch <branch> <url> /tmp/verify-<repo>
If a commit SHA is specified:
git clone <url> /tmp/verify-<repo>
cd /tmp/verify-<repo> && git fetch origin <sha> && git checkout <sha>
-
GitHub PR URL/number: Clone and check out the PR head
git clone <url> /tmp/verify-<repo>
cd /tmp/verify-<repo> && git fetch origin pull/<number>/head:pr-<number> && git checkout pr-<number>
-
GitLab MR URL/number: Clone and check out the MR head (GitLab
exposes MR heads under a different refspec than GitHub PRs)
git clone <url> /tmp/verify-<repo>
cd /tmp/verify-<repo> && git fetch origin merge-requests/<iid>/head:mr-<iid> && git checkout mr-<iid>
If the MR ref is not fetchable (some instances restrict it), fall back
to fetching the MR's source branch, obtainable via the GitLab API or
the MR page.
-
Local path: Use directly (verify it's a git repo with git rev-parse)
-
latest: Clone the default branch of the repo URL from the original
report's metadata.repository
If the clone is prohibitively large (>2GB), use --filter=blob:none for a
treeless clone — this still provides full commit history and allows
on-demand blob fetching:
git clone --filter=blob:none --branch <branch> <url> /tmp/verify-<repo>
2b. Record the patched commit
cd /tmp/verify-<repo> && git rev-parse HEAD
Store the full SHA — this goes into the verification report metadata.
2c. Generate the diff
Compute the diff between the original commit and the patched commit to
understand what changed:
git log --oneline <original-commit>..<patched-commit> 2>/dev/null || echo "Commits not in same history"
git diff <original-commit>..<patched-commit> --stat
If the original commit is not in the patched repo's history (force-push,
rebase, or different fork), fall back to direct file inspection at each
finding location. Note in the report that commit attribution (Phase 3)
was not possible.
Phase 3 — Attribute Remediations to Commits
Walk the commit history between the original audit date and the patched
HEAD to identify which commit(s) addressed each finding. This gives
reviewers a direct link from finding → fix commit → author, enabling
them to review the actual patch that resolved the issue.
3a. Build the commit log since the audit
Extract the audit date from the original report's metadata.date field
(YYYY-MM-DD format). If metadata.commit is available and reachable,
use it as the range start; otherwise use the date.
git -C /tmp/verify-<repo> log --format='%H %aI %an <%ae> %s' \
<original-commit>..<patched-commit> -- 2>/dev/null
git -C /tmp/verify-<repo> log --format='%H %aI %an <%ae> %s' \
--since="<audit-date>" -- 2>/dev/null
Store the full commit list (SHA, date, author, subject) for the report.
3b. Map findings to commits via file paths
For each finding, extract all file paths from locations[].path. Then
query the commit log for commits that touched those paths:
git -C /tmp/verify-<repo> log --format='%H %aI %s' \
<original-commit>..<patched-commit> -- <path1> <path2> ...
This produces a candidate list of commits that may have addressed the
finding. Multiple findings may map to the same commit (a single PR that
fixes several issues), and a single finding may map to multiple commits
(an iterative fix across several PRs).
3c. Narrow candidates by content analysis
For each candidate commit, inspect the diff to determine whether it
actually addresses the finding's root cause:
git -C /tmp/verify-<repo> show --stat <commit-sha>
git -C /tmp/verify-<repo> diff <commit-sha>^..<commit-sha> -- <path>
Evaluate the diff against the finding's description and remediation
guidance. A commit is a remediation commit for a finding if it:
- Modifies code at or near the finding's
locations[].path:lines
- The change addresses the root cause described in the finding's
description (not just a cosmetic change in the same file)
- The change is consistent with the finding's
remediation guidance
(or an equivalent alternative approach)
A commit that merely reformats, adds comments, or fixes an unrelated bug
in the same file is not a remediation commit.
3d. Record attribution data
For each finding, record:
-
remediation_commits: Array of commit objects, each with:
sha: Full 40-char commit SHA
short_sha: 7-char abbreviated SHA
date: ISO 8601 commit date
author: Commit author name and email
subject: Commit subject line
pr_number: Associated PR/MR number if identifiable from the commit
message (GitHub: Merge pull request #42, (#42) suffix;
GitLab: See merge request <group>/<repo>!42, !42)
relevance: direct (modifies the finding location and addresses
root cause), supporting (related change that contributes to the
fix but doesn't directly modify the finding location), or partial
(addresses some but not all locations)
-
unattributed: Boolean. true if no commit could be identified
that addresses this finding (the finding may be unresolved, or the
fix may predate the audit, or the commit history was unavailable).
To extract PR/MR numbers from commit messages:
git -C /tmp/verify-<repo> log --format='%H %s%n%b' <original-commit>..<patched-commit> -- <path> \
| grep -oP '(#\d+|\(#\d+\)|Merge pull request #\d+|!\d+|See merge request [^ ]+!\d+)'
3e. Build the commit timeline
Construct a chronological timeline of all remediation commits across all
findings. This becomes the commit_timeline section in the report —
a single view of the remediation effort:
<date> <short-sha> <author> <subject> → Addresses: FIND-001, FIND-003
<date> <short-sha> <author> <subject> → Addresses: FIND-002
<date> <short-sha> <author> <subject> → Addresses: FIND-005, FIND-006, FIND-007
Group commits that are part of the same PR together. This shows whether
remediations were batched by finding, by file, or by area.
3f. When attribution is not possible
If the original commit is not reachable from the patched HEAD (different
fork, force-pushed history, squash-merged PRs), commit-level attribution
may be partial or impossible. In this case:
Phase 4 — Verify Each Finding
For each finding in the original report, perform a targeted verification.
Apply the same framework criteria the secure-code-audit skill uses
(see the full framework reference in that skill's SKILL.md).
4a. Inspect the patched code at each finding location
For each locations[] entry in the finding:
- Check if the file still exists at the same path
- Read the code at the specified lines (and surrounding context —
±20 lines minimum)
- Compare against the original evidence in
evidence[].code
- Assess whether the root cause described in
description has been
addressed
4b. Apply framework-specific verification
Based on the finding's category, cwes, and framework references,
apply the same checks the original audit would have:
OWASP ASVS findings: Verify the specific ASVS requirement is now met.
For example, if the finding cited CWE-918 (SSRF), verify that URL inputs
are now validated/allowlisted.
OWASP K8s Top 10 findings (K01–K10): Re-check the specific K-ID
criteria against the patched manifests. For example:
- K01 (Insecure Workload Config): verify securityContext is now set
with the required fields
- K02 (Overly Permissive RBAC): verify ClusterRole/Role verbs are now
scoped appropriately
- K03 (Secrets Management): verify secrets are no longer logged/exposed
CIS / DISA STIG findings: Re-check the specific benchmark section
or STIG finding ID against the patched configuration.
SLSA / OpenSSF Scorecard findings: Verify dependency pinning,
branch protection, CI security improvements.
PEACH findings: Re-check the specific PEACH parameter (P/E/A/C/H)
against the patched isolation boundary.
4c. Determine the verdict
For each finding, assign one of these verdicts:
| Verdict | Criteria |
|---|
resolved | The root cause has been fully addressed. The vulnerable code/config has been changed in a way that eliminates the finding. The fix is correct and complete. |
partially_resolved | The fix addresses some aspects of the finding but not all. For example: one of three vulnerable locations was fixed, or the fix reduces severity but doesn't eliminate the root cause. |
unresolved | The finding remains present in the patched code. The vulnerable code/config is unchanged or the change does not address the root cause. |
new_approach | The finding is no longer applicable because the relevant code/feature was removed, refactored into a different pattern, or replaced entirely. Verify the new approach doesn't introduce equivalent vulnerabilities. |
regression | The fix introduced a new vulnerability related to the same area. Document the new issue with the same rigor as an original finding. |
false_positive | The owning team disputed the finding and re-analysis confirms the original finding was incorrect (the flagged behavior is not exploitable or was misread). Requires disposition_rationale naming who made the determination and the evidence. Do NOT use this to launder unresolved findings — if re-analysis confirms the finding, it stays unresolved. |
risk_accepted | The finding is real but the owning team formally accepted the risk (documented decision, ticket, or MR discussion). The code is unchanged by design. Requires disposition_rationale linking to the acceptance record. |
false_positive and risk_accepted exist so that team dispositions
don't get force-fitted into unresolved, which reads as "still broken"
in portfolio roll-ups. Both require independent re-analysis — a team's
claim alone is not sufficient evidence.
4d. Evidence requirements
Every verdict must include:
- The patched code at the finding location (or confirmation the
file/section was removed)
- Explanation of how the change addresses (or fails to address) the
root cause
- Framework reference — cite the same CWE, K-ID, CIS section, STIG
ID, SLSA level, or PEACH parameter as the original finding
- Residual risk — if
partially_resolved or new_approach, describe
any remaining risk
- Disposition rationale — if
false_positive or risk_accepted,
record who made the determination, when, and the supporting evidence
(triage record, MR discussion, risk-acceptance ticket)
Phase 5 — Check for Regressions
Beyond verifying each original finding, scan the diff for new
vulnerabilities introduced by the patch:
5a. Scope the regression scan
The scan target is the remediation work, not everything that landed since
the audit. Choose the scope explicitly and record the decision in the
report's notes field:
- Default: review every file modified in the diff from Phase 2c.
This is correct when verifying a fix branch, PR, or MR, where the diff
is the remediation.
latest mode / large diffs: when the diff spans more than ~50
files or ~5,000 changed lines (typical when months of unrelated merges
landed since the audit), restrict the scan to files touched by the
remediation commits attributed in Phase 3. If attribution failed
(unattributed findings), fall back to files matching the original
findings' locations[].path.
- Never silently truncate: if any modified files were excluded from the
regression scan, state in the report which scope was used and why.
5b. Diff-scoped analysis
Review every file in the chosen scope for:
- New instances of the same vulnerability classes found in the original
report (e.g., if the original report found hardcoded secrets, check if
the patch introduced new ones)
- Security anti-patterns in the fix itself (e.g., replacing one injection
sink with another, adding an allowlist with a bypass)
- New RBAC grants, new container capabilities, new network exposure
- Newly added dependencies without pinning
5c. Classify regressions
Regressions are findings that did not exist at the original commit but
are present at the patched commit. Report them with the same finding
structure and evidence standards as the original audit (id, title,
severity, cwes, cvss, locations, description, remediation, evidence) —
including a CVSS v3.1 score, exactly as secure-code-audit requires.
Use finding IDs in the format:
{REPO_SLUG}-{PATCHED_SHORTSHA}-REG-{NNN}
Phase 6 — Produce the Verification Report
6a. Report file naming
<repo-name>-remediation-verification.json
<repo-name>-remediation-verification.md
Default location: the same directory as the original audit report (e.g.
analysis-results/findings/<product>/<repo>/), so the verification sits
next to the findings it verifies. Only place it elsewhere when the user
explicitly specifies an output directory.
6b. JSON report structure
The report must validate against schema/verification.schema.json
(see Phase 7a).
{
"title": "<repo-name> Remediation Verification",
"metadata": {
"date": "YYYY-MM-DD",
"original_report": "<path-to-original-report>",
"original_commit": "<40-char SHA>",
"patched_commit": "<40-char SHA>",
"patched_ref": "<branch, pull/NN/head, merge-requests/NN/head, or default branch>",
"repository": "<GitHub or GitLab URL>",
"scope": "Targeted verification of N findings from the original audit",
"framework": "Same as original: OWASP ASVS v5.0, OWASP K8s Top 10, ...",
"auditor": "ai-security-harness",
"harness_version": "<from VERSION file>-<short SHA>"
},
"summary": {
"total_findings": <N>,
"by_verdict": {
"resolved": <N>,
"partially_resolved": <N>,
"unresolved": <N>,
"new_approach": <N>,
"regression": <N>,
"false_positive": <N>,
"risk_accepted": <N>
},
"regressions": <count of entries in regressions[]>,
"prose": "<Executive summary of the verification results>"
},
"verified_findings": [
{
"original_id": "<finding ID from original report>",
"original_title": "<finding title>",
"original_severity": "<severity>",
"verdict": "resolved|partially_resolved|unresolved|new_approach|regression|false_positive|risk_accepted",
"remediation_commits": [
{
"sha": "<40-char commit SHA>",
"short_sha": "<7-char SHA>",
"date": "<ISO 8601>",
"author": "<name> <<email>>",
"subject": "<commit subject line>",
"pr_number": <PR/MR number or null>,
"relevance": "direct|supporting|partial"
}
],
"unattributed": false,
"evidence": {
"original_code": "<code at finding location in original commit>",
"patched_code": "<code at finding location in patched commit>",
"explanation": "<how the change addresses or fails to address the root cause>",
"framework_reference": "<CWE, K-ID, CIS, STIG, SLSA, PEACH ref>"
},
"disposition_rationale": "<who determined false_positive/risk_accepted and why, null otherwise>",
"residual_risk": "<description if partially_resolved or new_approach, null otherwise>",
"residual_severity": "<recalculated severity if partially_resolved, null otherwise>"
}
],
"regressions": [
{
"id": "<REPO_SLUG>-<PATCHED_SHA7>-REG-001",
"title": "<description>",
"severity": "<severity>",
"cwes": ["CWE-NNN"],
"cvss": {"score": <0.0-10.0>, "vector": "CVSS:3.1/..."},
"locations": [{"path": "...", "lines": "..."}],
"description": "<detailed description>",
"remediation": "<how to fix the regression>",
"evidence": [{"code": "...", "language": "..."}],
"introduced_by": "<commit SHA or PR/MR that introduced this>"
}
],
"commit_timeline": [
{
"sha": "<7-char SHA>",
"full_sha": "<40-char SHA>",
"date": "<ISO 8601>",
"author": "<name> <<email>>",
"subject": "<commit subject line>",
"pr_number": <number or null>,
"addresses": ["<finding-id-1>", "<finding-id-2>"]
}
],
"recommendations": [
"<actionable next step for any unresolved or partially resolved findings>"
],
"notes": "<regression-scan scope decision, attribution caveats (squash merges, force-pushed history), and anything else a reviewer should know>"
}
6c. Markdown rendering
After producing the JSON, render a human-readable Markdown version:
# Remediation Verification: <repo-name>
| Field | Value |
|-------|-------|
| **Date** | YYYY-MM-DD |
| **Original Report** | <path> |
| **Original Commit** | <SHA> |
| **Patched Commit** | <SHA> |
| **Repository** | <URL> |
## Summary
| Verdict | Count |
|---------|-------|
| Resolved | N |
| Partially Resolved | N |
| Unresolved | N |
| New Approach | N |
| False Positive | N |
| Risk Accepted | N |
| Regressions | N |
<Executive summary prose>
## Commit Timeline
Commits since the original audit that addressed findings, in
chronological order.
| Date | Commit | Author | Subject | Addresses |
|------|--------|--------|---------|-----------|
| 2026-06-15 | `a1b2c3d` | Jane Dev | Pin GitHub Actions to SHA (#42) | FIND-001, FIND-004 |
| 2026-06-18 | `e4f5g6h` | John Eng | Scope ClusterRole RBAC verbs (#45) | FIND-002 |
| 2026-06-20 | `i7j8k9l` | Jane Dev | Add securityContext to operator pod (#47) | FIND-003, FIND-005 |
## Verification Details
### FIND-001: <title> — ✅ Resolved
**Original Severity**: High | **CWEs**: CWE-829
**Location**: `.github/workflows/build.yaml:27-54`
**Remediation Commit**: [`a1b2c3d`](https://github.com/<org>/<repo>/commit/a1b2c3d) — Pin GitHub Actions to SHA (#42) — Jane Dev, 2026-06-15
**Original Code:**
```yaml
- uses: actions/checkout@v4
Patched Code:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
Explanation: All GitHub Actions are now pinned to immutable commit
SHAs with version comments. This eliminates the tag-rewrite supply chain
risk (CWE-829).
FIND-003: — ⚠️ Partially Resolved
...
Regressions
REG-001:
...
Recommendations
- ...
### Phase 7 — Validate and Deliver
#### 7a. Validate the report
Run the harness validator against the verification schema (from the
workspace root):
```bash
python ai-security-harness/scripts/validate_report.py \
--schema verification.schema.json \
<path-to>/<repo-name>-remediation-verification.json
The validator enforces the report structure plus the consistency
cross-checks (verdict counts match actual verdicts, every finding has a
verdict, attribution consistency, chronological commit_timeline,
addresses referencing real findings, regression ID format). Fix every
reported error and re-run until it passes; treat warnings as review
prompts, not noise.
A few checks the validator cannot perform — verify them manually:
- Evidence code blocks are actual code from the repo, not fabricated
(spot-check against the clone)
- All commit SHAs in
remediation_commits and commit_timeline exist in
the repo history: git -C /tmp/verify-<repo> cat-file -e <sha>^{commit}
- All
original_id values are real finding IDs from the original report
7b. Present results
Present a summary table to the user:
## Verification Complete: <repo-name>
| Metric | Count |
|--------|-------|
| Total findings verified | N |
| ✅ Resolved | N |
| ⚠️ Partially resolved | N |
| ❌ Unresolved | N |
| 🔄 New approach | N |
| 🚫 False positive | N |
| 📋 Risk accepted | N |
| 🆕 Regressions found | N |
**Unresolved findings** (require further action):
- FIND-003: <title> (High)
- FIND-007: <title> (Medium)
**Regressions** (new issues introduced by the patch):
- REG-001: <title> (Medium)
After delivering, suggest feeding the verification report into the
findings ledger so the cumulative status reflects it:
/track-findings <product>/<repo> <path-to>/<repo>-remediation-verification.json
7c. Clean up
rm -rf /tmp/verify-<repo>
Full-Sweep Mode
Campaign-scale "what has been fixed thus far?" without campaign-scale
cost. The expensive part of verification is the per-finding LLM
re-audit; the sweep spends it only where a fix can possibly exist.
Step 1 — Build the worklist (deterministic, no subagent)
python3 "$SKILL_DIR/verify-remediation/build_verify_sweep.py" \
[--results-root <analysis-results>] [--include-branches] \
[--no-network] [--force]
The script walks every canonical (non-symlink) *-security-audit.json
under findings/, and for each repo compares the pinned audit SHA
against the live HEAD (git ls-remote, parallel), reads the sibling
disposition ledger for remediation-claim events
(fix_in_progress/resolved/partially_resolved), and checks for an
existing *-remediation-verification.json. It writes
findings/_manifest/verify-sweep-worklist.{json,md} with a tiered
queue:
| Tier | Meaning | Why this order |
|---|
| T1 | ledger remediation signal | someone claims work happened — verify the claim first |
| T2 | HEAD drifted + ≥1 critical | highest risk retired first |
| T3 | HEAD drifted + ≥1 high | |
| T4 | HEAD drifted, other | |
Repos with unchanged HEAD are skipped (nothing changed → nothing
fixed), as are already-verified repos (resumability: re-running the
sweep after interruption re-queues only what is still unverified) and
unreachable remotes (listed so VPN-only GitLab repos aren't silently
lost). Release-branch (__release-*) reports are excluded unless
--include-branches. The script routes only — it never authors a
verification verdict.
Step 2 — Confirm scope before spending tokens
Present the tier table and skip counts to the user and get confirmation
of which tiers / how many repos to verify this run (--tier / --limit
pre-answer this). Each queued repo costs roughly one targeted re-audit
of its findings — quote the queue's total findings count as the scope
estimate.
Step 3 — Verify, ingest, checkpoint — one repo at a time
For each worklist entry, in order:
- Run the standard procedure (Phases 1–7) with the original report =
entry.report and the patched repository =
entry.repository@entry.head_sha (pin the HEAD the sweep observed,
so results stay reproducible even if the repo moves again mid-sweep).
Write the verification report beside the audit report in the
findings tree.
- Feed it straight into the ledger:
/track-findings <product>/<repo> <repo>-remediation-verification.json
— verification reports are class-1 evidence, the only sanctioned path
to resolution: resolved.
- Continue to the next entry. The verification report on disk IS the
checkpoint — an interrupted sweep resumes by re-running Step 1.
Step 4 — Sweep summary
After the run (or the --limit cap), report: repos verified this run,
findings resolved / partially resolved / unresolved / regressions,
tiers remaining, and suggest rebuilding the dashboards
(/executive-summary-findings, /findings-trends) so the remediation
sections reflect the new class-1 evidence.
Framework Reference
This skill applies the same frameworks as secure-code-audit. The
authoritative framework documentation is in that skill's SKILL.md at
harnessing/secure-code-audit/SKILL.md. When verifying findings,
always refer back to the specific framework section that applies.
Quick Framework Cross-Reference
| Finding Category | Framework | Key Checks for Verification |
|---|
| Insecure workload config | K01 | securityContext, runAsNonRoot, readOnlyRootFilesystem, capabilities, resource limits |
| Overly permissive RBAC | K02 | ClusterRole/Role verbs scoped, no wildcards, no cluster-admin equivalent |
| Secrets management | K03 | No secrets in logs/env/source, volume mounts used, encryption at rest |
| Missing policy enforcement | K04 | PSA labels, admission webhooks with failurePolicy: Fail |
| Network segmentation | K05 | NetworkPolicy present, no unnecessary exposure, egress restrictions |
| Exposed components | K06 | No unauth webhooks/dashboards/debug endpoints |
| Vulnerable components | K07 | Pinned images (digest), updated deps, SBOM present |
| Cloud lateral movement | K08 | Scoped IAM roles, no overly broad cloud permissions |
| Broken auth | K09 | automountServiceAccountToken: false where unused, dedicated SAs |
| Logging/monitoring | K10 | Structured logging, no sensitive data in logs, probes present |
| Supply chain | SLSA/Scorecard | SHA-pinned actions, signed images, SECURITY.md, Dependabot/Renovate |
| Tenant isolation | PEACH | Per-tenant auth/encryption/privileges/connectivity/hygiene |
| Application security | ASVS | Input validation, output encoding, auth, session mgmt, cryptography |
| Config hardening | CIS/STIG | API server flags, etcd TLS, kubelet hardening, file permissions |
Verdict Decision Tree
For each finding:
│
├─ Team formally disputed or accepted the finding?
│ ├─ Re-analysis confirms the finding was wrong → "false_positive"
│ │ (document disposition_rationale)
│ ├─ Finding real, risk formally accepted → "risk_accepted"
│ │ (document disposition_rationale)
│ └─ Re-analysis confirms the finding is real
│ and unaccepted → continue below
│
├─ File/section at location still exists?
│ ├─ No → Was the feature removed/refactored?
│ │ ├─ Yes → Check new approach for equivalent vulns → "new_approach"
│ │ └─ No → "unresolved" (file deleted but vuln may have moved)
│ │
│ └─ Yes → Code at location changed?
│ ├─ No → "unresolved"
│ └─ Yes → Change addresses root cause per framework criteria?
│ ├─ Fully → "resolved"
│ ├─ Partially → "partially_resolved" (document residual)
│ └─ No → "unresolved" (cosmetic change only)
│
└─ Check diff for new vulns in same area → any found? → "regression"
Gotchas
Grouped by where they apply in the procedure. Treat this as the
pre-delivery checklist: before presenting results, confirm none of these
were tripped.
Verdict integrity (Phase 4)
-
Don't trust the diff alone. A file may have been modified without
fixing the finding (e.g., reformatting, adding comments, fixing an
unrelated bug in the same file). Always verify the root cause is
addressed, not just that the lines changed.
-
Moved code is not fixed code. If vulnerable code was moved to a
different file or function, it's still unresolved unless the move
also addressed the vulnerability.
-
Partial fixes may reduce severity. If a fix addresses 2 of 3
vulnerable locations, mark as partially_resolved and note the
residual severity (which may be lower than the original).
-
New approach requires its own assessment. If the patched code uses
an entirely different pattern, verify the new pattern against the same
framework criteria. A refactor that replaces one vulnerability with
another is a regression, not new_approach.
-
Check for compensating controls. Sometimes a finding is resolved
not by changing the flagged code but by adding a compensating control
(e.g., adding a ValidatingAdmissionPolicy instead of modifying the
operator's RBAC). This is valid if the compensating control fully
mitigates the risk — mark as resolved with an explanation.
Evidence standards by framework (Phases 4b/4d)
-
Supply chain findings require exact verification. For dependency
pinning findings, verify the exact pin format (commit SHA, not tag).
For GitHub Actions, actions/checkout@v4 → actions/checkout@v4.2.2
is NOT sufficient — it must be pinned to the full commit SHA.
-
RBAC findings require complete verification. If the finding cited
wildcard verbs in a ClusterRole, verify that every wildcard has been
scoped, not just the one mentioned in the evidence block.
Git history & attribution (Phases 2–3)
-
Squash merges obscure individual commits. Many repos squash-merge
PRs/MRs, producing a single commit per PR. In this case, the remediation
commit is the squash commit and the PR/MR number (from the commit
message) is the more useful reference. Always extract pr_number when
present.
-
Verifying an unmerged PR/MR produces SHAs that may go stale. If the
repo later squash-merges or rebases the branch, the remediation_commits
SHAs in the report will not exist on the default branch. The pr_number
is the durable reference — record it, and note metadata.patched_ref
so the report is traceable to the exact ref that was verified.
-
GitHub and GitLab expose merge heads differently. GitHub PRs:
refs/pull/<number>/head. GitLab MRs: refs/merge-requests/<iid>/head.
Using the wrong refspec fails silently with "couldn't find remote ref" —
match the refspec to the forge before falling back to the source branch.
-
Rebased or force-pushed history breaks commit ranges. If
git log <original>..<patched> fails, the original commit may have
been rewritten. Fall back to date-based log (--since) and note in
the report that commit-range attribution was not possible.
-
A single commit may fix multiple findings. This is common when
fixes are batched into one PR. The commit should appear in each
finding's remediation_commits and once in commit_timeline with
all addressed finding IDs listed.
-
An unresolved finding should still have attribution attempted.
If commits touched the finding's files but didn't fix the root cause,
record them with relevance: partial — this helps reviewers
understand that the area was touched but the specific issue wasn't
addressed.
Scope discipline
-
Don't re-audit the entire repo. This skill verifies specific
findings, not the full codebase. If the user wants a comprehensive
re-audit, use the secure-code-audit skill instead.
-
The original report is the source of truth for what to verify.
If a finding was in the original report, it must have a verdict in the
verification report — even if it's unresolved. Never silently skip
findings.