Skip to content

jonmest/herot-firewall

herot-firewall

CI

A package firewall for your dependency installs. It sits between your package manager (npm / pnpm / yarn / pip / cargo / go) and the public registries as a policy-enforcing proxy, and decides per request whether to serve, block, or rewrite each package against a signed policy, before anything reaches a developer machine or CI.

Beyond plain block/allow it can enforce a minimum release age, hide blocked versions from registry metadata, verify Sigstore / PEP 740 provenance, strip risky npm lifecycle scripts, match artifacts against a known-bad hash list, isolate cache per tenant, and emit an audit decision for every request.

It is a single Rust binary with no database.

This repo holds three crates:

Crate What
herot-bundle Pure wire types for the signed policy bundle. The single source of truth.
herot-firewall The firewall server. Reads policy, enforces, audits.
herot-cli herot command for signing bundles, verifying signatures, and checking firewall health.

Quickstart

# generate a signing keypair
openssl genpkey -algorithm ed25519 -out priv.pem
openssl pkey -in priv.pem -pubout -out pub.pem

# write a policy bundle and sign it
cat > bundle.json <<'EOF'
{
  "bundle_id": "demo",
  "bundle_version": 1,
  "decisions": [],
  "blocks": [
    {"ecosystem": "npm", "package": "left-pad", "version": "1.3.0",
     "reason": "compromised release", "incident_id": "DEMO-1"}
  ]
}
EOF
cargo run -p herot-cli -- bundle sign bundle.json --key priv.pem --out signed.json

# run the firewall against the signed bundle (a local path or an HTTPS URL)
export POLICY_FILE=/abs/path/to/signed.json     # or POLICY_FILE=https://your.host/signed.json
export POLICY_PUBLIC_KEY_PATH=/abs/path/to/pub.pem
export NPM_UPSTREAM_URL=https://registry.npmjs.org
export PROXY_ALLOW_ANONYMOUS=1
cargo run -p herot-firewall

Point your package manager at http://localhost:4873. Installing left-pad@1.3.0 will be blocked; other versions and packages pass through.

For the simplest setup, skip signing and point POLICY_FILE at a local TOML policy instead. See examples/block-a-package/ and docs/configuration.md.

Configuration

Three orthogonal axes, each chosen via env vars:

  • Bundle source - POLICY_FILE accepts a path or https:// URL. A URL source can be authenticated so the bundle need not be public: set POLICY_URL_BEARER (or POLICY_URL_BEARER_FILE to read it from a mounted secret) for Authorization: Bearer, or POLICY_URL_HEADER="Name: value" for any other scheme. Auth requires an https URL and the credential is redacted in logs; the bundle signature is verified regardless.
  • Decision sink - DECISION_SINK=stdout|file|url with DECISION_SINK_PATH= / DECISION_SINK_URL=.
  • Principal auth - PROXY_ALLOW_ANONYMOUS=1 for dev, PRINCIPALS_FILE= for bearer tokens, OIDC_TRUST_FILE= for GitHub Actions JWTs.

Private upstream registries: UPSTREAM_CREDENTIALS_FILE for direct-host credentials, PRIVATE_UPSTREAMS_FILE for scope-based routing (npm @scope, pypi/cargo prefix).

See firewall/README.md and docs/configuration.md for the full configuration and env reference.

Scope and maturity

This is a pre-release (0.0.x) standalone project. Treat it as experimental: the policy bundle format and the API are still changing, and every 0.0.x build may break compatibility. Worth knowing before you deploy:

  • npm is the most complete ecosystem. PyPI, Go modules, and Cargo are served by the same binary and cover the install path, but with fewer controls today.
  • Two policy modes ship here. A local policy file (TOML, filesystem trust) and a signed bundle loaded from a path or HTTPS URL (verified against a pinned Ed25519 key).
  • Heavy subsystems are opt-in. The Redis shared cache (redis feature) and OpenTelemetry OTLP export (otel feature) are off by default to keep the binary and its dependency surface small.

Capability matrix

What each ecosystem enforces today:

Capability npm PyPI Go Cargo
Block exact version / version range yes yes yes yes
Hide blocked versions from metadata yes yes version list only no (download blocked, index unfiltered)
Minimum release age yes no no no
Sigstore / provenance attestation yes yes (PEP 740) no no
Artifact integrity check yes yes n/a n/a
Strip install / lifecycle scripts yes no no no
Private-upstream routing yes (@scope) yes (prefix) no yes (prefix)

A blocked package is refused with a per-ecosystem status: npm and PyPI return 403, Go returns 410, Cargo returns 451. The same status applies whether the denial came from a policy block or an inline-detection match; quarantine is an audit distinction, not a separate status. Signed-decision lanes are allow and block only. Version ranges (version_range) use Cargo-style semver parsing across npm, Go, and Cargo, so npm-dialect range syntax (^1 || ^2, 1.x, dist-tags) is not supported in ranges; exact-version blocks are unaffected.

Operational notes

  • MAX_BODY_MB defaults to 16. Artifacts are buffered in full to run integrity and inline checks; a package larger than the limit is rejected with 502. Raise it for ecosystems with large wheels/zips.
  • Cache-miss and prefetch are rate-limited (PROXY_CACHE_MISS_MAX_RPS, PROXY_ARTIFACT_PREFETCH_MAX_RPS); a large cold-cache install can hit 429. Tune these for CI fan-out.
  • No-policy behavior is asymmetric. With PROXY_ALLOW_NO_POLICY=1 (no policy loaded), npm/PyPI/Cargo pass through while Go fails closed. Run with a loaded policy in production.
  • Redis and audit-sink failures. A configured Redis backend that cannot initialize now fails startup (rather than silently degrading); at runtime, shared-cache errors fall back to direct upstream fetch. The decision sink is at-most-once (see SECURITY.md).

See SECURITY.md for the threat model, trust boundaries, and residual risks.

Building

cargo build --workspace
cargo test --workspace
cargo clippy --workspace --all-targets

License

Dual-licensed under MIT or Apache-2.0 at your option. See LICENSE-MIT and LICENSE-APACHE.

About

A package firewall for npm, PyPI, Go, and Cargo dependency installs: a single-binary, policy-enforcing registry proxy.

Topics

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors