action-pinner 0.1.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 (67) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +406 -0
  3. package/action.yml +53 -0
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.js +2 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/src/action-mode.d.ts +1 -0
  8. package/dist/src/action-mode.js +109 -0
  9. package/dist/src/action-mode.js.map +1 -0
  10. package/dist/src/cli.d.ts +2 -0
  11. package/dist/src/cli.js +780 -0
  12. package/dist/src/cli.js.map +1 -0
  13. package/dist/src/config.d.ts +2 -0
  14. package/dist/src/config.js +291 -0
  15. package/dist/src/config.js.map +1 -0
  16. package/dist/src/dependabot.d.ts +1 -0
  17. package/dist/src/dependabot.js +11 -0
  18. package/dist/src/dependabot.js.map +1 -0
  19. package/dist/src/enforcement.d.ts +12 -0
  20. package/dist/src/enforcement.js +238 -0
  21. package/dist/src/enforcement.js.map +1 -0
  22. package/dist/src/github-app.d.ts +6 -0
  23. package/dist/src/github-app.js +4 -0
  24. package/dist/src/github-app.js.map +1 -0
  25. package/dist/src/index.d.ts +2 -0
  26. package/dist/src/index.js +16 -0
  27. package/dist/src/index.js.map +1 -0
  28. package/dist/src/logging.d.ts +8 -0
  29. package/dist/src/logging.js +38 -0
  30. package/dist/src/logging.js.map +1 -0
  31. package/dist/src/multi-repo-scanner.d.ts +69 -0
  32. package/dist/src/multi-repo-scanner.js +121 -0
  33. package/dist/src/multi-repo-scanner.js.map +1 -0
  34. package/dist/src/netrc-auth.d.ts +13 -0
  35. package/dist/src/netrc-auth.js +123 -0
  36. package/dist/src/netrc-auth.js.map +1 -0
  37. package/dist/src/org.d.ts +49 -0
  38. package/dist/src/org.js +162 -0
  39. package/dist/src/org.js.map +1 -0
  40. package/dist/src/pattern-match.d.ts +5 -0
  41. package/dist/src/pattern-match.js +59 -0
  42. package/dist/src/pattern-match.js.map +1 -0
  43. package/dist/src/pinner.d.ts +6 -0
  44. package/dist/src/pinner.js +148 -0
  45. package/dist/src/pinner.js.map +1 -0
  46. package/dist/src/pr.d.ts +87 -0
  47. package/dist/src/pr.js +165 -0
  48. package/dist/src/pr.js.map +1 -0
  49. package/dist/src/report.d.ts +10 -0
  50. package/dist/src/report.js +54 -0
  51. package/dist/src/report.js.map +1 -0
  52. package/dist/src/resolver.d.ts +44 -0
  53. package/dist/src/resolver.js +227 -0
  54. package/dist/src/resolver.js.map +1 -0
  55. package/dist/src/scanner.d.ts +8 -0
  56. package/dist/src/scanner.js +128 -0
  57. package/dist/src/scanner.js.map +1 -0
  58. package/dist/src/types.d.ts +170 -0
  59. package/dist/src/types.js +41 -0
  60. package/dist/src/types.js.map +1 -0
  61. package/dist/src/version.d.ts +1 -0
  62. package/dist/src/version.js +22 -0
  63. package/dist/src/version.js.map +1 -0
  64. package/dist/src/workflow-paths.d.ts +4 -0
  65. package/dist/src/workflow-paths.js +29 -0
  66. package/dist/src/workflow-paths.js.map +1 -0
  67. package/package.json +62 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jon Galloway and contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,406 @@
