From 0ecd6ba6ff0e80f8f23f509c122d0247da0f5584 Mon Sep 17 00:00:00 2001 From: Elvira <239804565+HNS2112@users.noreply.github.com> Date: Mon, 3 Aug 2026 22:31:07 +0300 Subject: [PATCH 1/4] docs: add PR template; ignore runtime data dir Signed-off-by: Elvira <239804565+HNS2112@users.noreply.github.com> --- .github/pull_request_template.md | 13 +++++++++++++ .gitignore | 2 ++ 2 files changed, 15 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..f336a9fbd --- /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 diff --git a/.gitignore b/.gitignore index 1c0a1e94b..966b15200 100644 --- a/.gitignore +++ b/.gitignore @@ -61,3 +61,5 @@ Thumbs.db # *.sbom.cdx.json, zap-*.html/json, trivy-*.txt (Lab 9 scan evidence) # flake.nix, flake.lock (Lab 11) # wasm/main.go, spin.toml, go.sum (Lab 12) +data/ +app/data/ From 8768460afe0b3273bc441dd8b60ac000509128cc Mon Sep 17 00:00:00 2001 From: HNS <239804565+HNS2112@users.noreply.github.com> Date: Tue, 4 Aug 2026 19:44:42 +0300 Subject: [PATCH 2/4] docs: upstream moved while you worked Signed-off-by: HNS <239804565+HNS2112@users.noreply.github.com> From 5a2d0913b0439bc8875bd9e62062fcf50aa3ca3d Mon Sep 17 00:00:00 2001 From: HNS <239804565+HNS2112@users.noreply.github.com> Date: Thu, 6 Aug 2026 19:35:28 +0300 Subject: [PATCH 3/4] fix(lab9): add security headers middleware with regression test Signed-off-by: HNS <239804565+HNS2112@users.noreply.github.com> --- app/Dockerfile | 24 + app/handlers.go | 3 + app/handlers_test.go | 1 - compose.yaml | 29 ++ security/sbom.cdx.json | 461 +++++++++++++++++ security/summary.txt | 34 ++ security/trivy-config.txt | 24 + security/trivy-fs.txt | 10 + security/trivy-image.txt | 107 ++++ security/zap-before-after.txt | 22 + security/zap-report-after.html | 793 ++++++++++++++++++++++++++++ security/zap-report-after.json | 149 ++++++ security/zap-report.html | 914 +++++++++++++++++++++++++++++++++ security/zap-report.json | 189 +++++++ 14 files changed, 2759 insertions(+), 1 deletion(-) create mode 100644 app/Dockerfile create mode 100644 compose.yaml create mode 100644 security/sbom.cdx.json create mode 100644 security/summary.txt create mode 100644 security/trivy-config.txt create mode 100644 security/trivy-fs.txt create mode 100644 security/trivy-image.txt create mode 100644 security/zap-before-after.txt create mode 100644 security/zap-report-after.html create mode 100644 security/zap-report-after.json create mode 100644 security/zap-report.html create mode 100644 security/zap-report.json diff --git a/app/Dockerfile b/app/Dockerfile new file mode 100644 index 000000000..7f43d41ae --- /dev/null +++ b/app/Dockerfile @@ -0,0 +1,24 @@ +# syntax=docker/dockerfile:1 + +FROM golang:1.24.6-bookworm AS builder +WORKDIR /src +COPY go.mod go.su[m] ./ +RUN go mod download +COPY . . +RUN CGO_ENABLED=0 go build \ + -trimpath \ + -ldflags='-s -w' \ + -o /out/quicknotes . +RUN mkdir -p /data-empty + +FROM busybox:1.37-uclibc AS busybox + +FROM gcr.io/distroless/static:nonroot +WORKDIR /app +COPY --from=builder /out/quicknotes /app/quicknotes +COPY --from=builder /src/seed.json /app/seed.json +COPY --from=busybox /bin/wget /bin/wget +COPY --from=builder --chown=65532:65532 /data-empty /data +USER nonroot:nonroot +EXPOSE 8080 +ENTRYPOINT ["/app/quicknotes"] diff --git a/app/handlers.go b/app/handlers.go index c534979c5..8fd457568 100644 --- a/app/handlers.go +++ b/app/handlers.go @@ -50,6 +50,9 @@ func (sw *statusWriter) WriteHeader(code int) { func (s *Server) wrap(h http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { sw := &statusWriter{ResponseWriter: w, code: 200} + w.Header().Set("X-Content-Type-Options", "nosniff") + w.Header().Set("Cross-Origin-Resource-Policy", "same-origin") + w.Header().Set("Cache-Control", "no-store") h(sw, r) s.requestsTotal.Add(1) if c, ok := s.requestsByCode[sw.code]; ok { diff --git a/app/handlers_test.go b/app/handlers_test.go index 9dff2e3e5..461b348ba 100644 --- a/app/handlers_test.go +++ b/app/handlers_test.go @@ -130,4 +130,3 @@ func TestMetrics_ExposesPrometheusFormat(t *testing.T) { } } } - diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 000000000..384ee7640 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,29 @@ +services: + quicknotes: + build: ./app + image: quicknotes:lab6 + ports: + - "8080:8080" + environment: + ADDR: ":8080" + DATA_PATH: /data/notes.json + SEED_PATH: /app/seed.json + healthcheck: + test: ["CMD", "/bin/wget", "-q", "-O", "-", "http://127.0.0.1:8080/health"] + interval: 10s + timeout: 3s + retries: 3 + start_period: 5s + cap_drop: + - ALL + read_only: true + tmpfs: + - /tmp + security_opt: + - no-new-privileges:true + volumes: + - quicknotes-data:/data + restart: unless-stopped + +volumes: + quicknotes-data: diff --git a/security/sbom.cdx.json b/security/sbom.cdx.json new file mode 100644 index 000000000..41529c5cf --- /dev/null +++ b/security/sbom.cdx.json @@ -0,0 +1,461 @@ +{ + "$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.6", + "serialNumber": "urn:uuid:b791e153-7412-47f7-b36f-884d65fb77ae", + "version": 1, + "metadata": { + "timestamp": "2026-08-06T16:14:39+00:00", + "tools": { + "components": [ + { + "type": "application", + "group": "aquasecurity", + "name": "trivy", + "version": "0.59.1" + } + ] + }, + "component": { + "bom-ref": "d4a16e93-63f8-4631-9dd0-9c7683a7ae9e", + "type": "container", + "name": "quicknotes:lab6", + "properties": [ + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:187cfc6d1e3e8a40a5e64653bcd3239c140807dcf1c09e48021178705a5a6139" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:275a30dd8ce958b21daa9ad962c6fbc09f98306ee2f486b65c9075dc257b1412" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:46e24989b88589c4a304c48e7965da94c0295ea2f3b78d39f5a17b40c42998d9" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:4cde6b0bb6f50a5f255eef7b2a42162c661cf776b803225dcac9a659e396bb6b" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:4d049f83d9cf21d1f5cc0e11deaf36df02790d0e60c1a3829538fb4b61685368" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:50abe06dfc0957e14cb8332ed242e8b884ef2563a9eb202e5172f243f62792f7" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:5fd2536c39c0700be8b7b4344e375196da2f126842fd8ede66996a18860a3890" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:621c35e751a51a9a9dc3e80aa0b7fe8be2a93402ea6ccd307d30852cd7776cda" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:665c05b48768a41e80bacab55868d4e8a1b6e692c9480b5222cbcb1cadce7bd3" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:6f1cdceb6a3146f0ccb986521156bef8a422cdbb0863396f7f751f575ba308f4" + }, + { + "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:d04afa9c71aa18597e0ab5562527b34c196152cb643a5e7fccfd0735dbed2874" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:e4747cb572b3f9246f210229c413f6e8ff15f497076c7374c34ae73321167c32" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:e5b2b2af420afca6a71de60fce58e3f90fe47af3f882773d0e48c9518ee5860c" + }, + { + "name": "aquasecurity:trivy:ImageID", + "value": "sha256:1891d1e9ef09172ca5529e9d460953246c039e8bce2cfd2f7a6ba0fb380dbb71" + }, + { + "name": "aquasecurity:trivy:Labels:com.docker.compose.project", + "value": "devops-intro" + }, + { + "name": "aquasecurity:trivy:Labels:com.docker.compose.service", + "value": "quicknotes" + }, + { + "name": "aquasecurity:trivy:Labels:com.docker.compose.version", + "value": "2.40.3" + }, + { + "name": "aquasecurity:trivy:RepoTag", + "value": "quicknotes:lab6" + }, + { + "name": "aquasecurity:trivy:SchemaVersion", + "value": "2" + } + ] + } + }, + "components": [ + { + "bom-ref": "1fa0a89e-5aaf-4746-920b-5bd5f0845b9c", + "type": "operating-system", + "name": "debian", + "version": "13.6", + "properties": [ + { + "name": "aquasecurity:trivy:Class", + "value": "os-pkgs" + }, + { + "name": "aquasecurity:trivy:Type", + "value": "debian" + } + ] + }, + { + "bom-ref": "6b0e0869-888e-4328-b65b-85f206fd358d", + "type": "application", + "name": "app/quicknotes", + "properties": [ + { + "name": "aquasecurity:trivy:Class", + "value": "lang-pkgs" + }, + { + "name": "aquasecurity:trivy:Type", + "value": "gobinary" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/base-files@13.8%2Bdeb13u6?arch=amd64&distro=debian-13.6", + "type": "library", + "supplier": { + "name": "Santiago Vila " + }, + "name": "base-files", + "version": "13.8+deb13u6", + "licenses": [ + { + "license": { + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "verbatim" + } + } + ], + "purl": "pkg:deb/debian/base-files@13.8%2Bdeb13u6?arch=amd64&distro=debian-13.6", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:50abe06dfc0957e14cb8332ed242e8b884ef2563a9eb202e5172f243f62792f7" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "base-files@13.8+deb13u6" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "base-files" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "13.8+deb13u6" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/media-types@13.0.0?arch=all&distro=debian-13.6", + "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.6", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:275a30dd8ce958b21daa9ad962c6fbc09f98306ee2f486b65c9075dc257b1412" + }, + { + "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.6", + "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.6", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:621c35e751a51a9a9dc3e80aa0b7fe8be2a93402ea6ccd307d30852cd7776cda" + }, + { + "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.6", + "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.6", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:bec7e6bb35e05d1284f28b10d2150c259717d91c658c4c10c08424bb9466caba" + }, + { + "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.6", + "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.6", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:c8b007d0206e4b10ed4d3b3d99dfeab47c2648e82011989fd78a5731baf33fc3" + }, + { + "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:665c05b48768a41e80bacab55868d4e8a1b6e692c9480b5222cbcb1cadce7bd3" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "quicknotes" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gobinary" + } + ] + }, + { + "bom-ref": "pkg:golang/stdlib@v1.24.6", + "type": "library", + "name": "stdlib", + "version": "v1.24.6", + "purl": "pkg:golang/stdlib@v1.24.6", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:665c05b48768a41e80bacab55868d4e8a1b6e692c9480b5222cbcb1cadce7bd3" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "stdlib@v1.24.6" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gobinary" + } + ] + } + ], + "dependencies": [ + { + "ref": "1fa0a89e-5aaf-4746-920b-5bd5f0845b9c", + "dependsOn": [ + "pkg:deb/debian/base-files@13.8%2Bdeb13u6?arch=amd64&distro=debian-13.6", + "pkg:deb/debian/media-types@13.0.0?arch=all&distro=debian-13.6", + "pkg:deb/debian/netbase@6.5?arch=all&distro=debian-13.6", + "pkg:deb/debian/tzdata-legacy@2026b-0%2Bdeb13u1?arch=all&distro=debian-13.6", + "pkg:deb/debian/tzdata@2026b-0%2Bdeb13u1?arch=all&distro=debian-13.6" + ] + }, + { + "ref": "6b0e0869-888e-4328-b65b-85f206fd358d", + "dependsOn": [ + "pkg:golang/quicknotes" + ] + }, + { + "ref": "d4a16e93-63f8-4631-9dd0-9c7683a7ae9e", + "dependsOn": [ + "1fa0a89e-5aaf-4746-920b-5bd5f0845b9c", + "6b0e0869-888e-4328-b65b-85f206fd358d" + ] + }, + { + "ref": "pkg:deb/debian/base-files@13.8%2Bdeb13u6?arch=amd64&distro=debian-13.6", + "dependsOn": [] + }, + { + "ref": "pkg:deb/debian/media-types@13.0.0?arch=all&distro=debian-13.6", + "dependsOn": [] + }, + { + "ref": "pkg:deb/debian/netbase@6.5?arch=all&distro=debian-13.6", + "dependsOn": [] + }, + { + "ref": "pkg:deb/debian/tzdata-legacy@2026b-0%2Bdeb13u1?arch=all&distro=debian-13.6", + "dependsOn": [] + }, + { + "ref": "pkg:deb/debian/tzdata@2026b-0%2Bdeb13u1?arch=all&distro=debian-13.6", + "dependsOn": [] + }, + { + "ref": "pkg:golang/quicknotes", + "dependsOn": [ + "pkg:golang/stdlib@v1.24.6" + ] + }, + { + "ref": "pkg:golang/stdlib@v1.24.6", + "dependsOn": [] + } + ], + "vulnerabilities": [] +} diff --git a/security/summary.txt b/security/summary.txt new file mode 100644 index 000000000..ca670fde1 --- /dev/null +++ b/security/summary.txt @@ -0,0 +1,34 @@ +=== trivy image === +quicknotes:lab6 (debian 13.6) +Total: 0 (HIGH: 0, CRITICAL: 0) +app/quicknotes (gobinary) +Total: 15 (HIGH: 14, CRITICAL: 1) + +=== trivy fs === +0 findings (no external dependencies, no go.sum) + +=== trivy config === +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 +──────────────────────────────────────── + + + +=== SBOM components === +operating-system debian 13.6 +application app/quicknotes - +library base-files 13.8+deb13u6 +library media-types 13.0.0 +library netbase 6.5 +library tzdata-legacy 2026b-0+deb13u1 +library tzdata 2026b-0+deb13u1 +library quicknotes - +library stdlib v1.24.6 diff --git a/security/trivy-config.txt b/security/trivy-config.txt new file mode 100644 index 000000000..f0ce2e702 --- /dev/null +++ b/security/trivy-config.txt @@ -0,0 +1,24 @@ +2026-08-06T16:15:53Z INFO [misconfig] Misconfiguration scanning is enabled +2026-08-06T16:15:53Z INFO [misconfig] Need to update the built-in checks +2026-08-06T16:15:53Z INFO [misconfig] Downloading the built-in checks... +31.27 KiB / 165.46 KiB [----------->________________________________________________] 18.90% ? p/s ?79.26 KiB / 165.46 KiB [---------------------------->_______________________________] 47.90% ? p/s ?165.46 KiB / 165.46 KiB [---------------------------------------------] 100.00% 449.86 KiB p/s 600ms2026-08-06T16:15:57Z ERROR [rego] Error occurred while parsing. Trying to fallback to embedded check file_path="root/.cache/trivy/policy/content/policies/cloud/policies/aws/ec2/specify_ami_owners.rego" err="root/.cache/trivy/policy/content/policies/cloud/policies/aws/ec2/specify_ami_owners.rego:30: rego_type_error: undefined ref: input.aws.ec2.requestedamis[__local622__]\n\tinput.aws.ec2.requestedamis[__local622__]\n\t ^\n\t have: \"requestedamis\"\n\t want (one of): [\"instances\" \"launchconfigurations\" \"launchtemplates\" \"networkacls\" \"securitygroups\" \"subnets\" \"volumes\" \"vpcs\"]" +2026-08-06T16:15:57Z ERROR [rego] Failed to find embedded check, skipping file_path="root/.cache/trivy/policy/content/policies/cloud/policies/aws/ec2/specify_ami_owners.rego" +2026-08-06T16:15:57Z ERROR [rego] Error occurred while parsing file_path="root/.cache/trivy/policy/content/policies/cloud/policies/aws/ec2/specify_ami_owners.rego" err="root/.cache/trivy/policy/content/policies/cloud/policies/aws/ec2/specify_ami_owners.rego:30: rego_type_error: undefined ref: input.aws.ec2.requestedamis[__local622__]\n\tinput.aws.ec2.requestedamis[__local622__]\n\t ^\n\t have: \"requestedamis\"\n\t want (one of): [\"instances\" \"launchconfigurations\" \"launchtemplates\" \"networkacls\" \"securitygroups\" \"subnets\" \"volumes\" \"vpcs\"]" +2026-08-06T16:15:58Z ERROR [rego] Error occurred while parsing. Trying to fallback to embedded check file_path="root/.cache/trivy/policy/content/policies/cloud/policies/aws/ec2/specify_ami_owners.rego" err="root/.cache/trivy/policy/content/policies/cloud/policies/aws/ec2/specify_ami_owners.rego:30: rego_type_error: undefined ref: input.aws.ec2.requestedamis[__local622__]\n\tinput.aws.ec2.requestedamis[__local622__]\n\t ^\n\t have: \"requestedamis\"\n\t want (one of): [\"instances\" \"launchconfigurations\" \"launchtemplates\" \"networkacls\" \"securitygroups\" \"subnets\" \"volumes\" \"vpcs\"]" +2026-08-06T16:15:58Z ERROR [rego] Failed to find embedded check, skipping file_path="root/.cache/trivy/policy/content/policies/cloud/policies/aws/ec2/specify_ami_owners.rego" +2026-08-06T16:15:58Z ERROR [rego] Error occurred while parsing file_path="root/.cache/trivy/policy/content/policies/cloud/policies/aws/ec2/specify_ami_owners.rego" err="root/.cache/trivy/policy/content/policies/cloud/policies/aws/ec2/specify_ami_owners.rego:30: rego_type_error: undefined ref: input.aws.ec2.requestedamis[__local622__]\n\tinput.aws.ec2.requestedamis[__local622__]\n\t ^\n\t have: \"requestedamis\"\n\t want (one of): [\"instances\" \"launchconfigurations\" \"launchtemplates\" \"networkacls\" \"securitygroups\" \"subnets\" \"volumes\" \"vpcs\"]" +2026-08-06T16:15:58Z INFO Detected config files num=1 + +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/security/trivy-fs.txt b/security/trivy-fs.txt new file mode 100644 index 000000000..446afcb27 --- /dev/null +++ b/security/trivy-fs.txt @@ -0,0 +1,10 @@ +2026-08-06T16:13:15Z INFO [vulndb] Need to update DB +2026-08-06T16:13:15Z INFO [vulndb] Downloading vulnerability DB... +2026-08-06T16:13:15Z INFO [vulndb] Downloading artifact... repo="mirror.gcr.io/aquasec/trivy-db:2" +2026-08-06T16:13:54Z INFO [vulndb] Artifact successfully downloaded repo="mirror.gcr.io/aquasec/trivy-db:2" +2026-08-06T16:13:54Z INFO [vuln] Vulnerability scanning is enabled +2026-08-06T16:13:54Z INFO [secret] Secret scanning is enabled +2026-08-06T16:13:54Z INFO [secret] If your scanning is slow, please try '--scanners vuln' to disable secret scanning +2026-08-06T16:13:54Z INFO [secret] Please see also https://aquasecurity.github.io/trivy/v0.59/docs/scanner/secret#recommendation for faster secret detection +2026-08-06T16:13:54Z INFO Number of language-specific files num=1 +2026-08-06T16:13:54Z INFO [gomod] Detecting vulnerabilities... diff --git a/security/trivy-image.txt b/security/trivy-image.txt new file mode 100644 index 000000000..2d78aa7dc --- /dev/null +++ b/security/trivy-image.txt @@ -0,0 +1,107 @@ +Unable to find image 'aquasec/trivy:0.59.1' locally +0.59.1: Pulling from aquasec/trivy +38a8310d387e: Pulling fs layer +1d671f98de6b: Pulling fs layer +2c38dcf52ab2: Pulling fs layer +2167091a7879: Pulling fs layer +2167091a7879: Waiting +1d671f98de6b: Verifying Checksum +1d671f98de6b: Download complete +38a8310d387e: Verifying Checksum +38a8310d387e: Download complete +38a8310d387e: Pull complete +1d671f98de6b: Pull complete +2167091a7879: Verifying Checksum +2167091a7879: Download complete +2c38dcf52ab2: Verifying Checksum +2c38dcf52ab2: Download complete +2c38dcf52ab2: Pull complete +2167091a7879: Pull complete +Digest: sha256:029e990b328d149bf0a9ffe355919041e1f86192db2df47e217f8a36dd42ceac +Status: Downloaded newer image for aquasec/trivy:0.59.1 +2026-08-06T16:11:33Z INFO [vulndb] Need to update DB +2026-08-06T16:11:33Z INFO [vulndb] Downloading vulnerability DB... +2026-08-06T16:11:33Z INFO [vulndb] Downloading artifact... repo="mirror.gcr.io/aquasec/trivy-db:2" +2026-08-06T16:11:47Z INFO [vulndb] Artifact successfully downloaded repo="mirror.gcr.io/aquasec/trivy-db:2" +2026-08-06T16:11:47Z INFO [vuln] Vulnerability scanning is enabled +2026-08-06T16:11:47Z INFO [secret] Secret scanning is enabled +2026-08-06T16:11:47Z INFO [secret] If your scanning is slow, please try '--scanners vuln' to disable secret scanning +2026-08-06T16:11:47Z INFO [secret] Please see also https://aquasecurity.github.io/trivy/v0.59/docs/scanner/secret#recommendation for faster secret detection +2026-08-06T16:11:47Z INFO Detected OS family="debian" version="13.6" +2026-08-06T16:11:47Z INFO [debian] Detecting vulnerabilities... os_version="13" pkg_num=5 +2026-08-06T16:11:47Z INFO Number of language-specific files num=1 +2026-08-06T16:11:47Z INFO [gobinary] Detecting vulnerabilities... +2026-08-06T16:11:47Z WARN Using severities from other vendors for some vulnerabilities. Read https://aquasecurity.github.io/trivy/v0.59/docs/scanner/vulnerability#severity-selection for details. + +quicknotes:lab6 (debian 13.6) +============================= +Total: 0 (HIGH: 0, CRITICAL: 0) + + +app/quicknotes (gobinary) +========================= +Total: 15 (HIGH: 14, CRITICAL: 1) + +┌─────────┬────────────────┬──────────┬────────┬───────────────────┬──────────────────────────────┬──────────────────────────────────────────────────────────────┐ +│ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │ Title │ +├─────────┼────────────────┼──────────┼────────┼───────────────────┼──────────────────────────────┼──────────────────────────────────────────────────────────────┤ +│ stdlib │ CVE-2025-68121 │ CRITICAL │ fixed │ v1.24.6 │ 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-61726 │ HIGH │ │ │ 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-39822 │ │ │ │ 1.25.12, 1.26.5, 1.27.0-rc.2 │ golang: Go os.Root: Symlink following vulnerability allows │ +│ │ │ │ │ │ │ directory traversal │ +│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2026-39822 │ +│ ├────────────────┤ │ │ ├──────────────────────────────┼──────────────────────────────────────────────────────────────┤ +│ │ CVE-2026-39836 │ │ │ │ 1.25.10, 1.26.3 │ net: golang: Go net package: Denial of Service via NUL byte │ +│ │ │ │ │ │ │ in... │ +│ │ │ │ │ │ │ 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 │ +│ ├────────────────┤ │ │ ├──────────────────────────────┼──────────────────────────────────────────────────────────────┤ +│ │ CVE-2026-42504 │ │ │ │ 1.25.11, 1.26.4 │ mime: golang: Golang MIME: Denial of Service via │ +│ │ │ │ │ │ │ maliciously-crafted MIME header │ +│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2026-42504 │ +└─────────┴────────────────┴──────────┴────────┴───────────────────┴──────────────────────────────┴──────────────────────────────────────────────────────────────┘ diff --git a/security/zap-before-after.txt b/security/zap-before-after.txt new file mode 100644 index 000000000..7119f8b8a --- /dev/null +++ b/security/zap-before-after.txt @@ -0,0 +1,22 @@ +=== ZAP baseline BEFORE fix === +WARN-NEW: 3, PASS: 64 + X-Content-Type-Options Header Missing [10021] x1 + Storable and Cacheable Content [10049] x4 + Cross-Origin-Resource-Policy Header Missing or Invalid [90004] x1 + +=== headers BEFORE === +HTTP/1.1 200 OK / Content-Type / Date / Content-Length (4 headers, none security-related) + +=== ZAP baseline AFTER fix === +WARN-NEW: 1, PASS: 66 + Non-Storable Content [10049] x4 (informational: no-store is working) + +=== headers AFTER === +HTTP/1.1 200 OK +Cache-Control: no-store +Content-Type: application/json +Cross-Origin-Resource-Policy: same-origin +X-Content-Type-Options: nosniff +Date: Thu, 06 Aug 2026 16:35:23 GMT +Content-Length: 635 + diff --git a/security/zap-report-after.html b/security/zap-report-after.html new file mode 100644 index 000000000..f0be10ad8 --- /dev/null +++ b/security/zap-report-after.html @@ -0,0 +1,793 @@ + + + + +ZAP Scanning Report + + + +

