Skip to content

Validate that agent egress cannot bypass configured release controls #659

Description

@imran-siddique

Parent tracker: agentrust-io/.github#41

Problem and scope

Merged #657 supplies operator-owned tool and response sensitivity ceilings. Those checks do not mediate direct agent sockets, files, subprocesses, alternate model endpoints or every log sink.

Define a reusable confinement contract and exercise one supported reference deployment. Keep OS/runtime adapters explicit; do not claim that an SDK wrapper alone confines arbitrary agent code.

Acceptance criteria

  • Inventory plaintext sources and sinks, subprocess inheritance, local files, DNS/network, alternate endpoints, stdout/stderr, telemetry and audit metadata.
  • Define which component mediates each route, its trusted boundary, startup behavior and behavior when the mediator is unavailable.
  • Build a canary harness with permitted-path positives and direct bypass attempts. Observe attempted release at independently controlled sinks, not only application error messages.
  • A chosen reference sandbox blocks routes outside the approved gateway; removing its relevant restriction makes the corresponding test fail.
  • Test restart, child processes and configuration changes without a fail-open window.
  • Publish supported deployment assumptions and untested channels, including covert/side channels. Canary scans do not establish absence of all leakage.

Dependencies and boundaries

#124 owns policy enforcement inside the TEE; #462 owns measurement-bound signing-key custody. This issue owns mediation around that enforcement. Hardware-backed claims require those dependencies and measured application identity. Existing #657 is merged; do not reopen its completed scope or treat its unit tests as confinement evidence.

Reference implementation status (2026-09-18)

The checked criteria refer to the bounded native-Linux reference, with acceptance review still pending. #662 was approved and merged. Follow-up #663 at a56f5ab adds bridge/watchdog failure handling, live operator restrictions and audit/export probes.

The hosted confinement run passed all 57 tests in 22.34 seconds; all 17 hosted checks passed, with two conditional skips. The confinement-evidence artifact contains independently observed delivery/termination outcomes and host versions. The full local suite passed 1,992 tests with 25 skips and 89.46% coverage.

Protected runs delivered only to approved sinks. Network, file, logging and sink-ceiling mutations produced the intended leaks. A separate payload-free lease watchdog stopped the container after bridge kill/pause; watcher death also stopped the exchange. Removing the watcher left the adversarial container alive. Live revocation/restoration preserves the original cMCP sensitivity and sink ceilings; removing the gate fails the queued-call regression. Invalid/stale operator revisions close admission until a valid newer update.

Actual SQLite records, chain entries, SDK-exported spans and logs were inspected for response echo, upstream errors, malformed stdout and stderr echo. Two tests first reproduced private-data leaks in audit-observer/OTel failure diagnostics, which now omit sink representations and exception tracebacks. The host core-handler refusal and hard core limits from #662 remain in force.

The route inventory, reproducible commands and trust assumptions are in docs/confinement.md. Keep this issue open for review of #663. The reference assumes a surviving trusted host, watcher and responsive daemon; simultaneous watcher/bridge loss, pre-admission orphan collection and host-wide recovery are outside its claim. Live restrictions are not arbitrary hot catalog/classifier replacement or rollback-protected policy storage. Arbitrary exporters, sensitive metadata/hashes, downstream custody and covert channels are not certified by literal canary tests. No hardware-backed guarantee or memory-erasure claim is made.

Activity

  1. Yatsuiii commented on Sep 18, 2026

    @Yatsuiii
    Contributor

    Adding one route to the AC#1 inventory that I think behaves differently from the others listed.

    Process crash artifacts. On a fatal signal with core disposition (SIGSEGV/SIGABRT from a native dependency, or an operator kill -QUIT), the kernel writes the gateway's address space to whatever /proc/sys/kernel/core_pattern designates. At mcp/proxy.py:1719 the full upstream response is held as a str in gateway memory before the #657 ceiling is applied, so a dump at that point contains payloads the gateway is in the act of withholding. CPython does not zero freed objects, so it also reaches back over earlier calls in the same process. This is not a side channel, it is a direct plaintext write to storage.

    What makes it different from the listed routes is that the workload cannot mediate it. core_pattern is not namespaced: a process that is PID 1 in its own PID namespace and root in its own user namespace reads the host value and cannot write it, and I confirmed a crash there is captured by the host handler and stored outside the namespace. ulimit -c 0 is also weaker than it looks. With a piped core_pattern the kernel still enters the dump path and invokes the handler (WCOREDUMP set in the wait status); systemd-coredump declines to persist only because it reads the limit via the %c specifier, and a handler that ignores it writes the dump anyway. So the destination is a host property, which I think puts this inside AC#4 rather than only AC#6.

    Relevant to the subprocess-inheritance clause in AC#1: RLIMIT_CORE is an inherited process attribute, and mcp/stdio.py:222 spawns children with no preexec_fn, so whatever the gateway carries, every stdio child gets. A single resource.setrlimit(RLIMIT_CORE, (0, 0)) at startup would cover the whole process tree, but per the above it is necessary rather than sufficient, so the host core_pattern probably has to become a published deployment assumption too.

  2. imran-siddique commented on Sep 29, 2026

    @imran-siddique
    MemberAuthor

    @Yatsuiii each of these landed in #662, merged the same day: the host core_pattern is checked before plaintext admission and piped handlers refuse startup, since Linux ignores RLIMIT_CORE on a piped dump; RLIMIT_CORE=0 is set hard and soft on the bridge and the agent, and the stdio tools inherit it. See the crash-artifacts row in docs/confinement.md and check_core_pattern in tests/confinement/test_adapter.py. With #663 merged on 21 September every acceptance item here is delivered, so I am closing this; the published assumptions and untested channels are in the same doc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions