Skip to content

Commit 9e5f75d

Browse files
committed
herot-firewall: a package firewall for npm, PyPI, Go, and Cargo
A policy-enforcing registry proxy that sits between package managers and the public registries. It decides per request whether to serve, block, or rewrite each package against a signed or local policy, before it reaches a developer machine or CI. Crates: herot-bundle (signed-bundle wire types), herot-firewall (the server), herot-cli (bundle signing/verification and CI install).
0 parents  commit 9e5f75d

295 files changed

Lines changed: 46705 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.dockerignore

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Build output and VCS metadata.
2+
target/
3+
.git/
4+
.gitignore
5+
6+
# Docs are not needed by the proxy image build. The Dockerfile copies only
7+
# Cargo.toml, Cargo.lock, rust-toolchain.toml, deny.toml, and the three crate
8+
# directories.
9+
**/*.md
10+
11+
# Not part of the proxy build context.
12+
demo/
13+
.github/
14+
15+
# Fuzzing artifacts and corpora (firewall/fuzz is excluded from the workspace).
16+
firewall/fuzz/corpus/
17+
firewall/fuzz/artifacts/
18+
19+
# Local env files.
20+
.env
21+
.env.local
22+
*.local
23+
.DS_Store

.editorconfig

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
end_of_line = lf
6+
insert_final_newline = true
7+
trim_trailing_whitespace = true
8+
9+
[*.rs]
10+
indent_style = space
11+
indent_size = 4
12+
13+
[*.{yml,yaml,toml,md,json}]
14+
indent_style = space
15+
indent_size = 2
16+
17+
[*.md]
18+
trim_trailing_whitespace = false
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: Setup Rust
2+
description: Install a Rust toolchain without depending on a third-party setup action.
3+
4+
inputs:
5+
toolchain:
6+
description: Rust toolchain name.
7+
required: false
8+
default: stable
9+
components:
10+
description: Comma-separated rustup components.
11+
required: false
12+
default: ''
13+
targets:
14+
description: Comma-separated rustup targets.
15+
required: false
16+
default: ''
17+
18+
runs:
19+
using: composite
20+
steps:
21+
- name: Install Rust toolchain
22+
shell: bash
23+
env:
24+
TOOLCHAIN: ${{ inputs.toolchain }}
25+
COMPONENTS: ${{ inputs.components }}
26+
TARGETS: ${{ inputs.targets }}
27+
run: |
28+
set -euo pipefail
29+
rustup toolchain install "$TOOLCHAIN" --profile minimal
30+
rustup default "$TOOLCHAIN"
31+
32+
if [ -n "$COMPONENTS" ]; then
33+
IFS=',' read -ra components <<< "$COMPONENTS"
34+
for component in "${components[@]}"; do
35+
component="${component//[[:space:]]/}"
36+
[ -z "$component" ] && continue
37+
rustup component add "$component" --toolchain "$TOOLCHAIN"
38+
done
39+
fi
40+
41+
if [ -n "$TARGETS" ]; then
42+
IFS=',' read -ra targets <<< "$TARGETS"
43+
for target in "${targets[@]}"; do
44+
target="${target//[[:space:]]/}"
45+
[ -z "$target" ] && continue
46+
rustup target add "$target" --toolchain "$TOOLCHAIN"
47+
done
48+
fi

.github/workflows/ci.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths-ignore:
7+
- '**.md'
8+
pull_request:
9+
paths-ignore:
10+
- '**.md'
11+
12+
permissions:
13+
contents: read
14+
15+
concurrency:
16+
group: ci-${{ github.workflow }}-${{ github.ref }}
17+
cancel-in-progress: true
18+
19+
jobs:
20+
quality:
21+
name: CI / Rust stable quality
22+
runs-on: ubuntu-latest
23+
steps:
24+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
25+
- uses: ./.github/actions/setup-rust
26+
with:
27+
components: rustfmt, clippy
28+
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
29+
- run: cargo fmt --all -- --check
30+
- run: cargo clippy --workspace --all-targets -- -D warnings
31+
- run: cargo build --workspace --all-targets
32+
- run: cargo test --workspace
33+
- run: cargo clippy -p herot-firewall --features redis,otel --all-targets -- -D warnings
34+
- run: cargo test -p herot-firewall --features redis,otel
35+
36+
cargo-deny:
37+
name: CI / cargo-deny
38+
runs-on: ubuntu-latest
39+
steps:
40+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
41+
- uses: EmbarkStudios/cargo-deny-action@091bd13faf7e96935df105635d17ce9a607bc99d # v2
42+
with:
43+
command: check advisories bans licenses sources

.github/workflows/release-cli.yml

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
name: Release CLI
2+
3+
# Builds the herot CLI for linux/macos/windows on release events. Each
4+
# binary is signed with sigstore/cosign keyless (workflow OIDC identity)
5+
# and uploaded to the release as an artifact.
6+
#
7+
# Manual dispatch builds binaries against HEAD without signing, useful
8+
# for pre-release smoke tests.
9+
10+
on:
11+
release:
12+
types: [published]
13+
workflow_dispatch:
14+
inputs:
15+
ref:
16+
description: Git ref to build from (default HEAD)
17+
required: false
18+
19+
permissions:
20+
contents: write
21+
id-token: write
22+
23+
concurrency:
24+
group: release-cli-${{ github.workflow }}-${{ github.ref }}
25+
cancel-in-progress: false
26+
27+
jobs:
28+
build:
29+
name: Build / ${{ matrix.target }}
30+
if: github.server_url == 'https://github.com'
31+
strategy:
32+
fail-fast: false
33+
matrix:
34+
include:
35+
- target: x86_64-unknown-linux-gnu
36+
os: ubuntu-latest
37+
archive: tar.gz
38+
- target: aarch64-unknown-linux-gnu
39+
os: ubuntu-latest
40+
archive: tar.gz
41+
cross: true
42+
- target: x86_64-apple-darwin
43+
os: macos-latest
44+
archive: tar.gz
45+
- target: aarch64-apple-darwin
46+
os: macos-latest
47+
archive: tar.gz
48+
- target: x86_64-pc-windows-msvc
49+
os: windows-latest
50+
archive: zip
51+
runs-on: ${{ matrix.os }}
52+
steps:
53+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
54+
with:
55+
ref: ${{ inputs.ref || github.ref }}
56+
57+
- uses: ./.github/actions/setup-rust
58+
with:
59+
targets: ${{ matrix.target }}
60+
61+
- name: Install cross (linux aarch64 only)
62+
if: matrix.cross
63+
run: cargo install cross --locked --version 0.2.5
64+
65+
- name: Build
66+
shell: bash
67+
run: |
68+
if [ "${{ matrix.cross }}" = "true" ]; then
69+
cross build --release --target ${{ matrix.target }} -p herot-cli
70+
else
71+
cargo build --release --target ${{ matrix.target }} -p herot-cli
72+
fi
73+
74+
- name: Package
75+
id: pkg
76+
shell: bash
77+
run: |
78+
set -euo pipefail
79+
version="${GITHUB_REF##*/v}"
80+
if [ "$version" = "$GITHUB_REF" ]; then
81+
version="dev-${GITHUB_SHA::7}"
82+
fi
83+
name="herot-${version}-${{ matrix.target }}"
84+
mkdir -p "dist/${name}"
85+
if [ "${{ matrix.archive }}" = "zip" ]; then
86+
cp "target/${{ matrix.target }}/release/herot.exe" "dist/${name}/"
87+
cp README.md LICENSE-MIT LICENSE-APACHE "dist/${name}/" 2>/dev/null || true
88+
(cd dist && 7z a "${name}.zip" "${name}/")
89+
echo "artifact=dist/${name}.zip" >> "$GITHUB_OUTPUT"
90+
else
91+
cp "target/${{ matrix.target }}/release/herot" "dist/${name}/"
92+
cp README.md LICENSE-MIT LICENSE-APACHE "dist/${name}/" 2>/dev/null || true
93+
(cd dist && tar -czf "${name}.tar.gz" "${name}/")
94+
echo "artifact=dist/${name}.tar.gz" >> "$GITHUB_OUTPUT"
95+
fi
96+
echo "name=${name}" >> "$GITHUB_OUTPUT"
97+
98+
- name: Sign with cosign keyless
99+
if: github.event_name == 'release'
100+
uses: sigstore/cosign-installer@d7d6bc7722e3daa8354c50bcb52f4837da5e9b6a # v3.8.1
101+
102+
- name: Sign artifact
103+
if: github.event_name == 'release'
104+
shell: bash
105+
env:
106+
ARTIFACT: ${{ steps.pkg.outputs.artifact }}
107+
run: |
108+
set -euo pipefail
109+
cosign sign-blob --yes \
110+
--output-signature "${ARTIFACT}.sig" \
111+
--output-certificate "${ARTIFACT}.pem" \
112+
"${ARTIFACT}"
113+
114+
- name: Upload to release
115+
if: github.event_name == 'release'
116+
env:
117+
GH_TOKEN: ${{ github.token }}
118+
ARTIFACT: ${{ steps.pkg.outputs.artifact }}
119+
shell: bash
120+
run: |
121+
gh release upload "${{ github.event.release.tag_name }}" \
122+
"${ARTIFACT}" "${ARTIFACT}.sig" "${ARTIFACT}.pem" \
123+
--clobber
124+
125+
- name: Upload as workflow artifact (manual dispatch)
126+
if: github.event_name == 'workflow_dispatch'
127+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
128+
with:
129+
name: ${{ steps.pkg.outputs.name }}
130+
path: ${{ steps.pkg.outputs.artifact }}
131+
if-no-files-found: error

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
target/
2+
.env
3+
.env.local
4+
*.local
5+
.DS_Store

