Repository navigation
Releases
v0.2.0
The second public release. It publishes the same images, chart and yj CLI as 0.1.0, now signed, each image with an SBOM and build provenance.
Before upgrading from 0.1.0, check these changes to default behaviour:
-
Package uploads (
npm publish,twine upload,mvn deploy) are refused under the defaultFW_WRITE_POLICY=block(#184). -
Every package from a PyPI index with no JSON API (pypiserver, a Nexus PyPI proxy) is held for approval under the default
FW_UNSCORABLE_POLICY=block(#221). -
On npm and PyPI, a package the gate was told does not exist, but which the registry serves to the client, is withheld as temporarily unavailable (#204, #221).
-
A known-malware snapshot that removes many entries at once is held until
FW_MALWARE_FEED_RELEASEreleases it (#216). -
A request path still holding
%after one decode is refused with a 400 (#208). -
Listeners are plain TCP, no longer Multipath TCP;
GODEBUG=multipathtcp=1restores it. -
A scanner launched outside the shipped compose file needs
SCHEDULER_DOCKER_NETWORKpointed at a network the approval service is not on. -
Containers: the release cooldown can hold back new images from Docker Hub, Quay and Google's registries (#127). These registries record when each image was pushed, and an image gate now reads that time. Docker Hub's comes from its tag API on
hub.docker.com, a new destination (docs/EGRESS.md). Quay's comes from its tag API, and that of gcr.io,*-docker.pkg.devand registry.k8s.io from the registry's own tag listing. The date inside an image is still never used, because whoever built the image writes it. A time counts only for the exact image the gate serves. On Quay and Docker Hub, a pull by digest is dated only as the platform image of a multi-arch pull by tag. Any other pull by digest is held while the window is on. It is off by default for containers (FW_MIN_RELEASE_AGE_DAYSis 0 on an image gate). Popular tags are re-pushed every few days when their base image changes: on 2026-10-31, 10 of 10 popular official tags were under 14 days old. A held tag is a failed pull, because there is no older version for the client to fall back to. Behind any other registry (ghcr.io,registry:2) the setting is still not enforced, and the gate says so at startup and on/policy. -
Security: no Multipath TCP on any listener (CVE-2026-98432). Go has opened every Linux listener as Multipath TCP since 1.24 where the kernel supports it, and the one demonstrated remote-root exploit for CVE-2026-98432, a use-after-free in the kernel's IPv4 TCP connection setup, targets MPTCP listeners. Every Yellow Jack binary is now built with
multipathtcp=0, so its listeners are plain TCP. This removes the exploit path that was demonstrated, not the kernel bug: patch the host kernel (fixed in 5.10.272, 5.15.223, 6.1.190, 6.6.159, 6.12.113, 6.18.56 and 7.2.10, and in 7.3; Ubuntu and RHEL had no fixed kernel as of 2026-10-30). An operator who needs MPTCP setsGODEBUG=multipathtcp=1. Containers share the host kernel, so this applies to the images as well. -
Security: scanners run sandboxed, on a network of their own. A scanner runs Scorecard against a repository anyone can publish, so a parsing bug in it, or in the 18-month-old Go runtime the bundled Scorecard v5.2.1 is built with (#224), would be hostile input running in that container. That container ran with the daemon's default capabilities and a writable root, with no limits, and on the stack's network, where the approval service approves packages without authentication (D77). Now, under either launcher, each scanner container:
- runs with no capabilities and
no-new-privileges; - has a read-only root, with
/tmpas an anonymous volume deleted along with the container; - is limited to 2 GiB of memory (the largest scan measured, torvalds/linux, peaked at 1,153 MiB) and 256 processes;
- runs on
${YJ_NETWORK:-yellowjack}-scan, which only the scheduler also joins, so it cannot reach approval, postgres or the gates (checked on a running stack bye2e/async_local.shleg 33).
With the real scanner image, kubernetes/kubernetes (6.2) and torvalds/linux (2.3) scored the same sandboxed and unsandboxed. A memory-backed
/tmpwas tried and rejected: when too small, it raised torvalds/linux's score from 2.3 to 2.7, because the file checks read fewer files. Anyone launching scanners outside the shipped compose file must pointSCHEDULER_DOCKER_NETWORKat a network approval is not on. - runs with no capabilities and
-
Go: the gate serves the checksum database, and Go modules work under TLS interception (paid build) (#218). The go command verifies every module against
sum.golang.org, and goes to it directly when its proxy does not offer to serve it, which proxy.golang.org does not. The Go gate now offers it: it answers the go command's check itself and relays the database's signed answers verbatim, so a client needs no route of its own tosum.golang.org. Verification is unchanged, because the go command checks those answers against a key built into the toolchain.sum.golang.orgis a new destination for the Go gate (docs/EGRESS.md). With that, TLS interception was measured with a realgo getthrough the paid build's intercept port: the module is evaluated, the checksum lookup stays on the one host, and a refused module failsgo getwith the gate's reason. -
Docs: a CA reaches a systemd-managed Docker daemon after
systemctl restart docker(#143). The "install the CA, then restart the Docker daemon" instruction indocs/CLIENT_TRUST.mdwas measured with a daemon run as a bare process. It now holds, measured, for the daemon a Debian or Ubuntu server runs under systemd: still not trusted before the restart, trusted aftersystemctl restart docker. -
Security: a package the gate could not look up is no longer recorded as simply "allowed" on PyPI, Maven and Go (#221). The gate's lookup reads a resource the client never asks for: PyPI's JSON API, Maven's
maven-metadata.xml, a Go proxy's@latest. Some registries, measured, do not serve it: pypiserver and a Nexus PyPI proxy serve project pages but no JSON API, and a Go proxy served from a module cache directory has no@latest. Before this change, a lookup "not found" on those registries meant the package was served with no evaluation.- PyPI: the lookup now asks the project page, the page pip itself asks for, before it says a package is unknown. A project page served to a developer's credential but not to the gate's own is withheld as on npm. A project served by an index with no JSON API cannot be evaluated, so it now takes
FW_UNSCORABLE_POLICY. Under the defaultblock, every package from such an index (pypiserver, a Nexus PyPI proxy) is held for approval, where before it was served unevaluated. An operator who accepts that setsFW_UNSCORABLE_POLICY=allow-but-log, and each such serve is recorded as not evaluated. - Go: the lookup falls back to the version list when the proxy has no
@latest, so those modules are now evaluated. - Maven and Go: where the lookup found nothing but the registry served the request, the request is still served, because withholding might never clear, and the record says it was not evaluated.
- Where to see it: these cases show in the console's audit log as "not evaluated" and are counted on
yellowjack_served_unevaluated_total.
- PyPI: the lookup now asks the project page, the page pip itself asks for, before it says a package is unknown. A project page served to a developer's credential but not to the gate's own is withheld as on npm. A project served by an index with no JSON API cannot be evaluated, so it now takes
-
Docs: client trust on Ubuntu holds for an ordinary user, not only root (#143).
docs/CLIENT_TRUST.md's Ubuntu results were measured as root. Every client was measured again as an ordinary account with a cleared environment, with the same results: pip in a developer's own venv and every apt-installed client are reached by a CA installed the ordinary Debian way, whileuv, a Node or JDK release tarball unpacked into the user's home, and code using the venv's owncertifiare not. The page now says so. -
Licences: every image and release carries the third-party notices (#213). The binaries statically link the Go standard library and, in the approval service, the PostgreSQL driver and its dependencies, whose MIT and BSD licences require their notices to travel with the binary.
THIRD_PARTY_NOTICES.mdlists each module, its version and licence, and which binary links it, followed by each licence in full. It is generated from what the binaries actually link, and a test fails when a dependency changes without it. Every image now has/licensesholding it and Yellow Jack's ownLICENSE, the GitHub release attaches it, and the optional scanner image also carries OpenSSF Scorecard's licence. The file also states the terms of the data the product uses: the known-malware feed is derived from OpenSSF Malicious Packages (Apache License 2.0), and each feed snapshot now begins with a comment line saying so; the gate queries deps.dev from your network, and Yellow Jack does not redistribute deps.dev data. -
Release: the images and the chart are signed, and every image carries an SBOM and build provenance (#212). The release workflow signs the three images and the Helm chart with Sigstore keyless signing, each by digest. It attaches to each image an SPDX SBOM, made by syft from the published image and signed as a cosign attestation by the same workflow identity, and GitHub's SLSA build provenance for the run that built it. Both are stored beside the image, so the image index a cluster pulls is unchanged. The release checks every signature and attestation as a user would (
cosign verify,cosign verify-attestation,gh attestation verify) before it creates the GitHub release;docs/RELEASING.mdgives the commands. 0.1.0 is not signed; verify it by the digests in its release notes. -
Contributing: the guide says what is true (#211). Until a Contributor License Agreement exists, this repository cannot merge outside code. CONTRIBUTING.md says so, asks for issues instead, and explains that the repository is published from the developme...
v0.1.0
The first public release. It publishes the gate, the control plane and the console as
ghcr.io/flyyellowjack/yellowjack-{firewall,approval,console}:0.1.0, each built for
linux/amd64 and linux/arm64 against Go's certified FIPS 140-3 module; the Helm chart as
oci://ghcr.io/flyyellowjack/charts/yellowjack at 0.1.0, pinned to those images by digest;
and the yj CLI for five platforms, with a signed checksum list.
helm install yellowjack oci://ghcr.io/flyyellowjack/charts/yellowjack --version 0.1.0
On the version number: a 0.1.0 tag of an earlier build existed in this repository from
2026-09-27, but its images were never made public, so nobody outside could install it. This
release replaces it.
What it does
- A package firewall for five ecosystems: npm, PyPI, Maven, container images (OCI) and Go
modules. Clients point at the gate with one registry line (.npmrc,pip.conf,
settings.xml, a registry mirror,GOPROXY). Every package they fetch, transitive
dependencies included, is served from your registry or refused with a reason and a next
step. - Known-malware refusal, first and offline. A feed of published malicious packages
(build it from OpenSSF Malicious Packages data withscripts/build-malware-feed.py) is
checked before anything else and needs no network, so it keeps enforcing through an
upstream outage or in an air gap. Advisories may name every version or one release. - A release cooldown. A release younger than
FW_MIN_RELEASE_AGE_DAYS(14 days by
default) is held back, and the client's resolver is steered to the newest release old enough.
It is on for npm, PyPI, Maven and Go modules; container images carry no trustworthy publish
date, so it is not enforced for them. - Your own allow and block lists, by package (
name@*), by one release, or by a version
range on the allow list, re-read while the gate runs. An allow entry outranks a malware
advisory by default (FW_ADMIN_OUTRANKS); every such pull is logged and audited. - Optional OpenSSF Scorecard scoring of each package's source repository, from deps.dev or
scanned on your own machines, with packages below a threshold held for a person to review.
FW_SCORECARD_MODE=offruns the gate on the lists, the malware feed and the cooldown alone. - Refusing the bytes, not only the metadata. A lockfile install, a direct blob pull or a
chosen file name fetches bytes without asking for metadata; those routes are judged too.
UnderFW_BYTE_GATE=enforce, a container layer the gate cannot tie to a release is refused. - npm build provenance. The Sigstore build-provenance attestation of each npm release is
verified inside the gate and logged;FW_ATTESTATION_POLICY=requirerefuses a release
without valid provenance. - Install-time code. An npm release that runs code at install can be refused or held for
a person (FW_INSTALL_SCRIPT_POLICY, off by default). - People decide what scoring cannot. A quarantined release waits in the console's
Decisions queue. A person allows exactly the release that would install, or a scope of
releases, and every ruling is recorded. Blocks can be posted to Slack or Microsoft Teams. - Report mode (
FW_MODE=report): every verdict computed and logged, nothing refused. - The console: what the gate is doing, the approval queue, every refusal explained, the
policy in force as text, gate health, a per-package lookup, request and download volumes,
and the audit log. Sign-in with OIDC (tested with Keycloak) or a shared credential, with
Admin and Read-only roles. - A decision record you can export (NDJSON): every verdict with the inputs that produced
it, including the score, how old the score was, the threshold and a fingerprint of the
policy in force, and the file and SHA-256 of the bytes each npm and PyPI install received. yj dry-run: ask the gate what annpm installorpip installwould be refused
before it runs.- Deployment: a Helm chart with a high-availability layout for the gates, a Docker
Compose stack, a reference deployment, and tested guides for Keycloak, Nexus Repository and
Harbor. An optional Prometheus endpoint (FW_METRICS_LISTEN). - Opt-in usage data, off by default. Nothing is sent unless you set
APPROVAL_USAGE_URL;
the console shows the exact report before you turn it on.
Security
- Every image is built against the certified FIPS 140-3 Go Cryptographic Module, with Go
1.26.10, and reports at startup whether FIPS mode is on. - Every base image is pinned by digest. Services run as a non-root user on a read-only root
filesystem and need no writable host directory. - The vulnerability scan in CI runs on the same Go toolchain the images are built with, so a
new standard-library advisory fails the build instead of passing unnoticed. - A credential written into
FW_UPSTREAMis masked in the gate's log. - See
SECURITY.mdfor how to report a vulnerability, anddocs/RELEASING.mdfor which
releases get fixes.
Known limitations
Stated here rather than discovered later:
- The approval service has no authentication of its own. Keep it off untrusted networks.
The Helm chart's NetworkPolicy (on by default) admits only the gate and console pods, and the
Compose stack publishes it on loopback only. - Scorecard scores the source repository, not the published artifact, so a poisoned
release of a healthy project is not caught by the score alone. The malware feed, the
cooldown, provenance and the byte gate are the layers for that. - Enforcement is cooperative. A developer who removes the registry line is not gated.
Closing that takes network egress rules (no direct route from build machines to the public
registries); TLS interception is not part of this edition. - Build-provenance verification covers npm only.
- Scanning on your own machines (
FW_SCORECARD_MODE=local) runs with Docker Compose from
this repository. The scheduler and scanner images are not published, and the Helm chart
does not run local mode. - Editing the lists in the console needs a console image built with git. The published
console shows the lists; you edit them in your list files. - OIDC sign-in is tested with Keycloak. Other providers are not yet measured.
Images
ghcr.io/flyyellowjack/yellowjack-firewall:0.1.0@sha256:5f9f651b149c59a37afc1a0cd4831e873c4912bcad1614313a49dd9f16dc1992ghcr.io/flyyellowjack/yellowjack-approval:0.1.0@sha256:04b2b31f29dbd87dce0aa2a39c60168779b43b9a851c7e2e654a81986dee9598ghcr.io/flyyellowjack/yellowjack-console:0.1.0@sha256:3466fe5f7e3628b30f31c4115f60f2c5fef1127d7c29b9217460be21f9ca57ed
Install
helm install yellowjack oci://ghcr.io/flyyellowjack/charts/yellowjack --version 0.1.0
yj
The dry-run CLI for linux/amd64, linux/arm64, darwin/amd64, darwin/arm64 and windows/amd64.
SHA256SUMS lists every binary and is signed with Sigstore keyless signing by this
release workflow. Verify the signature, then the binary:
cosign verify-blob --bundle SHA256SUMS.sigstore.json \
--certificate-identity https://github.com/flyyellowjack/yellowjack/.github/workflows/release.yml@refs/tags/v0.1.0 \
--certificate-oidc-issuer https://token.actions.githubusercontent.com SHA256SUMS
sha256sum --ignore-missing -c SHA256SUMS