# 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
//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"}}]},
