Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions .github/workflows/pr-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ jobs:
id: source-branch
if: inputs.enforce_source_branches
continue-on-error: true
uses: LerianStudio/github-actions-shared-workflows/src/validate/pr-source-branch@v1.19.1-beta.2
uses: LerianStudio/github-actions-shared-workflows/src/validate/pr-source-branch@v1.20.0-beta.1
with:
github-token: ${{ secrets.MANAGE_TOKEN || github.token }}
allowed-branches: ${{ inputs.allowed_source_branches }}
Expand All @@ -103,7 +103,7 @@ jobs:
- name: Validate PR title
id: title
continue-on-error: true
uses: LerianStudio/github-actions-shared-workflows/src/validate/pr-title@v1.19.1-beta.2
uses: LerianStudio/github-actions-shared-workflows/src/validate/pr-title@v1.20.0-beta.1
with:
github-token: ${{ github.token }}
types: ${{ inputs.pr_title_types }}
Expand All @@ -113,7 +113,7 @@ jobs:
- name: Validate PR description
id: description
continue-on-error: true
uses: LerianStudio/github-actions-shared-workflows/src/validate/pr-description@v1.19.1-beta.2
uses: LerianStudio/github-actions-shared-workflows/src/validate/pr-description@v1.20.0-beta.1
with:
min-length: ${{ inputs.min_description_length }}

Expand Down Expand Up @@ -161,15 +161,15 @@ jobs:
- name: Check PR metadata
id: metadata
continue-on-error: true
uses: LerianStudio/github-actions-shared-workflows/src/validate/pr-metadata@v1.19.1-beta.2
uses: LerianStudio/github-actions-shared-workflows/src/validate/pr-metadata@v1.20.0-beta.1
with:
github-token: ${{ secrets.MANAGE_TOKEN || github.token }}
dry-run: ${{ inputs.dry_run && 'true' || 'false' }}

- name: Check PR size
id: size
continue-on-error: true
uses: LerianStudio/github-actions-shared-workflows/src/validate/pr-size@v1.19.1-beta.2
uses: LerianStudio/github-actions-shared-workflows/src/validate/pr-size@v1.20.0-beta.1
with:
github-token: ${{ secrets.MANAGE_TOKEN || github.token }}
base-ref: ${{ github.base_ref }}
Expand All @@ -179,7 +179,7 @@ jobs:
id: labels
if: inputs.enable_auto_labeler && !inputs.dry_run
continue-on-error: true
uses: LerianStudio/github-actions-shared-workflows/src/validate/pr-labels@v1.19.1-beta.2
uses: LerianStudio/github-actions-shared-workflows/src/validate/pr-labels@v1.20.0-beta.1
with:
github-token: ${{ secrets.MANAGE_TOKEN || github.token }}
config-path: ${{ inputs.labeler_config_path }}
Expand All @@ -202,7 +202,7 @@ jobs:

steps:
- name: PR Checks Summary
uses: LerianStudio/github-actions-shared-workflows/src/validate/pr-checks-summary@v1.19.1-beta.2
uses: LerianStudio/github-actions-shared-workflows/src/validate/pr-checks-summary@v1.20.0-beta.1
with:
source-branch-result: ${{ needs.blocking-checks.outputs.source-branch-result || 'skipped' }}
title-result: ${{ needs.blocking-checks.outputs.title-result || 'skipped' }}
Expand All @@ -217,7 +217,7 @@ jobs:
name: Notify
needs: [blocking-checks, advisory-checks, pr-checks-summary]
if: always() && github.event.pull_request.draft != true && !inputs.dry_run
uses: LerianStudio/github-actions-shared-workflows/.github/workflows/slack-notify.yml@v1.19.1-beta.2
uses: LerianStudio/github-actions-shared-workflows/.github/workflows/slack-notify.yml@v1.20.0-beta.1
with:
status: ${{ (needs.blocking-checks.outputs.source-branch-result == 'failure' || needs.blocking-checks.outputs.title-result == 'failure' || needs.blocking-checks.outputs.description-result == 'failure') && 'failure' || 'success' }}
workflow_name: "PR Validation"
Expand Down
22 changes: 11 additions & 11 deletions src/lint/pinned-actions/action.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
name: Pinned Actions Check
description: Ensure external action references use final release versions (vX or vX.Y.Z); internal actions may use pre-releases with a warning.
description: Ensure external actions are pinned by commit SHA; internal actions (LerianStudio) accept any semver tag.

