- Add redact package to detect and mask secrets (AWS keys, private keys, API tokens, bearer tokens, credentials, home paths) in agent output before posting as comments in TaskService - Enforce agent visibility on issue assignment: private agents can only be assigned by their owner or workspace admins - Add visibility picker (workspace/private) to CreateAgentDialog, default to private - Grey out unassignable private agents in the assignee picker with lock icon indicator
71 lines
2.4 KiB
Go
71 lines
2.4 KiB
Go
// Package redact provides functions for detecting and masking secrets
|
|
// in agent output before it reaches the database or WebSocket broadcast.
|
|
package redact
|
|
|
|
import (
|
|
"os"
|
|
"os/user"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// secretPattern pairs a compiled regex with its replacement text.
|
|
type secretPattern struct {
|
|
re *regexp.Regexp
|
|
replacement string
|
|
}
|
|
|
|
// Patterns are checked in order; first match wins per position.
|
|
var patterns = []secretPattern{
|
|
// AWS access key IDs (always start with AKIA)
|
|
{regexp.MustCompile(`\bAKIA[0-9A-Z]{16}\b`), "[REDACTED AWS KEY]"},
|
|
|
|
// AWS secret access keys (40 char base64-ish, preceded by a common separator)
|
|
{regexp.MustCompile(`(?i)(?:aws_secret_access_key|secret_?access_?key)\s*[=:]\s*[A-Za-z0-9/+=]{40}`), "[REDACTED AWS SECRET]"},
|
|
|
|
// PEM private keys (multi-line)
|
|
{regexp.MustCompile(`(?s)-----BEGIN[A-Z\s]*PRIVATE KEY-----.*?-----END[A-Z\s]*PRIVATE KEY-----`), "[REDACTED PRIVATE KEY]"},
|
|
|
|
// GitHub tokens (classic PAT, fine-grained, OAuth, etc.)
|
|
{regexp.MustCompile(`\b(ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{36,255}\b`), "[REDACTED GITHUB TOKEN]"},
|
|
|
|
// OpenAI / Anthropic API keys
|
|
{regexp.MustCompile(`\bsk-[A-Za-z0-9_-]{20,}\b`), "[REDACTED API KEY]"},
|
|
|
|
// Slack tokens
|
|
{regexp.MustCompile(`\bxox[bporas]-[A-Za-z0-9\-]{10,}\b`), "[REDACTED SLACK TOKEN]"},
|
|
|
|
// Generic "Bearer <token>" in output
|
|
{regexp.MustCompile(`(?i)\bBearer\s+[A-Za-z0-9\-._~+/]+=*\b`), "Bearer [REDACTED]"},
|
|
|
|
// Generic key=value patterns for common secret env var names
|
|
{regexp.MustCompile(`(?i)(?:API_KEY|API_SECRET|SECRET_KEY|ACCESS_TOKEN|AUTH_TOKEN|PRIVATE_KEY|DATABASE_URL|DB_PASSWORD|REDIS_URL)\s*[=:]\s*\S+`), "[REDACTED CREDENTIAL]"},
|
|
}
|
|
|
|
// homeDir is resolved once at init for path redaction.
|
|
var homeDir string
|
|
var username string
|
|
|
|
func init() {
|
|
homeDir, _ = os.UserHomeDir()
|
|
if u, err := user.Current(); err == nil {
|
|
username = u.Username
|
|
}
|
|
}
|
|
|
|
// Text scans the input string for known secret patterns and replaces
|
|
// matches with safe placeholders. It also masks the local user's home
|
|
// directory path to prevent leaking the username.
|
|
func Text(s string) string {
|
|
for _, p := range patterns {
|
|
s = p.re.ReplaceAllString(s, p.replacement)
|
|
}
|
|
|
|
// Redact home directory paths (e.g. /Users/john/ → /Users/****/).
|
|
if homeDir != "" && username != "" {
|
|
masked := strings.Replace(homeDir, username, "****", 1)
|
|
s = strings.ReplaceAll(s, homeDir, masked)
|
|
}
|
|
|
|
return s
|
|
}
|