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