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
48 changes: 39 additions & 9 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,11 @@ jobs:
attestations: write

runs-on: ubuntu-latest
env:
PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }}

steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
show-progress: false

- name: Set up Python
Expand All @@ -30,6 +28,12 @@ jobs:
python-version: "3.12"
cache: pip

- name: Verify release identity and protected-main ancestry
run: |
PACKAGE_VERSION="$(python -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')"
test "v${PACKAGE_VERSION}" = "${GITHUB_REF_NAME}"
git merge-base --is-ancestor "${GITHUB_SHA}" refs/remotes/origin/main

- name: Install release tooling
run: python -m pip install --require-hashes -r requirements-release.lock.txt

Expand All @@ -54,6 +58,16 @@ jobs:
dist/*.tar.gz
dist/sbom.spdx.json

- name: Upload Python distributions for optional PyPI publication
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: python-distributions-${{ github.ref_name }}
path: |
dist/*.whl
dist/*.tar.gz
if-no-files-found: error
retention-days: 30

- name: Publish GitHub Release
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
with:
Expand All @@ -62,9 +76,25 @@ jobs:
dist/*.tar.gz
dist/sbom.spdx.json

- name: Publish to PyPI
if: ${{ env.PYPI_API_TOKEN != '' }}
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ env.PYPI_API_TOKEN }}
run: twine upload dist/*.whl dist/*.tar.gz

publish-pypi:
if: github.repository == 'BlueDot-IT/GhostMCP' && vars.PYPI_PUBLISH_ENABLED == 'true'
needs: release
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/ghostmcp-server
permissions:
id-token: write
steps:
- name: Download Python distributions
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: python-distributions-${{ github.ref_name }}
path: dist

- name: Publish with PyPI Trusted Publishing
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
with:
packages-dir: dist/
print-hash: true
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# Changelog

## Unreleased
## 0.2.1 — 2026-08-30

- Required TLS 1.2 or newer unconditionally for certificate inspection.
- Corrected repository badges and vulnerability-reporting links after the BlueDot migration.
- Made CI and release dependency installation consume committed hash-locked requirements.
- Updated vulnerable dependency floors and audited lock files.
- Replaced token-based PyPI upload with an explicitly gated OIDC Trusted Publisher job.

## 0.2.0 — 2026-07-25

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ CI validates Python 3.11 and 3.12, dependency locks, linting, typing, Bandit, de

## Release status

The package version is `0.2.0`. The runtime is beta-quality: policy enforcement is fail-closed, but operators must still layer network egress controls, least privilege, protected secrets, and written authorization around it.
The package version is `0.2.1`. The runtime is beta-quality: policy enforcement is fail-closed, but operators must still layer network egress controls, least privilege, protected secrets, and written authorization around it.

## License

Expand Down
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

GhostMCP is currently a beta project. Security updates are provided for the latest `main` branch. Older commits, unmerged branches, locally modified copies, and prerelease artifacts may not receive fixes.

The current package version is `0.2.0`. Review configuration and deployment changes before upgrading between beta revisions.
The current package version is `0.2.1`. Review configuration and deployment changes before upgrading between beta revisions.

## Reporting a vulnerability

Expand Down
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# GhostMCP documentation

GhostMCP is a beta security automation server for authorized assessment workflows. These documents describe the hardened runtime in version `0.2.0`.
GhostMCP is a beta security automation server for authorized assessment workflows. These documents describe the hardened runtime in version `0.2.1`.

## Start here

Expand Down
2 changes: 1 addition & 1 deletion ghostmcp/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.2.0"
__version__ = "0.2.1"
2 changes: 1 addition & 1 deletion ghostmcp/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ async def lifespan(_app: FastAPI):
_worker = None


app = FastAPI(title="GhostMCP Dashboard", version="0.2.0", lifespan=lifespan)
app = FastAPI(title="GhostMCP Dashboard", version="0.2.1", lifespan=lifespan)

# Templates
template_dir = Path(__file__).parent / "templates"
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "ghostmcp-server"
version = "0.2.0"
version = "0.2.1"
description = "Security-focused MCP server for authorized assessment workflows"
readme = "README.md"
requires-python = ">=3.11"
Expand Down
60 changes: 60 additions & 0 deletions tests/test_release_metadata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from __future__ import annotations

import tomllib
import unittest
from pathlib import Path

import ghostmcp
from ghostmcp.dashboard import app

ROOT = Path(__file__).resolve().parents[1]
EXPECTED_VERSION = "0.2.1"


class ReleaseMetadataTests(unittest.TestCase):
def test_release_identity_is_consistent(self) -> None:
project = tomllib.loads(
(ROOT / "pyproject.toml").read_text(encoding="utf-8")
)["project"]
changelog = (ROOT / "CHANGELOG.md").read_text(encoding="utf-8")
readme = (ROOT / "README.md").read_text(encoding="utf-8")
security = (ROOT / "SECURITY.md").read_text(encoding="utf-8")

self.assertEqual(project["name"], "ghostmcp-server")
self.assertEqual(project["version"], EXPECTED_VERSION)
self.assertEqual(ghostmcp.__version__, EXPECTED_VERSION)
self.assertEqual(app.version, EXPECTED_VERSION)
self.assertIn(f"## {EXPECTED_VERSION} — ", changelog)
self.assertIn(f"package version is `{EXPECTED_VERSION}`", readme)
self.assertIn(f"package version is `{EXPECTED_VERSION}`", security)

def test_release_uses_explicitly_gated_trusted_publishing(self) -> None:
workflow = (ROOT / ".github/workflows/release.yml").read_text(
encoding="utf-8"
)
release_job, publish_job = workflow.split(
"\n publish-pypi:\n", maxsplit=1
)

self.assertIn("fetch-depth: 0", release_job)
self.assertIn(
'test "v${PACKAGE_VERSION}" = "${GITHUB_REF_NAME}"',
release_job,
)
self.assertIn(
'git merge-base --is-ancestor "${GITHUB_SHA}" '
"refs/remotes/origin/main",
release_job,
)
self.assertIn("vars.PYPI_PUBLISH_ENABLED == 'true'", publish_job)
self.assertIn("environment:\n name: pypi", publish_job)
self.assertIn("permissions:\n id-token: write", publish_job)
self.assertIn(
"pypa/gh-action-pypi-publish@dc37677", publish_job
)
self.assertNotIn("PYPI_API_TOKEN", workflow)
self.assertNotIn("TWINE_PASSWORD", workflow)


if __name__ == "__main__":
unittest.main()