View on GitHub

field-cage

A lightweight eBPF agent that monitors and restricts outbound network connections on GitHub Actions runners.

GitHub Actions

Use the composite action to run field-cage on a runner. It downloads the pinned release binary, verifies its SHA-256 checksum, and starts the agent in the background for the rest of the job.

Inline allowlist

No separate policy file needed:

- uses: takihito/field-cage@v0.1.0
  with:
    version: v0.1.0
    mode: block
    allow: |
      github.com
      api.github.com
      objects.githubusercontent.com
      registry.npmjs.org

External config file

For complex or shared policies:

- uses: takihito/field-cage@v0.1.0
  with:
    version: v0.1.0                          # must match the `uses:` ref
    config: .github/field-cage-policy.yml    # omit for audit mode with no policy
    mode: audit                              # audit (log-only) or block

allow and config are mutually exclusive — using both at the same time is an error.

Notes

Report: a formatted job summary

Add takihito/field-cage/report at the end of the job (with if: always(), since a denial or a failed step earlier shouldn’t skip reporting) to render the log as a GitHub Actions job summary, with denials raised as annotations:

- uses: takihito/field-cage@v0.1.0
  with:
    version: v0.1.0
    mode: audit
    allow: |
      github.com
      api.github.com

# ... steps that generate outbound traffic ...

- uses: takihito/field-cage/report@v0.1.0
  if: always()
  with:
    version: v0.1.0        # keep in sync with the main step's version
    fail-on-deny: false    # set true to fail the job on any DENY verdict (typically for block mode)

It writes a table of denied/allowed/skipped destinations to $GITHUB_STEP_SUMMARY, emits one annotation per denied destination (warning in block mode, notice in audit mode, since audit mode never actually blocked anything), and exposes denied-count, allowed-count, suggested-allowlist (a JSON array of destinations observed, for use as a starting point for a policy — review before adopting it), and log-file (the log path it actually resolved and rendered) as step outputs. The full raw log is not copied into the job log by default (set dump-log: true to opt in) or uploaded as an artifact (set upload-log: true) — the summary above is preferred. As of the first release after v0.1.0, it also stops the agent after rendering (stop-agent: false to opt out — see “Stop the agent” below); pin version to that release or later to get this behavior. See report/action.yml for every input.

Stop the agent

stop/action.yml and report’s default auto-stop behavior are not in v0.1.0 — pin version/the uses: ref to the first release tag that includes them (check Releases) rather than v0.1.0.

takihito/field-cage/report stops the agent by default, so most jobs never need this directly. If a job doesn’t use report (or sets stop-agent: false because a later step still needs the agent running), call takihito/field-cage/stop as the true last step, with if: always():

- uses: takihito/field-cage/stop@vX.Y.Z
  if: always()

It sends the agent SIGTERM, waits briefly, and escalates to SIGKILL if needed. This matters most in block mode: the agent enforces via a cgroup-wide eBPF hook that also covers the runner’s own process, so an agent left running when the job wraps up can end up blocking the runner’s own status-reporting traffic — which looks like a hung job rather than a clean failure.

CLI: text, JSON, or CSV

The same aggregation is available directly from the binary via the report subcommand, for local use or any CI system:

field-cage report --log /tmp/field-cage.log --format text
field-cage report --log /tmp/field-cage.log --format json
field-cage report --log /tmp/field-cage.log --format csv

--format auto (the default) picks markdown on a GitHub Actions runner (GITHUB_ACTIONS=true) and text otherwise, so the action above and a local run of the same binary need no extra flags to each get the right output. --raw skips aggregation and emits one row per connection event (text, json, or csv only) — useful for piping into other tools. Run field-cage report --help for the full flag list.

Releases

Binaries (linux/amd64, linux/arm64) and a checksums.txt are published to GitHub Releases by GoReleaser. Versioning is managed by tagpr: merging the auto-maintained release PR pushes a vX.Y.Z tag, which triggers the release build.

Each release includes a cosign keyless signature bundle (checksums.txt.bundle) and a SLSA Level 3 provenance attestation (checksums.txt.intoto.jsonl), both published as release assets.

Verify the checksum signature:

cosign verify-blob \
  --bundle checksums.txt.bundle \
  --certificate-identity "https://github.com/takihito/field-cage/.github/workflows/release.yml@refs/tags/vX.Y.Z" \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  checksums.txt

Verify SLSA provenance:

slsa-verifier verify-artifact \
  --provenance-path checksums.txt.intoto.jsonl \
  --source-uri github.com/takihito/field-cage \
  --source-tag vX.Y.Z \
  field-cage_linux_amd64   # or field-cage_linux_arm64