Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions submissions/lab10-walkthrough.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Five-minute walkthrough

I built a small DevSecOps program around OWASP Juice Shop rather than treating the labs as separate tool demos. The flow starts with getting the application running in a repeatable way, then adds controls at the points where risk enters: secrets before commit, signed commits, dependency visibility, code scanning, dynamic testing, infrastructure scanning, container hardening, artifact signing, runtime detection, and finally DefectDojo as the place where the findings become a managed backlog.

The first lesson was that every scanner sees a different slice of the same system. In Lab 4, Syft gave me the SBOM and Grype/Trivy turned it into dependency and image findings. That was useful, but it was also noisy: a vulnerable package in the image is not automatically the most urgent thing if the code path is unreachable or there is no fix. Lab 5 gave more concrete evidence because Semgrep looked at the source and ZAP attacked the running application. Lab 6 showed how infrastructure mistakes create risk before the app even starts. Lab 7 made the container harder to abuse. Labs 8 and 9 added the controls that do not look like normal vulnerability findings: signed artifacts, attestations, policy-as-code, and Falco runtime alerts.

The specific finding I would trace is SQL injection. Semgrep reported `javascript.sequelize.security.audit.sequelize-injection-express.express-sequelize-injection` in `routes/search.ts` and `routes/login.ts`, where untrusted input reaches Sequelize queries. ZAP then confirmed the same class of issue dynamically: the authenticated scan reported SQL Injection against `/rest/products/search` with server error evidence. That combination changes the priority. A SAST-only result could be a false positive, and a DAST-only result could lack enough source context to fix quickly, but together they point to a real bug, a reachable endpoint, and a clear remediation path: replace string-built SQL with parameterized queries, rerun Semgrep, rerun the ZAP authenticated scan, and only close the DefectDojo finding when both sides agree.

One thing that did not work cleanly was the DefectDojo import itself. The lab maps `auth-report.json` to the `ZAP Scan` parser, but DefectDojo 2.58.3 rejected the JSON report and asked for XML. I did not ignore the DAST result or invent a count; I converted the same ZAP JSON data into the traditional ZAP XML shape and imported the same 13 alerts. Another small trap was `trivy-k8s.json`: it imported through `Trivy Operator Scan`, but produced 0 findings because the file shape was not the Trivy Operator CRD format. That is a useful operational lesson: an HTTP 201 import response does not prove the data became useful findings.

The number I would put on a slide is **581 active findings, but only after source context**. On its own, 581 sounds like a fire. Broken down, it becomes a program: 36 Critical, 253 High, 233 Medium, 46 Low, and 13 Info; 568 SLA-scoped findings were inside SLA immediately after import because the import date was today, while the 13 Info findings had no SLA clock. The more important slide note is that the raw number mixes real application bugs, duplicate or overlapping image findings, deliberately vulnerable IaC fixtures, and control evidence that does not appear in DefectDojo at all.

My priority would be to fix the SQL injection first, then rebuild the image and triage the Critical dependency findings with fix availability. I would not let the large IaC fixture count drive the sprint unless those files were actually headed to production. For Labs 8 and 9, I would not force everything into a vulnerability dashboard: cosign verification should stay a CI gate, while Falco alerts and Conftest failures should become generic findings only when they need an owner and a deadline.

The outcome is not "I ran ten tools." The outcome is a defensible workflow: evidence comes from multiple layers, findings are deduplicated by location and component, deadlines are explicit, risk acceptance has an expiry, and the next action is clear enough for a manager and a developer to agree on.
107 changes: 107 additions & 0 deletions submissions/lab10.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Lab 10 — Vulnerability Management: The Capstone

DefectDojo **2.58.3** was run from the pinned `release` Docker Compose stack. Because port `8080` was already used by another local container, I exposed this instance on `http://127.0.0.1:8081`. The product was `OWASP Juice Shop`, product type `Research and Development`, engagement `Course Semester Run`.

## Task 1

### Imported reports

| Lab | File | Parser | Active findings |
|---|---|---|---:|
| 4 | `labs/lab4/grype-from-sbom.json` | Anchore Grype | 182 |
| 4 | `labs/lab4/trivy.json` | Trivy Scan | 176 |
| 5 | `labs/lab5/results/semgrep.json` | Semgrep JSON Report | 27 |
| 5 | `labs/lab5/results/auth-report.json` converted to ZAP XML | ZAP Scan | 13 |
| 6 | `labs/lab6/results/checkov-terraform/results_json.json` | Checkov Scan | 80 |
| 6 | `labs/lab6/results/kics-ansible/results.json` | KICS Scan | 10 |
| 6 | `labs/lab6/results/kics-pulumi/results.json` | KICS Scan | 6 |
| 7 | `labs/lab7/results/trivy-image.json` | Trivy Scan | 87 |
| 7 | `labs/lab7/results/trivy-k8s.json` | Trivy Operator Scan | 0 |

Two parser details mattered. First, DefectDojo 2.58.3 rejected the ZAP JSON report with `Wrong file format, please use xml`, so I converted the same Lab 5 `auth-report.json` into the traditional ZAP XML shape and imported the same 13 alerts. Second, `trivy-k8s.json` imported successfully through `Trivy Operator Scan` but produced 0 findings; I kept that result rather than re-importing it as a different scanner, because the lab asks for the mapped parser and a green import is not the same thing as parsed findings.

### Active findings by severity

| Severity | Count |
|---|---:|
| Critical | 36 |
| High | 253 |
| Medium | 233 |
| Low | 46 |
| Info | 13 |
| **Total active** | **581** |

Query used:

