Skip to main content

Setup & configuration

Privacy & Security

This page covers how Bodega One handles your data and what runs to protect it - local storage, network isolation, shell command controls, file sandbox boundaries, and content guardrails.

Your data stays local

When you use a local provider (Ollama, LM Studio, llama.cpp, vLLM, etc.), all inference runs on your hardware. Conversation history, long-term memory, and your knowledge base are stored in a local SQLite database. Conversation content is never uploaded, and there is no usage analytics. Bodega keeps a local-only diagnostics trace on disk (never transmitted). Two optional paths can reach the network, and both are disabled by Air-Gap mode: crash reporting (off unless you opt in AND a crash key is built into the release) and - on the beta build - the email you enter on the activation screen (added to our beta mailing list).

A third, audit-oriented path is the optional OTLP audit export (telemetry.otlp_export_enabled, off by default; configured via settings keys only, no UI yet): it batches telemetry events and POSTs them to a local OpenTelemetry collector you run. The endpoint is validated against the localhost allowlist before every POST, so a non-loopback URL is rejected even with air-gap off - a loopback POST never leaves the machine, which is why this path is intentionally not blocked by Air-Gap mode. It also inherits the general.telemetry_enabled master gate.

To confirm your active provider: the model picker in the top bar shows which provider is in use. To change providers, go to Settings → Models → Providers.

If you use a cloud provider (OpenAI, Anthropic, Groq, etc.), your messages go to that provider's servers. Bodega One does not sit in the middle or store copies - but those messages do leave your machine. Switch to a local provider for full network isolation.

The diagnostics bundle (Settings → About → Export Diagnostics Bundle) stays on disk. Review it before sharing publicly - it can include file paths and command strings you have typed.

Where your data lives (and how to remove it)

Everything Bodega stores lives in one per-user folder - nothing is scattered across your system:

  • Windows: %APPDATA%\Bodega One
  • macOS: ~/Library/Application Support/Bodega One
  • Linux: ~/.config/Bodega One

Inside it: the SQLite database (bodega.db - conversations, memory, knowledge base), logs/, a local-only telemetry/ trace, downloaded models (llama-models/) plus the managed llama-server binary, and code-session worktrees (bodega-worktrees/). The diagnostics bundle (Settings → About → Export Diagnostics Bundle) prints the exact path at the top of the file.

Removing Bodega cleanly: the uninstaller removes the application but deliberately leaves this data folder in place - re-downloading multi-GB models on every reinstall would be painful. To wipe everything, uninstall and then delete the folder above. On Windows the uninstaller reminds you of this on screen.

Air-gap mode

Air-gap mode is a single toggle that activates 20 independent enforcement layers across the app. When on, agent tools cannot make outbound connections, the auto-updater is disabled, cloud speech-to-text is blocked, Cloud Boost routes to local, and license verification enters a grace period.

To enable:

  1. Open Settings → Privacy & Safety
  2. Under Network Privacy, toggle Air-Gap Mode on
  3. A toast confirms the change

To turn it off, toggle it again.

When air-gap is active, an Air-Gap badge appears in the top bar (shield icon + "Air-Gap" label) as a persistent visual confirmation. It is always visible while air-gap mode is on - you never have to open Settings to verify the status.

