chore: merge devsecops into main (#546)

* fix(workflows): standardize runner configuration for security jobs

* ci(actionlint): add Blacksmith runner label to config

Add blacksmith-2vcpu-ubuntu-2404 to actionlint self-hosted-runner labels config
to suppress "unknown label" warnings during workflow linting.

This label is used across all workflows after the Blacksmith migration.

* fix(actionlint): adjust indentation for self-hosted runner labels

* feat(security): enhance security workflow with CodeQL analysis steps

* fix(security): update CodeQL action to version 4 for improved analysis

* fix(security): remove duplicate permissions in security workflow

* fix(security): revert CodeQL action to v3 for stability

The v4 version was causing workflow file validation failures.
Reverting to proven v3 version that is working on main branch.

* fix(security): remove duplicate permissions causing workflow validation failure

The permissions block had duplicate security-events and actions keys,
which caused YAML validation errors and prevented workflow execution.

Fixes: workflow file validation failures on main branch

* fix(security): remove pull_request trigger to reduce costs

* fix(security): restore PR trigger but skip codeql on PRs

* fix(security): resolve YAML syntax error in security workflow

* refactor(security): split CodeQL into dedicated scheduled workflow

* fix(security): update workflow name to Rust Package Security Audit

* fix(codeql): remove push trigger, keep schedule and on-demand only

* feat(codeql): add CodeQL configuration file to ignore specific paths

* Potential fix for code scanning alert no. 39: Hard-coded cryptographic value

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* fix(ci): resolve auto-response workflow merge markers

* fix(build): restore ChannelMessage reply_target usage

* ci(workflows): run workflow sanity on workflow pushes for all branches

* ci(workflows): rename auto-response workflow to PR Auto Responder

* ci(workflows): require owner approval for workflow file changes

---------

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
This commit is contained in:
Will Sarg 2026-02-17 10:10:14 -05:00 committed by GitHub
parent bb641d28c2
commit 500e6bd0ec
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 120 additions and 8 deletions

View file

@ -1,4 +1,4 @@
name: Auto Response
name: PR Auto Responder
on:
issues:

View file

@ -24,6 +24,7 @@ jobs:
docs_only: ${{ steps.scope.outputs.docs_only }}
docs_changed: ${{ steps.scope.outputs.docs_changed }}
rust_changed: ${{ steps.scope.outputs.rust_changed }}
workflow_changed: ${{ steps.scope.outputs.workflow_changed }}
docs_files: ${{ steps.scope.outputs.docs_files }}
base_sha: ${{ steps.scope.outputs.base_sha }}
steps:
@ -55,6 +56,7 @@ jobs:
echo "docs_only=false"
echo "docs_changed=false"
echo "rust_changed=true"
echo "workflow_changed=false"
echo "base_sha="
} >> "$GITHUB_OUTPUT"
write_empty_docs_files
@ -67,6 +69,7 @@ jobs:
echo "docs_only=false"
echo "docs_changed=false"
echo "rust_changed=false"
echo "workflow_changed=false"
echo "base_sha=$BASE"
} >> "$GITHUB_OUTPUT"
write_empty_docs_files
@ -76,10 +79,15 @@ jobs:
docs_only=true
docs_changed=false
rust_changed=false
workflow_changed=false
docs_files=()
while IFS= read -r file; do
[ -z "$file" ] && continue
if [[ "$file" == .github/workflows/* ]]; then
workflow_changed=true
fi
if [[ "$file" == docs/* ]] \
|| [[ "$file" == *.md ]] \
|| [[ "$file" == *.mdx ]] \
@ -112,6 +120,7 @@ jobs:
echo "docs_only=$docs_only"
echo "docs_changed=$docs_changed"
echo "rust_changed=$rust_changed"
echo "workflow_changed=$workflow_changed"
echo "base_sha=$BASE"
echo "docs_files<<EOF"
printf '%s\n' "${docs_files[@]}"
@ -260,10 +269,94 @@ jobs:
if: steps.collect_links.outputs.count == '0'
run: echo "No added links in changed docs lines. Link check skipped."
workflow-owner-approval:
name: Workflow Owner Approval
needs: [changes]
if: github.event_name == 'pull_request' && needs.changes.outputs.workflow_changed == 'true'
runs-on: blacksmith-2vcpu-ubuntu-2404
permissions:
contents: read
pull-requests: read
steps:
- name: Require owner approval for workflow file changes
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
WORKFLOW_OWNER_LOGINS: ${{ vars.WORKFLOW_OWNER_LOGINS || 'theonlyhennygod,willsarg' }}
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const prNumber = context.payload.pull_request?.number;
if (!prNumber) {
core.setFailed("Missing pull_request context.");
return;
}
const ownerAllowlist = (process.env.WORKFLOW_OWNER_LOGINS || "")
.split(",")
.map((login) => login.trim().toLowerCase())
.filter(Boolean);
if (ownerAllowlist.length === 0) {
core.setFailed("WORKFLOW_OWNER_LOGINS is empty. Set a repository variable or use a fallback value.");
return;
}
const files = await github.paginate(github.rest.pulls.listFiles, {
owner,
repo,
pull_number: prNumber,
per_page: 100,
});
const workflowFiles = files
.map((file) => file.filename)
.filter((name) => name.startsWith(".github/workflows/"));
if (workflowFiles.length === 0) {
core.info("No workflow files changed in this PR.");
return;
}
core.info(`Workflow files changed:\n- ${workflowFiles.join("\n- ")}`);
const reviews = await github.paginate(github.rest.pulls.listReviews, {
owner,
repo,
pull_number: prNumber,
per_page: 100,
});
const latestReviewByUser = new Map();
for (const review of reviews) {
const login = review.user?.login;
if (!login) continue;
latestReviewByUser.set(login.toLowerCase(), review.state);
}
const approvedUsers = [...latestReviewByUser.entries()]
.filter(([, state]) => state === "APPROVED")
.map(([login]) => login);
if (approvedUsers.length === 0) {
core.setFailed("Workflow files changed but no approving review is present.");
return;
}
const ownerApprover = approvedUsers.find((login) => ownerAllowlist.includes(login));
if (!ownerApprover) {
core.setFailed(
`Workflow files changed. Approvals found (${approvedUsers.join(", ")}), but none match WORKFLOW_OWNER_LOGINS.`,
);
return;
}
core.info(`Workflow owner approval present: @${ownerApprover}`);
ci-required:
name: CI Required Gate
if: always()
needs: [changes, lint, lint-strict-delta, test, build, docs-only, non-rust, docs-quality]
needs: [changes, lint, lint-strict-delta, test, build, docs-only, non-rust, docs-quality, workflow-owner-approval]
runs-on: blacksmith-2vcpu-ubuntu-2404
steps:
- name: Enforce required status
@ -273,10 +366,17 @@ jobs:
docs_changed="${{ needs.changes.outputs.docs_changed }}"
rust_changed="${{ needs.changes.outputs.rust_changed }}"
workflow_changed="${{ needs.changes.outputs.workflow_changed }}"
docs_result="${{ needs.docs-quality.result }}"
workflow_owner_result="${{ needs.workflow-owner-approval.result }}"
if [ "${{ needs.changes.outputs.docs_only }}" = "true" ]; then
echo "docs=${docs_result}"
echo "workflow_owner_approval=${workflow_owner_result}"
if [ "$workflow_changed" = "true" ] && [ "$workflow_owner_result" != "success" ]; then
echo "Workflow files changed but workflow owner approval gate did not pass."
exit 1
fi
if [ "$docs_changed" = "true" ] && [ "$docs_result" != "success" ]; then
echo "Docs-only change touched markdown docs, but docs-quality did not pass."
exit 1
@ -288,6 +388,11 @@ jobs:
if [ "$rust_changed" != "true" ]; then
echo "rust_changed=false (non-rust fast path)"
echo "docs=${docs_result}"
echo "workflow_owner_approval=${workflow_owner_result}"
if [ "$workflow_changed" = "true" ] && [ "$workflow_owner_result" != "success" ]; then
echo "Workflow files changed but workflow owner approval gate did not pass."
exit 1
fi
if [ "$docs_changed" = "true" ] && [ "$docs_result" != "success" ]; then
echo "Docs changed but docs-quality did not pass."
exit 1
@ -306,12 +411,18 @@ jobs:
echo "test=${test_result}"
echo "build=${build_result}"
echo "docs=${docs_result}"
echo "workflow_owner_approval=${workflow_owner_result}"
if [ "$lint_result" != "success" ] || [ "$lint_strict_delta_result" != "success" ] || [ "$test_result" != "success" ] || [ "$build_result" != "success" ]; then
echo "Required CI jobs did not pass."
exit 1
fi
if [ "$workflow_changed" = "true" ] && [ "$workflow_owner_result" != "success" ]; then
echo "Workflow files changed but workflow owner approval gate did not pass."
exit 1
fi
if [ "$docs_changed" = "true" ] && [ "$docs_result" != "success" ]; then
echo "Docs changed but docs-quality did not pass."
exit 1

View file

@ -7,7 +7,6 @@ on:
- ".github/*.yml"
- ".github/*.yaml"
push:
branches: [main]
paths:
- ".github/workflows/**"
- ".github/*.yml"