MCP release security checks

August 25, 2026 ยท View on GitHub

The standalone MCP profile is unauthenticated. Its job UUIDs are bearer capabilities, and the endpoint must be protected by network policy or an authenticated gateway outside a trusted environment. The server bounds a single request's workflow input by rejecting submit_query calls longer than AIQ_MCP_MAX_QUERY_CHARS (default 8000) characters before any job is enqueued; submission rate limiting is deployment-owned and belongs at the gateway or ingress in front of the endpoint. The full runtime model is documented in Expose AI-Q as an MCP Server.

Reproducible dependency evidence

The required Script Validation CI job creates the Linux CPython 3.13 container's production-only environment for aiq-mcp-server and archives:

  • a CycloneDX 1.5 dependency SBOM;
  • the JSON result from the exact-lock uv audit gate; and
  • a package-license inventory built from the exact production environment, including hashes of bundled license and NOTICE files without copying their text or local paths into CI logs.

The same checks can be reproduced from the repository root:

uv export --preview-features sbom-export \
  --project mcp --frozen --no-dev --no-default-groups \
  --format cyclonedx1.5 --output-file aiq-mcp.cdx.json >/dev/null

uv audit --preview-features audit-command,json-output \
  --project mcp --frozen --no-dev --no-default-groups \
  --output-format json > aiq-mcp-vulnerabilities.json || test "$?" -eq 1

uv audit --preview-features audit-command,json-output \
  --project mcp --frozen --no-dev --no-default-groups \
  --ignore-until-fixed GHSA-f4j7-r4q5-qw2c \
  --ignore-until-fixed GHSA-2wm9-hf6c-p5cr \
  --ignore-until-fixed GHSA-36p7-vc44-83pf \
  --ignore-until-fixed GHSA-xph7-9rjv-w5fr \
  --output-format json >/dev/null
UV_PROJECT_ENVIRONMENT=/tmp/aiq-mcp-release \
  uv sync --project mcp --frozen --no-dev --no-default-groups --no-editable
/tmp/aiq-mcp-release/bin/python mcp/scripts/check_license_inventory.py \
  aiq-mcp.cdx.json aiq-mcp-licenses.json

uv audit audits the isolated mcp/uv.lock; it does not audit the root AI-Q workspace lock. CI archives the unfiltered MCP JSON, including accepted findings, and runs the exception-aware command separately as the pass/fail gate.

No-fix vulnerability exception

The following exceptions are accepted only while the advisory service reports no fixed release. The --ignore-until-fixed form automatically turns an exception back into a failure when a fix becomes available.

AdvisoryTransitive packageMCP reachability and compensating control
GHSA-f4j7-r4q5-qw2cChromaDBPresent through the optional knowledge-layer backend. config_mcp.yml has no knowledge-retrieval function and the MCP application does not mount the Chroma server API named by the advisory.
GHSA-2wm9-hf6c-p5crChromaDBRequires an authenticated Chroma API user. The MCP profile has no knowledge-retrieval function and does not expose or mount the Chroma server API.
GHSA-36p7-vc44-83pfChromaDBRequires an authenticated Chroma API user with collection-update permission. The MCP profile has no knowledge-retrieval function and does not expose or mount the Chroma server API.
GHSA-xph7-9rjv-w5frChromaDBApplies to ChromaDB's SimpleRBACAuthorizationProvider. The MCP profile has no knowledge-retrieval function and does not expose or mount the Chroma server API.

The exact public function allowlist is enforced by mcp/tests/test_config_and_packaging.py. Removing this transitive package cleanly requires a future minimal aiq-agent distribution or optional-dependency refactor; uninstalling it after resolution would make package metadata inaccurate.

The MCP project requires nltk>=3.10.0 so its production lock includes NLTK's path-security fixes. NLTK 3.10 adds defusedxml; the MCP project constrains that dependency to the stable 0.7 release line because its prerelease policy would otherwise select a 0.8 release candidate. NLTK no longer requires an audit exception.

The audit also reports archived project status for transitive packages. An archived status is tracked as maintenance risk but is not itself a known vulnerability. New vulnerability records still fail the required CI check.

Security dependency override

The isolated MCP lock installs cryptography==50.0.0 to replace vulnerable earlier releases. langchain-litellm==0.6.6 still declares an upper bound below 49, while nvidia-nat-core==1.8.0 and oci==2.178.0 declare upper bounds below 47, so the MCP project's uv override intentionally supersedes those stale bounds. The MCP config does not enable OCI or NAT authentication. The root AI-Q lock is separate and keeps cryptography>=46.0.6,<47 so its environment remains within NAT's declared range.

This override is security policy for the frozen MCP project and release container, not a functional requirement of MCP or a published package constraint. Only the frozen mcp/uv.lock profile carries the audited 50.0.0 guarantee.

Platform compatibility

The audited MCP release profile is supported on Linux x86_64 with CPython 3.13. The required CI job creates and imports that exact frozen environment, then builds and boots the release container on the same platform. Running the frozen source project on other 64-bit hosts is a development convenience, not a release-validated distribution path.

The upgrade to cryptography==50.0.0 crosses the 49.0.0 compatibility boundary, which removed x86_64 macOS and 32-bit Windows wheels. Those platforms are not supported by this frozen profile; run the Linux release container on a supported 64-bit Linux/container host. This platform narrowing does not affect the root AI-Q environment, which remains on NAT's declared cryptography>=46.0.6,<47 range. Publishing or claiming support for another target requires a target-specific frozen-environment import check, vulnerability audit, license inventory, and protocol smoke.

mcp/scripts/check_runtime_dependencies.py performs the full installed requirement check and permits only those three exact owner/version/dependency/ specifier tuples. It fails on any other incompatibility and also fails when an upstream release makes an exception stale. The release image runs the same script with --verify-imports, which additionally imports every runtime module, asserts the exact mcp and nvidia-nat-core release pins, and checks the tavily_web_search NAT plugin entry point.

License metadata policy

mcp/scripts/check_license_inventory.py fails when a direct runtime dependency is absent, a package has no evidence, GPL/AGPL metadata appears, private runtime or source metadata reappears, or a reviewed version/license/NOTICE hash changes. docx2txt and py-rust-stemmers publish no license metadata; their exact current wheels bundle MIT license files whose hashes are verified.

The inventory deliberately reports, but does not make a legal determination about, the current LGPL dependencies, the ambiguous nemoguardrails classifier, or the fastembed NOTICE entries mentioning CC-BY-NC and Gemma terms. Those exact versions and file hashes remain marked manual_review_required. They are inherited through broad optional AI-Q dependency groups and are not configured by config_mcp.yml. Distribution still requires the releasing organization's license/NOTICE policy review; a new or changed finding fails CI instead of being silently accepted.

This is an engineering evidence and drift gate, not a general SPDX-license allowlist or legal approval. The marker-excluded packages in the CycloneDX document are not installed in the Linux CPython 3.13 release image and remain listed as platform_excluded. Publishing an image for another platform requires a target-specific inventory and organizational license/NOTICE review. The repository's curated LICENSE-THIRD-PARTY is not used as the exact MCP dependency inventory; the archived JSON is.

The supported distributable artifact is the release container built from the repository root. The frozen source project is the supported development path. aiq-mcp-server and its repository-local dependency closure are not published as generic Python wheels. CI builds the MCP wheel only as an internal packaging check and verifies that it embeds the repository's Apache-2.0 license through PEP 639 license-files metadata.