From e0b9bcc4ca06d1c877b18e90afebb5d767317ff2 Mon Sep 17 00:00:00 2001 From: smairon Date: Sat, 6 Jun 2026 12:06:11 +0300 Subject: [PATCH 1/8] docs: add PR template Signed-off-by: smairon --- .github/pull_request_template.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..1a68db5e5 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,13 @@ +## Goal + + +## Changes +- + +## Testing + + +## Checklist +- [ ] Title is a clear sentence (≤ 70 chars) +- [ ] Commits are signed (`git log --show-signature`) +- [ ] `submissions/labN.md` updated From c5c5f10e32a4fe05626f9daf3758ba7c8580173e Mon Sep 17 00:00:00 2001 From: "man@smairon.ru" Date: Tue, 9 Jun 2026 07:43:36 +0300 Subject: [PATCH 2/8] docs: upstream moved while you worked Signed-off-by: man@smairon.ru From 042f7ee901ae23ec2a06d99113f93e6a442e76f8 Mon Sep 17 00:00:00 2001 From: "man@smairon.ru" Date: Tue, 23 Jun 2026 11:33:15 +0300 Subject: [PATCH 3/8] docs(lab6): task1 submission Signed-off-by: man@smairon.ru --- app/Dockerfile | 25 ++++++++++ submissions/lab6.md | 115 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 app/Dockerfile create mode 100644 submissions/lab6.md diff --git a/app/Dockerfile b/app/Dockerfile new file mode 100644 index 000000000..7bf1c8a46 --- /dev/null +++ b/app/Dockerfile @@ -0,0 +1,25 @@ +FROM golang:1.24.0-alpine3.21 AS builder + +WORKDIR /src + +COPY go.mod ./ +RUN go mod download + +COPY *.go ./ +COPY seed.json ./ + +RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/quicknotes . + +FROM gcr.io/distroless/static:nonroot + +COPY --from=builder /out/quicknotes /quicknotes +COPY --from=builder /src/seed.json /seed.json + +ENV ADDR=:8080 \ + DATA_PATH=/data/notes.json \ + SEED_PATH=/seed.json + +USER 65532:65532 +EXPOSE 8080 + +ENTRYPOINT ["/quicknotes"] \ No newline at end of file diff --git a/submissions/lab6.md b/submissions/lab6.md new file mode 100644 index 000000000..4c0f26838 --- /dev/null +++ b/submissions/lab6.md @@ -0,0 +1,115 @@ +# Lab 6 — Task 1 + +## Dockerfile + +File: `app/Dockerfile` + +```dockerfile +FROM golang:1.24.0-alpine3.21 AS builder + +WORKDIR /src + +COPY go.mod ./ +RUN go mod download + +COPY *.go ./ +COPY seed.json ./ + +RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/quicknotes . + +FROM gcr.io/distroless/static:nonroot + +COPY --from=builder /out/quicknotes /quicknotes +COPY --from=builder /src/seed.json /seed.json + +ENV ADDR=:8080 \ + DATA_PATH=/data/notes.json \ + SEED_PATH=/seed.json + +USER 65532:65532 +EXPOSE 8080 + +ENTRYPOINT ["/quicknotes"] +``` + +## Image size + +```text +REPOSITORY TAG IMAGE ID CREATED SIZE +quicknotes lab6 202d7cf66323 8 seconds ago 14.2MB +``` + +## `docker inspect` config excerpt + +Equivalent excerpt from `docker inspect quicknotes:lab6`: + +```json +{ + "User": "65532:65532", + "ExposedPorts": { + "8080/tcp": {} + }, + "Entrypoint": [ + "/quicknotes" + ] +} +``` + +## Builder image size comparison + +```text +REPOSITORY TAG IMAGE ID CREATED SIZE +golang 1.24.0-alpine3.21 2d40d4fc278d 16 months ago 385MB +``` + +The final runtime image is `14.2MB`, compared with `385MB` for the builder base image. + +## Build and run verification + +```text +$ docker run -d --rm -p 8080:8080 -v "$PWD/data:/data" quicknotes:lab6 +$ curl -s http://localhost:8080/health +{"notes":7,"status":"ok"} +``` + +## Design answers + +### a) Why does layer order matter? + +Docker reuses cached layers only until the first instruction whose inputs change. If the Dockerfile does `COPY . .` before `go mod download`, then any source edit invalidates the `COPY . .` layer and forces Docker to run `go mod download` again, even though dependencies did not change. + +If the Dockerfile copies `go.mod` first, runs `go mod download`, and only then copies the source files, a source-only change keeps the dependency layer cached. On this app the rebuild time difference is small because the module has no external dependencies, but the cache behavior is still correct. + +Measured rebuilds after a source-only edit: + +```text +Bad order: COPY . . -> go mod download -> go build real 8.36s +Good order: COPY go.mod -> go mod download -> COPY src -> go build real 8.04s +``` + +Observed step behavior: + +- Bad order: `COPY . .`, `RUN go mod download`, and `RUN go build` all reran. +- Good order: `COPY go.mod` and `RUN go mod download` stayed cached; only source copy and build reran. + +In a real service with many downloaded modules, the good order saves much more time because it avoids network work on every source change. + +### b) Why `CGO_ENABLED=0`? + +`CGO_ENABLED=0` forces a pure-Go static binary that does not need a dynamic linker or C runtime in the final image. That is exactly what a distroless static runtime expects. + +If you forget it and the build produces a dynamically linked binary, the container usually fails to start in `gcr.io/distroless/static:nonroot` because the required loader or shared libraries are not present. The common symptom is an error like `no such file or directory` even though the binary file exists. + +### c) What is `gcr.io/distroless/static:nonroot`? + +It is a minimal runtime image for statically linked programs. It contains only the small set of runtime files needed to launch the application safely as a non-root user, such as basic identity metadata and CA certificates. + +It does not contain a shell, package manager, compiler, or normal debugging tools. There is no `sh`, no `apt`, no `apk`, and no extra userland utilities. + +That matters for CVEs because fewer installed packages means a much smaller attack surface and fewer OS-level vulnerabilities to scan, patch, or exploit. It does not remove bugs from the application itself, but it does remove a lot of unnecessary operating-system baggage. + +### d) What do `-ldflags='-s -w'` and `-trimpath` do, and what is the cost? + +`-ldflags='-s -w'` strips the symbol table and DWARF debug information from the binary. The main benefit is a smaller image. The cost is worse post-build debugging because the binary carries less debug metadata. + +`-trimpath` removes local filesystem paths from the compiled binary. That improves reproducibility and avoids leaking machine-specific build paths. The cost is that stack traces and debug output are slightly less informative because absolute source paths are no longer embedded. \ No newline at end of file From fc8e35c77868d7e0ea4422c21328c0d1d3a9951b Mon Sep 17 00:00:00 2001 From: "man@smairon.ru" Date: Tue, 23 Jun 2026 21:48:09 +0300 Subject: [PATCH 4/8] docs(lab6): task2 submission Signed-off-by: man@smairon.ru --- app/Dockerfile | 2 + app/main.go | 34 +++++++++++++++ compose.yaml | 23 ++++++++++ submissions/lab6.md | 101 ++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 157 insertions(+), 3 deletions(-) create mode 100644 compose.yaml diff --git a/app/Dockerfile b/app/Dockerfile index 7bf1c8a46..0653e0d0d 100644 --- a/app/Dockerfile +++ b/app/Dockerfile @@ -9,11 +9,13 @@ COPY *.go ./ COPY seed.json ./ RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/quicknotes . +RUN mkdir -p /out/data && cp /src/seed.json /out/data/notes.json FROM gcr.io/distroless/static:nonroot COPY --from=builder /out/quicknotes /quicknotes COPY --from=builder /src/seed.json /seed.json +COPY --from=builder --chown=65532:65532 /out/data /data ENV ADDR=:8080 \ DATA_PATH=/data/notes.json \ diff --git a/app/main.go b/app/main.go index e258ffcfe..1f834bf3f 100644 --- a/app/main.go +++ b/app/main.go @@ -4,9 +4,11 @@ import ( "context" "errors" "log" + "net" "net/http" "os" "os/signal" + "strings" "syscall" "time" ) @@ -16,6 +18,13 @@ func main() { dataPath := envOrDefault("DATA_PATH", "data/notes.json") seedPath := envOrDefault("SEED_PATH", "seed.json") + if len(os.Args) > 1 && os.Args[1] == "healthcheck" { + if err := runHealthcheck(addr); err != nil { + log.Fatal(err) + } + return + } + if err := ensureSeeded(dataPath, seedPath); err != nil { log.Fatalf("seed: %v", err) } @@ -83,3 +92,28 @@ func dirname(p string) string { } return "." } + +func runHealthcheck(addr string) error { + hostport := addr + if strings.HasPrefix(hostport, ":") { + hostport = "127.0.0.1" + hostport + } else if host, port, err := net.SplitHostPort(addr); err == nil { + if host == "" || host == "0.0.0.0" || host == "::" { + host = "127.0.0.1" + } + hostport = net.JoinHostPort(host, port) + } + + client := &http.Client{Timeout: 2 * time.Second} + resp, err := client.Get("http://" + hostport + "/health") + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return errors.New("health endpoint returned non-200 status") + } + + return nil +} diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 000000000..1e1909691 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,23 @@ +services: + quicknotes: + build: + context: ./app + image: quicknotes:lab6 + ports: + - "8080:8080" + environment: + ADDR: ":8080" + DATA_PATH: /data/notes.json + SEED_PATH: /seed.json + volumes: + - quicknotes-data:/data + healthcheck: + test: ["CMD", "/quicknotes", "healthcheck"] + interval: 10s + timeout: 3s + retries: 3 + start_period: 3s + restart: unless-stopped + +volumes: + quicknotes-data: \ No newline at end of file diff --git a/submissions/lab6.md b/submissions/lab6.md index 4c0f26838..380d4ca9d 100644 --- a/submissions/lab6.md +++ b/submissions/lab6.md @@ -16,11 +16,13 @@ COPY *.go ./ COPY seed.json ./ RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/quicknotes . +RUN mkdir -p /out/data && cp /src/seed.json /out/data/notes.json FROM gcr.io/distroless/static:nonroot COPY --from=builder /out/quicknotes /quicknotes COPY --from=builder /src/seed.json /seed.json +COPY --from=builder --chown=65532:65532 /out/data /data ENV ADDR=:8080 \ DATA_PATH=/data/notes.json \ @@ -36,7 +38,7 @@ ENTRYPOINT ["/quicknotes"] ```text REPOSITORY TAG IMAGE ID CREATED SIZE -quicknotes lab6 202d7cf66323 8 seconds ago 14.2MB +quicknotes lab6 01506b43afce About a minute ago 14.9MB ``` ## `docker inspect` config excerpt @@ -62,7 +64,7 @@ REPOSITORY TAG IMAGE ID CREATED SIZE golang 1.24.0-alpine3.21 2d40d4fc278d 16 months ago 385MB ``` -The final runtime image is `14.2MB`, compared with `385MB` for the builder base image. +The final runtime image is `14.9MB`, compared with `385MB` for the builder base image. ## Build and run verification @@ -112,4 +114,97 @@ That matters for CVEs because fewer installed packages means a much smaller atta `-ldflags='-s -w'` strips the symbol table and DWARF debug information from the binary. The main benefit is a smaller image. The cost is worse post-build debugging because the binary carries less debug metadata. -`-trimpath` removes local filesystem paths from the compiled binary. That improves reproducibility and avoids leaking machine-specific build paths. The cost is that stack traces and debug output are slightly less informative because absolute source paths are no longer embedded. \ No newline at end of file +`-trimpath` removes local filesystem paths from the compiled binary. That improves reproducibility and avoids leaking machine-specific build paths. The cost is that stack traces and debug output are slightly less informative because absolute source paths are no longer embedded. + +# Lab 6 - Task 2 + +## compose.yaml + +File: `compose.yaml` + +```yaml +services: + quicknotes: + build: + context: ./app + image: quicknotes:lab6 + ports: + - "8080:8080" + environment: + ADDR: ":8080" + DATA_PATH: /data/notes.json + SEED_PATH: /seed.json + volumes: + - quicknotes-data:/data + healthcheck: + test: ["CMD", "/quicknotes", "healthcheck"] + interval: 10s + timeout: 3s + retries: 3 + start_period: 3s + restart: unless-stopped + +volumes: + quicknotes-data: +``` + +## Healthcheck verification + +```text +NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS +devops-intro-quicknotes-1 quicknotes:lab6 "/quicknotes" quicknotes 5 seconds ago Up 5 seconds (healthy) 0.0.0.0:8080->8080/tcp, [::]:8080->8080/tcp +``` + +## Persistence test output + +```text +$ docker compose up --build -d +[+] Running 4/4 + ✔ quicknotes:lab6 Built + ✔ Network devops-intro_default Created + ✔ Volume "devops-intro_quicknotes-data" Created + ✔ Container devops-intro-quicknotes-1 Started + +$ curl -s -X POST -H 'Content-Type: application/json' -d '{"title":"durable","body":"survive a restart"}' http://localhost:8080/notes +{"id":5,"title":"durable","body":"survive a restart","created_at":"2026-06-23T08:35:31.488789635Z"} + +$ curl -s http://localhost:8080/notes | grep durable +[{"id":1,"title":"Welcome to QuickNotes","body":"This is the project you'll containerize, deploy, monitor, and harden across all 10 labs.","created_at":"2026-01-15T10:00:00Z"},{"id":2,"title":"Read app/main.go first","body":"Start by understanding the entry point - env vars, signal handling, graceful shutdown.","created_at":"2026-01-15T10:05:00Z"},{"id":3,"title":"DevOps mantra","body":"If it hurts, do it more often.","created_at":"2026-01-15T10:10:00Z"},{"id":4,"title":"Endpoint cheat-sheet","body":"GET /notes GET /notes/{id} POST /notes DELETE /notes/{id} GET /health GET /metrics","created_at":"2026-01-15T10:15:00Z"},{"id":5,"title":"durable","body":"survive a restart","created_at":"2026-06-23T08:35:31.488789635Z"}] + +$ docker compose down +$ docker compose up -d + +$ curl -s http://localhost:8080/notes | grep durable +[{"id":1,"title":"Welcome to QuickNotes","body":"This is the project you'll containerize, deploy, monitor, and harden across all 10 labs.","created_at":"2026-01-15T10:00:00Z"},{"id":2,"title":"Read app/main.go first","body":"Start by understanding the entry point - env vars, signal handling, graceful shutdown.","created_at":"2026-01-15T10:05:00Z"},{"id":3,"title":"DevOps mantra","body":"If it hurts, do it more often.","created_at":"2026-01-15T10:10:00Z"},{"id":4,"title":"Endpoint cheat-sheet","body":"GET /notes GET /notes/{id} POST /notes DELETE /notes/{id} GET /health GET /metrics","created_at":"2026-01-15T10:15:00Z"},{"id":5,"title":"durable","body":"survive a restart","created_at":"2026-06-23T08:35:31.488789635Z"}] + +$ docker compose down -v +$ docker compose up -d + +$ curl -s http://localhost:8080/notes | grep durable +durable absent +``` + +## Design answers + +### e) Distroless has no shell. How do you healthcheck it? + +I used a binary that is already in the image: the QuickNotes executable itself. I added a `healthcheck` mode, and Compose runs it with exec form: + +```yaml +healthcheck: + test: ["CMD", "/quicknotes", "healthcheck"] +``` + +That helper performs an HTTP GET to `http://127.0.0.1:8080/health` and exits non-zero on failure. This works in distroless because it does not require `sh`, `curl`, `wget`, or any package manager. + +### f) Why does `volumes: [quicknotes-data:/data]` survive `docker compose down`? What destroys it? + +It survives `docker compose down` because named volumes are separate Docker objects from containers and networks. `down` removes the containers and the project network, but it leaves named volumes in place by default. + +The volume is destroyed by `docker compose down -v`, or by explicit volume removal such as `docker volume rm devops-intro_quicknotes-data` or a broader cleanup like `docker volume prune`. + +### g) What does `depends_on` without `condition: service_healthy` actually wait for? What bug can it cause? + +Without `condition: service_healthy`, `depends_on` only waits for the dependent container process to start, not for the application inside it to become ready. + +The bug is a startup race: a second service can start immediately after first, try to connect before it is ready to accept requests, and fail with connection errors even though Compose started containers in the declared order. \ No newline at end of file From ac212dcf2a7ffbb9a4161eb86006fb20966ab2c2 Mon Sep 17 00:00:00 2001 From: "man@smairon.ru" Date: Wed, 24 Jun 2026 07:39:04 +0300 Subject: [PATCH 5/8] docs(lab6): bonus submission Signed-off-by: man@smairon.ru --- .github/pull_request_template.md | 23 +++++-- compose.yaml | 7 ++ submissions/lab6.md | 113 ++++++++++++++++++++++++++++++- 3 files changed, 136 insertions(+), 7 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 1a68db5e5..35d9fda87 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,13 +1,24 @@ ## Goal - +Complete Lab 6 for QuickNotes: containerize the app, run it with Compose and persistence, and apply the bonus hardening defaults. ## Changes -- +- Task 1: + Add a multi-stage distroless Dockerfile with a static stripped Go binary, nonroot runtime, and image size under 25 MB. +- Task 2: + Add `compose.yaml` with port publishing, named volume persistence, env vars, restart policy, and a distroless-compatible healthcheck. +- Bonus: + Harden the `quicknotes` service with `cap_drop: [ALL]`, `read_only: true`, `tmpfs: /tmp`, and `no-new-privileges`; document Docker and Trivy verification in `submissions/lab6.md`. ## Testing - +- `go test ./...` +- `docker build -t quicknotes:lab6 ./app` +- `docker run --rm -p 8080:8080 -v "$PWD/app/data:/data" quicknotes:lab6` and `curl /health` +- `docker compose up --build -d`, POST note, verify persistence across `down/up`, verify reset with `down -v` +- `docker inspect` checks for nonroot, dropped capabilities, read-only root, and `no-new-privileges` +- `docker compose exec quicknotes sh` fails as expected +- `trivy image --severity HIGH,CRITICAL quicknotes:lab6` ## Checklist -- [ ] Title is a clear sentence (≤ 70 chars) -- [ ] Commits are signed (`git log --show-signature`) -- [ ] `submissions/labN.md` updated +- [x] Title is a clear sentence (≤ 70 chars) +- [x] Commits are signed (`git log --show-signature`) +- [x] `submissions/labN.md` updated diff --git a/compose.yaml b/compose.yaml index 1e1909691..570829bf7 100644 --- a/compose.yaml +++ b/compose.yaml @@ -11,12 +11,19 @@ services: SEED_PATH: /seed.json volumes: - quicknotes-data:/data + tmpfs: + - /tmp healthcheck: test: ["CMD", "/quicknotes", "healthcheck"] interval: 10s timeout: 3s retries: 3 start_period: 3s + cap_drop: + - ALL + read_only: true + security_opt: + - no-new-privileges:true restart: unless-stopped volumes: diff --git a/submissions/lab6.md b/submissions/lab6.md index 380d4ca9d..339d0a3cf 100644 --- a/submissions/lab6.md +++ b/submissions/lab6.md @@ -136,12 +136,19 @@ services: SEED_PATH: /seed.json volumes: - quicknotes-data:/data + tmpfs: + - /tmp healthcheck: test: ["CMD", "/quicknotes", "healthcheck"] interval: 10s timeout: 3s retries: 3 start_period: 3s + cap_drop: + - ALL + read_only: true + security_opt: + - no-new-privileges:true restart: unless-stopped volumes: @@ -207,4 +214,108 @@ The volume is destroyed by `docker compose down -v`, or by explicit volume remov Without `condition: service_healthy`, `depends_on` only waits for the dependent container process to start, not for the application inside it to become ready. -The bug is a startup race: a second service can start immediately after first, try to connect before it is ready to accept requests, and fail with connection errors even though Compose started containers in the declared order. \ No newline at end of file +The bug is a startup race: a second service can start immediately after first, try to connect before it is ready to accept requests, and fail with connection errors even though Compose started containers in the declared order. + +# Lab 6 - Bonus Task + +## Hardened `services.quicknotes` snippet + +```yaml +quicknotes: + build: + context: ./app + image: quicknotes:lab6 + ports: + - "8080:8080" + environment: + ADDR: ":8080" + DATA_PATH: /data/notes.json + SEED_PATH: /seed.json + volumes: + - quicknotes-data:/data + tmpfs: + - /tmp + healthcheck: + test: ["CMD", "/quicknotes", "healthcheck"] + interval: 10s + timeout: 3s + retries: 3 + start_period: 3s + cap_drop: + - ALL + read_only: true + security_opt: + - no-new-privileges:true + restart: unless-stopped +``` + +## Verification outputs + +### 1) `USER nonroot` + +`Dockerfile` already uses `USER 65532:65532`. Runtime proof: + +```text +$ docker inspect quicknotes:lab6 --format '{{json .Config.User}}' +"65532:65532" +``` + +### 2) No shell available + +`Dockerfile` already uses the distroless runtime image `gcr.io/distroless/static:nonroot`. Proof: + +```text +$ docker compose exec -T quicknotes sh +OCI runtime exec failed: exec failed: unable to start container process: exec: "sh": executable file not found in $PATH: unknown +``` + +### 3) Capabilities dropped + +```text +$ docker inspect devops-intro-quicknotes-1 --format '{{json .HostConfig.CapDrop}}' +["ALL"] +``` + +### 4) Read-only root filesystem + +Because the distroless image has no shell and no `touch` binary, I verified this at the Docker engine level instead of trying to run a fake write test command inside the container: + +```text +$ docker inspect devops-intro-quicknotes-1 --format '{{.HostConfig.ReadonlyRootfs}}' +true +``` + +The writable locations are only the named volume at `/data` and the explicit tmpfs mount at `/tmp`. + +### 5) `no-new-privileges` + +```text +$ docker inspect devops-intro-quicknotes-1 --format '{{json .HostConfig.SecurityOpt}}' +["no-new-privileges:true"] +``` + +## Trivy summary + +Command used: + +```text +$ docker run --rm -v /var/run/docker.sock:/var/run/docker.sock aquasec/trivy:0.59.1 image --severity HIGH,CRITICAL --no-progress quicknotes:lab6 +``` + +Summary: + +```text +quicknotes:lab6 (debian 13.5) +============================= +Total: 0 (HIGH: 0, CRITICAL: 0) + +quicknotes (gobinary) +===================== +Total: 17 (HIGH: 16, CRITICAL: 1) +``` + +Interpretation: the distroless static base did its job on the OS side, with zero HIGH/CRITICAL base-image findings. The remaining findings come from the Go binary built with `go1.24.0`, so they are application-runtime issues in the bundled standard library rather than extra packages from the container image. + +## Most security per line of YAML + +If I had to pick one line, `cap_drop: [ALL]` gives the most security per line of Compose. It removes the default Linux capabilities that many containers do not actually need, which sharply reduces the blast radius of a compromise. `read_only: true` is a close second because it blocks a lot of persistence and tampering paths with one flag, but dropping capabilities is the stronger general hardening default for this app. The real takeaway is that these controls stack well: distroless, nonroot, dropped capabilities, read-only root, and `no-new-privileges` each cover a different failure mode. \ No newline at end of file From 909e67f23415d059a698bbc652daf6c44ee9720f Mon Sep 17 00:00:00 2001 From: "man@smairon.ru" Date: Tue, 7 Jul 2026 20:46:58 +0300 Subject: [PATCH 6/8] docs(lab9): task1 submission Signed-off-by: man@smairon.ru --- .../lab9-artifacts/quicknotes-lab6.cdx.json | 474 ++++++++++++++++++ submissions/lab9-artifacts/trivy-config.txt | 14 + submissions/lab9-artifacts/trivy-fs.txt | 16 + submissions/lab9-artifacts/trivy-image.txt | 68 +++ submissions/lab9.md | 202 ++++++++ 5 files changed, 774 insertions(+) create mode 100644 submissions/lab9-artifacts/quicknotes-lab6.cdx.json create mode 100644 submissions/lab9-artifacts/trivy-config.txt create mode 100644 submissions/lab9-artifacts/trivy-fs.txt create mode 100644 submissions/lab9-artifacts/trivy-image.txt create mode 100644 submissions/lab9.md diff --git a/submissions/lab9-artifacts/quicknotes-lab6.cdx.json b/submissions/lab9-artifacts/quicknotes-lab6.cdx.json new file mode 100644 index 000000000..9fbb8c142 --- /dev/null +++ b/submissions/lab9-artifacts/quicknotes-lab6.cdx.json @@ -0,0 +1,474 @@ +{ + "$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.6", + "serialNumber": "urn:uuid:004ff727-a73e-4811-8e0d-08e3e0ebf1ad", + "version": 1, + "metadata": { + "timestamp": "2026-07-07T17:33:25+00:00", + "tools": { + "components": [ + { + "type": "application", + "group": "aquasecurity", + "name": "trivy", + "version": "0.59.1" + } + ] + }, + "component": { + "bom-ref": "pkg:oci/quicknotes@sha256%3Ad60c10b58753feb3dce466f9d14698958019b01e899bed7a3dde749807154b62?arch=arm64&repository_url=index.docker.io%2Flibrary%2Fquicknotes", + "type": "container", + "name": "quicknotes:lab6", + "purl": "pkg:oci/quicknotes@sha256%3Ad60c10b58753feb3dce466f9d14698958019b01e899bed7a3dde749807154b62?arch=arm64&repository_url=index.docker.io%2Flibrary%2Fquicknotes", + "properties": [ + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:187cfc6d1e3e8a40a5e64653bcd3239c140807dcf1c09e48021178705a5a6139" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:275a30dd8ce958b21daa9ad962c6fbc09f98306ee2f486b65c9075dc257b1412" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:4cde6b0bb6f50a5f255eef7b2a42162c661cf776b803225dcac9a659e396bb6b" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:4d049f83d9cf21d1f5cc0e11deaf36df02790d0e60c1a3829538fb4b61685368" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:5fd2536c39c0700be8b7b4344e375196da2f126842fd8ede66996a18860a3890" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:621c35e751a51a9a9dc3e80aa0b7fe8be2a93402ea6ccd307d30852cd7776cda" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:6f1cdceb6a3146f0ccb986521156bef8a422cdbb0863396f7f751f575ba308f4" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:8c1a7c4365a2a1ae8d7f361675388c8094a9f30514d9dfc0e06682f9367e759f" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:9147cb45e95ddc95e2bba618e43a87edb91629c78a9a51144dbd2e9b23206d5f" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:ad24070089cab35f8c62507fa8b3cc4315da7ebc477c3cbd9bf2700b481d365b" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:ad51d0769d16ba578106a177987dfe3d2e02c1668c852b795b2f6b024068242a" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:af5aa97ebe6ce1604747ec1e21af7136ded391bcabe4acef882e718a87c86bcc" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:bd3cdfae1d3fdd83a2231d608969b38b82349777c2fff9a7c12d54f8ac5c9b38" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:bec7e6bb35e05d1284f28b10d2150c259717d91c658c4c10c08424bb9466caba" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:c8b007d0206e4b10ed4d3b3d99dfeab47c2648e82011989fd78a5731baf33fc3" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:e74c594f8bc0269456af305d5e71c5cc9fc080fbae3112e09d0399850a440d5f" + }, + { + "name": "aquasecurity:trivy:ImageID", + "value": "sha256:d60c10b58753feb3dce466f9d14698958019b01e899bed7a3dde749807154b62" + }, + { + "name": "aquasecurity:trivy:RepoDigest", + "value": "quicknotes@sha256:d60c10b58753feb3dce466f9d14698958019b01e899bed7a3dde749807154b62" + }, + { + "name": "aquasecurity:trivy:RepoTag", + "value": "quicknotes:lab6" + }, + { + "name": "aquasecurity:trivy:SchemaVersion", + "value": "2" + } + ] + } + }, + "components": [ + { + "bom-ref": "0269970a-c45c-4c7f-9ce6-74f200f4e49a", + "type": "operating-system", + "name": "debian", + "version": "13.5", + "properties": [ + { + "name": "aquasecurity:trivy:Class", + "value": "os-pkgs" + }, + { + "name": "aquasecurity:trivy:Type", + "value": "debian" + } + ] + }, + { + "bom-ref": "938e56bf-b4b9-488c-a16a-fb0a8f9372d3", + "type": "application", + "name": "quicknotes", + "properties": [ + { + "name": "aquasecurity:trivy:Class", + "value": "lang-pkgs" + }, + { + "name": "aquasecurity:trivy:Type", + "value": "gobinary" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/base-files@13.8%2Bdeb13u5?arch=arm64&distro=debian-13.5", + "type": "library", + "supplier": { + "name": "Santiago Vila " + }, + "name": "base-files", + "version": "13.8+deb13u5", + "licenses": [ + { + "license": { + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "verbatim" + } + } + ], + "purl": "pkg:deb/debian/base-files@13.8%2Bdeb13u5?arch=arm64&distro=debian-13.5", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:8c1a7c4365a2a1ae8d7f361675388c8094a9f30514d9dfc0e06682f9367e759f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:115c774471ecdf6d6bf2f7f3ff02075abc7092bca65df86b8b013699ecb2eb0a" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "base-files@13.8+deb13u5" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "base-files" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "13.8+deb13u5" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/media-types@13.0.0?arch=all&distro=debian-13.5", + "type": "library", + "supplier": { + "name": "Mime-Support Packagers " + }, + "name": "media-types", + "version": "13.0.0", + "licenses": [ + { + "license": { + "name": "ad-hoc" + } + } + ], + "purl": "pkg:deb/debian/media-types@13.0.0?arch=all&distro=debian-13.5", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:275a30dd8ce958b21daa9ad962c6fbc09f98306ee2f486b65c9075dc257b1412" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:d6b1b89eccacc15c2420b2776d72c1dae334a00805ed9af54bf2f71e4d536f28" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "media-types@13.0.0" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "media-types" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "13.0.0" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/netbase@6.5?arch=all&distro=debian-13.5", + "type": "library", + "supplier": { + "name": "Marco d'Itri " + }, + "name": "netbase", + "version": "6.5", + "licenses": [ + { + "license": { + "name": "GPL-2.0-only" + } + } + ], + "purl": "pkg:deb/debian/netbase@6.5?arch=all&distro=debian-13.5", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:621c35e751a51a9a9dc3e80aa0b7fe8be2a93402ea6ccd307d30852cd7776cda" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:c172f21841dff4c8cf45cde46589c1c2616cefe7e819965e92e6d3475c428aa0" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "netbase@6.5" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "netbase" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "6.5" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/tzdata-legacy@2026b-0%2Bdeb13u1?arch=all&distro=debian-13.5", + "type": "library", + "supplier": { + "name": "GNU Libc Maintainers " + }, + "name": "tzdata-legacy", + "version": "2026b-0+deb13u1", + "licenses": [ + { + "license": { + "name": "public-domain" + } + } + ], + "purl": "pkg:deb/debian/tzdata-legacy@2026b-0%2Bdeb13u1?arch=all&distro=debian-13.5", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:bec7e6bb35e05d1284f28b10d2150c259717d91c658c4c10c08424bb9466caba" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:99ba982a9142213c751a1709dcf088e63d8601f03b3f211bae037be698fef270" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "tzdata-legacy@2026b-0+deb13u1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "tzdata" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "0+deb13u1" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "2026b" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/tzdata@2026b-0%2Bdeb13u1?arch=all&distro=debian-13.5", + "type": "library", + "supplier": { + "name": "GNU Libc Maintainers " + }, + "name": "tzdata", + "version": "2026b-0+deb13u1", + "licenses": [ + { + "license": { + "name": "public-domain" + } + } + ], + "purl": "pkg:deb/debian/tzdata@2026b-0%2Bdeb13u1?arch=all&distro=debian-13.5", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:c8b007d0206e4b10ed4d3b3d99dfeab47c2648e82011989fd78a5731baf33fc3" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:99515e7b4d35e0652d3b0fde571b6ec269222ecacc506f026e1758d6261e9109" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "tzdata@2026b-0+deb13u1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "tzdata" + }, + { + "name": "aquasecurity:trivy:SrcRelease", + "value": "0+deb13u1" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "2026b" + } + ] + }, + { + "bom-ref": "pkg:golang/quicknotes", + "type": "library", + "name": "quicknotes", + "purl": "pkg:golang/quicknotes", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:9147cb45e95ddc95e2bba618e43a87edb91629c78a9a51144dbd2e9b23206d5f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:9d548c456623945e4ce2a564aa41656696daf1974bfe823ef61d0c027ac19338" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "quicknotes" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gobinary" + } + ] + }, + { + "bom-ref": "pkg:golang/stdlib@v1.24.0", + "type": "library", + "name": "stdlib", + "version": "v1.24.0", + "purl": "pkg:golang/stdlib@v1.24.0", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:9147cb45e95ddc95e2bba618e43a87edb91629c78a9a51144dbd2e9b23206d5f" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:9d548c456623945e4ce2a564aa41656696daf1974bfe823ef61d0c027ac19338" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "stdlib@v1.24.0" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gobinary" + } + ] + } + ], + "dependencies": [ + { + "ref": "0269970a-c45c-4c7f-9ce6-74f200f4e49a", + "dependsOn": [ + "pkg:deb/debian/base-files@13.8%2Bdeb13u5?arch=arm64&distro=debian-13.5", + "pkg:deb/debian/media-types@13.0.0?arch=all&distro=debian-13.5", + "pkg:deb/debian/netbase@6.5?arch=all&distro=debian-13.5", + "pkg:deb/debian/tzdata-legacy@2026b-0%2Bdeb13u1?arch=all&distro=debian-13.5", + "pkg:deb/debian/tzdata@2026b-0%2Bdeb13u1?arch=all&distro=debian-13.5" + ] + }, + { + "ref": "938e56bf-b4b9-488c-a16a-fb0a8f9372d3", + "dependsOn": [ + "pkg:golang/quicknotes" + ] + }, + { + "ref": "pkg:deb/debian/base-files@13.8%2Bdeb13u5?arch=arm64&distro=debian-13.5", + "dependsOn": [] + }, + { + "ref": "pkg:deb/debian/media-types@13.0.0?arch=all&distro=debian-13.5", + "dependsOn": [] + }, + { + "ref": "pkg:deb/debian/netbase@6.5?arch=all&distro=debian-13.5", + "dependsOn": [] + }, + { + "ref": "pkg:deb/debian/tzdata-legacy@2026b-0%2Bdeb13u1?arch=all&distro=debian-13.5", + "dependsOn": [] + }, + { + "ref": "pkg:deb/debian/tzdata@2026b-0%2Bdeb13u1?arch=all&distro=debian-13.5", + "dependsOn": [] + }, + { + "ref": "pkg:golang/quicknotes", + "dependsOn": [ + "pkg:golang/stdlib@v1.24.0" + ] + }, + { + "ref": "pkg:golang/stdlib@v1.24.0", + "dependsOn": [] + }, + { + "ref": "pkg:oci/quicknotes@sha256%3Ad60c10b58753feb3dce466f9d14698958019b01e899bed7a3dde749807154b62?arch=arm64&repository_url=index.docker.io%2Flibrary%2Fquicknotes", + "dependsOn": [ + "0269970a-c45c-4c7f-9ce6-74f200f4e49a", + "938e56bf-b4b9-488c-a16a-fb0a8f9372d3" + ] + } + ], + "vulnerabilities": [] +} diff --git a/submissions/lab9-artifacts/trivy-config.txt b/submissions/lab9-artifacts/trivy-config.txt new file mode 100644 index 000000000..be6e7861f --- /dev/null +++ b/submissions/lab9-artifacts/trivy-config.txt @@ -0,0 +1,14 @@ + +app/Dockerfile (dockerfile) +=========================== +Tests: 28 (SUCCESSES: 27, FAILURES: 1) +Failures: 1 (UNKNOWN: 0, LOW: 1, MEDIUM: 0, HIGH: 0, CRITICAL: 0) + +AVD-DS-0026 (LOW): Add HEALTHCHECK instruction in your Dockerfile +════════════════════════════════════════ +You should add HEALTHCHECK instruction in your docker container images to perform the health check on running containers. + +See https://avd.aquasec.com/misconfig/ds026 +──────────────────────────────────────── + + diff --git a/submissions/lab9-artifacts/trivy-fs.txt b/submissions/lab9-artifacts/trivy-fs.txt new file mode 100644 index 000000000..93a185474 --- /dev/null +++ b/submissions/lab9-artifacts/trivy-fs.txt @@ -0,0 +1,16 @@ + +.vagrant/machines/default/virtualbox/private_key (secrets) +========================================================== +Total: 1 (HIGH: 1, CRITICAL: 0) + +HIGH: AsymmetricPrivateKey (private-key) +════════════════════════════════════════ +Asymmetric Private Key +──────────────────────────────────────── + .vagrant/machines/default/virtualbox/private_key:1 +──────────────────────────────────────── + 1 [ BEGIN OPENSSH PRIVATE KEY-----*******************************************************************************************************************************************************************************************************************************************************************************************************************************************-----END OPENSSH PRI + 2 +──────────────────────────────────────── + + diff --git a/submissions/lab9-artifacts/trivy-image.txt b/submissions/lab9-artifacts/trivy-image.txt new file mode 100644 index 000000000..0de5f71b3 --- /dev/null +++ b/submissions/lab9-artifacts/trivy-image.txt @@ -0,0 +1,68 @@ + +quicknotes:lab6 (debian 13.5) +============================= +Total: 0 (HIGH: 0, CRITICAL: 0) + + +quicknotes (gobinary) +===================== +Total: 14 (HIGH: 13, CRITICAL: 1) + +┌─────────┬────────────────┬──────────┬────────┬───────────────────┬──────────────────────────────┬──────────────────────────────────────────────────────────────┐ +│ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │ Title │ +├─────────┼────────────────┼──────────┼────────┼───────────────────┼──────────────────────────────┼──────────────────────────────────────────────────────────────┤ +│ stdlib │ CVE-2025-68121 │ CRITICAL │ fixed │ v1.24.0 │ 1.24.13, 1.25.7, 1.26.0-rc.3 │ crypto/tls: crypto/tls: Incorrect certificate validation │ +│ │ │ │ │ │ │ during TLS session resumption │ +│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2025-68121 │ +│ ├────────────────┼──────────┤ │ ├──────────────────────────────┼──────────────────────────────────────────────────────────────┤ +│ │ CVE-2025-22874 │ HIGH │ │ │ 1.24.4 │ crypto/x509: Usage of ExtKeyUsageAny disables policy │ +│ │ │ │ │ │ │ validation in crypto/x509 │ +│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2025-22874 │ +│ ├────────────────┤ │ │ ├──────────────────────────────┼──────────────────────────────────────────────────────────────┤ +│ │ CVE-2025-61726 │ │ │ │ 1.24.12, 1.25.6 │ golang: net/url: Memory exhaustion in query parameter │ +│ │ │ │ │ │ │ parsing in net/url │ +│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2025-61726 │ +│ ├────────────────┤ │ │ ├──────────────────────────────┼──────────────────────────────────────────────────────────────┤ +│ │ CVE-2025-61729 │ │ │ │ 1.24.11, 1.25.5 │ crypto/x509: golang: Denial of Service due to excessive │ +│ │ │ │ │ │ │ resource consumption via crafted... │ +│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2025-61729 │ +│ ├────────────────┤ │ │ ├──────────────────────────────┼──────────────────────────────────────────────────────────────┤ +│ │ CVE-2026-25679 │ │ │ │ 1.25.8, 1.26.1 │ net/url: Incorrect parsing of IPv6 host literals in net/url │ +│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2026-25679 │ +│ ├────────────────┤ │ │ ├──────────────────────────────┼──────────────────────────────────────────────────────────────┤ +│ │ CVE-2026-27145 │ │ │ │ 1.25.11, 1.26.4 │ crypto/x509: golang: golang crypto/x509: Denial of Service │ +│ │ │ │ │ │ │ via excessive processing of DNS... │ +│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2026-27145 │ +│ ├────────────────┤ │ │ ├──────────────────────────────┼──────────────────────────────────────────────────────────────┤ +│ │ CVE-2026-32280 │ │ │ │ 1.25.9, 1.26.2 │ crypto/x509: crypto/tls: golang: Go: Denial of Service │ +│ │ │ │ │ │ │ vulnerability in certificate chain building... │ +│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2026-32280 │ +│ ├────────────────┤ │ │ │ ├──────────────────────────────────────────────────────────────┤ +│ │ CVE-2026-32281 │ │ │ │ │ crypto/x509: golang: Go crypto/x509: Denial of Service via │ +│ │ │ │ │ │ │ inefficient certificate chain validation... │ +│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2026-32281 │ +│ ├────────────────┤ │ │ │ ├──────────────────────────────────────────────────────────────┤ +│ │ CVE-2026-32283 │ │ │ │ │ crypto/tls: golang: Go crypto/tls: Denial of Service via │ +│ │ │ │ │ │ │ multiple TLS 1.3 key... │ +│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2026-32283 │ +│ ├────────────────┤ │ │ ├──────────────────────────────┼──────────────────────────────────────────────────────────────┤ +│ │ CVE-2026-33811 │ │ │ │ 1.25.10, 1.26.3 │ net: golang: Go net package: Denial of Service via long │ +│ │ │ │ │ │ │ CNAME response... │ +│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2026-33811 │ +│ ├────────────────┤ │ │ │ ├──────────────────────────────────────────────────────────────┤ +│ │ CVE-2026-33814 │ │ │ │ │ net/http/internal/http2: golang: golang.org/x/net: Go │ +│ │ │ │ │ │ │ HTTP/2: Denial of Service via malformed │ +│ │ │ │ │ │ │ SETTINGS_MAX_FRAME_SIZE frame... │ +│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2026-33814 │ +│ ├────────────────┤ │ │ │ ├──────────────────────────────────────────────────────────────┤ +│ │ CVE-2026-39820 │ │ │ │ │ net/mail: golang: Go net/mail: Denial of Service via crafted │ +│ │ │ │ │ │ │ email inputs │ +│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2026-39820 │ +│ ├────────────────┤ │ │ │ ├──────────────────────────────────────────────────────────────┤ +│ │ CVE-2026-39836 │ │ │ │ │ ELSA-2026-22121: golang security update (IMPORTANT) │ +│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2026-39836 │ +│ ├────────────────┤ │ │ │ ├──────────────────────────────────────────────────────────────┤ +│ │ CVE-2026-42499 │ │ │ │ │ net/mail: golang: net/mail: Denial of Service via │ +│ │ │ │ │ │ │ pathological email address parsing │ +│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2026-42499 │ +└─────────┴────────────────┴──────────┴────────┴───────────────────┴──────────────────────────────┴──────────────────────────────────────────────────────────────┘ diff --git a/submissions/lab9.md b/submissions/lab9.md new file mode 100644 index 000000000..ab5a8b244 --- /dev/null +++ b/submissions/lab9.md @@ -0,0 +1,202 @@ +# Lab 9 + +## Task 1 — Trivy: Image + Filesystem + Config + SBOM (6 pts) + +Scan date: 2026-07-07 + +Pinned scanner version: + +```text +aquasec/trivy:0.59.1 +``` + +Artifacts saved in `submissions/lab9-artifacts/`: + +- `trivy-image.txt` +- `trivy-fs.txt` +- `trivy-config.txt` +- `quicknotes-lab6.cdx.json` + +## Commands used + +```bash +docker build -t quicknotes:lab6 ./app + +docker run --rm \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v /Users/axxil/Study/Innopolis/DevOps-Intro:/work \ + -v /Users/axxil/.cache/trivy:/root/.cache \ + aquasec/trivy:0.59.1 \ + image --severity HIGH,CRITICAL --format table \ + -o /work/submissions/lab9-artifacts/trivy-image.txt \ + quicknotes:lab6 + +docker run --rm \ + -v /Users/axxil/Study/Innopolis/DevOps-Intro:/work \ + -v /Users/axxil/.cache/trivy:/root/.cache \ + aquasec/trivy:0.59.1 \ + fs --severity HIGH,CRITICAL --format table \ + -o /work/submissions/lab9-artifacts/trivy-fs.txt \ + /work + +docker run --rm \ + -v /Users/axxil/Study/Innopolis/DevOps-Intro:/work \ + -v /Users/axxil/.cache/trivy:/root/.cache \ + aquasec/trivy:0.59.1 \ + config --skip-check-update --format table \ + -o /work/submissions/lab9-artifacts/trivy-config.txt \ + /work + +docker run --rm \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v /Users/axxil/Study/Innopolis/DevOps-Intro:/work \ + -v /Users/axxil/.cache/trivy:/root/.cache \ + aquasec/trivy:0.59.1 \ + image --format cyclonedx \ + -o /work/submissions/lab9-artifacts/quicknotes-lab6.cdx.json \ + quicknotes:lab6 +``` + +## Top of each scan output + +### 1. Image scan + +```text +quicknotes:lab6 (debian 13.5) +============================= +Total: 0 (HIGH: 0, CRITICAL: 0) + + +quicknotes (gobinary) +===================== +Total: 14 (HIGH: 13, CRITICAL: 1) + +┌─────────┬────────────────┬──────────┬────────┬───────────────────┬──────────────────────────────┬──────────────────────────────────────────────────────────────┐ +│ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │ Title │ +├─────────┼────────────────┼──────────┼────────┼───────────────────┼──────────────────────────────┼──────────────────────────────────────────────────────────────┤ +│ stdlib │ CVE-2025-68121 │ CRITICAL │ fixed │ v1.24.0 │ 1.24.13, 1.25.7, 1.26.0-rc.3 │ crypto/tls: crypto/tls: Incorrect certificate validation │ +│ │ │ │ │ │ │ during TLS session resumption │ +│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2025-68121 │ +│ ├────────────────┼──────────┤ │ ├──────────────────────────────┼──────────────────────────────────────────────────────────────┤ +│ │ CVE-2025-22874 │ HIGH │ │ │ 1.24.4 │ crypto/x509: Usage of ExtKeyUsageAny disables policy │ +│ │ │ │ │ │ │ validation in crypto/x509 │ +``` + +### 2. Filesystem scan + +```text +.vagrant/machines/default/virtualbox/private_key (secrets) +========================================================== +Total: 1 (HIGH: 1, CRITICAL: 0) + +HIGH: AsymmetricPrivateKey (private-key) +════════════════════════════════════════ +Asymmetric Private Key +──────────────────────────────────────── + .vagrant/machines/default/virtualbox/private_key:1 +``` + +### 3. Config scan + +```text +app/Dockerfile (dockerfile) +=========================== +Tests: 28 (SUCCESSES: 27, FAILURES: 1) +Failures: 1 (UNKNOWN: 0, LOW: 1, MEDIUM: 0, HIGH: 0, CRITICAL: 0) + +AVD-DS-0026 (LOW): Add HEALTHCHECK instruction in your Dockerfile +════════════════════════════════════════ +You should add HEALTHCHECK instruction in your docker container images to perform the health check on running containers. +``` + +### 4. SBOM generation + +First 30 lines of `quicknotes-lab6.cdx.json`: + +```json +{ + "$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.6", + "serialNumber": "urn:uuid:004ff727-a73e-4811-8e0d-08e3e0ebf1ad", + "version": 1, + "metadata": { + "timestamp": "2026-07-07T17:33:25+00:00", + "tools": { + "components": [ + { + "type": "application", + "group": "aquasecurity", + "name": "trivy", + "version": "0.59.1" + } + ] + }, + "component": { + "bom-ref": "pkg:oci/quicknotes@sha256%3Ad60c10b58753feb3dce466f9d14698958019b01e899bed7a3dde749807154b62?arch=arm64&repository_url=index.docker.io%2Flibrary%2Fquicknotes", + "type": "container", + "name": "quicknotes:lab6", + "purl": "pkg:oci/quicknotes@sha256%3Ad60c10b58753feb3dce466f9d14698958019b01e899bed7a3dde749807154b62?arch=arm64&repository_url=index.docker.io%2Flibrary%2Fquicknotes", + "properties": [ + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:187cfc6d1e3e8a40a5e64653bcd3239c140807dcf1c09e48021178705a5a6139" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:275a30dd8ce958b21daa9ad962c6fbc09f98306ee2f486b65c9075dc257b1412" + } +``` + +## HIGH/CRITICAL triage table + +All HIGH/CRITICAL findings came from the image scan and filesystem scan. The config scan had zero HIGH/CRITICAL findings. + +| Scan | Finding | Severity | Disposition | Reason | Re-check by | +|------|---------|----------|-------------|--------|-------------| +| image | CVE-2025-68121 | CRITICAL | ACCEPT | QuickNotes serves plain HTTP with `http.ListenAndServe` on `:8080`; no TLS server or outbound TLS session resumption path is configured in the current lab deployment. If this image is reused for HTTPS or internet-facing use, this becomes a FIX immediately. | 2026-10-07 | +| image | CVE-2025-22874 | HIGH | ACCEPT | The app does not validate client certificates, parse trust chains, or perform custom X.509 policy evaluation. In this deployment the vulnerable path is not used. | 2026-10-07 | +| image | CVE-2025-61726 | HIGH | ACCEPT | This is a `net/url` query parsing issue. The lab API is local-only and does not expose query-based business logic. For any shared or public deployment, upgrade the Go toolchain first. | 2026-10-07 | +| image | CVE-2025-61729 | HIGH | ACCEPT | The app does not use TLS certificate validation paths in normal operation, so this `crypto/x509` DoS issue is not reachable in the current lab setup. | 2026-10-07 | +| image | CVE-2026-25679 | HIGH | ACCEPT | The service does not parse attacker-controlled host literals or construct outbound URLs from user input, so this `net/url` parsing issue is not relevant to the current routes. | 2026-10-07 | +| image | CVE-2026-27145 | HIGH | ACCEPT | Another `crypto/x509` DoS issue. QuickNotes neither terminates TLS nor validates remote cert chains in its request path. | 2026-10-07 | +| image | CVE-2026-32280 | HIGH | ACCEPT | Certificate chain building is not part of the app's current runtime behavior, so this path is not exercised by the lab service. | 2026-10-07 | +| image | CVE-2026-32281 | HIGH | ACCEPT | Same X.509 validation surface as above; present in the bundled stdlib, but not used by the app in the current deployment. | 2026-10-07 | +| image | CVE-2026-32283 | HIGH | ACCEPT | This is a TLS 1.3 DoS issue. QuickNotes runs plain HTTP only; no TLS listener or h2/h2c configuration is enabled. | 2026-10-07 | +| image | CVE-2026-33811 | HIGH | ACCEPT | The app does not perform attacker-controlled DNS lookups. The only client-side call is the local healthcheck to `127.0.0.1`. | 2026-10-07 | +| image | CVE-2026-33814 | HIGH | ACCEPT | The HTTP server is started with `ListenAndServe` and no TLS, so HTTP/2 is not part of the current deployment path. | 2026-10-07 | +| image | CVE-2026-39820 | HIGH | ACCEPT | `net/mail` is not used anywhere in the QuickNotes codebase, so the vulnerable parsing path is not reachable. | 2026-10-07 | +| image | CVE-2026-39836 | HIGH | ACCEPT | This is an umbrella Go security advisory surfaced from the bundled stdlib. For the current local lab scope the exposure stays limited, but if the image leaves lab-only use it should be rebuilt on a current Go 1.24 patch release. | 2026-10-07 | +| image | CVE-2026-42499 | HIGH | ACCEPT | Another `net/mail` parser issue; QuickNotes does not accept or parse email address inputs. | 2026-10-07 | +| filesystem | AsymmetricPrivateKey in `.vagrant/machines/default/virtualbox/private_key` | HIGH | ACCEPT | This is a real private key, but it is local Vagrant runtime state, ignored by Git (`.gitignore` excludes `.vagrant/`), not copied into the container image, and would not exist in a clean CI checkout. It is workstation hygiene risk, not a shipped artifact risk. | 2026-10-07 | + +## Design answers + +### a) CVE severity is one input, not the answer. What else matters? + +Severity is only the starting point. It is also worth checking reachability from the actual QuickNotes code, exploit preconditions, internet exposure, and whether the vulnerable component is part of the shipped artifact or only local development state. + +Examples from this lab: + +- The image scan found many HIGH/CRITICAL issues in the bundled Go stdlib, but most are in TLS, X.509, HTTP/2, or `net/mail` paths that QuickNotes does not use in its current plain-HTTP local deployment. +- The filesystem scan found a real private key, but it lives under ignored `.vagrant/` runtime state and is not part of the image or tracked Git content. + +Other useful triage inputs are fixed-version availability, exploit maturity, blast radius, compensating controls, and whether a finding is in a transient lab environment or a production-facing service. + +### b) Distroless images often show zero HIGH/CRITICAL. Why is the minimal base the strongest single security control? + +Because the best vulnerability is the one you never ship. A minimal runtime removes shells, package managers, and large piles of OS packages that would otherwise add both attack surface and CVEs. + +That showed up directly in this scan: the OS layer reported `0` HIGH/CRITICAL findings. The remaining image issues came from the Go binary itself, not from extra runtime packages. Distroless does not fix application bugs, but it sharply reduces everything unrelated to the app. + +### c) `.trivyignore` lets you suppress findings. When is that the right move, and when is it security theater? + +It is the right move when the finding is understood, documented, time-bounded, and either not reachable yet or not fixable yet. A good suppression has an owner, a reason, and a review date. + +It becomes security theater when it is used to make CI green without changing risk, especially for issues that are shipped to users, are reachable, or already have a straightforward fix. A suppression without a reason or date is just a permanent blindfold. + +### d) The SBOM is a list of components. What concrete future problem does having it today solve? + +This makes it possible to quickly determine whether a recently disclosed vulnerability, such as CVE-X, affects an artefact that has already been compiled. + +If an incident like Log4Shell were to recur, thanks to the SBOM, there would be no need to recompile images or search the entire repository using `grep` just to ascertain the extent of the vulnerability. You can check the list of components in the released artefact and, in a matter of minutes, determine whether `quicknotes:lab6` contains the vulnerable package and its version. From f85a69db02f60937e092e72206f0f9b258bc2d72 Mon Sep 17 00:00:00 2001 From: "man@smairon.ru" Date: Tue, 7 Jul 2026 21:25:54 +0300 Subject: [PATCH 7/8] docs(lab9): task2 submission Signed-off-by: man@smairon.ru --- app/handlers.go | 14 + app/handlers_test.go | 51 ++ submissions/lab9-artifacts/zap-post.html | 619 +++++++++++++++++++++++ submissions/lab9-artifacts/zap-post.json | 99 ++++ submissions/lab9-artifacts/zap-post.md | 136 +++++ submissions/lab9-artifacts/zap-post.txt | 77 +++ submissions/lab9-artifacts/zap-pre.html | 566 +++++++++++++++++++++ submissions/lab9-artifacts/zap-pre.json | 89 ++++ submissions/lab9-artifacts/zap-pre.md | 122 +++++ submissions/lab9-artifacts/zap-pre.txt | 76 +++ submissions/lab9.md | 177 +++++++ 11 files changed, 2026 insertions(+) create mode 100644 submissions/lab9-artifacts/zap-post.html create mode 100644 submissions/lab9-artifacts/zap-post.json create mode 100644 submissions/lab9-artifacts/zap-post.md create mode 100644 submissions/lab9-artifacts/zap-post.txt create mode 100644 submissions/lab9-artifacts/zap-pre.html create mode 100644 submissions/lab9-artifacts/zap-pre.json create mode 100644 submissions/lab9-artifacts/zap-pre.md create mode 100644 submissions/lab9-artifacts/zap-pre.txt diff --git a/app/handlers.go b/app/handlers.go index c534979c5..c8711195b 100644 --- a/app/handlers.go +++ b/app/handlers.go @@ -34,6 +34,20 @@ func (s *Server) Routes() *http.ServeMux { mux.HandleFunc("POST /notes", s.wrap(s.handleCreateNote)) mux.HandleFunc("GET /notes/{id}", s.wrap(s.handleGetNote)) mux.HandleFunc("DELETE /notes/{id}", s.wrap(s.handleDeleteNote)) + return withSecurityHeaders(mux) +} + +func withSecurityHeaders(next http.Handler) *http.ServeMux { + mux := http.NewServeMux() + mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Cache-Control", "no-store, max-age=0") + w.Header().Set("Content-Security-Policy", "default-src 'none'") + w.Header().Set("Expires", "0") + w.Header().Set("Pragma", "no-cache") + w.Header().Set("X-Content-Type-Options", "nosniff") + w.Header().Set("X-Frame-Options", "DENY") + next.ServeHTTP(w, r) + })) return mux } diff --git a/app/handlers_test.go b/app/handlers_test.go index 9dff2e3e5..f58e85ec6 100644 --- a/app/handlers_test.go +++ b/app/handlers_test.go @@ -131,3 +131,54 @@ func TestMetrics_ExposesPrometheusFormat(t *testing.T) { } } +func TestSecurityHeaders_PresentOnSuccessfulResponse(t *testing.T) { + srv := newTestServer(t) + rec := do(t, srv, http.MethodGet, "/health", nil) + + if got := rec.Header().Get("Content-Security-Policy"); got != "default-src 'none'" { + t.Fatalf("Content-Security-Policy = %q", got) + } + if got := rec.Header().Get("Cache-Control"); got != "no-store, max-age=0" { + t.Fatalf("Cache-Control = %q", got) + } + if got := rec.Header().Get("Pragma"); got != "no-cache" { + t.Fatalf("Pragma = %q", got) + } + if got := rec.Header().Get("Expires"); got != "0" { + t.Fatalf("Expires = %q", got) + } + if got := rec.Header().Get("X-Content-Type-Options"); got != "nosniff" { + t.Fatalf("X-Content-Type-Options = %q", got) + } + if got := rec.Header().Get("X-Frame-Options"); got != "DENY" { + t.Fatalf("X-Frame-Options = %q", got) + } +} + +func TestSecurityHeaders_PresentOnNotFoundResponse(t *testing.T) { + srv := newTestServer(t) + rec := do(t, srv, http.MethodGet, "/does-not-exist", nil) + + if rec.Code != http.StatusNotFound { + t.Fatalf("expected 404, got %d", rec.Code) + } + if got := rec.Header().Get("Content-Security-Policy"); got != "default-src 'none'" { + t.Fatalf("Content-Security-Policy = %q", got) + } + if got := rec.Header().Get("Cache-Control"); got != "no-store, max-age=0" { + t.Fatalf("Cache-Control = %q", got) + } + if got := rec.Header().Get("Pragma"); got != "no-cache" { + t.Fatalf("Pragma = %q", got) + } + if got := rec.Header().Get("Expires"); got != "0" { + t.Fatalf("Expires = %q", got) + } + if got := rec.Header().Get("X-Content-Type-Options"); got != "nosniff" { + t.Fatalf("X-Content-Type-Options = %q", got) + } + if got := rec.Header().Get("X-Frame-Options"); got != "DENY" { + t.Fatalf("X-Frame-Options = %q", got) + } +} + diff --git a/submissions/lab9-artifacts/zap-post.html b/submissions/lab9-artifacts/zap-post.html new file mode 100644 index 000000000..3e056702b --- /dev/null +++ b/submissions/lab9-artifacts/zap-post.html @@ -0,0 +1,619 @@ + + + + +ZAP Scanning Report + + + +

