Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions analysis/freeze/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,3 +212,10 @@ including the failed BND audit. Pilot v8 remains the historical fresh-seed
registration; pilot v9 inherits its commitment unverified and does not
authorize collection. A pilot run needs a fresh-seed run registration and
execution-context admission for the revised graphs and cache.

CSE v24 and pilot v10 supersede v23 and pilot v9 after #589 adds
session-bound capability audits and refuses the unsupported loopback endpoint
grant. They inherit #578's public-observation and cache source coverage,
commitments and pending gates. Historical registrations remain unchanged.
The inherited seed commitments remain unverified without private sources;
these registrations do not authorize collection or relay-backed confinement.
393 changes: 393 additions & 0 deletions analysis/freeze/cse-causal-study-pilot-v10/declaration.json

Large diffs are not rendered by default.

584 changes: 584 additions & 0 deletions analysis/freeze/cse-causal-study-pilot-v10/freeze-manifest.json

Large diffs are not rendered by default.

359 changes: 359 additions & 0 deletions analysis/freeze/cse-causal-study-v24/declaration.json

Large diffs are not rendered by default.

555 changes: 555 additions & 0 deletions analysis/freeze/cse-causal-study-v24/freeze-manifest.json

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions analysis/treatment-integrity/macos-confinement/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,15 @@ adapter hash, imported `system.sb` hash, compiled-profile hash, and policy id.
Temporary synthetic hidden bytes and the compiled profile are removed after the
audit; only their hashes, exposure booleans, and role-scoped policy metadata are
retained.

Since the session-bound attestation (2026-09-14), `audit-macos --policy
<policy.json>` runs the same matrix under a session's exact policy
(`MacOSPolicy.canonical_dict` JSON) so the retained record's profile hash and
policy id are the ones `wrap_verified_command` demands; `policy_source` in the
report says whether the synthetic or a session policy was audited, and the
report's hidden roots are digests, not paths. Only `deny-external` is supported.
The proposed `loopback` grant is refused because a port pin cannot isolate
one service across the host's IPv4 and IPv6 addresses. Previously passing
loopback attestations cannot authorize a launch. This change preserves
session-bound audit support but does not enable relay-backed confined sessions.
See `docs/monolithic/matched-session.md`.
31 changes: 31 additions & 0 deletions docs/monolithic/matched-session.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,37 @@ process record, including on a nonzero process exit. Final snapshots and
postflight failures remain in `matched-session.json` alongside the original
launcher failure.

The `attestation` must have been produced under the arm's exact confinement
policy, or the launch wrapper refuses it: `python -m
aisle.harness.treatment_confinement audit-macos --policy <policy.json>
--output <attestation.json>` runs the full deny/allow matrix under that policy
(its sentinels are placed inside the policy's own visible, output and hidden
roots and removed afterwards) and retains the profile hash and policy id the
wrapper demands (TRT-5/TRT-7). The policy must admit the audit's probes
(`/bin/bash`, `/bin/cat`, the Apple developer Git, and either `/usr/bin/nc`
or a Python interpreter for the Unix-socket case) and must not admit its
unlisted-executable control (`/usr/bin/printf`). By default each sentinel goes
into the last directory of the matching root list; `--sentinel-visible`,
`--sentinel-output` and `--sentinel-hidden` name controller-private
directories instead so the audit never writes into another arm's readable
view. The retained report replaces hidden-root paths with their digests
(`policy_id` still binds the full policy). A sentinel left behind by an audit
process that died uncleanly is reclaimed on the next run; any other occupant
refuses. Only `deny-external` (no network) is supported. The proposed
`loopback` port policy is rejected during compilation, provider admission,
audit startup, and launch verification, including for previously passing
attestations. macOS SBPL's `localhost` selector covers every address owned by
the host: a relay on `127.0.0.1:<port>` can coexist with a separate service on
`[::1]:<port>`. A port number therefore cannot establish exclusive relay
access. Literal IP endpoint selectors are rejected by the sandbox compiler.

Session-bound audits remain available for `deny-external`. A pinned
`provider.relay_port` configures a listener but grants no network authority.
Relay-backed confined sessions remain unavailable until an exclusive transport
is implemented and validated. This stop condition must not be bypassed with a
broader policy or a cached attestation. The pilot still requires exact-context
admission and independent confinement evidence before collection.

