dirsim,
an Active Directory you can break safely.
A behavioral simulator of Microsoft Active Directory. Forests, OUs, users, groups, GPOs, trusts, Kerberos-style tickets, replication topology --- exposed via REST/SSE plus a Next.js console.
dirsim is not an LDAP/Kerberos wire-protocol reimplementation. The concepts and enforcement semantics match AD; the transport is HTTP+JSON. Every write goes through a Validate → Authorize → Append DirectoryEvent → Publish → Project pipeline, which is what makes scenario reset, time travel, and replication-delay simulation work. NT hashes are computed for real so external attack tooling can be exercised against exported data, but no real wire crypto goes over the network.
ITech scope
- Single API process (
apps/api) with internal service boundaries enforced by the package layout. The "services" in the architecture document are folders and workspace packages, not separate Node processes — which keeps the deploy story honest (one binary, one port) while still letting the simulator core stay pure. - Pure core, impure edges.
@dirsim/corecontains only pure functions: SID and DN parsing, LDAP filter evaluation, NT-hash computation, UAC bit decoding, and the well-known SID table. Anything that touches I/O, sockets, or the clock lives inapps/api. The boundary is enforceable because@dirsim/coredeclares no runtime dependencies that could pull infsornettransitively. - Store is in-memory, persisted by snapshots when
SIM_DATA_DIRis set. The directory auto-restores from the last snapshot on boot and re-snapshots everySIM_SNAPSHOT_INTERVAL_SECONDS(default 60), plus on SIGTERM, plus before any operator-triggered scenario reset. Snapshots are JSON, not a binary format, so a triage of "what does my fixture actually contain" is ajqaway. - Production guard.
NODE_ENV=productionrefuses to boot unlessAPI_JWT_SECRET,API_OPERATOR_PASSWORD, andAPI_CORS_ORIGINSare set, and the CORS list refuses a literal*. The guard catches the most common deploy mistake (running the demo config in front of real traffic) before the listener binds. - NT hashes are computed for real with the MD4 algorithm, so an export can be exercised against credential-stuffing tooling without modification. The KDC, by contrast, is simulated — tickets are signed JWTs with AD-shaped claims rather than ASN.1 DER, which is the deliberate dividing line between "behavioral" and "wire-protocol" simulation.
IIArchitecture
Event-sourced mutation pipeline. Every write follows the same five-step path: validate with Zod, authorize against operator RBAC (owner > scenario_author > observer), append a DirectoryEvent to the log, publish on the in-process event bus, and project into the in-memory directory. The event log is the source of truth; the directory is a projection. That separation is what makes scenario reset, time travel, and replication-delay simulation work — resetting a scenario means replaying from a starting event index, and a "lossy replication link" is a per-replica filter that holds events back for a configurable lag.
Kerberos is simulated, not real. Tickets are signed JWTs whose claim shape mirrors a real PAC: SIDs, group membership, four delegation modes (none, unconstrained, constrained, RBCD), and the same flag bits a Kerberoast attempt would inspect. The signed-JWT tickets are sufficient to exercise PowerView, BloodHound, and Rubeus-style tooling against the export, but they do not implement the actual ASN.1 KRB-CRED structure. NT hashes are real because the value of an attack-tooling exercise is in the credential math, not the wire format.
The Resultant Set of Policy engine in @dirsim/policy implements LSDOU ordering, Block Inheritance, Enforced links, and loopback processing, which is enough to reproduce most of the GPO ordering bugs that show up in real environments. Replication topology is a directed graph of replicas with per-edge latency; an event published in forest A appears in a peer in forest B at the configured delay, which is what lets the simulator reproduce the "AD looked fine on DC1 and broken on DC2" class of incident.
IIIWorkspace map
The monorepo is npm workspaces (Node 20+). Packages are scoped @dirsim/*; the apps depend on packages, never the other way around, and the dependency graph is a DAG enforced by Turborepo’s task pipeline.
@dirsim/schemas— Zod schemas. The single source of truth for HTTP payloads, the seed loader, agent-tool inputs, and the test fixtures. A schema change is one edit; downstream consumers refuse to compile if it breaks.@dirsim/core— pure functions only. SID, DN, LDAP filter eval, NT-hash, UAC, well-known SIDs. No I/O, no clock, no async.@dirsim/store— in-memory directory: CRUD, search, group membership resolution, the event log itself. The store is the only writer of events; everything else publishes through it.@dirsim/kerberos— KDC simulation: AS-REQ / TGS-REQ / AP-REQ flow, PAC construction, S4U2Self / S4U2Proxy, Kerberoast and AS-REP roast attack surfaces.@dirsim/policy— Resultant Set of Policy engine. LSDOU, Block Inheritance, Enforced, loopback — tested against real-world GPO ordering puzzles.@dirsim/sim— virtual clock, replication topology, scenario engine, the agent-tool surface for LLM-driven exercises.@dirsim/ldap-bridge— read-only LDAPv3 TCP listener. Off unlessLDAP_BRIDGE_PORTis set; provided for tools that absolutely insist on speaking LDAP, with the explicit caveat that writes are not supported.@dirsim/seed-contoso— example forest fixture:contoso.localwith a one-way trust tofabrikam.local, OUs, GPOs, a few classic misconfigurations to find.@dirsim/api— Fastify gateway. 18 REST endpoints, an SSE channel, the agent-tool surface, plus/metrics(Prometheus) and/healthz.@dirsim/web— Next.js 14 admin console. Consumes both REST and SSE; the live event stream is what makes "watch the directory react in real time" feel like the value prop.
IVWhere the line is drawn
dirsim is a behavioral simulator, not an emulator. It implements the state machine a real Active Directory goes through — with realistic timings, replication delays, GPO precedence, and Kerberos failure modes — but it does not speak the LDAP wire protocol or the SMB stack. That choice keeps the surface comprehensible and the model testable. It also means dirsim cannot stand in for a real domain controller for any tool that talks raw LDAP at a TCP socket; the read-only LDAP bridge is provided as a partial accommodation, and its limits are called out at the API boundary so an integrator does not learn them from a confusing outage.
VSurface
The surface is an 18-endpoint REST API plus an SSE event channel and a Next.js 14 console that consumes both. Operators describe a topology — forests, trusts, OUs, GPOs, GPO links, group memberships, well-known SIDs, replication links — in plain JSON; the simulator boots that JSON into a running model with replication latency, GPO precedence (LSDOU + Block Inheritance + Enforced + loopback), and Kerberos state machines that you can poke and observe in real time over the SSE channel. The use case is “what does this misconfiguration actually do” experimentation without standing up a real lab forest, plus exercising attack tooling like BloodHound and Rubeus against a known-good fixture.
VIConstraints
This is a behavioral simulator, not an emulator. It does not implement the LDAP wire protocol or the SMB stack; it implements the state machine that a real AD goes through, with realistic timings, replication delays, GPO ordering puzzles, and Kerberos failure modes. That choice keeps the surface comprehensible and the model testable in plain TypeScript, but it means dirsim cannot stand in for a real domain controller for any tool that talks raw LDAP at a TCP socket. The read-only LDAP bridge in @dirsim/ldap-bridge is provided as a partial accommodation for tools that absolutely insist on speaking LDAP, but its limits (read-only, no schema modification, no replication topology surfaced via LDAP) are called out at the API boundary so an integrator does not learn them from a confusing outage.