+ + + ZAP Scanning Report +

+

+ + +

+ + Site: http://127.0.0.1:8080 + +

+ +

+ Generated on Thu, 6 Aug 2026 16:34:13 +

+ +

+ ZAP Version: 2.17.0 +

+ +

+ ZAP by Checkmarx +

+ + +

Summary of Alerts

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

Insights

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LevelReasonSiteDescriptionStatistic
+
Info
+
+
Informational
+
+
http://127.0.0.1:8080
+
+
Percentage of responses with status code 2xx
+
+
33 %
+
+
Info
+
+
Informational
+
+
http://127.0.0.1:8080
+
+
Percentage of responses with status code 4xx
+
+
66 %
+
+
Info
+
+
Informational
+
+
http://127.0.0.1:8080
+
+
Percentage of endpoints with content type application/json
+
+
25 %
+
+
Info
+
+
Informational
+
+
http://127.0.0.1:8080
+
+
Percentage of endpoints with content type text/plain
+
+
75 %
+
+
Info
+
+
Informational
+
+
http://127.0.0.1:8080
+
+
Percentage of endpoints with method GET
+
+
100 %
+
+
Info
+
+
Informational
+
+
http://127.0.0.1:8080
+
+
Count of total endpoints
+
+
4
+
+
+ + + + + + +