Deterministic integration tests run real child processes for both arms through
an explicitly synthetic pass-through adapter. Those tests cover the process
boundary and failure retention. Their synthetic access logs and capability
Expand Down
7 changes: 1 addition & 6 deletions src/aisle/harness/matched_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,7 @@ def _worker_binding(launch, config, config_path):
raise ValueError("worker runtime differs from bound run runtime")
if launch["python_sha256"] != hashlib.sha256(worker_interpreter().read_bytes()).hexdigest():
raise ValueError("worker and controller interpreter identities differ")
policy = MacOSPolicy(
**{
key: value if key == "network_policy" else tuple(Path(p) for p in value)
for key, value in launch["policy"].items()
}
)
policy = MacOSPolicy.from_canonical(launch["policy"])
readable = (*policy.visible_roots, *policy.runtime_read_roots, *policy.output_roots)
if any(config_path.is_relative_to(p) for p in readable) or not any(
config_path.is_relative_to(p) for p in policy.hidden_roots
Expand Down
17 changes: 10 additions & 7 deletions src/aisle/harness/matched_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,13 +315,10 @@ def _permission_ids(
for arm in sorted(ARMS):
other = next(name for name in ARMS if name != arm)
view, other_view = Path(views[arm]).resolve(), Path(views[other]).resolve()
declared = bindings[arm]["policy"]
policy = MacOSPolicy(
**{
key: value if key == "network_policy" else tuple(Path(p) for p in value)
for key, value in declared.items()
}
)
try:
policy = MacOSPolicy.from_canonical(bindings[arm]["policy"])
except ConfinementError as exc:
raise AdmissionError(f"invalid confinement policy: {exc}") from exc
editable = {view / p for p in manifests[arm]["repository"]["editable_allowlist"]}
if set(policy.output_roots) != editable | {scratch[arm]}:
raise AdmissionError("confinement write authority differs from declared edit grants")
Expand Down Expand Up @@ -410,12 +407,18 @@ def _verify_launches(
)
if "provider" in launch:
from aisle.harness.provider_relay import verify_provider
from aisle.harness.treatment_confinement import verify_relay_port

try:
verify_provider(launch["provider"])
ceiling = candidates[arm]["budget"].get("frontend_tool_ceiling")
if type(ceiling) is not int or ceiling <= 0:
raise ValueError("provider requires a shared frontend dispatch ceiling")
bindings = (execution or {}).get("confinement_bindings") or {}
verify_relay_port(
bindings.get(arm, {}).get("policy") if isinstance(bindings, dict) else None,
launch["provider"],
)
except (ValueError, TypeError) as exc:
raise AdmissionError("invalid provider binding: " + str(exc)) from exc
if "code_mode_host" in launch:
Expand Down
8 changes: 2 additions & 6 deletions src/aisle/harness/matched_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -616,12 +616,8 @@ def _dispatch(
raise AdmissionError(
"bound typed runtime requires a validation launch binding"
)
declared = current["confinement_bindings"][self.arm]["policy"]
policy = MacOSPolicy(
**{
key: value if key == "network_policy" else tuple(Path(p) for p in value)
for key, value in declared.items()
}
policy = MacOSPolicy.from_canonical(
current["confinement_bindings"][self.arm]["policy"]
)
compiled = compile_macos_profile(policy)
python_hash = hashlib.sha256(self.python.read_bytes()).hexdigest()
Expand Down
7 changes: 1 addition & 6 deletions src/aisle/harness/monolithic_run_prepare.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,7 @@ def prepare_monolithic_run(
if launch["runtime_record"] != runtime or launch["attestation"]["adapter"]["sha256"] != adapter:
raise ValueError("worker declaration differs from admitted runtime or adapter")
bundle, output = Path(launch["bundle"]).absolute(), Path(output).absolute()
policy = MacOSPolicy(
**{
key: value if key == "network_policy" else tuple(Path(p) for p in value)
for key, value in launch["policy"].items()
}
)
policy = MacOSPolicy.from_canonical(launch["policy"])
protected = [
Path(controller_root),
*(Path(p) for p in views.values()),
Expand Down
15 changes: 12 additions & 3 deletions src/aisle/harness/provider_relay.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,17 @@ def verify_provider(binding):
"""Require an explicit upstream; local HTTP is for operator-owned fixtures only."""
_require(
type(binding) is dict
and set(binding) - {"hosted_tool_contract"}
in ({"base_url"}, {"base_url", "requires_openai_auth"}),
and {"base_url"} <= set(binding)
and set(binding)
<= {"base_url", "requires_openai_auth", "hosted_tool_contract", "relay_port"},
"invalid provider binding",
)
_require(type(binding.get("requires_openai_auth", True)) is bool, "invalid provider auth mode")
relay_port = binding.get("relay_port", 0)
_require(
type(relay_port) is int and not isinstance(relay_port, bool) and 0 <= relay_port < 65536,
"invalid relay port",
)
value = binding["base_url"]
_require(type(value) is str and 0 < len(value) <= 2048, "invalid provider URL")
url = urlsplit(value)
Expand Down Expand Up @@ -86,6 +92,7 @@ class ProviderRelay:
def __init__(self, binding, *, dispatch, output, timeout_s, delegated_tools=()):
self.url = verify_provider(binding)
self.hosted_contract = binding.get("hosted_tool_contract")
self._binding_port = binding.get("relay_port", 0)
_require(
type(timeout_s) in (int, float) and 0 < timeout_s < 86400, "invalid provider deadline"
)
Expand Down Expand Up @@ -380,7 +387,9 @@ def do_POST(self):
relay._handle(self)

try:
self._server = Server(("127.0.0.1", 0), Handler)
# An explicit relay_port fixes the listener's address; it grants
# no confinement authority. Zero keeps the ephemeral default.
self._server = Server(("127.0.0.1", self._binding_port), Handler)
self.address = self._server.server_address
self._retain(
"listener.json",
Expand Down
Loading
Loading