Security
Security is layered. Bypassing one layer trips another. Six independent controls:
- Permission profiles — which tools the agent can even reach for
- Access control — which users can talk to the agent at all
- Credential proxy — API keys never touch the SDK directly
- SSRF + DNS-rebinding guards — outbound HTTP is IP-checked and pinned
- Prompt-injection sanitizer — LLM input goes through NFKC + pattern redaction
- Input validation — every tool argument is validated before dispatch
Permission profiles
Section titled “Permission profiles”Set via codebuddy.permissionScope.defaultProfile. Profiles filter the tool list at construction:
| Profile | Behavior |
|---|---|
restricted | Read-only. No terminal, no writes, no browser. Read tools + search + think. |
standard | Read/write. Terminal requires modal approval outside a small safe-command list. Default. |
trusted | Same tools; auto-approves the safe-command list. Catastrophic patterns still deny. |
Catastrophic patterns deny in ALL profiles including trusted:
| Category | Pattern |
|---|---|
| Destructive fs | rm -rf /, rmdir / |
| Disk wipe | mkfs*, dd of=/dev/* |
| Fork bomb | :(){ :|: & };: |
| Remote-code exec | curl … | bash, wget … | python |
| Privilege escalation | chmod 777, chown root |
Custom per-workspace additions in .codebuddy/permissions.json:
{ "profile": "standard", "commandDenyPatterns": ["docker rm", "kubectl delete namespace"], "toolAllowlist": [], "toolBlocklist": ["browser"]}Limits: max 200 chars per pattern (ReDoS defense), max 64 KB config file, patterns compiled at load time.
Access control (team mode)
Section titled “Access control (team mode)”AccessControlService gates who can invoke the agent. Modes (codebuddy.accessControl.defaultMode): open (default — no restrictions) / allow (only listed users) / deny (block listed users). Overridden by .codebuddy/access.json if present.
Identity resolution: GitHub auth first, git config user.email fallback. 5-minute cache. Email format validated against RFC patterns; GitHub usernames validated as 1–39 alphanumeric/hyphen.
Audit log: 500-entry ring buffer, throttled to one write per 100 ms.
interface AccessAuditEntry { timestamp: number; user: string; action: string; allowed: boolean; }Credential proxy
Section titled “Credential proxy”Enable via codebuddy.credentialProxy.enabled. When on:
- Keys stay in OS keychain; a localhost-only HTTP proxy injects them into upstream requests. Keys never appear in logs, memory, or agent context.
- Per-provider token-bucket rate limit.
- Short-lived session tokens via
X-CodeBuddy-Proxy-Tokenheader. - 10 MB body cap, 5-min upstream timeout, 30-s idle client timeout.
- 1000-entry audit ring buffer.
See Credential Proxy for the full setup.
SSRF + DNS rebinding
Section titled “SSRF + DNS rebinding”validateOutboundUrlAsync applies to every outbound HTTP the extension itself makes (MCP SSE, Langfuse OTLP, browser navigation, telemetry). Enforces:
- Protocol allowlist:
http:/https:only. - Address block: RFC1918, loopback (127.x, ::1), link-local (169.254.x, fe80::/10), CGNAT (100.64/10), IPv6 unique-local (fc00::/7), IPv4-in-IPv6 mapped equivalents.
- Encoding-obfuscation resistant: catches octal (
0177.0.0.1), decimal (2130706433), hex (0x7f000001). - Length caps: hostname ≤ 253, path ≤ 2048, total URL ≤ 8192.
DNS-rebinding pinning: validateAndPinOutboundUrl additionally returns a pinned dns.LookupFunction that fixes the resolved IP for the socket connect. Applied to Langfuse + generic OTLP as of 2026-07-10. MCP SSE + Playwright browser paths still use the un-pinned variant (they need a dep or wrapper to migrate; tracked).
Post-navigation DNS-rebinding backstop on browser navigation as an extra safety net.
External security config
Section titled “External security config”.codebuddy/security.json at workspace root. Loaded at activation, reloads on change.
{ "allowedPaths": [ { "path": "/shared/configs", "allowReadWrite": false, "description": "Read-only shared configs" } ], "commandDenyPatterns": ["docker\\s+system\\s+prune", "kubectl\\s+delete\\s+namespace"], "networkAllowPatterns": ["^https://api\\.example\\.com"], "networkDenyPatterns": ["^https?://169\\.254\\.169\\.254"], "blockedPathPatterns": [".credentials", "secrets"]}Always-blocked path segments (no config needed):
.ssh, .gnupg, .gpg, .aws, .azure, .gcloud, .kube, .docker, credentials, .netrc, .npmrc, .pypirc, id_rsa, id_ed25519, id_ecdsa, id_dsa, private_key, .secret, .env, .env.*, .token, .htpasswd
Always-blocked network patterns:
^https?://169.254.169.254— AWS/GCP metadata endpoint^https?://metadata.google.internal— GCP metadata^https?://168.63.129.16— Azure IMDS^https?://0.0.0.0— Localhost-with-creds edge case
Limits: 50 user patterns per category, 10 000 chars max input, invalid regex logged + skipped (fail-open per pattern, fail-closed per category).
Prompt-injection defense
Section titled “Prompt-injection defense”Every LLM-bound text goes through sanitizeForLLM():
- NFKC normalization — prevents homoglyph attacks (Cyrillic-looks-like-Latin).
- Pattern redaction — 15+ regexes replace malicious text with
[REDACTED]. - Hard cap — 8000 chars max per input.
Detected patterns:
| Category | Examples |
|---|---|
| Instruction override | ”ignore previous instructions”, “disregard all previous”, “forget everything” |
| Role hijack | ”you are now”, “act as jailbreak”, “pretend you are” |
| Special tokens | [INST], [/INST], <|im_start|>, <|im_end|>, <|endoftext|> |
| Structural markers | system:, assistant:, human:, <system>, <prompt>, <instruction> |
MCP responses additionally wrapped in <mcp_response server="…" tool="…" trust="untrusted">…</mcp_response> envelopes with nested closing tags defanged.
Input validation
Section titled “Input validation”InputGuard at every tool boundary:
| Validation | Rule |
|---|---|
| Browser element refs | Max 512 chars; null bytes, control chars, shell metachars blocked |
| Keyboard keys | ^[A-Za-z0-9+\-_]{1,64}$ |
| File paths | WorkspaceIdentityService.validatePathWithinWorkspace() — symlink-resolving |
| URLs | Protocol + length via NavigationGuard |
Data privacy
Section titled “Data privacy”- Local by default. All extension data (memory, checkpoints, logs, skills, audit) stays on disk under
.codebuddy/. Never uploaded. - Direct provider calls. Requests go straight to your provider (or to the credential proxy on localhost). No CodeBuddy-controlled intermediary.
- Telemetry opt-in. No data leaves your machine unless you explicitly configure Langfuse or another OTLP endpoint.
- Filesystem permissions.
.codebuddy/is0700, DB files are0600, atomic write-temp-rename to prevent inspection during writes. At-rest encryption (SQLCipher) is on the roadmap. - API key handling. Keys live in the OS keychain via
SecretStorageService, never insettings.json. Cache is zeroed with null bytes ondispose()so heap snapshots can’t recover plaintext.
When to reset
Section titled “When to reset”| Situation | Command |
|---|---|
| MCP server config changed | CodeBuddy: Reset MCP Server Approvals |
| Skill installer prompted for the wrong file | CodeBuddy: Reset Skill Installer State |
| Detected identity wrong (team mode) | CodeBuddy: Reset Access Cache |
| Doubt about any of the above | CodeBuddy: Run Doctor |
Related
Section titled “Related”- Access control — allowlist/denylist team mode
- Credential proxy — full proxy setup
- Permission scoping — permission-scope JSON
- Telemetry — Langfuse + OTLP wiring (with DNS pinning)