diff --git a/Dockerfile b/Dockerfile index 3fc7be3..b78425f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -38,4 +38,14 @@ RUN --mount=type=secret,id=sentry_auth_token \ FROM caddy:2-alpine COPY Caddyfile /etc/caddy/Caddyfile COPY --from=builder /app/web/dist /srv +# Source maps must never ship (ADR 0014). When a Sentry token is present, Vite +# emits hidden maps and the plugin uploads then deletes them (see the builder +# note above) — but nothing verifies that delete succeeded. If it silently +# fails (or a toolchain change alters the behavior), maps would be served +# publicly. Unconditional: runs on every build of this image regardless of +# whether maps were emitted. `&&`/`||` routing makes a find error fail the +# build too, rather than pass it vacuously. +RUN maps="$(find -L /srv -name '*.map')" && [ -z "$maps" ] || \ + { echo 'ERROR: source maps leaked into served assets (Sentry upload/delete likely failed):'; \ + printf '%s\n' "$maps"; exit 1; } EXPOSE 8080 diff --git a/docs/adr/0014-sentry-source-maps-and-release-commits.md b/docs/adr/0014-sentry-source-maps-and-release-commits.md index 684f65b..153cab4 100644 --- a/docs/adr/0014-sentry-source-maps-and-release-commits.md +++ b/docs/adr/0014-sentry-source-maps-and-release-commits.md @@ -22,7 +22,7 @@ The release name matches `VITE_SENTRY_RELEASE` (the git tag) already set in `ins ## Consequences - Production traces de-minify to real `*.tsx:line`; releases appear in Sentry with their commits, enabling suspect-commit blaming and regression detection. -- The token never touches an image layer and no `.map` is served — verified by building the image with no secret and confirming `find /srv -name '*.map'` is empty. +- The token never touches an image layer and no `.map` is served. The no-`.map` invariant is enforced by an unconditional guard in the Dockerfile's serve stage that fails any image build (PR, push-to-main, or release) if a `.map` file survives into the served assets — automating what was originally a one-off manual check, and covering the release path where the plugin's delete step actually runs. - `@sentry/cli` (a plugin dependency) ships a native binary; its build script is allow-listed in `pnpm-workspace.yaml` (`onlyBuiltDependencies`) so the Alpine image build has it. The single static `linux-x64` binary runs on musl. - **Dependency-footprint + licensing (per [ADR 0001](0001-standalone-public-repo.md)).** This adds `@sentry/vite-plugin` (MIT) and ~15 transitive packages, including **`@sentry/cli`, licensed FSL-1.1-MIT — source-available, *not* OSI-approved** (it converts to MIT two years after each release). The footprint grows but FreightDesk's *distributed* artifact stays MIT-clean: `@sentry/vite-plugin` is a **devDependency**, and the published image is `caddy:2-alpine` + the static `dist` only — the builder stage holding `@sentry/cli` and its binary is discarded, so no FSL code is ever redistributed. The FSL terms (no competing product for 2 years) don't bite either: FreightDesk merely invokes the tool at build time and isn't a Sentry competitor. There is no MIT-licensed equivalent for Sentry source-map upload, so this is the cost of de-minified traces. Recorded explicitly because ADR 0001 makes the dependency footprint and licensing a reviewable public-facing artifact, not a silent default. - **Stays forker-generic / no admin-token dependency (per [ADR 0001](0001-standalone-public-repo.md)).** The build no-ops without `SENTRY_AUTH_TOKEN`: a forker clones and `pnpm build` / `docker build`s with zero Sentry config and gets a working app — no maps emitted, no upload attempted, no failure. The token is the canonical operator's convenience, a separable secret, never a build prerequisite.