| name | secure-code-audit |
| description | Use when the user asks to perform a security audit, security review, vulnerability assessment, or code analysis of a GitHub repository, operator, or batch of repositories using OWASP ASVS, OWASP Kubernetes Top 10, CIS Kubernetes Benchmark, DISA STIG, SLSA, OpenSSF Scorecard, or PEACH tenant-isolation frameworks. |
Secure Code Audit
Perform a comprehensive security assessment of one or more codebases using industry-standard frameworks: OWASP ASVS v5.0, OWASP Kubernetes Top 10 (2025), CIS Kubernetes Benchmark v2.0, DISA STIG for Kubernetes V2R6, SLSA v1.2, OpenSSF Scorecard, and the PEACH tenant-isolation framework.
Input
$ARGUMENTS is one of the following:
-
A single GitHub repository URL (e.g. https://github.com/stackrox/stackrox).
-
A payload analysis CSV file produced by the list-payload-repositories skill or the operator-catalog scripts. The CSV uses the following schema:
GitHub Repository, GitHub URL, Organization, Repo Name, Branch, Category, Payload Image Key(s), Image Count
- Use the GitHub URL column as the repository source.
- Use the Branch column to determine the ref to analyze. If the branch value starts with
commit:, treat the remainder as a commit SHA. Otherwise treat it as a branch name.
- Use the Category column for prioritization (see below).
-
A payload analysis Markdown file (.md) produced by the same tooling. Parse the categorized repository tables to extract GitHub URLs and branch refs.
Repository Access
Prefer the GitHub MCP tools and fetch to read source code remotely whenever possible. This avoids cloning large repositories and is faster for targeted file inspection.
When a full local checkout is required for deep analysis (e.g. running static analysis tools, tracing cross-file data flows, or inspecting build configurations):
git clone --depth 1 --branch <branch-or-ref> <github-url> <local-path>
If the ref is a commit SHA rather than a branch name, clone with --depth 1 and then git fetch origin <sha> && git checkout <sha>.
Always analyze the branch or commit ref specified in the input data, not the repository default branch. The ref represents the exact code shipped in the operator version being assessed.
Deduplication
Before beginning analysis, deduplicate the input list by (GitHub URL, Branch) pair. If the same repository at the same ref appears across multiple operator versions or payload images:
- Analyze it once.
- Place the canonical report in the first product's output directory.
- Create symbolic links from every other product directory that shares the same repo+ref back to the canonical report.
This avoids redundant work when repos like openshift/kubernetes or stolostron/multicluster-observability-operator appear across many operator versions.
Prioritization
Process repositories in the following order:
- Core OpenShift platform — repositories from the OCP release payload (ClusterOperators, core platform machinery, installer, CAPI providers, HyperShift, operating system images).
- Operators — repositories from the OLM operator catalog, ordered by image count descending (higher image count = larger attack surface).
- Shared Infrastructure — kube-rbac-proxy, kube-state-metrics, configmap-reloader, and similar shared components.
- Base images and dependencies — RHEL base images, language runtimes, databases.
Within each tier, process repositories with the most payload images first.
Security Assessment Framework
OWASP ASVS (Application Security)
Using the OWASP ASVS v5.0 (CSV reference) as the primary application security framework, perform a comprehensive security review covering:
- Insecure coding practices
- Improper input sanitization
- SSRF / CSRF
- Confused deputy vulnerabilities
- SQL injection and other injection classes
- Credential leaks and secrets in source
- Vulnerable dependencies
OWASP Kubernetes Top 10 (Kubernetes & Operator Security)
Using the OWASP Kubernetes Top 10 (2025) as the Kubernetes-specific security framework, assess every Kubernetes operator or controller repository against the following risk categories:
| ID | Risk Category | What to Check |
|---|
| K01 | Insecure Workload Configurations | Containers running as root, missing readOnlyRootFilesystem, absent resource limits/requests, privilege escalation not disabled (allowPrivilegeEscalation: true), unnecessary capabilities not dropped, hostNetwork/hostPID/hostIPC enabled, missing securityContext in pod and container specs |
| K02 | Overly Permissive Authorization | Wildcard (*) verbs or resources in ClusterRole/Role manifests, cluster-admin bindings, RBAC grants broader than required for the operator's function, controllers that create or modify RBAC resources, impersonation usage, token request/projection without scoping |
| K03 | Secrets Management Failures | Secrets logged or printed to stdout/stderr, secrets passed as environment variables instead of volume mounts, hardcoded credentials or tokens in source, missing encryption-at-rest configuration, secrets not scoped to namespaces |
| K04 | Lack of Cluster Level Policy Enforcement | Missing Pod Security Admission (PSA) labels on namespaces, no OPA/Gatekeeper or Kyverno policies referenced, admission webhooks without failurePolicy: Fail, no resource quota or limit range enforcement |
| K05 | Missing Network Segmentation Controls | Missing NetworkPolicy for operator pods, unnecessary exposure of metrics/healthz/pprof endpoints, operator services exposed as LoadBalancer or NodePort without justification, no egress restrictions |
| K06 | Overly Exposed Kubernetes Components | Unauthenticated webhook endpoints, exposed dashboard or debug endpoints, API server flags that weaken security (e.g. --anonymous-auth=true), exposed etcd ports |
| K07 | Misconfigured and Vulnerable Cluster Components | Unpinned base images (tag instead of digest), known CVEs in vendored dependencies (go.sum/go.mod), outdated Kubernetes client libraries, missing SBOM or Dockerfile best practices, unsigned container images |
| K08 | Cluster to Cloud Lateral Movement | IRSA/OIDC misconfigurations, overly broad cloud IAM roles referenced in service accounts, pod identity webhook configurations that grant excessive cloud permissions, credentials for cloud APIs stored insecurely |
| K09 | Broken Authentication Mechanisms | Service accounts with auto-mounted tokens that are unused, missing automountServiceAccountToken: false where tokens are not needed, default service account used instead of dedicated accounts, missing authentication on operator-exposed APIs or webhooks |
| K10 | Inadequate Logging and Monitoring | Missing structured logging, sensitive data in log output, no audit logging configuration, missing health/readiness/liveness probes, no metrics exposure for monitoring, leader election without proper observability |
For each K01–K10 finding, reference the specific OWASP Kubernetes Top 10 ID alongside the CWE and CVSS score.
CIS Kubernetes Benchmark & DISA STIG (Configuration Hardening)
Using the CIS Kubernetes Benchmark v2.0 and the DISA STIG for Kubernetes V2R6 as configuration hardening references, assess any manifests, Helm charts, Kustomize overlays, or deployment configurations shipped in the repository against:
| Area | What to Check |
|---|
| API Server Configuration | Flags that weaken security (--anonymous-auth, --insecure-port, --insecure-bind-address), missing admission controllers, audit log configuration |
| etcd Security | Peer and client TLS authentication, encryption at rest, access controls |
| Kubelet Hardening | Anonymous auth disabled, read-only port disabled, certificate rotation, protectKernelDefaults |
| TLS / Certificate Management | Minimum TLS 1.2 enforced, certificate validity and rotation, hardcoded certificates |
| File Permissions | Manifest files, kubeconfig files, and PKI material with overly permissive modes |
| Pod Security Admission | PSA enforcement mode (enforce vs warn vs audit), restricted vs baseline vs privileged profile selection |
For operator repositories, focus on manifests and configuration the operator deploys or manages — not cluster-level settings the operator cannot control. Reference CIS Benchmark section numbers (e.g. CIS 1.2.1) or DISA STIG finding IDs (e.g. V-242381) where applicable.
SLSA & OpenSSF Scorecard (Supply Chain Integrity)
Using SLSA v1.2 and OpenSSF Scorecard as supply chain assessment frameworks, evaluate each repository's build and release integrity:
| Area | What to Check |
|---|
| SLSA Build Provenance | Whether the project generates signed provenance attestations, uses a hardened build platform, and isolates builds (SLSA L1–L3) |
| Image Signing | Container images signed with cosign/Sigstore, signature verification in deployment manifests or admission policies |
| Dependency Pinning | All dependencies pinned by hash/digest (Go modules, container base images, GitHub Actions), no floating tags or latest references |
| Branch Protection | Required reviews, status checks, signed commits, no force-push to default branch |
| CI/CD Security | No dangerous workflow patterns (e.g. pull_request_target with checkout), token permissions scoped to minimum, no secrets in workflow logs |
| SBOM | Software Bill of Materials generated and published with releases |
| Vulnerability Disclosure | SECURITY.md present with clear reporting instructions, coordinated disclosure process |
| Scorecard Checks | If an OpenSSF Scorecard is available for the repo (via api.securityscorecards.dev), include the overall score and flag any checks scoring below 5/10 |
Reference SLSA levels (e.g. SLSA L1, SLSA L2) and Scorecard check names (e.g. Pinned-Dependencies, Branch-Protection) in findings.
PEACH (Multi-Tenant Isolation)
PEACH is Wiz Research's framework for modeling and hardening tenant isolation in cloud applications. The acronym names the five hardening parameters a security boundary must satisfy: Privilege hardening, Encryption hardening, Authentication hardening, Connectivity hardening, and Hygiene.
Use it here as the tenant-isolation reference to assess the strength of the security boundaries between tenants in any multi-tenant component. The root cause of most cross-tenant vulnerabilities (ChaosDB, ExtraReplica, Hell's Keychain) is an improperly implemented security boundary compounded by an otherwise-harmless bug in a customer-facing interface — PEACH models both.
Attribution & license. The PEACH framework — including the interface-complexity table, security-boundary taxonomy, P.E.A.C.H. hardening parameters, and remediation levers reproduced in this section — is © 2022 Wiz, Inc. and is licensed under Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0). This section is a derivative work: the upstream text has been adapted for source-code audit of the Red Hat Hybrid Platforms portfolio (Kubernetes/OpenShift-specific detection cues, report-schema mapping). Per the ShareAlike term, this adapted section is redistributed under the same CC BY-NC-SA 4.0 license.
Sources: PEACH whitepaper v1.1 (PDF) · peach.wiz.io · wiz-sec-public/peach-framework on GitHub · Wiz blog — Introducing PEACH · ChaosDB case study.
Apply this section only when the repository is multi-tenant, i.e. one deployment of the code serves more than one customer, namespace, cluster, or trust domain. Typical triggers in this portfolio:
- Managed / hosted services (ROSA, ARO, OSD, HCP/HyperShift, ACS Cloud Service, Insights, OCM, Backplane) where one control plane serves many customer clusters.
- Operators or controllers that reconcile resources across many namespaces on behalf of distinct teams (multi-tenant mode,
watchNamespaces: "", AllNamespaces install mode).
- Admission webhooks, aggregated API servers, proxies, or gateways that broker requests for multiple tenants.
- Shared data planes: observability backends, registries, queues, or databases that co-locate data from multiple tenants.
Always emit a top-level peach_isolation_review object in the report. If the repository is single-tenant (one deployment == one customer/trust domain), set {"applicable": false, "rationale": "<why>"} and skip the rest of this section. If multi-tenant, set "applicable": true and populate interfaces[] from Step 1 below.
Step 1 — Isolation Review
For each customer-facing interface the component exposes (API endpoint, CRD, webhook, CLI, DB client, message consumer, file/image ingester), determine four things:
| # | Question | How to Answer From Source |
|---|
| 1 | Interface complexity — how much attacker-controlled input does the interface accept and how much does it do with it? | Classify per the table below. Higher complexity == higher prior probability of an escape bug and therefore requires stronger boundaries behind it. |
| 2 | Shared or duplicated? — does every tenant hit the same running instance of this interface, or does each tenant get its own copy? | Look at Deployment/StatefulSet cardinality, HyperShift HostedCluster vs management-cluster placement, per-namespace vs cluster-scoped install, sharding/routing code. Shared interfaces put the boundary inside the process; duplicated interfaces push it out to the infrastructure. |
| 3 | Security boundary type — what actually separates tenant A from tenant B behind this interface? | Identify which of the six boundary types below is doing the work. Weak boundary + high-complexity shared interface is the highest-risk combination. |
| 4 | Boundary strength — how well is that boundary hardened? | Score the boundary against the five P.E.A.C.H. hardening parameters below. |
Interface complexity reference:
| Interface Type | Typical Input | Typical Process | Complexity |
|---|
| Arbitrary code execution environment (e.g. user-supplied containers, webhooks, scripts, functions) | Arbitrary | Execution | High |
| Database / query client (SQL, PromQL, LogQL, search DSL) | Query language | Database operation | High |
| Arbitrary file scanner / image parser | Arbitrary | Parsing | Medium |
| Binary data parser (Protobuf, gRPC, custom TLV) | Structured binary | Parsing | Medium |
| Web crawler / renderer | JavaScript / HTML | Rendering | Medium |
| Port scanner / metadata collector | Metadata | Parsing | Low |
| Reverse proxy / gateway | Arbitrary | Proxy | Low |
| Queue message upload | Arbitrary | Proxy | Low |
| Data entry form / simple REST field | String | Parsing | Low |
| Bucket / object upload | Arbitrary | Storage | Low |
Security boundary types (strongest → weakest as a sole boundary):
| Boundary | Definition | Relative Strength | What to Look For in the Repo |
|---|
| Hardware separation | Tenants on separate physical machines / bare-metal | Highest | Dedicated-host scheduling, bare-metal install docs |
| Hardware virtualization | Tenants in separate VMs on shared hardware | High | KubeVirt / Kata / HyperShift hosted control planes, per-tenant VMs |
| Network segmentation | Tenants on separate machines in a shared network segmented into per-tenant VPNs/subnets | High (secondary) | Per-tenant VPC/subnet/OVN network, NetworkPolicy default-deny, service mesh mTLS with tenant identity |
| Identity segmentation | Per-tenant roles bounded by deny-by-default policy so no tenant can act on another's resources | High (secondary) | Per-tenant ServiceAccount / IAM role, RBAC scoped to tenant namespace, SubjectAccessReview / impersonation checks in request path |
| Containerization | Tenants in separate containers / pods sharing a kernel | Low on its own | Look for the compensating controls PEACH prescribes: seccomp / AppArmor / SELinux / gVisor, non-root, read-only rootfs, dropped caps, hardened node OS. Absence of these on a container-only boundary is a finding. |
| Data segmentation | Tenants share a datastore; separation is by per-tenant encryption/authorization keys only | Low | Row-level tenant_id filters, shared bucket with prefix-per-tenant, shared etcd/DB with logical partitioning. Highly implementation-dependent — audit key storage and every query path for missing tenant scoping. |
Step 2 — Boundary Hardening (P.E.A.C.H.)
For every boundary identified in Step 1, assess the five hardening parameters. Each unmet parameter on an in-use boundary is a candidate finding.
| ID | Parameter | What to Check |
|---|
| PEACH-P | Privilege hardening | Tenants and the hosts running them have only the minimum permissions required. No tenant can read or write another tenant's data unless both explicitly opt in. No host has read/write to other hosts. Privileges are verified before the operation executes. In code: per-request authorization that includes the tenant identifier; controllers that scope List/Watch/Get to the caller's namespace; no shared ServiceAccount with cluster-wide read; cloud IAM roles scoped per tenant, not per service. |
| PEACH-E | Encryption hardening | Data at rest and in transit for each tenant is encrypted with a key unique to that tenant, regardless of where it is stored. Per-tenant activity logs are encrypted with a secret shared only between that tenant and the control plane. In code: per-tenant KMS key / envelope key derivation; no single service-wide encryption key protecting all tenants' data; TLS between tenant and control plane terminates on a per-tenant identity. |
| PEACH-A | Authentication hardening | Communication between each tenant and the control plane (both directions) authenticates with a key or certificate unique to that tenant. Keys are validated; self-signed / unvalidated keys are rejected. In code: no shared bearer token or static credential across tenants; mTLS or signed-token auth with tenant-bound subject; InsecureSkipVerify / --insecure-skip-tls-verify absent on tenant↔control-plane paths; token audience/issuer checked. |
| PEACH-C | Connectivity hardening | Inter-host connectivity is default-deny: a tenant's host cannot initiate connections to other tenants' hosts, and does not accept inbound from them, except via the control plane (hub-and-spoke). Tenants cannot reach arbitrary external or in-environment resources — only a pre-approved allowlist. In code/manifests: default-deny NetworkPolicy plus explicit control-plane allow; egress restrictions / proxy allowlists; no hostNetwork on tenant workloads; SSRF guards on any tenant-supplied URL. |
| PEACH-H | Hygiene | The environment reachable from a tenant contains nothing that helps an attacker who has already escaped one boundary: (a) Secrets — no keys/credentials on the interface, datastore, or underlying host that authenticate to other tenants or decrypt their traffic/logs; (b) Software — no built-in tooling or source on the host that enables recon or lateral movement (debug shells, cloud CLIs, package managers, compilers in production images); (c) Logs — each tenant's logs are inaccessible to other tenants, and logs a tenant can read contain no other tenant's activity. In code: distroless/minimal base images; no service-wide secret mounted into per-tenant pods; log queries and metrics endpoints filter by tenant; node/host credentials (kubelet, cloud IMDS) not reachable from tenant workloads. |
Step 3 — Remediation Levers
PEACH defines three independent levers for reducing cross-tenant risk. Every PEACH finding's remediation field should recommend one (or a combination), chosen against operational context (budget, compliance, expected use-case):
| Lever | What It Means | When To Recommend | Concrete Remediations in This Portfolio |
|---|
| Reduce interface complexity | Right-size the interface to the expected input; limit what actions a caller can perform so a would-be attacker has less degree of control. | The interface accepts more than the use-case needs (e.g. full query language where a fixed set of filters would do; arbitrary container spec where a constrained template would do). | Replace free-form query with parameterized/allow-listed filters; validate and canonicalize input at the edge; drop unused verbs/endpoints; disable debug/exec features in production builds. |
| Improve separation | Strengthen the existing boundary — add missing P.E.A.C.H. hardening, upgrade the boundary type, or layer an additional boundary on top. | The boundary type is appropriate but under-hardened (unmet PEACH-* parameters), or a low-strength boundary is the only one present. | Add default-deny NetworkPolicy + per-tenant SA (identity + network segmentation on top of containerization); move container-only isolation to Kata/gVisor/KubeVirt; add per-tenant KMS envelope keys to a shared datastore. |
| Increase duplication | Move shared functionality outside the vendor's trust boundary and instantiate it per organizing principle (per-tenant, per-region, per-cluster). | The interface is high-complexity and shared, so a single escape yields fleet-wide impact. Prioritize duplicating complex components over simple ones. | Move a shared admission webhook / aggregated API into each HostedCluster; give each tenant its own DB schema/instance instead of row-level partitioning; shard the control plane per customer segment. |
Blast Radius
Blast radius is the set of tenants an attacker can affect after exploiting a single interface bug. The whitepaper's canonical high-blast-radius pattern is: high-complexity interface → shared instance → low-strength boundary (containerization or data segmentation) → unmet P.E.A.C.H. parameter. That chain is exactly what turned a Jupyter LPE into fleet-wide credential theft in ChaosDB — a shared, high-complexity notebook interface behind container-only isolation with gaps in all five parameters (shared admin certs → PEACH-P; shared API-key encryption key → PEACH-E; unvalidated self-signed certs → PEACH-A; iptables-only network controls bypassable from the host → PEACH-C; unrelated certs/keys reachable from the tenant → PEACH-H).
Rate any finding matching this pattern high or critical. Duplicated interfaces or high-strength boundaries (hardware virtualization, network/identity segmentation) shrink blast radius to one tenant and can be rated one step lower.
Transparency
The whitepaper's third systemic gap is that vendors rarely document isolation. Where the audited component is itself consumed by downstream customers (e.g. a managed service or a layered product), record in the finding remediation and/or executive_summary.key_risks whether the component publicly answers, for each customer-facing interface: (a) which security-boundary type separates tenants, and (b) which P.E.A.C.H. parameters that boundary meets. Absence of a documented isolation model is itself an informational finding tagged PEACH-H.
Reporting PEACH Findings
- Set the structured
peach_references array on the finding object (e.g. ["PEACH-C", "PEACH-H"]) — this is validated by schema/report.schema.json. Also cite the ID(s) in prose in description alongside the CWE and CVSS.
- In
description, name the interface (with its complexity), whether it is shared or duplicated, the boundary type relied on, and which P.E.A.C.H. parameter is unmet.
- Add one entry per interface to the top-level
peach_isolation_review.interfaces[] array (name, complexity, shared, boundary_type, hardening_gaps, finding_ids). This is what render_report.py renders as the isolation-review table in the Markdown report.
- Map
remediation to one of the three levers in Step 3.
Existing Scanner Results
Include any findings that already exist in the repository from automated scanning tools such as: Dependabot, govulncheck, Snyk, Trivy, Grype, Coverity, and Renovate. Check for:
.github/dependabot.yml and Dependabot PRs/alerts
go.sum / go.mod for known vulnerable dependencies
.snyk, .trivyignore, or similar policy files
- CI pipeline configurations that run security scanners
- Any
SECURITY.md or vulnerability disclosure policy
Lines of Code Measurement
Before writing the report, compute the lines of source code evaluated at the analyzed ref and record the result in metadata.loc_reviewed (integer total) and metadata.loc_breakdown (structured per-language counts). This feeds the campaign-wide /loc-dashboard and lets reviewers normalize finding density per KLoC.
Tool preference (first available wins):
tokei --output json --exclude vendor --exclude node_modules --exclude third_party <local-path>
cloc --json --exclude-dir=vendor,node_modules,third_party <local-path>
scc --format json --exclude-dir vendor,node_modules,third_party <local-path>
git -C <local-path> ls-files -z \
| grep -zvE '^(vendor/|node_modules/|third_party/)' \
| xargs -0 wc -l | tail -1
Exclusions: always exclude vendor/, node_modules/, third_party/, _output/, and generated code (zz_generated*.go, *.pb.go, bindata.go). Record every excluded path/glob in metadata.loc_breakdown.excludes so the count is reproducible.
Populate the report:
"metadata": {
"loc_reviewed": 184223,
"loc_breakdown": {
"total": 184223,
"by_language": {"Go": 171004, "YAML": 9862, "Shell": 2110, "Makefile": 1247},
"tool": "tokei",
"excludes": ["vendor/", "zz_generated*.go", "*.pb.go"]
}
}
If a full local checkout was not required (analysis done entirely via GitHub MCP / fetch), obtain per-language byte counts from the GitHub API (GET /repos/{owner}/{repo}/languages) and record "tool": "github-languages-api" — note in metadata.additional that the figure is bytes-derived, not a true line count. render_report.py renders loc_breakdown as a per-language table in the Markdown output and surfaces the total in the metadata header.
Output
Repository Layout
The validation scripts, renderer, and JSON schema are bundled in this repository:
ai-security-harness/
├── scripts/
│ ├── validate_report.py # JSON Schema + cross-validation checks
│ └── render_report.py # JSON → Markdown renderer (post-processing)
├── schema/
│ └── report.schema.json # authoritative report schema
└── findings/ # report output directory
Report Placement
Place each report in the findings/ directory within this repository:
findings/<product-name>/<repo-name>/<repo-name>-security-audit.json
Where:
<product-name> is the operator package name or product identifier (e.g. advanced-cluster-management, rhacs-operator, openshift for core platform repos).
<repo-name> is the repository name without the GitHub organization prefix.
For deduplicated repos shared across products, place the canonical report under the first product alphabetically and create symbolic links from the others:
findings/advanced-cluster-management/kube-rbac-proxy/kube-rbac-proxy-security-audit.json (canonical)
findings/multicluster-engine/kube-rbac-proxy/kube-rbac-proxy-security-audit.json (symlink → canonical)
Report File Name
Each report file must be named: <repo-name>-security-audit.json
Finding IDs
Every finding id must be globally unique across the entire campaign, not just within its own report, so that a finding can be located by ID alone without first knowing which repository it belongs to. Use the canonical format:
{REPO_SLUG}-{SHORTSHA}-{NNN}
| Component | Derivation |
|---|
REPO_SLUG | The repository name (without the GitHub org), uppercased, with every character outside [A-Z0-9] replaced by _, truncated to at most 24 characters. E.g. cluster-monitoring-operator → CLUSTER_MONITORING_OPERA; kube-rbac-proxy → KUBE_RBAC_PROXY; stackrox → STACKROX. |
SHORTSHA | The first 7 lowercase hex characters of the commit SHA being audited — the same value written to metadata.commit. If the input specified a branch rather than a commit, resolve it: git rev-parse --short=7 HEAD after checkout, or gh api repos/{org}/{repo}/commits/{branch} --jq '.sha[0:7]' when working via the GitHub MCP tools. |
NNN | Three-digit zero-padded sequence starting at 001 and incrementing per finding within this report. |
Examples: STACKROX-4f9e812-003 · KUBE_RBAC_PROXY-9cb7556-012 · CLUSTER_MONITORING_OPERA-a1b2c3d-001.
Always populate metadata.commit with the full 40-char SHA (or at minimum the same 7-char short SHA) so the ID is reproducible and the finding can be traced to an exact code state. The regex the schema and validator enforce for reports produced by harness ≥ 0.12.0 is:
^[A-Z][A-Z0-9_]{0,23}-[a-f0-9]{7}-\d{3}$
Reports produced by earlier harness versions may carry legacy IDs (FIND-001, etc.); these still pass schema validation but draw a strict-mode warning. Do not emit legacy IDs in new reports.
Report Format
Reports are JSON files validated against schema/report.schema.json in this repository. The validator script scripts/validate_report.py enforces both the JSON Schema and additional cross-validation checks (ID uniqueness, severity count consistency, cross-references between sections). Do not produce Markdown — Markdown rendering is a separate post-processing step via scripts/render_report.py.
Report Structure Reference
The JSON report has the following top-level keys. The schema (schema/report.schema.json) is the authoritative source of truth for types, constraints, and optional fields.
Required keys:
| Key | Description |
|---|
title | Report title. Must contain a security-related keyword: security, audit, assessment, review, finding, or analysis. |
metadata | Object with required date (YYYY-MM-DD, the audit execution date — never a commit or CVE date) and scope (≥10 chars). Optional: repository, commit, framework, auditor, methodology, tools (array), loc_reviewed (integer preferred; string accepted for backward compatibility), loc_breakdown (object — see Lines of Code Measurement; expected on all new reports), additional (object). Must include harness_version — read the semver from the VERSION file at the root of the ai-security-harness repository and append the short git SHA (e.g., 0.1.0-4dd9796). |
executive_summary | Object with prose (≥50 chars) and severity_counts (object with required critical, high, medium, low integer counts). Optional: key_risks (array of strings), positive_observations (array of strings). |
severity_criteria | Array of ≥4 entries, each with level (critical/high/medium/low/informational) and definition (≥20 chars). Must include at least critical, high, medium, and low. |
findings | Array of finding objects (see below). |
findings_summary | Array of ≥4 severity count entries, each with severity, count, and finding_ids. Counts and IDs must match the actual findings. |
remediation_roadmap | Array of ≥1 items, each with priority, action (≥10 chars), and addresses (array of finding IDs). |
Finding object fields:
| Field | Required | Description |
|---|
id | Yes | Globally-unique ID in the form {REPO_SLUG}-{SHORTSHA}-{NNN} (see Finding IDs above). Must be unique across the report and across the campaign. |
title | Yes | Short description (≥5 chars). |
severity | Yes | One of: critical, high, medium, low, informational. |
cwes | Yes | Array of ≥1 CWE identifiers matching CWE-NNN (e.g. CWE-269). |
locations | Yes | Array of ≥1 objects with required path and optional lines, description. |
description | Yes | Detailed description of the vulnerability (≥50 chars). |
remediation | Yes | Remediation guidance (≥10 chars). |
cvss | No | Object with score (0.0–10.0) and vector string. |
capec | No | Array of CAPEC IDs matching CAPEC-NNN. |
evidence | No | Array of code blocks, each with required code and optional language, caption. |
attack_pattern | No | Description of the attack scenario. |
category | No | Finding category (e.g. "Improper Privilege Management"). |
asvs_references | No | Array of ASVS requirement IDs. |
peach_references | No | Array of PEACH hardening-parameter IDs (PEACH-P, PEACH-E, PEACH-A, PEACH-C, PEACH-H). Set only when peach_isolation_review.applicable is true. |
validation_status | No | One of: confirmed, corrected, false_positive, not_verified, hardening. |
source_findings | No | Array of source scanner finding IDs (Dependabot, Trivy, etc.). |
Optional top-level keys:
| Key | Description |
|---|
dependency_audit | Object with optional prose and entries array (each: package, version, status, optional notes). |
negative_results | Array of areas verified clean, each with area, result, optional files. |
asvs_coverage | Array of ASVS chapter coverage entries. |
scanner_correlation | Array of scanner tool results. |
peach_isolation_review | Object with required applicable (bool) and optional rationale, interfaces[] (each: name, complexity, shared, boundary_type, hardening_gaps, finding_ids, notes). Always emit — set applicable: false for single-tenant components. |
footer | Free-text footer string. |
Field Conventions
A drift audit across 5,488 portfolio reports (2026-07-08) showed that every field this section did not pin oscillated from batch to batch. These conventions are normative; the validator enforces or warns on each (harness ≥ 0.15.0).
validation_status — set on every finding, with these semantics:
| Value | Meaning | When to use |
|---|
not_verified | Static review only; no execution evidence | Default for every finding this skill produces. An audit report fresh out of this skill should be predominantly not_verified. |
confirmed | Verified by execution evidence — a fuzz crash, proof-of-concept, failing test, or a validations-pipeline report (validation.schema.json under analysis-results/validations/) — or by a human reviewer performing triage of the finding | Only when that evidence or human determination exists and is referenced in the finding (e.g. via source_findings, an evidence block, or a reviewer attribution). |
corrected | Finding was revised after initial write-up (wrong location, severity, or scope fixed) | During report revision. |
false_positive | Refuted by the same execution-evidence classes or a human reviewer; retained for the audit trail | After execution-based or human verification refutes it. |
hardening | Accurate defense-in-depth/benchmark gap with no concrete exploit path (triage exclusion rule 13) | Never set at audit time. This value is written into cumulative reports by the disposition ledger when triage renders a hardening verdict (see docs/disposition-ledger.md). |
Automated static-review votes — including /triage LLM adversarial verification — do not change validation_status in the audit report itself: those verdicts flow through the disposition ledger (scripts/emit_triage_ledger_events.py → track-findings), which derives the cumulative report's status under the evidence-class precedence and countersign rules. A human reviewer performing triage of a finding may set confirmed or false_positive directly. Do not mark findings confirmed because the analysis felt certain; the validator flags all-confirmed audit reports as over-claiming.
CVSS — required on every critical/high/medium/low finding. Omit for informational findings (posture and hygiene observations are not scored).
attack_pattern — required on every critical and high finding: a concrete, step-by-step attack scenario naming the attacker's starting position and end state.
category — use exactly one token from this vocabulary (kebab-case) so cross-report aggregation is lossless:
injection · authentication · authorization · secrets-management · supply-chain · insecure-workload-config · network-exposure · cryptography · input-validation · path-traversal · cross-site-scripting · ssrf · resource-management · logging-monitoring · data-exposure · tenant-isolation
Framework mappings (OWASP K8s K-IDs, CIS sections, STIG IDs, ASVS chapters) belong in description, asvs_references, and the CWE list — not in category.
metadata.framework — a ; -separated list of exactly these tokens, including only the frameworks actually applied to this repository:
OWASP ASVS v5.0 · OWASP Kubernetes Top 10 2025 · CIS Kubernetes Benchmark v2.0 · DISA STIG for Kubernetes V2R6 · SLSA v1.2 · OpenSSF Scorecard · PEACH v1.1
Include the Kubernetes frameworks only when the repo ships manifests, charts, or operator code; include PEACH only when peach_isolation_review.applicable is true.
metadata.date — the date the audit ran, never a commit or CVE publication date.
asvs_references — populate on findings in application-security categories (injection, authentication, authorization, input-validation, cross-site-scripting, ssrf, cryptography); ASVS is the primary framework and unreferenced findings weaken traceability.
Report Validation
After writing the JSON report, you must validate it before considering the report complete. Do not move on to the next repository until the current report passes validation with 0 errors.
Step 1 — Run the validator:
python scripts/validate_report.py <path-to-report.json>
The validator must exit with Result: ALL PASSED and 0 errors.
Step 2 — Fix any errors and re-validate:
If errors appear, read each error message, fix the JSON, and re-run the validator. Repeat until 0 errors. Common error categories:
- Schema violations — missing required fields, wrong types, values too short, pattern mismatches (e.g. CWE format)
- Cross-validation errors —
findings_summary counts don't match actual findings, finding_ids reference nonexistent IDs, remediation_roadmap addresses unknown findings, duplicate finding IDs, missing mandatory severity criteria levels
Step 3 — Run strict mode (recommended):
python scripts/validate_report.py --strict <path-to-report.json>
Strict mode surfaces warnings for missing recommended sections (dependency_audit, negative_results, footer, CVSS scores on findings, positive_observations). Address these warnings when the source material supports it.
Step 4 — Render Markdown:
After validation passes, render a human-readable Markdown version of the report:
python scripts/render_report.py <path-to-report.json> -o <path-to-report.md>
The output file should be placed alongside the JSON report with the same base name but a .md extension:
findings/<product-name>/<repo-name>/<repo-name>-security-audit.md
This Markdown file is for human consumption only — the JSON file remains the authoritative report artifact.
A report is not complete until scripts/validate_report.py exits with 0 errors. Do not begin analysis of the next repository until the current report passes validation.
Batch Execution
When this skill is driven at scale — workflow scripts, loop templates (_manifest/*_AGENT_TEMPLATE.md), or hand-written subagent prompts — the batch prompt must not restate the report schema. Every documented consistency regression in the portfolio (legacy FIND-NNN IDs, loc_reviewed typed as "STRING", all-confirmed validation status, vanished attack_pattern, free-form categories) entered through a batch prompt that paraphrased this skill from memory and then drifted from it.
Rules for batch prompts:
- Reference, don't restate. Point the subagent at this file (
harnessing/secure-code-audit/SKILL.md) and at schema/report.schema.json for structure. The prompt may add target-specific context (what the repo is, which surfaces to prioritize, output paths) — never field formats, ID schemes, or type information.
- The validator is the source of truth. Require the strict-mode validate → fix → re-validate loop verbatim (see Report Validation). A batch prompt that conflicts with the validator is wrong by definition.
- Pin the harness version once. Read
VERSION at batch start and pass the same harness_version string to every subagent, so version-gated checks apply uniformly across the batch.
- Spot-check the first report of any new batch template against a recent known-good report before fanning out the remaining repositories.