mirror of
https://github.com/hanzoai/id.git
synced 2026-10-16 18:38:15 +00:00
This repo read EVENT_INGEST_KEY; hanzo.chat, hanzo.app, hanzo.ai, platform and the shared hanzoai/ci `build_secrets` all read PUBLISHABLE_KEY. Same key, same KMS path, two spellings — and this repo was the last holdout, which is why the duplicate entry had to exist at all. That duplication is a live rotation hazard: two KMS entries holding one value means rotating either one silently 403s whichever half still reads the other. Collapsing to a single name is what lets the duplicate be deleted. Renamed end to end: the build-arg, the ENV, the Vite-inlined VITE_PUBLISHABLE_KEY, the ambient type, hanzo.yml's build_secrets, and every message that names the KMS address in a failure. Also corrected a claim this file repeats and that turned out to be false everywhere it appeared: an unkeyed beacon does NOT take an "anonymous lane" that files rows under a $public tenant and answers 200. Measured against the live door, with and without a browser Origin, for pageviews and exceptions alike: a hard 401 ingest_key_required. Nothing arrives. That myth is why "we still get errors, just not events" was believed across four repos while keyless surfaces reported nothing at all. No behaviour change beyond the name: the fail-closed `case` gate and the inlining assertion are untouched, and this repo's gate was already sound — the case rejects an empty value before the grep, so it never had the `grep -F ""` hole that let hanzoai/chat pass keyless builds. Co-authored-by: Hanzo Dev <[email protected]>
98 lines
5.1 KiB
Docker
98 lines
5.1 KiB
Docker
# syntax=docker/dockerfile:1.7
|
|
# Hanzo ID — Vite SPA built once, served by hanzoai/spa.
|
|
FROM node:24-alpine AS build
|
|
WORKDIR /build
|
|
ENV PNPM_HOME=/pnpm PATH=$PNPM_HOME:$PATH
|
|
RUN corepack enable && corepack prepare [email protected] --activate
|
|
|
|
# THE LOCKFILE SHIPS, and the install is frozen to it.
|
|
#
|
|
# This used to omit pnpm-lock.yaml and run `--frozen-lockfile=false`, so the
|
|
# image resolved the whole tree FRESH on every build while `pnpm test` on the
|
|
# runner resolved it from the lockfile. Two different dependency graphs from one
|
|
# commit: the tested one, and the shipped one. It went green for as long as free
|
|
# resolution happened to agree, and stopped the moment it did not — adding one
|
|
# dependency (@hanzo/event) moved vite from the lockfile's
|
|
# 7.3.5_@[email protected]_… to 7.3.6_@[email protected] and the build died on
|
|
# `Cannot find module '/build/apps/web/node_modules/vite/bin/vite.js'`. Nothing
|
|
# was wrong with the source: the same commit builds cleanly when installed from
|
|
# the lockfile.
|
|
#
|
|
# A resolver free to drift ships a bundle no one has run. Frozen, the image gets
|
|
# the exact tree the tests passed against, and a lockfile that has gone stale
|
|
# fails HERE — loudly, naming the mismatch — instead of silently building
|
|
# something else.
|
|
#
|
|
# EVERY workspace member's package.json must be present before a frozen install:
|
|
# pnpm validates the lockfile against all of them and refuses if one is missing.
|
|
# apps/account is not built into this image, but it IS in the workspace, so its
|
|
# manifest is required for the check to pass.
|
|
COPY pnpm-workspace.yaml pnpm-lock.yaml package.json tsconfig.base.json ./
|
|
COPY apps/web/package.json apps/web/
|
|
COPY apps/account/package.json apps/account/
|
|
COPY pkgs/shared/package.json pkgs/shared/
|
|
COPY pkgs/auth/package.json pkgs/auth/
|
|
COPY pkgs/connect/package.json pkgs/connect/
|
|
COPY pkgs/idv/package.json pkgs/idv/
|
|
COPY pkgs/onboarding/package.json pkgs/onboarding/
|
|
RUN pnpm install --frozen-lockfile
|
|
|
|
COPY apps apps
|
|
COPY pkgs pkgs
|
|
|
|
# Publishable event-ingest key (pk-live-…), inlined by Vite into the bundle.
|
|
#
|
|
# PUBLISHABLE_KEY is the name in KMS (org `hanzo`, path `deploy`, env `prod`)
|
|
# and on the --build-arg; the VITE_ prefix is what makes Vite inline it, and it
|
|
# is a property of THIS build, so it is applied here and the secret store keeps
|
|
# the ONE plain name.
|
|
#
|
|
# Publishable and write-only by design — it authorizes a write into one org and
|
|
# can read nothing — so shipping it in a bundle is the documented use. It is
|
|
# still a credential: it comes from KMS via CI. Never commit a value here.
|
|
#
|
|
# Deliberately NO default. An absent key is not a degraded mode: the door refuses
|
|
# the beacon outright. Measured against the live endpoint with and without a
|
|
# browser Origin, for pageviews AND exceptions alike: a hard 401
|
|
# `ingest_key_required`. Nothing arrives.
|
|
#
|
|
# This comment used to say the unkeyed beacon took an "anonymous lane" that filed
|
|
# rows under a `$public` tenant and answered 200. That lane is not implemented in
|
|
# the deployed cloud, and the belief was expensive: it is why "we still get
|
|
# errors, just not events" was accepted across four repos while the true answer
|
|
# was that a keyless surface reports nothing at all. hanzo.id ran exactly that
|
|
# way, which is the failure this gate exists to make loud.
|
|
ARG PUBLISHABLE_KEY
|
|
ENV VITE_PUBLISHABLE_KEY=$PUBLISHABLE_KEY
|
|
# Fail closed, and gate HERE because this is the one path every builder passes
|
|
# through — a guard in a workflow protects that lane only.
|
|
RUN case "$PUBLISHABLE_KEY" in \
|
|
pk-*) : ;; \
|
|
'') echo "PUBLISHABLE_KEY is empty - pass --build-arg PUBLISHABLE_KEY=<pk-...> (KMS deploy/PUBLISHABLE_KEY, env prod)" >&2; exit 1 ;; \
|
|
*) echo "PUBLISHABLE_KEY is not a publishable key (expected a pk- prefix)" >&2; exit 1 ;; \
|
|
esac
|
|
|
|
# Do NOT re-declare ARG VITE_PUBLISHABLE_KEY below this line. A later ARG of the
|
|
# same name shadows the ENV set above with an empty default, so the key resolves,
|
|
# passes the gate, and is then blanked before Vite inlines it — every step green,
|
|
# the bundle unattributed. That is exactly how hanzo.chat 1.0.58 shipped.
|
|
#
|
|
# `&&`, not `;`: with `;` the RUN exits with the status of the LAST command and a
|
|
# failed build would be masked. Assert on the bytes that actually ship — a key
|
|
# present in the environment and absent from the bundle is indistinguishable from
|
|
# success everywhere except the warehouse, where the traffic simply stops being
|
|
# attributable.
|
|
RUN pnpm --filter @hanzo/id-web build && \
|
|
{ grep -rqF "$VITE_PUBLISHABLE_KEY" apps/web/dist || \
|
|
{ echo "ERROR: the ingest key is not in apps/web/dist - hanzo.id would ship unattributed" >&2; exit 1; }; }
|
|
|
|
# SPA server stage — hanzoai/spa is the correct base for a Vite SPA:
|
|
# history-API fallthrough for client-side routes AND a SPA-safe CSP.
|
|
# hanzoai/static defaults to `Content-Security-Policy: default-src 'none'`
|
|
# (built for static assets, not an app that loads its own bundle), which
|
|
# blocks the SPA's own scripts and leaves a blank page. hanzoai/spa serves
|
|
# index.html for all routes with a sane CSP. Defaults: PORT=3000, ROOT=/public.
|
|
FROM ghcr.io/hanzoai/spa:1.4.8
|
|
COPY --from=build /build/apps/web/dist /public
|
|
EXPOSE 3000
|