```console
curl -H "Authorization: Token $DD_TOKEN" \
"$DD_URL/api/v2/findings/?active=true&limit=1000" |
jq '[.results[].severity] | group_by(.) | map({severity: .[0], count: length})'
```

### Duplicate titles

`Secret Management: Passwords and Secrets - Generic Password` appeared 7 times. I treat these as separate findings, not one duplicate, because the API showed different locations across Ansible and Pulumi files: `inventory.ini` lines 5, 10, 18, 19, `configure.yml:16`, `deploy.yml:12`, and `Pulumi-vulnerable.yaml:16`.

`javascript.sequelize.security.audit.sequelize-injection-express.express-sequelize-injection` appeared 6 times. These are also separate code findings: two are in real route handlers, `routes/login.ts:34` and `routes/search.ts:23`, while four are in `data/static/codefixes/*.ts` challenge files. Same rule title, different file and line, so this is one weakness class repeated in multiple places rather than a single record counted six times.

### What I would act on this week

The findings I would act on first are from Lab 5 and Labs 4/7. Lab 5 has the strongest evidence because Semgrep points to raw Sequelize sinks and ZAP confirms SQL injection behavior against the running app, especially the product search endpoint. Labs 4 and 7 add fixable Critical/High dependency and image findings such as `crypto-js`, `jsonwebtoken`, `decompress`, and `marsdb`; these affect the shipped artifact and should be triaged with fix availability. Lab 6 is useful but noisier for this week's project risk because the Checkov/KICS findings are mostly in deliberately vulnerable IaC teaching fixtures, and many duplicate rule titles represent multiple sample files rather than production infrastructure.

## Task 2

### SLA numbers

| Severity | Default | Mine | Why |
|---|---:|---:|---|
| Critical | 7 days | **3 days** | Critical findings on an internet-facing app should either be fixed or explicitly contained inside the current week. |
| High | 30 days | **14 days** | Two weeks keeps High findings inside one sprint boundary instead of letting them drift for a month. |
| Medium | 90 days | **45 days** | Medium findings should be planned work, not a quarterly parking lot. |
| Low | 120 days | **90 days** | Low risk can wait, but it should still age out within a quarter. |

Applied through `PATCH /api/v2/sla_configurations/1/`; the API returned `critical=3 high=14 medium=45 low=90`.

### Active findings by severity and source

By severity: Critical 36, High 253, Medium 233, Low 46, Info 13, total 581.

| Source | Active findings |
|---|---:|
| Anchore Grype | 182 |
| Trivy Scan, Lab 4 image/SBOM | 176 |
| Semgrep JSON Report | 27 |
| ZAP Scan | 13 |
| Checkov Scan | 80 |
| KICS Scan, Ansible | 10 |
| KICS Scan, Pulumi | 6 |
| Trivy Scan, Lab 7 image | 87 |
| Trivy Operator Scan | 0 |

### Age and SLA compliance

Method: `GET /api/v2/findings/?active=true&limit=1000`, using DefectDojo's `date` and `sla_days_remaining` fields. The import was performed on **2026-10-11**, and every active finding has `date=2026-10-11`.

| Metric | Value | Calculation |
|---|---:|---|
| Median age of active findings | 0 days | sorted age list midpoint; all active findings were imported today |
| Oldest active finding | 0 days | max age over active findings |
| Inside SLA | 100% of SLA-scoped findings | 568 of 568 findings with `sla_days_remaining` are `>= 0`; 13 Info findings have no SLA clock |

This means the SLA clock is configured, but the compliance number is not yet operationally meaningful because DefectDojo only learned about these findings today. In a real program I would preserve original discovery dates from each scanner import.

### Labs 8 and 9 gap

Lab 8 produced cosign signatures and SBOM attestation evidence; Lab 9 produced Falco runtime alerts and Conftest policy results. None of those appear in the 581 findings because they are control evidence and runtime/policy events, not standard vulnerability scanner records. I would keep cosign verification as a CI release gate and import Falco/Conftest failures as Generic Findings or send them to a SIEM, then create DefectDojo findings only when an event needs owner, severity, and SLA tracking.

### Risk acceptance

I would risk-accept `GHSA-5mrr-rgp6-x4gr in marsdb:0.6.11` until **2026-12-15**, the engagement end date. The reason is that it is a Critical transitive dependency finding in an old Juice Shop dependency tree where a clean upgrade path may be larger than the lab scope. The compensating control is the hardened Lab 7 runtime posture: non-root container, dropped capabilities, read-only root filesystem, and restricted Kubernetes networking from Lab 9, reducing what exploitation can write to or reach. The acceptance must expire on 2026-12-15 and be re-reviewed rather than silently renewed.

### Executive summary

The project now has one DefectDojo view with 581 active findings across SCA, SAST, DAST, IaC, and container-image scanning, but the raw total overstates immediate risk because teaching fixtures and repeated rule titles inflate the backlog. The biggest real risk is SQL injection in the Juice Shop application, because Semgrep identifies the vulnerable source locations and ZAP confirms the behavior against the running service. To close the risk, I need one short development cycle to parameterize the SQL paths, rebuild the image, rerun ZAP/Semgrep/Trivy, and then triage remaining Critical dependency findings under the new 3-day SLA.

## Bonus note

The hardest part to explain concisely was the difference between a raw finding count and a security program. A dashboard can say "581 findings", but it does not automatically tell which are duplicated CVEs, teaching-fixture noise, runtime evidence, or release gates. Next time I would document the decision rule for each control while building it: what it proves, where its evidence lives, whether it creates a DefectDojo finding, and who owns it when it fails.