Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ Layered prompt-injection / approval-fatigue defenses. Full reference: [docs/SECU
- **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.
- **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`.
- **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`.
- **Secret redaction** (`internal/redact/redact.go`) — 20+ patterns: OpenAI, Anthropic, GitHub PAT, AWS, PEM, JWT, Vault, Google OAuth, SendGrid, Discord, DB URLs, etc.
- **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.

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

Expand Down
4 changes: 4 additions & 0 deletions cmd/odek/security_report_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,10 @@ func TestReport_RedactMissesRealSecretFormats(t *testing.T) {
{"db_url_mongo", "mongodb+srv://root:VeryLongMongoPassword1234@cluster.mongodb.net/db", "Mongo URL with embedded password"},
{"discord_bot", "N01234567890123456789012345.aBcDe.6789abcdef0123456789abcdef012345678", "Discord bot token (synthetic test value)"},
{"sendgrid", "SG.dQw4w9WgXcQ-AbCdEfGh.JkLmNoPqRsTuVwXyZ0123456789abcdefghij", "SendGrid API key"},
{"groq", "gsk_abcdefghijklmnopqrstuvwxyz1234567890", "Groq API key (gsk_ prefix)"},
{"xai", "xai-abcdefghijklmnopqrstuvwxyz1234567890_0123456789", "xAI API key (xai- prefix)"},
{"huggingface", "hf_abcdefghijklmnopqrstuvwxyz1234567890", "HuggingFace token (hf_ prefix)"},
{"anthropic_underscore", "sk-ant-api03-abcdefghijklmnopqrstuvwxyz_1234567890", "Anthropic key with underscore in body"},
}

for _, tc := range cases {
Expand Down
2 changes: 1 addition & 1 deletion docs/SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ Files attached through the Web UI are sourced from the browser trust boundary. T

### 10. Secret redaction

`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.).
`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.).

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`.

Expand Down
14 changes: 11 additions & 3 deletions internal/redact/redact.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,17 @@ import (
// The first matching pattern wins — subsequent patterns are skipped for
// that portion of text.
var patterns = []*regexp.Regexp{
// OpenAI / AI provider keys: sk-<alphanumeric+hyphens>
// sk-proj- variant for project-scoped keys
regexp.MustCompile(`sk-[a-zA-Z0-9-]{32,}`),
// Provider-specific AI API keys (ordered before the generic sk- pattern).
// Groq: gsk_<alphanumeric>
regexp.MustCompile(`gsk_[a-zA-Z0-9]{32,}`),
// xAI: xai-<alphanumeric+hyphens/underscores>
regexp.MustCompile(`xai-[a-zA-Z0-9_-]{32,}`),
// HuggingFace: hf_<alphanumeric>
regexp.MustCompile(`hf_[a-zA-Z0-9]{32,}`),
// OpenAI / generic AI provider keys: sk-<alphanumeric+hyphens+underscores>
// sk-proj- variant for project-scoped keys. The underscore is required
// because Anthropic keys (sk-ant-...) and some provider keys contain it.
regexp.MustCompile(`sk-[a-zA-Z0-9_-]{32,}`),

// GitHub personal access tokens (classic + fine-grained)
regexp.MustCompile(`ghp_[a-zA-Z0-9]{36,}`),
Expand Down
21 changes: 21 additions & 0 deletions internal/redact/redact_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ func TestRedactSecrets_OpenAIKey(t *testing.T) {
tests := []string{
"sk-proj-abcdefghijklmnopqrstuvwxyz123456",
"sk-1234567890abcdefghijklmnopqrstuv",
// Anthropic-style keys contain underscores in the body.
"sk-ant-api03-abcdefghijklmnopqrstuvwxyz_1234567890",
}
for _, input := range tests {
result := RedactSecrets(input)
Expand All @@ -31,6 +33,25 @@ func TestRedactSecrets_OpenAIKey(t *testing.T) {
}
}

func TestRedactSecrets_ProviderKeys(t *testing.T) {
cases := []struct {
name string
secret string
}{
{"groq", "gsk_abcdefghijklmnopqrstuvwxyz1234567890"},
{"xai", "xai-abcdefghijklmnopqrstuvwxyz1234567890_0123456789"},
{"huggingface", "hf_abcdefghijklmnopqrstuvwxyz1234567890"},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
result := RedactSecrets(tc.secret)
if result != "[REDACTED]" {
t.Errorf("%s key not redacted: %q → %q", tc.name, tc.secret, result)
}
})
}
}

func TestRedactSecrets_GitHubToken(t *testing.T) {
tests := []string{
"ghp_abcdefghijklmnopqrstuvwxyz1234567890",
Expand Down
Loading