container-audit
Skill7 starsChanged 27 days agoMITView on GitHub
Static Docker/container security audit across 6 dimensions (K1-K6): base image provenance and pinning, privilege and runtime hardening, secret and build-context hygiene, image minimalism and attack surface, known-vulnerability scan (Trivy/Grype), and compose/orchestration hardening. Static-first — parses Dockerfile, Containerfile, and docker-compose (incl. override/merge files) with zero Docker and zero network; the CVE dimension (K5) runs Trivy/Grype only when present, else degrades to N/A. Reserved dimensions K7-K10 (Kubernetes) activate behind --k8s. HEALTHY/NEEDS ATTENTION/AT RISK/CRITICAL grade with critical gates. Distinct from infra-audit (live host daemon over SSH) and ci-audit (Docker build speed in the pipeline). Switches: zuvo:container-audit full | [path] | --static | --scan | --dockerfile <p> | --compose <p> | --quick | --k8s | --persist-backlog
- Reads credentials
- Deletes or force-pushes
3 other projects use a version of this.
Adopted by
Most recently taken up by
- briiirussell/cybersecurity-skillstheir own version, first seen Nov 1, 2026
- imMamdouhaboammar/cybersecurity-skillstheir own version, first seen Sep 5, 2026
- gricha/dangerous-skillstheir own version, first seen Mar 23, 2026
Other versions3 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 3
- briiirussell/cybersecurity-skills★ 489same name
- gricha/dangerous-skills★ 38same name
- imMamdouhaboammar/cybersecurity-skills★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository58 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- a11y-auditSkill
- agent-benchmarkSkill
- api-auditSkill
- architectureSkill
- backlogSkill
- benchmarkSkill
- brainstormSkill
- buildSkill
- canarySkill
- ci-auditSkill
- code-auditSkill
- content-auditSkill
- content-expandSkill
- content-fixSkill
- content-migrationSkill
- context-auditSkill
- db-auditSkill
- debugSkill
- dependency-auditSkill
- deploySkill
- design-reviewSkill
- designSkill
- docsSkill
- env-auditSkill
- executeSkill
- fix-testsSkill
- geo-auditSkill
- geo-fixSkill
- incidentSkill
- infra-auditSkill
- leadsSkill
- mutation-testSkill
- pentestSkill
- performance-auditSkill
- planSkill
- presentationSkill
- profile-sessionSkill
- receive-reviewSkill
- refactor-radarSkill
- refactorSkill
- release-docsSkill
- retroSkill
- reviewSkill
- security-auditSkill
- seo-auditSkill
- seo-fixSkill
- shipSkill
- skill-evalSkill
- structure-auditSkill
- test-auditSkill
- tests-performanceSkill
- ui-design-teamSkill
- using-zuvoSkill
- worktreeSkill
- write-articleSkill
- write-e2eSkill
- write-testsSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
15 rules. 0 are common, 5 are distinctive, and 10 are very specific to this project.
0
Common
Most of the 3 edited versions we read keep these.
5
Distinctive
This author's own choices. Worth a look.
10
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 36
When to use: before shipping a Dockerized service, after editing a Dockerfile or compose file, periodic container hardening review, …
- Distinctive: line 44
Read every file below before starting.
- Distinctive: line 57
If any file is MISSING, STOP.
- Very specific: line 122
When CodeSift is genuinely unavailable, the equivalent Glob/Read/grep fallback SATISFIES the gate (record scan_secrets: grep-fallback(<N> …
- Very specific: line 125
NO secret scan at all, or retrospective: skipped.
- Very specific: line 133
Every finding needs path/to/file:LINE (verify-audit gate).
- Very specific: line 161
If NO container artifacts are found → report "no container artifacts" and exit HEALTHY (N/A — nothing to audit).
- Very specific: line 209
Only a @sha256: digest is truly immutable.
- Very specific: line 334
Read literally as "the dimension scored 0", these gates would be nearly unreachable and the audit would pass containers it exists to fail: …
- Very specific: line 402
Write zuvo/audits/container-audit-[YYYY-MM-DD].json — write a .tmp then atomic-rename:
- Distinctive: line 421
No actual secret values appear anywhere (Gate 2).
- Very specific: line 481
If gate_status = FAIL → VERDICT = INCOMPLETE.
- Distinctive: line 489
Run: <ISO-8601-Z> container-audit <project> <N-critical> <N-total> <VERDICT> - <N>-dimensions <NOTES> <BRANCH> <SHA7> <INCLUDES> <TIER>
- Very specific: line 493
Follow the retrospective protocol from retrospective.md.
- Very specific: line 496
After printing this block, append the Run: line value (without the Run: prefix) to the log file path resolved per run-logger.md.
“Common” is measured: kept by at least 6 in 10 of the 3 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
zuvo:container-audit
Audit container artifacts (Dockerfile, Containerfile, docker-compose) as code for
security defects — running as root, mutable :latest bases, secrets baked into layers,
bloated attack surface, known CVEs, and docker.sock mounts. Single-pass, read-only.
When to use: before shipping a Dockerized service, after editing a Dockerfile or
compose file, periodic container hardening review, pre-deploy gate.
When NOT to use: auditing the live Docker daemon on a remote host over SSH
(zuvo:infra-audit IS9); Docker build speed / cache in CI (zuvo:ci-audit CI8);
application-code vulnerabilities (zuvo:security-audit).
Mandatory File Loading
Read every file below before starting. Print the checklist.
CORE FILES LOADED:
1. ../../shared/includes/codesift-setup.md -- [READ | MISSING -> STOP]
2. ../../shared/includes/env-compat.md -- [READ | MISSING -> STOP]
3. ../../shared/includes/report-output-location.md -- [READ | MISSING -> STOP]
4. ../../shared/includes/severity-vocabulary.md -- [READ | MISSING -> STOP]
5. ../../shared/includes/run-logger.md -- [READ | MISSING -> STOP]
6. ../../shared/includes/retrospective.md -- [READ | MISSING -> STOP]
7. ../../shared/includes/backlog-protocol.md -- [READ | MISSING -> STOP] (Filing findings through backlog-add)If any file is MISSING, STOP. Do not proceed from memory.
Argument Parsing
| Token | Behavior |
(empty) or full | All applicable dimensions, scope = project root |
[path] | Scope to a directory (monorepo package / service) |
--static | Force pure-static: skip live Trivy/Grype even if present (K5 via trivy config or N/A) |
--scan | Force live CVE scan; missing scanner OR declined image pull → exit non-zero (scan aborted), never a silent downgrade |
--dockerfile <path> | Audit a single Dockerfile |
--compose <path> | Audit a single compose file |
--quick | Critical gates only (K1 + K2 + K3) |
--k8s | Activate reserved dimensions K7-K10 (Kubernetes manifest/RBAC/Pod-Security/network-policy) |
--persist-backlog | Record CRITICAL/HIGH findings via backlog-add |
Safety Gates
GATE 1 -- Read-Only
The only write targets are zuvo/audits/ and, through backlog-add, memory/backlog.md and memory/findings/. FORBIDDEN: modifying any Dockerfile, compose, or
source file; building, running, tagging, or pushing images.
GATE 2 -- Secret Censorship
Any secret value — from ANY source (an ENV/ARG/.env, a hardcoded literal in a RUN
command, a compose environment: entry, a scanned config) — is replaced with *** before it
reaches the report. Variable NAMES may show; VALUES never. .env.example placeholders may
show as-is.
GATE 3 -- Consent Before Any Image PULL
trivy config and the native Dockerfile/compose parse run freely (no pull). Before any
trivy image <ref> / grype <ref> that must PULL an image, prompt:
Pull image <ref> to scan for CVEs? This performs network egress to <registry>. [y/N] —
default DENY. In a non-interactive / CI context (no TTY, no way to answer) do NOT prompt —
treat it as an automatic DENY. Decline (or non-interactive) → default mode: K5 falls back to
trivy config/native with an annotation; --scan mode: exit non-zero. The prompt (or the
non-interactive auto-deny) and the outcome are logged.
GATE 4 -- No Daemon / Socket Interaction
The audit never connects to /var/run/docker.sock or a remote daemon (that is
zuvo:infra-audit). Detecting a docker.sock mount in a compose file (K6) is static
text analysis, not a socket connection.
MANDATORY TOOL CALLS — Container Audit Validity Gate
INVALID if any tool below is skipped. "DEFERRED", "N/A", --quick are NOT valid reasons.
| Tool | Trigger | Skip allowed? |
get_file_tree | Always | NO — discover Dockerfile*/compose*/.dockerignore |
search_text | Always | NO — FROM/USER/COPY/ARG/ENV/mount scan |
search_patterns | Always | NO — K2/K3/K6 anti-patterns |
scan_secrets | Always | NO — K3 baked-secret detection |
audit_scan | Always | NO — compound check |
The gate checks the CHECK was PERFORMED, not that a specific CodeSift tool was literally invoked.
When CodeSift is genuinely unavailable, the equivalent Glob/Read/grep fallback SATISFIES the
gate (record scan_secrets: grep-fallback(<N> hits), codesift: unavailable). What is REJECTED is a
check silently NOT DONE: scan_secrets: skipped, codesift: unavailable used as an excuse to run
NO secret scan at all, or retrospective: skipped.
VERDICT vs logging: the audit VERDICT (PASS/WARN/FAIL/INCOMPLETE) is independent of the postamble
log-append. "append-runlog exit 0" below means the log wrapper returned 0 (append succeeded) — a
FAIL audit still appends its run line and returns 0 from the wrapper; the VERDICT is FAIL, not the
exit code. A --scan abort (GATE 3) exits BEFORE the postamble and does not write a run line.
POSTAMBLE (on a completed audit): report on disk → retro appended → ~/.zuvo/append-runlog returns 0.
Every finding needs path/to/file:LINE (verify-audit gate).
Mandatory-tools-acknowledgment: I will run get_file_tree + search_text + search_patterns + scan_secrets + audit_scan (or their grep/Glob fallback if CodeSift is unavailable) for this container audit. Every finding will cite a `path/to/file:LINE` resolving in the current tree.Degraded mode (CodeSift unavailable): fall back to Glob/Read/grep for artifact discovery and pattern scans; note codesift=unavailable in telemetry and proceed — the fallback satisfies the mandatory-tools gate (above), do NOT block.
Phase 0: Detect and Scope
0.1 Target Resolution
Set TARGET_ROOT from arguments. All subsequent scans use this path.
0.2 Artifact Discovery
Enumerate container artifacts (use get_file_tree; cross-check with git ls-files):
| Pattern | Kind |
Dockerfile*, *.dockerfile, Containerfile | image build |
docker-compose*.y{a,}ml, compose*.y{a,}ml | compose |
docker-compose.override.y{a,}ml, compose.override.y{a,}ml | compose override (merged into effective config) |
.dockerignore | build-context filter |
*.yaml/*.yml under k8s/, kubernetes/, manifests/ | Kubernetes (K7-K10, only when --k8s) |
If NO container artifacts are found → report "no container artifacts" and exit
HEALTHY (N/A — nothing to audit). This is not a failure. Note: get_file_tree HAS run at
this point (that is how "none found" was determined, satisfying the mandatory-tools gate for it);
the remaining artifact-scanning tools (search_text/search_patterns/scan_secrets over
Dockerfiles) are vacuously N/A because there is nothing to scan — record them as
N/A(no-artifacts), not skipped.
0.3 Scanner and Socket Detection
command -v trivy/command -v grype→ K5 mode (live vs N/A).- Note Docker socket presence only to decide whether an image pull is even possible; never connect.
Print:
CONTAINER ARTIFACT INVENTORY
------------------------------------
Target: [TARGET_ROOT]
Dockerfiles: [N]
Compose files: [N] (+ [N] override)
.dockerignore: [present/absent]
K8s manifests: [N] (audited only with --k8s)
CVE scanner: [trivy | grype | none -> K5 N/A]
------------------------------------Phase 1: Dimension Analysis (K1-K6, + reserved K7-K10)
Single-pass inline execution. No sub-agents.
Multi-stage scope rule: runtime dimensions (K2, K4, K6) are judged on the FINAL stage
only — root/tools in a builder stage is fine. K1 (pinning) and K5 (CVE) apply to every
EXTERNAL base ref in ALL stages (a FROM node:latest AS builder is still pulled →
supply-chain risk). INTERNAL stage refs (FROM builder, where builder is a prior stage in
the same file) are exempt.
K1: Base Image Provenance & Pinning -- Weight 15, Critical Gate
| Check | Good | Bad | Severity |
| Base fully pinned | @sha256:… digest (immutable) | :latest, :stable, or no tag | CRITICAL |
| Version tag (no digest) | node:20.11.1 — acceptable but MUTABLE (a tag can be re-pushed) | — | MEDIUM (recommend adding @sha256:) |
| Trusted registry | Official / org registry | Unknown/anonymous registry | HIGH |
| Every stage's external base | All pinned | A builder stage on :latest | HIGH |
Var-interpolated FROM $REG/img | Resolvable + pinned | Unresolvable build-arg base | HIGH (flag, do not silent-pass) |
Only a @sha256: digest is truly immutable; a specific version tag is pinned-enough to clear the
critical gate but is flagged MEDIUM (recommend a digest). Critical gate: any final-image external base on :latest/:stable/no-tag → FAIL. The gate fires
on that FINDING; a K1 score of 0 also fires it but is not required.
rule_ids: K1-latest-tag, K1-no-tag, K1-mutable-version-tag, K1-untrusted-registry, K1-unpinned-builder.
K2: Privilege & Runtime Hardening -- Weight 18, Critical Gate
| Check | Good | Bad | Severity |
| Non-root user | USER <non-root> in final stage (or distroless :nonroot) | No USER → runs as root | CRITICAL |
Compose user: override | absent, or a non-root uid | compose user: root/user: "0" overriding the image's non-root USER back to root | CRITICAL |
| Privileged (compose) | privileged: false / absent | privileged: true | CRITICAL |
| Capabilities (compose) | cap_drop: [ALL] + minimal cap_add | No cap_drop / cap_add: [ALL] | HIGH |
| no-new-privileges (compose) | security_opt: [no-new-privileges:true] | absent | MEDIUM |
| Read-only rootfs (compose) | read_only: true where feasible | writable rootfs unnecessarily | MEDIUM |
Distroless :nonroot variants satisfy the non-root check even without a USER line — do NOT
false-flag them. Critical gate: the image runs as root AND/OR privileged: true → FAIL. The gate fires on that
FINDING; a K2 score of 0 also fires it but is not required.
rule_ids: K2-root-user, K2-privileged, K2-no-cap-drop, K2-no-new-privileges.
K3: Secret & Build-Context Hygiene -- Weight 18, Critical Gate
| Check | Good | Bad | Severity |
| No secret in ENV/ARG | Config-only ENV/ARG | ENV API_KEY=… / ARG SECRET=… baked to a layer | CRITICAL |
| Build secrets | RUN --mount=type=secret | secret passed via ARG (persisted in history) | CRITICAL |
.dockerignore present | excludes .env, .git, keys | absent with a blind COPY . . | CRITICAL |
| No hardcoded creds | secrets via runtime env / manager | literal password/token in Dockerfile | CRITICAL |
K3 only flags ENV/ARG whose NAME matches secret patterns (*KEY*, *SECRET*, *TOKEN*,
*PASSWORD*, *CRED*), never benign build config (VERSION, NODE_ENV). Critical gate: any secret baked into the image, OR a blind COPY . . with no .dockerignore
→ FAIL. The gate fires on that FINDING; a K3 score of 0 also fires it but is not required.
rule_ids: K3-secret-in-env, K3-secret-in-arg, K3-no-dockerignore, K3-hardcoded-cred.
K4: Image Minimalism & Attack Surface -- Weight 12
| Check | Good | Bad | Severity |
| Multi-stage | builder + slim runtime | single fat stage shipping build tools | HIGH |
| Minimal base | distroless / -slim / alpine | full ubuntu/node (non-slim) for runtime | MEDIUM |
| Package-manager cache | --no-cache / rm -rf /var/lib/apt/lists | apt/apk cache left in a layer | MEDIUM |
| No dev/debug tools in final | prod image has no curl/nc/shell (distroless) | debug tooling shipped to prod | MEDIUM |
rule_ids: K4-no-multistage, K4-fat-base, K4-pkg-cache, K4-debug-tools.
K5: Known Vulnerability Scan -- Weight 15, Critical Gate (only when a scan actually ran)
The audit never BUILDS an image (GATE 1), so K5 scans the declared base image(s) and their OS
packages — the pullable FROM refs — NOT a final built image (which does not exist without a build).
- Scanner present → run
trivy config(static Dockerfile/compose misconfig, invoked with--skip-check-update, no pull) and, with GATE-3 consent,trivy image <base-ref>/grype <base-ref>to scan the BASE image's CVEs. Parse findings from--format json— NEVER gate on the scanner's raw exit code (Trivy exits 1 on findings by design). - Scanner absent OR pull declined OR non-interactive/CI (no consent possible) → K5 = N/A (degraded), annotated, EXCLUDED from the denominator (see Scoring). Never a hard block.
Critical gate: applies ONLY when an image scan actually ran — a CRITICAL-severity CVE in the
base image triggers FAIL. trivy config misconfig findings alone (no image scan) do NOT trip
the gate. rule_ids: K5-critical-cve, K5-high-cve, K5-config-misconfig.
K6: Compose & Orchestration Hardening -- Weight 12
Evaluate the effective merged config (base + *.override.y*ml) so an override cannot
silently re-introduce a risk the base avoided.
| Check | Good | Bad | Severity |
| Docker socket mount | absent | /var/run/docker.sock in volumes: | CRITICAL |
| Host network | absent | network_mode: host | HIGH |
| Resource limits | mem_limit/cpus/deploy.resources set | unbounded | MEDIUM |
| Healthcheck | healthcheck: present | absent | MEDIUM |
| Host namespaces | absent | pid: host / ipc: host | HIGH |
| Port binding | bound to loopback where private | needlessly on 0.0.0.0 | LOW |
rule_ids: K6-docker-sock, K6-host-network, K6-no-limits, K6-no-healthcheck, K6-host-pid.
K7-K10: Kubernetes (RESERVED — N/A (requires --k8s) unless --k8s)
Registered so --k8s-off runs report them inert (excluded from the denominator) and a future
release can activate them additively without touching K1-K6:
- K7 Manifest security (kubesec) · K8 RBAC least-privilege · K9 Pod Security Standards / admission · K10 Network policies.
Without --k8s (or with no k8s manifests present): each is scored N/A (requires --k8s),
excluded from the denominator, and emits ZERO findings.
Current status: the K7-K10 CHECK LOGIC is not yet implemented (DD4 — 0 k8s targets in the fleet
at ship time). Passing --k8s today prints K7-K10: reserved — Kubernetes checks not yet
implemented in this release and proceeds with K1-K6 only; it does NOT fabricate k8s findings. The
dimension slots exist so a future release adds the check logic additively without renumbering K1-K6.
Phase 2: Scoring
K1 = [0-15] Base Image Provenance & Pinning (critical gate)
K2 = [0-18] Privilege & Runtime Hardening (critical gate)
K3 = [0-18] Secret & Build-Context Hygiene (critical gate)
K4 = [0-12] Image Minimalism & Attack Surface
K5 = [0-15] Known Vulnerability Scan (critical gate when scan ran; N/A if no scanner)
K6 = [0-12] Compose & Orchestration Hardening
K7-K10 = N/A (requires --k8s)Score = (sum of applicable dim scores) / (sum of applicable max weights) × 100.
The denominator DROPS any dimension scored N/A — K5 when no scanner ran (denominator 90→75),
K6 when no compose file exists, K7-K10 always unless --k8s, and K4/K5/K6 under --quick
(which runs only the K1-K3 critical gates → denominator = 51). (Variable-denominator model, same
as api-audit D11=N/A — NOT env-audit's fixed denominator.) Under --quick, the score reflects
the critical-gate trio only; note mode=quick (K1-K3) in the report so the narrower denominator
is explicit.
Critical gates fire on the FINDING, not on an aggregate score of zero. Any one of these → overall FAIL, whatever the dimension's numeric score:
| Gate | Fires on |
| K1 | any K1-latest-tag / K1-no-tag finding on a final-image external base |
| K2 | any K2-root-user or K2-privileged finding (incl. a compose user: root/"0" override) |
| K3 | any CRITICAL-severity K3 finding (secret value reachable in image, ARG/ENV, or build context) |
| K5 | K5 actually ran AND a critical CVE is present |
Read literally as "the dimension scored 0", these gates would be nearly unreachable and the audit
would pass containers it exists to fail: K2 carries six checks across 18 points, so an image that
runs as root but sets cap_drop: [ALL], no-new-privileges and read_only: true scores well
above zero — the root finding alone must FAIL it, and under a score-based reading it would not.
A dimension score of 0 remains sufficient to fire the gate; it is simply not necessary.
| Grade | Score |
| HEALTHY | ≥ 80% |
| NEEDS ATTENTION | ≥ 60% and < 80% |
| AT RISK | ≥ 40% and < 60% |
| CRITICAL | < 40% |
Bands are explicit inequalities because the variable denominator makes a fractional score the
norm, not the exception: 62/90 = 68.9% and 51/75 = 68% both land between the hyphenated 60–79
and 80 bands, so a 60–79% row would leave real scores ungraded (or silently rounded into a
different grade).
Phase 3: Report
Save to: zuvo/audits/container-audit-[YYYY-MM-DD].md and .json — at the project root
(zuvo/ resolves via git rev-parse --show-toplevel; override $ZUVO_OUTPUT_DIR. See
../../shared/includes/report-output-location.md).
Markdown Report Structure
# Container Security Audit Report
## Metadata
| Field | Value |
|-------|-------|
| Project | [name] |
| Date | [YYYY-MM-DD] |
| Scope | [TARGET_ROOT] |
| Dockerfiles / Compose | [N] / [N] |
| CVE scanner | [trivy | grype | none (K5 N/A)] |
## Executive Summary
**Score: [N] / 100** -- [HEALTHY / NEEDS ATTENTION / AT RISK / CRITICAL]
Critical gates: [PASS | FAIL — which]
| Metric | Count | (CRITICAL / HIGH / MEDIUM)
## Dimension Scores
| # | Dimension | Score | Max | Notes |
|---|-----------|-------|-----|-------|
| K1 | Base Image Provenance & Pinning | [N] | 15 | |
| K2 | Privilege & Runtime Hardening | [N] | 18 | |
| K3 | Secret & Build-Context Hygiene | [N] | 18 | |
| K4 | Image Minimalism & Attack Surface | [N] | 12 | |
| K5 | Known Vulnerability Scan | [N | N/A] | 15 | |
| K6 | Compose & Orchestration Hardening | [N] | 12 | |
| K7-K10 | Kubernetes | N/A | — | requires --k8s |
| **Total** | | **[N]** | **[applicable]** | |
## Findings (sorted by severity)
[Per finding: rule_id, dimension, severity, file:line, description, fix]
## Remediation Roadmap
### Quick Wins (< 1 hour)
### Short-term (1 day)
### Medium-term (1 week)JSON Findings (for a future container-fix consumer)
Write zuvo/audits/container-audit-[YYYY-MM-DD].json — write a .tmp then atomic-rename:
{
"score": 0,
"grade": "CRITICAL",
"critical_gate": "FAIL",
"dimensions": { "K1": 0, "K2": 0, "K3": 0, "K4": 0, "K5": "N/A", "K6": 0 },
"findings": [
{ "dimension": "K2", "severity": "CRITICAL", "file": "Dockerfile", "line": 1,
"rule_id": "K2-root-user", "message": "image runs as root (no USER)", "fix": "add `USER node`" }
]
}Report Validation
- Dimension scores sum to the applicable total; denominator excludes N/A dims.
- Finding counts match the Executive Summary.
- No actual secret values appear anywhere (Gate 2).
Backlog Persistence (--persist-backlog)
With --persist-backlog, every CRITICAL and HIGH finding goes through backlog-add, one call per finding (../../shared/includes/backlog-protocol.md "Filing from a skill"; the bars and the dedupe are the protocol's, so compute no fingerprint and restate nothing). <level> is critical or high. Without the flag nothing is recorded.
A Generic write per finding. The class is CQ for a K4 finding and SEC for every other dimension (K1 to K3, K5, K6 and, with --k8s, K7 to K10). A Dockerfile or compose finding is filed at its line. A SEC one takes --anchor "$A", copied from the line that shows the defect or, for something absent (no USER), from the line of the code that lacks it, such as the FROM line of that stage ("The location rule"): ~/.zuvo/backlog-add --repo "$R" --class SEC --conf <N> --source container-audit --file "$F" --line <n> --anchor "$A" --severity <level> --title "$T". A CQ one takes no anchor: ~/.zuvo/backlog-add --repo "$R" --class CQ --conf <N> --source container-audit --file "$F" --line <n> --severity <level> --title "$T". A finding on a live host with no repository file causing it takes --target "$U" --evidence "$E" in place of --file, --line and --anchor, and no anchor at all, U the host name or host:port. A finding on an image is filed at the Dockerfile or compose line that names it, FROM or image:, with the image in --evidence "$E"; when no repository file names the image, it takes the target form with U set to image: and the reference as the audit saw it (image:nginx:1.25, image:ghcr.io/org/app:tag), whatever the class: without that prefix redis:7 is read as host redis, port 7 (backlog-protocol.md, the --target row). A reference the helper refuses (REFUSED bad-target: an uppercase repository, a truncated digest; a truncated digest on a name with no /, such as image:redis@sha256:abc, comes back REFUSED secret-like-value, since its @ has the user@ shape) is reported as NOT PERSISTED (<reason>) with the reference, never rewritten into a shape it accepts. <C> the class by the same rule: ~/.zuvo/backlog-add --repo "$R" --class <C> --conf <N> --source container-audit --target "$U" --evidence "$E" --severity <level> --title "$T".
A secret is never copied into a record (backlog-protocol.md "Filing from a skill", "The location rule", the secrets sentence). For a K3 finding the anchor is the key or variable name on that line (ENV API_KEY=, ARG NPM_TOKEN), never its value, and neither is the value in T, E or U; this is Gate 2 applied to everything recorded, with *** for any value that must show at all.
Assign "$R" and "$T" before each call, "$F" only for a file finding ("$A" only for a SEC one), "$U" and "$E" only for a live-target finding, as "Free text goes through a variable" in that section says; an empty value you pass is a bug, and backlog-add refuses a missing required field (REFUSED missing-field:<f>). Report the B- or F-id each call prints and Recorded: N (backlog: a, ledger: b) ("The report line" in backlog-protocol.md), plus each NOT PERSISTED (<reason>) by name; on a non-zero exit act on the protocol's exit table, and never append by hand.
This audit does not commit: it stages nothing. It lists the wrote= paths backlog-add printed as Backlog writes: <path>, ... (Backlog writes: none when there are none), in its report for the user when it runs standalone, and in its report for the calling skill's closing commit to carry when another skill runs it (backlog-protocol.md "Filing from a skill", Committing what backlog-add wrote).
Phase 4: Next-Step Routing
RECOMMENDED NEXT ACTION
------------------------------------
K3 CRITICAL (secret baked) -> zuvo:security-audit --static
K5 CRITICAL CVEs -> zuvo:dependency-audit
K1=0 (mutable base) -> pin FROM to a digest
K2=0 (root/privileged) -> add USER + drop privileged
Score < 60% -> fix critical gates, re-audit
Score >= 80% -> schedule next audit in 3 months
------------------------------------CONTAINER-AUDIT COMPLETE
Score: [N] / 100 -- [grade] Artifacts: [N Dockerfiles] / [N compose] | CVE scanner: [trivy | grype | none] Dimensions: [N scored] | Critical gates: [PASS/FAIL] Findings: [N critical] / [N total]
Validity Gate (REQUIRED — print BEFORE Run line, AFTER retro append + append-runlog)
VALIDITY GATE
artifacts_found: dockerfiles=<N> compose=<N>
required_tool_calls:
get_file_tree: [<N> | NOT_CALLED — VIOLATES_TRIGGER]
search_text: [<N> | NOT_CALLED — VIOLATES_TRIGGER]
search_patterns: [<N> | NOT_CALLED — VIOLATES_TRIGGER]
scan_secrets: [<N> hits | NOT_CALLED — VIOLATES_TRIGGER]
audit_scan: [<N> | NOT_CALLED — VIOLATES_TRIGGER]
k5_mode: [scanned(trivy) | scanned(grype) | N/A(no-scanner) | N/A(pull-declined)]
postamble:
report_md: [written | NOT_WRITTEN]
report_json: [written | NOT_WRITTEN]
retros_log_appended: [yes(bytes_added=N) | NOT_APPENDED]
retros_md_appended: [yes(entry_count=N) | NOT_APPENDED]
verify_audit_pass: [yes(<verified>/<total>) | NOT_RUN | REJECTED]
gate_status: [PASS | FAIL — <which gates missing>]If gate_status = FAIL → VERDICT = INCOMPLETE.
Append the Run line via the retro-gated wrapper (NOT direct >> runs.log):
printf '%b\n' "$RUN_LINE" | ~/.zuvo/append-runlogRun: <ISO-8601-Z> container-audit <project> <N-critical> <N-total> <VERDICT> - <N>-dimensions <NOTES> <BRANCH> <SHA7> <INCLUDES> <TIER>
Retrospective (REQUIRED)
Follow the retrospective protocol from retrospective.md. Gate check → structured questions →
TSV emit → markdown append. If gate check skips: print "RETRO: skipped (trivial session)".
After printing this block, append the Run: line value (without the Run: prefix) to the log
file path resolved per run-logger.md.
VERDICT: FAIL if any critical GATE trips OR ≥4 critical findings; WARN if 1-3 critical findings and no gate tripped; PASS if 0 critical findings. (A tripped critical gate always wins over the finding-count band — one tripped K2 is FAIL, not WARN.)
A gate "trips" per the Gate / Fires on table above — on the FINDING, not on Kn=0. Do not
restate the trip condition here as K1=0 OR K2=0 OR K3=0: that is the score-based reading the
table exists to replace, and it makes the gates nearly unreachable (K2 spreads six checks over 18
points, so a root-running privileged container still scores well above 0 and would pass the very
gate meant to fail it). One table, one place — this line points at it.
Execution Notes
- Single-pass inline execution, no sub-agents required (like
env-audit). - Static dimensions (K1-K4, K6) need zero Docker and zero network — CI-safe.
- K5 is the only network-touching dimension and is consent-gated (GATE 3); absent scanner → N/A, never a block.
- Multi-stage: runtime dims judge the final stage; pinning/CVE cover every external base.
- Monorepos: use
[path]to scope to one service. --k8sactivates the K7-K10 registry slots; check logic is reserved (not yet implemented) — a--k8srun today prints the reserved notice and audits K1-K6 only.- K6 merged-config evaluation covers the conventional
*.override.y*mlfile. An explicit multi-file merge (docker compose -f a.yml -f b.yml) is NOT auto-discovered — a known limitation; pass the specific file via--composeto audit it directly. - K3 flags secret-shaped ENV/ARG by NAME pattern;
scan_secrets(value-shaped detection) runs in parallel and catches secrets whose variable name does not match the pattern.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

