GL-7 Part 1: passphrase hardening on the customer page (security)
The Setup Command panel rendered the per-customer retrieval passphrase (the secret that fetches the WHOLE config) in cleartext twice — as #retrieval-pw text and baked into the Option-3 curl's X-Retrieval-Password header — which contradicts the panel's own "never on the command line" guidance. Now: the retrieval password is MASKED by default (bullet run) with Reveal/Hide + copy-secret controls (value lives in data-secret — the existing reveal model); the Option-3 debug command carries a <YOUR-RETRIEVAL-PASSWORD> placeholder, never the secret. Render test asserts the secret is not baked into any command + is masked by default; red-proof (bake it back) FAILS. (A zero-secret-in-DOM reveal-on-demand fetch is a noted follow-up, not this task.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
@@ -113,3 +113,64 @@ func TestTemplates_DashboardCriticalBadge(t *testing.T) {
|
||||
t.Errorf("critical badge renders AFTER the error badge (crit@%d, err@%d) — must be first", critIdx, errIdx)
|
||||
}
|
||||
}
|
||||
|
||||
// GL-7 Part 1: the retrieval passphrase must be MASKED by default and NEVER baked into a copyable
|
||||
// command block. The value still ships in data-secret (the reveal/copy mechanism — the existing
|
||||
// model), but the Option-3 debug curl must carry a placeholder, not the secret.
|
||||
// RED-PROOF: revert the Option-3 block to `X-Retrieval-Password: {{.Config.RetrievalPassword}}`
|
||||
// (or the #retrieval-pw code back to the raw value) → the secret appears in a command / unmasked →
|
||||
// the "not in the -H command" / masked assertions FAIL.
|
||||
func TestTemplates_PassphraseHardened(t *testing.T) {
|
||||
st, err := store.New(filepath.Join(t.TempDir(), "t.db"), log.New(io.Discard, "", 0))
|
||||
if err != nil {
|
||||
t.Fatalf("store.New: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { st.Close() })
|
||||
const secret = "correct-horse-battery-staple-9f2a"
|
||||
if err := st.SaveCustomerConfig(&store.CustomerConfig{
|
||||
CustomerID: "peti-felhom", CustomerName: "Peti", Domain: "sajatfelhom.hu",
|
||||
RetrievalPassword: secret, APIKey: "apikey-xyz", Status: "active",
|
||||
}); err != nil {
|
||||
t.Fatalf("SaveCustomerConfig: %v", err)
|
||||
}
|
||||
s := New(st, "", "", "test", time.Hour, log.New(io.Discard, "", 0))
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/configs/peti-felhom", nil)
|
||||
s.handleCustomerUnified(rr, req, "peti-felhom")
|
||||
if rr.Code != 200 {
|
||||
t.Fatalf("customer page status = %d", rr.Code)
|
||||
}
|
||||
html := rr.Body.String()
|
||||
|
||||
// (a) the secret must NOT appear inside the Option-3 curl command (no X-Retrieval-Password: <secret>).
|
||||
if strings.Contains(html, "X-Retrieval-Password: "+secret) {
|
||||
t.Errorf("passphrase is baked into the Option-3 command (must be a placeholder)")
|
||||
}
|
||||
// The Option-3 command carries the placeholder instead.
|
||||
if !strings.Contains(html, "YOUR-RETRIEVAL-PASSWORD") {
|
||||
t.Errorf("Option-3 command missing the passphrase placeholder")
|
||||
}
|
||||
// (b) the visible retrieval-pw node is masked by default (bullets), not the cleartext value.
|
||||
if !strings.Contains(html, `id="retrieval-pw"`) {
|
||||
t.Fatalf("retrieval-pw node missing")
|
||||
}
|
||||
// the reveal control + copy-secret wiring must be present (the value lives in data-secret).
|
||||
if !strings.Contains(html, `onclick="toggleSecret('retrieval-pw')"`) ||
|
||||
!strings.Contains(html, `onclick="copySecret('retrieval-pw')"`) {
|
||||
t.Errorf("reveal/copy-secret controls missing")
|
||||
}
|
||||
if !strings.Contains(html, `data-secret="`+secret+`"`) {
|
||||
t.Errorf("data-secret not populated for the reveal control")
|
||||
}
|
||||
// the DEFAULT visible masked text is a run of bullet entities; the raw secret is only in
|
||||
// data-secret, never the code node's visible text content.
|
||||
i := strings.Index(html, `id="retrieval-pw"`)
|
||||
codeText := html[i : strings.Index(html[i:], "</code>")+i]
|
||||
if !strings.Contains(codeText, "•••") {
|
||||
t.Errorf("retrieval-pw is not masked by default (no bullet-entity run)")
|
||||
}
|
||||
if strings.Contains(codeText[strings.Index(codeText, ">")+1:], secret) {
|
||||
t.Errorf("raw secret is the retrieval-pw node's visible default text (must be masked)")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user