Learn DAST with OWASP ZAP, generate Software Bills of Materials with Syft, scan for vulnerabilities with Grype, sign artifacts with Cosign, and understand SLSA provenance - through the lens of real supply chain attacks like SolarWinds and XZ Utils.
SAST scans source code without running it. SCA scans your dependencies without running them. Both are essential — but they both share the same fundamental limitation: they cannot see what happens when the application actually runs. Some vulnerabilities are invisible to static analysis. A perfectly written authentication function can still be bypassed if the server is misconfigured. An API that correctly validates parameters in code might expose sensitive data if the deployed configuration has debugging enabled. A dependency with no known CVEs might behave dangerously when combined with another dependency in a specific runtime environment. DAST — Dynamic Application Security Testing — solves this. It attacks a running instance of your application the same way a real attacker would: sending crafted HTTP requests, testing for injection vulnerabilities, checking for insecure headers, and probing authentication flows. Because it tests the actual running system, it catches vulnerabilities that only exist at runtime. Beyond DAST, there is a class of attack that bypasses all application-level testing entirely: supply chain attacks. These attacks do not target your code at all — they target the build process, the tools you use, and the dependencies you pull in. Defending against them requires knowing exactly what is in your software (SBOM), verifying that it has not been tampered with (Cosign), and proving how it was built (SLSA). This module covers both: DAST for finding runtime vulnerabilities, and the modern supply chain security stack for proving your software is trustworthy. ---
SAST and DAST are complementary — they find different categories of vulnerabilities. Understanding what each catches helps you build a complete testing strategy. ``` SAST (Static): Runs against: Source code Application state: Not running Finds: Code-level vulnerabilities, insecure patterns, dangerous functions Misses: Misconfigurations, server settings, runtime-only paths When: During development, on every PR DAST (Dynamic): Runs against: Running application Application state: Must be deployed to a test environment Finds: Runtime vulnerabilities, configuration issues, authentication flaws Misses: Code quality, deep business logic issues When: After deployment to staging, before release Example SAST finds: SQL injection in UserController.java line 47 Missing input validation on payment amount Hardcoded API key in config.properties Example DAST finds: Server exposes X-Powered-By: Express 4.16.0 header (reveals technology) Login endpoint accepts 1000 attempts/minute (no rate limiting at server level) API returns stack trace on 500 errors (debug mode enabled in production) CORS misconfigured — any origin can access the API ``` Neither tool finds everything on its own. A mature DevSecOps pipeline runs both. ---
OWASP ZAP (Zed Attack Proxy) is the world's most widely used DAST tool. It is maintained by OWASP and is completely free and open source. ZAP works as a proxy — it sits between your browser or test client and the application, examining every request and response. ZAP has two main scan modes: **Baseline Scan** — passive scanning only. ZAP visits the application, records all requests and responses, and flags security issues it can detect without actually attacking the application. No forms are submitted, no credentials are tested, nothing is modified. Safe to run against any environment including production-like staging. **Full Scan** — active scanning. After the baseline, ZAP actively attacks the application with crafted requests. It tests for SQL injection, XSS, command injection, path traversal, and dozens of other vulnerability classes. Can modify data and cause side effects — only run against a dedicated test environment, never against production or shared staging. ### Run ZAP Baseline Scan ```bash # Baseline scan — safe, passive only docker run -t ghcr.io/zaproxy/zaproxy:stable zap-baseline.py \ -t https://your-staging-app.example.com \ -r zap-baseline-report.html # Baseline scan with verbose output and JSON report docker run -t ghcr.io/zaproxy/zaproxy:stable zap-baseline.py \ -t https://your-staging-app.example.com \ -r zap-baseline-report.html \ -J zap-baseline-report.json \ -d # Show debug messages ``` ### Run ZAP Full Scan ```bash # Full active scan — ONLY run against dedicated test environment docker run -v $(pwd):/zap/wrk/:rw \ -t ghcr.io/zaproxy/zaproxy:stable \ zap-full-scan.py \ -t https://your-test-app.example.com \ -r full-scan-report.html \ -J full-scan-report.json # With custom configuration file to tune which rules fire docker run -v $(pwd):/zap/wrk/:rw \ -t ghcr.io/zaproxy/zaproxy:stable \ zap-full-scan.py \ -t https://your-test-app.example.com \ -c zap-rules.conf \ -r full-scan-report.html ``` ### ZAP Exit Codes ``` 0 — Success (scan completed, no findings above threshold) 1 — At least one FAIL finding (pipeline should block on this) 2 — At least one WARN finding, no FAIL 3 — Any other failure (ZAP crashed, target unreachable) ``` Use exit code 1 to block deployments when high-severity findings are detected. ### Add ZAP to GitHub Actions ```yaml # .github/workflows/dast.yml name: DAST Security Scan on: # Run after deployment to staging deployment_status: jobs: dast: name: OWASP ZAP DAST runs-on: ubuntu-latest # Only run when staging deployment succeeds if: github.event.deployment_status.state == 'success' steps: - uses: actions/checkout@v4 # ZAP Baseline Scan — safe for every deployment - name: ZAP Baseline Scan uses: zaproxy/action-baseline@v0.12.0 with: target: ${{ github.event.deployment_status.environment_url }} rules_file_name: '.zap/rules.tsv' cmd_options: '-a' # Upload ZAP report as artifact - name: Upload ZAP Report uses: actions/upload-artifact@v4 if: always() with: name: zap-baseline-report path: | report_html.html report_json.json ``` ### Configure ZAP Rules ZAP has hundreds of rules. You can configure individual rules to PASS, IGNORE, INFO, WARN, or FAIL: ``` # .zap/rules.tsv # Format: RuleID Level [Optional comment] # X-Frame-Options header missing — WARN but don't block 10020 WARN X-Frame-Options header # Server version disclosure — FAIL the build 10036 FAIL Server header reveals version # Missing HSTS header — FAIL 10035 FAIL HSTS header missing # CSP misconfiguration — WARN 10055 WARN CSP wildcard # SQL Injection — always FAIL 40018 FAIL SQL Injection 40019 FAIL SQL Injection (MySQL) 40020 FAIL SQL Injection (Hypersonic SQL) ``` Create a `zap-gen.conf` with `zap-full-scan.py -g zap-gen.conf` to get the default configuration file showing all available rule IDs. ### Reading ZAP Findings A typical ZAP finding looks like: ``` Alert: Missing Anti-clickjacking Header Risk: Medium Confidence: Medium URL: https://your-app.example.com/dashboard Description: The response does not include either Content-Security-Policy with 'frame-ancestors' directive or X-Frame-Options to protect against 'ClickJacking' attacks. Solution: Most modern Web browsers support the X-Frame-Options HTTP header. Ensure it's set on all web pages returned by your site (if you expect the page to be framed only by pages on your server, e.g. it's part of a FRAMESET, then you'll want to use SAMEORIGIN, otherwise if you never expect the page to be framed, you should use DENY). Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options https://owasp.org/www-community/attacks/Clickjacking CWE-1021: Improper Restriction of Rendered UI Layers or Frames ``` Every finding includes the affected URL, risk level, description, and exactly how to fix it. ---
A Software Bill of Materials (SBOM) is the ingredient list for your software — every component, library, version, license, and dependency relationship, in a machine-readable format. Without an SBOM, answering "do we use this vulnerable library?" requires manually searching every repository, every Dockerfile, every requirements file. With an SBOM, the answer comes from a single query. The Log4Shell incident illustrated this perfectly. Organisations with SBOMs identified which products were affected within hours of the CVE being published. Organisations without them spent weeks in manual inventory work. ### Why SBOM Matters Now — Regulatory Context The EU Cyber Resilience Act (CRA), effective December 2027, requires every product with digital elements sold in the EU to have an SBOM. The US Executive Order 14028 already requires SBOMs for federal software procurement. SBOMs are becoming mandatory infrastructure, not optional best practice. ### SBOM Formats — CycloneDX vs SPDX Two formats dominate: **CycloneDX** — created by OWASP, optimised for security use cases. Integrates cleanly with vulnerability tools (Grype, Snyk, OWASP Dependency-Track). Supports VEX (Vulnerability Exploitability eXchange) for documenting which CVEs actually affect your product. Best choice for most DevSecOps teams. **SPDX** — ISO/IEC 5962:2021, created by the Linux Foundation. Stronger license metadata. Native format for Yocto-based embedded Linux builds. Required by some government contracts. Both are machine-readable (JSON or XML). Both meet CRA and EO 14028 requirements. ### Install Syft ```bash # Install curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin # Verify syft version ``` ### Generate an SBOM ```bash # Scan a directory — generates SBOM for all detected packages syft /path/to/your-project -o cyclonedx-json > sbom.json # Scan a container image — scans OS packages AND language dependencies syft node:18-alpine -o cyclonedx-json > sbom-node18.json # Generate multiple formats simultaneously syft your-app:latest \ -o cyclonedx-json=./sbom.cdx.json \ -o spdx-json=./sbom.spdx.json # Scan by Docker image digest (immutable — always scans the exact same image) syft your-registry/your-app@sha256:abc123... -o cyclonedx-json > sbom.json ``` ### What Syft Detects ```bash # Example output for a Node.js application syft my-node-app:latest NAME VERSION TYPE axios 1.4.0 npm body-parser 1.20.2 npm express 4.18.2 npm lodash 4.17.21 npm node 18.17.0 binary npm 9.6.7 binary # Plus OS-level packages, certificates, etc. ``` For a container image, Syft scans both the OS layer (Alpine/Debian packages) and all language-specific package manifests (package.json, requirements.txt, go.mod, pom.xml, Cargo.toml, etc.) found inside the image. ### Attach SBOM to Container Image The cleanest approach is attaching the SBOM directly to the container image as an attestation using Cosign (covered later in this module): ```bash # Generate SBOM syft your-app:latest -o cyclonedx-json > sbom.json # Attach SBOM to image as a signed attestation cosign attest --yes \ --predicate sbom.json \ --type cyclonedx \ your-registry/your-app@sha256:abc123... # Now anyone who pulls the image can also retrieve the SBOM cosign verify-attestation \ --type cyclonedx \ --certificate-identity YOUR_WORKFLOW \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ your-registry/your-app@sha256:abc123... ``` ---
Syft generates the inventory. Grype scans that inventory against known vulnerability databases — NVD (National Vulnerability Database), GitHub Advisory Database, and others — to find CVEs in your components. Syft + Grype together are the open-source equivalent of Snyk or Trivy for container and filesystem scanning. ### Install Grype ```bash curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin ``` ### Scan with Grype ```bash # Scan a container image directly grype your-app:latest # Scan an SBOM generated by Syft grype sbom:./sbom.json # Scan and fail if critical or high vulnerabilities are found grype your-app:latest --fail-on high # Output as JSON for programmatic processing grype your-app:latest -o json > grype-report.json # Output as table (human-readable) grype your-app:latest -o table ``` ### Example Grype Output ``` NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY lodash 4.17.4 4.17.21 npm CVE-2021-23337 Critical axios 0.19.0 0.21.1 npm CVE-2021-3749 High express 4.16.0 4.17.3 npm CVE-2022-24999 High openssl 1.1.1n 1.1.1q deb CVE-2022-2068 Medium ``` Each finding shows: package name, installed version, the version that fixes it, and the CVE ID and severity. Grype automatically tells you what to upgrade to. ### Integrate Syft + Grype into CI/CD ```yaml # .github/workflows/sbom-scan.yml name: SBOM Generation and Vulnerability Scan on: push: branches: [main] permissions: contents: read packages: write id-token: write # For Cosign signing jobs: sbom-and-scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Build container image run: docker build -t my-app:${{ github.sha }} . - name: Generate SBOM with Syft uses: anchore/sbom-action@v0 with: image: my-app:${{ github.sha }} format: cyclonedx-json output-file: sbom.cyclonedx.json - name: Scan SBOM with Grype uses: anchore/scan-action@v3 id: scan with: sbom: sbom.cyclonedx.json fail-build: true severity-cutoff: high - name: Upload SARIF report uses: github/codeql-action/upload-sarif@v3 if: always() with: sarif_file: ${{ steps.scan.outputs.sarif }} - name: Install Cosign uses: sigstore/cosign-installer@v3 - name: Sign image and attach SBOM run: | # Sign the image cosign sign --yes my-registry/my-app@${{ steps.build.outputs.digest }} # Attach signed SBOM attestation cosign attest --yes \ --predicate sbom.cyclonedx.json \ --type cyclonedx \ my-registry/my-app@${{ steps.build.outputs.digest }} ``` ---
SAST, SCA, DAST, and SBOM scanning all verify that your code and dependencies are secure. But none of them prove one critical thing: that the artifact you are deploying was actually built from the code you think it was, by the pipeline you think built it, and has not been modified since. This is the supply chain security gap. It is exactly what the SolarWinds and XZ Utils attacks exploited. ### What the SolarWinds Attack Teaches Us In December 2020, Russian intelligence compromised SolarWinds' build system. They injected malicious code into the build process for the Orion platform. The resulting software was signed with SolarWinds' legitimate certificates and distributed through official update channels to 18,000 organisations. The attack worked because: * The build system was not isolated — attackers could modify the build * The resulting binary was legitimately signed — no signature check would detect the tampering * There was no provenance — nobody could prove which source code produced which binary SLSA (Supply-chain Levels for Software Artifacts) and Cosign keyless signing together prevent this class of attack. SLSA ensures the build environment is isolated and tamper-resistant. Cosign provides cryptographic proof that a specific, trusted pipeline (not just any process with access to the signing key) produced the artifact. ### What the XZ Utils Attack Teaches Us In 2024, a malicious actor spent two years contributing legitimate code to the xz compression library to earn maintainer trust, then inserted a backdoor (CVE-2024-3094, CVSS 10.0) into a release. The discovery was accidental — a Microsoft engineer noticed SSH was 500ms slower than expected. The XZ attack teaches a different lesson: even the source code itself can be compromised through social engineering of maintainers. SBOM scanning catches known CVEs in released packages. It does not catch a backdoor inserted by a maintainer before the CVE is published. The defence is provenance and supply chain hygiene: know exactly which commits went into your build, verify the build environment was not tampered with, and monitor for unexpected changes in dependencies. ### SLSA Framework — Build Integrity Levels SLSA defines four levels of build integrity assurance: ``` SLSA Level 0 — No guarantees No documentation of how the software was built Most software today SLSA Level 1 — Provenance exists Build generates provenance (metadata about how it was built) Provenance is available but not verified Achievable immediately using GitHub Actions SLSA Level 2 — Hosted build, signed provenance Build happens on a hosted build service (GitHub Actions, GitLab CI) Provenance is signed by the build service — cannot be forged by the build Achievable in 1-2 days with existing tooling Would have made SolarWinds-style build injection detectable SLSA Level 3 — Hardened build environment Build environment is isolated — build steps cannot influence each other Credentials used to sign provenance are not accessible to build steps Requires dedicated infrastructure investment Provides very high assurance for critical software ``` Level 2 is the realistic target for most teams. It is achievable with GitHub Actions and provides the core protection against build tampering. ### Generate SLSA Provenance with GitHub Actions GitHub provides an official action for generating SLSA provenance: ```yaml name: Build with SLSA Provenance on: push: branches: [main] release: types: [published] permissions: contents: read packages: write id-token: write # Required for OIDC signing attestations: write jobs: build: runs-on: ubuntu-latest outputs: image-digest: ${{ steps.build.outputs.digest }} steps: - uses: actions/checkout@v4 - name: Login to registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push image id: build uses: docker/build-push-action@v6 with: push: true tags: ghcr.io/${{ github.repository }}:${{ github.sha }} - name: Generate SLSA provenance uses: actions/attest-build-provenance@v1 with: subject-name: ghcr.io/${{ github.repository }} subject-digest: ${{ steps.build.outputs.digest }} push-to-registry: true - name: Sign with Cosign (additional keyless signature) uses: sigstore/cosign-installer@v3 - name: Sign image run: | cosign sign --yes \ ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }} ``` The `actions/attest-build-provenance` step generates a signed attestation that records: * Which GitHub Actions workflow triggered the build * Which repository commit was used * Which runner environment executed the build * When the build happened This attestation is stored in the OCI registry alongside the image and can be verified by anyone with access to the image. ### Verify Provenance Before Deployment ```bash # Verify the image was built by your specific workflow gh attestation verify \ --owner your-org \ ghcr.io/your-org/your-app@sha256:abc123... # Expected output: # Loaded digest sha256:abc123... # Loaded 1 attestation from GitHub API # ✓ Verification succeeded! # # The following attested predicates' Claims have been verified: # Build repo: https://github.com/your-org/your-app # Build workflow: .github/workflows/build.yml@refs/heads/main # Build runner: GitHub-hosted # Verify with Cosign keyless cosign verify \ --certificate-identity \ "https://github.com/your-org/your-app/.github/workflows/build.yml@refs/heads/main" \ --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \ ghcr.io/your-org/your-app@sha256:abc123... ``` ### Enforce Verified Images in Kubernetes ```yaml # kyverno-verify-policy.yaml apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: require-signed-images spec: validationFailureAction: Enforce rules: - name: verify-signature-and-sbom match: any: - resources: kinds: [Pod] verifyImages: - imageReferences: - "ghcr.io/your-org/*" attestors: - entries: - keyless: # Only images signed by your specific workflow subject: "https://github.com/your-org/*/.github/workflows/build.yml@refs/heads/main" issuer: "https://token.actions.githubusercontent.com" rekor: url: https://rekor.sigstore.dev ``` Any pod that tries to run an unsigned image from your organisation's registry is rejected at admission time before it ever starts. ---
SAST scans source code without running it. SCA scans your dependencies without running them. Both are essential — but th...
SAST and DAST are complementary — they find different categories of vulnerabilities. Understanding what each catches hel...
OWASP ZAP (Zed Attack Proxy) is the world's most widely used DAST tool. It is maintained by OWASP and is completely free...
A Software Bill of Materials (SBOM) is the ingredient list for your software — every component, library, version, licens...
Syft generates the inventory. Grype scans that inventory against known vulnerability databases — NVD (National Vulnerabi...
SAST, SCA, DAST, and SBOM scanning all verify that your code and dependencies are secure. But none of them prove one cri...
Combining DAST, SBOM, and artifact signing into a complete pipeline: This pipeline enforces the complete chain: SBOM gen...
Part 1 — Generate and Scan an SBOM Part 2 — Run ZAP Against a Vulnerable App Part 3 — Sign an Image with Cosign Part 4 —...
Aligns directly with DevOps, Site Reliability (SRE), and Platform Engineering job descriptions.