Skip to content

Security

Security is layered. Bypassing one layer trips another. Six independent controls:

  1. Permission profiles — which tools the agent can even reach for
  2. Access control — which users can talk to the agent at all
  3. Credential proxy — API keys never touch the SDK directly
  4. SSRF + DNS-rebinding guards — outbound HTTP is IP-checked and pinned
  5. Prompt-injection sanitizer — LLM input goes through NFKC + pattern redaction
  6. Input validation — every tool argument is validated before dispatch

Set via codebuddy.permissionScope.defaultProfile. Profiles filter the tool list at construction:

ProfileBehavior
restrictedRead-only. No terminal, no writes, no browser. Read tools + search + think.
standardRead/write. Terminal requires modal approval outside a small safe-command list. Default.
trustedSame tools; auto-approves the safe-command list. Catastrophic patterns still deny.

Catastrophic patterns deny in ALL profiles including trusted:

CategoryPattern
Destructive fsrm -rf /, rmdir /
Disk wipemkfs*, dd of=/dev/*
Fork bomb:(){ :|: & };:
Remote-code execcurl … | bash, wget … | python
Privilege escalationchmod 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.

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

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-Token header.
  • 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.

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.

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

Every LLM-bound text goes through sanitizeForLLM():

  1. NFKC normalization — prevents homoglyph attacks (Cyrillic-looks-like-Latin).
  2. Pattern redaction — 15+ regexes replace malicious text with [REDACTED].
  3. Hard cap — 8000 chars max per input.

Detected patterns:

CategoryExamples
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 markerssystem:, assistant:, human:, <system>, <prompt>, <instruction>

MCP responses additionally wrapped in <mcp_response server="…" tool="…" trust="untrusted">…</mcp_response> envelopes with nested closing tags defanged.

InputGuard at every tool boundary:

ValidationRule
Browser element refsMax 512 chars; null bytes, control chars, shell metachars blocked
Keyboard keys^[A-Za-z0-9+\-_]{1,64}$
File pathsWorkspaceIdentityService.validatePathWithinWorkspace() — symlink-resolving
URLsProtocol + length via NavigationGuard
  • 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/ is 0700, DB files are 0600, 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 in settings.json. Cache is zeroed with null bytes on dispose() so heap snapshots can’t recover plaintext.
SituationCommand
MCP server config changedCodeBuddy: Reset MCP Server Approvals
Skill installer prompted for the wrong fileCodeBuddy: Reset Skill Installer State
Detected identity wrong (team mode)CodeBuddy: Reset Access Cache
Doubt about any of the aboveCodeBuddy: Run Doctor