codegate-ai 0.7.0 → 0.9.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 (135) hide show
  1. package/README.md +109 -40
  2. package/dist/cli.js +85 -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.d.ts +16 -0
  6. package/dist/commands/scan-content-command.js +65 -0
  7. package/dist/config/inline-ignore.d.ts +11 -0
  8. package/dist/config/inline-ignore.js +57 -0
  9. package/dist/config/suppression-policy.d.ts +22 -0
  10. package/dist/config/suppression-policy.js +146 -0
  11. package/dist/config.d.ts +23 -0
  12. package/dist/config.js +188 -3
  13. package/dist/layer2-static/action/parser.d.ts +3 -0
  14. package/dist/layer2-static/action/parser.js +165 -0
  15. package/dist/layer2-static/action/types.d.ts +39 -0
  16. package/dist/layer2-static/action/types.js +1 -0
  17. package/dist/layer2-static/advisories/agent-components.json +62 -0
  18. package/dist/layer2-static/advisories/cache.d.ts +6 -0
  19. package/dist/layer2-static/advisories/cache.js +44 -0
  20. package/dist/layer2-static/advisories/cache.ts +62 -0
  21. package/dist/layer2-static/advisories/gha-advisory-client.d.ts +7 -0
  22. package/dist/layer2-static/advisories/gha-advisory-client.js +4 -0
  23. package/dist/layer2-static/advisories/gha-advisory-client.ts +13 -0
  24. package/dist/layer2-static/advisories/gha-known-vulnerable-actions.json +4 -0
  25. package/dist/layer2-static/audits/registry.d.ts +3 -0
  26. package/dist/layer2-static/audits/registry.js +29 -0
  27. package/dist/layer2-static/audits/types.d.ts +12 -0
  28. package/dist/layer2-static/audits/types.js +1 -0
  29. package/dist/layer2-static/dependabot/parser.d.ts +3 -0
  30. package/dist/layer2-static/dependabot/parser.js +189 -0
  31. package/dist/layer2-static/dependabot/types.d.ts +50 -0
  32. package/dist/layer2-static/dependabot/types.js +1 -0
  33. package/dist/layer2-static/detectors/advisory-intelligence.d.ts +7 -0
  34. package/dist/layer2-static/detectors/advisory-intelligence.js +170 -0
  35. package/dist/layer2-static/detectors/command-exec.js +6 -0
  36. package/dist/layer2-static/detectors/dependabot-cooldown.d.ts +7 -0
  37. package/dist/layer2-static/detectors/dependabot-cooldown.js +54 -0
  38. package/dist/layer2-static/detectors/dependabot-execution.d.ts +7 -0
  39. package/dist/layer2-static/detectors/dependabot-execution.js +68 -0
  40. package/dist/layer2-static/detectors/rule-file.js +5 -0
  41. package/dist/layer2-static/detectors/workflow-anonymous-definition.d.ts +7 -0
  42. package/dist/layer2-static/detectors/workflow-anonymous-definition.js +48 -0
  43. package/dist/layer2-static/detectors/workflow-archived-uses.d.ts +7 -0
  44. package/dist/layer2-static/detectors/workflow-archived-uses.js +147 -0
  45. package/dist/layer2-static/detectors/workflow-artipacked.d.ts +7 -0
  46. package/dist/layer2-static/detectors/workflow-artipacked.js +62 -0
  47. package/dist/layer2-static/detectors/workflow-bot-conditions.d.ts +7 -0
  48. package/dist/layer2-static/detectors/workflow-bot-conditions.js +108 -0
  49. package/dist/layer2-static/detectors/workflow-cache-poisoning.d.ts +7 -0
  50. package/dist/layer2-static/detectors/workflow-cache-poisoning.js +67 -0
  51. package/dist/layer2-static/detectors/workflow-concurrency-limits.d.ts +7 -0
  52. package/dist/layer2-static/detectors/workflow-concurrency-limits.js +76 -0
  53. package/dist/layer2-static/detectors/workflow-dangerous-triggers.d.ts +6 -0
  54. package/dist/layer2-static/detectors/workflow-dangerous-triggers.js +38 -0
  55. package/dist/layer2-static/detectors/workflow-excessive-permissions.d.ts +6 -0
  56. package/dist/layer2-static/detectors/workflow-excessive-permissions.js +65 -0
  57. package/dist/layer2-static/detectors/workflow-forbidden-uses.d.ts +12 -0
  58. package/dist/layer2-static/detectors/workflow-forbidden-uses.js +148 -0
  59. package/dist/layer2-static/detectors/workflow-github-env.d.ts +7 -0
  60. package/dist/layer2-static/detectors/workflow-github-env.js +49 -0
  61. package/dist/layer2-static/detectors/workflow-hardcoded-container-credentials.d.ts +7 -0
  62. package/dist/layer2-static/detectors/workflow-hardcoded-container-credentials.js +90 -0
  63. package/dist/layer2-static/detectors/workflow-impostor-commit.d.ts +9 -0
  64. package/dist/layer2-static/detectors/workflow-impostor-commit.js +127 -0
  65. package/dist/layer2-static/detectors/workflow-insecure-commands.d.ts +7 -0
  66. package/dist/layer2-static/detectors/workflow-insecure-commands.js +72 -0
  67. package/dist/layer2-static/detectors/workflow-known-vuln-action.d.ts +8 -0
  68. package/dist/layer2-static/detectors/workflow-known-vuln-action.js +70 -0
  69. package/dist/layer2-static/detectors/workflow-misfeature.d.ts +7 -0
  70. package/dist/layer2-static/detectors/workflow-misfeature.js +101 -0
  71. package/dist/layer2-static/detectors/workflow-obfuscation.d.ts +7 -0
  72. package/dist/layer2-static/detectors/workflow-obfuscation.js +79 -0
  73. package/dist/layer2-static/detectors/workflow-overprovisioned-secrets.d.ts +7 -0
  74. package/dist/layer2-static/detectors/workflow-overprovisioned-secrets.js +81 -0
  75. package/dist/layer2-static/detectors/workflow-ref-confusion.d.ts +7 -0
  76. package/dist/layer2-static/detectors/workflow-ref-confusion.js +69 -0
  77. package/dist/layer2-static/detectors/workflow-ref-version-mismatch.d.ts +9 -0
  78. package/dist/layer2-static/detectors/workflow-ref-version-mismatch.js +179 -0
  79. package/dist/layer2-static/detectors/workflow-secrets-inherit.d.ts +7 -0
  80. package/dist/layer2-static/detectors/workflow-secrets-inherit.js +59 -0
  81. package/dist/layer2-static/detectors/workflow-secrets-outside-env.d.ts +7 -0
  82. package/dist/layer2-static/detectors/workflow-secrets-outside-env.js +99 -0
  83. package/dist/layer2-static/detectors/workflow-self-hosted-runner.d.ts +7 -0
  84. package/dist/layer2-static/detectors/workflow-self-hosted-runner.js +67 -0
  85. package/dist/layer2-static/detectors/workflow-stale-action-refs.d.ts +7 -0
  86. package/dist/layer2-static/detectors/workflow-stale-action-refs.js +178 -0
  87. package/dist/layer2-static/detectors/workflow-superfluous-actions.d.ts +7 -0
  88. package/dist/layer2-static/detectors/workflow-superfluous-actions.js +70 -0
  89. package/dist/layer2-static/detectors/workflow-template-injection.d.ts +6 -0
  90. package/dist/layer2-static/detectors/workflow-template-injection.js +94 -0
  91. package/dist/layer2-static/detectors/workflow-undocumented-permissions.d.ts +7 -0
  92. package/dist/layer2-static/detectors/workflow-undocumented-permissions.js +101 -0
  93. package/dist/layer2-static/detectors/workflow-unpinned-images.d.ts +7 -0
  94. package/dist/layer2-static/detectors/workflow-unpinned-images.js +108 -0
  95. package/dist/layer2-static/detectors/workflow-unpinned-uses.d.ts +7 -0
  96. package/dist/layer2-static/detectors/workflow-unpinned-uses.js +60 -0
  97. package/dist/layer2-static/detectors/workflow-unredacted-secrets.d.ts +7 -0
  98. package/dist/layer2-static/detectors/workflow-unredacted-secrets.js +92 -0
  99. package/dist/layer2-static/detectors/workflow-unsound-condition.d.ts +7 -0
  100. package/dist/layer2-static/detectors/workflow-unsound-condition.js +90 -0
  101. package/dist/layer2-static/detectors/workflow-unsound-contains.d.ts +7 -0
  102. package/dist/layer2-static/detectors/workflow-unsound-contains.js +88 -0
  103. package/dist/layer2-static/detectors/workflow-use-trusted-publishing.d.ts +7 -0
  104. package/dist/layer2-static/detectors/workflow-use-trusted-publishing.js +66 -0
  105. package/dist/layer2-static/engine.d.ts +13 -2
  106. package/dist/layer2-static/engine.js +578 -41
  107. package/dist/layer2-static/github/cache.d.ts +8 -0
  108. package/dist/layer2-static/github/cache.js +41 -0
  109. package/dist/layer2-static/github/client.d.ts +17 -0
  110. package/dist/layer2-static/github/client.js +43 -0
  111. package/dist/layer2-static/rule-engine.d.ts +1 -1
  112. package/dist/layer2-static/rule-engine.js +1 -13
  113. package/dist/layer2-static/rule-pack-loader.d.ts +10 -0
  114. package/dist/layer2-static/rule-pack-loader.js +187 -0
  115. package/dist/layer2-static/workflow/injection-sinks.json +5 -0
  116. package/dist/layer2-static/workflow/parser.d.ts +3 -0
  117. package/dist/layer2-static/workflow/parser.js +88 -0
  118. package/dist/layer2-static/workflow/types.d.ts +15 -0
  119. package/dist/layer2-static/workflow/types.js +1 -0
  120. package/dist/layer3-dynamic/toxic-flow.js +6 -0
  121. package/dist/pipeline.d.ts +1 -1
  122. package/dist/pipeline.js +11 -10
  123. package/dist/report/finding-fingerprint.d.ts +5 -0
  124. package/dist/report/finding-fingerprint.js +47 -0
  125. package/dist/reporter/markdown.js +25 -3
  126. package/dist/reporter/sarif.js +25 -5
  127. package/dist/reporter/terminal.js +37 -0
  128. package/dist/scan-target/fetch-plan.d.ts +8 -0
  129. package/dist/scan-target/fetch-plan.js +30 -0
  130. package/dist/scan-target/staging.js +60 -5
  131. package/dist/scan.d.ts +7 -1
  132. package/dist/scan.js +157 -40
  133. package/dist/types/finding.d.ts +10 -1
  134. package/dist/types/finding.js +5 -0
  135. package/package.json +1 -1
