codegate-ai 0.8.0 → 0.9.1

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 (124) hide show
  1. package/README.md +48 -15
  2. package/dist/cli.js +41 -7
  3. package/dist/commands/scan-command.d.ts +6 -1
  4. package/dist/commands/scan-command.js +1 -1
  5. package/dist/commands/scan-content-command.js +5 -1
  6. package/dist/config/inline-ignore.d.ts +11 -0
  7. package/dist/config/inline-ignore.js +57 -0
  8. package/dist/config/suppression-policy.d.ts +8 -0
  9. package/dist/config/suppression-policy.js +66 -1
  10. package/dist/config.d.ts +19 -1
  11. package/dist/config.js +160 -1
  12. package/dist/layer2-static/action/parser.d.ts +3 -0
  13. package/dist/layer2-static/action/parser.js +165 -0
  14. package/dist/layer2-static/action/types.d.ts +39 -0
  15. package/dist/layer2-static/action/types.js +1 -0
  16. package/dist/layer2-static/advisories/cache.d.ts +6 -0
  17. package/dist/layer2-static/advisories/cache.js +44 -0
  18. package/dist/layer2-static/advisories/cache.ts +62 -0
  19. package/dist/layer2-static/advisories/gha-advisory-client.d.ts +7 -0
  20. package/dist/layer2-static/advisories/gha-advisory-client.js +4 -0
  21. package/dist/layer2-static/advisories/gha-advisory-client.ts +13 -0
  22. package/dist/layer2-static/advisories/gha-known-vulnerable-actions.json +4 -0
  23. package/dist/layer2-static/audits/registry.d.ts +3 -0
  24. package/dist/layer2-static/audits/registry.js +29 -0
  25. package/dist/layer2-static/audits/types.d.ts +12 -0
  26. package/dist/layer2-static/audits/types.js +1 -0
  27. package/dist/layer2-static/dependabot/parser.d.ts +3 -0
  28. package/dist/layer2-static/dependabot/parser.js +189 -0
  29. package/dist/layer2-static/dependabot/types.d.ts +50 -0
  30. package/dist/layer2-static/dependabot/types.js +1 -0
  31. package/dist/layer2-static/detectors/dependabot-cooldown.d.ts +7 -0
  32. package/dist/layer2-static/detectors/dependabot-cooldown.js +54 -0
  33. package/dist/layer2-static/detectors/dependabot-execution.d.ts +7 -0
  34. package/dist/layer2-static/detectors/dependabot-execution.js +68 -0
  35. package/dist/layer2-static/detectors/workflow-anonymous-definition.d.ts +7 -0
  36. package/dist/layer2-static/detectors/workflow-anonymous-definition.js +48 -0
  37. package/dist/layer2-static/detectors/workflow-archived-uses.d.ts +7 -0
  38. package/dist/layer2-static/detectors/workflow-archived-uses.js +147 -0
  39. package/dist/layer2-static/detectors/workflow-artipacked.d.ts +7 -0
  40. package/dist/layer2-static/detectors/workflow-artipacked.js +62 -0
  41. package/dist/layer2-static/detectors/workflow-bot-conditions.d.ts +7 -0
  42. package/dist/layer2-static/detectors/workflow-bot-conditions.js +108 -0
  43. package/dist/layer2-static/detectors/workflow-cache-poisoning.d.ts +7 -0
  44. package/dist/layer2-static/detectors/workflow-cache-poisoning.js +67 -0
  45. package/dist/layer2-static/detectors/workflow-concurrency-limits.d.ts +7 -0
  46. package/dist/layer2-static/detectors/workflow-concurrency-limits.js +76 -0
  47. package/dist/layer2-static/detectors/workflow-dangerous-triggers.d.ts +6 -0
  48. package/dist/layer2-static/detectors/workflow-dangerous-triggers.js +38 -0
  49. package/dist/layer2-static/detectors/workflow-excessive-permissions.d.ts +6 -0
  50. package/dist/layer2-static/detectors/workflow-excessive-permissions.js +65 -0
  51. package/dist/layer2-static/detectors/workflow-forbidden-uses.d.ts +12 -0
  52. package/dist/layer2-static/detectors/workflow-forbidden-uses.js +148 -0
  53. package/dist/layer2-static/detectors/workflow-github-env.d.ts +7 -0
  54. package/dist/layer2-static/detectors/workflow-github-env.js +49 -0
  55. package/dist/layer2-static/detectors/workflow-hardcoded-container-credentials.d.ts +7 -0
  56. package/dist/layer2-static/detectors/workflow-hardcoded-container-credentials.js +90 -0
  57. package/dist/layer2-static/detectors/workflow-impostor-commit.d.ts +9 -0
  58. package/dist/layer2-static/detectors/workflow-impostor-commit.js +127 -0
  59. package/dist/layer2-static/detectors/workflow-insecure-commands.d.ts +7 -0
  60. package/dist/layer2-static/detectors/workflow-insecure-commands.js +72 -0
  61. package/dist/layer2-static/detectors/workflow-known-vuln-action.d.ts +8 -0
  62. package/dist/layer2-static/detectors/workflow-known-vuln-action.js +70 -0
  63. package/dist/layer2-static/detectors/workflow-misfeature.d.ts +7 -0
  64. package/dist/layer2-static/detectors/workflow-misfeature.js +101 -0
  65. package/dist/layer2-static/detectors/workflow-obfuscation.d.ts +7 -0
  66. package/dist/layer2-static/detectors/workflow-obfuscation.js +79 -0
  67. package/dist/layer2-static/detectors/workflow-overprovisioned-secrets.d.ts +7 -0
  68. package/dist/layer2-static/detectors/workflow-overprovisioned-secrets.js +81 -0
  69. package/dist/layer2-static/detectors/workflow-ref-confusion.d.ts +7 -0
  70. package/dist/layer2-static/detectors/workflow-ref-confusion.js +69 -0
  71. package/dist/layer2-static/detectors/workflow-ref-version-mismatch.d.ts +9 -0
  72. package/dist/layer2-static/detectors/workflow-ref-version-mismatch.js +179 -0
  73. package/dist/layer2-static/detectors/workflow-secrets-inherit.d.ts +7 -0
  74. package/dist/layer2-static/detectors/workflow-secrets-inherit.js +59 -0
  75. package/dist/layer2-static/detectors/workflow-secrets-outside-env.d.ts +7 -0
  76. package/dist/layer2-static/detectors/workflow-secrets-outside-env.js +99 -0
  77. package/dist/layer2-static/detectors/workflow-self-hosted-runner.d.ts +7 -0
  78. package/dist/layer2-static/detectors/workflow-self-hosted-runner.js +67 -0
  79. package/dist/layer2-static/detectors/workflow-stale-action-refs.d.ts +7 -0
  80. package/dist/layer2-static/detectors/workflow-stale-action-refs.js +178 -0
  81. package/dist/layer2-static/detectors/workflow-superfluous-actions.d.ts +7 -0
  82. package/dist/layer2-static/detectors/workflow-superfluous-actions.js +70 -0
  83. package/dist/layer2-static/detectors/workflow-template-injection.d.ts +6 -0
  84. package/dist/layer2-static/detectors/workflow-template-injection.js +94 -0
  85. package/dist/layer2-static/detectors/workflow-undocumented-permissions.d.ts +7 -0
  86. package/dist/layer2-static/detectors/workflow-undocumented-permissions.js +101 -0
  87. package/dist/layer2-static/detectors/workflow-unpinned-images.d.ts +7 -0
  88. package/dist/layer2-static/detectors/workflow-unpinned-images.js +108 -0
  89. package/dist/layer2-static/detectors/workflow-unpinned-uses.d.ts +7 -0
  90. package/dist/layer2-static/detectors/workflow-unpinned-uses.js +60 -0
  91. package/dist/layer2-static/detectors/workflow-unredacted-secrets.d.ts +7 -0
  92. package/dist/layer2-static/detectors/workflow-unredacted-secrets.js +92 -0
  93. package/dist/layer2-static/detectors/workflow-unsound-condition.d.ts +7 -0
  94. package/dist/layer2-static/detectors/workflow-unsound-condition.js +90 -0
  95. package/dist/layer2-static/detectors/workflow-unsound-contains.d.ts +7 -0
  96. package/dist/layer2-static/detectors/workflow-unsound-contains.js +88 -0
  97. package/dist/layer2-static/detectors/workflow-use-trusted-publishing.d.ts +7 -0
  98. package/dist/layer2-static/detectors/workflow-use-trusted-publishing.js +66 -0
  99. package/dist/layer2-static/engine.d.ts +9 -1
  100. package/dist/layer2-static/engine.js +490 -50
  101. package/dist/layer2-static/github/cache.d.ts +8 -0
  102. package/dist/layer2-static/github/cache.js +41 -0
  103. package/dist/layer2-static/github/client.d.ts +17 -0
  104. package/dist/layer2-static/github/client.js +43 -0
  105. package/dist/layer2-static/rules/claude-code.json +16 -0
  106. package/dist/layer2-static/rules/codex.json +16 -0
  107. package/dist/layer2-static/rules/common.json +15 -0
  108. package/dist/layer2-static/rules/copilot.json +15 -0
  109. package/dist/layer2-static/rules/cursor.json +15 -0
  110. package/dist/layer2-static/rules/opencode.json +15 -0
  111. package/dist/layer2-static/workflow/injection-sinks.json +5 -0
  112. package/dist/layer2-static/workflow/parser.d.ts +3 -0
  113. package/dist/layer2-static/workflow/parser.js +88 -0
  114. package/dist/layer2-static/workflow/types.d.ts +15 -0
  115. package/dist/layer2-static/workflow/types.js +1 -0
  116. package/dist/pipeline.d.ts +1 -1
  117. package/dist/pipeline.js +3 -3
  118. package/dist/reporter/sarif.js +23 -5
  119. package/dist/reporter/terminal.js +12 -0
  120. package/dist/scan.d.ts +7 -1
  121. package/dist/scan.js +154 -40
  122. package/dist/types/finding.d.ts +1 -1
  123. package/dist/types/finding.js +5 -0
  124. package/package.json +1 -1
