diff --git a/.github/workflows/e2e-wheel.yml b/.github/workflows/e2e-wheel.yml index 31fb8316..b5d33939 100644 --- a/.github/workflows/e2e-wheel.yml +++ b/.github/workflows/e2e-wheel.yml @@ -23,6 +23,11 @@ on: - "src/**" workflow_dispatch: +# Least-privilege token (Scorecard Token-Permissions). This workflow only +# reads the repo to build + smoke-test the wheel; it needs no write scopes. +permissions: + contents: read + jobs: e2e-wheel: name: e2e-wheel (${{ matrix.os }}) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 79c77748..71346c5b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -41,12 +41,24 @@ jobs: runs-on: ubuntu-latest permissions: contents: write + id-token: write # keyless Sigstore signing (OIDC); no signing key exists steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: dist path: dist/ + - name: Sign artifacts with Sigstore (keyless, OIDC) + # Produces dist/.sigstore.json bundles next to each wheel/sdist. + # The 'gh release create dist/*' step below then attaches them to the + # GitHub Release, which is where Scorecard's Signed-Releases check looks. + # release-signing-artifacts is disabled: the action's own auto-attach only + # fires on a 'release: published' event, but we trigger on 'push: tags', + # so we upload the bundles ourselves via the dist/* glob. + uses: sigstore/gh-action-sigstore-python@04cffa1d795717b140764e8b640de88853c92acc # v3.3.0 + with: + inputs: ./dist/*.tar.gz ./dist/*.whl + release-signing-artifacts: false - name: Extract latest CHANGELOG section run: | # Grab the topmost "## [...]" block from CHANGELOG.md as release notes. diff --git a/CHANGELOG.md b/CHANGELOG.md index 116b2855..f83c1f6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,26 @@ and this project uses [Calendar Versioning](https://calver.org/) in the format --- +## [2026.W24] - 2026-06-08 + +### Security + +- **Least-privilege token on the `e2e-wheel` workflow.** `e2e-wheel.yml` was the + one CI workflow missing a top-level `permissions:` block (it was added during + the cross-OS work after PR #624 hardened the others), so it ran with the + default broad `GITHUB_TOKEN` scopes — dropping the OpenSSF Scorecard + Token-Permissions check to 0. Added `permissions: contents: read`; the + workflow only reads the repo to build and smoke-test the wheel. +- **Signed GitHub Releases via Sigstore (keyless).** The release workflow now + signs the built wheel + sdist with `sigstore/gh-action-sigstore-python` + (keyless OIDC — no signing key is generated, stored, or held by anyone) and + attaches the resulting `.sigstore.json` bundles to the GitHub Release. PyPI + uploads were already attested via Trusted Publishing; this extends verifiable + provenance to artifacts pulled from GitHub Releases and satisfies the OpenSSF + Scorecard Signed-Releases check. First proof lands on the next `v*` tag. + +--- + ## [2026.W23] - 2026-06-02 ### Fixed diff --git a/pyproject.toml b/pyproject.toml index fa59439f..faf0d209 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "aipass" -version = "2.5.1" +version = "2.5.2" description = "A local multi-agent framework where your AI agents keep their memory, work together, and never ask you to re-explain context" readme = "README.md" license = "MIT" diff --git a/src/aipass/__init__.py b/src/aipass/__init__.py index ca6fc5c1..dc734538 100644 --- a/src/aipass/__init__.py +++ b/src/aipass/__init__.py @@ -4,4 +4,4 @@ pip install aipass https://github.com/AIOSAI/AIPass """ -__version__ = "2.5.1" +__version__ = "2.5.2" diff --git a/src/aipass/flow/templates/playbook_plans/sunday_merge.md b/src/aipass/flow/templates/playbook_plans/sunday_merge.md index c8f9fea0..7fbec6bb 100644 --- a/src/aipass/flow/templates/playbook_plans/sunday_merge.md +++ b/src/aipass/flow/templates/playbook_plans/sunday_merge.md @@ -26,7 +26,8 @@ the vectorized trail. Close when done. - [ ] On `dev`, working tree understood: `drone @git status --all` - [ ] Confirm what's shipping this week — scan uncommitted changes + already-pushed dev commits ahead of main: `git rev-list --count main..dev` (read git, raw ok) - [ ] No surprise files (stray `/tmp` artifacts, test pollution, `.recovery`/`.archive` churn). Clean = archive, never delete. -- [ ] Decide: **release tag this week?** (tag = PyPI publish + GitHub Release). If yes, note target version. +- [ ] **Version state check** (informs the bump decision): read the **two** release-tied versions — `grep '^version' pyproject.toml` and `grep __version__ src/aipass/__init__.py` (they should match; if drifted, note it) — and what PyPI already has: `curl -s https://pypi.org/pypi/aipass/json | python3 -c "import sys,json;print(json.load(sys.stdin)['info']['version'])"`. PyPI rejects a duplicate, so the target must be > published. +- [ ] Decide: **release tag this week?** (tag = PyPI publish + GitHub Release). If yes, note target version. (Significance call is the user's — the PATCH-default rule below is guidance, and the actual release history is a useful tie-breaker.) ## 2. Verify, commit, CHANGELOG @@ -56,6 +57,7 @@ The PR gate (verified against `.github/workflows/`): - [ ] **User's call to merge** — confirm GO - [ ] `drone @git merge ` (squash-merge) +- [ ] ⚠️ The merge command **echoes the PR's ORIGINAL opening description** — often stale if the PR accumulated more work after it was opened. Don't trust it as the merge summary; the real contents are `git log main..dev` from before the merge. - [ ] ⚠️ **Verify `dev` SURVIVES the merge** (the #625 scar — empirical, every time): `drone @git branches` → `dev` still present; `git rev-parse dev` resolves ## 6. Post-merge realign @@ -80,10 +82,15 @@ How the release fires (verified `publish.yml`): a `v*` **git tag push** runs bui - GitHub Release notes = the **topmost `## [...]` CHANGELOG block** (awk-extracted). Steps: -- [ ] Bump `pyproject.toml` version per the rule above, **on dev so it rides into the PR** (then main's merge commit carries the right version) +- [ ] Bump the version in **BOTH** files (they must match the tag, or `__version__` ships wrong): `pyproject.toml` `version` **and** `src/aipass/__init__.py` `__version__`. Do it **on dev so it rides into the PR** (then main's merge commit carries the right version). ⚠️ These two drift easily — `__init__.py` is the one that gets forgotten. - [ ] Confirm the CHANGELOG top section is the release notes you want -- [ ] **Push the tag — MANUAL (drone has no `tag` verb):** Patrick, or raw `git tag v ` + `git push origin v` via `!`, on the merged main commit -- [ ] Verify PyPI shows the new version + the GitHub Release appeared +- [ ] After merge + `drone @git sync`, get the **real** merged-main sha: `git rev-parse HEAD`. **Verify the version on that exact commit BEFORE tagging:** `git show HEAD:pyproject.toml | grep '^version'` and `git show HEAD:src/aipass/__init__.py | grep __version__` — both must equal the tag. +- [ ] **Push the tag — MANUAL (drone has no `tag` verb; devpulse can't push tags):** user runs it, via `!` or terminal. **Two SEPARATE lines, paste the real sha (no `<…>` placeholders, no `&&`):** + ``` + git tag v + git push origin v + ``` +- [ ] Verify PyPI shows the new version + the GitHub Release appeared (`curl -s https://pypi.org/pypi/aipass/json | python3 -c "import sys,json;print(json.load(sys.stdin)['info']['version'])"`) - [ ] Record the tag → Run Summary ## 8. Wrap