Skip to content

fix(scim): harden Phase 7 SCIM after security/pitfall/plan review#56

Merged
scarson merged 5 commits intodevfrom
fix/scim-review-hardening
Mar 20, 2026
Merged

fix(scim): harden Phase 7 SCIM after security/pitfall/plan review#56
scarson merged 5 commits intodevfrom
fix/scim-review-hardening

Conversation

@scarson
Copy link
Copy Markdown
Owner

@scarson scarson commented Mar 20, 2026

Summary

Post-implementation review of Phase 7 SCIM provisioning surfaced 6 actionable findings across security review, pitfall check, and plan compliance check. This PR addresses all of them, plus follow-up issues found during dead code review.

Original findings

  • S1 (Medium): SCIM group store methods switched from withBypassTx to withOrgTx with AND org_id in all SQL queries — defense-in-depth tenant isolation
  • S2 (Medium): X-Forwarded-Proto validated (only http/https) via shared scimScheme() helper; fixed inconsistency between user and group URL builders
  • S5 (Low): N+1 identity lookup in list users replaced with batch ListIdentitiesByProviderAndUsers using ANY($2::uuid[])
  • P2 (Plan gap): SCIM audit actor_id now nil (SQL NULL) instead of uuid.Nil, matching design doc
  • P3 (Plan gap): Unsupported group filter attributes now log slog.Warn instead of silently filtering
  • P4 (Plan gap): Entry-level slog.InfoContext with org_id/scim_config_id added to all 11 SCIM handlers

Follow-up findings from dead code review

  • GetGroupIfActive refactored with same withOrgTx + AND org_id pattern
  • RemoveSCIMManagedGroupMember was missing orgID entirely — SQL had no AND org_id and used withBypassTx (bypassed RLS). Fixed.
  • AddGroupMemberSCIMManaged switched from withBypassTx to withOrgTx
  • Redundant manual group.OrgID != orgID checks removed (dead code after SQL changes)

Review context

Full review ran: /pitfall-check (clean), /security-review (1H→Info, 2M, 3L), /plan-check against design doc (4 gaps). Pitfall check found 0 issues. The "High" (SHA-256 vs argon2id) was downgraded — 256-bit entropy makes SHA-256 correct for bearer tokens.

Test plan

  • internal/auth SCIM tests: 3 PASS
  • internal/store SCIM tests: 21 PASS (testcontainers)
  • internal/api SCIM tests: 55+ PASS (e2e with testcontainers)
  • golangci-lint run: 0 issues
  • 3-round review loop with fixes between rounds
  • Dead code scan for remaining withBypassTx on org-scoped tables

🤖 Generated with Claude Code

scarson and others added 5 commits March 19, 2026 22:54
Post-implementation review of Phase 7 SCIM found 6 actionable issues.
This commit addresses all of them:

S1: SCIM group store methods now accept orgID and use withOrgTx instead
    of withBypassTx, adding AND org_id to all SQL queries for defense-
    in-depth tenant isolation. All callers and tests updated.

S2: Extract shared scimScheme() helper that validates X-Forwarded-Proto
    (only "http"/"https" accepted). Fixes inconsistency between
    scimUserLocation and scimBaseURL.

S5: Replace N+1 per-member GetIdentityByProviderAndUser in list users
    with batch ListIdentitiesByProviderAndUsers using ANY($2::uuid[]).

P2: SCIM audit log actor_id now nil (SQL NULL) instead of uuid.Nil
    (all-zeros UUID), matching design doc and enabling IS NULL queries.

P3: matchesSCIMGroupFilter now logs slog.Warn for unsupported filter
    attributes instead of silently returning false.

P4: Entry-level slog.InfoContext with org_id/scim_config_id added to
    all 11 SCIM handlers for consistent observability.

All SCIM tests passing (auth: 3, store: 21, api: 55+). Lint clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
GetSCIMGroup now queries with AND org_id, so groups from other orgs
return nil. The nil check already handles this — the manual
group.OrgID != orgID comparisons were dead code.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Same pattern as the SCIM group store refactor: withBypassTx → withOrgTx,
AND org_id added to SQL query. Removes redundant manual group.OrgID
check in patchSCIMGroupMappingHandler (now dead code — query returns
nil for cross-org groups).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The MSRC adapter's /cvrf/v3.0/csaf/{id} endpoint never existed on
Microsoft's API. Plan covers switching to the real CSAF 2.0 static
file distribution at msrc.microsoft.com/csaf/advisories/, capturing
golden fixtures, writing EPSS/MSRC golden tests, adding MSRC to
SeedCorpus, and completing Phase 10 verification.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
RemoveSCIMManagedGroupMember was missing orgID entirely — SQL had no
AND org_id clause and used withBypassTx, bypassing RLS. Added orgID
parameter and AND org_id = $3 to the DELETE query.

AddGroupMemberSCIMManaged already had orgID in the INSERT but used
withBypassTx. Switched to withOrgTx for dual-layer isolation.

Both methods now follow the same withOrgTx + AND org_id pattern as
all other org-scoped store methods.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@scarson scarson merged commit 3f074cb into dev Mar 20, 2026
@scarson scarson deleted the fix/scim-review-hardening branch March 20, 2026 04:24
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