Skip to content

test(deploy): add a gate against invalid master-key defaults - #237

Merged
argszero merged 1 commit into
mainfrom
test/deploy-master-key-gate
Sep 14, 2026
Merged

argszero merged 1 commit into
mainfrom
test/deploy-master-key-gate

Conversation

@argszero

Copy link
Copy Markdown
Owner

What

docker-compose.yml used to ship:

- ATP_MASTER_KEY=${ATP_MASTER_KEY:-dev-master-key-请替换}

dev-master-key-请替换 is not 64 hex chars, and crypto::parse_master_key requires exactly 32 bytes / 64 hex chars. So Config/Crypto::from_config logged 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's ci/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 in src/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:

shape example verdict
Required ${ATP_MASTER_KEY:?msg} allowed (fail-loud, no default)
Default ${ATP_MASTER_KEY:-x} x must be 64 hex
Literal ATP_MASTER_KEY=x x must be 64 hex (empty allowed)
NotALiteral $(openssl rand -hex 32) / ${VAR} skipped — not a default value

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 test 246 → 249 passed, 0 failed (cargo fmt --check clean; clippy reports only the pre-existing local-toolchain collapsible_match at src/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 one Required setting, 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):

leg input result
M1 compose line → ${ATP_MASTER_KEY:-dev-master-key-请替换} (the pre-fix shape) RED — 默认值不是 64 位 hex
M2 compose line → bare ATP_MASTER_KEY=dev-master-key-请替换 (Literal branch) RED — 字面量主密钥不是 64 位 hex
M3 config.example.toml example → not-a-hex-value (config branch) RED — master_key 示例值不是 64 位 hex
live unmodified tree GREEN 3/3

One 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 != orig assertion; only then did it go red.

Notes

`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
argszero merged commit bc4b111 into main Sep 14, 2026
1 check passed
@argszero
argszero deleted the test/deploy-master-key-gate branch September 14, 2026 11:34
@argszero argszero mentioned this pull request Sep 14, 2026
11 tasks
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant