Skip to content

Commit e1cadc9

Browse files
Copilotneilime
andcommitted
feat(linter): add workflow permissions analysis with actions-permissions
- Add new permissions-analysis job to linter.yml workflow - Job runs GitHubSecurityLab/actions-permissions to analyze workflow permissions - Follows same pattern as actions-pinning job for consistency - Uses minimal permissions (contents: read) - Analyzes same workflow files as defined in action-files input - Updates documentation to reflect new permissions analysis feature Co-authored-by: neilime <314088+neilime@users.noreply.github.com>
1 parent 94dde6b commit e1cadc9

2 files changed

Lines changed: 84 additions & 0 deletions

File tree

.github/workflows/linter.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Executes:
2929
- [Super-Linter](https://github.com/super-linter/super-linter), with some opinionated defaults.
3030
- [CodeQL](https://docs.github.com/en/code-security/code-scanning/introduction-to-code-scanning/about-code-scanning-with-codeql) to analyze the code.
3131
- [Ratchet](https://github.com/sethvargo/ratchet) to check that GitHub Action versions are pinned.
32+
- [Actions Permissions](https://github.com/GitHubSecurityLab/actions-permissions) to analyze and optimize workflow permissions.
3233

3334
### Permissions
3435

.github/workflows/linter.yml

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
# - [Super-Linter](https://github.com/super-linter/super-linter), with some opinionated defaults.
55
# - [CodeQL](https://docs.github.com/en/code-security/code-scanning/introduction-to-code-scanning/about-code-scanning-with-codeql) to analyze the code.
66
# - [Ratchet](https://github.com/sethvargo/ratchet) to check that GitHub Action versions are pinned.
7+
# - [Actions Permissions](https://github.com/GitHubSecurityLab/actions-permissions) to analyze and optimize workflow permissions.
78

89
name: "Linter"
910
on:
@@ -216,3 +217,85 @@ jobs:
216217
if: ${{ steps.get-files-to-lint.outputs.files }}
217218
with:
218219
args: "lint --format human --format actions ${{ steps.get-files-to-lint.outputs.files }}"
220+
221+
permissions-analysis:
222+
name: 🔐 Workflow Permissions Analysis
223+
runs-on: ${{ fromJson(inputs.runs-on) }}
224+
if: ${{ inputs.action-files }}
225+
permissions:
226+
contents: read
227+
steps:
228+
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
229+
with:
230+
fetch-depth: "${{ inputs.lint-all && 1 || 0 }}"
231+
232+
- id: changed-files
233+
uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 # v47.0.0
234+
if: ${{ inputs.lint-all == false }}
235+
with:
236+
files: ${{ inputs.action-files }}
237+
dir_names_exclude_current_dir: true
238+
239+
- id: get-files-to-analyze
240+
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
241+
with:
242+
script: |
243+
const fs = require("node:fs");
244+
const path = require("node:path");
245+
246+
const changedFiles = ${{ toJSON(steps.changed-files.outputs.all_changed_and_modified_files) }};
247+
248+
let actionFiles = [];
249+
if (changedFiles !== null) {
250+
actionFiles = changedFiles.split(" ").filter(file => file && fs.existsSync(file));
251+
} else {
252+
const actionFilesInput = ${{ toJson(inputs.action-files) }};
253+
254+
for (const actionFile of actionFilesInput.split("\n")) {
255+
let sanitizedActionFile = actionFile.trim();
256+
if (sanitizedActionFile === "") {
257+
continue;
258+
}
259+
260+
if (path.isAbsolute(sanitizedActionFile)) {
261+
// Ensure actionFile is within the workspace
262+
if (!sanitizedActionFile.startsWith(process.env.GITHUB_WORKSPACE)) {
263+
return core.setFailed(`Action file / directory is not within the workspace: ${sanitizedActionFile}`);
264+
}
265+
} else {
266+
sanitizedActionFile = path.join(process.env.GITHUB_WORKSPACE, sanitizedActionFile);
267+
}
268+
actionFiles.push(sanitizedActionFile);
269+
}
270+
271+
if (actionFiles.length === 0) {
272+
return core.setFailed("No action files to analyze.");
273+
}
274+
275+
async function getActionFiles(actionFile) {
276+
const globber = await glob.create(actionFile,{ matchactionFilesInput: false });
277+
return await globber.glob();
278+
}
279+
280+
actionFiles = (await Promise.all(actionFiles.map(getActionFiles)))
281+
.flat()
282+
.map((file) => path.relative(process.env.GITHUB_WORKSPACE, file));
283+
284+
if (actionFiles.length === 0) {
285+
return core.setFailed("No action files to analyze.");
286+
}
287+
}
288+
289+
const files = actionFiles.map((file) => path.relative(process.env.GITHUB_WORKSPACE, file));
290+
const filesOutput = [...new Set(files)].join("\n").trim();
291+
292+
if (filesOutput.length === 0) {
293+
return;
294+
}
295+
296+
core.setOutput("files", filesOutput);
297+
298+
- uses: GitHubSecurityLab/actions-permissions@f24e0c210427f4c43e516265a4ca94e1e30e95f9 # v1.0.10
299+
if: ${{ steps.get-files-to-analyze.outputs.files }}
300+
with:
301+
path: ${{ steps.get-files-to-analyze.outputs.files }}

0 commit comments

Comments
 (0)