Skip to content
Closed
Show file tree
Hide file tree
Changes from 14 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
34 changes: 9 additions & 25 deletions .github/merge-queue-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,43 +14,27 @@
"all_required": true,
"status_checks": [
{
"context": "CI - Comprehensive Gates / setup",
"context": "ci-guard / attestation-bundle-verifier",
"required": true,
"description": "Environment setup and change detection"
"description": "Portable attestation bundle admissibility verification"
},
{
"context": "CI - Comprehensive Gates / lint-and-typecheck",
"context": "merge-surge / pr-fast",
"required": true,
"description": "Code quality and TypeScript validation"
"description": "Fast protected-branch PR verification lane"
},
{
"context": "CI - Comprehensive Gates / unit-integration-tests",
"context": "merge-surge / merge-queue",
"required": true,
"description": "Test execution with ≥80% coverage requirement"
"description": "Merge-group protected-branch verification lane"
},
{
"context": "CI - Comprehensive Gates / security-gates",
"context": "security-gates / gate",
"required": true,
"description": "SBOM generation, vulnerability scan, secret detection"
},
{
"context": "CI - Comprehensive Gates / build-and-attestation",
"required": true,
"description": "Application build and artifact generation"
},
{
"context": "CI - Comprehensive Gates / merge-readiness",
"required": true,
"description": "Overall merge readiness evaluation"
"description": "Deterministic security gate with pinned-action evidence emission"
}
],
"optional_checks": [
{
"context": "CI - Comprehensive Gates / schema-api-validation",
"required": false,
"description": "GraphQL schema validation (conditional on changes)"
}
]
"optional_checks": []
},
"merge_policies": {
"min_entries_to_merge": 1,
Expand Down
39 changes: 6 additions & 33 deletions .github/required-checks.yml
Original file line number Diff line number Diff line change
@@ -1,47 +1,20 @@
<<<<<<< HEAD
# Required Status Checks Configuration
# =====================================
# DEPRECATED: This file is maintained for historical reference only.
#
# CANONICAL SOURCE: docs/ci/REQUIRED_CHECKS_POLICY.yml (v2.2.0)
#
# The authoritative definition of required checks is in:
# docs/ci/REQUIRED_CHECKS_POLICY.yml
#
# That file defines:
# - always_required: checks that must pass on every commit
# - conditional_required: checks that run based on changed files
# - informational: non-blocking checks for observability
#
# This file remains for legacy tooling compatibility but should NOT
# be used as a source of truth for branch protection or merge queue
# configuration.
#
# Last updated: 2026-03-25
# Status: ARCHIVED - refer to REQUIRED_CHECKS_POLICY.yml
=======
# Canonical list of required status checks for protected branches
# Order is stable and intentional (deterministic diffs)
# NOTE: Canonical policy source is governance/ga/required-checks.yaml.
# Keep this file in sync for legacy verification consumers.
>>>>>>> pr-21871

version: 2
protected_branches:
- main

# DEPRECATED: See docs/ci/REQUIRED_CHECKS_POLICY.yml for current checks
# This file remains a maintained legacy consumer surface.
# The canonical required-check source is governance/ga/required-checks.yaml.
required_checks:
- pr-fast
- merge-queue
- ci-guard / attestation-bundle-verifier
- merge-surge / merge-queue
- merge-surge / pr-fast
- security-gates / gate

notes:
owner: summit-ga
<<<<<<< HEAD
canonical_source: docs/ci/REQUIRED_CHECKS_POLICY.yml
status: archived
migration_date: 2026-03-25
reason: Consolidated to single source of truth to eliminate conflicting definitions
=======
policy: governance/ga/required-checks.yaml
>>>>>>> pr-21871
2 changes: 1 addition & 1 deletion .github/scripts/run-agent-graph-check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ const graph: CapabilityGraph = {
from: "agent:security-engineer",
to: "workflow:policy-enforcement",
allow: true,
requiredChecks: ["security-gates"],
requiredChecks: ["security-gates / gate"],
evidenceKinds: ["security-audit"],
maxCostUsd: 5.0,
maxLatencyMs: 5000,
Expand Down
55 changes: 51 additions & 4 deletions .github/workflows/_policy-enforcer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,21 @@ jobs:
shopt -s nullglob

violations=0
base_ref="${GITHUB_BASE_REF:-main}"

for f in .github/workflows/*.yml .github/workflows/*.yaml; do
mapfile -t changed_workflows < <(
git diff --name-only "origin/${base_ref}...HEAD" -- \
'.github/workflows/*.yml' \
'.github/workflows/*.yaml'
)

if [ "${#changed_workflows[@]}" -eq 0 ]; then
echo "No workflow changes detected; skipping broad-trigger policy check."
exit 0
fi

for f in "${changed_workflows[@]}"; do
[ -f "$f" ] || continue
base="$(basename "$f")"

# Skip canonical pilot gate files that are intentionally PR-facing.
Expand All @@ -44,12 +57,46 @@ jobs:
;;
esac

if grep -Eq '^[[:space:]]*pull_request:' "$f"; then
if ! grep -Eq '^[[:space:]]+paths:' "$f"; then
echo "Violation: $f has pull_request trigger without path scoping."
current_has_pr=false
current_has_paths=false
if grep -Eq '^[[:space:]]*(pull_request|pull_request_target):' "$f"; then
current_has_pr=true
fi
if grep -Eq '^[[:space:]]+paths:' "$f"; then
current_has_paths=true
fi
Comment on lines +60 to +67
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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

sample=$'on:\n  pull_request:\n  push:\n    paths:\n      - ".github/workflows/**"\n'

if printf '%s' "$sample" | grep -Eq '^[[:space:]]*(pull_request|pull_request_target):'; then
  echo "current_has_pr=true"
fi

if printf '%s' "$sample" | grep -Eq '^[[:space:]]+paths:'; then
  echo "current_has_paths=true"
fi

echo 'Expected policy result: PR trigger is present, but PR path scoping is absent.'

Repository: BrianCLong/summit

Length of output: 181


🏁 Script executed:

# Explore the repository structure and locate the file
find . -name "_policy-enforcer.yml" -type f 2>/dev/null | head -5

Repository: BrianCLong/summit

Length of output: 101


🏁 Script executed:

# Read the policy enforcer file to understand the full context
if [ -f ".github/workflows/_policy-enforcer.yml" ]; then
  cat -n ".github/workflows/_policy-enforcer.yml"
else
  echo "File not found at expected location"
  find . -name "*policy-enforcer*" 2>/dev/null
fi

Repository: BrianCLong/summit

Length of output: 4347


Parse on.pull_request{,_target}.paths specifically instead of grepping for any paths: key.

The current_has_paths and base_has_paths booleans go true for any indented paths: key anywhere in the workflow, including push.paths. A workflow with pull_request: trigger but lacking path restrictions will incorrectly pass this guard if that workflow also has push.paths or paths under any other trigger.

Example: A workflow with on: { pull_request:, push: { paths: [".github/workflows/**"] } } would incorrectly satisfy the policy, allowing a PR-facing trigger without actual PR path scoping.

This issue applies to both line ranges: 60-67 (current workflow check) and 80-87 (base workflow check).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/_policy-enforcer.yml around lines 60 - 67, The current
checks set current_has_paths/base_has_paths true for any indented "paths:"
anywhere; change them to only detect "paths:" that belong to the pull_request or
pull_request_target trigger. Replace the simple grep '^[[:space:]]+paths:' "$f"
logic with a targeted parse: first locate a pull_request or pull_request_target
key (grep -n for '^[[:space:]]*(pull_request|pull_request_target):'), then
inspect only the following indented block up until the next top-level key for a
'^[[:space:]]+paths:' line (or use yq to read .on.pull_request.paths
/.on.pull_request_target.paths). Apply the same replacement for both the
current_* (lines shown) and base_* checks so only pull-request-specific path
restrictions are considered.


if ! git cat-file -e "origin/${base_ref}:${f}" 2>/dev/null; then
if [ "$current_has_pr" = true ] && [ "$current_has_paths" = false ]; then
echo "Violation: $f introduces a PR-facing trigger without path scoping."
violations=1
fi
continue
fi

base_file="$(mktemp)"
git show "origin/${base_ref}:${f}" > "$base_file"

base_has_pr=false
base_has_paths=false
if grep -Eq '^[[:space:]]*(pull_request|pull_request_target):' "$base_file"; then
base_has_pr=true
fi
if grep -Eq '^[[:space:]]+paths:' "$base_file"; then
base_has_paths=true
fi

if [ "$current_has_pr" = true ] && [ "$base_has_pr" = false ] && [ "$current_has_paths" = false ]; then
echo "Violation: $f adds a PR-facing trigger to an existing workflow without path scoping."
violations=1
fi

if [ "$base_has_pr" = true ] && [ "$base_has_paths" = true ] && [ "$current_has_pr" = true ] && [ "$current_has_paths" = false ]; then
echo "Violation: $f removes required path scoping from an existing PR-facing workflow."
violations=1
fi

rm -f "$base_file"
done

test "$violations" -eq 0
Expand Down
96 changes: 96 additions & 0 deletions .github/workflows/admissibility-gate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
name: Admissibility Gate

on:
pull_request:
workflow_dispatch:

permissions:
contents: read
id-token: write
attestations: write

jobs:
evidence-admissibility:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20

- name: Enable Corepack
run: corepack enable

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Install Syft
uses: anchore/sbom-action/download-syft@v0.17.0

- name: Install Cosign
uses: sigstore/cosign-installer@v3.8.1

- name: Build deterministic artifact payload
run: |
mkdir -p dist
sha256sum package.json pnpm-lock.yaml | awk '{print $1}' | sort > dist/admissible-artifact.txt

- name: Generate SBOM (CycloneDX)
run: syft . -o cyclonedx-json=evidence/sbom.cdx.json

- name: Assert SBOM completeness
run: |
test -f evidence/sbom.cdx.json
jq -e '.components and (.components | length > 0)' evidence/sbom.cdx.json

- name: Generate provenance attestation (SLSA)
uses: actions/attest-build-provenance@v3
with:
subject-path: dist/admissible-artifact.txt

- name: Materialize deterministic provenance snapshot
run: |
DIGEST="$(sha256sum dist/admissible-artifact.txt | awk '{print $1}')"
jq -n \
--arg digest "sha256:${DIGEST}" \
--arg repo "${{ github.repository }}" \
'{
_type: "https://in-toto.io/Statement/v1",
predicateType: "https://slsa.dev/provenance/v1",
subject: [{name: "dist/admissible-artifact.txt", digest: {sha256: ($digest | sub("^sha256:"; ""))}}],
builder: {id: "https://github.com/actions/runner"},
invocation: {configSource: {uri: $repo}}
}' > evidence/provenance.json

- name: Sign and verify artifact signature
run: |
cosign generate-key-pair
cosign sign-blob --yes --key cosign.key --output-signature evidence/artifact.sig dist/admissible-artifact.txt
cosign verify-blob --key cosign.pub --signature evidence/artifact.sig dist/admissible-artifact.txt

- name: Build evidence report/metrics/stamp
env:
ARTIFACT_PATH: dist/admissible-artifact.txt
SBOM_PATH: evidence/sbom.cdx.json
PROVENANCE_PATH: evidence/provenance.json
SIGNATURE_VERIFIED: "true"
run: node scripts/ci/build_admissibility_evidence.mjs

- name: Evaluate admissibility gate
run: pnpm verify:admissibility --input evidence/report.json

- name: Upload evidence artifacts
uses: actions/upload-artifact@v4
with:
name: admissibility-evidence-${{ github.run_id }}
path: |
evidence/report.json
evidence/metrics.json
evidence/stamp.json
evidence/sbom.cdx.json
evidence/provenance.json
evidence/artifact.sig
cosign.pub
8 changes: 4 additions & 4 deletions .github/workflows/branch-protection-drift.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4

- name: Determine Branch
id: branch
Expand All @@ -60,7 +60,7 @@ jobs:

- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v2
uses: actions/create-github-app-token@31c86eb3b33c9b601a1f60f98dcbfd1d70f379b4 # v2.0.0
continue-on-error: true
with:
app-id: ${{ secrets.BRANCH_PROTECTION_APP_ID }}
Expand Down Expand Up @@ -133,7 +133,7 @@ jobs:
echo "extra_count=${EXTRA}" >> "$GITHUB_OUTPUT"

- name: Upload Drift Report
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: always()
with:
name: branch-protection-drift-report
Expand All @@ -144,7 +144,7 @@ jobs:

- name: Upsert governance issue (non-PR only)
if: github.event_name != 'pull_request' && steps.check.outputs.drift_detected == 'true' && github.event.inputs.dry_run != 'true'
uses: actions/github-script@v6
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const fs = require('fs');
Expand Down
49 changes: 49 additions & 0 deletions .github/workflows/ci-guard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,16 @@ name: ci-guard

on:
pull_request:
branches: [main]
merge_group:
types: [checks_requested]
push:
branches: [main]

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
drift:
runs-on: ubuntu-latest
Expand All @@ -27,3 +34,45 @@ jobs:
- uses: actions/checkout@v4
- run: node .repoos/scripts/ci/compute_control_checksum.mjs > .repoos/control/checksum.txt
- run: cat .repoos/control/checksum.txt


attestation-bundle-verifier:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22

- name: Run attestation-bundle verifier tests
run: node --test --test-name-pattern="attestation bundle" scripts/ci/__tests__/governance_mutation_guard.test.mjs

- name: Verify required checks contract
run: node scripts/ci/verify_required_checks_contract.mjs

- name: Verify synthetic attestation bundle
shell: bash
run: |
set -euo pipefail
bundle_dir="$(mktemp -d)/attestations"
mkdir -p "$bundle_dir"

cat > "$bundle_dir/subject.json" <<'EOF'
{"subject_digest":"sha256:1111111111111111111111111111111111111111111111111111111111111111","subject_type":"decision-bundle","bundle_version":"0.1"}
EOF

cat > "$bundle_dir/verification-summary.json" <<'EOF'
{"subject":{"digest":"sha256:1111111111111111111111111111111111111111111111111111111111111111"},"verifier":{"name":"summit verify","version":"0.1"},"policy_digest":"sha256:2222222222222222222222222222222222222222222222222222222222222222","results":[{"property":"summit.verification.passed","status":"PASS"}]}
EOF

cat > "$bundle_dir/decision-proof.json" <<'EOF'
{"decision_id":"DEC-1","subject_digest":"sha256:1111111111111111111111111111111111111111111111111111111111111111","lineage_run_id":"RUN-1","inputs":[{"type":"bundle","ref":"subject.json"}],"tools":[{"name":"summit verify","version":"0.1"}],"policies_applied":["baseline"],"verification_ref":"verification-summary.json","reproducible":true,"verdict":"ADMISSIBLE"}
EOF

cat > "$bundle_dir/policy.json" <<'EOF'
{"policy_digest":"sha256:2222222222222222222222222222222222222222222222222222222222222222"}
EOF

node scripts/ci/verify_summit_attestation_bundle.mjs "$bundle_dir"
Loading
Loading