Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
30 changes: 30 additions & 0 deletions .github/actions/security-scan/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Security Scan
description: Run SAST and SCA security scans

inputs:
scan-path:
description: Path to scan
required: false
default: "."

runs:
using: composite
steps:
- name: Run Semgrep SAST
uses: semgrep/semgrep-action@713efdd345f3035192eaa63f56867b88e63e4e5d # v1.0.0
with:
config: auto

- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@18f2510ee396bbf400402947e7f3b01483832965 # v0.31.0
with:
scan-type: fs
scan-ref: ${{ inputs.scan-path }}
format: sarif
output: trivy-results.sarif

- name: Upload Trivy results to GitHub Security
uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
if: always()
with:
sarif_file: trivy-results.sarif
46 changes: 46 additions & 0 deletions .github/actions/sync-settings/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Sync Repository Settings
description: Compare and apply GitHub settings across all repos against a baseline

inputs:
mode:
description: "Run mode: --dry-run or --apply"
required: true
default: "--dry-run"
github_token:
description: PAT with repo and admin scopes
required: true

outputs:
report_file:
description: Path to the generated report
value: ${{ steps.sync.outputs.report_file }}
total_repos:
description: Number of repos scanned
value: ${{ steps.parse.outputs.total_repos }}
compliant:
description: Number of compliant repos
value: ${{ steps.parse.outputs.compliant }}
drift:
description: Number of repos with drift
value: ${{ steps.parse.outputs.drift }}
has_drift:
description: Whether any drift was detected
value: ${{ steps.parse.outputs.has_drift }}

runs:
using: composite
steps:
- name: Run settings sync
id: sync
shell: bash
env:
GH_TOKEN: ${{ inputs.github_token }}
REPORT_FILE: reports/sync-report.md
run: |
./scripts/sync-repo-settings.sh "${{ inputs.mode }}"
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

${{ inputs.mode }} is directly interpolated into the run: shell command, which is a script injection risk. Although the current caller constrains this input to a choice type with fixed values, the composite action itself doesn't validate the input. If reused by another workflow that passes untrusted input, this could allow arbitrary command execution.

Pass the input via an environment variable instead (e.g., env: MODE: ${{ inputs.mode }}), and reference it as "$MODE" in the script. This follows the recommended GitHub Actions security practice of never using ${{ }} expressions directly in run: blocks.

Copilot uses AI. Check for mistakes.
echo "report_file=reports/sync-report.md" >> "$GITHUB_OUTPUT"

- name: Parse report
id: parse
shell: bash
run: ./scripts/generate-report.sh reports/sync-report.md
36 changes: 36 additions & 0 deletions .github/actions/update-pre-commit-composite/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Update Pre-commit Hooks Composite Action
description: Updates pre-commit hook versions and creates a PR

inputs:
github_token:
description: GitHub token for creating PRs
required: true

runs:
using: composite
steps:
- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.x"

- name: Install pre-commit
shell: bash
run: pip install pre-commit

- name: Update hooks
shell: bash
run: pre-commit autoupdate

- name: Create Pull Request
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
with:
token: ${{ inputs.github_token }}
commit-message: "chore: update pre-commit hook versions"
title: "chore: update pre-commit hook versions"
body: |
Automated update of pre-commit hook versions.

Review the changes to `.pre-commit-config.yaml` and merge if CI passes.
branch: chore/update-pre-commit-hooks
delete-branch: true
16 changes: 16 additions & 0 deletions .github/workflows/quality-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,22 @@ jobs:
fi
done

- name: Validate baseline schema
run: |
ERRORS=0
for section in repo_settings security branch_protection labels required_files; do
if ! jq -e ".$section" config/baseline.json > /dev/null 2>&1; then
echo "ERROR: Missing section '$section' in baseline.json"
ERRORS=$((ERRORS + 1))
else
echo "OK: section '$section' present"
fi
done
if [ "$ERRORS" -gt 0 ]; then
echo "ERROR: baseline.json schema validation failed"
exit 1
fi
Comment on lines +90 to +110
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

This only checks section presence, not the baseline schema.

Lines 85-91 will still pass if security.vulnerability_alerts is missing or if a label entry omits color/description. That leaves the new baseline contract effectively unvalidated.

Suggested jq-based validation
       - name: Validate baseline schema
         run: |
-          ERRORS=0
-          for section in repo_settings security branch_protection labels required_files; do
-            if ! jq -e ".$section" config/baseline.json > /dev/null 2>&1; then
-              echo "ERROR: Missing section '$section' in baseline.json"
-              ERRORS=$((ERRORS + 1))
-            else
-              echo "OK: section '$section' present"
-            fi
-          done
-          if [ "$ERRORS" -gt 0 ]; then
-            echo "ERROR: baseline.json schema validation failed"
-            exit 1
-          fi
+          jq -e '
+            (.repo_settings | type == "object") and
+            (.security | type == "object") and
+            (.security.vulnerability_alerts | type == "boolean") and
+            (.branch_protection | type == "object") and
+            (.required_files | type == "array") and
+            (.labels | type == "array") and
+            ([.labels[] | (.name | type == "string")
+                         and (.color | type == "string" and test("^[0-9A-Fa-f]{6}$"))
+                         and (.description | type == "string")] | all)
+          ' config/baseline.json >/dev/null || {
+            echo "ERROR: baseline.json schema validation failed"
+            exit 1
+          }

