# Plugin API safety verification

Scope: implemented API v1, including Analytics/owned-App materialization. User packages now require the signed fixed-arena sandboxed helper, explicit digest-bound enablement and declared permissions. See PLUGIN_ISOLATION_NOTES.md and ISOLATION_CHECKPOINT.md for native denial/resource evidence. This is regression evidence, not an independent security certification; supported-OS and final distribution gates remain open.

Baseline before this slice: 295/295 passed. Focused new Analytics/materialization/native UI suite: 11/11 passed. Full source/package suites each passed 306/306; million-row verification passed. Detailed evidence is recorded in the completed ANALYTICS_CHECKPOINT.md.

## Enforcement and evidence

| Boundary | Lowest relevant enforcement | Executed test coverage |
| --- | --- | --- |
| Package identity/versions/paths/consent | PluginManifest/Package/Manager; bounded regular files, contained entry, digest consent, reserved bundled root + ID | PluginTests; PluginContextExpansionTests installation/digest/revocation; PluginIsolationTests signed user consent/lock/digest/signature checks |
| Read permissions / genuine SQLite RO | PluginDatabase.handle/query; read-only connections, authorizer, exact positional bindings | PluginDatabaseTests: typed values, permission denial, write/PRAGMA/ATTACH/file function rejection, committed WAL reads, truncation/deadlines, queued/active cancellation/replacement |
| Locked or unlicensed writes | PluginWritePolicy epoch plus transaction progress and commit hooks; never just disabled UI | PluginWriteTests permission/lock/license/old epoch and active relock/cancel; PluginWriteJobTests, PluginJobFormTests and PluginMaterializationTests |
| One atomic mutation | Host BEGIN IMMEDIATE/COMMIT; expected counts; no triggers/functions/REPLACE/DDL/RETURNING in ordinary DML | PluginWriteTests batch rollback/optimistic mismatch/oversize; post-COMMIT helper failure reports accepted write; imports/forms/jobs use the same route |
| New App publication | PluginAppService private staging, migration transaction, no-replace publication | PluginAppTests and PluginTemplateTests: ordering/replay/incompatibility/constraints/revocation/no-overwrite, zero metadata in ordinary files, configured template owner/dependency checks |
| Explicit adoption | Canonical schema comparison, declared version, confirmed metadata only, writer-reserved backup | PluginAppAdoptionTests: extra/mismatched schema/metadata denial, no mutation on review, consent/license, stale version, snapshot failure, rollback, disable/cancel/replacement |
| Owned migrations | Ordered hashed history, canonical operations, transaction/commit hooks | PluginAppUpgradeTests and PluginMigrationOperationTests: WAL/DELETE journals, competing writer, restored recovery, no-op/replay, DDL failure, history tampering, stale schema, source replacement, cancel/relock/disable during snapshot |
| Custom and streaming transfers | Explicit native file selection, regular/no-follow paths, bounded private spool, exact mapping, snapshot-backed native insert, atomic export publication | PluginImporterTests, PluginExporterTests, PluginStreamingTests: symlinks/sidecars, exact typed values, immutable review, schema drift, live-WAL read snapshot, cancellation/rollback, output preservation, abandoned staging cleanup |
| Bulk App results | Same-owner canonical schema and required permissions; one read-only SELECT; full private spool; retained source revision; reviewed replacement with mandatory backup | PluginMaterializationTests: 100K/million exact outputs, NULL/BLOB/Int64, empty replacement, constraint rollback, lock/license, stale review, disable/close, relock during backup and insertion, bad SQL/functions/multiple statements, missing ownership/schema permission, forged trigger/schema, snapshot failure, approval denial and staging cleanup |
| Native callbacks and lifecycle | Manager generations, leases, helper termination, model generation before publishing | PluginJobTests, PluginWriteActionTests, PluginContextExpansionTests and UI suites: failure/timeout, cancelled/closed/revoked runs, late output discarded, no direct write context in job computation/inspectors/actions/previews |
| Files/clipboard/selection/storage/events | Per-invocation explicit grants, host permission revision, source/alias protection, scoped storage, RO observer | PluginCapabilityTests and PluginContextExpansionTests: missing permissions, exact output IDs, no overwrite, per-plugin namespace, denied capabilities, shared-input revocation, one-shot changes, no unrequested table fetching |
| Durable outcome vs helper success | Host write receipt + external PluginHistoryStore | PluginHistoryTests and PluginWriteTests: accepted COMMIT despite later helper failure, rollback outcome, crash/unknown records, bounded pages/retention, missing source/snapshot, recovery read-only and post-commit publication failure |
| Analytics reference boundary | Only public registrations/queries/forms/materialization/transfers; no private plugin hooks | PluginAnalyticsTests and PluginAnalyticsUITests: real template/import/form/pipeline/chart/export/reopen, invalid pipeline preserves previous output, no ordinary-file adoption, JSON duplicate/precision rejection, read-only denial, native result review, Light/Dark sizing and actual Raw Database button |

