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
3 changes: 0 additions & 3 deletions .github/governance-audits.json

This file was deleted.

17 changes: 7 additions & 10 deletions .github/governance-audits.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,19 @@ This workflow checks complete repository coverage and effective admin-team permi
reviewed main. It reads GitHub state and never changes a repository setting or team grant.
Private coverage diagnostics stay in a temporary file; public reports contain only the outcome.

The daily schedule is enabled in `governance-audits.json` after verified main controls. An explicit
manual opt-in runs the same complete auditors and reporter. A false manual input skips every
App-token and report step.
Invalid policy or incomplete admission reports failure rather than silently clearing a gap.
The daily schedule and input-free manual dispatch run the same complete auditors and
reporter. Admission requires the canonical workflow on reviewed main; an unexpected
event, incomplete context or failed output reports UNKNOWN and cannot clear a gap.

The audit job requests Metadata(read) and Administration(read) over the complete installation.
Its token is revoked by the App action's post handler. The separate reporter has only issue-writing
authority, receives no App key, and keeps one tracking issue open after a failed or incomplete
audit. It closes that issue only after both complete audits succeed.

Initial delivery is tracked in [#406](https://github.com/devantler-tech/.github/issues/406).
Scheduled admission is activated in [#411](https://github.com/devantler-tech/.github/issues/411).
Actual main enabled/disabled controls and the standalone coverage workflow pass. The first natural
scheduled observation and retirement of both the manual opt-in and scheduled rollout flag remain
in [#395](https://github.com/devantler-tech/.github/issues/395). Legacy maintenance
retirement remains [#84](https://github.com/devantler-tech/.github/issues/84).
Routine admission has no experimental switches. The natural daily run completed
both full checks and the success reporter; its implementation and observation are
tracked in [#395](https://github.com/devantler-tech/.github/issues/395).
Legacy maintenance retirement remains [#84](https://github.com/devantler-tech/.github/issues/84).

Organization-settings adoption and observer scheduling remain separate in
[#85](https://github.com/devantler-tech/.github/issues/85) and
Expand Down
10 changes: 1 addition & 9 deletions .github/workflows/governance-audits.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,7 @@ name: 🔎 Routine governance audits
on:
schedule:
- cron: "17 6 * * *"
workflow_dispatch:
inputs:
run-audit:
description: "Run the read-only audit; scheduled rollout and flag retirement are tracked in issue 395"
type: boolean
required: false
default: false
workflow_dispatch: {}

permissions: {}

Expand Down Expand Up @@ -41,8 +35,6 @@ jobs:
persist-credentials: false
- name: Decide audit admission
id: admission
env:
REQUESTED: ${{ inputs.run-audit }}
run: bash scripts/governance-audit-admission.sh

audit:
Expand Down
9 changes: 1 addition & 8 deletions .github/workflows/repository-admin-team-audit.yaml
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
name: 🔎 Repository admin-team audit

on:
workflow_dispatch:
inputs:
run-audit:
description: "Opt in to the read-only live audit; activation and flag retirement are tracked in #395"
type: boolean
required: false
default: false
workflow_dispatch: {}

permissions: {}

Expand All @@ -21,7 +15,6 @@ jobs:
github.event_name == 'workflow_dispatch'
&& github.repository == 'devantler-tech/.github'
&& github.ref == 'refs/heads/main'
&& (inputs.run-audit == true || inputs.run-audit == 'true')
runs-on: ubuntu-latest
timeout-minutes: 12
permissions:
Expand Down
8 changes: 4 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,10 @@ retirement remain in #404.

Routine repository/admin-team monitoring is described in
[`.github/governance-audits.md`](.github/governance-audits.md). Its daily schedule is enabled;
manual execution requires explicit opt-in on main. Keep admission, private-diagnostic suppression,
complete-outcome reporting and source/credential mutations in required CI. Scheduled admission
is reviewed and the actual main controls pass; #395 owns scheduled observation and
rollout-flag retirement. A complete manual result does not retire legacy maintenance (#84).
manual execution is input-free and requires reviewed main. Keep admission, private-diagnostic suppression,
complete-outcome reporting and source/credential mutations in required CI. Routine admission has no experimental switches. Natural scheduled observation and
main dispatch prove both full checks and the reporter. Legacy maintenance retirement
remains a separate gate (#84).

**Validate before every PR** (the sole required check, `CI - Required Checks`, gates on this):

Expand Down
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1162,8 +1162,7 @@ roles when returned by the API. Unknown custom rights, malformed metadata and
conflicting built-in rights remain UNKNOWN. Both forms use the same canonical
permission join for policy evaluation and repeated-read stability checks.

The workflow is default off and runs only from reviewed `main`. Enable its
`run-audit` dispatch input for an evaluation. Its App token requests repository
The input-free manual workflow runs only from reviewed `main`. Its App token requests repository
Metadata and Administration read permissions; missing grants fail token creation.
A short-lived App JWT separately reads the authenticated installation's identity,
all-repository selection and suspension state, binds it to that token's installation
Expand All @@ -1173,8 +1172,8 @@ GitHub's [installation read](https://docs.github.com/en/rest/apps/apps#get-an-in
provides this proof; its [repository-list response](https://docs.github.com/en/rest/apps/installations#list-repositories-accessible-to-the-app-installation)
provides the independent pagination totals. Installation mode requires this reviewed
main workflow context and its unrestricted token mint.
A skipped run is not evidence of compliance. Live grant verification, activation
and flag retirement remain tracked in #395, as part of maintenance retirement #84.
A skipped run is not evidence of compliance. The routine combined workflow also runs
daily and reports only complete outcomes. Legacy maintenance retirement remains #84.

An operator with organization-admin visibility can evaluate the shared checker
with `bash scripts/check-repository-admin-teams.sh --organization-admin`.
Expand Down
19 changes: 3 additions & 16 deletions scripts/governance-audit-admission.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# Admit only the reviewed main workflow; scheduled rollout starts disabled.
# Admit routine audits only from the reviewed main workflow.
set -euo pipefail
finished=false
on_exit() {
Expand All @@ -21,19 +21,6 @@ unknown() {
"${GITHUB_WORKFLOW_REF:-}" == devantler-tech/.github/.github/workflows/governance-audits.yaml@refs/heads/main &&
"${GITHUB_WORKFLOW_SHA:-}" =~ ^[0-9a-f]{40}$ && -n "${GITHUB_OUTPUT:-}" ]] || unknown
case "${GITHUB_EVENT_NAME:-}" in schedule | workflow_dispatch) ;; *) unknown ;; esac
policy="$(cd "$(dirname "$0")/.." && pwd)/.github/governance-audits.json"
if (($# > 0)); then
[[ $# == 2 && "$1" == --policy ]] || unknown
policy="$2"
fi
jq -es 'length == 1 and (.[0] | type == "object" and keys == ["scheduled"] and
(.scheduled | type == "boolean"))' "$policy" >/dev/null 2>&1 || unknown
enabled=false
if [[ "$GITHUB_EVENT_NAME" == schedule ]]; then
[[ "${REQUESTED:-}" == '' ]] || unknown
enabled="$(jq -r .scheduled "$policy")"
else
case "${REQUESTED:-}" in true) enabled=true ;; false | '') ;; *) unknown ;; esac
fi
printf 'enabled=%s\n' "$enabled" >>"$GITHUB_OUTPUT"
(($# == 0)) || unknown
printf 'enabled=true\n' >>"$GITHUB_OUTPUT"
finished=true
73 changes: 26 additions & 47 deletions tests/governance-audit-admission.sh
Original file line number Diff line number Diff line change
@@ -1,56 +1,35 @@
#!/usr/bin/env bash
# Evaluate the actual admission script with both rollout states and hostile contexts.
# Evaluate reviewed-main admission and preserve UNKNOWN for hostile or incomplete contexts.
set -euo pipefail
root="$(cd "$(dirname "$0")/.." && pwd)"
work="$(mktemp -d)"
trap 'rm -rf "$work"' EXIT
script="$root/scripts/governance-audit-admission.sh"
[[ -f "$script" ]] || {
echo 'FAIL: governance admission is missing' >&2
exit 1
}
printf '%s' '{"scheduled":false}' >"$work/off.json"
printf '%s' '{"scheduled":true}' >"$work/on.json"
run_case() {
local name="$1" event="$2" requested="$3" policy="$4" want="$5" decision="${6:-}" status=0
local context="${CONTEXT_OVERRIDE:-AUDIT_TEST_CONTEXT=default}"
: >"$work/output"
env -i PATH="$PATH" GITHUB_ACTIONS=true GITHUB_REPOSITORY=devantler-tech/.github \
GITHUB_REF=refs/heads/main \
GITHUB_WORKFLOW_REF=devantler-tech/.github/.github/workflows/governance-audits.yaml@refs/heads/main \
GITHUB_WORKFLOW_SHA=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa GITHUB_EVENT_NAME="$event" \
GITHUB_OUTPUT="$work/output" REQUESTED="$requested" "$context" \
bash "$script" --policy "$policy" >"$work/log" 2>&1 || status=$?
[[ "$status" == "$want" ]] || {
echo "FAIL: $name returned $status" >&2
cat "$work/log" >&2
exit 1
}
if [[ "$want" == 0 ]]; then
[[ "$(cat "$work/output")" == "enabled=$decision" ]] || {
echo "FAIL: $name emitted a wrong decision" >&2
exit 1
}
else
[[ ! -s "$work/output" ]] || {
echo "FAIL: $name admitted incomplete evidence" >&2
exit 1
}
fi
echo "PASS: $name"
local name="$1" event="$2" want="$3"; shift 3
local context="${CONTEXT_OVERRIDE:-AUDIT_TEST_CONTEXT=default}" status=0
: >"$work/output"
env -i PATH="$PATH" GITHUB_ACTIONS=true GITHUB_REPOSITORY=devantler-tech/.github \
GITHUB_REF=refs/heads/main \
GITHUB_WORKFLOW_REF=devantler-tech/.github/.github/workflows/governance-audits.yaml@refs/heads/main \
GITHUB_WORKFLOW_SHA=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa GITHUB_EVENT_NAME="$event" \
GITHUB_OUTPUT="$work/output" "$context" \
bash "$script" "$@" >"$work/log" 2>&1 || status=$?
[[ "$status" == "$want" ]] || { echo "FAIL: $name returned $status" >&2; cat "$work/log" >&2; exit 1; }
if [[ "$want" == 0 ]]; then
[[ "$(cat "$work/output")" == enabled=true ]] || { echo "FAIL: $name emitted a wrong decision" >&2; exit 1; }
else
[[ ! -s "$work/output" ]] || { echo "FAIL: $name admitted incomplete evidence" >&2; exit 1; }
[[ "$(cat "$work/log")" == *UNKNOWN* ]] || { echo "FAIL: $name omitted UNKNOWN" >&2; exit 1; }
fi
echo "PASS: $name"
}
run_case scheduled-default-off schedule '' "$work/off.json" 0 false
run_case scheduled-enabled schedule '' "$work/on.json" 0 true
run_case manual-off workflow_dispatch false "$work/on.json" 0 false
run_case manual-default-off workflow_dispatch '' "$work/off.json" 0 false
run_case manual-opt-in workflow_dispatch true "$work/off.json" 0 true
run_case malformed-input workflow_dispatch yes "$work/off.json" 2
run_case untrusted-event pull_request true "$work/on.json" 2
for override in GITHUB_ACTIONS=false GITHUB_REPOSITORY=outside/fixture GITHUB_REF=refs/heads/candidate GITHUB_WORKFLOW_REF=other GITHUB_WORKFLOW_SHA=main; do
CONTEXT_OVERRIDE="$override" run_case "$override" workflow_dispatch true "$work/on.json" 2
done
for policy in '{"scheduled":"true"}' '{"scheduled":null}' '{}' '{"scheduled":true,"extra":true}' '{bad' '{"scheduled":true}{"scheduled":true}'; do
printf '%s' "$policy" >"$work/bad.json"
run_case invalid-policy schedule '' "$work/bad.json" 2
run_case scheduled schedule 0
run_case input-free-manual workflow_dispatch 0
run_case untrusted-event pull_request 2
for override in GITHUB_ACTIONS=false GITHUB_REPOSITORY=outside/fixture GITHUB_REF=refs/heads/candidate GITHUB_WORKFLOW_REF=other GITHUB_WORKFLOW_SHA=main GITHUB_OUTPUT=; do
CONTEXT_OVERRIDE="$override" run_case "$override" workflow_dispatch 2
done
run_case missing-policy schedule '' "$work/missing" 2
run_case unexpected-argument schedule 2 --unexpected
run_case retired-policy-argument schedule 2 --policy "$work/unused"
CONTEXT_OVERRIDE="GITHUB_OUTPUT=$work/absent/output" run_case unwritable-output workflow_dispatch 2
7 changes: 3 additions & 4 deletions tests/governance-audits-boundary.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ verify() {
def normalized: gsub("\\s+";" ") | sub(" $";"");
(.on | keys == ["schedule","workflow_dispatch"]) and
.on.schedule == [{cron:"17 6 * * *"}] and
.on.workflow_dispatch.inputs["run-audit"].type == "boolean" and
.on.workflow_dispatch.inputs["run-audit"].default == false and
.on.workflow_dispatch == {} and
.permissions == {} and .env == null and .defaults == null and
(.jobs | keys == ["admit","audit","report"]) and
.jobs.admit.permissions == {contents:"read"} and
Expand All @@ -25,7 +24,7 @@ verify() {
.jobs.admit.steps[1].with["persist-credentials"] == false and
.jobs.admit.steps[2].run == "bash scripts/governance-audit-admission.sh" and
.jobs.admit.outputs.enabled == "${{ steps.admission.outputs.enabled }}" and
.jobs.admit.steps[2].env == {REQUESTED:"${{ inputs.run-audit }}"} and
.jobs.admit.steps[2].env == null and
.jobs.audit.needs == ["admit"] and
(.jobs.audit.if | normalized) == "needs.admit.outputs.enabled == '\''true'\''" and
.jobs.audit.permissions == {contents:"read"} and
Expand Down Expand Up @@ -73,7 +72,7 @@ while IFS=$'\t' read -r name mutation; do
echo "PASS: rejects $name"
done <<'CASES'
PR admission .on.pull_request={}
default manual activation .on.workflow_dispatch.inputs["run-audit"].default=true
retired rollout input .on.workflow_dispatch.inputs["run-audit"]={type:"boolean",default:false}
untrusted admission .jobs.admit.if="true"
mutable admission source .jobs.admit.steps[1].with.ref="main"
skipped admission .jobs.admit.steps[2].if="false"
Expand Down
9 changes: 4 additions & 5 deletions tests/repository-admin-audit-boundary.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@ yq -o=json '.' "$root/.github/workflows/repository-admin-team-audit.yaml" >"$wor
verify() {
jq -e '
(.on | keys == ["workflow_dispatch"]) and .permissions == {} and
.on.workflow_dispatch.inputs["run-audit"].type == "boolean" and
.on.workflow_dispatch.inputs["run-audit"].default == false and
.on.workflow_dispatch == {} and
(.jobs | keys == ["audit"]) and
(.jobs.audit.if | gsub("\\s+";" ") | sub(" $";"")) ==
"github.event_name == '\''workflow_dispatch'\'' && github.repository == '\''devantler-tech/.github'\'' && github.ref == '\''refs/heads/main'\'' && (inputs.run-audit == true || inputs.run-audit == '\''true'\'')" and
"github.event_name == '\''workflow_dispatch'\'' && github.repository == '\''devantler-tech/.github'\'' && github.ref == '\''refs/heads/main'\''" and
.jobs.audit.permissions == {contents:"read"} and
(.jobs.audit.steps | length == 4) and
(.jobs.audit.steps[0].uses | startswith("step-security/harden-runner@")) and
Expand All @@ -32,13 +31,13 @@ verify() {
GH_APP_PRIVATE_KEY:"${{ secrets.APP_PRIVATE_KEY }}"}
' "$1" >/dev/null 2>&1
}
verify "$work/source.json" || { echo 'FAIL: live audit must retain its default-off main-only read-only boundary' >&2; exit 1; }
verify "$work/source.json" || { echo 'FAIL: live audit must retain its main-only read-only boundary' >&2; exit 1; }
while IFS=$'\t' read -r label mutation; do
jq "$mutation" "$work/source.json" >"$work/mutated.json"
if verify "$work/mutated.json"; then echo "FAIL: $label accepted" >&2; exit 1; fi
echo "PASS: rejects $label"
done <<'CASES'
default activation .on.workflow_dispatch.inputs["run-audit"].default=true
retired rollout input .on.workflow_dispatch.inputs["run-audit"]={type:"boolean",default:false}
PR trigger .on.pull_request={}
unguarded credentials .jobs.audit.if="true"
write-scoped workflow token .jobs.audit.permissions.contents="write"
Expand Down
Loading