inputs:
files:
description: Comma-separated list of workflow/composite files to check (empty = skip)
required: false
default: ""
warn-patterns:
description: Pipe-separated org/owner prefixes to warn instead of fail (e.g. internal orgs not yet on a release tag)
description: Pipe-separated org/owner prefixes to treat as internal (warn instead of fail)
required: false
default: "LerianStudio/"

Expand Down Expand Up @@ -57,28 +57,28 @@ runs:
done

if [ "$is_internal" = true ]; then
# Internal: final releases (vX, vX.Y.Z) pass silently; pre-releases (beta, rc) warn
if printf '%s\n' "$ref" | grep -Eq '^v[0-9]+$|^v[0-9]+\.[0-9]+\.[0-9]+$'; then
# Internal: any semver ref passes — vX, vX.Y.Z, vX.Y.Z-beta.N, vX.Y.Z-rc.N, branches like develop/main
if printf '%s\n' "$ref" | grep -Eq '^v[0-9]+(\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?)?$|^(develop|main)$'; then
continue
fi
echo "::warning file=${file},line=${line_num}::Internal action not pinned to a final release version: $normalized"
echo "::warning file=${file},line=${line_num}::Internal action not pinned to a version: $normalized"
warnings=$((warnings + 1))
else
# External: only final releases allowed — vX or vX.Y.Z (no beta, no rc)
if printf '%s\n' "$ref" | grep -Eq '^v[0-9]+$|^v[0-9]+\.[0-9]+\.[0-9]+$'; then
# External: must be a commit SHA (40 or 64 hex chars)
if printf '%s\n' "$ref" | grep -Eiq '^[0-9a-f]{40,64}$'; then
continue
fi
echo "::error file=${file},line=${line_num}::Unpinned action found: $normalized"
echo "::error file=${file},line=${line_num}::External action not pinned by SHA: $normalized (use full commit SHA with a # vX.Y.Z comment)"
violations=$((violations + 1))
fi
done < <(grep -nE '^[[:space:]]*(-[[:space:]]*)?uses:[[:space:]].*@' "$file" 2>/dev/null || true)
done

if [ "$warnings" -gt 0 ]; then
echo "::warning::Found $warnings internal action(s) not pinned to a release version. Consider pinning to vX.Y.Z."
echo "::warning::Found $warnings internal action(s) not pinned to a version. Consider pinning to vX.Y.Z."
fi
if [ "$violations" -gt 0 ]; then
echo "::error::Found $violations unpinned external action(s). Pin to a final release version (vX or vX.Y.Z)."
echo "::error::Found $violations external action(s) not pinned by commit SHA. Pin using the full SHA with a version comment (e.g., @abc123 # v6)."
exit 1
fi
echo "All external actions are properly pinned."
echo "All actions are properly pinned."
88 changes: 63 additions & 25 deletions src/notify/pr-lint-reporter/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ runs:
using: composite
steps:
- name: Post lint report to PR
uses: actions/github-script@v8
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
github-token: ${{ inputs.github-token }}
script: |
Expand Down Expand Up @@ -155,44 +155,49 @@ runs:
body += `| ${c.label} | ${filesSummary(c)} | ${icon(c.result)} ${c.result} |\n`;
}

// ── Failures collapse with annotations ──
// ── Fetch annotations for failures and warnings ──
const failed = checks.filter(c => c.result === 'failure');
if (failed.length > 0) {
const { data: { jobs } } = await github.rest.actions.listJobsForWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.runId,
});

const jobAnnotations = {};
for (const job of jobs) {
if (!failed.find(c => c.jobName === job.name)) continue;
try {
const annotations = await github.paginate(github.rest.checks.listAnnotations, {
owner: context.repo.owner,
repo: context.repo.repo,
check_run_id: job.id,
per_page: 100,
});
jobAnnotations[job.name] = annotations.filter(a => a.annotation_level === 'failure');
} catch (e) {
core.warning(`Could not fetch annotations for ${job.name}: ${e.message}`);
}
const needsAnnotations = checks.filter(c => c.result === 'failure' || c.result === 'success');

const { data: { jobs } } = await github.rest.actions.listJobsForWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.runId,
});