## Important limits

- The helper has a fixed JavaScript allocation arena and signed OS sandbox, plus CPU/wall/IPC/footprint limits. Native memory outside the arena is monitored separately. Local package execution requires explicit consent and a valid isolated helper; installation is inert. These proofs are current-host evidence, not an independent certification or a completed supported-OS release matrix.
- SQLite file identity/VFS checks cover ordinary replacement and lifecycle races. They do not claim an atomic defense against an adversarial same-user process racing filesystem renames.
- App migration metadata proves declared compatibility, not authenticity. It is not a cryptographic ledger. The separate mutation journal cannot atomically commit with a user's database; ambiguous crash outcomes remain explicit.
- In-process native UI tests cover exercised controls, layouts and lifecycle on this test Mac. Supported-OS coverage, complete VoiceOver/manual UX review and signed/notarized release qualification remain separate open entries.
- Every future mutation capability must add to this matrix and rerun its tests. Previously passed evidence does not authorize new bypass paths.

## Automation and runner containment — 2026-09-30

- `PluginAutomationTests`: validates persisted rule bounds, exact trigger conditions, 10,000-event coalescing, disabled/active/chain-origin suppression, file identity replacement, source-byte preservation, no preferences creation for an ordinary open, symlink/oversize/duplicate rejection.
- `PluginAutomationUITests`: real public read/write job chains; attended preview and snapshot commit; lock/license failures stop later steps; cancellation/disable/changed digest/closed session; persisted opt-in/new-open request without replay; real browser insert and duplicate failure; CSV import; external change without invented insertion; window/source replacement; light/dark native Raw Database button and Escape. Existing reviewed-job tests retain rollback/snapshot/revision/cancel coverage; per-step commits are not a cross-step transaction.
- `PluginRunnerResourceTests`: real allocating JavaScript is rejected by the allocator or terminated, next invocation succeeds; independent wall deadline works without caller polling; admission saturates/releases. Existing PluginTests keep timeout/output/cancel/failure/third-party refusal coverage. A memory watchdog is not a hard heap allocator limit or OS sandbox, and is not grounds to enable user packages.

These tests supplement, rather than replace, the existing permissions, source lifecycle, accepted-COMMIT, migrations, import/export, grid and Quick Look suites. Native layout assertions are not a completed multi-OS/VoiceOver review.

## Isolation verification — 2026-09-30 / 2026-10-01

PluginIsolationTests and the repeatable native sandbox probe establish the new allocator/OS/pipe boundary described in PLUGIN_ISOLATION_NOTES.md. Full source 321/321 and packaged 321/321 pass. PluginHistoryTests now also reject symlink/hard-link companions after the concurrent journal-resolution race fix. The existing 902-MiB 10M-row fixture passes full-screen rendered scrolling/resizing (20K visited rows, 41 retained views, peak 77,400,704 bytes, resize 58 ms); its SHA-256 is unchanged. This is a controlled test-process result, not a guarantee for every dataset or the full 100M/10-GB release matrix.