1
+ # action-pinner
2
+
3
+ Pin GitHub Actions refs like `@v4` or `@main` to immutable commit SHAs so your workflows are safer to review, harder to tamper with, and easier to reproduce. `action-pinner` scans workflow files, rewrites refs in place, enforces policy in CI, and can open a pull request with the changes.
4
+
5
+ > **Node:** 20+    **License:** MIT
6
+
7
+ ## Why pin actions?
8
+
9
+ - **Tags can move.** `@v4` and `@main` are mutable; a SHA is not.
10
+ - **Supply chain risk goes down.** Pinning limits surprise changes from compromised or retagged releases.
11
+ - **Builds become reproducible.** The same workflow definition resolves to the same code every time.
12
+ - **Audits get easier.** SHA-based refs and exception metadata leave a clearer review trail.
13
+
14
+ ## Quick Start
15
+
16
+ Install dependencies and build the CLI:
17
+
18
+ ```bash
19
+ npm install
20
+ npm run build
21
+ ```
22
+
23
+ ## Install
24
+
25
+ Run directly without installing:
26
+
27
+ ```bash
28
+ npx action-pinner scan
29
+ npx action-pinner fix
30
+ ```
31
+
32
+ Or install globally:
33
+
34
+ ```bash
35
+ npm install -g action-pinner
36
+ ```
37
+
38
+ Examples below use `action-pinner` on your PATH. From a local clone, you can also run `node dist/index.js <command>`.
39
+
40
+ Scan for unpinned actions:
41
+
42
+ ```bash
43
+ action-pinner scan
44
+ ```
45
+
46
+ Rewrite workflow files in place:
47
+
48
+ ```bash
49
+ action-pinner fix
50
+ ```
51
+
52
+ Fail CI when unpinned refs are found:
53
+
54
+ ```bash
55
+ action-pinner enforce
56
+ ```
57
+
58
+ ## CLI Commands
59
+
60
+ ### `scan`
61
+
62
+ Find unpinned `uses:` refs without modifying files.
63
+
64
+ ```bash
65
+ action-pinner scan
66
+ action-pinner scan --path ".github/workflows"
67
+ action-pinner scan --exclude-path ".github/workflows/legacy/**"
68
+ action-pinner scan --include-action "actions/*" --exclude-action "actions/cache"
69
+ action-pinner scan --github-org octo-org --include-repo "platform-*" --exclude-repo "*-archive"
70
+ action-pinner scan --repo octo-org/service-a octo-org/service-b --json
71
+ ```
72
+
73
+ Flags:
74
+
75
+ - `--config <path>`: config file path (default: `.action-pinner.json`)
76
+ - `--path <path...>`: workflow file, directory, or glob to scan
77
+ - `--exclude-path <path...>`: workflow file, directory, or glob to skip
78
+ - `--include-action <pattern...>`: only scan matching actions
79
+ - `--exclude-action <pattern...>`: skip matching actions
80
+ - `--repo <owner/repo...>`: explicit multi-repo targets
81
+ - `--github-org <org>`: enumerate repositories from an organization
82
+ - `--include-repo <pattern...>` / `--exclude-repo <pattern...>`: repo filters for org or explicit targets
83
+ - `--json`: emit machine-readable JSON
84
+ - `--token <token>`: GitHub token override
85
+ - `--github-api-url <url>`: GitHub API base URL, including GHES
86
+ - `--use-netrc`: read credentials from `.netrc` / `_netrc`
87
+
88
+ ### `fix`
89
+
90
+ Resolve mutable refs to SHAs and update workflow files in place.
91
+
92
+ ```bash
93
+ action-pinner fix
94
+ action-pinner fix --dry-run
95
+ action-pinner fix --path ".github/workflows/release.yml"
96
+ action-pinner fix --continue-on-error --fail-on-ambiguous
97
+ action-pinner fix --comment-format "pin@{ref}"
98
+ ```
99
+
100
+ Flags:
101
+
102
+ - `--dry-run`: preview changes without writing files
103
+ - All `scan` flags except `--json`
104
+ - `--continue-on-error`: skip unresolved refs instead of failing the run
105
+ - `--fail-on-ambiguous`: fail if a ref resolves ambiguously
106
+ - `--comment-format <template>`: customize pinned version comments with `{ref}`, `{action}`, and `{sha_short}` tokens
107
+
108
+ ### `enforce`
109
+
110
+ Use policy mode for CI. `enforce` reports allowed refs, violations, invalid exceptions, and exits non-zero when policy fails.
111
+
112
+ ```bash
113
+ action-pinner enforce
114
+ action-pinner enforce --allow-action "actions/*"
115
+ action-pinner enforce --exception "actions/upload-artifact@v3::**/legacy.yml"
116
+ action-pinner enforce --json
117
+ ```
118
+
119
+ Flags:
120
+
121
+ - All `scan` flags
122
+ - `--allow-action <pattern...>`: allowlist unpinned actions by pattern
123
+ - `--exception <rule...>`: allow a specific exception using `<action>[@ref][::workflow-glob]`
124
+ - `--continue-on-error`: continue when a ref cannot be resolved
125
+ - `--fail-on-ambiguous`: fail if a ref resolves ambiguously
126
+
127
+ ### `pr`
128
+
129
+ Pin refs, create a branch, and publish a pull request using the `pr` config block.
130
+
131
+ ```bash
132
+ action-pinner pr
133
+ action-pinner pr --path ".github/workflows"
134
+ action-pinner pr --continue-on-error --fail-on-ambiguous
135
+ action-pinner pr --comment-format "{ref}"
136
+ ```
137
+
138
+ Flags:
139
+
140
+ - All `scan` flags except `--json`
141
+ - `--continue-on-error`: skip unresolved refs instead of failing the run
142
+ - `--fail-on-ambiguous`: fail if a ref resolves ambiguously
143
+ - `--comment-format <template>`: override the configured version comment template for this run
144
+
145
+ ### `dependabot-snippet`
146
+
147
+ Generate a `github-actions` Dependabot snippet for pinned workflows.
148
+
149
+ ```bash
150
+ action-pinner dependabot-snippet
151
+ ```
152
+
153
+ ## GitHub Action Usage
154
+
155
+ Run `action-pinner` as a GitHub Action:
156
+
157
+ ```yaml
158
+ - uses: jongalloway/action-pinner@v1
159
+ with:
160
+ mode: scan
161
+ config: .action-pinner.json
162
+ ```
163
+
164
+ Use `enforce` to gate workflow changes in CI:
165
+
166
+ ```yaml
167
+ name: enforce-pinned-actions
168
+
169
+ on:
170
+ pull_request:
171
+ push:
172
+ branches: [main]
173
+
174
+ jobs:
175
+ action-pinner:
176
+ runs-on: ubuntu-latest
177
+ steps:
178
+ - uses: actions/checkout@v4
179
+ - uses: jongalloway/action-pinner@v1
180
+ with:
181
+ mode: enforce
182
+ config: .action-pinner.json
183
+ ```
184
+
185
+ Action inputs:
186
+
187
+ - `mode`: `scan`, `fix`, `enforce`, or `pr`
188
+ - `config`: config file path
189
+ - `path`, `exclude_path`, `include_action`, `exclude_action`
190
+ - `allow_actions`, `exception_rules`
191
+ - `json`
192
+
193
+ ## Pre-commit
194
+
195
+ Use `action-pinner` as a [pre-commit](https://pre-commit.com/) hook to scan workflow changes before they land:
196
+
197
+ ```yaml
198
+ # .pre-commit-config.yaml
199
+ repos:
200
+ - repo: https://github.com/jongalloway/action-pinner
201
+ rev: v0.1.0
202
+ hooks:
203
+ - id: action-pinner-scan
204
+ ```
205
+
206
+ Available hooks:
207
+
208
+ - `action-pinner-scan`: runs `action-pinner scan` against `.github/workflows` and fails if unpinned refs are found.
209
+ - `action-pinner-fix`: runs `action-pinner fix` against `.github/workflows` to auto-pin refs before commit.
210
+
211
+ ## Configuration
212
+
213
+ Example `.action-pinner.json`:
214
+
215
+ ```json
216
+ {
217
+ "$schema": "./schemas/action-pinner.schema.json",
218
+ "mode": "scan",
219
+ "include": [
220
+ ".github/workflows/**/*.yml",
221
+ ".github/workflows/**/*.yaml"
222
+ ],
223
+ "exclude": [
224
+ ".github/workflows/legacy/**"
225
+ ],
226
+ "repos": [
227
+ "octo-org/service-a",
228
+ "octo-org/service-b"
229
+ ],
230
+ "includeRepos": [
231
+ "platform-*"
232
+ ],
233
+ "excludeRepos": [
234
+ "*-archive"
235
+ ],
236
+ "excludeActions": [
237
+ "actions/cache"
238
+ ],
239
+ "org": {
240
+ "name": "octo-org",
241
+ "includePrivate": true,
242
+ "includeArchived": false
243
+ },
244
+ "enforcement": {
245
+ "enabled": true,
246
+ "failOnUnpinned": true,
247
+ "allowActions": [
248
+ "actions/*",
249
+ "github/codeql-action"
250
+ ],
251
+ "exceptions": [
252
+ {
253
+ "action": "actions/upload-artifact",
254
+ "ref": "v3",
255
+ "workflow": "**/legacy.yml",
256
+ "reason": "Temporary migration exception",
257
+ "expiresAt": "2026-12-31"
258
+ }
259
+ ]
260
+ },
261
+ "pr": {
262
+ "create": true,
263
+ "branchPrefix": "chore/action-pinner",
264
+ "title": "Pin GitHub Actions to commit SHAs",
265
+ "labels": [
266
+ "security",
267
+ "dependencies"
268
+ ],
269
+ "reviewers": [
270
+ "octocat"
271
+ ],
272
+ "assignees": [
273
+ "hubot"
274
+ ]
275
+ },
276
+ "dependabot": {
277
+ "addVersionComments": true,
278
+ "commentFormat": "{ref}",
279
+ "generateConfigSnippet": false
280
+ },
281
+ "githubApiUrl": "https://enterprise.example.com/api/v3",
282
+ "useNetrc": false
283
+ }
284
+ ```
285
+
286
+ Notes:
287
+
288
+ - CLI flags override config values.
289
+ - `reason` and `expiresAt` make exceptions easier to review and clean up.
290
+ - `pr.create: false` creates the branch and commit without publishing a PR.
291
+ - `dependabot.commentFormat` supports `{ref}`, `{action}`, and `{sha_short}` tokens. Use `""` for no version comment, or set `dependabot.addVersionComments: false` to suppress comments entirely.
292
+
293
+ ## Authentication
294
+
295
+ Authentication precedence:
296
+
297
+ | Priority | Source |
298
+ | --- | --- |
299
+ | 1 | `--token <token>` |
300
+ | 2 | `PIN_ACTIONS_TOKEN` |
301
+ | 3 | `.netrc` / `_netrc` when `--use-netrc` is enabled |
302
+ | 4 | `GITHUB_TOKEN` |
303
+ | 5 | Anonymous GitHub API access |
304
+
305
+ Examples:
306
+
307
+ ```bash
308
+ action-pinner scan --token ghp_xxx
309
+ action-pinner scan --use-netrc
310
+ action-pinner scan --github-api-url https://enterprise.example.com/api/v3
311
+ ```
312
+
313
+ For GitHub Enterprise Server, set `--github-api-url` or `githubApiUrl` in config. See [docs/ENTERPRISE.md](./docs/ENTERPRISE.md).
314
+
315
+ ## Multi-Repo and Org Scanning
316
+
317
+ Scan explicit repositories:
318
+
319
+ ```bash
320
+ action-pinner scan --repo octo-org/service-a octo-org/service-b
321
+ ```
322
+
323
+ Discover repositories from an organization, then narrow the set:
324
+
325
+ ```bash
326
+ action-pinner scan --github-org octo-org --include-repo "platform-*" --exclude-repo "*-archive"
327
+ ```
328
+
329
+ Target a subset of workflow files across selected repos:
330
+
331
+ ```bash
332
+ action-pinner scan --github-org octo-org --path ".github/workflows/**" --exclude-path "**/legacy/**"
333
+ ```
334
+
335
+ For user-owned repositories, pass explicit `--repo owner/repo` values.
336
+
337
+ ## Enforcement Allowlists and Exceptions
338
+
339
+ Allowlist broad cases:
340
+
341
+ ```bash
342
+ action-pinner enforce --allow-action "actions/*"
343
+ ```
344
+
345
+ Add a narrow CLI exception:
346
+
347
+ ```bash
348
+ action-pinner enforce --exception "actions/upload-artifact@v3::**/legacy.yml"
349
+ ```
350
+
351
+ Config-driven exceptions are better for review history:
352
+
353
+ ```json
354
+ {
355
+ "enforcement": {
356
+ "failOnUnpinned": true,
357
+ "allowActions": ["actions/*"],
358
+ "exceptions": [
359
+ {
360
+ "action": "actions/upload-artifact",
361
+ "ref": "v3",
362
+ "workflow": "**/legacy.yml",
363
+ "reason": "Legacy workflow still migrating",
364
+ "expiresAt": "2026-12-31"
365
+ }
366
+ ]
367
+ }
368
+ }
369
+ ```
370
+
371
+ Rules:
372
+
373
+ - `allowActions` is pattern-based and broad.
374
+ - `exceptions` are specific and auditable.
375
+ - Expired or malformed exceptions fail closed.
376
+
377
+ ## Security
378
+
379
+ - **Fail closed:** unresolved refs, invalid exceptions, and policy violations fail enforcement by default.
380
+ - **Token safe:** tokens are redacted from logs; use the smallest possible scopes.
381
+ - **Deterministic output:** scans, rewrites, and fingerprints are stable on the same input.
382
+
383
+ See [SECURITY.md](./SECURITY.md) for the security policy and [docs/ENTERPRISE.md](./docs/ENTERPRISE.md) for GHES guidance.
384
+
385
+ ## Acknowledgments
386
+
387
+ This project was inspired by [mheap/pin-github-action](https://github.com/mheap/pin-github-action), which pioneered the idea of pinning GitHub Actions to commit SHAs. `action-pinner` is a completely new implementation built from scratch using modern Node.js and the GitHub REST API, designed to address long-standing community requests including:
388
+
389
+ - [Enterprise GitHub support](https://github.com/mheap/pin-github-action/issues/169)
390
+ - [Support netrc auth](https://github.com/mheap/pin-github-action/issues/168)
391
+ - [Published as a GitHub Action](https://github.com/mheap/pin-github-action/issues/141)
392
+ - [Default to `.github/workflows/`](https://github.com/mheap/pin-github-action/issues/201)
393
+
394
+ Thank you to [@mheap](https://github.com/mheap) and the contributors to that project for the inspiration.
395
+
396
+ ## Contributing
397
+
398
+ Clone the repo, install dependencies, and run:
399
+
400
+ ```bash
401
+ npm test
402
+ npm run lint
403
+ npm run build
404
+ ```
405
+
406
+ Open an issue or PR at [github.com/jongalloway/action-pinner/issues](https://github.com/jongalloway/action-pinner/issues).
package/action.yml ADDED
@@ -0,0 +1,53 @@
1
+ name: "action-pinner"
2
+ description: "Scan and pin unpinned GitHub Actions references to commit SHAs."
3
+ author: "action-pinner contributors"
4
+ inputs:
5
+ mode:
6
+ description: "Mode to run (scan|fix|enforce|pr)"
7
+ required: false
8
+ default: "scan"
9
+ config:
10
+ description: "Path to .action-pinner.json"
11
+ required: false
12
+ default: ".action-pinner.json"
13
+ path:
14
+ description: "Workflow file, directory, or glob to scan"
15
+ required: false
16
+ exclude_path:
17
+ description: "Workflow file, directory, or glob to exclude"
18
+ required: false
19
+ include_action:
20
+ description: "Comma-separated or newline-delimited action patterns to include"
21
+ required: false
22
+ exclude_action:
23
+ description: "Comma-separated or newline-delimited action patterns to exclude"
24
+ required: false
25
+ allow_actions:
26
+ description: "Comma-separated or newline-delimited enforcement allowlist patterns"
27
+ required: false
28
+ exception_rules:
29
+ description: "Comma-separated or newline-delimited enforcement exception rules (<action>[@ref][::workflow-glob])"
30
+ required: false
31
+ json:
32
+ description: "Emit JSON output"
33
+ required: false
34
+ default: "false"
35
+ outputs:
36
+ compliant:
37
+ description: "Whether enforcement completed without violations or invalid exceptions"
38
+ allowed_count:
39
+ description: "Number of unpinned refs allowed by allowlists or exceptions"
40
+ violation_count:
41
+ description: "Number of violating unpinned refs"
42
+ invalid_exception_count:
43
+ description: "Number of malformed or expired exceptions"
44
+ fingerprint:
45
+ description: "Deterministic run fingerprint"
46
+ config_hash:
47
+ description: "Deterministic config hash"
48
+ branding:
49
+ icon: "lock"
50
+ color: "blue"
51
+ runs:
52
+ using: "node20"
53
+ main: "dist/index.js"
@@ -0,0 +1 @@
1
+ import "./src/index.js";
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import "./src/index.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,gBAAgB,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function runActionMode(): Promise<void>;
@@ -0,0 +1,109 @@
1
+ import { appendFile } from "node:fs/promises";
2
+ import { loadConfig } from "./config.js";
3
+ import { evaluateEnforcement } from "./enforcement.js";
4
+ import { buildRunFingerprint } from "./report.js";
5
+ import { scanWorkflows } from "./scanner.js";
6
+ import { getToolVersion } from "./version.js";
7
+ import { resolveWorkflowPatterns } from "./workflow-paths.js";
8
+ import { runCli } from "./cli.js";
9
+ export async function runActionMode() {
10
+ const mode = process.env.INPUT_MODE ?? "scan";
11
+ const configPath = process.env.INPUT_CONFIG ?? ".action-pinner.json";
12
+ const args = buildCliArgs(mode, configPath);
13
+ await runCli(args);
14
+ if (mode === "enforce") {
15
+ await writeEnforcementOutputs(configPath);
16
+ }
17
+ }
18
+ function buildCliArgs(mode, configPath) {
19
+ const args = [mode, "--config", configPath];
20
+ appendListFlag(args, "--path", parseListInput(process.env.INPUT_PATH));
21
+ appendListFlag(args, "--exclude-path", parseListInput(process.env.INPUT_EXCLUDE_PATH));
22
+ appendListFlag(args, "--include-action", parseListInput(process.env.INPUT_INCLUDE_ACTION));
23
+ appendListFlag(args, "--exclude-action", parseListInput(process.env.INPUT_EXCLUDE_ACTION));
24
+ if (mode === "enforce") {
25
+ appendListFlag(args, "--allow-action", parseListInput(process.env.INPUT_ALLOW_ACTIONS));
26
+ appendListFlag(args, "--exception", parseListInput(process.env.INPUT_EXCEPTION_RULES));
27
+ }
28
+ if (parseBooleanInput(process.env.INPUT_JSON)) {
29
+ args.push("--json");
30
+ }
31
+ return args;
32
+ }
33
+ async function writeEnforcementOutputs(configPath) {
34
+ const outputPath = process.env.GITHUB_OUTPUT;
35
+ if (!outputPath) {
36
+ return;
37
+ }
38
+ const config = await loadConfig(configPath);
39
+ const include = resolveWorkflowPatterns(parseListInput(process.env.INPUT_PATH) || config.include);
40
+ const excludeInput = parseListInput(process.env.INPUT_EXCLUDE_PATH) ?? config.exclude;
41
+ const exclude = excludeInput.length > 0 ? resolveWorkflowPatterns(excludeInput) : [];
42
+ const includeActions = parseListInput(process.env.INPUT_INCLUDE_ACTION) ?? [];
43
+ const excludeActions = parseListInput(process.env.INPUT_EXCLUDE_ACTION) ?? config.excludeActions;
44
+ const allowActions = parseListInput(process.env.INPUT_ALLOW_ACTIONS) ?? config.enforcement.allowActions;
45
+ const exceptions = [
46
+ ...config.enforcement.exceptions,
47
+ ...parseExceptionRules(parseListInput(process.env.INPUT_EXCEPTION_RULES))
48
+ ];
49
+ const result = evaluateEnforcement(await scanWorkflows(include, process.cwd(), {
50
+ excludePatterns: exclude,
51
+ includeActions,
52
+ excludeActions
53
+ }), {
54
+ allowActions,
55
+ exceptions
56
+ });
57
+ const fingerprint = buildRunFingerprint(config, await getToolVersion());
58
+ const lines = [
59
+ `compliant=${result.compliant}`,
60
+ `allowed_count=${result.summary.allowedCount}`,
61
+ `violation_count=${result.summary.violationCount}`,
62
+ `invalid_exception_count=${result.summary.invalidExceptionCount}`,
63
+ `fingerprint=${fingerprint.fingerprint}`,
64
+ `config_hash=${fingerprint.configHash}`
65
+ ];
66
+ await appendFile(outputPath, `${lines.join("\n")}\n`, "utf8");
67
+ }
68
+ function appendListFlag(args, flag, values) {
69
+ if (!values || values.length === 0) {
70
+ return;
71
+ }
72
+ args.push(flag, ...values);
73
+ }
74
+ function parseListInput(value) {
75
+ if (!value) {
76
+ return undefined;
77
+ }
78
+ const normalized = value.replace(/\r/g, "\n").trim();
79
+ if (!normalized) {
80
+ return undefined;
81
+ }
82
+ const separator = normalized.includes("\n") ? /\n+/ : /,/;
83
+ const values = normalized
84
+ .split(separator)
85
+ .map((entry) => entry.trim())
86
+ .filter(Boolean);
87
+ return values.length > 0 ? values : undefined;
88
+ }
89
+ function parseBooleanInput(value) {
90
+ return value?.trim().toLowerCase() === "true";
91
+ }
92
+ function parseExceptionRules(values) {
93
+ if (!values || values.length === 0) {
94
+ return [];
95
+ }
96
+ return values.map((rawRule) => {
97
+ const [actionAndRef, workflow] = rawRule.split("::", 2);
98
+ const [action, ref] = actionAndRef.split("@", 2);
99
+ if (!action) {
100
+ throw new Error(`Invalid enforcement exception rule '${rawRule}'. Expected <action>[@ref][::workflow-glob].`);
101
+ }
102
+ return {
103
+ action,
104
+ ref: ref || undefined,
105
+ workflow: workflow || undefined
106
+ };
107
+ });
108
+ }
109
+ //# sourceMappingURL=action-mode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action-mode.js","sourceRoot":"","sources":["../../src/action-mode.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export declare function runCli(argv?: string[]): Promise<void>;