diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0fe9d52..19853e1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,6 +14,9 @@ jobs: name: Test on Node ${{ matrix.node-version }} runs-on: ubuntu-latest + permissions: + contents: read + strategy: fail-fast: false matrix: @@ -25,10 +28,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Set up Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: ${{ matrix.node-version }} cache: 'npm' @@ -53,7 +58,7 @@ jobs: - name: Upload coverage to Codecov if: matrix.node-version == '22.x' - uses: codecov/codecov-action@v6 + uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 with: token: ${{ secrets.CODECOV_TOKEN }} files: ./coverage/lcov.info diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index c4370c5..5ba3e11 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -33,16 +33,18 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Initialize CodeQL - uses: github/codeql-action/init@v4 + uses: github/codeql-action/init@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} queries: security-and-quality - name: Perform CodeQL analysis - uses: github/codeql-action/analyze@v4 + uses: github/codeql-action/analyze@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3 with: category: "/language:${{ matrix.language }}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e281061..caa9148 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,8 +12,7 @@ on: type: string permissions: - contents: write - id-token: write + contents: read concurrency: group: release-${{ github.ref }} @@ -24,15 +23,20 @@ jobs: name: Publish to npm runs-on: ubuntu-latest + permissions: + contents: write + id-token: write + steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ inputs.tag || github.ref }} fetch-depth: 0 + persist-credentials: false - name: Set up Node.js - uses: actions/setup-node@v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 22.x registry-url: "https://registry.npmjs.org" @@ -99,7 +103,7 @@ jobs: - name: Create GitHub Release if: startsWith(github.ref, 'refs/tags/v') || (github.event_name == 'workflow_dispatch' && inputs.tag != '') - uses: softprops/action-gh-release@v3 + uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 with: tag_name: ${{ inputs.tag || github.ref_name }} name: ${{ inputs.tag || github.ref_name }} diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index e5163eb..e45dbd3 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -7,6 +7,9 @@ on: push: branches: - main + pull_request: + branches: + - main workflow_dispatch: permissions: read-all @@ -24,25 +27,31 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Run analysis - uses: ossf/scorecard-action@v2.4.3 + uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 with: results_file: results.sarif results_format: sarif - publish_results: true + # Only publish to the public OpenSSF database from main / scheduled + # runs; PR runs are gating-only so they don't pollute the score. + publish_results: ${{ github.event_name != 'pull_request' }} - name: Upload artifact - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: SARIF file path: results.sarif retention-days: 5 + # Code-scanning SARIF upload only runs on push/schedule. On PRs the + # token does not have `security-events: write` for fork PRs and the + # alert UI is meant to track main-branch state. - name: Upload to code-scanning - uses: github/codeql-action/upload-sarif@v4 + if: github.event_name != 'pull_request' + uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3 with: sarif_file: results.sarif diff --git a/CHANGELOG.md b/CHANGELOG.md index 577389e..97ea431 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ ## [Unreleased] +## [0.5.2] - 2026-05-05 + +### Security + +- All GitHub Actions in every workflow are now pinned to immutable commit SHAs with a trailing `# vX.Y.Z` comment (`actions/checkout`, `actions/setup-node`, `actions/upload-artifact`, `codecov/codecov-action`, `github/codeql-action/*`, `ossf/scorecard-action`, `softprops/action-gh-release`). Addresses the Scorecard `Pinned-Dependencies` check and prevents tag-retag supply-chain attacks (`.github/workflows/ci.yml`, `.github/workflows/codeql.yml`, `.github/workflows/release.yml`, `.github/workflows/scorecard.yml`) +- `release.yml` no longer grants `contents: write` and `id-token: write` at the workflow level; both are scoped to the `publish` job only. Top-level token now defaults to `contents: read`. Addresses Scorecard `Token-Permissions` (`.github/workflows/release.yml`) +- Every `actions/checkout` step now passes `persist-credentials: false`, so the auth token is dropped from `.git/config` after checkout completes (`.github/workflows/ci.yml`, `.github/workflows/codeql.yml`, `.github/workflows/release.yml`, `.github/workflows/scorecard.yml`) +- `SECURITY.md` added at repo root with private vulnerability reporting instructions (GitHub private advisory + email), supported-versions matrix, response SLA, and out-of-scope notes. Addresses Scorecard `Security-Policy` (`SECURITY.md`) +- Added explicit job-level `permissions: contents: read` to the CI test job for least-privilege defense in depth (`.github/workflows/ci.yml`) +- Scorecard workflow now also runs on `pull_request` so it can be a required status check on protected branches. PR runs skip `publish_results` and the SARIF code-scanning upload — those remain `push` / `schedule` / `branch_protection_rule` only — so the public OpenSSF database and the security tab still reflect main-branch state (`.github/workflows/scorecard.yml`) + ### Internal - Dependabot configured for npm and `github-actions` ecosystems with weekly schedule and grouped updates so dev-only patch/minor bumps land as a single PR (`.github/dependabot.yml`) diff --git a/README.md b/README.md index 48e11a4..ffd6959 100644 --- a/README.md +++ b/README.md @@ -349,6 +349,10 @@ Runs the Jest test suite covering all modules. Issues and PRs are welcome. Please include tests and keep the API surface small and focused. +## Security + +Security vulnerabilities should be reported privately via [GitHub private advisories](https://github.com/Hiprax/errors/security/advisories/new) or by email — see [`SECURITY.md`](./SECURITY.md) for the full policy, supported versions, and response timeline. Please do not open public GitHub issues for security problems. + ## License MIT © Hiprax diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..062b943 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,59 @@ +# Security Policy + +## Supported Versions + +`@hiprax/errors` follows semantic versioning. Security fixes are applied to the +latest minor release line on the `main` branch. Older minor lines are not +patched separately; please upgrade to the latest version published on +[npm](https://www.npmjs.com/package/@hiprax/errors). + +| Version | Supported | +| ------- | ------------------ | +| Latest minor on `main` | :white_check_mark: | +| Older minors | :x: | + +## Reporting a Vulnerability + +If you believe you have found a security vulnerability in `@hiprax/errors`, +please report it privately. **Do not open a public GitHub issue** for security +problems. + +You can choose either of the following channels: + +1. **GitHub private vulnerability report (preferred):** + + + This opens a private advisory visible only to maintainers. GitHub will + notify the reporter as the report is triaged. + +2. **Email:** `sajadkhmz@gmail.com` with the subject `SECURITY: @hiprax/errors`. + +When reporting, please include as much of the following as you can: + +- A description of the issue and its impact. +- The affected versions of `@hiprax/errors` (and Node.js / Express versions + used to reproduce, if relevant). +- A minimal reproduction (code snippet, repository, or steps). +- Any suggested mitigation, if known. + +## What to Expect + +- We aim to acknowledge new reports within **5 business days**. +- We will work with the reporter on a timeline for the fix and coordinated + disclosure. +- Once a fix is ready, we publish a patched version to npm and a GitHub + Security Advisory crediting the reporter (unless they ask to remain + anonymous). + +## Out of Scope + +The following are not considered vulnerabilities for this package: + +- Misconfiguration in a consumer's Express application (for example, failing + to register `errorMiddleware` last, or leaking sensitive data through a + custom `cause` in production logs). +- Denial of service caused by extremely large payloads passed into helpers; + upstream input validation is the consumer's responsibility. +- Issues that require modifying the package's internal exports at runtime. + +Thank you for helping keep `@hiprax/errors` and its users safe. diff --git a/package.json b/package.json index 4113a5d..17dac6f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hiprax/errors", - "version": "0.5.1", + "version": "0.5.2", "description": "A modular error handling solution for Express.js applications.", "main": "./dist/index.js", "module": "./dist/index.mjs",