Skip to content

Commit 00b9607

Browse files
committed
Updated workflow audit input
1 parent 3b0823f commit 00b9607

File tree

2 files changed

+57
-21
lines changed

2 files changed

+57
-21
lines changed

README.md

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,35 @@ For Enterprise Server or Data Residency users, please set `GITHUB_BASE_URL` in y
2020
### audit_workflow_runs.js
2121

2222
```text
23-
node audit_workflow_runs.js <org or enterprise name> <ent|org|repo> <start date> <end date> [<action>] [<commit SHA>]
23+
node audit_workflow_runs.js <org or enterprise name> <ent|org|repo> <start date> <end date> [<output-file>] [<input-file>]
2424
```
2525

26-
Results are printed to the console in CSV, and also appended to a file in the current directory, named `workflow_audit_results.sljson`.
26+
Results are printed to the console in CSV, and also appended to a file in the current directory, named `workflow_audit_results.sljson` by default.
27+
28+
By default all Actions are listed, but you can filter by particular Actions using a JSON formatted input file.
2729

2830
For example:
2931

3032
```bash
31-
node audit_workflow_runs.js github org 2025-03-13 2025-03-15 tj-actions/changed-files 0e58ed8671d6b60d0890c21b07f8835ace038e67
33+
node audit_workflow_runs.js github org 2025-03-13 2025-03-15 github_actions_audit.json
3234
```
3335

36+
#### JSON input file format
37+
38+
The JSON input file should an object with the keys being the name of the Action, and the value being an array of the commits you are interested in.
39+
40+
Use the Action name in the format `owner/repo` or `owner/repo/path`, where `path` can contain any number of slashes.
41+
42+
You can express some wildcards - use `*` after the first `/` in the Action to include all repositories under the owner, and use `*` in the commit array (or leave it empty) to include all commits.
43+
44+
An Action name given without a path will match any Action in that repository, whether or not it has a path. You can also explictly use `*` in the path to match any path.
45+
3446
### find_compromised_secrets.js
3547

3648
> [!NOTE]
37-
> This is relevant only to secrets leaked after the `tj-actions/changed-files` and `reviewdog` compromises in March 2025.
38-
39-
This script takes the output of `audit_workflow_runs.js` and searches for secrets that were leaked in those workflow runs.
49+
> This is relevant only to secrets leaked because of the `tj-actions/changed-files` and `reviewdog` compromises in March 2025.
4050
41-
You should take the output from the single-line JSON file for any known-compromised Actions and run it through this script.
51+
This script takes the structured single-line JSON output of `audit_workflow_runs.js` (not the convenience CSV output) and searches for secrets that were leaked in those workflow runs.
4252

4353
```text
4454
node find_compromised_secrets.js < <path sljson file>
@@ -54,6 +64,10 @@ node find_compromised_secrets.js < workflow_audit_results.sljson
5464

5565
## Changelog
5666

67+
### 2025-05-28 15:30Z
68+
69+
Updated audit script to take JSON input to filter by Actions and commits.
70+
5771
### 2025-05-20 18:15Z
5872

5973
Added script to allow decoding secrets from workflows affected by a particular set of compromises in March 2025.

audit_workflow_runs.js

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -299,23 +299,51 @@ function matchActionsToAuditTargets(result, actionsToAudit) {
299299
const sha = result.sha;
300300

301301
const [owner, repo] = name.split("/");
302+
const path = name.split("/").slice(2).join("/");
302303

303304
const owners = Object.keys(actionsToAudit);
304305

306+
let matchedRepo = "";
307+
let matchedPath = "";
308+
305309
if (owners.includes(owner)) {
306310
const repos = Object.keys(actionsToAudit[owner]);
307311

308312
if (repos.includes(repo) || repos.includes("*")) {
309-
const hashes = actionsToAudit[owner][repo] ?? actionsToAudit[owner]["*"];
313+
matchedRepo = repos.includes(repo) ? repo : "*";
314+
315+
const paths = Object.keys(actionsToAudit[owner][matchedRepo]);
316+
317+
if (paths.includes(path) || paths.includes("*") || paths.includes("")) {
318+
matchedPath = paths.includes(path) ? path : (paths.includes("*") ? "*" : "");
319+
320+
const hashes = actionsToAudit[owner][matchedRepo][matchedPath];
310321

311-
if (hashes.includes(sha) || hashes.includes("*") || hashes.length == 0) {
312-
return true;
322+
if (hashes.includes(sha) || hashes.includes("*") || hashes.length == 0) {
323+
return true;
324+
}
313325
}
314326
}
315327
}
316328
return false;
317329
}
318330

331+
function parseFromInputFile(actionsToAuditFilename) {
332+
const actionsToAudit = {};
333+
if (actionsToAuditFilename) {
334+
const actionsToAuditFile = fs.readFileSync(actionsToAuditFilename, "utf-8");
335+
const actionsToAuditRaw = JSON.parse(actionsToAuditFile);
336+
for (const [action, hashes] of Object.entries(actionsToAuditRaw)) {
337+
const [org, repo] = action.split("/");
338+
const path = action.split("/").slice(2).join("/");
339+
actionsToAudit[org] ??= {};
340+
actionsToAudit[org][repo] ??= {};
341+
actionsToAudit[org][repo][path] = hashes;
342+
}
343+
}
344+
return actionsToAudit;
345+
}
346+
319347
async function main() {
320348
// Parse CLI arguments
321349
const args = process.argv.slice(2);
@@ -333,25 +361,19 @@ async function main() {
333361
orgOrEnt,
334362
startDate,
335363
endDate,
336-
actionsToAuditFilename,
337364
argsOutputFilename,
365+
actionsToAuditFilename,
338366
] = args;
339367

340368
if (!["ent", "org", "repo"].includes(orgOrEnt)) {
341369
console.error("<org|ent|repo> must be 'ent', 'org', 'repo'");
342370
return;
343371
}
344372

345-
const actionsToAudit = {};
346-
if (actionsToAuditFilename) {
347-
const actionsToAuditFile = fs.readFileSync(actionsToAuditFilename, "utf-8");
348-
const actionsToAuditRaw = JSON.parse(actionsToAuditFile);
349-
for (const [action, hashes] of Object.entries(actionsToAuditRaw)) {
350-
const [org, repo] = action.split("/");
351-
actionsToAudit[org] = actionsToAudit[org] || {};
352-
actionsToAudit[org][repo] = hashes;
353-
}
354-
}
373+
const actionsToAudit = parseFromInputFile(actionsToAuditFilename);
374+
375+
console.log("Actions to audit:");
376+
console.log(JSON.stringify(actionsToAudit, null, 2));
355377

356378
const outputFilename = argsOutputFilename || "workflow_audit_results.sljson";
357379

0 commit comments

Comments
 (0)