@@ -7,6 +7,41 @@ import { detectIdeSettingsIssues } from "./detectors/ide-settings.js";
7
7
  import { detectPluginManifestIssues } from "./detectors/plugin-manifest.js";
8
8
  import { detectRuleFileIssues } from "./detectors/rule-file.js";
9
9
  import { detectSymlinkEscapes } from "./detectors/symlink.js";
10
+ import { detectWorkflowExcessivePermissions } from "./detectors/workflow-excessive-permissions.js";
11
+ import { detectWorkflowDangerousTriggers } from "./detectors/workflow-dangerous-triggers.js";
12
+ import { detectWorkflowTemplateInjection } from "./detectors/workflow-template-injection.js";
13
+ import { detectWorkflowKnownVulnAction } from "./detectors/workflow-known-vuln-action.js";
14
+ import { detectWorkflowUnpinnedUses } from "./detectors/workflow-unpinned-uses.js";
15
+ import { detectWorkflowArtipacked } from "./detectors/workflow-artipacked.js";
16
+ import { detectWorkflowCachePoisoning } from "./detectors/workflow-cache-poisoning.js";
17
+ import { detectWorkflowGithubEnv } from "./detectors/workflow-github-env.js";
18
+ import { detectWorkflowInsecureCommands } from "./detectors/workflow-insecure-commands.js";
19
+ import { detectWorkflowSelfHostedRunner } from "./detectors/workflow-self-hosted-runner.js";
20
+ import { detectWorkflowOverprovisionedSecrets } from "./detectors/workflow-overprovisioned-secrets.js";
21
+ import { detectWorkflowSecretsOutsideEnv } from "./detectors/workflow-secrets-outside-env.js";
22
+ import { detectWorkflowSecretsInherit } from "./detectors/workflow-secrets-inherit.js";
23
+ import { detectWorkflowUndocumentedPermissions } from "./detectors/workflow-undocumented-permissions.js";
24
+ import { detectWorkflowUseTrustedPublishing } from "./detectors/workflow-use-trusted-publishing.js";
25
+ import { detectWorkflowArchivedUses } from "./detectors/workflow-archived-uses.js";
26
+ import { detectWorkflowStaleActionRefs } from "./detectors/workflow-stale-action-refs.js";
27
+ import { detectWorkflowForbiddenUses } from "./detectors/workflow-forbidden-uses.js";
28
+ import { detectWorkflowRefConfusion } from "./detectors/workflow-ref-confusion.js";
29
+ import { detectWorkflowRefVersionMismatch } from "./detectors/workflow-ref-version-mismatch.js";
30
+ import { detectWorkflowImpostorCommit } from "./detectors/workflow-impostor-commit.js";
31
+ import { detectWorkflowUnpinnedImages } from "./detectors/workflow-unpinned-images.js";
32
+ import { detectWorkflowAnonymousDefinition } from "./detectors/workflow-anonymous-definition.js";
33
+ import { detectWorkflowConcurrencyLimits } from "./detectors/workflow-concurrency-limits.js";
34
+ import { detectWorkflowSuperfluousActions } from "./detectors/workflow-superfluous-actions.js";
35
+ import { detectWorkflowMisfeature } from "./detectors/workflow-misfeature.js";
36
+ import { detectWorkflowObfuscation } from "./detectors/workflow-obfuscation.js";
37
+ import { detectWorkflowUnsoundCondition } from "./detectors/workflow-unsound-condition.js";
38
+ import { detectWorkflowUnsoundContains } from "./detectors/workflow-unsound-contains.js";
39
+ import { detectDependabotCooldown } from "./detectors/dependabot-cooldown.js";
40
+ import { detectDependabotExecution } from "./detectors/dependabot-execution.js";
41
+ import { detectWorkflowHardcodedContainerCredentials } from "./detectors/workflow-hardcoded-container-credentials.js";
42
+ import { detectWorkflowUnredactedSecrets } from "./detectors/workflow-unredacted-secrets.js";
43
+ import { detectWorkflowBotConditions } from "./detectors/workflow-bot-conditions.js";
44
+ import { filterRegisteredAudits } from "./audits/registry.js";
10
45
  import { FINDING_CATEGORIES } from "../types/finding.js";
