diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..099e750b6 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,41 @@ +name: Release to GHCR + +on: + push: + tags: + - 'v*' + +jobs: + build-and-push: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - name: Log in to the Container registry + uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 + with: + images: ghcr.io/${{ github.repository }}/quicknotes + tags: | + type=semver,pattern={{version}} + type=raw,value=latest + + - name: Build and push Docker image + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 + with: + context: ./app + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} \ No newline at end of file diff --git a/app/Dockerfile b/app/Dockerfile new file mode 100644 index 000000000..592659d75 --- /dev/null +++ b/app/Dockerfile @@ -0,0 +1,24 @@ +FROM golang:1.24.5-alpine AS builder + +WORKDIR /src + +COPY go.mod ./ +RUN go mod download + +COPY . . + +RUN CGO_ENABLED=0 go build \ + -trimpath \ + -ldflags="-s -w" \ + -o /quicknotes + +FROM gcr.io/distroless/static:nonroot + +COPY --from=builder /quicknotes /quicknotes +COPY --from=builder /src/seed.json /seed.json + +EXPOSE 8080 + +USER 65532:65532 + +ENTRYPOINT ["/quicknotes"] \ No newline at end of file diff --git a/app/handlers.go b/app/handlers.go index c534979c5..e2efbef72 100644 --- a/app/handlers.go +++ b/app/handlers.go @@ -26,7 +26,20 @@ func NewServer(store *Store) *Server { return &Server{store: store, requestsByCode: by} } -func (s *Server) Routes() *http.ServeMux { +func SecurityHeaders(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("X-Content-Type-Options", "nosniff") + w.Header().Set("Cross-Origin-Resource-Policy", "same-origin") + w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0") + w.Header().Set("Pragma", "no-cache") + w.Header().Set("X-Frame-Options", "DENY") + w.Header().Set("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'") + + next.ServeHTTP(w, r) + }) +} + +func (s *Server) Routes() http.Handler { mux := http.NewServeMux() mux.HandleFunc("GET /health", s.wrap(s.handleHealth)) mux.HandleFunc("GET /metrics", s.wrap(s.handleMetrics)) @@ -34,7 +47,7 @@ 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 mux + return SecurityHeaders(mux) } type statusWriter struct { diff --git a/app/handlers_test.go b/app/handlers_test.go index 9dff2e3e5..82b4faafb 100644 --- a/app/handlers_test.go +++ b/app/handlers_test.go @@ -131,3 +131,12 @@ func TestMetrics_ExposesPrometheusFormat(t *testing.T) { } } +func TestSecurityHeaders(t *testing.T) { + srv := newTestServer(t) + + rec := do(t, srv, http.MethodGet, "/health", nil) + + if rec.Header().Get("X-Content-Type-Options") != "nosniff" { + t.Fatal("missing X-Content-Type-Options header") + } +} diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 000000000..495cbf9ae --- /dev/null +++ b/compose.yaml @@ -0,0 +1,22 @@ +services: + quicknotes: + image: quicknotes:lab6 + build: + context: ./app + user: "0:0" + + ports: + - "8080:8080" + + environment: + ADDR: ":8080" + DATA_PATH: "/data/notes.json" + SEED_PATH: "/seed.json" + + volumes: + - quicknotes-data:/data + + restart: unless-stopped + +volumes: + quicknotes-data: \ No newline at end of file diff --git a/sbom.json b/sbom.json new file mode 100644 index 000000000..91b5be6fa --- /dev/null +++ b/sbom.json @@ -0,0 +1,481 @@ +{ + "$schema": "http://cyclonedx.org/schema/bom-1.7.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.7", + "serialNumber": "urn:uuid:9e850e41-d9e8-46dd-89b0-a07d2c22eebb", + "version": 1, + "metadata": { + "timestamp": "2026-07-03T20:46:51+00:00", + "tools": { + "components": [ + { + "type": "application", + "manufacturer": { + "name": "Aqua Security Software Ltd." + }, + "group": "aquasecurity", + "name": "trivy", + "version": "0.72.0" + } + ] + }, + "component": { + "bom-ref": "pkg:oci/quicknotes@sha256:7cb3a02c11cf2ff470db3d1a9424b5b504d22a78cf0a33e541054e06c0c383b3?arch=amd64&repository_url=index.docker.io%2Flibrary%2Fquicknotes", + "type": "container", + "name": "quicknotes:lab6", + "purl": "pkg:oci/quicknotes@sha256:7cb3a02c11cf2ff470db3d1a9424b5b504d22a78cf0a33e541054e06c0c383b3?arch=amd64&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:5892e2ebce362e40d2093eb8a7e460aa390423bf3a948d509edad0a1d413e508" + }, + { + "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:92cb9c37b7d3957ac56645a979418f65e6c5bdba00eb99622affae5fc124ac07" + }, + { + "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:f9b720b12c137c381ff66e91fc9d534365b7a7c785f2cc4882d5cbecea93acce" + }, + { + "name": "aquasecurity:trivy:ImageID", + "value": "sha256:7cb3a02c11cf2ff470db3d1a9424b5b504d22a78cf0a33e541054e06c0c383b3" + }, + { + "name": "aquasecurity:trivy:Reference", + "value": "quicknotes:lab6" + }, + { + "name": "aquasecurity:trivy:RepoDigest", + "value": "quicknotes@sha256:7cb3a02c11cf2ff470db3d1a9424b5b504d22a78cf0a33e541054e06c0c383b3" + }, + { + "name": "aquasecurity:trivy:RepoTag", + "value": "quicknotes:lab6" + }, + { + "name": "aquasecurity:trivy:SchemaVersion", + "value": "2" + }, + { + "name": "aquasecurity:trivy:Size", + "value": "9106944" + } + ] + } + }, + "components": [ + { + "bom-ref": "54cc6d80-592b-414e-b3de-cbfad43ed8e4", + "type": "operating-system", + "name": "debian", + "version": "13.5", + "properties": [ + { + "name": "aquasecurity:trivy:Class", + "value": "os-pkgs" + }, + { + "name": "aquasecurity:trivy:Type", + "value": "debian" + } + ] + }, + { + "bom-ref": "615ad830-9d25-4bec-a0e1-56eecf392d13", + "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=amd64&distro=debian-13.5", + "type": "library", + "supplier": { + "name": "Santiago Vila " + }, + "name": "base-files", + "version": "13.8+deb13u5", + "licenses": [ + { + "license": { + "id": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "verbatim" + } + } + ], + "purl": "pkg:deb/debian/base-files@13.8%2Bdeb13u5?arch=amd64&distro=debian-13.5", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:92cb9c37b7d3957ac56645a979418f65e6c5bdba00eb99622affae5fc124ac07" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:47de5dd0b812c573630914955e26abda537e09b5286a824c96e22e3854d4dd53" + }, + { + "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": { + "id": "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:f9b720b12c137c381ff66e91fc9d534365b7a7c785f2cc4882d5cbecea93acce" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:aa0574b2f7185ab28e65d536c216699d8eeb45d74924214e26efae7f53e01e4f" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "quicknotes" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gobinary" + } + ] + }, + { + "bom-ref": "pkg:golang/stdlib@v1.24.5", + "type": "library", + "name": "stdlib", + "version": "v1.24.5", + "purl": "pkg:golang/stdlib@v1.24.5", + "properties": [ + { + "name": "aquasecurity:trivy:LayerDiffID", + "value": "sha256:f9b720b12c137c381ff66e91fc9d534365b7a7c785f2cc4882d5cbecea93acce" + }, + { + "name": "aquasecurity:trivy:LayerDigest", + "value": "sha256:aa0574b2f7185ab28e65d536c216699d8eeb45d74924214e26efae7f53e01e4f" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "stdlib@v1.24.5" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gobinary" + } + ] + } + ], + "dependencies": [ + { + "ref": "54cc6d80-592b-414e-b3de-cbfad43ed8e4", + "dependsOn": [ + "pkg:deb/debian/base-files@13.8%2Bdeb13u5?arch=amd64&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": "615ad830-9d25-4bec-a0e1-56eecf392d13", + "dependsOn": [ + "pkg:golang/quicknotes" + ] + }, + { + "ref": "pkg:deb/debian/base-files@13.8%2Bdeb13u5?arch=amd64&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.5" + ] + }, + { + "ref": "pkg:golang/stdlib@v1.24.5", + "dependsOn": [] + }, + { + "ref": "pkg:oci/quicknotes@sha256:7cb3a02c11cf2ff470db3d1a9424b5b504d22a78cf0a33e541054e06c0c383b3?arch=amd64&repository_url=index.docker.io%2Flibrary%2Fquicknotes", + "dependsOn": [ + "54cc6d80-592b-414e-b3de-cbfad43ed8e4", + "615ad830-9d25-4bec-a0e1-56eecf392d13" + ] + } + ], + "vulnerabilities": [] +} diff --git a/submissions/artifacts lab 9/zap-baseline-after.html b/submissions/artifacts lab 9/zap-baseline-after.html new file mode 100644 index 000000000..ad21ca4db --- /dev/null +++ b/submissions/artifacts lab 9/zap-baseline-after.html @@ -0,0 +1,618 @@ + + + + +ZAP Scanning Report + + + +

+ + + ZAP Scanning Report +

+

+ + +

+ + Site: http://localhost:8080 + +

+ +

+ Generated on Tue, 7 Jul 2026 15:24:25 +

+ +

+ ZAP Version: 2.17.0 +

+ +

+ ZAP by Checkmarx +

+ + +

Summary of Alerts

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

Insights

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LevelReasonSiteDescriptionStatistic
+
Info
+
+
Informational
+
+
http://localhost:8080
+
+
Percentage of responses with status code 4xx
+
+
100 %
+
+
Info
+
+
Informational
+
+
http://localhost:8080
+
+
Percentage of endpoints with content type text/plain
+
+
100 %
+
+
Info
+
+
Informational
+
+
http://localhost:8080
+
+
Percentage of endpoints with method GET
+
+
100 %
+
+
Info
+
+
Informational
+
+
http://localhost:8080
+
+
Count of total endpoints
+
+
2
+
+
+ + + + + + +

Summary of Sequences

+

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

+ + + + + + + + +

Alerts

+ + + + + + + + + + + + + + +
NameRisk LevelNumber of Instances
Storable and Cacheable ContentInformational3
+
+ + + +

Alert Detail

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
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://localhost:8080
Node Namehttp://localhost: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://localhost:8080/robots.txt
Node Namehttp://localhost: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://localhost:8080/sitemap.xml
Node Namehttp://localhost: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/submissions/artifacts lab 9/zap-baseline-after.json b/submissions/artifacts lab 9/zap-baseline-after.json new file mode 100644 index 000000000..1326e9479 --- /dev/null +++ b/submissions/artifacts lab 9/zap-baseline-after.json @@ -0,0 +1,103 @@ +{ + "@programName": "ZAP", + "@version": "2.17.0", + "@generated": "Tue, 7 Jul 2026 15:24:25", + "created": "2026-07-07T15:24:25.946051577Z", + "insights":[ + { + "level": "Info", + "reason": "Informational", + "site": "http://localhost:8080", + "key": "insight.code.4xx", + "description": "Percentage of responses with status code 4xx", + "statistic": "100" + }, + { + "level": "Info", + "reason": "Informational", + "site": "http://localhost:8080", + "key": "insight.endpoint.ctype.text/plain", + "description": "Percentage of endpoints with content type text/plain", + "statistic": "100" + }, + { + "level": "Info", + "reason": "Informational", + "site": "http://localhost:8080", + "key": "insight.endpoint.method.GET", + "description": "Percentage of endpoints with method GET", + "statistic": "100" + }, + { + "level": "Info", + "reason": "Informational", + "site": "http://localhost:8080", + "key": "insight.endpoint.total", + "description": "Count of total endpoints", + "statistic": "2" + } + ], + "site":[ + { + "@name": "http://localhost:8080", + "@host": "localhost", + "@port": "8080", + "@ssl": "false", + "alerts": [ + { + "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": "0", + "uri": "http://localhost:8080", + "nodeName": "http:\/\/localhost: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://localhost:8080/robots.txt", + "nodeName": "http:\/\/localhost: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": "1", + "uri": "http://localhost:8080/sitemap.xml", + "nodeName": "http:\/\/localhost: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": "7" + } + ] + } + ], + "sequences":[ + ] + +} diff --git a/submissions/artifacts lab 9/zap-baseline-before.html b/submissions/artifacts lab 9/zap-baseline-before.html new file mode 100644 index 000000000..1ef3f9dc0 --- /dev/null +++ b/submissions/artifacts lab 9/zap-baseline-before.html @@ -0,0 +1,598 @@ + + + + +ZAP Scanning Report + + + +

+ + + ZAP Scanning Report +

+

+ + +

+ + Site: http://localhost:8080 + +

+ +

+ Generated on Tue, 7 Jul 2026 11:25:20 +

+ +

+ 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 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://localhost: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://localhost: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://localhost:8080/robots.txt
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://localhost: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.
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/submissions/artifacts lab 9/zap-baseline-before.json b/submissions/artifacts lab 9/zap-baseline-before.json new file mode 100644 index 000000000..ab2cb5c9a --- /dev/null +++ b/submissions/artifacts lab 9/zap-baseline-before.json @@ -0,0 +1,99 @@ +{ + "@programName": "ZAP", + "@version": "2.16.1", + "@generated": "Tue, 7 Jul 2026 11:25:20", + "created": "2026-07-07T11:25:20.305323059Z", + "site":[ + { + "@name": "http://localhost:8080", + "@host": "localhost", + "@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": "0", + "uri": "http://localhost: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": "7" + }, + { + "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://localhost: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": "4", + "uri": "http://localhost:8080/robots.txt", + "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://localhost: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": "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": "7" + } + ] + } + ], + "sequences":[ + ] + +} diff --git a/submissions/image.png b/submissions/image.png new file mode 100644 index 000000000..ee76ac755 Binary files /dev/null and b/submissions/image.png differ diff --git a/submissions/lab10.md b/submissions/lab10.md new file mode 100644 index 000000000..929f53160 --- /dev/null +++ b/submissions/lab10.md @@ -0,0 +1,129 @@ +# Lab10 submission + +### Release workflow: + +``` +name: Release to GHCR + +on: + push: + tags: + - 'v*' + +jobs: + build-and-push: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - name: Log in to the Container registry + uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 + with: + images: ghcr.io/${{ github.repository }}/quicknotes + tags: | + type=semver,pattern={{version}} + type=raw,value=latest + + - name: Build and push Docker image + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 + with: + context: ./app + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} +``` + +### Registry URL: +https://ghcr.io/Long1Tail/DevOps-Intro/quicknotes:v0.1.0 + +### Successfull clean pull: +``` +docker pull ghcr.io/long1tail/devops-intro/quicknotes:0.1.0 +0.1.0: Pulling from long1tail/devops-intro/quicknotes +2507cd600e9f: Pull complete +f5f1a349e172: Pull complete +Digest: sha256:e9067010d468f0142e4ed907f539adf4d060957b8889b6a1bb9f9d3cfcbf5fe7 +Status: Downloaded newer image for ghcr.io/long1tail/devops-intro/quicknotes:0.1.0 +ghcr.io/long1tail/devops-intro/quicknotes:0.1.0 +``` + +[green URL]('https://github.com/Long1Tail/DevOps-Intro/actions/runs/28883826581') + +- a. `GITHUB_TOKEN` works well inside github ecosystem, meanwile OIDC is required fot other party autentification wothout sending secrets. Clud belives GitHub Actions token. +- b. Tag `:v0.1.0` gives guaranties of reproducibility and allows no changes. We always know what code will be executed. Tag `:latest` exists for user's comfort +- c. Principle of Least Privilege. If token will leak, with `packages: write` attacker will be able only to ruin image registry, meanwhile `write:all` would allow to change code, allow PR and release malware from developer's name. + +[Space URL]('https://huggingface.co/spaces/Long1Tail/quicknotes') + +Space Dockerfile: +``` +FROM ghcr.io/long1tail/devops-intro/quicknotes:v0.1.0 +``` + +```curl -v https://huggingface.co/sp +aces/Long1Tail/quicknotes/health` +< HTTP/2 200 +< content-type: application/json +{"notes":4,"status":"ok"} +``` + +### "Warm" latency: +``` +$ for i in $(seq 1 5); do + curl -w '%{time_total}\n' -o /dev/null -s \ + https://long1tail-quicknotes.hf.space/health + done +0.312 +0.298 +0.301 +0.289 +0.295 +``` + +### Cold latency: +| Attempt | Start time | +------------------------ +| 1 | 18.4 s +| 2 | 16.1 s +| 3 | 17.8 s + +- d. Cloud run is optimised on fast start, it keeps keeps image in cache. HF just stops container in VM and next start requires this container to boot again. +- e. HF's default port is 7860. quicknotes' image ddesined to listen port 8080. Flag `app_port` redirects traffic +- f. By building on HF we violates Build Once, Deploy Anywhere principle. Also, it makes deploy faster. + +### Bonus + +``` +docker compose up -d # QuickNotes on localhost:8080 +cloudflared tunnel --url http://localhost:8080 # assigned https://carb-spine-tier-winter.trycloudflare.com +``` + +![Verification](image.png) + + +| Metric | HF Spaces (hosted) | Cloudflare Tunnel (local-via-edge) | +|--------|---------------------|------------------------------------| +| Warm p50 | 305 ms | 54 ms | +| Warm p95 | 400 ms | 89 ms | +| Cold start | ~19.5 s | N/A (continuously local) +| Public URL stability | stable | requires laptop running permanently | +| Cost | 5 hole from dohnut | 30 kg of last-year snow | + +(just kidding, both variants are free) + +- g. HF is true cloud, as all operations take place on some external server. cloud run just provides a tunnel and still requires resources of my machine. for a user there is no difference, he still gets a public URL. +- h. HF case: Mostly distance between server and client. Cloudflare: route from corner CF server to client and processing +- i. It's perfect for externall access to resources in closed, local networks. But not for public web apps, as it have limits in reliability and troughput. \ No newline at end of file