@@ -1,4 +1,5 @@
1
1
  import { detectCommandExecution } from "./detectors/command-exec.js";
2
+ import { detectAdvisoryIntelligence } from "./detectors/advisory-intelligence.js";
2
3
  import { detectConsentBypass } from "./detectors/consent-bypass.js";
3
4
  import { detectEnvOverrides } from "./detectors/env-override.js";
4
5
  import { detectGitHookIssues } from "./detectors/git-hooks.js";
@@ -6,6 +7,125 @@ import { detectIdeSettingsIssues } from "./detectors/ide-settings.js";
6
7
  import { detectPluginManifestIssues } from "./detectors/plugin-manifest.js";
7
8
  import { detectRuleFileIssues } from "./detectors/rule-file.js";
8
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";
45
+ import { FINDING_CATEGORIES } from "../types/finding.js";
46
+ import { buildFindingEvidence } from "./evidence.js";
47
+ import { evaluateRule, loadRulePacks } from "./rule-engine.js";
48
+ const GENERIC_AFFECTED_TOOLS = [
49
+ "claude-code",
50
+ "codex-cli",
51
+ "opencode",
52
+ "cursor",
53
+ "windsurf",
54
+ "github-copilot",
55
+ ];
56
+ function parseRuleSeverity(value) {
57
+ const normalized = value.trim().toUpperCase();
58
+ if (normalized === "CRITICAL" ||
59
+ normalized === "HIGH" ||
60
+ normalized === "MEDIUM" ||
61
+ normalized === "LOW") {
62
+ return normalized;
63
+ }
64
+ return "INFO";
65
+ }
66
+ function parseRuleCategory(value) {
67
+ const normalized = value.trim().toUpperCase();
68
+ if (FINDING_CATEGORIES.some((category) => category === normalized)) {
69
+ return normalized;
70
+ }
71
+ return "CONFIG_PRESENT";
72
+ }
73
+ function remediationActionsForRule(rule) {
74
+ if (rule.query_type === "text_pattern") {
75
+ return ["quarantine_file", "remove_block"];
76
+ }
77
+ return ["remove_field", "replace_with_default"];
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
+ }
91
+ function findingFromRulePackMatch(file, rule) {
92
+ const locationField = rule.query_type === "text_pattern" ? "content" : rule.query;
93
+ const evidence = buildFindingEvidence({
94
+ textContent: file.textContent,
95
+ searchTerms: [rule.query],
96
+ fallbackValue: `${locationField} matched rule ${rule.id}`,
97
+ });
98
+ const affectedTools = rule.tool === "*" ? GENERIC_AFFECTED_TOOLS : [rule.tool];
99
+ return {
100
+ rule_id: rule.id,
101
+ finding_id: `RULE_PACK-${rule.id}-${file.filePath}-${locationField}`,
102
+ severity: parseRuleSeverity(rule.severity),
103
+ category: parseRuleCategory(rule.category),
104
+ layer: "L2",
105
+ file_path: file.filePath,
106
+ location: { field: locationField },
107
+ description: rule.description,
108
+ affected_tools: affectedTools,
109
+ cve: rule.cve ?? null,
110
+ owasp: rule.owasp,
111
+ cwe: rule.cwe,
112
+ confidence: "HIGH",
113
+ fixable: true,
114
+ remediation_actions: remediationActionsForRule(rule),
115
+ metadata: {
116
+ sources: [file.filePath, locationField],
117
+ risk_tags: ["rule-pack"],
118
+ origin: "rule-pack",
119
+ },
120
+ evidence: evidence?.evidence ?? null,
121
+ suppressed: false,
122
+ };
123
+ }
124
+ function hasEquivalentFinding(findings, candidate) {
125
+ return findings.some((finding) => finding.rule_id === candidate.rule_id &&
126
+ finding.file_path === candidate.file_path &&
127
+ (finding.location.field ?? "") === (candidate.location.field ?? ""));
128
+ }
9
129
  function dedupeFindings(findings) {
10
130
  const deduped = new Map();
11
131
  for (const finding of findings) {
@@ -30,54 +150,471 @@ function dedupeFindings(findings) {
30
150
  }
31
151
  return Array.from(deduped.values());
32
152
  }
33
- export function runStaticEngine(input) {
34
- const findings = [];
35
- for (const file of input.files) {
36
- findings.push(...detectEnvOverrides({
37
- filePath: file.filePath,
38
- parsed: file.parsed,
39
- textContent: file.textContent,
40
- trustedApiDomains: input.config.trustedApiDomains,
41
- }));
42
- findings.push(...detectConsentBypass({
43
- filePath: file.filePath,
44
- parsed: file.parsed,
45
- textContent: file.textContent,
46
- trustedApiDomains: input.config.trustedApiDomains,
47
- }));
48
- findings.push(...detectCommandExecution({
49
- filePath: file.filePath,
50
- parsed: file.parsed,
51
- textContent: file.textContent,
52
- knownSafeMcpServers: input.config.knownSafeMcpServers,
53
- knownSafeFormatters: input.config.knownSafeFormatters,
54
- knownSafeLspServers: input.config.knownSafeLspServers,
55
- blockedCommands: input.config.blockedCommands,
56
- }));
57
- if (input.config.checkIdeSettings) {
58
- findings.push(...detectIdeSettingsIssues({
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({
59
167
  filePath: file.filePath,
60
168
  parsed: file.parsed,
61
169
  textContent: file.textContent,
62
- projectRoot: input.projectRoot,
63
- }));
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) {
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);
592
+ const rulePackRules = loadRulePacks({
593
+ rule_pack_paths: input.config.rulePackPaths ?? [],
594
+ allowed_rules: input.config.allowedRules ?? [],
595
+ skip_rules: input.config.skipRules ?? [],
596
+ });
597
+ for (const file of input.files) {
598
+ for (const audit of activeFileAudits) {
599
+ findings.push(...(await audit.run({ file, input })));
64
600
  }
65
- findings.push(...detectPluginManifestIssues({
66
- filePath: file.filePath,
67
- parsed: file.parsed,
68
- textContent: file.textContent,
69
- trustedApiDomains: input.config.trustedApiDomains,
70
- blockedCommands: input.config.blockedCommands,
71
- }));
72
- if (file.format === "text" || file.format === "markdown") {
73
- findings.push(...detectRuleFileIssues({
601
+ for (const rule of rulePackRules) {
602
+ if (!evaluateRule(rule, {
74
603
  filePath: file.filePath,
604
+ format: file.format,
605
+ parsed: file.parsed,
75
606
  textContent: file.textContent,
76
- unicodeAnalysis: input.config.unicodeAnalysis,
77
- }));
607
+ })) {
608
+ continue;
609
+ }
610
+ const candidate = findingFromRulePackMatch(file, rule);
611
+ if (!hasEquivalentFinding(findings, candidate)) {
612
+ findings.push(candidate);
613
+ }
78
614
  }
79
615
  }
80
- findings.push(...detectSymlinkEscapes({ symlinkEscapes: input.symlinkEscapes }));
81
- findings.push(...detectGitHookIssues({ hooks: input.hooks, knownSafeHooks: input.config.knownSafeHooks }));
616
+ for (const audit of activeGlobalAudits) {
617
+ findings.push(...(await audit.run({ input })));
618
+ }
82
619
  return dedupeFindings(findings);
83
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
+ }