@vigolium/piolium 0.0.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 (271) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +117 -0
  3. package/agents/access-auditor.md +300 -0
  4. package/agents/assumption-breaker.md +154 -0
  5. package/agents/attack-designer.md +116 -0
  6. package/agents/code-scanner.md +139 -0
  7. package/agents/concurrency-auditor.md +238 -0
  8. package/agents/confirm-writer.md +257 -0
  9. package/agents/context-reviewer.md +274 -0
  10. package/agents/cross-verifier.md +165 -0
  11. package/agents/cve-scout.md +381 -0
  12. package/agents/env-builder.md +282 -0
  13. package/agents/env-profiler.md +205 -0
  14. package/agents/evidence-collector.md +140 -0
  15. package/agents/finding-grader.md +142 -0
  16. package/agents/finding-writer.md +148 -0
  17. package/agents/flow-tracer.md +106 -0
  18. package/agents/goal-backtracer.md +146 -0
  19. package/agents/history-miner.md +467 -0
  20. package/agents/independent-verifier.md +118 -0
  21. package/agents/intent-mapper.md +183 -0
  22. package/agents/longshot-collector.md +128 -0
  23. package/agents/longshot-prober.md +126 -0
  24. package/agents/patch-auditor.md +73 -0
  25. package/agents/poc-author.md +124 -0
  26. package/agents/poc-runner.md +194 -0
  27. package/agents/probe-lead.md +269 -0
  28. package/agents/red-challenger.md +101 -0
  29. package/agents/report-composer.md +208 -0
  30. package/agents/review-adjudicator.md +216 -0
  31. package/agents/spec-auditor.md +155 -0
  32. package/agents/taint-tracer.md +265 -0
  33. package/agents/test-locator.md +209 -0
  34. package/agents/threat-modeler.md +132 -0
  35. package/agents/variant-scanner.md +108 -0
  36. package/agents/variant-spotter.md +110 -0
  37. package/bin/piolium.mjs +376 -0
  38. package/extensions/piolium/_vendor/yaml.bundle.d.mts +6 -0
  39. package/extensions/piolium/_vendor/yaml.bundle.mjs +139 -0
  40. package/extensions/piolium/agent-runner.ts +322 -0
  41. package/extensions/piolium/agents.ts +266 -0
  42. package/extensions/piolium/audit-state.ts +522 -0
  43. package/extensions/piolium/bundled-resources.ts +97 -0
  44. package/extensions/piolium/candidate-scan.ts +966 -0
  45. package/extensions/piolium/command-target.ts +177 -0
  46. package/extensions/piolium/console-stream.ts +57 -0
  47. package/extensions/piolium/export-results.ts +380 -0
  48. package/extensions/piolium/findings.ts +448 -0
  49. package/extensions/piolium/heartbeat.ts +182 -0
  50. package/extensions/piolium/help.ts +234 -0
  51. package/extensions/piolium/index.ts +1865 -0
  52. package/extensions/piolium/longshot.ts +530 -0
  53. package/extensions/piolium/matcher-suggestions.ts +196 -0
  54. package/extensions/piolium/matcher-utils.ts +83 -0
  55. package/extensions/piolium/modes/balanced.ts +750 -0
  56. package/extensions/piolium/modes/confirm-bootstrap.ts +186 -0
  57. package/extensions/piolium/modes/confirm.ts +697 -0
  58. package/extensions/piolium/modes/deep.ts +917 -0
  59. package/extensions/piolium/modes/diff.ts +177 -0
  60. package/extensions/piolium/modes/lite.ts +540 -0
  61. package/extensions/piolium/modes/longshot.ts +595 -0
  62. package/extensions/piolium/modes/merge.ts +204 -0
  63. package/extensions/piolium/modes/phase-runner.ts +267 -0
  64. package/extensions/piolium/modes/reinvest.ts +546 -0
  65. package/extensions/piolium/modes/revisit.ts +279 -0
  66. package/extensions/piolium/modes.ts +48 -0
  67. package/extensions/piolium/phase-labels.ts +123 -0
  68. package/extensions/piolium/phase-status-strip.ts +92 -0
  69. package/extensions/piolium/prompt-prefix-editor.ts +39 -0
  70. package/extensions/piolium/providers/anthropic-vertex.ts +836 -0
  71. package/extensions/piolium/recon.ts +409 -0
  72. package/extensions/piolium/result-stats.ts +105 -0
  73. package/extensions/piolium/retry.ts +120 -0
  74. package/extensions/piolium/scheduler.ts +212 -0
  75. package/extensions/piolium/secrets.ts +368 -0
  76. package/extensions/piolium/tools/web-tools.ts +148 -0
  77. package/package.json +77 -0
  78. package/skills/agentic-actions-auditor/SKILL.md +327 -0
  79. package/skills/agentic-actions-auditor/references/action-profiles.md +186 -0
  80. package/skills/agentic-actions-auditor/references/cross-file-resolution.md +209 -0
  81. package/skills/agentic-actions-auditor/references/foundations.md +94 -0
  82. package/skills/agentic-actions-auditor/references/vector-a-env-var-intermediary.md +77 -0
  83. package/skills/agentic-actions-auditor/references/vector-b-direct-expression-injection.md +83 -0
  84. package/skills/agentic-actions-auditor/references/vector-c-cli-data-fetch.md +83 -0
  85. package/skills/agentic-actions-auditor/references/vector-d-pr-target-checkout.md +88 -0
  86. package/skills/agentic-actions-auditor/references/vector-e-error-log-injection.md +88 -0
  87. package/skills/agentic-actions-auditor/references/vector-f-subshell-expansion.md +82 -0
  88. package/skills/agentic-actions-auditor/references/vector-g-eval-of-ai-output.md +91 -0
  89. package/skills/agentic-actions-auditor/references/vector-h-dangerous-sandbox-configs.md +102 -0
  90. package/skills/agentic-actions-auditor/references/vector-i-wildcard-allowlists.md +88 -0
  91. package/skills/audit/SKILL.md +562 -0
  92. package/skills/audit/assets/icon.svg +7 -0
  93. package/skills/audit/hooks/scripts/validate_phase_output.py +550 -0
  94. package/skills/audit/references/adversarial-review.md +148 -0
  95. package/skills/audit/references/architecture-aware-sast.md +306 -0
  96. package/skills/audit/references/audit-workflow.md +737 -0
  97. package/skills/audit/references/chamber-protocol.md +384 -0
  98. package/skills/audit/references/creative-attack-modes.md +221 -0
  99. package/skills/audit/references/deep-analysis.md +273 -0
  100. package/skills/audit/references/domain-attack-playbooks.md +1129 -0
  101. package/skills/audit/references/knowledge-base-template.md +513 -0
  102. package/skills/audit/references/real-env-validation.md +191 -0
  103. package/skills/audit/references/report-templates.md +417 -0
  104. package/skills/audit/references/triage-and-prereqs.md +134 -0
  105. package/skills/audit/scripts/consolidate_drafts.py +554 -0
  106. package/skills/audit/scripts/partition_findings.py +152 -0
  107. package/skills/audit/scripts/rg-hotspots.sh +121 -0
  108. package/skills/audit/scripts/stamp_file_state.py +349 -0
  109. package/skills/code-reviewer/SKILL.md +65 -0
  110. package/skills/codeql/SKILL.md +281 -0
  111. package/skills/codeql/references/build-fixes.md +90 -0
  112. package/skills/codeql/references/diagnostic-query-templates.md +339 -0
  113. package/skills/codeql/references/extension-yaml-format.md +209 -0
  114. package/skills/codeql/references/important-only-suite.md +153 -0
  115. package/skills/codeql/references/language-details.md +207 -0
  116. package/skills/codeql/references/macos-arm64e-workaround.md +179 -0
  117. package/skills/codeql/references/performance-tuning.md +111 -0
  118. package/skills/codeql/references/quality-assessment.md +172 -0
  119. package/skills/codeql/references/ruleset-catalog.md +63 -0
  120. package/skills/codeql/references/run-all-suite.md +92 -0
  121. package/skills/codeql/references/sarif-processing.md +79 -0
  122. package/skills/codeql/references/threat-models.md +51 -0
  123. package/skills/codeql/workflows/build-database.md +280 -0
  124. package/skills/codeql/workflows/create-data-extensions.md +261 -0
  125. package/skills/codeql/workflows/run-analysis.md +301 -0
  126. package/skills/differential-review/SKILL.md +220 -0
  127. package/skills/differential-review/adversarial.md +203 -0
  128. package/skills/differential-review/methodology.md +234 -0
  129. package/skills/differential-review/patterns.md +300 -0
  130. package/skills/differential-review/reporting.md +369 -0
  131. package/skills/fp-check/SKILL.md +125 -0
  132. package/skills/fp-check/references/bug-class-verification.md +114 -0
  133. package/skills/fp-check/references/deep-verification.md +143 -0
  134. package/skills/fp-check/references/evidence-templates.md +91 -0
  135. package/skills/fp-check/references/false-positive-patterns.md +115 -0
  136. package/skills/fp-check/references/gate-reviews.md +27 -0
  137. package/skills/fp-check/references/standard-verification.md +78 -0
  138. package/skills/insecure-defaults/SKILL.md +117 -0
  139. package/skills/insecure-defaults/references/examples.md +409 -0
  140. package/skills/last30days/SKILL.md +444 -0
  141. package/skills/sarif-parsing/SKILL.md +483 -0
  142. package/skills/sarif-parsing/resources/jq-queries.md +162 -0
  143. package/skills/sarif-parsing/resources/sarif_helpers.py +331 -0
  144. package/skills/security-threat-model/LICENSE.txt +201 -0
  145. package/skills/security-threat-model/SKILL.md +81 -0
  146. package/skills/security-threat-model/agents/openai.yaml +4 -0
  147. package/skills/security-threat-model/references/prompt-template.md +255 -0
  148. package/skills/security-threat-model/references/security-controls-and-assets.md +32 -0
  149. package/skills/semgrep/SKILL.md +212 -0
  150. package/skills/semgrep/references/rulesets.md +162 -0
  151. package/skills/semgrep/references/scan-modes.md +110 -0
  152. package/skills/semgrep/references/scanner-task-prompt.md +140 -0
  153. package/skills/semgrep/scripts/merge_sarif.py +203 -0
  154. package/skills/semgrep/workflows/scan-workflow.md +311 -0
  155. package/skills/semgrep-rule-creator/SKILL.md +168 -0
  156. package/skills/semgrep-rule-creator/references/quick-reference.md +202 -0
  157. package/skills/semgrep-rule-creator/references/workflow.md +240 -0
  158. package/skills/semgrep-rule-variant-creator/SKILL.md +205 -0
  159. package/skills/semgrep-rule-variant-creator/references/applicability-analysis.md +250 -0
  160. package/skills/semgrep-rule-variant-creator/references/language-syntax-guide.md +324 -0
  161. package/skills/semgrep-rule-variant-creator/references/workflow.md +518 -0
  162. package/skills/sharp-edges/SKILL.md +292 -0
  163. package/skills/sharp-edges/references/auth-patterns.md +252 -0
  164. package/skills/sharp-edges/references/case-studies.md +274 -0
  165. package/skills/sharp-edges/references/config-patterns.md +333 -0
  166. package/skills/sharp-edges/references/crypto-apis.md +190 -0
  167. package/skills/sharp-edges/references/lang-c.md +205 -0
  168. package/skills/sharp-edges/references/lang-csharp.md +285 -0
  169. package/skills/sharp-edges/references/lang-go.md +270 -0
  170. package/skills/sharp-edges/references/lang-java.md +263 -0
  171. package/skills/sharp-edges/references/lang-javascript.md +269 -0
  172. package/skills/sharp-edges/references/lang-kotlin.md +265 -0
  173. package/skills/sharp-edges/references/lang-php.md +245 -0
  174. package/skills/sharp-edges/references/lang-python.md +274 -0
  175. package/skills/sharp-edges/references/lang-ruby.md +273 -0
  176. package/skills/sharp-edges/references/lang-rust.md +272 -0
  177. package/skills/sharp-edges/references/lang-swift.md +287 -0
  178. package/skills/sharp-edges/references/language-specific.md +588 -0
  179. package/skills/spec-to-code-compliance/SKILL.md +357 -0
  180. package/skills/spec-to-code-compliance/resources/COMPLETENESS_CHECKLIST.md +69 -0
  181. package/skills/spec-to-code-compliance/resources/IR_EXAMPLES.md +417 -0
  182. package/skills/spec-to-code-compliance/resources/OUTPUT_REQUIREMENTS.md +105 -0
  183. package/skills/supply-chain-risk-auditor/SKILL.md +67 -0
  184. package/skills/supply-chain-risk-auditor/resources/results-template.md +41 -0
  185. package/skills/variant-analysis/METHODOLOGY.md +327 -0
  186. package/skills/variant-analysis/SKILL.md +142 -0
  187. package/skills/variant-analysis/resources/codeql/cpp.ql +119 -0
  188. package/skills/variant-analysis/resources/codeql/go.ql +69 -0
  189. package/skills/variant-analysis/resources/codeql/java.ql +71 -0
  190. package/skills/variant-analysis/resources/codeql/javascript.ql +63 -0
  191. package/skills/variant-analysis/resources/codeql/python.ql +80 -0
  192. package/skills/variant-analysis/resources/semgrep/cpp.yaml +98 -0
  193. package/skills/variant-analysis/resources/semgrep/go.yaml +63 -0
  194. package/skills/variant-analysis/resources/semgrep/java.yaml +61 -0
  195. package/skills/variant-analysis/resources/semgrep/javascript.yaml +60 -0
  196. package/skills/variant-analysis/resources/semgrep/python.yaml +72 -0
  197. package/skills/variant-analysis/resources/variant-report-template.md +75 -0
  198. package/skills/vuln-report/SKILL.md +137 -0
  199. package/skills/vuln-report/agents/openai.yaml +4 -0
  200. package/skills/vuln-report/references/report-template.md +135 -0
  201. package/skills/wooyun-legacy/SKILL.md +367 -0
  202. package/skills/wooyun-legacy/references/bank-penetration.md +222 -0
  203. package/skills/wooyun-legacy/references/checklists/command-execution-checklist.md +119 -0
  204. package/skills/wooyun-legacy/references/checklists/csrf-checklist.md +74 -0
  205. package/skills/wooyun-legacy/references/checklists/file-upload-checklist.md +108 -0
  206. package/skills/wooyun-legacy/references/checklists/info-disclosure-checklist.md +114 -0
  207. package/skills/wooyun-legacy/references/checklists/logic-flaws-checklist.md +95 -0
  208. package/skills/wooyun-legacy/references/checklists/misconfig-checklist.md +124 -0
  209. package/skills/wooyun-legacy/references/checklists/path-traversal-checklist.md +87 -0
  210. package/skills/wooyun-legacy/references/checklists/rce-checklist.md +93 -0
  211. package/skills/wooyun-legacy/references/checklists/sql-injection-checklist.md +97 -0
  212. package/skills/wooyun-legacy/references/checklists/ssrf-checklist.md +99 -0
  213. package/skills/wooyun-legacy/references/checklists/unauthorized-access-checklist.md +89 -0
  214. package/skills/wooyun-legacy/references/checklists/weak-password-checklist.md +115 -0
  215. package/skills/wooyun-legacy/references/checklists/xss-checklist.md +103 -0
  216. package/skills/wooyun-legacy/references/checklists/xxe-checklist.md +130 -0
  217. package/skills/wooyun-legacy/references/info-disclosure.md +975 -0
  218. package/skills/wooyun-legacy/references/logic-flaws.md +721 -0
  219. package/skills/wooyun-legacy/references/path-traversal.md +1191 -0
  220. package/skills/wooyun-legacy/references/telecom-penetration.md +156 -0
  221. package/skills/wooyun-legacy/references/unauthorized-access.md +980 -0
  222. package/skills/wooyun-legacy/references/xss.md +746 -0
  223. package/skills/zeroize-audit/SKILL.md +371 -0
  224. package/skills/zeroize-audit/configs/c.yaml +21 -0
  225. package/skills/zeroize-audit/configs/default.yaml +128 -0
  226. package/skills/zeroize-audit/configs/rust.yaml +83 -0
  227. package/skills/zeroize-audit/prompts/report_template.md +238 -0
  228. package/skills/zeroize-audit/prompts/system.md +163 -0
  229. package/skills/zeroize-audit/prompts/task.md +97 -0
  230. package/skills/zeroize-audit/references/compile-commands.md +231 -0
  231. package/skills/zeroize-audit/references/detection-strategy.md +191 -0
  232. package/skills/zeroize-audit/references/ir-analysis.md +252 -0
  233. package/skills/zeroize-audit/references/mcp-analysis.md +221 -0
  234. package/skills/zeroize-audit/references/poc-generation.md +470 -0
  235. package/skills/zeroize-audit/references/rust-zeroization-patterns.md +867 -0
  236. package/skills/zeroize-audit/schemas/input.json +83 -0
  237. package/skills/zeroize-audit/schemas/output.json +140 -0
  238. package/skills/zeroize-audit/tools/analyze_asm.sh +202 -0
  239. package/skills/zeroize-audit/tools/analyze_cfg.py +381 -0
  240. package/skills/zeroize-audit/tools/analyze_heap.sh +211 -0
  241. package/skills/zeroize-audit/tools/analyze_ir_semantic.py +429 -0
  242. package/skills/zeroize-audit/tools/diff_ir.sh +135 -0
  243. package/skills/zeroize-audit/tools/diff_rust_mir.sh +189 -0
  244. package/skills/zeroize-audit/tools/emit_asm.sh +67 -0
  245. package/skills/zeroize-audit/tools/emit_ir.sh +77 -0
  246. package/skills/zeroize-audit/tools/emit_rust_asm.sh +178 -0
  247. package/skills/zeroize-audit/tools/emit_rust_ir.sh +150 -0
  248. package/skills/zeroize-audit/tools/emit_rust_mir.sh +158 -0
  249. package/skills/zeroize-audit/tools/extract_compile_flags.py +284 -0
  250. package/skills/zeroize-audit/tools/generate_poc.py +1329 -0
  251. package/skills/zeroize-audit/tools/mcp/apply_confidence_gates.py +113 -0
  252. package/skills/zeroize-audit/tools/mcp/check_mcp.sh +68 -0
  253. package/skills/zeroize-audit/tools/mcp/normalize_mcp_evidence.py +125 -0
  254. package/skills/zeroize-audit/tools/scripts/check_llvm_patterns.py +481 -0
  255. package/skills/zeroize-audit/tools/scripts/check_mir_patterns.py +554 -0
  256. package/skills/zeroize-audit/tools/scripts/check_rust_asm.py +424 -0
  257. package/skills/zeroize-audit/tools/scripts/check_rust_asm_aarch64.py +300 -0
  258. package/skills/zeroize-audit/tools/scripts/check_rust_asm_x86.py +283 -0
  259. package/skills/zeroize-audit/tools/scripts/find_dangerous_apis.py +375 -0
  260. package/skills/zeroize-audit/tools/scripts/semantic_audit.py +923 -0
  261. package/skills/zeroize-audit/tools/track_dataflow.sh +196 -0
  262. package/skills/zeroize-audit/tools/validate_rust_toolchain.sh +298 -0
  263. package/skills/zeroize-audit/workflows/phase-0-preflight.md +150 -0
  264. package/skills/zeroize-audit/workflows/phase-1-source-analysis.md +144 -0
  265. package/skills/zeroize-audit/workflows/phase-2-compiler-analysis.md +139 -0
  266. package/skills/zeroize-audit/workflows/phase-3-interim-report.md +46 -0
  267. package/skills/zeroize-audit/workflows/phase-4-poc-generation.md +46 -0
  268. package/skills/zeroize-audit/workflows/phase-5-poc-validation.md +136 -0
  269. package/skills/zeroize-audit/workflows/phase-6-final-report.md +44 -0
  270. package/skills/zeroize-audit/workflows/phase-7-test-generation.md +42 -0
  271. package/themes/piolium-srcery.json +94 -0
