From 1392f86d677e83c9c3a6f5bfc26597f9460a3543 Mon Sep 17 00:00:00 2001 From: allen0099 Date: Sun, 27 Sep 2026 12:02:05 +0000 Subject: [PATCH 1/4] docs: add a security policy pointing to private vulnerability reporting SECURITY.md names the supported release line (latest 0.3.x), where to report (GitHub private vulnerability reporting, not public issues), what to include and what to expect. Linked from CONTRIBUTING (English and zh-TW), the README and the zh-TW home page. Refs #300 --- README.md | 2 +- SECURITY.md | 58 +++++++++++++++++++++++++++++++++ docs/CONTRIBUTING.md | 8 +++++ i18n/zh-TW/docs/CONTRIBUTING.md | 4 +++ i18n/zh-TW/docs/index.md | 2 +- 5 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 SECURITY.md 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/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} From e44aab4c7bb0f878028ca9b8ddbc7d8244834756 Mon Sep 17 00:00:00 2001 From: allen0099 Date: Sun, 27 Sep 2026 12:02:12 +0000 Subject: [PATCH 2/4] ci: restrict token permissions and pin actions to commit SHAs - lint.yml, docs.yml and renovate-validate.yml get a top-level permissions: contents: read; none of them writes anything. - Every actions/checkout that does not push sets persist-credentials: false, including the coverage badge job: the deploy action unsets the checkout's auth header and pushes with its own token input. The release job's checkout keeps its credentials, since it pushes the release commit and tag with git. - Every action is pinned to a full commit SHA with a version comment; pypa/gh-action-pypi-publish@release/v1 is pinned to v1.14.2, the current head of that branch. - Renovate extends helpers:pinGitHubActionDigests so the pins keep updating. Refs #300 --- .github/renovate.json5 | 4 +++- .github/workflows/coverage.yml | 23 +++++++++++++++-------- .github/workflows/docs.yml | 11 ++++++++--- .github/workflows/lint.yml | 11 ++++++++--- .github/workflows/lowest.yml | 8 +++++--- .github/workflows/release.yml | 21 +++++++++++---------- .github/workflows/renovate-validate.yml | 9 +++++++-- .github/workflows/test.yml | 8 +++++--- 8 files changed, 62 insertions(+), 33 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 21bf880..affe223 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", 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 From 04456837c388c1cf960e8792e87dde0123353a67 Mon Sep 17 00:00:00 2001 From: allen0099 Date: Sun, 27 Sep 2026 12:08:59 +0000 Subject: [PATCH 3/4] ci: hold GitHub Actions updates for 3 days before Renovate raises them Actions are pinned to SHAs and non-major updates automerge, so a compromised upstream release would be merged as soon as CI passed. minimumReleaseAge 3 days (timestamp-required, internalChecksFilter strict) delays them; automerge still applies afterwards. Refs #300 --- .github/renovate.json5 | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index affe223..5557f19 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -45,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"], From 9000785369916b71191f26b38ccaf15eb5a5abfd Mon Sep 17 00:00:00 2001 From: allen0099 Date: Sun, 27 Sep 2026 12:15:21 +0000 Subject: [PATCH 4/4] docs(changelog): add the #300 fragment --- changelog.d/300.added.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelog.d/300.added.md 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.