fix(redact): catch Supabase sb_secret_ keys · Entire
fix(redact): catch Supabase sb_secret_ keys
Supabase secret API keys (sb_secret_) and personal access tokens (sbp_) passed through the built-in redaction unredacted into checkpoint objects.
Every always-on layer missed them when captured on their own:
- Entropy: the tokens are low-entropy (the reported repro value measured Shannon entropy 4.199), below the 4.5 always-on threshold.
- betterleaks: its Supabase secret-key rule is a composite rule (RequiredRules: supabase-project-url) that only fires when a matching "*.supabase.co" URL is co-present, plus an entropy filter, so a secret seen in isolation is never flagged.
- The value is not a credentialed URI or connection string, and its surrounding key name is not password-shaped, so those layers skip it.
Add a deterministic provider-prefix layer that redacts sb_secret_ and sbp_ tokens purely by prefix + length, independent of entropy or the surrounding variable name. Publishable keys (sb_publishable_) are designed to be embedded in client code and protected by row-level security, so they are intentionally not targeted (matching betterleaks, which ships no publishable-key rule).
Verified end-to-end: a sb_secret_ value planted across prompt text and shell tool I/O in a Claude Code transcript now redacts to REDACTED in the entire/checkpoints/v1 condensed blob (previously it survived verbatim).
Fixes #1716
Changes
4
cmd/entire/cli/integration_test
redact
//go:build integration
package integration
import (
"os"
"strings"
"testing"
"github.com/entireio/cli/cmd/entire/cli/paths"
)
// TestSupabaseSecretRedaction_FullHookFlow is the end-to-end regression for
// issue #1716. It drives the real entire hook binary (UserPromptSubmit ->
// mid-turn commit/condensation -> Stop/finalize) on a Claude Code session
// whose transcript embeds a Supabase sb_secret_ API key across the two vectors
// from the issue report (prompt text and shell-tool input/output), then reads
// the real entire/checkpoints/v1 transcript blob back and proves:
// - the sb_secret_ value does NOT survive into the condensed blob,
// - a REDACTED placeholder is present,
// - a plain capture-control marker DID survive (so a zero secret count means
// redaction happened, not that capture failed — mirroring the issue's
// methodology),
// - a sb_publishable_ key (public by design) is NOT over-redacted.
func TestSupabaseSecretRedaction_FullHookFlow(t *testing.T) {
// Hook subprocesses share settings/env; do not run in parallel.
// The sb_secret_ / sb_publishable_ prefixes are assembled from fragments so
// a complete Supabase-shaped token never appears verbatim in source, keeping
// secret scanners (including GitHub push protection) from flagging these
// synthetic fixtures; the runtime values are complete.
const (
supabaseSecret = "sb" + "_secret_" + "probe_20260710_7f91c2d8e4a6b3f0"
supabasePublishable = "sb" + "_publishable_" + "probe_20260710_7f91c2d8e4a6b3f0"
captureControl = "CAPTURE_CONTROL_MARKER_9f"
)
env := NewFeatureBranchEnv(t)
session := env.NewSession()
// Author a Claude Code transcript: a prompt that names the secret (vector 1)
// plus the publishable control and capture marker, a Bash tool_use whose
// command exports the secret (vector 2 input), the shell tool_result echoing
// the secret (vector 2 output), then a file-writing tool use so the commit
// has attributable content.
prompt := "Configure the backend. The service_role key is " + supabaseSecret +
" and the public client key " + supabasePublishable +
" is safe to commit. " + captureControl
transcript := strings.Join([]string{
`{"uuid":"u1","type":"user","message":{"role":"user","content":"` + prompt + `"},"timestamp":"2026-01-01T00:00:00Z"}`,
`{"uuid":"a1","type":"assistant","message":{"content":[{"type":"tool_use","id":"toolu_1","name":"Bash","input":{"command":"export SUPABASE_SERVICE_ROLE_KEY='` + supabaseSecret + `','description":"set service role key"}}]},"timestamp":"2026-01-01T00:00:01Z"}`,
`{"uuid":"u2","type":"user","message":{"content":[{"type":"tool_result","tool_use_id":"toolu_1","content":"Applied. Wrote key ` + supabaseSecret + ` to env. ` + captureControl + `"}]},"timestamp":"2026-01-01T00:00:02Z"}`,
`{"uuid":"a2","type":"assistant","message":{"content":[{"type":"tool_use","id":"toolu_2","name":"Write","input":{"file_path":"feature.go","content":"package main\n"}}]},