Summary of Sequences

+

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

+ + + + + + + + +

Alerts

+ + + + + + + + + + + + + + + + + + + + + + +
NameRisk LevelNumber of Instances
Non-Storable ContentInformational1
Storable and Cacheable ContentInformational3
+
+ + + +

Alert Detail

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
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://127.0.0.1:8080/notes
Node Namehttp://127.0.0.1:8080/notes
MethodGET
Parameter
Attack
Evidenceno-store
Other Info
Instances1
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
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
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://127.0.0.1:8080/
Node Namehttp://127.0.0.1:8080/
MethodGET
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.
+ +
URLhttp://127.0.0.1:8080/robots.txt
Node Namehttp://127.0.0.1:8080/robots.txt
MethodGET
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.
+ +
URLhttp://127.0.0.1:8080/sitemap.xml
Node Namehttp://127.0.0.1:8080/sitemap.xml
MethodGET
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.
+ +
Instances3
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/security/zap-report-after.json b/security/zap-report-after.json new file mode 100644 index 000000000..9d3d4a871 --- /dev/null +++ b/security/zap-report-after.json @@ -0,0 +1,149 @@ +{ + "@programName": "ZAP", + "@version": "2.17.0", + "@generated": "Thu, 6 Aug 2026 16:34:13", + "created": "2026-08-06T16:34:13.134260240Z", + "insights":[ + { + "level": "Info", + "reason": "Informational", + "site": "http://127.0.0.1:8080", + "key": "insight.code.2xx", + "description": "Percentage of responses with status code 2xx", + "statistic": "33" + }, + { + "level": "Info", + "reason": "Informational", + "site": "http://127.0.0.1:8080", + "key": "insight.code.4xx", + "description": "Percentage of responses with status code 4xx", + "statistic": "66" + }, + { + "level": "Info", + "reason": "Informational", + "site": "http://127.0.0.1:8080", + "key": "insight.endpoint.ctype.application/json", + "description": "Percentage of endpoints with content type application/json", + "statistic": "25" + }, + { + "level": "Info", + "reason": "Informational", + "site": "http://127.0.0.1:8080", + "key": "insight.endpoint.ctype.text/plain", + "description": "Percentage of endpoints with content type text/plain", + "statistic": "75" + }, + { + "level": "Info", + "reason": "Informational", + "site": "http://127.0.0.1:8080", + "key": "insight.endpoint.method.GET", + "description": "Percentage of endpoints with method GET", + "statistic": "100" + }, + { + "level": "Info", + "reason": "Informational", + "site": "http://127.0.0.1:8080", + "key": "insight.endpoint.total", + "description": "Count of total endpoints", + "statistic": "4" + } + ], + "site":[ + { + "@name": "http://127.0.0.1:8080", + "@host": "127.0.0.1", + "@port": "8080", + "@ssl": "false", + "alerts": [ + { + "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": "4", + "uri": "http://127.0.0.1:8080/notes", + "nodeName": "http:\/\/127.0.0.1:8080\/notes", + "method": "GET", + "param": "", + "attack": "", + "evidence": "no-store", + "otherinfo": "" + } + ], + "count": "1", + "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" + }, + { + "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://127.0.0.1:8080/", + "nodeName": "http:\/\/127.0.0.1:8080\/", + "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://127.0.0.1:8080/robots.txt", + "nodeName": "http:\/\/127.0.0.1:8080\/robots.txt", + "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": "2", + "uri": "http://127.0.0.1:8080/sitemap.xml", + "nodeName": "http:\/\/127.0.0.1:8080\/sitemap.xml", + "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": "3", + "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": "10" + } + ] + } + ], + "sequences":[ + ] + +} diff --git a/security/zap-report.html b/security/zap-report.html new file mode 100644 index 000000000..3d7228029 --- /dev/null +++ b/security/zap-report.html @@ -0,0 +1,914 @@ + + + + +ZAP Scanning Report + + + +

