Repository navigation
feat(infra): native network enforcement collector (B5 PR4) - #1939
ai-mountain wants to merge 6 commits into
Conversation
Adds net-agent (line protocol on stdin/stdout or one owner-only Unix socket) and net-once (fixed plan behind a bounded collector gate) to dittobench-coding-enforcement-probe. Attempts are handshake-only: TCP connect and close without payload, keepalive-only checks of established flows, one CONNECT line to the refusing proxy and one DNS question to the candidate's resolver. Outcomes are catalog names; unknown errors are probe_error. The agent never decides a match or writes a record. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Default-off root collector for network_enforcement records. It measures the release-recorded probe runner and every agent's /proc/<pid>/exe from outside, pins the Docker socket and empty client config, binds the daemon identity, compares the loaded nft table entry for entry with the profile's compiled connectivity policy, and records normalized scoped and deny ruleset digests, the worker cgroup and the refusing proxy in a new network_binding the offline verifier checks. The connectivity role gains a network_enforcement worker mode. Tests use a simulated host and nft listings recorded in a throwaway user and network namespace; CI re-records them. Nothing runs on a host and no approval is produced. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
No dashboard preview for this commit. This PR resolved to preview profiles Stack previews are never automatic. A maintainer can provision one for this PR: gh workflow run preview-stack.yml -f pr=1939 # provision
gh workflow run preview-stack.yml -f pr=1939 -f action=retire # free the slotIt runs on the default branch, resolves the head commit from the API, and comments dashboard, Platform, and isolated Backroom URLs once readiness succeeds. Exact SHA: |
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Host preflight v3 hashed the raw `nft -j list table` output, which every worker start and stop changes (new handles, leftover scoped base chains and sets), so the verifier's pre/post-collection comparison refused every real network enforcement record. Preflight v4 records nft_ruleset_semantic_sha256 instead: the listing without metainfo, handles, counter values and named-element timeouts/expiries, pruned of provably inert objects (unreferenced empty regular chains, unreferenced empty sets and maps, rule-less accept filter base chains), with objects sorted and rules kept in chain order. The collector now uses the preflight's own stripping from the same reviewed checkout instead of a copy. The verifier requires v4 and refuses v3. A recorded kernel listing after deny, scoped policy, deny and element expiry has the same semantic digest as the initial deny guard, and the collector's accept-path record now verifies against preflights carrying those digests. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ace checks in #1939's network_enforcement connectivity mode network_enforcement reads no worker configuration, so #1919's attempt binding dereferenced a skipped slurp and refused the mode. Enforcement collection also requires the host router namespace. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Peyton-Spencer
left a comment
There was a problem hiding this comment.
Open Code Review pass completed at 0f6b9e194b3a476996a24835ef8d41332af8e478 using z-ai/glm-5.3-flash through Ditto’s Code Review endpoint. Coverage: 28/28 added, modified, renamed, or type-changed files in the PR diff.
Model candidates were checked against the source; independently confirmed actionable findings, if any, are posted separately as inline review comments. This was a static review; no PR code or test suite was executed. This comment is not a merge approval.
…mage probe smoke check Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ve-network-collector Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
# Conflicts: # infra/ansible/roles/coding_hosted_connectivity/defaults/main.yml # infra/ansible/roles/coding_hosted_connectivity/tasks/main.yml
…ace checks in #1939's network_enforcement connectivity mode network_enforcement reads no worker configuration, so #1919's attempt binding dereferenced a skipped slurp and refused the mode. Enforcement collection also requires the host router namespace. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Ready for review. Merge-time note: inherits #1927's grading-profile fixture pin, and #1919's five attempt-config and router-namespace tasks need |
There was a problem hiding this comment.
Ditto Review · Beta
Partial review for 83996319261f47ddb26db314b5ea5efa3039e1b8.
No actionable findings in the reviewed files. This is not an approval or proof that the PR is bug-free.
Some coverage was incomplete. See the run in the developer console before relying on this review.
Configure Ditto Review. Inference is billed through your Ditto Router endpoint; targeted verification is included during beta.
Stacked on #1934 (
agent/coding-native-probe-binding). B5 PR4: thenetwork_enforcementcollector only. Resource, pre-exec and cleanup collection stay in PR5.Default-off. Nothing here has run on a host. No Terraform, Ansible apply, SSH, gcloud, secret, signing key or production write was used. The collector is not wired into
coding-hosted-operateor any workflow step, and it never produces an approval.What it adds
Probe runner agent (
dittobench-coding-enforcement-probe)net-agentanswers one JSON request per line, on stdin/stdout or on one owner-only Unix socket.net-onceruns a fixed plan of connect attempts. It waits behind a collector gate (a FIFO read with a 120 s deadline).TCP_USER_TIMEOUTonly;CONNECTline, and the candidate's resolver gets one DNS question.probe_error. The agent never decides whether a probe matched.Collector (
infra/scripts/collect-coding-native-enforcement.py, the path the verifier already pins)network --config FILE --confirm "COLLECT NATIVE NETWORK ENFORCEMENT EVIDENCE".resource,preexecandcleanupsubcommands exit 2.Verifier
network_binding:network_binding.Role (
coding_hosted_connectivity)coding_hosted_worker_mode: network_enforcement.ExecStartruns the release probe runner asnet-agentin the exact worker cgroup.ExecStopPostrunsnet-onceas the worker user, afterrevoke.Targets fixture
internal/codingenforcement/fixtures/network/targets.jsonholds the public, IPv6, metadata, DNS, proxy authority and port targets.fixtures_sha256.Preflight v4: semantic nft ruleset digest (
inspect-coding-native-host.py)nft_snapshot_sha256hashed the rawnft -jlisting. A worker start and stop always changes it, so the verifier's pre/post-collection comparison refused every real network record.dittobench-coding-native-host-preflight-v4and recordsnft_ruleset_semantic_sha256. The verifier requires v4 and refuses v3 as unknown.handle, and counter packets/bytes (zeroed; the counter statement stays). Named set and map elements lose theirtimeoutandexpires.jump/gotonames;@name;filter, policyacceptand no rules.drop, another type or extra attributes, and every other object.normalize_rulesetnow calls the preflight'snft_entries, loaded from the same reviewed checkout, where both files are already pinned. The scoped and deny ruleset digests are unchanged on the recorded listings.How it maps to the owner decisions
_endpoint_sha256labels andinputs.connectivity_profile_sha256. Raw addresses, including source addresses and container IPs, stay in the collector. A test checks this.python3 -I -B egress-proxy.py <listed endpoint>. Profilecandidate_tcpmust be exactly the router plus the proxy. Proxy connect andCONNECTrefusal are probed, and the script digest is recorded.worker.trusted.handshakeis connect-and-close. Expiry flows are checked with zero-payload keepalive. A test asserts that trusted endpoints only ever seehandshake,connectorestablish.probe_runner_sha256./proc/<pid>/exeis hashed for every agent (worker main PID, daemon agent, container agents, eachnet-oncebefore its gate), before and afterhello. A self-report that differs is refused. Docker always runs as the daemon user viaunix:///run/ditto-coding-hosted/docker.sockand the emptyDOCKER_CONFIG. The daemon identity digest must equal the preflight's before and after. Identity, host IDs and interfaces come from/proc. Router source is observed at the worker-cgroup listener. Host-loopback and sibling denials only count if the outside listener saw nothing.same_bootand list daemon restart and reboot recovery as not covered, unchanged.nft binding
inet ditto_coding_hostedtable is normalized: handles, counter values and set timeouts are dropped.connectivity-policy.py policycompiles for the installed profile and daemon UID.Tests
go test -p 2 ./internal/codingenforcement/... ./cmd/dittobench-coding-enforcement-probe/passes. It covers:net-oncegate and exclusive report.DITTOBENCH_REQUIRE_LIVE_NFT=1. Files run:test_coding_native_network_collector.py,test_coding_native_enforcement_evidence.py,test_coding_hosted_connectivity.py,test_coding_native_host_preflight.py;test_coding_native_release.py,test_coding_native_matrix.py,test_coding_qualification.py,test_coding_hosted_image_import.py,test_coding_hosted_runtime_bundle.py.nft-deny-after-expiry.json(deny guard, scoped policy, deny guard, element expiry):coding-native-evidence.pyverification, with no raw address and no approval key.host_address, proxy forwarding, DNS, daemon trusted, worker handshake, sibling and host loopback seen by the outside listener, established flow surviving expiry, trusted reachable after stop.ansible-playbook --check tests/coding-hosted-connectivity.ymlpasses, including the new mode's render.ditto/.coding-native-release.ymljob now also runs the collector and role tests, and lints and type-checks the collector. On the disposable runner it installs nftables and relaxes the AppArmor user-namespace sysctl, so the live listing test is required (DITTOBENCH_REQUIRE_LIVE_NFT=1). No workflow step executes the collector. The guard test now allows its name in that job only as a path filter or a ruff/mypy target.What this does not cover (residual assumptions)
acceptfilter base chains relies on netfilter semantics (an accept from one base chain does not end the hook's other base chains). It is exercised in a throwaway namespace only, not on the host kernel.hostonly.rootless-netns(the held in-namespace router fd hand-off) is unsupported. Under slirp4netnscandidate.router.sourcewill recordhost_addressand the verifier will refuse, which is the known rootless router blocker.sandbox.runArgsForNetworkand a released language image. It is not started bysandbox.LocalDockerand does not use a screened miner image. The executor interface check uses a--network nonecontainer, not the production executor launch.State.Pidand itsdocker-initchild are host PIDs.10.0.2.2.systemd-run --user --machine=ditto-coding-hosted@.host --pipeworks as root with lingering./run/ditto-coding-hosted-enforcement.closedis ambiguous forestablished_after_expiry: a server closing an idle connection just after expiry looks like a cut flow.network_enforcementmode (it replaces the canary worker unit; reinstallsingleafter);🤖 Generated with Claude Code
Note
Add native network enforcement collector with
net-agentandnet-onceprobe subcommandsprobe.NetAgentsession protocol in network.go supporting connect, handshake, proxy, DNS, listen/accept, and established-flow checks over a bounded JSON-lines protocol, plusprobe.RunNetPlanfor one-shot plan execution.net-agentandnet-oncesubcommands todittobench-coding-enforcement-probein network.go;net-agentserves over stdio or a single owner-only Unix socket,net-onceruns a gated one-shot plan and writes an exclusive report.network_enforcementmode to the Ansiblecoding_hosted_connectivityrole in main.yml and worker.service.j2, installing connectivity policy, starting the probe runner asnet-agent, and running a gatednet-oncestop hook.parse_preflightnow rejects v2/v3 preflights and the formernft_snapshot_sha256field; verification comparesnft_ruleset_semantic_sha256instead, so any out-of-tree consumer still expecting raw snapshot digests or v3 preflights will fail.Macroscope summarized 8399631.