Skip to content

Commit 9a3c561

Browse files
authored
fix: redact Groq, xAI, HuggingFace keys and underscores in sk- keys (M-11) (#68)
Adds missing provider secret patterns and closes the Anthropic-key escape where an underscore appeared in the first ~19 body chars. Changes: - internal/redact/redact.go: added gsk_, xai-, hf_ patterns and included '_' in the sk- character class. - Added TestRedactSecrets_ProviderKeys and expanded coverage cases. - Updated TestReport_RedactMissesRealSecretFormats. - Updated docs/SECURITY.md and AGENTS.md.
1 parent 4c91aea commit 9a3c561

5 files changed

Lines changed: 38 additions & 5 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ Layered prompt-injection / approval-fatigue defenses. Full reference: [docs/SECU
193193
- **git config code execution** (`internal/danger/classifier.go`) — `git -c alias.*=!<cmd>`, `git -c core.pager=...`, `git -c core.fsmonitor=...`, `git -c credential.helper=...`, and the `git config` subcommand are classified as `code_execution` because they can inject arbitrary shell commands.
194194
- **find / rsync destructive flags** (`internal/danger/classifier.go`) — `find -delete` and `rsync --delete` / `--del` / `--remove-source-files` are classified as `destructive`; `find -fprint` / `-fprintf` are classified as `local_write`.
195195
- **Shell operand path classification** (`internal/danger/classifier.go`) — file operands and redirect targets of shell commands are checked with `ClassifyPath`, so writes to `~/.bashrc`, `~/.zshrc`, `~/.profile`, `~/.ssh`, `~/.odek`, etc. are escalated to `system_write` instead of auto-allowed `local_write`.
196-
- **Secret redaction** (`internal/redact/redact.go`) — 20+ patterns: OpenAI, Anthropic, GitHub PAT, AWS, PEM, JWT, Vault, Google OAuth, SendGrid, Discord, DB URLs, etc.
196+
- **Secret redaction** (`internal/redact/redact.go`) — 20+ patterns: OpenAI/Anthropic `sk-` (including underscore-bearing bodies), Groq `gsk_`, xAI `xai-`, HuggingFace `hf_`, GitHub PAT, AWS, PEM, JWT, Vault, Google OAuth, SendGrid, Discord, DB URLs, etc.
197197

198198
### Security findings (`sec_findings.md`)
199199

cmd/odek/security_report_validation_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,10 @@ func TestReport_RedactMissesRealSecretFormats(t *testing.T) {
181181
{"db_url_mongo", "mongodb+srv://root:VeryLongMongoPassword1234@cluster.mongodb.net/db", "Mongo URL with embedded password"},
182182
{"discord_bot", "N01234567890123456789012345.aBcDe.6789abcdef0123456789abcdef012345678", "Discord bot token (synthetic test value)"},
183183
{"sendgrid", "SG.dQw4w9WgXcQ-AbCdEfGh.JkLmNoPqRsTuVwXyZ0123456789abcdefghij", "SendGrid API key"},
184+
{"groq", "gsk_abcdefghijklmnopqrstuvwxyz1234567890", "Groq API key (gsk_ prefix)"},
185+
{"xai", "xai-abcdefghijklmnopqrstuvwxyz1234567890_0123456789", "xAI API key (xai- prefix)"},
186+
{"huggingface", "hf_abcdefghijklmnopqrstuvwxyz1234567890", "HuggingFace token (hf_ prefix)"},
187+
{"anthropic_underscore", "sk-ant-api03-abcdefghijklmnopqrstuvwxyz_1234567890", "Anthropic key with underscore in body"},
184188
}
185189

186190
for _, tc := range cases {

docs/SECURITY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ Files attached through the Web UI are sourced from the browser trust boundary. T
246246

247247
### 10. Secret redaction
248248

249-
`internal/redact` scans every tool output and session/memory write for known secret formats and replaces matches with `[REDACTED]` before they reach Telegram replies, persistent sessions, or memory. Patterns include OpenAI `sk-`, Anthropic `sk-ant-`, GitHub PATs (classic + fine-grained), AWS access keys, multi-line PEM private keys, JWT, generic `api_key=` / `password=` env lines, Slack `xoxb-`, Stripe `sk_live_`, Google API keys, Twilio `SK`, HashiCorp Vault `hvs.` / `hvb.`, Google OAuth `ya29.` / `1//0`, SendGrid `SG.`, Discord bot tokens (M/N/O-anchored), and DB URLs with embedded credentials (`postgresql://`, `mongodb://`, etc.).
249+
`internal/redact` scans every tool output and session/memory write for known secret formats and replaces matches with `[REDACTED]` before they reach Telegram replies, persistent sessions, or memory. Patterns include OpenAI `sk-` (and underscore-bearing bodies such as Anthropic `sk-ant-...`), Groq `gsk_`, xAI `xai-`, HuggingFace `hf_`, GitHub PATs (classic + fine-grained), AWS access keys, multi-line PEM private keys, JWT, generic `api_key=` / `password=` env lines, Slack `xoxb-`, Stripe `sk_live_`, Google API keys, Twilio `SK`, HashiCorp Vault `hvs.` / `hvb.`, Google OAuth `ya29.` / `1//0`, SendGrid `SG.`, Discord bot tokens (M/N/O-anchored), and DB URLs with embedded credentials (`postgresql://`, `mongodb://`, etc.).
250250

251251
If you find a format that leaks, add a regex to `internal/redact/redact.go:31-100` and a row to `TestReport_RedactMissesRealSecretFormats` in `cmd/odek/security_report_validation_test.go`.
252252

internal/redact/redact.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,17 @@ import (
3434
// The first matching pattern wins — subsequent patterns are skipped for
3535
// that portion of text.
3636
var patterns = []*regexp.Regexp{
37-
// OpenAI / AI provider keys: sk-<alphanumeric+hyphens>
38-
// sk-proj- variant for project-scoped keys
39-
regexp.MustCompile(`sk-[a-zA-Z0-9-]{32,}`),
37+
// Provider-specific AI API keys (ordered before the generic sk- pattern).
38+
// Groq: gsk_<alphanumeric>
39+
regexp.MustCompile(`gsk_[a-zA-Z0-9]{32,}`),
40+
// xAI: xai-<alphanumeric+hyphens/underscores>
41+
regexp.MustCompile(`xai-[a-zA-Z0-9_-]{32,}`),
42+
// HuggingFace: hf_<alphanumeric>
43+
regexp.MustCompile(`hf_[a-zA-Z0-9]{32,}`),
44+
// OpenAI / generic AI provider keys: sk-<alphanumeric+hyphens+underscores>
45+
// sk-proj- variant for project-scoped keys. The underscore is required
46+
// because Anthropic keys (sk-ant-...) and some provider keys contain it.
47+
regexp.MustCompile(`sk-[a-zA-Z0-9_-]{32,}`),
4048

4149
// GitHub personal access tokens (classic + fine-grained)
4250
regexp.MustCompile(`ghp_[a-zA-Z0-9]{36,}`),

internal/redact/redact_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ func TestRedactSecrets_OpenAIKey(t *testing.T) {
2222
tests := []string{
2323
"sk-proj-abcdefghijklmnopqrstuvwxyz123456",
2424
"sk-1234567890abcdefghijklmnopqrstuv",
25+
// Anthropic-style keys contain underscores in the body.
26+
"sk-ant-api03-abcdefghijklmnopqrstuvwxyz_1234567890",
2527
}
2628
for _, input := range tests {
2729
result := RedactSecrets(input)
@@ -31,6 +33,25 @@ func TestRedactSecrets_OpenAIKey(t *testing.T) {
3133
}
3234
}
3335

36+
func TestRedactSecrets_ProviderKeys(t *testing.T) {
37+
cases := []struct {
38+
name string
39+
secret string
40+
}{
41+
{"groq", "gsk_abcdefghijklmnopqrstuvwxyz1234567890"},
42+
{"xai", "xai-abcdefghijklmnopqrstuvwxyz1234567890_0123456789"},
43+
{"huggingface", "hf_abcdefghijklmnopqrstuvwxyz1234567890"},
44+
}
45+
for _, tc := range cases {
46+
t.Run(tc.name, func(t *testing.T) {
47+
result := RedactSecrets(tc.secret)
48+
if result != "[REDACTED]" {
49+
t.Errorf("%s key not redacted: %q → %q", tc.name, tc.secret, result)
50+
}
51+
})
52+
}
53+
}
54+
3455
func TestRedactSecrets_GitHubToken(t *testing.T) {
3556
tests := []string{
3657
"ghp_abcdefghijklmnopqrstuvwxyz1234567890",

0 commit comments

Comments
 (0)