fix(auth): require auth on /saml/providers/{idp}/users and complete response examples - #654
Merged
Conversation
…esponse examples
Triage and remediation of the 2026-07-30 CATS baseline (20260730T220551Z,
542 true positives).
GET /saml/providers/{idp}/users returned 401 on 69 of 69 requests and had
never worked. cmd/server/main.go listed "/saml/" in publicPathPrefixes, so
PublicPathsMiddleware marked every SAML path public, the JWT middleware
skipped validation, and ListSAMLUsers' GetAuthenticatedUser found no user in
context. The handler failed closed, so this was an availability defect rather
than an exposure -- but the blanket prefix meant any future authenticated
/saml/ route would silently lose its JWT context too. The spec marks exactly
one /saml/* operation as requiring bearerAuth and the middleware contradicted
it. Replaced with the three exact public paths plus isPublicSAMLPath for the
two provider-scoped routes, which have a variable segment and so cannot be a
prefix. Matching exactly three segments keeps new /saml/ routes private until
added deliberately. Verified live: 200 with users where it was 401, 401 when
anonymous, and 403 cross-provider -- the handler's second security check is
reachable for the first time.
Completed 23 response examples. CATS validates a 2xx body against the declared
`example` rather than the `schema` (Endava/cats#206): when an example is
present the response's property names must be a subset of it, and the schema
is never consulted. This accounted for 428 of the 542 findings. Rather than
suppress, the examples that genuinely omitted fields the server returns are
now complete, so the findings clear legitimately and real schema regressions
still surface. Example structure comes from observed responses -- a project's
`team` is documented as the {id, name} summary it actually is -- and values
from schema examples, so no fixture data enters published docs. 422 of the 428
now have covering examples; the remainder is MinimalDiagramModel.metadata, an
additionalProperties map whose keys are unbounded and which therefore cannot
satisfy the check from the spec side.
RepositoryBulkUpdateItem now requires `name`, which the bulk-update handler
has always enforced while no schema declared it. GET .../collaborate is marked
x-skip-deleted-resource-check: sessions are not CRUD resources, so a 200 after
a DELETE is correct rather than a stale-resource leak. charlie is added to the
seeded CATS Test Group because /me/groups/{group_id}/members answers from the
caller's own membership; both routes are read-only so the campaign cannot undo
it.
Together these take the baseline from 542 findings to roughly 85, with the
residue understood rather than unexplained.
Fixes #650
Refs #637, #651, #652, #653, Endava/cats#206
The marker's sha is the commit its body last changed at, which could not be known until 2cab847 existed.
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.
Triage and remediation of the 2026-07-30 CATS baseline (
20260730T220551Z, 542 true positives).Takes it to roughly 85, with the residue understood rather than unexplained.
/saml/providers/{idp}/users)/me/groups/{id}/members)CheckDeletedResourcesNotAvailable)Real bug:
GET /saml/providers/{idp}/usersnever worked (#650)Returned 401 on 69 of 69 requests.
cmd/server/main.golisted"/saml/"inpublicPathPrefixes, soPublicPathsMiddlewaremarked every SAML path public, the JWT middlewareskipped validation, and
ListSAMLUsers'GetAuthenticatedUserfound no user in context. Thehandler failed closed, making this an availability defect rather than an exposure — but the blanket
prefix meant any future authenticated
/saml/route would silently lose its JWT context too. Thespec marks exactly one
/saml/*operation as requiringbearerAuth, and the middlewarecontradicted it.
Replaced with the three exact public paths plus
isPublicSAMLPathfor the two provider-scopedroutes, which have a variable segment and so cannot be expressed as a prefix. Matching exactly
three segments keeps any new
/saml/route private until added deliberately. This strictlynarrows the set of public paths.
Verified live against k3s:
/saml/providers/tmi/users+ valid token/saml/providers/tmi/usersanonymous/saml/providers/okta/users+ valid tokenCATS validates responses against
example, notschema(#637 → Endava/cats#206)Root-caused the 428 findings that were 79% of the backlog. When a response declares an
example,CATS requires the body's property names to be a subset of it and never consults the schema —
replacing the schema with
{"type":"object"}or eventruestill fails. Reproduced standalone(1 path, a 20-line stub); deleting the
examplealone flips fail → pass. Validated across the wholerun: 428 flagged / 0 with a covering example, zero counterexamples.
This explains the
Note-vs-TeamNotediscriminator that stalled two sessions: schemas with noexample never run the check, exhaustive ones pass, partial ones fail 100% of the time.
Remediation is not suppression — the 23 genuinely incomplete examples are now complete, so the
findings clear legitimately and real schema regressions still surface. Structure comes from observed
responses (a project's
teamis documented as the{id, name}summary it actually is, not afully-populated Team) and values from schema examples, so no fixture data enters published docs.
422/428 now covered; the rest is
MinimalDiagramModel.metadata, anadditionalPropertiesmap whosekeys are unbounded and which therefore cannot satisfy the check from the spec side.
Smaller fixes
RepositoryBulkUpdateItemnow requiresname, whichrepository_sub_resource_handlers.go:592has always enforced while no schema declared it.
GET .../collaboratemarkedx-skip-deleted-resource-check— sessions are not CRUD resources, soa 200 after a DELETE is correct, not a stale-resource leak.
charlieadded to the seededCATS Test Group:/me/groups/{group_id}/membersanswers from thecaller's own membership. Both routes are read-only, so the campaign cannot undo it.
Verification
make validate-openapi— 0 errors, all 328 operations annotatedmake generate-api(oapi-codegen v2.7.1) — only change beyond the embedded spec isRepositoryBulkUpdateItem.Namelosingomitempty; stays*string, handler nil-check unaffectedmake build-server,make lint— 0 issuesmake test-unit— 2451 passed, 0 failedmake test-integration— 82 passed, 0 failed, 9 skipped/security-review— no HIGH or MEDIUM findings.isPublicSAMLPathfails closed on traversal,empty provider, wrong case and trailing slash. No fixture or PII strings entered the spec.
cmd/server/public_paths_test.go— 17 path cases guarding fix(auth): GET /saml/providers/{idp}/users always returns 401 - blanket /saml/ public prefix starves it of JWT context #650TestIdentityLinkfailed once mid-session and was chased down: a pre-existing flake (#653), nota regression. Both the clean base and this branch pass 82/82.
Follow-ups filed
PATCH/PUTfor
/threat_models/{id}across the entire run, so no fuzzer ever exercised a successful threatmodel update. Affects all 8 decoyed anchors.
chat/sessionsreturns HTTP 200 with an SSEevent: errorwhen creation is refusedbefore streaming.
TestIdentityLinkflake (UnixNano() % 10000suffix collisions).HANDOFF.mdcarries the full triage, including the conclusions for the ~80 findings confirmed ascorrect server behavior so they are not re-derived next session.
Fixes #650
🤖 Generated with Claude Code
https://claude.ai/code/session_01WnrVyySuyJFqcMxB8q3pNc