Skip to content
Closed
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
8 changes: 8 additions & 0 deletions .github/nightly-allowed-files.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Nightly scan output files — shared allowlist used by:
# - nightly-scan.yml (inline path validation)
# - validate-nightly-pr.yml (PR file check)
NIGHTLY_ALLOWED_FILES=(
"azure_linux_images.db"
"docs/daily_recommendations.md"
"docs/daily_recommendations.json"
)
141 changes: 124 additions & 17 deletions .github/workflows/nightly-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,37 @@ jobs:
timeout-minutes: 360
permissions:
contents: write
pull-requests: write
steps:
- name: 🔑 Generate App Token
id: app-token
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
# TODO: Replace GITHUB_TOKEN with a GitHub App token once available.
# See https://github.com/microsoft/sbi/issues/6

- name: ⤵️ Checkout (with LFS)
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
token: ${{ steps.app-token.outputs.token }}
lfs: true
fetch-depth: 0

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

workflow_dispatch runs can be triggered from non-main refs, but this workflow later hard-resets the results branch from origin/main. If the dispatch is run on another branch, origin/main may not be fetched by actions/checkout (default fetches only the triggering ref), causing git checkout -B "$BRANCH" origin/main to fail or use a stale ref. Consider checking out main explicitly (or fetching origin main before the reset) so manual runs behave deterministically.

Suggested change
- name: 🔄 Fetch main ref explicitly
run: git fetch --no-tags origin main:refs/remotes/origin/main

Copilot uses AI. Check for mistakes.
- name: ✅ Require default branch for manual runs
if: github.event_name == 'workflow_dispatch'
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
run: |
if [ "${GITHUB_REF}" != "refs/heads/${DEFAULT_BRANCH}" ]; then
echo "::error::Run workflow_dispatch from ${DEFAULT_BRANCH} only."
exit 1
fi

- name: 🔄 Fetch default branch explicitly
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
run: git fetch --no-tags origin "${DEFAULT_BRANCH}:refs/remotes/origin/${DEFAULT_BRANCH}"

- name: 🌿 Prepare nightly results branch
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
run: git checkout -B nightly-scan-results "origin/${DEFAULT_BRANCH}"

- name: 🚧 Setup Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
Expand Down Expand Up @@ -85,19 +101,110 @@ jobs:
ls -lh azure_linux_images.db || true
sqlite3 azure_linux_images.db 'SELECT COUNT(*) as images FROM images;' || true

- name: 📝 Commit & push updates
- name: 📝 Create PR with scan results
if: success()
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
GH_TOKEN: ${{ github.token }}
run: |
git config user.name 'sbi-nightly-bot[bot]'
git config user.email '${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com'
git add -f azure_linux_images.db docs/daily_recommendations.md docs/daily_recommendations.json || true
if [ -n "$(git diff --cached --name-only)" ]; then
CHANGED_FILES=$(git diff --cached --name-only | tr '\n' ' ')
echo "Changed files: $CHANGED_FILES"
git commit -m "chore: update nightly scan results [skip ci]"
git push
else
git config user.name 'github-actions[bot]'
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'

BRANCH="nightly-scan-results"
PR_TITLE="chore: update nightly scan results"

# Source the shared allowlist from the trusted triggering commit
# so earlier tooling cannot taint the file list.
git restore --source='${{ github.sha }}' -- .github/nightly-allowed-files.sh
source .github/nightly-allowed-files.sh

is_allowed_file() {
local candidate="$1"
local allowed_file

for allowed_file in "${NIGHTLY_ALLOWED_FILES[@]}"; do
if [ "$candidate" = "$allowed_file" ]; then
return 0
fi
done

return 1
}

# Validate expected scan artifacts exist
MISSING=()
for f in "${NIGHTLY_ALLOWED_FILES[@]}"; do
[ -f "$f" ] || MISSING+=("$f")
done
Comment on lines +134 to +138

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The expected file list is hard-coded in multiple places (this artifact existence loop, the later git add -f ..., and the sourced NIGHTLY_ALLOWED_FILES allowlist). This duplication can drift over time and the allowlist validation will always pass as long as git add stays hard-coded. Consider driving the existence check and git add from NIGHTLY_ALLOWED_FILES so there’s a single source of truth (and optionally validating the full working tree diff against it).

