Exam Prep · CDP · professional · command & tool reference

The Command & Tool Reference

The CDP is five live challenges against a real environment, six hours on the clock, no multiple choice, and — as the exam guide covers — no chatbot or AI assistant allowed anywhere in that window. Whatever documentation you can technically reach, hunting for the exact flag that turns a scan into a build-failing gate is time a six-hour, five-challenge clock doesn't forgive. This page is the muscle memory: every command on it is real and copy-pasteable, organized by the stage of the pipeline it belongs to — threat modeling, secrets, SAST, SCA, DAST, container signing, IaC policy, Kubernetes admission, compliance, cloud posture, and the aggregation/detection layer underneath all of it. Read it beside Know It Cold for the concepts these commands assume, and the triage playbook for when a scan isn't behaving the way you expected.

☺ Explain it like I'm 10

A mechanic who's good at their job doesn't stop to read the manual before picking up a wrench — they already know which wrench fits which bolt, because they've done it a thousand times. This page is that mechanic's toolbox, labeled and organized: not a manual to read during the repair, but the exact motions to have ready before the car's even up on the lift. Practice the motions now, so on exam day your hands already know which tool to reach for the instant you read what's broken.

🦫🐰Your hosts for this topic: Benny the Beaver & Remy the Rabbit — Benny knows every tool in the shed and exactly which job it's built for, and Remy is pure speed: fast recall, no hesitation, never fumbling for the same flag twice.
⚠ Verify tool versions yourself

Flag names and subcommand shapes on this page are accurate as written and drawn directly from this course's own tool pages — but every tool here ships new releases regularly, and a few (Trivy's config subcommand, Crossplane-adjacent policy tooling, Rego's v0-to-v1 syntax cutover) have visibly reorganized their CLI surface across major versions before. If a flag on this page doesn't match what's installed in front of you, <tool> --help is on the machine and is authoritative for the exact version you're running — trust it over this page, and over any tutorial older than the release you're on.

1 · The habit that matters more than any single flag

☺ Like you're 10: A scan that finds a real problem and still lets the build through isn't broken software — it's a build that was never actually told "stop if you see this."

Almost every tool on this page defaults to exiting 0 — success — regardless of what it found. Findings alone don't fail a build; a severity threshold plus an exit-code flag is what turns a report into a gate, and forgetting that flag is the single most common reason a team believes their pipeline is enforcing something it has never actually enforced. This isn't a one-off gotcha specific to one tool — it repeats identically across Trivy, Conftest, Snyk, Dependency-Check, and OpenSCAP, which is exactly why it gets its own table later on this page rather than staying buried inside each tool's own section.

The loop, every task, every tool 1 · Scope image · fs · dir cluster · repo 2 · Run tool + real subcommand 3 · Threshold --severity HIGH,CRITICAL 4 · Exit code --exit-code 1 NOT the default 5 · Verify re-run exit 0, clean next finding — reset scope, run again Skipped step 4? Default exit code is 0 on most tools here — a "gate" nobody actually verified is not a gate Step 4 is the one that turns a correct scan into a scored fix.

2 · Threat modeling — OWASP Threat Dragon

☺ Like you're 10: This one's mostly point-and-click, not typing — but the file it saves is a real JSON document you can read, diff, and check into git like anything else.

Threat Dragon is diagram-driven, not CLI-driven, so there's less muscle memory to drill than the rest of this page — but knowing how to stand it up fast, and what the saved model actually looks like underneath the diagram, is worth having cold.

# Desktop edition — no server, no account, fully offline. Download the signed
# .dmg / .exe / .AppImage from the project's GitHub Releases page ahead of time.

# Web edition, self-hosted:
$ docker run -p 3000:3000 owasp/threat-dragon:latest
# open http://localhost:3000 — choose "local" storage to skip a Git provider
# entirely, or sign in with GitHub/GitLab/Bitbucket to read and write straight to a repo

A saved model is a plain JSON file with a summary block and a detail block holding a diagram's nodes (processes, data stores, external entities, trust boundaries) and edges (data flows) — each threat lives attached to whichever element it applies to, tagged against STRIDE. That's the shape worth recognizing if a task hands you an existing .json model and asks you to add a threat to it by hand rather than through the GUI. See threat modeling for STRIDE itself, and the Threat Dragon tool page for the full model schema.

3 · Secrets detection & management — gitleaks, TruffleHog, Vault

☺ Like you're 10: Two tools go looking for a secret that already escaped into git history; a third one makes sure secrets never have to be typed into a file in the first place.

gitleaks — full-history and pre-commit scanning

# full history walk against the default embedded ruleset
$ gitleaks detect --source . -v

# working tree only, ignoring git entirely (non-git directories)
$ gitleaks detect --source . --no-git --no-banner