What air-gap blocks:

  • web_search and web_fetch tool calls
  • Shell commands that make network requests (curl, wget, git clone/fetch/pull/push, npm install, pip install, cargo install, go get, docker pull, and script execution via node, python, deno)
  • Cloud speech-to-text (fall back to local faster-whisper-server)
  • Auto-update checks - the Check for Updates button shows "Blocked (air-gap)"
  • Cloud Boost (re-routed to local provider)
  • License verification calls
  • llama.cpp binary install and GGUF download endpoints (return 403)
  • HuggingFace model search and repo file access (return empty results)
  • Ollama binary install and model-hub pull (return 403)
  • Remote model-catalog refresh (the optional External Model Catalog auto-update is refused - Bodega never fetches the remote catalog in air-gap mode and falls back to the bundled/cached catalog)
  • MCP servers that are marked as requiring network access, including any MCP server configured with a remote (non-stdio) URL transport - refused at the point you save it, not just at connect time
  • Non-loopback Ollama embeddings - if your embeddings provider is Ollama pointed at anything other than localhost/127.0.0.1 (a LAN or tunneled Ollama), embedding calls are refused rather than silently sending document text off the machine
  • Any resolved LLM provider endpoint that isn't loopback - this applies at settings-save time and at request time, so a preset misconfigured to point at a non-loopback address is caught even if it wasn't openai. As of beta.38 the request-time check covers the Ollama and Anthropic chat paths too (previously only the OpenAI-compatible path was guarded at request time), and the two background probes - refreshing a provider's model list and its health badge - refuse the same way instead of quietly making a real network call. The probes fail soft (empty list / unhealthy badge) so opening the model picker under air-gap never errors. The Cloud Boost health probe at startup is skipped entirely rather than dispatched and refused after the fact
  • The agent-controlled browser's non-localhost navigation (preview_interaction's navigate/submit actions against a real website, not your local dev server) - refused before the request goes out
  • Any persisted agent-browser logins - turning air-gap on wipes every site holding "keep the agent signed in" persistence, with a confirmation that names the affected sites before it clears them. (Live guests on those sites are stopped before the wipe, so a page still running cannot write cookies back into the jar as it is being cleared.) Per-site persistence is live for all users, so this is a real wipe, not a legacy cleanup - see the Agent Browser page.

What air-gap does not block: your LLM provider's own traffic. If you have a cloud LLM configured, those messages still go out. Use a local provider for complete isolation.

Known limitation: the warning banner in Settings only fires when your provider preset is specifically openai. If you have Anthropic, Groq, Azure, or another cloud preset configured with air-gap on, no banner appears. Check your provider selection manually. (The underlying enforcement above is not limited to the openai preset - this limitation is only about the banner.)

Agent web browsing is off by default

The agent can drive a real browser. Reaching anything other than localhost is gated by browser.widened_enabled, which ships off - separately from air-gap, and independently of it.

With the setting off, the widened capability is not described to the model at all, and a non-localhost navigate is refused at dispatch even if a stale session tries one. Your own localhost preview is never gated by this.

The setting is global-only - deliberately not project-config-whitelisted, so a repo you clone cannot enable outbound agent browsing on your machine. Turning it on requires confirming a dialog, and takes effect on the next new chat or session rather than mid-conversation.

Once it is on, each new site still needs your explicit approval, and typing and form submits are gated per call. The full model - approvals, per-site logins, what the agent cannot do - is on the Agent Browser page.

The honest risk, stated the same way the Settings dialog states it: a page the agent reads can contain text written to steer it, and nobody has a mechanical fix for that. The outbound-URL scanning catches plaintext secrets, not encoded ones. It is a speed bump, not a boundary.

Air-Gap Vault: what a per-project vault does not cover

A project can be marked as an Air-Gap Vault, which forces air-gap on for that project's tool calls even when the global toggle is off. It is tighten-only: a vault can never turn a globally-enabled air-gap off.

Coverage is a hand-maintained list. The agent browser and vision routing were both fixed on 2026-08-10: for a vaulted project, a non-localhost navigate or submit and a non-local vision call are all refused at dispatch, through getEffectiveAirGap(projectVault). preview_script inherits that refusal because it runs each of its steps through preview_interaction.

One half is still global-only: the registration-time gate. The tool array is built from the global general.air_gap value, so under a vault with the global toggle off the widened browsing affordance is still described to the model - it is refused when the model tries to use it, rather than never mentioned. Everything the vault is meant to stop is stopped; it is stopped by refusal, not by silence.

If you want a project offline with no refusals to read at all, turn the global air-gap toggle on as well.

Air-gap attestation

For audit or compliance needs, Bodega can produce a signed, tamper-evident record of its air-gap enforcement. The Copy attestation button at Settings → Privacy & Safety → Network Privacy generates an HMAC-signed artifact summarizing, over the window since the app started, the count of recorded outbound fetches and the per-category ledger of air-gap refusals.

The HMAC secret never leaves the machine, and building the attestation reads local counters only - it makes no outbound call (so it works under air-gap). The artifact can be re-verified later, even on another machine, via the verify endpoint.