As per coding guidelines, "Confirm the JSON structure aligns with the baseline schema validated by quality checks (Validate baseline schema step) and used by per-repo drift/metadata checks."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Validate baseline schema
run: |
ERRORS=0
for section in repo_settings security branch_protection labels required_files; do
if ! jq -e ".$section" config/baseline.json > /dev/null 2>&1; then
echo "ERROR: Missing section '$section' in baseline.json"
ERRORS=$((ERRORS + 1))
else
echo "OK: section '$section' present"
fi
done
if [ "$ERRORS" -gt 0 ]; then
echo "ERROR: baseline.json schema validation failed"
exit 1
fi
- name: Validate baseline schema
run: |
jq -e '
(.repo_settings | type == "object") and
(.security | type == "object") and
(.security.vulnerability_alerts | type == "boolean") and
(.branch_protection | type == "object") and
(.required_files | type == "array") and
(.labels | type == "array") and
([.labels[] | (.name | type == "string")
and (.color | type == "string" and test("^[0-9A-Fa-f]{6}$"))
and (.description | type == "string")] | all)
' config/baseline.json >/dev/null || {
echo "ERROR: baseline.json schema validation failed"
exit 1
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/quality-checks.yml around lines 82 - 96, The current
"Validate baseline schema" shell block only checks top-level section presence;
update it to validate required nested keys and label fields using jq so missing
subkeys fail CI. Replace or extend the loop that iterates sections
(repo_settings, security, branch_protection, labels, required_files) with
explicit jq checks such as verifying .security.vulnerability_alerts exists and
is an object, and that .labels is an array with every element containing .color
and .description (e.g. fail if any .labels[] | select(.color==null or
.description==null) is found); ensure ERRORS increments and the script exits
non‑zero with a clear message when any nested validation fails.


actions-security:
name: Actions Security
runs-on: ubuntu-latest
Expand Down
20 changes: 5 additions & 15 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ on:
permissions:
contents: read
security-events: write
actions: read

jobs:
security-scan:
Expand All @@ -17,21 +18,10 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Run Semgrep
uses: returntocorp/semgrep-action@713efdd345f3035192eaa63f56867b88e63e4e5d # v1.0.0
with:
config: auto

- name: Run Trivy
uses: aquasecurity/trivy-action@18f2510ee396bbf400402947e7f3b01483832965 # v0.31.0
with:
scan-type: fs
format: sarif
output: trivy-results.sarif
persist-credentials: false

- name: Upload Trivy SARIF
uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
if: always()
- name: Run Security Scan
uses: ./.github/actions/security-scan
with:
sarif_file: trivy-results.sarif
scan-path: "."
153 changes: 94 additions & 59 deletions .github/workflows/sync-settings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,104 +17,139 @@ on:

permissions:
contents: read
issues: write
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

jobs:
sync:
name: Sync Settings
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Sync repository settings
env:
GH_TOKEN: ${{ secrets.ORG_SETTINGS_PAT }}
REPORT_FILE: reports/sync-report.md
run: |
MODE="${{ github.event.inputs.mode || '--apply' }}"
./scripts/sync-repo-settings.sh "$MODE"

- name: Parse report
id: report
run: ./scripts/generate-report.sh reports/sync-report.md
- name: Run settings sync
id: sync
uses: ./.github/actions/sync-settings
with:
mode: ${{ github.event.inputs.mode || '--apply' }}
github_token: ${{ secrets.ORG_SETTINGS_PAT }}

- name: Upload report artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: sync-report
name: sync-report-${{ github.run_number }}
path: reports/sync-report.md
retention-days: 90

- name: Send email report
- name: Post job summary
if: always()
uses: dawidd6/action-send-mail@v3.12.0
with:
server_address: smtp.gmail.com
server_port: 587
username: ${{ secrets.EMAIL_USERNAME }}
password: ${{ secrets.EMAIL_PASSWORD }}
subject: |
GitHub Settings Sync Report — ${{ steps.report.outputs.drift > 0 && 'Drift Detected' || 'All Compliant' }}
to: gamaware@gmail.com
from: GitHub Org Settings <${{ secrets.EMAIL_USERNAME }}>
body: |
GitHub Organization Settings Sync Report
==========================================

Date: ${{ github.event.head_commit.timestamp || github.event.repository.updated_at }}
Mode: ${{ github.event.inputs.mode || '--apply' }}
Run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}

Repositories scanned: ${{ steps.report.outputs.total_repos }}
Compliant: ${{ steps.report.outputs.compliant }}
Drift detected: ${{ steps.report.outputs.drift }}

Full report is attached and available as a workflow artifact.
attachments: reports/sync-report.md
run: |
{
echo "## Settings Sync Results"
echo ""
echo "| Metric | Value |"
echo "| --- | --- |"
echo "| Repositories scanned | ${{ steps.sync.outputs.total_repos }} |"
echo "| Compliant | ${{ steps.sync.outputs.compliant }} |"
echo "| Drift detected | ${{ steps.sync.outputs.drift }} |"
echo "| Mode | ${{ github.event.inputs.mode || '--apply' }} |"
echo ""
echo "### Full Report"
echo ""
cat reports/sync-report.md
} >> "$GITHUB_STEP_SUMMARY"

- name: Create or update drift issue
if: steps.sync.outputs.has_drift == 'true'
env:
GH_TOKEN: ${{ secrets.ORG_SETTINGS_PAT }}
run: |
TITLE="chore: settings drift detected — $(date '+%Y-%m-%d')"
BODY=$(cat <<'ISSUE_EOF'
## Settings Drift Report

**Run**: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
**Mode**: ${{ github.event.inputs.mode || '--apply' }}
**Repos with drift**: ${{ steps.sync.outputs.drift }} / ${{ steps.sync.outputs.total_repos }}

See the [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for the full report.

ISSUE_EOF
)

# Close previous drift issues
gh issue list --label "settings-drift" --state open --json number --jq '.[].number' | while read -r num; do
gh issue close "$num" --comment "Superseded by new sync run."
done
Comment thread
coderabbitai[bot] marked this conversation as resolved.

# Create new issue
gh issue create --title "$TITLE" --body "$BODY" --label "settings-drift"

- name: Close drift issue if compliant
if: steps.sync.outputs.has_drift == 'false'
env:
GH_TOKEN: ${{ secrets.ORG_SETTINGS_PAT }}
run: |
gh issue list --label "settings-drift" --state open --json number --jq '.[].number' | while read -r num; do
gh issue close "$num" --comment "All repositories are now compliant."
done
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

new-repo-check:
name: Discover New Repositories
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Check for new repos
id: newrepos
env:
GH_TOKEN: ${{ secrets.ORG_SETTINGS_PAT }}
run: |
echo "## New Repository Discovery" > reports/new-repos.md
echo "" >> reports/new-repos.md

# Get all current repos
gh repo list gamaware --no-archived --json name,createdAt --jq '.[] | "\(.name) (created: \(.createdAt))"' --limit 200 > /tmp/all-repos.txt

# Check repos created in the last 7 days
WEEK_AGO=$(date -u -d '7 days ago' '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || date -u -v-7d '+%Y-%m-%dT%H:%M:%SZ')
mkdir -p reports
{
echo "## New Repository Discovery"
echo ""
} > reports/new-repos.md

WEEK_AGO=$(date -u -d '7 days ago' '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null \
|| date -u -v-7d '+%Y-%m-%dT%H:%M:%SZ')
NEW_REPOS=$(gh repo list gamaware --no-archived --json name,createdAt \
--jq "[.[] | select(.createdAt > \"$WEEK_AGO\")] | .[].name" --limit 200 || echo "")
--jq "[.[] | select(.createdAt > \"$WEEK_AGO\")] | .[].name" \
--limit 200 || echo "")
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

if [ -n "$NEW_REPOS" ]; then
echo "New repositories found in the last 7 days:" >> reports/new-repos.md
echo "" >> reports/new-repos.md
echo "$NEW_REPOS" | while read -r repo; do
echo "- **$repo**" >> reports/new-repos.md
done
echo "" >> reports/new-repos.md
echo "These repositories will be included in the next settings sync." >> reports/new-repos.md
echo "has_new=true" >> "$GITHUB_OUTPUT"
{
echo "New repositories found in the last 7 days:"
echo ""
echo "$NEW_REPOS" | while read -r repo; do
echo "- **$repo**"
done
echo ""
echo "These repositories will be included in the next settings sync."
} >> reports/new-repos.md
else
echo "has_new=false" >> "$GITHUB_OUTPUT"
echo "No new repositories found in the last 7 days." >> reports/new-repos.md
fi

cat reports/new-repos.md
cat reports/new-repos.md >> "$GITHUB_STEP_SUMMARY"

- name: Create issue for new repos
if: steps.newrepos.outputs.has_new == 'true'
env:
GH_TOKEN: ${{ secrets.ORG_SETTINGS_PAT }}
run: |
BODY=$(cat reports/new-repos.md)
gh issue create \
--title "chore: new repositories discovered — $(date '+%Y-%m-%d')" \
--body "$BODY" \
--label "new-repo"

- name: Upload new repos report
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: new-repos-report
name: new-repos-report-${{ github.run_number }}
path: reports/new-repos.md
retention-days: 30
Loading
Loading