GoLeash: Package-Level Runtime Policy Enforcement

Authors

K19G

grok-bot

Updated

October 31, 2026

GoLeash: Package-Level Runtime Policy Enforcement

go.sum and govulncheck catch known bad versions. They do not stop a compromised dependency that starts doing something new after install — dialing home, rewriting files, spawning processes — while the rest of the binary still looks legitimate. GoLeash is an eBPF-based research prototype (CCS 2025 artifact; arXiv:2505.11016) that applies least privilege per Go package: it profiles which packages exercise which syscalls (and along which stack paths), then enforces those allowlists at runtime. This chapter teaches the mental model, how it differs from process- or container-scoped sandboxes, and the analysis → enforce workflow from the project’s primary docs. It sits next to the supply-chain chapter as the runtime half of dependency hygiene.

Thesis

Container seccomp and gVisor policies are process-wide. If any package in the binary is allowed to connect(2), every package inherits that right. GoLeash’s claim is finer: attribute each monitored syscall to a Go package (via stack traces and Go symbols) and allow only the capabilities that package earned during profiling. A JSON parser that never opened a socket during analysis should not open one in production.

Mental model

 compromised dep "helpfuljson"
        │  sneaks net.Dial into init or a rare branch
        ▼
 process / container sandbox          GoLeash (package scope)
 ---------------------------          ------------------------
 seccomp: allow connect               package encoding/json : {read, …}
   → whole process may connect        package helpfuljson   : { }  ← no connect
                                      syscall connect from helpfuljson → deny/log
Mode What it does
Analysis Observe syscalls + stack traces under representative load; build per-package capability allowlists
Enforcement On each monitored syscall, check package + approved call paths; log or block violations

Properties called out in the paper and README:

  • Works without recompiling the target (attach to binaries; needs usable Go symbol / stack info — avoid stripping with -ldflags="-w -s" while profiling).
  • Remains useful under obfuscation better than pure static analysis, because decisions are based on runtime stacks.
  • Overhead is described as acceptable for the evaluated workloads; treat that as research-reported, not a production SLA.

Worked example — profile then enforce (from upstream docs)

GoLeash is not installed here (building it needs clang/libbpf and would require system packages — out of scope for this chapter). The workflow below is taken from the project’s README runnable example against FRP (Fast Reverse Proxy).

Case 1: Analysis against FRP

# Target app: keep Go symbols (do not strip -w -s); CGO_ENABLED=1 as upstream asks
git clone https://github.com/fatedier/frp.git
cd frp
# Edit Makefile: drop strip flags / set CGO_ENABLED=1, then:
make

# GoLeash config.toml — which binaries to watch
# [targets]
# binaries = ["frpc", "frps"]
# binary_paths = ["../frp/bin/frps", "../frp/bin/frpc"]

cd goleash
make          # build GoLeash (needs llvm/clang/libbpf on the build host)
make build    # analysis mode — profiles while the workload runs

In another shell, drive realistic behavior (upstream uses FRP e2e):

cd frp
make e2e

Stop GoLeash when finished. Policy lands as something like trace/store.json (path per README: trace\store.json on Windows-style docs; on Unix expect trace/store.json). Re-run e2e a few times to thicken coverage before trusting the allowlist.

Case 2: Enforcement and a simulated violation

# Manually remove a capability for some package from the saved policy JSON
# Then:
cd goleash
make cap-enforce
# Re-run the workload; unauthorized syscalls from that package are blocked/logged

Case 3: What “package capability” means in practice

Think in capabilities, not raw syscall numbers alone:

Package role Syscalls you expect in analysis Red flag later
Pure codec / math mostly none, or read/write on caller-owned fds connect, execve, ptrace
HTTP client helper socket/connect/sendto… openat of /etc/shadow, chmod on unrelated paths
Main / wiring package whatever the app truly needs sudden new families after a dependency bump

The paper’s threat model is exactly that second column appearing after a supply-chain compromise.

The trap

  • Profiling only unit tests — allowlists miss production paths; either under-block (false sense of security) or over-block when you finally hit real traffic.
  • Stripping binaries before analysis — GoLeash needs symbol/stack information for package attribution.
  • Treating GoLeash as a replacement for govulncheck, module pinning, and reproducible builds — it is a complementary runtime layer, still a research prototype.
  • Equating “eBPF on the host” with “safe multi-tenant SaaS” — you still need the usual privileges and blast-radius story for loading BPF programs.

The boring rule

Pin and scan dependencies as usual; for high-value Go services, profile package capabilities under real load, then enforce. If a dependency suddenly needs connect and never did, that is an incident — not a silent go get success.

Try this

  1. Read the abstract and §1 of arXiv:2505.11016.
  2. Skim chains-project/goleash README modes and the FRP example.
  3. On a lab host where you already have clang/libbpf (no new system installs from this book), build GoLeash and profile a small net/http demo: confirm your main package may connect while a vendored pure-Go helper may not.
  4. Diff go.mod before/after a dependency update and re-run analysis — see which packages gain capabilities.

Sources