Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 155 additions & 0 deletions .github/workflows/publish-agentpin-0.3.1.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
name: Publish AgentPin 0.3.1 security backport

# Deliberately single-use: no caller-controlled source, package, or version.
on:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: publish-agentpin-0.3.1
cancel-in-progress: false

env:
RELEASE_COMMIT: 780144e9cf173a8295574a0a6bf7e443d6ca7ac8
RUSTUP_TOOLCHAIN: 1.99.0
CARGO_PROFILE_DEV_DEBUG: "0"
CARGO_PROFILE_TEST_DEBUG: "0"
CARGO_INCREMENTAL: "0"

jobs:
validate:
if: >-
github.repository == 'ThirdKeyAI/AgentPin' &&
github.ref == 'refs/heads/main' &&
github.actor == 'jaschadub' &&
github.triggering_actor == 'jaschadub'
runs-on: ubuntu-latest
timeout-minutes: 20
outputs:
lock_hash: ${{ steps.lock.outputs.sha256 }}
artifact_id: ${{ steps.upload.outputs.artifact-id }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ThirdKeyAI/AgentPin
ref: 780144e9cf173a8295574a0a6bf7e443d6ca7ac8
persist-credentials: false
- name: Install pinned Rust
run: rustup toolchain install 1.99.0 --profile minimal
- name: Verify source and package identity
run: |
test "$(git rev-parse HEAD)" = "$RELEASE_COMMIT"
cargo metadata --no-deps --format-version 1 |
jq -e '.packages[] | select(.name == "agentpin") | .version == "0.3.1"'
- name: Resolve, test, and verify package without publication credentials
run: |
cargo generate-lockfile
cargo test --workspace --all-features --locked
cargo package --package agentpin@0.3.1 --locked
cargo install cargo-audit --version 0.22.2 --locked
cargo audit --deny warnings
git diff --exit-code
- name: Record tested lockfile
id: lock
run: |
digest="$(sha256sum Cargo.lock | cut -d ' ' -f 1)"
echo "sha256=$digest" >> "$GITHUB_OUTPUT"
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
id: upload
with:
name: agentpin-0.3.1-lockfile
path: Cargo.lock
if-no-files-found: error
retention-days: 1

publish:
needs: validate
if: >-
github.repository == 'ThirdKeyAI/AgentPin' &&
github.ref == 'refs/heads/main' &&
github.actor == 'jaschadub' &&
github.triggering_actor == 'jaschadub'
# Fresh runner: no dependency build scripts/tests execute with access to
# publication credentials. Transfer lockfile data only, never build caches.
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ThirdKeyAI/AgentPin
ref: 780144e9cf173a8295574a0a6bf7e443d6ca7ac8
persist-credentials: false
- name: Install pinned Rust
run: rustup toolchain install 1.99.0 --profile minimal
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
artifact-ids: ${{ needs.validate.outputs.artifact_id }}
path: ${{ runner.temp }}/agentpin-lock-artifact
merge-multiple: true
digest-mismatch: error
skip-decompress: true
- name: Accept only the tested regular lockfile from the untrusted archive
env:
LOCK_SHA256: ${{ needs.validate.outputs.lock_hash }}
LOCK_ARTIFACT_DIR: ${{ runner.temp }}/agentpin-lock-artifact
run: |
python3 - <<'PY'
import hashlib
import os
from pathlib import Path
import re
import stat
import zipfile

def reject(message):
raise SystemExit(message)

expected = os.environ["LOCK_SHA256"]
if re.fullmatch(r"[0-9a-f]{64}", expected) is None:
reject("Invalid lockfile digest")
entries = list(Path(os.environ["LOCK_ARTIFACT_DIR"]).iterdir())
if len(entries) != 1 or not stat.S_ISREG(entries[0].lstat().st_mode):
reject("Expected exactly one regular archive")
if entries[0].stat().st_size > 2 * 1024 * 1024:
reject("Archive exceeds size limit")
with zipfile.ZipFile(entries[0]) as archive:
members = archive.infolist()
if len(members) != 1:
reject("Archive must contain only Cargo.lock")
member = members[0]
file_type = stat.S_IFMT(member.external_attr >> 16)
if member.filename != "Cargo.lock" or member.is_dir():
reject("Unexpected archive path")
if file_type not in (0, stat.S_IFREG):
reject("Lockfile must be a regular file")
if member.file_size > 1024 * 1024:
reject("Lockfile exceeds size limit")
if member.compress_type not in (zipfile.ZIP_STORED, zipfile.ZIP_DEFLATED):
reject("Unsupported archive compression")
with archive.open(member) as stream:
data = stream.read(1024 * 1024 + 1)
if len(data) != member.file_size or len(data) > 1024 * 1024:
reject("Invalid lockfile size")
if hashlib.sha256(data).hexdigest() != expected:
reject("Lockfile digest mismatch")
# Never extract archive paths or configuration into the clean checkout.
with open("Cargo.lock", "xb") as destination:
destination.write(data)
PY
- name: Check exact source, package, and tested lockfile
env:
LOCK_SHA256: ${{ needs.validate.outputs.lock_hash }}
run: |
test "$(git rev-parse HEAD)" = "$RELEASE_COMMIT"
[[ "$LOCK_SHA256" =~ ^[0-9a-f]{64}$ ]]
printf '%s Cargo.lock\n' "$LOCK_SHA256" | sha256sum --check
cargo metadata --locked --no-deps --format-version 1 |
jq -e '.packages[] | select(.name == "agentpin") | .version == "0.3.1"'
git diff --exit-code
- name: Publish only the verified AgentPin 0.3.1 crate
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
# Package verification ran in the separate, unprivileged job.
run: cargo publish --package agentpin@0.3.1 --registry crates-io --locked --no-verify
Loading