Additional

Security Guide for End Users

Synced from github.com/CoWork-OS/CoWork-OS/docs

This document explains the security model, permissions, and considerations for users who clone and run CoWork OS on their machines.

Overview

CoWork OS is an AI-powered task automation tool that can execute actions on your behalf. By design, it has capabilities that require careful consideration:

  • Execute command tools when the active access profile exposes them
  • Read and write files
  • Browse the web
  • Connect to external APIs

All of these capabilities are governed by the active access profile, consent rules, and hard guardrails, and are sandboxed where possible.

CoWork OS can also expose Chronicle, an opt-in desktop recent-screen context feature. Chronicle keeps a short local passive screen buffer to resolve vague on-screen references, but it does not send those passive screenshots to external providers by itself. Chronicle is configured from Settings > Tools > Chronicle, with pause/resume controls and explicit consent gating. See Chronicle.


Permissions Model

Access Profiles and Workspace Permissions

Each task has an effective access profile. Choose Ask for approval, Approve for me, Full access, or a validated custom profile from the main composer, or set the default in Settings > System & Security > Permissions. A profile combines sandbox, approval, reviewer, command-tool, filesystem, domain, and network policy. Workspace booleans remain coarse compatibility gates; the permission engine still decides whether a specific action should be allowed, denied, or prompted.

PermissionDescriptionDefault
ReadRead files within the workspaceEnabled
WriteCreate and modify filesEnabled
DeleteRemove files; still subject to explicit permission rules and approvalDisabled
Command toolsExecute commands when exposed by the active access profile; still subject to guardrails, sandboxing, and approvalProfile-controlled

Recommendation: Choose the least-privileged access profile that fits the task. Delete remains separate and should be enabled only where you trust the AI to remove files.

There is no separate shell enable/disable switch for a new task. Older tasks may retain a legacy shell field for compatibility, but a named profile is the authority for modern task command-tool availability. See Access Profiles for custom rules, inheritance, migration, and fail-closed behavior.

Approval System

Approval prompts are now part of a layered permission engine. For a named profile, work inside the granted boundary is ordinary execution: reads, writes, edits, artifact creation, and bounded local commands do not create an approval request. A profile with approval: "never" denies a missing boundary instead of waiting for a prompt.

The local runtime keeps the legacy tool-approval popup queue disabled by default. An allow decision runs silently; an unresolved ask becomes an assistant message plus a durable inline Deny / Allow once input card. This includes network/on-request access, credential use, data export, MCP and other external side effects, eligible outside-workspace paths, and explicit opt-outs from automatic approval. Set COWORK_APPROVAL_PROMPTS=on before launch only to restore the legacy queue for diagnostics. Hard guardrails still block dangerous commands before any approval path, and exact reasons identify the rule, profile boundary, or guardrail involved. Protected paths, administrator denials, approval: "never", and automated tasks without human input remain fail-closed. Pending approval rows and assistant cards fail closed on restart.

When the legacy queue is enabled, you can approve or deny each request individually and persist some approvals as session, workspace, or profile rules. In the normal runtime, respond to the inline assistant card for tool-policy decisions; it grants Allow once. Private browser input and native credential confirmations use the separate surfaces described below.

For the full evaluation order, rule precedence, and persistence model, see Permission System.

The composer access selector uses the same task-level profile model as the permission engine: Ask for approval, Approve for me, Full access, or a validated custom profile. A profile combines sandbox, approval, reviewer, command-tool, filesystem, and network boundaries. There is no separate shell enable/disable switch. Approval is not a substitute for sandboxing, and a custom profile cannot widen the restrictions inherited from its parent.

Private Browser Credentials And Checkout

The local Browser Workbench's Passwords & autofill menu and browser_vault share a main-process credential service. Enter passwords, authenticator setup keys, verification codes, addresses, cards, and manager unlock passwords only in its private dialog. Answers use owner-only IPC and stay out of chat, tool arguments, task events, and memory. Saved usernames and item metadata remain visible to the agent. Approved autofill supplies the private values to the destination page, which can read its own fields.

Saved login and saved-code access requires Touch ID where available or native confirmation. Address/card fills require native confirmation. Agent card fills also require fresh tool approval with automatic approval and standing grants disabled; this never authorizes placing an order. Navigation, cancellation, takeover, and changed task authority invalidate pending work.

Local saved items use OS-backed encryption. If it is unavailable, private input offers one-use entry with saving disabled. CVCs and entered verification codes are never saved. 1Password and Bitwarden require an installed, signed-in CLI; unlock sessions stay in memory, scoped to the workspace, and expire after 30 minutes idle. Their adapters supply logins and OTPs; checkout items come from the local vault.

Agent screenshots, page scripts, storage reads, and native computer control are blocked during private input and while a filled document remains active. Captures that overlap a private flow are discarded. Prompt protection lasts through the cleared UI's paint acknowledgement; document protection ends on full navigation or tab destruction. Text snapshots retain refs and mask filled values, including split verification codes and commonly formatted card numbers. This is a guard on CoWork's tool paths, not a system-wide screen-capture restriction.

The flow requires the local desktop Workbench and an available human-input channel. Headless, external, and cloud browser routes cannot substitute ordinary typing or shell commands for it. Passkeys, security keys, device approvals, and framed fields require a manual handoff. Inspect the page after fill_outcome_unknown before retrying; successful filling alone does not prove login. See setup, supported fields, and fixture-validation limits.

Agent Security with Numbat

CoWork can add a Numbat-backed agent-security decision before the ordinary permission and approval layers. The integration is disabled by default and receives a bounded projection of the pending tool call rather than the full task transcript. In Monitor mode it records findings without blocking; in Enforce mode a Numbat denial can block the action, but an allow result cannot grant permission, suppress an approval, or weaken sandbox and network controls.

Configure the runtime under Settings > System & Security > Agent Security or through runtime.agentSecurity in the admin policy. Keep enforcement off until binary provenance, health, and rules have been checked. See Agent Security with Numbat for defaults, failure policy, rule sources, CLI operations, retention, and incident case bundles.

