feat(config): one config.toml → fail-closed prophet doctor + native rc emit (POSIX/Windows) - #60
Merged
Merged
Conversation
… rc emit (POSIX/Windows) The rollout gap: config was ~18 scattered env vars, .noetica is a state dir (not config), prophet-cli read no config, and shell dotfiles diverge per build (SourceOS Nix / macOS brew / Windows PowerShell). This adds ONE platform-neutral source of truth + the shared core all three bindings sit on: - config.py: load(config.toml) + doctor(cfg) [fail-closed check-set: required keys, kube-context is NOT a prod cluster, referenced secret files exist and are 0600 on POSIX (cred:/DPAPI on Windows), CLIs on PATH] + emit(target=posix|powershell) that GENERATES the native rc so the three platforms never drift. - prophet doctor [--json|--quiet|--emit posix|powershell|--config PATH] and prophet config [path|show|init], wired into the argparse cli (intercepted before the delegating parser). - config.example.toml (documented template; `prophet config init` writes it). Verified: doctor is fail-closed (missing secret / prod-context / missing key → exit 1 'would NOT work as advertised'); emit produces .prophetrc (export/XDG/*_FILE 0600) and prophet.psm1 ($env:/%APPDATA%/cred: DPAPI). +8 tests. Next: the SourceOS binder (doctor.sh calls this; sourceos-shell.nix writes ~/.prophetrc), then macOS (brew caveats), then the Windows toolchain packaging.
| errors = [r for r in results if r["level"] == "error"] | ||
| if as_json: | ||
| import json | ||
| print(json.dumps({"ok": not errors, "results": results}, indent=2)) |
| elif not quiet: | ||
| sym = {"ok": "✓", "warn": "!", "error": "✗"} | ||
| for r in results: | ||
| print(f" {sym.get(r['level'],'?')} {r['name']}: {r['message']}", file=sys.stderr) |
| print(f"prophet doctor: {e}", file=sys.stderr) | ||
| return 1 | ||
| if a.emit: | ||
| sys.stdout.write(emit(cfg, target=a.emit)) |
CodeQL flagged four high-severity alerts on this PR and it was right on all four. I had earlier dismissed the failure as a 1s startup fault without opening it — it was a real finding. THREE were clear-text logging of sensitive information. `doctor` built its messages with the RESOLVED PATH of every secret and then printed them, so a `prophet doctor` run in any CI log would permanently disclose where each signing key lives. Not the key — the location, which is attack surface on its own, and which is exactly what this estate's own rule about scanners not echoing their matches exists to prevent. The reporter now names the SECRET and the CONDITION and nothing else: "present, 0600", "present but 0o640 — must be 0600", "referenced secret file is missing". Redaction is not leniency: the wrong mode is still refused, and a test asserts both halves. `emit` deliberately still carries the reference, because it is a GENERATOR rather than a reporter — its stdout IS the rc file, and the rc file must contain the path for the shell to resolve at load. It carries no secret VALUE. Said so at the write site, and pinned by a test, so the distinction does not get "fixed" later by someone reading only the first half. THE FOURTH was my own negative-control fixture chmod'ing a file to 0o644 — world-readable — to prove the 0600 check refuses it. 0o640 proves exactly the same thing (anything but 0600 is refused) without creating a world-readable file on disk to do it. 10 tests pass.
| def test_secret_file_existence_and_0600_enforced_on_posix(): | ||
| with tempfile.TemporaryDirectory() as td: | ||
| good = Path(td) / "k.key"; good.write_text("x"); os.chmod(good, 0o600) | ||
| bad = Path(td) / "b.key"; bad.write_text("x"); os.chmod(bad, 0o640) # group-readable: fails 0600, not world-readable |
| with tempfile.TemporaryDirectory() as td: | ||
| secret = Path(td) / "sovereign-root.key" | ||
| secret.write_text("x") | ||
| os.chmod(secret, 0o640) # wrong mode, so it is reported at all |
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.
The rollout gap
Config was ~18 env vars scattered across 6 namespaces;
.noeticais a state dir, not config;prophet-cliread no config; and the shell dotfiles diverge per build (SourceOS Nix / macOS brew / Windows PowerShell — different shells, service managers, secret stores). Nothing made "install → it works as advertised" true.One source of truth, three native bindings
config.toml— the single human-edited config (endpoints, contexts, tenancy, opt-ins, secret references).prophet doctor— a fail-closed check-set (same on every platform): required keys present, kube-context is not a production cluster, referenced secret files exist and are 0600 on POSIX (cred:/DPAPI on Windows), CLIs on PATH. This is what turns "works as advertised" into an enforced control.prophet doctor --emit posix|powershell— generates the native rc (.prophetrc/prophet.psm1) so the three platforms never drift by hand.prophet config path|show|init.Verified: fail-closed (missing secret /
gke_prodcontext / missing key → exit 1 "would NOT work as advertised"); emitters produce POSIX (export/XDG/*_FILE0600) and Windows ($env:/%APPDATA%/cred:DPAPI). +8 tests.Next binders: SourceOS (
doctor.shcalls this;sourceos-shell.nixwrites~/.prophetrc), macOS (brew caveats), and the Windows toolchain packaging (winget/choco — currently absent).