@etcsec-com/etc-collector 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (617) hide show
  1. package/.env.example +60 -0
  2. package/.env.test.example +33 -0
  3. package/.github/workflows/ci.yml +83 -0
  4. package/.github/workflows/release.yml +246 -0
  5. package/.prettierrc.json +10 -0
  6. package/CHANGELOG.md +15 -0
  7. package/Dockerfile +57 -0
  8. package/LICENSE +190 -0
  9. package/README.md +194 -0
  10. package/dist/api/controllers/audit.controller.d.ts +21 -0
  11. package/dist/api/controllers/audit.controller.d.ts.map +1 -0
  12. package/dist/api/controllers/audit.controller.js +179 -0
  13. package/dist/api/controllers/audit.controller.js.map +1 -0
  14. package/dist/api/controllers/auth.controller.d.ts +16 -0
  15. package/dist/api/controllers/auth.controller.d.ts.map +1 -0
  16. package/dist/api/controllers/auth.controller.js +146 -0
  17. package/dist/api/controllers/auth.controller.js.map +1 -0
  18. package/dist/api/controllers/export.controller.d.ts +27 -0
  19. package/dist/api/controllers/export.controller.d.ts.map +1 -0
  20. package/dist/api/controllers/export.controller.js +80 -0
  21. package/dist/api/controllers/export.controller.js.map +1 -0
  22. package/dist/api/controllers/health.controller.d.ts +5 -0
  23. package/dist/api/controllers/health.controller.d.ts.map +1 -0
  24. package/dist/api/controllers/health.controller.js +16 -0
  25. package/dist/api/controllers/health.controller.js.map +1 -0
  26. package/dist/api/controllers/jobs.controller.d.ts +13 -0
  27. package/dist/api/controllers/jobs.controller.d.ts.map +1 -0
  28. package/dist/api/controllers/jobs.controller.js +125 -0
  29. package/dist/api/controllers/jobs.controller.js.map +1 -0
  30. package/dist/api/controllers/providers.controller.d.ts +15 -0
  31. package/dist/api/controllers/providers.controller.d.ts.map +1 -0
  32. package/dist/api/controllers/providers.controller.js +112 -0
  33. package/dist/api/controllers/providers.controller.js.map +1 -0
  34. package/dist/api/dto/AuditRequest.dto.d.ts +6 -0
  35. package/dist/api/dto/AuditRequest.dto.d.ts.map +1 -0
  36. package/dist/api/dto/AuditRequest.dto.js +3 -0
  37. package/dist/api/dto/AuditRequest.dto.js.map +1 -0
  38. package/dist/api/dto/AuditResponse.dto.d.ts +17 -0
  39. package/dist/api/dto/AuditResponse.dto.d.ts.map +1 -0
  40. package/dist/api/dto/AuditResponse.dto.js +3 -0
  41. package/dist/api/dto/AuditResponse.dto.js.map +1 -0
  42. package/dist/api/dto/TokenRequest.dto.d.ts +6 -0
  43. package/dist/api/dto/TokenRequest.dto.d.ts.map +1 -0
  44. package/dist/api/dto/TokenRequest.dto.js +3 -0
  45. package/dist/api/dto/TokenRequest.dto.js.map +1 -0
  46. package/dist/api/dto/TokenResponse.dto.d.ts +12 -0
  47. package/dist/api/dto/TokenResponse.dto.d.ts.map +1 -0
  48. package/dist/api/dto/TokenResponse.dto.js +3 -0
  49. package/dist/api/dto/TokenResponse.dto.js.map +1 -0
  50. package/dist/api/middlewares/authenticate.d.ts +12 -0
  51. package/dist/api/middlewares/authenticate.d.ts.map +1 -0
  52. package/dist/api/middlewares/authenticate.js +141 -0
  53. package/dist/api/middlewares/authenticate.js.map +1 -0
  54. package/dist/api/middlewares/errorHandler.d.ts +3 -0
  55. package/dist/api/middlewares/errorHandler.d.ts.map +1 -0
  56. package/dist/api/middlewares/errorHandler.js +30 -0
  57. package/dist/api/middlewares/errorHandler.js.map +1 -0
  58. package/dist/api/middlewares/rateLimit.d.ts +3 -0
  59. package/dist/api/middlewares/rateLimit.d.ts.map +1 -0
  60. package/dist/api/middlewares/rateLimit.js +34 -0
  61. package/dist/api/middlewares/rateLimit.js.map +1 -0
  62. package/dist/api/middlewares/validate.d.ts +4 -0
  63. package/dist/api/middlewares/validate.d.ts.map +1 -0
  64. package/dist/api/middlewares/validate.js +31 -0
  65. package/dist/api/middlewares/validate.js.map +1 -0
  66. package/dist/api/routes/audit.routes.d.ts +5 -0
  67. package/dist/api/routes/audit.routes.d.ts.map +1 -0
  68. package/dist/api/routes/audit.routes.js +24 -0
  69. package/dist/api/routes/audit.routes.js.map +1 -0
  70. package/dist/api/routes/auth.routes.d.ts +6 -0
  71. package/dist/api/routes/auth.routes.d.ts.map +1 -0
  72. package/dist/api/routes/auth.routes.js +22 -0
  73. package/dist/api/routes/auth.routes.js.map +1 -0
  74. package/dist/api/routes/export.routes.d.ts +5 -0
  75. package/dist/api/routes/export.routes.d.ts.map +1 -0
  76. package/dist/api/routes/export.routes.js +16 -0
  77. package/dist/api/routes/export.routes.js.map +1 -0
  78. package/dist/api/routes/health.routes.d.ts +4 -0
  79. package/dist/api/routes/health.routes.d.ts.map +1 -0
  80. package/dist/api/routes/health.routes.js +11 -0
  81. package/dist/api/routes/health.routes.js.map +1 -0
  82. package/dist/api/routes/index.d.ts +10 -0
  83. package/dist/api/routes/index.d.ts.map +1 -0
  84. package/dist/api/routes/index.js +20 -0
  85. package/dist/api/routes/index.js.map +1 -0
  86. package/dist/api/routes/providers.routes.d.ts +5 -0
  87. package/dist/api/routes/providers.routes.d.ts.map +1 -0
  88. package/dist/api/routes/providers.routes.js +13 -0
  89. package/dist/api/routes/providers.routes.js.map +1 -0
  90. package/dist/api/validators/audit.schemas.d.ts +60 -0
  91. package/dist/api/validators/audit.schemas.d.ts.map +1 -0
  92. package/dist/api/validators/audit.schemas.js +55 -0
  93. package/dist/api/validators/audit.schemas.js.map +1 -0
  94. package/dist/api/validators/auth.schemas.d.ts +17 -0
  95. package/dist/api/validators/auth.schemas.d.ts.map +1 -0
  96. package/dist/api/validators/auth.schemas.js +21 -0
  97. package/dist/api/validators/auth.schemas.js.map +1 -0
  98. package/dist/app.d.ts +3 -0
  99. package/dist/app.d.ts.map +1 -0
  100. package/dist/app.js +62 -0
  101. package/dist/app.js.map +1 -0
  102. package/dist/config/config.schema.d.ts +65 -0
  103. package/dist/config/config.schema.d.ts.map +1 -0
  104. package/dist/config/config.schema.js +95 -0
  105. package/dist/config/config.schema.js.map +1 -0
  106. package/dist/config/index.d.ts +4 -0
  107. package/dist/config/index.d.ts.map +1 -0
  108. package/dist/config/index.js +75 -0
  109. package/dist/config/index.js.map +1 -0
  110. package/dist/container.d.ts +47 -0
  111. package/dist/container.d.ts.map +1 -0
  112. package/dist/container.js +137 -0
  113. package/dist/container.js.map +1 -0
  114. package/dist/data/database.d.ts +13 -0
  115. package/dist/data/database.d.ts.map +1 -0
  116. package/dist/data/database.js +68 -0
  117. package/dist/data/database.js.map +1 -0
  118. package/dist/data/jobs/token-cleanup.job.d.ts +23 -0
  119. package/dist/data/jobs/token-cleanup.job.d.ts.map +1 -0
  120. package/dist/data/jobs/token-cleanup.job.js +96 -0
  121. package/dist/data/jobs/token-cleanup.job.js.map +1 -0
  122. package/dist/data/migrations/migration.runner.d.ts +13 -0
  123. package/dist/data/migrations/migration.runner.d.ts.map +1 -0
  124. package/dist/data/migrations/migration.runner.js +136 -0
  125. package/dist/data/migrations/migration.runner.js.map +1 -0
  126. package/dist/data/models/Token.model.d.ts +30 -0
  127. package/dist/data/models/Token.model.d.ts.map +1 -0
  128. package/dist/data/models/Token.model.js +3 -0
  129. package/dist/data/models/Token.model.js.map +1 -0
  130. package/dist/data/repositories/token.repository.d.ts +16 -0
  131. package/dist/data/repositories/token.repository.d.ts.map +1 -0
  132. package/dist/data/repositories/token.repository.js +97 -0
  133. package/dist/data/repositories/token.repository.js.map +1 -0
  134. package/dist/providers/azure/auth.provider.d.ts +5 -0
  135. package/dist/providers/azure/auth.provider.d.ts.map +1 -0
  136. package/dist/providers/azure/auth.provider.js +13 -0
  137. package/dist/providers/azure/auth.provider.js.map +1 -0
  138. package/dist/providers/azure/azure-errors.d.ts +40 -0
  139. package/dist/providers/azure/azure-errors.d.ts.map +1 -0
  140. package/dist/providers/azure/azure-errors.js +121 -0
  141. package/dist/providers/azure/azure-errors.js.map +1 -0
  142. package/dist/providers/azure/azure-retry.d.ts +41 -0
  143. package/dist/providers/azure/azure-retry.d.ts.map +1 -0
  144. package/dist/providers/azure/azure-retry.js +85 -0
  145. package/dist/providers/azure/azure-retry.js.map +1 -0
  146. package/dist/providers/azure/graph-client.d.ts +26 -0
  147. package/dist/providers/azure/graph-client.d.ts.map +1 -0
  148. package/dist/providers/azure/graph-client.js +146 -0
  149. package/dist/providers/azure/graph-client.js.map +1 -0
  150. package/dist/providers/azure/graph.provider.d.ts +23 -0
  151. package/dist/providers/azure/graph.provider.d.ts.map +1 -0
  152. package/dist/providers/azure/graph.provider.js +161 -0
  153. package/dist/providers/azure/graph.provider.js.map +1 -0
  154. package/dist/providers/azure/queries/app.queries.d.ts +6 -0
  155. package/dist/providers/azure/queries/app.queries.d.ts.map +1 -0
  156. package/dist/providers/azure/queries/app.queries.js +9 -0
  157. package/dist/providers/azure/queries/app.queries.js.map +1 -0
  158. package/dist/providers/azure/queries/policy.queries.d.ts +6 -0
  159. package/dist/providers/azure/queries/policy.queries.d.ts.map +1 -0
  160. package/dist/providers/azure/queries/policy.queries.js +9 -0
  161. package/dist/providers/azure/queries/policy.queries.js.map +1 -0
  162. package/dist/providers/azure/queries/user.queries.d.ts +7 -0
  163. package/dist/providers/azure/queries/user.queries.d.ts.map +1 -0
  164. package/dist/providers/azure/queries/user.queries.js +10 -0
  165. package/dist/providers/azure/queries/user.queries.js.map +1 -0
  166. package/dist/providers/interfaces/IGraphProvider.d.ts +31 -0
  167. package/dist/providers/interfaces/IGraphProvider.d.ts.map +1 -0
  168. package/dist/providers/interfaces/IGraphProvider.js +3 -0
  169. package/dist/providers/interfaces/IGraphProvider.js.map +1 -0
  170. package/dist/providers/interfaces/ILDAPProvider.d.ts +37 -0
  171. package/dist/providers/interfaces/ILDAPProvider.d.ts.map +1 -0
  172. package/dist/providers/interfaces/ILDAPProvider.js +3 -0
  173. package/dist/providers/interfaces/ILDAPProvider.js.map +1 -0
  174. package/dist/providers/ldap/acl-parser.d.ts +8 -0
  175. package/dist/providers/ldap/acl-parser.d.ts.map +1 -0
  176. package/dist/providers/ldap/acl-parser.js +157 -0
  177. package/dist/providers/ldap/acl-parser.js.map +1 -0
  178. package/dist/providers/ldap/ad-mappers.d.ts +8 -0
  179. package/dist/providers/ldap/ad-mappers.d.ts.map +1 -0
  180. package/dist/providers/ldap/ad-mappers.js +162 -0
  181. package/dist/providers/ldap/ad-mappers.js.map +1 -0
  182. package/dist/providers/ldap/ldap-client.d.ts +33 -0
  183. package/dist/providers/ldap/ldap-client.d.ts.map +1 -0
  184. package/dist/providers/ldap/ldap-client.js +195 -0
  185. package/dist/providers/ldap/ldap-client.js.map +1 -0
  186. package/dist/providers/ldap/ldap-errors.d.ts +48 -0
  187. package/dist/providers/ldap/ldap-errors.d.ts.map +1 -0
  188. package/dist/providers/ldap/ldap-errors.js +120 -0
  189. package/dist/providers/ldap/ldap-errors.js.map +1 -0
  190. package/dist/providers/ldap/ldap-retry.d.ts +14 -0
  191. package/dist/providers/ldap/ldap-retry.d.ts.map +1 -0
  192. package/dist/providers/ldap/ldap-retry.js +102 -0
  193. package/dist/providers/ldap/ldap-retry.js.map +1 -0
  194. package/dist/providers/ldap/ldap-sanitizer.d.ts +12 -0
  195. package/dist/providers/ldap/ldap-sanitizer.d.ts.map +1 -0
  196. package/dist/providers/ldap/ldap-sanitizer.js +104 -0
  197. package/dist/providers/ldap/ldap-sanitizer.js.map +1 -0
  198. package/dist/providers/ldap/ldap.provider.d.ts +21 -0
  199. package/dist/providers/ldap/ldap.provider.d.ts.map +1 -0
  200. package/dist/providers/ldap/ldap.provider.js +165 -0
  201. package/dist/providers/ldap/ldap.provider.js.map +1 -0
  202. package/dist/providers/ldap/queries/computer.queries.d.ts +6 -0
  203. package/dist/providers/ldap/queries/computer.queries.d.ts.map +1 -0
  204. package/dist/providers/ldap/queries/computer.queries.js +9 -0
  205. package/dist/providers/ldap/queries/computer.queries.js.map +1 -0
  206. package/dist/providers/ldap/queries/group.queries.d.ts +6 -0
  207. package/dist/providers/ldap/queries/group.queries.d.ts.map +1 -0
  208. package/dist/providers/ldap/queries/group.queries.js +9 -0
  209. package/dist/providers/ldap/queries/group.queries.js.map +1 -0
  210. package/dist/providers/ldap/queries/user.queries.d.ts +7 -0
  211. package/dist/providers/ldap/queries/user.queries.d.ts.map +1 -0
  212. package/dist/providers/ldap/queries/user.queries.js +10 -0
  213. package/dist/providers/ldap/queries/user.queries.js.map +1 -0
  214. package/dist/providers/smb/smb.provider.d.ts +68 -0
  215. package/dist/providers/smb/smb.provider.d.ts.map +1 -0
  216. package/dist/providers/smb/smb.provider.js +382 -0
  217. package/dist/providers/smb/smb.provider.js.map +1 -0
  218. package/dist/server.d.ts +2 -0
  219. package/dist/server.d.ts.map +1 -0
  220. package/dist/server.js +44 -0
  221. package/dist/server.js.map +1 -0
  222. package/dist/services/audit/ad-audit.service.d.ts +70 -0
  223. package/dist/services/audit/ad-audit.service.d.ts.map +1 -0
  224. package/dist/services/audit/ad-audit.service.js +1019 -0
  225. package/dist/services/audit/ad-audit.service.js.map +1 -0
  226. package/dist/services/audit/attack-graph.service.d.ts +62 -0
  227. package/dist/services/audit/attack-graph.service.d.ts.map +1 -0
  228. package/dist/services/audit/attack-graph.service.js +702 -0
  229. package/dist/services/audit/attack-graph.service.js.map +1 -0
  230. package/dist/services/audit/audit.service.d.ts +4 -0
  231. package/dist/services/audit/audit.service.d.ts.map +1 -0
  232. package/dist/services/audit/audit.service.js +10 -0
  233. package/dist/services/audit/audit.service.js.map +1 -0
  234. package/dist/services/audit/azure-audit.service.d.ts +37 -0
  235. package/dist/services/audit/azure-audit.service.d.ts.map +1 -0
  236. package/dist/services/audit/azure-audit.service.js +153 -0
  237. package/dist/services/audit/azure-audit.service.js.map +1 -0
  238. package/dist/services/audit/detectors/ad/accounts.detector.d.ts +37 -0
  239. package/dist/services/audit/detectors/ad/accounts.detector.d.ts.map +1 -0
  240. package/dist/services/audit/detectors/ad/accounts.detector.js +881 -0
  241. package/dist/services/audit/detectors/ad/accounts.detector.js.map +1 -0
  242. package/dist/services/audit/detectors/ad/adcs.detector.d.ts +21 -0
  243. package/dist/services/audit/detectors/ad/adcs.detector.d.ts.map +1 -0
  244. package/dist/services/audit/detectors/ad/adcs.detector.js +227 -0
  245. package/dist/services/audit/detectors/ad/adcs.detector.js.map +1 -0
  246. package/dist/services/audit/detectors/ad/advanced.detector.d.ts +63 -0
  247. package/dist/services/audit/detectors/ad/advanced.detector.d.ts.map +1 -0
  248. package/dist/services/audit/detectors/ad/advanced.detector.js +867 -0
  249. package/dist/services/audit/detectors/ad/advanced.detector.js.map +1 -0
  250. package/dist/services/audit/detectors/ad/attack-paths.detector.d.ts +16 -0
  251. package/dist/services/audit/detectors/ad/attack-paths.detector.d.ts.map +1 -0
  252. package/dist/services/audit/detectors/ad/attack-paths.detector.js +369 -0
  253. package/dist/services/audit/detectors/ad/attack-paths.detector.js.map +1 -0
  254. package/dist/services/audit/detectors/ad/compliance.detector.d.ts +28 -0
  255. package/dist/services/audit/detectors/ad/compliance.detector.d.ts.map +1 -0
  256. package/dist/services/audit/detectors/ad/compliance.detector.js +896 -0
  257. package/dist/services/audit/detectors/ad/compliance.detector.js.map +1 -0
  258. package/dist/services/audit/detectors/ad/computers.detector.d.ts +30 -0
  259. package/dist/services/audit/detectors/ad/computers.detector.d.ts.map +1 -0
  260. package/dist/services/audit/detectors/ad/computers.detector.js +799 -0
  261. package/dist/services/audit/detectors/ad/computers.detector.js.map +1 -0
  262. package/dist/services/audit/detectors/ad/gpo.detector.d.ts +17 -0
  263. package/dist/services/audit/detectors/ad/gpo.detector.d.ts.map +1 -0
  264. package/dist/services/audit/detectors/ad/gpo.detector.js +257 -0
  265. package/dist/services/audit/detectors/ad/gpo.detector.js.map +1 -0
  266. package/dist/services/audit/detectors/ad/groups.detector.d.ts +19 -0
  267. package/dist/services/audit/detectors/ad/groups.detector.d.ts.map +1 -0
  268. package/dist/services/audit/detectors/ad/groups.detector.js +488 -0
  269. package/dist/services/audit/detectors/ad/groups.detector.js.map +1 -0
  270. package/dist/services/audit/detectors/ad/index.d.ts +15 -0
  271. package/dist/services/audit/detectors/ad/index.d.ts.map +1 -0
  272. package/dist/services/audit/detectors/ad/index.js +51 -0
  273. package/dist/services/audit/detectors/ad/index.js.map +1 -0
  274. package/dist/services/audit/detectors/ad/kerberos.detector.d.ts +17 -0
  275. package/dist/services/audit/detectors/ad/kerberos.detector.d.ts.map +1 -0
  276. package/dist/services/audit/detectors/ad/kerberos.detector.js +293 -0
  277. package/dist/services/audit/detectors/ad/kerberos.detector.js.map +1 -0
  278. package/dist/services/audit/detectors/ad/monitoring.detector.d.ts +23 -0
  279. package/dist/services/audit/detectors/ad/monitoring.detector.d.ts.map +1 -0
  280. package/dist/services/audit/detectors/ad/monitoring.detector.js +328 -0
  281. package/dist/services/audit/detectors/ad/monitoring.detector.js.map +1 -0
  282. package/dist/services/audit/detectors/ad/network.detector.d.ts +39 -0
  283. package/dist/services/audit/detectors/ad/network.detector.d.ts.map +1 -0
  284. package/dist/services/audit/detectors/ad/network.detector.js +257 -0
  285. package/dist/services/audit/detectors/ad/network.detector.js.map +1 -0
  286. package/dist/services/audit/detectors/ad/password.detector.d.ts +14 -0
  287. package/dist/services/audit/detectors/ad/password.detector.d.ts.map +1 -0
  288. package/dist/services/audit/detectors/ad/password.detector.js +235 -0
  289. package/dist/services/audit/detectors/ad/password.detector.js.map +1 -0
  290. package/dist/services/audit/detectors/ad/permissions.detector.d.ts +20 -0
  291. package/dist/services/audit/detectors/ad/permissions.detector.d.ts.map +1 -0
  292. package/dist/services/audit/detectors/ad/permissions.detector.js +392 -0
  293. package/dist/services/audit/detectors/ad/permissions.detector.js.map +1 -0
  294. package/dist/services/audit/detectors/ad/trusts.detector.d.ts +11 -0
  295. package/dist/services/audit/detectors/ad/trusts.detector.d.ts.map +1 -0
  296. package/dist/services/audit/detectors/ad/trusts.detector.js +186 -0
  297. package/dist/services/audit/detectors/ad/trusts.detector.js.map +1 -0
  298. package/dist/services/audit/detectors/azure/app-security.detector.d.ts +11 -0
  299. package/dist/services/audit/detectors/azure/app-security.detector.d.ts.map +1 -0
  300. package/dist/services/audit/detectors/azure/app-security.detector.js +184 -0
  301. package/dist/services/audit/detectors/azure/app-security.detector.js.map +1 -0
  302. package/dist/services/audit/detectors/azure/conditional-access.detector.d.ts +10 -0
  303. package/dist/services/audit/detectors/azure/conditional-access.detector.d.ts.map +1 -0
  304. package/dist/services/audit/detectors/azure/conditional-access.detector.js +130 -0
  305. package/dist/services/audit/detectors/azure/conditional-access.detector.js.map +1 -0
  306. package/dist/services/audit/detectors/azure/privilege-security.detector.d.ts +8 -0
  307. package/dist/services/audit/detectors/azure/privilege-security.detector.d.ts.map +1 -0
  308. package/dist/services/audit/detectors/azure/privilege-security.detector.js +113 -0
  309. package/dist/services/audit/detectors/azure/privilege-security.detector.js.map +1 -0
  310. package/dist/services/audit/detectors/azure/user-security.detector.d.ts +14 -0
  311. package/dist/services/audit/detectors/azure/user-security.detector.d.ts.map +1 -0
  312. package/dist/services/audit/detectors/azure/user-security.detector.js +198 -0
  313. package/dist/services/audit/detectors/azure/user-security.detector.js.map +1 -0
  314. package/dist/services/audit/detectors/index.d.ts +2 -0
  315. package/dist/services/audit/detectors/index.d.ts.map +1 -0
  316. package/dist/services/audit/detectors/index.js +38 -0
  317. package/dist/services/audit/detectors/index.js.map +1 -0
  318. package/dist/services/audit/response-formatter.d.ts +176 -0
  319. package/dist/services/audit/response-formatter.d.ts.map +1 -0
  320. package/dist/services/audit/response-formatter.js +240 -0
  321. package/dist/services/audit/response-formatter.js.map +1 -0
  322. package/dist/services/audit/scoring.service.d.ts +15 -0
  323. package/dist/services/audit/scoring.service.d.ts.map +1 -0
  324. package/dist/services/audit/scoring.service.js +139 -0
  325. package/dist/services/audit/scoring.service.js.map +1 -0
  326. package/dist/services/auth/crypto.service.d.ts +19 -0
  327. package/dist/services/auth/crypto.service.d.ts.map +1 -0
  328. package/dist/services/auth/crypto.service.js +135 -0
  329. package/dist/services/auth/crypto.service.js.map +1 -0
  330. package/dist/services/auth/errors.d.ts +19 -0
  331. package/dist/services/auth/errors.d.ts.map +1 -0
  332. package/dist/services/auth/errors.js +46 -0
  333. package/dist/services/auth/errors.js.map +1 -0
  334. package/dist/services/auth/token.service.d.ts +41 -0
  335. package/dist/services/auth/token.service.d.ts.map +1 -0
  336. package/dist/services/auth/token.service.js +208 -0
  337. package/dist/services/auth/token.service.js.map +1 -0
  338. package/dist/services/config/config.service.d.ts +6 -0
  339. package/dist/services/config/config.service.d.ts.map +1 -0
  340. package/dist/services/config/config.service.js +64 -0
  341. package/dist/services/config/config.service.js.map +1 -0
  342. package/dist/services/export/export.service.d.ts +28 -0
  343. package/dist/services/export/export.service.d.ts.map +1 -0
  344. package/dist/services/export/export.service.js +28 -0
  345. package/dist/services/export/export.service.js.map +1 -0
  346. package/dist/services/export/formatters/csv.formatter.d.ts +8 -0
  347. package/dist/services/export/formatters/csv.formatter.d.ts.map +1 -0
  348. package/dist/services/export/formatters/csv.formatter.js +46 -0
  349. package/dist/services/export/formatters/csv.formatter.js.map +1 -0
  350. package/dist/services/export/formatters/json.formatter.d.ts +40 -0
  351. package/dist/services/export/formatters/json.formatter.d.ts.map +1 -0
  352. package/dist/services/export/formatters/json.formatter.js +58 -0
  353. package/dist/services/export/formatters/json.formatter.js.map +1 -0
  354. package/dist/services/jobs/azure-job-runner.d.ts +38 -0
  355. package/dist/services/jobs/azure-job-runner.d.ts.map +1 -0
  356. package/dist/services/jobs/azure-job-runner.js +199 -0
  357. package/dist/services/jobs/azure-job-runner.js.map +1 -0
  358. package/dist/services/jobs/index.d.ts +4 -0
  359. package/dist/services/jobs/index.d.ts.map +1 -0
  360. package/dist/services/jobs/index.js +20 -0
  361. package/dist/services/jobs/index.js.map +1 -0
  362. package/dist/services/jobs/job-runner.d.ts +64 -0
  363. package/dist/services/jobs/job-runner.d.ts.map +1 -0
  364. package/dist/services/jobs/job-runner.js +952 -0
  365. package/dist/services/jobs/job-runner.js.map +1 -0
  366. package/dist/services/jobs/job-store.d.ts +27 -0
  367. package/dist/services/jobs/job-store.d.ts.map +1 -0
  368. package/dist/services/jobs/job-store.js +261 -0
  369. package/dist/services/jobs/job-store.js.map +1 -0
  370. package/dist/services/jobs/job.types.d.ts +67 -0
  371. package/dist/services/jobs/job.types.d.ts.map +1 -0
  372. package/dist/services/jobs/job.types.js +36 -0
  373. package/dist/services/jobs/job.types.js.map +1 -0
  374. package/dist/types/ad.types.d.ts +74 -0
  375. package/dist/types/ad.types.d.ts.map +1 -0
  376. package/dist/types/ad.types.js +3 -0
  377. package/dist/types/ad.types.js.map +1 -0
  378. package/dist/types/adcs.types.d.ts +58 -0
  379. package/dist/types/adcs.types.d.ts.map +1 -0
  380. package/dist/types/adcs.types.js +38 -0
  381. package/dist/types/adcs.types.js.map +1 -0
  382. package/dist/types/attack-graph.types.d.ts +135 -0
  383. package/dist/types/attack-graph.types.d.ts.map +1 -0
  384. package/dist/types/attack-graph.types.js +58 -0
  385. package/dist/types/attack-graph.types.js.map +1 -0
  386. package/dist/types/audit.types.d.ts +34 -0
  387. package/dist/types/audit.types.d.ts.map +1 -0
  388. package/dist/types/audit.types.js +3 -0
  389. package/dist/types/audit.types.js.map +1 -0
  390. package/dist/types/azure.types.d.ts +61 -0
  391. package/dist/types/azure.types.d.ts.map +1 -0
  392. package/dist/types/azure.types.js +3 -0
  393. package/dist/types/azure.types.js.map +1 -0
  394. package/dist/types/config.types.d.ts +63 -0
  395. package/dist/types/config.types.d.ts.map +1 -0
  396. package/dist/types/config.types.js +3 -0
  397. package/dist/types/config.types.js.map +1 -0
  398. package/dist/types/error.types.d.ts +33 -0
  399. package/dist/types/error.types.d.ts.map +1 -0
  400. package/dist/types/error.types.js +70 -0
  401. package/dist/types/error.types.js.map +1 -0
  402. package/dist/types/finding.types.d.ts +133 -0
  403. package/dist/types/finding.types.d.ts.map +1 -0
  404. package/dist/types/finding.types.js +3 -0
  405. package/dist/types/finding.types.js.map +1 -0
  406. package/dist/types/gpo.types.d.ts +39 -0
  407. package/dist/types/gpo.types.d.ts.map +1 -0
  408. package/dist/types/gpo.types.js +15 -0
  409. package/dist/types/gpo.types.js.map +1 -0
  410. package/dist/types/token.types.d.ts +26 -0
  411. package/dist/types/token.types.d.ts.map +1 -0
  412. package/dist/types/token.types.js +3 -0
  413. package/dist/types/token.types.js.map +1 -0
  414. package/dist/types/trust.types.d.ts +45 -0
  415. package/dist/types/trust.types.d.ts.map +1 -0
  416. package/dist/types/trust.types.js +71 -0
  417. package/dist/types/trust.types.js.map +1 -0
  418. package/dist/utils/entity-converter.d.ts +17 -0
  419. package/dist/utils/entity-converter.d.ts.map +1 -0
  420. package/dist/utils/entity-converter.js +285 -0
  421. package/dist/utils/entity-converter.js.map +1 -0
  422. package/dist/utils/graph.util.d.ts +66 -0
  423. package/dist/utils/graph.util.d.ts.map +1 -0
  424. package/dist/utils/graph.util.js +382 -0
  425. package/dist/utils/graph.util.js.map +1 -0
  426. package/dist/utils/logger.d.ts +7 -0
  427. package/dist/utils/logger.d.ts.map +1 -0
  428. package/dist/utils/logger.js +86 -0
  429. package/dist/utils/logger.js.map +1 -0
  430. package/dist/utils/type-name-normalizer.d.ts +5 -0
  431. package/dist/utils/type-name-normalizer.d.ts.map +1 -0
  432. package/dist/utils/type-name-normalizer.js +218 -0
  433. package/dist/utils/type-name-normalizer.js.map +1 -0
  434. package/docker-compose.yml +26 -0
  435. package/docs/api/README.md +178 -0
  436. package/docs/api/openapi.yaml +1524 -0
  437. package/eslint.config.js +54 -0
  438. package/jest.config.js +38 -0
  439. package/package.json +97 -0
  440. package/scripts/fetch-ad-cert.sh +142 -0
  441. package/src/.gitkeep +0 -0
  442. package/src/api/.gitkeep +0 -0
  443. package/src/api/controllers/.gitkeep +0 -0
  444. package/src/api/controllers/audit.controller.ts +313 -0
  445. package/src/api/controllers/auth.controller.ts +258 -0
  446. package/src/api/controllers/export.controller.ts +153 -0
  447. package/src/api/controllers/health.controller.ts +16 -0
  448. package/src/api/controllers/jobs.controller.ts +187 -0
  449. package/src/api/controllers/providers.controller.ts +165 -0
  450. package/src/api/dto/.gitkeep +0 -0
  451. package/src/api/dto/AuditRequest.dto.ts +8 -0
  452. package/src/api/dto/AuditResponse.dto.ts +19 -0
  453. package/src/api/dto/TokenRequest.dto.ts +8 -0
  454. package/src/api/dto/TokenResponse.dto.ts +14 -0
  455. package/src/api/middlewares/.gitkeep +0 -0
  456. package/src/api/middlewares/authenticate.ts +203 -0
  457. package/src/api/middlewares/errorHandler.ts +54 -0
  458. package/src/api/middlewares/rateLimit.ts +35 -0
  459. package/src/api/middlewares/validate.ts +32 -0
  460. package/src/api/routes/.gitkeep +0 -0
  461. package/src/api/routes/audit.routes.ts +77 -0
  462. package/src/api/routes/auth.routes.ts +71 -0
  463. package/src/api/routes/export.routes.ts +34 -0
  464. package/src/api/routes/health.routes.ts +14 -0
  465. package/src/api/routes/index.ts +40 -0
  466. package/src/api/routes/providers.routes.ts +24 -0
  467. package/src/api/validators/.gitkeep +0 -0
  468. package/src/api/validators/audit.schemas.ts +59 -0
  469. package/src/api/validators/auth.schemas.ts +59 -0
  470. package/src/app.ts +87 -0
  471. package/src/config/.gitkeep +0 -0
  472. package/src/config/config.schema.ts +108 -0
  473. package/src/config/index.ts +82 -0
  474. package/src/container.ts +221 -0
  475. package/src/data/.gitkeep +0 -0
  476. package/src/data/database.ts +78 -0
  477. package/src/data/jobs/token-cleanup.job.ts +166 -0
  478. package/src/data/migrations/.gitkeep +0 -0
  479. package/src/data/migrations/001_initial_schema.sql +47 -0
  480. package/src/data/migrations/migration.runner.ts +125 -0
  481. package/src/data/models/.gitkeep +0 -0
  482. package/src/data/models/Token.model.ts +35 -0
  483. package/src/data/repositories/.gitkeep +0 -0
  484. package/src/data/repositories/token.repository.ts +160 -0
  485. package/src/providers/.gitkeep +0 -0
  486. package/src/providers/azure/.gitkeep +0 -0
  487. package/src/providers/azure/auth.provider.ts +14 -0
  488. package/src/providers/azure/azure-errors.ts +189 -0
  489. package/src/providers/azure/azure-retry.ts +168 -0
  490. package/src/providers/azure/graph-client.ts +315 -0
  491. package/src/providers/azure/graph.provider.ts +294 -0
  492. package/src/providers/azure/queries/app.queries.ts +9 -0
  493. package/src/providers/azure/queries/policy.queries.ts +9 -0
  494. package/src/providers/azure/queries/user.queries.ts +10 -0
  495. package/src/providers/interfaces/.gitkeep +0 -0
  496. package/src/providers/interfaces/IGraphProvider.ts +117 -0
  497. package/src/providers/interfaces/ILDAPProvider.ts +142 -0
  498. package/src/providers/ldap/.gitkeep +0 -0
  499. package/src/providers/ldap/acl-parser.ts +231 -0
  500. package/src/providers/ldap/ad-mappers.ts +280 -0
  501. package/src/providers/ldap/ldap-client.ts +259 -0
  502. package/src/providers/ldap/ldap-errors.ts +188 -0
  503. package/src/providers/ldap/ldap-retry.ts +267 -0
  504. package/src/providers/ldap/ldap-sanitizer.ts +273 -0
  505. package/src/providers/ldap/ldap.provider.ts +293 -0
  506. package/src/providers/ldap/queries/computer.queries.ts +9 -0
  507. package/src/providers/ldap/queries/group.queries.ts +9 -0
  508. package/src/providers/ldap/queries/user.queries.ts +10 -0
  509. package/src/providers/smb/smb.provider.ts +653 -0
  510. package/src/server.ts +60 -0
  511. package/src/services/.gitkeep +0 -0
  512. package/src/services/audit/.gitkeep +0 -0
  513. package/src/services/audit/ad-audit.service.ts +1481 -0
  514. package/src/services/audit/attack-graph.service.ts +1104 -0
  515. package/src/services/audit/audit.service.ts +12 -0
  516. package/src/services/audit/azure-audit.service.ts +286 -0
  517. package/src/services/audit/detectors/ad/accounts.detector.ts +1232 -0
  518. package/src/services/audit/detectors/ad/adcs.detector.ts +449 -0
  519. package/src/services/audit/detectors/ad/advanced.detector.ts +1270 -0
  520. package/src/services/audit/detectors/ad/attack-paths.detector.ts +600 -0
  521. package/src/services/audit/detectors/ad/compliance.detector.ts +1421 -0
  522. package/src/services/audit/detectors/ad/computers.detector.ts +1188 -0
  523. package/src/services/audit/detectors/ad/gpo.detector.ts +485 -0
  524. package/src/services/audit/detectors/ad/groups.detector.ts +685 -0
  525. package/src/services/audit/detectors/ad/index.ts +84 -0
  526. package/src/services/audit/detectors/ad/kerberos.detector.ts +424 -0
  527. package/src/services/audit/detectors/ad/monitoring.detector.ts +501 -0
  528. package/src/services/audit/detectors/ad/network.detector.ts +538 -0
  529. package/src/services/audit/detectors/ad/password.detector.ts +324 -0
  530. package/src/services/audit/detectors/ad/permissions.detector.ts +637 -0
  531. package/src/services/audit/detectors/ad/trusts.detector.ts +315 -0
  532. package/src/services/audit/detectors/azure/app-security.detector.ts +246 -0
  533. package/src/services/audit/detectors/azure/conditional-access.detector.ts +186 -0
  534. package/src/services/audit/detectors/azure/privilege-security.detector.ts +176 -0
  535. package/src/services/audit/detectors/azure/user-security.detector.ts +280 -0
  536. package/src/services/audit/detectors/index.ts +18 -0
  537. package/src/services/audit/response-formatter.ts +604 -0
  538. package/src/services/audit/scoring.service.ts +234 -0
  539. package/src/services/auth/.gitkeep +0 -0
  540. package/src/services/auth/crypto.service.ts +230 -0
  541. package/src/services/auth/errors.ts +47 -0
  542. package/src/services/auth/token.service.ts +420 -0
  543. package/src/services/config/.gitkeep +0 -0
  544. package/src/services/config/config.service.ts +75 -0
  545. package/src/services/export/.gitkeep +0 -0
  546. package/src/services/export/export.service.ts +99 -0
  547. package/src/services/export/formatters/csv.formatter.ts +124 -0
  548. package/src/services/export/formatters/json.formatter.ts +160 -0
  549. package/src/services/jobs/azure-job-runner.ts +312 -0
  550. package/src/services/jobs/index.ts +9 -0
  551. package/src/services/jobs/job-runner.ts +1280 -0
  552. package/src/services/jobs/job-store.ts +384 -0
  553. package/src/services/jobs/job.types.ts +182 -0
  554. package/src/types/.gitkeep +0 -0
  555. package/src/types/ad.types.ts +91 -0
  556. package/src/types/adcs.types.ts +107 -0
  557. package/src/types/attack-graph.types.ts +260 -0
  558. package/src/types/audit.types.ts +42 -0
  559. package/src/types/azure.types.ts +68 -0
  560. package/src/types/config.types.ts +79 -0
  561. package/src/types/error.types.ts +69 -0
  562. package/src/types/finding.types.ts +284 -0
  563. package/src/types/gpo.types.ts +72 -0
  564. package/src/types/smb2.d.ts +73 -0
  565. package/src/types/token.types.ts +32 -0
  566. package/src/types/trust.types.ts +140 -0
  567. package/src/utils/.gitkeep +0 -0
  568. package/src/utils/entity-converter.ts +453 -0
  569. package/src/utils/graph.util.ts +609 -0
  570. package/src/utils/logger.ts +111 -0
  571. package/src/utils/type-name-normalizer.ts +302 -0
  572. package/tests/.gitkeep +0 -0
  573. package/tests/e2e/.gitkeep +0 -0
  574. package/tests/fixtures/.gitkeep +0 -0
  575. package/tests/integration/.gitkeep +0 -0
  576. package/tests/integration/README.md +156 -0
  577. package/tests/integration/ad-audit.integration.test.ts +216 -0
  578. package/tests/integration/api/.gitkeep +0 -0
  579. package/tests/integration/api/endpoints.integration.test.ts +431 -0
  580. package/tests/integration/auth/jwt-authentication.integration.test.ts +358 -0
  581. package/tests/integration/providers/.gitkeep +0 -0
  582. package/tests/integration/providers/azure-basic.integration.test.ts +167 -0
  583. package/tests/integration/providers/ldap-basic.integration.test.ts +152 -0
  584. package/tests/integration/providers/ldap-connectivity.test.ts +44 -0
  585. package/tests/integration/providers/ldap-provider.integration.test.ts +347 -0
  586. package/tests/mocks/.gitkeep +0 -0
  587. package/tests/setup.ts +16 -0
  588. package/tests/unit/.gitkeep +0 -0
  589. package/tests/unit/api/middlewares/authenticate.test.ts +446 -0
  590. package/tests/unit/providers/.gitkeep +0 -0
  591. package/tests/unit/providers/azure/azure-errors.test.ts +193 -0
  592. package/tests/unit/providers/azure/azure-retry.test.ts +254 -0
  593. package/tests/unit/providers/azure/graph-provider.test.ts +313 -0
  594. package/tests/unit/providers/ldap/ad-mappers.test.ts +392 -0
  595. package/tests/unit/providers/ldap/ldap-provider.test.ts +376 -0
  596. package/tests/unit/providers/ldap/ldap-retry.test.ts +377 -0
  597. package/tests/unit/providers/ldap/ldap-sanitizer.test.ts +301 -0
  598. package/tests/unit/sample.test.ts +19 -0
  599. package/tests/unit/services/.gitkeep +0 -0
  600. package/tests/unit/services/audit/detectors/ad/accounts.detector.test.ts +393 -0
  601. package/tests/unit/services/audit/detectors/ad/advanced.detector.test.ts +380 -0
  602. package/tests/unit/services/audit/detectors/ad/computers.detector.test.ts +440 -0
  603. package/tests/unit/services/audit/detectors/ad/groups.detector.test.ts +276 -0
  604. package/tests/unit/services/audit/detectors/ad/kerberos.detector.test.ts +215 -0
  605. package/tests/unit/services/audit/detectors/ad/password.detector.test.ts +226 -0
  606. package/tests/unit/services/audit/detectors/ad/permissions.detector.test.ts +244 -0
  607. package/tests/unit/services/audit/detectors/azure/app-security.detector.test.ts +349 -0
  608. package/tests/unit/services/audit/detectors/azure/conditional-access.detector.test.ts +374 -0
  609. package/tests/unit/services/audit/detectors/azure/privilege-security.detector.test.ts +374 -0
  610. package/tests/unit/services/audit/detectors/azure/user-security.detector.test.ts +297 -0
  611. package/tests/unit/services/auth/crypto.service.test.ts +296 -0
  612. package/tests/unit/services/auth/token.service.test.ts +579 -0
  613. package/tests/unit/services/export/export.service.test.ts +241 -0
  614. package/tests/unit/services/export/formatters/csv.formatter.test.ts +270 -0
  615. package/tests/unit/services/export/formatters/json.formatter.test.ts +258 -0
  616. package/tests/unit/utils/.gitkeep +0 -0
  617. package/tsconfig.json +50 -0