Automation Studio Approvals

Main-sidebar Automation Studio classifies each step as read, local write, external write, or data export. New flows default to confirming external work; data exports always pause in a live run. A step's Skip for safe actions option applies only to read/local work and cannot suppress external-write or export approval.

Before turning on a flow:

  1. run a dry test and inspect the previewed destinations and payload shape;
  2. review every required Google scope and the selected account;
  3. keep connector allowlists narrow when a flow can call MCP tools;
  4. use a dedicated signing secret for each webhook receiver and rotate it by turning off affected flows first;
  5. verify any interrupted remote action before approving a post-restart retry;
  6. remember that stored-payload redaction is key-based defense in depth, not permission to place secrets in ordinary free-text fields.

See Automation Studio for the policy matrix, signed webhook controls, cancellation boundaries, recovery rules, and retention behavior.

Workspace Rule Management

Workspace-local permission rules are visible in Settings > System & Security for the active workspace. From there you can:

  • browse workspace-local rules
  • remove a rule directly
  • persist new workspace rules from approval prompts

Workspace-local rule removal updates both the local SQLite row and the workspace policy manifest. If the manifest write fails, the database removal still succeeds and the app reports the partial result.

Configurable Guardrails

CoWork OS includes configurable guardrails in Settings > System & Security > Safety Limits to limit what the agent can do:

GuardrailDescriptionDefault
Token BudgetMax tokens (input + output) per user turn (each follow-up message starts a new count); a task's own budget counts the whole task, always applies, and can only lower this limit2,000,000 (enabled)
Cost BudgetMax estimated cost (USD) per task; a task's own budget always applies but can only lower this limit$10.00 (enabled)
Iteration LimitMax LLM calls per continuation window; reaching it auto-continues within the continuation caps500 (enabled)
Dangerous CommandsBlock dangerous command-tool commands matching patternsEnabled
File Size LimitMax file size the agent can write50 MB (enabled)
Domain RulesProfile- and administrator-controlled destination allow/deny rulesProfile-controlled

Dangerous Command Blocking

The following command patterns are blocked by default:

| Pattern | Risk | | ------------------------ | --------------------- | ---------------- | | sudo | Elevated privileges | | rm -rf / or rm -rf ~ | Mass deletion | | mkfs | Filesystem formatting | | dd if= | Direct disk writes | | Fork bombs | Process exhaustion | | curl\|bash, wget\|sh | Remote code execution | | chmod 777 | Overly permissive | | > /dev/sd | Direct device writes | | :(){ : | :& };: | Fork bomb syntax |

Commands are blocked before reaching the approval dialog. You can add custom patterns in Settings.

Trusted-command patterns now feed the permission engine as compatibility rules instead of acting as the final approval system.

Domain Rules

When a profile contains positive domain rules, built-in browser and network tools are restricted to the specified destinations:

  • Exact match: github.com
  • Wildcard: *.google.com (matches subdomains, not the apex)
  • **.example.com matches the apex and descendants
  • Deny rules win over allow rules

Arbitrary subprocess networking is not domain-aware and fails closed when a profile requires domain-level enforcement without a domain-aware proxy.


What the App Can Access

File System Access

