42 lines
2 KiB
Go
42 lines
2 KiB
Go
|
|
package sandbox
|
||
|
|
|
||
|
|
import "regexp"
|
||
|
|
|
||
|
|
// secretPatterns match credential formats with distinctive, low-false-
|
||
|
|
// positive shapes — Phase 2 §8.2. Tool output flows straight into the
|
||
|
|
// model's context (and from there potentially into transcripts, logs, or a
|
||
|
|
// remote provider), so anything a read/bash/webfetch call happens to sweep
|
||
|
|
// up (a .env file, a verbose CLI printing its token) gets masked before the
|
||
|
|
// model ever sees it. Deliberately conservative: only patterns that are
|
||
|
|
// unmistakably secrets, so redaction never mangles ordinary code or prose.
|
||
|
|
var secretPatterns = []*regexp.Regexp{
|
||
|
|
// OpenAI (sk-..., incl. sk-proj-) and Anthropic (sk-ant-...) API keys.
|
||
|
|
regexp.MustCompile(`\bsk-(?:ant-|proj-)?[a-zA-Z0-9_\-]{20,}\b`),
|
||
|
|
// GitHub tokens: classic (ghp_/gho_/ghu_/ghs_/ghr_) and fine-grained.
|
||
|
|
regexp.MustCompile(`\bgh[pousr]_[A-Za-z0-9]{36,}\b`),
|
||
|
|
regexp.MustCompile(`\bgithub_pat_[A-Za-z0-9_]{22,}\b`),
|
||
|
|
// AWS access key IDs.
|
||
|
|
regexp.MustCompile(`\b(?:AKIA|ASIA)[0-9A-Z]{16}\b`),
|
||
|
|
// Slack tokens (xoxb-, xoxp-, xoxa-, xoxr-, xoxs-).
|
||
|
|
regexp.MustCompile(`\bxox[baprs]-[0-9A-Za-z\-]{10,}\b`),
|
||
|
|
// Google API keys.
|
||
|
|
regexp.MustCompile(`\bAIza[0-9A-Za-z_\-]{35}\b`),
|
||
|
|
// PEM private key blocks (RSA/EC/OpenSSH/PGP...), including the body.
|
||
|
|
regexp.MustCompile(`-----BEGIN [A-Z ]*PRIVATE KEY( BLOCK)?-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY( BLOCK)?-----`),
|
||
|
|
// JWTs (three base64url segments, header always starts with eyJ).
|
||
|
|
regexp.MustCompile(`\beyJ[A-Za-z0-9_\-]{10,}\.eyJ[A-Za-z0-9_\-]{10,}\.[A-Za-z0-9_\-]{10,}\b`),
|
||
|
|
}
|
||
|
|
|
||
|
|
// RedactedPlaceholder is what each detected secret is replaced with.
|
||
|
|
const RedactedPlaceholder = "[REDACTED]"
|
||
|
|
|
||
|
|
// Redact masks anything in input matching a known secret pattern. Safe to
|
||
|
|
// call on every tool output: with no matches it returns input unchanged
|
||
|
|
// (same underlying string, no allocation beyond the scans).
|
||
|
|
func Redact(input string) string {
|
||
|
|
for _, p := range secretPatterns {
|
||
|
|
input = p.ReplaceAllString(input, RedactedPlaceholder)
|
||
|
|
}
|
||
|
|
return input
|
||
|
|
}
|