childflow is a per-command-tree network sandbox for Linux. Run one command and its child processes in an isolated network context, control DNS / hosts / proxy behavior, apply outbound policy, capture only that tree's traffic, and emit structured flow logs for that tree.
About
childflow runs one command tree in an isolated network context and applies DNS, hosts, proxy, sandbox, policy, and capture controls only to that tree.
This is useful for tools that do not honor proxy environment variables consistently. childflow forces the proxy at the command tree's network path instead of relying on HTTP_PROXY, HTTPS_PROXY, or LD_PRELOAD-style interception.
It has two Linux backends: rootless-internal for the default day-to-day path, and rootful via --root when you need host-integrated behavior such as --iface or transparent interception.
- affects only the target command tree, not the whole host session
- can force DNS,
/etc/hosts, proxying, sandbox policy, packet capture, structured flow logging, and reusable profiles per command tree - can force proxying without depending on
HTTP_PROXY,HTTPS_PROXY, orLD_PRELOADtricks - can apply allow / deny CIDR policy and default-deny rules to outbound traffic
- defaults to
rootless-internal - uses
--rootonly for features like--ifaceand transparent interception
Examples
The Docker demo also includes reusable domain-policy profiles under docker/demo/profiles, including allow-domain / allow-domain-exact samples and deny-by-domain examples that intentionally fail, emit policy_violation flow-log entries with matched_domain, and can be summarized again with childflow --report.
Install
cargo
cargo install childflow
Requirements
Host requirements:
- Linux only
ipiptablesip6tables
Additional rootless-internal requirements:
- user, network, and mount namespace support
/dev/net/tun- user namespaces enabled on the host
uidmapis recommended on Debian / Ubuntu style systems fornewuidmap/newgidmapfallback
Additional rootful requirements:
- root privileges
- writable
/proc/sys/net/ipv4/ip_forward - writable
/proc/sys/net/ipv6/conf/all/forwarding - Linux features required for TPROXY when proxy interception is used
If you are evaluating from macOS or another non-Linux environment, use the Docker workflows instead of trying to run the binary directly.
Usage
$ childflow --help
Run one command tree inside a controlled network sandbox
Usage: childflow [OPTIONS] [COMMAND]...
Arguments:
[COMMAND]... Command to execute
Options:
--profile <PROFILE>
Load effective defaults from a TOML profile file. Explicit CLI flags override the profile
--dump-profile
Print the effective profile as TOML and exit
-c, --capture <OUTPUT>
Write only the target command tree's traffic as pcapng
-C, --capture-point <OUTPUT_VIEW>
Select which capture point or view `--capture` should write. `child` is the current stable view [default: child] [possible values: child, egress, wire-egress, both]
--root
Use the rootful backend. Without this flag, childflow uses the default rootless backend
--doctor
Diagnose whether the current host is ready for the selected backend
--doctor-format <DOCTOR_FORMAT>
Select the output format for `--doctor` [default: text] [possible values: text, json]
--report <REPORT>
Read a structured flow log and print a report instead of running a command
--report-format <REPORT_FORMAT>
Select the output format for `--report` [possible values: text, markdown, json]
-d, --dns <DNS>
Force DNS traffic for the child tree to this IPv4 or IPv6 resolver
--hosts-file <HOSTS_FILE>
Bind-mount an `/etc/hosts`-format file over the child's `/etc/hosts` so those entries are consulted first during name resolution
-p, --proxy <PROXY>
Configure an upstream proxy URI, for example http://127.0.0.1:8080, https://proxy.example.com:443, or socks5://host.docker.internal:10080. `--root` uses transparent interception, while the default rootless backend relays outbound TCP through the selected proxy from the parent-side engine
-U, --proxy-user <PROXY_USER>
Username for upstream proxy authentication
-P, --proxy-password <PROXY_PASSWORD>
Password for upstream proxy authentication
--proxy-insecure
Ignore certificate trust errors for https:// upstream proxies while still validating the hostname
--summary
Print a post-run summary to stderr
--summary-format <SUMMARY_FORMAT>
Select the output format for `--summary` [default: text] [possible values: text, json]
--flow-log <FLOW_LOG>
Write structured flow events as JSON Lines. Currently supported only by the default rootless backend
--offline
Block all outbound networking for the child tree, including DNS forwarding
--block-private
Block child-tree traffic to private, loopback, link-local, and ULA-style destinations
--block-metadata
Block common cloud metadata endpoints such as 169.254.169.254
--default-policy <DEFAULT_POLICY>
Choose whether unmatched outbound destinations are allowed or denied [default: allow] [possible values: allow, deny]
--allow-cidr <ALLOW_CIDRS>
Allow outbound destinations that fall within this IPv4 or IPv6 CIDR
--allow-domain-exact <ALLOW_DOMAINS_EXACT>
Allow outbound destinations whose resolved DNS names exactly match this domain. Currently supported only by the default rootless backend
--allow-domain <ALLOW_DOMAINS>
Allow outbound destinations whose resolved DNS names match this domain rule. Currently supported only by the default rootless backend
--deny-cidr <DENY_CIDRS>
Deny outbound destinations that fall within this IPv4 or IPv6 CIDR
--deny-domain-exact <DENY_DOMAINS_EXACT>
Deny outbound destinations whose resolved DNS names exactly match this domain. Currently supported only by the default rootless backend
--deny-domain <DENY_DOMAINS>
Deny outbound destinations whose resolved DNS names match this domain rule. Currently supported only by the default rootless backend
--proxy-only
Require outbound traffic to use the configured upstream proxy path
--fail-on-leak
Exit non-zero if childflow blocks traffic that the child process did not treat as fatal. Currently supported only by the default rootless backend
-i, --iface <IFACE>
Force the host-side egress interface for the child's direct traffic
-h, --help
Print help
-V, --version
Print version
CLI Examples
Basic
Run one command tree with the default rootless backend:
childflow -- curl https://example.com
Capture only that tree's traffic:
childflow -c rootless.pcapng -- curl https://example.com
Force DNS resolution through a specific resolver:
childflow -d 1.1.1.1 -- curl https://example.com
Override /etc/hosts for just this command tree:
childflow --hosts-file ./hosts.override -- curl http://demo.internal
Policy
Run completely offline:
childflow --offline -- cargo testBlock common cloud metadata endpoints:
childflow --block-metadata -- ./my-client
Block private, loopback, and link-local destinations:
childflow --block-private -- curl https://example.com
Switch to default-deny and allow only one destination:
childflow \ --default-policy deny \ --allow-cidr 203.0.113.10/32 \ -- curl http://203.0.113.10/
Block a destination range explicitly:
childflow --deny-cidr 10.0.0.0/8 -- ./scanner
Allow only traffic that resolves from a specific domain:
childflow \ --default-policy deny \ --allow-domain example.com \ -- curl https://example.com
Block a domain and its subdomains:
childflow --deny-domain example.com -- curl https://api.example.com
Allow only one exact hostname while still denying unmatched subdomains:
childflow \ --default-policy deny \ --allow-domain-exact auth.example.com \ -- curl https://auth.example.com
Proxy
Force a simple HTTP proxy path:
childflow -p http://127.0.0.1:8080 -- curl https://example.com
Run a tool that would not normally honor proxy environment variables:
childflow \ -p http://127.0.0.1:8080 \ -- gobuster dir -u http://target.local/ -w ./wordlist.txt
Use an authenticated HTTPS upstream proxy:
childflow \ -p https://proxy.example.com:443 \ -U alice \ -P secret \ -- curl https://example.com
Require all outbound TCP to use the configured proxy path:
childflow \ --proxy-only \ -p http://127.0.0.1:8080 \ -- curl https://example.com
Treat blocked direct traffic as a failed run:
childflow \ --proxy-only \ --fail-on-leak \ -p http://127.0.0.1:8080 \ -- ./client
Profiles
Run a stored profile as-is:
childflow --profile ./profiles/default-deny.toml
Load a profile and still override the command on the CLI:
childflow --profile ./profiles/default-deny.toml -- curl https://example.com
Inspect the merged effective profile after CLI overrides:
childflow \ --profile ./profiles/default-deny.toml \ --deny-cidr 198.51.100.0/24 \ --dump-profile
Observability
Write a structured flow log for later inspection:
childflow \ --flow-log ./flow.jsonl \ --deny-cidr 10.0.0.0/8 \ -- curl https://example.com
Print a post-run summary with top targets and common failure reasons:
childflow \ --summary \ --flow-log ./flow.jsonl \ --deny-cidr 10.0.0.0/8 \ -- curl https://example.com
Emit the post-run summary as JSON for CI or wrapper tooling:
childflow \ --summary \ --summary-format json \ --flow-log ./flow.jsonl \ -- curl https://example.com
Render a text or Markdown report from a saved flow log:
childflow --report ./flow.jsonl childflow --report ./flow.jsonl --report-format markdown childflow --report ./flow.jsonl --report-format json
Markdown reports start with a compact highlight block, so CI artifacts and issue comments surface the main target and failure modes first:
## Highlights - proxy usage: proxied connect attempts=1, direct connect attempts=0 - top connection target: `93.184.216.34:443` (attempts=1, ok=1, error=0, flow_end=0, dns_names=example.com) - most common policy violation: `proxy_only` (1) - most common connect error: `connection refused` (2) - most common runtime failure: `tap_create_blocked` (1) - most common runtime failure phase: `child_bootstrap` (1)
Check what the current host can support before running:
childflow --doctor childflow --doctor --doctor-format json childflow --root --doctor
Rootful
Use the rootful backend when you need host-integrated behavior:
sudo childflow --root -c capture.pcapng -- curl https://example.com
ICMP and Traceroute
Run ping inside the isolated command tree:
childflow -- ping -c 1 8.8.8.8 childflow -- ping -6 -c 1 2606:4700:4700::1111
Run traceroute inside the isolated command tree:
childflow -- traceroute -n -q 1 -w 2 8.8.8.8 childflow -- traceroute -I -n -q 1 -w 2 8.8.8.8
Description
Backend Summary
| Feature | rootless-internal |
rootful |
|---|---|---|
| Isolated execution | Yes | Yes |
| DNS override | Yes | Yes |
/etc/hosts override |
Yes | Yes |
| Outbound TCP | Yes | Yes |
| UDP relay | Yes | Yes |
| Proxy support | Yes, via parent-side relay engine | Yes, via transparent interception path |
| Policy controls | Yes | Yes |
| Structured flow log | Yes | Not yet |
--fail-on-leak |
Yes | Not yet |
| Transparent proxy / TPROXY | No | Yes |
--iface |
No | Yes |
| Packet capture | Optional, with child, egress, wire-egress, and both views |
Optional, with child, egress, wire-egress, and both views |
| Status | Default and recommended path | Advanced fallback for features that still require host-side networking |
Use rootless-internal by default. It is the main path for isolated execution, DNS control, proxying, packet capture, ping, and traceroute without host-wide rootful setup.
Use --root when you specifically need host-integrated behavior that the rootless path does not expose yet, including:
- transparent proxying
- interface-forced direct egress with
--iface - broader raw-ICMP behavior than the current rootless relay engine implements
Policy Controls
childflow can treat the command tree as a small outbound policy domain.
--offlinedeny all outbound traffic and disable DNS forwarding--block-privatedeny private, loopback, link-local, and ULA-style destinations--block-metadatadeny common cloud metadata endpoints--default-policy denydeny destinations unless they match an explicit allow rule--allow-cidrallow IPv4 or IPv6 CIDRs--allow-domainallow destinations whose resolved DNS names match an exact domain or one of its subdomains--allow-domain-exactallow destinations whose resolved DNS names exactly match a single hostname--deny-cidrdeny IPv4 or IPv6 CIDRs--deny-domaindeny destinations whose resolved DNS names match an exact domain or one of its subdomains--deny-domain-exactdeny destinations whose resolved DNS names exactly match a single hostname--proxy-onlyrequire outbound traffic to use the configured proxy path--fail-on-leakreturn non-zero when childflow blocks traffic but the child process still exits0
Current notes:
--proxy-onlyis primarily a TCP-focused control; in the rootless backend, direct DNS / UDP / ICMP traffic is also blocked rather than relayed--fail-on-leakis currently supported only byrootless-internal--allow-domain,--allow-domain-exact,--deny-domain, and--deny-domain-exactare currently supported only byrootless-internal- domain rules are normalized to lowercase and also match subdomains, so
example.commatches bothexample.comandapi.example.com - exact rules are also normalized to lowercase, but only match the single requested hostname
Profiles
childflow can load reusable TOML profiles with --profile.
childflow --profile ./profiles/default-deny.toml
extends = "./base.toml" capture = "./captures/run.pcapng" flow_log = "./logs/run.jsonl" summary = true doctor_format = "json" report_format = "json" summary_format = "json" dns = "1.1.1.1" backend = "rootless-internal" block_private = true block_metadata = true default_policy = "deny" allow_cidrs = ["203.0.113.10/32"] allow_domains_exact = ["auth.example.com"] allow_domains = ["example.com"] command = ["curl", "https://203.0.113.10/healthz"]
You can also print the merged effective profile after CLI overrides:
childflow \ --profile ./profiles/default-deny.toml \ --deny-cidr 198.51.100.0/24 \ --dump-profile
Current notes:
- profile files currently use TOML
- profiles can inherit from a shared base with
extends = "./base.toml" - merge order is: parent profile, child profile, then explicit CLI flags
- CLI flags override profile values when both are present
- for list-valued settings such as
allow_cidrsanddeny_cidrs, explicit CLI flags replace the profile list instead of appending to it - the same replacement behavior applies to
allow_domains,allow_domains_exact,deny_domains, anddeny_domains_exact - an explicit CLI command after
--replaces the profilecommand --dump-profileprints the merged effective TOML and exits without running the command- relative paths inside a profile are resolved relative to the profile file itself
- profile keys use command-oriented names such as
capture,capture_point,backend,flow_log,doctor_format,report_format,summary_format,default_policy,allow_cidrs, anddeny_cidrs - profile keys also support
allow_domains,allow_domains_exact,deny_domains, anddeny_domains_exactfor rootless domain policy doctor_formatis used when you runchildflow --doctorwith that profile loadedreport_formatis used when you runchildflow --report <flow.jsonl>with that profile loaded--rootremains a CLI-only convenience flag; usebackend = "rootful"in profiles when you want the rootful backend- the fuller key-by-key schema is documented in docs/profile-schema.md
Flow Log
childflow can emit structured JSON Lines flow events with --flow-log.
childflow --flow-log ./flow.jsonl -- curl https://example.com
childflow --summary --flow-log ./flow.jsonl -- curl https://example.com
Current event types:
dns_querydns_answerconnect_attemptconnect_resultpolicy_violationflow_endruntime_failure
Current schema notes:
- every event includes
schema_version: 1 connect_attempt,connect_result, andflow_endinclude stableremote_ip/remote_portfieldsconnect_result.statusis currently one ofokorerrordns_queryanddns_answerinclude stableserver_ip/server_portfieldsdns_answer.modeis currently one ofrelayedorsynthetic_emptypolicy_violationincludes structured fields such asaction,reason_code,control, andmatched_cidrwhen applicabledns_answer.answer_ipscarries the resolved A / AAAA addresses observed for a DNS namepolicy_violation.matched_domainis set when a--deny-domainrule blocked the query or the resolved destination
Current notes:
--flow-logis currently supported only byrootless-internal- each line is standalone JSON, so it is easy to inspect with tools such as
jq - flow logs complement
--capture; use--capturefor packet-level inspection and--flow-logfor higher-level execution tracing runtime_failurerecords stablereason_codevalues such astap_create_blockedorpacket_capture_blockedwhen setup or runtime fails--summarywill also show aggregate flow-log event counts, the top connection target, common policy violations, commonly matched blocked domains, common connect errors, runtime failure reason codes, and runtime failure phases after the run--summary --summary-format jsonalso includes lightweightdns_policy_rows, so post-run tooling can inspect DNS name / answer IP / matched blocked domain / target correlations without reading the fuller report--summaryalso surfaces the top DNS policy correlation so it is easier to spot the most important DNS-name / matched-domain / target grouping at a glance- top connection targets in
--summary/--reportalso include correlateddns_nameswhenchildflowobserved DNS answers for the target IP - DNS-oriented report views also surface correlated
matched_domains, so it is easier to connect a queried name, its resolved IPs, the observed target socket, and the domain rule that blocked it - the shared machine-readable surfaces are indexed in docs/observability-schema.md
- the fuller JSON summary schema is documented in docs/summary-schema.md
--report ./flow.jsonlrenders a fuller post-run report from the saved flow log--report-format markdownemits a Markdown report that is convenient for artifacts or issue comments--report-format jsonemits a machine-readable report that is convenient for CI artifacts or wrapper tooling, including sorted arrays for ranked sections such as policy violations, matched blocked domains, connect errors, and runtime failures- the JSON report also includes flattened
dns_policy_rows, so external tooling can iterate DNS-name / answer-IP / target / matched-domain correlations without unpacking nested sections - the fuller JSON report schema is documented in docs/report-schema.md
- the fuller event-by-event schema is documented in docs/flow-log-schema.md
Doctor and Report
childflow --doctor is the quickest way to see whether the current host can support the selected backend.
- for
rootless-internal, it checks capability-oriented items such as user namespaces, uidmap helpers,/dev/net/tun, AF_PACKET capture, and Ubuntu-style AppArmor userns restrictions - for
rootful, it checks root privileges, forwarding sysctls, required external commands, and AF_PACKET capture --doctor-format jsonemits the same diagnosis in a machine-readable form for CI logs or wrapper tooling- the fuller JSON doctor schema is documented in docs/doctor-schema.md
After a run, childflow --report ./flow.jsonl turns the saved flow log into a text, Markdown, or JSON summary with:
- event counts
- protocol counts
- proxy usage
- policy violation reason counts
- matched blocked domain counts
- connect error counts
- runtime failure reason counts
- runtime failure phase counts
- top connection targets
- DNS target / policy correlations
The Docker demo also includes reusable domain-policy profiles such as docker/demo/profiles/domain-allow-origin.toml, docker/demo/profiles/domain-allow-origin-exact.toml, and docker/demo/profiles/domain-deny-origin.toml that show how to persist allow_domains, allow_domains_exact, and deny_domains rules alongside the rest of a sandbox definition.
Example: run a reusable deny-domain profile, then summarize the saved flow log:
childflow --profile ./docker/demo/profiles/domain-deny-origin.toml || true childflow \ --report ./docker/demo/profiles/logs/domain-deny-origin.jsonl \ --report-format markdown
That report will surface the blocked DNS name, matched domain rule, correlated answer IPs, and any observed target socket in one artifact.
Capture Modes
childflow is intended to capture only the target command tree's traffic, not unrelated host traffic.
The default child mode keeps the isolated child-side view.
egresssynthetic egress-oriented view on both backendswire-egressreal host egress capture on both backendsbothwrites sibling.child.pcapngand.egress.pcapngfiles
Generated pcapng files also embed metadata describing the capture view, backend, kind, and interface.
For the fuller comparison of current capture points and the planned child / egress / wire-egress / both capture-point direction, see docs/technical-details.md.
License
MIT. See LICENSE.

























