Additional

Approval boundary validation

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

Implementation date: 2026-09-18. Local validation, not a release or deployment record.

No-popup follow-up (2026-09-19)

The default local runtime no longer opens the legacy approval modal. Allowed operations execute silently. A policy result that remains ask is represented by an assistant_message followed by the durable inline request_user_input card approval_decision, with Deny first and an explicit Allow once option. The path covers network/on-request access, credential use, data export, MCP and other external side effects, eligible outside-workspace paths, and explicit allowAutoApprove: false requests. Hard denials, approval: "never", automated tasks without a human-input channel, and protected/administrator-restricted paths fail closed. Set COWORK_APPROVAL_PROMPTS=on only to exercise the legacy queue diagnostically.

Startup recovery now reads the complete pending approval and input-request queues. It fails pending approval rows and assistant approval cards closed instead of reviving them after restart; the next attempt must create a fresh decision. The one-time memory migration backed up the live database at ~/Library/Application Support/cowork-os/backups/cowork-os.pre-no-prompt-memory-cleanup-20260919-0129.db and rejected 12,679 stale pending_memory_writes rows without replaying them. New durable memory writes auto-commit in the default no-prompt mode; explicit COWORK_MEMORY_WRITE_APPROVAL_MODE values remain available for controlled review runs.

Focused approval, input-request, policy, timeout, registry, and memory tests passed after this follow-up. The development instance was restarted after the patch so the running main process now loads the no-popup policy.

Named profiles now authorize routine work from their actual filesystem, shell, network, rule and consent boundaries. Ordinary allowed work does not create an approval lifecycle. never denies missing authority without prompting; it does not widen sandbox access. Legacy tasks keep their compatibility policy. New root task creators share the same default-profile normalizer.

Settings migration is versioned and stores the previous representation. Explicit profiles and legacy restrictions are retained. Pending approvals are bound to operation arguments and effective authority; policy changes invalidate them. Late responses cannot revive completed tasks, and concurrent requests resolve independently. Persistent shell grants clear when scope changes.

The macOS sandbox's localhost exception was narrowed to outbound loopback destinations. Native testing found the previous local-address rule admitted external traffic. The corrected rule permits workspace writes and denies both a write outside the allowed roots and external TCP access.

Reproduction and executable coverage

npm run qa:approval-boundaries runs the focused policy, migration, path, broker, registered-tool, and approval-dialog suites. npm run qa:approval-boundaries:smoke builds the Electron artifact and runs isolated, real SQLite-backed daemon/tool scenarios without calling a model provider.

The same smoke can be run against both shipped execution artifacts:

npm run build:daemon
COWORK_SMOKE_DIST=daemon node scripts/qa/approval-boundary-smoke.cjs
ELECTRON_RUN_AS_NODE=1 node_modules/electron/dist/Electron.app/Contents/MacOS/Electron scripts/qa/approval-boundary-smoke.cjs

The Electron command uses its embedded runtime, not a renderer-driven provider conversation. The script uses a new temporary database and workspace and removes its own fixtures afterward. Bounded command scenarios require an available OS sandbox and deliberately fail without one.

ScenarioEvidence
Fresh temporary-session note create/update/read/edit/mkdirRegistered tools and real daemon, all three built-in profiles; zero approval calls, rows, events or blocked status
Routine bounded shell commandReal registered run_command, Ask for approval and Approve for me; OS sandbox active, zero requests
Sibling temporary session escapeBounded shell write using ../ is rejected and leaves the sibling session untouched
Eligible external file exceptionExactly one pending row; one-shot response resolves it
Bounded neverExternal write denied without an approval request
Pending policy narrowingChanged profile invalidates an outstanding approval
Completed taskLate response denied without changing completed status
Concurrent requestsIndependent decisions; duplicate response inert; task stays blocked while another request remains
CancellationAborted execution clears the pending approval
Path and capability constraintsFocused canonical-path, protected-path, symlink, profile inheritance, read-only, missing-backend and network suites
Migration and creatorsSettings v1/v2, bounded dont_ask, explicit profile/legacy fields and Control Plane tests

Checks completed during implementation

  • Harness: 804 tests across 56 files, plus six deterministic replay fixtures.
  • Additional surface run: 199 tests across mailbox, improvement automation, cron, Control Plane, browser/network policy, and sandbox suites.
  • Focused boundary run: 291 tests across policy, migration, path, broker, registered tools, approval presentation, file mutation races, sandbox, ACP authority, and Council cron profile propagation.
  • Execution/network/browser run: 136 tests covering shell, browser, network policy, executor cancellation, and follow-up lifecycle.
  • Type check, Electron build, Node daemon build, CLI build, and renderer build passed. Renderer reports the existing large-chunk warning.
  • Real SQLite-backed smoke passed under Node, the Node daemon artifact, and Electron's embedded runtime on macOS. Each run covers routine create/update/read/edit/mkdir and bounded shell work with zero approval calls, rejects a sibling-session ../ escape, and covers exception, never, stale-authority, completed-task, concurrent, and cancellation cases.

These runs overlap; their counts must not be added into a unique-test total. The scoped security harness reports 128 static candidates across 13 high-risk files when run with an explicit file list and --fail-on-findings; it is not a clean gate because its verifier/proof stages are intentionally not run and its text scanner flags sandboxed spawn calls, validated process-management helpers, and unrelated dirty-file candidates. The candidate report is retained at /tmp/cowork-approval-complete-security-report.json; no candidate was silently suppressed.

The production-fix scenario is recorded in scripts/qa/eval-cases/approval-boundary-regression.json with executable coverage pointers. The JSON is a regression specification, not an executed model evaluation. The PR regression-policy command skips outside a pull-request event; no PR/CI or release result is claimed here.

Live OS enforcement is verified on macOS. Linux/Docker and Windows behavior has unit coverage only in this run. External ACP runtimes cannot enforce the bounded policy and now reject bounded tasks before spawning. They require explicit unrestricted authority and retain their separate consent configuration; a native CoWork profile never silently promotes them to approve-all.