Skip to content

fix(mcp-bridge): refuse undeclared arguments on every tool - #359

Merged
hyperpolymath merged 2 commits into
mainfrom
fix/enforce-input-schema
Oct 8, 2026
Merged

hyperpolymath merged 2 commits into
mainfrom
fix/enforce-input-schema

Conversation

@hyperpolymath

@hyperpolymath hyperpolymath commented Oct 8, 2026 •

Copy link
Copy Markdown
Owner

Summary

Every MCP tool's inputSchema declares additionalProperties: false, but the bridge enforced that only for the 13 routed tools (browser/cloud/comms/ml, since #349). This PR enforces it for every tool in the full list, so a call carrying an argument its schema does not declare is refused with JSON-RPC -32602 before dispatch.

📌 New pins

Head SHA: f3b48f2b1d7c18f9f8f85a6db3312fd59b761d78. No action, lockfile or container pins added or changed.

Changes

  • mcp-bridge/lib/dispatcher.js: validateRoutedArgs becomes validateDeclaredArgs and applies to every tool. The routing-key refusal for routed tools is kept. The deprecated coord_promote_to_supervisor alias is checked against coord_promote_to_master's schema, and a tool missing from the full list is refused as -32601 Unknown tool in the gate.
  • mcp-bridge/lib/tools.js: declares arguments the handlers already read.
    • sender_role (optional enum) on coord_send and coord_send_gated.
    • role and capabilities on coord_register, copied from the local-coord-mcp cartridge.json, which already accepts them.
  • mcp-bridge/tests/declared_args_test.js (new):
    • one case per tool plus the alias, checking that an undeclared argument is refused;
    • one case per tool checking that every declared argument is accepted;
    • three end-to-end tools/call cases (non-routed, coord, unknown tool).
  • mcp-bridge/tests/routing_args_test.js: the old test asserted that coord_send accepts an undeclared sender_role. It now asserts that coord_send accepts the declared argument.
  • package.json test script runs the new file. CHANGELOG.adoc has a line under Unreleased.

Scope: this checks top-level argument names only. Types, required and enums are unchanged.

RSR Quality Checklist

