diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 21bf880..5557f19 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -1,6 +1,8 @@ { $schema: "https://docs.renovatebot.com/renovate-schema.json", - extends: ["config:recommended"], + // Workflows pin every action to a commit SHA with a `# vX.Y.Z` comment; + // this keeps the pins (and their comments) up to date. + extends: ["config:recommended", "helpers:pinGitHubActionDigests"], dependencyDashboard: true, semanticCommits: "enabled", semanticCommitType: "chore", @@ -43,6 +45,33 @@ automerge: false, labels: ["dependencies", "major-update"], }, + { + // Actions are pinned to commit SHAs, and non-major updates automerge + // (rule above). Without a delay, a compromised upstream release would be + // merged as soon as CI passes, including the PyPI publish action that + // release.yml runs. This holds each update until its release is 3 days + // old; automerge still applies after that. + // + // github-actions updates come from the `github-tags` datasource, which + // provides a release timestamp: the committedDate of the commit the + // matched tag points to. Digest updates are aged against the newest + // matching version's timestamp. `timestamp-required` (the default since + // Renovate 42, set explicitly here) keeps an update without a timestamp + // pending instead of letting it through immediately. `strict` (also the + // default) creates no branch until the age check passes. Pending + // updates are listed on the Dependency Dashboard, where they can be + // forced. + // + // Limitation: committedDate is set by whoever made the commit, and a + // tag that is force-pushed is aged against the original date. So this + // is a delay for ordinary releases, not a guarantee against a + // determined attacker. + description: "Wait 3 days before raising (and automerging) GitHub Actions updates", + matchManagers: ["github-actions"], + minimumReleaseAge: "3 days", + minimumReleaseAgeBehaviour: "timestamp-required", + internalChecksFilter: "strict", + }, { description: "lowest.yml pins the oldest supported Python on purpose", matchFileNames: [".github/workflows/lowest.yml"], diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 713080a..19b4885 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -34,15 +34,17 @@ jobs: - 6379:6379 steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Set up Python - uses: actions/setup-python@v7 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.14" - name: Install uv - uses: astral-sh/setup-uv@v10.2.0 + uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0 - name: Sync dependencies run: uv sync @@ -67,7 +69,7 @@ jobs: - name: Keep the badge for the deploy job if: github.event_name == 'push' - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: coverage-badge path: coverage.svg @@ -91,17 +93,22 @@ jobs: cancel-in-progress: false steps: - # The deploy action runs git in the workspace, so it needs a checkout. - - uses: actions/checkout@v7 + # The deploy action runs git in the workspace, so it needs a checkout. It + # does not need the checkout's credentials: it drops the checkout's auth + # header and pushes to a remote URL built from its own `token` input + # (the job's GITHUB_TOKEN by default). + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Download coverage badge - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: coverage-badge path: badge - name: Upload coverage badge - uses: JamesIves/github-pages-deploy-action@v4 + uses: JamesIves/github-pages-deploy-action@fa24774553152dd7873cd16ebd8d959b010c5445 # v4.9.0 with: branch: coverage-badge # Only the badge. `clean` removes what earlier deploys left there: diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index d161749..0744e10 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -28,20 +28,25 @@ on: - 'uv.lock' - '.github/workflows/docs.yml' +permissions: + contents: read + jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Set up Python - uses: actions/setup-python@v7 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.14" - name: Install uv - uses: astral-sh/setup-uv@v10.2.0 + uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0 # Same command Read the Docs runs; a broken link or docstring reference # fails the PR here instead of the published site. diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 945a165..028e543 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -14,20 +14,25 @@ on: pull_request: branches: [ "master" ] +permissions: + contents: read + jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Set up Python - uses: actions/setup-python@v7 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.14" - name: Install uv - uses: astral-sh/setup-uv@v10.2.0 + uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0 - name: Sync dependencies run: uv sync --group dev --extra jwt diff --git a/.github/workflows/lowest.yml b/.github/workflows/lowest.yml index 0466399..64e7022 100644 --- a/.github/workflows/lowest.yml +++ b/.github/workflows/lowest.yml @@ -36,17 +36,19 @@ jobs: - 6379:6379 steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Set up Python - uses: actions/setup-python@v7 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: # The oldest supported Python. Renovate leaves it alone (see # .github/renovate.json5). python-version: "3.10" - name: Install uv - uses: astral-sh/setup-uv@v10.2.0 + uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0 - name: Sync dependencies run: uv sync diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8e87e21..81edcea 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -87,19 +87,19 @@ jobs: echo "::error::Releases run from refs/heads/master only, not $REF. Tick dry run to rehearse elsewhere." >&2 exit 1 - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 # Tags, for the "already released" check below # This job runs every dev dependency; leave it no git credentials. persist-credentials: false - name: Set up Python - uses: actions/setup-python@v7 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.14" - name: Install uv - uses: astral-sh/setup-uv@v10.2.0 + uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0 # The extras are redundant — the dev group already pins redis, pymemcache, # PyJWT and orjson — but they are named anyway. This is the one job where @@ -255,7 +255,7 @@ jobs: # carries, the notes and `dist/`, exactly as this job produced them, so # nothing after the gate builds or bumps anything a second time. - name: Upload the release files - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: release-v${{ steps.version.outputs.version }} path: | @@ -285,13 +285,14 @@ jobs: steps: # The same commit `build` checked out and tested: both default to # `github.sha`. If master has moved since, the push below is rejected as - # a non-fast-forward and nothing is tagged. - - uses: actions/checkout@v7 + # a non-fast-forward and nothing is tagged. This checkout keeps its + # credentials: the steps below push with git. + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 # Overwrites pyproject.toml, uv.lock and CHANGELOG.md with the bumped # and promoted copies, and adds release-notes.md and dist/. - name: Download the release files - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: release-v${{ needs.build.outputs.version }} @@ -322,7 +323,7 @@ jobs: git push origin "refs/tags/v$VERSION" - name: Create GitHub Release - uses: softprops/action-gh-release@v3 + uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3 with: tag_name: v${{ needs.build.outputs.version }} name: v${{ needs.build.outputs.version }} @@ -348,12 +349,12 @@ jobs: steps: - name: Download the release files - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: release-v${{ needs.build.outputs.version }} - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2 - name: Summary env: diff --git a/.github/workflows/renovate-validate.yml b/.github/workflows/renovate-validate.yml index 08392bc..8d21d4e 100644 --- a/.github/workflows/renovate-validate.yml +++ b/.github/workflows/renovate-validate.yml @@ -12,15 +12,20 @@ on: - '.github/renovate.json5' - '.github/workflows/renovate-validate.yml' +permissions: + contents: read + jobs: validate: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Set up Node.js - uses: actions/setup-node@v7 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 24 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cc39da9..9456603 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -41,15 +41,17 @@ jobs: python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v7 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: ${{ matrix.python-version }} - name: Install uv - uses: astral-sh/setup-uv@v10.2.0 + uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0 - name: Sync dependencies run: uv sync --extra jwt diff --git a/README.md b/README.md index 0f08982..2fc9543 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ async def report(cache: AppCache): - [Distributed lock](https://fastapi-cachex.readthedocs.io/en/latest/LOCK/) — `CacheLock` for multi-process mutual exclusion - [Runnable examples](https://github.com/allen0099/FastAPI-CacheX/tree/master/examples) — one complete app per feature, each covered by the test suite - [API reference](https://fastapi-cachex.readthedocs.io/en/latest/api/http-caching/) -- [Development guide](https://fastapi-cachex.readthedocs.io/en/latest/DEVELOPMENT/) and [contributing](https://fastapi-cachex.readthedocs.io/en/latest/CONTRIBUTING/) +- [Development guide](https://fastapi-cachex.readthedocs.io/en/latest/DEVELOPMENT/) and [contributing](https://fastapi-cachex.readthedocs.io/en/latest/CONTRIBUTING/) · [Security policy](https://github.com/allen0099/FastAPI-CacheX/blob/master/SECURITY.md) — report vulnerabilities privately, not in public issues - [Changelog](https://github.com/allen0099/FastAPI-CacheX/blob/master/CHANGELOG.md) · [Known limitations and planned work](https://github.com/allen0099/FastAPI-CacheX/issues) ## License diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..0c252fe --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,58 @@ +# Security Policy + +## Supported versions + +Security fixes are released for the latest 0.3.x release line only. Upgrade +to the newest 0.3.x release before reporting, and check whether the problem is +still there. + +| Version | Supported | +|--------------------------|-----------| +| Latest 0.3.x (now 0.3.8) | Yes | +| Any older release | No | + +A fix ships as a new 0.3.x patch release; earlier releases are not patched. + +## Reporting a vulnerability + +Please do **not** open a public issue, pull request or discussion for a +security problem. Report it privately through GitHub's private vulnerability +reporting instead: + + + +The report is visible only to you and the maintainers until an advisory is +published. + +Examples of what counts: a way to forge, reuse or steal a session or OAuth +state token, to read or poison another client's cached response, or to make +the library leak data it was given to protect. A bug in your own application's +use of the library, or in a dependency that FastAPI-CacheX does not work around, +is usually better reported to that project. + +## What to include + +The more of these a report has, the faster it can be confirmed: + +- The FastAPI-CacheX version, the Python version, and the backend in use + (memory, Redis or Memcached) with its server version. +- The affected component, for example `@cache`, `CacheManager`, the session + middleware, `StateManager` or `CacheLock`, and the relevant configuration. +- Steps to reproduce, ideally a minimal FastAPI app or test case. +- What an attacker can achieve, and under which conditions. +- Any fix or mitigation you have in mind. + +## What to expect + +FastAPI-CacheX is maintained by volunteers in their spare time, so there are no +guaranteed response times. What you can expect: + +- An acknowledgement once a maintainer has read the report, and a follow-up + after it has been looked into, saying whether it is being treated as a + vulnerability. +- If it is, a fix in a new 0.3.x patch release, followed by a published GitHub + security advisory that credits you, unless you prefer not to be named. +- Questions in the advisory thread if the report needs more detail. + +Please give the maintainers a reasonable chance to release a fix before +disclosing the problem publicly. diff --git a/changelog.d/300.added.md b/changelog.d/300.added.md new file mode 100644 index 0000000..007b3ae --- /dev/null +++ b/changelog.d/300.added.md @@ -0,0 +1,5 @@ +**Added a security policy.** `SECURITY.md` explains how to report a +vulnerability privately through GitHub private vulnerability reporting instead +of a public issue, which release line gets security fixes (the latest 0.3.x) +and what to include in a report. It is linked from the contributing guide and +the README. diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index cefe2f6..a7fa387 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -8,6 +8,14 @@ We love your input! We want to make contributing to FastAPI-CacheX as easy and t - Proposing new features - Becoming a maintainer +## Reporting a Security Vulnerability + +Please do not report security problems in public issues or pull requests. +Report them privately through +[GitHub private vulnerability reporting](https://github.com/allen0099/FastAPI-CacheX/security/advisories/new) +instead. The [security policy](https://github.com/allen0099/FastAPI-CacheX/blob/master/SECURITY.md) +lists the supported versions and what to include in a report. + ## Development Process 1. Fork the project diff --git a/i18n/zh-TW/docs/CONTRIBUTING.md b/i18n/zh-TW/docs/CONTRIBUTING.md index 788717c..0a5e8dd 100644 --- a/i18n/zh-TW/docs/CONTRIBUTING.md +++ b/i18n/zh-TW/docs/CONTRIBUTING.md @@ -8,6 +8,10 @@ - 提議新功能 - 成為維護者 +## 回報安全性漏洞 {#reporting-a-security-vulnerability} + +請不要在公開的 issue 或 Pull Request 中回報安全性問題,而是透過 [GitHub 私下漏洞回報](https://github.com/allen0099/FastAPI-CacheX/security/advisories/new)私下回報。支援的版本以及回報應包含的內容,請見[安全性政策](https://github.com/allen0099/FastAPI-CacheX/blob/master/SECURITY.md)(英文)。 + ## 開發流程 {#development-process} 1. Fork 這個專案 diff --git a/i18n/zh-TW/docs/index.md b/i18n/zh-TW/docs/index.md index 9941e3a..24ed71d 100644 --- a/i18n/zh-TW/docs/index.md +++ b/i18n/zh-TW/docs/index.md @@ -84,7 +84,7 @@ async def report(cache: AppCache): - [分散式鎖](LOCK.md):以 `CacheLock` 在多個行程之間互斥 - [可執行範例](https://github.com/allen0099/FastAPI-CacheX/tree/master/examples)(英文):每個功能一個完整的應用程式,皆由測試套件涵蓋 - [API 參考](https://fastapi-cachex.readthedocs.io/en/latest/api/http-caching/)(英文) -- [開發指南](https://fastapi-cachex.readthedocs.io/en/latest/DEVELOPMENT/)(英文)與[貢獻指南](CONTRIBUTING.md) +- [開發指南](https://fastapi-cachex.readthedocs.io/en/latest/DEVELOPMENT/)(英文)與[貢獻指南](CONTRIBUTING.md) · [安全性政策](https://github.com/allen0099/FastAPI-CacheX/blob/master/SECURITY.md)(英文):請私下回報漏洞,不要開公開 issue - [變更紀錄](https://github.com/allen0099/FastAPI-CacheX/blob/master/CHANGELOG.md)(英文) · [已知限制與規劃中的工作](https://github.com/allen0099/FastAPI-CacheX/issues) ## 授權 {#license}