Write down what the agent may reach
Permission prompts ask the agent to police itself. A box policy is resolved before the agent starts, enforced outside its process, and digested into every receipt.
Command-line flags are convenient for experiments and poor as a long-term security policy. They arrive one at a time, disappear from code review, and are easy to vary between developers. A repository profile makes the boundary one object that can be discussed before any agent process exists.
1. Add a named profile
Create .h5i/env.toml in the repository. This example supports a bounded review that needs the GitHub API:
[profile.review]
isolation = "supervised"
[profile.review.fs]
read = ["/usr", "/etc"]
write = ["$WORK"]
[profile.review.net]
mode = "deny"
egress = ["api.github.com"]
unix = false
[profile.review.resources]
mem = "4G"
procs = 256
wall = "30m"$WORK is the box workspace, not your current checkout. mode = "deny" makes the allowlist meaningful: everything not named is refused.
Read every field as authority
The filesystem block says which existing data enters the box and where writes can land. The network block says whether destinations exist from the box's point of view. The resource block bounds how long a mistaken loop or fork-heavy build can consume the machine. None is application configuration. Each is part of the security claim.
2. Choose the tier by threat model
| Tier | Use it for | Boundary to remember |
|---|---|---|
workspace | Checkout separation only | No confinement |
process | Fast local build and test | Shared kernel; network is deny or host |
supervised | Untrusted dependencies and bounded egress | Shared kernel; L3/L4 egress enforcement |
container | Portable image-based environments | Proxy-respecting L7 egress only |
microvm | Work that must not share the host kernel | Needs virtualization, msb, and a pre-pulled image |
container buys portability. It does not provide tighter egress enforcement than supervised. Pick the property you need instead of assuming every higher-sounding rung is stronger in every dimension.
3. Prove the requested policy is satisfiable
$ h5i box probe
$ h5i box create policy-check --profile review
$ h5i box status policy-check
$ h5i box doctor policy-checkAn explicit tier either exists or creation fails. h5i does not silently downgrade. The status prints the resolved policy, while doctor checks that the box can still support its claim.
The stored policy.resolved.toml is the version to audit after creation. Variables such as $WORK, platform-specific grants, engine selection, and runtime defaults have been expanded there. Editing .h5i/env.toml later does not retroactively change an existing box; create a new one if the boundary changes.
4. Let denials guide refinement
$ h5i box run policy-check -- npm test
$ h5i box log policy-check
$ h5i box export policy-check --out ./policy-check-reportA denied registry host may justify one more destination. A denied telemetry host usually does not. Treat each addition as a reviewable transfer of authority, not a way to make the error disappear.
unix = true permits AF_UNIX sockets, which can carry file descriptors through SCM_RIGHTS. The browser profile needs this; most build profiles do not.5. Commit the policy with the code
A checked-in profile gives reviewers one file to discuss. At creation, h5i resolves machine-specific values, serializes the result, hashes it, and puts that digest on the receipts. The repository states the intended boundary; the receipt names the boundary that actually ran.
Understand what the same egress list means at each tier
The profile may contain the same hostname list while the enforcement changes underneath it. At supervised, h5i resolves and pins addresses, installs nftables rules in a private network namespace, pins DNS through a hosts file, and gates socket creation. A client that ignores proxy variables still meets packet-layer rules.
At container, the list configures an HTTP/HTTPS CONNECT proxy. This covers ordinary package managers, SDKs, and command-line HTTP clients that respect proxy configuration. It does not constrain arbitrary raw connections through rootless NAT. The policy syntax is shared; the reported enforcement layer tells you what the list proves.
At microvm, the guest network stack evaluates destination rules. Enforcement is L3/L4, but denied attempts do not currently produce the same per-host receipt summary. Stronger blocking and richer evidence are independent properties.
Add credentials as grants, not environment inheritance
If the task needs an authenticated API, do not add the real token to env.pass. Declare an auth grant whose credential is resolved on the host and whose client can be pointed at a base URL. The box receives a per-run dummy; the broker injects the real credential only toward the pinned upstream.
Keep the service token narrow anyway. The broker protects possession and destination. It does not turn repository-wide administration into read-only access.
Resource limits are platform claims too
Wall-clock limits are enforceable everywhere. Memory and process-count ceilings at the host-kernel tiers are not honestly enforceable on macOS, so h5i marks them rather than pretending. Choose container or microvm if a real ceiling is part of the threat model.
A limit should match the workload with enough headroom for ordinary peaks. A browser build that legitimately needs three gigabytes will teach nobody anything when capped at one. The useful ceiling prevents unbounded behavior without converting normal execution into noise.
Test the failure path, not only the happy path
After creation, deliberately request one path and one destination that should be denied. Then inspect the log or export. This confirms both enforcement and evidence routing on the current host.
$ h5i box run policy-check -- sh -c 'cat ~/.ssh/id_ed25519'
# expected: read refused or path absent
$ h5i box run policy-check -- curl https://example.invalid
# expected: destination refused
$ h5i box log policy-checkDo this with harmless targets. The exercise is not a penetration test; it is a smoke test that the written boundary appears in behavior and in the review record.
Common policy mistakes
- Granting all of HOME: this defeats the credential and configuration boundary. Seed only the runtime state the built-in profile needs.
- Using host networking to fix one registry: add the registry destination or a warm cache instead.
- Enabling Unix sockets by default: local sockets can carry file descriptors and ambient host authority.
- Choosing container because it sounds stronger: use it for image portability; choose supervised for packet-layer egress.
- Changing policy without recreating the box: existing boxes keep the policy digest they started with.
Reference
- Complete policy reference and built-in profiles.
- Credential and secret grants.
- The threat model behind the tier choice.
- Credential proxy design and open limits.
Questions that come up
What happens if my machine cannot provide the requested tier?
Why is container egress weaker than supervised egress?
Are memory and process limits enforced on macOS?
Five tiers, five different promises
Read the threat-model argument behind the ladder.
Make authority reviewable
A small policy file is easier to reason about than a trail of permission clicks.