Skip to content

Harden release floor, validate-first tools, and list pagination#23

Merged
secure-ssid merged 4 commits into
mainfrom
claude/release-validator-min-tools-cgj0k9
Jul 2, 2026
Merged

Harden release floor, validate-first tools, and list pagination#23
secure-ssid merged 4 commits into
mainfrom
claude/release-validator-min-tools-cgj0k9

Conversation

@secure-ssid

@secure-ssid secure-ssid commented Jul 2, 2026

Copy link
Copy Markdown
Owner

Summary

Four related hardening fixes, each with regression tests:

1. Release floor can no longer be disabled (scripts/validate_release.py)

  • --min-tools accepted 0/negatives, silently turning the tool-catalog floor into a no-op. A _positive_int argparse type now rejects non-integer and < 1 values with exit code 2 (error: argument --min-tools: expected a positive integer, got '0').
  • _build_parser() extracted from main() so CLI parsing is unit-testable.

2. Validate-first ordering (mcp_servers/nac.py, ops.py, config.py)

  • test_aaa built the Central client (triggering a live OAuth token refresh) before validating required args; invalid calls now return early with no side effects. This also made the release gate's unit-test step pass hermetically in credential-less environments.
  • Same reorder applied to the other tools with the pattern: poe_bounce, port_bounce, cable_test, reboot_device, create_webhook. Clients are now built only after validation — and after user confirmation for destructive ops.

3. Double-applied pagination offsets (config.py, monitoring.py)

  • Server-paged list tools re-sliced the returned page with the same offset, so any offset>0 page came back empty with a false truncated: False: passpoint profile/identity lists (_config_list_response), list_config_templates, and list_sites (under CENTRALMCP_BOUND_LISTS). They now slice from 0 and report the true offset, matching the existing list_devices pattern.

4. Bounding-contract violations (config.py, tool_router.py)

  • list_config_assignments and list_firmware_upgrades returned raw unbounded responses; both now take limit/offset (plus full_list for assignments) via bound_collection_response.
  • Router wrappers list_sites/list_devices advertised a full_list parameter the backend tools don't have — FastMCP silently dropped it, returning a truncated page with no marker. Removed.
  • Router list_scopes wrapper exposed no paging, so orgs with >100 scopes couldn't page past the first 100; it now forwards limit/offset/full_list.

No callers depended on the old behavior: nothing in the repo passes --min-tools, and the phantom full_list never worked.

Checklist

  • I used fake hosts, fake IDs, and redacted payloads in examples, tests, logs, and screenshots.
  • I did not commit real credentials, tokens, tenant IDs, customer data, generated indexes, or local MCP config files.
  • I updated matching docs when behavior, setup, environment variables, public tool counts, or GitHub Pages content changed. (No docs reference the changed flags/parameters; public tool counts unchanged at 346 — enforced by test_public_docs_tool_counts_match_catalog.)
  • I kept user-facing examples on the low-token router path (find_tool, invoke_read_tool, invoke_tool) where possible. (Not applicable — no user-facing examples changed.)
  • I ran targeted tests for the changed behavior (validator, NAC AAA, ops validate-first, webhook, and new test_list_output_bounding.py suites).
  • I ran uv run python scripts/validate_release.py --skip-rag — 684 unit tests pass, RAG eval auto-skipped (local indexes absent), tool catalog floor satisfied (346 ≥ 204), exit 0.

Security

For exploitable details or accidental credential exposure, do not use this pull request. Follow SECURITY.md.

Contributor notes

See CONTRIBUTING.md for local setup, validation, and no-secret contribution guidance.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AoFhWvujADVzy5GJq4aQ53

claude added 4 commits July 2, 2026 04:55
A zero or negative --min-tools value made the release tool-catalog
floor a no-op. Parse the flag with a positive-integer argparse type,
extract _build_parser() so CLI parsing is testable, and cover the
parser and CLI accept/reject paths in unit tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AoFhWvujADVzy5GJq4aQ53
test_aaa constructed the Central client (triggering a live OAuth token
refresh) before checking its required arguments, so invalid calls hit
the network and the two invalid-input unit tests failed in any
environment without credentials. Move get_client() below the
validation branches so bad input returns early with no side effects,
making `validate_release.py --skip-rag` pass hermetically.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AoFhWvujADVzy5GJq4aQ53
Apply the same validate-first ordering as test_aaa to the remaining
tools that constructed the Central client before rejecting invalid
input: poe_bounce, port_bounce, cable_test, reboot_device, and
create_webhook. Clients are now built only after validation (and after
user confirmation for destructive ops), so rejected or declined calls
have no credential/token side effects. Add regression tests for each
rejected-input path asserting get_client is never called.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AoFhWvujADVzy5GJq4aQ53
Server-paged list tools re-sliced the returned page with the same
offset, so any offset>0 page came back empty or wrong with a false
truncated=False: passpoint profile/identity lists via
_config_list_response, list_config_templates, and monitoring
list_sites (under CENTRALMCP_BOUND_LISTS). They now slice from 0 and
report the true offset, matching the existing list_devices pattern.

Bound the two list tools that returned raw unbounded responses:
list_config_assignments and list_firmware_upgrades gain limit/offset
(and full_list for assignments) via bound_collection_response.

Fix router convenience wrappers: list_sites/list_devices advertised a
full_list parameter the backend tools don't have — FastMCP silently
dropped it, returning a truncated page with no marker; the parameter
is removed. list_scopes gains limit/offset/full_list forwarding so
orgs with more scopes than one page can page past it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AoFhWvujADVzy5GJq4aQ53
@secure-ssid secure-ssid changed the title Reject non-positive --min-tools release floors Harden release floor, validate-first tools, and list pagination Jul 2, 2026
@secure-ssid secure-ssid marked this pull request as ready for review July 2, 2026 13:38
@secure-ssid secure-ssid merged commit 4affdfa into main Jul 2, 2026
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.

2 participants