+ + + ZAP Scanning Report +

+

+ + +

+ + Site: http://127.0.0.1:8080 + +

+ +

+ Generated on Thu, 6 Aug 2026 16:24:12 +

+ +

+ ZAP Version: 2.17.0 +

+ +

+ ZAP by Checkmarx +

+ + +

Summary of Alerts

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

Insights

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LevelReasonSiteDescriptionStatistic
+
Info
+
+
Informational
+
+
http://127.0.0.1:8080
+
+
Percentage of responses with status code 2xx
+
+
33 %
+
+
Info
+
+
Informational
+
+
http://127.0.0.1:8080
+
+
Percentage of responses with status code 4xx
+
+
66 %
+
+
Info
+
+
Informational
+
+
http://127.0.0.1:8080
+
+
Percentage of endpoints with content type application/json
+
+
25 %
+
+
Info
+
+
Informational
+
+
http://127.0.0.1:8080
+
+
Percentage of endpoints with content type text/plain
+
+
75 %
+
+
Info
+
+
Informational
+
+
http://127.0.0.1:8080
+
+
Percentage of endpoints with method GET
+
+
100 %
+
+
Info
+
+
Informational
+
+
http://127.0.0.1:8080
+
+
Count of total endpoints
+
+
4
+
+
+ + + + + + +

