rony-llm-agent/pkg/tools/sandbox/redact.go
Victor Vargas 07d1840e7e feat(sandbox): network egress policy, secret redaction, untrusted-content fencing
NetworkPolicy validates scheme/host and re-validates resolved IPs at dial
time and on redirects (DNS-rebinding defense), with cloud metadata
endpoints always blocked. Redact masks known credential shapes (OpenAI/
Anthropic/GitHub/AWS/Slack/Google keys, PEM blocks, JWTs) in tool output.
WrapUntrusted fences fetched web content against prompt injection, paired
with UntrustedContentInstruction for the system prompt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 14:50:45 -07:00

41 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
}