@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,1188 @@
1
+ /**
2
+ * Computers Security Vulnerability Detector
3
+ *
4
+ * Detects computer-related vulnerabilities in AD.
5
+ * Story 1.7: AD Vulnerability Detection Engine
6
+ *
7
+ * Vulnerabilities detected (28):
8
+ * - COMPUTER_CONSTRAINED_DELEGATION (Critical)
9
+ * - COMPUTER_RBCD (Critical)
10
+ * - COMPUTER_IN_ADMIN_GROUP (Critical)
11
+ * - COMPUTER_DCSYNC_RIGHTS (Critical)
12
+ * - COMPUTER_UNCONSTRAINED_DELEGATION (Critical)
13
+ * - COMPUTER_OS_OBSOLETE_XP (Critical)
14
+ * - COMPUTER_OS_OBSOLETE_2003 (Critical)
15
+ * - COMPUTER_STALE_INACTIVE (High)
16
+ * - COMPUTER_PASSWORD_OLD (High)
17
+ * - COMPUTER_WITH_SPNS (High)
18
+ * - COMPUTER_NO_LAPS (High)
19
+ * - COMPUTER_ACL_ABUSE (High)
20
+ * - COMPUTER_OS_OBSOLETE_2008 (High)
21
+ * - COMPUTER_OS_OBSOLETE_VISTA (High)
22
+ * - DC_NOT_IN_DC_OU (High) - Phase 2C
23
+ * - COMPUTER_NO_BITLOCKER (High) - Phase 4
24
+ * - COMPUTER_DISABLED_NOT_DELETED (Medium)
25
+ * - COMPUTER_WRONG_OU (Medium)
26
+ * - COMPUTER_WEAK_ENCRYPTION (Medium)
27
+ * - COMPUTER_DESCRIPTION_SENSITIVE (Medium)
28
+ * - COMPUTER_PRE_WINDOWS_2000 (Medium)
29
+ * - COMPUTER_NEVER_LOGGED_ON (Medium)
30
+ * - COMPUTER_DUPLICATE_SPN (Medium) - Phase 2C
31
+ * - SERVER_NO_ADMIN_GROUP (Medium) - Phase 2C
32
+ * - COMPUTER_LEGACY_PROTOCOL (Medium) - Phase 4
33
+ * - COMPUTER_ADMIN_COUNT (Low)
34
+ * - COMPUTER_SMB_SIGNING_DISABLED (Low)
35
+ * - WORKSTATION_IN_SERVER_OU (Low) - Phase 2C
36
+ */
37
+
38
+ import { ADComputer } from '../../../../types/ad.types';
39
+ import { Finding } from '../../../../types/finding.types';
40
+ import { toAffectedComputerEntities, ldapAttrToString } from '../../../../utils/entity-converter';
41
+
42
+ /**
43
+ * Windows FILETIME epoch offset in milliseconds
44
+ * Difference between 1601-01-01 and 1970-01-01
45
+ */
46
+ const FILETIME_EPOCH_OFFSET = 11644473600000;
47
+
48
+ /**
49
+ * Convert any date format to timestamp in milliseconds
50
+ * Handles: Date object, ISO string, FILETIME (number/string), undefined/null
51
+ *
52
+ * @param value - Date in any format
53
+ * @returns Timestamp in milliseconds, or null if invalid
54
+ */
55
+ function toTimestamp(value: any): number | null {
56
+ if (!value) return null;
57
+
58
+ // Already a Date object
59
+ if (value instanceof Date) {
60
+ const time = value.getTime();
61
+ return isNaN(time) ? null : time;
62
+ }
63
+
64
+ // String value
65
+ if (typeof value === 'string') {
66
+ // Try ISO date first (e.g., "2024-01-15T10:30:00.000Z")
67
+ if (value.includes('-') && value.includes('T')) {
68
+ const date = new Date(value);
69
+ const time = date.getTime();
70
+ return isNaN(time) ? null : time;
71
+ }
72
+
73
+ // Try LDAP generalizedTime format (e.g., "20260115123456.0Z" or "20260115123456Z")
74
+ const gtMatch = value.match(/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/);
75
+ if (gtMatch && gtMatch[1] && gtMatch[2] && gtMatch[3] && gtMatch[4] && gtMatch[5] && gtMatch[6]) {
76
+ const date = new Date(
77
+ Date.UTC(
78
+ parseInt(gtMatch[1], 10),
79
+ parseInt(gtMatch[2], 10) - 1,
80
+ parseInt(gtMatch[3], 10),
81
+ parseInt(gtMatch[4], 10),
82
+ parseInt(gtMatch[5], 10),
83
+ parseInt(gtMatch[6], 10)
84
+ )
85
+ );
86
+ const time = date.getTime();
87
+ return isNaN(time) ? null : time;
88
+ }
89
+
90
+ // Try as numeric FILETIME string
91
+ const parsed = parseInt(value, 10);
92
+ if (!isNaN(parsed) && parsed > 0) {
93
+ return filetimeToTimestamp(parsed);
94
+ }
95
+ return null;
96
+ }
97
+
98
+ // Number - could be FILETIME or Unix timestamp
99
+ if (typeof value === 'number') {
100
+ if (value <= 0) return null;
101
+ // FILETIME values are huge (> 100 trillion), Unix timestamps are ~1.7 trillion ms
102
+ if (value > 100000000000000) {
103
+ return filetimeToTimestamp(value);
104
+ }
105
+ // If it's reasonable (after year 2000 and before year 2100)
106
+ if (value > 946684800000 && value < 4102444800000) {
107
+ return value; // Already a Unix timestamp in ms
108
+ }
109
+ // Could be Unix seconds
110
+ if (value > 946684800 && value < 4102444800) {
111
+ return value * 1000;
112
+ }
113
+ // Assume FILETIME
114
+ return filetimeToTimestamp(value);
115
+ }
116
+
117
+ return null;
118
+ }
119
+
120
+ /**
121
+ * Convert Windows FILETIME to Unix timestamp in milliseconds
122
+ */
123
+ function filetimeToTimestamp(filetime: number): number | null {
124
+ // Invalid or "Never" values
125
+ if (filetime === 0 || filetime >= Number.MAX_SAFE_INTEGER) {
126
+ return null;
127
+ }
128
+
129
+ // Convert 100-ns intervals to milliseconds and adjust epoch
130
+ const ms = filetime / 10000 - FILETIME_EPOCH_OFFSET;
131
+
132
+ // Validate reasonable range (year 1970 to 2100)
133
+ if (ms < 0 || ms > 4102444800000) {
134
+ return null;
135
+ }
136
+
137
+ return ms;
138
+ }
139
+
140
+ /**
141
+ * Check for computer with constrained delegation
142
+ */
143
+ export function detectComputerConstrainedDelegation(
144
+ computers: ADComputer[],
145
+ includeDetails: boolean
146
+ ): Finding {
147
+ const affected = computers.filter((c) => {
148
+ const delegateTo = (c as any)['msDS-AllowedToDelegateTo'];
149
+ // Check that attribute exists, is not null, not empty string, and not empty array
150
+ return delegateTo && (Array.isArray(delegateTo) ? delegateTo.length > 0 : delegateTo !== '');
151
+ });
152
+
153
+ return {
154
+ type: 'COMPUTER_CONSTRAINED_DELEGATION',
155
+ severity: 'critical',
156
+ category: 'computers',
157
+ title: 'Computer Constrained Delegation',
158
+ description: 'Computer with constrained Kerberos delegation. Can impersonate users to specified services.',
159
+ count: affected.length,
160
+ affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
161
+ };
162
+ }
163
+
164
+ /**
165
+ * Check for computer with RBCD configured
166
+ */
167
+ export function detectComputerRbcd(computers: ADComputer[], includeDetails: boolean): Finding {
168
+ const affected = computers.filter((c) => {
169
+ const rbcdAttr = (c as any)['msDS-AllowedToActOnBehalfOfOtherIdentity'];
170
+ // Check that attribute exists, is not null, not empty string, and not empty array
171
+ return rbcdAttr && (Array.isArray(rbcdAttr) ? rbcdAttr.length > 0 : rbcdAttr !== '');
172
+ });
173
+
174
+ return {
175
+ type: 'COMPUTER_RBCD',
176
+ severity: 'critical',
177
+ category: 'computers',
178
+ title: 'Computer RBCD',
179
+ description: 'Computer with Resource-Based Constrained Delegation. Enables privilege escalation via RBCD attack.',
180
+ count: affected.length,
181
+ affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
182
+ };
183
+ }
184
+
185
+ /**
186
+ * Check for computer in admin groups
187
+ */
188
+ export function detectComputerInAdminGroup(computers: ADComputer[], includeDetails: boolean): Finding {
189
+ const adminGroups = ['Domain Admins', 'Enterprise Admins'];
190
+
191
+ const affected = computers.filter((c) => {
192
+ const memberOf = (c as any).memberOf;
193
+ if (!memberOf) return false;
194
+ return memberOf.some((dn: string) => adminGroups.some((group) => dn.includes(`CN=${group}`)));
195
+ });
196
+
197
+ return {
198
+ type: 'COMPUTER_IN_ADMIN_GROUP',
199
+ severity: 'critical',
200
+ category: 'computers',
201
+ title: 'Computer in Admin Group',
202
+ description: 'Computer account in Domain Admins or Enterprise Admins. Computer compromise leads to domain admin access.',
203
+ count: affected.length,
204
+ affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
205
+ };
206
+ }
207
+
208
+ /**
209
+ * Check for computer with DCSync rights
210
+ */
211
+ export function detectComputerDcsyncRights(computers: ADComputer[], includeDetails: boolean): Finding {
212
+ // Note: This requires ACL analysis which would be done in permissions detector
213
+ // For now, we detect computers with the replication rights attribute
214
+ const affected = computers.filter((c) => {
215
+ return 'replicationRights' in c && (c as any).replicationRights;
216
+ });
217
+
218
+ return {
219
+ type: 'COMPUTER_DCSYNC_RIGHTS',
220
+ severity: 'critical',
221
+ category: 'computers',
222
+ title: 'Computer DCSync Rights',
223
+ description: 'Computer with DCSync replication rights. Can extract all domain password hashes.',
224
+ count: affected.length,
225
+ affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
226
+ };
227
+ }
228
+
229
+ /**
230
+ * Check for computer with unconstrained delegation
231
+ */
232
+ export function detectComputerUnconstrainedDelegation(
233
+ computers: ADComputer[],
234
+ includeDetails: boolean
235
+ ): Finding {
236
+ const affected = computers.filter((c) => {
237
+ const uac = (c as any).userAccountControl;
238
+ if (!uac) return false;
239
+ return (uac & 0x80000) !== 0; // TRUSTED_FOR_DELEGATION
240
+ });
241
+
242
+ return {
243
+ type: 'COMPUTER_UNCONSTRAINED_DELEGATION',
244
+ severity: 'critical',
245
+ category: 'computers',
246
+ title: 'Computer Unconstrained Delegation',
247
+ description: 'Computer with unconstrained delegation enabled. Servers can be used for privilege escalation attacks.',
248
+ count: affected.length,
249
+ affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
250
+ };
251
+ }
252
+
253
+ /**
254
+ * Check for stale/inactive computers (90+ days)
255
+ * Note: Computers that have NEVER logged on are handled by COMPUTER_NEVER_LOGGED_ON
256
+ */
257
+ export function detectComputerStaleInactive(computers: ADComputer[], includeDetails: boolean): Finding {
258
+ const now = Date.now();
259
+ const ninetyDaysAgo = now - 90 * 24 * 60 * 60 * 1000;
260
+
261
+ const affected = computers.filter((c) => {
262
+ // Only check enabled computers
263
+ if (!c.enabled) return false;
264
+
265
+ // Try lastLogon first, then lastLogonTimestamp (replicated, more reliable)
266
+ const lastLogonTime =
267
+ toTimestamp(c.lastLogon) ?? toTimestamp((c as any)['lastLogonTimestamp']);
268
+
269
+ // Skip if no logon time (handled by COMPUTER_NEVER_LOGGED_ON)
270
+ if (!lastLogonTime) return false;
271
+
272
+ return lastLogonTime < ninetyDaysAgo;
273
+ });
274
+
275
+ return {
276
+ type: 'COMPUTER_STALE_INACTIVE',
277
+ severity: 'high',
278
+ category: 'computers',
279
+ title: 'Computer Stale/Inactive',
280
+ description: 'Computer inactive for 90+ days. Orphaned computer accounts could be exploited without detection.',
281
+ count: affected.length,
282
+ affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
283
+ };
284
+ }
285
+
286
+ /**
287
+ * Check for computer with old password (>90 days)
288
+ */
289
+ export function detectComputerPasswordOld(computers: ADComputer[], includeDetails: boolean): Finding {
290
+ const now = Date.now();
291
+ const ninetyDaysAgo = now - 90 * 24 * 60 * 60 * 1000;
292
+
293
+ // Debug: track why computers are filtered out
294
+ let debugStats = { total: 0, disabled: 0, noPwdLastSet: 0, recent: 0, old: 0 };
295
+
296
+ const affected = computers.filter((c) => {
297
+ debugStats.total++;
298
+ // Only check enabled computers
299
+ if (!c.enabled) {
300
+ debugStats.disabled++;
301
+ return false;
302
+ }
303
+
304
+ // Try pwdLastSet first, then passwordLastSet
305
+ const pwdLastSet = toTimestamp((c as any).pwdLastSet) ?? toTimestamp((c as any).passwordLastSet);
306
+ if (!pwdLastSet) {
307
+ debugStats.noPwdLastSet++;
308
+ return false;
309
+ }
310
+
311
+ if (pwdLastSet < ninetyDaysAgo) {
312
+ debugStats.old++;
313
+ return true;
314
+ }
315
+ debugStats.recent++;
316
+ return false;
317
+ });
318
+
319
+ return {
320
+ type: 'COMPUTER_PASSWORD_OLD',
321
+ severity: 'high',
322
+ category: 'computers',
323
+ title: 'Computer Password Old',
324
+ description: 'Computer password not changed for 90+ days. Increases risk of password-based attacks.',
325
+ count: affected.length,
326
+ affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
327
+ details: {
328
+ debug: debugStats,
329
+ threshold: '90 days',
330
+ checkDate: new Date(ninetyDaysAgo).toISOString(),
331
+ },
332
+ };
333
+ }
334
+
335
+ /**
336
+ * Check for computer with SPNs (Kerberoastable)
337
+ */
338
+ export function detectComputerWithSpns(computers: ADComputer[], includeDetails: boolean): Finding {
339
+ const affected = computers.filter((c) => {
340
+ const spns = (c as any).servicePrincipalName;
341
+ return spns && spns.length > 0;
342
+ });
343
+
344
+ return {
345
+ type: 'COMPUTER_WITH_SPNS',
346
+ severity: 'high',
347
+ category: 'computers',
348
+ title: 'Computer with SPNs',
349
+ description: 'Computer with Service Principal Names. Enables Kerberoasting attack against computer account.',
350
+ count: affected.length,
351
+ affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
352
+ };
353
+ }
354
+
355
+ /**
356
+ * Check for computer without LAPS
357
+ * Checks for both legacy LAPS (ms-Mcs-AdmPwd) and Windows LAPS (msLAPS-Password)
358
+ *
359
+ * Note: Password attributes may not be readable without LAPS admin rights,
360
+ * so we also check expiration time attributes which are more accessible.
361
+ * If LAPS schema is not extended, ALL computers are flagged.
362
+ */
363
+ export function detectComputerNoLaps(computers: ADComputer[], includeDetails: boolean): Finding {
364
+ // Stats for debugging
365
+ const total = computers.length;
366
+ const disabled = computers.filter((c) => !c.enabled).length;
367
+ const enabled = total - disabled;
368
+
369
+ // Count DCs (SERVER_TRUST_ACCOUNT = 0x2000)
370
+ const domainControllers = computers.filter((c) => {
371
+ const uac = (c as any).userAccountControl;
372
+ return uac && (uac & 0x2000) !== 0;
373
+ }).length;
374
+
375
+ // Check if any computer has LAPS attributes (indicates schema is extended)
376
+ let hasLegacyLapsSchema = false;
377
+ let hasWindowsLapsSchema = false;
378
+ let withLegacyLaps = 0;
379
+ let withWindowsLaps = 0;
380
+
381
+ const affected = computers.filter((c) => {
382
+ // Only check enabled computers (workstations and servers, not DCs)
383
+ if (!c.enabled) return false;
384
+
385
+ // Skip Domain Controllers (they don't use LAPS)
386
+ const uac = (c as any).userAccountControl;
387
+ if (uac && (uac & 0x2000) !== 0) return false; // SERVER_TRUST_ACCOUNT = DC
388
+
389
+ const comp = c as Record<string, unknown>;
390
+
391
+ // Check for legacy LAPS - password or expiration time
392
+ // Note: LDAP may return [] for non-existent attributes, so check for actual values
393
+ const legacyLaps = comp['ms-Mcs-AdmPwd'];
394
+ const legacyLapsExpiry = comp['ms-Mcs-AdmPwdExpirationTime'];
395
+
396
+ // Helper to check if value is a real LAPS value (not empty/null/undefined/[])
397
+ const isValidLapsValue = (val: unknown): boolean => {
398
+ if (val === undefined || val === null || val === '') return false;
399
+ if (Array.isArray(val) && val.length === 0) return false;
400
+ if (val === '0' || val === 0) return false;
401
+ return true;
402
+ };
403
+
404
+ // Track if schema attributes exist with actual values
405
+ if (isValidLapsValue(legacyLaps) || isValidLapsValue(legacyLapsExpiry)) {
406
+ hasLegacyLapsSchema = true;
407
+ }
408
+
409
+ const hasLegacyLaps = isValidLapsValue(legacyLaps) || isValidLapsValue(legacyLapsExpiry);
410
+ if (hasLegacyLaps) withLegacyLaps++;
411
+
412
+ // Check for Windows LAPS - password or expiration time
413
+ const windowsLaps = comp['msLAPS-Password'];
414
+ const windowsLapsExpiry = comp['msLAPS-PasswordExpirationTime'];
415
+
416
+ // Track if schema attributes exist with actual values
417
+ if (isValidLapsValue(windowsLaps) || isValidLapsValue(windowsLapsExpiry)) {
418
+ hasWindowsLapsSchema = true;
419
+ }
420
+
421
+ const hasWindowsLaps = isValidLapsValue(windowsLaps) || isValidLapsValue(windowsLapsExpiry);
422
+ if (hasWindowsLaps) withWindowsLaps++;
423
+
424
+ // No LAPS if neither legacy nor Windows LAPS is configured
425
+ return !hasLegacyLaps && !hasWindowsLaps;
426
+ });
427
+
428
+ // Determine severity based on schema availability
429
+ const schemaExtended = hasLegacyLapsSchema || hasWindowsLapsSchema;
430
+ const eligibleComputers = enabled - domainControllers;
431
+
432
+ return {
433
+ type: 'COMPUTER_NO_LAPS',
434
+ severity: !schemaExtended ? 'critical' : 'high', // Critical if schema not extended
435
+ category: 'computers',
436
+ title: !schemaExtended ? 'LAPS Not Deployed (Schema Not Extended)' : 'Computer No LAPS',
437
+ description: !schemaExtended
438
+ ? 'LAPS schema is not extended in Active Directory. ALL local admin passwords are unmanaged and likely shared across computers.'
439
+ : 'Computer without LAPS deployed. Shared/static local admin passwords across workstations.',
440
+ count: affected.length,
441
+ affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
442
+ details: {
443
+ debug: {
444
+ total,
445
+ enabled,
446
+ disabled,
447
+ domainControllers,
448
+ eligibleComputers,
449
+ withLegacyLaps,
450
+ withWindowsLaps,
451
+ withoutLaps: affected.length,
452
+ schemaExtended,
453
+ hasLegacyLapsSchema,
454
+ hasWindowsLapsSchema,
455
+ },
456
+ recommendation: !schemaExtended
457
+ ? 'Install LAPS (legacy or Windows LAPS) and extend the AD schema. Then deploy via GPO.'
458
+ : 'Deploy LAPS to remaining computers via GPO.',
459
+ },
460
+ };
461
+ }
462
+
463
+ /**
464
+ * Check for computer with ACL abuse potential
465
+ */
466
+ export function detectComputerAclAbuse(computers: ADComputer[], includeDetails: boolean): Finding {
467
+ // Note: This requires ACL analysis which would be done in permissions detector
468
+ // For now, we detect computers with suspicious ACL attributes
469
+ const affected = computers.filter((c) => {
470
+ return 'dangerousAcl' in c && (c as any).dangerousAcl;
471
+ });
472
+
473
+ return {
474
+ type: 'COMPUTER_ACL_ABUSE',
475
+ severity: 'high',
476
+ category: 'computers',
477
+ title: 'Computer ACL Abuse',
478
+ description: 'Computer with dangerous ACL permissions. Can modify computer object properties and escalate privileges.',
479
+ count: affected.length,
480
+ affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
481
+ };
482
+ }
483
+
484
+ /**
485
+ * Check for disabled computers not deleted (>30 days)
486
+ */
487
+ export function detectComputerDisabledNotDeleted(computers: ADComputer[], includeDetails: boolean): Finding {
488
+ const now = Date.now();
489
+ const thirtyDaysAgo = now - 30 * 24 * 60 * 60 * 1000;
490
+
491
+ // Debug stats
492
+ const debugStats: {
493
+ total: number;
494
+ enabled: number;
495
+ noWhenChanged: number;
496
+ recent: number;
497
+ old: number;
498
+ sampleDates: {
499
+ name: string;
500
+ raw: unknown;
501
+ rawType: string;
502
+ rawStringified: string;
503
+ parsed: number | null;
504
+ asIso: string | null;
505
+ whenCreated: unknown;
506
+ }[];
507
+ } = { total: 0, enabled: 0, noWhenChanged: 0, recent: 0, old: 0, sampleDates: [] };
508
+
509
+ const affected = computers.filter((c) => {
510
+ debugStats.total++;
511
+ if (c.enabled) {
512
+ debugStats.enabled++;
513
+ return false;
514
+ }
515
+
516
+ // Use toTimestamp for robust date handling
517
+ const rawWhenChanged = (c as any).whenChanged;
518
+ const whenChangedTime = toTimestamp(rawWhenChanged);
519
+
520
+ // Capture sample dates for debugging (first 5 disabled computers)
521
+ if (debugStats.sampleDates.length < 5) {
522
+ debugStats.sampleDates.push({
523
+ name: c.sAMAccountName || 'unknown',
524
+ raw: rawWhenChanged,
525
+ rawType: typeof rawWhenChanged,
526
+ rawStringified: JSON.stringify(rawWhenChanged),
527
+ parsed: whenChangedTime,
528
+ asIso: whenChangedTime ? new Date(whenChangedTime).toISOString() : null,
529
+ whenCreated: (c as any).whenCreated, // Also capture whenCreated for comparison
530
+ });
531
+ }
532
+
533
+ if (!whenChangedTime) {
534
+ debugStats.noWhenChanged++;
535
+ return false;
536
+ }
537
+
538
+ if (whenChangedTime < thirtyDaysAgo) {
539
+ debugStats.old++;
540
+ return true;
541
+ }
542
+ debugStats.recent++;
543
+ return false;
544
+ });
545
+
546
+ return {
547
+ type: 'COMPUTER_DISABLED_NOT_DELETED',
548
+ severity: 'medium',
549
+ category: 'computers',
550
+ title: 'Computer Disabled Not Deleted',
551
+ description: 'Disabled computer not deleted (>30 days). Clutters AD, potential security oversight.',
552
+ count: affected.length,
553
+ affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
554
+ details: {
555
+ debug: debugStats,
556
+ threshold: '30 days',
557
+ checkDate: new Date(thirtyDaysAgo).toISOString(),
558
+ nowDate: new Date(now).toISOString(),
559
+ },
560
+ };
561
+ }
562
+
563
+ /**
564
+ * Check for computer in default Computers container (not organized into OUs)
565
+ *
566
+ * Computers in "CN=Computers,DC=..." are in the default container, not an OU.
567
+ * This indicates they haven't been organized and may not receive proper GPOs.
568
+ *
569
+ * Note: This is different from PingCastle's "Computer_Wrong_OU" which may
570
+ * check for different criteria. We check for computers in default container.
571
+ */
572
+ export function detectComputerWrongOu(computers: ADComputer[], includeDetails: boolean): Finding {
573
+ const affected = computers.filter((c) => {
574
+ // Check if computer is directly in the default Computers container
575
+ // DN format: CN=COMPUTER$,CN=Computers,DC=domain,DC=com
576
+ const dnLower = c.dn.toLowerCase();
577
+
578
+ // Check if it's in CN=Computers (not OU=)
579
+ // This catches: CN=PC01$,CN=Computers,DC=example,DC=com
580
+ const isInDefaultContainer = dnLower.includes(',cn=computers,dc=');
581
+
582
+ return isInDefaultContainer;
583
+ });
584
+
585
+ return {
586
+ type: 'COMPUTER_WRONG_OU',
587
+ severity: 'medium',
588
+ category: 'computers',
589
+ title: 'Computer in Default Container',
590
+ description:
591
+ 'Computer in default Computers container instead of an organizational OU. ' +
592
+ 'May not receive proper Group Policy and indicates lack of organization.',
593
+ count: affected.length,
594
+ affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
595
+ };
596
+ }
597
+
598
+ /**
599
+ * Check for computer with weak encryption (DES/RC4 only)
600
+ */
601
+ export function detectComputerWeakEncryption(computers: ADComputer[], includeDetails: boolean): Finding {
602
+ const affected = computers.filter((c) => {
603
+ const encTypes = (c as any)['msDS-SupportedEncryptionTypes'];
604
+ if (typeof encTypes !== 'number') return false;
605
+ // Check if only DES/RC4 (no AES)
606
+ return (encTypes & 0x18) === 0 && (encTypes & 0x7) !== 0;
607
+ });
608
+
609
+ return {
610
+ type: 'COMPUTER_WEAK_ENCRYPTION',
611
+ severity: 'medium',
612
+ category: 'computers',
613
+ title: 'Computer Weak Encryption',
614
+ description: 'Computer with weak encryption types (DES/RC4 only). Vulnerable to Kerberos downgrade attacks.',
615
+ count: affected.length,
616
+ affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
617
+ };
618
+ }
619
+
620
+ /**
621
+ * Check for computer description with sensitive data
622
+ */
623
+ export function detectComputerDescriptionSensitive(computers: ADComputer[], includeDetails: boolean): Finding {
624
+ const sensitivePatterns = [
625
+ /password|passwd|pwd/i,
626
+ /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/, // IP addresses
627
+ /admin|root|sa/i,
628
+ ];
629
+
630
+ const affected = computers.filter((c) => {
631
+ const rawDesc = (c as any).description;
632
+ const description = ldapAttrToString(rawDesc);
633
+ if (!description) return false;
634
+ return sensitivePatterns.some((pattern) => pattern.test(description));
635
+ });
636
+
637
+ return {
638
+ type: 'COMPUTER_DESCRIPTION_SENSITIVE',
639
+ severity: 'medium',
640
+ category: 'computers',
641
+ title: 'Computer Description Sensitive',
642
+ description: 'Computer description contains sensitive data (passwords, IPs, etc.). Information disclosure.',
643
+ count: affected.length,
644
+ affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
645
+ };
646
+ }
647
+
648
+ /**
649
+ * Check for Pre-Windows 2000 computer accounts
650
+ */
651
+ export function detectComputerPreWindows2000(computers: ADComputer[], includeDetails: boolean): Finding {
652
+ const affected = computers.filter((c) => {
653
+ const os = ldapAttrToString(c.operatingSystem);
654
+ if (!os) return false;
655
+ return /Windows NT|Windows 2000|Windows 95|Windows 98/i.test(os);
656
+ });
657
+
658
+ return {
659
+ type: 'COMPUTER_PRE_WINDOWS_2000',
660
+ severity: 'medium',
661
+ category: 'computers',
662
+ title: 'Pre-Windows 2000 Computer',
663
+ description: 'Pre-Windows 2000 compatible computer. Weak security settings, potential compatibility exploits.',
664
+ count: affected.length,
665
+ affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
666
+ };
667
+ }
668
+
669
+ /**
670
+ * Check for computer with adminCount attribute
671
+ */
672
+ export function detectComputerAdminCount(computers: ADComputer[], includeDetails: boolean): Finding {
673
+ const affected = computers.filter((c) => {
674
+ const adminCount = (c as any).adminCount;
675
+ return adminCount === 1;
676
+ });
677
+
678
+ return {
679
+ type: 'COMPUTER_ADMIN_COUNT',
680
+ severity: 'low',
681
+ category: 'computers',
682
+ title: 'Computer adminCount Set',
683
+ description: 'Computer with adminCount attribute set to 1. May indicate current or former administrative privileges.',
684
+ count: affected.length,
685
+ affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
686
+ };
687
+ }
688
+
689
+ /**
690
+ * Check for computer with SMB signing disabled
691
+ */
692
+ export function detectComputerSmbSigningDisabled(computers: ADComputer[], includeDetails: boolean): Finding {
693
+ // Note: This would typically require querying computer configuration
694
+ // For now, we check for an attribute that would be set if SMB signing is disabled
695
+ const affected = computers.filter((c) => {
696
+ return 'smbSigningDisabled' in c && (c as any).smbSigningDisabled;
697
+ });
698
+
699
+ return {
700
+ type: 'COMPUTER_SMB_SIGNING_DISABLED',
701
+ severity: 'low',
702
+ category: 'computers',
703
+ title: 'Computer SMB Signing Disabled',
704
+ description: 'Computer with SMB signing disabled. Vulnerable to SMB relay attacks (informational finding).',
705
+ count: affected.length,
706
+ affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
707
+ };
708
+ }
709
+
710
+ /**
711
+ * Obsolete OS patterns for detection
712
+ */
713
+ const OBSOLETE_OS_PATTERNS = [
714
+ {
715
+ pattern: /Windows XP/i,
716
+ type: 'COMPUTER_OS_OBSOLETE_XP',
717
+ severity: 'critical' as const,
718
+ osName: 'Windows XP',
719
+ },
720
+ {
721
+ pattern: /Server 2003/i,
722
+ type: 'COMPUTER_OS_OBSOLETE_2003',
723
+ severity: 'critical' as const,
724
+ osName: 'Windows Server 2003',
725
+ },
726
+ {
727
+ pattern: /Server 2008(?!\s*R2)/i, // 2008 but not 2008 R2
728
+ type: 'COMPUTER_OS_OBSOLETE_2008',
729
+ severity: 'high' as const,
730
+ osName: 'Windows Server 2008',
731
+ },
732
+ {
733
+ pattern: /Windows Vista/i,
734
+ type: 'COMPUTER_OS_OBSOLETE_VISTA',
735
+ severity: 'high' as const,
736
+ osName: 'Windows Vista',
737
+ },
738
+ ];
739
+
740
+ /**
741
+ * Check for computers running obsolete operating systems
742
+ * Returns multiple findings, one per OS type detected
743
+ */
744
+ export function detectComputerObsoleteOS(
745
+ computers: ADComputer[],
746
+ includeDetails: boolean
747
+ ): Finding[] {
748
+ return OBSOLETE_OS_PATTERNS.map(({ pattern, type, severity, osName }) => {
749
+ const affected = computers.filter((c) => {
750
+ const os = ldapAttrToString(c.operatingSystem);
751
+ return os && pattern.test(os);
752
+ });
753
+
754
+ return {
755
+ type,
756
+ severity,
757
+ category: 'computers' as const,
758
+ title: `Obsolete OS: ${osName}`,
759
+ description: `Computers running ${osName}, an unsupported operating system. No security patches available, making these systems highly vulnerable to exploitation.`,
760
+ count: affected.length,
761
+ affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
762
+ };
763
+ }).filter((f) => f.count > 0);
764
+ }
765
+
766
+ /**
767
+ * Check for computers that have never logged on
768
+ * Enabled computers with no lastLogon date may indicate orphaned or unused accounts
769
+ *
770
+ * Checks both lastLogon (local to DC) and lastLogonTimestamp (replicated).
771
+ */
772
+ export function detectComputerNeverLoggedOn(
773
+ computers: ADComputer[],
774
+ includeDetails: boolean
775
+ ): Finding {
776
+ const affected = computers.filter((c) => {
777
+ if (!c.enabled) return false;
778
+
779
+ // Check both lastLogon and lastLogonTimestamp
780
+ const lastLogonTime = toTimestamp(c.lastLogon) ?? toTimestamp((c as any)['lastLogonTimestamp']);
781
+
782
+ // No logon time means never logged on
783
+ return !lastLogonTime;
784
+ });
785
+
786
+ return {
787
+ type: 'COMPUTER_NEVER_LOGGED_ON',
788
+ severity: 'medium',
789
+ category: 'computers',
790
+ title: 'Computer Never Logged On',
791
+ description:
792
+ 'Enabled computer accounts that have never authenticated to the domain. These may be orphaned accounts from failed deployments or unused systems that should be cleaned up.',
793
+ count: affected.length,
794
+ affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
795
+ };
796
+ }
797
+
798
+ /**
799
+ * Check for pre-created computer accounts (disabled + never logged on)
800
+ * These are staging accounts that were created but never used.
801
+ * PingCastle: Computer_Pre_Created
802
+ */
803
+ export function detectComputerPreCreated(
804
+ computers: ADComputer[],
805
+ includeDetails: boolean
806
+ ): Finding {
807
+ const affected = computers.filter((c) => {
808
+ // Disabled computer that has never logged on
809
+ if (c.enabled) return false;
810
+
811
+ // Check if it has never logged on
812
+ const lastLogonTime = toTimestamp(c.lastLogon) ?? toTimestamp((c as any)['lastLogonTimestamp']);
813
+ return !lastLogonTime;
814
+ });
815
+
816
+ return {
817
+ type: 'COMPUTER_PRE_CREATED',
818
+ severity: 'medium',
819
+ category: 'computers',
820
+ title: 'Computer Pre-Created (Staging)',
821
+ description:
822
+ 'Disabled computer accounts that have never logged on. These are staging accounts that were created but never deployed. Should be reviewed and cleaned up.',
823
+ count: affected.length,
824
+ affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
825
+ };
826
+ }
827
+
828
+ // ==================== PHASE 2C DETECTORS ====================
829
+
830
+ /**
831
+ * Detect Domain Controllers not in Domain Controllers OU
832
+ * DCs should always be in the default Domain Controllers OU
833
+ */
834
+ export function detectDcNotInDcOu(computers: ADComputer[], includeDetails: boolean): Finding {
835
+ const dcPatterns = [/^DC\d*/i, /domain controller/i];
836
+
837
+ const affected = computers.filter((c) => {
838
+ // Check if it's a domain controller
839
+ const dnsName = typeof c.dNSHostName === 'string' ? c.dNSHostName : (Array.isArray(c.dNSHostName) ? c.dNSHostName[0] : '');
840
+ const isDC =
841
+ dcPatterns.some((p) => p.test(c.sAMAccountName || '')) ||
842
+ (dnsName && dnsName.toLowerCase().includes('dc')) ||
843
+ ((c.userAccountControl ?? 0) & 0x2000) !== 0; // SERVER_TRUST_ACCOUNT flag
844
+
845
+ if (!isDC) return false;
846
+
847
+ // Check if it's in the Domain Controllers OU
848
+ const isInDCOU = c.dn.toLowerCase().includes('ou=domain controllers');
849
+
850
+ return !isInDCOU;
851
+ });
852
+
853
+ return {
854
+ type: 'DC_NOT_IN_DC_OU',
855
+ severity: 'high',
856
+ category: 'computers',
857
+ title: 'Domain Controller Not in Domain Controllers OU',
858
+ description:
859
+ 'Domain Controllers found outside the Domain Controllers OU. This may indicate misconfiguration or an attempt to hide a rogue DC.',
860
+ count: affected.length,
861
+ affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
862
+ details:
863
+ affected.length > 0
864
+ ? {
865
+ recommendation:
866
+ 'Move all Domain Controllers to the Domain Controllers OU for proper GPO application and management.',
867
+ risks: [
868
+ 'GPOs targeting Domain Controllers OU may not apply',
869
+ 'May indicate rogue or compromised DC',
870
+ 'Security baselines may not be applied correctly',
871
+ ],
872
+ }
873
+ : undefined,
874
+ };
875
+ }
876
+
877
+ /**
878
+ * Detect duplicate SPNs across computers
879
+ * Duplicate SPNs cause authentication failures
880
+ */
881
+ export function detectComputerDuplicateSpn(computers: ADComputer[], includeDetails: boolean): Finding {
882
+ const spnMap = new Map<string, ADComputer[]>();
883
+
884
+ // Build SPN to computer mapping
885
+ for (const computer of computers) {
886
+ const spns = computer['servicePrincipalName'];
887
+ if (!spns || !Array.isArray(spns)) continue;
888
+
889
+ for (const spn of spns) {
890
+ const normalizedSpn = (spn as string).toLowerCase();
891
+ if (!spnMap.has(normalizedSpn)) {
892
+ spnMap.set(normalizedSpn, []);
893
+ }
894
+ spnMap.get(normalizedSpn)!.push(computer);
895
+ }
896
+ }
897
+
898
+ // Find duplicates
899
+ const duplicateComputers = new Set<ADComputer>();
900
+ const duplicateSpns: { spn: string; computers: string[] }[] = [];
901
+
902
+ for (const [spn, computersList] of spnMap.entries()) {
903
+ if (computersList.length > 1) {
904
+ duplicateSpns.push({
905
+ spn,
906
+ computers: computersList.map((c) => c.sAMAccountName || c.dn),
907
+ });
908
+ computersList.forEach((c) => duplicateComputers.add(c));
909
+ }
910
+ }
911
+
912
+ return {
913
+ type: 'COMPUTER_DUPLICATE_SPN',
914
+ severity: 'medium',
915
+ category: 'computers',
916
+ title: 'Duplicate SPNs Detected',
917
+ description:
918
+ 'Multiple computers share the same Service Principal Name. This causes Kerberos authentication failures.',
919
+ count: duplicateComputers.size,
920
+ affectedEntities: includeDetails ? toAffectedComputerEntities(Array.from(duplicateComputers)) : undefined,
921
+ details:
922
+ duplicateSpns.length > 0
923
+ ? {
924
+ duplicateSpns: duplicateSpns.slice(0, 10), // Show first 10
925
+ totalDuplicates: duplicateSpns.length,
926
+ recommendation:
927
+ 'Remove duplicate SPNs using setspn -D. Ensure each SPN is unique across the domain.',
928
+ }
929
+ : undefined,
930
+ };
931
+ }
932
+
933
+ /**
934
+ * Detect servers without local admin groups properly configured
935
+ * Servers should have documented local administrators
936
+ */
937
+ export function detectServerNoAdminGroup(computers: ADComputer[], includeDetails: boolean): Finding {
938
+ const serverPatterns = [/server/i, /^srv/i, /^sql/i, /^web/i, /^app/i, /^db/i, /^file/i];
939
+ const serverOsPatterns = [/server/i];
940
+
941
+ const affected = computers.filter((c) => {
942
+ // Check if it's a server
943
+ const os = ldapAttrToString(c.operatingSystem);
944
+ const isServer =
945
+ serverPatterns.some((p) => p.test(c.sAMAccountName || '')) ||
946
+ (os && serverOsPatterns.some((p) => p.test(os)));
947
+
948
+ if (!isServer) return false;
949
+
950
+ // Check if it's enabled
951
+ if (!c.enabled) return false;
952
+
953
+ // Check if there's a corresponding admin group (naming convention: ServerName-Admins or similar)
954
+ // This is a heuristic - we flag servers that might not have managed admin groups
955
+ // In practice, this would need to be verified against a CMDB or documented standard
956
+
957
+ // Flag if it's a server with description indicating it's unmanaged
958
+ const rawDesc = c['description'];
959
+ const description = (typeof rawDesc === 'string' ? rawDesc : (Array.isArray(rawDesc) ? rawDesc[0] : '') || '').toLowerCase();
960
+ const isUnmanaged =
961
+ description.includes('unmanaged') ||
962
+ description.includes('legacy') ||
963
+ description.includes('deprecated');
964
+
965
+ return isUnmanaged;
966
+ });
967
+
968
+ return {
969
+ type: 'SERVER_NO_ADMIN_GROUP',
970
+ severity: 'medium',
971
+ category: 'computers',
972
+ title: 'Server Without Managed Admin Group',
973
+ description:
974
+ 'Servers identified as unmanaged or without proper administrative group documentation. Local admin access may not be properly controlled.',
975
+ count: affected.length,
976
+ affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
977
+ details:
978
+ affected.length > 0
979
+ ? {
980
+ recommendation:
981
+ 'Create dedicated admin groups for each server (e.g., SRV01-Admins) and document access.',
982
+ risks: [
983
+ 'Unknown administrators may have access',
984
+ 'Audit trail for admin actions may be incomplete',
985
+ 'Compliance violations for access management',
986
+ ],
987
+ }
988
+ : undefined,
989
+ };
990
+ }
991
+
992
+ /**
993
+ * Detect workstations in server OUs
994
+ * Workstations should be in workstation OUs, not server OUs
995
+ */
996
+ export function detectWorkstationInServerOu(computers: ADComputer[], includeDetails: boolean): Finding {
997
+ const serverOuPatterns = [/ou=servers/i, /ou=server/i, /ou=datacenter/i, /ou=production/i];
998
+ const workstationOsPatterns = [/windows 10/i, /windows 11/i, /windows 7/i, /windows 8/i];
999
+ const workstationNamePatterns = [/^ws/i, /^pc/i, /^laptop/i, /^desktop/i, /^nb/i];
1000
+
1001
+ const affected = computers.filter((c) => {
1002
+ // Check if it's in a server OU
1003
+ const isInServerOU = serverOuPatterns.some((p) => p.test(c.dn));
1004
+ if (!isInServerOU) return false;
1005
+
1006
+ // Check if it's actually a workstation (not a server)
1007
+ const os = ldapAttrToString(c.operatingSystem);
1008
+ const isWorkstation =
1009
+ workstationNamePatterns.some((p) => p.test(c.sAMAccountName || '')) ||
1010
+ (os && workstationOsPatterns.some((p) => p.test(os)));
1011
+
1012
+ return isWorkstation;
1013
+ });
1014
+
1015
+ return {
1016
+ type: 'WORKSTATION_IN_SERVER_OU',
1017
+ severity: 'low',
1018
+ category: 'computers',
1019
+ title: 'Workstation in Server OU',
1020
+ description:
1021
+ 'Workstation computers found in server OUs. This causes incorrect GPO application and may indicate organizational issues.',
1022
+ count: affected.length,
1023
+ affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
1024
+ details:
1025
+ affected.length > 0
1026
+ ? {
1027
+ recommendation: 'Move workstations to appropriate workstation OUs for proper GPO targeting.',
1028
+ impact: 'Server-targeted GPOs may apply to workstations causing configuration issues.',
1029
+ }
1030
+ : undefined,
1031
+ };
1032
+ }
1033
+
1034
+ /**
1035
+ * Detect computers without BitLocker encryption
1036
+ *
1037
+ * Computers without disk encryption are vulnerable to physical attacks
1038
+ * where hard drives can be removed and read.
1039
+ *
1040
+ * @param computers - Array of AD computers
1041
+ * @param includeDetails - Whether to include affected entity details
1042
+ * @returns Finding for COMPUTER_NO_BITLOCKER
1043
+ */
1044
+ export function detectComputerNoBitlocker(
1045
+ computers: ADComputer[],
1046
+ includeDetails: boolean
1047
+ ): Finding {
1048
+ // BitLocker status is stored in ms-FVE-RecoveryInformation objects under the computer
1049
+ // We can check for msDS-isGC or look for recovery info attributes
1050
+ // For now, we check servers (not workstations) that likely need encryption
1051
+ const serversWithoutBitlocker = computers.filter((c) => {
1052
+ if (!c.enabled) return false;
1053
+ const os = ldapAttrToString(c.operatingSystem).toLowerCase();
1054
+ const isServer = os.includes('server');
1055
+ // Check if BitLocker recovery info exists (would need separate query)
1056
+ // For now, flag servers that might need BitLocker review
1057
+ const hasBitlockerInfo = (c as Record<string, unknown>)['ms-FVE-RecoveryInformation'] !== undefined;
1058
+ return isServer && !hasBitlockerInfo;
1059
+ });
1060
+
1061
+ return {
1062
+ type: 'COMPUTER_NO_BITLOCKER',
1063
+ severity: 'high',
1064
+ category: 'computers',
1065
+ title: 'BitLocker Not Detected',
1066
+ description:
1067
+ 'Servers without BitLocker recovery information in AD. ' +
1068
+ 'Unencrypted disks are vulnerable to physical theft and offline attacks.',
1069
+ count: serversWithoutBitlocker.length,
1070
+ affectedEntities: includeDetails
1071
+ ? toAffectedComputerEntities(serversWithoutBitlocker)
1072
+ : undefined,
1073
+ details: {
1074
+ recommendation:
1075
+ 'Enable BitLocker on all servers and configure AD backup of recovery keys.',
1076
+ note: 'This detection checks for ms-FVE-RecoveryInformation in AD. Standalone BitLocker may not be detected.',
1077
+ },
1078
+ };
1079
+ }
1080
+
1081
+ /**
1082
+ * Detect computers using legacy protocols
1083
+ *
1084
+ * Computers configured to use legacy/insecure protocols like SMBv1,
1085
+ * NTLMv1, or LM are vulnerable to various attacks.
1086
+ *
1087
+ * @param computers - Array of AD computers
1088
+ * @param includeDetails - Whether to include affected entity details
1089
+ * @returns Finding for COMPUTER_LEGACY_PROTOCOL
1090
+ */
1091
+ export function detectComputerLegacyProtocol(
1092
+ computers: ADComputer[],
1093
+ includeDetails: boolean
1094
+ ): Finding {
1095
+ // Check for computers with legacy OS that likely use legacy protocols
1096
+ const legacyOsPatterns = [
1097
+ /Windows XP/i,
1098
+ /Windows 2000/i,
1099
+ /Windows NT/i,
1100
+ /Server 2003/i,
1101
+ /Windows Vista/i,
1102
+ ];
1103
+
1104
+ // Also check msDS-SupportedEncryptionTypes for weak encryption
1105
+ const affected = computers.filter((c) => {
1106
+ if (!c.enabled) return false;
1107
+ const os = ldapAttrToString(c.operatingSystem);
1108
+
1109
+ // Legacy OS definitely uses legacy protocols
1110
+ const hasLegacyOs = legacyOsPatterns.some((p) => p.test(os));
1111
+ if (hasLegacyOs) return true;
1112
+
1113
+ // Check supported encryption types (if only DES/RC4)
1114
+ const encTypes = (c as Record<string, unknown>)['msDS-SupportedEncryptionTypes'] as number | undefined;
1115
+ if (encTypes !== undefined) {
1116
+ // If only DES (0x1, 0x2) or RC4 (0x4) are supported, it's legacy
1117
+ const onlyLegacy = (encTypes & 0x18) === 0; // No AES128 (0x8) or AES256 (0x10)
1118
+ if (onlyLegacy && encTypes > 0) return true;
1119
+ }
1120
+
1121
+ return false;
1122
+ });
1123
+
1124
+ return {
1125
+ type: 'COMPUTER_LEGACY_PROTOCOL',
1126
+ severity: 'medium',
1127
+ category: 'computers',
1128
+ title: 'Legacy Protocol Support',
1129
+ description:
1130
+ 'Computers configured to use legacy protocols (SMBv1, NTLMv1, DES/RC4 only). ' +
1131
+ 'These are vulnerable to relay attacks, credential theft, and encryption downgrade.',
1132
+ count: affected.length,
1133
+ affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
1134
+ details: {
1135
+ recommendation:
1136
+ 'Upgrade legacy systems or disable legacy protocols. Enable AES encryption support.',
1137
+ protocols: ['SMBv1', 'NTLMv1', 'DES', 'RC4'],
1138
+ },
1139
+ };
1140
+ }
1141
+
1142
+ /**
1143
+ * Detect all computer-related vulnerabilities
1144
+ */
1145
+ export function detectComputersVulnerabilities(
1146
+ computers: ADComputer[],
1147
+ includeDetails: boolean
1148
+ ): Finding[] {
1149
+ // Get obsolete OS findings (returns array)
1150
+ const obsoleteOsFindings = detectComputerObsoleteOS(computers, includeDetails);
1151
+
1152
+ return [
1153
+ ...obsoleteOsFindings,
1154
+ detectComputerNeverLoggedOn(computers, includeDetails),
1155
+ detectComputerPreCreated(computers, includeDetails),
1156
+ detectComputerConstrainedDelegation(computers, includeDetails),
1157
+ detectComputerRbcd(computers, includeDetails),
1158
+ detectComputerInAdminGroup(computers, includeDetails),
1159
+ detectComputerDcsyncRights(computers, includeDetails),
1160
+ detectComputerUnconstrainedDelegation(computers, includeDetails),
1161
+ detectComputerStaleInactive(computers, includeDetails),
1162
+ detectComputerPasswordOld(computers, includeDetails),
1163
+ detectComputerWithSpns(computers, includeDetails),
1164
+ detectComputerNoLaps(computers, includeDetails),
1165
+ detectComputerAclAbuse(computers, includeDetails),
1166
+ detectComputerDisabledNotDeleted(computers, includeDetails),
1167
+ detectComputerWrongOu(computers, includeDetails),
1168
+ detectComputerWeakEncryption(computers, includeDetails),
1169
+ detectComputerDescriptionSensitive(computers, includeDetails),
1170
+ detectComputerPreWindows2000(computers, includeDetails),
1171
+ detectComputerAdminCount(computers, includeDetails),
1172
+ detectComputerSmbSigningDisabled(computers, includeDetails),
1173
+ // Phase 2C: Enhanced detections
1174
+ detectDcNotInDcOu(computers, includeDetails),
1175
+ detectComputerDuplicateSpn(computers, includeDetails),
1176
+ detectServerNoAdminGroup(computers, includeDetails),
1177
+ detectWorkstationInServerOu(computers, includeDetails),
1178
+ // Phase 4: Advanced detections
1179
+ detectComputerNoBitlocker(computers, includeDetails),
1180
+ detectComputerLegacyProtocol(computers, includeDetails),
1181
+ ].filter((finding) => {
1182
+ // Include findings with count > 0
1183
+ if (finding.count > 0) return true;
1184
+ // Also include findings with debug details (for troubleshooting)
1185
+ if (finding.details?.['debug']) return true;
1186
+ return false;
1187
+ });
1188
+ }