Summary of Sequences

+

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

+ + + + + + + + +

Alerts

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameRisk LevelNumber of Instances
Cross-Origin-Resource-Policy Header Missing or InvalidLow1
X-Content-Type-Options Header MissingLow1
Storable and Cacheable ContentInformational4
+
+ + + +

Alert Detail

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
Low
Cross-Origin-Resource-Policy Header Missing or Invalid
Description +
Cross-Origin-Resource-Policy header is an opt-in header designed to counter side-channels attacks like Spectre. Resource should be specifically set as shareable amongst different origins.
+ +
URLhttp://127.0.0.1:8080/health
Node Namehttp://127.0.0.1:8080/health
MethodGET
ParameterCross-Origin-Resource-Policy
Attack
Evidence
Other Info
Instances1
Solution +
Ensure that the application/web server sets the Cross-Origin-Resource-Policy header appropriately, and that it sets the Cross-Origin-Resource-Policy header to 'same-origin' for all web pages.
+
+ +
'same-site' is considered as less secured and should be avoided.
+
+ +
If resources must be shared, set the header to 'cross-origin'.
+
+ +
If possible, ensure that the end user uses a standards-compliant and modern web browser that supports the Cross-Origin-Resource-Policy header (https://caniuse.com/mdn-http_headers_cross-origin-resource-policy).
+ +
Reference + https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cross-Origin-Embedder-Policy + +
CWE Id693
WASC Id14
Plugin Id90004
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
Low
X-Content-Type-Options Header Missing
Description +
The Anti-MIME-Sniffing header X-Content-Type-Options was not set to 'nosniff'. This allows older versions of Internet Explorer and Chrome to perform MIME-sniffing on the response body, potentially causing the response body to be interpreted and displayed as a content type other than the declared content type. Current (early 2014) and legacy versions of Firefox will use the declared content type (if one is set), rather than performing MIME-sniffing.
+ +
URLhttp://127.0.0.1:8080/health
Node Namehttp://127.0.0.1:8080/health
MethodGET
Parameterx-content-type-options
Attack
Evidence
Other Info +
This issue still applies to error type pages (401, 403, 500, etc.) as those pages are often still affected by injection issues, in which case there is still concern for browsers sniffing pages away from their actual content type.
+
+ +
At "High" threshold this scan rule will not alert on client or server error responses.
+ +
Instances1
Solution +
Ensure that the application/web server sets the Content-Type header appropriately, and that it sets the X-Content-Type-Options header to 'nosniff' for all web pages.
+
+ +
If possible, ensure that the end user uses a standards-compliant and modern web browser that does not perform MIME-sniffing at all, or that can be directed by the web application/web server to not perform MIME-sniffing.
+ +
Reference + https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/compatibility/gg622941(v=vs.85) +
+ + https://owasp.org/www-community/Security_Headers + +
CWE Id693
WASC Id15
Plugin Id10021
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
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://127.0.0.1:8080/
Node Namehttp://127.0.0.1:8080/
MethodGET
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.
+ +
URLhttp://127.0.0.1:8080/health
Node Namehttp://127.0.0.1:8080/health
MethodGET
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.
+ +
URLhttp://127.0.0.1:8080/robots.txt
Node Namehttp://127.0.0.1:8080/robots.txt
MethodGET
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.
+ +
URLhttp://127.0.0.1:8080/sitemap.xml
Node Namehttp://127.0.0.1:8080/sitemap.xml
MethodGET
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.
+ +
Instances4
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/security/zap-report.json b/security/zap-report.json new file mode 100644 index 000000000..fcc69b88f --- /dev/null +++ b/security/zap-report.json @@ -0,0 +1,189 @@ +{ + "@programName": "ZAP", + "@version": "2.17.0", + "@generated": "Thu, 6 Aug 2026 16:24:12", + "created": "2026-08-06T16:24:12.161361050Z", + "insights":[ + { + "level": "Info", + "reason": "Informational", + "site": "http://127.0.0.1:8080", + "key": "insight.code.2xx", + "description": "Percentage of responses with status code 2xx", + "statistic": "33" + }, + { + "level": "Info", + "reason": "Informational", + "site": "http://127.0.0.1:8080", + "key": "insight.code.4xx", + "description": "Percentage of responses with status code 4xx", + "statistic": "66" + }, + { + "level": "Info", + "reason": "Informational", + "site": "http://127.0.0.1:8080", + "key": "insight.endpoint.ctype.application/json", + "description": "Percentage of endpoints with content type application/json", + "statistic": "25" + }, + { + "level": "Info", + "reason": "Informational", + "site": "http://127.0.0.1:8080", + "key": "insight.endpoint.ctype.text/plain", + "description": "Percentage of endpoints with content type text/plain", + "statistic": "75" + }, + { + "level": "Info", + "reason": "Informational", + "site": "http://127.0.0.1:8080", + "key": "insight.endpoint.method.GET", + "description": "Percentage of endpoints with method GET", + "statistic": "100" + }, + { + "level": "Info", + "reason": "Informational", + "site": "http://127.0.0.1:8080", + "key": "insight.endpoint.total", + "description": "Count of total endpoints", + "statistic": "4" + } + ], + "site":[ + { + "@name": "http://127.0.0.1:8080", + "@host": "127.0.0.1", + "@port": "8080", + "@ssl": "false", + "alerts": [ + { + "pluginid": "90004", + "alertRef": "90004-1", + "alert": "Cross-Origin-Resource-Policy Header Missing or Invalid", + "name": "Cross-Origin-Resource-Policy Header Missing or Invalid", + "riskcode": "1", + "confidence": "2", + "riskdesc": "Low (Medium)", + "desc": "

Cross-Origin-Resource-Policy header is an opt-in header designed to counter side-channels attacks like Spectre. Resource should be specifically set as shareable amongst different origins.

", + "instances":[ + { + "id": "9", + "uri": "http://127.0.0.1:8080/health", + "nodeName": "http:\/\/127.0.0.1:8080\/health", + "method": "GET", + "param": "Cross-Origin-Resource-Policy", + "attack": "", + "evidence": "", + "otherinfo": "" + } + ], + "count": "1", + "systemic": false, + "solution": "

Ensure that the application/web server sets the Cross-Origin-Resource-Policy header appropriately, and that it sets the Cross-Origin-Resource-Policy header to 'same-origin' for all web pages.

'same-site' is considered as less secured and should be avoided.

If resources must be shared, set the header to 'cross-origin'.

If possible, ensure that the end user uses a standards-compliant and modern web browser that supports the Cross-Origin-Resource-Policy header (https://caniuse.com/mdn-http_headers_cross-origin-resource-policy).

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

https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cross-Origin-Embedder-Policy

", + "cweid": "693", + "wascid": "14", + "sourceid": "1" + }, + { + "pluginid": "10021", + "alertRef": "10021", + "alert": "X-Content-Type-Options Header Missing", + "name": "X-Content-Type-Options Header Missing", + "riskcode": "1", + "confidence": "2", + "riskdesc": "Low (Medium)", + "desc": "

The Anti-MIME-Sniffing header X-Content-Type-Options was not set to 'nosniff'. This allows older versions of Internet Explorer and Chrome to perform MIME-sniffing on the response body, potentially causing the response body to be interpreted and displayed as a content type other than the declared content type. Current (early 2014) and legacy versions of Firefox will use the declared content type (if one is set), rather than performing MIME-sniffing.

", + "instances":[ + { + "id": "2", + "uri": "http://127.0.0.1:8080/health", + "nodeName": "http:\/\/127.0.0.1:8080\/health", + "method": "GET", + "param": "x-content-type-options", + "attack": "", + "evidence": "", + "otherinfo": "This issue still applies to error type pages (401, 403, 500, etc.) as those pages are often still affected by injection issues, in which case there is still concern for browsers sniffing pages away from their actual content type.\nAt \"High\" threshold this scan rule will not alert on client or server error responses." + } + ], + "count": "1", + "systemic": false, + "solution": "

Ensure that the application/web server sets the Content-Type header appropriately, and that it sets the X-Content-Type-Options header to 'nosniff' for all web pages.

If possible, ensure that the end user uses a standards-compliant and modern web browser that does not perform MIME-sniffing at all, or that can be directed by the web application/web server to not perform MIME-sniffing.

", + "otherinfo": "

This issue still applies to error type pages (401, 403, 500, etc.) as those pages are often still affected by injection issues, in which case there is still concern for browsers sniffing pages away from their actual content type.

At \"High\" threshold this scan rule will not alert on client or server error responses.

", + "reference": "

https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/compatibility/gg622941(v=vs.85)

https://owasp.org/www-community/Security_Headers

", + "cweid": "693", + "wascid": "15", + "sourceid": "9" + }, + { + "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": "4", + "uri": "http://127.0.0.1:8080/", + "nodeName": "http:\/\/127.0.0.1:8080\/", + "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": "6", + "uri": "http://127.0.0.1:8080/health", + "nodeName": "http:\/\/127.0.0.1:8080\/health", + "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://127.0.0.1:8080/robots.txt", + "nodeName": "http:\/\/127.0.0.1:8080\/robots.txt", + "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": "0", + "uri": "http://127.0.0.1:8080/sitemap.xml", + "nodeName": "http:\/\/127.0.0.1:8080\/sitemap.xml", + "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": "4", + "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": "10" + } + ] + } + ], + "sequences":[ + ] + +} From d884f7a4f8ce95c7d533d2f902603ad2c1efd0a8 Mon Sep 17 00:00:00 2001 From: HNS <239804565+HNS2112@users.noreply.github.com> Date: Thu, 6 Aug 2026 19:39:14 +0300 Subject: [PATCH 4/4] docs(lab9): Trivy and ZAP scanning report Signed-off-by: HNS <239804565+HNS2112@users.noreply.github.com> --- .gitignore | 3 + submissions/lab9.md | 321 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 324 insertions(+) create mode 100644 submissions/lab9.md diff --git a/.gitignore b/.gitignore index 966b15200..b570e6ef2 100644 --- a/.gitignore +++ b/.gitignore @@ -63,3 +63,6 @@ Thumbs.db # wasm/main.go, spin.toml, go.sum (Lab 12) data/ app/data/ +zap-report.* +zap-report-after.* +zap.yaml diff --git a/submissions/lab9.md b/submissions/lab9.md new file mode 100644 index 000000000..05e1d6f57 --- /dev/null +++ b/submissions/lab9.md @@ -0,0 +1,321 @@ +# Lab 9 — Security Scanning: Trivy and ZAP + +**Author:** HNS ([@HNS2112](https://github.com/HNS2112)) +**Date:** 6 August 2026 +**Environment:** Ubuntu 24.04, Docker 29.1.3, Compose 2.40.3, Trivy 0.59.1, ZAP stable + +Raw scanner output is committed under `security/`. + +--- + +## Task 1 — Trivy + +### 1.1 Four scans + +The image under test is the one built in Lab 6: multi-stage, distroless-static, +nonroot. Built here on **amd64**, where it comes to 9.22 MB — against 16.2 MB for +the arm64 build on the same Dockerfile. The gap is architecture plus the busybox +`wget` that the macOS build carried for its healthcheck. Worth stating plainly: +an image size claim without an architecture attached does not mean much. + +**`trivy image`** + +```console +$ trivy image --severity HIGH,CRITICAL quicknotes:lab6 + +quicknotes:lab6 (debian 13.6) +Total: 0 (HIGH: 0, CRITICAL: 0) + +app/quicknotes (gobinary) +Total: 15 (HIGH: 14, CRITICAL: 1) +``` + +**`trivy fs`** — zero findings. `app/go.mod` has no `require` block and there is +no `go.sum`, so there are no third-party dependencies to check. The scanner +detected one language-specific file and found nothing in it. + +**`trivy config`** + +```console +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 +``` + +**SBOM (CycloneDX)** — 9 components: + +| Type | Name | Version | +|---|---|---| +| operating-system | debian | 13.6 | +| application | app/quicknotes | — | +| library | base-files | 13.8+deb13u6 | +| library | media-types | 13.0.0 | +| library | netbase | 6.5 | +| library | tzdata-legacy | 2026b-0+deb13u1 | +| library | tzdata | 2026b-0+deb13u1 | +| library | quicknotes | — | +| library | stdlib | v1.24.6 | + +### 1.2 Triage + +| Finding | Severity | Verdict | Reasoning | +|---|---|---|---| +| CVE-2025-68121 — `crypto/tls` certificate validation on session resumption | CRITICAL | **Fix** | Fixed in Go 1.24.13; a toolchain bump is a one-line change with no code impact | +| 14 × DoS in `net/url`, `crypto/x509`, `net`, `net/mail`, `mime` | HIGH | **Fix where the toolchain allows, accept the rest** | Same single remediation — bump Go — but several require 1.25.x or 1.26.x, which the `go 1.24` directive in `go.mod` rules out | +| AVD-DS-0026 — no HEALTHCHECK in Dockerfile | LOW | **False positive** | See below | +| Debian base layer | — | Nothing to do | Zero findings | + +**On the HEALTHCHECK finding.** Trivy is right about the file and wrong about the +system. There is no `HEALTHCHECK` instruction in the Dockerfile — but there is a +healthcheck, declared in `compose.yaml` and running `wget` against `/health` +every 10 seconds. Lab 6 verified it reporting `healthy`. + +Trivy scans artifacts one at a time. It read the Dockerfile without the +compose file that deploys it, so it could not see the check that exists. This is +the structural limit of static configuration scanning: the rule is sound, the +verdict is not, and the difference is only visible to someone who knows how the +pieces are deployed together. + +The fix would be to duplicate the check as a `HEALTHCHECK` instruction — but the +distroless image has no shell, so `HEALTHCHECK CMD` would need the same busybox +`wget` copied in, growing the image for a check the orchestrator already performs. +Accepted as a false positive rather than silenced with an ignore file, so the +reasoning stays visible. + +**On the 15 Go stdlib findings.** The OS layer scanned clean, which is the whole +promise of a distroless base. Every finding is in the Go standard library +compiled into the binary. The base image cannot help with that: a minimal base +removes OS-layer CVEs and says nothing about what you compile into it. + +Two things bound how far this can be remediated. The `go 1.24` directive in +`go.mod` caps the toolchain, and several fixes only landed in 1.25.x or later — +`CVE-2026-39822` needs 1.25.12. And most of the DoS findings are in packages +QuickNotes never calls; `net/mail` and `mime` are linked in but unreachable from +any handler. The count is an input to triage, not a verdict. + +The identical 15 findings appeared on both the arm64 and amd64 builds, which +confirms the CVEs travel with the compiler rather than the platform. + +### 1.3 Design questions + +#### a) Why does `trivy image` find things `trivy fs` does not? + +They scan different things. `trivy fs` reads the source tree: manifests such as +`go.mod`/`go.sum`, lockfiles, and files on disk. `trivy image` reads the built +artifact: OS packages installed in the image layers and — crucially here — +binaries, which it inspects for the Go build information embedded by the +compiler. + +On this project the difference is stark. `fs` found nothing because the source +declares no dependencies. `image` found 15, all in `stdlib v1.24.6` — a +"dependency" that exists only after compilation and appears in no manifest. No +amount of source scanning would surface it. + +#### b) What is an SBOM for, and who consumes it? + +An SBOM is an inventory of what is actually inside a shipped artifact, in a +machine-readable format. The value is in answering a question that arrives +*after* the fact: when a CVE is published against some library, which of your +running services contain it? + +Without an SBOM that question requires rebuilding and rescanning everything. +With one it is a query over stored documents. The consumers are incident +response during a zero-day, procurement and compliance teams who need provenance, +and downstream users who inherit your artifact's risk. + +The SBOM here is instructive: of nine components, seven are distroless leftovers +(`base-files`, `tzdata`, `netbase`, `media-types`) and only two are mine — +`quicknotes` and `stdlib`. All 15 CVEs are in the latter. + +#### c) Why scan configuration as well as content? + +Content scanning asks "is anything in here known-vulnerable". Configuration +scanning asks "is this put together in a way that creates risk", and the two miss +completely different classes of problem. + +A container running as root with a fully patched base has zero CVEs and a large +blast radius. A Dockerfile pinned to `:latest` is not vulnerable today and is +unreproducible tomorrow. Neither shows up in a vulnerability database, because +neither is a vulnerability — they are decisions. Trivy ran 28 such checks against +this Dockerfile and 27 passed, which is a statement about the Lab 6 choices +(nonroot, pinned base, no package manager) rather than about any CVE. + +#### d) Why does a scan finding not automatically mean "fix it"? + +Because severity is a property of the vulnerability, not of your exposure to it. +CVSS scores a worst case across all deployments; whether it applies depends on +whether the vulnerable code path is reachable, whether an attacker can reach the +input, and what the blast radius is if they do. + +This lab has both cases side by side. The `crypto/tls` CRITICAL is worth fixing: +the path is real and the remedy is a toolchain bump. The `net/mail` DoS findings +are formally HIGH but unreachable — QuickNotes has no email handling — and would +be fixed only as a by-product of the same bump. The HEALTHCHECK finding should +not be actioned at all, because the premise is wrong. + +Treating every finding as mandatory has a cost beyond wasted effort: it produces +alert fatigue, and a team that has learned to click through its scanner output +will click through the one that mattered. + +--- + +## Task 2 — ZAP + +### 2.1 Baseline scan + +```console +$ docker run --rm --network host -v "$PWD:/zap/wrk:rw" ghcr.io/zaproxy/zaproxy:stable \ + zap-baseline.py -t http://127.0.0.1:8080/notes -r zap-report.html -J zap-report.json + +WARN-NEW: X-Content-Type-Options Header Missing [10021] x 1 + http://127.0.0.1:8080/notes (200 OK) +WARN-NEW: Storable and Cacheable Content [10049] x 4 + http://127.0.0.1:8080/notes (200 OK) + http://127.0.0.1:8080/ (404 Not Found) + http://127.0.0.1:8080/robots.txt (404 Not Found) + http://127.0.0.1:8080/sitemap.xml (404 Not Found) +WARN-NEW: Cross-Origin-Resource-Policy Header Missing or Invalid [90004] x 1 + http://127.0.0.1:8080/notes (200 OK) + +FAIL-NEW: 0 WARN-NEW: 3 PASS: 64 +``` + +**A note on the target URL.** The first attempt pointed at `http://127.0.0.1:8080` +and produced almost nothing — WARN 1, PASS 66. The spider got a 404 on `/` and +had nothing to crawl, so it only ever saw error pages. QuickNotes has no route on +`/`; every endpoint is under `/health`, `/notes`, `/metrics`. Re-targeting at +`/notes` gave the scanner a real 200 response to inspect and the findings +appeared. + +That is worth recording as a finding about the tooling rather than the +application: a DAST scan against a URL that returns nothing useful produces a +clean report, and a clean report from a scan that never reached the application +is worse than no report at all. + +Confirming what the application actually sent: + +```console +$ curl -sI http://localhost:8080/notes +HTTP/1.1 200 OK +Content-Type: application/json +Date: Thu, 06 Aug 2026 16:24:23 GMT +Content-Length: 635 +``` + +Four headers, none of them security-related. + +### 2.2 Triage + +| Finding | Verdict | Reasoning | +|---|---|---| +| X-Content-Type-Options Missing [10021] | **Fix** | One header, no downside; prevents MIME sniffing turning a JSON response into something executable | +| Cross-Origin-Resource-Policy Missing [90004] | **Fix** | One header; this API is not meant to be embedded cross-origin | +| Storable and Cacheable Content [10049] | **Fix, with reservations** | `/notes` is unauthenticated today, but caching note contents in a shared proxy is undesirable; see §2.4 on why this one is a judgement call | + +### 2.3 The fix + +All routes already pass through one wrapper, `Server.wrap`, which existed for +metrics. Three lines there cover every endpoint, present and future: + +```go +func (s *Server) wrap(h http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + sw := &statusWriter{ResponseWriter: w, code: 200} + w.Header().Set("X-Content-Type-Options", "nosniff") + w.Header().Set("Cross-Origin-Resource-Policy", "same-origin") + w.Header().Set("Cache-Control", "no-store") + h(sw, r) + s.requestsTotal.Add(1) + if c, ok := s.requestsByCode[sw.code]; ok { + c.Add(1) + } + } +} +``` + +Placement matters: the headers are set *before* `h(sw, r)` runs. Once a handler +calls `Write` or `WriteHeader` the header map is flushed and further changes are +silently ignored — a Go gotcha that would have produced a fix that passes review +and does nothing. + +Regression test, covering two routes rather than one so that a future route added +outside `wrap` is caught: + +```go +func TestSecurityHeaders_PresentOnAllRoutes(t *testing.T) { + srv := newTestServer(t) + for _, target := range []string{"/health", "/notes"} { + rec := do(t, srv, http.MethodGet, target, nil) + for header, want := range map[string]string{ + "X-Content-Type-Options": "nosniff", + "Cross-Origin-Resource-Policy": "same-origin", + "Cache-Control": "no-store", + } { + if got := rec.Header().Get(header); got != want { + t.Errorf("%s on %s: got %q, want %q", header, target, got, want) + } + } + } +} +``` + +```console +$ go test -race -count=1 ./... +ok quicknotes 1.015s + +$ go vet ./... && gofmt -l . +(clean) +``` + +### 2.4 Re-scan: before and after + +```console +$ curl -sI http://localhost:8080/notes +HTTP/1.1 200 OK +Cache-Control: no-store +Content-Type: application/json +Cross-Origin-Resource-Policy: same-origin +X-Content-Type-Options: nosniff +Date: Thu, 06 Aug 2026 16:33:29 GMT +Content-Length: 635 +``` + +| Rule | Before | After | +|---|---|---| +| X-Content-Type-Options Missing [10021] | WARN | gone | +| Cross-Origin-Resource-Policy Missing [90004] | WARN | gone | +| Insufficient Site Isolation Against Spectre [90004] | WARN | **PASS** | +| Storable and Cacheable Content [10049] | WARN ×4 | replaced by *Non-Storable Content* ×4 | +| **Totals** | **WARN 3, PASS 64** | **WARN 1, PASS 66** | + +**The remaining warning is the interesting one.** Rule 10049 did not disappear — +it inverted. "Storable and Cacheable Content" became "Non-Storable Content": the +same rule now reporting the opposite fact. ZAP is not complaining; it is stating +that `no-store` is in effect, and leaving the judgement to a human. + +This is the clearest illustration in the lab of what a scanner can and cannot do. +It can determine that responses are not cacheable. It cannot decide whether that +is correct for this API — right for unauthenticated note contents that should not +sit in a shared proxy, wrong for public static assets where it throws away +performance for nothing. Chasing the warning count to zero would mean removing a +header that was added deliberately. + +Accepted and documented rather than silenced. + +--- + +## Bonus Task — govulncheck in CI + +Not attempted. + +--- + +## Summary + +| Task | Status | +|------|--------| +| Task 1 — Trivy image / fs / config / SBOM, triage, design questions | Complete | +| Task 2 — ZAP baseline, triage, code fix, regression test, re-scan | Complete | +| Bonus — govulncheck in CI | Not attempted |