4242 ./action.yml
4343 ./.github/workflows/**/*.yml
4444 ./actions/**/*.yml
45+ workflow-files :
46+ description : |
47+ List of files or directories where GitHub workflows are located.
48+ Supports glob patterns.
49+ Leave empty to disable the check.
50+ type : string
51+ required : false
52+ default : |
53+ ./.github/workflows/*.yml
4554 lint-all :
4655 description : " Run checks on all files, not just the changed ones."
4756 type : boolean
@@ -136,10 +145,12 @@ jobs:
136145 with :
137146 category : " /language:${{matrix.language}}"
138147
139- actions-pinning :
140- name : 📌 Check GitHub Actions Pinning
148+ prepare-actions-linting :
141149 runs-on : ${{ fromJson(inputs.runs-on) }}
142- if : ${{ inputs.action-files }}
150+ if : ${{ inputs.action-files || inputs.workflow-files }}
151+ outputs :
152+ action-files : ${{ steps.get-files-to-lint.outputs.action-files }}
153+ workflow-names : ${{ steps.get-files-to-lint.outputs.workflow-names }}
143154 steps :
144155 - uses : actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
145156 with :
@@ -149,7 +160,9 @@ jobs:
149160 uses : tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 # v47.0.0
150161 if : ${{ inputs.lint-all == false }}
151162 with :
152- files : ${{ inputs.action-files }}
163+ files : |
164+ ${{ inputs.action-files }}
165+ ${{ inputs.workflow-files }}
153166 dir_names_exclude_current_dir : true
154167
155168 - id : get-files-to-lint
@@ -161,141 +174,109 @@ jobs:
161174
162175 const changedFiles = ${{ toJSON(steps.changed-files.outputs.all_changed_and_modified_files) }};
163176
164- let actionFiles = [];
165- if (changedFiles !== null) {
166- actionFiles = changedFiles.split(" ").filter(file => file && fs.existsSync(file));
167- } else {
168- const actionFilesInput = ${{ toJson(inputs.action-files) }};
169-
170- for (const actionFile of actionFilesInput.split("\n")) {
171- let sanitizedActionFile = actionFile.trim();
172- if (sanitizedActionFile === "") {
177+ function parseFilePatterns(filePatterns) {
178+ const patterns = [];
179+ for (const filePattern of filePatterns.split("\n")) {
180+ let sanitizedFilePattern = filePattern.trim();
181+ if (sanitizedFilePattern === "") {
173182 continue;
174183 }
175184
176- if (path.isAbsolute(sanitizedActionFile )) {
177- // Ensure actionFile is within the workspace
178- if (!sanitizedActionFile .startsWith(process.env.GITHUB_WORKSPACE)) {
179- return core.setFailed(`Action file / directory is not within the workspace: ${sanitizedActionFile }`);
185+ if (path.isAbsolute(sanitizedFilePattern )) {
186+ // Ensure filePattern is within the workspace
187+ if (!sanitizedFilePattern .startsWith(process.env.GITHUB_WORKSPACE)) {
188+ return core.setFailed(`File / directory is not within the workspace: ${sanitizedFilePattern }`);
180189 }
181190 } else {
182- sanitizedActionFile = path.join(process.env.GITHUB_WORKSPACE, sanitizedActionFile );
191+ sanitizedFilePattern = path.join(process.env.GITHUB_WORKSPACE, sanitizedFilePattern );
183192 }
184- actionFiles.push(sanitizedActionFile);
185- }
186-
187- if (actionFiles.length === 0) {
188- return core.setFailed("No action files to lint.");
189- }
190-
191- async function getActionFiles(actionFile) {
192- const globber = await glob.create(actionFile,{ matchactionFilesInput: false });
193- return await globber.glob();
193+ patterns.push(sanitizedFilePattern);
194194 }
195+ return patterns;
196+ }
195197
196- actionFiles = (await Promise.all(actionFiles.map(getActionFiles)))
198+ async function findFilesByPatterns(filePatterns) {
199+ const foundFiles = (await Promise.all(filePatterns.map(
200+ async (filePattern) => {
201+ const globber = await glob.create(filePattern, { excludeHiddenFiles: false });
202+ return await globber.glob();
203+ }
204+ )))
197205 .flat()
198206 .map((file) => path.relative(process.env.GITHUB_WORKSPACE, file));
199207
200- if (actionFiles.length === 0) {
201- return core.setFailed("No action files to lint.");
202- }
203- }
204-
205- const files = actionFiles.map((file) => path.relative(process.env.GITHUB_WORKSPACE, file));
206- const filesOutput = [...new Set(files)].join(" ").trim();
207-
208- if (filesOutput.length === 0) {
209- return;
208+ return [...new Set(foundFiles)];
210209 }
211-
212- core.setOutput("files", filesOutput);
213-
214- - id : ratchet
215- # FIXME: should be updated by dependabot. See https://github.com/dependabot/dependabot-core/issues/8362
216- uses : " docker://ghcr.io/sethvargo/ratchet:0.11.3@sha256:242445a1c55430ad7477e6fcf2027c77d03f5760702537bca4cf622e7338fc81" # 0.11.3
217- if : ${{ steps.get-files-to-lint.outputs.files }}
218- with :
219- 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-
210+
248211 let actionFiles = [];
249212 if (changedFiles !== null) {
250213 actionFiles = changedFiles.split(" ").filter(file => file && fs.existsSync(file));
251214 } else {
252215 const actionFilesInput = ${{ toJson(inputs.action-files) }};
216+ const parsedActionFiles = parseFilePatterns(actionFilesInput);
253217
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);
218+ if (parsedActionFiles.length === 0) {
219+ return core.setFailed("No action files to lint.");
269220 }
270221
271- if (actionFiles.length === 0) {
272- return core.setFailed("No action files to analyze.");
273- }
222+ actionFiles = await findFilesByPatterns(parsedActionFiles);
223+ }
274224
275- async function getActionFiles(actionFile) {
276- const globber = await glob.create(actionFile,{ matchactionFilesInput: false });
277- return await globber.glob();
278- }
225+ actionFiles = actionFiles.map((file) => path.relative(process.env.GITHUB_WORKSPACE, file));
226+ const actionFilesOutput = [...new Set(actionFiles)].join(" ").trim();
279227
280- actionFiles = (await Promise.all(actionFiles.map(getActionFiles)))
281- .flat()
282- .map((file) => path.relative(process.env.GITHUB_WORKSPACE, file));
228+ if (actionFilesOutput.length > 0) {
229+ core.setOutput("action-files", actionFilesOutput);
230+ }
283231
284- if (actionFiles.length === 0) {
285- return core.setFailed("No action files to analyze.");
232+ let workflowNames = [];
233+ const workflowFilesInput = ${{ toJson(inputs.workflow-files) }};
234+ const parsedWorkflowFiles = parseFilePatterns(workflowFilesInput);
235+ const workflowFiles = await findFilesByPatterns(parsedWorkflowFiles);
236+
237+ for (const workflowFile of workflowFiles) {
238+ try {
239+ const workflowContent = fs.readFileSync(workflowFile, "utf8");
240+ const match = workflowContent.match(/name:\s*(.+)/);
241+ if (match) {
242+ workflowNames.push(match[1].trim());
243+ } else {
244+ workflowNames.push(path.basename(workflowFile, path.extname(workflowFile)));
245+ }
246+ } catch (error) {
247+ return core.setFailed(`Failed to read workflow file ${workflowFile}: ${error.message}`);
286248 }
287249 }
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;
250+ workflowNames = [...new Set(workflowNames)];
251+ if (workflowNames.length > 0) {
252+ core.setOutput("workflow-names", JSON.stringify(workflowNames));
294253 }
295254
296- core.setOutput("files", filesOutput);
255+ actions-pinning :
256+ name : 📌 Check GitHub Actions Pinning
257+ needs : prepare-actions-linting
258+ runs-on : ${{ fromJson(inputs.runs-on) }}
259+ if : ${{ needs.prepare-actions-linting.outputs.action-files }}
260+ steps :
261+ - uses : actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
297262
298- - uses : GitHubSecurityLab/actions-permissions@f24e0c210427f4c43e516265a4ca94e1e30e95f9 # v1.0.10
299- if : ${{ steps.get-files-to-analyze.outputs.files }}
263+ # FIXME: should be updated by dependabot. See https://github.com/dependabot/dependabot-core/issues/8362
264+ - uses : " docker://ghcr.io/sethvargo/ratchet:0.11.3@sha256:242445a1c55430ad7477e6fcf2027c77d03f5760702537bca4cf622e7338fc81" # 0.11.3
265+ if : ${{ needs.prepare-actions-linting.outputs.action-files }}
266+ with :
267+ args : " lint --format human --format actions ${{ needs.prepare-actions-linting.outputs.action-files }}"
268+
269+ permissions-analysis :
270+ name : 🔐 Workflow Permissions Analysis
271+ runs-on : ${{ fromJson(inputs.runs-on) }}
272+ needs : prepare-actions-linting
273+ if : ${{ needs.prepare-actions-linting.outputs.action-files }}
274+ permissions :
275+ actions : read
276+ strategy :
277+ matrix :
278+ name : ${{ fromJson(needs.prepare-actions-linting.outputs.workflow-names) }}
279+ steps :
280+ - uses : GitHubSecurityLab/actions-permissions/advisor@37c927c24552caa0ef6040ab0876db729cc12754 # v1.0.2-beta7
300281 with :
301- path : ${{ steps.get-files-to-analyze.outputs.files }}
282+ name : ${{ matrix.name }}
0 commit comments