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 }