+ + + ZAP Scanning Report +

+

+ + +

+ + Site: http://quicknotes:8080 + +

+ +

+ Generated on Tue, 7 Jul 2026 18:15:39 +

+ +

+ ZAP Version: 2.16.1 +

+ +

+ ZAP by Checkmarx +

+ + +

Summary of Alerts

+ + + + + + + + + + + + + + + + + + + + + + + + + +
Risk LevelNumber of Alerts
+
High
+
+
0
+
+
Medium
+
+
0
+
+
Low
+
+
1
+
+
Informational
+
+
1
+
+
False Positives:
+
+
0
+
+
+ + + + +

Summary of Sequences

+

For each step: result (Pass/Fail) - risk (of highest alert(s) for the step, if any).

+ + + + + + +

Alerts

+ + + + + + + + + + + + + + + + + + + + + + +
NameRisk LevelNumber of Instances
ZAP is Out of DateLow1
Non-Storable ContentInformational3
+
+ + + +

Alert Detail

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
Low
ZAP is Out of Date
Description +
The version of ZAP you are using to test your app is out of date and is no longer being updated.
+
+ +
The risk level is set based on how out of date your ZAP version is.
+ +
URLhttp://quicknotes:8080/sitemap.xml
MethodGET
Parameter
Attack
Evidence
Other InfoThe latest version of ZAP is 2.17.0
Instances1
Solution +
Download the latest version of ZAP from https://www.zaproxy.org/download/ and install it.
+ +
Reference + https://www.zaproxy.org/download/ + +
CWE Id1104
WASC Id45
Plugin Id10116
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
Informational
Non-Storable Content
Description +
The response contents are not storable by caching components such as proxy servers. If the response does not contain sensitive, personal or user-specific information, it may benefit from being stored and cached, to improve performance.
+ +
URLhttp://quicknotes:8080
MethodGET
Parameter
Attack
Evidenceno-store
Other Info
URLhttp://quicknotes:8080/robots.txt
MethodGET
Parameter
Attack
Evidenceno-store
Other Info
URLhttp://quicknotes:8080/sitemap.xml
MethodGET
Parameter
Attack
Evidenceno-store
Other Info
Instances3
Solution +
The content may be marked as storable by ensuring that the following conditions are satisfied:
+
+ +
The request method must be understood by the cache and defined as being cacheable ("GET", "HEAD", and "POST" are currently defined as cacheable)
+
+ +
The response status code must be understood by the cache (one of the 1XX, 2XX, 3XX, 4XX, or 5XX response classes are generally understood)
+
+ +
The "no-store" cache directive must not appear in the request or response header fields
+
+ +
For caching by "shared" caches such as "proxy" caches, the "private" response directive must not appear in the response
+
+ +
For caching by "shared" caches such as "proxy" caches, the "Authorization" header field must not appear in the request, unless the response explicitly allows it (using one of the "must-revalidate", "public", or "s-maxage" Cache-Control response directives)
+
+ +
In addition to the conditions above, at least one of the following conditions must also be satisfied by the response:
+
+ +
It must contain an "Expires" header field
+
+ +
It must contain a "max-age" response directive
+
+ +
For "shared" caches such as "proxy" caches, it must contain a "s-maxage" response directive
+
+ +
It must contain a "Cache Control Extension" that allows it to be cached
+
+ +
It must have a status code that is defined as cacheable by default (200, 203, 204, 206, 300, 301, 404, 405, 410, 414, 501).
+ +
Reference + https://datatracker.ietf.org/doc/html/rfc7234 +
+ + https://datatracker.ietf.org/doc/html/rfc7231 +
+ + https://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html + +
CWE Id524
WASC Id13
Plugin Id10049
+
+ + + + + +

