reconcile: full verdicts (Parts 1-5) + H10 plaintext-secret evidence test
C3 = MOOT-by-architecture (whole-LXC PBS DR; GetAppDrivePath systemDataPath fallback). 7 HIGH survivors: C2/H5/H6/H7/H8 FIXED, C3 MOOT, H10 PARTIAL/LIVE (tag added a WARN but still persists plaintext on crypto.Encrypt failure — failing test proves it). MOOT bucket C1/H9/H11 + file-gone Lows confirmed gone-and-not-migrated (agent watchdog + agent backup checked). M/L mechanical triage tables. Merged fix list: CTRL-001 > deploy.go slice (CTRL-T2-1 + H10) > AGENT-001 (spike) > M2. Tallies: FIXED 5 / MOOT 11 / LIVE 1 (H10) + M2(CTRL-T3-1). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
package stacks
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestSaveAppConfigH10PlaintextOnEncryptFailure is a RECONCILE evidence test for
|
||||
// BUGHUNT finding H10 (v0.30.3) at commit eea235b. The `// H10 fix` comment in
|
||||
// SaveAppConfig (deploy.go:661) only ADDED A WARN LOG; on a crypto.Encrypt
|
||||
// failure the code still FALLS THROUGH and persists the secret value in
|
||||
// plaintext:
|
||||
//
|
||||
// if enc, err := crypto.Encrypt(encKey, v); err == nil { ...; continue }
|
||||
// } else { log.Printf("[WARN] ... saving as plaintext") } // deploy.go:662
|
||||
// saveCfg.Env[k] = v // deploy.go:665 — plaintext persisted
|
||||
//
|
||||
// A bad-length encKey makes aes.NewCipher (inside crypto.Encrypt) return an
|
||||
// error, exercising that branch. This test asserts the SAFE invariant ("a
|
||||
// sensitive value must never be written to app.yaml in plaintext"). It FAILS at
|
||||
// the recorded commit — evidence that the tagged fix is observability only, not
|
||||
// fail-closed. Do NOT weaken this test; the fix is to RETURN an error instead of
|
||||
// falling through to the plaintext write.
|
||||
func TestSaveAppConfigH10PlaintextOnEncryptFailure(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
const secret = "supersecret-pw-do-not-leak"
|
||||
cfg := &AppConfig{
|
||||
Deployed: true,
|
||||
Env: map[string]string{"DB_PASSWORD": secret},
|
||||
}
|
||||
|
||||
// Non-nil but invalid-length key (5 bytes) → aes.NewCipher fails →
|
||||
// crypto.Encrypt returns an error → SaveAppConfig hits the H10 branch.
|
||||
badKey := []byte("short")
|
||||
|
||||
if err := SaveAppConfig(dir, cfg, badKey, []string{"DB_PASSWORD"}); err != nil {
|
||||
// SaveAppConfig currently does NOT error on encrypt failure; if a future
|
||||
// fix makes it fail-closed by returning an error, that is the desired
|
||||
// behavior and this test should be updated to assert the error instead.
|
||||
t.Fatalf("H10 (would-be-fixed): SaveAppConfig returned an error on encrypt failure: %v — "+
|
||||
"if this is the new fail-closed behavior, update the test to assert it", err)
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(filepath.Join(dir, "app.yaml"))
|
||||
if err != nil {
|
||||
t.Fatalf("reading app.yaml: %v", err)
|
||||
}
|
||||
if strings.Contains(string(data), secret) {
|
||||
t.Fatalf("H10: app.yaml contains the secret in PLAINTEXT after a crypto.Encrypt failure "+
|
||||
"(SaveAppConfig logged a WARN but still persisted it at deploy.go:665). app.yaml:\n%s", data)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user