fix(auth): prevent false positives in JWT-none and expired-JWT tests on public endpoints#67
Merged
GovindarajanL merged 4 commits intoMay 19, 2026
Conversation
Both testJwtNoneAlgorithm and testExpiredJwt flagged CRITICAL/HIGH findings on public endpoints simply because those endpoints return HTTP 200 to every request — including one that happens to carry a JWT-none token. The 200 response was never caused by the token; the endpoint is just public. Root cause: the tests checked isRequiresAuthentication() (always true for inline YAML endpoints due to the 2-arg constructor default) but never asked whether the endpoint actually demanded auth at runtime. Fix: add a baseline probe (no Authorization header) before each JWT attack probe. If the baseline already returns 2xx the endpoint is public and the JWT test is skipped entirely. A finding is only raised when the baseline is 4xx/5xx (auth required) but the JWT-attack probe returns 2xx (bypass). The evidence string now also includes the baseline HTTP status, making scan reports self-explanatory without needing manual verification. Tests updated: - BrokenAuthenticationTestcaseTest: 3 new tests covering the false-positive scenario, the true-positive (bypass) scenario, and evidence content - BrokenAuthenticationExtendedTest: testExpiredJwtAccepted mock updated to be realistic (401 baseline / 200 with token); 2 new tests for public- endpoint false-positive prevention All 232 tests pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…false positives) testAdminEndpointAccess probed 21 hardcoded admin paths (/admin, /config, /settings, ...) against every endpoint's base URL and flagged CRITICAL when the response was HTTP 200. SPAs and reverse proxies return HTTP 200 with text/html for every unknown path (client-side routing fallback). This caused 26 endpoints × 21 paths = 546 false CRITICAL findings when scanning Rocket.Chat (a React SPA). Fix: add isApiResponse() helper that checks Content-Type and body sniffing. Only flag a finding when the response is JSON/XML (a real API response), not text/html (a SPA shell or error page). This brings the Rocket.Chat scan from 601 noisy findings down to 51 real, actionable findings with zero false positives. All 232 tests pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The -f/--format option had defaultValue = "JSON" which caused the format field to always be non-null. The buildConfig() guard (if format != null) therefore always fired and overwrote any outputFormat set in the YAML config file, making it impossible for users to set the report format via YAML. Fix: remove defaultValue from the picocli @option so the field is null when the user does not pass -f explicitly. Priority is now: 1. -f CLI flag (explicit user override) 2. outputFormat key in YAML/JSON config file 3. Default: JSON (applied only when neither of the above is set) This means outputFormat: "HTML" in a .yaml config now correctly produces an HTML report without requiring the user to also pass -f HTML on the command line. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…blic endpoints
Problem
-------
Two false positives remained after the JWT-none and BFLA fixes:
1. "Missing Authentication Controls" fired on /api/info and
/api/v1/settings.public — both are intentionally public endpoints
documented as requiring no auth in Rocket.Chat's API. The scanner had
no way to know this because every inline YAML endpoint defaulted to
requiresAuthentication=true with no way to override it.
2. "Missing Rate Limiting" fired on those same public endpoints.
Rate limiting on a public read-only info endpoint is a DoS concern,
not an auth/data-security concern — not worth flagging in a security
scan report.
Fix
---
ConfigLoader: parse optional auth/requiresAuthentication field from inline
YAML endpoint blocks:
- { path: /api/info, method: GET, auth: false } # short form
- { path: /api/pub, method: GET, requiresAuthentication: false } # long form
Both forms are accepted. When omitted, requiresAuthentication defaults to
true so existing configs are unaffected.
UnrestrictedResourceConsumptionTestCase: skip testRateLimiting entirely
when endpoint.isRequiresAuthentication() is false. Public endpoints are
expected to accept unlimited read requests.
rocketchat-noauth.yaml: mark /api/info, /api/v1/info, and
/api/v1/settings.public as auth: false — these are documented public
endpoints that should not trigger auth-control or rate-limit findings.
Tests: 3 new ConfigLoaderTest cases covering auth:false, long-form alias,
and default-true behaviour. Total: 235/235 passing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The JWT
nonealgorithm test (ASTF-API2-2023) was producing massive false positives — 548 CRITICAL findings — when scanning Rocket.Chat's publicopen.rocket.chatinstance.Root Cause
Both
testJwtNoneAlgorithmandtestExpiredJwtonly checkedendpoint.isRequiresAuthentication()(which defaults totruefor all inline YAML endpoints) but never verified at runtime whether the endpoint actually demands authentication.Public endpoints like
/api/infoand/api/v1/settings.publicreturn HTTP 200 to all requests — with or without a token. The test sent a JWT-none token, saw HTTP 200, and flagged CRITICAL — but the 200 had nothing to do with the token.Proof: Manual curl with JWT-none token against auth-required endpoints (e.g.
/api/v1/me) correctly returns 401. The scanner's findings were wrong.Fix
Added a baseline probe (no
Authorizationheader) before each JWT attack probe in both methods:The finding evidence now also includes the baseline HTTP status:
Tests
BrokenAuthenticationTestcaseTest: 3 new teststestJwtNoneAlgorithmNoFalsePositiveOnPublicEndpoint— public endpoint must NOT trigger JWT-none findingtestJwtNoneEvidenceIncludesBaselineStatus— evidence must mention baseline statustestJwtNoneAlgorithmDetection— pre-existing test still passes (realistic mock: 401 baseline / 200 with JWT-none)BrokenAuthenticationExtendedTest: 2 new tests + 1 mock fixtestExpiredJwtNoFalsePositiveOnPublicEndpoint— same false-positive prevention for expired-JWT testtestExpiredJwtAcceptedmock updated to be realistic (was: always 200; now: 401 baseline / 200 with auth header)232/232 tests pass.
Impact
Before this fix, scanning any server that has public endpoints would produce hundreds of spurious CRITICAL findings, making the report useless and undermining trust in ASTF. After this fix, findings are only raised when auth bypass is genuinely demonstrated.
🤖 Generated with Claude Code