ProudAuthDocs

Directory integration threat model#

Scope: LDAP and Active Directory federation, directory synchronization, directory-authoritative accounts and outbound SCIM provisioning (docs/integrations/directory.md, outbound-provisioning.md; PROV-010..PROV-014, PROV-017, PROV-018, FED-015, USR-022). Inbound SCIM is covered in system.md. Method as in system.md. This is the project's own code review, not an independent assessment (SEC-018), and the code has been tested only against the in-process ldaptest server and an in-process SCIM double, not real directories or SCIM services (QA-004).

Assets#

AssetWhereWhy it matters
Service-account bind passwords, downstream bearer tokensprovisioning_connectors.secret_ciphertext (AES-256-GCM, associated data directory-connector-credential:<connector id>)Read the directory (and write the write-back attributes); act as ProudAuth at the downstream
User passwords of directory-sourced accountsOnly in transit: forwarded in a bind to the account's own directoryAccount takeover in the directory and everywhere it is trusted
Linksconnector_external_ids (immutable ID, DN, snapshot of imported values, source state)Decide which directory owns an account
Connector definitionsprovisioning_connectors.config (endpoints, filters, mappings, ceilings)Decide which servers receive passwords and what a directory may change
Run evidencedirectory_sync_runs, directory_sync_findingsPlans and conflicts; must not leak other tenants' accounts

Trust boundaries#

BoundaryInsideOutsideEnforcement
Server/worker → directoryConfigured endpoints of a connectorOther LDAP servers, referral targetsEndpoint list only; TLS with verification; egress policy at connection time (directory.allow_private_network, directory.egress_allowlist, residency); referrals never followed
Worker → downstream SCIMThe connector's base_urlOther hostsWebhook egress policy at save and at connection; no redirects; bounded size and time
Realm directory administrationRealm administrators (realm.provisioning.manage; reads also realm.provisioning.read)Organization administratorsAdministration audience, MFA session
Organization directory administrationHolders of org.integrations.write in that organizationOther organizationsorgGuard; other organizations' connectors are 404; ceilings bounded by the creator's
Directory → accountsThe connector's own linked accounts and its organizationProtected and privileged accounts, other organizations, accounts another source ownsAdoption and deprovisioning rules; one directory per account

STRIDE#