11
46
  import { buildFindingEvidence } from "./evidence.js";
12
47
  import { evaluateRule, loadRulePacks } from "./rule-engine.js";
@@ -41,6 +76,18 @@ function remediationActionsForRule(rule) {
41
76
  }
42
77
  return ["remove_field", "replace_with_default"];
43
78
  }
79
+ function resolveDisabledAuditIds(policies) {
80
+ if (!policies) {
81
+ return [];
82
+ }
83
+ return Object.entries(policies)
84
+ .filter(([, policy]) => policy?.disable === true)
85
+ .map(([ruleId]) => ruleId);
86
+ }
87
+ function findRulePolicyConfig(policies, ruleId) {
88
+ const config = policies?.[ruleId]?.config;
89
+ return config && typeof config === "object" ? config : undefined;
90
+ }
44
91
  function findingFromRulePackMatch(file, rule) {
45
92
  const locationField = rule.query_type === "text_pattern" ? "content" : rule.query;
46
93
  const evidence = buildFindingEvidence({
@@ -103,61 +150,453 @@ function dedupeFindings(findings) {
103
150
  }
104
151
  return Array.from(deduped.values());
105
152
  }
106
- export function runStaticEngine(input) {
153
+ function buildFileAudits() {
154
+ return [
155
+ {
156
+ id: "env-overrides",
157
+ run: ({ file, input }) => detectEnvOverrides({
158
+ filePath: file.filePath,
159
+ parsed: file.parsed,
160
+ textContent: file.textContent,
161
+ trustedApiDomains: input.config.trustedApiDomains,
162
+ }),
163
+ },
164
+ {
165
+ id: "consent-bypass",
166
+ run: ({ file, input }) => detectConsentBypass({
167
+ filePath: file.filePath,
168
+ parsed: file.parsed,
169
+ textContent: file.textContent,
170
+ trustedApiDomains: input.config.trustedApiDomains,
171
+ }),
172
+ },
173
+ {
174
+ id: "command-execution",
175
+ run: ({ file, input }) => detectCommandExecution({
176
+ filePath: file.filePath,
177
+ parsed: file.parsed,
178
+ textContent: file.textContent,
179
+ knownSafeMcpServers: input.config.knownSafeMcpServers,
180
+ knownSafeFormatters: input.config.knownSafeFormatters,
181
+ knownSafeLspServers: input.config.knownSafeLspServers,
182
+ blockedCommands: input.config.blockedCommands,
183
+ }),
184
+ },
185
+ {
186
+ id: "ide-settings",
187
+ run: ({ file, input }) => input.config.checkIdeSettings
188
+ ? detectIdeSettingsIssues({
189
+ filePath: file.filePath,
190
+ parsed: file.parsed,
191
+ textContent: file.textContent,
192
+ projectRoot: input.projectRoot,
193
+ })
194
+ : [],
195
+ },
196
+ {
197
+ id: "plugin-manifest",
198
+ run: ({ file, input }) => detectPluginManifestIssues({
199
+ filePath: file.filePath,
200
+ parsed: file.parsed,
201
+ textContent: file.textContent,
202
+ trustedApiDomains: input.config.trustedApiDomains,
203
+ blockedCommands: input.config.blockedCommands,
204
+ }),
205
+ },
206
+ {
207
+ id: "advisory-intelligence",
208
+ run: ({ file }) => detectAdvisoryIntelligence({
209
+ filePath: file.filePath,
210
+ parsed: file.parsed,
211
+ textContent: file.textContent,
212
+ }),
213
+ },
214
+ {
215
+ id: "rule-file",
216
+ run: ({ file, input }) => file.format === "text" || file.format === "markdown"
217
+ ? detectRuleFileIssues({
218
+ filePath: file.filePath,
219
+ textContent: file.textContent,
220
+ unicodeAnalysis: input.config.unicodeAnalysis,
221
+ })
222
+ : [],
223
+ },
224
+ {
225
+ id: "workflow-unpinned-uses",
226
+ run: ({ file, input }) => input.config.workflowAuditsEnabled
227
+ ? detectWorkflowUnpinnedUses({
228
+ filePath: file.filePath,
229
+ parsed: file.parsed,
230
+ textContent: file.textContent,
231
+ })
232
+ : [],
233
+ },
234
+ {
235
+ id: "workflow-dangerous-triggers",
236
+ run: ({ file, input }) => input.config.workflowAuditsEnabled
237
+ ? detectWorkflowDangerousTriggers({
238
+ filePath: file.filePath,
239
+ parsed: file.parsed,
240
+ })
241
+ : [],
242
+ },
243
+ {
244
+ id: "workflow-excessive-permissions",
245
+ run: ({ file, input }) => input.config.workflowAuditsEnabled
246
+ ? detectWorkflowExcessivePermissions({
247
+ filePath: file.filePath,
248
+ parsed: file.parsed,
249
+ })
250
+ : [],
251
+ },
252
+ {
253
+ id: "workflow-template-injection",
254
+ run: ({ file, input }) => input.config.workflowAuditsEnabled
255
+ ? detectWorkflowTemplateInjection({
256
+ filePath: file.filePath,
257
+ parsed: file.parsed,
258
+ })
259
+ : [],
260
+ },
261
+ {
262
+ id: "workflow-known-vuln-action",
263
+ onlineRequired: true,
264
+ run: ({ file, input }) => input.config.workflowAuditsEnabled
265
+ ? detectWorkflowKnownVulnAction({
266
+ filePath: file.filePath,
267
+ parsed: file.parsed,
268
+ runtimeMode: input.config.runtimeMode,
269
+ })
270
+ : [],
271
+ },
272
+ {
273
+ id: "workflow-artipacked",
274
+ run: ({ file, input }) => input.config.workflowAuditsEnabled
275
+ ? detectWorkflowArtipacked({
276
+ filePath: file.filePath,
277
+ parsed: file.parsed,
278
+ textContent: file.textContent,
279
+ })
280
+ : [],
281
+ },
282
+ {
283
+ id: "workflow-cache-poisoning",
284
+ run: ({ file, input }) => input.config.workflowAuditsEnabled
285
+ ? detectWorkflowCachePoisoning({
286
+ filePath: file.filePath,
287
+ parsed: file.parsed,
288
+ textContent: file.textContent,
289
+ })
290
+ : [],
291
+ },
292
+ {
293
+ id: "workflow-github-env",
294
+ run: ({ file, input }) => input.config.workflowAuditsEnabled
295
+ ? detectWorkflowGithubEnv({
296
+ filePath: file.filePath,
297
+ parsed: file.parsed,
298
+ textContent: file.textContent,
299
+ })
300
+ : [],
301
+ },
302
+ {
303
+ id: "workflow-insecure-commands",
304
+ run: ({ file, input }) => input.config.workflowAuditsEnabled
305
+ ? detectWorkflowInsecureCommands({
306
+ filePath: file.filePath,
307
+ parsed: file.parsed,
308
+ textContent: file.textContent,
309
+ })
310
+ : [],
311
+ },
312
+ {
313
+ id: "workflow-self-hosted-runner",
314
+ run: ({ file, input }) => input.config.workflowAuditsEnabled
315
+ ? detectWorkflowSelfHostedRunner({
316
+ filePath: file.filePath,
317
+ parsed: file.parsed,
318
+ textContent: file.textContent,
319
+ })
320
+ : [],
321
+ },
322
+ {
323
+ id: "workflow-overprovisioned-secrets",
324
+ run: ({ file, input }) => input.config.workflowAuditsEnabled
325
+ ? detectWorkflowOverprovisionedSecrets({
326
+ filePath: file.filePath,
327
+ parsed: file.parsed,
328
+ textContent: file.textContent,
329
+ })
330
+ : [],
331
+ },
332
+ {
333
+ id: "workflow-secrets-outside-env",
334
+ run: ({ file, input }) => input.config.workflowAuditsEnabled
335
+ ? detectWorkflowSecretsOutsideEnv({
336
+ filePath: file.filePath,
337
+ parsed: file.parsed,
338
+ textContent: file.textContent,
339
+ })
340
+ : [],
341
+ },
342
+ {
343
+ id: "workflow-secrets-inherit",
344
+ run: ({ file, input }) => input.config.workflowAuditsEnabled
345
+ ? detectWorkflowSecretsInherit({
346
+ filePath: file.filePath,
347
+ parsed: file.parsed,
348
+ textContent: file.textContent,
349
+ })
350
+ : [],
351
+ },
352
+ {
353
+ id: "workflow-undocumented-permissions",
354
+ run: ({ file, input }) => input.config.workflowAuditsEnabled
355
+ ? detectWorkflowUndocumentedPermissions({
356
+ filePath: file.filePath,
357
+ parsed: file.parsed,
358
+ textContent: file.textContent,
359
+ })
360
+ : [],
361
+ },
362
+ {
363
+ id: "workflow-use-trusted-publishing",
364
+ run: ({ file, input }) => input.config.workflowAuditsEnabled
365
+ ? detectWorkflowUseTrustedPublishing({
366
+ filePath: file.filePath,
367
+ parsed: file.parsed,
368
+ textContent: file.textContent,
369
+ })
370
+ : [],
371
+ },
372
+ {
373
+ id: "workflow-archived-uses",
374
+ onlineRequired: true,
375
+ run: ({ file, input }) => input.config.workflowAuditsEnabled
376
+ ? detectWorkflowArchivedUses({
377
+ filePath: file.filePath,
378
+ parsed: file.parsed,
379
+ textContent: file.textContent,
380
+ })
381
+ : [],
382
+ },
383
+ {
384
+ id: "workflow-stale-action-refs",
385
+ onlineRequired: true,
386
+ run: ({ file, input }) => input.config.workflowAuditsEnabled
387
+ ? detectWorkflowStaleActionRefs({
388
+ filePath: file.filePath,
389
+ parsed: file.parsed,
390
+ textContent: file.textContent,
391
+ })
392
+ : [],
393
+ },
394
+ {
395
+ id: "workflow-forbidden-uses",
396
+ run: ({ file, input }) => input.config.workflowAuditsEnabled
397
+ ? detectWorkflowForbiddenUses({
398
+ filePath: file.filePath,
399
+ parsed: file.parsed,
400
+ textContent: file.textContent,
401
+ config: findRulePolicyConfig(input.config.rulePolicies, "workflow-forbidden-uses"),
402
+ })
403
+ : [],
404
+ },
405
+ {
406
+ id: "workflow-ref-confusion",
407
+ run: ({ file, input }) => input.config.workflowAuditsEnabled
408
+ ? detectWorkflowRefConfusion({
409
+ filePath: file.filePath,
410
+ parsed: file.parsed,
411
+ textContent: file.textContent,
412
+ })
413
+ : [],
414
+ },
415
+ {
416
+ id: "workflow-ref-version-mismatch",
417
+ onlineRequired: true,
418
+ run: ({ file, input }) => input.config.workflowAuditsEnabled
419
+ ? detectWorkflowRefVersionMismatch({
420
+ filePath: file.filePath,
421
+ parsed: file.parsed,
422
+ textContent: file.textContent,
423
+ runtimeMode: input.config.runtimeMode,
424
+ })
425
+ : [],
426
+ },
427
+ {
428
+ id: "workflow-impostor-commit",
429
+ onlineRequired: true,
430
+ run: ({ file, input }) => input.config.workflowAuditsEnabled
431
+ ? detectWorkflowImpostorCommit({
432
+ filePath: file.filePath,
433
+ parsed: file.parsed,
434
+ textContent: file.textContent,
435
+ runtimeMode: input.config.runtimeMode,
436
+ })
437
+ : [],
438
+ },
439
+ {
440
+ id: "workflow-unpinned-images",
441
+ run: ({ file, input }) => input.config.workflowAuditsEnabled
442
+ ? detectWorkflowUnpinnedImages({
443
+ filePath: file.filePath,
444
+ parsed: file.parsed,
445
+ textContent: file.textContent,
446
+ })
447
+ : [],
448
+ },
449
+ {
450
+ id: "workflow-anonymous-definition",
451
+ run: ({ file, input }) => input.config.workflowAuditsEnabled
452
+ ? detectWorkflowAnonymousDefinition({
453
+ filePath: file.filePath,
454
+ parsed: file.parsed,
455
+ textContent: file.textContent,
456
+ })
457
+ : [],
458
+ },
459
+ {
460
+ id: "workflow-concurrency-limits",
461
+ run: ({ file, input }) => input.config.workflowAuditsEnabled
462
+ ? detectWorkflowConcurrencyLimits({
463
+ filePath: file.filePath,
464
+ parsed: file.parsed,
465
+ textContent: file.textContent,
466
+ })
467
+ : [],
468
+ },
469
+ {
470
+ id: "workflow-superfluous-actions",
471
+ run: ({ file, input }) => input.config.workflowAuditsEnabled
472
+ ? detectWorkflowSuperfluousActions({
473
+ filePath: file.filePath,
474
+ parsed: file.parsed,
475
+ textContent: file.textContent,
476
+ })
477
+ : [],
478
+ },
479
+ {
480
+ id: "workflow-misfeature",
481
+ run: ({ file, input }) => input.config.workflowAuditsEnabled
482
+ ? detectWorkflowMisfeature({
483
+ filePath: file.filePath,
484
+ parsed: file.parsed,
485
+ textContent: file.textContent,
486
+ })
487
+ : [],
488
+ },
489
+ {
490
+ id: "workflow-obfuscation",
491
+ run: ({ file, input }) => input.config.workflowAuditsEnabled
492
+ ? detectWorkflowObfuscation({
493
+ filePath: file.filePath,
494
+ parsed: file.parsed,
495
+ textContent: file.textContent,
496
+ })
497
+ : [],
498
+ },
499
+ {
500
+ id: "workflow-unsound-condition",
501
+ run: ({ file, input }) => input.config.workflowAuditsEnabled
502
+ ? detectWorkflowUnsoundCondition({
503
+ filePath: file.filePath,
504
+ parsed: file.parsed,
505
+ textContent: file.textContent,
506
+ })
507
+ : [],
508
+ },
509
+ {
510
+ id: "workflow-unsound-contains",
511
+ run: ({ file, input }) => input.config.workflowAuditsEnabled
512
+ ? detectWorkflowUnsoundContains({
513
+ filePath: file.filePath,
514
+ parsed: file.parsed,
515
+ textContent: file.textContent,
516
+ })
517
+ : [],
518
+ },
519
+ {
520
+ id: "dependabot-cooldown",
521
+ run: ({ file, input }) => input.config.workflowAuditsEnabled
522
+ ? detectDependabotCooldown({
523
+ filePath: file.filePath,
524
+ parsed: file.parsed,
525
+ textContent: file.textContent,
526
+ })
527
+ : [],
528
+ },
529
+ {
530
+ id: "dependabot-execution",
531
+ run: ({ file, input }) => input.config.workflowAuditsEnabled
532
+ ? detectDependabotExecution({
533
+ filePath: file.filePath,
534
+ parsed: file.parsed,
535
+ textContent: file.textContent,
536
+ })
537
+ : [],
538
+ },
539
+ {
540
+ id: "hardcoded-container-credentials",
541
+ run: ({ file, input }) => input.config.workflowAuditsEnabled
542
+ ? detectWorkflowHardcodedContainerCredentials({
543
+ filePath: file.filePath,
544
+ parsed: file.parsed,
545
+ textContent: file.textContent,
546
+ })
547
+ : [],
548
+ },
549
+ {
550
+ id: "unredacted-secrets",
551
+ run: ({ file, input }) => input.config.workflowAuditsEnabled
552
+ ? detectWorkflowUnredactedSecrets({
553
+ filePath: file.filePath,
554
+ parsed: file.parsed,
555
+ textContent: file.textContent,
556
+ })
557
+ : [],
558
+ },
559
+ {
560
+ id: "bot-conditions",
561
+ run: ({ file, input }) => input.config.workflowAuditsEnabled
562
+ ? detectWorkflowBotConditions({
563
+ filePath: file.filePath,
564
+ parsed: file.parsed,
565
+ textContent: file.textContent,
566
+ })
567
+ : [],
568
+ },
569
+ ];
570
+ }
571
+ function buildGlobalAudits() {
572
+ return [
573
+ {
574
+ id: "symlink-escapes",
575
+ run: ({ input }) => detectSymlinkEscapes({ symlinkEscapes: input.symlinkEscapes }),
576
+ },
577
+ {
578
+ id: "git-hooks",
579
+ run: ({ input }) => detectGitHookIssues({ hooks: input.hooks, knownSafeHooks: input.config.knownSafeHooks }),
580
+ },
581
+ ];
582
+ }
583
+ export async function runStaticEngine(input) {
107
584
  const findings = [];
585
+ const runtimeSelection = {
586
+ persona: input.config.persona,
587
+ runtimeMode: input.config.runtimeMode,
588
+ disabledAuditIds: resolveDisabledAuditIds(input.config.rulePolicies),
589
+ };
590
+ const activeFileAudits = filterRegisteredAudits(buildFileAudits(), runtimeSelection);
591
+ const activeGlobalAudits = filterRegisteredAudits(buildGlobalAudits(), runtimeSelection);
108
592
  const rulePackRules = loadRulePacks({
109
593
  rule_pack_paths: input.config.rulePackPaths ?? [],
110
594
  allowed_rules: input.config.allowedRules ?? [],
111
595
  skip_rules: input.config.skipRules ?? [],
112
596
  });
113
597
  for (const file of input.files) {
114
- findings.push(...detectEnvOverrides({
115
- filePath: file.filePath,
116
- parsed: file.parsed,
117
- textContent: file.textContent,
118
- trustedApiDomains: input.config.trustedApiDomains,
119
- }));
120
- findings.push(...detectConsentBypass({
121
- filePath: file.filePath,
122
- parsed: file.parsed,
123
- textContent: file.textContent,
124
- trustedApiDomains: input.config.trustedApiDomains,
125
- }));
126
- findings.push(...detectCommandExecution({
127
- filePath: file.filePath,
128
- parsed: file.parsed,
129
- textContent: file.textContent,
130
- knownSafeMcpServers: input.config.knownSafeMcpServers,
131
- knownSafeFormatters: input.config.knownSafeFormatters,
132
- knownSafeLspServers: input.config.knownSafeLspServers,
133
- blockedCommands: input.config.blockedCommands,
134
- }));
135
- if (input.config.checkIdeSettings) {
136
- findings.push(...detectIdeSettingsIssues({
137
- filePath: file.filePath,
138
- parsed: file.parsed,
139
- textContent: file.textContent,
140
- projectRoot: input.projectRoot,
141
- }));
142
- }
143
- findings.push(...detectPluginManifestIssues({
144
- filePath: file.filePath,
145
- parsed: file.parsed,
146
- textContent: file.textContent,
147
- trustedApiDomains: input.config.trustedApiDomains,
148
- blockedCommands: input.config.blockedCommands,
149
- }));
150
- findings.push(...detectAdvisoryIntelligence({
151
- filePath: file.filePath,
152
- parsed: file.parsed,
153
- textContent: file.textContent,
154
- }));
155
- if (file.format === "text" || file.format === "markdown") {
156
- findings.push(...detectRuleFileIssues({
157
- filePath: file.filePath,
158
- textContent: file.textContent,
159
- unicodeAnalysis: input.config.unicodeAnalysis,
160
- }));
598
+ for (const audit of activeFileAudits) {
599
+ findings.push(...(await audit.run({ file, input })));
161
600
  }
162
601
  for (const rule of rulePackRules) {
163
602
  if (!evaluateRule(rule, {
@@ -174,7 +613,8 @@ export function runStaticEngine(input) {
174
613
  }
175
614
  }
176
615
  }
177
- findings.push(...detectSymlinkEscapes({ symlinkEscapes: input.symlinkEscapes }));
178
- findings.push(...detectGitHookIssues({ hooks: input.hooks, knownSafeHooks: input.config.knownSafeHooks }));
616
+ for (const audit of activeGlobalAudits) {
617
+ findings.push(...(await audit.run({ input })));
618
+ }
179
619
  return dedupeFindings(findings);
180
620
  }
@@ -0,0 +1,8 @@
1
+ export interface AdvisoryPayload {
2
+ generatedAt: number;
3
+ advisories: Record<string, string[]>;
4
+ }
5
+ export declare const GITHUB_METADATA_CACHE_FILE = "gha-advisories.json";
6
+ export declare function resolveGithubMetadataCachePath(cacheDir: string): string;
7
+ export declare function loadCachedAdvisoryPayload(cacheDir: string, maxAgeMs: number, now?: number): AdvisoryPayload | null;
8
+ export declare function saveCachedAdvisoryPayload(cacheDir: string, payload: AdvisoryPayload): void;
@@ -0,0 +1,41 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ export const GITHUB_METADATA_CACHE_FILE = "gha-advisories.json";
4
+ export function resolveGithubMetadataCachePath(cacheDir) {
5
+ return join(cacheDir, GITHUB_METADATA_CACHE_FILE);
6
+ }
7
+ function isRecord(value) {
8
+ return typeof value === "object" && value !== null && !Array.isArray(value);
9
+ }
10
+ function isAdvisoryPayload(value) {
11
+ if (!isRecord(value) || typeof value.generatedAt !== "number") {
12
+ return false;
13
+ }
14
+ if (!isRecord(value.advisories)) {
15
+ return false;
16
+ }
17
+ return Object.values(value.advisories).every((entry) => Array.isArray(entry) && entry.every((item) => typeof item === "string"));
18
+ }
19
+ export function loadCachedAdvisoryPayload(cacheDir, maxAgeMs, now = Date.now()) {
20
+ const path = resolveGithubMetadataCachePath(cacheDir);
21
+ if (!existsSync(path)) {
22
+ return null;
23
+ }
24
+ try {
25
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
26
+ if (!isAdvisoryPayload(parsed)) {
27
+ return null;
28
+ }
29
+ if (now - parsed.generatedAt > maxAgeMs) {
30
+ return null;
31
+ }
32
+ return parsed;
33
+ }
34
+ catch {
35
+ return null;
36
+ }
37
+ }
38
+ export function saveCachedAdvisoryPayload(cacheDir, payload) {
39
+ mkdirSync(cacheDir, { recursive: true });
40
+ writeFileSync(resolveGithubMetadataCachePath(cacheDir), JSON.stringify(payload, null, 2), "utf8");
41
+ }
@@ -0,0 +1,17 @@
1
+ import type { RuntimeMode } from "../../config.js";
2
+ import { type AdvisoryPayload } from "./cache.js";
3
+ export interface GithubMetadataClientOptions {
4
+ runtimeMode?: RuntimeMode;
5
+ cacheDir?: string;
6
+ cacheMaxAgeMs?: number;
7
+ now?: number;
8
+ }
9
+ export interface GithubMetadataClient {
10
+ runtimeMode: RuntimeMode;
11
+ cacheDir: string;
12
+ cacheMaxAgeMs: number;
13
+ isOnlineEnabled(): boolean;
14
+ loadKnownVulnerableActions(bundle: Record<string, string[]>): AdvisoryPayload;
15
+ }
16
+ export declare function createGithubMetadataClient(options?: GithubMetadataClientOptions): GithubMetadataClient;
17
+ export declare function loadBundledGithubAdvisories(options?: GithubMetadataClientOptions): AdvisoryPayload;
@@ -0,0 +1,43 @@
1
+ import { homedir } from "node:os";
2
+ import { join } from "node:path";
3
+ import bundledAdvisories from "../advisories/gha-known-vulnerable-actions.json" with { type: "json" };
4
+ import { loadCachedAdvisoryPayload, saveCachedAdvisoryPayload, } from "./cache.js";
5
+ function normalizeAdvisoryMap(value) {
6
+ const normalized = {};
7
+ for (const [action, versions] of Object.entries(value)) {
8
+ normalized[action.toLowerCase()] = versions.map((version) => version.toLowerCase());
9
+ }
10
+ return normalized;
11
+ }
12
+ export function createGithubMetadataClient(options = {}) {
13
+ const runtimeMode = options.runtimeMode ?? "offline";
14
+ const cacheDir = options.cacheDir ?? join(homedir(), ".codegate", "cache");
15
+ const cacheMaxAgeMs = options.cacheMaxAgeMs ?? 24 * 60 * 60 * 1000;
16
+ const now = options.now ?? Date.now();
17
+ return {
18
+ runtimeMode,
19
+ cacheDir,
20
+ cacheMaxAgeMs,
21
+ isOnlineEnabled() {
22
+ return runtimeMode === "online";
23
+ },
24
+ loadKnownVulnerableActions(bundle) {
25
+ const payload = {
26
+ generatedAt: now,
27
+ advisories: normalizeAdvisoryMap(bundle),
28
+ };
29
+ if (!runtimeMode || runtimeMode !== "online") {
30
+ return payload;
31
+ }
32
+ const cached = loadCachedAdvisoryPayload(cacheDir, cacheMaxAgeMs, now);
33
+ if (cached) {
34
+ return cached;
35
+ }
36
+ saveCachedAdvisoryPayload(cacheDir, payload);
37
+ return payload;
38
+ },
39
+ };
40
+ }
41
+ export function loadBundledGithubAdvisories(options = {}) {
42
+ return createGithubMetadataClient(options).loadKnownVulnerableActions(bundledAdvisories);
43
+ }