Live SCIM server conformance testing and payload validation (RFC 7643/7644)
Project description
scim-sanity
Test your SCIM server's RFC 7643/7644 conformance and validate payloads before they hit production — from the command line or a browser-based GUI built with React and Cloudscape.
Features
scim-sanity is a pragmatic, production-oriented SCIM conformance and interoperability harness:
- Server conformance probe — CRUD lifecycle tests against a live SCIM endpoint covering discovery, User/Group/Agent/AgenticApplication operations, search, pagination, and error handling. Failures produce a prioritised Fix Summary — each issue with a trouble description, fix, and rationale.
- Payload validation — Static SCIM JSON analysis before sending data to a server. Catches missing required attributes, immutable field violations, null value misuse, and schema URN errors.
- Example payload library — Curated valid and invalid SCIM resources with RFC citations, loadable directly into the validator.
- Web GUI — Browser-based interface built with React and Cloudscape Design System. Validate, probe, and browse examples without touching the terminal.
- Agentic identity support — Agent and AgenticApplication resources per IETF
draft-abbey-scim-agent-extension-00. - Strict and compat modes — Strict mode (default) treats all spec deviations as failures. Compat mode downgrades known real-world deviations (e.g.,
application/jsoninstead ofapplication/scim+json) to warnings. - Minimal dependencies — Core CLI requires only Click. The web GUI is opt-in via
pip install scim-sanity[web].
Installation
pip install scim-sanity
With the optional web GUI:
pip install 'scim-sanity[web]'
Or from source:
git clone https://github.com/thomaselliottbetz/scim-sanity.git
cd scim-sanity
python -m venv venv
source venv/bin/activate
pip install -e ".[web,dev]"
Web GUI
scim-sanity includes an optional browser-based interface built with React and the AWS Cloudscape Design System. The CLI remains the primary interface and is unchanged.
Installation
pip install 'scim-sanity[web]'
Usage
scim-sanity web
Then open http://127.0.0.1:8000 in your browser. Options:
scim-sanity web --port 8080 --host 0.0.0.0
Pages
| Page | Path | Description |
|---|---|---|
| Validate | /validate |
Paste or load a SCIM JSON payload and validate it against RFC 7643/7644 rules. Supports full resources and PATCH operations. Load any example from the built-in library. |
| Probe | /probe |
Configure and run a live server conformance probe. Results are grouped by test phase with status indicators and a prioritised Fix Summary when failures are present. |
| Examples | /examples |
Browse 16 curated RFC example payloads. Filter by resource type (User, Group, Agent, AgenticApplication, PATCH) or validity. Load any example directly into the Validate page. |
Server Conformance Probe
Test a live SCIM server for RFC 7643/7644 conformance. The probe creates, modifies, and deletes real resources on the target server, then cleans up after itself.
⚠️ Warning: This tool performs destructive operations. Do not run against production tenants without explicit authorization.
# Basic probe with bearer token
scim-sanity probe https://example.com/scim/v2 --token <token> --i-accept-side-effects
# Basic auth
scim-sanity probe https://example.com/scim/v2 --username admin --password secret --i-accept-side-effects
# Compat mode (known deviations become warnings, not failures)
scim-sanity probe <url> --token <token> --compat --i-accept-side-effects
# JSON output for CI/CD
scim-sanity probe <url> --token <token> --json-output --i-accept-side-effects
# Test only a specific resource type
scim-sanity probe <url> --token <token> --resource Agent --i-accept-side-effects
# Self-signed certificates
scim-sanity probe <url> --token <token> --tls-no-verify --i-accept-side-effects
# Leave test resources on the server for inspection
scim-sanity probe <url> --token <token> --skip-cleanup --i-accept-side-effects
# Custom timeout and proxy
scim-sanity probe <url> --token <token> --timeout 60 --proxy http://proxy:8080 --i-accept-side-effects
# Custom CA bundle
scim-sanity probe <url> --token <token> --ca-bundle /path/to/ca-cert.pem --i-accept-side-effects
Probe Options
| Option | Description |
|---|---|
--token |
Bearer token for authentication |
--username / --password |
Basic auth credentials |
--i-accept-side-effects |
Required. Acknowledge that the probe creates/deletes resources |
--strict / --compat |
Strict (default) or compat validation mode |
--json-output |
Output results as JSON |
--resource |
Test a specific resource type (User, Group, Agent, AgenticApplication) |
--skip-cleanup |
Leave test resources on the server |
--tls-no-verify |
Skip TLS certificate verification |
--timeout |
Per-request timeout in seconds (default: 30) |
--proxy |
HTTP/HTTPS proxy URL |
--ca-bundle |
Path to custom CA certificate bundle |
Safety Guardrails
The probe implements several safety measures to prevent accidental damage:
- Explicit consent — Refuses to run without
--i-accept-side-effects. - Namespace isolation — All test resources are prefixed with
scim-sanity-test-to avoid collisions with real data. - Resource caps — Hard limit of 10 agents in rapid lifecycle tests.
- 429 retry — Automatically retries on 429 Too Many Requests, honoring
Retry-Afterheaders (max 3 retries). - 500 transience detection — When a POST returns 500, the probe retries once after a brief delay using the same request headers. If the retry succeeds, the result is recorded as a warning ("transient instability") and the CRUD lifecycle continues with the resource created by the retry. If both attempts fail, content-type rejection diagnosis runs before reporting the final failure.
- Timeouts — Per-request timeouts prevent hung runs.
- Cleanup — Deletes all created test resources in reverse order (groups before users). Skippable with
--skip-cleanup. - Failure semantics — If the process is interrupted, partial cleanup may occur; orphaned test resources are possible and should be removed manually.
- Secret redaction — Authorization headers are redacted in any JSON output or logs.
Test Sequence
The probe runs 7 phases. Each phase tests specific RFC clauses against real HTTP traffic — no mocking.
-
Discovery (RFC 7644 §4)
- GET
/ServiceProviderConfig,/Schemas,/ResourceTypes - Asserts: HTTP 200,
Content-Type: application/scim+json, parseable JSON body - A server that omits these endpoints forces clients to hardcode assumptions about server capabilities
- GET
-
User CRUD Lifecycle (RFC 7644 §3.3, §3.4.1, §3.5.1, §3.6; RFC 7643 §4.1)
- POST → asserts 201,
Content-Type: application/scim+json,Locationheader,id,meta.created,meta.lastModified - GET by id → asserts 200, same Content-Type and meta fields
- PUT → asserts 200, same Content-Type and meta fields
- GET after PUT → asserts the updated field value persisted
- PATCH
active=false→ asserts 200 or 204 - GET after PATCH → asserts
activeisfalse - DELETE → asserts 204 No Content (RFC 7644 §3.6)
- GET after DELETE → asserts 404
- POST → asserts 201,
-
Group CRUD Lifecycle (RFC 7644 §3.3; RFC 7643 §4.2)
- Same sequence as User
- Additional PATCH: add a member, then remove all members — asserts 200 each
-
Agent CRUD Lifecycle (draft-abbey-scim-agent-extension-00)
- Same sequence as User
- Skipped if server does not advertise Agent support in
/ResourceTypes
-
AgenticApplication CRUD Lifecycle (draft-abbey-scim-agent-extension-00)
- Same sequence as User
- Skipped if server does not advertise AgenticApplication support
5a. Agent Rapid Lifecycle (draft-abbey-scim-agent-extension-00)
- Create and immediately delete multiple agents (default 10) to test ephemeral provisioning at machine speed
- Skipped if server does not support Agents
-
Search (RFC 7644 §3.4.2, §8.1)
- GET
/Users→ asserts ListResponse envelope (schemas,totalResults,Resources),Content-Type: application/scim+json - GET
/Users?filter=...→ asserts 200 (or 400 if partial filter support) - GET
/Users?startIndex=1&count=1→ asserts pagination parameters honored - GET
/Users?count=0→ assertstotalResultspresent with emptyResources
- GET
-
Error Handling (RFC 7644 §3.12)
- GET nonexistent resource → asserts 404 with SCIM error schema (
schemas,status) - POST invalid JSON body → asserts 400 with SCIM error schema
- POST missing required field (
userName) → asserts 400 with SCIM error schema
- GET nonexistent resource → asserts 404 with SCIM error schema (
Strict vs Compat Mode
Strict mode (--strict, default) treats all RFC deviations as failures.
Compat mode (--compat) applies a curated Deviation Policy: known, widespread ecosystem deviations are downgraded to warnings instead of failures. This list is intentional and versioned.
Current compat warnings include:
application/jsoninstead ofapplication/scim+json- DELETE 204 with response body
- Location header mismatch with
meta.location - Missing error schema in error responses
- ETag/meta.version mismatch
Warnings appear in output but don't cause a non-zero exit code.
Always failures (not compat-eligible): Some deviations are reported as FAIL in both strict and compat mode because they fundamentally break RFC-compliant clients:
- Server rejects
Content-Type: application/scim+jsonrequests (e.g., with 500) but acceptsapplication/json— diagnosed automatically and cited against RFC 7644 §8.2.
Error response reporting: When a server returns a 4xx or 5xx status for a resource endpoint, only the unexpected status code is reported. Predictable side-effects (missing id, meta, schemas in the error body) are suppressed to avoid obscuring the root cause with cascade noise.
Real-World Server Behavior
Enterprise SCIM servers often exhibit:
- Rate limiting (429 + Retry-After)
- Eventual consistency (a GET immediately after PUT may briefly return stale data)
- Partial filter support or restricted query capabilities
scim-sanity attempts to behave accordingly by retrying on 429, validating boundary cases, and clearly reporting unsupported or nonconformant behavior.
Fix Summary
When failures are present, the probe appends a prioritised Fix Summary after the results. Each entry has three lines:
[P1] Trouble: Wrong Content-Type on SCIM responses (12 tests affected)
Fix: Set Content-Type: application/scim+json on all responses served from /scim/v2/
Rationale: Compliant clients inspect Content-Type before parsing — every response
is rejected regardless of whether the body is otherwise correct.
Issues are ordered by severity (P1 most critical). The fix summary is omitted when all tests pass. In JSON output mode, the same information is available as an issues array (see below).
JSON Output (Stable Interface)
scim-sanity probe <url> --token <token> --json-output --i-accept-side-effects
{
"scim_sanity_version": "0.6.0",
"mode": "strict",
"timestamp": "2026-02-24 09:15:00",
"summary": {
"total": 32,
"passed": 14,
"failed": 15,
"warnings": 0,
"skipped": 3,
"errors": 0
},
"issues": [
{
"priority": "P1",
"title": "Wrong Content-Type on SCIM responses",
"rationale": "Compliant clients inspect Content-Type before parsing — every response is rejected regardless of whether the body is otherwise correct.",
"fix": "Set Content-Type: application/scim+json on all responses served from /scim/v2/",
"affected_tests": 12
}
],
"results": [
{"name": "GET /ServiceProviderConfig", "status": "fail", "message": "Content-Type should be application/scim+json, got 'text/html; charset=utf-8'", "phase": "Phase 1 — Discovery"}
]
}
The JSON schema is treated as a public interface and is stable within major versions.
Payload Validation (Linting)
Statically validate (lint) SCIM resource payloads and PATCH operations before sending them to a server. Resource type is auto-detected from schema URNs. This is a spec-driven validator with linter-style ergonomics: fast, offline, and suitable for CI/CD gating.
# Validate a resource file
scim-sanity user.json
# Validate a PATCH operation
scim-sanity --patch patch.json
# Validate from stdin
echo '{"schemas":["urn:ietf:params:scim:schemas:core:2.0:User"],"userName":"user@example.com"}' | scim-sanity --stdin
# Use in CI/CD pipelines
scim-sanity payload.json || exit 1
Validation Rules
Required attributes:
- User:
userName - Group:
displayName - Agent:
name - AgenticApplication:
name
What it checks:
- Schema URN validity and presence
- Required attributes per resource type
- Immutable attributes (
id,meta) not set by client - Null values (use PATCH
removeinstead) - PATCH operation structure (
op,path,valuecorrectness) - Complex and multi-valued attribute structure
Exit Codes
0— Validation passed (or all probe tests passed)1— Validation failed, probe failures detected, or error
Payload Examples
What the linter catches
Given a payload with a missing required field and a client-set immutable attribute:
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"id": "123",
"name": {"givenName": "John"}
}
Found 3 error(s):
❌ Missing required attribute: 'userName' (schema: urn:ietf:params:scim:schemas:core:2.0:User) at userName
❌ User resource missing required attribute: 'userName'
❌ Immutable attribute 'id' should not be set by client (mutability: readOnly) at id
Minimal valid examples
User
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"userName": "john.doe@example.com"
}
Group
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
"displayName": "Engineering Team"
}
Agent
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:Agent"],
"name": "automation-agent"
}
PATCH operation
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [{"op": "replace", "path": "displayName", "value": "New Name"}]
}
Pre-commit Integration
repos:
- repo: local
hooks:
- id: scim-sanity
name: Validate SCIM resources
entry: python -m scim_sanity
language: system
types: [json]
exclude: |
(?x)^(
.*/node_modules/.*|
.*/\.venv/.*|
.*/venv/.*|
.*package\.json$|
.*package-lock\.json$|
.*tsconfig.*\.json$|
.*jsconfig\.json$
)$
pass_filenames: true
stages: [commit]
Ansible Integration
Action plugin for SCIM validation in Ansible playbooks. See ansible/README.md.
- name: Validate SCIM payload
scim_validate:
payload: "{{ user_payload }}"
operation: full
register: validation_result
Identity Provider Guides
Security and Compliance
Development
git clone https://github.com/thomaselliottbetz/scim-sanity.git
cd scim-sanity
python -m venv venv
source venv/bin/activate
pip install -e ".[web,dev]"
pytest -v
Web GUI development
Two processes run during development:
# Terminal 1 — Python API (auto-reloads on source changes)
pip install -e ".[web]"
uvicorn scim_sanity.api:app --reload --port 8000
# Terminal 2 — Vite dev server with hot reload
cd web
npm install
npm run dev # http://localhost:5173, proxies /api/* to :8000
To build the frontend for production:
cd web
npm run build # outputs to web/dist/
The built static files are served automatically by scim-sanity web — no separate frontend process needed.
Planned Improvements
PATCH filter expression testing (RFC 7644 §3.5.2) — The probe currently tests simple PATCH paths (active, members). Complex filter-based paths such as emails[type eq "work"].value are a known interop pain point and are not yet covered.
Phase 1 schema content validation — Discovery endpoint tests currently verify HTTP 200 and correct Content-Type but do not validate that the returned schema bodies are well-formed or consistent with the resources the server actually implements.
Phase 6 resource body validation — The search phase validates the ListResponse envelope structure but does not inspect individual resources within the Resources array. A server returning well-formed envelopes with non-conformant resource bodies would currently pass.
GitHub Action — A ready-to-use GitHub Action for running the probe or linter in CI/CD pipelines without requiring a local Python environment.
Docker image — A zero-setup container image for running the probe against any reachable SCIM endpoint without installing Python or pip.
Related Projects
entra-google-security-bridge — Post-provisioning security monitoring and attribute synchronization for hybrid Microsoft Entra ID + Google Workspace environments. Covers what scim-sanity doesn't: once provisioning is verified as spec-compliant, this tool handles ongoing risky sign-in detection, guest user auditing, OAuth token scanning, and OU synchronization driven by Entra ID attribute changes.
Contributing
Contributions via Pull Request.
License
MIT License - see LICENSE file.
References
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file scim_sanity-0.6.0.tar.gz.
File metadata
- Download URL: scim_sanity-0.6.0.tar.gz
- Upload date:
- Size: 56.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6d04579a4c518002abfba21783786852ecb2d76894afad7f3c8021de96310998
|
|
| MD5 |
50722cf6c003c4f21855d71c42c3bce5
|
|
| BLAKE2b-256 |
078eabb04c5b94e062403ad2f7d77027f0ea546a8fcb1bd4811170272db52736
|
File details
Details for the file scim_sanity-0.6.0-py3-none-any.whl.
File metadata
- Download URL: scim_sanity-0.6.0-py3-none-any.whl
- Upload date:
- Size: 48.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c8350509bcc15f71506929f00de0c3b3f1c644b4683a0e66a9ad40bb1450725d
|
|
| MD5 |
718540d6097cb3463bd50f787e8c9dfc
|
|
| BLAKE2b-256 |
5a62b8c3cf86d90569b599f916829e4f113d034ce342a9e43a42ecc192767eb8
|