Skip to content

feat(process): preauthorize inner host integrations from trusted config - #55

Merged
locez merged 3 commits into
mainfrom
feat/preauthorize-inner-host-integrations
Sep 15, 2026
Merged

locez merged 3 commits into
mainfrom
feat/preauthorize-inner-host-integrations

Conversation

@locez

@locez locez commented Sep 15, 2026

Copy link
Copy Markdown
Owner

Summary

Trusted global configuration now preauthorizes the inner action sandbox. [permissions] ssh_agent / dbus / gpg_agent = true puts the matching integration into the ordinary action baseline once its endpoint validates; readonly_paths / readwrite_paths were already directly inner-visible. review_paths, deny_paths, network, and product-private paths keep their per-action gates.

Why: requiring a second request for a capability the user had already declared in their own trusted config made the sandbox impractical for agent work without adding any authority the configuration had not granted.

What changes

Area Change
Wiring MerryConfig::host_integrations() to action_process_backend_options to ProcessBackendOptions.host_integrations to BwrapProcessEnvironment, with prepared_action_process_backend_options as the single production entry point
Reachability vs announcement SSH_AUTH_SOCK / DBUS_SESSION_BUS_ADDRESS / GNUPGHOME stay named even when deny_paths or review_paths masks the mount, so a masked endpoint fails at connect time instead of the capability silently disappearing
Plan composition One sandbox plan resolves the enabled bindings once and shares them between mounts and the client environment; the positional tuple becomes a typed HostIntegrationBinding
Cleanup Remove the unused PathAccessRuleSource::TrustedGlobalConfigWritableCeiling variant; configured read-write paths are preauthorized directly
Tests Shared sandboxed re-entry scaffolding that fails closed when an --exact child filter matches no test; permission admission tests split by the policy each group owns
Docs README, examples/config.toml, model-visible capability text, and permission schema descriptions

Boundaries preserved

  • review_paths remain the per-action gate; deny_paths always mask and are never reopened by an approval.
  • Only user-trusted global config preauthorizes; project-local config never grants host-wide access.
  • Network is never baseline-preauthorized.
  • .git metadata stays read-only by default with a per-action grant path.
  • Configured integrations still validate the endpoint per action; an integration the config did not enable still requires review.

Verification

  • cargo fmt --all --check - clean
  • cargo clippy --all-targets --all-features -- -D warnings - exit 0
  • cargo test --all - 2083 passed, 0 failed across 58 suites
  • git diff --check - exit 0
  • Commit 01ce81e is GPG-signed (%G? = G, signer Locez <locez@locez.com>)

Notes and follow-ups

  • No end-to-end D-Bus client test inside both sandboxes yet; current coverage is options mapping plus plan-level mount and export assertions.
  • The stable-prefix prompt text changes once in this commit; the prefix stays stable within a session.

Trusted global configuration now preauthorizes the inner action sandbox:
`[permissions] ssh_agent/dbus/gpg_agent = true` puts the matching
integration into the ordinary action baseline once its endpoint
validates, and `readonly_paths`/`readwrite_paths` were already directly
inner-visible. `review_paths`, `deny_paths`, network, and
product-private paths keep their per-action gates, project-local
configuration still never grants host-wide access, and endpoint
validation keeps running per action.

Why: requiring a second request for a capability the user had already
declared in their own trusted config made the sandbox impractical for
agent work without adding any authority the configuration had not
granted.

Behavior changes in this commit:

- Path policy keeps deciding reachability, not announcement.
  SSH_AUTH_SOCK, DBUS_SESSION_BUS_ADDRESS, and GNUPGHOME stay named even
  when a deny or review rule masks the mount, so the failure appears at
  connect time instead of the capability silently disappearing.
- One sandbox plan resolves the enabled host-integration bindings once
  and shares them between mounts and the client environment, replacing
  the unnamed positional tuple with a typed binding.
- Drop the unused PathAccessRuleSource::TrustedGlobalConfigWritableCeiling
  variant; configured read-write paths are preauthorized directly.
- Share the sandboxed re-entry test scaffolding and fail closed when an
  `--exact` child filter matches no test, which previously passed as an
  empty run.
- Split the permission admission tests by the policy each group owns and
  document the simplified model in README and examples/config.toml.

Verification: cargo fmt --all --check; cargo clippy --all-targets
--all-features -- -D warnings; cargo test --all (2083 passed, 0 failed
across 58 suites); git diff --check.
Why: the model only requested network after a sandboxed command already
failed, and it reached for `grep -r` even when `rg` and `fd` were
installed. The prompt named the capability mechanism but never told the
model that network is never implicit, so `gh`-style commands were tried
first and recovered second.

Behavior:
- `CODING_AGENT_POLICY_PROMPT` now states that an action starts with no
  network and decides per command instead of per failure, and that a
  withheld capability is the first explanation for a credentials,
  authentication, or connectivity error.
- The network schema, the `permissions` object description, the
  `run_process` description, and the failed-action recovery guidance
  repeat the same contract where the model reads it.
- The coding profile probes the action PATH once per composition and
  appends `Action PATH search tools:` to the workspace capability
  summary, so the prompt asks for `rg`/`fd` only when they exist and
  names the bounded `grep -r`/`find` fallback otherwise.
- `action_process_path()` becomes the single owner of the PATH a
  sandboxed action inherits; the probe and the bubblewrap environment
  both resolve it there.
- The long capability summary moves to `concat!` so each bullet is one
  reviewable line.

Verification: cargo fmt --all --check, cargo clippy --all-targets
--all-features -- -D warnings, and cargo test --all (58 suites, 2089
passed, 0 failed) all pass. Six new probe tests cover rg+fd, the
`fdfind` distribution name, each missing-tool wording, candidate
construction from named PATH directories, and the composed profile
summary; contract tests pin the model-visible text.

Refs: #55
Why: `test_close_cancels_an_unfinished_run_and_keeps_terminal_result`
started a run with an instantly completing provider and closed it
immediately. Whether `close()` observed cancellation or an already
finished run therefore depended on thread scheduling, so the CI job
failed with `COMPLETED` instead of `CANCELLED` on a loaded runner.

Evidence: under 64 busy loops on a 32-core host the old formulation
reported `completed` in 273 of 300 iterations (first at iteration 2),
while the same engine build with a provider that cannot finish on its
own reported `cancelled` in 300 of 300.

Behavior:
- The cancellation test now closes a pending-provider run, which reaches
  the runtime's cancellation path deterministically and still proves the
  durable terminal result survives for `result()`.
- A new test covers the other half of the contract: a run drained to EOF
  keeps its completed result after `close()`.
- `AgentRun.cancel`, `AgentRun.close`, and the SDK README now say that
  cancellation is cooperative and that a run which reached its terminal
  state first keeps that result instead of being rewritten.

Verification: uv sync --locked, ruff format --check ., ruff check .,
ty check, and pytest tests -q (53 passed) all pass; tests/test_run.py
passes three times in a row under the same CPU load that reproduced the
failure. uv build succeeds.

Refs: #55
@locez
locez merged commit 8a96dac into main Sep 15, 2026
8 checks passed
@locez
locez deleted the feat/preauthorize-inner-host-integrations branch September 15, 2026 19:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant