actions-warden 0.2.0 → 0.3.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 (62) hide show
  1. package/AGENTS.md +189 -0
  2. package/CONTRIBUTING.md +109 -0
  3. package/README.md +272 -224
  4. package/RELEASING.md +338 -0
  5. package/SECURITY.md +25 -3
  6. package/docs/AI-AGENTS.md +458 -0
  7. package/docs/CLI.md +421 -0
  8. package/docs/CONFIGURATION.md +340 -0
  9. package/docs/DEVELOPMENT.md +350 -0
  10. package/docs/GITHUB-ACTION.md +281 -0
  11. package/docs/JAVASCRIPT-API.md +355 -0
  12. package/docs/OUTPUTS.md +409 -0
  13. package/docs/README.md +27 -0
  14. package/examples/org-scan.yml +42 -0
  15. package/examples/upgrade-pr.yml +57 -0
  16. package/llms.txt +38 -0
  17. package/package.json +32 -10
  18. package/skills/actions-warden/SKILL.md +140 -38
  19. package/src/action.js +317 -0
  20. package/src/cli.js +267 -22
  21. package/src/commands/audit.js +189 -36
  22. package/src/commands/org-scan.js +549 -0
  23. package/src/commands/pin.js +59 -56
  24. package/src/commands/report.js +122 -10
  25. package/src/commands/upgrade.js +102 -62
  26. package/src/commands/verify.js +193 -0
  27. package/src/index.js +21 -4
  28. package/src/lib/action-status.js +27 -0
  29. package/src/lib/agent-mode.js +175 -0
  30. package/src/lib/annotations.js +250 -0
  31. package/src/lib/baseline.js +103 -0
  32. package/src/lib/cache.js +47 -10
  33. package/src/lib/concurrency.js +27 -0
  34. package/src/lib/config.js +185 -0
  35. package/src/lib/execution.js +71 -0
  36. package/src/lib/formatter.js +127 -8
  37. package/src/lib/github-org.js +374 -0
  38. package/src/lib/identity.js +62 -0
  39. package/src/lib/ignore.js +7 -6
  40. package/src/lib/org-checkpoint.js +378 -0
  41. package/src/lib/org-progress.js +60 -0
  42. package/src/lib/parser.js +326 -52
  43. package/src/lib/patcher.js +199 -0
  44. package/src/lib/paths.js +35 -12
  45. package/src/lib/redact.js +65 -4
  46. package/src/lib/resolver.js +225 -43
  47. package/src/lib/targets.js +28 -0
  48. package/src/lib/triggers.js +12 -0
  49. package/src/lib/writer.js +45 -8
  50. package/src/rules/excessive-permissions.js +24 -33
  51. package/src/rules/index.js +19 -1
  52. package/src/rules/pull-request-target-checkout.js +149 -18
  53. package/src/rules/reusable-workflow-secrets.js +32 -0
  54. package/src/rules/script-injection.js +77 -12
  55. package/src/rules/secrets-in-env.js +101 -18
  56. package/src/rules/unpinned-action.js +3 -2
  57. package/src/rules/unpinned-container-image.js +39 -0
  58. package/src/rules/unpinned-docker-action.js +30 -0
  59. package/src/rules/untrusted-self-hosted-runner.js +109 -0
  60. package/src/rules/workflow-run-artifact-execution.js +122 -0
  61. package/src/rules/workflow-structure.js +396 -0
  62. package/src/version.js +3 -0