Required

  • Tests pass: bun test mcp-bridge/tests/ gives 210 pass, 0 fail, and npm run test (the repo's node --test script) gives 173 pass, 0 fail.
  • Code is formatted: the repo has no JS formatter configured for mcp-bridge/. I matched the surrounding style.
  • Linter is clean: no JS linter is configured for mcp-bridge/. Not run.
  • No banned language patterns: plain .js only, no new TypeScript or Python.
  • No unsafe blocks without // SAFETY: comments: not applicable, no Rust or Zig touched.
  • No banned functions.
  • SPDX license headers present: the new test file carries the MPL-2.0 header used by its siblings.
  • No secrets, credentials, or .env files included.

As Applicable

  • .machine_readable/*.a2ml: not applicable; A2ML is retired (D308).
  • Documentation updated for user-facing changes: CHANGELOG.adoc.
  • TOPOLOGY.md: not applicable, architecture unchanged.
  • CHANGELOG updated.
  • New dependencies reviewed: not applicable, there are none.
  • ABI/FFI changes validated: not applicable, no src/abi/ or ffi/zig/ change.

Pre-existing red checks (deferred)

Both checks below are also red on main at 7dd5897d. This PR does not touch the code either one covers.

Testing

  • Suite:
    • bun test mcp-bridge/tests/: 210 pass, 0 fail.
    • npm run test: 173 pass, 0 fail.
  • Planted positive (mutant run): I put the pre-change behaviour back temporarily (if (!routingKey) return null;, which checks routed tools only). With that in place, declared_args_test.js fails exactly 58 cases: 55 non-routed tools, the alias, and the two end-to-end refusals. 84 cases still pass. Then I restored the fix.
  • Schema coverage:
    • Every args.<field> read in api-clients.js (60 reads) is declared. I confirmed the check catches a missing field by planting one undeclared read.
    • The coord tool schemas match the local-coord-mcp manifest, apart from the two coord_register fields now added.
    • The per-tool table test calls validateDeclaredArgs directly, so the 60/min rate limiter cannot change which error a refusal returns.

🤖 Generated with Claude Code

https://claude.ai/code/session_019j8She9eTFx54r6aL6sCHP

hyperpolymath and others added 2 commits October 8, 2026 07:14
Every tool's inputSchema declares additionalProperties:false, but the
bridge only enforced it for the 13 routed tools. The argument gate now
checks every tool in the full list, plus the deprecated
coord_promote_to_supervisor alias against coord_promote_to_master's
schema. Unlisted tools are refused as unknown before dispatch.

Arguments the handlers already read are now declared: sender_role on
coord_send and coord_send_gated, and role and capabilities on
coord_register (as in the local-coord-mcp manifest).

Only top-level argument names are checked; types, required fields and
enums are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019j8She9eTFx54r6aL6sCHP
Signed-off-by: Jonathan D.A. Jewell <6759885+hyperpolymath@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019j8She9eTFx54r6aL6sCHP
Signed-off-by: Jonathan D.A. Jewell <6759885+hyperpolymath@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 3bac3a4c-4004-470b-b67f-1bfc02d55c37
📥 Commits

Reviewing files that changed from the base of the PR and between 7dd5897 and f3b48f2.

📒 Files selected for processing (6)
  • CHANGELOG.adoc
  • mcp-bridge/lib/dispatcher.js
  • mcp-bridge/lib/tools.js
  • mcp-bridge/tests/declared_args_test.js
  • mcp-bridge/tests/routing_args_test.js
  • package.json

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

📜 Recent review details
⏰ Context from checks skipped due to timeout. (19)
  • GitHub Check: Dogfooding compliance summary
  • GitHub Check: E2E — Full REST + MCP Bridge
  • GitHub Check: Bench — mcp-bridge (path-claims)
  • GitHub Check: Aspect — Thread Safety + ABI Contract + SPDX
  • GitHub Check: Bench — FFI Catalogue + Mount/Unmount + Hash
  • GitHub Check: E2E — Order Ticket (FFI layer)
  • GitHub Check: governance / Guix packaging policy (Nix retired)
  • GitHub Check: governance / Exemption ratchet
  • GitHub Check: governance / Actions lockfile verify
  • GitHub Check: governance / Workflow security linter
  • GitHub Check: governance / Language / package anti-pattern policy
  • GitHub Check: governance / Check Workflow Staleness
  • GitHub Check: analyze (javascript-typescript, none)
  • GitHub Check: Bridge — bun (unit + boot smoke)
  • GitHub Check: Idris2 type-check (core + all cartridge ABIs)
  • GitHub Check: Detect relevant changes
  • GitHub Check: Hypatia Neurosymbolic Analysis
  • GitHub Check: SonarQube
  • GitHub Check: semgrep-cloud-platform/scan
⚠️ CI failures not shown inline (15)

GitHub Actions: Build / 0_SonarQube.txt: fix(mcp-bridge): refuse undeclared arguments on every tool

Conclusion: failure

View job details

##[group]Run SonarSource/sonarqube-scan-action@v8.1.0
 with:
   projectBaseDir: .
   scannerVersion: 8.1.0.6389
   scannerBinariesUrl: https://binaries.sonarsource.com/Distribution/sonar-scanner-cli
   skipSignatureVerification: false
 env:
   SONAR_***REDACTED_SECRET_ASSIGNMENT***
 ##[endgroup]
 Installing Sonar Scanner CLI 8.1.0.6389 for linux-x64...
 Downloading from: https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-8.1.0.6389-linux-x64.zip
 Downloading signature from: https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-8.1.0.6389-linux-x64.zip.asc
 Importing SonarSource public key from hkps://keyserver.ubuntu.com...
 [command]/usr/bin/gpg --homedir /home/runner/work/_temp/gpg-home-1791440142179-2074 --batch --keyserver hkps://keyserver.ubuntu.com --recv-keys 679F1EE92B19609DE816FDE81DB198F93525EC1A
 gpg: keybox '/home/runner/work/_temp/gpg-home-1791440142179-2074/pubring.kbx' created
 gpg: /home/runner/work/_temp/gpg-home-1791440142179-2074/trustdb.gpg: trustdb created
 gpg: key 1DB198F93525EC1A: public key "SonarSource S.A. <infra@sonarsource.com>" imported
 gpg: Total number processed: 1
 gpg:               imported: 1
 Successfully imported key from hkps://keyserver.ubuntu.com
 ✓ SonarSource public key imported successfully
 Verifying GPG signature...
 [command]/usr/bin/gpg --homedir /home/runner/work/_temp/gpg-home-1791440142179-2074 --batch --verify /home/runner/work/_temp/ad7cd718-2782-4c05-a100-d6871eb0c4b7 /home/runner/work/_temp/4eb54613-2c79-4851-91ab-59d7d3db3dd8
 gpg: Signature made Tue Apr 21 07:20:26 2026 UTC
 gpg:                using RSA key D1436C0DBACEA48702AF97C363F1DD7753B8B315
 gpg: Good signature from "SonarSource S.A. <infra@sonarsource.com>" [unknown]
 gpg: WARNING: This key is not certified with a trusted signature!
 gpg:          There is no indication that the signature belongs to the owner.
 Primary key fingerprint: 679F 1EE9 2B19 609D E816  FDE8 1DB1 98F9 3525 EC1A
   ...

GitHub Actions: Build / SonarQube: fix(mcp-bridge): refuse undeclared arguments on every tool

Conclusion: failure

View job details

##[group]Run SonarSource/sonarqube-scan-action@v8.1.0
 with:
   projectBaseDir: .
   scannerVersion: 8.1.0.6389
   scannerBinariesUrl: https://binaries.sonarsource.com/Distribution/sonar-scanner-cli
   skipSignatureVerification: false
 env:
   SONAR_***REDACTED_SECRET_ASSIGNMENT***
 ##[endgroup]
 Installing Sonar Scanner CLI 8.1.0.6389 for linux-x64...
 Downloading from: https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-8.1.0.6389-linux-x64.zip
 Downloading signature from: https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-8.1.0.6389-linux-x64.zip.asc
 Importing SonarSource public key from hkps://keyserver.ubuntu.com...
 [command]/usr/bin/gpg --homedir /home/runner/work/_temp/gpg-home-1791440142179-2074 --batch --keyserver hkps://keyserver.ubuntu.com --recv-keys 679F1EE92B19609DE816FDE81DB198F93525EC1A
 gpg: keybox '/home/runner/work/_temp/gpg-home-1791440142179-2074/pubring.kbx' created
 gpg: /home/runner/work/_temp/gpg-home-1791440142179-2074/trustdb.gpg: trustdb created
 gpg: key 1DB198F93525EC1A: public key "SonarSource S.A. <infra@sonarsource.com>" imported
 gpg: Total number processed: 1
 gpg:               imported: 1
 Successfully imported key from hkps://keyserver.ubuntu.com
 ✓ SonarSource public key imported successfully
 Verifying GPG signature...
 [command]/usr/bin/gpg --homedir /home/runner/work/_temp/gpg-home-1791440142179-2074 --batch --verify /home/runner/work/_temp/ad7cd718-2782-4c05-a100-d6871eb0c4b7 /home/runner/work/_temp/4eb54613-2c79-4851-91ab-59d7d3db3dd8
 gpg: Signature made Tue Apr 21 07:20:26 2026 UTC
 gpg:                using RSA key D1436C0DBACEA48702AF97C363F1DD7753B8B315
 gpg: Good signature from "SonarSource S.A. <infra@sonarsource.com>" [unknown]
 gpg: WARNING: This key is not certified with a trusted signature!
 gpg:          There is no indication that the signature belongs to the owner.
 Primary key fingerprint: 679F 1EE9 2B19 609D E816  FDE8 1DB1 98F9 3525 EC1A
   ...

GitHub Actions: Governance / 2_governance _ Actions lockfile verify.txt: fix(mcp-bridge): refuse undeclared arguments on every tool

Conclusion: failure

View job details

##[group]Run set -uo pipefail
 �[36;1mset -uo pipefail�[0m
 �[36;1m# Stage the gate, the verifier and the exemption ledger. When THIS�[0m
 �[36;1m# repository is standards, its own working tree already holds all�[0m
 �[36;1m# three (self-lint); every other caller uses the pinned checkout.�[0m
 �[36;1mif [ "$GITHUB_REPOSITORY" = hyperpolymath/standards ]; then�[0m
 �[36;1m  SRC=scripts�[0m
 �[36;1m  LEDGERSRC=.machine_readable�[0m
 �[36;1m  echo "Using this repository's own gate + verifier + ledger (standards self-lint)."�[0m
 �[36;1melse�[0m
 �[36;1m  SRC=.standards-lock/scripts�[0m
 �[36;1m  LEDGERSRC=.standards-lock/.machine_readable�[0m
 �[36;1mfi�[0m
 �[36;1mfor f in check-actions-lock-gate.sh update-actions-lock.sh; do�[0m
 �[36;1m  if [ ! -f "$SRC/$f" ]; then�[0m
 �[36;1m    echo "::error::actions-lock gate: $f not found in $SRC (pinned standards checkout failed?)"�[0m

GitHub Actions: Governance / governance _ Actions lockfile verify: fix(mcp-bridge): refuse undeclared arguments on every tool

Conclusion: failure

View job details

##[group]Run set -uo pipefail
 �[36;1mset -uo pipefail�[0m
 �[36;1m# Stage the gate, the verifier and the exemption ledger. When THIS�[0m
 �[36;1m# repository is standards, its own working tree already holds all�[0m
 �[36;1m# three (self-lint); every other caller uses the pinned checkout.�[0m
 �[36;1mif [ "$GITHUB_REPOSITORY" = hyperpolymath/standards ]; then�[0m
 �[36;1m  SRC=scripts�[0m
 �[36;1m  LEDGERSRC=.machine_readable�[0m
 �[36;1m  echo "Using this repository's own gate + verifier + ledger (standards self-lint)."�[0m
 �[36;1melse�[0m
 �[36;1m  SRC=.standards-lock/scripts�[0m
 �[36;1m  LEDGERSRC=.standards-lock/.machine_readable�[0m
 �[36;1mfi�[0m
 �[36;1mfor f in check-actions-lock-gate.sh update-actions-lock.sh; do�[0m
 �[36;1m  if [ ! -f "$SRC/$f" ]; then�[0m
 �[36;1m    echo "::error::actions-lock gate: $f not found in $SRC (pinned standards checkout failed?)"�[0m

GitHub Actions: Governance / 4_governance _ Well-Known (RFC 9116 + RSR).txt: fix(mcp-bridge): refuse undeclared arguments on every tool

Conclusion: failure

View job details

##[group]Run SECTXT=""
 �[36;1mSECTXT=""�[0m
 �[36;1m[ -f ".well-known/security.txt" ] && SECTXT=".well-known/security.txt"�[0m
 �[36;1m[ -f "security.txt" ] && SECTXT="security.txt"�[0m
 �[36;1mif [ -z "$SECTXT" ]; then�[0m
 �[36;1m  echo "::warning::No security.txt found."�[0m
 �[36;1m  exit 0�[0m
 �[36;1mfi�[0m
 �[36;1mgrep -q "^Contact:" "$SECTXT" || { echo "::error::Missing Contact field"; exit 1; }�[0m

GitHub Actions: Governance / governance _ Well-Known (RFC 9116 + RSR): fix(mcp-bridge): refuse undeclared arguments on every tool

Conclusion: failure

View job details

##[group]Run SECTXT=""
 �[36;1mSECTXT=""�[0m
 �[36;1m[ -f ".well-known/security.txt" ] && SECTXT=".well-known/security.txt"�[0m
 �[36;1m[ -f "security.txt" ] && SECTXT="security.txt"�[0m
 �[36;1mif [ -z "$SECTXT" ]; then�[0m
 �[36;1m  echo "::warning::No security.txt found."�[0m
 �[36;1m  exit 0�[0m
 �[36;1mfi�[0m
 �[36;1mgrep -q "^Contact:" "$SECTXT" || { echo "::error::Missing Contact field"; exit 1; }�[0m

GitHub Actions: Governance / governance _ Well-Known (RFC 9116 + RSR): fix(mcp-bridge): refuse undeclared arguments on every tool

Conclusion: failure

View job details

##[group]Run MIXED=$(grep -rE 'src="http://|href="http://' --include="*.html" --include="*.htm" . 2>/dev/null | grep -vE 'localhost|127\.0\.0\.1|example\.com|lol/|node_modules/|third-party/|vendor/' | head -5)
 �[36;1mMIXED=$(grep -rE 'src="http://|href="http://' --include="*.html" --include="*.htm" . 2>/dev/null | grep -vE 'localhost|127\.0\.0\.1|example\.com|lol/|node_modules/|third-party/|vendor/' | head -5)�[0m
 �[36;1mif [ -n "$MIXED" ]; then�[0m
 �[36;1m  echo "::error::Mixed content (HTTP in HTML)"�[0m

GitHub Actions: Governance / 7_governance _ Security policy checks.txt: fix(mcp-bridge): refuse undeclared arguments on every tool

Conclusion: failure

View job details

##[group]Run set -uo pipefail
 �[36;1mset -uo pipefail�[0m
 �[36;1mDIR=.github/canonical-references�[0m
 �[36;1mif [ ! -d "$DIR" ]; then�[0m
 �[36;1m  echo "ℹ️  [R5] no $DIR/ — skipped (repo has not opted in)"�[0m
 �[36;1m  exit 0�[0m
 �[36;1mfi�[0m
 �[36;1m# Rule files are read with yq, never a hand parser (YAML-POLICY Y-1); this�[0m
 �[36;1m# gate previously parsed them with Python, which the estate bans.�[0m
 �[36;1mif ! command -v yq >/dev/null 2>&1 || ! command -v jq >/dev/null 2>&1; then�[0m
 �[36;1m  echo "❌ [R5] yq and jq are required on the runner for YAML rule parsing"�[0m
 �[36;1m  exit 2�[0m
 �[36;1mfi�[0m
 �[36;1mmapfile -t files < <(find "$DIR" -maxdepth 1 -type f \( -name '*.yml' -o -name '*.yaml' \) ! -name '.*' | LC_ALL=C sort)�[0m
 �[36;1mif [ "${#files[@]}" -eq 0 ]; then�[0m
 �[36;1m  echo "ℹ️  [R5] $DIR/ has no .yml/.yaml rules — skipped"�[0m
 �[36;1m  exit 0�[0m
 �[36;1mfi�[0m
 �[36;1m�[0m
 �[36;1merr=$(mktemp)�[0m
 �[36;1mtrap 'rm -f "$err"' EXIT�[0m
 �[36;1mtotal=0�[0m
 �[36;1mfor rf in "${files[@]}"; do�[0m
 �[36;1m  if ! cfg=$(yq -o json '.' "$rf" 2>&1); then�[0m
 �[36;1m    echo "❌ [R5] $rf: not parseable YAML: $cfg"; total=$((total + 1)); continue�[0m
 �[36;1m  fi�[0m
 �[36;1m  if [ "$(jq -r 'type' <<<"$cfg")" != object ]; then�[0m
 �[36;1m    echo "❌ [R5] $rf: top-level must be a mapping"; total=$((total + 1)); continue�[0m
 �[36;1m  fi�[0m
 �[36;1m  rid=$(jq -r --arg b "$(basename "$rf")" 'if has("id") then .id | tostring else $b end' <<<"$cfg")�[0m
 �[36;1m  desc=$(jq -r '.description // ""' <<<"$cfg")�[0m
 �[36;1m  canon=$(jq -r '.canonical_pointer // ""' <<<"$cfg")�[0m
 �[36;1m  mapfile -t pats < <(jq -r '(.patterns // [])[]' <<<"$cfg")�[0m
 �[36;1m  mapfile -t includes < <(jq -r '((.scope // {}).include // [])[]' <<<"$cfg")�[0m
 �[36;1m  if [ "${#pats[@]}" -eq 0 ] || [ "${#includes[@]}" -eq 0 ]; then�[0m
 �[36;1m    echo "❌ [R5:$rid] missing patterns or scope.include in $rf"�[0m
 �[36;1m    total=$((total + 1)); continue�[0m
 �[36...

GitHub Actions: Governance / governance _ Security policy checks: fix(mcp-bridge): refuse undeclared arguments on every tool

Conclusion: failure

View job details

##[group]Run set -uo pipefail
 �[36;1mset -uo pipefail�[0m
 �[36;1mDIR=.github/canonical-references�[0m
 �[36;1mif [ ! -d "$DIR" ]; then�[0m
 �[36;1m  echo "ℹ️  [R5] no $DIR/ — skipped (repo has not opted in)"�[0m
 �[36;1m  exit 0�[0m
 �[36;1mfi�[0m
 �[36;1m# Rule files are read with yq, never a hand parser (YAML-POLICY Y-1); this�[0m
 �[36;1m# gate previously parsed them with Python, which the estate bans.�[0m
 �[36;1mif ! command -v yq >/dev/null 2>&1 || ! command -v jq >/dev/null 2>&1; then�[0m
 �[36;1m  echo "❌ [R5] yq and jq are required on the runner for YAML rule parsing"�[0m
 �[36;1m  exit 2�[0m
 �[36;1mfi�[0m
 �[36;1mmapfile -t files < <(find "$DIR" -maxdepth 1 -type f \( -name '*.yml' -o -name '*.yaml' \) ! -name '.*' | LC_ALL=C sort)�[0m
 �[36;1mif [ "${#files[@]}" -eq 0 ]; then�[0m
 �[36;1m  echo "ℹ️  [R5] $DIR/ has no .yml/.yaml rules — skipped"�[0m
 �[36;1m  exit 0�[0m
 �[36;1mfi�[0m
 �[36;1m�[0m
 �[36;1merr=$(mktemp)�[0m
 �[36;1mtrap 'rm -f "$err"' EXIT�[0m
 �[36;1mtotal=0�[0m
 �[36;1mfor rf in "${files[@]}"; do�[0m
 �[36;1m  if ! cfg=$(yq -o json '.' "$rf" 2>&1); then�[0m
 �[36;1m    echo "❌ [R5] $rf: not parseable YAML: $cfg"; total=$((total + 1)); continue�[0m
 �[36;1m  fi�[0m
 �[36;1m  if [ "$(jq -r 'type' <<<"$cfg")" != object ]; then�[0m
 �[36;1m    echo "❌ [R5] $rf: top-level must be a mapping"; total=$((total + 1)); continue�[0m
 �[36;1m  fi�[0m
 �[36;1m  rid=$(jq -r --arg b "$(basename "$rf")" 'if has("id") then .id | tostring else $b end' <<<"$cfg")�[0m
 �[36;1m  desc=$(jq -r '.description // ""' <<<"$cfg")�[0m
 �[36;1m  canon=$(jq -r '.canonical_pointer // ""' <<<"$cfg")�[0m
 �[36;1m  mapfile -t pats < <(jq -r '(.patterns // [])[]' <<<"$cfg")�[0m
 �[36;1m  mapfile -t includes < <(jq -r '((.scope // {}).include // [])[]' <<<"$cfg")�[0m
 �[36;1m  if [ "${#pats[@]}" -eq 0 ] || [ "${#includes[@]}" -eq 0 ]; then�[0m
 �[36;1m    echo "❌ [R5:$rid] missing patterns or scope.include in $rf"�[0m
 �[36;1m    total=$((total + 1)); continue�[0m
 �[36...

GitHub Actions: Governance / 9_governance _ UUID v7 conformance.txt: fix(mcp-bridge): refuse undeclared arguments on every tool

Conclusion: failure

View job details

##[group]Run chmod +x .standards-uuid/scripts/check-uuid-v7.sh
 �[36;1mchmod +x .standards-uuid/scripts/check-uuid-v7.sh�[0m
 �[36;1m.standards-uuid/scripts/check-uuid-v7.sh .�[0m
 shell: /usr/bin/bash -e {0}
 ##[endgroup]
 ./.machine_readable/CLADE.a2ml: non-v7 UUID literal (5f5df93b-789d-5ff6-8a44-9dc6ed90a7b1)
 UUID v7 check failed. Use the estate UUID v7 standard and type external/legacy IDs explicitly.
 ##[error]Process completed with exit code 1.

GitHub Actions: Governance / governance _ UUID v7 conformance: fix(mcp-bridge): refuse undeclared arguments on every tool

Conclusion: failure

View job details

##[group]Run chmod +x .standards-uuid/scripts/check-uuid-v7.sh
 �[36;1mchmod +x .standards-uuid/scripts/check-uuid-v7.sh�[0m
 �[36;1m.standards-uuid/scripts/check-uuid-v7.sh .�[0m
 shell: /usr/bin/bash -e {0}
 ##[endgroup]
 ./.machine_readable/CLADE.a2ml: non-v7 UUID literal (5f5df93b-789d-5ff6-8a44-9dc6ed90a7b1)
 UUID v7 check failed. Use the estate UUID v7 standard and type external/legacy IDs explicitly.
 ##[error]Process completed with exit code 1.

GitHub Actions: Governance / 10_governance _ Code quality + docs.txt: fix(mcp-bridge): refuse undeclared arguments on every tool

Conclusion: failure

View job details

##[group]Run set -eo pipefail
 �[36;1mset -eo pipefail�[0m
 �[36;1m# Arming policy, and the evidence it rests on: standards#991.�[0m
 �[36;1m#�[0m
 �[36;1m#   retired-filename -> BLOCKS. A STABLE predicate:�[0m
 �[36;1m#   the retired `.a2ml` spelling of the launcher standard was�[0m
 �[36;1m#   deleted upstream on 2026-09-22�[0m
 �[36;1m#   (standards#952) and stays deleted, so a caller that is clean�[0m
 �[36;1m#   today cannot become defective without editing the citation�[0m
 �[36;1m#   itself. Measured 2026-09-22 over EVERY clone in the estate --�[0m
 �[36;1m#   595 scanned, 553 carrying an origin/main. 432 reference this�[0m
 �[36;1m#   reusable workflow, but only 12 do so at a MUTABLE ref (@main),�[0m
 �[36;1m#   and a new step reaches ONLY those 12: a caller pinned at a SHA�[0m
 �[36;1m#   freezes this whole file, this step included, so it can never�[0m
 �[36;1m#   receive the step at all. The real gate was run against all 12:�[0m
 �[36;1m#   12/12 rc=0, retired=0. Five slugs do carry the retired literal�[0m
 �[36;1m#   (tma-mark2, canonical-ums, the-nash-equilibrium,�[0m
 �[36;1m#   launch-scaffolder, trigger) and their overlap with the armed 12�[0m
 �[36;1m#   is ZERO -- so arming this tier reds ZERO live callers. A�[0m
 �[36;1m#   known-answer positive control fired (rc=1) on three of those�[0m
 �[36;1m#   defective repos through the identical harness, so the twelve�[0m
 �[36;1m#   zeros are a real measurement and not a broken probe.�[0m
 �[36;1m#�[0m
 �[36;1m#   stale-version -> WARNS, and does not block. A TIME-DEPENDENT�[0m
 �[36;1m#   predicate: the gate compares against its own CURRENT_VERSION, so�[0m
 �[36;1m#   every correctly-citing caller flips to defect the moment the�[0m
 �[36;1m#   standard bumps, having done nothing. A baked-in cutoff DATE does�[0m
 �[36;1m#   not cure that -- the #505 split above can use one because its�[0m
 �[36;1m#   missing-CONTRIBUTING population is static, while this population�[0m
 �[36;1m#   is regenerated at every...

GitHub Actions: Governance / governance _ Code quality + docs: fix(mcp-bridge): refuse undeclared arguments on every tool

Conclusion: failure

View job details

##[group]Run set -eo pipefail
 �[36;1mset -eo pipefail�[0m
 �[36;1m# Arming policy, and the evidence it rests on: standards#991.�[0m
 �[36;1m#�[0m
 �[36;1m#   retired-filename -> BLOCKS. A STABLE predicate:�[0m
 �[36;1m#   the retired `.a2ml` spelling of the launcher standard was�[0m
 �[36;1m#   deleted upstream on 2026-09-22�[0m
 �[36;1m#   (standards#952) and stays deleted, so a caller that is clean�[0m
 �[36;1m#   today cannot become defective without editing the citation�[0m
 �[36;1m#   itself. Measured 2026-09-22 over EVERY clone in the estate --�[0m
 �[36;1m#   595 scanned, 553 carrying an origin/main. 432 reference this�[0m
 �[36;1m#   reusable workflow, but only 12 do so at a MUTABLE ref (@main),�[0m
 �[36;1m#   and a new step reaches ONLY those 12: a caller pinned at a SHA�[0m
 �[36;1m#   freezes this whole file, this step included, so it can never�[0m
 �[36;1m#   receive the step at all. The real gate was run against all 12:�[0m
 �[36;1m#   12/12 rc=0, retired=0. Five slugs do carry the retired literal�[0m
 �[36;1m#   (tma-mark2, canonical-ums, the-nash-equilibrium,�[0m
 �[36;1m#   launch-scaffolder, trigger) and their overlap with the armed 12�[0m
 �[36;1m#   is ZERO -- so arming this tier reds ZERO live callers. A�[0m
 �[36;1m#   known-answer positive control fired (rc=1) on three of those�[0m
 �[36;1m#   defective repos through the identical harness, so the twelve�[0m
 �[36;1m#   zeros are a real measurement and not a broken probe.�[0m
 �[36;1m#�[0m
 �[36;1m#   stale-version -> WARNS, and does not block. A TIME-DEPENDENT�[0m
 �[36;1m#   predicate: the gate compares against its own CURRENT_VERSION, so�[0m
 �[36;1m#   every correctly-citing caller flips to defect the moment the�[0m
 �[36;1m#   standard bumps, having done nothing. A baked-in cutoff DATE does�[0m
 �[36;1m#   not cure that -- the #505 split above can use one because its�[0m
 �[36;1m#   missing-CONTRIBUTING population is static, while this population�[0m
 �[36;1m#   is regenerated at every...

GitHub Actions: Governance / 14_governance _ Workflow security linter.txt: fix(mcp-bridge): refuse undeclared arguments on every tool

Conclusion: failure

View job details

##[group]Run # GitHub Actions REJECTS a workflow with duplicate keys: the run is
 �[36;1m# GitHub Actions REJECTS a workflow with duplicate keys: the run is�[0m
 �[36;1m# `failure` with no jobs, no log and no check run. Nothing else here�[0m
 �[36;1m# can see it, because yaml.safe_load silently keeps the LAST�[0m
 �[36;1m# duplicate and reports success — so the file "parses" and every�[0m
 �[36;1m# other lint passes. Measured 2026-08-05: nine workflows in hypatia�[0m
 �[36;1m# were dead this way, including a CodeQL workflow with zero�[0m
 �[36;1m# successful runs in its entire lifetime.�[0m
 �[36;1mset -euo pipefail�[0m
 �[36;1mSCRIPT=".standards-dupkey/scripts/check-workflow-duplicate-keys.sh"�[0m
 �[36;1m# Self-hosting fallback: when THIS repository is standards, its own�[0m
 �[36;1m# working tree already holds the script, and during a rename that copy�[0m
 �[36;1m# is the only correct one — the pinned main checkout still has the old�[0m
 �[36;1m# name. Preferring the fetched copy keeps every other caller on the�[0m
 �[36;1m# canonical version.�[0m
 �[36;1mif [ ! -f "$SCRIPT" ] && [ -f scripts/check-workflow-duplicate-keys.sh ]; then�[0m
 �[36;1m  SCRIPT="scripts/check-workflow-duplicate-keys.sh"�[0m
 �[36;1m  echo "Using this repository's own copy (standards self-lint)."�[0m
 �[36;1mfi�[0m
 �[36;1mif [ ! -f "$SCRIPT" ]; then�[0m
 �[36;1m  echo "::error::duplicate-key checker not found — neither fetched from" \�[0m

GitHub Actions: Governance / governance _ Workflow security linter: fix(mcp-bridge): refuse undeclared arguments on every tool

Conclusion: failure

View job details

##[group]Run # GitHub Actions REJECTS a workflow with duplicate keys: the run is
 �[36;1m# GitHub Actions REJECTS a workflow with duplicate keys: the run is�[0m
 �[36;1m# `failure` with no jobs, no log and no check run. Nothing else here�[0m
 �[36;1m# can see it, because yaml.safe_load silently keeps the LAST�[0m
 �[36;1m# duplicate and reports success — so the file "parses" and every�[0m
 �[36;1m# other lint passes. Measured 2026-08-05: nine workflows in hypatia�[0m
 �[36;1m# were dead this way, including a CodeQL workflow with zero�[0m
 �[36;1m# successful runs in its entire lifetime.�[0m
 �[36;1mset -euo pipefail�[0m
 �[36;1mSCRIPT=".standards-dupkey/scripts/check-workflow-duplicate-keys.sh"�[0m
 �[36;1m# Self-hosting fallback: when THIS repository is standards, its own�[0m
 �[36;1m# working tree already holds the script, and during a rename that copy�[0m
 �[36;1m# is the only correct one — the pinned main checkout still has the old�[0m
 �[36;1m# name. Preferring the fetched copy keeps every other caller on the�[0m
 �[36;1m# canonical version.�[0m
 �[36;1mif [ ! -f "$SCRIPT" ] && [ -f scripts/check-workflow-duplicate-keys.sh ]; then�[0m
 �[36;1m  SCRIPT="scripts/check-workflow-duplicate-keys.sh"�[0m
 �[36;1m  echo "Using this repository's own copy (standards self-lint)."�[0m
 �[36;1mfi�[0m
 �[36;1mif [ ! -f "$SCRIPT" ]; then�[0m
 �[36;1m  echo "::error::duplicate-key checker not found — neither fetched from" \�[0m
🧰 Additional context used
📚 Code guidelines (1)
.github/copilot-instructions.md — auto-discovered
📓 Path-based instructions (1)
Source excerpt: SPDX: `MPL-2.0` on all new files.

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Files:

  • package.json
  • mcp-bridge/tests/routing_args_test.js
  • mcp-bridge/lib/tools.js
  • CHANGELOG.adoc
  • mcp-bridge/lib/dispatcher.js
  • mcp-bridge/tests/declared_args_test.js
🔇 Additional comments (6)
mcp-bridge/lib/tools.js (1)

596-605: LGTM!

Also applies to: 655-655, 804-804

mcp-bridge/lib/dispatcher.js (1)

58-58: LGTM!

Also applies to: 66-70, 76-76, 87-87, 91-94, 100-100, 103-103, 105-105, 142-147

CHANGELOG.adoc (1)

31-40: LGTM!

mcp-bridge/tests/declared_args_test.js (1)

1-95: LGTM!

mcp-bridge/tests/routing_args_test.js (1)

81-82: LGTM!

package.json (1)

23-23: LGTM!


📝 Summary

Summary by CodeRabbit

  • Changed
    • Tool calls now reject undeclared arguments before execution, with a clear error response.
    • Registration can include an initial role and capabilities. Send operations can include a sender role.
    • A deprecated supervisor-promotion alias is supported. Unknown tools return an error.

Walkthrough

The MCP bridge now validates arguments for all known tools against their input schemas before dispatch. The change also adds coordinator argument declarations, a deprecated tool alias, and tests for validation outcomes.

Changes

MCP Argument Validation

Layer / File(s) Summary
Coordinator argument contracts
mcp-bridge/lib/tools.js
coord_register declares optional role and capabilities arguments. coord_send and coord_send_gated declare optional sender_role arguments.
Dispatcher argument validation
mcp-bridge/lib/dispatcher.js, CHANGELOG.adoc
The dispatcher resolves coord_promote_to_supervisor through coord_promote_to_master. It rejects unknown tools with -32601 and invalid arguments with -32602 before dispatch. The changelog records these behaviours and the added arguments.
Validation tests and test command
mcp-bridge/tests/declared_args_test.js, mcp-bridge/tests/routing_args_test.js, package.json
Tests cover declared and undeclared arguments, unknown tools, and rejection before dispatch. The routing test checks the declared sender_role value. The test command includes the new validation test file.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Merge Risk: ⚪ Minimal · up to f3b48

The argument-validation change is ready to merge after normal checks; no actionable issue remains.

Security Architecture Review

Security architecture risk: 🔵 Low · up to f3b48

The change tightens accepted argument names while preserving existing routing controls. No introduced privilege escalation was established, but downstream identity-to-role enforcement remains unverified.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The contract change affects callers of every known tool through the existing transports. Coordinator role metadata reaches the configured coordinator service, while routed tools retain their existing cartridge selection. The comparison establishes no new service route or privilege grant; tenant, asset, credential, and deployment scope cannot be quantified from the available source.

Security Findings and Attack Paths

  • observed — Caller-controlled role metadata participates in envelope validation and is forwarded to the coordinator. This behavior predates the PR. Whether an attacker could use it to gain authority remains unresolved because the downstream binding between registration tokens, persisted roles, and gated-send authorization was not available; this is not a verified introduced vulnerability.

Trust Boundaries and Controls

  • observed — Declared argument names and envelope-contract validation are not authenticated-role binding. The checked-in coordinator manifest describes registration roles excluding master and separate promotion, but that fixture does not prove production enforcement. Envelope validation can also be skipped when its dependencies are unavailable unless strict enforcement is configured.

Resilience and Maintainability Implications

  • observed — The compared coordinator forwarding and bridge path-claim lifecycle are unchanged. Claim annotations remain post-success; progress refreshes claims and outcome reporting releases them. Envelope rejection precedes forwarding, and network or non-JSON failures bypass post-response bookkeeping. The new role fields do not enter these bookkeeping branches; downstream concurrency, interruption, repetition, and recovery guarantees remain unverified.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 4 files. (2 skipped: 2 …
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely describes the main change: refusing undeclared arguments on every MCP tool.
Description check ✅ Passed The description follows the repository template. It explains the purpose, changes, testing, scope, checklist status, and deferred checks. Unconfigured formatting and lint checks are clearly identified…
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

A rabbit checks each tool’s declared fare,
And sends no extra arguments there.
New roles and skills join the schema’s list,
Unknown calls meet an error they can’t resist.
Tests hop through each path with care.

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown

🏁 path-claims bench

Commit 6510ad4

Numbers
path-claims bench  (node v22.23.3)

  scenario                                              iters       ms        ns/op          ops/s
  --------------------------------------------------------------------------------------------------------------
  register: 10 active claims, 3 new paths               50000 iters    152 ms      3.05 µs/op    328.3k ops/s
  register: 100 active claims, 3 new paths              20000 iters    247 ms     12.38 µs/op     80.8k ops/s
  register: 1000 active claims, 3 new paths              5000 iters    761 ms    152.23 µs/op      6.6k ops/s
  register: 100 active claims, 20 new paths              5000 iters    292 ms     58.59 µs/op     17.1k ops/s

  pathsOverlap: deep diverge at segment 4             1000000 iters    122 ms     122.6 ns/op     8.16M ops/s
  pathsOverlap: short prefix match                    1000000 iters    105 ms     105.6 ns/op     9.47M ops/s

  refresh (existing claim)                             100000 iters      8 ms      81.4 ns/op    12.28M ops/s
  list (100 active claims)                              50000 iters    222 ms      4.44 µs/op    225.1k ops/s

  (Bench numbers depend on host; use deltas across commits, not absolute values.)

Host-dependent — compare deltas across commits, not absolute values.

@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown

🔍 Hypatia Security Scan

Findings: 111 issues detected

Severity Count
🔴 Critical 10
🟠 High 16
🟡 Medium 85

⚠️ Action Required: Critical security issues found!

View findings
[
  {
    "reason": "Job `sonarqube` in build.yml has no `timeout-minutes:` declaration. Default is 6 hours — a stuck codeload fetch or runner hang can burn budget. Add `timeout-minutes: 10` (or proportional).",
    "type": "missing_timeout_minutes",
    "file": ".github/workflows/build.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium",
    "recipe_id": "recipe-add-workflow-timeout-minutes",
    "job": "sonarqube"
  },
  {
    "reason": "Job `triage` in label-triage.yml has no `timeout-minutes:` declaration. Default is 6 hours — a stuck codeload fetch or runner hang can burn budget. Add `timeout-minutes: 10` (or proportional).",
    "type": "missing_timeout_minutes",
    "file": ".github/workflows/label-triage.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium",
    "recipe_id": "recipe-add-workflow-timeout-minutes",
    "job": "triage"
  },
  {
    "reason": "Job `sync` in labels.yml has no `timeout-minutes:` declaration. Default is 6 hours — a stuck codeload fetch or runner hang can burn budget. Add `timeout-minutes: 10` (or proportional).",
    "type": "missing_timeout_minutes",
    "file": ".github/workflows/labels.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium",
    "recipe_id": "recipe-add-workflow-timeout-minutes",
    "job": "sync"
  },
  {
    "reason": "Job `deploy` in pages-deploy.yml has no `timeout-minutes:` declaration. Default is 6 hours — a stuck codeload fetch or runner hang can burn budget. Add `timeout-minutes: 10` (or proportional).",
    "type": "missing_timeout_minutes",
    "file": ".github/workflows/pages-deploy.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium",
    "recipe_id": "recipe-add-workflow-timeout-minutes",
    "job": "deploy"
  },
  {
    "reason": "Step uses `peter-evans/repository-dispatch` with `token: ${{ secrets.FARM_DISPATCH_TOKEN }}` but has no `if: secrets.FARM_DISPATCH_TOKEN != ''` gate. On repos where the secret hasn't been propagated the action fails on every push, red-maining the repo. Add the step-level gate (or env+if pattern) so the missing-secret path is a clean skip instead of a red.",
    "type": "secret_action_without_presence_gate",
    "file": ".github/workflows/instant-sync.yml",
    "action": "peter-evans/repository-dispatch",
    "rule_module": "workflow_audit",
    "severity": "high",
    "fix_recipe": "add_secret_presence_gate"
  },
  {
    "reason": "codeql.yml does not list `language: actions` in its matrix, but the repo has workflow files. CodeQL's `actions` language scans workflow YAML for injection and other CI/CD-specific weaknesses — every repo with workflows benefits. Add an entry to `matrix.include` with `language: actions` + `build-mode: none`.",
    "type": "codeql_missing_actions_language",
    "file": ".github/workflows/codeql.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium",
    "fix_recipe": "add_codeql_actions_language"
  },
  {
    "line": 39,
    "reason": "job in .github/workflows/labels.yml references `secrets.*` but does not install `step-security/harden-runner` — review outbound-egress monitoring",
    "type": "RE001",
    "file": ".github/workflows/labels.yml",
    "action": "report",
    "rule_module": "research_extensions",
    "severity": "medium"
  },
  {
    "line": 46,
    "reason": "job in .github/workflows/push-email-notify.yml references `secrets.*` but does not install `step-security/harden-runner` — review outbound-egress monitoring",
    "type": "RE001",
    "file": ".github/workflows/push-email-notify.yml",
    "action": "report",
    "rule_module": "research_extensions",
    "severity": "medium"
  },
  {
    "line": 32,
    "reason": "job in .github/workflows/build.yml references `secrets.*` but does not install `step-security/harden-runner` — review outbound-egress monitoring",
    "type": "RE001",
    "file": ".github/workflows/build.yml",
    "action": "report",
    "rule_module": "research_extensions",
    "severity": "medium"
  },
  {
    "line": 44,
    "reason": "job in .github/workflows/container-publish.yml references `secrets.*` but does not install `step-security/harden-runner` — review outbound-egress monitoring",
    "type": "RE001",
    "file": ".github/workflows/container-publish.yml",
    "action": "report",
    "rule_module": "research_extensions",
    "severity": "medium"
  }
]

Powered by Hypatia Neurosymbolic CI/CD Intelligence

@hyperpolymath
hyperpolymath merged commit 68c9e96 into main Oct 8, 2026
59 of 61 checks passed
@hyperpolymath
hyperpolymath deleted the fix/enforce-input-schema branch October 8, 2026 06:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant