d237098d83
GitHub PATs and Stripe secret keys have no scriptable rotation API (GitHub's create-token API was removed in 2020; Stripe has no create-key endpoint), so they belong in the guided change-password layer, not as Rotators. But they arrive from the env/file scanner as generic Source="env" tokens with no host, so the worklist showed them as "manual — no web page". Recognise them by their well-known PUBLIC value prefixes (ghp_/gho_/ghs_/ github_pat_/…, sk_live_/sk_test_/rk_live_/…) at scan time and record a non-secret Meta["service"] hint — a fixed service NAME, never the secret bytes. discover.ServiceForSecret does the detection; env.go attaches it for generic tokens, file.go before Store wipes the buffer. links.HostFor consults the hint and maps github→github.com / stripe→stripe.com, so the curated change-password URLs (already in the table) now light up for these credentials. Leak-safe (service name is derived from a public prefix, not the secret) and verified by the existing assertNoLeak checks. go build/vet clean; full suite 179 tests pass. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
31 lines
969 B
Go
31 lines
969 B
Go
package discover
|
|
|
|
import "testing"
|
|
|
|
func TestServiceForSecret(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
val string
|
|
want string
|
|
}{
|
|
{"github classic pat", "ghp_0123456789abcdefABCDEF", "github"},
|
|
{"github fine-grained pat", "github_pat_11ABCDE0000xyz", "github"},
|
|
{"github oauth", "gho_abcdef0123456789", "github"},
|
|
{"github server", "ghs_abcdef0123456789", "github"},
|
|
{"stripe secret live", "sk_live_51HxYzAbCdEf", "stripe"},
|
|
{"stripe secret test", "sk_test_51HxYzAbCdEf", "stripe"},
|
|
{"stripe restricted", "rk_live_51HxYzAbCdEf", "stripe"},
|
|
{"leading whitespace", "\n ghp_abc123", "github"},
|
|
{"unknown opaque token", "xoxb-not-a-known-prefix", ""},
|
|
{"random high entropy", "Zk9fQ2pVw7eR1tY3", ""},
|
|
{"empty", "", ""},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
if got := ServiceForSecret([]byte(c.val)); got != c.want {
|
|
t.Errorf("ServiceForSecret(%q) = %q, want %q", c.val, got, c.want)
|
|
}
|
|
})
|
|
}
|
|
}
|