test(deploy): add a gate against invalid master-key defaults - #237
Merged
Merged
Conversation
`docker-compose.yml` shipped `ATP_MASTER_KEY=${ATP_MASTER_KEY:-dev-master-key-请替换}`.
That value is not 64 hex chars, so `crypto::from_config` logged one error and fell
through to a *random* dev master key: every restart left already-encrypted upstream
keys undecryptable, and the only trace was a single startup line — a silent all-503.
The same defect was found the same day in the deployment repo's compose file.
The convention was prose in a comment, so nothing kept it from recurring. This pins it
in `cargo test`: a master-key default in a shipped artifact must be either absent or
exactly 64 hex chars.
Classified by shape, not by line — `Required` (`${VAR:?msg}`), `Default`
(`${VAR:-x}`), `Literal` (bare `VAR=x`), and `NotALiteral` (an example shell command
such as `$(openssl rand -hex 32)`, or a `${VAR}` passthrough) are each judged
separately, so the file's own quick-start comments do not read as defaults.
Test-only module (`#[cfg(test)]`), compile-time file reads, zero new dependencies.
A comment is not a default, and a valid 64-hex default stays allowed.
argszero
added a commit
that referenced
this pull request
Sep 14, 2026
Ships the 76 PRs merged since v0.7.22 (#161-#237), the largest release so far. Database schema moves 12 -> 14: - v13 (#217): `keys.used` changes unit from tokens to points, and the live values are healed from the ledger (`used = SUM(transactions.pts WHERE type='consume')`), gated on schema_version < 13. - v14 (#234): four indexes for the monthly aggregates — `transactions(user_id, time, type, pts)`, `transactions(time, type, pts)`, `usage_records(time)`, `usage_records(user_id, time)`. Deployments must apply the DeepSeek flash rename (#233) to their own config.toml: `seed_models` is a full sync, so a model absent from the config is deleted at startup; the retired names are gone from config.example.toml. - Cargo.toml / Cargo.lock: 0.7.22 -> 0.7.23. - ui/index.html: asset cache-bust 20260912-4 / 20260912-5 / 20260914-1 / 20260914-4 / 20260914-9 -> 20260914-10 (all five refs). - CHANGELOG.md: v0.7.23 entry, grouped by area with the PR and hash of each fix. Gates: `cargo test` 249 passed / 0 failed, `cargo fmt --check` clean, `node --check` on the four ui/js files OK.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
docker-compose.ymlused to ship:- ATP_MASTER_KEY=${ATP_MASTER_KEY:-dev-master-key-请替换}dev-master-key-请替换is not 64 hex chars, andcrypto::parse_master_keyrequires exactly 32 bytes / 64 hex chars. SoConfig/Crypto::from_configlogged one error and fell through to a random dev master key. Consequence: every restart made already-encrypted upstream keys undecryptable — a silent all-503, with a single startup line as the only trace. The same defect was found the same day in the deployment repo'sci/docker-compose.yml, where it caused a real multi-minute outage.The convention ("no default, or a valid 64-hex default") lived only in prose, so nothing stopped it recurring. This PR pins it in
cargo test.How
New test-only module
src/deploy_gate.rs(#[cfg(test)], registered insrc/main.rs; not in release builds). It scans the shipped deployment/config artifacts (docker-compose.yml,config/config.example.toml,Dockerfile,README.md,README.en.md) at compile time (include_str!, so no working-directory dependency) and requires every master-key default to be either absent or exactly 64 hex chars.Classification is by shape, not by line:
Required${ATP_MASTER_KEY:?msg}Default${ATP_MASTER_KEY:-x}xmust be 64 hexLiteralATP_MASTER_KEY=xxmust be 64 hex (empty allowed)NotALiteral$(openssl rand -hex 32)/${VAR}A comment is not a default, and a valid 64-hex default stays allowed. The file's own quick-start comment block contains
ATP_MASTER_KEY=$(openssl rand -hex 32), which the classifier correctly skips.Tests
cargo test246 → 249 passed, 0 failed (cargo fmt --checkclean; clippy reports only the pre-existing local-toolchaincollapsible_matchatsrc/protocol.rs:662, which is green in CI on the same code).Three new tests:
no_master_key_default_that_is_not_valid_hex— the invariant.the_scanner_actually_finds_the_settings_it_guards— positive control: compose must contain exactly oneRequiredsetting, no literal default, and the scanner must actually see the quick-start comments (otherwise "invisible" and "compliant" are indistinguishable).classifier_and_config_parser_flag_the_pre_fix_shapes— synthetic-input control on the classifier/parser themselves.A/B (teeth verification)
Snapshots taken in memory and restored byte-for-byte (no
git checkout):${ATP_MASTER_KEY:-dev-master-key-请替换}(the pre-fix shape)默认值不是 64 位 hexATP_MASTER_KEY=dev-master-key-请替换(Literalbranch)字面量主密钥不是 64 位 hexconfig.example.tomlexample →not-a-hex-value(config branch)master_key 示例值不是 64 位 hexOne self-caught instrumentation error worth recording: the first M2 attempt was a no-op (the replacement string did not match, so the measured file never moved) and reported GREEN — a false "the gate has no teeth" reading. Redone with an explicit
mutated != origassertion; only then did it go red.Notes
docker-compose.yml(already fixed in fix(deploy): require ATP_MASTER_KEY in docker-compose instead of defaulting to an invalid value #235) — this is the gate that keeps it fixed.docker compose configvalidation hinted at in the work order is not reproducible in this environment (docker: command not found); marked pending rather than claimed.