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>
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
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 ""
|
||||
}
|
||||
Reference in New Issue
Block a user