Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1 +1,13 @@
* @darkroomengineering/devs

# Release-critical paths. Changes here can compromise the supply chain
# (workflow swap, manifest tampering, registry config). Reviewers should
# verify that any modification is intentional and does not weaken the
# publish pipeline. Replace with a smaller `release-admins` team once
# one exists.
/.github/workflows/ @darkroomengineering/devs
/.github/CODEOWNERS @darkroomengineering/devs
/.github/dependabot.yml @darkroomengineering/devs
/package.json @darkroomengineering/devs
/.npmrc @darkroomengineering/devs
/SECURITY.md @darkroomengineering/devs
34 changes: 26 additions & 8 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,30 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates


version: 2
updates:
- package-ecosystem: "github-actions" # See documentation for possible values
directory: "/" # Location of package manifests
# Track new versions of GitHub Actions. Dependabot rewrites SHA-pinned actions
# back to the new SHA, preserving the pinning policy in release.yml.
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
groups:
actions:
patterns:
- "*"

# Track advisories on npm dev dependencies. Grouped to reduce PR noise.
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
groups:
dev-dependencies:
dependency-type: "development"
update-types:
- "minor"
- "patch"
ignore:
# Peer deps — bumping these in this repo doesn't change what consumers get.
- dependency-name: "react"
- dependency-name: "vue"
- dependency-name: "@nuxt/kit"
90 changes: 90 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
name: Release

# Publishes `lenis` to npm with build provenance and OIDC-based Trusted Publishing.
# No long-lived NPM_TOKEN exists in this repository or in GitHub Actions secrets:
# the npm CLI exchanges a short-lived GitHub OIDC token for a publish credential at
# the moment of publish, scoped to this workflow file in this repository.
#
# Prerequisites (one-time setup on npmjs.com, see SECURITY.md):
# - Configure `lenis` with a Trusted Publisher pointing at:
# repository: darkroomengineering/lenis
# workflow file: .github/workflows/release.yml
# - Revoke every classic / automation npm token after the first successful run.

on:
push:
tags:
- 'v*'

# Minimum permissions for the whole workflow. The publish job widens to id-token:write.
permissions:
contents: read

concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false

jobs:
publish:
name: Publish to npm
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
id-token: write # Required for npm Trusted Publishing + provenance attestations.

steps:
# Restrict outbound network from the runner so a compromised dep can't exfiltrate.
# Start in `audit` mode; tighten to `block` after observing allowed endpoints.
- name: Harden runner
uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1
with:
egress-policy: audit
disable-sudo: true

- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: 1.3.13

- name: Setup Node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 22
registry-url: 'https://registry.npmjs.org'

# Trusted Publishing requires npm >= 11.5.1; Node 22 ships with npm 10.x.
- name: Upgrade npm to a Trusted-Publishing-capable version
run: npm install -g npm@latest

# Refuse to publish if the git tag and package.json version disagree.
# Prevents an attacker who can push a tag from publishing an arbitrary version.
- name: Verify tag matches package.json version
run: |
PKG_VERSION="v$(node -p "require('./package.json').version")"
if [ "${GITHUB_REF_NAME}" != "${PKG_VERSION}" ]; then
echo "::error::Tag ${GITHUB_REF_NAME} does not match package.json version ${PKG_VERSION}"
exit 1
fi

- name: Install dependencies
run: bun install --frozen-lockfile

- name: Build
run: bun run build

# Refuse to publish if dist/ is missing or empty after the build.
- name: Verify build output
run: |
if [ ! -d dist ] || [ -z "$(ls -A dist)" ]; then
echo "::error::dist/ is missing or empty after build"
exit 1
fi

- name: Publish to npm
run: npm publish --provenance --access public
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ yarn-error.log*
.vercel
.eslintcache

# Keep root /.npmrc tracked (provenance=true setting, no secrets).
# Ignore nested .npmrc files where auth tokens may end up.
.npmrc
!/.npmrc


packages/core/dist/
Expand Down
2 changes: 2 additions & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
; Redundant with publishConfig.provenance in package.json, kept for defense in depth.
provenance=true
120 changes: 120 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# Security Policy

## Reporting a vulnerability

