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
4 changes: 2 additions & 2 deletions .github/workflows/agent-permission-diff.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0

- name: Scan agent-facing permission changes
uses: saagpatel/[email protected]
uses: saagpatel/agent-permission-diff-bot@20bc07d1f8052765d3a65378222a08869a7dd027 # v0.5.0
with:
mode: observe
fail-on: critical
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ jobs:
python-version: ["3.11", "3.12", "3.13"]

steps:
- uses: actions/checkout@v7
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

- uses: astral-sh/setup-uv@v7
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
with:
python-version: ${{ matrix.python-version }}

Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

- name: Initialize CodeQL
uses: github/codeql-action/init@v4
uses: github/codeql-action/init@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1
with:
languages: python

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
uses: github/codeql-action/analyze@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1
6 changes: 3 additions & 3 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,16 @@ jobs:
id-token: write # OIDC trusted publishing

steps:
- uses: actions/checkout@v7
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

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

- name: Build wheel and sdist
run: uv build

- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247 # v1.14.1
with:
packages-dir: dist/
6 changes: 3 additions & 3 deletions .github/workflows/scorecard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,18 @@ jobs:
id-token: write
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false

- name: Run OpenSSF Scorecard
uses: ossf/[email protected]
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
with:
results_file: scorecard.sarif
results_format: sarif
publish_results: true

- name: Upload SARIF to code scanning
uses: github/codeql-action/upload-sarif@v4
uses: github/codeql-action/upload-sarif@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1
with:
sarif_file: scorecard.sarif
2 changes: 1 addition & 1 deletion .github/workflows/self-audit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
security-events: write
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

- name: Audit MCP servers (local action)
uses: ./
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Security

- Raised the minimum `mcp` Python SDK version to 1.28.1, closing the vulnerable
WebSocket origin-validation, HTTP session-principal binding, and experimental
task-isolation ranges reported by GHSA-vj7q-gjh5-988w,
GHSA-jpw9-pfvf-9f58, and GHSA-hvrp-rf83-w775.
- Pinned every external GitHub Action used by MCPAudit's CI, CodeQL,
self-audit, Scorecard, composite-action, and PyPI publishing paths to an
immutable commit while retaining the reviewed release version in comments.

### Added

- `proof-before-action` — a local-first CLI that compares a declared action
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ For an end-to-end generator-to-auditor demo packet, see
| Language | Python 3.11+ |
| CLI | Click 8 |
| Output | Rich |
| MCP protocol | `mcp` SDK 1.27+ |
| MCP protocol | `mcp` SDK 1.28.1+ |
| Validation | Pydantic v2 |
| Config parsing | PyYAML + json5 |
| Watch mode | `watchfiles` (optional extra) |
Expand Down
4 changes: 2 additions & 2 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ runs:
using: composite
steps:
- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version: "3.x"

Expand Down Expand Up @@ -160,7 +160,7 @@ runs:

- name: Upload SARIF to code scanning
if: ${{ always() && steps.sarif-check.outputs.exists == 'true' }}
uses: github/codeql-action/upload-sarif@v4
uses: github/codeql-action/upload-sarif@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1
with:
sarif_file: ${{ steps.scan.outputs.sarif-file }}
category: ${{ inputs.sarif-category }}
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ dependencies = [
"rich>=15.0.0",
"anyio>=4.0",
"json5>=0.10",
"mcp>=1.27.0",
"mcp>=1.28.1",
Comment thread
saagpatel marked this conversation as resolved.
"pydantic>=2.0",
"pyyaml>=6.0",
]
Expand Down
75 changes: 75 additions & 0 deletions tests/test_supply_chain_policy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
from __future__ import annotations

import re
import tomllib
from pathlib import Path

REPO_ROOT = Path(__file__).resolve().parents[1]
FULL_COMMIT_SHA = re.compile(r"[0-9a-f]{40}")
# Resolved from the named releases in each action's owning GitHub repository.
# Changing any row is an explicit supply-chain review event, not a shape-only update.
REVIEWED_ACTION_RELEASES = {
"actions/checkout": ("9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0", "v7.0.0"),
"actions/setup-python": ("ece7cb06caefa5fff74198d8649806c4678c61a1", "v6.3.0"),
"astral-sh/setup-uv": ("37802adc94f370d6bfd71619e3f0bf239e1f3b78", "v7.6.0"),
"github/codeql-action/analyze": ("7188fc363630916deb702c7fdcf4e481b751f97a", "v4.37.1"),
"github/codeql-action/init": ("7188fc363630916deb702c7fdcf4e481b751f97a", "v4.37.1"),
"github/codeql-action/upload-sarif": ("7188fc363630916deb702c7fdcf4e481b751f97a", "v4.37.1"),
"ossf/scorecard-action": ("4eaacf0543bb3f2c246792bd56e8cdeffafb205a", "v2.4.3"),
"pypa/gh-action-pypi-publish": ("ba38be9e461d3875417946c167d0b5f3d385a247", "v1.14.1"),
"saagpatel/agent-permission-diff-bot": (
"20bc07d1f8052765d3a65378222a08869a7dd027",
"v0.5.0",
),
}


def test_mcp_runtime_dependency_excludes_known_vulnerable_versions() -> None:
project = tomllib.loads((REPO_ROOT / "pyproject.toml").read_text(encoding="utf-8"))["project"]
mcp_requirement = next(
requirement for requirement in project["dependencies"] if requirement.startswith("mcp")
)

match = re.fullmatch(r"mcp>=(\d+)\.(\d+)\.(\d+)", mcp_requirement)
assert match is not None, "mcp must retain an explicit minimum safe version"
assert tuple(map(int, match.groups())) >= (1, 28, 1)


def test_external_github_actions_are_pinned_to_immutable_commits() -> None:
action_files = [
REPO_ROOT / "action.yml",
*(REPO_ROOT / ".github" / "workflows").glob("*.yml"),
*(REPO_ROOT / ".github" / "workflows").glob("*.yaml"),
]

invalid_uses: list[str] = []
observed_actions: set[str] = set()
for action_file in action_files:
for line_number, line in enumerate(action_file.read_text(encoding="utf-8").splitlines(), start=1):
match = re.match(r"\s*(?:-\s*)?uses:\s*(\S+)(?:\s+#\s+(\S+))?\s*$", line)
if match is None:
continue
action_ref = match.group(1)
if action_ref.startswith("./") or action_ref.startswith("docker://"):
continue
action, separator, revision = action_ref.rpartition("@")
release = match.group(2)
expected = REVIEWED_ACTION_RELEASES.get(action)
observed_actions.add(action)
if (
not separator
or FULL_COMMIT_SHA.fullmatch(revision) is None
or release is None
or expected != (revision, release)
):
invalid_uses.append(
f"{action_file.relative_to(REPO_ROOT)}:{line_number}: {action_ref}"
f" # {release or '<missing release>'}"
)

assert not invalid_uses, "external actions must match reviewed release commits:\n" + "\n".join(
invalid_uses
)
assert observed_actions == set(REVIEWED_ACTION_RELEASES), (
"reviewed action policy and repository use must stay in sync"
)
14 changes: 10 additions & 4 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.