Security policy
This project handles payment-message construction and signing; we take vulnerability reports seriously and appreciate coordinated disclosure.
Reporting a vulnerability
Section titled “Reporting a vulnerability”Do not open a public issue for security problems.
Preferred channel: GitHub private vulnerability reporting — use the “Report a vulnerability” button under the repository’s Security tab.
If you cannot use GitHub, email joaobuenosi@gmail.com with subject
[SECURITY] fednow-oss.
What to include: affected crate and version/commit, reproduction steps or PoC, and impact assessment if you have one.
What to expect
Section titled “What to expect”- Acknowledgement within 72 hours.
- A triage verdict (accepted / not a vulnerability / needs info) within 7 days.
- Fix timeline agreed case by case; critical issues in released code get priority.
- Credit in the release notes and a CVE requested via GitHub Security Advisories when applicable, unless you prefer to stay anonymous.
Scope notes
Section titled “Scope notes”- This project never ships with credentials, certificates or live endpoints; reports about the absence of those are out of scope.
- The simulator (
fednow-sim) is a development tool and is not hardened for exposure to untrusted networks; running it on the public internet is out of scope.
Gateway API authentication
Section titled “Gateway API authentication”The gateway’s northbound REST API (fednow-gateway) requires
Authorization: Bearer <key> on every route except the GET /healthz liveness
probe. The gateway fails closed: with no key configured it refuses to
start. It has no setting that turns authentication off. Keys come from the
environment (FEDNOW_GW_API_KEYS for full access, FEDNOW_GW_READ_API_KEYS
for read-only monitoring, FEDNOW_GW_OPERATOR_API_KEYS for the operators who
release or cancel a payment the pre-send risk check held) and are never stored
in this repository. The gateway keeps only their SHA-256 digests, compares in
constant time, and never writes a key to a log line or a response. Missing or
unknown keys get 401, and a key on a route outside its tier gets 403. A
full-access key cannot release a held payment, so the application that
submits payments cannot release its own holds. The details and the reasoning are in
the gateway README.
The gateway does not terminate TLS. A bearer key sent over plain HTTP can be read by anyone on the path, so outside a single machine run the gateway behind a TLS-terminating proxy or service mesh (which is also where mTLS belongs).
Supply chain
Section titled “Supply chain”Every release is built by a public GitHub Actions workflow
(.github/workflows/release.yml) and ships with:
| Asset | What it is |
|---|---|
fednow-oss-<tag>-x86_64-linux.tar.gz |
the binaries, LICENSE, README.md, CHANGELOG.md |
fednow-oss-<tag>.cdx.json |
CycloneDX SBOM of the workspace, generated by cargo-cyclonedx from Cargo.lock |
fednow-oss-<tag>.spdx.json |
SPDX SBOM of the same release |
SHA256SUMS |
SHA-256 of the tarball and both SBOMs |
<asset>.sigstore.json |
one Sigstore signature bundle per asset above |
fednow-oss-<tag>.intoto.jsonl |
SLSA build provenance covering all four assets |
Signing is keyless. There is no private key and no signing secret in this repository: the workflow exchanges its short-lived GitHub Actions OIDC token for an ephemeral certificate from Sigstore’s CA (Fulcio), signs with it, and records the signature in the public Rekor transparency log. The certificate binds the signature to this workflow, in this repository, at that tag — which is what you check when you verify.
The signature bundles are named <asset>.sigstore.json because that is the format they
are in: since cosign v3.0, cosign sign-blob --bundle writes the standard Sigstore
protobuf bundle, JSON-serialised, rather than the older cosign-specific envelope, and
.sigstore.json is that format’s conventional extension.
v0.3.1 named the same files
.cosign.bundle. The bytes were already the Sigstore bundle format; only the extension was non-standard. Releases from the next tag onwards use.sigstore.json. If you are verifying v0.3.1, substitute.cosign.bundlefor.sigstore.jsonin every command below — nothing else changes.
Alongside the signatures, each release carries SLSA build provenance
(fednow-oss-<tag>.intoto.jsonl), generated by
actions/attest-build-provenance.
The two are not redundant. A signature says this workflow, at this tag, signed these
bytes. Provenance is a machine-readable in-toto statement saying these bytes were
produced by this workflow run, from this commit, with these inputs — the thing a policy
engine evaluates, rather than a human reading a certificate identity out of a command
line.
Verifying a release
Section titled “Verifying a release”Signing starts with v0.3.1, under the old
.cosign.bundlename. Earlier releases ship no signature at all; there is nothing to verify on them, and this section does not claim otherwise. v0.3.0 is a special case: its release run failed in the SBOM step, before cosign ran, so that release carries no assets at all — not the tarball, not the SBOMs, not the checksums. Do not treat v0.3.0 as a release; use v0.3.1 or later. Build provenance starts with the first release after v0.3.1.
You need cosign v3 or newer — the version the release workflow signs with, and the one that reads the bundle format it writes. Nothing else: no key to fetch, no keyring to trust.
Download the asset you want and its .sigstore.json bundle, then run the following with
TAG set to the release tag you downloaded:
$ TAG=v0.3.2 # the release you downloaded$ cosign verify-blob \ --bundle "fednow-oss-$TAG-x86_64-linux.tar.gz.sigstore.json" \ --certificate-identity "https://github.com/joaoabuenosi/fednow-oss/.github/workflows/release.yml@refs/tags/$TAG" \ --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \ "fednow-oss-$TAG-x86_64-linux.tar.gz"Verified OKVerified OK means: this exact byte sequence was signed by a run of this release
workflow at that tag, and the signature is in the transparency log. Any other output
is a failure — do not use the artifact.
The same command verifies the SBOMs and SHA256SUMS; swap both filenames:
$ cosign verify-blob \ --bundle "SHA256SUMS.sigstore.json" \ --certificate-identity "https://github.com/joaoabuenosi/fednow-oss/.github/workflows/release.yml@refs/tags/$TAG" \ --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \ SHA256SUMSTwo things worth being explicit about:
- Do not verify with
--certificate-identity-regexpand a permissive pattern. The point of the check is the exact workflow-and-tag identity above. - Checksums alone prove nothing about origin:
SHA256SUMSis published through the same channel as the artifacts. Verify its signature, then check the sums against it.
Verifying build provenance
Section titled “Verifying build provenance”Provenance answers a different question from the signature, and is checked with a
different tool: the GitHub CLI, gh v2.49 or newer. No cosign
and no bundle download — gh fetches the attestation from GitHub’s attestation API and
checks it against the artifact you have in front of you:
$ gh attestation verify "fednow-oss-$TAG-x86_64-linux.tar.gz" \ --repo joaoabuenosi/fednow-oss \ --signer-workflow joaoabuenosi/fednow-oss/.github/workflows/release.ymlLoaded digest sha256:... for file://fednow-oss-...✓ Verification succeeded!--signer-workflow is the part that matters. Without it, --repo alone accepts an
attestation signed by any workflow in this repository; with it, the statement must name
the release workflow. It is the same claim --certificate-identity pins for cosign.
The provenance is also published as a release asset, so the check works with no network
access to GitHub’s API — point gh at the downloaded .intoto.jsonl instead:
$ gh attestation verify "fednow-oss-$TAG-x86_64-linux.tar.gz" \ --bundle "fednow-oss-$TAG.intoto.jsonl" \ --repo joaoabuenosi/fednow-oss \ --signer-workflow joaoabuenosi/fednow-oss/.github/workflows/release.ymlOne bundle covers all four assets — the tarball, both SBOMs and SHA256SUMS — so the
same file verifies any of them; swap the filename in the first argument.
Practices
Section titled “Practices”- Third-party GitHub Actions are pinned to a commit SHA, not a tag — in every
workflow, with a trailing
# <version>comment recording which release the SHA stands for. A pin is reviewed by reading the pinned commit’saction.ymland confirming it accepts the inputs the workflow passes, not by re-resolving the version comment: some upstream tags (dtolnay/rust-toolchain’sv1,rustsec/audit-check’sv2) are moved as new versions ship, so the comment can drift from the tag while the pin itself stays exactly as intended. - Workflows declare
permissions: {}at the top level; each job requests only the scopes it needs, and each scope carries a comment saying which call needs it. - Both of the above are enforced on every CI run, not just asserted here:
.github/scripts/check-workflow-hardening.shfails the build if any workflow gains auses:that is not a 40-character commit SHA, or loses its top-levelpermissions:key. - Action pins are kept current by Dependabot, which bumps the SHA and rewrites the
version comment with it (
.github/dependabot.yml). cargo auditruns on every lockfile change and daily (.github/workflows/audit.yml).- Static analysis: CodeQL runs on every pull request and
every push to
main, over all four languages in this repository — Rust, Python, Java and the GitHub Actions workflows themselves — with findings in the code scanning tab (.github/workflows/codeql.yml). - The ISO 20022 parsers and validators are fuzzed with
cargo-fuzz: targets live infuzz/fuzz_targets/, seeded from the repository’s own valid fixtures, and a nightly job runs each target time-boxed (.github/workflows/fuzz.yml). A crash is a bug report, not a CI annoyance: the corpus and the crashing input are uploaded as run artifacts. - OpenSSF Scorecard runs weekly and on pushes to
main, publishes its results to the public OpenSSF API, and uploads findings to this repository’s code scanning tab (.github/workflows/scorecard.yml). - Container images are not yet published; when they are (see issue #64) they will be signed by digest with the same keyless flow.
Pacsmith is an independent open-source project. Not affiliated with, endorsed by or sponsored by the Federal Reserve. FedNow is a service mark of the Federal Reserve Banks.