Honest scope: the attestation reflects recorded outbound fetches and counted refusals - it is not a guarantee that zero bytes ever left the box. The outbound counter observes the app's fetch paths; commands run through child_process (e.g. a shell curl) bypass that counter, which is why air-gap also blocks network-making shell commands at the tool layer.

Shell command security

The shell tool that the agent uses to run terminal commands has four independent layers of protection:

1. Blocked-pattern list. A fixed list of destructive and credential-reading commands is rejected before execution. This is hard-coded and applies regardless of permission mode.

2. Filtered child-process environment. Shell commands run with a curated subset of environment variables (paths, build-tool vars, locale) - not your full process.env. This prevents API keys, tokens, or other secrets set at the OS level from leaking into command output. Added in beta.26. This filtering is configurable via the global shell.env_policy setting with three levels: core (default - the historical allowlist of path, build-tool, and locale variables), none (bare shell essentials only), and full (pass the whole process.env through - opt-in, and still credential-scanned by layer 3). The shell.env_allow_extra setting additively widens core or none with named variables you trust (e.g. a build token). Both keys are GLOBAL-only - never project-config-whitelisted, so a committed repo config cannot widen a teammate's env exposure - and FORCE_COLOR=0 is always applied last.

3. Output credential scanning. After every command, the output is scanned and matched secrets are replaced with [REDACTED: type] before the result reaches the agent. Patterns covered:

  • SSH private keys
  • AWS access keys (AKIA...)
  • GitHub PATs (ghp_, gho_)
  • OpenAI API keys (sk-..., sk-proj-...)
  • Anthropic API keys (sk-ant-...)
  • Slack tokens, JWTs, PGP private keys
  • Database connection strings (mongodb://, postgres://, etc.)
  • Inline secrets in KEY=value or KEY: "value" forms
  • High-entropy strings over 40 characters. This last rule measures actual Shannon entropy and character-class diversity rather than just matching the base64 alphabet, so low-alphabet data is left intact: DNA/RNA sequences, hex digests and git SHAs, UUIDs, long decimal numbers, and source-code identifiers are not redacted. Every other pattern above is prefix-anchored and always applies

4. Project-directory enforcement. The working directory cannot be outside your project root. The Windows type command (equivalent to cat) is also blocked to prevent indirect credential reads.

Approval behavior: In Ask mode (the default for new sessions), every shell command pauses for your approval before it runs. An approval card appears in the Agent panel, on the tool call it belongs to, with Allow and Reject buttons (Enter and Esc). One card per pending call: the card shows the exact command it will approve.

In Ask mode, shell can never auto-approve. Ask mode has an opt-in "auto-approve reads" tier (agent.auto_approve_reads, off by default) that stops prompting for a short list of read-only tools - grep, glob, code_search, find_symbol, and the four query_* tools. shell sits on a permanent never-auto-approve list alongside web_fetch, web_search, github_context, and github_pr_create, so no tier setting and no learned rule can ever add it. github_pr_create goes further: it asks for your approval on every call in every mode, including Act - opening a pull request is externally visible, so it never rides Act mode's silent approval. Plan mode never auto-approves anything at all.

Act mode is different, and you should read it as what it says. In Act mode there is no approval prompt for any tool, shell included. That is the point of the mode. The layers above - blocked patterns, filtered environment, output credential scanning, project-directory enforcement - still apply and are not affected by the mode. What Act mode removes is your click, not the sandbox.

To change the approval mode, use the Ask / Act / Plan pill in the Agent panel header.

Agent permission modes

The Agent panel mode controls when the agent waits for your approval:

Mode File writes Shell commands Reads and searches
Act Automatic Automatic Automatic
Ask (default) Pause for approval Pause for approval Automatic
Plan Blocked until plan approved Blocked until plan approved Automatic

Switch modes with the pill in the Agent panel header. Research, Debug, and Advisor panels are always read-only regardless of this setting.

Shell Command Sandbox (Settings → Privacy & Safety → Content Filtering) adjusts what counts as "safe" in Ask and Act modes:

  • Off - every shell command requires manual approval
  • Moderate - safe commands auto-approve after a 30-second timeout
  • Strict - only read-only commands auto-approve

Note: the sandbox level affects the approval UI flow in Ask mode, not the underlying security gate. It cannot make shell auto-approve in Ask mode - that is blocked structurally. It also has no bearing on Act mode, where nothing prompts in the first place.

Approvals are a Code Mode mechanism. The per-tool approval gate runs for code-mode sessions. Chat Mode does not prompt at all - it is constrained by a much smaller tool set instead. shell and run_tests are never offered there, in any configuration.

Chat Mode's file access is folder-aware, and worth understanding:

  • No folder attached - reads only. file_system write, delete, create, append, rename, and mkdir are refused, and str_replace is not available.
  • Folder attached (chat menu → link a project folder) - str_replace becomes available and file_system writes are allowed, scoped to that folder, without an approval prompt. Attaching a folder to a chat session is the moment Chat Mode gains the ability to change your files.

Smart approval - fewer interruptions in Ask mode

Ask mode pauses for approval on every tool call, which is safe but noisy when the agent is just reading around. Smart approval lets a low-risk, read-only tool call auto-approve itself when it clearly matches what you just asked for - so a grep, a glob, or a web search the agent obviously needs doesn't make you click.

The security posture is the important part. Smart approval can only ever shrink the set of things you're asked about; it can never widen it:

  • Write tools are hard-blocked from auto-approval. shell, file_system writes, and str_replace always wait for your click.
  • The classifier runs last, after every existing security gate (the read-only predicate, the shell hard-block, command-risk classification). It is the final narrowing step, not a bypass.
  • It only acts in Ask mode (Act has no approval prompt anyway).
  • This first version uses static heuristics - no model call, no network - so it adds no latency and is safe under air-gap.

When a call auto-approves, no approval card appears at all. When it can't, you get the normal prompt as before.

It is off by default. Turn it on with permission.smart_approval, and set how confident the classifier must be before it auto-approves with permission.smart_approval_confidence_threshold (default 0.80). Both are global settings - a repo's project config can never flip them on for you.

Approval learning - review and accept repeated approvals

Bodega records repeated approval decisions in Ask mode as candidates for friction-reduction: approve the same kind of moderate-risk shell command five separate times with zero rejections, and it's logged as a suggestion in Settings → Privacy & Safety → Learned Approval Rules. Dangerous-tier commands are never eligible, and nothing changes automatically - a suggestion sits there until you explicitly accept or dismiss it.

Accepting a suggestion only changes Act mode behavior: that exact command shape auto-approves in Act mode from then on, with no approval card. Ask mode is untouched by acceptance - it keeps asking every time, regardless of what you've accepted, and there is no code path where an accepted rule applies while Ask mode is active. You can revoke an accepted rule at any time from the same panel; revoking takes effect immediately, on the very next matching command - no restart needed.

Accepting is deliberately app-only - there is no CLI command to accept a suggestion, so a headless run can never widen its own permissions. Already-accepted rules DO apply to headless bodega run sessions when that run itself is in Act mode; a headless Ask-mode run gets no auto-approve benefit from this feature.

Advanced gating: permission profiles & lifecycle hooks

Beyond Ask/Act/Plan, two power-user controls let you constrain or extend what the agent does. Both only narrow or gate access - never widen it.

Named permission profiles. A profile is a reusable named rule set that denies tools by name, or allows/denies file writes by path glob - e.g. a readonly profile, or one that confines edits to src/** and blocks src/secrets/**. Activate one and every tool call is checked against it (after hooks, before the mode firewall). A profile can only restrict access; it composes with Ask/Act/Plan.

Edit profiles and pick which one is enforced at Settings → Agent → Permission Profiles. Rules are evaluated in a fixed order: a tool on the deny list is refused; if the allow list is non-empty, a tool absent from it is refused; then for a tool carrying a file path, the last matching path rule decides. Anything not decided by a rule is allowed. Both settings keys (permission.profiles, permission.active_profile) are global-only - they are deliberately not in the project-config whitelist, so a repo you clone cannot install or activate a profile on your machine. There is no CLI command to edit or activate a profile; a headless bodega run enforces whatever profile you activated in the app, because the gate lives in the shared backend.

Lifecycle hooks. Run your own shell command at six points: before a tool (PreToolUse), after a tool (PostToolUse), when a session first runs a turn (SessionStart), on every prompt you send (UserPromptSubmit), when a turn ends (Stop), and when a session is deleted (SessionEnd) - to lint, format, validate, log, or block. Two of them can stop work: a PreToolUse hook can stop the tool (exit code 2) or rewrite its input, and a UserPromptSubmit hook can stop the whole turn before the model is called. PostToolUse, SessionStart, Stop and SessionEnd observe only - they run, but they cannot cancel what already happened. Trust model: hooks you write yourself are trusted, but hooks committed in a repo's .bodega/config.json are never run until you explicitly approve them for that exact command + project - so cloning a repo can't run a hook behind your back.

Settings → Privacy & Safety → Lifecycle Hooks is where all of this lives: write your own hooks, approve or revoke this project's hooks, and read the load warnings. Approval is per exact command and project path - editing an approved hook's command changes its identity and sends it back to the pending list, and approving it in one repo approves nothing in another. Revoking takes effect on the very next tool call. Hooks can also be switched off wholesale with the hooks.enabled setting.

App hooks and CLI hooks are separate, and always will be. The app reads project hooks from .bodega/config.json and your own hooks from its settings database. The Bodega One Code CLI reads project hooks from .bodega.yml and your own hooks from ~/.config/bodega/settings.yaml. Neither reads the other's files, so hooks you write for the CLI do not run in the app and vice versa - if you want a hook in both places, write it in both. If a project has a .bodega.yml with a hooks: block, the app shows a warning in that Settings panel saying those hooks will not run and where to move them. The same panel reports every other reason a configured hook did not load: a mistyped event name, an entry with no command, or a .bodega/config.json that isn't valid JSON - cases where the hook would otherwise be indistinguishable from one you never wrote.

Plan mode and approval persistence

In Plan mode, the agent writes a plan and stops. Nothing touches your files until you approve. Plan approvals persist to the database - they survive disconnects and app restarts. You can close the app after the agent produces a plan and come back later; the approval card will still be there.

This is intentional for long-running or background sessions.

Tool-level approvals (Ask mode prompts for individual file writes or shell commands) work differently: closing the app cancels the auto-decision timer and leaves the card waiting for you to come back. Only an anonymous session with no way to reattach still rejects. The session continues when you reconnect.

File system sandbox

The file_system tool the agent uses to read, write, and delete files is sandboxed to your open project folder. The boundary is set to whatever folder you opened in Code mode.

Requests that escape the boundary are rejected:

  • Path traversal attempts (../)
  • Absolute paths outside the project
  • Symlinks whose real targets point outside the project (the symlink target is resolved against the actual filesystem, not the symlink path)

Write and append operations also block a set of dangerous file extensions.

Errors from out-of-bounds attempts appear as tool call failure cards in the Agent panel.

The shell tool uses a separate enforcement path. Its working directory is blocked from escaping the project root. These two sandboxes are independent - passing the file system sandbox check doesn't bypass the shell sandbox, and vice versa.

Web fetch SSRF protection

The web_fetch tool blocks requests to private network addresses. This prevents an agent from being tricked into fetching internal services on your network (Server-Side Request Forgery).

Blocked targets:

  • All private IPv4 ranges: 127.x, 10.x, 192.168.x, 172.16–31.x, 169.254.x
  • localhost, 0.0.0.0
  • IPv6 loopback (::1), link-local (fe80::), and unique local (fc00::/7, covers fc* and fd*)
  • IPv6-mapped IPv4 addresses (::ffff:)
  • Decimal and hex integer IP notation (e.g. 2130706433 resolves to 127.0.0.1)

Only http: and https: are allowed. Other protocols are rejected.

DNS rebinding protection: after the hostname passes the initial check, it is resolved to an IP address and that IP is rechecked against the private ranges. This catches hostnames that look public but resolve to a private address.

Read-only panel enforcement

The Research, Debug, and Advisor panels cannot write files or run shell commands. This is enforced in ToolPreExecutionGuards - the guard fires at the pre-execution layer, before any tool runs, regardless of what mode the Agent panel is set to.

If you need the agent to take an action based on what Research or Debug found, copy the relevant output and send it to the Agent panel. Research also has a Save to Knowledge Base button that pins findings without writing project files.

Content guardrails

Guardrails apply filters to user messages before the agent processes them.

To configure:

  1. Open Settings → Privacy & Safety → Content Filtering
  2. Toggle Enable Guardrails on
  3. Adjust the sub-settings below:
Setting What it does Range
Custom Blocked Patterns One regex per line - messages matching any pattern are rejected Any valid regex
Shell Command Sandbox Auto-approval behavior for shell commands Off / Moderate / Strict
Max Input Length Maximum characters per message 100–100,000
Messages Per Minute Rate limit for incoming messages 1–200
Session Token Budget Maximum tokens consumed per session 1,000–1,000,000
  1. Click Save Safety Settings to apply

When Enable Guardrails is off, the entire Content Filtering section is dimmed and none of the sub-settings apply.

Custom Blocked Patterns are validated as you type - invalid regex lines show a red error with the line number. Changes don't apply until you save.

MCP server network access control

Each MCP server has a Network Access flag set when you add it in Settings → MCP Servers. When air-gap mode is on:

  • Servers marked as requiring network access cannot connect
  • New server additions with network access return 403
  • If a network-access server is already connected, tool calls to it are intercepted and blocked at the pre-execution layer

The network access flag is self-reported at configuration time - the app cannot verify whether a server actually makes outbound calls. The flag defaults to true (network access required) for new servers, which is the safer default.

The legacy single-server MCP endpoint blocks entirely in air-gap mode, not just network-access servers.

Memory and knowledge base

Memory entries and knowledge base cards are stored in local SQLite. Every query includes a user_id filter - cross-user memory access is treated as a security bug, not a missing feature.

Nothing is synced or backed up externally. To see what the agent remembers, ask it: "What do you remember about me?" To remove a specific entry, say: "Forget [fact]".

Knowledge base entries are managed via gear menu → Knowledge.

API server authentication

Bodega One can expose an OpenAI-compatible API on port 1337 (Settings → API Server). This lets external tools - Claude Code, Continue, Cursor, custom scripts - use Bodega as an LLM backend.

The server binds to localhost only and is not exposed to your network by default. When no API key is set, it accepts unauthenticated local requests. If other users or processes can reach port 1337 on your machine, set an API key in Settings → API Server → API Key - requests will then require it as a Bearer token.

Tips

Attaching a folder to a chat session is a permission change. It is presented as a convenience - link a folder so the agent can see your code - but it is also what gives Chat Mode the ability to write files, with no approval prompt. If you only want the agent to read, don't attach the folder; ask about files in Code Mode instead, where Ask mode applies.

Air-gap does not cover your LLM provider. It blocks the agent's tools, updates, cloud speech-to-text, Boost, and the agent browser - not the model you chose. With a cloud model configured, your messages still leave the machine with air-gap on. Only a local provider gives you full isolation, and the Settings warning banner only fires for one specific provider preset, so check your provider yourself.

Turning on air-gap deletes things, it doesn't just block them. Persisted agent-browser logins are wiped, not suspended. You get a confirmation naming the sites first - read it, because turning air-gap back off does not restore them.

The attestation counts what it can see, and says so. It reflects recorded outbound fetches through the app's own fetch paths and counted refusals. A command run through the shell - a curl - bypasses that counter entirely, which is why air-gap also blocks network-making shell commands at the tool layer. Read it as evidence, not proof.

A learned approval rule only ever affects Act mode. Accepting a suggestion in Learned Approval Rules does not make Ask mode quieter - Ask keeps prompting every time regardless of what you have accepted. And acceptance is app-only by design: a headless run can never widen its own permissions.

Keyboard shortcuts

KeysAction
Ctrl+1Switch to Chat mode
Ctrl+2Switch to Code mode
Ctrl+LOpen Agent panel

This page mirrors the in-app docs hub for app version 1.0.0-beta.41. Found something unclear or out of date? Tell us on Discord. New here? Download the free beta and follow along.