Ship, Securely, Ch. 3: Reviewing Results
Part of Ship, Securely, a hands-on DevSecOps book.
Chapter 3. Reviewing Results
SAST — Static Application Security Testing
What it is
SAST analyzes source code without executing it to find risky patterns (e.g., unsanitized inputs, unsafe APIs). In this project we use Semgrep (via the sast target) to scan the Go app and write results to artifacts/semgrep.json.
Why we need it
Catches code-level flaws early, while they’re cheap to fix, and shifts security left. Typical hits: injection, XSS, insecure crypto, unsafe deserialization, etc.
What it shows (how to read it)
Each finding includes a rule/check ID, severity, a message, and a location (file + line). Prioritize by severity and by “tainted data flow” (user input reaching a dangerous sink).
Use:
jq -r '.results[]? | "\(.extra.severity)\t\(.check_id)\t\(.path):\(.start.line)\t\(.extra.message)"' artifacts/semgrep.json | column -t | head
Signal vs noise
- Signal: “High/Critical” issues; user-controlled data in sinks (e.g., HTML output, DB queries).
- Noise: Style or low-confidence patterns that aren’t reachable or relevant. Triaging is normal.
Limits May miss runtime-only bugs and can raise false positives. Tune rules over time.
Makefile link
make sast (and optionally a gate like sast_block if you enable it).
Quick checklist
- Triage High/Critical first.
- Validate the code path (is the finding reachable?).
- Fix or suppress with rationale (minimize ignores).
- Re-run and confirm the artifact is clean(er).
SiC — Secrets in Code
What it is
Search for credentials in code (API keys, tokens, passwords). We use TruffleHog via secrets and write to artifacts/trufflehog.json.
In this book we favor offline, permissive scanning for training.
Why we need it Leaked secrets are a fast path to compromise (cloud keys, DB creds). Catching them before commit/merge is critical.
What it shows (how to read it) Look for the detector (e.g., AWS, GitHub PAT), the file/line, and whether it’s verified (if verification is enabled) or redacted. Example summaries:
jq -r 'select(.Source=="filesystem") | [.Detector, .Path, .RawV2] | @tsv' artifacts/trufflehog.json | head
Signal vs noise
- Signal: Real-looking keys/tokens, especially with known formats.
- Noise: Dummy/test strings, IDs that look random but aren’t secrets.
Limits Pattern/entropy-based: may miss weak-looking secrets or raise false positives. History scans can be noisy (we keep it local and focused for speed).
Makefile link
make secrets (scans app/ only in this starter).
Quick checklist
- If real: remove from code and rotate the secret immediately.
- If false positive: document and suppress carefully.
- Add guardrails (env vars, .gitignore, pre-commit hooks).
- Keep the scanner in CI.
IaC — Infrastructure as Code (Kubernetes, Terraform, etc.)
What it is
Static analysis of infra config for misconfigurations. We use Checkov to scan Kubernetes manifests and output artifacts/checkov.json.
Why we need it A secure app can be undone by an insecure deployment (e.g., privileged pods, public buckets, no encryption).
What it shows (how to read it)
Look for FAILED checks, their resource (e.g., Deployment), file/line, and recommended fix/guideline. Start with high-severity misconfigs: privileged containers, missing runAsNonRoot, lack of readOnlyRootFilesystem, wide network policies, etc.
jq -r '.results.failed_checks[]? | [.check_id, .check_name, .file_path, .resource] | @tsv' artifacts/checkov.json | column -t
Signal vs noise
- Signal: Privilege escalation, root containers, public exposure, missing auth/enc.
- Noise: Pure best-practices with low risk in your context (document if skipping).
Limits Template-only view (lacks runtime context), may need exceptions for valid deviations.
Makefile link
make iac
Quick checklist
- Fix high-risk misconfigs first.
- Apply secure defaults in base templates.
- Document justified exceptions (with reasons).
- Re-scan to confirm.
SBOM — Software Bill of Materials
What it is
An inventory of components (OS packages, libraries, modules). We use Syft to generate artifacts/sbom.json.
Why we need it Visibility: answer “What’s in our software?” It’s essential for rapid CVE impact checks, license review, and supply-chain transparency.
What it shows (how to read it) Base image OS and version, language/runtime versions, third-party packages. Use it to answer “Do we include X?” and to feed SCA.
jq -r '.artifacts[]? | [.name, .version, .type] | @tsv' artifacts/sbom.json | column -t | head
Signal vs noise
- Signal: Presence and versions of major components (OS, language runtime, key libs).
- Noise: Long tail of transitive packages (query as needed; don’t read linearly).
Limits Snapshot in time; may miss unusual binaries; large but queryable.
Makefile link
make sbom
Quick checklist
- Verify key components (OS, runtime) match expectations.
- Store SBOM as a release artifact.
- Use SBOM to drive SCA and incident response.
- Consider SBOM diffs per release.
SCA — Software Composition Analysis (Vulnerability Scanning)
What it is
Scan dependencies/OS packages for known CVEs. We use Trivy to scan the built image into artifacts/trivy-image.json.
Why we need it Most risk comes from third-party code. SCA flags known-bad versions and points to fixed versions.
What it shows (how to read it) For each vulnerable package: CVE ID, severity, installed vs fixed version, and references. Prioritize Critical/High. Example:
jq -r '.Results[]?.Vulnerabilities[]? | "\(.Severity)\t\(.PkgName)@\(.InstalledVersion)\tfix:\(.FixedVersion // "none")\t\(.VulnerabilityID)"' artifacts/trivy-image.json | column -t | head
Signal vs noise
- Signal: Critical/High vulns in reachable components.
- Noise: Low/Medium in unused tools; duplicates; “no fix” items (track & mitigate).
Limits Depends on vuln DB freshness; zero-days won’t appear; remediation can require upgrades.
Makefile link
make sca
Action plan
- Triage Critical/High immediately; note fixes.
- Upgrade base image / libs; re-scan.
- Mitigate or accept risk temporarily with rationale if no fix.
- Automate regular updates (e.g., Dependabot) and nightly scans.
DAST — Dynamic Application Security Testing
What it is
Probe the running app like an external attacker. We use OWASP ZAP (baseline) via zap and save reports under artifacts/zap/.
Why we need it Finds runtime issues: reflected XSS, missing headers, auth/logic exposure, misconfig in real responses.
What it shows (how to read it) Alerts grouped by type with risk level, description, solution, and example URLs. Expect common findings like missing security headers (CSP, X-Frame-Options). Treat High (e.g., exploitable injection) as urgent; fix Mediums for hardening.
Signal vs noise
- Signal: Confirmed vulns (XSS/SQLi), exposed admin endpoints, auth bypass.
- Noise: Info-level headers, duplicate URLs, occasional false positives—verify manually.
Limits Coverage depends on crawl/auth; can be slow or noisy; logic flaws may need manual testing.
Makefile link
make zap (runs after deploy in make cd)
Quick checklist
- Prioritize High/Medium alerts; reproduce locally.
- Add/adjust headers (CSP, X-Frame-Options, CORP, HSTS) or sanitize inputs.
- Re-scan after fixes.
- Schedule deeper scans periodically; script auth for protected areas if needed.
Evidence & Index (Optional but Recommended)
Bundle current artifacts and generate an index for quick review and sharing.
Make targets (optional additions):
make evidence→artifacts/evidence-YYYY-MM-DD.zipmake artifacts-index→artifacts/ARTIFACTS.md
These help you hand a reviewer/manager a portable proof-pack.
Putting It All Together
Each artifact covers a different angle:
- SAST → your code
- SiC → your secrets
- IaC → your infra configs
- SBOM → your ingredients
- SCA → known CVEs in those ingredients
- DAST → your live surface
Run them, read them, fix the high-signal items, and iterate. Over time you’ll tune rules, reduce noise, and ship with a clean, auditable security posture—locally, on your laptop.
Chapter 3. Vulnerability Management
Approaches to Handling Scanner Results
Teams typically choose one of three models. Pick the one that matches your risk tolerance and delivery needs.
| Approach | Description | Pros | Cons | When to use |
|---|---|---|---|---|
| A. Block All | Fail the build on any scanner finding. | Maximal caution. Nothing slips by. | High noise → developer fatigue. False positives block work. | Small codebases, demo-only, or very high-risk environments. |
| B. Tuned Blocking | Fail only on specific high-confidence, high-impact findings. Log the rest. | Catches the worst issues early. Preserves velocity. | Requires policy + tuning per tool. | Most teams. Good balance of safety + flow. |
| C. Post-Pipeline Tracking | Never fail the build; route all findings into a tracker and work them off by SLA. | Zero “scanner broke my PR” moments. Full context triage. | Risks being ignored unless you enforce SLAs & reviews. | When velocity is critical and you have disciplined triage/SLAs. |
Recommendation: Start with B (Tuned Blocking). Block only on issues you’re confident about; track everything else. Revisit thresholds as the team’s signal/noise improves.
When (and How) to Block — By Tool
Use these practical policies to decide what should actually fail the pipeline.
Secrets (TruffleHog)
- Block if: A finding is clearly a real credential (e.g., verified by the tool or matches a high-confidence detector you trust).
- Don’t block if: Ambiguous/entropy-only strings. Log and review.
- Tuning tips:
- Restrict scope (we scan
app/only). - Add allowlists for known fake/test keys.
- Restrict scope (we scan
- Make target:
make secrets→artifacts/trufflehog.json.
SAST (Semgrep)
- Block if: Rules tagged High/Critical or OWASP Top 10 patterns that are obviously exploitable (e.g., reflected XSS sinks, SQLi).
- Don’t block if: Style-only or low-confidence rules.
- Tuning tips:
- Keep a small curated ruleset in
tools/semgrep/. - Use
// nosemgrepsparingly with a comment explaining why.
- Keep a small curated ruleset in
- Make target:
make sast→artifacts/semgrep.json.
IaC (Checkov)
- Block if: Severe misconfigs (privileged pods,
runAsRoot, missingreadOnlyRootFilesystem, overly broad RBAC, public exposure). - Don’t block if: Cosmetic tags/labels or org-specific exceptions with justification.
- Tuning tips:
- Inline suppressions with reason (e.g.,
# checkov:skip=CKV_K8S_...: reason). - Maintain a small policy file of allowed skips.
- Inline suppressions with reason (e.g.,
- Make target:
make iac→artifacts/checkov.json.
SCA / Container Vulns (Trivy)
- Block if: Critical (and optionally High) vulns with fixes present—especially in the base image or critical runtime libs.
- Don’t block if: “Unfixed” items (track them), dev-only deps, or non-runtime paths.
- Tuning tips:
- Use flags like
--ignore-unfixedand a.trivyignorefor accepted CVEs. - Pin and regularly update base images.
- Use flags like
- Make target:
make sca→artifacts/trivy-image.json.
DAST (ZAP)
- Block if: For gated environments only (staging/prod) and confirmed exploitable Highs (rare for baseline).
- Default here: Don’t block; treat as actionable feedback for hardening (headers, input handling).
- Make target:
make zap→artifacts/zap/.
False Positives & Tuning (Quick Guide)
| Tool | Typical noise | How to tune |
|---|---|---|
| TruffleHog | Random high-entropy strings, test secrets | Limit to app/; prefer verified detectors for blocking; add explicit allowlists. |
| Semgrep | Low-severity style checks; matches in tests/generated code | Keep a curated ruleset; add // nosemgrep with justification; .semgrepignore for paths. |
| Checkov | Context-missing flags (e.g., enforcement elsewhere) | Inline # checkov:skip=...: reason; central list of accepted skips; target only high-risk rules for blocking. |
| Trivy | Large lists of Medium/Low or no-fix vulns | --severity HIGH,CRITICAL, --ignore-unfixed, .trivyignore; focus on runtime-reachable packages. |
| ZAP | Info-level headers, duplicates | Treat as “advise”; confirm manually; add headers (CSP, HSTS, XFO) and re-run. |
Tip: After your first run, invest 30–60 minutes tuning; you’ll earn that time back every day.
Vulnerability Management Lifecycle
Triage
Classify new findings: severity, exploitability, exposure (internet-facing?), and environment (prod vs. dev). Decide: Critical/High (now), Medium/Low (backlog), False Positive, Info.Assign / Ownership
Create a ticket (or use DefectDojo assignment) and give each finding an owner. Add a target due date based on severity (e.g., Critical ≤ 7 days, High ≤ 30 days).Validate
Reproduce or verify the issue. Mark False Positive if it’s not real; otherwise confirm scope. Add repro steps or links for the assignee.Remediate / Mitigate / Accept
- Fix (preferred): Code change, dependency upgrade, or config hardening.
- Mitigate: Temporary control (WAF rule, feature flag).
- Risk accept: Document rationale + an expiry/review date.
Retest
Re-run the relevant scan; confirm the finding disappears. Keep evidence (screenshot/report snippet or commit SHA).Close & Document
Close the item; note the fix and any preventative measures (e.g., new rule, pre-commit hook, pinned base image). Review accepted risks periodically.
What “Good” Looks Like (Checklist)
- Scans automated in CI (
sast,secrets,iac,sbom,sca,zapas needed). - Clear blocking policy documented and implemented (only high-signal issues fail the build).
- Exception flow exists (inline suppressions with reasons; reviewable allows).
- Single source of truth for findings (DefectDojo, issues, or a shared tracker).
- Regular triage cadence; every finding has an owner and due date.
- SLAs by severity (e.g., Critical ≤ 7d, High ≤ 30d) and reported on.
- Retest required before closure; evidence stored in
./artifacts(or tracker). - False positives managed (marked, suppressed, tuned at the tool).
- Risk acceptances documented with expiry/review.
- Metrics (open by severity, age, SLA compliance) reviewed; continuous tuning.
Practical Next Steps in This Repo
Start with Tuned Blocking:
- Secrets: block only high-confidence.
- SAST/IaC: block critical rules; log others.
- SCA: block Critical (and optionally High) with fixes.
Add a Findings Triage readme page for your team (who triages, when, SLAs).
(Optional) Bring up DefectDojo via Docker Compose and import today’s
artifacts/*.json.Track & close 2–3 issues end-to-end to practice the lifecycle.
You now have a clear, practical path from “scans produced findings” to “we handled them, and can prove it.”
Chapter 3. Triage
Plan (short & actionable)
Scope parsed:
artifacts/semgrep.json,trufflehog.json,checkov.json,trivy-image.json,sbom.jsonCode & k8s:app/main.go,app/Dockerfile,infra/k8s/deployment.yaml,service.yaml,kustomization.yamlTop issues to fix now (P0/P1):
- SCA (Trivy): 1× HIGH CVE in Go stdlib (Go 1.22.12) with fixes available.
- IaC (Checkov): missing readiness/liveness probes, service account token hardening, seccomp, caps drop, default namespace, image digest, imagePullPolicy, network policy.
- Secrets (TruffleHog): AWS key pattern in
app/main.go, unverified canary (training string) — clean up or allowlist. - SAST (Semgrep): “HTTP server without TLS” warning (local dev) + we know a reflected XSS in
/echo(not flagged by the current ruleset) — we’ll remediate.
Retest targets:
make sca iac sast secrets cd zapthen capture deltas under./artifacts.
Triage (actual findings)
| Tool | Finding (short) | Evidence | Priority | Rationale |
|---|---|---|---|---|
| Trivy (SCA) | CVE-2025-47907 in Go stdlib v1.22.12; fixed in 1.23.12 / 1.24.6 | artifacts/trivy-image.json → HIGH (1) | P0 | Known high with fix; affects runtime (Go stdlib). |
| Checkov (IaC) | Default namespace used | CKV_K8S_21 in infra/k8s/deployment.yaml | P1 | We apply into ship at runtime, but static file lacks metadata.namespace → fail. |
| Checkov (IaC) | No readiness/liveness probes | CKV_K8S_8, CKV_K8S_9 | P1 | Basic resilience & security posture. |
| Checkov (IaC) | SA token hardening | CKV_K8S_38 | P1 | Add automountServiceAccountToken: false + SA binding as needed. |
| Checkov (IaC) | Seccomp not set | CKV_K8S_31 | P1 | Add seccompProfile: RuntimeDefault. |
| Checkov (IaC) | Capabilities not explicitly dropped / NET_RAW | CKV_K8S_37, CKV_K8S_28 | P1 | Explicitly drop ALL; add only what’s needed (likely none). |
| Checkov (IaC) | Image pull policy & digest | CKV_K8S_15, CKV_K8S_43 | P2 | In dev this is flexible; still recommend a digest pin pattern for prod chapter. |
| Checkov (IaC) | No NetworkPolicy | CKV2_K8S_6 | P1 | Add minimal default deny + allow app ingress/DNS egress. |
| TruffleHog (Secrets) | AWS canary token string in code, Verified=false | artifacts/trufflehog.json → file /repo/app/main.go:28 | P1 | Training string; remove or allowlist to prevent noise. |
| Semgrep (SAST) | HTTP server without TLS (WARNING) | artifacts/semgrep.json | P2 | Dev cluster uses Ingress/Tunnel; note TLS termination guidance. |
| Known (not flagged) | Reflected XSS at /echo | Code review app/main.go | P1 | Intentionally insecure; we’ll fix now and show DAST improvement. |
Validate (quick proofs)
- CVE-2025-47907 present: Trivy HIGH lists
stdlib v1.22.12with fixed versions1.23.12 / 1.24.6. Ourgo.modisgo 1.22; build base image isgolang:1.22-alpine. - K8s misconfigs: Checkov flags map to manifest conditions (namespace missing, no probes, no seccomp, etc.).
- Secrets:
Raw&RawV2include AWS-looking key;Verified=falseandis_canary=trueinExtraData→ safe training token, still noisy. - XSS:
/echo?input=<script>alert(1)</script>will reflect unescaped input (seefmt.Fprintf(w, "You said: %s", input)).
Remediation (concrete diffs/snippets)
Keep changes minimal, safe, and easy to teach. I’ve split by domain.
1) SCA: upgrade Go toolchain & runtime image
app/Dockerfile (build base)
Change base to a fixed Go stream (e.g., 1.24.6):
# build stage
-FROM golang:1.22-alpine AS build
+FROM golang:1.24.6-alpine AS build
WORKDIR /src
COPY . .
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /out/server ./main.go
(Optionally keep alpine runtime, or use gcr.io/distroless/static:nonroot in a later “golden images” chapter.)
Retest: make build sca → confirm HIGH count drops to 0.
2) App: fix reflected XSS & add basic headers
app/main.go — escape user input and set common headers:
import (
"fmt"
"html/template"
"html" // add
"log"
"net/http"
"os"
)
mux.HandleFunc("/echo", func(w http.ResponseWriter, r *http.Request) {
input := r.URL.Query().Get("input")
w.Header().Set("Content-Type", "text/html; charset=utf-8")
// Security headers (baseline)
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("X-Frame-Options", "DENY")
w.Header().Set("Referrer-Policy", "no-referrer")
// Optional minimal CSP for demo
w.Header().Set("Content-Security-Policy", "default-src 'self'")
// Escape untrusted input to prevent reflected XSS
fmt.Fprintf(w, "You said: %s", html.EscapeString(input))
})
Retest: make cd && make zap → XSS should no longer be reported (and manual <script> should render as text).
3) Secrets: remove canary key from code (or allowlist)
If it’s in the source (line ~28), delete it or replace with a clearly fake, non-pattern string. If you need it for teaching, add a comment and allowlist it in your TruffleHog config later so CI doesn’t nag students.
Retest: make secrets → verified_secrets: 0, no AWS key finding.
4) K8s Hardening (pass key Checkov rules)
infra/k8s/deployment.yaml
Add namespace, SA token control, seccomp, drop caps, probes, and (optionally) imagePullPolicy:
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
namespace: ship # add to satisfy CKV_K8S_21
labels: { app: app }
spec:
replicas: 1
selector: { matchLabels: { app: app } }
template:
metadata: { labels: { app: app } }
spec:
automountServiceAccountToken: false # CKV_K8S_38
securityContext:
seccompProfile:
type: RuntimeDefault # CKV_K8S_31
containers:
- name: app
image: ship-securely/app:dev
+ imagePullPolicy: Always # CKV_K8S_15 (dev-friendly but satisfies rule)
ports: [{ containerPort: 3000 }]
securityContext:
runAsUser: 10001
runAsGroup: 10001
runAsNonRoot: true
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
+ capabilities:
+ drop: ["ALL"] # CKV_K8S_37 / CKV_K8S_28
resources:
requests: { cpu: "50m", memory: "64Mi" }
limits: { cpu: "250m", memory: "256Mi" }
+ readinessProbe:
+ httpGet: { path: /healthz, port: 3000 }
+ initialDelaySeconds: 2
+ periodSeconds: 5
+ livenessProbe:
+ httpGet: { path: /healthz, port: 3000 }
+ initialDelaySeconds: 5
+ periodSeconds: 10
NetworkPolicy (satisfy CKV2_K8S_6): add default deny + allow app ingress & DNS egress:
# infra/k8s/netpol/00-default-deny.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
namespace: ship
spec:
podSelector: {}
policyTypes: ["Ingress", "Egress"]
# infra/k8s/netpol/20-allow-app-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-app-ingress
namespace: ship
spec:
podSelector:
matchLabels: { app: app }
ingress:
- from:
- podSelector: {} # relax as needed for demo
ports:
- protocol: TCP
port: 3000
# infra/k8s/netpol/10-allow-dns-egress.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-egress
namespace: ship
spec:
podSelector: {}
policyTypes: ["Egress"]
egress:
- to:
- namespaceSelector: {}
ports:
- protocol: UDP
port: 53
(You already have netpol-apply in your full Makefile; for the starter, include these three files and a simple make target later in the book.)
Retest: make iac → Checkov failed set should drop (probes, seccomp, SA token, caps, default ns, netpol).
Note on image digest (
CKV_K8S_43): For the local-only starter, we can document digest pinning and enforce it in the “golden images” chapter (students pin a digest when they have a registry). We’ll mark this P2 for now.
Retest (commands to run)
# Rebuild and rescan image after Go upgrade
make build sca
# Re-scan code and secrets after app/main.go changes
make sast secrets
# Re-apply K8s and re-scan IaC
make cd
make iac
# Optional: run ZAP baseline to confirm no reflected XSS
make zap
Capture summaries (before/after) into ARTIFACTS.md, and drop a dated bundle via your evidence target (if present).
Close & Document
Closed (fixed):
- CVE-2025-47907 (Go stdlib) — fixed by upgrading to
golang:1.24.6-alpine; Trivy HIGH=0. - Reflected XSS in
/echo— fixed byhtml.EscapeStringand security headers; ZAP/Manual confirm. - Checkov: namespace, probes, seccomp, caps drop, SA token, netpol — fixed with manifest edits;
make iacclean for these checks. - TruffleHog AWS token — removed or allowlisted with clear comment (no verified secrets).
- CVE-2025-47907 (Go stdlib) — fixed by upgrading to
Accepted/Deferred (documented with reason & revisit date):
- Image digest pinning (
CKV_K8S_43) in local starter — defer to “Golden Images” chapter; add note in text and set a reminder to enforce later.
- Image digest pinning (
Prevention:
- Keep Go base image current; consider pinning digests in golden-image workflow.
- Keep Semgrep rules curated; add
.semgrepignoreif needed for tests/gen code. - Maintain
.trivyignorefor vetted, low-risk CVEs if/when needed. - Preserve K8s hardening defaults in base manifests; teach overrides deliberately.
Quick “diff of impact” (what should change in artifacts)
artifacts/trivy-image.json:HIGHcount from 1 → 0.artifacts/semgrep.json: still a TLS warning (expected in local dev), but no XSS path (it wasn’t flagged before; now provably mitigated).artifacts/trufflehog.json: no AWS detector hit /verified_secrets=0.artifacts/checkov.json: failures forCKV_K8S_8/9/31/37/28/38/21/CKV2_K8S_6drop.artifacts/zap/: XSS alert (if any) gone; headers findings likely improved.
Chapter 3. Retesting
Artifacts Delta — Current vs Previous
This table summarizes what changed in the security artifacts after the latest fixes.
Use the “Verify” commands (right column) to reproduce the numbers locally.
| Tool | Metric (scope) | Before | After | Δ | Evidence file(s) | Verify locally |
|---|---|---|---|---|---|---|
| Trivy | High vulns (image) | 1 | 0 | −1 | artifacts/trivy-image.json | `jq ‘[.Results[]?.Vulnerabilities[]? |
| Semgrep | Findings total (Go app) | N/A | N/A | — | artifacts/semgrep.json | `jq ‘.results? |
| Semgrep | XSS (reflected) | (not flagged) | (not flagged) | — | (manual validation) | Visit http://…/echo?input=<script>alert(1)</script> → script should not execute |
| TruffleHog | Verified secrets (app/ only) | 0 | 0 | 0 | artifacts/trufflehog.json | `jq -c ‘select(.Source==“filesystem” and (.Verified==true))’ artifacts/trufflehog.json |
| Checkov | Failed checks (K8s manifests) | X | Y | ΔX | artifacts/checkov.json | `jq ‘.summary? // {} |
| Checkov | Key hardening present (seccomp, caps drop, probes, SA token, ns) | present | — | infra/k8s/deployment.yaml | `grep -nE ‘seccompProfile | |
| ZAP | High / Medium alerts (baseline) | 0 / M | 0 / M’ | — | artifacts/zap/report.json (if present) | `jq -r ‘[.site[]?.alerts[]? |
Notes
- Trivy: expect High=0 after upgrading the Go toolchain/base image (e.g.,
golang:1.24.6-alpine). - Semgrep: our fix removes the reflected XSS behavior (manual validation); your ruleset may not have flagged it originally.
- TruffleHog: canary strings should be removed or allow-listed; we block only on verified credentials.
- Checkov: after adding probes, seccomp, caps drop, SA token hardening, namespace, and basic NetPols, failures should drop significantly.
- ZAP: baseline tends to report header hygiene as Medium/Low; after adding headers (CSP/XFO/XCTO/HSTS*), counts should reduce (HSTS only with TLS).
How to Refresh This Table
Capture a “before” snapshot (optional if you already have one):
rm -rf artifacts_before && mkdir -p artifacts_before cp -a artifacts/* artifacts_before/ 2>/dev/null || trueApply Fixes and re-run scans:
make build sca
make sast secrets
make iac
make cd
make zap
- Make delta table
bash scripts/artifacts_delta.sh > artifacts/ARTIFACTS.md
echo "Wrote artifacts/ARTIFACTS.md"
How to reproduce
optional: snapshot current as “before”
rm -rf artifacts_before && mkdir -p artifacts_before cp -a artifacts/* artifacts_before/ 2>/dev/null || true
regenerate table
bash scripts/artifacts_delta.sh > artifacts/ARTIFACTS.md
Makefile convenience target
Add this to your starter Makefile so readers can do make artifacts-delta:
artifacts-delta:
@bash scripts/artifacts_delta.sh > artifacts/ARTIFACTS.md
@echo "Wrote artifacts/ARTIFACTS.md"
Chapter 3. Checklist
Codify how you raise bars safely and reproducibly.
Gates & Ratcheting
- Ratcheting plan: What’s your 6-week path from WARN→ENFORCE? (document week-by-week)
- Exit codes: Which tools produce non-zero exit on gate fail (Trivy, Semgrep, Checkov)? Who can override?
Base Images & Digests
- Golden base: Which base images are allowed per language? (e.g., distroless, alpine)
- Digest pinning: Will you pin digests for base & runtime? Where are pins recorded/updated?
- Update cadence: Who bumps base images and how often (weekly, monthly)?
Signing & Provenance
- Signing mode: Offline blob signing vs registry signature — which is feasible in your environment?
- Keys: Who holds
cosign.key/cosign.pub? How are they backed up/rotated?
SBOM Diff & Licenses
- Diff trigger: On release candidate or every merge to main?
- License allowlist: What is your current allow/deny? Who approves exceptions?
Mirrors & Caching
- Dependency mirrors: Will you run a local Go proxy (and/or npm cache)? Who owns uptime/space?
- Air-gapped plan: If internet egress is cut, can builds still succeed with mirrors + pinned bases?
Promotion & Evidence
- Promotion rule: Define “promote on pass” criteria (which gates must be green).
- Evidence bundling: Who packages evidence ZIPs and where are they archived?