diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index e26a02d..7c8cd57 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -17,9 +17,13 @@ env: CARGO_TERM_COLOR: always jobs: + # THREAT[TM-RELEASE-001]: Only publishing jobs exchange OIDC for short-lived credentials. publish-fetchkit: name: Publish fetchkit runs-on: ubuntu-latest + permissions: + contents: read + id-token: write steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: @@ -52,14 +56,21 @@ jobs: fi echo "Version verified: $TAG_VERSION" + - name: Authenticate with crates.io + id: auth + uses: rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18 # v1.0.5 + - name: Publish fetchkit to crates.io run: cargo publish -p fetchkit --locked env: - CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} publish-fetchkit-cli: name: Publish fetchkit-cli runs-on: ubuntu-latest + permissions: + contents: read + id-token: write needs: publish-fetchkit steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 @@ -73,7 +84,11 @@ jobs: - name: Wait for crates.io index update run: sleep 30 + - name: Authenticate with crates.io + id: auth + uses: rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18 # v1.0.5 + - name: Publish fetchkit-cli to crates.io run: cargo publish -p fetchkit-cli --locked env: - CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} diff --git a/AGENTS.md b/AGENTS.md index 1bb071f..7d3d4ec 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -141,7 +141,7 @@ Workflows: - `.github/workflows/publish.yml` - Publishes to crates.io on GitHub Release or manual dispatch Requirements: -- `CARGO_REGISTRY_TOKEN` secret must be configured in repo settings +- Configure crates.io Trusted Publishing for both crates: GitHub owner `everruns`, repository `fetchkit`, workflow `publish.yml`, no environment. Publishing jobs exchange OIDC for short-lived tokens; no repository publisher secret is used. Note: `fetchkit-python` is not published to crates.io (`publish = false`). Python release automation is not configured in this repo yet. diff --git a/knowledge/log.md b/knowledge/log.md index 1a240e1..8ddaa1e 100644 --- a/knowledge/log.md +++ b/knowledge/log.md @@ -1,5 +1,9 @@ # Fetchkit Knowledge Update Log +## 2026-10-25 + +* **Publishing authentication**: Bind both Rust crates to GitHub OIDC in `publish.yml`; publishing jobs use short-lived tokens from a pinned official auth action, with no legacy secret fallback. Document recovery from current main with the original immutable release source tag and actual registry consumer verification. + ## 2026-10-23 * **Release 0.6.0**: Replace the temporary Markkit git override with registry minimum 0.1.1; require Tokio 1.54.1 for RUSTSEC-2026-0386. Publish both Rust crates with `--locked`. The minor bump reflects public `FetchOptions` changes already accumulated since 0.5.0; see the changelog migration notes. diff --git a/knowledge/operations/release-process.md b/knowledge/operations/release-process.md index e9ee9e6..768cebd 100644 --- a/knowledge/operations/release-process.md +++ b/knowledge/operations/release-process.md @@ -129,9 +129,37 @@ cargo build --workspace --exclude fetchkit-python --release ### Authentication -- GitHub Actions publishing requires `CARGO_REGISTRY_TOKEN` +- Both published crates have crates.io GitHub Trusted Publishers bound to owner + `everruns`, repository `fetchkit`, workflow `publish.yml`, without an environment. +- Only the two publishing jobs have `id-token: write`; both retain `contents: read`. + The pinned `rust-lang/crates-io-auth-action` exchanges their OIDC identities for + short-lived credentials and revokes them when the jobs finish. +- `CARGO_REGISTRY_TOKEN` is populated from the auth step's output, not from a + repository secret. Existing legacy secrets are unused; this change does not + revoke or replace credentials or require trusted-publishing-only enforcement. +- Publisher bindings authorize this workflow persistently. Repository maintainers + who can change/run it are part of the publishing trust boundary. - Manual release preparation may require GitHub CLI auth for PR creation and merge steps +### Retrying an Existing Release + +Before retrying, check both crates.io versions and the immutable release tag SHA. +Never move the tag or increment the version to work around an authentication failure. +If only the authentication workflow changed after tagging, dispatch the workflow +from `main` while retaining the original source tag: + +```bash +gh workflow run publish.yml --ref main -f release_tag=v0.6.0 +``` + +Both publishing jobs check out the supplied release tag. Running the workflow at +an old tag instead would also select that tag's old authentication implementation. +After successful upload, verify registry version/checksum/source revision, a fresh +library consumer, and `cargo install fetchkit-cli --version '=0.6.0' --locked`. +A GitHub Release or successful package build alone does not prove publication. +If one crate is already published, do not rerun its upload blindly; resume only the +remaining failed publishing job after checking the registry state. + ### Operator Responsibilities - Human reviews and merges the release PR diff --git a/knowledge/security/threat-model.md b/knowledge/security/threat-model.md index 5365c3c..b4a01c6 100644 --- a/knowledge/security/threat-model.md +++ b/knowledge/security/threat-model.md @@ -44,6 +44,7 @@ Verified in this review: | TM-LEAK | Information Leakage | Error messages, metadata exposure, timing | | TM-CONV | Content Conversion | HTML parsing abuse, injection via converted content | | TM-AUTH | Bot Authentication | Signing key exposure, replay, signature scope | +| TM-RELEASE | Release Authentication | Publisher credential leakage and workflow identity | ### Managing Threat IDs @@ -542,6 +543,25 @@ None — all previously open threats have been mitigated. | Authority-scoped signatures | TM-AUTH | Signature covers `@authority`; per-host binding | | Graceful signing failure | TM-AUTH | Signing errors logged, request proceeds unsigned | +## Release Authentication (TM-RELEASE-001) + +**Threat**: A leaked long-lived CI publisher secret permits unauthorized crate +publication outside the intended repository workflow. + +**Mitigation**: Both crates trust only GitHub repository `everruns/fetchkit`, workflow +`publish.yml`, without an environment. Only its two publish jobs request an OIDC +identity, exchanged by a pinned official action for a short-lived crates.io token. +The action revokes that token when the job completes; the old repository secret +is not read. Publishing retains the release-tag source checkout and `--locked`. + +**Trust boundary**: Maintainers able to modify/run that workflow retain publishing +authority. Existing crate ownership, API-token policy, and credentials are unchanged; +trusted-publishing-only enforcement is not enabled by this change. + +**Verification**: Workflow lint, exact-head CI, registry metadata/source checks, and +consumer/CLI installation after publication. An auth failure must stop publication; +there is no fallback to an unrelated credential. + ## References - [Fetchkit Tool Contract](../foundations/tool-contract.md) — public tool behavior and security controls