package/docs/CLI.md ADDED
@@ -0,0 +1,421 @@
1
+ # CLI reference
2
+
3
+ This reference describes the public `actions-warden` command-line interface.
4
+ The command's own help is authoritative for the installed version:
5
+
6
+ ```sh
7
+ actions-warden --help
8
+ actions-warden audit --help
9
+ actions-warden org-scan --help
10
+ ```
11
+
12
+ ## Install and authenticate
13
+
14
+ Node.js 20 or newer is required.
15
+
16
+ ```sh
17
+ npm install --global actions-warden
18
+ actions-warden --version
19
+ ```
20
+
21
+ For reproducible one-off automation, invoke an exact package version:
22
+
23
+ ```sh
24
+ npx --yes actions-warden@0.3.0 audit
25
+ ```
26
+
27
+ Network-backed commands resolve credentials in this order:
28
+
29
+ 1. `--token <token>`
30
+ 2. `GITHUB_TOKEN`
31
+ 3. `GH_TOKEN`
32
+ 4. anonymous GitHub API access
33
+
34
+ Prefer an environment variable over `--token` because command-line arguments
35
+ may be visible to other local processes. Give the token only the repository
36
+ metadata and contents read access needed for the requested scope.
37
+
38
+ ## Target and output options
39
+
40
+ The repository commands (`audit`, `pin`, `verify`, `upgrade`, and `report`)
41
+ share these options:
42
+
43
+ | option | default | behavior |
44
+ |---|---|---|
45
+ | `-w, --workflow <pattern...>` | discovery | One or more files, directories, or globs |
46
+ | `--cwd <dir>` | current directory | Repository and path-safety root |
47
+ | `--token <token>` | environment | GitHub API credential for network-backed work |
48
+ | `--format <format>` | `toon` | `toon`, `json`, `text`, or `sarif` |
49
+ | `--output <destination>` | `stdout` | `stdout` or `file` |
50
+ | `--output-path <path>` | none | Required with `--output=file`; resolved inside `--cwd` |
51
+
52
+ Without `--workflow`, actions-warden discovers:
53
+
54
+ ```text
55
+ .github/workflows/*.yml
56
+ .github/workflows/*.yaml
57
+ action.yml
58
+ action.yaml
59
+ **/action.yml
60
+ **/action.yaml
61
+ ```
62
+
63
+ Paths and directories may be absolute or relative to `--cwd`. Quote globs so
64
+ actions-warden, not the shell, expands them. An explicit file, directory, or
65
+ glob that resolves to no workflow files is an error.
66
+
67
+ ```sh
68
+ actions-warden audit -w .github/workflows/release.yml
69
+ actions-warden audit -w '.github/workflows/*.yml'
70
+ actions-warden audit -w .github/workflows .github/actions
71
+ actions-warden audit --cwd ../service
72
+ ```
73
+
74
+ Output files are created atomically and must remain within the real working
75
+ directory after symlinks are resolved:
76
+
77
+ ```sh
78
+ actions-warden audit \
79
+ --format=sarif \
80
+ --output=file \
81
+ --output-path=reports/actions-warden.sarif
82
+ ```
83
+
84
+ ## `audit`
85
+
86
+ Scan local workflows and composite actions without making network calls or
87
+ changing files.
88
+
89
+ ```sh
90
+ actions-warden audit \
91
+ [--severity=low|medium|high|critical] \
92
+ [--explain] \
93
+ [--config=<path> | --ignore-config] \
94
+ [--baseline=<path>]
95
+ ```
96
+
97
+ The default includes every severity. `--severity` keeps findings at or above
98
+ the selected threshold; parse errors always remain visible. `--explain` adds a
99
+ plain-language remediation field.
100
+
101
+ Create a reviewed baseline from all current findings:
102
+
103
+ ```sh
104
+ actions-warden audit \
105
+ --create-baseline=.actions-warden-baseline.json
106
+ ```
107
+
108
+ `--create-baseline` cannot be combined with `--baseline`. Parser failures are
109
+ never written into the baseline. See [configuration and baselines](./CONFIGURATION.md).
110
+
111
+ Exit code `0` means no unsuppressed findings; `1` means findings were reported;
112
+ `2` means the scan could not be invoked safely or correctly.
113
+
114
+ ## `pin`
115
+
116
+ Resolve mutable external action and reusable-workflow refs to full commit SHAs.
117
+
118
+ ```sh
119
+ actions-warden pin [--dry-run] [--write] [--fix=<id>]
120
+ ```
121
+
122
+ Dry-run is the default. A planned rewrite looks like:
123
+
124
+ ```yaml
125
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # actions-warden-ref: v5
126
+ ```
127
+
128
+ The metadata preserves the readable version for `verify` and `upgrade`.
129
+ Existing comments and scalar quoting are preserved. The target commit is
130
+ verified as belonging to the referenced repository before a plan is accepted.
131
+
132
+ Review the dry-run output, then either apply the whole plan:
133
+
134
+ ```sh
135
+ actions-warden pin --write
136
+ ```
137
+
138
+ or one exact stable ID:
139
+
140
+ ```sh
141
+ actions-warden pin --fix=18b82e86d7c14fe2 --write
142
+ ```
143
+
144
+ `--write` and `--dry-run` together are rejected.
145
+
146
+ ## `verify`
147
+
148
+ Verify action and reusable-workflow pins against GitHub.
149
+
150
+ ```sh
151
+ actions-warden verify
152
+ ```
153
+
154
+ For every external reference, verification checks that:
155
+
156
+ 1. the ref is a full 40-character commit SHA;
157
+ 2. the commit belongs to the referenced repository; and
158
+ 3. `actions-warden-ref` metadata, when present, resolves to that SHA.
159
+
160
+ A valid SHA without version metadata produces a warning. An unpinned,
161
+ unverifiable, or metadata-mismatched reference is an error and returns status
162
+ `FAIL`.
163
+
164
+ ## `upgrade`
165
+
166
+ Plan or apply newer action versions while preserving immutable SHA pins.
167
+
168
+ ```sh
169
+ actions-warden upgrade \
170
+ [--mode=major|minor|patch] \
171
+ [--min-age=<days>] \
172
+ [--dry-run] [--write] [--fix=<id>]
173
+ ```
174
+
175
+ Defaults are `--mode=minor --min-age=7` and dry-run. Tag discovery is paginated;
176
+ prereleases and downgrades are excluded. A candidate tag must be older than the
177
+ cooldown. The age comes from GitHub release publication data when available,
178
+ then from locally recorded first-seen evidence for the exact tag-to-SHA mapping.
179
+ Missing evidence fails closed.
180
+
181
+ SHA-pinned references need `actions-warden-ref` metadata to establish the
182
+ current semantic version. Legacy plain-semver comments remain readable.
183
+
184
+ ```sh
185
+ # Review every eligible minor update
186
+ actions-warden upgrade --mode=minor --min-age=14 --format=json
187
+
188
+ # Apply one reviewed change
189
+ actions-warden upgrade --fix=<ID> --write
190
+ ```
191
+
192
+ ## `report`
193
+
194
+ Produce one non-writing view of audit findings, pin plans, and upgrade plans.
195
+
196
+ ```sh
197
+ actions-warden report \
198
+ [--severity=low|medium|high|critical] \
199
+ [--explain] \
200
+ [--mode=major|minor|patch] \
201
+ [--min-age=<days>] \
202
+ [--offline] \
203
+ [--config=<path> | --ignore-config] \
204
+ [--baseline=<path>]
205
+ ```
206
+
207
+ The audit determines the target set; pin and upgrade use exactly that same
208
+ scope. A location with an upgrade is not duplicated as a pin proposal.
209
+ `--offline` skips both network-backed planning phases and returns only the
210
+ local audit.
211
+
212
+ Use report for review, issue generation, or an AI planning step. It never
213
+ accepts `--write`.
214
+
215
+ ## `org-scan`
216
+
217
+ Audit workflow security across repositories visible to a GitHub token.
218
+
219
+ ```sh
220
+ actions-warden org-scan <organization> \
221
+ [--repository <pattern...>] \
222
+ [--visibility=all|public|private|internal] \
223
+ [--include-archived] [--include-disabled] [--include-forks] \
224
+ [--max-repos=<count>] [--concurrency=<1-16>] \
225
+ [--severity=low|medium|high|critical] [--explain] \
226
+ [--config=<path> | --ignore-config] [--baseline=<path>] \
227
+ [--checkpoint=<path> | --resume=<path>] \
228
+ [--progress=auto|always|never] \
229
+ [--agent-mode | --no-agent-mode] \
230
+ [--format=toon|json|text|sarif] \
231
+ [--output=stdout|file] [--output-path=<path>]
232
+ ```
233
+
234
+ Defaults:
235
+
236
+ - visibility: `all`;
237
+ - archived, disabled, and forked repositories: excluded;
238
+ - repository concurrency: `4`;
239
+ - repository count: every eligible repository;
240
+ - audit severity: every level;
241
+ - progress: `auto` (stderr only when attached to an interactive terminal);
242
+ - agent mode: disabled unless explicitly selected or enabled by the
243
+ environment.
244
+
245
+ Repository patterns match either `name` or `owner/name`, case-insensitively.
246
+ An explicit pattern set that matches no eligible repository is an error.
247
+ Selection is stable before `--max-repos` is applied.
248
+
249
+ ```sh
250
+ actions-warden org-scan my-org \
251
+ --repository 'service-*' 'my-org/platform-*' \
252
+ --visibility=private \
253
+ --severity=high \
254
+ --format=json \
255
+ --output=file \
256
+ --output-path=reports/org.json
257
+ ```
258
+
259
+ The result includes discovered, eligible, selected, scanned, skipped, failed,
260
+ and workflow-bearing repository counts. Per-repository operational errors make
261
+ the overall status `FAIL`; they do not abort reporting for repositories that
262
+ can still be scanned.
263
+
264
+ ### Agent mode
265
+
266
+ There is no reliable cross-agent process or environment signal. Agent callers
267
+ opt in explicitly:
268
+
269
+ ```sh
270
+ actions-warden org-scan my-org --agent-mode
271
+ ```
272
+
273
+ An integration can instead set the mode once:
274
+
275
+ ```sh
276
+ export ACTIONS_WARDEN_MODE=agent
277
+ actions-warden org-scan my-org
278
+ ```
279
+
280
+ When the corresponding options were not explicitly supplied, agent mode sets:
281
+
282
+ - `--format=json`;
283
+ - `--output=file` with a guarded scope-keyed report path;
284
+ - `--progress=never`;
285
+ - a guarded scope-keyed checkpoint path, creating it when absent and resuming
286
+ it when present.
287
+
288
+ The artifact key includes the organization, repository filters, inclusion
289
+ flags, repository limit, severity, explanation setting, normalized policy,
290
+ baseline contents, package version, and rule catalog. A scope or security
291
+ control change therefore selects a different checkpoint rather than
292
+ overwriting incompatible state. Generated paths have these shapes:
293
+
294
+ ```text
295
+ .actions-warden-agent.<scope-key>.report.json
296
+ .actions-warden-agent.<scope-key>.checkpoint.json
297
+ ```
298
+
299
+ An explicitly selected report format changes the report extension. Generated
300
+ files are mode `0600` when first created and may reveal private security
301
+ posture; protect them and ignore `.actions-warden-agent.*` in consuming
302
+ repositories when appropriate.
303
+
304
+ After writing the full report, stdout contains only a bounded JSON receipt:
305
+
306
+ ```js
307
+ {
308
+ schemaVersion: '1.0',
309
+ kind: 'actions-warden-agent-receipt',
310
+ command: 'org-scan',
311
+ organization: string,
312
+ status: 'OK' | 'FAIL',
313
+ summary: object,
314
+ report: { path: string, format: 'toon' | 'json' | 'text' | 'sarif' },
315
+ checkpoint: { path: string, resumed: boolean }
316
+ }
317
+ ```
318
+
319
+ The process still exits `0` for `OK`, `1` for a completed `FAIL`, and `2` for
320
+ an invocation error. Explicit `--format`, `--output`, `--output-path`,
321
+ `--progress`, `--checkpoint`, and `--resume` choices override agent defaults.
322
+ With explicit `--output=stdout`, stdout is the complete selected report and no
323
+ receipt is added. Use `--no-agent-mode` to override an inherited environment
324
+ marker. `ACTIONS_WARDEN_MODE` must be exactly `agent` when set.
325
+
326
+ ### Progress and resume
327
+
328
+ Progress is independent of the report format and is written only to stderr.
329
+ This keeps stdout valid JSON, SARIF, TOON, or text. Select `always` for CI or a
330
+ redirected terminal log, and `never` when a caller wants no progress channel:
331
+
332
+ ```sh
333
+ actions-warden org-scan my-org --format=json --progress=always > report.json
334
+ ```
335
+
336
+ Create an atomic checkpoint after each completed repository:
337
+
338
+ ```sh
339
+ actions-warden org-scan my-org \
340
+ --severity=high \
341
+ --checkpoint=.actions-warden-org-checkpoint.json \
342
+ --format=json \
343
+ --output=file \
344
+ --output-path=reports/org.json
345
+ ```
346
+
347
+ Resume with the same organization, filters, inclusion flags, repository limit,
348
+ severity, explanation setting, configuration, baseline, tool version, and rule
349
+ catalog:
350
+
351
+ ```sh
352
+ actions-warden org-scan my-org \
353
+ --severity=high \
354
+ --resume=.actions-warden-org-checkpoint.json \
355
+ --format=json \
356
+ --output=file \
357
+ --output-path=reports/org.json
358
+ ```
359
+
360
+ The token, concurrency, output format, progress mode, and report destination do
361
+ not affect checkpoint compatibility. A resumed scan always lists repositories
362
+ again and fetches a fresh default-branch tree for each selected repository.
363
+ An error-free result is reused only when repository identity, default branch,
364
+ and tree SHA still match. Changed repositories and any checkpointed result with
365
+ an operational error are scanned again.
366
+
367
+ `--checkpoint` starts a new checkpoint and replaces an existing file at that
368
+ path. `--resume` requires a valid existing checkpoint and updates it. The
369
+ checkpoint and report paths must differ, remain inside `--cwd`, and have an
370
+ existing parent directory. A checkpoint cannot replace the active config or
371
+ baseline. Checkpoints contain redacted report evidence and revision metadata,
372
+ not the token or raw YAML, but can still reveal private repository names,
373
+ paths, findings, and security posture; protect and retain them accordingly.
374
+ Checkpoint reads and writes are capped at 256 MiB.
375
+
376
+ The scanner reads the default-branch Git tree and YAML blobs in memory. It
377
+ never clones, checks out, or executes remote content. It fails closed on
378
+ truncated trees and enforces these bounds per repository:
379
+
380
+ - 1,000 workflow and composite-action files;
381
+ - 2 MiB per YAML file;
382
+ - 32 MiB of YAML source in total.
383
+
384
+ Remote organization listings, trees, and blobs bypass the general disk cache so
385
+ a report uses a fresh branch view and raw private workflow content is not
386
+ persisted. An explicitly requested checkpoint stores only redacted report data
387
+ and validated revision metadata.
388
+ See [the scheduled Action example](../examples/org-scan.yml).
389
+
390
+ ## `rules`
391
+
392
+ Print the rule catalog compiled into the installed version:
393
+
394
+ ```sh
395
+ actions-warden rules
396
+ actions-warden rules --format=json
397
+ ```
398
+
399
+ This command has no repository or network dependency.
400
+
401
+ ## Cache and network behavior
402
+
403
+ Local `pin`, `verify`, `upgrade`, and online `report` work caches successful
404
+ GitHub API responses for one hour. The cache root is selected in this order:
405
+
406
+ 1. `ACTIONS_WARDEN_CACHE_DIR`
407
+ 2. `$XDG_CACHE_HOME/actions-warden`
408
+ 3. `~/.cache/actions-warden`
409
+
410
+ Cache keys include a hash of the authentication identity, so authenticated
411
+ responses are not shared with anonymous or different-token calls. Requests
412
+ have timeouts, bounded retries, in-flight deduplication, and ETag revalidation
413
+ where available. Resolver failures are returned as errors; the CLI does not
414
+ silently substitute an unverified value.
415
+
416
+ ## Related guides
417
+
418
+ - [Configuration](./CONFIGURATION.md)
419
+ - [Output contracts](./OUTPUTS.md)
420
+ - [GitHub Action](./GITHUB-ACTION.md)
421
+ - [AI and coding agents](./AI-AGENTS.md)