palaryn 0.1.0 → 0.3.2

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 (344) hide show
  1. package/README.md +243 -588
  2. package/dist/sdk/typescript/src/client.js +2 -2
  3. package/dist/sdk/typescript/src/client.js.map +1 -1
  4. package/dist/src/anomaly/detector.d.ts +7 -4
  5. package/dist/src/anomaly/detector.d.ts.map +1 -1
  6. package/dist/src/anomaly/detector.js +22 -12
  7. package/dist/src/anomaly/detector.js.map +1 -1
  8. package/dist/src/audit/logger.d.ts +10 -0
  9. package/dist/src/audit/logger.d.ts.map +1 -1
  10. package/dist/src/audit/logger.js +52 -38
  11. package/dist/src/audit/logger.js.map +1 -1
  12. package/dist/src/auth/routes.d.ts.map +1 -1
  13. package/dist/src/auth/routes.js +35 -0
  14. package/dist/src/auth/routes.js.map +1 -1
  15. package/dist/src/budget/manager.d.ts +5 -0
  16. package/dist/src/budget/manager.d.ts.map +1 -1
  17. package/dist/src/budget/manager.js +32 -0
  18. package/dist/src/budget/manager.js.map +1 -1
  19. package/dist/src/budget/model-pricing.d.ts +20 -0
  20. package/dist/src/budget/model-pricing.d.ts.map +1 -0
  21. package/dist/src/budget/model-pricing.js +107 -0
  22. package/dist/src/budget/model-pricing.js.map +1 -0
  23. package/dist/src/budget/usage-extractor.d.ts +3 -1
  24. package/dist/src/budget/usage-extractor.d.ts.map +1 -1
  25. package/dist/src/budget/usage-extractor.js +47 -3
  26. package/dist/src/budget/usage-extractor.js.map +1 -1
  27. package/dist/src/config/defaults.d.ts.map +1 -1
  28. package/dist/src/config/defaults.js +65 -13
  29. package/dist/src/config/defaults.js.map +1 -1
  30. package/dist/src/dlp/tool-patterns.d.ts +7 -0
  31. package/dist/src/dlp/tool-patterns.d.ts.map +1 -0
  32. package/dist/src/dlp/tool-patterns.js +34 -0
  33. package/dist/src/dlp/tool-patterns.js.map +1 -0
  34. package/dist/src/executor/filesystem-executor.d.ts +28 -0
  35. package/dist/src/executor/filesystem-executor.d.ts.map +1 -0
  36. package/dist/src/executor/filesystem-executor.js +192 -0
  37. package/dist/src/executor/filesystem-executor.js.map +1 -0
  38. package/dist/src/executor/http-executor.d.ts.map +1 -1
  39. package/dist/src/executor/http-executor.js +22 -2
  40. package/dist/src/executor/http-executor.js.map +1 -1
  41. package/dist/src/executor/index.d.ts +4 -0
  42. package/dist/src/executor/index.d.ts.map +1 -1
  43. package/dist/src/executor/index.js +9 -1
  44. package/dist/src/executor/index.js.map +1 -1
  45. package/dist/src/executor/shell-executor.d.ts +22 -0
  46. package/dist/src/executor/shell-executor.d.ts.map +1 -0
  47. package/dist/src/executor/shell-executor.js +119 -0
  48. package/dist/src/executor/shell-executor.js.map +1 -0
  49. package/dist/src/executor/sql-executor.d.ts +29 -0
  50. package/dist/src/executor/sql-executor.d.ts.map +1 -0
  51. package/dist/src/executor/sql-executor.js +114 -0
  52. package/dist/src/executor/sql-executor.js.map +1 -0
  53. package/dist/src/executor/websocket-executor.d.ts +26 -0
  54. package/dist/src/executor/websocket-executor.d.ts.map +1 -0
  55. package/dist/src/executor/websocket-executor.js +205 -0
  56. package/dist/src/executor/websocket-executor.js.map +1 -0
  57. package/dist/src/interceptor/index.d.ts +2 -0
  58. package/dist/src/interceptor/index.d.ts.map +1 -0
  59. package/dist/src/interceptor/index.js +6 -0
  60. package/dist/src/interceptor/index.js.map +1 -0
  61. package/dist/src/interceptor/provider-interceptor.d.ts +36 -0
  62. package/dist/src/interceptor/provider-interceptor.d.ts.map +1 -0
  63. package/dist/src/interceptor/provider-interceptor.js +302 -0
  64. package/dist/src/interceptor/provider-interceptor.js.map +1 -0
  65. package/dist/src/mcp/auth-verifier.d.ts.map +1 -1
  66. package/dist/src/mcp/auth-verifier.js +3 -2
  67. package/dist/src/mcp/auth-verifier.js.map +1 -1
  68. package/dist/src/mcp/bridge.d.ts +14 -10
  69. package/dist/src/mcp/bridge.d.ts.map +1 -1
  70. package/dist/src/mcp/bridge.js +51 -227
  71. package/dist/src/mcp/bridge.js.map +1 -1
  72. package/dist/src/mcp/http-transport.d.ts +2 -0
  73. package/dist/src/mcp/http-transport.d.ts.map +1 -1
  74. package/dist/src/mcp/http-transport.js +117 -66
  75. package/dist/src/mcp/http-transport.js.map +1 -1
  76. package/dist/src/mcp/internal-auth.d.ts +13 -0
  77. package/dist/src/mcp/internal-auth.d.ts.map +1 -0
  78. package/dist/src/mcp/internal-auth.js +12 -0
  79. package/dist/src/mcp/internal-auth.js.map +1 -0
  80. package/dist/src/mcp/tool-definitions.d.ts +41 -0
  81. package/dist/src/mcp/tool-definitions.d.ts.map +1 -0
  82. package/dist/src/mcp/tool-definitions.js +491 -0
  83. package/dist/src/mcp/tool-definitions.js.map +1 -0
  84. package/dist/src/middleware/auth.js.map +1 -1
  85. package/dist/src/middleware/session.js.map +1 -1
  86. package/dist/src/middleware/validate.d.ts +8 -0
  87. package/dist/src/middleware/validate.d.ts.map +1 -1
  88. package/dist/src/middleware/validate.js +45 -0
  89. package/dist/src/middleware/validate.js.map +1 -1
  90. package/dist/src/policy/engine.d.ts +4 -0
  91. package/dist/src/policy/engine.d.ts.map +1 -1
  92. package/dist/src/policy/engine.js +117 -0
  93. package/dist/src/policy/engine.js.map +1 -1
  94. package/dist/src/saas/routes.d.ts.map +1 -1
  95. package/dist/src/saas/routes.js +355 -22
  96. package/dist/src/saas/routes.js.map +1 -1
  97. package/dist/src/server/app.d.ts.map +1 -1
  98. package/dist/src/server/app.js +24 -3
  99. package/dist/src/server/app.js.map +1 -1
  100. package/dist/src/server/gateway.d.ts.map +1 -1
  101. package/dist/src/server/gateway.js +17 -0
  102. package/dist/src/server/gateway.js.map +1 -1
  103. package/dist/src/server/index.d.ts.map +1 -1
  104. package/dist/src/server/index.js +18 -0
  105. package/dist/src/server/index.js.map +1 -1
  106. package/dist/src/storage/interfaces.d.ts +14 -3
  107. package/dist/src/storage/interfaces.d.ts.map +1 -1
  108. package/dist/src/storage/memory.d.ts +2 -0
  109. package/dist/src/storage/memory.d.ts.map +1 -1
  110. package/dist/src/storage/memory.js +6 -0
  111. package/dist/src/storage/memory.js.map +1 -1
  112. package/dist/src/storage/postgres.d.ts +5 -0
  113. package/dist/src/storage/postgres.d.ts.map +1 -1
  114. package/dist/src/storage/postgres.js +16 -0
  115. package/dist/src/storage/postgres.js.map +1 -1
  116. package/dist/src/storage/redis.d.ts +10 -0
  117. package/dist/src/storage/redis.d.ts.map +1 -1
  118. package/dist/src/storage/redis.js +65 -0
  119. package/dist/src/storage/redis.js.map +1 -1
  120. package/dist/src/types/budget.d.ts +4 -0
  121. package/dist/src/types/budget.d.ts.map +1 -1
  122. package/dist/src/types/config.d.ts +58 -0
  123. package/dist/src/types/config.d.ts.map +1 -1
  124. package/dist/src/types/events.d.ts +1 -0
  125. package/dist/src/types/events.d.ts.map +1 -1
  126. package/dist/src/types/policy.d.ts +11 -1
  127. package/dist/src/types/policy.d.ts.map +1 -1
  128. package/dist/src/types/tool-result.d.ts +11 -0
  129. package/dist/src/types/tool-result.d.ts.map +1 -1
  130. package/dist/tests/unit/app-routes.test.d.ts +2 -0
  131. package/dist/tests/unit/app-routes.test.d.ts.map +1 -0
  132. package/dist/tests/unit/app-routes.test.js +715 -0
  133. package/dist/tests/unit/app-routes.test.js.map +1 -0
  134. package/dist/tests/unit/audit-logger.test.js +105 -0
  135. package/dist/tests/unit/audit-logger.test.js.map +1 -1
  136. package/dist/tests/unit/auth-providers.test.d.ts +2 -0
  137. package/dist/tests/unit/auth-providers.test.d.ts.map +1 -0
  138. package/dist/tests/unit/auth-providers.test.js +279 -0
  139. package/dist/tests/unit/auth-providers.test.js.map +1 -0
  140. package/dist/tests/unit/auth-routes-extended.test.d.ts +2 -0
  141. package/dist/tests/unit/auth-routes-extended.test.d.ts.map +1 -0
  142. package/dist/tests/unit/auth-routes-extended.test.js +993 -0
  143. package/dist/tests/unit/auth-routes-extended.test.js.map +1 -0
  144. package/dist/tests/unit/auth-verifier.test.d.ts +2 -0
  145. package/dist/tests/unit/auth-verifier.test.d.ts.map +1 -0
  146. package/dist/tests/unit/auth-verifier.test.js +505 -0
  147. package/dist/tests/unit/auth-verifier.test.js.map +1 -0
  148. package/dist/tests/unit/billing-routes.test.d.ts +2 -0
  149. package/dist/tests/unit/billing-routes.test.d.ts.map +1 -0
  150. package/dist/tests/unit/billing-routes.test.js +432 -0
  151. package/dist/tests/unit/billing-routes.test.js.map +1 -0
  152. package/dist/tests/unit/config-defaults.test.d.ts +2 -0
  153. package/dist/tests/unit/config-defaults.test.d.ts.map +1 -0
  154. package/dist/tests/unit/config-defaults.test.js +119 -0
  155. package/dist/tests/unit/config-defaults.test.js.map +1 -0
  156. package/dist/tests/unit/defaults.test.js +0 -10
  157. package/dist/tests/unit/defaults.test.js.map +1 -1
  158. package/dist/tests/unit/filesystem-executor.test.d.ts +2 -0
  159. package/dist/tests/unit/filesystem-executor.test.d.ts.map +1 -0
  160. package/dist/tests/unit/filesystem-executor.test.js +280 -0
  161. package/dist/tests/unit/filesystem-executor.test.js.map +1 -0
  162. package/dist/tests/unit/gateway-branches.test.d.ts +2 -0
  163. package/dist/tests/unit/gateway-branches.test.d.ts.map +1 -0
  164. package/dist/tests/unit/gateway-branches.test.js +1039 -0
  165. package/dist/tests/unit/gateway-branches.test.js.map +1 -0
  166. package/dist/tests/unit/http-executor-branches.test.d.ts +2 -0
  167. package/dist/tests/unit/http-executor-branches.test.d.ts.map +1 -0
  168. package/dist/tests/unit/http-executor-branches.test.js +495 -0
  169. package/dist/tests/unit/http-executor-branches.test.js.map +1 -0
  170. package/dist/tests/unit/logger.test.d.ts +2 -0
  171. package/dist/tests/unit/logger.test.d.ts.map +1 -0
  172. package/dist/tests/unit/logger.test.js +97 -0
  173. package/dist/tests/unit/logger.test.js.map +1 -0
  174. package/dist/tests/unit/mcp-internal-auth.test.d.ts +2 -0
  175. package/dist/tests/unit/mcp-internal-auth.test.d.ts.map +1 -0
  176. package/dist/tests/unit/mcp-internal-auth.test.js +445 -0
  177. package/dist/tests/unit/mcp-internal-auth.test.js.map +1 -0
  178. package/dist/tests/unit/metrics.test.js +102 -0
  179. package/dist/tests/unit/metrics.test.js.map +1 -1
  180. package/dist/tests/unit/model-pricing.test.d.ts +2 -0
  181. package/dist/tests/unit/model-pricing.test.d.ts.map +1 -0
  182. package/dist/tests/unit/model-pricing.test.js +87 -0
  183. package/dist/tests/unit/model-pricing.test.js.map +1 -0
  184. package/dist/tests/unit/oauth-stores.test.d.ts +2 -0
  185. package/dist/tests/unit/oauth-stores.test.d.ts.map +1 -0
  186. package/dist/tests/unit/oauth-stores.test.js +260 -0
  187. package/dist/tests/unit/oauth-stores.test.js.map +1 -0
  188. package/dist/tests/unit/policy-engine.test.js +466 -0
  189. package/dist/tests/unit/policy-engine.test.js.map +1 -1
  190. package/dist/tests/unit/provider-interceptor.test.d.ts +2 -0
  191. package/dist/tests/unit/provider-interceptor.test.d.ts.map +1 -0
  192. package/dist/tests/unit/provider-interceptor.test.js +472 -0
  193. package/dist/tests/unit/provider-interceptor.test.js.map +1 -0
  194. package/dist/tests/unit/saas-routes-branches.test.d.ts +2 -0
  195. package/dist/tests/unit/saas-routes-branches.test.d.ts.map +1 -0
  196. package/dist/tests/unit/saas-routes-branches.test.js +2165 -0
  197. package/dist/tests/unit/saas-routes-branches.test.js.map +1 -0
  198. package/dist/tests/unit/saas-routes-crud.test.d.ts +2 -0
  199. package/dist/tests/unit/saas-routes-crud.test.d.ts.map +1 -0
  200. package/dist/tests/unit/saas-routes-crud.test.js +332 -0
  201. package/dist/tests/unit/saas-routes-crud.test.js.map +1 -0
  202. package/dist/tests/unit/saas-routes-data.test.d.ts +2 -0
  203. package/dist/tests/unit/saas-routes-data.test.d.ts.map +1 -0
  204. package/dist/tests/unit/saas-routes-data.test.js +405 -0
  205. package/dist/tests/unit/saas-routes-data.test.js.map +1 -0
  206. package/dist/tests/unit/saas-routes.test.js +3 -3
  207. package/dist/tests/unit/saas-routes.test.js.map +1 -1
  208. package/dist/tests/unit/shell-executor.test.d.ts +2 -0
  209. package/dist/tests/unit/shell-executor.test.d.ts.map +1 -0
  210. package/dist/tests/unit/shell-executor.test.js +145 -0
  211. package/dist/tests/unit/shell-executor.test.js.map +1 -0
  212. package/dist/tests/unit/sql-executor.test.d.ts +2 -0
  213. package/dist/tests/unit/sql-executor.test.d.ts.map +1 -0
  214. package/dist/tests/unit/sql-executor.test.js +177 -0
  215. package/dist/tests/unit/sql-executor.test.js.map +1 -0
  216. package/dist/tests/unit/stream-proxy.test.d.ts +2 -0
  217. package/dist/tests/unit/stream-proxy.test.d.ts.map +1 -0
  218. package/dist/tests/unit/stream-proxy.test.js +147 -0
  219. package/dist/tests/unit/stream-proxy.test.js.map +1 -0
  220. package/dist/tests/unit/tool-definitions.test.d.ts +2 -0
  221. package/dist/tests/unit/tool-definitions.test.d.ts.map +1 -0
  222. package/dist/tests/unit/tool-definitions.test.js +184 -0
  223. package/dist/tests/unit/tool-definitions.test.js.map +1 -0
  224. package/dist/tests/unit/usage-extractor.test.js +140 -0
  225. package/dist/tests/unit/usage-extractor.test.js.map +1 -1
  226. package/dist/tests/unit/webhook-handler.test.d.ts +2 -0
  227. package/dist/tests/unit/webhook-handler.test.d.ts.map +1 -0
  228. package/dist/tests/unit/webhook-handler.test.js +453 -0
  229. package/dist/tests/unit/webhook-handler.test.js.map +1 -0
  230. package/dist/tests/unit/webhook-routes.test.d.ts +2 -0
  231. package/dist/tests/unit/webhook-routes.test.d.ts.map +1 -0
  232. package/dist/tests/unit/webhook-routes.test.js +69 -0
  233. package/dist/tests/unit/webhook-routes.test.js.map +1 -0
  234. package/dist/tests/unit/websocket-executor.test.d.ts +2 -0
  235. package/dist/tests/unit/websocket-executor.test.d.ts.map +1 -0
  236. package/dist/tests/unit/websocket-executor.test.js +121 -0
  237. package/dist/tests/unit/websocket-executor.test.js.map +1 -0
  238. package/package.json +8 -2
  239. package/policy-packs/demo_fail.yaml +41 -0
  240. package/policy-packs/full_tools.yaml +136 -0
  241. package/src/admin/index.ts +1 -0
  242. package/src/admin/routes.ts +509 -0
  243. package/src/admin/templates.ts +572 -0
  244. package/src/anomaly/detector.ts +730 -0
  245. package/src/anomaly/index.ts +1 -0
  246. package/src/approval/manager.ts +569 -0
  247. package/src/approval/webhook.ts +133 -0
  248. package/src/audit/logger.ts +490 -0
  249. package/src/auth/index.ts +5 -0
  250. package/src/auth/password.ts +21 -0
  251. package/src/auth/pkce.ts +22 -0
  252. package/src/auth/providers.ts +208 -0
  253. package/src/auth/routes.ts +561 -0
  254. package/src/auth/session.ts +84 -0
  255. package/src/billing/index.ts +6 -0
  256. package/src/billing/plan-enforcer.ts +135 -0
  257. package/src/billing/routes.ts +229 -0
  258. package/src/billing/stripe-client.ts +58 -0
  259. package/src/billing/webhook-handler.ts +182 -0
  260. package/src/billing/webhook-routes.ts +28 -0
  261. package/src/budget/manager.ts +679 -0
  262. package/src/budget/model-pricing.ts +119 -0
  263. package/src/budget/usage-extractor.ts +214 -0
  264. package/src/cli.ts +91 -0
  265. package/src/config/defaults.ts +261 -0
  266. package/src/config/validate.ts +88 -0
  267. package/src/dlp/composite-scanner.ts +213 -0
  268. package/src/dlp/index.ts +9 -0
  269. package/src/dlp/interfaces.ts +34 -0
  270. package/src/dlp/patterns.ts +30 -0
  271. package/src/dlp/prompt-injection-backend.ts +181 -0
  272. package/src/dlp/prompt-injection-patterns.ts +302 -0
  273. package/src/dlp/regex-backend.ts +181 -0
  274. package/src/dlp/scanner.ts +502 -0
  275. package/src/dlp/text-normalizer.ts +225 -0
  276. package/src/dlp/tool-patterns.ts +35 -0
  277. package/src/dlp/trufflehog-backend.ts +190 -0
  278. package/src/executor/filesystem-executor.ts +196 -0
  279. package/src/executor/http-executor.ts +349 -0
  280. package/src/executor/index.ts +9 -0
  281. package/src/executor/interfaces.ts +11 -0
  282. package/src/executor/noop-executor.ts +23 -0
  283. package/src/executor/registry.ts +64 -0
  284. package/src/executor/shell-executor.ts +148 -0
  285. package/src/executor/slack-executor.ts +176 -0
  286. package/src/executor/sql-executor.ts +146 -0
  287. package/src/executor/websocket-executor.ts +211 -0
  288. package/src/index.ts +24 -0
  289. package/src/interceptor/index.ts +1 -0
  290. package/src/interceptor/provider-interceptor.ts +315 -0
  291. package/src/mcp/auth-verifier.ts +152 -0
  292. package/src/mcp/bridge.ts +703 -0
  293. package/src/mcp/http-transport.ts +698 -0
  294. package/src/mcp/index.ts +9 -0
  295. package/src/mcp/internal-auth.ts +14 -0
  296. package/src/mcp/oauth-pages.ts +139 -0
  297. package/src/mcp/oauth-postgres-stores.ts +278 -0
  298. package/src/mcp/oauth-provider.ts +536 -0
  299. package/src/mcp/oauth-stores.ts +202 -0
  300. package/src/mcp/server.ts +55 -0
  301. package/src/mcp/tool-definitions.ts +562 -0
  302. package/src/metrics/collector.ts +357 -0
  303. package/src/metrics/index.ts +1 -0
  304. package/src/middleware/auth.ts +814 -0
  305. package/src/middleware/session.ts +85 -0
  306. package/src/middleware/validate.ts +130 -0
  307. package/src/policy/engine.ts +815 -0
  308. package/src/policy/index.ts +2 -0
  309. package/src/policy/opa-engine.ts +829 -0
  310. package/src/proxy/forward-proxy.ts +649 -0
  311. package/src/proxy/index.ts +1 -0
  312. package/src/ratelimit/limiter.ts +196 -0
  313. package/src/replay/engine.ts +142 -0
  314. package/src/replay/index.ts +1 -0
  315. package/src/saas/index.ts +1 -0
  316. package/src/saas/routes.ts +2178 -0
  317. package/src/server/app.ts +985 -0
  318. package/src/server/errors.ts +49 -0
  319. package/src/server/gateway.ts +1130 -0
  320. package/src/server/index.ts +307 -0
  321. package/src/server/logger.ts +255 -0
  322. package/src/server/stream-proxy.ts +202 -0
  323. package/src/storage/file-persistence.ts +315 -0
  324. package/src/storage/index.ts +4 -0
  325. package/src/storage/interfaces.ts +287 -0
  326. package/src/storage/memory.ts +686 -0
  327. package/src/storage/postgres.ts +1831 -0
  328. package/src/storage/redis.ts +835 -0
  329. package/src/tracing/index.ts +1 -0
  330. package/src/tracing/provider.ts +100 -0
  331. package/src/trust/calculator.ts +141 -0
  332. package/src/trust/index.ts +7 -0
  333. package/src/types/budget.ts +36 -0
  334. package/src/types/config.ts +278 -0
  335. package/src/types/events.ts +41 -0
  336. package/src/types/express.d.ts +14 -0
  337. package/src/types/index.ts +7 -0
  338. package/src/types/policy.ts +83 -0
  339. package/src/types/stripe-config.ts +11 -0
  340. package/src/types/subscription.ts +59 -0
  341. package/src/types/tool-call.ts +47 -0
  342. package/src/types/tool-result.ts +82 -0
  343. package/src/types/user.ts +125 -0
  344. package/tsconfig.json +24 -0
