Skip to content

Security

Security is layered. Bypassing one layer trips another. Seven 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
  7. Sandboxed code execution — user- and model-authored code runs only in the QuickJS WASM sandbox, read-and-transform only

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 commands are denied in every profile, including trusted — destructive filesystem wipes, disk formatting/overwrite, fork bombs, pipe-to-shell remote execution, and privilege-escalation patterns. This denial cannot be re-enabled by configuration.

Custom per-workspace additions in .codebuddy/permissions.json:

{
"profile": "standard",
"commandDenyPatterns": ["docker rm", "kubectl delete namespace"],
"toolAllowlist": [],
"toolBlocklist": ["browser"]
}

Patterns are bounds-checked and 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, cached briefly. Every access decision is written to an audit log.

Enable via codebuddy.credentialProxy.enabled. When on:

  • Keys stay in the OS keychain; a localhost-only HTTP proxy injects them into upstream requests. Keys never appear in logs, memory, or agent context.
  • Per-provider rate limiting and short-lived session tokens.
  • Request size and timeout limits, with an audit log.

See Credential Proxy for the full setup.

Every outbound HTTP request the extension makes (MCP SSE, telemetry/OTLP export, browser navigation) is validated before it connects:

  • Protocol allowlist (http: / https: only).
  • Private, loopback, link-local, CGNAT, and cloud-metadata address ranges are blocked — including encoded/obfuscated forms of those addresses.
  • URL length bounds.

Outbound telemetry additionally pins the resolved IP to the socket connection to defeat DNS-rebinding, and browser navigation has a post-navigation rebinding backstop.

.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 (no config needed): access to well-known secret and credential locations — SSH/GPG keys, cloud-provider credential directories, dotenv (.env) files, and other recognized secret files — is refused regardless of settings. Outbound requests to cloud-metadata endpoints are likewise always blocked.

Invalid user patterns are skipped, and the number and size of patterns are bounded.

Untrusted text bound for the LLM is normalized (NFKC, to defeat homoglyph tricks), scanned for known instruction-override and role-hijack patterns which are redacted, and length-capped. MCP tool output is additionally wrapped in an untrusted <mcp_response … trust="untrusted"> envelope so the model treats it as data, not instructions.

Every tool argument is validated before dispatch — file paths resolve through validatePathWithinWorkspace() (symlink-resolving, workspace-scoped), URLs go through the outbound-URL guards, and other inputs are bounds- and character-checked.

Any code CodeBuddy didn’t ship — user/team scripts and model-authored REPL code — runs only inside the QuickJS WASM sandbox, never in the extension host. The sandbox is read-and-transform only: it cannot write files, spawn shells, or reach the network on its own; those paths stay behind the normal modal-approval tool flow. When the code interpreter can dispatch subagents, each subagent’s write reach is fenced. Details in Sandbox and Subagents.

  • 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.
  • Connector credentials in terminals. When a connector’s credentials are injected into a spawned command’s environment, they layer over the process environment additively, invalid names are dropped, and core system variables are never overwritten — so a key authenticates a CLI without appearing in the command string. See Connectors.
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