feat(capsule): add allow_persistent operator sub-grant gating spawn-persistent - #873
Conversation
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request enforces a stricter security boundary for persistent process spawning within the capsule environment. By requiring an explicit 'allow_persistent' sub-grant in addition to the existing 'host_process' capability, the host now correctly conforms to the documented WIT contract, preventing unauthorized persistent background processes from outliving their parent instances. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. A process that lives past its day, Needs permission to go on its way. With a gate now in place, It won't leave a trace, Unless the operator says 'okay'. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces a new capability, allow_persistent, which acts as an operator sub-grant on top of host_process to allow capsules to spawn persistent background processes that outlive the instance. The capability is disabled by default (fail-closed) and is verified during process spawning, returning CapabilityDenied if missing. Tests and manifest structures have been updated accordingly to include this new capability. There are no review comments, and I have no feedback to provide.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
…ersistent The astrid:process WIT documents spawn-persistent as gated on host_process AND an operator allow_persistent sub-grant, but the host only checked host_process + an authenticated principal — the sub-grant did not exist. Adds it: - CapabilitiesDef gains allow_persistent (bool, #[serde(default)] fail-closed), an operator-reviewed sub-grant of host_process. host_process alone grants ephemeral spawn/spawn-background; persistent exec (a child that outlives the pooled instance and, on macOS, lacks die-with-parent) additionally requires this opt-in. The serde-derived held_names/has surface it automatically, so enumerate-capabilities reports it. - spawn-persistent now returns capability-denied (audited) when the capsule has host_process but not allow_persistent; the ephemeral tier is unaffected. No WIT change — the contract already specified this gate; this makes the host match it. Capsules that spawn persistent (e.g. capsule-shell) must add allow_persistent to their manifest.
659aaaf to
088704f
Compare
There was a problem hiding this comment.
Pull request overview
This PR makes the host runtime conform to the astrid:[email protected] contract by introducing and enforcing an operator sub-grant (allow_persistent) required for astrid:process.spawn-persistent, in addition to the existing host_process capability.
Changes:
- Add
CapabilitiesDef.allow_persistentas a fail-closed (#[serde(default)]) operator sub-grant layered on top ofhost_process. - Gate
spawn-persistentto returncapability-denied(audited) unlessallow_persistentis held. - Update capability introspection tests and test manifests to include the new field.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| crates/astrid-capsule/src/security/manifest_gate.rs | Updates a test manifest literal to include the new fail-closed capability field. |
| crates/astrid-capsule/src/manifest/capabilities.rs | Introduces allow_persistent and updates held_names/has tests for the new capability name. |
| crates/astrid-capsule/src/engine/wasm/host/process/mod.rs | Enforces the new allow_persistent gate in the spawn_persistent host implementation with auditing. |
| crates/astrid-capsule/src/engine/mcp_tests.rs | Updates a test manifest literal to include allow_persistent: false. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
## Linked Issue Closes #853 ## Summary `chore: release` — bumps all workspace crates **0.7.0 → 0.8.0** and rolls `CHANGELOG [Unreleased]` into `[0.8.0] - 2026-06-10`. Consolidates the ~50 PRs landed since v0.7.0: - **HTTP admin gateway** (#756 + follow-ups) — `astrid-gateway`: principals/caps/quotas/groups/invites/env over HTTP, audit SSE + historical queries, agent-prompt SSE, OpenAPI emission, bus-direct admin path (285× throughput), rustls TLS, CORS, metrics, invite + keypair CLI verbs. - **Runtime concurrency overhaul** (#813/#816/#819/#820) — routed per-(capsule, topic, principal) IPC with DRR fairness, async Wasmtime, dynamic instance pools, split blocking/IO semaphores, per-principal fuel + memory ledgers with enforcement and usage reporting. - **Host process + introspection surface** — `astrid:process` persistent tier (#866/#867/#871/#873), capability introspection (#868/#869), `astrid mcp serve` (#879/#880), `astrid-emit` (#814/#842). - **Security** — macOS 15+ sandbox no longer silently disabled (#855/#857), audit-feed principal scoping (#850/#851), failed-redeem audit rows (#846), `self:agent:list` roster leak (#848), pair-device redeem rate-limit (#844), bearer revocation + wire format v2 (#772). Breaking: `Capsule.toml` `[publish]`/`[subscribe]`-only IPC surface (#858/#864/#865, `[[interceptor]]` / `ipc_*` arrays / `[[topic]]` removed), bearer v2, MSRV 1.95, `astrid-openclaw` removed (#833). ## Changes - `[workspace.package].version` `0.7.0` → `0.8.0` + the 23 in-workspace path-dependency pins (now including `astrid-emit`; `axum`/`axum-server` stay on their external 0.7). - CHANGELOG: `[Unreleased]` → `[0.8.0] - 2026-06-10` with a release **synopsis** (extracted into the GitHub release body by release.yml on tag). Keeps the earlier roll's curation (canonical section order, bullets verbatim) and merges the 13 entries added to `[Unreleased]` by the 12 PRs that landed after the branch was first cut (#851–#885) — verified both directions that no bullet was lost. Branch history note: rebuilt on current `main` (was `CONFLICTING` and 12 commits behind); previous tip was `54c7294`. ## Release steps (maintainer) 1. Merge this PR. 2. Tag `v0.8.0` on the merge commit → release.yml creates the GitHub release from the CHANGELOG block. 3. Publish/distribute the new `astrid` binaries as usual. ## Test Plan Release PR — no code changes. `cargo check --workspace` passes on the rebuilt branch; full test/clippy/MSRV matrix runs in CI. The changelog merge was verified bullet-by-bullet in both directions: every `[Unreleased]` bullet on `main` appears in the rolled `[0.8.0]` section, and every bullet from the earlier curated roll is preserved verbatim.
Linked Issue
Closes #872
Summary
astrid:process.spawn-persistentis documented ([email protected]) as gated onhost_processand an operatorallow_persistentsub-grant, but the host only checkedhost_process+ an authenticated principal — the sub-grant did not exist, so the gate the contract promised was never enforced. This makes it real, with no WIT change.Changes
CapabilitiesDef.allow_persistent(bool,#[serde(default)], fail-closed) — an operator-reviewed sub-grant ofhost_process(the "Airlock Override" precedent).host_processalone grants the ephemeralspawn/spawn-backgroundtiers; a persistent child (outlives the pooled instance; on macOS lacksdie-with-parent) additionally requires this opt-in.spawn-persistent: returnscapability-denied(audited) when the capsule hashost_processbut notallow_persistent. Checked off the manifest-derivedcapability_names; ephemeral tiers unaffected.enumerate-capabilitiesreports it automatically via the serde-derivedheld_names/has— no parallel list to drift.[Unreleased].No WIT change — the contract already specified this gate; this makes the host conform. Capsules that spawn persistent processes (e.g.
astrid-capsule-shell) must addallow_persistentto their manifest[capabilities].Test Plan
Automated
cargo test --workspacepasses — incl. the four integration-testCapabilitiesDefliterals updated for the new field (the original CI failure)clippy --all-targetsclean)Unit-tested: the manifest →
capability_nameslink via theheld_names/hasserde-derivation tests (all-held, default-holds-nothing, unknown-name). A host-fn integration test for the denial path would need security-gate scaffolding (no existing pattern) — noted as a follow-up.Checklist
[Unreleased]