Skip to content
Merged
Show file tree
Hide file tree
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
143 changes: 132 additions & 11 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,33 +1,154 @@
name: Publish to PyPI

on:
push:
tags:
- 'v*'
workflow_dispatch:
Comment thread
saagpatel marked this conversation as resolved.
inputs:
tag:
description: Exact release tag (for example, v2.5.0)
required: true
type: string
commit:
description: Exact 40-character commit recorded in the release approval
required: true
type: string

permissions:
actions: read
contents: read

jobs:
build-and-publish:
name: Build and publish to PyPI
validate-dispatch-ref:
name: Refuse non-main workflow definitions
runs-on: ubuntu-latest
steps:
- name: Require workflow dispatch from main
env:
DISPATCH_REF: ${{ github.ref }}
run: |
test "$DISPATCH_REF" = "refs/heads/main"

build:
name: Verify and build exact release
needs: validate-dispatch-ref
runs-on: ubuntu-latest
environment: pypi
permissions:
contents: read
id-token: write # OIDC trusted publishing

steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ inputs.commit }}
fetch-depth: 0

- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
with:
python-version: "3.11"

- name: Read back protected PyPI environment
env:
GH_TOKEN: ${{ github.token }}
run: |
curl --fail --location --silent --show-error \
--header "Accept: application/vnd.github+json" \
--header "Authorization: Bearer $GH_TOKEN" \
--header "X-GitHub-Api-Version: 2026-03-10" \
"$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/environments/pypi" \
--output "$RUNNER_TEMP/pypi-environment.json"
Comment thread
saagpatel marked this conversation as resolved.

- name: Verify release authorization binding
env:
RELEASE_COMMIT: ${{ inputs.commit }}
RELEASE_TAG: ${{ inputs.tag }}
run: |
git fetch --force --tags origin main
uv run python scripts/verify_release.py \
--tag "$RELEASE_TAG" \
--commit "$RELEASE_COMMIT" \
--environment-json "$RUNNER_TEMP/pypi-environment.json" \
--require-publishable

- name: Install locked dependencies
run: uv sync --dev --locked

- name: Run release quality gate
run: |
uv run pytest
uv run ruff check
uv run ruff format --check
uv run mypy .
uv lock --check
git diff --check

- name: Build wheel and sdist
run: uv build
run: uv build --clear