# a repo-local rule/allowlist file instead of the embedded defaults
$ gitleaks detect --source . --config .gitleaks.toml

# the pre-commit hook shape — fires on staged changes only, before the commit lands
$ gitleaks protect --staged --redact -v

Exit code: 1 when leaks are found, 0 when clean — already a real gate by default, unlike most of the rest of this page. The pre-commit hook is a courtesy, not the control: it lives inside .git/, isn't cloned automatically, and is trivially bypassed with git commit --no-verify. The CI-level gitleaks detect run is the one that actually can't be skipped from a developer's own machine.

TruffleHog — verified secrets, not just pattern matches

# full history, every branch, by default
$ trufflehog git file://.

# the flag that matters most for a merge gate: only LIVE-verified hits block
$ trufflehog git file://. --only-verified

# scan a filesystem path, a GitHub org, or an S3 bucket the same way
$ trufflehog filesystem ./
$ trufflehog github --org=acme --only-verified
$ trufflehog s3 --bucket=acme-build-artifacts

TruffleHog's distinguishing move is live verification: a regex match on an AWS-key-shaped string is a candidate, not a finding, until TruffleHog actually calls the issuing provider's API to confirm the credential is still active. --only-verified is the flag that turns "here are 40 things that look like keys" into "here are the 3 that will actually work if someone uses them" — the difference between a noisy gate developers learn to ignore and one they trust.

HashiCorp Vault — so there's nothing left to leak

Vault isn't a scanner; it's the reason a secrets scan should come back clean in the first place — dynamic, short-lived credentials instead of a standing password sitting in a config file.

# dynamic database credentials — minted per request, expire on their own
$ vault secrets enable database
$ vault write database/roles/readonly db_name=orders-postgres \
    creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
    default_ttl=1h max_ttl=24h
$ vault read database/creds/readonly            # mint a fresh, unique credential right now

$ vault lease renew database/creds/readonly/2n8Fh3k9s...   # extend, up to max_ttl
$ vault lease revoke -prefix database/creds/readonly        # incident response: kill EVERY outstanding lease for this role

# transit engine — encryption as a service, the app never holds the key
$ vault secrets enable transit
$ vault write -f transit/keys/orders-pii-key
$ vault write transit/encrypt/orders-pii-key plaintext=$(base64 <<< "4111-1111-1111-1111")
$ vault write transit/decrypt/orders-pii-key ciphertext="vault:v1:8SDd3W..."

# AppRole — how a CI job authenticates without a human typing a password
$ vault auth enable approle
$ vault write auth/approle/role/ci-pipeline token_policies="ci-policy" token_ttl=15m token_max_ttl=30m secret_id_ttl=10m secret_id_num_uses=1

See secrets management for the lifecycle argument in full, and the Vault tool page for the AWS/GCP/Azure and PKI engines that follow the same generate-short-lived-credential-then-revoke pattern.

4 · Static analysis (SAST) — Semgrep, SonarQube, CodeQL

☺ Like you're 10: Three different ways of reading code without running it — pattern matching, a persistent dashboard, and a full logical model of how data actually flows through the program.

Semgrep — pattern-based, fast, diff-aware in CI

# local, exploratory scan against a curated registry pack
$ semgrep scan --config=p/ci .
$ semgrep scan --config=p/owasp-top-ten .
$ semgrep scan --config=.semgrep/rules/acme-db-run-taint.yaml .   # one custom rule file

# semgrep ci — the CI-specific subcommand, diff-aware: only NEW findings on
# this branch vs. the baseline are blocking. Logged-in orgs get their
# configured rulesets automatically, no --config needed.
$ semgrep login
$ semgrep ci

# scope to only what changed since a commit, without the ci subcommand's extras
$ semgrep scan --config=p/ci --baseline-commit=origin/main .

# machine-readable output for a dashboard, DefectDojo, or GitHub code scanning
$ semgrep scan --config=p/ci --sarif -o results.sarif .

# apply a rule's own fix as a reviewable diff — never blind autofix in CI
$ semgrep scan --config=.semgrep/ --autofix --dry-run .

Suppress one line for one rule with // nosemgrep: acme-db-run-unsanitized-query — always name the rule ID; a bare nosemgrep silences every rule on that line, including ones added later. See the Semgrep tool page for writing a custom taint-mode rule against an internal wrapper function.

SonarQube — the persistent dashboard and Quality Gate

