Skip to content

Security: jonmest/herot-firewall

Security

SECURITY.md

Security Policy

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.

Reporting a vulnerability

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.

Response window

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.

Security-relevant surface

The following areas are the highest-value to scrutinize:

  • Signature verification - Ed25519 verification of policy bundles (herot-bundle canonicalization via RFC 8785, the proxy bundle loader, and the CLI bundle sign/bundle verify paths). 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.

Trust boundaries

  • 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.

Cryptographic verification

  • Ed25519 signature verification for policy bundles, over RFC 8785 canonical bytes. The CLI signer and the firewall verifier use the same serde_jcs version 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.

Trusted computing base

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.

Residual risks and known limitations

  • Redirect SSRF (mitigated). Upstream redirects are restricted to https, capped at 4 hops, blocked for IP-literal and localhost targets, 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_at has passed (or cannot be parsed) makes the firewall return 503 for all traffic and report not-ready. Monitor herot_firewall_policy_expired_refusals_total and the readiness policy_expiry check, and rotate bundles before they expire.
  • Metrics and detailed readiness require a token. /metrics returns 401 and /_ready is minimal unless PROXY_OBSERVABILITY_TOKEN is set.
  • Strict mode is the recommended production posture. PROXY_STRICT=1 requires a signed policy source with a pinned key, a cache size cap, an https PROXY_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.

There aren't any published security advisories