@@ -0,0 +1,186 @@
1
+ # Action Security Profiles
2
+
3
+ Security-relevant configuration fields, default behaviors, dangerous configuration patterns, and remediation guidance for each supported AI action. Referenced by SKILL.md Step 5 for action-specific remediation.
4
+
5
+ ## Claude Code Action
6
+
7
+ ### Default Security Posture
8
+
9
+ - Bash tool disabled by default; commands must be explicitly allowed via `--allowedTools` in `claude_args`
10
+ - Only users with repository write access can trigger (default when `allowed_non_write_users` is omitted)
11
+ - GitHub Apps and bots blocked by default (when `allowed_bots` is omitted)
12
+ - Commits to new branch, does NOT auto-create PRs (requires human review)
13
+ - Built-in prompt sanitization strips HTML comments, invisible characters, markdown image alt text, hidden HTML attributes, HTML entities
14
+ - `show_full_output: false` by default (prevents secret leakage in workflow logs)
15
+
16
+ ### Dangerous Configurations
17
+
18
+ | Configuration | Risk |
19
+ |--------------|------|
20
+ | `claude_args: "--allowedTools Bash(*)"` | Unrestricted shell access; any prompt injection achieves full RCE |
21
+ | `allowed_non_write_users: "*"` | Any GitHub user can trigger the action, including external contributors and attackers |
22
+ | `allowed_bots: "*"` | Any bot can trigger, enables automated attack chains via bot-to-bot escalation |
23
+ | `show_full_output: true` (in public repos) | Exposes full conversation including potential secrets in workflow logs |
24
+ | `prompt` containing `${{ github.event.* }}` | Direct expression injection of attacker-controlled content into AI prompt |
25
+
26
+ ### Remediation Patterns
27
+
28
+ **Restrict shell access:** Replace `Bash(*)` with specific tool patterns:
29
+
30
+ ```yaml
31
+ claude_args: '--allowedTools "Bash(npm test:*) Bash(git diff:*)"'
32
+ ```
33
+
34
+ **Restrict user access:** Remove wildcard allowlists or replace with explicit user lists:
35
+
36
+ ```yaml
37
+ allowed_non_write_users: "trusted-user1,trusted-user2"
38
+ ```
39
+
40
+ **Restrict bot access:** Remove `allowed_bots: "*"` or list specific trusted bots:
41
+
42
+ ```yaml
43
+ allowed_bots: "dependabot[bot],renovate[bot]"
44
+ ```
45
+
46
+ **Prevent prompt injection:** Never pass attacker-controlled event data (issue body, PR title, comment body) to the `prompt` field via env vars or direct `${{ }}` expressions. Validate and sanitize input in a prior step.
47
+
48
+ **Protect log output:** Keep `show_full_output: false` (default) in public repositories.
49
+
50
+ ## OpenAI Codex
51
+
52
+ ### Default Security Posture
53
+
54
+ - Sandbox defaults to `workspace-write` (read/edit in workspace, run commands locally, no network)
55
+ - Safety strategy defaults to `drop-sudo` (removes sudo privileges before running Codex)
56
+ - Empty `allow-users` permits only write-access repository members (default)
57
+ - `allow-bots: false` by default
58
+ - Network off by default (must be explicitly enabled)
59
+ - Protected paths: `.git`, `.vig-agent/`, `.codex/` directories are read-only even in writable sandbox
60
+
61
+ ### Dangerous Configurations
62
+
63
+ | Configuration | Risk |
64
+ |--------------|------|
65
+ | `sandbox: danger-full-access` | No sandbox, no approvals, unrestricted filesystem and network access |
66
+ | `safety-strategy: unsafe` | Disables all safety enforcement including sudo restrictions |
67
+ | `allow-users: "*"` | Any GitHub user can trigger the action |
68
+ | `allow-bots: true` | Any bot can trigger, enables automated attack chains |
69
+ | `danger-full-access` + `unsafe` combined | Maximum exposure: no sandbox, no safety, full system access |
70
+
71
+ ### Remediation Patterns
72
+
73
+ **Restrict sandbox:** Use the default or a more restrictive mode:
74
+
75
+ ```yaml
76
+ sandbox: workspace-write # default: workspace access only, no network
77
+ sandbox: read-only # for analysis-only tasks
78
+ ```
79
+
80
+ **Restrict safety strategy:** Use the default or a stricter option:
81
+
82
+ ```yaml
83
+ safety-strategy: drop-sudo # default: removes sudo privileges
84
+ safety-strategy: unprivileged-user # stronger: runs as unprivileged user
85
+ ```
86
+
87
+ **Restrict user access:** Remove wildcard or replace with explicit user list:
88
+
89
+ ```yaml
90
+ allow-users: "maintainer1,maintainer2"
91
+ ```
92
+
93
+ **Restrict bot access:** Keep `allow-bots: false` (default) unless specific trusted bots need access.
94
+
95
+ **Organization-level enforcement:** Use `requirements.toml` to block `danger-full-access` at the organization level, preventing individual repos from weakening sandbox policy.
96
+
97
+ ## Gemini CLI
98
+
99
+ ### Default Security Posture
100
+
101
+ - Sandbox off by default for the GitHub Action (no `--sandbox` flag set)
102
+ - When sandbox is enabled, default profile is `permissive-open` (restricts writes outside project directory)
103
+ - Default approval mode requires confirmation for tool calls
104
+ - When `--yolo` is used, sandbox is enabled automatically (safety measure)
105
+ - Tool restriction via `tools.core` allowlist in settings JSON (e.g., `["run_shell_command(echo)"]`)
106
+ - No built-in user allowlist field -- access controlled by workflow trigger permissions only
107
+
108
+ ### Dangerous Configurations
109
+
110
+ | Configuration | Risk |
111
+ |--------------|------|
112
+ | `settings: '{"sandbox": false}'` | Explicitly disables sandbox (note: JSON inside YAML string) |
113
+ | `--yolo` or `--approval-mode=yolo` in CLI args | Disables approval prompts for all tool calls |
114
+ | `tools.core` containing `run_shell_command(echo)` | Enables subshell expansion bypass -- confirmed RCE vector (Vector F) |
115
+ | `tools.allowed: ["*"]` | Bypasses confirmation for all tools |
116
+
117
+ ### Remediation Patterns
118
+
119
+ **Enable sandbox:** Add sandbox configuration to the settings JSON:
120
+
121
+ ```yaml
122
+ settings: '{"sandbox": true}'
123
+ ```
124
+
125
+ Or pass the `--sandbox` flag in CLI arguments.
126
+
127
+ **Remove dangerous approval modes:** Remove `--yolo` and `--approval-mode=yolo` from CLI args. Use the default approval mode that requires confirmation for tool calls.
128
+
129
+ **Restrict tool lists:** Remove `run_shell_command(echo)` and other expandable commands from `tools.core`. Use specific non-shell tools only:
130
+
131
+ ```json
132
+ {
133
+ "tools": {
134
+ "core": ["read_file", "write_file", "list_directory"]
135
+ }
136
+ }
137
+ ```
138
+
139
+ **Container-based sandboxing:** If shell access is required, use container-based sandboxing to limit blast radius rather than relying on the built-in sandbox profile alone.
140
+
141
+ ## GitHub AI Inference
142
+
143
+ ### Default Security Posture
144
+
145
+ - Inference-only API call -- no shell access, no filesystem access, no sandbox to configure
146
+ - Access controlled by GitHub token scope
147
+ - Primary risks: prompt injection via untrusted event data (Vector B), and AI output flowing to `eval` in subsequent workflow steps (Vector G)
148
+
149
+ ### Dangerous Configurations
150
+
151
+ | Configuration | Risk |
152
+ |--------------|------|
153
+ | `prompt` containing `${{ github.event.* }}` | Attacker-controlled event contexts injected directly into AI prompt (Vector B) |
154
+ | Overly scoped `token` parameter | Grants more permissions than needed, expanding blast radius of any exploitation |
155
+ | AI output consumed by `eval`/`exec` in subsequent steps | Converts inference-only action into code execution vector (Vector G) |
156
+
157
+ ### Remediation Patterns
158
+
159
+ **Sanitize prompt inputs:** Validate and sanitize event data before including in prompts. Do not pass raw `${{ github.event.issue.body }}` or similar attacker-controlled fields.
160
+
161
+ **Minimize token scope:** Use minimum-scope tokens following the principle of least privilege. Only grant permissions the inference call actually needs.
162
+
163
+ **Protect AI output consumption:** Never pass AI output through `eval`, `exec`, or unquoted `$()` in subsequent workflow steps:
164
+
165
+ ```yaml
166
+ # DANGEROUS: AI output executed as code
167
+ - run: eval "${{ steps.inference.outputs.result }}"
168
+
169
+ # SAFE: AI output stored and validated before use
170
+ - run: |
171
+ RESULT='${{ steps.inference.outputs.result }}'
172
+ echo "$RESULT" # display only, no execution
173
+ ```
174
+
175
+ **Validate structured output:** If structured output (JSON) is needed from the AI, validate against a schema before using in shell commands.
176
+
177
+ ## Per-Action Remediation Quick Reference
178
+
179
+ | Remediation Need | Claude Code Action | OpenAI Codex | Gemini CLI | GitHub AI Inference |
180
+ |-----------------|-------------------|--------------|------------|-------------------|
181
+ | Restrict shell access | `--allowedTools "Bash(specific:*)"` | `sandbox: workspace-write` | Remove expandable commands from `tools.core` | N/A (no shell) |
182
+ | Restrict user access | `allowed_non_write_users: "user1,user2"` | `allow-users: "user1,user2"` | Control via workflow trigger permissions | Control via token scope |
183
+ | Disable dangerous mode | Remove `Bash(*)` from `claude_args` | Remove `danger-full-access` from `sandbox` | Remove `--yolo` from CLI args | N/A |
184
+ | Sandbox enforcement | N/A (tool-level restriction) | `sandbox: read-only` | `"sandbox": true` in settings JSON | N/A (no execution) |
185
+ | Block bot triggers | Remove `allowed_bots: "*"` | Set `allow-bots: false` | Control via workflow trigger conditions | Control via token scope |
186
+ | Protect output/logs | Keep `show_full_output: false` | N/A | N/A | Never `eval` AI output |
@@ -0,0 +1,209 @@
1
+ # Cross-File Resolution: Composite Actions and Reusable Workflows
2
+
3
+ AI agents can be hidden inside composite actions and reusable workflows, invisible when analyzing only the caller workflow file. This reference documents how to classify `uses:` references, resolve the referenced files, trace input mappings across file boundaries, and report unresolved references.
4
+
5
+ Resolution is limited to one level deep (fixed). If a resolved file contains its own cross-file references, log them as unresolved -- do not follow.
6
+
7
+ ## uses: Reference Classification
8
+
9
+ Parse each `uses:` value to determine its type and resolution strategy.
10
+
11
+ | `uses:` Pattern | Reference Type | Resolution | In Scope? |
12
+ |----------------|---------------|------------|-----------|
13
+ | `./path/to/action` | Local composite action | Read `{path}/action.yml` from filesystem | YES |
14
+ | `./.github/workflows/called.yml` | Local reusable workflow | Read file from filesystem | YES |
15
+ | `owner/repo/.github/workflows/file.yml@ref` | Remote reusable workflow | Fetch via `gh api` Contents API | YES |
16
+ | `docker://image:tag` | Docker image | N/A -- no steps to analyze | NO (skip) |
17
+ | `owner/repo@ref` (without `.github/workflows/`) | Remote action | Would require remote action.yml fetch | NO (skip silently) |
18
+
19
+ **Classification algorithm:**
20
+
21
+ ```
22
+ Given a uses: value:
23
+ 1. Starts with "./" AND path contains ".github/workflows/" -> LOCAL REUSABLE WORKFLOW
24
+ 2. Starts with "./" -> LOCAL COMPOSITE ACTION
25
+ 3. Contains ".github/workflows/" AND contains "@" -> REMOTE REUSABLE WORKFLOW
26
+ 4. Starts with "docker://" -> DOCKER IMAGE (skip)
27
+ 5. Else -> REMOTE ACTION (out of scope, skip silently)
28
+ ```
29
+
30
+ Order matters: check step 1 before step 2, because local reusable workflows also start with `./`.
31
+
32
+ **Context distinction:** Step-level `uses:` (inside `steps:` array) references actions. Job-level `uses:` (at the same level as `runs-on:`) references reusable workflows. Local reusable workflows use job-level `uses:` with a `./` prefix.
33
+
34
+ ## Local Composite Action Resolution
35
+
36
+ **Given:** `uses: ./path/to/action` at the step level.
37
+
38
+ **Local analysis mode:**
39
+ 1. Read `{path}/action.yml` from the filesystem using the Read tool
40
+ 2. If not found, try `{path}/action.yaml` (GitHub supports both, prefers `.yml`)
41
+ 3. If neither exists, log as unresolved with reason "File not found"
42
+
43
+ **Remote analysis mode:**
44
+ 1. Fetch via Contents API: `gh api repos/{owner}/{repo}/contents/{path}/action.yml?ref={ref} --jq '.content | @base64d'`
45
+ 2. On 404, try `action.yaml`: `gh api repos/{owner}/{repo}/contents/{path}/action.yaml?ref={ref} --jq '.content | @base64d'`
46
+ 3. If both 404, log as unresolved with reason "File not found"
47
+
48
+ **Type discrimination -- check `runs.using`:**
49
+
50
+ | `runs.using` Value | Action Type | Analyze? |
51
+ |-------------------|-------------|----------|
52
+ | `composite` | Composite action | YES -- scan `runs.steps[]` |
53
+ | `node12`, `node16`, `node20`, `node24` | JavaScript action | NO -- skip silently |
54
+ | `docker` | Docker action | NO -- skip silently |
55
+
56
+ Only composite actions have `runs.steps[]` containing workflow-style steps. If `runs.using` is not `composite`, skip silently -- do NOT log as unresolved.
57
+
58
+ **Analysis of composite action steps:**
59
+ 1. For each step in `runs.steps[]`, check `uses:` against the known AI action references (SKILL.md Step 2)
60
+ 2. If an AI action is found, capture its `with:` fields for security context (SKILL.md Step 3)
61
+ 3. Run the same attack vector detection (SKILL.md Step 4) on each AI action step found
62
+ 4. Any `uses:` cross-file references found inside the resolved file are logged as unresolved (depth limit) -- do NOT follow them
63
+
64
+ ## Local Reusable Workflow Resolution
65
+
66
+ **Given:** Job-level `uses: ./.github/workflows/called.yml`.
67
+
68
+ **Local analysis mode:**
69
+ 1. Read the file from the filesystem using the Read tool
70
+
71
+ **Remote analysis mode:**
72
+ 1. Fetch via Contents API using the same repo context: `gh api repos/{owner}/{repo}/contents/.github/workflows/{filename}?ref={ref} --jq '.content | @base64d'`
73
+
74
+ The resolved file is a complete workflow YAML with `on: workflow_call`. Analyze it through the existing Steps 2-4 detection pipeline -- identify AI action steps, capture security context, and detect attack vectors.
75
+
76
+ **Input mapping:** The caller passes values via job-level `with:`, and the called workflow accesses them via `${{ inputs.* }}` (defined under `on: workflow_call: inputs:`).
77
+
78
+ ## Remote Reusable Workflow Resolution
79
+
80
+ **Given:** Job-level `uses: owner/repo/.github/workflows/file.yml@ref`.
81
+
82
+ **Parse the reference:**
83
+ - Extract: `owner`, `repo`, file path (everything after `repo/` and before `@`), and `ref` (everything after `@`)
84
+ - Example: `org/shared/.github/workflows/review.yml@main` -> owner=`org`, repo=`shared`, path=`.github/workflows/review.yml`, ref=`main`
85
+
86
+ **Fetch:**
87
+ ```
88
+ gh api repos/{owner}/{repo}/contents/.github/workflows/{filename}?ref={ref} --jq '.content | @base64d'
89
+ ```
90
+
91
+ This is the same Contents API pattern established in Step 0 (Phase 5).
92
+
93
+ **Error handling:**
94
+ - 404: Log as unresolved with reason "404 Not Found (repository may be private)"
95
+ - Auth error (401/403): Log as unresolved with reason "Authentication required"
96
+
97
+ Analyze the resolved workflow YAML through existing Steps 2-4. Cross-file findings mix into the main findings list sorted by severity -- they just have a longer file-chain trace.
98
+
99
+ ## Input Mapping Traces
100
+
101
+ When an AI action is found inside a resolved file, trace the data flow from the caller's `with:` values through `inputs.*` to the AI prompt field. This extends the existing data flow trace pattern from foundations.md.
102
+
103
+ ### Composite Action Input Trace
104
+
105
+ ```
106
+ Caller workflow (.github/workflows/review.yml):
107
+ steps:
108
+ - uses: ./actions/issue-triage
109
+ with:
110
+ issue_body: ${{ github.event.issue.body }} # attacker-controlled
111
+
112
+ Composite action (actions/issue-triage/action.yml):
113
+ inputs:
114
+ issue_body:
115
+ description: "The issue body text"
116
+ runs:
117
+ using: composite
118
+ steps:
119
+ - uses: anthropics/claude-code-action@v1
120
+ with:
121
+ prompt: "Triage this issue: ${{ inputs.issue_body }}"
122
+ ```
123
+
124
+ **Data flow trace:**
125
+ ```
126
+ 1. Attacker creates issue with malicious content in body
127
+ 2. Caller: with.issue_body = ${{ github.event.issue.body }}
128
+ -> .github/workflows/review.yml, jobs.triage.steps[1]
129
+ 3. Composite action receives: inputs.issue_body = attacker content
130
+ -> actions/issue-triage/action.yml, inputs.issue_body
131
+ 4. AI action: prompt contains ${{ inputs.issue_body }}
132
+ -> actions/issue-triage/action.yml, runs.steps[0]
133
+ 5. Claude executes with tainted prompt -- attacker achieves prompt injection
134
+ ```
135
+
136
+ ### Reusable Workflow Input Trace
137
+
138
+ ```
139
+ Caller workflow (.github/workflows/ci.yml):
140
+ jobs:
141
+ ai-review:
142
+ uses: org/shared/.github/workflows/ai-review.yml@main
143
+ with:
144
+ pr_body: ${{ github.event.pull_request.body }}
145
+
146
+ Called workflow (org/shared/.github/workflows/ai-review.yml):
147
+ on:
148
+ workflow_call:
149
+ inputs:
150
+ pr_body:
151
+ type: string
152
+ jobs:
153
+ review:
154
+ runs-on: ubuntu-latest
155
+ steps:
156
+ - uses: anthropics/claude-code-action@v1
157
+ with:
158
+ prompt: "Review this PR: ${{ inputs.pr_body }}"
159
+ ```
160
+
161
+ **Data flow trace:**
162
+ ```
163
+ 1. Attacker opens PR with malicious content in body
164
+ 2. Caller: with.pr_body = ${{ github.event.pull_request.body }}
165
+ -> .github/workflows/ci.yml, jobs.ai-review
166
+ 3. Reusable workflow receives: inputs.pr_body = attacker content
167
+ -> org/shared/.github/workflows/ai-review.yml, on.workflow_call.inputs
168
+ 4. AI action: prompt contains ${{ inputs.pr_body }}
169
+ -> org/shared/.github/workflows/ai-review.yml, jobs.review.steps[0]
170
+ 5. Claude executes with tainted prompt via pull_request_target (has secrets access)
171
+ ```
172
+
173
+ The trace format follows the same stacked multi-line style as other data flow traces in this skill. Each hop shows the relevant YAML location. Cross-file findings have a longer trace because they span multiple files, but are otherwise identical to direct findings.
174
+
175
+ ## Depth Limit and Unresolved References
176
+
177
+ **Depth limit:** Fixed at 1 level. The top-level workflow is depth 0. Resolved files (composite actions and reusable workflows) are depth 1. Any cross-file references found at depth 1 are logged as unresolved with reason "Depth limit exceeded (max 1 level)" -- do NOT follow them.
178
+
179
+ This covers the overwhelming majority of real-world patterns. Deeper nesting is rare and may indicate intentional obfuscation, which is worth noting in findings.
180
+
181
+ **Unresolved reference reporting:**
182
+
183
+ When any references could not be resolved, add an "Unresolved References" section at the end of the report:
184
+
185
+ ```markdown
186
+ ## Unresolved References
187
+
188
+ | Reference | Source | Reason |
189
+ |-----------|--------|--------|
190
+ | `org/private/.github/workflows/scan.yml@v2` | `.github/workflows/ci.yml` jobs.scan | 404 Not Found (repository may be private) |
191
+ | `./actions/deep-nested` | `actions/wrapper/action.yml` runs.steps[2] | Depth limit exceeded (max 1 level) |
192
+ ```
193
+
194
+ - Omit this section entirely when all references resolve successfully
195
+ - The summary counts total findings only -- it does not count resolved or unresolved references
196
+
197
+ ## Edge Cases
198
+
199
+ **action.yml vs action.yaml:** Try `.yml` first, fall back to `.yaml`. GitHub supports both filenames and prefers `.yml`. This applies to both filesystem reads and API fetches.
200
+
201
+ **Non-composite actions at local paths:** When `./path/to/action` resolves to a JavaScript or Docker action (`runs.using` is `node*` or `docker`), skip silently. Do NOT log as unresolved -- these are valid actions that simply have no analyzable workflow-style steps.
202
+
203
+ **Local paths in remote analysis mode:** Fetch via Contents API using the same repo context. The `./` prefix is relative to the repository root, and the Contents API can retrieve any path: `gh api repos/{owner}/{repo}/contents/{path}/action.yml?ref={ref}`.
204
+
205
+ **Missing files:** Log as unresolved with the specific reason (404, file not found, etc.). Do not treat missing files as errors that halt analysis -- continue with remaining references.
206
+
207
+ **Circular references:** The depth-1 limit prevents infinite resolution. Even if Action A references Action B and Action B references Action A, the skill only follows one level. References found at depth 1 are logged as unresolved, not followed.
208
+
209
+ **Job-level vs step-level `uses:`:** Job-level `uses:` (same indent level as `runs-on:`) indicates a reusable workflow call. Step-level `uses:` (inside a `steps:` array) indicates an action reference. The classification algorithm handles this distinction: reusable workflows are resolved as complete workflow files; composite actions are resolved via `action.yml`.
@@ -0,0 +1,94 @@
1
+ # Shared Foundations: Attacker-Controlled Input Model
2
+
3
+ This reference documents cross-cutting concepts that all 9 attack vector detection heuristics depend on. Read this before analyzing individual vectors.
4
+
5
+ ## Attacker-Controlled GitHub Context Expressions
6
+
7
+ These `github.event.*` expressions resolve to content an external attacker can influence. Dangerous contexts typically end with: `body`, `default_branch`, `email`, `head_ref`, `label`, `message`, `name`, `page_name`, `ref`, `title`.
8
+
9
+ **High-frequency (seen across PoC workflows):**
10
+
11
+ - `github.event.issue.body` -- issue body text
12
+ - `github.event.issue.title` -- issue title
13
+ - `github.event.comment.body` -- comment text on issues or PRs
14
+ - `github.event.pull_request.body` -- PR description
15
+ - `github.event.pull_request.title` -- PR title
16
+ - `github.event.pull_request.head.ref` -- PR source branch name
17
+ - `github.event.pull_request.head.sha` -- PR commit SHA (used in checkout)
18
+
19
+ **Lower-frequency but still dangerous:**
20
+
21
+ - `github.event.review.body` -- review comment text
22
+ - `github.event.discussion.body`, `github.event.discussion.title`
23
+ - `github.event.pages.*.page_name` -- wiki page name
24
+ - `github.event.commits.*.message`, `github.event.commits.*.author.email`, `github.event.commits.*.author.name`
25
+ - `github.event.head_commit.message`, `github.event.head_commit.author.email`, `github.event.head_commit.author.name`
26
+ - `github.head_ref` -- branch name (attacker-controlled in fork PRs)
27
+
28
+ Any `${{ }}` expression referencing these contexts carries attacker-controlled content into whatever consumes the resolved value.
29
+
30
+ ## How env: Blocks Work in GitHub Actions
31
+
32
+ Environment variables can be set at three scopes:
33
+
34
+ 1. **Workflow-level** `env:` (top of file) -- inherited by all jobs and steps
35
+ 2. **Job-level** `env:` (under `jobs.<id>:`) -- inherited by all steps in that job
36
+ 3. **Step-level** `env:` (under a step) -- available only to that step
37
+
38
+ Narrower scopes override broader ones. Critically, `${{ }}` expressions in `env:` values are evaluated BEFORE the step runs. The step only sees the resolved string value, never the expression. This is the mechanism behind Vector A: the AI agent receives attacker content through an env var without any `${{ }}` expression appearing in the prompt field itself.
39
+
40
+ ```
41
+ env:
42
+ ISSUE_BODY: ${{ github.event.issue.body }} # evaluated at workflow parse time
43
+ # By the time the step runs, ISSUE_BODY contains the raw attacker text
44
+ ```
45
+
46
+ ## Security-Relevant Trigger Events
47
+
48
+ These `on:` events expose workflows to external attacker-controlled input:
49
+
50
+ | Trigger | Attacker-Controlled Data | Risk Level |
51
+ |---------|-------------------------|------------|
52
+ | `issues` (opened, edited) | Issue title, body | External users can create issues |
53
+ | `issue_comment` (created) | Comment body | External users can comment |
54
+ | `pull_request_target` | PR title, body, head ref, head SHA | Runs in base branch context WITH secrets |
55
+ | `pull_request` | Head ref, head SHA | Typically no secrets from forks, but ref is controlled |
56
+ | `discussion` / `discussion_comment` | Discussion title, body, comment body | External users can create discussions |
57
+ | `workflow_dispatch` | Input values | Triggering user controls all inputs |
58
+
59
+ Note: `push` events from the default branch and `pull_request` events that do not grant secrets to forks are generally lower risk for prompt injection because the attacker cannot influence the content that reaches the AI agent without already having write access.
60
+
61
+ ## Data Flow Model
62
+
63
+ Attacker input reaches AI agents through three distinct paths:
64
+
65
+ **Path 1 -- Direct expression interpolation:**
66
+ ```
67
+ github.event.*.body -> ${{ }} in prompt field -> AI processes attacker text
68
+ ```
69
+
70
+ **Path 2 -- Env var intermediary:**
71
+ ```
72
+ github.event.*.body -> env: VAR: ${{ }} -> prompt reads $VAR -> AI processes attacker text
73
+ ```
74
+
75
+ **Path 3 -- Runtime fetch:**
76
+ ```
77
+ github.event.*.number -> gh issue view N -> API returns attacker body -> AI processes attacker text
78
+ ```
79
+
80
+ Path 2 requires extra attention because the prompt field contains zero `${{ }}` expressions, making the injection invisible in the prompt itself. Path 3 is missed because the attacker content is not present in the workflow YAML at all -- it is fetched at runtime.
81
+
82
+ ## AI Action Prompt Field Names
83
+
84
+ Where each supported action receives prompt content that could carry attacker input:
85
+
86
+ | Action | Prompt Fields | Notes |
87
+ |--------|--------------|-------|
88
+ | `anthropics/claude-code-action` | `with.prompt` | Also check `with.claude_args` for embedded instructions |
89
+ | `google-github-actions/run-gemini-cli` | `with.prompt` | Shell-style env var interpolation in prompt text |
90
+ | `google-gemini/gemini-cli-action` | `with.prompt` | Legacy/archived Gemini action reference |
91
+ | `openai/codex-action` | `with.prompt`, `with.prompt-file` | `prompt-file` may point to attacker-controlled file |
92
+ | `actions/ai-inference` | `with.prompt`, `with.system-prompt`, `with.system-prompt-file` | System prompt is also an injection surface |
93
+
94
+ When checking for attacker-controlled content in prompts, examine ALL fields listed for the relevant action, not just the primary `prompt` field.
@@ -0,0 +1,77 @@
1
+ # Vector A: Env Var Intermediary
2
+
3
+ Attacker data flows from GitHub event context into `env:` blocks, and the AI prompt references those env var names -- the AI agent reads the attacker content from environment variables at runtime. The prompt field contains zero `${{ }}` expressions, making this pattern invisible to tools that only scan for direct expression injection.
4
+
5
+ ## Applicable Actions
6
+
7
+ | Action | Applicable | Notes |
8
+ |--------|-----------|-------|
9
+ | Claude Code Action | Yes | Prompt instructs AI to read env vars via `echo "$VAR"` |
10
+ | Gemini CLI | Yes | Shell-style `"${VAR}"` interpolation in prompt text |
11
+ | OpenAI Codex | Yes | Similar env var reference pattern in prompt instructions |
12
+ | GitHub AI Inference | Yes | Prompt text can reference env var names for the runner to resolve |
13
+
14
+ ## Trigger Events
15
+
16
+ Any event where attacker-controlled body, title, or comment fields are exposed: `issues` (opened, edited), `issue_comment` (created), `pull_request_target`, `discussion`, `discussion_comment`. See [foundations.md](foundations.md) for the complete list of attacker-controlled contexts.
17
+
18
+ ## Data Flow
19
+
20
+ ```
21
+ github.event.issue.body
22
+ -> env: ISSUE_BODY: ${{ github.event.issue.body }} (evaluated BEFORE step runs)
23
+ -> prompt instruction references "ISSUE_BODY"
24
+ -> AI agent reads env var at runtime
25
+ -> attacker content in AI context
26
+ ```
27
+
28
+ The `${{ }}` expression is in the `env:` block, not the prompt. By the time the step executes, the env var contains the raw attacker text. The AI agent reads it as a normal environment variable.
29
+
30
+ ## What to Look For
31
+
32
+ This is a TWO-PART match. Both conditions must be true:
33
+
34
+ 1. **Part A -- Env var with attacker-controlled value:** Find `env:` keys (at workflow, job, or step scope) whose values contain `${{ github.event.* }}` expressions referencing attacker-controlled contexts (see [foundations.md](foundations.md) for the complete list)
35
+ 2. **Part B -- Prompt references that env var name:** Check if the AI action step's `with.prompt` (or `with.prompt-file`) references those env var names -- by exact name string, `"${VAR}"` shell expansion, `echo "$VAR"` instruction, or text mentioning the variable name
36
+
37
+ Both parts must be present. An env var with attacker content that is never referenced in the prompt is not this vector. A prompt referencing env vars that contain only safe values is not this vector.
38
+
39
+ ## Where to Look
40
+
41
+ - `env:` blocks at all three scopes: workflow-level (top of file), job-level (under `jobs.<id>:`), and step-level (on the AI action step itself)
42
+ - The `with.prompt` field of the AI action step
43
+ - Prior steps in the same job that set env vars via `echo "NAME=value" >> $GITHUB_ENV`
44
+
45
+ ## Why It Matters
46
+
47
+ This pattern is invisible to naive grep-based tools that only scan for `${{ }}` in prompt fields. GitHub's own security documentation recommends using env vars as an intermediary to prevent script injection in `run:` blocks -- but this recommendation does not account for AI agents that read env vars by name. An attacker's issue body, PR description, or comment text flows into the AI prompt without any visible expression injection.
48
+
49
+ ## Example: Vulnerable Pattern
50
+
51
+ ```yaml
52
+ on:
53
+ issues:
54
+ types: [opened]
55
+
56
+ jobs:
57
+ triage:
58
+ runs-on: ubuntu-latest
59
+ steps:
60
+ - uses: google-github-actions/run-gemini-cli@v0
61
+ env:
62
+ ISSUE_TITLE: '${{ github.event.issue.title }}' # attacker-controlled
63
+ ISSUE_BODY: '${{ github.event.issue.body }}' # attacker-controlled
64
+ with:
65
+ prompt: |
66
+ Review the issue title and body provided in the environment
67
+ variables: "${ISSUE_TITLE}" and "${ISSUE_BODY}".
68
+ # No ${{ }} here -- but attacker data still reaches the AI
69
+ ```
70
+
71
+ **Data flow:** `github.event.issue.body` -> `env: ISSUE_BODY` -> prompt instruction `"${ISSUE_BODY}"` -> Gemini reads env var -> attacker content in AI context.
72
+
73
+ ## False Positives
74
+
75
+ - **Safe context values:** Env vars containing non-attacker-controlled values like `${{ github.repository }}`, `${{ github.run_id }}`, or `${{ secrets.* }}` -- these are NOT attacker-controlled
76
+ - **Unreferenced env vars:** Env vars with attacker-controlled values that are NOT referenced in any AI prompt (e.g., used only in non-AI steps like shell scripts or build tools)
77
+ - **Explicit untrusted handling:** Env vars where the prompt explicitly treats the content as untrusted with effective input validation (rare in practice -- most workflows pass the content directly)
@@ -0,0 +1,83 @@
1
+ # Vector B: Direct Expression Injection
2
+
3
+ Direct `${{ github.event.* }}` expressions embedded in AI prompt fields. The YAML engine evaluates the expression at workflow runtime, embedding the attacker's raw text directly into the prompt string before the AI processes it. This pattern is visually obvious in the YAML -- the `${{ }}` expressions are right there in the prompt field -- but still commonly deployed because workflow authors assume the AI will handle untrusted input responsibly.
4
+
5
+ ## Applicable Actions
6
+
7
+ | Action | Applicable | Notes |
8
+ |--------|-----------|-------|
9
+ | Claude Code Action | Yes | Check `with.prompt` and `with.claude_args` for embedded expressions |
10
+ | Gemini CLI | Yes | Check `with.prompt` for direct expressions |
11
+ | OpenAI Codex | Yes | Check `with.prompt`, `with.prompt-file` (if resolving to attacker-controlled path), `with.codex-args` |
12
+ | GitHub AI Inference | Yes | Check `with.prompt`, `with.system-prompt`, `with.system-prompt-file` |
13
+
14
+ Check ALL `with:` fields that accept text content, not just `prompt:`. Each action has multiple fields that are injection surfaces.
15
+
16
+ ## Trigger Events
17
+
18
+ Any event exposing attacker-controlled contexts: `issues` (opened, edited), `issue_comment` (created), `pull_request_target`, `discussion`, `discussion_comment`. See [foundations.md](foundations.md) for the complete list of attacker-controlled contexts.
19
+
20
+ ## Data Flow
21
+
22
+ ```
23
+ github.event.issue.body
24
+ -> ${{ github.event.issue.body }} evaluated at YAML parse time
25
+ -> raw attacker text becomes part of the prompt string literal
26
+ -> AI processes the prompt containing attacker content
27
+ ```
28
+
29
+ The expression is resolved before any step code executes. The AI action receives a prompt string that already contains the attacker's text as if the workflow author had typed it.
30
+
31
+ ## What to Look For
32
+
33
+ `${{ github.event.* }}` expressions inside any text-accepting field of an AI action step:
34
+
35
+ - `with.prompt` -- the primary prompt field (all actions)
36
+ - `with.system-prompt` -- system prompt (GitHub AI Inference)
37
+ - `with.prompt-file` -- if it resolves to an attacker-controlled path (Codex, AI Inference)
38
+ - `with.claude_args` -- may embed expressions as inline instructions (Claude Code Action)
39
+ - `with.codex-args` -- may embed expressions (OpenAI Codex)
40
+
41
+ The expression must reference an attacker-controlled context. See [foundations.md](foundations.md) for the complete list.
42
+
43
+ Also check multiline `prompt: |` blocks -- expressions can appear on any line within the block scalar.
44
+
45
+ ## Where to Look
46
+
47
+ The `with:` block of AI action steps. Focus on all fields listed above, not just `prompt:`. Expressions in `env:` blocks are Vector A, not Vector B.
48
+
49
+ ## Why It Matters
50
+
51
+ While visually obvious, this vector remains common because developers treat AI prompts like natural language rather than code. The `${{ }}` evaluation happens at the YAML level before the AI agent runs, so the attacker's content is indistinguishable from the workflow author's intended prompt text. The AI has no way to tell which parts of its prompt are trusted instructions and which are attacker-injected content.
52
+
53
+ ## Example: Vulnerable Pattern
54
+
55
+ ```yaml
56
+ on:
57
+ issues:
58
+ types: [opened]
59
+
60
+ jobs:
61
+ gather-labels:
62
+ runs-on: ubuntu-latest
63
+ steps:
64
+ - uses: openai/codex-action@main
65
+ with:
66
+ allow-users: "*"
67
+ prompt: |
68
+ Issue title:
69
+ ${{ github.event.issue.title }}
70
+ Issue body:
71
+ ${{ github.event.issue.body }}
72
+ Analyze this issue and suggest appropriate labels.
73
+ # Attacker content is embedded directly in the prompt at YAML eval time
74
+ ```
75
+
76
+ **Data flow:** `github.event.issue.body` -> `${{ }}` evaluation -> prompt string literal -> Codex processes attacker-controlled prompt content.
77
+
78
+ ## False Positives
79
+
80
+ - **Integer/enum contexts:** `${{ github.event.issue.number }}` -- integers, not attacker-controlled text. `${{ github.event.action }}` -- limited set of values (opened, edited, etc.), not free text
81
+ - **Safe contexts:** `${{ github.repository }}`, `${{ github.run_id }}`, `${{ github.actor }}` -- not attacker-controlled free text (though `github.actor` is the username, which has limited character set)
82
+ - **Expressions in env: blocks:** Those are Vector A, not Vector B. Vector B is specifically about expressions directly in prompt or other `with:` fields
83
+ - **Expressions in non-AI steps:** `${{ }}` expressions in `run:` blocks or non-AI action `with:` blocks are standard GitHub Actions script injection concerns, not specific to this skill's scope