{"id":1207,"date":"2026-10-11T03:35:02","date_gmt":"2026-10-11T03:35:02","guid":{"rendered":"https:\/\/www.devopsschool.com\/tutorials\/?p=1207"},"modified":"2026-10-11T03:35:05","modified_gmt":"2026-10-11T03:35:05","slug":"trivy-zero-to-intermediate-hands-on-tutorial","status":"publish","type":"post","link":"https:\/\/www.devopsschool.com\/tutorials\/trivy-zero-to-intermediate-hands-on-tutorial\/","title":{"rendered":"Trivy: Zero to Intermediate &#8211; Hands-on Tutorial"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\"><strong>Course duration:<\/strong>&nbsp;3 hours (180 minutes)<br><strong>Audience:<\/strong>&nbsp;Absolute beginners, software developers, students, DevOps\/DevSecOps engineers, SREs, and security practitioners<br><strong>Reference Trivy version:<\/strong>&nbsp;<strong>v0.75.0<\/strong>, released October 1, 2026<br><strong>Last documentation verification:<\/strong>&nbsp;<strong>October 11, 2026<\/strong><br><strong>Learning style:<\/strong>&nbsp;Approximately 40% concepts and 60% guided practice<br><strong>Platforms:<\/strong>&nbsp;Windows 11 (PowerShell), macOS (Terminal), Ubuntu Linux (Bash)<br><strong>Level on completion:<\/strong>&nbsp;Beginner to practical intermediate; not a substitute for a full application-security certification<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><strong>Scope and accuracy note:<\/strong>&nbsp;Installation paths, command syntax, supported scanners, filtering, reporting, SBOMs, and GitHub Actions integration were cross-checked against Trivy&#8217;s official documentation as of the verification date. Scan output, finding counts, CVEs, fixed versions, and database-update timing change over time and are deliberately not invented here.&nbsp;<strong>The labs have not been executed in your environment.<\/strong>&nbsp;Follow the verification steps after each task. For organizational use, validate downloaded binaries and apply your normal software supply-chain controls.<\/p>\n<\/blockquote>\n\n\n\n<h2 class=\"wp-block-heading\">Contents<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/opt\/RajeshAll\/github\/rajeshkumarin\/ppts\/trivy\/Trivy_Zero_to_Intermediate_Gold_Standard_3_Hour_Tutorial.md#course-plan-and-lab-prerequisites\">Course plan and lab prerequisites<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/opt\/RajeshAll\/github\/rajeshkumarin\/ppts\/trivy\/Trivy_Zero_to_Intermediate_Gold_Standard_3_Hour_Tutorial.md#module-1--introduction-to-trivy-10-min\">Module 1 \u2014 Introduction to Trivy (10 min)<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/opt\/RajeshAll\/github\/rajeshkumarin\/ppts\/trivy\/Trivy_Zero_to_Intermediate_Gold_Standard_3_Hour_Tutorial.md#module-2--security-and-vulnerability-fundamentals-10-min\">Module 2 \u2014 Security and vulnerability fundamentals (10 min)<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/opt\/RajeshAll\/github\/rajeshkumarin\/ppts\/trivy\/Trivy_Zero_to_Intermediate_Gold_Standard_3_Hour_Tutorial.md#module-3--architecture-installation-and-setup-15-min\">Module 3 \u2014 Architecture, installation, and setup (15 min)<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/opt\/RajeshAll\/github\/rajeshkumarin\/ppts\/trivy\/Trivy_Zero_to_Intermediate_Gold_Standard_3_Hour_Tutorial.md#module-4--container-image-scanning-25-min\">Module 4 \u2014 Container-image scanning (25 min)<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/opt\/RajeshAll\/github\/rajeshkumarin\/ppts\/trivy\/Trivy_Zero_to_Intermediate_Gold_Standard_3_Hour_Tutorial.md#module-5--filesystem-and-repository-scanning-15-min\">Module 5 \u2014 Filesystem and repository scanning (15 min)<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/opt\/RajeshAll\/github\/rajeshkumarin\/ppts\/trivy\/Trivy_Zero_to_Intermediate_Gold_Standard_3_Hour_Tutorial.md#module-6--iac-misconfiguration-scanning-20-min\">Module 6 \u2014 IaC misconfiguration scanning (20 min)<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/opt\/RajeshAll\/github\/rajeshkumarin\/ppts\/trivy\/Trivy_Zero_to_Intermediate_Gold_Standard_3_Hour_Tutorial.md#module-7--secret-scanning-10-min\">Module 7 \u2014 Secret scanning (10 min)<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/opt\/RajeshAll\/github\/rajeshkumarin\/ppts\/trivy\/Trivy_Zero_to_Intermediate_Gold_Standard_3_Hour_Tutorial.md#module-8--filtering-triage-and-remediation-20-min\">Module 8 \u2014 Filtering, triage, and remediation (20 min)<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/opt\/RajeshAll\/github\/rajeshkumarin\/ppts\/trivy\/Trivy_Zero_to_Intermediate_Gold_Standard_3_Hour_Tutorial.md#module-9--reports-and-sboms-15-min\">Module 9 \u2014 Reports and SBOMs (15 min)<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/opt\/RajeshAll\/github\/rajeshkumarin\/ppts\/trivy\/Trivy_Zero_to_Intermediate_Gold_Standard_3_Hour_Tutorial.md#module-10--cicd-integration-15-min\">Module 10 \u2014 CI\/CD integration (15 min)<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/opt\/RajeshAll\/github\/rajeshkumarin\/ppts\/trivy\/Trivy_Zero_to_Intermediate_Gold_Standard_3_Hour_Tutorial.md#module-11--kubernetes-cluster-scanning-10-min\">Module 11 \u2014 Kubernetes cluster scanning (10 min)<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/opt\/RajeshAll\/github\/rajeshkumarin\/ppts\/trivy\/Trivy_Zero_to_Intermediate_Gold_Standard_3_Hour_Tutorial.md#module-12--capstone-exercise-15-min\">Module 12 \u2014 Capstone exercise (15 min)<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/opt\/RajeshAll\/github\/rajeshkumarin\/ppts\/trivy\/Trivy_Zero_to_Intermediate_Gold_Standard_3_Hour_Tutorial.md#troubleshooting-and-production-checklist\">Troubleshooting and production checklist<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/opt\/RajeshAll\/github\/rajeshkumarin\/ppts\/trivy\/Trivy_Zero_to_Intermediate_Gold_Standard_3_Hour_Tutorial.md#quick-command-reference-quiz-answers-and-official-sources\">Quick command reference, quiz answers, and official sources<\/a><\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Course plan and lab prerequisites<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">What you will achieve<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">After the course, you can independently install Trivy; scan public and local container images; inspect code repositories and dependency lockfiles; detect Terraform, Dockerfile, and Kubernetes misconfigurations; scan for secrets; distinguish actionable vulnerabilities from accepted risks; produce machine-readable reports and software bills of materials; and enforce a simple CI security gate.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Timetable and lab deliverables<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Module<\/th><th class=\"has-text-align-left\" data-align=\"left\">Subject<\/th><th class=\"has-text-align-right\" data-align=\"right\">Minutes<\/th><th class=\"has-text-align-left\" data-align=\"left\">Learner&#8217;s evidence<\/th><\/tr><\/thead><tbody><tr><td>1<\/td><td>Introduction and use cases<\/td><td class=\"has-text-align-right\" data-align=\"right\">10<\/td><td>State four classes of findings and two limitations<\/td><\/tr><tr><td>2<\/td><td>CVE, CVSS, severity, packages<\/td><td class=\"has-text-align-right\" data-align=\"right\">10<\/td><td>Explain one sample finding<\/td><\/tr><tr><td>3<\/td><td>Architecture + installation<\/td><td class=\"has-text-align-right\" data-align=\"right\">15<\/td><td><code>trivy --version<\/code>&nbsp;and first successful scan<\/td><\/tr><tr><td>4<\/td><td>Container images<\/td><td class=\"has-text-align-right\" data-align=\"right\">25<\/td><td>Image scan and a locally built demo image (Docker optional)<\/td><\/tr><tr><td>5<\/td><td>Filesystem + repositories<\/td><td class=\"has-text-align-right\" data-align=\"right\">15<\/td><td>Source-tree and remote-repository scans<\/td><\/tr><tr><td>6<\/td><td>IaC scanning<\/td><td class=\"has-text-align-right\" data-align=\"right\">20<\/td><td>Scan intentionally insecure Terraform and Kubernetes YAML<\/td><\/tr><tr><td>7<\/td><td>Secret detection<\/td><td class=\"has-text-align-right\" data-align=\"right\">10<\/td><td>Scan a synthetic secret and inspect a finding<\/td><\/tr><tr><td>8<\/td><td>Triage + remediation<\/td><td class=\"has-text-align-right\" data-align=\"right\">20<\/td><td>Record, remediate, filter, and rescan one issue<\/td><\/tr><tr><td>9<\/td><td>JSON, SARIF, and SBOM<\/td><td class=\"has-text-align-right\" data-align=\"right\">15<\/td><td>Save three report formats<\/td><\/tr><tr><td>10<\/td><td>CI\/CD<\/td><td class=\"has-text-align-right\" data-align=\"right\">15<\/td><td>Create and understand a GitHub Actions gate<\/td><\/tr><tr><td>11<\/td><td>Kubernetes<\/td><td class=\"has-text-align-right\" data-align=\"right\">10<\/td><td>Manifest scan or optional authorized cluster scan<\/td><\/tr><tr><td>12<\/td><td>Final practical<\/td><td class=\"has-text-align-right\" data-align=\"right\">15<\/td><td>Submit an evidence-based security report<\/td><\/tr><tr><td><strong>Total<\/strong><\/td><td><\/td><td class=\"has-text-align-right\" data-align=\"right\"><strong>180<\/strong><\/td><td><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Suggested path:<\/strong>&nbsp;This timetable is an instructor-led agenda. The optional Docker build, Windows\/macOS alternatives, live Kubernetes access, extra exercises, and some download times may extend self-study beyond three hours. Skip optional work when following the strict schedule.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Requirements<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Item<\/th><th class=\"has-text-align-left\" data-align=\"left\">Required?<\/th><th class=\"has-text-align-left\" data-align=\"left\">Why<\/th><\/tr><\/thead><tbody><tr><td>Windows\/macOS\/Ubuntu laptop with a terminal<\/td><td>Yes<\/td><td>Execute Trivy<\/td><\/tr><tr><td>Internet access to approved registries and Trivy databases<\/td><td>Yes for initial scans<\/td><td>Pull images and update scanning databases<\/td><\/tr><tr><td>Permission to install a local CLI<\/td><td>Yes<\/td><td>Install Trivy and optional utilities<\/td><\/tr><tr><td>Text editor, e.g., VS Code<\/td><td>Yes<\/td><td>Save and edit practice files<\/td><\/tr><tr><td>Git<\/td><td>Recommended<\/td><td>Clone the safe teaching repository and commit CI config<\/td><\/tr><tr><td>Docker Engine \/ Docker Desktop<\/td><td>Optional for image-building task<\/td><td>Build an image locally; public-image scans can work without a local Docker daemon<\/td><\/tr><tr><td><code>jq<\/code><\/td><td>Optional on Windows, recommended on macOS\/Linux<\/td><td>Explore JSON reports; alternatives are given<\/td><\/tr><tr><td>GitHub repository with Actions enabled<\/td><td>Optional<\/td><td>Actually run the CI workflow; the YAML can be studied offline<\/td><\/tr><tr><td><code>kubectl<\/code>&nbsp;plus permission to read a Kubernetes cluster<\/td><td>Optional<\/td><td>Live-cluster demo only; YAML scanning requires no cluster<\/td><\/tr><tr><td>Terraform CLI, an AWS account, or cloud credentials<\/td><td><strong>Not required<\/strong><\/td><td>Trivy performs static scanning on the local&nbsp;<code>.tf<\/code>&nbsp;files<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Safety boundary:<\/strong>&nbsp;Practice only on sample files, public images, and systems you are authorized to scan. Never paste real credentials into a lab. Do not&nbsp;<code>terraform apply<\/code>&nbsp;insecure examples, expose a test cluster, or grant broad Kubernetes permissions just for a demonstration. Scanners do not prove an application is secure; they provide evidence to analyze.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Lab workspace and shell conventions<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Use this workspace for all modules.&nbsp;<strong>Bash<\/strong>&nbsp;commands apply directly to macOS Terminal, Ubuntu, and Git Bash\/WSL on Windows.&nbsp;<strong>PowerShell<\/strong>&nbsp;examples are called out explicitly. For Windows without Bash, create the same named files using your editor and run Trivy in PowerShell; the&nbsp;<code>trivy<\/code>&nbsp;commands themselves are shell-neutral.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><em># Bash \u2014 macOS\/Ubuntu\/Git Bash\/WSL<\/em>\nmkdir -p \"$HOME\/trivy-workshop\"\/{app,terraform,kubernetes,secrets,reports}\ncd \"$HOME\/trivy-workshop\"\npwd\n<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code><em># PowerShell \u2014 Windows; no Administrator rights needed<\/em>\n$Lab = Join-Path $HOME 'trivy-workshop'\n'app','terraform','kubernetes','secrets','reports' | ForEach-Object {\n  New-Item -ItemType Directory -Force -Path (Join-Path $Lab $_) | Out-Null\n}\nSet-Location $Lab\nGet-Location\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected: an empty&nbsp;<code>trivy-workshop<\/code>&nbsp;folder containing&nbsp;<code>app\/<\/code>,&nbsp;<code>terraform\/<\/code>,&nbsp;<code>kubernetes\/<\/code>,&nbsp;<code>secrets\/<\/code>, and&nbsp;<code>reports\/<\/code>. All subsequent commands assume you are inside&nbsp;<code>trivy-workshop<\/code>&nbsp;unless otherwise stated.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Module 1 \u2014 Introduction to Trivy (10 min)<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">Objectives<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Define Trivy and DevSecOps; identify its principal scan targets and security findings; explain the difference between finding potential exposure and detecting an active attack; and know when to use each mode.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What is Trivy?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Trivy<\/strong>&nbsp;is an open-source security scanner maintained by Aqua Security and contributors. It inspects artifacts and configurations for&nbsp;<strong>known package vulnerabilities<\/strong>,&nbsp;<strong>security misconfigurations<\/strong>,&nbsp;<strong>exposed secrets<\/strong>, and&nbsp;<strong>license-related findings<\/strong>. Trivy can scan container images, filesystem directories, repositories, SBOMs, Kubernetes clusters, and other supported targets. Each target has different default scanners, so check&nbsp;<code>--scanners<\/code>&nbsp;rather than assuming every check runs automatically.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In&nbsp;<strong>DevSecOps<\/strong>, teams include security checks during development, commits, builds, deployments, and operations rather than waiting for a final audit.&nbsp;<strong>Shift left<\/strong>&nbsp;means bringing useful checks earlier in that flow. Trivy helps identify many preventable problems while developers can still change code, dependencies, or configuration easily.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Targets vs. findings: the key distinction<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Target = what you inspect<\/th><th class=\"has-text-align-left\" data-align=\"left\">Example command<\/th><th class=\"has-text-align-left\" data-align=\"left\">Typical question<\/th><\/tr><\/thead><tbody><tr><td>Container image<\/td><td><code>trivy image alpine:3.20<\/code><\/td><td>Are installed OS or library packages vulnerable?<\/td><\/tr><tr><td>Local source tree<\/td><td><code>trivy fs .<\/code><\/td><td>Are dependency lockfiles or plaintext secrets problematic?<\/td><\/tr><tr><td>Remote source repository<\/td><td><code>trivy repo https:\/\/github.com\/aquasecurity\/trivy-ci-test<\/code><\/td><td>What known problems exist in the repository?<\/td><\/tr><tr><td>IaC and configuration<\/td><td><code>trivy config .\/kubernetes<\/code><\/td><td>Are manifest permissions or container security settings unsafe?<\/td><\/tr><tr><td>Generated SBOM<\/td><td><code>trivy sbom .\/reports\/sbom.json<\/code><\/td><td>Are any inventoried dependencies known-vulnerable?<\/td><\/tr><tr><td>Kubernetes cluster<\/td><td><code>trivy k8s --report summary<\/code><\/td><td>What issues are discoverable from authorized cluster access?<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Finding types:<\/strong>&nbsp;vulnerability (<code>vuln<\/code>), misconfiguration (<code>misconfig<\/code>), secret (<code>secret<\/code>), and license (<code>license<\/code>). Some other Trivy capabilities are outside this course.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Trivy compared with other approaches<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Approach<\/th><th class=\"has-text-align-left\" data-align=\"left\">Main purpose<\/th><th class=\"has-text-align-left\" data-align=\"left\">Trivy relationship<\/th><\/tr><\/thead><tbody><tr><td>Application log monitoring<\/td><td>Observe what happened at runtime<\/td><td>Complementary: Trivy does not replace event monitoring<\/td><\/tr><tr><td>Antivirus\/EDR<\/td><td>Detect suspicious files or endpoint behavior<\/td><td>Complementary: Trivy is not a complete EDR\/antivirus replacement<\/td><\/tr><tr><td>DAST or penetration testing<\/td><td>Exercise a running application&#8217;s exposed behavior<\/td><td>Complementary: Trivy does not prove an exploit works<\/td><\/tr><tr><td>Traditional CVE\/SCA scanner<\/td><td>Match dependencies to known advisories<\/td><td>Overlapping functionality; Trivy also handles images and configuration<\/td><\/tr><tr><td>Admission policy \/ runtime defense<\/td><td>Stop or investigate workloads at runtime<\/td><td>Complementary: use policies and runtime tools alongside Trivy<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">What Trivy cannot guarantee<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Trivy cannot promise to find zero-day vulnerabilities without applicable detection data, prove exploitability solely from a CVE record, reveal every business-logic flaw, inspect every dynamic runtime state, establish compliance by itself, or fix findings automatically. No output is not proof of no risk.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Mini exercise (2 minutes):<\/strong>&nbsp;Say which scan target you would choose for (a) an old Docker image, (b) an open-to-the-world SSH security group in Terraform, (c) a credential in a source file, and (d) a live Kubernetes cluster. Answers:&nbsp;<code>image<\/code>,&nbsp;<code>config<\/code>,&nbsp;<code>fs<\/code>\/<code>repo<\/code>&nbsp;with&nbsp;<code>secret<\/code>, and&nbsp;<code>k8s<\/code>, respectively.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Checkpoint:<\/strong>&nbsp;Explain in your own words why a CVE scan and runtime intrusion detection are different.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Module 2 \u2014 Security and vulnerability fundamentals (10 min)<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">Objectives<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Understand vulnerability, threat, risk, CVE, CWE, CVSS, advisory severity, fixed version, and the two major sources of vulnerable packages.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Core vocabulary<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Term<\/th><th class=\"has-text-align-left\" data-align=\"left\">Plain-English definition<\/th><\/tr><\/thead><tbody><tr><td><strong>Asset<\/strong><\/td><td>Something worth protecting: image, server, application, credential, or customer data<\/td><\/tr><tr><td><strong>Vulnerability<\/strong><\/td><td>A weakness that may be exploited<\/td><\/tr><tr><td><strong>Threat<\/strong><\/td><td>An actor, event, or technique that could take advantage of a weakness<\/td><\/tr><tr><td><strong>Risk<\/strong><\/td><td>Likelihood and business impact of an unwanted outcome, informed by exposure<\/td><\/tr><tr><td><strong>CVE<\/strong><\/td><td>Common Vulnerabilities and Exposures identifier for a published issue, such as&nbsp;<code>CVE-YYYY-NNNN<\/code><\/td><\/tr><tr><td><strong>CWE<\/strong><\/td><td>Common Weakness Enumeration: category of underlying software weakness<\/td><\/tr><tr><td><strong>CVSS<\/strong><\/td><td>Common Vulnerability Scoring System: a structured severity scoring framework<\/td><\/tr><tr><td><strong>IOC<\/strong><\/td><td>Indicator of compromise, for example evidence of a malicious file or network destination<\/td><\/tr><tr><td><strong>SCA<\/strong><\/td><td>Software Composition Analysis: identify third-party components and known vulnerabilities<\/td><\/tr><tr><td><strong>OS package<\/strong><\/td><td>Operating-system-managed dependency, e.g.,&nbsp;<code>openssl<\/code>&nbsp;or&nbsp;<code>libc<\/code><\/td><\/tr><tr><td><strong>Language library<\/strong><\/td><td>App dependency, e.g., a Python package or Node.js npm package<\/td><\/tr><tr><td><strong>Fixed version<\/strong><\/td><td>An available vendor-supported version containing a relevant fix, if one exists<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Severity and practical priority<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Trivy reports&nbsp;<strong>UNKNOWN<\/strong>,&nbsp;<strong>LOW<\/strong>,&nbsp;<strong>MEDIUM<\/strong>,&nbsp;<strong>HIGH<\/strong>, and&nbsp;<strong>CRITICAL<\/strong>. A critical-looking result is important, but severity alone is not a complete remediation policy. Consider reachability, network exposure, privileges, business impact, exploit activity, availability of a fix, and compensating controls. Trivy uses ecosystem-specific vulnerability sources, and distro-maintained backports can mean package version comparisons are not as simple as upstream version ordering.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Hypothetical example \u2014 not a real detected CVE:<\/strong><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Field<\/th><th class=\"has-text-align-left\" data-align=\"left\">Example<\/th><th class=\"has-text-align-left\" data-align=\"left\">Interpretation<\/th><\/tr><\/thead><tbody><tr><td>Finding ID<\/td><td><code>CVE-20XX-12345<\/code><\/td><td>Look up the advisory and the affected conditions<\/td><\/tr><tr><td>Package<\/td><td><code>examplelib<\/code><\/td><td>Dependency containing the weak component<\/td><\/tr><tr><td>Installed version<\/td><td><code>1.2.0<\/code><\/td><td>Version currently inventoried<\/td><\/tr><tr><td>Fixed version<\/td><td><code>1.2.9<\/code><\/td><td>Candidate update, if compatible and vendor supported<\/td><\/tr><tr><td>Severity<\/td><td><code>HIGH<\/code><\/td><td>Strong triage signal, not proof of exploitability<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Why both operating-system and application scanning matter<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A container image might bundle an Ubuntu\/Debian base with OS packages&nbsp;<strong>and<\/strong>&nbsp;Python\/Java\/npm dependencies. Updating application dependencies without updating the base image leaves OS problems behind; replacing the base without updating lockfiles may leave library findings. A useful remediation plan addresses both.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Micro-quiz:<\/strong>&nbsp;(1) Is a CVE an attack? (2) Does a missing fixed version mean the finding is false? (3) Can a distro backport a fix without changing to the upstream major version?&nbsp;<strong>Answers:<\/strong>&nbsp;No; no; yes.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Module 3 \u2014 Architecture, installation, and setup (15 min)<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">Objectives<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Understand how scanners run, install the CLI on your OS, verify its operation, and know what the vulnerability database and local cache do.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">How a scan works<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>flowchart LR\n    A&#91;Target: image, filesystem, repo, config, Kubernetes] --&gt; B&#91;Trivy CLI]\n    B --&gt; C&#91;Inventory packages and inspect files or configuration]\n    C --&gt; D&#91;Select enabled scanners]\n    E&#91;Vulnerability and policy data \/ updates] --&gt; D\n    D --&gt; F&#91;Match vulnerabilities, misconfigs, secrets, licenses]\n    F --&gt; G&#91;Apply severity and suppression filters]\n    G --&gt; H&#91;Table, JSON, SARIF, SBOM \/ exit code]\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Trivy analyzes the target, identifies relevant packages and files, consults vulnerability databases and built-in or downloaded security checks, then returns structured results. It does&nbsp;<strong>not<\/strong>&nbsp;need a persistent background service in normal standalone CLI mode. A&nbsp;<strong>client\/server<\/strong>&nbsp;mode exists for specialized centralized deployments but is out of scope for this workshop.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Updates and caches:<\/strong>&nbsp;The first scan usually downloads database\/check content and takes longer. Later scans can reuse local copies and refresh them as necessary. Network policy must permit the approved registry and database endpoints. Offline\/air-gapped operation requires separate pre-provisioning and maintenance procedures; it is not achieved merely by adding&nbsp;<code>--skip-db-update<\/code>&nbsp;on an empty installation.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Quick Installation and Configuration<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Choose&nbsp;<strong>one<\/strong>&nbsp;OS method below. No AWS\/cloud login is needed. For Docker-based exercises, install Docker Desktop or Docker Engine separately from its official site; scanning public remote images does not automatically require Docker to be running.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Ubuntu Linux \u2014 official APT repository<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Where:<\/strong>&nbsp;Ubuntu Terminal (Bash).&nbsp;<strong>Privileges:<\/strong>&nbsp;<code>sudo<\/code>&nbsp;for OS package installation only.&nbsp;<strong>Dependencies:<\/strong>&nbsp;<code>wget<\/code>,&nbsp;<code>gnupg<\/code>, CA certificates;&nbsp;<code>git<\/code>&nbsp;and&nbsp;<code>jq<\/code>&nbsp;aid later exercises.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><em># Step 1: prepare dependencies<\/em>\nsudo apt-get update\nsudo apt-get install -y ca-certificates wget curl gnupg git jq\n\n<em># Step 2: install the public signing key from the official Trivy repository<\/em>\nwget -qO - https:\/\/aquasecurity.github.io\/trivy-repo\/deb\/public.key \\\n  | gpg --dearmor \\\n  | sudo tee \/usr\/share\/keyrings\/trivy.gpg &gt;\/dev\/null\n\n<em># Step 3: register the official package repository<\/em>\nprintf '%s\\n' \\\n  'deb &#91;signed-by=\/usr\/share\/keyrings\/trivy.gpg] https:\/\/aquasecurity.github.io\/trivy-repo\/deb generic main' \\\n  | sudo tee \/etc\/apt\/sources.list.d\/trivy.list\n\n<em># Step 4: install and verify<\/em>\nsudo apt-get update\nsudo apt-get install -y trivy\ntrivy --version\ncommand -v trivy\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected:<\/strong>&nbsp;<code>trivy --version<\/code>&nbsp;shows a version number; the target for this tutorial is&nbsp;<code>0.75.0<\/code>. The APT repository may provide newer versions later. To reproduce precisely, use the official v0.75.0 release package appropriate to your CPU architecture after validating the release checksums\/signature. If your network blocks the repository, involve your package administrator rather than disabling TLS or signature checking.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Check:<\/strong>&nbsp;<code>trivy image --scanners vuln alpine:3.20<\/code>&nbsp;should reach a public registry and complete an image scan. Zero or multiple findings are both possible; success means the command completes without a download\/scan error.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Common problem:<\/strong>&nbsp;<code>NO_PUBKEY<\/code>\/repository signature problem \u2192 confirm the key was downloaded from the intended host, keyring is readable, and source-list entry matches; do not disable GPG verification.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">macOS \u2014 official Homebrew method<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Where:<\/strong>&nbsp;macOS Terminal.&nbsp;<strong>Privileges:<\/strong>&nbsp;normal user with working Homebrew; do not use&nbsp;<code>sudo brew<\/code>.&nbsp;<strong>Dependencies:<\/strong>&nbsp;Homebrew installed from&nbsp;<a href=\"https:\/\/brew.sh\/\">brew.sh<\/a>&nbsp;via your organization-approved process; optional&nbsp;<code>git<\/code>,&nbsp;<code>jq<\/code>, and Docker Desktop.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><em># Step 1: confirm Homebrew exists<\/em>\nbrew --version\n\n<em># Step 2: install Trivy and convenient tutorial tools<\/em>\nbrew update\nbrew install trivy git jq\n\n<em># Step 3: verify the executable and version<\/em>\ntrivy --version\ncommand -v trivy\n\n<em># Step 4: first public-image scan (no local Docker daemon required)<\/em>\ntrivy image --scanners vuln alpine:3.20\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected:<\/strong>&nbsp;Brew places&nbsp;<code>trivy<\/code>&nbsp;on the shell PATH and the image scan produces a report. On Apple Silicon, Homebrew commonly uses&nbsp;<code>\/opt\/homebrew\/bin<\/code>; on Intel,&nbsp;<code>\/usr\/local\/bin<\/code>. Avoid hardcoding either path.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Common problem:<\/strong>&nbsp;<code>zsh: command not found: brew<\/code>&nbsp;\u2192 install\/initialize Homebrew using its own printed post-install instructions and open a new terminal.&nbsp;<code>zsh: command not found: trivy<\/code>&nbsp;\u2192 verify&nbsp;<code>brew list trivy<\/code>&nbsp;and&nbsp;<code>brew --prefix<\/code>&nbsp;and repair the Homebrew PATH.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Windows 11 \u2014 official release ZIP via PowerShell<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Where:<\/strong>&nbsp;Windows&nbsp;<strong>PowerShell<\/strong>&nbsp;(not Bash), normal user.&nbsp;<strong>Privileges:<\/strong>&nbsp;Administrator is&nbsp;<strong>not<\/strong>&nbsp;required if installing under your own profile.&nbsp;<strong>Dependencies:<\/strong>&nbsp;network access to GitHub Releases; ZIP extraction is built in. The example is for&nbsp;<strong>64-bit x86 Windows<\/strong>; use a release asset matching your hardware if different.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><em># Step 1: define the verified target version and per-user install location<\/em>\n$Version = '0.75.0'\n$InstallDir = Join-Path $HOME 'Tools\\Trivy'\n$ZipPath = Join-Path $env:TEMP ('trivy_' + $Version + '_windows-64bit.zip')\n$Url = 'https:\/\/github.com\/aquasecurity\/trivy\/releases\/download\/v' +\n       $Version + '\/trivy_' + $Version + '_windows-64bit.zip'\n\n<em># Step 2: download only from the official GitHub release<\/em>\nNew-Item -ItemType Directory -Force -Path $InstallDir | Out-Null\nInvoke-WebRequest -Uri $Url -OutFile $ZipPath\n\n<em># Step 3: unpack and check the downloaded executable<\/em>\nExpand-Archive -Path $ZipPath -DestinationPath $InstallDir -Force\n&amp; (Join-Path $InstallDir 'trivy.exe') --version\n\n<em># Step 4: persist a per-user PATH entry if it is not present<\/em>\n$UserPath = &#91;Environment]::GetEnvironmentVariable('Path','User')\nif (($UserPath -split ';') -notcontains $InstallDir) {\n    &#91;Environment]::SetEnvironmentVariable(\n        'Path', (($UserPath.TrimEnd(';') + ';' + $InstallDir).TrimStart(';')), 'User')\n}\n$env:Path += ';' + $InstallDir\n\n<em># Step 5: run the first scan<\/em>\ntrivy --version\ntrivy image --scanners vuln alpine:3.20\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected:<\/strong>&nbsp;<code>trivy.exe<\/code>&nbsp;reports version&nbsp;<code>0.75.0<\/code>; the scan completes and prints findings or a zero-finding summary. New PowerShell windows receive the updated user PATH. Windows Defender\/organization download policies may require approval.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Common problem:<\/strong>&nbsp;ZIP returns HTTP 404 \u2192 visit&nbsp;<a href=\"https:\/\/github.com\/aquasecurity\/trivy\/releases\/tag\/v0.75.0\">official release assets<\/a>&nbsp;and confirm exact asset name and CPU architecture.&nbsp;<code>trivy<\/code>&nbsp;not found \u2192 use&nbsp;<code>&amp; (Join-Path $InstallDir 'trivy.exe') --version<\/code>, restart PowerShell, and verify PATH. Do not disable endpoint protections to force installation.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Optional containerized Trivy instead of host installation<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">On a Linux host with Docker, you can run the official image without installing the CLI globally:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><em># Linux Docker; public-image scan without mounting Docker socket<\/em>\n<em># First run may fetch the image and database. Cache is persisted to a user directory.<\/em>\nmkdir -p \"$HOME\/.cache\/trivy-container\"\ndocker run --rm \\\n  -v \"$HOME\/.cache\/trivy-container:\/root\/.cache\/\" \\\n  aquasec\/trivy:0.75.0 image --scanners vuln alpine:3.20\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">For scanning&nbsp;<strong>local<\/strong>&nbsp;daemon images, socket mounting gives containerized Trivy broad access to the host Docker engine; treat that as privileged access and use it only in a trusted lab. This option is supplemental, not a requirement.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">CLI essentials and version check<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>trivy --help                 <em># List top-level targets and commands<\/em>\ntrivy image --help           <em># Inspect flags for image scans<\/em>\ntrivy fs --help              <em># Inspect filesystem-scan flags<\/em>\ntrivy config --help          <em># Inspect IaC-only scanning options<\/em>\ntrivy --version              <em># Check the installed CLI version<\/em>\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Typical syntax is&nbsp;<code>trivy &lt;target&gt; [flags] &lt;path-or-image&gt;<\/code>.&nbsp;<code>--scanners<\/code>&nbsp;chooses finding classes;&nbsp;<code>--severity<\/code>&nbsp;filters output by severity;&nbsp;<code>--format<\/code>&nbsp;changes the report format;&nbsp;<code>--output<\/code>&nbsp;writes a file; and&nbsp;<code>--exit-code<\/code>&nbsp;changes the exit status when qualifying findings exist. The defaults differ by scan target, so explicit scanner selection improves reproducibility.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Checkpoint:<\/strong>&nbsp;Why might the first scan take longer? What is the difference between a scan target and a scanner? Is a Docker daemon required for scanning a public image by registry access?<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Module 4 \u2014 Container-image scanning (25 min)<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">Objectives and prerequisites<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Learn the image-layer and package concepts, scan an old and current image, identify affected packages, filter by severity, distinguish&nbsp;<code>--ignore-unfixed<\/code>&nbsp;from remediation, and optionally build and scan your own container. Prerequisite: Module 3 installation and registry access. Docker is needed&nbsp;<strong>only<\/strong>&nbsp;for the local-image build.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">What is inside an image?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A container image typically contains a&nbsp;<strong>base operating-system filesystem<\/strong>, installed&nbsp;<strong>system libraries<\/strong>, optional&nbsp;<strong>language packages<\/strong>, application files, and image metadata. Trivy primarily inventories known components and compares them with applicable advisories; a container image is not automatically safe because its app source code is small or its tag says&nbsp;<code>latest<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>flowchart TD\n    A&#91;Image: application + language packages + OS base] --&gt; B&#91;Package \/ metadata inventory]\n    B --&gt; C&#91;OS-vulnerability advisory matching]\n    B --&gt; D&#91;Application-library advisory matching]\n    C --&gt; E&#91;Combined results]\n    D --&gt; E\n    E --&gt; F&#91;Review CVEs, fix versions, exploit context]\n    F --&gt; G&#91;Update base image or dependencies]\n    G --&gt; H&#91;Rebuild and rescan]\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Lab 4A \u2014 Scan a public image<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Where:<\/strong>&nbsp;Terminal\/PowerShell in&nbsp;<code>trivy-workshop<\/code>.&nbsp;<strong>Prerequisite:<\/strong>&nbsp;Trivy is on PATH; internet access.&nbsp;<strong>No Docker daemon required for this portion.<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><em># 1. Basic image vulnerability scan<\/em>\ntrivy image --scanners vuln nginx:1.20\n\n<em># 2. Restrict displayed results to HIGH and CRITICAL<\/em>\ntrivy image --scanners vuln --severity HIGH,CRITICAL nginx:1.20\n\n<em># 3. Display findings for which the advisory identifies an available fix<\/em>\ntrivy image --scanners vuln --ignore-unfixed nginx:1.20\n\n<em># 4. Compare against a more current maintained image channel<\/em>\ntrivy image --scanners vuln --severity HIGH,CRITICAL nginx:stable-alpine\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Flags explained:<\/strong>&nbsp;<code>image<\/code>&nbsp;selects the image target;&nbsp;<code>--scanners vuln<\/code>&nbsp;makes the intent explicit;&nbsp;<code>--severity HIGH,CRITICAL<\/code>&nbsp;filters the visible severities;&nbsp;<code>--ignore-unfixed<\/code>&nbsp;hides findings without a reported fix. It does&nbsp;<strong>not<\/strong>&nbsp;patch software or establish that an unfixed issue is harmless.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected result:<\/strong>&nbsp;A report identifies the target and possibly one or more vulnerable packages with IDs, severities, installed versions, and fix-version information.&nbsp;<strong>Counts are intentionally unspecified<\/strong>&nbsp;because registry contents, OS support, advisory feeds, and the current DB change. If&nbsp;<code>nginx:1.20<\/code>&nbsp;is no longer retrievable, choose another intentionally older public demo image from an authorized registry and record the actual tag.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Verification:<\/strong>&nbsp;Both scans complete without a registry\/network error. Write down two package names (if findings exist), their severity, whether a fix is listed, and whether the issue is from the base OS or an application library.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Reading the report<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Result column<\/th><th class=\"has-text-align-left\" data-align=\"left\">What it means<\/th><th class=\"has-text-align-left\" data-align=\"left\">Action<\/th><\/tr><\/thead><tbody><tr><td><code>Library<\/code>&nbsp;\/ package name<\/td><td>Detected affected software component<\/td><td>Identify dependency owner<\/td><\/tr><tr><td><code>Vulnerability<\/code><\/td><td>CVE\/advisory ID<\/td><td>Read vendor advisory and affected conditions<\/td><\/tr><tr><td><code>Severity<\/code><\/td><td>Advisory risk category<\/td><td>Prioritize with real system context<\/td><\/tr><tr><td><code>Installed Version<\/code><\/td><td>Component version in the artifact<\/td><td>Verify artifact provenance<\/td><\/tr><tr><td><code>Fixed Version<\/code><\/td><td>Published fixed candidate, if any<\/td><td>Evaluate update or rebuild<\/td><\/tr><tr><td><code>Status<\/code>, where shown<\/td><td>Whether fix exists or vendor accepts\/deferred work<\/td><td>Decide treatment and record exceptions<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Avoid two beginner errors: (1) treating image tags as immutable; in real releases prefer image digests and traceable build IDs; and (2) equating zero displayed HIGH\/CRITICAL findings with a secure image.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Lab 4B \u2014 Build a tiny local image (optional Docker track)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Where:<\/strong>&nbsp;<code>trivy-workshop<\/code>&nbsp;on macOS\/Linux\/Git Bash\/WSL, or use a text editor on Windows.&nbsp;<strong>Prerequisite:<\/strong>&nbsp;Docker Engine\/Desktop running. We create a very small HTTP server using Python standard library; no&nbsp;<code>pip install<\/code>&nbsp;is needed.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>File 1 \u2014&nbsp;<code>app\/server.py<\/code>&nbsp;(entire file):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>from http.server import BaseHTTPRequestHandler, HTTPServer\n\nclass Handler(BaseHTTPRequestHandler):\n    def do_GET(self):\n        self.send_response(200)\n        self.send_header(\"Content-Type\", \"text\/plain; charset=utf-8\")\n        self.end_headers()\n        self.wfile.write(b\"Trivy lab application is running\\n\")\n\nif __name__ == \"__main__\":\n    HTTPServer((\"0.0.0.0\", 8080), Handler).serve_forever()\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>File 2 \u2014&nbsp;<code>Dockerfile<\/code>&nbsp;(intentionally simplified learning configuration):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>FROM python:3.11-slim\nWORKDIR \/opt\/app\nCOPY app\/server.py .\/server.py\nEXPOSE 8080\nCMD &#91;\"python\", \"server.py\"]\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The example is intentionally missing an explicit non-root&nbsp;<code>USER<\/code>; this creates material to inspect and fix later. It is&nbsp;<strong>not<\/strong>&nbsp;a production Dockerfile.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Bash file creation (copy ready):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><em># In ~\/trivy-workshop<\/em>\nmkdir -p app\ncat &gt; app\/server.py &lt;&lt;'PY'\nfrom http.server import BaseHTTPRequestHandler, HTTPServer\n\nclass Handler(BaseHTTPRequestHandler):\n    def do_GET(self):\n        self.send_response(200)\n        self.send_header(\"Content-Type\", \"text\/plain; charset=utf-8\")\n        self.end_headers()\n        self.wfile.write(b\"Trivy lab application is running\\n\")\n\nif __name__ == \"__main__\":\n    HTTPServer((\"0.0.0.0\", 8080), Handler).serve_forever()\nPY\n\ncat &gt; Dockerfile &lt;&lt;'DOCKERFILE'\nFROM python:3.11-slim\nWORKDIR \/opt\/app\nCOPY app\/server.py .\/server.py\nEXPOSE 8080\nCMD &#91;\"python\", \"server.py\"]\nDOCKERFILE\n\n# Build, inspect, and scan\n\ndocker build -t trivy-workshop:insecure .\ndocker image inspect trivy-workshop:insecure --format '{{.Id}}'\ntrivy image --scanners vuln trivy-workshop:insecure\ntrivy config .\/Dockerfile\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Windows without Bash:<\/strong>&nbsp;Save the two shown file contents in&nbsp;<code>trivy-workshop\\app\\server.py<\/code>&nbsp;and&nbsp;<code>trivy-workshop\\Dockerfile<\/code>&nbsp;using VS Code. In PowerShell from&nbsp;<code>trivy-workshop<\/code>, run the same&nbsp;<code>docker build<\/code>,&nbsp;<code>docker image inspect<\/code>,&nbsp;<code>trivy image<\/code>, and&nbsp;<code>trivy config<\/code>&nbsp;commands. The double braces in&nbsp;<code>docker image inspect<\/code>&nbsp;are Docker template syntax, not placeholders.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected:<\/strong>&nbsp;<code>docker build<\/code>&nbsp;exits successfully;&nbsp;<code>docker image inspect<\/code>&nbsp;prints an image identifier;&nbsp;<code>trivy image<\/code>&nbsp;analyzes the local image. The config scan may flag the lack of an explicit non-root user and other defaults depending on current policies.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Optional runtime verification:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><em># Docker publishes port 8080 on localhost only; Ctrl+C stops the foreground container.<\/em>\ndocker run --rm -p 127.0.0.1:8080:8080 trivy-workshop:insecure\n<em># In a second terminal:<\/em>\ncurl http:\/\/127.0.0.1:8080\/\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">PowerShell equivalent verification:&nbsp;<code>Invoke-WebRequest http:\/\/127.0.0.1:8080\/ | Select-Object -ExpandProperty Content<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected response:<\/strong>&nbsp;<code>Trivy lab application is running<\/code>. A successful runtime test does&nbsp;<strong>not<\/strong>&nbsp;prove the image is secure.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Lab 4C \u2014 Remediate and rescan<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">After observing image and Dockerfile results, replace&nbsp;<code>Dockerfile<\/code>&nbsp;with this&nbsp;<strong>improved baseline<\/strong>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>FROM python:3.11-slim\nRUN useradd --create-home --uid 10001 appuser\nWORKDIR \/opt\/app\nCOPY --chown=appuser:appuser app\/server.py .\/server.py\nUSER appuser\nEXPOSE 8080\nCMD &#91;\"python\", \"server.py\"]\n<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code><em># Optional Docker track only<\/em>\ndocker build -t trivy-workshop:nonroot .\ntrivy config .\/Dockerfile\ntrivy image --scanners vuln trivy-workshop:nonroot\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Verify:<\/strong>&nbsp;Dockerfile now explicitly declares a non-root user. Some misconfiguration results may disappear; dependency vulnerabilities may&nbsp;<strong>remain<\/strong>&nbsp;because both versions use the same base image. In production, also lock an approved base&nbsp;<strong>digest<\/strong>, rebuild regularly, test as non-root, and evaluate running with immutable\/readonly filesystems where appropriate.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Common problems:<\/strong>&nbsp;<code>Cannot connect to the Docker daemon<\/code>&nbsp;\u2192 start Docker or skip the local build;&nbsp;<code>UNAUTHORIZED<\/code>\/<code>TOOMANYREQUESTS<\/code>&nbsp;\u2192 authenticate to the allowed registry or try later; unable to fetch a tag \u2192 verify the tag exists; high findings with blank fixed version \u2192 plan mitigation, upstream replacement, or documented risk acceptance rather than inventing a patch.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Checkpoint:<\/strong>&nbsp;What is the functional difference between&nbsp;<code>--ignore-unfixed<\/code>&nbsp;and upgrading a package? Why could an updated Dockerfile still produce image CVEs?<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Module 5 \u2014 Filesystem and repository scanning (15 min)<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">Objectives<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Differentiate&nbsp;<code>trivy fs<\/code>&nbsp;and&nbsp;<code>trivy repo<\/code>; understand dependency manifests versus lockfiles; scan a folder and a remote repository; explicitly enable several scanner classes.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Filesystem vs. Git repository<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong><code>trivy fs PATH<\/code><\/strong>\u00a0inspects a local filesystem tree; good for checked-out projects and build directories. Its documented defaults include vulnerability and secret scanners.<\/li>\n\n\n\n<li><strong><code>trivy repo URL<\/code><\/strong>\u00a0reads a local or remote Git repository, with repository-specific behavior and Git metadata; good for remote code inspection and CI. Its documented defaults also include vulnerability and secret scanners.<\/li>\n\n\n\n<li><strong><code>trivy config PATH<\/code><\/strong>\u00a0targets configuration misconfiguration analysis directly;\u00a0<strong>misconfiguration detection is not enabled by default<\/strong>\u00a0for generic\u00a0<code>fs<\/code>,\u00a0<code>repo<\/code>, or\u00a0<code>image<\/code>\u00a0commands.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Dependencies may be declared in&nbsp;<code>package.json<\/code>,&nbsp;<code>requirements.txt<\/code>,&nbsp;<code>pom.xml<\/code>, and other manifests. Resolved dependency lockfiles, such as&nbsp;<code>package-lock.json<\/code>,&nbsp;<code>Pipfile.lock<\/code>,&nbsp;<code>poetry.lock<\/code>, or&nbsp;<code>Gemfile.lock<\/code>, help a scanner determine actual component versions. Trivy&#8217;s coverage varies by language, dependency ecosystem, and artifact type. Check its&nbsp;<a href=\"https:\/\/trivy.dev\/docs\/latest\/coverage\/language\/\">language coverage<\/a>&nbsp;rather than assuming a manifest alone always yields a complete dependency inventory.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Lab 5A \u2014 Inspect your workspace<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code><em># Run in ~\/trivy-workshop<\/em>\ntrivy fs .\n\n<em># Explicitly combine three scanner classes<\/em>\ntrivy fs --scanners vuln,secret,misconfig .\n\n<em># Restrict to vulnerability checks on the project tree<\/em>\ntrivy fs --scanners vuln .\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected:<\/strong>&nbsp;Trivy scans files present so far. If you created only&nbsp;<code>server.py<\/code>&nbsp;and&nbsp;<code>Dockerfile<\/code>, you may see few or no package vulnerabilities because there are no dependency lockfiles. When combining scanners, Terraform\/Kubernetes\/Dockerfile misconfiguration files from the next module will add more findings.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Lab 5B \u2014 Scan an official example repository<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code><em># Remote Git scan: no local clone necessary<\/em>\ntrivy repo https:\/\/github.com\/aquasecurity\/trivy-ci-test\n\n<em># Optional local path for inspecting manifests\/lockfiles; requires Git<\/em>\n<em># Run this inside trivy-workshop. The repository is for learning only.<\/em>\ngit clone https:\/\/github.com\/aquasecurity\/trivy-ci-test.git sample-repo\ntrivy fs --scanners vuln .\/sample-repo\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected:<\/strong>&nbsp;The repository scans successfully and may identify vulnerable dependencies in supported lockfiles.&nbsp;<strong>Record actual findings rather than fixed example totals.<\/strong>&nbsp;If a network rule prevents external Git cloning, scan a locally available, authorized sample repository instead.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Reading lockfile results<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A lockfile can point to an exact or resolved dependency version. Trivy compares these versions to ecosystem advisories and may report both&nbsp;<strong>direct<\/strong>&nbsp;dependencies (chosen by a project) and&nbsp;<strong>transitive<\/strong>&nbsp;dependencies (pulled in by direct ones). Remediation is typically through a dependency-manager update and lockfile regeneration, not manual editing of lockfiles without testing.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Practical task:<\/strong>&nbsp;Inspect&nbsp;<code>sample-repo<\/code>&nbsp;for a lockfile, identify one library reported by Trivy, then explain which direct parent package or dependency update would need investigation. If no current vulnerability is returned, report that fact and identify a supported lockfile anyway.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Common problems:<\/strong>&nbsp;no results because no supported package metadata exists; remote&nbsp;<code>repo<\/code>&nbsp;access requires Git connectivity; private repositories need appropriate access tokens (do not print them); scanning only&nbsp;<code>--scanners vuln<\/code>&nbsp;intentionally excludes secrets and IaC issues.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Checkpoint:<\/strong>&nbsp;Why is&nbsp;<code>trivy fs --scanners misconfig .<\/code>&nbsp;different from&nbsp;<code>trivy fs .<\/code>? Why are lockfiles especially helpful in dependency scanning?<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Module 6 \u2014 IaC misconfiguration scanning (20 min)<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">Objectives<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Understand static IaC analysis; create insecure and safer Terraform and Kubernetes resources; scan a Dockerfile, Terraform, and Kubernetes YAML; interpret check IDs; fix a problem without deploying anything.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Infrastructure as Code (IaC)<\/strong>&nbsp;is the description of infrastructure in versioned configuration files, such as Terraform and Kubernetes YAML. A misconfiguration can be dangerous even when the application has no software CVEs. Examples include public SSH ingress, privileged containers, excessive IAM permissions, missing encryption, or missing workload restrictions. Static scanning checks what the file&nbsp;<strong>declares<\/strong>, not necessarily every runtime condition.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Lab 6A \u2014 Check Dockerfile policy<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code><em># The Dockerfile was created in Module 4<\/em>\ntrivy config .\/Dockerfile\ntrivy fs --scanners misconfig .\/Dockerfile\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected:<\/strong>&nbsp;Trivy evaluates built-in Dockerfile checks. Some checks depend on installed policy revisions and image metadata. If you already changed the Dockerfile to non-root, compare with the earlier example and document what improved.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Lab 6B \u2014 Terraform configuration, insecure on purpose<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Where:<\/strong>&nbsp;save the following&nbsp;<strong>complete<\/strong>&nbsp;contents as&nbsp;<code>terraform\/main.tf<\/code>.&nbsp;<strong>Do not run Terraform apply.<\/strong>&nbsp;You do not need an AWS account, an AWS SDK, or Terraform installed for&nbsp;<code>trivy config<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># terraform\/main.tf \u2014 LEARNING EXAMPLE: NEVER APPLY IN A REAL ACCOUNT\nresource \"aws_security_group\" \"open_ssh\" {\n  name        = \"trivy-workshop-open-ssh\"\n  description = \"Intentionally insecure SSH exposure for static scanning\"\n\n  ingress {\n    description = \"INSECURE: SSH exposed to the entire Internet\"\n    from_port   = 22\n    to_port     = 22\n    protocol    = \"tcp\"\n    cidr_blocks = &#91;\"0.0.0.0\/0\"]\n  }\n\n  egress {\n    description = \"Demo egress\"\n    from_port   = 0\n    to_port     = 0\n    protocol    = \"-1\"\n    cidr_blocks = &#91;\"0.0.0.0\/0\"]\n  }\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Bash create and scan:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cat &gt; terraform\/main.tf &lt;&lt;'TF'\nresource \"aws_security_group\" \"open_ssh\" {\n  name        = \"trivy-workshop-open-ssh\"\n  description = \"Intentionally insecure SSH exposure for static scanning\"\n  ingress {\n    description = \"INSECURE: SSH exposed to the entire Internet\"\n    from_port   = 22\n    to_port     = 22\n    protocol    = \"tcp\"\n    cidr_blocks = &#91;\"0.0.0.0\/0\"]\n  }\n  egress {\n    description = \"Demo egress\"\n    from_port   = 0\n    to_port     = 0\n    protocol    = \"-1\"\n    cidr_blocks = &#91;\"0.0.0.0\/0\"]\n  }\n}\nTF\n\ntrivy config .\/terraform\ntrivy config --severity HIGH,CRITICAL .\/terraform\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Windows PowerShell scan:<\/strong>&nbsp;after saving the file in&nbsp;<code>terraform\\main.tf<\/code>, run&nbsp;<code>trivy config .\\terraform<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected:<\/strong>&nbsp;A misconfiguration related to overly broad inbound SSH exposure should be considered by the applicable AWS security-group checks. Note the actual check ID, file path, line, severity, message, and suggested change. If the policy revision does not report the condition, inspect&nbsp;<code>trivy config --help<\/code>, policy\/check update errors, and the file content; do&nbsp;<strong>not<\/strong>&nbsp;fabricate a specific check ID.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fix:<\/strong>&nbsp;In this controlled learning file, change the ingress CIDR from&nbsp;<code>0.0.0.0\/0<\/code>&nbsp;to&nbsp;<code>10.0.0.0\/24<\/code>, then rerun&nbsp;<code>trivy config .\/terraform<\/code>. In a real system, restrict ingress to an explicitly authorized jump host, VPN, or security group; an RFC1918 range alone is not sufficient proof of secure access.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Lab 6C \u2014 Kubernetes YAML, intentionally insecure<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Where:<\/strong>&nbsp;save as&nbsp;<code>kubernetes\/deployment-insecure.yaml<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>apiVersion: apps\/v1\nkind: Deployment\nmetadata:\n  name: insecure-web\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: insecure-web\n  template:\n    metadata:\n      labels:\n        app: insecure-web\n    spec:\n      containers:\n        - name: web\n          image: nginx:1.20\n          ports:\n            - containerPort: 80\n          securityContext:\n            privileged: true\n            allowPrivilegeEscalation: true\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Bash create:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cat &gt; kubernetes\/deployment-insecure.yaml &lt;&lt;'YAML'\napiVersion: apps\/v1\nkind: Deployment\nmetadata:\n  name: insecure-web\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: insecure-web\n  template:\n    metadata:\n      labels:\n        app: insecure-web\n    spec:\n      containers:\n        - name: web\n          image: nginx:1.20\n          ports:\n            - containerPort: 80\n          securityContext:\n            privileged: true\n            allowPrivilegeEscalation: true\nYAML\n\ntrivy config .\/kubernetes\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected:<\/strong>&nbsp;Built-in Kubernetes policies may flag the privileged setting, privilege escalation, or other missing hardening properties. Policy severity and IDs can change. This file is&nbsp;<strong>not intended for deployment<\/strong>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Lab 6D \u2014 Safer Kubernetes pattern<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Save as&nbsp;<code>kubernetes\/deployment-secure.yaml<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>apiVersion: apps\/v1\nkind: Deployment\nmetadata:\n  name: restricted-demo\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: restricted-demo\n  template:\n    metadata:\n      labels:\n        app: restricted-demo\n    spec:\n      containers:\n        - name: worker\n          image: busybox:1.36.1\n          command: &#91;\"sh\", \"-c\", \"sleep 3600\"]\n          securityContext:\n            privileged: false\n            allowPrivilegeEscalation: false\n            runAsNonRoot: true\n            runAsUser: 10001\n            readOnlyRootFilesystem: true\n            capabilities:\n              drop: &#91;\"ALL\"]\n            seccompProfile:\n              type: RuntimeDefault\n          resources:\n            requests:\n              cpu: 25m\n              memory: 32Mi\n            limits:\n              cpu: 100m\n              memory: 64Mi\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Scan and compare:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>trivy config .\/kubernetes\/deployment-insecure.yaml\ntrivy config .\/kubernetes\/deployment-secure.yaml\n\n<em># Try combined classes across the entire workshop (may take longer)<\/em>\ntrivy fs --scanners vuln,misconfig,secret .\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected:<\/strong>&nbsp;The restrictive manifest demonstrates least privilege, a non-root UID, seccomp, dropping capabilities, and resource limits. It may still have findings: scanners can recommend additional controls or discover unrelated issues. This is an illustrative configuration, not a fully reviewed production deployment.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Validation:<\/strong>&nbsp;Review the exact before\/after findings and explain which settings address which risks. Optional&nbsp;<code>kubectl --dry-run=client -f<\/code>&nbsp;behavior varies by kubectl version and cluster schema availability; validation is&nbsp;<strong>not<\/strong>&nbsp;required for static Trivy scanning. Never deploy the insecure manifest just to make an alert appear.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Key practice and troubleshooting<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Run\u00a0<code>trivy config PATH<\/code>\u00a0for IaC-only work; when combining with vulnerability or secret checks, use\u00a0<code>trivy fs --scanners vuln,misconfig,secret PATH<\/code>.<\/li>\n\n\n\n<li>Inspect\u00a0<code>--severity<\/code>\u00a0and\u00a0<code>--format json<\/code>\u00a0when table output feels incomplete.<\/li>\n\n\n\n<li>Trivy policies\/check bundles may update independently of the CLI; record the date of each scan.<\/li>\n\n\n\n<li>If a file is skipped, confirm its extension is supported, check\u00a0<code>--skip-files<\/code>\/<code>--skip-dirs<\/code>, and verify the file parses correctly.<\/li>\n\n\n\n<li>A passing IaC scan cannot substitute for reviewed identity\/access policies, networking, cloud-native services, or runtime behavior.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Checkpoint:<\/strong>&nbsp;Is a Kubernetes manifest scan equivalent to scanning a running cluster? What is risky about&nbsp;<code>privileged: true<\/code>? Why should Terraform misconfiguration scanning happen before&nbsp;<code>apply<\/code>?<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Module 7 \u2014 Secret scanning (10 min)<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">Objectives<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Explain the risks of hardcoded credentials, execute a secret scan, understand false positives, safely configure a custom teaching rule, and know the correct response to a real leaked token.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Why it matters<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">An API token, cloud key, database password, or private key committed to a repository can be exposed through forks, build artifacts, or developer machines. Secret scanning finds certain credential&nbsp;<strong>patterns<\/strong>, not all secrets in every encoding or every location. A detected string might be a test example; a real leaked credential must be&nbsp;<strong>revoked or rotated<\/strong>, not merely deleted from the latest commit.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Lab 7A \u2014 Default secret scanner<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code><em># Scan the local workspace; avoids adding vulnerability noise<\/em>\ntrivy fs --scanners secret .\n\n<em># Scan a repository's secret patterns (authorized repositories only)<\/em>\ntrivy repo --scanners secret https:\/\/github.com\/aquasecurity\/trivy-ci-test\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected:<\/strong>&nbsp;A list of findings or no findings, depending on the files and built-in checks. Trivy has built-in detection patterns for common tokens and private keys, but&nbsp;<code>0 findings<\/code>&nbsp;does not imply every credential is absent.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Lab 7B \u2014 Guaranteed training example with a custom rule<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">To avoid needing a real key, create a&nbsp;<strong>synthetic teaching pattern<\/strong>&nbsp;and an explicit custom rule. The following token is not a usable credential. It is intended to demonstrate a match&nbsp;<strong>only when our custom rule is enabled<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>File&nbsp;<code>secrets\/demo.env<\/code>&nbsp;(entire contents):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Deliberately fake token for this workshop; not for application use\nTRAINING_FAKE_API_TOKEN=DEMO_A1B2C3D4E5F6\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>File&nbsp;<code>trivy-secret.yaml<\/code>&nbsp;(entire contents):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>rules:\n  - id: tutorial-fake-api-token\n    category: general\n    title: Workshop fake API token\n    severity: HIGH\n    path: '.*\\.env$'\n    keywords:\n      - TRAINING_FAKE_API_TOKEN\n    regex: '(?P&lt;secret&gt;TRAINING_FAKE_API_TOKEN=DEMO_&#91;A-Z0-9]{12})'\n    secret-group-name: secret\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Bash creation and scan:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cat &gt; secrets\/demo.env &lt;&lt;'ENVFILE'\n<em># Deliberately fake token for this workshop; not for application use<\/em>\nTRAINING_FAKE_API_TOKEN=DEMO_A1B2C3D4E5F6\nENVFILE\n\ncat &gt; trivy-secret.yaml &lt;&lt;'SECRETCFG'\nrules:\n  - id: tutorial-fake-api-token\n    category: general\n    title: Workshop fake API token\n    severity: HIGH\n    path: '.*\\.env$'\n    keywords:\n      - TRAINING_FAKE_API_TOKEN\n    regex: '(?P&lt;secret&gt;TRAINING_FAKE_API_TOKEN=DEMO_&#91;A-Z0-9]{12})'\n    secret-group-name: secret\nSECRETCFG\n\ntrivy fs --scanners secret \\\n  --secret-config .\/trivy-secret.yaml .\/secrets\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Windows:<\/strong>&nbsp;use your editor to save&nbsp;<code>secrets\\demo.env<\/code>&nbsp;and&nbsp;<code>trivy-secret.yaml<\/code>&nbsp;in the workspace root. Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>trivy fs --scanners secret --secret-config .\\trivy-secret.yaml .\\secrets\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected:<\/strong>&nbsp;A finding associated with&nbsp;<code>tutorial-fake-api-token<\/code>&nbsp;in&nbsp;<code>secrets\/demo.env<\/code>, normally reported at&nbsp;<code>HIGH<\/code>, with the matching line. If absent, check that the YAML indents are spaces,&nbsp;<code>path<\/code>&nbsp;matches&nbsp;<code>.env<\/code>, file encoding is readable, and the&nbsp;<code>--secret-config<\/code>&nbsp;path is correct. Use&nbsp;<code>trivy fs --help<\/code>&nbsp;to verify the flag.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Verification:<\/strong>&nbsp;Point to the rule ID, severity, detected path, and example line. Explain why a custom regex is appropriate for a company-specific credential format&nbsp;<strong>but not proof<\/strong>&nbsp;that every secret is discovered.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Response to a real secret incident<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Identify the owner and scope without posting the full secret in a ticket or chat.<\/li>\n\n\n\n<li>Revoke\/rotate the credential in its issuing service; investigate misuse and other copies.<\/li>\n\n\n\n<li>Remove it from code and artifacts; address repository history following your incident procedure.<\/li>\n\n\n\n<li>Move the credential to an approved secrets manager; narrow its permissions and lifetime.<\/li>\n\n\n\n<li>Add prevention checks and verify that new scans do not reproduce the leak.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Checkpoint:<\/strong>&nbsp;If a GitHub token was genuinely committed and then deleted, is the incident resolved?&nbsp;<strong>No:<\/strong>&nbsp;the token may persist in Git history and copies; rotate\/revoke it first.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Module 8 \u2014 Filtering, triage, and remediation (20 min)<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">Objectives<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Turn raw alerts into an actionable remediation plan; correctly use severity filtering,&nbsp;<code>--ignore-unfixed<\/code>,&nbsp;<code>--exit-code<\/code>, and&nbsp;<code>.trivyignore<\/code>; and verify improvement through re-scanning.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">A practical triage framework<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A security&nbsp;<strong>finding<\/strong>&nbsp;is an observation; a&nbsp;<strong>decision<\/strong>&nbsp;needs context. Work through these questions:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Is the finding real and in scope?<\/strong>\u00a0Check package names, affected versions, advisory conditions, and evidence.<\/li>\n\n\n\n<li><strong>Is the vulnerable code or path exposed?<\/strong>\u00a0Consider external connectivity, permissions, workload role, and active exploitation information.<\/li>\n\n\n\n<li><strong>Is an upgrade available?<\/strong>\u00a0Check fixed versions and vendor-supported package streams.<\/li>\n\n\n\n<li><strong>What is the safest change?<\/strong>\u00a0Upgrade a dependency, rebuild with a patched base, fix IaC, rotate a secret, or add a compensating control.<\/li>\n\n\n\n<li><strong>Can we verify?<\/strong>\u00a0Rebuild or rescan the\u00a0<strong>actual released artifact<\/strong>, not only an unbuilt Dockerfile.<\/li>\n\n\n\n<li><strong>If we accept risk, who approved it and for how long?<\/strong>\u00a0Document owner, rationale, expiry, and follow-up.<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">Lab 8A \u2014 Severity filtering and build gates<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code><em># Scan without failing a shell script solely because findings exist (default behavior)<\/em>\ntrivy image --scanners vuln --severity HIGH,CRITICAL nginx:1.20\n\n<em># Exit status 1 if at least one selected issue is reported; 0 if no selected findings<\/em>\ntrivy image --scanners vuln --severity HIGH,CRITICAL \\\n  --exit-code 1 nginx:1.20\n\n<em># Bash: inspect status of the command immediately above<\/em>\nprintf 'Trivy exit status: %s\\n' \"$?\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Windows PowerShell:<\/strong>&nbsp;after the scan, run&nbsp;<code>Write-Host \"Trivy exit status: $LASTEXITCODE\"<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Important distinction:<\/strong>&nbsp;<code>--exit-code 1<\/code>&nbsp;changes behavior when&nbsp;<strong>qualifying findings<\/strong>&nbsp;exist. It is not a way to classify the root cause of every scan failure. Registry\/network\/parse errors also cause nonzero exit status, often with a different code. CI should report the scan error separately from vulnerability-policy failures.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Lab 8B \u2014 Filter vs. remediate<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code><em># Show only issues with available fixes (useful for upgrade planning)<\/em>\ntrivy image --scanners vuln --severity HIGH,CRITICAL \\\n  --ignore-unfixed nginx:1.20\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Why this matters:<\/strong>&nbsp;Hide\/unhide operations change the&nbsp;<strong>report<\/strong>, not the artifact. A CVE that has no reported fixed version still deserves triage if it is exposed. For IaC settings, fix the corresponding configuration rather than searching for an OS package version.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Lab 8C \u2014 Record a finding and validate a fix<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Create a remediation note (a plain Markdown file is sufficient) using this&nbsp;<strong>template<\/strong>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>## Finding record\n- Target (image\/tag\/digest\/file):\n- Scanner type:\n- Finding ID and actual severity:\n- Package\/check and exact affected location:\n- Evidence and scan date:\n- Reachability \/ business impact:\n- Recommended action and owner:\n- Planned deadline:\n- Verification command and result:\n- Exception approver \/ expiration, if needed:\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Practice:<\/strong>&nbsp;Pick either the open-SSH Terraform finding from Module 6 or a fixable image vulnerability from Module 4. Apply one justified change, rescan, and record a&nbsp;<strong>before\/after difference<\/strong>. For the Terraform example, editing&nbsp;<code>cidr_blocks<\/code>&nbsp;to an approved private range is the lab-level remediation; in real environments use documented network access requirements.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Lab 8D \u2014 Learn suppressions without hiding risk<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The simple&nbsp;<code>.trivyignore<\/code>&nbsp;format accepts finding IDs and optional expiration annotations. Use this&nbsp;<strong>only for an explicitly approved exception<\/strong>, not to make CI green by default.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Illustration only \u2014 replace the ID with an actual finding from your scan:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Sample syntax, not an actual acceptance decision:\nCVE-2099-0001 exp:2026-11-30\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If the finding ID is real and currently in the result, save your reviewed exception to&nbsp;<code>.trivyignore<\/code>&nbsp;in the directory where you run Trivy; then compare&nbsp;<code>trivy image ...<\/code>&nbsp;before and after. To avoid an accidental suppression during later exercises,&nbsp;<strong>delete the training-only&nbsp;<code>.trivyignore<\/code>&nbsp;after the demonstration<\/strong>. Trivy also supports an experimental structured&nbsp;<code>.trivyignore.yaml<\/code>&nbsp;form with extra fields; check the current stability warning before relying on it in production.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Never approve a blanket ignore of every HIGH\/CRITICAL result.<\/strong>&nbsp;Prefer short-lived, scoped, ticket-linked exceptions with an owner and deadline.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Fix strategy cheat sheet<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Finding<\/th><th class=\"has-text-align-left\" data-align=\"left\">Typical first treatment<\/th><th class=\"has-text-align-left\" data-align=\"left\">Verification<\/th><\/tr><\/thead><tbody><tr><td>Vulnerable OS package<\/td><td>Rebuild from vendor-maintained, patched base<\/td><td>Rescan newly built image digest<\/td><\/tr><tr><td>Vulnerable npm\/Python\/etc. dependency<\/td><td>Upgrade compatible direct dependency\/lockfile<\/td><td>Run unit tests, then rescan exact artifact<\/td><\/tr><tr><td>World-open SSH in Terraform<\/td><td>Restrict allowed source or remove SSH access<\/td><td>Scan updated&nbsp;<code>.tf<\/code>, review plan<\/td><\/tr><tr><td>Privileged Kubernetes container<\/td><td>Remove privilege, drop capabilities, use non-root<\/td><td>Scan safe manifest and integration-test workload<\/td><\/tr><tr><td>Hardcoded token<\/td><td>Revoke\/rotate, remove, investigate exposure<\/td><td>Confirm credential invalid and rescan repo<\/td><\/tr><tr><td>Apparently false-positive CVE<\/td><td>Validate vendor advisory and affected configuration<\/td><td>Document evidence, expiry, and exception approval<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Checkpoint:<\/strong>&nbsp;Which is better evidence: a severity-filtered green report or a rescan of a rebuilt fixed image? Why should every suppression have an owner and expiry?<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Module 9 \u2014 Reports and SBOMs (15 min)<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">Objectives<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Export table\/JSON\/SARIF results; understand an SBOM; generate CycloneDX and SPDX inventories; read a machine-readable result without relying on specific CVE totals.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Formats in plain language<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Format<\/th><th class=\"has-text-align-left\" data-align=\"left\">Typical consumer<\/th><th class=\"has-text-align-left\" data-align=\"left\">What it contains<\/th><\/tr><\/thead><tbody><tr><td>Table<\/td><td>Human reading a terminal or log<\/td><td>Convenient security findings<\/td><\/tr><tr><td>JSON<\/td><td>Scripts, dashboards, ingestion APIs<\/td><td>Structured Trivy results and metadata<\/td><\/tr><tr><td>SARIF<\/td><td>Code scanning integrations<\/td><td>Standardized static-analysis findings<\/td><\/tr><tr><td>CycloneDX<\/td><td>Software supply-chain and inventory systems<\/td><td>SBOM package and component relationships; vulnerability inclusion depends on options<\/td><\/tr><tr><td>SPDX \/ SPDX JSON<\/td><td>Component\/license inventory consumers<\/td><td>Machine-readable software package inventory<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">An&nbsp;<strong>SBOM<\/strong>&nbsp;(Software Bill of Materials) answers&nbsp;<strong>\u201cWhat software components are present?\u201d<\/strong>&nbsp;It does not by itself prove that the application is vulnerable or safe. Trivy&#8217;s CycloneDX generation, by default, prioritizes inventory; to add vulnerability findings explicitly pass&nbsp;<code>--scanners vuln<\/code>&nbsp;where supported.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Lab 9A \u2014 Create reports<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code><em># Always run from the trivy-workshop root<\/em>\nmkdir -p reports\n\n<em># 1. JSON vulnerability results<\/em>\ntrivy image --scanners vuln --format json \\\n  --output reports\/image-findings.json nginx:1.20\n\n<em># 2. SARIF results for infrastructure-as-code<\/em>\ntrivy config --format sarif \\\n  --output reports\/iac-findings.sarif .\/terraform\n\n<em># 3. CycloneDX SBOM (component inventory)<\/em>\ntrivy image --format cyclonedx \\\n  --output reports\/image-sbom.cdx.json nginx:1.20\n\n<em># 4. SPDX JSON SBOM (alternate standard)<\/em>\ntrivy image --format spdx-json \\\n  --output reports\/image-sbom.spdx.json nginx:1.20\n\n<em># 5. Optional: scan an already generated SBOM for known vulnerabilities<\/em>\ntrivy sbom --scanners vuln reports\/image-sbom.cdx.json\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>PowerShell:<\/strong>&nbsp;create the&nbsp;<code>reports<\/code>&nbsp;folder first with&nbsp;<code>New-Item -ItemType Directory -Path .\\reports -Force | Out-Null<\/code>, then run the same&nbsp;<code>trivy<\/code>&nbsp;commands without Bash line-continuation characters (<code>\\<\/code>)\u2014put each command on a single PowerShell line.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected:<\/strong>&nbsp;Four files exist under&nbsp;<code>reports\/<\/code>. Contents vary by image digest and current database. The SBOM file should contain a component inventory even when vulnerability results are empty; the SARIF file should be a valid JSON-like structured report conforming to SARIF.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Verify with Bash:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ls -lh reports\/\njq -r '.ArtifactName \/\/ .Metadata.ImageID \/\/ \"Artifact metadata unavailable\"' \\\n  reports\/image-findings.json\njq -r '.bomFormat, .specVersion, (.components | length)' \\\n  reports\/image-sbom.cdx.json\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Windows PowerShell verification (no&nbsp;<code>jq<\/code>&nbsp;required):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Get-ChildItem .\\reports\n$Sbom = Get-Content .\\reports\\image-sbom.cdx.json -Raw | ConvertFrom-Json\n$Sbom.bomFormat\n$Sbom.specVersion\n$Sbom.components.Count\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Understand:<\/strong>&nbsp;the image-findings JSON usually contains&nbsp;<code>Results<\/code>&nbsp;entries; vulnerabilities may be absent in some entries. Never assume all fields are populated.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Lab 9B \u2014 Extract a basic triage table from JSON (optional&nbsp;<code>jq<\/code>)<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>jq -r '\n  .Results&#91;]? | .Target as $target |\n  .Vulnerabilities&#91;]? |\n  select(.Severity == \"HIGH\" or .Severity == \"CRITICAL\") |\n  &#91;$target, .VulnerabilityID, .PkgName, .InstalledVersion,\n   (.FixedVersion \/\/ \"not listed\"), .Severity] | @tsv\n' reports\/image-findings.json\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected:<\/strong>&nbsp;Tab-separated finding rows if there are HIGH\/CRITICAL vulnerabilities. An empty result simply means no entries met this query; check the unfiltered report before concluding there are no vulnerabilities at other severities.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Lab 9C \u2014 Include vulnerability records in CycloneDX deliberately<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>trivy image --scanners vuln --format cyclonedx \\\n  --output reports\/image-sbom-with-vulns.cdx.json nginx:1.20\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Why:<\/strong>&nbsp;A software inventory and a vulnerability report serve related but distinct purposes. Ensure downstream consumers understand which data your SBOM contains.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Reporting security considerations<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Trivy reports can include file paths, dependency metadata, and secret-scan details. Treat CI artifacts as potentially sensitive; restrict repository\/report access, choose a retention period, avoid publishing raw leaked credentials, and review third-party ingestion destinations.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Checkpoint:<\/strong>&nbsp;Which output format fits GitHub code-scanning ingestion? Which formats represent package inventories? Why could an SBOM contain components but no vulnerability objects?<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Module 10 \u2014 CI\/CD integration (15 min)<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">Objectives<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Understand a simple&nbsp;<strong>security gate<\/strong>, add a Trivy GitHub Actions workflow, explain its failure modes, and distinguish build failure from a completed scan with findings. GitHub access is only needed to&nbsp;<strong>run<\/strong>&nbsp;the workflow; writing the YAML is a local exercise.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">How CI security gates work<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A CI\/CD pipeline can call Trivy automatically when code changes. A report answers&nbsp;<strong>what was found<\/strong>; a security gate determines&nbsp;<strong>whether the pipeline should continue<\/strong>&nbsp;according to an agreed policy. Teams may choose advisory-only checks first and progressively enforce approved severity thresholds. For mature pipelines, consider scan coverage, exception governance, SBOM retention, and false-positive management.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>flowchart LR\n    A&#91;Developer commit \/ pull request] --&gt; B&#91;CI checks out code]\n    B --&gt; C&#91;Trivy scans filesystem, dependencies, IaC and secrets]\n    C --&gt; D{Issues match policy?}\n    D --&gt;|No| E&#91;CI security gate passes]\n    D --&gt;|Yes| F&#91;CI fails and publishes findings]\n    F --&gt; G&#91;Remediate or approve time-bound exception]\n    G --&gt; A\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Lab 10A \u2014 Write a GitHub Actions workflow<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Where:<\/strong>&nbsp;<code>trivy-workshop\/.github\/workflows\/trivy-security.yml<\/code>.&nbsp;<strong>Prerequisites:<\/strong>&nbsp;local editor; an actual GitHub repository is optional. The&nbsp;<code>aquasecurity\/trivy-action<\/code>&nbsp;usage shown below is based on the official action&nbsp;<strong>v0.36.0<\/strong>&nbsp;interface and installs Trivy&nbsp;<strong>v0.75.0<\/strong>&nbsp;for the run.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Complete file contents:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>name: Trivy Security Check\n\non:\n  push:\n    branches: &#91;main]\n  pull_request:\n\npermissions:\n  contents: read\n\njobs:\n  security-scan:\n    name: Scan source and IaC\n    runs-on: ubuntu-24.04\n    steps:\n      - name: Checkout source\n        uses: actions\/checkout@v4\n\n      - name: Trivy filesystem security gate\n        uses: aquasecurity\/trivy-action@v0.36.0\n        with:\n          scan-type: fs\n          scan-ref: '.'\n          scanners: 'vuln,secret,misconfig'\n          format: table\n          severity: 'HIGH,CRITICAL'\n          exit-code: '1'\n          version: 'v0.75.0'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Bash creation (copy ready):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mkdir -p .github\/workflows\ncat &gt; .github\/workflows\/trivy-security.yml &lt;&lt;'WORKFLOW'\nname: Trivy Security Check\non:\n  push:\n    branches: &#91;main]\n  pull_request:\npermissions:\n  contents: read\njobs:\n  security-scan:\n    name: Scan source and IaC\n    runs-on: ubuntu-24.04\n    steps:\n      - name: Checkout source\n        uses: actions\/checkout@v4\n      - name: Trivy filesystem security gate\n        uses: aquasecurity\/trivy-action@v0.36.0\n        with:\n          scan-type: fs\n          scan-ref: '.'\n          scanners: 'vuln,secret,misconfig'\n          format: table\n          severity: 'HIGH,CRITICAL'\n          exit-code: '1'\n          version: 'v0.75.0'\nWORKFLOW\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Windows:<\/strong>&nbsp;create&nbsp;<code>.github\\workflows\\trivy-security.yml<\/code>&nbsp;using your editor, copying the full YAML above. GitHub Actions YAML syntax is the same on Windows\/macOS\/Linux.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Explain the important workflow options<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Item<\/th><th class=\"has-text-align-left\" data-align=\"left\">Meaning<\/th><\/tr><\/thead><tbody><tr><td><code>pull_request<\/code><\/td><td>Run when a PR is opened or updated<\/td><\/tr><tr><td><code>push<\/code>&nbsp;on&nbsp;<code>main<\/code><\/td><td>Scan the main branch when it changes<\/td><\/tr><tr><td><code>permissions: contents: read<\/code><\/td><td>Keep default token privileges low<\/td><\/tr><tr><td><code>actions\/checkout@v4<\/code><\/td><td>Give the job a copy of source files<\/td><\/tr><tr><td><code>scan-type: fs<\/code><\/td><td>Scan the checked-out filesystem, not an already-built image<\/td><\/tr><tr><td><code>scanners<\/code><\/td><td>Enable vulnerabilities, secrets, and misconfiguration checks explicitly<\/td><\/tr><tr><td><code>severity<\/code><\/td><td>Enforce only the severity bands listed<\/td><\/tr><tr><td><code>exit-code: '1'<\/code><\/td><td>Fail the step if matching findings exist<\/td><\/tr><tr><td><code>version<\/code><\/td><td>Choose the CLI version; independent of the action version<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected:<\/strong>&nbsp;On a GitHub repository with Actions enabled, the job checks out code and runs Trivy. If you commit the&nbsp;<strong>intentionally insecure<\/strong>&nbsp;Terraform or Kubernetes example, a matching HIGH\/CRITICAL IaC result may fail the gate. This is intentional lab behavior. A scan error (bad YAML, blocked DB download, inaccessible repository) is a different cause of failure and must be diagnosed separately.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Verify locally first:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>trivy fs --scanners vuln,secret,misconfig \\\n  --severity HIGH,CRITICAL --exit-code 1 .\n<em># Bash: inspect result immediately afterwards<\/em>\nprintf 'Status: %s\\n' \"$?\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>GitHub workflow validation:<\/strong>&nbsp;Commit the YAML into a&nbsp;<strong>test repository you control<\/strong>, open the GitHub Actions tab, inspect the exact Trivy step, read its output, fix one finding, and compare a new run. GitHub account\/repository setup and pushing are separate prerequisites; never automatically upload the lab to a company repository.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Keep lab reports and fake secrets out of Git<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Before committing, create a&nbsp;<code>.gitignore<\/code>&nbsp;in the workshop root:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Local outputs and deliberately synthetic practice files\nreports\/\nsecrets\/\n.trivyignore\n\n# Generic generated or local-only items\n*.log\n.env\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This exclusion does not make a genuine leaked credential safe and is not a replacement for secret scanning. It simply avoids accidentally publishing practice outputs. If you want the CI demonstration to fail on the insecure Terraform rule, commit only the&nbsp;<strong>synthetic IaC lab file<\/strong>, not a real secret.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Production hardening: action pinning and reporting<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The above&nbsp;<code>@v0.36.0<\/code>&nbsp;and&nbsp;<code>@v4<\/code>&nbsp;tags are convenient for a&nbsp;<strong>teaching example<\/strong>. For a production pipeline, resolve and pin every third-party GitHub Action to its verified,&nbsp;<strong>full commit SHA<\/strong>&nbsp;using your supply-chain review process. Do not invent a SHA or copy one from an unrelated release. Restrict workflow permissions and avoid executing unreviewed privileged workflow code from untrusted PRs.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Optional SARIF pattern:<\/strong>&nbsp;Run a separate reporting step with&nbsp;<code>format: 'sarif'<\/code>,&nbsp;<code>output: 'trivy-results.sarif'<\/code>, and&nbsp;<code>exit-code: '0'<\/code>, then use&nbsp;<code>github\/codeql-action\/upload-sarif<\/code>&nbsp;with&nbsp;<code>permissions: security-events: write<\/code>&nbsp;(where GitHub code scanning is supported). A separate gating step can still fail on qualifying issues. Avoid uploading raw secret contents to publicly visible artifacts.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>CI\/CD outside GitHub:<\/strong>&nbsp;GitLab CI and Jenkins execute the same CLI with their own runners and artifact storage. The key portable command is&nbsp;<code>trivy fs --scanners vuln,secret,misconfig --severity HIGH,CRITICAL --exit-code 1 .<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Checkpoint:<\/strong>&nbsp;What is the difference between&nbsp;<code>exit-code: '0'<\/code>&nbsp;for report collection and&nbsp;<code>exit-code: '1'<\/code>&nbsp;for a gate? Why should third-party workflow dependencies be commit-SHA pinned in production?<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Module 11 \u2014 Kubernetes cluster scanning (10 min)<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">Objectives<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Distinguish local YAML from live-cluster scanning; understand&nbsp;<code>trivy k8s<\/code>, common RBAC requirements, the node collector, namespace scoping, and the purpose of Trivy Operator.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Manifest vs. cluster: not the same scan<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><code>trivy config kubernetes\/<\/code>&nbsp;analyzes your saved Kubernetes YAML; it requires&nbsp;<strong>no cluster<\/strong>&nbsp;and has no insight into arbitrary settings applied to a running deployment by other controllers.&nbsp;<code>trivy k8s<\/code>&nbsp;uses Kubernetes API access to analyze cluster resources and associated images and can inspect broader posture. As of this document, the official guide labels Kubernetes-target scanning&nbsp;<strong>experimental<\/strong>. Some scans may download many images and, by default, the node collector may create Kubernetes Jobs. Do not use a production cluster casually.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Lab 11A \u2014 Everyone can scan manifests<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>trivy config .\/kubernetes\/deployment-insecure.yaml\ntrivy config .\/kubernetes\/deployment-secure.yaml\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected:<\/strong>&nbsp;Configuration findings (or none) for each YAML. Compare the security contexts and any check IDs. This is the main 10-minute course path if you have no cluster.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Lab 11B \u2014 Optional read-focused cluster demonstration<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Prerequisites:<\/strong>&nbsp;<code>kubectl<\/code>&nbsp;installed, a kubeconfig for a&nbsp;<strong>non-production cluster you are authorized to inspect<\/strong>, organization-approved permission to list resources, and an understanding of the cluster&#8217;s cost and network policy. Confirm the current context&nbsp;<strong>before<\/strong>&nbsp;running any cluster scan.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><em># Review before proceeding<\/em>\nkubectl config current-context\nkubectl auth can-i list deployments --all-namespaces\nkubectl auth can-i list pods --all-namespaces\n\n<em># If authorized: scope to default namespace, skip image downloads,<\/em>\n<em># and disable node-collector Jobs to reduce impact.<\/em>\ntrivy k8s --scanners misconfig --report summary \\\n  --include-namespaces default \\\n  --skip-images \\\n  --disable-node-collector\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected:<\/strong>&nbsp;A summary of misconfiguration results for resources Trivy can discover under your credentials.&nbsp;<code>--skip-images<\/code>&nbsp;omits image vulnerability\/secret analysis, and&nbsp;<code>--disable-node-collector<\/code>&nbsp;omits node-hardening checks, so this is&nbsp;<strong>not<\/strong>&nbsp;a comprehensive cluster audit. RBAC denied errors mean your role lacks necessary reads; do not grant cluster-admin merely to complete a workshop.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fuller scan, only with explicit approval:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><em># May download many images and create node-collector Jobs by default.<\/em>\n<em># Read the official role\/resource-impact guidance before running.<\/em>\ntrivy k8s --report summary\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Trivy Operator is a separate deployment that schedules recurring security work&nbsp;<strong>inside<\/strong>&nbsp;Kubernetes and exposes results over time. Installing the Operator and configuring metrics, storage, or its RBAC are&nbsp;<strong>advanced topics<\/strong>&nbsp;and are outside the 3-hour workshop.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Checkpoint:<\/strong>&nbsp;Why can&nbsp;<code>trivy k8s<\/code>&nbsp;demand Kubernetes permissions that&nbsp;<code>trivy config<\/code>&nbsp;does not? What security coverage is omitted by&nbsp;<code>--skip-images<\/code>&nbsp;and&nbsp;<code>--disable-node-collector<\/code>?<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Module 12 \u2014 Capstone exercise (15 min)<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">Scenario<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">You are a junior DevSecOps engineer asked to assess a small application before release. It has a container image, a Dockerfile, a Terraform security group, Kubernetes YAML, and an example secret. Your task is to identify real observations, prioritize them, fix one problem, preserve evidence, and suggest a simple CI gate.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Fifteen-minute guided task<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Minutes 0\u20133 \u2014 Inventory the workspace<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><em># Bash: files present in workshop (Windows: Get-ChildItem -Recurse)<\/em>\nfind . -maxdepth 3 -type f | sort\ntrivy --version\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Write down which files are&nbsp;<strong>source<\/strong>,&nbsp;<strong>configurations<\/strong>, and&nbsp;<strong>reports<\/strong>. Confirm your Trivy version.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Minutes 3\u20137 \u2014 Perform three different scans<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>trivy image --scanners vuln --severity HIGH,CRITICAL nginx:1.20\ntrivy config .\/terraform\ntrivy config .\/kubernetes\/deployment-insecure.yaml\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then run the safe custom secret demo if time permits:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>trivy fs --scanners secret --secret-config .\/trivy-secret.yaml .\/secrets\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Minutes 7\u201310 \u2014 Fix one meaningful finding<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Change the Terraform security-group CIDR from&nbsp;<code>0.0.0.0\/0<\/code>&nbsp;to the approved lab-restricted&nbsp;<code>10.0.0.0\/24<\/code>, or improve the Dockerfile security context, or change the Kubernetes manifest&#8217;s privileged setting and privilege escalation. Re-scan the exact edited file and record what changed. If a scanner did not flag the issue, still explain the insecure behavior rather than claiming a finding it did not report.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Minutes 10\u201313 \u2014 Export evidence<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mkdir -p reports\ntrivy image --scanners vuln --format json \\\n  --output reports\/capstone-image.json nginx:1.20\ntrivy config --format sarif \\\n  --output reports\/capstone-iac.sarif .\/terraform\ntrivy image --format cyclonedx \\\n  --output reports\/capstone-sbom.json nginx:1.20\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">PowerShell: run each Trivy line as a single command and create&nbsp;<code>reports<\/code>&nbsp;using&nbsp;<code>New-Item -ItemType Directory -Force reports<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Minutes 13\u201315 \u2014 Prepare a short release recommendation<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Complete this report in plain English (replace brackets with your actual observations):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Trivy security assessment \u2014 workshop\n\n- Scan date and CLI version: &#91;actual]\n- Image and repository targets: &#91;actual]\n- Vulnerability observations: &#91;actual counts\/IDs, or none]\n- IaC findings: &#91;actual check IDs, files, severity, or none]\n- Secret demonstration: &#91;detected\/not detected, custom rule used]\n- Top priority and why: &#91;severity + exposure + impact]\n- Action taken: &#91;exact before\/after change]\n- Verification: &#91;exact command and actual result]\n- Remaining risk or blockers: &#91;actual]\n- CI gate recommendation: &#91;scanners, severities, exception handling]\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Passing criteria<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>[ ] Correctly choose at least three scan targets and their flags.<\/li>\n\n\n\n<li>[ ] Explain the difference between package CVEs and IaC misconfigurations.<\/li>\n\n\n\n<li>[ ] Record one actual result without inventing counts or IDs.<\/li>\n\n\n\n<li>[ ] Make a security improvement and demonstrate a rescan.<\/li>\n\n\n\n<li>[ ] Create a JSON report, SARIF file, and CycloneDX SBOM.<\/li>\n\n\n\n<li>[ ] Explain what the CI policy should fail on and what it should merely report.<\/li>\n\n\n\n<li>[ ] State what remains\u00a0<strong>unknown<\/strong>\u00a0after Trivy scanning.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Stretch challenge (outside 3 hours):<\/strong>&nbsp;Add a separate image-scan job for a locally built application image, record the image digest, pin Actions by full SHA, and automate publishing a signed SBOM according to company policy.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Troubleshooting and production checklist<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">Troubleshooting table<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Read the&nbsp;<strong>symptom<\/strong>, identify likely cause, perform diagnosis, make the safest corrective change, and verify. Avoid running commands that discard files or change cluster state unless explicitly authorized.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Symptom<\/th><th class=\"has-text-align-left\" data-align=\"left\">Likely cause<\/th><th class=\"has-text-align-left\" data-align=\"left\">Diagnose<\/th><th class=\"has-text-align-left\" data-align=\"left\">Safe fix and verification<\/th><\/tr><\/thead><tbody><tr><td><code>trivy: command not found<\/code>&nbsp;\/&nbsp;<code>trivy<\/code>&nbsp;is not recognized<\/td><td>CLI absent or outside PATH<\/td><td><code>trivy --version<\/code>; Bash:&nbsp;<code>command -v trivy<\/code>; PowerShell:&nbsp;<code>Get-Command trivy<\/code><\/td><td>Install via official method, fix PATH, reopen terminal; rerun&nbsp;<code>trivy --version<\/code><\/td><\/tr><tr><td>Ubuntu APT key or signed repository error<\/td><td>Keyring not installed, key\/network mismatch<\/td><td>Inspect&nbsp;<code>\/etc\/apt\/sources.list.d\/trivy.list<\/code>, keyring path, and exact error<\/td><td>Re-follow official signed repository setup;&nbsp;<strong>never<\/strong>&nbsp;bypass repository signature checks; verify&nbsp;<code>apt-cache policy trivy<\/code><\/td><\/tr><tr><td>macOS&nbsp;<code>brew<\/code>\/Trivy not found<\/td><td>Homebrew shell initialization missing<\/td><td><code>brew --version<\/code>,&nbsp;<code>brew --prefix<\/code><\/td><td>Follow Homebrew&#8217;s shell-environment output and reopen Terminal; verify&nbsp;<code>trivy --version<\/code><\/td><\/tr><tr><td>Windows download says 404<\/td><td>Asset name or CPU architecture mismatch<\/td><td>Check official release assets and&nbsp;<code>$Url<\/code><\/td><td>Download correct signed\/checksummed release matching architecture; verify executable version<\/td><\/tr><tr><td><code>failed to download vulnerability DB<\/code><\/td><td>Proxy, DNS, TLS, policy, rate limit, or no network<\/td><td>Re-run with&nbsp;<code>--debug<\/code>; check DNS and allowed registry endpoints<\/td><td>Request correct proxy\/allowlist or retry; do not disable TLS verification; verify image scan succeeds<\/td><\/tr><tr><td><code>TOOMANYREQUESTS<\/code>&nbsp;\/ registry limit<\/td><td>Public registry pulls exceeded quota<\/td><td>Check registry response and rate-limit notice<\/td><td>Authenticate to approved registry or use approved mirror\/cache; rerun scan<\/td><\/tr><tr><td><code>Cannot connect to the Docker daemon<\/code><\/td><td>Docker stopped, socket unavailable, permission denied<\/td><td><code>docker info<\/code><\/td><td>Start\/configure Docker; use remote public-image scan or approved local socket access; verify&nbsp;<code>docker info<\/code><\/td><\/tr><tr><td>Trivy reports no dependency findings<\/td><td>No supported package metadata, scanner disabled, or genuinely no known CVEs<\/td><td><code>trivy fs --scanners vuln .<\/code>; inspect lockfiles<\/td><td>Scan supported lockfiles\/image artifacts; update database; do&nbsp;<strong>not<\/strong>&nbsp;assert zero risk from zero findings<\/td><\/tr><tr><td>A Dockerfile\/Terraform check does not appear<\/td><td>Misconfiguration scanner not enabled or file skipped<\/td><td><code>trivy config PATH<\/code>; inspect file and parse errors<\/td><td>Select&nbsp;<code>config<\/code>&nbsp;or&nbsp;<code>--scanners misconfig<\/code>; verify file extension and path<\/td><\/tr><tr><td>Synthetic secret is not detected<\/td><td>Custom rule not loaded, regex or path mismatch<\/td><td>Check&nbsp;<code>trivy-secret.yaml<\/code>&nbsp;and&nbsp;<code>secrets\/demo.env<\/code>; use&nbsp;<code>--secret-config<\/code><\/td><td>Correct indentation\/path\/group and rerun secret-only scan<\/td><\/tr><tr><td><code>unknown flag<\/code><\/td><td>CLI too old\/new, mistyped flag, wrong command<\/td><td><code>trivy --version<\/code>;&nbsp;<code>trivy &lt;target&gt; --help<\/code><\/td><td>Use CLI-specific documented flags or install target release; verify&nbsp;<code>--help<\/code><\/td><\/tr><tr><td>Scan takes too long<\/td><td>First DB download, large image\/repo, many files<\/td><td>Note target size, logs, timeout<\/td><td>Narrow scope, skip unrelated directories or use an approved cache; check again without silently dropping important scanners<\/td><\/tr><tr><td>GitHub Actions fails immediately<\/td><td>Invalid YAML\/action version, DB download error, missing repo permissions<\/td><td>Open the failing step&#8217;s log<\/td><td>Resolve syntax or access cause; do not suppress the failure; rerun on a PR<\/td><\/tr><tr><td>GitHub Actions fails with findings<\/td><td><code>exit-code: '1'<\/code>&nbsp;and policy matched<\/td><td>Inspect Trivy output and exact finding IDs<\/td><td>Remediate or approved time-bound exception; rerun and confirm outcome<\/td><\/tr><tr><td>Kubernetes&nbsp;<code>Forbidden<\/code>&nbsp;\/ RBAC denied<\/td><td>Read permissions absent or node collector needs extra rights<\/td><td><code>kubectl auth can-i ...<\/code>&nbsp;and review official required roles<\/td><td>Use approved least-privilege role; do not assign cluster-admin as a shortcut; rerun scoped scan<\/td><\/tr><tr><td>No Kubernetes context<\/td><td><code>kubectl<\/code>\/kubeconfig missing<\/td><td><code>kubectl config current-context<\/code><\/td><td>Obtain authorized context; use&nbsp;<code>trivy config<\/code>&nbsp;on manifests meanwhile<\/td><\/tr><tr><td>YAML parsing error<\/td><td>Indentation or invalid YAML syntax<\/td><td>Open file in editor with YAML validation; inspect actual line<\/td><td>Fix syntax and rerun&nbsp;<code>trivy config PATH<\/code><\/td><\/tr><tr><td><code>jq: command not found<\/code><\/td><td>Optional tool missing<\/td><td><code>jq --version<\/code><\/td><td>Install&nbsp;<code>jq<\/code>&nbsp;from a trusted package manager, or use PowerShell&nbsp;<code>ConvertFrom-Json<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Debugging commands: non-destructive<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>trivy --version\ntrivy image --help\ntrivy config --help\ntrivy --debug image --scanners vuln alpine:3.20\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><code>--debug<\/code>&nbsp;provides diagnostic logs; redact tokens, private URLs, and sensitive paths before sharing logs externally. Avoid&nbsp;<code>trivy clean<\/code>&nbsp;or deleting the cache casually; removing cached databases requires downloading them again and can break scanning in restricted networks.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Production readiness: minimum controls<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A useful Trivy implementation is more than a single green pipeline:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>[ ]\u00a0<strong>Coverage policy:<\/strong>\u00a0identify target types (image, dependency\/lockfile, IaC, secret, Kubernetes) and when each is scanned.<\/li>\n\n\n\n<li>[ ]\u00a0<strong>Reproducibility:<\/strong>\u00a0record CLI\/action version, vulnerability DB freshness, image digest, commit SHA, and timestamp for evidence.<\/li>\n\n\n\n<li>[ ]\u00a0<strong>Action integrity:<\/strong>\u00a0verify release checksum\/signature where available; pin third-party GitHub Actions to verified full commit SHAs in production.<\/li>\n\n\n\n<li>[ ]\u00a0<strong>Least privilege:<\/strong>\u00a0keep registry tokens, GitHub tokens, and cluster service accounts read-only where possible; isolate runners.<\/li>\n\n\n\n<li>[ ]\u00a0<strong>Credential protection:<\/strong>\u00a0store secrets in a proper manager; restrict report\/artifact access; rotate exposed keys immediately.<\/li>\n\n\n\n<li>[ ]\u00a0<strong>Triage ownership:<\/strong>\u00a0vulnerability remediation has a responsible owner, priority, deadline, and evidence of testing.<\/li>\n\n\n\n<li>[ ]\u00a0<strong>Exceptions:<\/strong>\u00a0require approval, rationale, scope, and expiration; regularly review all ignored findings.<\/li>\n\n\n\n<li>[ ]\u00a0<strong>Coverage limits:<\/strong>\u00a0supplement Trivy with SAST, tests, runtime detection, penetration testing, cloud security posture management, and dependency maintenance.<\/li>\n\n\n\n<li>[ ]\u00a0<strong>CI reliability:<\/strong>\u00a0distinguish scan-service failures from policy violations and avoid untracked silencing of checks.<\/li>\n\n\n\n<li>[ ]\u00a0<strong>Kubernetes caution:<\/strong>\u00a0understand permission requirements, possible node-collector Jobs, registry downloads, costs, and production change-control.<\/li>\n\n\n\n<li>[ ]\u00a0<strong>SBOM lifecycle:<\/strong>\u00a0store SBOMs with the corresponding release artifact and restrict access according to organizational policy.<\/li>\n\n\n\n<li>[ ]\u00a0<strong>Continuous improvement:<\/strong>\u00a0measure newly introduced findings, age of unresolved issues, false positives, and repeat vulnerabilities over time.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">From intermediate to advanced: next-step topics<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The following are&nbsp;<strong>deliberately outside<\/strong>&nbsp;the three-hour core and make good follow-up workshops:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Writing and testing custom misconfiguration checks with Rego\/OPA.<\/li>\n\n\n\n<li>Evaluating VEX statements, reachability, and exploit context instead of severity alone.<\/li>\n\n\n\n<li>Automating SBOM generation, attestation, signing, and provenance verification.<\/li>\n\n\n\n<li>Private registries, authentication, registry mirrors, cache and database distribution, and air-gapped environments.<\/li>\n\n\n\n<li>Enterprise exception policy, policy-as-code, scan baselines, and multi-team reporting.<\/li>\n\n\n\n<li>Deploying and managing Trivy Operator in Kubernetes with secure RBAC and metrics.<\/li>\n\n\n\n<li>Running concurrent scans at scale and optimizing CI pipelines, performance, and data freshness.<\/li>\n\n\n\n<li>Using Trivy alongside runtime security tools and cloud security posture programs.<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Quick command reference, quiz answers, and official sources<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">Copy-ready command reference<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code><em># Installation check<\/em>\ntrivy --version\n\n<em># Container images<\/em>\ntrivy image nginx:1.20\ntrivy image --scanners vuln --severity HIGH,CRITICAL nginx:1.20\ntrivy image --scanners vuln --ignore-unfixed nginx:1.20\n\n<em># Source code and repositories<\/em>\ntrivy fs .\ntrivy fs --scanners vuln,secret,misconfig .\ntrivy repo https:\/\/github.com\/aquasecurity\/trivy-ci-test\n\n<em># IaC-only checks<\/em>\ntrivy config .\/Dockerfile\ntrivy config .\/terraform\ntrivy config .\/kubernetes\n\n<em># Secrets<\/em>\ntrivy fs --scanners secret --secret-config .\/trivy-secret.yaml .\/secrets\n\n<em># Reports, software inventory, and scan gates<\/em>\ntrivy image --scanners vuln --format json --output reports\/image.json nginx:1.20\ntrivy config --format sarif --output reports\/iac.sarif .\/terraform\ntrivy image --format cyclonedx --output reports\/sbom.json nginx:1.20\ntrivy image --format spdx-json --output reports\/sbom.spdx.json nginx:1.20\ntrivy image --scanners vuln --severity HIGH,CRITICAL --exit-code 1 nginx:1.20\n\n<em># Kubernetes: requires approved access; disable node collector in casual demos<\/em>\ntrivy k8s --scanners misconfig --report summary \\\n  --include-namespaces default --skip-images --disable-node-collector\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Knowledge checks: suggested answers<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Module<\/th><th class=\"has-text-align-left\" data-align=\"left\">Checkpoint answer<\/th><\/tr><\/thead><tbody><tr><td>1<\/td><td>A CVE scan finds known vulnerable components or misconfigurations; runtime intrusion tools observe or respond to behavior, which Trivy does not fully cover.<\/td><\/tr><tr><td>2<\/td><td>A CVE identifies a published issue, not an attack. Missing fixed versions do not make a CVE false; OS maintainers may backport fixes.<\/td><\/tr><tr><td>3<\/td><td>The first scan often downloads vulnerability\/check data. Target is the artifact; scanner is the type of issue checked. Public-registry image scans do not inherently require a local Docker daemon.<\/td><\/tr><tr><td>4<\/td><td><code>--ignore-unfixed<\/code>&nbsp;only filters findings; it changes no packages. Dockerfile hardening does not necessarily update vulnerable OS or language libraries.<\/td><\/tr><tr><td>5<\/td><td>Default&nbsp;<code>fs<\/code>&nbsp;scanning includes vulnerability and secret checks;&nbsp;<code>misconfig<\/code>&nbsp;needs explicit selection. Lockfiles reveal resolved versions.<\/td><\/tr><tr><td>6<\/td><td>A YAML scan inspects source configuration; cluster scanning inspects discovered live resources. Privileged containers can bypass isolation. Pre-deploy IaC scanning catches issues before they become infrastructure.<\/td><\/tr><tr><td>7<\/td><td>Deleting an exposed token from a current commit is insufficient; revoke\/rotate it and investigate history\/copies.<\/td><\/tr><tr><td>8<\/td><td>A rebuilt-and-rescanned artifact provides better fix evidence than filtered output. Exceptions need owner and expiry so suppressed risk is reassessed.<\/td><\/tr><tr><td>9<\/td><td>SARIF supports code-scanning ingestion; CycloneDX and SPDX represent SBOMs. Component inventories can exist without vulnerability data.<\/td><\/tr><tr><td>10<\/td><td>Report-only&nbsp;<code>exit-code: 0<\/code>&nbsp;allows collection;&nbsp;<code>exit-code: 1<\/code>&nbsp;enforces a gate. Full commit-SHA pinning limits unexpected third-party workflow changes.<\/td><\/tr><tr><td>11<\/td><td><code>k8s<\/code>&nbsp;needs Kubernetes API permission and may do cluster-impacting work; static&nbsp;<code>config<\/code>&nbsp;does not. Skipping images and the node collector omits image findings and node hardening assessment.<\/td><\/tr><tr><td>12<\/td><td>A good report records actual findings, a justified fix, proof from rescanning, a clear CI recommendation, and unknown residual risks.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Official documentation and last-verified references<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">All links below are to upstream vendor\/project or relevant standard documentation.&nbsp;<strong>Last checked: October 11, 2026.<\/strong>&nbsp;Some pages show examples using older images by design; always check your installed release&#8217;s&nbsp;<code>--help<\/code>&nbsp;output.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><a href=\"https:\/\/trivy.dev\/docs\/latest\/\">Trivy official documentation (latest)<\/a>\u00a0\u2014 introduction, targets, supported scanners.<\/li>\n\n\n\n<li><a href=\"https:\/\/github.com\/aquasecurity\/trivy\/releases\/tag\/v0.75.0\">Trivy v0.75.0 GitHub release<\/a>\u00a0\u2014 release identity, assets, and verification materials; released October 1, 2026.<\/li>\n\n\n\n<li><a href=\"https:\/\/trivy.dev\/docs\/latest\/getting-started\/installation\/\">Official installation methods<\/a>\u00a0\u2014 Ubuntu, macOS, Windows, binary, container.<\/li>\n\n\n\n<li><a href=\"https:\/\/trivy.dev\/docs\/latest\/guide\/target\/container_image\/\">Container image scanning<\/a>\u00a0\u2014 image lookup, layers, Docker\/registry scanning.<\/li>\n\n\n\n<li><a href=\"https:\/\/trivy.dev\/docs\/latest\/guide\/target\/filesystem\/\">Filesystem scanning<\/a>\u00a0\u2014 local project scanning and defaults.<\/li>\n\n\n\n<li><a href=\"https:\/\/trivy.dev\/docs\/latest\/guide\/target\/repository\/\">Code repository scanning<\/a>\u00a0\u2014 local\/remote Git repositories.<\/li>\n\n\n\n<li><a href=\"https:\/\/trivy.dev\/docs\/latest\/guide\/scanner\/misconfiguration\/\">Misconfiguration scanning<\/a>\u00a0\u2014 IaC, built-in checks, scanner selection.<\/li>\n\n\n\n<li><a href=\"https:\/\/trivy.dev\/docs\/latest\/guide\/scanner\/secret\/\">Secret scanning<\/a>\u00a0\u2014 built-in detections, custom regex rules, secret configuration.<\/li>\n\n\n\n<li><a href=\"https:\/\/trivy.dev\/docs\/latest\/guide\/configuration\/filtering\/\">Result filtering and suppression<\/a>\u00a0\u2014 severity, status,\u00a0<code>.trivyignore<\/code>, exceptions.<\/li>\n\n\n\n<li><a href=\"https:\/\/trivy.dev\/docs\/latest\/guide\/configuration\/reporting\/\">Reporting formats<\/a>\u00a0\u2014 JSON, SARIF, templates.<\/li>\n\n\n\n<li><a href=\"https:\/\/trivy.dev\/docs\/latest\/guide\/supply-chain\/sbom\/\">SBOM generation and scanning<\/a>\u00a0\u2014 CycloneDX, SPDX, SBOM targets.<\/li>\n\n\n\n<li><a href=\"https:\/\/trivy.dev\/docs\/latest\/guide\/target\/kubernetes\/\">Kubernetes scanning<\/a>\u00a0\u2014 live scanning, required roles, node collector, experimental-status warning.<\/li>\n\n\n\n<li><a href=\"https:\/\/github.com\/aquasecurity\/trivy-action\">Official Trivy GitHub Action<\/a>\u00a0and\u00a0<a href=\"https:\/\/github.com\/aquasecurity\/trivy-action\/releases\">action releases<\/a>\u00a0\u2014 YAML inputs and action versions.<\/li>\n\n\n\n<li><a href=\"https:\/\/github.com\/aquasecurity\/trivy-operator\">Trivy Operator project<\/a>\u00a0\u2014 continuous Kubernetes security scans (advanced).<\/li>\n\n\n\n<li><a href=\"https:\/\/www.cve.org\/\">CVE Program<\/a>,\u00a0<a href=\"https:\/\/cwe.mitre.org\/\">CWE<\/a>, and\u00a0<a href=\"https:\/\/www.first.org\/cvss\/\">FIRST CVSS<\/a>\u00a0\u2014 background terminology.<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Final takeaway<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Scan \u2192 understand \u2192 prioritize \u2192 remediate \u2192 verify \u2192 automate<\/strong>&nbsp;is the practical Trivy workflow. Focus on demonstrating these six activities rather than memorizing option lists. If you can inspect a container image, a dependency inventory, a Terraform or Kubernetes file, and a CI report\u2014and can explain what Trivy did&nbsp;<strong>not<\/strong>&nbsp;check\u2014you have a solid practical beginner-to-intermediate foundation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><em>End of tutorial \u2014 Trivy Zero to Intermediate, 3-hour training, reference version v0.75.0.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Course duration:&nbsp;3 hours (180 minutes)Audience:&nbsp;Absolute beginners, software developers, students, DevOps\/DevSecOps engineers, SREs, and security practitionersReference Trivy version:&nbsp;v0.75.0, released October 1, 2026Last documentation verification:&nbsp;October 11, 2026Learning style:&nbsp;Approximately 40%&#8230; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-1207","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/posts\/1207","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/comments?post=1207"}],"version-history":[{"count":1,"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/posts\/1207\/revisions"}],"predecessor-version":[{"id":1208,"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/posts\/1207\/revisions\/1208"}],"wp:attachment":[{"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/media?parent=1207"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/categories?post=1207"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/tags?post=1207"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}