Files
incredigo/internal/discover/service.go
T
leetcrypt d237098d83 guide: wire GitHub PATs + Stripe keys into the guided worklist
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>
2026-06-20 15:35:42 -07:00

46 lines
2.0 KiB
Go

package discover
import "bytes"
// MetaService is the Meta key under which a scanner records the issuing service of
// a credential it recognised by value-prefix (e.g. "github", "stripe"). It is a
// NON-SECRET hint: the value is a fixed service name, never the secret material.
// The links/worklist layer turns it into a guided change-password URL for
// credentials that have no scriptable rotation API (GitHub PATs, Stripe keys).
const MetaService = "service"
// servicePrefixes maps a credential's well-known, PUBLIC value prefix to the
// service that issued it. Each prefix is a documented format marker — not the
// secret itself — so matching one and recording the service name leaks nothing.
var servicePrefixes = []struct {
prefix []byte
service string
}{
{[]byte("github_pat_"), "github"}, // fine-grained PAT
{[]byte("ghp_"), "github"}, // classic personal access token
{[]byte("gho_"), "github"}, // OAuth access token
{[]byte("ghu_"), "github"}, // user-to-server token
{[]byte("ghs_"), "github"}, // server-to-server token
{[]byte("ghr_"), "github"}, // refresh token
{[]byte("sk_live_"), "stripe"}, // secret key (live)
{[]byte("sk_test_"), "stripe"}, // secret key (test)
{[]byte("rk_live_"), "stripe"}, // restricted key (live)
{[]byte("rk_test_"), "stripe"}, // restricted key (test)
}
// ServiceForSecret recognises a credential by its well-known value prefix and
// returns the issuing service ("github", "stripe", …) or "" if unrecognised. It
// inspects only the leading bytes and returns a fixed service NAME, so no secret
// material escapes. Callers attach the result as Meta[MetaService] so the
// links/worklist layer can offer a guided rotation page for credentials that
// cannot be rotated through a provider API.
func ServiceForSecret(b []byte) string {
head := bytes.TrimLeft(b, " \t\r\n")
for _, sp := range servicePrefixes {
if bytes.HasPrefix(head, sp.prefix) {
return sp.service
}
}
return ""
}