@@ -0,0 +1,225 @@
1
+ /**
2
+ * Text normalizer for bypass-resistant prompt injection detection.
3
+ *
4
+ * Applies a series of transformations to collapse evasion techniques
5
+ * (zero-width chars, HTML entities, homoglyphs, leetspeak, etc.)
6
+ * into canonical ASCII text before pattern matching.
7
+ */
8
+
9
+ // ---------------------------------------------------------------------------
10
+ // Zero-width character stripping
11
+ // ---------------------------------------------------------------------------
12
+
13
+ /** Regex matching zero-width and invisible Unicode characters. */
14
+ export const ZERO_WIDTH_REGEX = /[\u200B\u200C\u200D\u00AD\uFEFF\u200E\u200F\u2060\u2061\u2062\u2063\u2064\u180E]/g;
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Homoglyph map (visually similar characters -> ASCII equivalents)
18
+ // ---------------------------------------------------------------------------
19
+
20
+ /** Map of Unicode homoglyphs to their ASCII equivalents. */
21
+ export const HOMOGLYPH_MAP: Record<string, string> = {
22
+ // Cyrillic -> Latin
23
+ '\u0430': 'a', // а
24
+ '\u0435': 'e', // е
25
+ '\u043E': 'o', // о
26
+ '\u0440': 'p', // р
27
+ '\u0441': 'c', // с
28
+ '\u0443': 'y', // у
29
+ '\u0445': 'x', // х
30
+ '\u0456': 'i', // і
31
+ '\u0458': 'j', // ј
32
+ '\u04BB': 'h', // һ
33
+ '\u0410': 'A', // А
34
+ '\u0412': 'B', // В
35
+ '\u0415': 'E', // Е
36
+ '\u041A': 'K', // К
37
+ '\u041C': 'M', // М
38
+ '\u041D': 'H', // Н
39
+ '\u041E': 'O', // О
40
+ '\u0420': 'P', // Р
41
+ '\u0421': 'C', // С
42
+ '\u0422': 'T', // Т
43
+ '\u0425': 'X', // Х
44
+ // Greek -> Latin
45
+ '\u03B1': 'a', // α
46
+ '\u03BF': 'o', // ο
47
+ '\u03C1': 'p', // ρ
48
+ '\u0391': 'A', // Α
49
+ '\u0392': 'B', // Β
50
+ '\u0395': 'E', // Ε
51
+ '\u0397': 'H', // Η
52
+ '\u0399': 'I', // Ι
53
+ '\u039A': 'K', // Κ
54
+ '\u039C': 'M', // Μ
55
+ '\u039D': 'N', // Ν
56
+ '\u039F': 'O', // Ο
57
+ '\u03A1': 'P', // Ρ
58
+ '\u03A4': 'T', // Τ
59
+ '\u03A7': 'X', // Χ
60
+ '\u03A5': 'Y', // Υ
61
+ '\u0396': 'Z', // Ζ
62
+ // Fullwidth -> ASCII (supplemental to NFKC — belt and suspenders)
63
+ '\uFF41': 'a',
64
+ '\uFF42': 'b',
65
+ '\uFF43': 'c',
66
+ '\uFF49': 'i',
67
+ '\uFF4E': 'n',
68
+ '\uFF4F': 'o',
69
+ '\uFF50': 'p',
70
+ '\uFF52': 'r',
71
+ '\uFF53': 's',
72
+ '\uFF54': 't',
73
+ '\uFF55': 'u',
74
+ // Common lookalikes
75
+ '\u0131': 'i', // ı (dotless i)
76
+ '\u0237': 'j', // ȷ (dotless j)
77
+ '\u01C0': 'l', // ǀ (dental click -> l)
78
+ };
79
+
80
+ // Build reverse lookup for efficiency
81
+ const homoglyphRegex = new RegExp(
82
+ '[' + Object.keys(HOMOGLYPH_MAP).join('') + ']',
83
+ 'g',
84
+ );
85
+
86
+ // ---------------------------------------------------------------------------
87
+ // Leetspeak map
88
+ // ---------------------------------------------------------------------------
89
+
90
+ /** Map of common leetspeak substitutions to their letter equivalents. */
91
+ export const LEETSPEAK_MAP: Record<string, string> = {
92
+ '0': 'o',
93
+ '1': 'i',
94
+ '3': 'e',
95
+ '4': 'a',
96
+ '5': 's',
97
+ '7': 't',
98
+ '@': 'a',
99
+ '$': 's',
100
+ '!': 'i',
101
+ };
102
+
103
+ const leetspeakRegex = /[013457@$!]/g;
104
+
105
+ // ---------------------------------------------------------------------------
106
+ // HTML entity decoding
107
+ // ---------------------------------------------------------------------------
108
+
109
+ /** Named HTML entities most commonly used for evasion. */
110
+ const NAMED_ENTITIES: Record<string, string> = {
111
+ '&lt;': '<',
112
+ '&gt;': '>',
113
+ '&amp;': '&',
114
+ '&quot;': '"',
115
+ '&apos;': "'",
116
+ '&nbsp;': ' ',
117
+ '&tab;': '\t',
118
+ };
119
+
120
+ /** Decode HTML entities (named + numeric decimal + numeric hex). */
121
+ function decodeHTMLEntities(input: string): string {
122
+ // Named entities
123
+ let result = input;
124
+ for (const [entity, char] of Object.entries(NAMED_ENTITIES)) {
125
+ // Case-insensitive replacement for named entities
126
+ const re = new RegExp(entity.replace(/[&;]/g, (c) => '\\' + c), 'gi');
127
+ result = result.replace(re, char);
128
+ }
129
+
130
+ // Decimal numeric entities: &#105; -> 'i'
131
+ result = result.replace(/&#(\d+);/g, (_match, digits) => {
132
+ const code = parseInt(digits, 10);
133
+ if (code > 0 && code <= 0x10FFFF) {
134
+ return String.fromCodePoint(code);
135
+ }
136
+ return _match;
137
+ });
138
+
139
+ // Hex numeric entities: &#x69; -> 'i'
140
+ result = result.replace(/&#x([0-9a-fA-F]+);/g, (_match, hex) => {
141
+ const code = parseInt(hex, 16);
142
+ if (code > 0 && code <= 0x10FFFF) {
143
+ return String.fromCodePoint(code);
144
+ }
145
+ return _match;
146
+ });
147
+
148
+ return result;
149
+ }
150
+
151
+ // ---------------------------------------------------------------------------
152
+ // URL decoding
153
+ // ---------------------------------------------------------------------------
154
+
155
+ /** Decode percent-encoded sequences (%69 -> 'i'). */
156
+ function decodeURLEncoding(input: string): string {
157
+ try {
158
+ return decodeURIComponent(input);
159
+ } catch {
160
+ // If decoding fails (malformed sequences), apply partial decoding
161
+ return input.replace(/%([0-9a-fA-F]{2})/g, (_match, hex) => {
162
+ return String.fromCharCode(parseInt(hex, 16));
163
+ });
164
+ }
165
+ }
166
+
167
+ // ---------------------------------------------------------------------------
168
+ // Main normalizer
169
+ // ---------------------------------------------------------------------------
170
+
171
+ /**
172
+ * Normalize text for bypass-resistant pattern matching.
173
+ *
174
+ * Applies transformations in order:
175
+ * 1. Strip zero-width / invisible Unicode characters
176
+ * 2. Unicode NFKC normalization (collapses fullwidth, ligatures, etc.)
177
+ * 3. Decode HTML entities (named + numeric)
178
+ * 4. Decode URL percent-encoding
179
+ * 5. Collapse homoglyphs (Cyrillic/Greek lookalikes -> ASCII)
180
+ * 6. Collapse repeated whitespace to single space
181
+ *
182
+ * @param input - The raw text to normalize.
183
+ * @returns The normalized text suitable for pattern matching.
184
+ */
185
+ export function normalizeText(input: string): string {
186
+ // Early exit for very short strings
187
+ if (input.length === 0) return input;
188
+
189
+ let text = input;
190
+
191
+ // 1. Strip zero-width characters
192
+ text = text.replace(ZERO_WIDTH_REGEX, '');
193
+
194
+ // 2. NFKC normalization (fullwidth -> ASCII, ligatures -> components, etc.)
195
+ text = text.normalize('NFKC');
196
+
197
+ // 3. Decode HTML entities
198
+ text = decodeHTMLEntities(text);
199
+
200
+ // 4. Decode URL percent-encoding
201
+ text = decodeURLEncoding(text);
202
+
203
+ // 5. Collapse homoglyphs
204
+ text = text.replace(homoglyphRegex, (ch) => HOMOGLYPH_MAP[ch] || ch);
205
+
206
+ // 6. Collapse whitespace (spaces, tabs, newlines) to single space and trim
207
+ text = text.replace(/\s+/g, ' ').trim();
208
+
209
+ return text;
210
+ }
211
+
212
+ /**
213
+ * Apply leetspeak normalization on top of standard normalization.
214
+ *
215
+ * Returns the leet-decoded version of the text. Callers should match
216
+ * patterns against BOTH the standard-normalized and leet-normalized text
217
+ * to catch leet evasions without causing false positives on normal text
218
+ * containing digits.
219
+ *
220
+ * @param normalizedInput - Text already passed through normalizeText().
221
+ * @returns The leet-decoded text.
222
+ */
223
+ export function normalizeLeetspeak(normalizedInput: string): string {
224
+ return normalizedInput.replace(leetspeakRegex, (ch) => LEETSPEAK_MAP[ch] || ch);
225
+ }
@@ -0,0 +1,35 @@
1
+ import { DLPPattern } from './patterns';
2
+
3
+ // Shell injection patterns
4
+ export const SHELL_INJECTION_PATTERNS: DLPPattern[] = [
5
+ { name: 'shell_pipe', pattern: /\|/g, severity: 'medium' },
6
+ { name: 'shell_subshell', pattern: /\$\(|\`/g, severity: 'high' },
7
+ { name: 'shell_redirect', pattern: /[><]{1,2}/g, severity: 'medium' },
8
+ { name: 'shell_semicolon', pattern: /;\s*\w/g, severity: 'high' },
9
+ { name: 'shell_background', pattern: /&\s*$/g, severity: 'medium' },
10
+ { name: 'shell_env_expansion', pattern: /\$\{[^}]+\}/g, severity: 'medium' },
11
+ ];
12
+
13
+ // Path traversal patterns
14
+ export const PATH_TRAVERSAL_PATTERNS: DLPPattern[] = [
15
+ { name: 'path_traversal', pattern: /\.\.\//g, severity: 'high' },
16
+ { name: 'path_traversal_encoded', pattern: /%2e%2e%2f/gi, severity: 'high' },
17
+ { name: 'path_null_byte', pattern: /%00/g, severity: 'high' },
18
+ { name: 'path_absolute_unix', pattern: /^\/(?:etc|proc|sys|dev|root|var\/log)\//g, severity: 'high' },
19
+ ];
20
+
21
+ // SQL injection patterns
22
+ export const SQL_INJECTION_PATTERNS: DLPPattern[] = [
23
+ { name: 'sql_union_select', pattern: /UNION\s+(?:ALL\s+)?SELECT/gi, severity: 'high' },
24
+ { name: 'sql_stacked_query', pattern: /;\s*(?:SELECT|INSERT|UPDATE|DELETE|DROP|ALTER|CREATE)\b/gi, severity: 'high' },
25
+ { name: 'sql_comment_injection', pattern: /(?:--|#|\/\*)/g, severity: 'medium' },
26
+ { name: 'sql_sleep_benchmark', pattern: /(?:SLEEP|BENCHMARK|WAITFOR\s+DELAY)\s*\(/gi, severity: 'high' },
27
+ { name: 'sql_info_schema', pattern: /INFORMATION_SCHEMA/gi, severity: 'high' },
28
+ ];
29
+
30
+ /** All tool-specific DLP patterns combined */
31
+ export const TOOL_DLP_PATTERNS: DLPPattern[] = [
32
+ ...SHELL_INJECTION_PATTERNS,
33
+ ...PATH_TRAVERSAL_PATTERNS,
34
+ ...SQL_INJECTION_PATTERNS,
35
+ ];
@@ -0,0 +1,190 @@
1
+ import { execFileSync } from 'child_process';
2
+ import { writeFileSync, unlinkSync } from 'fs';
3
+ import { tmpdir } from 'os';
4
+ import { join } from 'path';
5
+ import { randomUUID } from 'crypto';
6
+ import { DLPSeverity } from '../types/tool-result';
7
+ import { DLPBackend, DLPDetection } from './interfaces';
8
+
9
+ /**
10
+ * Configuration for the TruffleHog DLP backend.
11
+ */
12
+ export interface TruffleHogConfig {
13
+ /** Path to the trufflehog binary. Defaults to 'trufflehog' (resolved via PATH). */
14
+ binaryPath?: string;
15
+ /** Execution timeout in milliseconds. Defaults to 10000 (10s). */
16
+ timeout?: number;
17
+ }
18
+
19
+ /**
20
+ * Raw JSON output from a single trufflehog finding.
21
+ *
22
+ * TruffleHog emits one JSON object per line when run with --json.
23
+ */
24
+ interface TruffleHogFinding {
25
+ DetectorName?: string;
26
+ DetectorType?: number;
27
+ Verified?: boolean;
28
+ Raw?: string;
29
+ RawV2?: string;
30
+ SourceMetadata?: unknown;
31
+ ExtraData?: Record<string, unknown>;
32
+ }
33
+
34
+ /**
35
+ * DLP backend that delegates secret scanning to the TruffleHog binary.
36
+ *
37
+ * TruffleHog is a comprehensive secret scanner that supports 700+ detector
38
+ * types including verified credential checks against live services.
39
+ *
40
+ * This backend writes the input string to a temporary file, runs
41
+ * `trufflehog filesystem --json --no-update <tmpfile>`, and parses the
42
+ * line-delimited JSON output into DLPDetection objects.
43
+ *
44
+ * Graceful degradation: if the trufflehog binary is not installed or the
45
+ * process fails for any reason, the backend logs a warning and returns an
46
+ * empty array (no detections). This allows it to be safely composed with
47
+ * other backends without breaking the pipeline.
48
+ */
49
+ export class TruffleHogBackend implements DLPBackend {
50
+ readonly name = 'trufflehog';
51
+
52
+ private readonly binaryPath: string;
53
+ private readonly timeout: number;
54
+
55
+ constructor(config?: TruffleHogConfig) {
56
+ this.binaryPath = config?.binaryPath ?? 'trufflehog';
57
+ this.timeout = config?.timeout ?? 10_000;
58
+ }
59
+
60
+ /**
61
+ * Scan a string for secrets using trufflehog.
62
+ *
63
+ * 1. Writes the value to a temp file
64
+ * 2. Runs trufflehog filesystem in JSON mode
65
+ * 3. Parses each JSON line into a DLPDetection
66
+ * 4. Cleans up the temp file
67
+ *
68
+ * Returns an empty array if trufflehog is not available or fails.
69
+ */
70
+ scanString(value: string): DLPDetection[] {
71
+ const tmpFile = join(tmpdir(), `palaryn-dlp-${randomUUID()}.tmp`);
72
+
73
+ try {
74
+ // Write input to temp file for trufflehog to scan
75
+ writeFileSync(tmpFile, value, 'utf-8');
76
+
77
+ // Execute trufflehog
78
+ const stdout = execFileSync(
79
+ this.binaryPath,
80
+ ['filesystem', '--json', '--no-update', tmpFile],
81
+ {
82
+ timeout: this.timeout,
83
+ encoding: 'utf-8',
84
+ stdio: ['pipe', 'pipe', 'pipe'],
85
+ },
86
+ );
87
+
88
+ return this.parseOutput(stdout, value);
89
+ } catch (err: unknown) {
90
+ // Graceful degradation: log and return empty
91
+ const message = err instanceof Error ? err.message : String(err);
92
+ // Only log if it's not a "no findings" exit (trufflehog exits 0 with no output when clean)
93
+ if (!this.isNoFindingsError(message)) {
94
+ console.warn(`[TruffleHogBackend] scan failed: ${message}`);
95
+ }
96
+ return [];
97
+ } finally {
98
+ // Always clean up the temp file
99
+ try {
100
+ unlinkSync(tmpFile);
101
+ } catch {
102
+ // Ignore cleanup errors
103
+ }
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Parse trufflehog JSON output (one JSON object per line) into DLPDetection[].
109
+ *
110
+ * Each line is an independent JSON object representing a single finding.
111
+ */
112
+ private parseOutput(stdout: string, originalValue: string): DLPDetection[] {
113
+ if (!stdout || !stdout.trim()) {
114
+ return [];
115
+ }
116
+
117
+ const detections: DLPDetection[] = [];
118
+ const lines = stdout.trim().split('\n');
119
+
120
+ for (const line of lines) {
121
+ const trimmed = line.trim();
122
+ if (!trimmed) continue;
123
+
124
+ try {
125
+ const finding: TruffleHogFinding = JSON.parse(trimmed);
126
+ const detection = this.findingToDetection(finding, originalValue);
127
+ if (detection) {
128
+ detections.push(detection);
129
+ }
130
+ } catch {
131
+ // Skip malformed JSON lines
132
+ continue;
133
+ }
134
+ }
135
+
136
+ return detections;
137
+ }
138
+
139
+ /**
140
+ * Convert a single trufflehog finding to a DLPDetection.
141
+ *
142
+ * Severity mapping:
143
+ * - Verified findings -> 'high' (credential confirmed active)
144
+ * - Unverified findings -> 'medium' (potential secret)
145
+ */
146
+ private findingToDetection(finding: TruffleHogFinding, originalValue: string): DLPDetection | null {
147
+ const detectorName = finding.DetectorName;
148
+ if (!detectorName) {
149
+ return null;
150
+ }
151
+
152
+ const raw = finding.Raw || finding.RawV2 || '';
153
+ const severity: DLPSeverity = finding.Verified ? 'high' : 'medium';
154
+
155
+ // Locate the raw finding within the original string
156
+ let start = 0;
157
+ let end = 0;
158
+ if (raw) {
159
+ const idx = originalValue.indexOf(raw);
160
+ if (idx !== -1) {
161
+ start = idx;
162
+ end = idx + raw.length;
163
+ } else {
164
+ // If we can't locate the raw match, use the full string range
165
+ start = 0;
166
+ end = originalValue.length;
167
+ }
168
+ }
169
+
170
+ return {
171
+ pattern_name: `trufflehog:${detectorName}`,
172
+ severity,
173
+ match: raw || originalValue,
174
+ start,
175
+ end,
176
+ };
177
+ }
178
+
179
+ /**
180
+ * Check if the error is simply a "no findings" situation rather than a real failure.
181
+ *
182
+ * TruffleHog may exit with code 0 and empty output, or in some versions
183
+ * it may produce an error-like message when there are no findings.
184
+ */
185
+ private isNoFindingsError(message: string): boolean {
186
+ // execFileSync throws if the process exits with non-zero or produces no output
187
+ // for an empty scan. Check for common benign patterns.
188
+ return message.includes('ENOENT') === false && message.includes('ETIMEDOUT') === false;
189
+ }
190
+ }
@@ -0,0 +1,196 @@
1
+ import * as path from 'path';
2
+ import * as fs from 'fs/promises';
3
+ import { ToolCall } from '../types/tool-call';
4
+ import { ToolOutput } from '../types/tool-result';
5
+ import { ToolExecutor } from './interfaces';
6
+ import { FilesystemExecutorConfig } from '../types/config';
7
+
8
+ /**
9
+ * Filesystem executor for sandboxed file operations.
10
+ * Handles tool calls with tool name `file.*` (e.g., file.read, file.write).
11
+ * All paths are resolved relative to and contained within base_dir.
12
+ */
13
+ export class FilesystemExecutor implements ToolExecutor {
14
+ private config: FilesystemExecutorConfig;
15
+ private resolvedBaseDir: string;
16
+
17
+ constructor(config: FilesystemExecutorConfig) {
18
+ this.config = config;
19
+ this.resolvedBaseDir = path.resolve(config.base_dir);
20
+ }
21
+
22
+ async execute(toolCall: ToolCall): Promise<ToolOutput> {
23
+ const action = this.resolveAction(toolCall);
24
+
25
+ switch (action) {
26
+ case 'read':
27
+ return this.read(toolCall);
28
+ case 'write':
29
+ return this.write(toolCall);
30
+ case 'delete':
31
+ return this.delete(toolCall);
32
+ case 'list':
33
+ return this.list(toolCall);
34
+ case 'stat':
35
+ return this.stat(toolCall);
36
+ default:
37
+ throw new Error(`Unsupported filesystem action: ${action}`);
38
+ }
39
+ }
40
+
41
+ private resolveAction(toolCall: ToolCall): string {
42
+ if (toolCall.args.action && typeof toolCall.args.action === 'string') {
43
+ return toolCall.args.action;
44
+ }
45
+
46
+ const toolName = toolCall.tool.name;
47
+ const dotIndex = toolName.indexOf('.');
48
+ if (dotIndex !== -1) {
49
+ return toolName.substring(dotIndex + 1);
50
+ }
51
+
52
+ throw new Error(`Unsupported filesystem action: ${toolName}`);
53
+ }
54
+
55
+ /**
56
+ * Resolve and validate a path, ensuring it stays within base_dir.
57
+ * Prevents path traversal attacks.
58
+ */
59
+ private resolveSafePath(filePath: string): string {
60
+ const resolved = path.resolve(this.resolvedBaseDir, filePath);
61
+ if (!resolved.startsWith(this.resolvedBaseDir + path.sep) && resolved !== this.resolvedBaseDir) {
62
+ throw new Error(`Path traversal denied: "${filePath}" resolves outside base directory`);
63
+ }
64
+ return resolved;
65
+ }
66
+
67
+ private checkExtension(filePath: string): void {
68
+ const allowed = this.config.allowed_extensions;
69
+ if (allowed && allowed.length > 0) {
70
+ const ext = path.extname(filePath).toLowerCase();
71
+ if (!allowed.includes(ext)) {
72
+ throw new Error(`File extension "${ext}" is not allowed. Allowed: ${allowed.join(', ')}`);
73
+ }
74
+ }
75
+ }
76
+
77
+ private async read(toolCall: ToolCall): Promise<ToolOutput> {
78
+ const { path: filePath, encoding } = toolCall.args;
79
+
80
+ if (!filePath || typeof filePath !== 'string') {
81
+ throw new Error('Missing or invalid "path" argument for file.read');
82
+ }
83
+
84
+ const resolved = this.resolveSafePath(filePath);
85
+ this.checkExtension(resolved);
86
+
87
+ const stat = await fs.stat(resolved);
88
+ if (stat.size > this.config.max_file_size_bytes) {
89
+ throw new Error(`File size ${stat.size} exceeds max allowed ${this.config.max_file_size_bytes} bytes`);
90
+ }
91
+
92
+ const content = await fs.readFile(resolved, {
93
+ encoding: (encoding as BufferEncoding) || 'utf-8',
94
+ });
95
+
96
+ return { body: content, paths: [filePath] };
97
+ }
98
+
99
+ private async write(toolCall: ToolCall): Promise<ToolOutput> {
100
+ const { path: filePath, content, encoding, append } = toolCall.args;
101
+
102
+ if (!filePath || typeof filePath !== 'string') {
103
+ throw new Error('Missing or invalid "path" argument for file.write');
104
+ }
105
+ if (content === undefined || content === null) {
106
+ throw new Error('Missing "content" argument for file.write');
107
+ }
108
+
109
+ const resolved = this.resolveSafePath(filePath);
110
+ this.checkExtension(resolved);
111
+
112
+ const data = typeof content === 'string' ? content : JSON.stringify(content);
113
+
114
+ if (Buffer.byteLength(data) > this.config.max_file_size_bytes) {
115
+ throw new Error(`Content size exceeds max allowed ${this.config.max_file_size_bytes} bytes`);
116
+ }
117
+
118
+ // Ensure parent directory exists
119
+ await fs.mkdir(path.dirname(resolved), { recursive: true });
120
+
121
+ if (append) {
122
+ await fs.appendFile(resolved, data, {
123
+ encoding: (encoding as BufferEncoding) || 'utf-8',
124
+ });
125
+ } else {
126
+ await fs.writeFile(resolved, data, {
127
+ encoding: (encoding as BufferEncoding) || 'utf-8',
128
+ });
129
+ }
130
+
131
+ return { body: { written: true }, paths: [filePath] };
132
+ }
133
+
134
+ private async delete(toolCall: ToolCall): Promise<ToolOutput> {
135
+ const { path: filePath, recursive } = toolCall.args;
136
+
137
+ if (!filePath || typeof filePath !== 'string') {
138
+ throw new Error('Missing or invalid "path" argument for file.delete');
139
+ }
140
+
141
+ const resolved = this.resolveSafePath(filePath);
142
+
143
+ const stat = await fs.stat(resolved);
144
+ if (stat.isDirectory()) {
145
+ await fs.rm(resolved, { recursive: !!recursive });
146
+ } else {
147
+ await fs.unlink(resolved);
148
+ }
149
+
150
+ return { body: { deleted: true }, paths: [filePath] };
151
+ }
152
+
153
+ private async list(toolCall: ToolCall): Promise<ToolOutput> {
154
+ const { path: dirPath, pattern } = toolCall.args;
155
+
156
+ const targetPath = (dirPath && typeof dirPath === 'string') ? dirPath : '.';
157
+ const resolved = this.resolveSafePath(targetPath);
158
+
159
+ const entries = await fs.readdir(resolved, { withFileTypes: true });
160
+
161
+ let items = entries.map(entry => ({
162
+ name: entry.name,
163
+ type: entry.isDirectory() ? 'directory' : 'file',
164
+ }));
165
+
166
+ if (pattern && typeof pattern === 'string') {
167
+ const regex = new RegExp(pattern);
168
+ items = items.filter(item => regex.test(item.name));
169
+ }
170
+
171
+ return { body: items, paths: [targetPath] };
172
+ }
173
+
174
+ private async stat(toolCall: ToolCall): Promise<ToolOutput> {
175
+ const { path: filePath } = toolCall.args;
176
+
177
+ if (!filePath || typeof filePath !== 'string') {
178
+ throw new Error('Missing or invalid "path" argument for file.stat');
179
+ }
180
+
181
+ const resolved = this.resolveSafePath(filePath);
182
+ const stat = await fs.stat(resolved);
183
+
184
+ return {
185
+ body: {
186
+ size: stat.size,
187
+ is_file: stat.isFile(),
188
+ is_directory: stat.isDirectory(),
189
+ created: stat.birthtime.toISOString(),
190
+ modified: stat.mtime.toISOString(),
191
+ permissions: stat.mode.toString(8),
192
+ },
193
+ paths: [filePath],
194
+ };
195
+ }
196
+ }