Skip to content

fix: --exit-code no longer fails a passing scan on the INFO URL_SCAN_LIMITED note#36

Open
dmchaledev wants to merge 1 commit into
mainfrom
claude/amazing-franklin-6svzd1
Open

fix: --exit-code no longer fails a passing scan on the INFO URL_SCAN_LIMITED note#36
dmchaledev wants to merge 1 commit into
mainfrom
claude/amazing-franklin-6svzd1

Conversation

@dmchaledev

Copy link
Copy Markdown
Contributor

Summary

Scanning a secure https:// / wss:// endpoint with --exit-code prints PASSED but exits 1 — a false CI-gate failure on the advertised URL-scan path, and a regression of the intent behind the just-landed #27 fix (make secure endpoints pass).

The cause is in src/cli.ts. The exit decision counted every entry in report.findings:

const shouldFail =
  !report.passed ||
  (exitCode && report.findings.length > 0);

In URL mode a secure endpoint produces exactly one finding — the INFO URL_SCAN_LIMITED note. That note is documented as "Not a vulnerability — INFO severity, never fails a gate" (src/types.ts), and the report correctly sets passed: true (score 0, no critical). But report.findings.length > 0 is true because of the note, so --exit-code exits 1 anyway.

Repro (built CLI, before fix)

$ node dist/cli.js https://secure-mcp.example.com --format=table --exit-code
INFO   | URL_SCAN_LIMITED | URL Scan — Transport Checks Only
Found 1 finding(s): 0 critical, 0 high, 0 medium, 0 low
PASSED
$ echo $?
1            # ← PASSED, yet exit 1

Fix

--exit-code now fails only on actionable findings (severity above INFO); INFO notes never fail the gate. The decision was extracted into a pure, side-effect-free shouldFailExit() helper in src/exit-policy.ts — mirroring the existing args.ts / cli.ts split — so it is unit-testable without process.exit.

export function shouldFailExit(report: SecurityReport, exitCode: boolean): boolean {
  if (!report.passed) return true;
  if (!exitCode) return false;
  return report.findings.some((f) => f.severity !== Severity.INFO);
}

Behavior after fix (verified against built CLI)

Scan --exit-code Exit before Exit after
secure https:// URL yes 1 0
secure wss:// URL yes 1 0
http:// URL (real MISSING_TLS) yes 1 1
vulnerable config yes 1 1
secure config no 0 0

INFO-plus-real-finding still fails (the real finding drives the exit), so the change is scoped strictly to the "only an INFO note" case.

Tests

  • New src/__tests__/exit-policy.test.ts covering: not-passed always fails; clean report without --exit-code passes; actionable finding + --exit-code fails; the secure-URL INFO-only case does not fail; INFO + real finding still fails.
  • Full suite green: 123 passed. tsc --noEmit, eslint --max-warnings 0, and npm test all pass.

Files changed

  • src/exit-policy.ts — new pure shouldFailExit() helper
  • src/cli.ts — use the helper instead of inline findings.length > 0
  • src/__tests__/exit-policy.test.ts — new tests
  • CHANGELOG.md — note under Unreleased → Fixed

🤖 Generated with Claude Code

https://claude.ai/code/session_01ELkzDvarG6hoXSsuKdz5SR


Generated by Claude Code

Scanning a secure https://wss:// endpoint with --exit-code printed PASSED
but exited 1: the run counted the informational URL_SCAN_LIMITED note
(INFO severity) as a finding via `report.findings.length > 0`. This broke
the documented CI gate on the advertised URL-scan path and contradicted
the #27 fix that made secure endpoints pass.

--exit-code now fails only on actionable findings (severity above INFO);
INFO notes never fail the gate. The exit-code decision is extracted into a
pure, side-effect-free shouldFailExit() helper (mirroring the args.ts /
cli.ts split) with focused unit tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ELkzDvarG6hoXSsuKdz5SR
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