- name: Verify built distributions
env:
RELEASE_COMMIT: ${{ inputs.commit }}
RELEASE_TAG: ${{ inputs.tag }}
run: |
uv run python scripts/verify_release.py \
--tag "$RELEASE_TAG" \
--commit "$RELEASE_COMMIT" \
--environment-json "$RUNNER_TEMP/pypi-environment.json" \
--require-publishable \
--dist-dir dist
sha256sum dist/* | tee SHA256SUMS

- name: Upload exact candidate artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: mcp-audits-${{ inputs.tag }}
path: |
dist/*.whl
dist/*.tar.gz
SHA256SUMS
if-no-files-found: error
retention-days: 14

publish:
name: Publish approved artifacts to PyPI
needs: build
runs-on: ubuntu-latest
environment: pypi
permissions:
actions: read
contents: read
id-token: write # OIDC trusted publishing

steps:
- name: Verify protected PyPI environment is still enforced
env:
GH_TOKEN: ${{ github.token }}
run: |
curl --fail --location --silent --show-error \
--header "Accept: application/vnd.github+json" \
--header "Authorization: Bearer $GH_TOKEN" \
--header "X-GitHub-Api-Version: 2026-03-10" \
"$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/environments/pypi" \
--output "$RUNNER_TEMP/pypi-environment.json"
jq --exit-status '
.can_admins_bypass == false
and any(
.protection_rules[];
.type == "required_reviewers"
and .prevent_self_review == true
and (.reviewers | length) > 0
)
' "$RUNNER_TEMP/pypi-environment.json"

- name: Download exact candidate artifacts
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
name: mcp-audits-${{ inputs.tag }}
path: release/

- name: Read back artifact hashes
run: |
cd release
sha256sum -c SHA256SUMS
cat SHA256SUMS

- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247 # v1.14.1
with:
packages-dir: dist/
packages-dir: release/dist/
attestations: true
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [2.5.0] - Unreleased

### Security

- Raised the minimum `mcp` Python SDK version to 1.28.1, closing the vulnerable
Expand Down Expand Up @@ -990,7 +992,10 @@ real workstations; API surface may still shift before the `1.0.0` stable cut.
- Risk score output with pass/warn/fail thresholds
- `mcp-audit` CLI entry point

[Unreleased]: https://github.com/saagpatel/MCPAudit/compare/v2.2.1...HEAD
[Unreleased]: https://github.com/saagpatel/MCPAudit/compare/v2.4.0...HEAD
[2.5.0]: https://github.com/saagpatel/MCPAudit/compare/v2.4.0...HEAD
[2.4.0]: https://github.com/saagpatel/MCPAudit/compare/v2.3.0...v2.4.0
[2.3.0]: https://github.com/saagpatel/MCPAudit/compare/v2.2.1...v2.3.0
[2.2.1]: https://github.com/saagpatel/MCPAudit/compare/v2.2.0...v2.2.1
[2.2.0]: https://github.com/saagpatel/MCPAudit/compare/v2.1.0...v2.2.0
[2.1.0]: https://github.com/saagpatel/MCPAudit/compare/v2.0.0...v2.1.0
Expand Down
18 changes: 18 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,24 @@ MCPAudit parses and displays content from MCP server configs and MCP server meta

When the optional `anthropic` dependency is installed and `--llm-analysis` is used, MCPAudit sends selected tool names, descriptions, and parameter names to the Anthropic API for permission classification. In this mode, content from audited MCP server configs is transmitted over the network to a third-party service. Do not use `--llm-analysis` if your MCP configs contain sensitive information (API keys in args, internal hostnames, etc.).

### Proof Before Action

`proof-before-action` compares a declared boundary with observations from a
disposable Docker execution and emits a versioned offline evidence capsule.
Verification binds the staged subject, producer revision, deterministic report
projection, and an independently supplied root hash.

Its result is evidence about the declared and observed surfaces, not a general
sandbox or safety guarantee. Unknown, stale, masked, unmatched, incomplete,
unobservable, dirty, or authority-unverified evidence remains non-authoritative
and cannot become a passing safety claim.

The observer does not prove complete Unix-domain socket coverage, host-kernel
isolation, or safety outside the declared surfaces. A valid capsule also does
not prove that an operator independently obtained the root hash; callers must
supply and protect that trust input themselves. See
[`docs/SECURITY-REVIEW.md`](docs/SECURITY-REVIEW.md) for the detailed boundary.

---

## Supported versions
Expand Down
83 changes: 83 additions & 0 deletions docs/2.5-RELEASE-NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# MCPAudit 2.5.0 Release Boundary

Release status: candidate
Publication decision: NO-GO

MCPAudit 2.5.0 is a backward-compatible minor release. It packages the
capabilities added since 2.4.0 and the dependency and workflow hardening already
landed on `main`. It does not change the existing 2.x audit-report or SARIF
compatibility policy.

## Included

- Proof Before Action: declaration, disposable observation, comparison,
release-trust manifest, deterministic evidence capsule, offline HTML, and
independent verification.
- ProofOS PostgreSQL verification and SafeForge pre-install/runtime evidence.
- Structured handling for corrupted pin baselines.
- Preservation of escalation categories in terminal output.
- A minimum `mcp` SDK version of 1.28.1 and immutable reviewed GitHub Action
revisions.

## Compatibility and migration

- Python 3.11, 3.12, and 3.13 remain supported.
- The distribution remains `mcp-audits`; the installed commands are
`mcp-audit`, `mcp-audits`, and `proof-before-action`.
- Existing 2.x report consumers may continue to accept additive optional
fields. No stable field or SARIF rule identifier is removed or renamed.
- Environments that deliberately constrained `mcp<1.28.1` must upgrade that
dependency before installing 2.5.0.

## Security posture

Proof Before Action is local-first and fail-closed at its evidence boundaries.
Unknown, stale, masked, unmatched, incomplete, unobservable, dirty, or
authority-unverified evidence does not become a passing safety claim. Capsule
verification binds the staged subject, producer revision, report projection,
and an independently supplied root hash.

This evidence is narrower than a general sandbox guarantee. The Docker observer
does not prove complete Unix-domain socket coverage, host-kernel isolation, or
safety outside the declared and observed surfaces. Release-trust claims remain
only as authoritative as their exact producer/subject bindings and independently
supplied root.

## Process limitations

- The repository currently has one eligible human collaborator. Automated
review, CodeQL, CI, and permission-diff evidence reduce risk but do not replace
independent human review.
- Continuous fuzzing is not yet integrated. Deterministic fixture and
adversarial tests cover the shipped boundaries, but they are not a substitute
for a maintained fuzzing service.
- The project is not enrolled in the OpenSSF Best Practices badge program.
- Two independent redacted field reports have not been collected, so broad
downstream environment compatibility remains unproven.

## Rollback

Prefer reverting the consuming configuration while retaining MCPAudit 2.5.0 and
the `mcp>=1.28.1` security floor. Do not describe the existing `v2.4.0` Action
or pre-commit tag as a security-safe rollback: its published dependency metadata
allows vulnerable MCP SDK versions.

If an emergency package rollback is unavoidable, constrain both packages and
read back the resolved versions:

```bash
python -m pip install "mcp-audits==2.4.0" "mcp>=1.28.1"
python -c 'from importlib.metadata import version; print(version("mcp-audits"), version("mcp"))'
```

Evidence produced with new Proof Before Action schemas should be retained and
verified with the matching 2.5.0 producer rather than silently downgraded.

## Publication boundary

Merging the release-candidate PR does not authorize a tag, GitHub Release, PyPI
publication, deployment, or external registry update. Each requires separate
approval bound to the exact landed commit and tag. The manual publish workflow
builds and exposes the exact wheel and sdist hashes before the environment-bound
publish job. Public release remains `NO-GO` until the `pypi` environment requires
an independent reviewer.
Loading