Please **do not** open public GitHub issues for security problems. Email
`hello@darkroom.engineering` or use GitHub's
[private vulnerability reporting](https://github.com/darkroomengineering/lenis/security/advisories/new)
on this repository.

We aim to acknowledge reports within three business days.

## Supply-chain hardening

`lenis` is published to npm via a hardened pipeline designed to make
credential theft and tarball tampering ineffective.

The protections in this repository:

- **No long-lived npm token exists.** Publishing runs only from
`.github/workflows/release.yml`, which uses npm Trusted Publishing
(OIDC) to mint a short-lived publish credential at the moment of
publish. There is no `NPM_TOKEN` in GitHub Actions secrets, in CI, on
any maintainer's machine, or in a password manager.
- **Build provenance.** Every published tarball ships with an [npm
provenance attestation](https://docs.npmjs.com/generating-provenance-statements)
cryptographically tied to this repository, the workflow file, the
commit, and the runner. Consumers can verify provenance with
`npm audit signatures` (see below).
- **Tag-protected releases.** The workflow only runs on tag pushes
matching `v*` and refuses to publish unless the tag matches the
version in `package.json`.
- **Workflow integrity.** All GitHub Actions are pinned to commit SHAs
(not floating tags). Dependabot updates SHAs in PRs that go through
CODEOWNERS review.
- **Local `npm publish` is removed.** `package.json` no longer carries
`publish:main` / `publish:dev` scripts. There is no sanctioned path
to publish from a developer machine.
- **CODEOWNERS** requires review on `.github/`, `package.json`, `.npmrc`,
and this file — anything that could weaken the pipeline.
- **`files` allowlist** in `package.json` limits the published tarball
to `dist/` only; nothing else from the working tree is shipped.

## One-time setup (must be done outside the repository)

These steps cannot be encoded in repository files. A maintainer with admin
rights on the npm package and the GitHub repository must do them once.
After completion, audit them annually.

### On npmjs.com

1. **Confirm two-factor authentication is enforced for writes** on the
account or org that owns `lenis` on npm, and on every account with
publish rights to it.
2. **Configure Trusted Publisher** for `lenis`:
- Settings → Publishing access → "Enable Trusted Publisher"
- Publisher: GitHub Actions
- Repository: `darkroomengineering/lenis`
- Workflow filename: `.github/workflows/release.yml`
- Environment name: *(leave blank unless using GitHub Environments)*
3. **After the first OIDC-based publish succeeds** and the latest
version shows a provenance attestation, set the Publishing access
policy on `lenis` to **"Require two-factor authentication and
disallow tokens"** (the recommended option on
Settings → Publishing access). This forbids any token-based
publish path. Do not enable this before a successful OIDC publish,
or there will be no fallback if Trusted Publisher is misconfigured.
4. **Confirm no long-lived publish tokens remain** for the `lenis`
package.

### On github.com

1. **Branch protection on `main`**:
- Require a pull request before merging
- Require review from CODEOWNERS
- Require signed commits
- Restrict who can push (no direct push to `main`)
- Block force pushes and deletions
2. **Tag protection** for `v*`: restrict who can create release tags to
the same group that can approve releases.
3. **Restrict workflow permissions** (Settings → Actions → General):
- Workflow permissions: "Read repository contents and packages permissions"
- Allow GitHub Actions to create and approve pull requests: off
4. **Secret scanning + push protection**: on.

## Release flow (for maintainers)

```bash
bun run version:patch # or version:minor / version:major
git add package.json
git commit -m "v$(node -p "require('./package.json').version")"
git tag "v$(node -p "require('./package.json').version")"
git push --follow-tags
```

The tag push triggers `.github/workflows/release.yml`, which builds and
publishes. Nothing else publishes; if you ever feel the urge to run
`npm publish` locally, stop and investigate why the workflow isn't
working instead.

## Verifying a published version (for consumers)

After installing `lenis`, verify provenance and signatures:

```bash
npm audit signatures
```

You should see `lenis` listed under "verified registry signatures" with
a "provenance" attestation pointing at `github.com/darkroomengineering/lenis`
and a specific commit SHA. If provenance is missing, the version was not
published through the trusted pipeline — treat as suspect, do not run
`postinstall`, and report it through the channel above.

To inspect provenance for a specific version without installing:

```bash
npm view lenis@<version> --json | jq '.dist'
```

The `attestations` block lists the provenance bundle URL.
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,12 @@
"version:minor": "npm version minor --force --no-git-tag-version",
"version:major": "npm version major --force --no-git-tag-version",
"postversion": "bun run build && bun run readme",
"publish:dev": "npm publish --tag dev",
"publish:main": "npm publish"
"prepublishOnly": "bun run build"
},
"packageManager": "bun@1.3.13",
"publishConfig": {
"access": "public",
"provenance": true
},
"files": [
"dist"
Expand Down