Repository navigation
fix: remove code defaults from inter-service tokens and session secrets - #1441
alfredolopez80 wants to merge 19 commits into
Conversation
webhook-proxy read WEBHOOK_PROXY_CONTROL_API_SERVICE_TOKEN with `|| 'dev-webhook-proxy-token'`. Its image sets no NODE_ENV, so a cluster with the variable unset authenticated to control-api with that public string, and the base Secret's `replace-with-*` placeholder was sent as the bearer token, failing every registry lookup at request time instead of at startup. loadConfig() now requires a non-empty token and rejects `replace-with-*` placeholders, naming deploy/scripts/apply-inter-service-tokens.sh. Local copies of requiredNonEmpty (codex-llm-proxy) and assertNotPlaceholder (rpc-proxy) follow the repository convention of no shared config package. Minikube is unaffected: kustomize applies the placeholder, then apply-inter-service-tokens.sh patches the Secret and restarts webhook-proxy; no readiness wait targets it before that patch. Tests: test/config.test.ts covers unset, empty, whitespace-only and placeholder values, with the configured-value case as the witness. Mutations (restore the default, drop the placeholder check, constant token) each turn it red. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… unset token map config.internalTokens parsed CONTROL_API_INTERNAL_TOKENS with a default of `dev-external-rest-api-token,dev-rpc-proxy-token`. Nothing reads the field, and the production `^dev-` guard covers only internalServiceTokens, so it was the last dev token default outside that guard. Remove the field and parseInternalTokens. Its only effect was a placeholder check on a list that apply-inter-service-tokens.sh fills with the same tokens it writes to the validated service-token map. The script keeps writing the key; the manifest comments now say control-api no longer reads it. Orphans checked: internalTokens, parseInternalTokens, CONTROL_API_INTERNAL_TOKENS. Removed: the field, its parser and the "read by control-api" comments. Remaining references are the script (writer) and the shell contract tests that assert it. config.prod.test.ts adds the case the built-in default map creates: with CONTROL_API_INTERNAL_SERVICE_TOKENS unset in production, import is rejected as a default dev internal service token. A new witness case shows the production fixture alone imports. Disabling the dev-token loop turns the new case red. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…secrets in every NODE_ENV rpc-proxy read its control-api service token and both cookie signing secrets, and external-rest-api its service token, through requiredOrDevDefault: outside NODE_ENV=production each fell back to a public `dev-*` string. The shipped images bake NODE_ENV=production, but a single env entry overrides that, and the cookie secrets have no second control: rpc-proxy signs and verifies those sessions itself, so a known default lets anyone forge a Desktop or sandbox UI session. These four reads now use the existing required() plus the placeholder check, with no default in any NODE_ENV. The placeholder message names the script that writes each value (apply-inter-service-tokens.sh for tokens, gen-jwt-keys.sh for cookie secrets). Non-secret requiredOrDevDefault reads (URLs, issuer, audience, namespace, CORS) are unchanged. Deployed images already required these values, so clusters do not change. Test harness: both testJwtKey.setup.ts files supply random values; the rpc-proxy E2E spawn passes both cookie secrets; the two control-api suites that import external-rest-api config in-process stub its token. Tests: rpc-proxy config.test.ts and external-rest-api config.secrets.test.ts cover unset and placeholder values on the non-production path, with a read-back witness. Mutations (restore each dev default, drop each cookie placeholder check, constant secret, E2E spawn without cookie secrets) each turn them red. Docs: READMEs list the required secrets and the local exports; docs/deploy/minikube.md says where minikube's cookie secrets come from. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
List the five secrets that now stop startup when unset, empty or a `replace-with-*` placeholder, with the script that writes each in a cluster. webhook-proxy is the cluster-visible change: apply the manifests, then run apply-inter-service-tokens.sh. rpc-proxy and external-rest-api images already required these values. control-api no longer reads CONTROL_API_INTERNAL_TOKENS. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nv.dev.example consumer rpc-proxy/.env.example is the list a developer exports before `npm run dev`. Add RPC_PROXY_DESKTOP_COOKIE_SECRET and RPC_PROXY_SANDBOX_UI_COOKIE_SECRET, which rpc-proxy now requires with no code default, as `replace-with-*` placeholders that startup rejects until replaced. test/envExample.test.ts asserts every required secret is listed; a parser that finds no keys turns its witness red. .env.dev.example: the IMAP keys were reported as mismatched with channel-reader's CLERUM_EMAIL_USERNAME/PASSWORD. They are correct: the file feeds `make gcp-dev-apply-secrets` in keyper-labs/evenfire-infra, which writes CLERUM_EMAIL_IMAP_USER/PASS to the email-username/email-password Secret keys channel-reader reads in a cluster. The header now names that consumer and explains why the names stay, so they are not renamed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…undary .gitignore ignores `.env` and `.env.*` but tracks five templates on purpose (`!.env.example`, `!.env.test`, `!.env.e2e.example`, `!.env.dev.example`, `!.env.quickstart.example`). The public-boundary scanner exempted only names ending in `.env.example` or `.env.test`, so any change to `.env.dev.example`, `.env.quickstart.example` or `desktop-app/.env.e2e.example` failed T0 and T2 as a "sensitive file name" although the file is public by design. The scanner now also exempts those exact template file names, mirroring .gitignore. Every other `.env.*` name is still rejected, and template contents are still scanned for credentials. test-minikube-t2-contract.sh covers it: the three templates pass; `.env.dev`, `.env.dev.example.bak` and `.env.staging.example` are still rejected; a credential inside `.env.dev.example` is still reported. Removing the exemption, or widening it to every `.env*` name, turns the new cases red. The full contract suite passes under bash 3.2 and bash 5. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The base webhook-proxy-secrets Secret declared
WEBHOOK_PROXY_CONTROL_API_SERVICE_TOKEN with a replace-with-* value in
stringData. stringData is write-only, so a client-side kubectl apply sent
the key again on every apply and reset the token that
apply-inter-service-tokens.sh had written with kubectl patch. Since this
PR webhook-proxy refuses that value at startup, so every overlay apply not
followed by the script left webhook-proxy crash-looping, and every run of
the script minted a new token and restarted all consumers.
The Secret now ships `stringData: {}`, like the canary Secrets in
deploy/base/control-plane/secrets-canary.yaml. The token key is outside the
apply envelope, so applies keep it. Until the script has run, webhook-proxy
exits with "WEBHOOK_PROXY_CONTROL_API_SERVICE_TOKEN must be set".
Texts corrected to match the code: the script's token and env-var lists
(webhook-proxy, codex-llm-proxy and grok-llm-proxy tokens), the canary
comments that required the script to run before the apply and cited a
workflow file that is not in this repository, the webhook-proxy config
comment and README, and the title of the placeholder test.
Test: scripts/tests/test-inter-service-tokens.sh checks the manifest (the
Secret ships `stringData: {}`, declares no token key, and no base Secret
holds a replace-with- value) and two script cases through new stub
branches: a real token in the Secret is kept, and a placeholder is replaced
by a generated token on both the webhook-proxy and control-api sides.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ndary The public-boundary scanner tested the whole lower-cased path against `.env` and `.env.*`, so the gate fired only at the repository root. A nested `rpc-proxy/.env.local` or `deploy/.env.dev` passed, although .gitignore ignores such names at any depth and the scanner's own comment claimed every other `.env.*` name was rejected. The accept case for `desktop-app/.env.e2e.example` could not fail for the same reason. The gate now calls `is_env_file_name` on the last path component. The template exemption, which already worked on the file name, is unchanged, so the tracked templates at any depth still pass. A deletion of a committed nested env file is reported by name, as a root deletion is. Contract suite: nested reject cases (`rpc-proxy/.env.local`, `rpc-proxy/.env.prod`, `deploy/.env.dev`, `a/b/.env`), a nested `rpc-proxy/.env.example` accept case, and a nested deletion case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ic boundary Both credential patterns of the public-boundary scanner start with `\b` before the key word, and `\b` does not match between `_` and a letter. So `GCP_DB_PASSWORD=...`, `..._COOKIE_SECRET=...` and `..._SERVICE_TOKEN=...` were never matched, although this PR lifted the name gate for three more env templates on the argument that their contents are scanned. Widening the two existing patterns would scan code rather than env files (measured: 41 findings in 11 of the last 80 merges of dev, mostly JavaScript assignments and object keys). The existing patterns stay byte-identical. Two new patterns cover env-style assignments of a SCREAMING_SNAKE name ending in API_KEY, PASS, PASSWD, PASSWORD, SECRET, TOKEN, TOKENS, PRIVATE_KEY, ENCRYPTION_KEY or SIGNING_KEY: a line that is an assignment (`export` and spaces around `=` allowed), or an inline `NAME=value` that does not follow `.`. The value needs 8 or more characters and no `<` or `>`; the unquoted form excludes brackets and backticks and must end at whitespace, a quote or the end of the line. Exemptions of the new patterns only: a `replace-with-*` value, a value with no letter or digit, a control-api public development token `dev-...-token`, and a line in a source fixture path (`is_source_fixture`: a `test`, `tests` or `__tests__` directory at any depth, or a JS/TS `.test`, `.spec` or `.integration.test` file) unless the file is an env file. Not covered: a key word in the middle of the name, the YAML form, an unquoted value containing a bracket, and values under 8 characters. Measured on the last 80 first-parent merges of dev: 2 merges with 3 findings, all documentation placeholders in mcp-host/README.md. On the tracked tree: 10 more existing lines would be flagged if a later change edits them. The PR's own diff passes. Contract suite: 15 reject cases (prefixed names, punctuation, quoted values with spaces, spaces around `=`, nested and test-directory env templates, a non-JS `.test.py` file, and the PASS, PASSWD, TOKENS, ENCRYPTION_KEY and SIGNING_KEY names) and 16 accept cases (placeholders, empty values, `_FILE`, `_NAME`, `_TTL_MS`, `_HASH`, `_KEY_ID`, `_TOKENS_FILE` and `_PASS_THROUGH` names, development tokens, variables, angle-bracket placeholders, Python code and a shell test fixture). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ookie secrets The secret readers of rpc-proxy and external-rest-api built on `required()`, which refuses only an unset or empty variable. So a whitespace-only secret, a ` replace-with-*` placeholder with a leading space, and a 1-byte cookie HMAC key all loaded. No length check existed for the two cookie secrets, although the minikube ConfigMap claimed one. The fix is in the secret readers, not in `required()`, which also serves `requiredOrDevDefault` and the PEM key resolvers: - rpc-proxy `requiredSecret` trims the value, treats an empty result as unset, checks the placeholder on the trimmed value, and returns the trimmed value, as webhook-proxy does. It takes a minimum length in bytes; RPC_PROXY_DESKTOP_COOKIE_SECRET and RPC_PROXY_SANDBOX_UI_COOKIE_SECRET require 32 bytes (the HS256 key size). The service token has no minimum. Messages never echo the value or its length. - external-rest-api trims its control-api service token the same way. A cookie secret with a trailing newline in a hand-made Secret is now trimmed, so its HMAC key changes once and existing session cookies stop verifying. gen-jwt-keys.sh and the minikube overlay write values without surrounding whitespace. The rpc-proxy security E2E spawned the service with 25- and 28-byte cookie secrets; they are now 38 and 41 bytes. The minikube ConfigMap comment and the two READMEs state the minimum and the trim. Tests: empty, whitespace-only, leading-space placeholder and trimmed-read cases for every secret; for the cookie secrets a 31-byte refusal that does not echo the value, a 32-byte accept, and a 16-character, 32-byte UTF-8 accept; a 9-byte service token accept. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…wire it in base host-context-controller skipped the service-token check on GET /api/v1/desktop/<hostRef> whenever CONTEXT_MAPPER_DESKTOP_API_TOKEN was empty, using "the token is empty" as a stand-in for dev mode, and base never gave HCC that variable. So every cluster built from deploy/base ran the endpoint without authentication, although gen-jwt-keys.sh writes the token to both inter-service-tokens/desktop-api-token and rpc-proxy-secrets. host-context-controller: - The check is skipped only in CLERUM_DEV_MODE with no token configured. Outside dev mode an empty token denies every request, and main.ts refuses to start (`[HCC] FATAL`, exit 1) when the token is unset, empty, whitespace-only or a replace-with-* placeholder. The check runs after the HMAC secret check, so the HMAC error is reported when both are missing. The value is trimmed, as rpc-proxy trims its copy. - A denial logs one structured line through the service logger (`desktop status request denied`, reason token_not_configured, missing_authorization or token_mismatch); it never carries the token, the header or a length. rpc-proxy: RPC_PROXY_DESKTOP_API_TOKEN goes through requiredSecret (trimmed, no minimum length) and the Authorization header is always sent. Manifests: base HCC reads CONTEXT_MAPPER_DESKTOP_API_TOKEN from Secret inter-service-tokens, key desktop-api-token. The minikube patch keeps its literal, equal to the rpc-proxy ConfigMap value, and adds `valueFrom: null` so the inherited source is removed. Tests: a 13-row table for the desktop endpoint (dev mode x token x header, plus a dev-mode placeholder row) with the log line, its exact fields and a liveness witness; the LOG_LEVEL=warn case; a spawn test of main.ts for the start-up refusal and its order; the token assertion and the config trim; rpc-proxy secret cases, the header on both HCC calls and the .env.example list; a manifest contract in test-inter-service-tokens.sh for the base wiring, the writer and the minikube literals. The security E2E and the dev JWT verifier contract set the new variable. orphans checked: rpc-proxy main.ts production warning, HCC main.ts warning — removed; config.desktopApiToken readers kept (desktopProxy.ts x2, server.ts, main.ts). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
releases-and-upgrades.md: - The note now lists seven secrets, adds the rpc-proxy and host-context-controller desktop API token rows, and says that whitespace-only values are refused and surrounding whitespace is trimmed. - webhook-proxy: an unset variable fell back to dev-webhook-proxy-token only outside Kubernetes; in a cluster the pod received the base Secret's replace-with-* value. Base now ships the Secret empty, so applies keep the token. The first apply of this release leaves the previous value, so apply-inter-service-tokens.sh runs once after it. - rpc-proxy: deployed pods did change. They now refuse placeholder and whitespace-only secrets, cookie secrets under 32 bytes and a missing desktop API token. A trimmed cookie secret changes its HMAC key once, so users sign in again. - Desktop API token: a read-only pre-upgrade check, and three repairs that do not rotate other keys (copy from rpc-proxy, copy to rpc-proxy, write one new value to both). gen-jwt-keys.sh rotates every key, so it is not the repair. production.md: operators delete the three secret keys from their copy of the minikube rpc-proxy-config instead of replacing them, because gen-jwt-keys.sh writes all three to rpc-proxy-secrets and rpc-proxy reads the Secret after the ConfigMap; cookie secrets need 32 bytes. Step 3 says gen-jwt-keys.sh writes the desktop API token to both Secrets, both services require it, and the repair commands are in the upgrade note. minikube.md: the ConfigMap also carries the desktop API token, and HCC's copy is the literal in patches/dynamic-images.yaml. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * origin/dev: (27 commits) test(desktop-app): pin the order of a removal and a replace on one chat test(desktop-app): name catalog isolation tests by behaviour docs(desktop-app): state what a failed write of a kept turn leaves test(desktop-app): name rejected-send tests by behaviour test(desktop-app): fix rejected-send test titles and a witness docs(desktop-app): describe what each send failure reason and flag records docs(desktop-app): state what the removal rule and a withdrawal cover fix(desktop-app): release a send's activity entry on every exit fix(desktop-app): keep a held send that was posted and failed without a rejection refactor(desktop-app): share the durable write of a kept turn fix(desktop-app): show the failure sentence for a send kept behind Recover input test(desktop-app): cover the tombstone and sender checks of chat:removeMessages test(desktop-app): cover the unprotected guards of the rejected-send path test(desktop-app): cover the attachment guards of a rejected send refactor(desktop-app): simplify the rejected-send path docs(desktop-app): describe connector rows for an agent the catalog does not list test(desktop-app): state why the second catalog differs fix(desktop-app): return a rejected send to the composer feat(desktop-app): remove unanswered local turns from the chat store test(desktop-app): cover a held agent restored from a workspace snapshot ...
…h for one missing secret The start-up errors for a missing or placeholder cookie secret, desktop API token or HCC desktop token said to run deploy/scripts/gen-jwt-keys.sh. On an existing install that script rotates every key and resets control-postgres, which the upgrade note tells operators not to do. verify-secrets.sh listed only the regenerate-everything targets, and it now runs against base HCC through the new secretKeyRef. - rpc-proxy: the hint for values gen-jwt-keys.sh writes says to run it only on a new install and points an existing install to "Required secrets without code defaults" in docs/deploy/releases-and-upgrades.md. Tokens written by apply-inter-service-tokens.sh keep their hint, because that script keeps existing values. - host-context-controller: the desktop token hint says the same. - verify-secrets.sh: one missing key on a running install goes to the upgrade note's non-rotating repair; the gen-keys targets are marked as regenerating every key. - Upgrade note: seven secrets "become required" (five had a development value, the two desktop tokens an empty default), and the token script may run before or after the first apply; before leaves no window in which webhook-proxy stops (verified on a disposable cluster). Tests: rpc-proxy config.test.ts (3 placeholder rows and 2 short-cookie rows) and HCC desktopApiToken.test.ts (2 rows) assert the warning and the note's section name. Dropping the warning in either service turns them red. rpc-proxy 1226 and HCC 2934 tests pass, and tsc --noEmit is clean in both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* origin/dev: test(mcp-host): bound the retention sweep test on worker CPU time, not wall time test(mcp-host): restore the alias guard under the #1433 wrap; correct the 401 test text test(mcp-host): adapt the #1433 tests to the OpenAI family composition on dev docs(mcp-host): correct comments on the text repair and the 400 classification fix(mcp-host): report Anthropic's own message for a rejected HTTP 400 test(mcp-host): cover shared sub-objects, every request method and the new error literal test(mcp-host): cover the repair and the 400 classification on every provider arm fix(mcp-host): classify Vertex and Bedrock HTTP 400s as rejected requests fix(mcp-host): repair request text without recursion and refuse colliding keys fix(mcp-host): classify an OpenAI-family or Anthropic API-key HTTP 400 as a rejected request fix(llm-proxy): report an upstream 400 as upstream_rejected fix(mcp-host): repair lone UTF-16 surrogates before LLM dispatch
…efaults Conflicts: tests/e2e/package.json and package-lock.json differed only in the package version (0.1.109 here, 0.1.107 on dev); kept the higher one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Evenfire Reviewer — Round 1Reviewed head: Decision: Approve with follow-ups The secret defaults are gone and the wiring matches the description. webhook-proxy refuses an unset or Must fix
Verified
Follow-ups
Validation gaps
PR descriptionAccurate. The deploy precondition is real: a new webhook-proxy image on the old base manifest gets the placeholder written back and then refuses to start. Pre-existingPre-existing: 2 Low — non-blocking for this PR. Next actionMerge is reasonable. Land Full review archive: — Review performed by Evenfire Reviewer |
Review finding R1-M1 on PR #1441 (head 18813a6): checkDesktopAuth compared the Authorization header with `===`, which stops at the first differing character. The comparison predates this PR (`!==` on dev). The whole header is now compared with crypto.timingSafeEqual through a module-private safeEqual. Headers of another length return false before the comparison, since timingSafeEqual throws on unequal lengths and the token length is public (gen-jwt-keys.sh writes 64 hex characters). Buffers are UTF-16LE so two different strings never encode to equal bytes; with UTF-8, distinct unpaired surrogates would compare equal. The dev-mode skip, the three denial reasons, the log line and the 401 body are unchanged. Tests: 76 cases in server.test.ts. 37 header rows (dev mode on/off, scheme and whitespace variants, lengths, empty and missing header, no token, unpaired surrogate) give the same verdict on 18813a6 and on this commit. The same rows assert that timingSafeEqual is called exactly when the lengths match and that its result is the verdict; two forced results show the decision follows it. On 18813a6 those 39 fail. Mutations: revert to `===` 39 red, no length guard 67 red, auth check disabled 90 red. HCC 3010 tests pass and tsc --noEmit is clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… token Review finding L2-2 on b29c555: the comment on the `compared` field said the header is compared whenever it has the expected length. The two no-token rows have that length and are not compared, because checkDesktopAuth checks `token &&` first. Comment only; no test changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Summary
Secrets no longer have code defaults, and the base manifests stop resetting the webhook-proxy token.
Required secrets (no default in any
NODE_ENV, trimmed,replace-with-*refused)WEBHOOK_PROXY_CONTROL_API_SERVICE_TOKENRPC_PROXY_CONTROL_API_SERVICE_TOKEN,RPC_PROXY_DESKTOP_COOKIE_SECRET,RPC_PROXY_SANDBOX_UI_COOKIE_SECRET,RPC_PROXY_DESKTOP_API_TOKENCONTEXT_MAPPER_DESKTOP_API_TOKEN(outsideCLERUM_DEV_MODE)EXTERNAL_REST_API_CONTROL_API_SERVICE_TOKENstringData: {}), like the other token Secrets. Thebase manifest declared the token's
replace-with-*value instringData, so everykubectl apply(client- or server-side) reset the token that
apply-inter-service-tokens.shpatched. Verified on adisposable cluster: the old manifest resets a patched token, and the new one keeps it.
gen-jwt-keys.shwrites 64.whenever its token was empty, and base never wired the token. Base HCC now reads
inter-service-tokens/desktop-api-token. HCC refuses to start without the token outsideCLERUM_DEV_MODE. A denied request logs one line with a reason (token_not_configured,missing_authorization,token_mismatch), never the value. The minikube overlay keeps its literaltoken (
valueFrom: null), so the minikube renders change only inwebhook-proxy-secrets.CONTROL_API_INTERNAL_TOKENS(the field had no reader). It adds a testfor the unset service-token map in production.
gen-jwt-keys.shwrites, the hint says to run that script only on a newinstall. On an existing install it rotates every key, so the hint points to the upgrade note's
non-rotating repairs instead.
Public-boundary scanner (
scripts/tests/test-minikube-t2-public-boundary.sh).env*file-name gate applies at any depth. The env templates.gitignoretracks are exempt(
.env.example,.env.test,.env.e2e.example,.env.dev.example,.env.quickstart.example), andtheir contents are still scanned.
…_PASSWORD=,…_SECRET=,…_TOKEN(S)=,…_PASS(WD)=,…_ENCRYPTION_KEY=,…_SIGNING_KEY=).dev-…-tokenvalues, and env-style lines in test paths (test/,tests/,__tests__/, and JS/TS*.test.*,*.spec.*,*.integration.test.*). Env files are never exempt.dev history, 7 of 371 commits would have needed a reword.
Docs. The upgrade note (
docs/deploy/releases-and-upgrades.md, "Required secrets without codedefaults") covers:
It also updates
production.md,minikube.md, the READMEs,rpc-proxy/.env.exampleand the.env.dev.exampleheader. The.env.dev.exampleIMAP keys keep their names: the infra targetmake gcp-dev-apply-secretsreads them.Deploy precondition
Deploy the base manifests of this PR no later than its webhook-proxy image. Pairing a new webhook-proxy
image with base manifests from before this PR puts the
replace-with-*value back intowebhook-proxy-secretson every apply, and the new image refuses to start with it. Runapply-inter-service-tokens.shfor this release, before or after the first apply.Out of scope (follow-ups)
console.*calls to its structured logger: separate issue.It affects stack traces,
LOG_LEVELfiltering, key redaction and the documented[HCC] FATALtext.apply-inter-service-tokens.shstill writesCONTROL_API_INTERNAL_TOKENS, which nothing reads.Verification
Package and contract checks ran on
f3e2e0b82, the last commit with code changes. The later commitsmerge
devand do not touch the files this PR changes. Thetests/e2eversion conflict was resolvedto the higher version. T0 and T2 ran on head
18813a699.tsc --noEmit, tests)PUBLIC_BOUNDARY_PASSkubectl kustomizerenders (minikube ×4, base)webhook-proxy-secretsdiffers; one HCC token entryapplywithstringData(client- and server-side)18813a699)make minikube-t2, fresh branch profile,18813a699)MINIKUBE_T2_PASS: full bootstrap, pre-gate-sync, T1 Real PostgreSQL 1064/1064 tests in 132/132 files, NP-08 Host-to-HCC authorization pass, port-forwards pass🤖 Generated with Claude Code