CHANGELOG.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Changelog
2+
3+
All notable changes to this project are documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [Unreleased]
9+
10+
Initial development. Not yet released; the policy bundle format and the public
11+
API are still changing, so every `0.0.x` build may break compatibility.
12+
13+
### Added
14+
15+
- **herot-firewall** - a package firewall (a policy-enforcing registry proxy)
16+
for npm, PyPI, Go, and Cargo. Runs from local files and environment
17+
variables with no database: a local TOML policy file or a signed bundle
18+
(path or HTTPS URL), file-based principal auth (bearer tokens or GitHub
19+
Actions OIDC), per-host and scope-based private-upstream routing, and
20+
decision sinks to stdout, a file, or an HTTP endpoint. npm has the most
21+
complete control coverage; PyPI, Go, and Cargo cover the install path. The
22+
Redis shared cache and OpenTelemetry OTLP export are optional, off-by-
23+
default features.
24+
- **herot CLI** - local policy-bundle signing and verification
25+
(`herot bundle sign` / `herot bundle verify`) using Ed25519 PKCS8 keys,
26+
plus `herot info` to check firewall health.
27+
- **herot-bundle** - the wire-type crate that is the single source of truth
28+
for the signed policy bundle format (canonicalized per RFC 8785).
29+
- Worked `examples/` (single-package block, GitHub Actions OIDC, private
30+
registries) and a configuration reference at `docs/configuration.md`.

0 commit comments

Comments
 (0)