Copilot uses AI. Check for mistakes.
if [ ${#MISSING[@]} -gt 0 ]; then
echo "::error::Missing expected scan artifacts:"
printf ' - %s\n' "${MISSING[@]}"
exit 1
fi

# Validate the full working tree so unexpected files cannot be
# hidden by selectively staging only the allowlisted outputs.
UNEXPECTED=()
while IFS= read -r -d '' file; do
if ! is_allowed_file "$file"; then
UNEXPECTED+=("$file")
fi
done < <(git diff --name-only -z)
while IFS= read -r -d '' file; do
if ! is_allowed_file "$file"; then
UNEXPECTED+=("$file")
fi
done < <(git ls-files --others --exclude-standard -z)
if [ ${#UNEXPECTED[@]} -gt 0 ]; then
echo "::error::Nightly scan produced unexpected file changes:"
printf ' - %s\n' "${UNEXPECTED[@]}"
exit 1
fi

git add -f "${NIGHTLY_ALLOWED_FILES[@]}"
if [ -z "$(git diff --cached --name-only)" ]; then
echo 'No changes to commit.'
exit 0
fi

# Inline path validation: ensure only expected files are staged
while IFS= read -r -d '' file; do
if ! is_allowed_file "$file"; then
echo "::error::Unexpected file staged: $file"
exit 1
fi
done < <(git diff --cached --name-only -z)

CHANGED_FILES=$(git diff --cached --name-only | tr '\n' ' ')
echo "Changed files: $CHANGED_FILES"
git commit -m "chore: update nightly scan results"
git push --force-with-lease origin "$BRANCH"

printf '%s\n' \
"Automated nightly scan results update." \
"" \
"This PR was created by the nightly scan workflow and contains updated:" \
'- `azure_linux_images.db` — scan database' \
'- `docs/daily_recommendations.md` — markdown report' \
'- `docs/daily_recommendations.json` — JSON report' \
"" \
"> **Note:** PRs created by GITHUB_TOKEN do not trigger other workflows (GitHub limitation)." \
"> If required status checks are configured, you may need to manually re-push or close/reopen" \
"> the PR to trigger them. See https://github.com/microsoft/sbi/issues/6 for details." \
> /tmp/nightly-pr-body.md

# Create PR if one doesn't already exist (scoped to this repo, not forks)
EXISTING_PR=$(gh pr list --head "${{ github.repository_owner }}:$BRANCH" --base "$DEFAULT_BRANCH" --state open --json number --jq '.[0].number // empty')
if [ -z "$EXISTING_PR" ]; then
gh pr create \
--base "$DEFAULT_BRANCH" \
--head "$BRANCH" \
--title "$PR_TITLE" \
--body-file /tmp/nightly-pr-body.md
else
gh pr edit "$EXISTING_PR" \
--title "$PR_TITLE" \
--body-file /tmp/nightly-pr-body.md
Comment on lines +196 to +207

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This workflow hardcodes main as the PR base branch (gh pr list ... --base main / gh pr create --base main) while earlier steps derive DEFAULT_BRANCH from github.event.repository.default_branch. If the default branch ever changes, the workflow will start targeting the wrong base. Consider using the same default-branch value for PR base selection (and for the validate-nightly-pr.yml trigger/condition strategy) to keep behavior consistent.

Copilot uses AI. Check for mistakes.
Comment on lines +196 to +207

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR body text states that PRs created by GITHUB_TOKEN don’t trigger other workflows, but the workflow does not update an existing PR’s title/body when it reuses one. If the limitation/workaround text changes in the future, existing open nightly PRs may retain stale guidance. Consider updating the PR body when EXISTING_PR is found (e.g., gh pr edit) so the message stays accurate over time.

Suggested change
# Create PR if one doesn't already exist (scoped to this repo, not forks)
EXISTING_PR=$(gh pr list --head "${{ github.repository_owner }}:$BRANCH" --base main --state open --json number --jq '.[0].number // empty')
if [ -z "$EXISTING_PR" ]; then
printf '%s\n' \
"Automated nightly scan results update." \
"" \
"This PR was created by the nightly scan workflow and contains updated:" \
'- `azure_linux_images.db` — scan database' \
'- `docs/daily_recommendations.md` — markdown report' \
'- `docs/daily_recommendations.json` — JSON report' \
"" \
"> **Note:** PRs created by GITHUB_TOKEN do not trigger other workflows (GitHub limitation)." \
"> If required status checks are configured, you may need to manually re-push or close/reopen" \
"> the PR to trigger them. See https://github.com/microsoft/sbi/issues/6 for details." \
> /tmp/nightly-pr-body.md
gh pr create \
--base main \
--head "$BRANCH" \
--title "chore: update nightly scan results" \
--body-file /tmp/nightly-pr-body.md
PR_TITLE="chore: update nightly scan results"
printf '%s\n' \
"Automated nightly scan results update." \
"" \
"This PR was created by the nightly scan workflow and contains updated:" \
'- `azure_linux_images.db` — scan database' \
'- `docs/daily_recommendations.md` — markdown report' \
'- `docs/daily_recommendations.json` — JSON report' \
"" \
"> **Note:** PRs created by GITHUB_TOKEN do not trigger other workflows (GitHub limitation)." \
"> If required status checks are configured, you may need to manually re-push or close/reopen" \
"> the PR to trigger them. See https://github.com/microsoft/sbi/issues/6 for details." \
> /tmp/nightly-pr-body.md
# Create PR if one doesn't already exist (scoped to this repo, not forks)
EXISTING_PR=$(gh pr list --head "${{ github.repository_owner }}:$BRANCH" --base main --state open --json number --jq '.[0].number // empty')
if [ -z "$EXISTING_PR" ]; then
gh pr create \
--base main \
--head "$BRANCH" \
--title "$PR_TITLE" \
--body-file /tmp/nightly-pr-body.md
else
gh pr edit "$EXISTING_PR" \
--title "$PR_TITLE" \
--body-file /tmp/nightly-pr-body.md

Copilot uses AI. Check for mistakes.
fi
Comment on lines +196 to 208

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description says this workflow enables auto-merge (e.g., via gh pr merge --auto --squash), but this workflow currently only creates the PR and never configures auto-merge (nor does it do anything when EXISTING_PR is found). Either add the auto-merge step (and handle the existing-PR case) or update the PR description to match the implemented behavior.

Copilot uses AI. Check for mistakes.

- name: 📤 Upload database artifact
Expand Down
71 changes: 71 additions & 0 deletions .github/workflows/validate-nightly-pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# yaml-language-server: $schema=https://www.schemastore.org/github-workflow.json
---
name: Validate Nightly PR

on:
pull_request:

permissions: {}

jobs:
validate-nightly-pr:
name: 🔒 Validate Nightly PR Files
if: >-
github.event.pull_request.base.ref == github.event.repository.default_branch &&
github.head_ref == 'nightly-scan-results' &&
github.event.pull_request.head.repo.full_name == github.repository &&
github.event.pull_request.user.login == 'github-actions[bot]'
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions:
contents: read
pull-requests: read
steps:
- name: ⤵️ Checkout allowlist from base branch
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.event.pull_request.base.sha }}
sparse-checkout: .github/nightly-allowed-files.sh
sparse-checkout-cone-mode: false

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This checkout is only used to read the allowlist file, but it leaves the default persisted GitHub token credentials in the local git config. Other PR workflows in this repo set persist-credentials: false for read-only checkouts; doing the same here would better align with least-privilege and avoid accidental authenticated git operations.

Suggested change
sparse-checkout-cone-mode: false
sparse-checkout-cone-mode: false
persist-credentials: false

Copilot uses AI. Check for mistakes.
persist-credentials: false

- name: 📋 Check modified files
env:
GH_TOKEN: ${{ github.token }}
run: |
source .github/nightly-allowed-files.sh

# Get the list of changed files in this PR
CHANGED_FILES=$(gh api \
"repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/files" \
--paginate --jq '.[].filename')

UNEXPECTED=()
while IFS= read -r file; do
[ -z "$file" ] && continue
allowed=false
for af in "${NIGHTLY_ALLOWED_FILES[@]}"; do
if [ "$file" = "$af" ]; then
allowed=true
break
fi
done
if [ "$allowed" = "false" ]; then
UNEXPECTED+=("$file")
fi
done <<< "$CHANGED_FILES"

if [ ${#UNEXPECTED[@]} -gt 0 ]; then
echo "::error::Nightly scan PR contains unexpected files:"
printf ' - %s\n' "${UNEXPECTED[@]}"
echo ""
echo "Only the following files are allowed:"
printf ' - %s\n' "${NIGHTLY_ALLOWED_FILES[@]}"
exit 1
fi

echo "✅ All changed files are in the allowed set:"
while IFS= read -r file; do
[ -z "$file" ] && continue
printf ' - %s\n' "$file"
done <<< "$CHANGED_FILES"
Loading