ThreatMitigation (code)TestsResidual
I: service credential disclosureEncrypted under the key hierarchy with row-bound associated data; write-only API; rotation with PUT .../credential; never in responses, run errors, findings, logs or audit metadata; health, test results and errors expose categories and host names only (directory/directory.go, evidence.go)Leak assertions in TestLDAPDirectorySignIn and TestOutboundProvisioning (responses and audit metadata)The service account should be read-only except for the attributes permitted for write-back; ProudAuth cannot check its rights
I: user password exposurePasswords are only sent in a bind to the account's own configured directory over TLS (cleartext ldap:// without StartTLS only in development mode); never stored, logged or hashed locally; empty passwords never sent (no unauthenticated binds); more than 1024 bytes refused (directory/auth.go, ldapconn.go)TestLDAPDirectorySignIn, TestTLSA compromised directory server (or its TLS key) sees every password of its users at sign-in
T/E: LDAP filter injectionEvery value in a filter is escaped (ldap.EscapeFilter; binary GUIDs byte by byte); DNs are never built from input (binds use DNs the directory returned); operator filters are compiled and validated when savedTestFiltersEscapeInput (*@corp.test becomes \2a@corp.test), escaped identifiers in TestLDAPDirectorySignIn—
S: source confusion during failover (PROV-014)Only configured endpoints; failover only on transport errors; answers about the request (wrong password, no such object, refused bind) are final and never retried elsewhere; referrals never followed; a USN cursor is bound to its domain controllerTestFailoverOnTransportErrorsOnly, TestClassifyEndpoints of one connector must be replicas of the same directory; ProudAuth cannot verify that
S/I: man in the middleTLS 1.2 or later, certificate and host name always verified (system roots, directory.ca_file, connector roots); StartTLS before any bind; no skip-verify optionTestTLS—
E/I: SSRF through connector endpoints (TEST-052)Egress policy against every resolved address at connection time, plus residency; organization administrators reach only public addresses or operator-allowlisted ranges; outbound HTTP refuses redirects and bounds response size and timeEgress opt-in in TestOutboundProvisioning; TestDirectoryTenantIsolationAn organization connector can probe whether a public host answers LDAP
D: fallback to a weaker source (TEST-027, FED-012)Directory outage, disabled connector or disabled module → 503 upstream_unavailable for directory-sourced accounts, never a local password or another directory; local accounts unaffectedTestLDAPDirectorySignInA long directory outage blocks password sign-in for its users
I/E: tenant directory receives others' passwordsFirst sign-in provisioning is realm-only; organization connectors only ever bind accounts they linkTestDirectoryTenantIsolation, TestLDAPConfigRules—
E: account takeover through linkingNever by email text alone: adoption only by organization connectors, through a verified email in a domain the organization verified, of a non-protected, non-privileged account no other connector manages; realm connectors never adopt; first-sign-in conflicts are refused; adoption revokes the local passwordTestDirectorySynchronizationAn inbound SCIM connector can adopt a directory-sourced account into its organization (it manages only that membership)
E/I: tenant isolation (TEST-007, TEST-009)Organization connectors are visible and manageable only in their organization; they create memberships, groups and roles only there, within a ceiling bounded by the creator's; deprovisioning removes only their organization's access; outbound organization connectors receive only their members; findings never contain other accounts' IDs or attribute valuesTestDirectoryTenantIsolation, TestOutboundProvisioningAn organization connector, like inbound SCIM, can create realm accounts with usernames not yet taken (username squatting); emails are verified only through the organization's verified domains
E: privilege through groups (PROV-006, TEST-049)Protected accounts and privileged realm role holders are never changed or deprovisioned; group roles must stay within the ceiling at every run, including roles granted by others or inheritedTestDirectorySynchronizationOrganization connectors reactivate a membership on re-enable whoever suspended it
D/T: mass deletion or misconfigurationDeletion threshold with an explicit, audited override; incremental runs never deprovision; failed runs and runs over max_entries deprovision nothing; deletion detection only after a complete scan; dry runsTestDirectorySynchronizationA wrong user_filter below the threshold still deprovisions up to the threshold
T: oscillating overwrites (PROV-017)One authority per attribute; one directory per account; write-back first, local edit refused when it fails; synchronization compares with the current local valueTestDirectoryAuthority—
T: local edits diverge from the directory (USR-022)One authority check in every edit path, including password set and reactivation; 409 names the managed attributesTestDirectoryAuthority—
E: forged queue input (TEST-063)The job names only the run; the handler loads run and connector from the database and re-checks the requester's authority (INT-006)——
T: duplicate effects on retry (TEST-058)Per-entry idempotent application; deliveries coalesced and generation-checked; downstream create made idempotent through externalId; a resource the connector did not create is never adoptedTestOutboundProvisioning—
I: timing reveals unknown identifiersThe password step's minimum response time (security.min_auth_response)—Directory round trips longer than that bound, and 503 answers, are not masked
T: loops between inbound and outboundexclude_created_via and one directory per account; outbound never reads back into local accounts—An operator must configure the exclusion
R: unaudited connector actionsdirectory.* audit events with the connector as actor or target; operator actions in the connector's evidenceAll directory e2e tests—

Not offered, with reasons#

Kerberos/SPNEGO (PROV-015) and password write-back (PROV-013) are not implemented; the reasons are in directory.md and ADR 0018.

ProudAuth · Self-hosted identityBuilt 2026-10-13 · d9cc813ae809 · content snapshot
⌕

Search all guides and the API contract.

↑ ↓ to navigate Enter to open · Esc to close