@@ -0,0 +1,1104 @@
1
+ /**
2
+ * Attack Graph Service
3
+ *
4
+ * Builds and exports attack path data for visualization.
5
+ * Uses BFS to find shortest paths from non-privileged objects
6
+ * to privileged targets (Domain Admins, Enterprise Admins, etc.)
7
+ */
8
+
9
+ import { ADUser, ADGroup, ADComputer, AclEntry } from '../../types/ad.types';
10
+ import { ADCSCertificateTemplate } from '../../types/adcs.types';
11
+ import { ADGPO } from '../../types/gpo.types';
12
+ import {
13
+ AttackGraphExport,
14
+ AttackGraphNode,
15
+ AttackGraphRelation,
16
+ AttackPath,
17
+ AttackPathRisk,
18
+ AttackPathType,
19
+ AttackRelationType,
20
+ AttackTarget,
21
+ AttackGraphStats,
22
+ AttackGraphUniqueNode,
23
+ AttackChainElement,
24
+ AttackEntryPoint,
25
+ AttackNodeType,
26
+ ACCESS_MASK,
27
+ ACL_GUIDS,
28
+ PRIVILEGED_SID_SUFFIXES,
29
+ isAttackGraphNode,
30
+ } from '../../types/attack-graph.types';
31
+ import { logger } from '../../utils/logger';
32
+
33
+ /**
34
+ * Internal graph node with additional metadata
35
+ */
36
+ interface InternalNode {
37
+ id: string; // SID or DN-based ID
38
+ dn: string;
39
+ name: string;
40
+ type: AttackNodeType;
41
+ sid?: string;
42
+ isEnabled: boolean;
43
+ isPrivileged: boolean;
44
+ adminCount?: number;
45
+ memberOf: string[];
46
+ servicePrincipalName?: string[];
47
+ userAccountControl?: number;
48
+ delegateTo?: string[];
49
+ rbcdFrom?: string[];
50
+ }
51
+
52
+ /**
53
+ * Internal graph edge
54
+ */
55
+ interface InternalEdge {
56
+ source: string; // Node ID
57
+ target: string; // Node ID
58
+ relation: AttackRelationType;
59
+ accessMask?: number;
60
+ objectType?: string;
61
+ isAbusable: boolean;
62
+ }
63
+
64
+ /**
65
+ * BFS path result
66
+ */
67
+ interface BFSPath {
68
+ nodes: string[]; // Node IDs in order
69
+ edges: InternalEdge[]; // Edges between nodes
70
+ }
71
+
72
+ /**
73
+ * Attack Graph Service
74
+ */
75
+ export class AttackGraphService {
76
+ private nodes: Map<string, InternalNode> = new Map();
77
+ private edges: Map<string, InternalEdge[]> = new Map(); // source -> edges
78
+ private reverseEdges: Map<string, InternalEdge[]> = new Map(); // target -> edges
79
+ private privilegedTargets: Map<string, AttackTarget> = new Map();
80
+ private dnToId: Map<string, string> = new Map(); // DN -> node ID
81
+ private domainSid: string = '';
82
+ private domainName: string = '';
83
+
84
+ /**
85
+ * Initialize the attack graph service with AD data
86
+ */
87
+ constructor(
88
+ private users: ADUser[],
89
+ private groups: ADGroup[],
90
+ private computers: ADComputer[],
91
+ private aclEntries: AclEntry[],
92
+ _certTemplates: ADCSCertificateTemplate[] = [],
93
+ _gpos: ADGPO[] = [],
94
+ domain?: { name?: string; sid?: string }
95
+ ) {
96
+ this.domainName = domain?.name || '';
97
+ this.domainSid = domain?.sid || this.extractDomainSid();
98
+ logger.debug(`AttackGraphService initialized with domain SID: ${this.domainSid}`);
99
+ }
100
+
101
+ /**
102
+ * Build and export the attack graph
103
+ */
104
+ public export(maxPaths: number = 500): AttackGraphExport {
105
+ const startTime = Date.now();
106
+
107
+ // Build the graph
108
+ this.buildGraph();
109
+
110
+ // Identify privileged targets
111
+ this.identifyPrivilegedTargets();
112
+
113
+ // Find all attack paths
114
+ const paths = this.findAllAttackPaths(maxPaths);
115
+
116
+ // Compute statistics
117
+ const stats = this.computeStats(paths);
118
+
119
+ // Get unique nodes involved in paths
120
+ const uniqueNodes = this.getUniqueNodes(paths);
121
+
122
+ const duration = Date.now() - startTime;
123
+ logger.info(
124
+ `Attack graph exported: ${paths.length} paths, ${uniqueNodes.length} unique nodes in ${duration}ms`
125
+ );
126
+
127
+ return {
128
+ version: '1.0',
129
+ generatedAt: new Date().toISOString(),
130
+ domain: this.domainName,
131
+ targets: Array.from(this.privilegedTargets.values()),
132
+ paths,
133
+ stats,
134
+ uniqueNodes,
135
+ };
136
+ }
137
+
138
+ /**
139
+ * Build the internal graph from AD data
140
+ */
141
+ private buildGraph(): void {
142
+ // Add all nodes
143
+ this.addUserNodes();
144
+ this.addGroupNodes();
145
+ this.addComputerNodes();
146
+
147
+ // Add all edges
148
+ this.addMembershipEdges();
149
+ this.addAclEdges();
150
+ this.addDelegationEdges();
151
+ this.addKerberosEdges();
152
+
153
+ logger.debug(
154
+ `Graph built: ${this.nodes.size} nodes, ${this.countEdges()} edges`
155
+ );
156
+ }
157
+
158
+ /**
159
+ * Add user nodes to the graph
160
+ */
161
+ private addUserNodes(): void {
162
+ for (const user of this.users) {
163
+ const sid = this.extractSid(user);
164
+ const id = sid || this.dnToId.get(user.dn.toLowerCase()) || this.generateId(user.dn);
165
+
166
+ const node: InternalNode = {
167
+ id,
168
+ dn: user.dn,
169
+ name: user.sAMAccountName || user.displayName || user.dn,
170
+ type: 'user',
171
+ sid,
172
+ isEnabled: user.enabled,
173
+ isPrivileged: user.adminCount === 1,
174
+ adminCount: user.adminCount,
175
+ memberOf: user.memberOf || [],
176
+ servicePrincipalName: this.getSpn(user),
177
+ userAccountControl: user.userAccountControl,
178
+ delegateTo: this.getDelegationTargets(user),
179
+ };
180
+
181
+ this.nodes.set(id, node);
182
+ this.dnToId.set(user.dn.toLowerCase(), id);
183
+ this.edges.set(id, []);
184
+ this.reverseEdges.set(id, []);
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Add group nodes to the graph
190
+ */
191
+ private addGroupNodes(): void {
192
+ for (const group of this.groups) {
193
+ const sid = this.extractSid(group);
194
+ const id = sid || this.generateId(group.dn);
195
+
196
+ // Check if this is a privileged group by SID
197
+ const isPrivileged = this.isPrivilegedBySid(sid);
198
+
199
+ const node: InternalNode = {
200
+ id,
201
+ dn: group.dn,
202
+ name: group.sAMAccountName || group.displayName || group.cn || group.dn,
203
+ type: 'group',
204
+ sid,
205
+ isEnabled: true,
206
+ isPrivileged,
207
+ memberOf: group.memberOf || [],
208
+ };
209
+
210
+ this.nodes.set(id, node);
211
+ this.dnToId.set(group.dn.toLowerCase(), id);
212
+ this.edges.set(id, []);
213
+ this.reverseEdges.set(id, []);
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Add computer nodes to the graph
219
+ */
220
+ private addComputerNodes(): void {
221
+ for (const computer of this.computers) {
222
+ const sid = this.extractSid(computer);
223
+ const id = sid || this.generateId(computer.dn);
224
+
225
+ // Check if Domain Controller
226
+ const memberOf = (computer as any).memberOf || [];
227
+ const isDC = memberOf.some(
228
+ (m: string) =>
229
+ m.toLowerCase().includes('cn=domain controllers') ||
230
+ m.toLowerCase().includes('cn=contrôleurs de domaine')
231
+ );
232
+
233
+ const node: InternalNode = {
234
+ id,
235
+ dn: computer.dn,
236
+ name: computer.sAMAccountName || computer.dNSHostName || computer.cn || computer.dn,
237
+ type: 'computer',
238
+ sid,
239
+ isEnabled: computer.enabled,
240
+ isPrivileged: isDC,
241
+ memberOf: memberOf,
242
+ delegateTo: this.getDelegationTargets(computer),
243
+ rbcdFrom: this.getRbcdPrincipals(computer),
244
+ };
245
+
246
+ this.nodes.set(id, node);
247
+ this.dnToId.set(computer.dn.toLowerCase(), id);
248
+ this.edges.set(id, []);
249
+ this.reverseEdges.set(id, []);
250
+ }
251
+ }
252
+
253
+ /**
254
+ * Add membership edges (MemberOf)
255
+ */
256
+ private addMembershipEdges(): void {
257
+ for (const [nodeId, node] of this.nodes) {
258
+ for (const groupDn of node.memberOf) {
259
+ const targetId = this.dnToId.get(groupDn.toLowerCase());
260
+ if (targetId) {
261
+ this.addEdge({
262
+ source: nodeId,
263
+ target: targetId,
264
+ relation: 'MemberOf',
265
+ isAbusable: false, // Membership itself isn't abusable, but paths through it are
266
+ });
267
+ }
268
+ }
269
+ }
270
+ }
271
+
272
+ /**
273
+ * Add ACL-based edges
274
+ */
275
+ private addAclEdges(): void {
276
+ for (const acl of this.aclEntries) {
277
+ // Skip denied ACEs
278
+ if (acl.aceType !== '0' && acl.aceType !== 'ACCESS_ALLOWED_ACE_TYPE') {
279
+ continue;
280
+ }
281
+
282
+ const sourceId = this.resolveAclTrustee(acl.trustee);
283
+ const targetId = this.dnToId.get(acl.objectDn.toLowerCase());
284
+
285
+ if (!sourceId || !targetId) continue;
286
+
287
+ // Determine relation type based on access mask and object type
288
+ const relations = this.classifyAclRelation(acl);
289
+
290
+ for (const relation of relations) {
291
+ this.addEdge({
292
+ source: sourceId,
293
+ target: targetId,
294
+ relation: relation.type,
295
+ accessMask: acl.accessMask,
296
+ objectType: acl.objectType,
297
+ isAbusable: relation.isAbusable,
298
+ });
299
+ }
300
+ }
301
+ }
302
+
303
+ /**
304
+ * Add delegation edges
305
+ */
306
+ private addDelegationEdges(): void {
307
+ for (const [nodeId, node] of this.nodes) {
308
+ // Constrained delegation
309
+ if (node.delegateTo && node.delegateTo.length > 0) {
310
+ for (const spn of node.delegateTo) {
311
+ // Try to resolve SPN to a target
312
+ const targetId = this.resolveSPNTarget(spn);
313
+ if (targetId) {
314
+ this.addEdge({
315
+ source: nodeId,
316
+ target: targetId,
317
+ relation: 'AllowedToDelegate',
318
+ isAbusable: true,
319
+ });
320
+ }
321
+ }
322
+ }
323
+
324
+ // RBCD - reverse direction: those who can act get edge TO this computer
325
+ if (node.rbcdFrom && node.rbcdFrom.length > 0) {
326
+ for (const principalSid of node.rbcdFrom) {
327
+ const sourceId = this.nodes.has(principalSid)
328
+ ? principalSid
329
+ : this.findNodeBySid(principalSid);
330
+ if (sourceId) {
331
+ this.addEdge({
332
+ source: sourceId,
333
+ target: nodeId,
334
+ relation: 'AllowedToAct',
335
+ isAbusable: true,
336
+ });
337
+ }
338
+ }
339
+ }
340
+ }
341
+ }
342
+
343
+ /**
344
+ * Add Kerberos-related edges (HasSPN, NoPreauth)
345
+ * Note: These are tracked as node properties for path classification
346
+ */
347
+ private addKerberosEdges(): void {
348
+ // HasSPN and NoPreauth are tracked as node properties, not edges
349
+ // They are used during path classification to determine path type
350
+ // (KERBEROASTING, ASREP_ROASTING)
351
+ }
352
+
353
+ /**
354
+ * Identify privileged targets
355
+ */
356
+ private identifyPrivilegedTargets(): void {
357
+ for (const [nodeId, node] of this.nodes) {
358
+ let reason: string | null = null;
359
+
360
+ // Check SID-based privileged groups
361
+ if (node.sid) {
362
+ if (node.sid.endsWith(PRIVILEGED_SID_SUFFIXES.DOMAIN_ADMINS)) {
363
+ reason = 'Domain Admins';
364
+ } else if (node.sid.endsWith(PRIVILEGED_SID_SUFFIXES.ENTERPRISE_ADMINS)) {
365
+ reason = 'Enterprise Admins';
366
+ } else if (node.sid.endsWith(PRIVILEGED_SID_SUFFIXES.SCHEMA_ADMINS)) {
367
+ reason = 'Schema Admins';
368
+ } else if (node.sid.endsWith(PRIVILEGED_SID_SUFFIXES.ADMINISTRATORS)) {
369
+ reason = 'Administrators';
370
+ } else if (node.sid.endsWith(PRIVILEGED_SID_SUFFIXES.DOMAIN_CONTROLLERS)) {
371
+ reason = 'Domain Controllers';
372
+ } else if (node.sid.endsWith(PRIVILEGED_SID_SUFFIXES.ACCOUNT_OPERATORS)) {
373
+ reason = 'Account Operators';
374
+ } else if (node.sid.endsWith(PRIVILEGED_SID_SUFFIXES.BACKUP_OPERATORS)) {
375
+ reason = 'Backup Operators';
376
+ }
377
+ }
378
+
379
+ // Check adminCount=1
380
+ if (!reason && node.adminCount === 1) {
381
+ reason = 'adminCount=1';
382
+ }
383
+
384
+ // Check name-based privileged groups
385
+ if (!reason && node.type === 'group') {
386
+ const nameLower = node.name.toLowerCase();
387
+ if (
388
+ nameLower === 'domain admins' ||
389
+ nameLower === 'admins du domaine' ||
390
+ nameLower === 'administrateurs du domaine'
391
+ ) {
392
+ reason = 'Domain Admins';
393
+ } else if (
394
+ nameLower === 'enterprise admins' ||
395
+ nameLower === 'administrateurs de l\'entreprise'
396
+ ) {
397
+ reason = 'Enterprise Admins';
398
+ } else if (nameLower === 'administrators' || nameLower === 'administrateurs') {
399
+ reason = 'Administrators';
400
+ }
401
+ }
402
+
403
+ // Check Domain Controller computers
404
+ if (!reason && node.type === 'computer' && node.isPrivileged) {
405
+ reason = 'Domain Controller';
406
+ }
407
+
408
+ if (reason) {
409
+ node.isPrivileged = true;
410
+ this.privilegedTargets.set(nodeId, {
411
+ id: nodeId,
412
+ name: node.name,
413
+ type: node.type,
414
+ sid: node.sid,
415
+ dn: node.dn,
416
+ reason,
417
+ });
418
+ }
419
+ }
420
+
421
+ logger.debug(`Identified ${this.privilegedTargets.size} privileged targets`);
422
+ }
423
+
424
+ /**
425
+ * Find all attack paths using BFS
426
+ */
427
+ private findAllAttackPaths(maxPaths: number): AttackPath[] {
428
+ const paths: AttackPath[] = [];
429
+ const targetIds = Array.from(this.privilegedTargets.keys());
430
+
431
+ // For each non-privileged node, find shortest path to any privileged target
432
+ for (const [sourceId, sourceNode] of this.nodes) {
433
+ // Skip if source is already privileged
434
+ if (sourceNode.isPrivileged) continue;
435
+
436
+ // Skip disabled users
437
+ if (sourceNode.type === 'user' && !sourceNode.isEnabled) continue;
438
+
439
+ // BFS to find shortest path to any target
440
+ const path = this.bfsShortestPath(sourceId, targetIds);
441
+
442
+ if (path && path.nodes.length > 1) {
443
+ const attackPath = this.buildAttackPath(path, sourceNode, paths.length + 1);
444
+ if (attackPath) {
445
+ paths.push(attackPath);
446
+
447
+ if (paths.length >= maxPaths) {
448
+ logger.debug(`Reached max paths limit: ${maxPaths}`);
449
+ break;
450
+ }
451
+ }
452
+ }
453
+ }
454
+
455
+ // Sort by risk (critical first) then by hops (shortest first)
456
+ paths.sort((a, b) => {
457
+ const riskOrder: Record<AttackPathRisk, number> = {
458
+ critical: 0,
459
+ high: 1,
460
+ medium: 2,
461
+ low: 3,
462
+ };
463
+ if (riskOrder[a.risk] !== riskOrder[b.risk]) {
464
+ return riskOrder[a.risk] - riskOrder[b.risk];
465
+ }
466
+ return a.hops - b.hops;
467
+ });
468
+
469
+ return paths;
470
+ }
471
+
472
+ /**
473
+ * BFS to find shortest path from source to any target
474
+ */
475
+ private bfsShortestPath(sourceId: string, targetIds: string[]): BFSPath | null {
476
+ const targetSet = new Set(targetIds);
477
+ const visited = new Set<string>([sourceId]);
478
+ const queue: { nodeId: string; path: BFSPath }[] = [
479
+ { nodeId: sourceId, path: { nodes: [sourceId], edges: [] } },
480
+ ];
481
+
482
+ while (queue.length > 0) {
483
+ const current = queue.shift()!;
484
+
485
+ // Check if we reached a target
486
+ if (targetSet.has(current.nodeId) && current.path.nodes.length > 1) {
487
+ return current.path;
488
+ }
489
+
490
+ // Early termination for very long paths
491
+ if (current.path.nodes.length > 6) continue;
492
+
493
+ // Explore neighbors
494
+ const outEdges = this.edges.get(current.nodeId) || [];
495
+ for (const edge of outEdges) {
496
+ if (visited.has(edge.target)) continue;
497
+ visited.add(edge.target);
498
+
499
+ const newPath: BFSPath = {
500
+ nodes: [...current.path.nodes, edge.target],
501
+ edges: [...current.path.edges, edge],
502
+ };
503
+
504
+ // Prioritize paths to targets
505
+ if (targetSet.has(edge.target)) {
506
+ return newPath;
507
+ }
508
+
509
+ queue.push({ nodeId: edge.target, path: newPath });
510
+ }
511
+ }
512
+
513
+ return null;
514
+ }
515
+
516
+ /**
517
+ * Build an AttackPath from BFS result
518
+ */
519
+ private buildAttackPath(
520
+ bfsPath: BFSPath,
521
+ sourceNode: InternalNode,
522
+ pathIndex: number
523
+ ): AttackPath | null {
524
+ const chain: AttackChainElement[] = [];
525
+
526
+ // Build alternating node-relation-node chain
527
+ for (let i = 0; i < bfsPath.nodes.length; i++) {
528
+ const nodeId = bfsPath.nodes[i];
529
+ if (!nodeId) continue;
530
+ const node = this.nodes.get(nodeId);
531
+ if (!node) continue;
532
+
533
+ // Add node
534
+ chain.push(this.toAttackGraphNode(node));
535
+
536
+ // Add relation (if not last node)
537
+ if (i < bfsPath.edges.length) {
538
+ const edge = bfsPath.edges[i];
539
+ if (edge) {
540
+ chain.push(this.toAttackGraphRelation(edge));
541
+ }
542
+ }
543
+ }
544
+
545
+ // Get target node
546
+ const targetId = bfsPath.nodes[bfsPath.nodes.length - 1];
547
+ if (!targetId) return null;
548
+ const targetNode = this.nodes.get(targetId);
549
+ if (!targetNode) return null;
550
+
551
+ // Classify path type
552
+ const pathType = this.classifyPathType(bfsPath);
553
+
554
+ // Calculate risk
555
+ const risk = this.calculatePathRisk(bfsPath, pathType);
556
+
557
+ // Generate description and mitigation
558
+ const description = this.generateDescription(bfsPath, pathType, sourceNode, targetNode);
559
+ const mitigation = this.generateMitigation(bfsPath, pathType);
560
+
561
+ return {
562
+ id: `path-${String(pathIndex).padStart(3, '0')}`,
563
+ risk,
564
+ type: pathType,
565
+ hops: bfsPath.edges.length,
566
+ description,
567
+ chain,
568
+ entryPoint: this.toEntryPoint(sourceNode),
569
+ target: this.toAttackGraphNode(targetNode),
570
+ mitigation,
571
+ };
572
+ }
573
+
574
+ /**
575
+ * Classify path type based on edges
576
+ */
577
+ private classifyPathType(path: BFSPath): AttackPathType {
578
+ const relations = path.edges.map((e) => e.relation);
579
+
580
+ // Check for DCSync
581
+ if (relations.includes('DCSync')) {
582
+ return 'DCSYNC';
583
+ }
584
+
585
+ // Check for delegation abuse
586
+ if (relations.includes('AllowedToDelegate') || relations.includes('AllowedToAct')) {
587
+ return 'DELEGATION_ABUSE';
588
+ }
589
+
590
+ // Check for Kerberoasting (user with SPN -> privileged)
591
+ const firstNodeId = path.nodes[0];
592
+ const sourceNode = firstNodeId ? this.nodes.get(firstNodeId) : undefined;
593
+ if (
594
+ sourceNode?.type === 'user' &&
595
+ sourceNode.servicePrincipalName &&
596
+ sourceNode.servicePrincipalName.length > 0
597
+ ) {
598
+ return 'KERBEROASTING';
599
+ }
600
+
601
+ // Check for AS-REP roasting
602
+ if (
603
+ sourceNode?.userAccountControl &&
604
+ (sourceNode.userAccountControl & 0x400000) !== 0
605
+ ) {
606
+ return 'ASREP_ROASTING';
607
+ }
608
+
609
+ // Check for ownership abuse
610
+ if (relations.includes('Owns')) {
611
+ return 'OWNERSHIP_ABUSE';
612
+ }
613
+
614
+ // Check for ACL abuse
615
+ const aclRelations: AttackRelationType[] = [
616
+ 'GenericAll',
617
+ 'WriteDacl',
618
+ 'WriteOwner',
619
+ 'GenericWrite',
620
+ 'ForceChangePassword',
621
+ 'AddMember',
622
+ ];
623
+ if (relations.some((r) => aclRelations.includes(r))) {
624
+ return 'ACL_ABUSE';
625
+ }
626
+
627
+ // Check for pure group membership
628
+ if (relations.every((r) => r === 'MemberOf')) {
629
+ return 'GROUP_MEMBERSHIP';
630
+ }
631
+
632
+ // Default to ACL abuse
633
+ return 'ACL_ABUSE';
634
+ }
635
+
636
+ /**
637
+ * Calculate risk level for a path
638
+ */
639
+ private calculatePathRisk(path: BFSPath, pathType: AttackPathType): AttackPathRisk {
640
+ const hops = path.edges.length;
641
+ const hasAbusableEdge = path.edges.some((e) => e.isAbusable);
642
+
643
+ // DCSync is always critical
644
+ if (pathType === 'DCSYNC') return 'critical';
645
+
646
+ // Short paths with abusable edges are critical
647
+ if (hops <= 2 && hasAbusableEdge) return 'critical';
648
+
649
+ // Delegation abuse is high risk
650
+ if (pathType === 'DELEGATION_ABUSE') return 'high';
651
+
652
+ // Short paths are high risk
653
+ if (hops <= 2) return 'high';
654
+
655
+ // Medium paths
656
+ if (hops <= 4) return 'medium';
657
+
658
+ // Long paths are lower risk
659
+ return 'low';
660
+ }
661
+
662
+ /**
663
+ * Generate path description
664
+ */
665
+ private generateDescription(
666
+ path: BFSPath,
667
+ pathType: AttackPathType,
668
+ source: InternalNode,
669
+ target: InternalNode
670
+ ): string {
671
+ const typeDescriptions: Record<AttackPathType, string> = {
672
+ ACL_ABUSE: 'can abuse ACL permissions to reach',
673
+ KERBEROASTING: 'has an SPN and can be Kerberoasted to reach',
674
+ ASREP_ROASTING: 'has no pre-authentication and can be AS-REP roasted to reach',
675
+ DELEGATION_ABUSE: 'can abuse delegation to reach',
676
+ LATERAL_MOVEMENT: 'can move laterally to reach',
677
+ CERTIFICATE_ABUSE: 'can abuse certificate services to reach',
678
+ GROUP_MEMBERSHIP: 'is a member of groups leading to',
679
+ DCSYNC: 'has DCSync rights to',
680
+ OWNERSHIP_ABUSE: 'owns objects leading to',
681
+ };
682
+
683
+ return `${source.name} ${typeDescriptions[pathType]} ${target.name} in ${path.edges.length} hop(s)`;
684
+ }
685
+
686
+ /**
687
+ * Generate mitigation advice
688
+ */
689
+ private generateMitigation(_path: BFSPath, pathType: AttackPathType): string {
690
+ const mitigations: Record<AttackPathType, string> = {
691
+ ACL_ABUSE:
692
+ 'Review and remove unnecessary ACL permissions. Apply least privilege principle.',
693
+ KERBEROASTING:
694
+ 'Remove unnecessary SPNs from user accounts. Use Group Managed Service Accounts (gMSA) or strong passwords for service accounts.',
695
+ ASREP_ROASTING:
696
+ 'Enable Kerberos pre-authentication for all user accounts.',
697
+ DELEGATION_ABUSE:
698
+ 'Review and restrict delegation settings. Use resource-based constrained delegation with explicit trusts.',
699
+ LATERAL_MOVEMENT:
700
+ 'Segment networks and restrict local admin access. Implement LAPS for local administrator passwords.',
701
+ CERTIFICATE_ABUSE:
702
+ 'Review certificate template permissions. Disable vulnerable enrollment patterns.',
703
+ GROUP_MEMBERSHIP:
704
+ 'Review nested group memberships. Remove unnecessary members from privileged groups.',
705
+ DCSYNC:
706
+ 'Remove DCSync rights from non-DC accounts. Monitor for DCSync activity.',
707
+ OWNERSHIP_ABUSE:
708
+ 'Review object ownership. Ensure privileged objects are owned by appropriate administrators.',
709
+ };
710
+
711
+ return mitigations[pathType];
712
+ }
713
+
714
+ /**
715
+ * Convert internal node to export format
716
+ */
717
+ private toAttackGraphNode(node: InternalNode): AttackGraphNode {
718
+ return {
719
+ id: node.id,
720
+ name: node.name,
721
+ type: node.type,
722
+ sid: node.sid,
723
+ dn: node.dn,
724
+ domain: this.domainName,
725
+ isEnabled: node.isEnabled,
726
+ isPrivileged: node.isPrivileged,
727
+ };
728
+ }
729
+
730
+ /**
731
+ * Convert internal edge to export relation
732
+ */
733
+ private toAttackGraphRelation(edge: InternalEdge): AttackGraphRelation {
734
+ return {
735
+ relation: edge.relation,
736
+ isAbusable: edge.isAbusable,
737
+ accessMask: edge.accessMask,
738
+ objectType: edge.objectType,
739
+ };
740
+ }
741
+
742
+ /**
743
+ * Convert node to entry point
744
+ */
745
+ private toEntryPoint(node: InternalNode): AttackEntryPoint {
746
+ return {
747
+ id: node.id,
748
+ name: node.name,
749
+ type: node.type,
750
+ properties: {
751
+ hasSPN: node.servicePrincipalName && node.servicePrincipalName.length > 0,
752
+ noPreauth: node.userAccountControl
753
+ ? (node.userAccountControl & 0x400000) !== 0
754
+ : false,
755
+ passwordNotExpire: node.userAccountControl
756
+ ? (node.userAccountControl & 0x10000) !== 0
757
+ : false,
758
+ unconstrained: node.userAccountControl
759
+ ? (node.userAccountControl & 0x80000) !== 0
760
+ : false,
761
+ constrained: node.delegateTo && node.delegateTo.length > 0,
762
+ rbcd: node.rbcdFrom && node.rbcdFrom.length > 0,
763
+ adminCount: node.adminCount === 1,
764
+ enabled: node.isEnabled,
765
+ },
766
+ };
767
+ }
768
+
769
+ /**
770
+ * Compute statistics
771
+ */
772
+ private computeStats(paths: AttackPath[]): AttackGraphStats {
773
+ const byRisk = { critical: 0, high: 0, medium: 0, low: 0 };
774
+ const byType: Record<AttackPathType, number> = {
775
+ ACL_ABUSE: 0,
776
+ KERBEROASTING: 0,
777
+ ASREP_ROASTING: 0,
778
+ DELEGATION_ABUSE: 0,
779
+ LATERAL_MOVEMENT: 0,
780
+ CERTIFICATE_ABUSE: 0,
781
+ GROUP_MEMBERSHIP: 0,
782
+ DCSYNC: 0,
783
+ OWNERSHIP_ABUSE: 0,
784
+ };
785
+
786
+ let totalHops = 0;
787
+ let shortestPath = Infinity;
788
+ let longestPath = 0;
789
+
790
+ for (const path of paths) {
791
+ byRisk[path.risk]++;
792
+ byType[path.type]++;
793
+ totalHops += path.hops;
794
+ shortestPath = Math.min(shortestPath, path.hops);
795
+ longestPath = Math.max(longestPath, path.hops);
796
+ }
797
+
798
+ return {
799
+ totalPaths: paths.length,
800
+ byRisk,
801
+ byType,
802
+ averageHops: paths.length > 0 ? Math.round((totalHops / paths.length) * 100) / 100 : 0,
803
+ shortestPath: paths.length > 0 ? shortestPath : 0,
804
+ longestPath: paths.length > 0 ? longestPath : 0,
805
+ };
806
+ }
807
+
808
+ /**
809
+ * Get unique nodes involved in paths
810
+ */
811
+ private getUniqueNodes(paths: AttackPath[]): AttackGraphUniqueNode[] {
812
+ const nodeCount = new Map<string, { node: AttackGraphNode; count: number }>();
813
+
814
+ for (const path of paths) {
815
+ for (const element of path.chain) {
816
+ if (isAttackGraphNode(element)) {
817
+ const existing = nodeCount.get(element.id);
818
+ if (existing) {
819
+ existing.count++;
820
+ } else {
821
+ nodeCount.set(element.id, { node: element, count: 1 });
822
+ }
823
+ }
824
+ }
825
+ }
826
+
827
+ return Array.from(nodeCount.values())
828
+ .map(({ node, count }) => ({
829
+ id: node.id,
830
+ name: node.name,
831
+ type: node.type,
832
+ pathCount: count,
833
+ sid: node.sid,
834
+ }))
835
+ .sort((a, b) => b.pathCount - a.pathCount);
836
+ }
837
+
838
+ // ========== Helper Methods ==========
839
+
840
+ /**
841
+ * Add an edge to the graph
842
+ */
843
+ private addEdge(edge: InternalEdge): void {
844
+ const sourceEdges = this.edges.get(edge.source);
845
+ if (sourceEdges) {
846
+ sourceEdges.push(edge);
847
+ }
848
+
849
+ const targetEdges = this.reverseEdges.get(edge.target);
850
+ if (targetEdges) {
851
+ targetEdges.push(edge);
852
+ }
853
+ }
854
+
855
+ /**
856
+ * Count total edges
857
+ */
858
+ private countEdges(): number {
859
+ let count = 0;
860
+ for (const edges of this.edges.values()) {
861
+ count += edges.length;
862
+ }
863
+ return count;
864
+ }
865
+
866
+ /**
867
+ * Extract SID from AD object
868
+ */
869
+ private extractSid(obj: ADUser | ADGroup | ADComputer): string | undefined {
870
+ const sid = (obj as any).objectSid || (obj as any).objectSID || (obj as any).sid;
871
+ if (typeof sid === 'string') return sid;
872
+ if (Buffer.isBuffer(sid)) return this.bufferToSid(sid);
873
+ return undefined;
874
+ }
875
+
876
+ /**
877
+ * Convert Buffer to SID string
878
+ */
879
+ private bufferToSid(buffer: Buffer): string {
880
+ if (buffer.length < 8) return '';
881
+
882
+ const revision = buffer.readUInt8(0);
883
+ const subAuthCount = buffer.readUInt8(1);
884
+ const authority =
885
+ buffer.readUInt8(2) * Math.pow(2, 40) +
886
+ buffer.readUInt8(3) * Math.pow(2, 32) +
887
+ buffer.readUInt8(4) * Math.pow(2, 24) +
888
+ buffer.readUInt8(5) * Math.pow(2, 16) +
889
+ buffer.readUInt8(6) * Math.pow(2, 8) +
890
+ buffer.readUInt8(7);
891
+
892
+ let sid = `S-${revision}-${authority}`;
893
+
894
+ for (let i = 0; i < subAuthCount; i++) {
895
+ const offset = 8 + i * 4;
896
+ if (offset + 4 <= buffer.length) {
897
+ const subAuth = buffer.readUInt32LE(offset);
898
+ sid += `-${subAuth}`;
899
+ }
900
+ }
901
+
902
+ return sid;
903
+ }
904
+
905
+ /**
906
+ * Extract domain SID from first user/computer SID
907
+ */
908
+ private extractDomainSid(): string {
909
+ for (const user of this.users) {
910
+ const sid = this.extractSid(user);
911
+ if (sid) {
912
+ // Domain SID is everything except the last RID
913
+ const parts = sid.split('-');
914
+ if (parts.length > 4) {
915
+ return parts.slice(0, -1).join('-');
916
+ }
917
+ }
918
+ }
919
+ return '';
920
+ }
921
+
922
+ /**
923
+ * Check if SID is privileged
924
+ */
925
+ private isPrivilegedBySid(sid?: string): boolean {
926
+ if (!sid) return false;
927
+
928
+ for (const suffix of Object.values(PRIVILEGED_SID_SUFFIXES)) {
929
+ if (sid.endsWith(suffix)) return true;
930
+ }
931
+
932
+ return false;
933
+ }
934
+
935
+ /**
936
+ * Generate ID from DN
937
+ */
938
+ private generateId(dn: string): string {
939
+ return `dn:${dn.toLowerCase()}`;
940
+ }
941
+
942
+ /**
943
+ * Get SPN array from user
944
+ */
945
+ private getSpn(user: ADUser): string[] | undefined {
946
+ const spn = (user as any).servicePrincipalName;
947
+ if (!spn) return undefined;
948
+ return Array.isArray(spn) ? spn : [spn];
949
+ }
950
+
951
+ /**
952
+ * Get delegation targets
953
+ */
954
+ private getDelegationTargets(obj: ADUser | ADComputer): string[] | undefined {
955
+ const attr = (obj as any)['msDS-AllowedToDelegateTo'];
956
+ if (!attr) return undefined;
957
+ return Array.isArray(attr) ? attr : [attr];
958
+ }
959
+
960
+ /**
961
+ * Get RBCD principals
962
+ */
963
+ private getRbcdPrincipals(computer: ADComputer): string[] | undefined {
964
+ const attr = (computer as any)['msDS-AllowedToActOnBehalfOfOtherIdentity'];
965
+ if (!attr) return undefined;
966
+
967
+ // This is typically a binary security descriptor - would need to parse it
968
+ // For now, just track that RBCD is configured
969
+ return [];
970
+ }
971
+
972
+ /**
973
+ * Resolve ACL trustee to node ID
974
+ */
975
+ private resolveAclTrustee(trustee: string): string | undefined {
976
+ // Trustee might be a SID or DN
977
+ if (trustee.startsWith('S-1-')) {
978
+ // It's a SID - look up by SID
979
+ if (this.nodes.has(trustee)) {
980
+ return trustee;
981
+ }
982
+ return this.findNodeBySid(trustee);
983
+ }
984
+
985
+ // It's a DN
986
+ return this.dnToId.get(trustee.toLowerCase());
987
+ }
988
+
989
+ /**
990
+ * Find node by SID
991
+ */
992
+ private findNodeBySid(sid: string): string | undefined {
993
+ for (const [nodeId, node] of this.nodes) {
994
+ if (node.sid === sid) return nodeId;
995
+ }
996
+ return undefined;
997
+ }
998
+
999
+ /**
1000
+ * Resolve SPN to target node
1001
+ */
1002
+ private resolveSPNTarget(spn: string): string | undefined {
1003
+ // SPN format: service/host:port or service/host
1004
+ const parts = spn.split('/');
1005
+ if (parts.length < 2 || !parts[1]) return undefined;
1006
+
1007
+ const hostPart = parts[1].split(':')[0];
1008
+ if (!hostPart) return undefined;
1009
+ const host = hostPart.toLowerCase();
1010
+
1011
+ // Look for computer with matching hostname
1012
+ const hostShort = host.split('.')[0] || host;
1013
+ for (const [nodeId, node] of this.nodes) {
1014
+ if (node.type === 'computer') {
1015
+ const nodeName = node.name.toLowerCase().replace(/\$$/, '');
1016
+ if (nodeName === host || nodeName.startsWith(hostShort)) {
1017
+ return nodeId;
1018
+ }
1019
+ }
1020
+ }
1021
+
1022
+ return undefined;
1023
+ }
1024
+
1025
+ /**
1026
+ * Classify ACL to relation types
1027
+ */
1028
+ private classifyAclRelation(
1029
+ acl: AclEntry
1030
+ ): { type: AttackRelationType; isAbusable: boolean }[] {
1031
+ const relations: { type: AttackRelationType; isAbusable: boolean }[] = [];
1032
+ const mask = acl.accessMask;
1033
+ const objectType = acl.objectType?.toLowerCase();
1034
+
1035
+ // GenericAll
1036
+ if (mask & ACCESS_MASK.GENERIC_ALL) {
1037
+ relations.push({ type: 'GenericAll', isAbusable: true });
1038
+ }
1039
+
1040
+ // GenericWrite
1041
+ if (mask & ACCESS_MASK.GENERIC_WRITE) {
1042
+ relations.push({ type: 'GenericWrite', isAbusable: true });
1043
+ }
1044
+
1045
+ // WriteDacl
1046
+ if (mask & ACCESS_MASK.WRITE_DACL) {
1047
+ relations.push({ type: 'WriteDacl', isAbusable: true });
1048
+ }
1049
+
1050
+ // WriteOwner
1051
+ if (mask & ACCESS_MASK.WRITE_OWNER) {
1052
+ relations.push({ type: 'WriteOwner', isAbusable: true });
1053
+ }
1054
+
1055
+ // Extended rights - check object type
1056
+ if (mask & ACCESS_MASK.CONTROL_ACCESS && objectType) {
1057
+ // ForceChangePassword
1058
+ if (objectType === ACL_GUIDS.FORCE_CHANGE_PASSWORD.toLowerCase()) {
1059
+ relations.push({ type: 'ForceChangePassword', isAbusable: true });
1060
+ }
1061
+
1062
+ // DCSync rights
1063
+ if (
1064
+ objectType === ACL_GUIDS.DS_REPLICATION_GET_CHANGES.toLowerCase() ||
1065
+ objectType === ACL_GUIDS.DS_REPLICATION_GET_CHANGES_ALL.toLowerCase()
1066
+ ) {
1067
+ relations.push({ type: 'DCSync', isAbusable: true });
1068
+ }
1069
+ }
1070
+
1071
+ // Self membership (AddMember)
1072
+ if (mask & ACCESS_MASK.SELF && objectType === ACL_GUIDS.SELF_MEMBERSHIP.toLowerCase()) {
1073
+ relations.push({ type: 'AddMember', isAbusable: true });
1074
+ }
1075
+
1076
+ return relations;
1077
+ }
1078
+ }
1079
+
1080
+ /**
1081
+ * Create and export attack graph
1082
+ */
1083
+ export function computeAttackGraph(
1084
+ users: ADUser[],
1085
+ groups: ADGroup[],
1086
+ computers: ADComputer[],
1087
+ aclEntries: AclEntry[],
1088
+ certTemplates: ADCSCertificateTemplate[] = [],
1089
+ gpos: ADGPO[] = [],
1090
+ domain?: { name?: string; sid?: string },
1091
+ maxPaths: number = 500
1092
+ ): AttackGraphExport {
1093
+ const service = new AttackGraphService(
1094
+ users,
1095
+ groups,
1096
+ computers,
1097
+ aclEntries,
1098
+ certTemplates,
1099
+ gpos,
1100
+ domain
1101
+ );
1102
+
1103
+ return service.export(maxPaths);
1104
+ }