# sonar-project.properties — repo root
sonar.projectKey=acme_checkout
sonar.sources=src
sonar.tests=test
sonar.exclusions=**/vendor/**,**/generated/**
sonar.coverage.exclusions=**/*_test.go,**/migrations/**
$ sonar-scanner -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.login=$SONAR_TOKEN

# the Web API — read hotspots and record a review decision, not just click through the UI
$ curl -u "$SONAR_TOKEN:" \
    "$SONAR_HOST_URL/api/hotspots/search?projectKey=acme_checkout&status=TO_REVIEW"
$ curl -u "$SONAR_TOKEN:" -X POST \
    "$SONAR_HOST_URL/api/hotspots/change_status?hotspot=AY...&status=REVIEWED&resolution=SAFE"

SonarQube's gate is the Quality Gate, evaluated server-side, not a CLI flag — a CI step that runs sonar-scanner and then waits on the Quality Gate's own pass/fail webhook, usually via a dedicated "wait for quality gate" action, is what actually blocks a merge. A Security Hotspot is deliberately not the same status as a Vulnerability: a hotspot needs a human to mark it Safe or Fix, and an unreviewed hotspot pile doesn't fail the gate on its own the way an open Vulnerability does — check your Quality Gate's own condition list before assuming hotspots are blocking anything.

CodeQL — extraction, then a real logical query

# phase one: extraction — build (compiled langs) or parse (interpreted langs)
$ codeql database create checkout-db --language=python --source-root=.
$ codeql database create checkout-db --language=java --command="mvn -B clean install"

# phase two: evaluation — run one or more query packs against that database
$ codeql database analyze checkout-db codeql/python-queries --format=sarif-latest --output=results.sarif
$ codeql database analyze checkout-db ./custom-queries/db-run-sql-injection.ql --format=sarif-latest --output=results.sarif

# test a custom query against known-good/known-bad fixtures before it gates anything
$ codeql test run custom-queries/tests/

The two-phase split matters: extraction is not incremental across commits — every database create re-extracts from the current checkout, with no equivalent of Semgrep's --baseline-commit — which is exactly why CodeQL usually runs on a schedule and on merges to main rather than on every push to every branch. See the CodeQL tool page for the Datalog-flavored QL language itself and why compiled-language extraction needs your real build command.

5 · Software composition analysis (SCA) & SBOMs

☺ Like you're 10: Four different tools answer the same question — what's actually inside this thing, and is any of it known-broken — with different trade-offs between speed, depth, and cost.

Trivy — vulnerability, SBOM, and everything else, one binary

# scan a built image for OS + language vulnerabilities, fail the build on HIGH/CRITICAL
$ trivy image --severity HIGH,CRITICAL --exit-code 1 --ignore-unfixed registry.acme.io/checkout:1.8.2

# filesystem / repo targets
$ trivy fs .
$ trivy repo https://github.com/acme/checkout

# SBOM generation — same binary, either dominant format
$ trivy image --format cyclonedx --output sbom.cdx.json registry.acme.io/checkout:1.8.2
$ trivy image --format spdx-json --output sbom.spdx.json registry.acme.io/checkout:1.8.2

# scan an SBOM someone else already generated
$ trivy sbom sbom.cdx.json

# select exactly which scanners run: vuln, misconfig, secret, license
$ trivy fs --scanners vuln,secret,license .

Default exit code is 0 regardless of findings. --exit-code 1 combined with --severity is what turns the scan into a pass/fail check — see the Trivy tool page for the OS-package-vs-language-dependency matching split, and section 7 below for Trivy's misconfiguration scanner (trivy config).

Syft & Grype — SBOM generation and vulnerability matching, kept separate

$ syft ghcr.io/acme/checkout:2.4.0 -o table
$ syft dir:./checkout -o spdx-json > sbom.spdx.json
$ syft ghcr.io/acme/checkout:2.4.0 -o cyclonedx-json=sbom.cdx.json
$ syft convert sbom.syft.json -o cyclonedx-json=sbom.cdx.json     # reformat an existing SBOM

$ grype ghcr.io/acme/checkout:2.4.0
$ grype sbom:sbom.cdx.json                    # match an existing SBOM, don't re-catalog
$ grype ghcr.io/acme/checkout:2.4.0 --fail-on high   # the CI gate flag — grype's own --exit-code equivalent

Snyk — commercial SCA, container, and IaC

$ snyk auth
$ snyk test                                    # SCA against the current manifest
$ snyk test --all-projects --severity-threshold=high
$ snyk container test node:18-alpine --file=Dockerfile
$ snyk iac test main.tf
$ snyk fix                                      # apply the computed upgrade to the manifest/lockfile

# monitor = snapshot + future alerting. NEVER fails a build — it always exits 0.
$ snyk monitor

snyk test and snyk monitor get confused constantly: test is the point-in-time CI gate; monitor uploads a snapshot for later alerting and will never block a merge on its own.

OWASP Dependency-Check — the free, offline-capable baseline

# scheduled cache warm — does NOT scan anything, never fails a build
$ dependency-check.sh --updateonly --nvdApiKey "$NVD_API_KEY" --data /shared/odc-data

# the real pipeline scan — reads the warm cache, never touches the network for data
$ dependency-check.sh --project checkout --scan ./target/*.jar \
    --data /shared/odc-data --noupdate --failOnCVSS 7 --out ./odc-report --format ALL

--failOnCVSS 7 is Dependency-Check's exit-code gate — a numeric CVSS floor rather than a HIGH/CRITICAL label. See the Dependency-Check tool page for the NVD API key requirement and why the split between --updateonly and --noupdate exists at all: an unauthenticated NVD pull is rate-limited hard enough that a shared warm cache is close to mandatory in CI.

6 · Dynamic analysis (DAST) — OWASP ZAP, Burp Suite

☺ Like you're 10: Nothing here reads source code — it's all attacking the running application the way an outside visitor would, so the target has to actually be up before any of these commands mean anything.

OWASP ZAP — baseline, full, and API-driven scans

# baseline: spider for ~1 minute, then only the PASSIVE scanner — no attack payloads sent
$ docker run --rm -v $(pwd):/zap/wrk/:rw -t zaproxy/zap-stable \
    zap-baseline.py -t https://staging.internal.example.com \
    -r zap-baseline-report.html -J zap-baseline-report.json -I
    # -I: don't fail the build on WARN-level results while a rule set is being tuned

# full (active) scan: real SQLi/XSS/path-traversal/command-injection payloads, genuinely attacks
$ docker run --rm -v $(pwd):/zap/wrk/:rw -t zaproxy/zap-stable \
    zap-full-scan.py -t https://staging.internal.example.com \
    -j -r zap-full-report.html -J zap-full-report.json
    # -j: also run the AJAX spider first — needed for a JavaScript-heavy target

# API-driven: seeds the Sites tree from an OpenAPI/GraphQL spec instead of crawling HTML
$ docker run --rm -v $(pwd):/zap/wrk/:rw -t zaproxy/zap-stable \
    zap-api-scan.py -t https://staging.internal.example.com/openapi.json -f openapi

# the Automation Framework — a committed zap.yaml plan, the CI-native replacement for the wrapper scripts
$ docker run --rm -v $(pwd):/zap/wrk/:rw -t zaproxy/zap-stable \
    zap.sh -cmd -autorun /zap/wrk/zap-automation.yaml

The mechanical difference to have cold: baseline never sends a crafted payload — it only inspects traffic the spider's ordinary crawling produced. Full/active takes every discovered parameter and hands it to the Active Scanner, which genuinely attacks. An exitStatus job at the end of a zap.yaml plan (errorLevel/warnLevel) is what drives the exit code — the packaged zap-baseline.py/zap-full-scan.py scripts are themselves just generating that same automation plan behind the scenes on recent releases. See the ZAP tool page for the full zap.yaml job ordering (discovery jobs before passiveScan-wait before activeScan before report/exitStatus) and the authentication/session-handling setup a real scan needs.

⚠ Watch out

An active scan is a genuine attack, not a simulation — never point one at anything without explicit authorization, and never at production unless that authorization specifically covers it. It has no idea what an endpoint's business logic does: it will submit a "delete my account" form or trigger a real password-reset email flood, because from the scanner's point of view that's just a parameter to fuzz.

Burp Suite / Dastardly — the CI-shaped, free path

# Dastardly: PortSwigger's free, lightweight, CI-native scanner — JUnit output, no Burp license needed
$ docker run --rm \
  -e BURP_START_URL="https://staging.internal.example.com" \
  -e BURP_REPORT_FILE_PATH="/dastardly/dastardly-report.xml" \
  -v "$(pwd)":/dastardly \
  public.ecr.aws/portswigger/dastardly:latest

Dastardly's JUnit output slots straight into a CI test-results view alongside the rest of the test suite — the fast, always-on complement to a scheduled, deeper full Burp Suite Professional session run by a human. See the Burp Suite tool page for Intruder's payload-position syntax (§marker§) and where Burp still outruns ZAP on manual, expert-driven testing.

7 · Container hardening & signing — Trivy config, cosign

☺ Like you're 10: First check the blueprint before you build the house; then, once it's built, stamp it so anyone downstream can prove nobody swapped it for something else.

Trivy config — misconfiguration scanning before apply

$ trivy config --severity HIGH,CRITICAL infra/
$ trivy image --scanners misconfig registry.acme.io/checkout:1.8.2   # config-only, skip the vuln DB entirely

# suppress one specific finding with a reason on record
$ cat .trivyignore
AVD-AWS-0107

cosign — keyless signing, verification, and attestation

# sign by digest — NEVER a mutable tag. Keyless: no key file anywhere.
$ cosign sign --yes registry.acme.io/checkout@sha256:1a2b3c...

# verify — the identity flags below are NOT optional for keyless verification;
# without them cosign refuses to run, because an unconstrained verify would
# accept a signature from ANY keyless signer on the planet.
$ cosign verify \
    --certificate-identity="https://github.com/acme/checkout/.github/workflows/release.yml@refs/heads/main" \
    --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
    registry.acme.io/checkout@sha256:1a2b3c...

# attest — wrap a predicate (SBOM, SLSA provenance, a scan result) in a signed in-toto statement
$ cosign attest --yes --predicate sbom.cdx.json --type cyclonedx \
    registry.acme.io/checkout@sha256:1a2b3c...
$ cosign verify-attestation --type cyclonedx \
    --certificate-identity="https://github.com/acme/checkout/.github/workflows/release.yml@refs/heads/main" \
    --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
    registry.acme.io/checkout@sha256:1a2b3c...

# keyed signing — a KMS-backed key instead of keyless OIDC, when a durable org-owned key is required
$ cosign generate-key-pair --kms awskms:///alias/acme-image-signing
$ cosign sign --key awskms:///alias/acme-image-signing registry.acme.io/checkout@sha256:1a2b3c...

# air-gapped prep: fetch Sigstore's trust root (Fulcio CA, Rekor public key) in advance
$ cosign initialize

The chain worth having cold: cosign sign generates an ephemeral keypair, proves identity via OIDC, gets a ~10-minute Fulcio certificate, signs, and discards the key — verify doesn't check the certificate's validity against today's date, it checks that Rekor's recorded timestamp falls inside that original short window. See the Sigstore & cosign tool page for the full Fulcio/Rekor flow and wiring verification into a Kubernetes admission gate (section 9 below covers that gate itself, via Kyverno).

8 · Infrastructure as code & policy as code — Checkov, tfsec, OPA/Conftest

☺ Like you're 10: Two tools read the blueprint looking for a mistake against a built-in rulebook; a third one lets you write your own rulebook from scratch, in a language built for exactly that.

Checkov

# auto-detect every framework under this directory in one pass
$ checkov -d infra/
$ checkov -d infra/ --framework terraform,cloudformation,kubernetes,dockerfile

$ checkov -d infra/ --external-checks-dir ./custom_checks   # custom Python/YAML checks alongside the built-ins
$ checkov -d infra/ --skip-check CKV_AWS_130                # skip one check across the whole run
$ checkov -d infra/ --check CKV_AWS_130                      # inverse: run only a named allowlist

tfsec

$ tfsec infra/
$ tfsec infra/ --config-file .tfsec/config.yml
$ tfsec infra/ --minimum-severity HIGH

tfsec's rule engine has been absorbed into trivy config (section 7); most tfsec-authored rule IDs and the #tfsec:ignore:<ID> inline-suppression comment still work against the merged engine, because the underlying code is now shared rather than reimplemented twice.

OPA & Conftest

# render a Terraform plan to JSON first — raw .tf sees only literal values, not resolved ones
$ terraform show -json plan.tfplan > plan.json
$ conftest test --policy policy/terraform plan.json

$ conftest test --policy policy/kubernetes k8s/*.yaml
$ conftest test --policy policy/kubernetes --combine k8s/*.yaml    # merge every matched file into ONE input array
$ conftest test --policy policy/ --all-namespaces k8s/*.yaml       # evaluate every package under policy/, not just "main"
$ conftest test --policy policy/ --output junit k8s/*.yaml > results.xml

$ conftest push oci://ghcr.io/acme/policies:latest ./policy    # distribute a policy bundle as an OCI artifact
$ conftest pull oci://ghcr.io/acme/policies:latest
$ conftest verify --policy policy/                              # run the policies' OWN unit tests (wraps opa test)

# opa test / opa eval — the raw engine underneath Conftest
$ opa test policy/kubernetes -v
$ opa eval -d policy/ -i input.json "data.main.deny"

Exit code: 0 = no deny hits, 1 = at least one — that's the check a CI step reads. warn hits never affect the exit code, which is the trap: a rule quietly downgraded to warn "temporarily" has effectively been turned off. See the OPA & Conftest tool page for the Rego v0-vs-v1 rule-head syntax cutover (deny[msg] { ... } vs. deny contains msg if { ... }) that breaks a policy silently on a version upgrade.

9 · Kubernetes admission & runtime security — Kyverno, Falco

☺ Like you're 10: One doorman decides whether something's allowed in at all; a second guard keeps watching everything already inside, in case it starts behaving badly after the fact.

Kyverno — admission policy as Kubernetes-native YAML

$ helm repo add kyverno https://kyverno.github.io/kyverno/
$ helm install kyverno kyverno/kyverno -n kyverno --create-namespace

$ kubectl get cpol                          # every ClusterPolicy currently loaded — cpol/pol are the short names
$ kubectl get policyreport -A               # what Audit mode has been logging, cluster-wide
$ kubectl describe cpol require-non-root    # see which rule failed, and why

The one field that decides whether a Kyverno policy does anything at all: spec.validationFailureAction, which defaults to Audit — it logs a violation and lets the bad pod through. Flip it to Enforce only once the PolicyReport backlog it would generate is actually clean, or every existing violator starts failing to deploy the moment you flip it. Newer Kyverno spells this per-rule as spec.rules[].validate.failureAction instead of the policy-level field — confirm which one the installed CRD serves with kubectl explain clusterpolicy.spec.rules.validate.

Falco — runtime syscall detection

$ helm repo add falcosecurity https://falcosecurity.github.io/charts
$ helm install falco falcosecurity/falco \
    --namespace falco --create-namespace \
    --set driver.kind=modern_ebpf \
    --set falcosidekick.enabled=true

$ kubectl logs -n falco -l app.kubernetes.io/name=falco -f    # live rule matches as they fire

Custom rules load from custom_rules.d/, layered on top of the bundled default ruleset, and an exceptions block on an existing rule is the sanctioned way to narrow a noisy default without deleting the rule outright — see the Falco tool page for a real macro/list/rule triple and the eBPF-driver options (module / ebpf / modern_ebpf).

10 · Compliance as code — InSpec, OpenSCAP

☺ Like you're 10: Two different ways to check a machine against a checklist — one is a programming-language DSL you write yourself, the other is a government-published standard you load and run as-is.

InSpec — Ruby DSL, Train transport, any target

$ inspec init profile checkout-baseline
$ inspec check ./checkout-baseline                   # validate structure — no target contacted
$ inspec vendor ./checkout-baseline                    # resolve `depends`, pin inspec.lock

# --target's scheme picks the backend; omit it to check the local machine
$ inspec exec ./checkout-baseline --target ssh://ops@10.0.4.21 -i ~/.ssh/fleet_ed25519
$ inspec exec ./checkout-baseline --target winrm://Administrator@10.0.4.30
$ inspec exec ./checkout-baseline --target docker://checkout
$ inspec exec ./checkout-baseline

# interactive REPL — try a resource live before committing it to a control
$ inspec shell -t docker://checkout

# a full real run: env-specific inputs, a waiver file, two report formats at once
$ inspec exec ./checkout-baseline \
    --target ssh://ops@10.0.4.21 -i ~/.ssh/fleet_ed25519 \
    --input-file inputs-prod.yml \
    --waiver-file waivers.yml \
    --reporter cli json:results/checkout.json junit:results/checkout.xml

A waiver needs --waiver-file on the actual invocation to do anything — the YAML file sitting in the repo enforces nothing on its own. expiration_date in a waiver is enforced by InSpec itself: past that date the control runs for real again, no separate reminder needed.

OpenSCAP — the SCAP standard, DISA STIG content run as-is

# list every profile a datastream actually contains — before trusting any ID from a tutorial
$ oscap info ssg-rhel9-ds.xml

$ oscap xccdf validate ssg-rhel9-ds.xml               # validate the datastream's XML before trusting it

# the core evaluation command
$ oscap xccdf eval \
    --profile xccdf_org.ssgproject.content_profile_stig \
    --results-arf arf-$(hostname).xml \
    --report report-$(hostname).html \
    ssg-rhel9-ds.xml

# apply a tailoring file on top of the base profile it was built from
$ oscap xccdf eval --tailoring-file tailoring.xml --profile stig_bastion_customized \
    --results-arf arf-bastion.xml ssg-rhel9-ds.xml

# generate an Ansible remediation playbook for every FAILING rule — review before running it
$ oscap xccdf generate fix --profile xccdf_org.ssgproject.content_profile_stig \
    --fix-type ansible ssg-rhel9-ds.xml > remediation.yml

The sharpest contrast to keep straight for the exam: standing up STIG evidence in OpenSCAP is oscap xccdf eval against DISA's own datastream, zero controls hand-authored — the same artifact DISA itself expects an assessor to trust. The equivalent in InSpec means adopting a community-maintained translation or hand-writing several hundred controls yourself. See the OpenSCAP tool page for the full InSpec-vs-OpenSCAP comparison table and oscap-vm/oscap-podman for scanning an offline image or container without booting or starting it.

11 · Cloud security posture — Prowler, ScoutSuite

☺ Like you're 10: Both walk every cloud resource one at a time checking for a bad setting — one hands you a compliance-framework-shaped report, the other hands you an interactive HTML map of the whole account.

Prowler

$ pip install prowler
$ prowler aws                                            # whole account, every enabled region, every check

$ prowler aws --role arn:aws:iam::222233334444:role/ProwlerAudit --role-session-name prowler-audit

$ prowler aws --mutelist-file mutelist.yaml              # suppress known-accepted findings, with a reason on record

# re-group the SAME findings by which published framework they map to
$ prowler aws --compliance cis_2.0_aws
$ prowler aws --compliance pci_4.0_aws
$ prowler aws --compliance hipaa_aws
$ prowler aws --compliance soc2_aws

ScoutSuite

$ pip install scoutsuite
$ scout aws --profile prod-readonly                      # uses a named profile from ~/.aws/credentials
$ scout aws --profile prod-readonly --services s3,iam,ec2   # scope to specific services, skip a slow one
$ scout azure --cli
$ scout gcp --project-id acme-prod-217304

ScoutSuite's output is a self-contained interactive HTML report meant for a human to browse and triage, not a CI-blocking exit code — Prowler's --compliance mapping and mutelist-with-a-reason discipline are the pieces built for gating and audit evidence specifically. See cloud security posture for the CSPM concept both tools implement.

12 · Aggregation & detection — DefectDojo, Wazuh

☺ Like you're 10: One tool collects every other tool's homework into a single gradebook; the other keeps watching the running system long after every earlier check already said "looks fine."

DefectDojo — one findings backlog for every scanner on this page

# first import for a Test in an Engagement — DefectDojo recognizes each scanner's native report format
$ curl -s -X POST "https://defectdojo.internal.example.com/api/v2/import-scan/" \
  -H "Authorization: Token $DD_API_TOKEN" \
  -F "product_name=checkout-service" \
  -F "engagement_name=checkout-service · main · build #4821" \
  -F "scan_type=Trivy Scan" \
  -F "file=@trivy-report.json"

# every subsequent run of the SAME Test — reimport, so DefectDojo dedupes and tracks new-vs-closed findings
$ curl -s -X POST "https://defectdojo.internal.example.com/api/v2/reimport-scan/" \
  -H "Authorization: Token $DD_API_TOKEN" \
  -F "test=4821" -F "scan_type=Trivy Scan" -F "file=@trivy-report.json"

scan_type is the string DefectDojo uses to pick the right parser for that tool's native report format — "Trivy Scan," "ZAP Scan," "Semgrep JSON Report," "SonarQube Scan" and dozens more each have an exact expected string; get it wrong and the import silently parses nothing useful rather than erroring loudly. Import creates a fresh Test; reimport against the same Test is what gives DefectDojo the "still open, newly found, or now closed" delta a real vulnerability-management workflow needs — see the DefectDojo tool page and vulnerability management & triage for deduplication across multiple scanners flagging the same underlying CVE.

Wazuh — host-level detection after everything else already shipped

$ /var/ossec/bin/wazuh-control status
$ /var/ossec/bin/wazuh-control restart      # required after editing rules, decoders, or ossec.conf
$ /var/ossec/bin/manage_agents               # legacy CLI for agent key enrollment (authd is the modern path)

Wazuh's own agent-side ossec.conf is where the actual detection surface gets configured — a <syscheck> block with realtime="yes" whodata="yes" for file-integrity monitoring that can attribute a change to the exact process and user that made it, and a manager-side <command>/active-response pairing (like an automatic firewall-drop) for reacting to a detection rather than just logging it. See the Wazuh tool page for the decoder-then-rule pipeline a raw log line passes through before it becomes an alert with a MITRE ATT&CK technique ID attached.

13 · The flags people forget — the gate table

☺ Like you're 10: Almost every lost point on this page traces back to the same one missing flag, tool after tool — the exit code, forgotten.

If you revise nothing else from this page in the last hour before your challenge window opens, revise this table. Every row is a tool that runs cleanly, produces a report, and gates nothing when the flag in the third column is missing.

ToolDefault behaviorThe flag that makes it a gate
TrivyExit 0 regardless of findings--exit-code 1 combined with --severity HIGH,CRITICAL
Conftest / OPAExit 0 on warn, only deny hits change itWrite the rule as deny, not warnwarn never fails the build regardless of flags
Snyksnyk monitor always exits 0 — it's not a gate at allsnyk test --severity-threshold=high is the actual CI check
OWASP Dependency-CheckReports without failing unless told a threshold--failOnCVSS 7 (a numeric floor, not a HIGH/CRITICAL label)
GrypeExit 0 regardless of findings--fail-on high
ZAP (Automation Framework)Completes and reports either wayAn exitStatus job with errorLevel/warnLevel set, as the last job in the plan
Kyverno ClusterPolicyvalidationFailureAction: Audit (default) — logs only, admits the podvalidationFailureAction: Enforce (or per-rule validate.failureAction on newer builds)
InSpec waiverA waivers.yml with no flag pointing at it does nothing — every control runs unwaived--waiver-file waivers.yml on the actual inspec exec invocation
gitleaks / TruffleHog pre-commit hookA courtesy only — bypassed by git commit --no-verifyRun the same scan again as a required CI check; that's the one a developer can't opt out of locally
cosign keyless verifyOlder releases accepted ANY keyless signer with no identity flags at all--certificate-identity and --certificate-oidc-issuer — current cosign refuses to run without them

14 · Drill it — blank terminal, no notes

☺ Like you're 10: Reading this page again feels like studying and mostly isn't. Typing a command from a blank terminal, with nothing open, is the only version of practice the six-hour clock will actually reward.

Recognizing a command you've just read and producing it cold, twenty minutes into a live challenge with a target you've never seen, are different skills — and the exam only tests the second one.

🐰 Remy's drill · 20 min

Close this page. Open a blank terminal, no notes, no browser tab. From memory, write: a trivy image invocation that fails the build on HIGH/CRITICAL and ignores unfixed CVEs; a zap-baseline.py Docker invocation against a staging URL with an HTML and JSON report; an inspec exec against a Docker container target; a cosign sign plus the matching cosign verify with both identity flags; a conftest test against a rendered Terraform plan; and a Kyverno ClusterPolicy skeleton with validationFailureAction: Enforce. Then reopen this page and check every flag name, not just whether the command "looks right." Anything wrong twice goes on a flashcard, not a re-read. Then work it for real against a live pipeline in the capstone lab track.

🎬 At the Shift-Left Squad
🦫

Benny the Beaver: Ran trivy image against the build. Report came back with two CRITICALs. Pipeline's still green though — ship it?

🐢

Timmy the Turtle: Show me the exact command you ran.

🦫

Benny: trivy image registry.acme.io/checkout:1.8.2. That's it.

🐰

Remy the Rabbit: No --exit-code. Default is 0 no matter what it finds — that's not a gate, that's a report nobody's reading.

🦊

Foxy: Same story last week with a Kyverno policy stuck on Audit. It logged the violation and let the pod through anyway.

🐘

Ellie the Elephant: Every one of these tools defaults to "polite" unless you tell it not to be. That's not a bug in any single tool — it's the same trap, over and over.

🦫

Benny: Fine. --severity HIGH,CRITICAL --exit-code 1. Re-running now.

🐢

Timmy: And re-run it again after the fix, so I can see it exit 0 for real — not just believe you.

That's the reference. Fourteen tool categories' worth of real, copy-pasteable syntax and the one flag that repeats across nearly all of them. Pair it with Know It Cold for the concepts underneath these commands, the CDP study plan for where this fits a week-by-week schedule, the practice challenge bank and the mock exam sets for rehearsing the six-hour clock, the triage playbook for when a tool isn't behaving the way this page says it should, and the tool landscape for the fuller page on any single tool here. Then close all of them and open an empty terminal.

🐰 Remy's checkpoint

1. What does Trivy's exit code default to regardless of findings, and which two flags together turn a scan into an actual gate? 2. In a Kyverno ClusterPolicy, which field decides whether a violation is only logged or actually blocks the pod, and what does it default to? 3. Name the flag that makes cosign verify refuse to accept a signature from any keyless signer on the planet, and explain why both parts of it are needed. 4. What's the mechanical difference between snyk test and snyk monitor — and which one can actually fail a build? 5. In Conftest/OPA, what's the difference between a deny rule and a warn rule in terms of what actually happens to the exit code? 6. An InSpec waiver file exists in the repo with a correct expiration_date, but every waived control still fails loudly on the next run. What's the most likely missing piece?

Check your answers
  1. Exit code 0, regardless of what it found. --severity HIGH,CRITICAL (or whichever threshold matters) combined with --exit-code 1 is what makes a qualifying finding actually fail the build.
  2. spec.validationFailureAction (or, on newer Kyverno, the per-rule validate.failureAction). It defaults to Audit — the violation is logged in a PolicyReport but the pod is still admitted. It has to be set to Enforce to actually block anything.
  3. --certificate-identity together with --certificate-oidc-issuer. Both are needed because keyless trust is anchored to "this exact identity, issued by this exact provider" — checking only one half would still leave the door open to a different, unintended signer using the same issuer, or the right issuer vouching for the wrong identity.
  4. snyk test is a point-in-time gate — it exits non-zero on a qualifying finding and is what a required CI check should call. snyk monitor only takes a snapshot and uploads it for later alerting; it always exits 0 and can never fail a build on its own.
  5. A deny hit changes Conftest's exit code from 0 to 1 — that's what a CI step checks. A warn hit is reported but never changes the exit code, so a rule quietly moved to warn "temporarily" has effectively been turned into a non-blocking comment.
  6. The inspec exec invocation itself is almost certainly missing --waiver-file waivers.yml. A waiver file sitting in the repo with no flag pointing at it during the actual run enforces nothing — every control it was meant to waive runs unwaived, and the first sign anything's wrong is a build failing on findings that were supposedly already accounted for.