Authentication libraries are high-value targets for supply chain attacks. A compromised JWT library, an OAuth SDK that silently exfiltrates tokens, or a SAML parser with a backdoor can give attackers access to every application that depends on them. The npm ecosystem has seen dozens of these attacks — typosquatted packages, maintainer account takeovers, and malicious pull requests merged into legitimate packages. Code signing and release integrity mechanisms give users a way to verify that what they installed is what you published.
The supply chain threat model
A supply chain attack can occur at several points in the software distribution pipeline:
- Source code compromise: an attacker modifies the source code before release, either by compromising a developer's machine or by merging a malicious PR.
- Build system compromise: the CI/CD pipeline is compromised to inject malicious code into the build artifacts, even if the source code is clean.
- Registry compromise: the package registry (npm, PyPI) is compromised and the published artifact is replaced.
- Typosquatting: a package with a similar name is published to trick users into installing the wrong package.
- Dependency confusion: a public package is published with the same name as a private internal package, and the package manager fetches the attacker's version.
For auth libraries specifically, the attack payloads are high-value: logging credentials, exfiltrating tokens, or silently disabling validation checks.
GPG release signing
The traditional approach to release integrity is signing release artifacts with a GPG key. The maintainer's public key is published and users can verify the signature before trusting the artifact. This approach is widely used for Linux distributions and mature open source projects.
# Creating a release signing key gpg --full-generate-key # Choose RSA 4096, no expiry for a long-lived release key # Use a separate key for releases, not your personal key # Sign a release artifact gpg --armor --detach-sign bastionary-sdk-1.2.3.tgz # Produces bastionary-sdk-1.2.3.tgz.asc # Publish the signature alongside the artifact # Users can verify with: gpg --verify bastionary-sdk-1.2.3.tgz.asc bastionary-sdk-1.2.3.tgz
GPG signing has significant operational overhead: key management, key distribution (via keyservers or your own page), key rotation, and the fact that most developers do not actually verify signatures. It also does not address build provenance — you are signing the output, but not proving how it was built or from which source commit.
Sigstore and cosign
Sigstore is a newer approach to supply chain security that makes signing transparent and keyless. The cosign tool (part of the Sigstore project) signs artifacts using a short-lived certificate issued against the signer's OIDC identity (typically their GitHub, Google, or Microsoft account). The signature and certificate are recorded in a public, append-only transparency log called Rekor. Anyone can verify the signature without needing to distribute or trust a separate public key.
# Sign a container image with cosign (keyless mode using OIDC) cosign sign ghcr.io/yourorg/bastionary-sdk:1.2.3 # This will open a browser for OIDC authentication # The signature is stored in the container registry and logged to Rekor # Verify the signature cosign verify \ --certificate-identity-regexp="^https://github.com/yourorg/bastionary-sdk/" \ --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \ ghcr.io/yourorg/bastionary-sdk:1.2.3 # Sign an artifact (non-container) cosign sign-blob \ --output-certificate bastionary-sdk-1.2.3.tgz.pem \ --output-signature bastionary-sdk-1.2.3.tgz.sig \ bastionary-sdk-1.2.3.tgz
For GitHub Actions workflows, keyless signing is built in via OIDC identity tokens. The signature binds the artifact to the specific workflow, repository, and commit that produced it.
# GitHub Actions: sign artifacts in CI
name: Release
on:
release:
types: [published]
permissions:
id-token: write # Required for OIDC keyless signing
contents: read
jobs:
sign-and-publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install cosign
uses: sigstore/cosign-installer@v2
- name: Build artifact
run: npm pack
- name: Sign with cosign
run: |
cosign sign-blob \
--output-certificate bastionary-sdk-*.tgz.pem \
--output-signature bastionary-sdk-*.tgz.sig \
bastionary-sdk-*.tgz
- name: Upload to release
uses: softprops/action-gh-release@v1
with:
files: |
bastionary-sdk-*.tgz
bastionary-sdk-*.tgz.pem
bastionary-sdk-*.tgz.sig
SLSA provenance
Supply-chain Levels for Software Artifacts (SLSA, pronounced "salsa") is a framework for incrementally improving supply chain security. The levels range from basic (SLSA 1: build process documented) to highly secure (SLSA 4: hermetic, reproducible builds with two-party review). SLSA provenance is a signed attestation that describes how an artifact was built — the source repository, the commit hash, the build system, and the build parameters.
# Generating SLSA provenance with the official GitHub Actions generator
jobs:
build:
outputs:
hashes: ${{ steps.hash.outputs.hashes }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: npm ci && npm pack
- id: hash
run: |
echo "hashes=$(sha256sum bastionary-sdk-*.tgz | base64 -w0)" >> $GITHUB_OUTPUT
provenance:
needs: [build]
permissions:
id-token: write
contents: write
actions: read
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.4.0
with:
base64-subjects: "${{ needs.build.outputs.hashes }}"
slsa-verifier tool can check provenance attestations for packages that publish them. GitHub's npm registry and PyPI are adding provenance support. Making verification part of your dependency update workflow catches compromised dependencies before they reach production.npm package integrity
For npm packages specifically, package lock files (package-lock.json) and the npm registry's built-in integrity hashes provide some protection. Each package entry includes a resolved URL and an integrity hash. Running npm ci (rather than npm install) verifies these hashes on install and fails if they do not match.
// package-lock.json integrity entry
"bastionary-sdk": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/bastionary-sdk/-/bastionary-sdk-1.2.3.tgz",
"integrity": "sha512-abc123...==",
// This hash is verified by npm ci on every install
}
// Always use npm ci in production/CI, never npm install
// npm ci: uses exact versions from lockfile, verifies integrity hashes
// npm install: updates the lockfile, does not verify integrity
The combination of lockfile integrity verification (npm ci), Sigstore signing of release artifacts, and SLSA provenance for your build pipeline gives users multiple independent mechanisms to verify the integrity of what they are installing — even if the registry, the distribution channel, or an intermediate CDN is compromised.