Sequence Details

+ With the associated active scan results. + + + +
+ + + + + + + diff --git a/submissions/lab9-artifacts/zap-post.json b/submissions/lab9-artifacts/zap-post.json new file mode 100644 index 000000000..ca5c94e1f --- /dev/null +++ b/submissions/lab9-artifacts/zap-post.json @@ -0,0 +1,99 @@ +{ + "@programName": "ZAP", + "@version": "2.16.1", + "@generated": "Tue, 7 Jul 2026 18:15:39", + "created": "2026-07-07T18:15:39.295323667Z", + "site":[ + { + "@name": "http://quicknotes:8080", + "@host": "quicknotes", + "@port": "8080", + "@ssl": "false", + "alerts": [ + { + "pluginid": "10116", + "alertRef": "10116", + "alert": "ZAP is Out of Date", + "name": "ZAP is Out of Date", + "riskcode": "1", + "confidence": "3", + "riskdesc": "Low (High)", + "desc": "

The version of ZAP you are using to test your app is out of date and is no longer being updated.

The risk level is set based on how out of date your ZAP version is.

", + "instances":[ + { + "id": "3", + "uri": "http://quicknotes:8080/sitemap.xml", + "nodeName": null, + "method": "GET", + "param": "", + "attack": "", + "evidence": "", + "otherinfo": "The latest version of ZAP is 2.17.0" + } + ], + "count": "1", + "systemic": false, + "solution": "

Download the latest version of ZAP from https://www.zaproxy.org/download/ and install it.

", + "otherinfo": "

The latest version of ZAP is 2.17.0

", + "reference": "

https://www.zaproxy.org/download/

", + "cweid": "1104", + "wascid": "45", + "sourceid": "7" + }, + { + "pluginid": "10049", + "alertRef": "10049-1", + "alert": "Non-Storable Content", + "name": "Non-Storable Content", + "riskcode": "0", + "confidence": "2", + "riskdesc": "Informational (Medium)", + "desc": "

The response contents are not storable by caching components such as proxy servers. If the response does not contain sensitive, personal or user-specific information, it may benefit from being stored and cached, to improve performance.

", + "instances":[ + { + "id": "1", + "uri": "http://quicknotes:8080", + "nodeName": null, + "method": "GET", + "param": "", + "attack": "", + "evidence": "no-store", + "otherinfo": "" + }, + { + "id": "2", + "uri": "http://quicknotes:8080/robots.txt", + "nodeName": null, + "method": "GET", + "param": "", + "attack": "", + "evidence": "no-store", + "otherinfo": "" + }, + { + "id": "4", + "uri": "http://quicknotes:8080/sitemap.xml", + "nodeName": null, + "method": "GET", + "param": "", + "attack": "", + "evidence": "no-store", + "otherinfo": "" + } + ], + "count": "3", + "systemic": false, + "solution": "

The content may be marked as storable by ensuring that the following conditions are satisfied:

The request method must be understood by the cache and defined as being cacheable (\"GET\", \"HEAD\", and \"POST\" are currently defined as cacheable)

The response status code must be understood by the cache (one of the 1XX, 2XX, 3XX, 4XX, or 5XX response classes are generally understood)

The \"no-store\" cache directive must not appear in the request or response header fields

For caching by \"shared\" caches such as \"proxy\" caches, the \"private\" response directive must not appear in the response

For caching by \"shared\" caches such as \"proxy\" caches, the \"Authorization\" header field must not appear in the request, unless the response explicitly allows it (using one of the \"must-revalidate\", \"public\", or \"s-maxage\" Cache-Control response directives)

In addition to the conditions above, at least one of the following conditions must also be satisfied by the response:

It must contain an \"Expires\" header field

It must contain a \"max-age\" response directive

For \"shared\" caches such as \"proxy\" caches, it must contain a \"s-maxage\" response directive

It must contain a \"Cache Control Extension\" that allows it to be cached

It must have a status code that is defined as cacheable by default (200, 203, 204, 206, 300, 301, 404, 405, 410, 414, 501).

", + "otherinfo": "", + "reference": "

https://datatracker.ietf.org/doc/html/rfc7234

https://datatracker.ietf.org/doc/html/rfc7231

https://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html

", + "cweid": "524", + "wascid": "13", + "sourceid": "1" + } + ] + } + ], + "sequences":[ + ] + +} diff --git a/submissions/lab9-artifacts/zap-post.md b/submissions/lab9-artifacts/zap-post.md new file mode 100644 index 000000000..b1cb6bb8b --- /dev/null +++ b/submissions/lab9-artifacts/zap-post.md @@ -0,0 +1,136 @@ +# ZAP Scanning Report + +ZAP by [Checkmarx](https://checkmarx.com/). + + +## Summary of Alerts + +| Risk Level | Number of Alerts | +| --- | --- | +| High | 0 | +| Medium | 0 | +| Low | 1 | +| Informational | 1 | + + + + +## Alerts + +| Name | Risk Level | Number of Instances | +| --- | --- | --- | +| ZAP is Out of Date | Low | 1 | +| Non-Storable Content | Informational | 3 | + + + + +## Alert Detail + + + +### [ ZAP is Out of Date ](https://www.zaproxy.org/docs/alerts/10116/) + + + +##### Low (High) + +### Description + +The version of ZAP you are using to test your app is out of date and is no longer being updated. +The risk level is set based on how out of date your ZAP version is. + +* URL: http://quicknotes:8080/sitemap.xml + + * Method: `GET` + * Parameter: `` + * Attack: `` + * Evidence: `` + * Other Info: `The latest version of ZAP is 2.17.0` + + +Instances: 1 + +### Solution + +Download the latest version of ZAP from https://www.zaproxy.org/download/ and install it. + +### Reference + + +* [ https://www.zaproxy.org/download/ ](https://www.zaproxy.org/download/) + + +#### CWE Id: [ 1104 ](https://cwe.mitre.org/data/definitions/1104.html) + + +#### WASC Id: 45 + +#### Source ID: 3 + +### [ Non-Storable Content ](https://www.zaproxy.org/docs/alerts/10049/) + + + +##### Informational (Medium) + +### Description + +The response contents are not storable by caching components such as proxy servers. If the response does not contain sensitive, personal or user-specific information, it may benefit from being stored and cached, to improve performance. + +* URL: http://quicknotes:8080 + + * Method: `GET` + * Parameter: `` + * Attack: `` + * Evidence: `no-store` + * Other Info: `` +* URL: http://quicknotes:8080/robots.txt + + * Method: `GET` + * Parameter: `` + * Attack: `` + * Evidence: `no-store` + * Other Info: `` +* URL: http://quicknotes:8080/sitemap.xml + + * Method: `GET` + * Parameter: `` + * Attack: `` + * Evidence: `no-store` + * Other Info: `` + + +Instances: 3 + +### Solution + +The content may be marked as storable by ensuring that the following conditions are satisfied: +The request method must be understood by the cache and defined as being cacheable ("GET", "HEAD", and "POST" are currently defined as cacheable) +The response status code must be understood by the cache (one of the 1XX, 2XX, 3XX, 4XX, or 5XX response classes are generally understood) +The "no-store" cache directive must not appear in the request or response header fields +For caching by "shared" caches such as "proxy" caches, the "private" response directive must not appear in the response +For caching by "shared" caches such as "proxy" caches, the "Authorization" header field must not appear in the request, unless the response explicitly allows it (using one of the "must-revalidate", "public", or "s-maxage" Cache-Control response directives) +In addition to the conditions above, at least one of the following conditions must also be satisfied by the response: +It must contain an "Expires" header field +It must contain a "max-age" response directive +For "shared" caches such as "proxy" caches, it must contain a "s-maxage" response directive +It must contain a "Cache Control Extension" that allows it to be cached +It must have a status code that is defined as cacheable by default (200, 203, 204, 206, 300, 301, 404, 405, 410, 414, 501). + +### Reference + + +* [ https://datatracker.ietf.org/doc/html/rfc7234 ](https://datatracker.ietf.org/doc/html/rfc7234) +* [ https://datatracker.ietf.org/doc/html/rfc7231 ](https://datatracker.ietf.org/doc/html/rfc7231) +* [ https://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html ](https://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html) + + +#### CWE Id: [ 524 ](https://cwe.mitre.org/data/definitions/524.html) + + +#### WASC Id: 13 + +#### Source ID: 3 + + diff --git a/submissions/lab9-artifacts/zap-post.txt b/submissions/lab9-artifacts/zap-post.txt new file mode 100644 index 000000000..edb0dd6d1 --- /dev/null +++ b/submissions/lab9-artifacts/zap-post.txt @@ -0,0 +1,77 @@ +Using the Automation Framework +Total of 3 URLs +PASS: Vulnerable JS Library (Powered by Retire.js) [10003] +PASS: In Page Banner Information Leak [10009] +PASS: Cookie No HttpOnly Flag [10010] +PASS: Cookie Without Secure Flag [10011] +PASS: Re-examine Cache-control Directives [10015] +PASS: Cross-Domain JavaScript Source File Inclusion [10017] +PASS: Content-Type Header Missing [10019] +PASS: Anti-clickjacking Header [10020] +PASS: X-Content-Type-Options Header Missing [10021] +PASS: Information Disclosure - Debug Error Messages [10023] +PASS: Information Disclosure - Sensitive Information in URL [10024] +PASS: Information Disclosure - Sensitive Information in HTTP Referrer Header [10025] +PASS: HTTP Parameter Override [10026] +PASS: Information Disclosure - Suspicious Comments [10027] +PASS: Off-site Redirect [10028] +PASS: Cookie Poisoning [10029] +PASS: User Controllable Charset [10030] +PASS: User Controllable HTML Element Attribute (Potential XSS) [10031] +PASS: Viewstate [10032] +PASS: Directory Browsing [10033] +PASS: Heartbleed OpenSSL Vulnerability (Indicative) [10034] +PASS: Strict-Transport-Security Header [10035] +PASS: HTTP Server Response Header [10036] +PASS: Server Leaks Information via "X-Powered-By" HTTP Response Header Field(s) [10037] +PASS: Content Security Policy (CSP) Header Not Set [10038] +PASS: X-Backend-Server Header Information Leak [10039] +PASS: Secure Pages Include Mixed Content [10040] +PASS: HTTP to HTTPS Insecure Transition in Form Post [10041] +PASS: HTTPS to HTTP Insecure Transition in Form Post [10042] +PASS: User Controllable JavaScript Event (XSS) [10043] +PASS: Big Redirect Detected (Potential Sensitive Information Leak) [10044] +PASS: Retrieved from Cache [10050] +PASS: X-ChromeLogger-Data (XCOLD) Header Information Leak [10052] +PASS: Cookie without SameSite Attribute [10054] +PASS: CSP [10055] +PASS: X-Debug-Token Information Leak [10056] +PASS: Username Hash Found [10057] +PASS: X-AspNet-Version Response Header [10061] +PASS: PII Disclosure [10062] +PASS: Permissions Policy Header Not Set [10063] +PASS: Timestamp Disclosure [10096] +PASS: Hash Disclosure [10097] +PASS: Cross-Domain Misconfiguration [10098] +PASS: Source Code Disclosure [10099] +PASS: Weak Authentication Method [10105] +PASS: Reverse Tabnabbing [10108] +PASS: Modern Web Application [10109] +PASS: Dangerous JS Functions [10110] +PASS: Authentication Request Identified [10111] +PASS: Session Management Response Identified [10112] +PASS: Verification Request Identified [10113] +PASS: Script Served From Malicious Domain (polyfill) [10115] +PASS: Absence of Anti-CSRF Tokens [10202] +PASS: Private IP Disclosure [2] +PASS: Session ID in URL Rewrite [3] +PASS: Script Passive Scan Rules [50001] +PASS: Stats Passive Scan Rule [50003] +PASS: Insecure JSF ViewState [90001] +PASS: Java Serialization Object [90002] +PASS: Sub Resource Integrity Attribute Missing [90003] +PASS: Insufficient Site Isolation Against Spectre Vulnerability [90004] +PASS: Charset Mismatch [90011] +PASS: Application Error Disclosure [90022] +PASS: WSDL File Detection [90030] +PASS: Loosely Scoped Cookie [90033] +WARN-NEW: Non-Storable Content [10049] x 3 + http://quicknotes:8080 (404 Not Found) + http://quicknotes:8080/robots.txt (404 Not Found) + http://quicknotes:8080/sitemap.xml (404 Not Found) +WARN-NEW: ZAP is Out of Date [10116] x 1 + http://quicknotes:8080/sitemap.xml (404 Not Found) +FAIL-NEW: 0 FAIL-INPROG: 0 WARN-NEW: 2 WARN-INPROG: 0 INFO: 0 IGNORE: 0 PASS: 65 +Automation plan warnings: + Job spider error accessing URL http://quicknotes:8080 status code returned : 404 expected 200 +ZAP_EXIT_CODE=2 diff --git a/submissions/lab9-artifacts/zap-pre.html b/submissions/lab9-artifacts/zap-pre.html new file mode 100644 index 000000000..daa31af63 --- /dev/null +++ b/submissions/lab9-artifacts/zap-pre.html @@ -0,0 +1,566 @@ + + + + +ZAP Scanning Report + + + +

+ + + ZAP Scanning Report +

+

+ + +

+ + Site: http://quicknotes:8080 + +

+ +

+ Generated on Tue, 7 Jul 2026 18:12:31 +

+ +

+ ZAP Version: 2.16.1 +

+ +

+ ZAP by Checkmarx +

+ + +

Summary of Alerts

+ + + + + + + + + + + + + + + + + + + + + + + + + +
Risk LevelNumber of Alerts
+
High
+
+
0
+
+
Medium
+
+
0
+
+
Low
+
+
1
+
+
Informational
+
+
1
+
+
False Positives:
+
+
0
+
+
+ + + + +

Summary of Sequences

+

For each step: result (Pass/Fail) - risk (of highest alert(s) for the step, if any).

+ + + + + + +

Alerts

+ + + + + + + + + + + + + + + + + + + + + + +
NameRisk LevelNumber of Instances
ZAP is Out of DateLow1
Storable and Cacheable ContentInformational2
+
+ + + +

Alert Detail

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
Low
ZAP is Out of Date
Description +
The version of ZAP you are using to test your app is out of date and is no longer being updated.
+
+ +
The risk level is set based on how out of date your ZAP version is.
+ +
URLhttp://quicknotes:8080
MethodGET
Parameter
Attack
Evidence
Other InfoThe latest version of ZAP is 2.17.0
Instances1
Solution +
Download the latest version of ZAP from https://www.zaproxy.org/download/ and install it.
+ +
Reference + https://www.zaproxy.org/download/ + +
CWE Id1104
WASC Id45
Plugin Id10116
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
Informational
Storable and Cacheable Content
Description +
The response contents are storable by caching components such as proxy servers, and may be retrieved directly from the cache, rather than from the origin server by the caching servers, in response to similar requests from other users. If the response data is sensitive, personal or user-specific, this may result in sensitive information being leaked. In some cases, this may even result in a user gaining complete control of the session of another user, depending on the configuration of the caching components in use in their environment. This is primarily an issue where "shared" caching servers such as "proxy" caches are configured on the local network. This configuration is typically found in corporate or educational environments, for instance.
+ +
URLhttp://quicknotes:8080
MethodGET
Parameter
Attack
Evidence
Other InfoIn the absence of an explicitly specified caching lifetime directive in the response, a liberal lifetime heuristic of 1 year was assumed. This is permitted by rfc7234.
URLhttp://quicknotes:8080/sitemap.xml
MethodGET
Parameter
Attack
Evidence
Other InfoIn the absence of an explicitly specified caching lifetime directive in the response, a liberal lifetime heuristic of 1 year was assumed. This is permitted by rfc7234.
Instances2
Solution +
Validate that the response does not contain sensitive, personal or user-specific information. If it does, consider the use of the following HTTP response headers, to limit, or prevent the content being stored and retrieved from the cache by another user:
+
+ +
Cache-Control: no-cache, no-store, must-revalidate, private
+
+ +
Pragma: no-cache
+
+ +
Expires: 0
+
+ +
This configuration directs both HTTP 1.0 and HTTP 1.1 compliant caching servers to not store the response, and to not retrieve the response (without validation) from the cache, in response to a similar request.
+ +
Reference + https://datatracker.ietf.org/doc/html/rfc7234 +
+ + https://datatracker.ietf.org/doc/html/rfc7231 +
+ + https://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html + +
CWE Id524
WASC Id13
Plugin Id10049
+
+ + + + + +

Sequence Details

+ With the associated active scan results. + + + +
+ + + + + + + diff --git a/submissions/lab9-artifacts/zap-pre.json b/submissions/lab9-artifacts/zap-pre.json new file mode 100644 index 000000000..5a616824d --- /dev/null +++ b/submissions/lab9-artifacts/zap-pre.json @@ -0,0 +1,89 @@ +{ + "@programName": "ZAP", + "@version": "2.16.1", + "@generated": "Tue, 7 Jul 2026 18:12:32", + "created": "2026-07-07T18:12:32.038326178Z", + "site":[ + { + "@name": "http://quicknotes:8080", + "@host": "quicknotes", + "@port": "8080", + "@ssl": "false", + "alerts": [ + { + "pluginid": "10116", + "alertRef": "10116", + "alert": "ZAP is Out of Date", + "name": "ZAP is Out of Date", + "riskcode": "1", + "confidence": "3", + "riskdesc": "Low (High)", + "desc": "

The version of ZAP you are using to test your app is out of date and is no longer being updated.

The risk level is set based on how out of date your ZAP version is.

", + "instances":[ + { + "id": "2", + "uri": "http://quicknotes:8080", + "nodeName": null, + "method": "GET", + "param": "", + "attack": "", + "evidence": "", + "otherinfo": "The latest version of ZAP is 2.17.0" + } + ], + "count": "1", + "systemic": false, + "solution": "

Download the latest version of ZAP from https://www.zaproxy.org/download/ and install it.

", + "otherinfo": "

The latest version of ZAP is 2.17.0

", + "reference": "

https://www.zaproxy.org/download/

", + "cweid": "1104", + "wascid": "45", + "sourceid": "1" + }, + { + "pluginid": "10049", + "alertRef": "10049-3", + "alert": "Storable and Cacheable Content", + "name": "Storable and Cacheable Content", + "riskcode": "0", + "confidence": "2", + "riskdesc": "Informational (Medium)", + "desc": "

The response contents are storable by caching components such as proxy servers, and may be retrieved directly from the cache, rather than from the origin server by the caching servers, in response to similar requests from other users. If the response data is sensitive, personal or user-specific, this may result in sensitive information being leaked. In some cases, this may even result in a user gaining complete control of the session of another user, depending on the configuration of the caching components in use in their environment. This is primarily an issue where \"shared\" caching servers such as \"proxy\" caches are configured on the local network. This configuration is typically found in corporate or educational environments, for instance.

", + "instances":[ + { + "id": "1", + "uri": "http://quicknotes:8080", + "nodeName": null, + "method": "GET", + "param": "", + "attack": "", + "evidence": "", + "otherinfo": "In the absence of an explicitly specified caching lifetime directive in the response, a liberal lifetime heuristic of 1 year was assumed. This is permitted by rfc7234." + }, + { + "id": "3", + "uri": "http://quicknotes:8080/sitemap.xml", + "nodeName": null, + "method": "GET", + "param": "", + "attack": "", + "evidence": "", + "otherinfo": "In the absence of an explicitly specified caching lifetime directive in the response, a liberal lifetime heuristic of 1 year was assumed. This is permitted by rfc7234." + } + ], + "count": "2", + "systemic": false, + "solution": "

Validate that the response does not contain sensitive, personal or user-specific information. If it does, consider the use of the following HTTP response headers, to limit, or prevent the content being stored and retrieved from the cache by another user:

Cache-Control: no-cache, no-store, must-revalidate, private

Pragma: no-cache

Expires: 0

This configuration directs both HTTP 1.0 and HTTP 1.1 compliant caching servers to not store the response, and to not retrieve the response (without validation) from the cache, in response to a similar request.

", + "otherinfo": "

In the absence of an explicitly specified caching lifetime directive in the response, a liberal lifetime heuristic of 1 year was assumed. This is permitted by rfc7234.

", + "reference": "

https://datatracker.ietf.org/doc/html/rfc7234

https://datatracker.ietf.org/doc/html/rfc7231

https://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html

", + "cweid": "524", + "wascid": "13", + "sourceid": "7" + } + ] + } + ], + "sequences":[ + ] + +} diff --git a/submissions/lab9-artifacts/zap-pre.md b/submissions/lab9-artifacts/zap-pre.md new file mode 100644 index 000000000..ffb7a3f46 --- /dev/null +++ b/submissions/lab9-artifacts/zap-pre.md @@ -0,0 +1,122 @@ +# ZAP Scanning Report + +ZAP by [Checkmarx](https://checkmarx.com/). + + +## Summary of Alerts + +| Risk Level | Number of Alerts | +| --- | --- | +| High | 0 | +| Medium | 0 | +| Low | 1 | +| Informational | 1 | + + + + +## Alerts + +| Name | Risk Level | Number of Instances | +| --- | --- | --- | +| ZAP is Out of Date | Low | 1 | +| Storable and Cacheable Content | Informational | 2 | + + + + +## Alert Detail + + + +### [ ZAP is Out of Date ](https://www.zaproxy.org/docs/alerts/10116/) + + + +##### Low (High) + +### Description + +The version of ZAP you are using to test your app is out of date and is no longer being updated. +The risk level is set based on how out of date your ZAP version is. + +* URL: http://quicknotes:8080 + + * Method: `GET` + * Parameter: `` + * Attack: `` + * Evidence: `` + * Other Info: `The latest version of ZAP is 2.17.0` + + +Instances: 1 + +### Solution + +Download the latest version of ZAP from https://www.zaproxy.org/download/ and install it. + +### Reference + + +* [ https://www.zaproxy.org/download/ ](https://www.zaproxy.org/download/) + + +#### CWE Id: [ 1104 ](https://cwe.mitre.org/data/definitions/1104.html) + + +#### WASC Id: 45 + +#### Source ID: 3 + +### [ Storable and Cacheable Content ](https://www.zaproxy.org/docs/alerts/10049/) + + + +##### Informational (Medium) + +### Description + +The response contents are storable by caching components such as proxy servers, and may be retrieved directly from the cache, rather than from the origin server by the caching servers, in response to similar requests from other users. If the response data is sensitive, personal or user-specific, this may result in sensitive information being leaked. In some cases, this may even result in a user gaining complete control of the session of another user, depending on the configuration of the caching components in use in their environment. This is primarily an issue where "shared" caching servers such as "proxy" caches are configured on the local network. This configuration is typically found in corporate or educational environments, for instance. + +* URL: http://quicknotes:8080 + + * Method: `GET` + * Parameter: `` + * Attack: `` + * Evidence: `` + * Other Info: `In the absence of an explicitly specified caching lifetime directive in the response, a liberal lifetime heuristic of 1 year was assumed. This is permitted by rfc7234.` +* URL: http://quicknotes:8080/sitemap.xml + + * Method: `GET` + * Parameter: `` + * Attack: `` + * Evidence: `` + * Other Info: `In the absence of an explicitly specified caching lifetime directive in the response, a liberal lifetime heuristic of 1 year was assumed. This is permitted by rfc7234.` + + +Instances: 2 + +### Solution + +Validate that the response does not contain sensitive, personal or user-specific information. If it does, consider the use of the following HTTP response headers, to limit, or prevent the content being stored and retrieved from the cache by another user: +Cache-Control: no-cache, no-store, must-revalidate, private +Pragma: no-cache +Expires: 0 +This configuration directs both HTTP 1.0 and HTTP 1.1 compliant caching servers to not store the response, and to not retrieve the response (without validation) from the cache, in response to a similar request. + +### Reference + + +* [ https://datatracker.ietf.org/doc/html/rfc7234 ](https://datatracker.ietf.org/doc/html/rfc7234) +* [ https://datatracker.ietf.org/doc/html/rfc7231 ](https://datatracker.ietf.org/doc/html/rfc7231) +* [ https://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html ](https://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html) + + +#### CWE Id: [ 524 ](https://cwe.mitre.org/data/definitions/524.html) + + +#### WASC Id: 13 + +#### Source ID: 3 + + diff --git a/submissions/lab9-artifacts/zap-pre.txt b/submissions/lab9-artifacts/zap-pre.txt new file mode 100644 index 000000000..776c463ff --- /dev/null +++ b/submissions/lab9-artifacts/zap-pre.txt @@ -0,0 +1,76 @@ +Using the Automation Framework +Total of 3 URLs +PASS: Vulnerable JS Library (Powered by Retire.js) [10003] +PASS: In Page Banner Information Leak [10009] +PASS: Cookie No HttpOnly Flag [10010] +PASS: Cookie Without Secure Flag [10011] +PASS: Re-examine Cache-control Directives [10015] +PASS: Cross-Domain JavaScript Source File Inclusion [10017] +PASS: Content-Type Header Missing [10019] +PASS: Anti-clickjacking Header [10020] +PASS: X-Content-Type-Options Header Missing [10021] +PASS: Information Disclosure - Debug Error Messages [10023] +PASS: Information Disclosure - Sensitive Information in URL [10024] +PASS: Information Disclosure - Sensitive Information in HTTP Referrer Header [10025] +PASS: HTTP Parameter Override [10026] +PASS: Information Disclosure - Suspicious Comments [10027] +PASS: Off-site Redirect [10028] +PASS: Cookie Poisoning [10029] +PASS: User Controllable Charset [10030] +PASS: User Controllable HTML Element Attribute (Potential XSS) [10031] +PASS: Viewstate [10032] +PASS: Directory Browsing [10033] +PASS: Heartbleed OpenSSL Vulnerability (Indicative) [10034] +PASS: Strict-Transport-Security Header [10035] +PASS: HTTP Server Response Header [10036] +PASS: Server Leaks Information via "X-Powered-By" HTTP Response Header Field(s) [10037] +PASS: Content Security Policy (CSP) Header Not Set [10038] +PASS: X-Backend-Server Header Information Leak [10039] +PASS: Secure Pages Include Mixed Content [10040] +PASS: HTTP to HTTPS Insecure Transition in Form Post [10041] +PASS: HTTPS to HTTP Insecure Transition in Form Post [10042] +PASS: User Controllable JavaScript Event (XSS) [10043] +PASS: Big Redirect Detected (Potential Sensitive Information Leak) [10044] +PASS: Retrieved from Cache [10050] +PASS: X-ChromeLogger-Data (XCOLD) Header Information Leak [10052] +PASS: Cookie without SameSite Attribute [10054] +PASS: CSP [10055] +PASS: X-Debug-Token Information Leak [10056] +PASS: Username Hash Found [10057] +PASS: X-AspNet-Version Response Header [10061] +PASS: PII Disclosure [10062] +PASS: Permissions Policy Header Not Set [10063] +PASS: Timestamp Disclosure [10096] +PASS: Hash Disclosure [10097] +PASS: Cross-Domain Misconfiguration [10098] +PASS: Source Code Disclosure [10099] +PASS: Weak Authentication Method [10105] +PASS: Reverse Tabnabbing [10108] +PASS: Modern Web Application [10109] +PASS: Dangerous JS Functions [10110] +PASS: Authentication Request Identified [10111] +PASS: Session Management Response Identified [10112] +PASS: Verification Request Identified [10113] +PASS: Script Served From Malicious Domain (polyfill) [10115] +PASS: Absence of Anti-CSRF Tokens [10202] +PASS: Private IP Disclosure [2] +PASS: Session ID in URL Rewrite [3] +PASS: Script Passive Scan Rules [50001] +PASS: Stats Passive Scan Rule [50003] +PASS: Insecure JSF ViewState [90001] +PASS: Java Serialization Object [90002] +PASS: Sub Resource Integrity Attribute Missing [90003] +PASS: Insufficient Site Isolation Against Spectre Vulnerability [90004] +PASS: Charset Mismatch [90011] +PASS: Application Error Disclosure [90022] +PASS: WSDL File Detection [90030] +PASS: Loosely Scoped Cookie [90033] +WARN-NEW: Storable and Cacheable Content [10049] x 2 + http://quicknotes:8080 (404 Not Found) + http://quicknotes:8080/sitemap.xml (404 Not Found) +WARN-NEW: ZAP is Out of Date [10116] x 1 + http://quicknotes:8080 (404 Not Found) +FAIL-NEW: 0 FAIL-INPROG: 0 WARN-NEW: 2 WARN-INPROG: 0 INFO: 0 IGNORE: 0 PASS: 65 +Automation plan warnings: + Job spider error accessing URL http://quicknotes:8080 status code returned : 404 expected 200 +ZAP_EXIT_CODE=2 diff --git a/submissions/lab9.md b/submissions/lab9.md index ab5a8b244..4409d2963 100644 --- a/submissions/lab9.md +++ b/submissions/lab9.md @@ -200,3 +200,180 @@ It becomes security theater when it is used to make CI green without changing ri This makes it possible to quickly determine whether a recently disclosed vulnerability, such as CVE-X, affects an artefact that has already been compiled. If an incident like Log4Shell were to recur, thanks to the SBOM, there would be no need to recompile images or search the entire repository using `grep` just to ascertain the extent of the vulnerability. You can check the list of components in the released artefact and, in a matter of minutes, determine whether `quicknotes:lab6` contains the vulnerable package and its version. + +## Task 2 — OWASP ZAP Baseline + Fix at Least One Finding (4 pts) + +Scan date: 2026-07-07 + +Pinned scanner version: + +```text +ghcr.io/zaproxy/zaproxy:2.16.1 +``` + +Artifacts saved in `submissions/lab9-artifacts/`: + +- `zap-pre.html` +- `zap-pre.json` +- `zap-pre.md` +- `zap-pre.txt` +- `zap-post.html` +- `zap-post.json` +- `zap-post.md` +- `zap-post.txt` + +Because ZAP was run in a container on macOS, the scan targeted the QuickNotes service on the Compose network at `http://quicknotes:8080` rather than host `localhost`. + +## Commands used + +```bash +docker compose up -d --build quicknotes + +docker run --rm \ + --network devops-intro_default \ + -v "$PWD:/zap/wrk:rw" \ + ghcr.io/zaproxy/zaproxy:2.16.1 \ + zap-baseline.py \ + -t http://quicknotes:8080 \ + -r submissions/lab9-artifacts/zap-pre.html \ + -J submissions/lab9-artifacts/zap-pre.json \ + -w submissions/lab9-artifacts/zap-pre.md + +docker compose up -d --build quicknotes + +docker run --rm \ + --network devops-intro_default \ + -v "$PWD:/zap/wrk:rw" \ + ghcr.io/zaproxy/zaproxy:2.16.1 \ + zap-baseline.py \ + -t http://quicknotes:8080 \ + -r submissions/lab9-artifacts/zap-post.html \ + -J submissions/lab9-artifacts/zap-post.json \ + -w submissions/lab9-artifacts/zap-post.md +``` + +## ZAP summary + +Pre-fix summary: + +```text +FAIL-NEW: 0 FAIL-INPROG: 0 WARN-NEW: 2 WARN-INPROG: 0 INFO: 0 IGNORE: 0 PASS: 65 + +WARN-NEW: Storable and Cacheable Content [10049] x 2 + http://quicknotes:8080 (404 Not Found) + http://quicknotes:8080/sitemap.xml (404 Not Found) + +WARN-NEW: ZAP is Out of Date [10116] x 1 + http://quicknotes:8080 (404 Not Found) +``` + +Post-fix summary: + +```text +FAIL-NEW: 0 FAIL-INPROG: 0 WARN-NEW: 2 WARN-INPROG: 0 INFO: 0 IGNORE: 0 PASS: 65 + +WARN-NEW: Non-Storable Content [10049] x 3 + http://quicknotes:8080 (404 Not Found) + http://quicknotes:8080/robots.txt (404 Not Found) + http://quicknotes:8080/sitemap.xml (404 Not Found) + +WARN-NEW: ZAP is Out of Date [10116] x 1 + http://quicknotes:8080/sitemap.xml (404 Not Found) +``` + +## Full triage table for ZAP findings + +| Scan phase | ID | Name | Risk level | Affected URL / parameter | Disposition | Reason | +|------------|----|------|------------|---------------------------|-------------|--------| +| pre-fix | 10049 | Storable and Cacheable Content | Informational | `http://quicknotes:8080`, `http://quicknotes:8080/sitemap.xml` / none | FIX | Root and 404 responses were cacheable because no explicit cache-control headers were set. I fixed this by adding cache headers in middleware for every response. | +| pre-fix | 10116 | ZAP is Out of Date | Low | `http://quicknotes:8080` / none | SUPPRESS | This is a scanner self-finding, not an application defect. The lab explicitly required a pinned 2.16.x image; for a real CI gate I would update the scanner image when the course no longer requires 2.16.x. | +| post-fix | 10049 | Non-Storable Content | Informational | `http://quicknotes:8080`, `http://quicknotes:8080/robots.txt`, `http://quicknotes:8080/sitemap.xml` / none | ACCEPT | This is the expected result after the fix. The API now intentionally marks responses `no-store` so shared caches do not retain them. | +| post-fix | 10116 | ZAP is Out of Date | Low | `http://quicknotes:8080/sitemap.xml` / none | SUPPRESS | Same reasoning as above: scanner version issue, not QuickNotes behavior. | + +## Code fix + +Files changed: + +- `app/handlers.go` +- `app/handlers_test.go` + +Middleware excerpt: + +```go +func withSecurityHeaders(next http.Handler) *http.ServeMux { + mux := http.NewServeMux() + mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Cache-Control", "no-store, max-age=0") + w.Header().Set("Content-Security-Policy", "default-src 'none'") + w.Header().Set("Expires", "0") + w.Header().Set("Pragma", "no-cache") + w.Header().Set("X-Content-Type-Options", "nosniff") + w.Header().Set("X-Frame-Options", "DENY") + next.ServeHTTP(w, r) + })) + return mux +} +``` + +The router now returns `withSecurityHeaders(mux)` from `Routes()`, so the middleware applies to all routes, including `404` responses. + +Guarding test result: + +```text +passed=13 failed=0 +``` + +The tests assert the headers on both `/health` and an unmatched route, so they fail if the middleware is removed or stops wrapping the router globally. + +## Before/after proof for the fixed finding + +Before: + +```text +WARN-NEW: Storable and Cacheable Content [10049] x 2 + http://quicknotes:8080 (404 Not Found) + http://quicknotes:8080/sitemap.xml (404 Not Found) +``` + +After: + +```text +WARN-NEW: Non-Storable Content [10049] x 3 + http://quicknotes:8080 (404 Not Found) + http://quicknotes:8080/robots.txt (404 Not Found) + http://quicknotes:8080/sitemap.xml (404 Not Found) +``` + +This shows the original cacheability warning is gone. The same ZAP rule now reports the opposite condition, with evidence `no-store`, which is the intended safe outcome of the middleware fix. + +Live response header proof after rebuild: + +```text +HTTP/1.1 200 OK +Cache-Control: no-store, max-age=0 +Content-Security-Policy: default-src 'none' +Expires: 0 +Pragma: no-cache +X-Content-Type-Options: nosniff +X-Frame-Options: DENY +``` + +## Design answers + +### e) Why a middleware and not per-handler header sets? + +Middleware fixes the policy once at the HTTP boundary instead of repeating it in every handler. That avoids drift, makes the behavior consistent for success and error responses, and ensures new routes inherit the same security defaults automatically. + +This mattered here because the ZAP finding was triggered on `404` responses from `/` and `/sitemap.xml`, not only on the business handlers. Per-handler header sets would miss those paths much more easily. + +### f) `Content-Security-Policy: default-src 'none'` is the strictest CSP. What does it break? Why is it OK for QuickNotes but not for a website? + +It blocks all scripts, styles, images, fonts, frames, fetches, and other browser-loaded resources unless they are explicitly allowed. A normal website would break immediately because its HTML, JavaScript, CSS, images, and third-party assets would all be denied by default. + +QuickNotes is an API that returns JSON and Prometheus text, not a browser UI. For that kind of service, a very strict CSP is acceptable because there is no front-end resource loading behavior to preserve. + +### g) False positives vs accepted findings: ZAP often flags informational issues that aren't real problems. What's the cost of marking them all "accepted" without reading them? + +The cost is alert fatigue and loss of trust in the scanner. Once everything is blindly marked accepted, real findings disappear into the noise and future reviewers cannot tell whether a decision was thoughtful or lazy. + +It also destroys re-evaluation discipline. A finding that is harmless today can become important after an architecture change, but if it was mass-accepted with no reasoning, there is no reliable record of what assumption the acceptance depended on. From 83bc701adbe1d018d77b9c7ca0260c70781312a2 Mon Sep 17 00:00:00 2001 From: "man@smairon.ru" Date: Tue, 7 Jul 2026 21:29:28 +0300 Subject: [PATCH 8/8] docs(lab9): added handlers_test.go Signed-off-by: man@smairon.ru --- app/handlers_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/app/handlers_test.go b/app/handlers_test.go index f58e85ec6..1b3045f2e 100644 --- a/app/handlers_test.go +++ b/app/handlers_test.go @@ -181,4 +181,3 @@ func TestSecurityHeaders_PresentOnNotFoundResponse(t *testing.T) { t.Fatalf("X-Frame-Options = %q", got) } } -