ScopeAccess Level
Workspace directoriesRead/Write (based on permissions)
Outside workspaceApproval required for an explicit external-file operation; traversal otherwise blocked
System filesNo access
.cowork/policy/** inside a workspaceRead-only. Holds the permission mirror and tool-policy script
.git/** inside a workspaceRead-only, except .git/info/exclude

Technical details:

  • Path traversal protection prevents accessing files outside the workspace
  • Symlink attacks are mitigated through path normalization
  • Implementation: src/electron/agent/tools/file-tools.ts

Protected paths. Two categories of location are never mutable by tools, even with unrestricted file access or a granted approval:

  • OS locations (PROTECTED_FILESYSTEM_ROOTS)
  • In-workspace .cowork/policy/** and .git/** (PROTECTED_WORKSPACE_SEGMENTS)

Both are enforced in evaluateWorkspaceFilesystemAccess (src/electron/security/access-profile-paths.ts) and denied with reason protected_path, which is a hard boundary — no approval or rule can satisfy it. The rationale is that the policy files decide whether a tool call is allowed, and git hooks execute on the next commit outside the tool sandbox, so a tool able to write either could rewrite the rules that govern it. Reads are unaffected.

Relatedly, copy_file strips the executable bit from its destination, so copying an executable file cannot be used to create a new one.

Workspace Kit Project Access Rules

If a workspace contains a .cowork/projects/<projectId>/ACCESS.md file, built-in tools enforce per-project access based on the task's assigned agent role:

  • ## Allow and ## Deny sections accept agent role IDs (one per line prefixed with -).
  • Use all to match every agent role.
  • Deny wins over allow.

Enforcement applies to:

  • File/edit/grep/search tools when the path is inside .cowork/projects/<projectId>/...
  • Workspace-kit context injection (denied projects are excluded from injected context)

Important: these project-role rules do not replace the task access profile for process execution. Command tools are subject to the profile's sandbox and filesystem scope, while project-role rules continue to govern the file and workspace-kit surfaces listed above. Review command-tool approvals carefully, especially for tasks using a broad access profile.

Command-tool Execution

When command tools are exposed by the active access profile:

AspectImplementation
Working directoryRestricted to the active workspace and profile-approved roots
Environment variablesMinimal set (PATH, HOME, USER, SHELL, LANG, TERM, TMPDIR) plus toolchain configuration (proxies without credentials, CA bundles, relocated toolchain homes)
API keysNever passed to subprocesses
Timeout2 minutes by default (5 for installs, builds and tests); a command may ask for up to 30 minutes, capped by the current step's time budget
Output limit100KB (truncated if exceeded)

Security note: Your API keys and secrets are never exposed to shell commands. The app creates a minimal, safe environment for each command.

run_command first requires the active access profile to expose command tools, then applies guardrails, approval, and the selected access profile. Restricted profiles use the native macOS or Docker sandbox when available; if no OS sandbox is available (Windows, or Linux without Docker), execution fails closed. The one exception is per-command approval: unless admin policy sets requireSandboxForShell or turns off allowUnsandboxedShell (on by default), CoWork asks you to approve each such command explicitly before it runs with your full user permissions. That prompt cannot be auto-approved or answered by a "never ask" profile. Scoped filesystem rules are canonicalized before execution so symlinks and path traversal cannot escape the approved roots. Domain-scoped network rules are enforced for built-in network tools; arbitrary shell networking is denied when the active sandbox cannot enforce those domains.

execute_code requires the same command-tool capability and approval handling as shell execution for every supported language. Its separate network request still requires network permission. Code runs for up to 300 seconds (30 by default).

Browser Automation

The app includes Playwright for web automation:

CapabilityDetails
Navigate to URLsDestinations allowed by task and administrator network policy
Fill formsAs directed by task
Take screenshotsSaved to workspace
Execute JavaScriptWithin page context only
ModeHeadless by default

User agent: CoWork OS Browser Automation

Visible browser guests start at about:blank; main-process request guards and a local connection proxy must be installed before remote navigation. Registration resolves the task's effective access profile, and missing policies deny remote traffic. Chromium's proxy validates and pins each destination connection, including redirects, while preserving native cookies and TLS verification. Tabs sharing a persistent cookie partition require every active owner to permit the destination. Browser WebSockets are blocked. External CDP attachment is unavailable because existing sockets can predate interception; use a dedicated browser profile. Environment proxies are refused when destination pinning cannot be guaranteed.

Subprocess network access requires an unrestricted task network policy and explicit administrator permission. Domain restrictions require an OS sandbox that disables process networking; unsandboxed execution cannot claim to enforce network denial.

Chronicle Screen Context

Chronicle is separate from browser automation and from dedicated computer-use mouse/keyboard control.

CapabilityDetails
Passive captureOpt-in only; local recent-screen buffer in the desktop app
Consent / controlsExplicit consent before first enable; pause/resume from Settings or the tray menu when available
Storage modelRaw passive frames stay in app-local storage and are pruned aggressively
Workspace persistenceOnly task-used observations are copied into .cowork/chronicle/; linked screen_context memory generation can follow when enabled
Network behaviorNo automatic provider export; later vision analysis still follows normal approval rules
AvailabilityDesktop app only; not offered in headless or channel runtimes

Chronicle also introduces a prompt-injection risk from visible screen content. A malicious page, document, or chat window can place instructions on screen that the agent may later treat as relevant context. CoWork marks Chronicle text as untrusted screen text, but you should still keep Chronicle paused or off when viewing sensitive or untrusted material, and prefer direct source tools over screen-derived context when a file, URL, PR, or thread can be read directly.


Network Connections

LLM API Providers

The app connects to these services based on your configuration:

ProviderEndpointWhen Used
Anthropicapi.anthropic.comClaude models
AWS Bedrockbedrock-runtime.*.amazonaws.comBedrock models
Google AIgenerativelanguage.googleapis.comGemini models
OpenRouteropenrouter.aiOpenRouter models
Ollamalocalhost:11434 (default)Local models
MLX-LMlocalhost:8080/v1 (default)Local Apple Silicon models; model downloads may reach Hugging Face

Search Providers (DuckDuckGo built-in; others optional)

ProviderEndpointWhen Used
DuckDuckGohtml.duckduckgo.comFree built-in web search (no API key)
Tavilyapi.tavily.comWeb search (API key required)
Exaapi.exa.aiWeb/news search (API key required)
Brave Searchapi.search.brave.comWeb search (API key required)
SerpAPIserpapi.comWeb search (API key required)
Google Custom Searchcustomsearch.googleapis.comWeb search (API key required)

Other Connections

DestinationPurpose
api.github.comUpdate checks
api.telegram.orgTelegram bot (if configured)
Discord APIDiscord bot (if configured)
Signal (via signal-cli)Signal bot (if configured, local process)
Feishu / Lark APIsEnterprise messaging gateway traffic (if configured)
WeCom APIsEnterprise messaging gateway traffic (if configured)
Remote ACP/A2A endpointsFederated remote-agent invocation (if configured)

Internal Addresses Are Blocked for Agent Fetches

Agent-driven network tools (web_fetch, http_request, scraping, browser automation) cannot reach internal targets, checked before any allow rule so an allowlist entry cannot open them:

TargetReachable
Cloud instance metadata (169.254.169.254, metadata.google.internal)No
Private ranges (10/8, 172.16/12, 192.168/16), carrier-grade NATNo
Link-local, unique-local IPv6, unspecified address, IPv4-mapped formsNo
*.internal hostnamesNo
Loopback (127.0.0.1, localhost, ::1)Yes
Public internet, subject to domain policyYes

Loopback is deliberately allowed: the agent legitimately fetches development servers it has just started, and the app's own loopback services all require bearer tokens. A DNS name that resolves to an internal address is also refused. For web_fetch, http_request, and Canvas HTTP(S) traffic, the actual socket connects through the validated address rather than performing a separate DNS lookup. Each redirect is checked independently.

Implementation: src/electron/security/address-classes.ts, src/electron/security/pinned-fetch.ts, and evaluateNetworkPolicy.

Canvas and Resource Limits

Canvas windows and interactive previews enforce the owning task's live network permissions and domain rules. Task tools cannot execute or modify another task's Canvas session. With Ask for approval, remote resources require an authorized origin; grants apply only to the matching permission state and are not persisted across app restarts. Canvas remote traffic supports HTTP(S), with WebSocket and other remote schemes blocked. Local content and snapshots remain available when networking is disabled. See Live Canvas network access.

web_fetch and http_request cap decoded response bodies at 5 MiB and keep the request deadline active through body consumption. The maxLength option limits returned text independently. LINE, Teams, Google Chat, Feishu, and WeCom webhook handlers reject bodies over 1 MiB with HTTP 413 before processing. ClawHub downloads are limited to 5 MiB compressed; incremental extraction limits each file to 512 KiB and total expanded content to 5 MiB, with a 200-entry archive limit.

DOCX, ODT, PPTX, and XLSX inputs use a shared archive boundary: 50 MiB input, 2,048 entries, 16 MiB per expanded part, 64 MiB cumulative expansion, and a 10-second expansion deadline. Unsupported ZIP layouts and oversized inputs are rejected before parser or writer use. PPTX table expansion also caps spans, dimensions, and total cells before allocation. Cron webhook triggers authenticate their header before reading a body and reject input over 1 MiB with HTTP 413.

See the 2026-09-30 security fix record for the implementation map, validation results, and reproduction commands.

The 2026-10-02 follow-up record documents five additional fixes, currently local and unreleased. Cross-origin HTTP redirects discard caller headers and reject preserved request bodies. Grep matching uses a terminable worker with a 500 ms per-job deadline. Pulse rejects streamed input over 16 KiB, and Git skill imports reject symlinks before reading skill content. Read-only MCP tunnels now block every tool call, including allowlisted tools; discovery and resource reads remain available. See the follow-up record for compatibility changes and the limits of local verification.

The October 2 browser and runtime record documents nine further findings and two hardening changes, also local and unreleased. It covers subprocess administrator policy, browser startup guards, WebSocket refusal and pinned connections, live tunnel revocation, document archive/table limits, cron webhook authentication and buffering, and relay message containment. It also records Git fsmonitor and updater quit hardening, the 223-test remediation run, compatibility restrictions, and outstanding release and platform acceptance.

Links opened from app content go through a single scheme allowlist (src/electron/security/safe-external-url.ts) permitting only http:, https:, and mailto:. This covers the IPC channel, new-window requests, and will-navigate. Schemes such as file:, smb:, or an app-registered protocol are refused, because previewed documents can contain arbitrary hyperlink targets that the document converter does not validate.

ACP Remote Agents

Remote ACP delegation is constrained more tightly than ordinary outbound automation:

  • registration is scope-gated
  • non-operator clients are limited to their own ACP tasks and inbox reads by default
  • remote endpoints are validated before invocation
  • https is preferred, while plain http is intended only for loopback development
  • private and link-local IP targets are rejected by the remote invoker validation layer
  • remote requests use bounded timeouts so bad endpoints cannot hang the main process indefinitely

Control Plane Exposure

The Control Plane binds to loopback by default. Headless/managed deployments fail closed on 0.0.0.0/:: binds unless Tailscale exposure is enabled, the process is running in a privately published container with COWORK_CONTROL_PLANE_BIND_CONTEXT=container, or COWORK_CONTROL_PLANE_ALLOW_INSECURE_PUBLIC_BIND=1 is set as a break-glass override.

Reverse-proxied dashboards should set COWORK_CONTROL_PLANE_ALLOWED_ORIGINS to the public HTTPS origin. Only enable COWORK_CONTROL_PLANE_TRUST_PROXY=1 behind a proxy that controls forwarded headers.

Product Analytics and Outbound Data

CoWork Pulse installation-linked usage reporting is disabled by default and requires explicit opt-in. Automatic release checks in installed builds send version/platform/architecture/surface to pulse.coworkosapp.com without an installation identifier and contribute daily request counts. Source checkout update checks use Git against origin/main instead. Model providers, compatible gateways, web search, connectors, channels, and other services also receive operational requests when configured or invoked.

Opted-in Pulse profiles send bounded daily aggregates to a dedicated Cloudflare Worker/D1 database. The server stores an HMAC pseudonym rather than the raw random profile UUID. No prompt, response, file content/name, command, URL, raw task/session ID, or provider/model route is included. Stable pseudonyms make these aggregates longitudinal data, not anonymous data. Operator-configured OTLP telemetry is independent and includes allowlisted event names, timestamps, and hashed correlation IDs. See CoWork Pulse for the exact boundaries, retention, reset/delete semantics, in-flight consent limitations, and production runbook for credentials and backup handling.

Task state and application data are persisted locally by default. Prompts, selected memory snippets, files, credentials, and tool payloads leave the machine only when needed for a provider, gateway, connector, channel, browser target, or other external service that you configure or approve. Each service applies its own retention, privacy, and billing terms.


Data Storage

Encrypted Settings Storage (SecureSettingsRepository)

Settings stored through SecureSettingsRepository are encrypted inside the local SQLite database. The SQLite file itself is a normal better-sqlite3 database, not a whole-file SQLCipher database:

DataLocationEncryption
All Settingsapp.getPath('userData')/cowork-os.dbOS Keychain + AES-256
Database fileapp.getPath('userData')/cowork-os.dbPlain SQLite file; selected settings and sensitive fields are encrypted per category/feature
Machine IDapp.getPath('userData')/.cowork-machine-idStable identifier for encryption
Pulse identity/token and settingsSecure settings category pulseEncrypted settings; UUID is not derived from machine ID
Pulse consent windows/outboxProfile SQLite pulse_consent_windows / pulse_outboxOrdinary SQLite rows; outbox contains aggregate payload and UUID, not deletion token
Pulse receiptsProfile SQLite pulse_sent_daysOrdinary SQLite rows: acknowledged package IDs and days

Typical userData locations:

  • macOS: ~/Library/Application Support/cowork-os/
  • Linux: ~/.config/cowork-os/
  • Windows: %APPDATA%\\cowork-os\\

Encryption Layers

Primary: OS Keychain (when available)

  • macOS: Keychain Services
  • Windows: DPAPI (Data Protection API)
  • Linux: libsecret

Fallback: App-Level Encryption (app2: records)

  • AES-256-GCM encryption
  • Key derived via PBKDF2-SHA512 (210,000 iterations) from the per-install machine ID, with a random per-record salt stored alongside the ciphertext
  • Fails closed: if no machine identifier can be established, writing secure settings is refused rather than falling back to a key derived from predictable paths
  • The stored checksum covers the ciphertext, not the plaintext; plaintext integrity comes from AES-GCM's authentication tag
  • Legacy app: records written by earlier versions remain readable and are re-encrypted to the current format on their next successful load, so upgrades migrate in place with no user action

Unreadable settings

If a saved category can no longer be decrypted (for example after an OS keychain reset or a profile restore to another machine), CoWork treats it as missing so you can re-enter those settings. On the next save it copies the old encrypted row, never plaintext, into the secure_settings_unreadable_backup table and then writes the new settings, so the original data stays recoverable if the old keychain identity returns. The MCP server list stays read-only while its saved settings are unreadable.

Keychain identity check

CoWork stores a small known value encrypted with the OS keychain key. If a later launch cannot decrypt it (the app is using a different keychain identity), secure settings are not saved and an in-app notification explains why, so new data is never encrypted under a key the original identity cannot read. Restore Keychain access to CoWork OS Safe Storage and relaunch, or relaunch with COWORK_ACCEPT_NEW_KEYCHAIN_KEY=1 to move unreadable settings (ciphertext only) into secure_settings_unreadable_backup and continue with the current key. On macOS, settings still readable under a former CoWork identity are migrated automatically at startup.

See the Security Hardening Record for what changed and why.

Settings Categories

All these are stored encrypted in the database:

CategoryContents
voiceVoice settings, TTS/STT API keys
llmLLM provider settings, API keys
searchSearch provider settings, API keys
appearanceTheme, accent color preferences
personalityAgent personality settings
skillsManaged-skill settings and external skill directory pointers
guardrailsSafety limits and blocked patterns
hooksAutomation hooks configuration
mcpMCP server configurations
secure-mcp-tunnelsSecure MCP tunnel definitions and tunnel tokens
acpACP-related persisted settings and lifecycle metadata
controlplaneControl plane settings, tokens, allowed browser origins, and proxy trust settings
channelsChannel/gateway configurations
builtintoolsBuilt-in tool settings
tailscaleTailscale integration settings
queueTask queue settings
trayMenu bar/tray settings

Memory Write Governance

The normal local runtime does not show approval prompts. New memory writes commit immediately, including when an older saved Memory Hub setting selected a review mode. Memory Write Approval remains an explicit compatibility path for controlled runs and can stage durable memory writes before commit. Set COWORK_MEMORY_WRITE_APPROVAL_MODE to opt into it:

  • off: writes commit immediately
  • curated_only: fact writes (memory_remember, core memory candidate facts) wait for review
  • background_only: automatic capture, Dreaming, distillation (including core memory candidate facts) wait for review
  • all: every durable memory write waits for review

Pending rows live in pending_memory_writes inside the normal SQLite database. Since that table is not whole-file encrypted, CoWork blocks sensitive external-memory payloads before queueing them. Approvals first claim rows as applying, then replay the write with the gate bypassed and mark it applied; duplicate or stale approve attempts fail instead of replaying again. To clear a backlog after switching to the no-prompt policy, call MemoryWriteGate.rejectAllPending(): it records a system rejection and never replays the stored payload. The helper only claims rows still marked pending, so an in-flight applying replay is not interrupted. Take a SQLite backup before the one-time cleanup, run it while no replay is active, and verify pendingCount() is zero afterward; do not delete the database to clear this queue.

Data Integrity

Each stored setting includes:

  • SHA-256 checksum for integrity verification
  • Creation and update timestamps
  • Automatic corruption detection on load

What's Stored in the Database

  • Workspace configurations
  • Task history, events, and logs (including task prompts and timeline messages)
  • Channel/gateway configurations
  • Channel message history (incoming/outgoing message content for configured channels)
  • All encrypted settings (API keys, preferences, configurations)

Everything is stored locally on your machine. CoWork OS does not upload your database or message history to any CoWork OS servers.

API Key Security

Your API keys are:

  1. Encrypted using OS Keychain when available (macOS Keychain, Windows DPAPI, Linux libsecret)
  2. Fallback to AES-256 app-level encryption with stable machine-derived key
  3. Decrypted only when needed for API calls
  4. Never logged or displayed in full
  5. Never passed to shell commands or subprocesses
  6. Checksummed for integrity verification

Saved provider credentials remain bound to their configured endpoint during model discovery, connection tests, and settings saves. Changing the destination requires a replacement credential; leaving the key blank only reuses it for the saved destination. This also covers credentials inherited by image/video settings and custom providers.

Media and File Validation

CoWork also applies guardrails before certain file and media operations reach external providers:

  • large text writes are blocked by the configured file-size guardrail
  • binary files are rejected from text-only write paths
  • video-generation reference images/videos must be absolute paths, real files, and within supported size/type limits
  • external skill directories must be explicit existing absolute paths and are treated as read-only by the app

Electron Security Configuration

Security Settings

SettingValuePurpose
nodeIntegrationfalsePrevents renderer from accessing Node.js
contextIsolationtrueIsolates preload scripts from page context
sandboxDefaultUses Chromium sandbox

Content Security Policy (Production)

default-src 'self';
script-src 'self';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self' data:;
connect-src 'self' https:;
frame-ancestors 'none';
form-action 'self';

macOS Entitlements

EntitlementPurpose
allow-jitRequired for V8 JavaScript engine
allow-unsigned-executable-memoryRequired for Electron
allow-dyld-environment-variablesLoading native modules
files.user-selected.read-writeAccess to user-selected folders
network.clientConnect to LLM APIs

Not requested: Camera, microphone, or contacts.

Opt-in location access: get_current_location requests one-time location permission through the operating system's native dialog (macOS Core Location, Windows Location Services, or Linux GeoClue2). Each invocation requires explicit user consent — the permission is never auto-approved or persisted across tasks. Coordinates are not logged; only accuracy and source are recorded in task events.


Messaging Channel Security

If you use the gateway feature to connect messaging bots (Telegram, Discord, Slack, WhatsApp, iMessage, Signal):

Security Modes

ModeDescriptionRecommendation
OpenAnyone can use the botNot recommended for production
AllowlistOnly pre-approved user IDsGood for known users
PairingUsers must enter a code from the appBest for security

Best Practices

  1. Use pairing mode for bots accessible to others
  2. Generate new pairing codes for each user
  3. Revoke access for users who no longer need it
  4. Don't share bot tokens publicly

Auto-Update Mechanism

How Updates Work

For git clones (development):

  1. Checks GitHub API for new releases/commits
  2. User initiates update manually
  3. Runs: git pull, npm run setup, npm run build
  4. Requires app restart

For packaged builds:

  1. Uses electron-updater with GitHub releases
  2. Downloads from the official repository over HTTPS; the update feed is pinned at build time and cannot be redirected at runtime
  3. Downloads are explicit — autoDownload is off
  4. Verifies a detached signature before installing, when a release signing key is configured (see below)

Current signing status. Desktop artifacts are not code-signed: macOS builds are packaged ad-hoc and Windows builds carry no publisherName, which makes electron-updater's own signature check inactive. Without the detached-signature scheme enabled, the only integrity control is the sha512 in latest.yml — and that file is published to the same release as the artifact it describes, so it does not protect against someone able to replace release assets.

The detached Ed25519 signature scheme closes this and needs no code-signing certificate, but ships inert until a key is generated. See Release Signing for the current state and how to enable it.

Supply Chain Considerations

RiskMitigation
Malicious code in updateUpdates are user-initiated, not automatic; detached signature verification refuses a tampered artifact once a signing key is configured
Tampered release assetDetached signature over the artifact requires the private key, which lives only in CI secrets — replacing the asset and its checksum is not sufficient
Compromised dependenciesDependencies from reputable sources only
npm install risksThird-party lifecycle scripts disabled via .npmrc; npm run setup handles native rebuilds explicitly
Malicious MCP registry entryInstalling a remote registry entry requires explicit confirmation showing the exact command; bundled connectors always take precedence over same-named remote ones
Malicious plugin packhttps:// only, no clone redirects, no credential helper, and the manifest entry point is confined to the pack directory

Installing MCP Servers

Installing an MCP server whose definition came from the online registry prompts for confirmation first, showing the exact command, arguments, environment variable names, publisher, and transport that will be used. Nothing is spawned before that approval, and the install is refused outright if no confirmation route is available.

This applies to installs triggered from the marketplace UI and to installs requested by the agent (integration_setup), which matters because the agent can be influenced by content it reads. Connectors bundled with the app are not affected and install without a prompt; a remote entry reusing a bundled connector's name is discarded in favour of the bundled one.

Note: If you're security-conscious, review changes before updating:

git fetch origin
git diff HEAD..origin/main

Security Best Practices

For General Use

  1. Review command-tool calls before approving - read what will execute
  2. Use dedicated workspaces - don't point at sensitive directories
  3. Choose the least-privileged access profile - only grant what you need
  4. Keep updated - security fixes come through updates
  5. Protect your API keys - don't share configuration files

For Messaging Bots (Telegram/Discord/Slack/WhatsApp/iMessage/Signal)

  1. Never use "open" mode for public bots
  2. Use pairing codes for secure user onboarding
  3. Regularly audit connected users
  4. Revoke access when no longer needed
  5. For Signal: Use a dedicated phone number (registration deactivates other Signal instances)

For Secure MCP Tunnels

  1. Require relay admin auth before creating tunnel credentials.
  2. Use HTTPS/WSS for non-loopback relays.
  3. Prefer explicit tool allowlists over broad access.
  4. Enable read-only mode for remote inspection workflows.
  5. Rotate caller/client tokens when a device or remote caller is decommissioned.
  6. Review audit logs for blocked or unexpected tool calls.

See Secure MCP Tunnels for the tunnel-specific security model.

For Development

  1. Review code changes before pulling updates
  2. Audit dependencies periodically with npm audit
  3. Don't commit .env or settings files
  4. Use separate workspaces for testing

Threat Model

What CoWork OS Protects Against

ThreatProtection
Path traversalPath normalization and validation
Command injectionUser approval required
API key leakageEncrypted storage, minimal env
XSS attacksContent Security Policy
Unauthorized bot accessMultiple auth modes
Malicious skill IDsInput validation and sanitization
Binary name injectionShell metacharacter filtering

What Requires User Vigilance

RiskUser Responsibility
Approving malicious commandsReview before approving
Workspace selectionDon't add sensitive directories
Bot token securityKeep tokens private
Update verificationReview changes if concerned

Out of Scope

  • Protection against malicious LLM responses (AI safety)
  • Physical access to your machine
  • Compromised macOS system
  • Malicious code you add to workspaces

Verifying Security

Check Access Profile And Workspace Permissions

In the app, review the active profile from the composer or Settings > System & Security > Permissions:

  • sandbox and approval/reviewer posture
  • command-tool availability and network/domain scope
  • filesystem roots and read/write/deny rules
  • legacy workspace capability gates for older tasks

Audit Connected Users (Bots)

In the Gateway settings, you can:

  • View all connected users
  • Revoke access for specific users
  • Generate new pairing codes

Review Pending Approvals

The normal runtime has no popup approval queue. Review unresolved decisions in the task timeline's assistant message and inline input card, checking the exact operation, destination, and scope before choosing Allow once. The legacy pending-approval badge and queue are available only when COWORK_APPROVAL_PROMPTS=on is explicitly enabled. Pending rows and assistant approval cards fail closed on restart; retry the operation to receive a fresh decision.


Reporting Security Issues

If you discover a security vulnerability:

  1. Do NOT create a public GitHub issue
  2. Use GitHub Security Advisories (Security tab > Report a vulnerability)
  3. Include reproduction steps and impact assessment

See SECURITY.md for full details.


Advanced Security Framework (v0.3.8.7+)

CoWork OS includes a comprehensive security framework inspired by formal verification techniques.

Tool Groups & Risk Levels

Tools are categorized by risk level for policy-based access control:

Risk LevelToolsDescription
Readread_file, list_directory, search_filesLow risk, read-only operations
Writewrite_file, copy_file, create_directoryMedium risk, creates/modifies files
Destructivedelete_file, run_commandHigh risk, usually approval-gated unless an explicit allow rule or mode applies
Systemread_clipboard, take_screenshot, open_applicationSystem-level access
Networkweb_search, browser_*External network operations
Export / Egressmutating http_request, analyze_image, read_pdf_visualOutbound transfer of local bytes or payloads; reviewed separately from ordinary network reads

Ordinary uploaded-PDF reading uses the local parse_document extraction path. The extracted text is still treated as untrusted document data, but it is not an export/egress operation unless the task uses read_pdf_visual or another tool that sends local bytes to an external provider.

High-autonomy modes and session "Approve all" do not silently bypass this export/egress lane.

The computer-use family (screenshot, click, type_text, keypress, and related tools on macOS and Windows) is not low-risk read-only automation: it can drive arbitrary UI the operator can reach. Treat it as high trust and keep the computer_use built-in category disabled unless you need it. See Computer use.

Computer use security

  • Helper-targeted macOS permissions: Accessibility and Screen Recording are granted to the bundled helper runtime, with inline bootstrap at task time and settings shortcuts for recovery.
  • Windows visible-window constraint: Windows v1 only targets visible, non-minimized windows and may require comparable privilege for elevated apps.
  • Safety UX: Active sessions use a single-session lock, Esc abort, and shortcut guarding to reduce accidental cross-window effects and disruptive global hotkeys during automation.
  • Tool gating: Policy defers the computer-use lane unless the task signals native desktop GUI intent, so gateway and general tasks default to safer tool lanes.
  • Key chord blocklist: Certain OS-level shortcuts are rejected at the tool layer to avoid session or system disruption.

Full operator and troubleshooting guidance: Computer use.

Monotonic Policy Precedence (Deny-Wins)

Security policies are evaluated across multiple layers in order:

  1. Global Guardrails - Blocked commands, patterns
  2. Access Profile - Sandbox, approval, command-tool, filesystem, and network boundaries
  3. Legacy Workspace Permissions - Read, write, delete, and compatibility flags
  4. Context Restrictions - Gateway context (private/group/public)
  5. Tool-Specific Rules - Per-tool overrides

Key invariant: Once denied by any layer, a tool cannot be re-enabled by later layers. This prevents policy bypasses.

Context-Aware Tool Isolation

When tasks originate from gateway bots (WhatsApp/Telegram/Discord/Slack/iMessage/Signal), tools are restricted based on context:

ContextRestrictions
PrivateTarget access profile with no additional channel restriction
GroupTarget profile plus memory-tool restrictions (including clipboard) and any configured destructive-tool restrictions
PublicTarget profile plus the strongest configured channel restrictions; system/destructive operations may be blocked

This prevents accidental exposure of sensitive data in shared contexts.

Concurrent Access Safety

Critical operations use mutex locks and idempotency guarantees to prevent race conditions:

OperationProtection
Pairing code verificationMutex per channel + idempotency check
Approval responsesIdempotency prevents double-approval
Task creationDeduplication via idempotency keys

Brute-Force Protection

Pairing code verification includes protection against brute-force attacks:

FeatureValueDescription
Max attempts5Failed attempts before lockout
Lockout duration15 minutesTime before retry allowed
Code charset32 charactersExcludes ambiguous chars (I, O, 1, 0)
Code length6 characters~1 billion combinations
Estimated crack time>1000 yearsWith lockout enabled

When a user exceeds the maximum attempts:

  1. Account is locked for 15 minutes
  2. User sees remaining lockout time
  3. Attempts counter resets after lockout expires

Implementation: src/electron/gateway/security.ts

Command-Tool Sandboxing

On macOS, profile-enabled command tools execute within a generated sandbox-exec profile; on Linux and Windows, the Docker backend provides a process boundary when configured. Both backends are fed by the same canonical filesystem evaluator:

  • Restricts filesystem access to the active workspace and explicitly approved roots
  • Canonicalizes paths and resolves symlinks before allowing reads, writes, or deletes
  • Blocks network access unless the active profile and workspace permit it
  • Fails closed for arbitrary code with domain-scoped egress that the backend cannot enforce
  • Uses a minimal, filtered subprocess environment

The macOS backend also enforces positive read-only filesystem rules, recursive Git and policy-path protection, and the separate delete capability. With delete on, files and directories inside the workspace can be removed, moved and replaced (rm -r, mv, build tools that clean their output directory or rename a temporary directory into place). The workspace root and every directory on the way to a profile filesystem rule stay fixed, so a move cannot shed a rule; .git and .cowork/policy stay immutable wherever their parent is moved, and no writable location can create them, so a repository cannot be assembled elsewhere and moved in. A nested repository moved into private scratch is kept rather than deleted when scratch is cleaned up. Directory removal and moves stay denied in host roots outside the workspace. Private scratch directories remain available for temporary work.

Developer toolchains work inside the macOS sandbox without opening the home directory: installs and configuration (~/.cargo/bin, ~/.rustup, ~/.nvm, ~/.pyenv, ~/.local/bin, ~/.gitconfig, ...) are read-only, package download and build caches (~/.npm, ~/Library/Caches/{go-build,pip,uv,...}, ~/.cargo/registry, ~/go/pkg, ...) are read-write for writable workspaces, and PATH comes from the user's PATH plus known toolchain locations. Credential stores (~/.ssh, ~/.gnupg, ~/.aws, ~/.config/gcloud, ~/.docker, ~/.netrc, ~/.kube, ~/.npmrc, Keychains, browser profiles, Mail and Messages) are denied after those grants. npm sees a copy of ~/.npmrc with auth lines removed, so registry and script settings apply but tokens never enter the sandbox; installs from registries that require authentication must run outside it. Writable caches deny Git and policy names (except uv's empty per-bucket .git marker files) so they cannot stage a repository for the workspace. Residual risk: these caches are shared with your own builds, so a sandboxed command can leave cache content that a later unsandboxed build consumes, as it can with the workspace's own scripts and dependencies.

Docker checks every host mount against the active filesystem policy. It refuses mounts that expose denied descendants, writable mounts that violate read-only or delete rules, and writable workspace directory mounts whose future Git and policy paths cannot be protected. Read-only host mounts and private container scratch remain available. An unsupported policy returns an error before process launch.

Implementation: src/electron/security/access-profile-paths.ts, src/electron/agent/sandbox/macos-sandbox.ts, and src/electron/agent/sandbox/docker-sandbox.ts

Imported Capability Security

Imported skills and imported plugin packs now pass through the same install-time security gate before activation.

ProtectionDescription
Skill ID ValidationIDs must match ^[a-z0-9_-]+$ pattern (lowercase alphanumeric, hyphens, underscores)
Path Traversal PreventionIDs containing .., /, or \ are rejected
Binary Name SanitizationBinary names in requires.bins must match ^[a-zA-Z0-9._-]+$
Command Injection PreventionShell metacharacters in binary names are blocked before which execution
Debounced ReloadingRapid skill reloads are debounced (100ms) to prevent race conditions
Staged ImportsImported skills and plugin packs are scanned before they are moved into active managed storage
Bundle HeuristicsImported SKILL.md, bundled scripts, plugin manifests, declarative connectors, and suspicious URLs are inspected for high-confidence malicious patterns
Package Malware ChecksDetected npx / uvx package references can be checked against live package-malware intelligence
Quarantine Instead of ActivateImports with blocking findings are preserved in quarantine rather than registered into the active runtime
Persisted Scan ReportsManaged imports store a security report for warning UX, review, and later integrity checks
Digest EnforcementIf a managed imported bundle changes after install, CoWork can quarantine it again on the next load

Rejected inputs (skill IDs):

  • ../../../etc/passwd - Path traversal
  • foo/bar - Contains path separator
  • skill;rm -rf / - Special characters

Rejected inputs (binary names):

  • node; rm -rf / - Shell metacharacters
  • $(whoami) - Command substitution
  • `whoami` - Backtick execution

Imported bundles that cannot be fully checked against network-backed intelligence are allowed to install only when the local scan is otherwise clean, and the UI surfaces that reduced-confidence state as a warning.

Implementation:

  • src/electron/agent/skill-registry.ts (skill ID validation)
  • src/electron/agent/skill-eligibility.ts (binary name sanitization)
  • src/electron/security/capability-bundle-security.ts (bundle scanning, reports, digest verification, and quarantine)
  • src/electron/extensions/pack-installer.ts (pack install staging and scan gate)
  • src/electron/extensions/loader.ts (discovery-time integrity checks and quarantine enforcement)

Codex Security Scan Containment

The bundled Codex Security pack runs repository, diff, and deep multi-pass security scans through first-party plugin-pack skills. The old security_scan_* built-in helpers are no longer exposed; scan workflows use the normal workspace-scoped task tools plus bundled skill instructions, references, and scripts.

ProtectionDescription
First-party pack loadingThe bundled Codex Security pack is discovered from resources/plugin-packs/codex-security/ in development and plugin-packs/codex-security/ in packaged builds.
Normal workspace policyScan tasks use the same workspace path, command-tool, network, and approval controls as other CoWork tasks.
Artifact containmentScan artifacts should be written under the active workspace, normally .cowork/security-scans/<repo-name>/<scan-id>/.
Scoped-path disciplineScoped scans should use relative repository paths; absolute paths and .. segments should be rejected by the workflow before scanning.
Deep worker completenessDeep-scan reconciliation expects six usable workers, with all required files present and valid JSONL in worker ledgers/candidates.
Report rendering through bundled scriptsReport validation and HTML rendering should use bundled Codex Security scripts from the packaged plugin pack, not user-provided renderer paths.

These controls keep the scan workflow auditable and keep scan activity within the same policy boundary as normal CoWork task execution.

Implementation:

  • src/electron/agent/tools/registry.ts (normal workspace-scoped tool catalog used by scan skills)
  • resources/plugin-packs/codex-security/ (bundled scan skills, references, scripts, and assets)
  • src/electron/extensions/loader.ts and src/electron/extensions/registry.ts (directory-backed plugin-pack discovery and skill loading)

See Codex Security Scans for scan modes and artifact contracts.

Running Security Tests

npm run test                # Full suite (4,932 tests total: 4,854 passed, 78 skipped; includes security)
npx vitest run tests/security   # Security-focused tests only (135 tests)
npm run test:coverage       # With coverage report

Test files:

  • tests/security/tool-groups.test.ts - Tool categorization tests
  • tests/security/policy-manager.test.ts - Policy evaluation tests
  • tests/security/concurrency.test.ts - Mutex and idempotency tests
  • tests/security/sandbox-runner.test.ts - Sandbox execution tests
  • tests/security/gateway-security.test.ts - Brute-force protection tests

Summary

CoWork OS is designed with security in mind:

AspectStatus
API key storageEncrypted (OS keychain)
File accessSandboxed to workspace
Access profilesSandbox, approval, reviewer, command-tool, filesystem, domain, and network policy
Command executionExposed by the active profile; restricted profiles require approval and a sandbox where available
Network accessConfigured or user-invoked providers, gateways, connectors, channels, browser targets, update services, and other integrations
Product analyticsNo mandatory product analytics by default
Electron securityBest practices followed
GuardrailsConfigurable limits on tokens, cost, iterations, commands, file size, and domains
Policy systemMonotonic deny-wins precedence
Gateway securityContext-aware tool isolation
ConcurrencyMutex locks + idempotency guarantees
Imported capability securityInput validation, staged scanning, quarantine, persisted reports, and digest verification

The security model is transparent and consent-based. You remain in control of what the AI can do on your machine.

Guardrails Settings Location

All guardrail settings can be configured at:

  • Database: stored as an encrypted guardrails category inside app.getPath('userData')/cowork-os.db
  • UI: Settings (gear icon) → Guardrails tab

Settings Migration

Legacy JSON settings files are automatically migrated into encrypted SecureSettingsRepository categories:

  • Migration creates a .migration-backup file before proceeding
  • On successful migration, both backup and original are deleted
  • On failed migration, backup is preserved for recovery
  • Migration logs are available in the app console