const jobFailureAnnotations = {};
const jobWarningAnnotations = {};
for (const job of jobs) {
const check = needsAnnotations.find(c => c.jobName === job.name);
if (!check) continue;
try {
const annotations = await github.paginate(github.rest.checks.listAnnotations, {
owner: context.repo.owner,
repo: context.repo.repo,
check_run_id: job.id,
per_page: 100,
});
jobFailureAnnotations[job.name] = annotations.filter(a => a.annotation_level === 'failure');
jobWarningAnnotations[job.name] = annotations.filter(a => a.annotation_level === 'warning');
} catch (e) {
core.warning(`Could not fetch annotations for ${job.name}: ${e.message}`);
}
}

// ── Failures collapse ──
if (failed.length > 0) {
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
body += `\n<details>\n<summary>❌ Failures (${failed.length})</summary>\n\n`;

for (const c of failed) {
const annotations = jobAnnotations[c.jobName] || [];
const annotations = jobFailureAnnotations[c.jobName] || [];
body += `### ${c.label}\n\n`;

if (annotations.length === 0) {
body += `_No annotation details available — [view full logs](${runUrl})._\n\n`;
continue;
}

// Group by file path
const byFile = {};
for (const a of annotations) {
const key = a.path || '__general__';
Expand All @@ -216,6 +221,39 @@ runs:
body += '</details>\n\n';
}

// ── Warnings collapse ──
const checksWithWarnings = checks.filter(c => (jobWarningAnnotations[c.jobName] || []).length > 0);
if (checksWithWarnings.length > 0) {
const totalWarnings = checksWithWarnings.reduce((sum, c) => sum + (jobWarningAnnotations[c.jobName] || []).length, 0);
body += `<details>\n<summary>⚠️ Warnings (${totalWarnings})</summary>\n\n`;

for (const c of checksWithWarnings) {
const annotations = jobWarningAnnotations[c.jobName] || [];
body += `### ${c.label}\n\n`;

const byFile = {};
for (const a of annotations) {
const key = a.path || '__general__';
(byFile[key] = byFile[key] || []).push(a);
}

for (const [file, warns] of Object.entries(byFile)) {
if (file === '__general__') {
for (const w of warns) body += `- ${w.message}\n`;
} else {
body += `**\`${file}\`**\n`;
for (const w of warns) {
const loc = w.start_line ? ` (line ${w.start_line})` : '';
body += `- \`${file}${loc}\` — ${w.message}\n`;
}
}
body += '\n';
}
}

body += '</details>\n\n';
}

// ── Footer ──
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
body += `---\n<sub>🔍 [View full scan logs](${runUrl})</sub>\n`;
Expand Down
4 changes: 4 additions & 0 deletions src/security/codeql-config/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ runs:
echo "$PATHS" | while IFS= read -r p; do
echo " - '$p'"
done
echo ""
echo "query-filters:"
echo " - exclude:"
echo " id: actions/unpinned-tag"
} > "$CONFIG_FILE"

echo "skip=false" >> "$GITHUB_OUTPUT"
Expand Down
8 changes: 6 additions & 2 deletions src/security/codeql-reporter/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ runs:
steps:
- name: Post CodeQL report to PR
id: report
uses: actions/github-script@v8
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
SARIF_PATH: ${{ inputs.sarif-path }}
LANGUAGES: ${{ inputs.languages }}
Expand Down Expand Up @@ -125,8 +125,12 @@ runs:
return findings;
}

// ── Filter suppressed rules ──
// unpinned-tag is handled by our own pinned-actions lint check with org-aware logic
const SUPPRESSED_RULES = ['actions/unpinned-tag'];

// ── Build Report ──
const findings = readSarifFiles();
const findings = readSarifFiles().filter(f => !SUPPRESSED_RULES.includes(f.rule));
findingsCount = findings.length;

body += `## \u{1F6E1}\uFE0F CodeQL Analysis Results\n\n`;
Expand Down
Loading