scim-patch

TypeScript library that validates and applies RFC 7644 SCIM PATCH operations to user and group resources.

Library
npm
v0.9.3
31stars
Unlicense

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
69/100Good
Development Activity88
Maintenance88
Community28
Maturity60
Momentum12

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
77/100Good
Architecture82
Code Quality88
Innovation62
Learning Curve75

scim-patch implements the “Modifying with PATCH” section (3.5.2) of RFC 7644, the SCIM 2.0 protocol used by identity providers like Azure AD, Okta, and OneLogin to provision and update users and groups. Rather than hand-rolling path parsing and array-filter logic for every SCIM integration, it gives you two entry points: patchBodyValidation to check that an incoming PATCH request body is well-formed, and scimPatch to apply a list of add/remove/replace operations directly onto a SCIM resource object.

The library resolves dotted and schema-prefixed attribute paths (including extension schema URIs), supports SCIM’s array filter syntax (e.g. emails[type eq "work"].value) via the scim2-parse-filter package, and mirrors real-world provider quirks such as accepting capitalized operation names (Add, Remove, Replace) for AzureAD compatibility and treating a replace on a missing attribute as an add by default. It also explicitly guards against prototype-pollution paths (__proto__, constructor, prototype) in both the top-level path resolver and the nested assign helper, with dedicated tests for each.

Because identity provisioning is the actual target use case, the library is a thin, dependency-light layer (just fast-deep-equal and scim2-parse-filter) meant to sit inside a larger SCIM server implementation — you bring your own ScimResource-shaped objects and HTTP layer, and scim-patch handles just the PATCH semantics.

What You Get

  • patchBodyValidation to validate an incoming SCIM PATCH request body against the RFC’s schema and operation rules, throwing typed errors on malformed input
  • scimPatch to apply an array of add/remove/replace operations to a SCIM resource, with mutateDocument and treatMissingAsAdd options to control in-place mutation and missing-attribute behavior
  • Support for SCIM’s multi-valued attribute filter syntax (e.g. addresses[type eq "work"].country) backed by the scim2-parse-filter grammar
  • Compatibility handling for provider-specific quirks, including AzureAD’s capitalized operation names (Add, Remove, Replace) and one-login’s array-shaped remove values
  • Built-in prototype-pollution protection that rejects __proto__, constructor, and prototype segments in patch paths
  • A typed error hierarchy (InvalidScimPatchRequest, NoTarget, NoPathInScimPatchOp, and more) that maps cleanly onto SCIM’s scimType error codes for HTTP error responses

Common Use Cases

  • Implementing the PATCH endpoint of a self-hosted SCIM 2.0 provisioning server for user/group lifecycle management
  • Handling PATCH requests sent by enterprise identity providers such as Azure AD, Okta, or OneLogin during automated user provisioning
  • Validating inbound SCIM PATCH payloads before they reach business logic, rejecting malformed operations with RFC-compliant error codes
  • Applying incremental updates (e.g. deactivating a user, adding an email, removing a role) to an in-memory or database-backed SCIM resource without diffing full objects

Under The Hood

Architecture The library exposes two public functions from src/scimPatch.ts: patchBodyValidation, which walks a PATCH request body and validates each operation via validatePatchOperation, and scimPatch, which reduces an array of operations over a SCIM resource using a navigate/extractArray/addOrReplaceAttribute pipeline. Attribute paths are split on unescaped periods and, when a path contains a schema URI prefix (distinguished from core User/Group schemas), the URI is folded back into the path segments to address extension attributes. Every resolved path segment is checked against a DANGEROUS_KEYS set (__proto__, constructor, prototype) before use, and object traversal in both navigate and assign uses Object.prototype.hasOwnProperty.call rather than in or truthy checks specifically to avoid resolving to inherited built-ins — a defense against two named prototype-pollution CVEs (GHSA-9m6g-wc8r-q59c and GHSA-2mhw-wcx5-v3xj) that the test suite in prototypePollution.test.ts exercises directly. Multi-valued attribute filters are delegated to the external scim2-parse-filter grammar, keeping the SCIM filter language itself out of this codebase.

Tech Stack Written in strict-mode TypeScript (tsconfig.json sets strict, noImplicitReturns, noUnusedLocals) targeting ES2017/CommonJS output. Runtime dependencies are minimal: scim2-parse-filter for the SCIM filter grammar and fast-deep-equal for value comparison during remove/dedup operations. The dev toolchain uses tsc for builds, mocha + chai + nyc for testing and coverage, eslint with TypeScript and mocha plugins for linting, and benchmark for a dedicated perf test. CI (GitHub Actions) runs build, lint, test, and coverage-upload-to-Coveralls on every push and pull request; a separate release.yml workflow handles publishing.

Code Quality The test suite is extensive relative to the implementation size — scimPatch.test.ts alone runs over a thousand lines covering add/remove/replace across scalar, nested, and multi-valued-with-filter paths, patchValidation.test.ts covers request-body validation edge cases, and a dedicated prototypePollution.test.ts file specifically targets the two named security advisories with regression tests. Errors are modeled as a typed class hierarchy (ScimErrorInvalidScimPatch/InvalidScimRemoveValue → specific subclasses like NoTarget, FilterOnEmptyArray) rather than thrown strings, letting callers pattern-match on error type and expose RFC scimType codes. Strict TypeScript, ESLint, and CI enforcement across build/lint/test give reasonable confidence the exported surface behaves as typed.

API Design The public API is deliberately small — two functions and a set of exported types/errors — which keeps integration low-friction: consumers only need to shape their SCIM resource as a ScimResource-compatible object and call scimPatch(resource, operations, options). Defaults (mutateDocument: true, treatMissingAsAdd: true) match the most common self-hosted-provisioning behavior, and the two options that do exist map directly onto documented SCIM ambiguities (mutate-in-place vs. copy, and how to treat a replace against a missing attribute) rather than exposing internal implementation knobs. The README’s example walks through validating a request and then patching a concrete ScimUser type end-to-end, and the exported error classes double as documentation for the RFC’s scimType error codes.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search