herot-firewall sits on the package-install hot path and makes allow/block decisions for npm, PyPI, Go, and Cargo. Given the nature of the project, security is paramount.
Please report security issues privately through GitHub Security Advisories:
https://github.com/jonmest/herot-firewall/security/advisories/new
Do not open a public issue, pull request, or discussion for a suspected vulnerability. Public disclosure before a fix is available puts every downstream user at risk.
When reporting, include as much as you can:
- affected component (proxy, CLI, or
herot-bundle) and version/commit, - a description of the issue and its impact,
- reproduction steps or a proof of concept,
- any suggested remediation.
The goal is to acknowledge a report within 5 business days and keep you updated on triage and remediation progress. Once a fix is ready we will coordinate a disclosure timeline with you and credit you if you wish.
The following areas are the highest-value to scrutinize:
- Signature verification - Ed25519 verification of policy bundles
(
herot-bundlecanonicalization via RFC 8785, the proxy bundle loader, and the CLIbundle sign/bundle verifypaths). Downgrade-replay protection is part of this surface. - TLS and certificate handling - upstream connections and any certificate/attestation verification in the provenance path.
- Policy enforcement path - the decision logic that decides whether a package version is allowed, blocked, or quarantined. A bug that lets a blocked package through is in scope, as is one that leaks credentials.
- Credential handling - upstream and private-upstream credentials read from local files and environment variables.
Configuration mistakes (e.g. running with PROXY_ALLOW_ANONYMOUS=1 in
production, or pointing POLICY_FILE at an unsigned source) are not
vulnerabilities in the proxy itself, but reports that highlight footguns are
still welcome via a normal issue.
- Operator-configured upstreams are trusted. The URLs in
NPM_UPSTREAM_URL,PRIVATE_UPSTREAMS_FILE, etc. are assumed to be chosen deliberately. The firewall does not restrict which hosts an operator points it at; private registries on private IPs are a supported configuration. - Policy bundles are trusted only after signature verification. A signed bundle is verified against a pinned Ed25519 key over the received bytes (RFC 8785 canonical JSON) before it is applied, and the type system prevents an unverified bundle from being enforced. A local TOML policy file is trusted by filesystem path and is intended for single-operator use.
- The client (package manager) is untrusted. Request paths, package names, and tokens are validated; an unknown bearer token is rejected rather than silently treated as anonymous.
- Ed25519 signature verification for policy bundles, over RFC 8785 canonical
bytes. The CLI signer and the firewall verifier use the same
serde_jcsversion so the signed byte stream cannot diverge. - Full Sigstore verification: Rekor inclusion-proof recomputation, SET signature against a pinned production key, signed-checkpoint binding, and Fulcio certificate-chain verification to a bundled root with a code-signing EKU at the entry's integrated time.
- Constant-time comparison (
subtle) for tokens and integrity digests. No homemade cryptographic primitives.
Attestation verification performs X.509 certificate parsing (x509-parser,
rustls-webpki) and ASN.1 handling in process; this is the largest part of
the trusted computing base and is inherent to verifying Sigstore bundles
locally. The Redis (redis) and OpenTelemetry (otel) features are off by
default to keep the default dependency surface small.
- Redirect SSRF (mitigated). Upstream redirects are restricted to
https, capped at 4 hops, blocked for IP-literal andlocalhosttargets, and the client pins to public webpki roots. A redirect to a hostname that resolves via DNS to a private/loopback address is not yet blocked at resolution time; exploitation would require the internal target to present a publicly trusted TLS certificate. A connect-time global-IP check is tracked as future work and is complicated by private upstreams legitimately using private IPs. - Audit delivery is at-most-once. The decision sink (stdout/file/HTTP) is fire-and-forget: enforcement is always applied, but if the sink is unavailable the audit record is logged and dropped, not retried or spooled.
- An expired or unparsable bundle fails the whole instance closed. A loaded
bundle whose
expires_athas passed (or cannot be parsed) makes the firewall return503for all traffic and report not-ready. Monitorherot_firewall_policy_expired_refusals_totaland the readinesspolicy_expirycheck, and rotate bundles before they expire. - Metrics and detailed readiness require a token.
/metricsreturns401and/_readyis minimal unlessPROXY_OBSERVABILITY_TOKENis set. - Strict mode is the recommended production posture.
PROXY_STRICT=1requires a signed policy source with a pinned key, a cache size cap, an httpsPROXY_PUBLIC_BASE_URL, and https for every upstream URL, and it fails ready on stale policy. Outside strict mode,http://upstreams are accepted.
See the README "Scope and maturity" and capability-matrix sections for functional coverage gaps per ecosystem.