Security

August 14, 2026 · View on GitHub

English | 中文

Recovered images are private

Images pulled out of session storage are written 0600 into a 0700 directory. By default that directory is a fresh, unpredictable <tmpdir>/deepsee-paste-* minted per run, so nobody on a shared machine can pre-create a known path (recursive mkdir leaves an existing directory's mode alone) and read the bytes. A pasted screenshot can hold anything. An explicit --out-dir is honoured but refused when unsafe: it must be a real directory, not a symlink, owned by you, with no group or world access.

Recovery is also scoped to one project: the working directory recorded inside the transcript is checked, not just the directory name, because directory slugs collide (/tmp/a.b and /tmp/a-b produce the same one). A neighbouring project's images are never handed over.

Settings and API keys

DeepSee's dsh settings panel writes to ~/.deepsee/config.json, which is created with 0600 permissions. Multiple Gemini keys are accepted, but the settings GET endpoint returns only keyCount; saved key values are never sent back to the browser or rendered into config show. The write endpoint accepts JSON only and rejects cross-origin browser requests, reducing the risk of an unrelated webpage changing local settings through the loopback server.

Gemini keys are used only by the vision provider. Auto and Customize force coding subtasks onto live DeepSeek provider routes; a Gemini credential is never forwarded to a coding child. Provider error text is scrubbed against every configured Gemini key before it is surfaced.

Visual self-checks also force the gemini-api provider. The initial preview URL must be loopback HTTP(S) without credentials, and Chrome/Chromium/Edge receives a fresh temporary profile with no stored sessions and no flags that disable the browser sandbox, web security, or origin checks. A local page can still load or navigate to resources allowed by ordinary browser rules, so point the tool only at a development server you trust. The profile is removed after the screenshot; the screenshot itself remains at the reported temporary path so the user can audit the exact artifact Gemini judged. Images are limited to 25 MiB, and identical checks are cached only in process memory.

Permissions passed to engines

DeepSee invokes agy with --dangerously-skip-permissions because prompt mode fails in some environments without it. The prompt restricts the agent to reading the one image it was given, and instructs it to treat image content strictly as data.

The claude-cli provider runs with --allowedTools Read only, so it can read local files and nothing else.

Both subprocess providers also run in a throwaway directory created fresh per call and removed afterward. For a local image it holds a private copy of that one image and nothing else, and it is a real copy, never a hardlink, so a provider writing to its temp path cannot touch the original. For a remote image the directory is empty and the agent downloads into it. Without this, text inside an image could steer a broadly-permissioned agent into reading files next to the original, or whatever project the caller happened to be in. Passing --workdir opts out and runs where you point it.

This is exposure reduction, not an OS sandbox: the agent can still read absolute paths, reach the network, and spawn processes. Treat it as a narrower default, not a security boundary. For images you do not trust, prefer -p gemini-api, which downloads the bytes itself and runs no local agent. Remote URLs already prefer the inline region: the failover chain for a remote URL tries the inline API providers first and the agents last. Who actually fetches a remote URL differs per provider, and only a local download can be guarded locally:

ProviderWho fetches a remote URLLocal guards
gemini-apideepsee downloads, sends bytes inlineprivate-address guards, magic-byte check, 25 MB cap
openai, anthropicthe URL is passed to the vendor, which fetches itnone locally; the vendor's own fetching policy applies
antigravity-cli, agent CLIsthe agent fetches on its ownnone locally

So the private-address guards, the magic-byte check, and the size cap protect exactly the paths where deepsee itself downloads: every local file read, and gemini-api's remote fetch. An explicit -p pins one provider and overrides the chain.

Image content is untrusted input

Text inside an image is untrusted, the same as a web page. A screenshot can contain instructions aimed at whatever reads it. The prompt says so explicitly, but that is mitigation, not a guarantee: analyze images you are willing to open, and prefer a sandboxed working directory when they came from elsewhere.

Evidence, not invention

What the engine cannot read goes into uncertainty rather than being filled in. v2 dropped pixel coordinates and confidence scores entirely, because those are the two fields models fabricate most convincingly.