Skip to content

fix: make Core CI tokens read-only by default - #4656

Open
chet wants to merge 1 commit into
NVIDIA:mainfrom
chet:gh-issue-4650
Open

fix: make Core CI tokens read-only by default#4656
chet wants to merge 1 commit into
NVIDIA:mainfrom
chet:gh-issue-4650

Conversation

@chet

@chet chet commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Core jobs without their own permissions block inherited the repository default for GITHUB_TOKEN, even when they only needed to read the checkout. This establishes contents: read for the whole workflow and keeps the two broader read-only jobs explicit.

  • Expected green-run effect: None. This changes token authority, not the commands or build work on a successful run.
  • What it really buys us: Ordinary Core jobs no longer inherit write authority they do not need, and a checked inventory makes a future permission increase visible in review.

security-codeql-scan keeps actions: read for workflow metadata, and lint-police keeps pull-requests: read for merge validation. TruffleHog's PR comments are disabled and that action does not upload SARIF, while the exact pinned image-promotion workflow requests only contents: read, so neither job needs a write exception.

Related issues

This supports #4650

Type of Change

  • Add - New feature or capability
  • Change - Changes in existing functionality
  • Fix - Bug fixes
  • Remove - Removed features or deprecated functionality
  • Internal - Internal changes (refactoring, tests, docs, etc.)

Breaking Changes

  • This PR contains breaking changes

Testing

  • Unit tests added/updated
  • Integration tests added/updated
  • Manual testing performed
  • No testing required (docs, internal refactor, etc.)
bash -n scripts/check-core-ci-permissions.sh
bash scripts/check-core-ci-permissions.sh
cargo make format-nightly
cargo make clippy
cargo make carbide-lints

Additional Notes

The checked inventory covers all 51 current Core jobs: 49 inherit contents: read, and two have exact, read-only exceptions. The validator exercises 19 focused fixtures, including nonstandard indentation, flow-style permission attempts, workflow-level writes, and reserved-owner collisions. External registry and publication credentials are unchanged; successful main and tag runs remain the compatibility proof for those event-only paths.

@chet
chet requested review from a team as code owners August 6, 2026 07:05
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Summary by CodeRabbit

  • Security

    • Restricted automated workflow permissions to read-only access where appropriate.
    • Reduced unnecessary permissions for security scanning and validation tasks.
    • Added safeguards to detect overly broad or unauthorized permissions.
  • Reliability

    • Strengthened continuous integration checks for workflow permissions.
    • Improved validation handling for failed requests and unexpected results.
  • Maintenance

    • Added comprehensive checks for configuration syntax, job coverage, and approved exceptions.
    • Improved validation guidance and error reporting.
    • Added automated safeguards to keep CI permissions consistent over time.

Walkthrough

The workflow applies a read-only contents baseline, narrows job permissions, and runs a strict Bash validator. The validator parses permission mappings, checks job exceptions, rejects unauthorized access, tests nineteen fixtures, and validates pull-request base-branch lookup failures.

Changes

Core CI permissions

Layer / File(s) Summary
Permission validator and fixtures
scripts/check-core-ci-permissions.sh
The script parses workflow and job permission blocks, validates inventories and exceptions, rejects unauthorized writes, runs nineteen fixtures, and validates workflow arguments and paths.
Workflow permission integration
.github/workflows/ci.yaml
The workflow sets read-only contents access, removes unnecessary job permissions, adds lint read access, invokes the validator from the changes job, and handles failed, empty, or null pull-request base-branch results.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ChangesJob as changes job
  participant Validator as check-core-ci-permissions.sh
  participant Workflow as ci.yaml
  ChangesJob->>Validator: invoke permission validation
  Validator->>Workflow: parse permission mappings
  Validator-->>ChangesJob: report validation result
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the primary change: making Core CI token permissions read-only by default.
Description check ✅ Passed The description directly explains the permission changes, retained exceptions, validation, and expected impact.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@chet

chet commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full_review, thanks!

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

@chet: I will perform a complete review of PR #4656.

✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
scripts/check-core-ci-permissions.sh (3)

333-334: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Simplify the cleanup trap and remove the glob risk.

rm -f "${fixture_dir}"/*.yaml expands to /*.yaml if fixture_dir is ever empty. set -e prevents that today, but the construct is fragile and leaves any non-.yaml file behind, which then makes rmdir fail inside the trap.

Use a single recursive removal with an end-of-options guard.

♻️ Suggested fix
 	fixture_dir="$(mktemp -d)"
-	trap 'rm -f "${fixture_dir}"/*.yaml; rmdir "${fixture_dir}"' EXIT
+	trap 'rm -rf -- "${fixture_dir}"' EXIT

As per path instructions: "Review scripts for shell safety, quoting, idempotency, dependency checks, error handling, and avoiding secret leakage in logs."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/check-core-ci-permissions.sh` around lines 333 - 334, Replace the
cleanup trap immediately after fixture_dir creation with a single recursive
removal command that uses an end-of-options guard and preserves the quoted
fixture_dir path. Remove the rm glob and separate rmdir so the trap safely
handles any contents and an empty or unusual directory path.

Source: Path instructions


114-117: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Clear in_jobs when a new top-level key starts.

in_jobs is set at jobs: and never reset. If any top-level mapping follows the jobs: block, its indent-2 keys match is_job_key and are counted as jobs. That inflates job_count, creates phantom owners in seen_jobs, and can attribute a later permissions: block to a non-existent job.

The current ci.yaml places jobs: last, so the defect is latent. Reset the flag so a future reorder does not silently change the result.

♻️ Suggested fix
+			if (indent == 0 && content != "jobs:") {
+				in_jobs = 0
+				current_job = ""
+			}
+
 			if (indent == 0 && content == "jobs:") {
 				in_jobs = 1
 				next
 			}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/check-core-ci-permissions.sh` around lines 114 - 117, Update the
top-level key handling in the YAML parsing loop so encountering any top-level
mapping key other than jobs: clears in_jobs before processing it. Preserve
setting in_jobs for jobs:, ensuring subsequent top-level sections are not
treated as job entries or associated permissions.

149-154: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Scope the misplaced rule to job-level keys only.

The rule flags any permissions: key at an indent greater than 2 that is not exactly 4. Step inputs can legitimately contain a key named permissions: under with: at indent 10. In that case the validator reports "must use four-space indentation" and fails CI with a misleading message.

Constrain the rule to the job mapping level. Ignore keys nested below a steps: sequence.

♻️ Suggested narrowing
+			if (in_jobs && indent == 4 && content ~ /^steps:/) {
+				in_steps = 1
+			} else if (in_jobs && indent <= 4) {
+				in_steps = 0
+			}
+
-			if (in_jobs && current_job != "" && indent > 2 && indent != 4 &&
+			if (in_jobs && current_job != "" && !in_steps && indent > 2 && indent != 4 &&
 				content ~ /^permissions:/) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/check-core-ci-permissions.sh` around lines 149 - 154, Update the
misplaced permissions check in the script’s job-processing logic to apply only
to job-mapping-level permissions keys, excluding keys nested beneath a steps:
sequence such as step with inputs. Preserve the existing indentation validation
for actual job-level permissions declarations and avoid reporting nested step
data.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@scripts/check-core-ci-permissions.sh`:
- Around line 333-334: Replace the cleanup trap immediately after fixture_dir
creation with a single recursive removal command that uses an end-of-options
guard and preserves the quoted fixture_dir path. Remove the rm glob and separate
rmdir so the trap safely handles any contents and an empty or unusual directory
path.
- Around line 114-117: Update the top-level key handling in the YAML parsing
loop so encountering any top-level mapping key other than jobs: clears in_jobs
before processing it. Preserve setting in_jobs for jobs:, ensuring subsequent
top-level sections are not treated as job entries or associated permissions.
- Around line 149-154: Update the misplaced permissions check in the script’s
job-processing logic to apply only to job-mapping-level permissions keys,
excluding keys nested beneath a steps: sequence such as step with inputs.
Preserve the existing indentation validation for actual job-level permissions
declarations and avoid reporting nested step data.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: a73afe58-2586-4d24-aff6-0b744c8e525e

📥 Commits

Reviewing files that changed from the base of the PR and between 4f0e11f and be1a230.

📒 Files selected for processing (2)
  • .github/workflows/ci.yaml
  • scripts/check-core-ci-permissions.sh

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
scripts/check-core-ci-permissions.sh (3)

333-334: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Simplify the cleanup trap.

rmdir fails if the directory holds any file that does not match *.yaml. rm -rf removes the directory unconditionally and keeps the trap free of that dependency.

♻️ Proposed simplification
 	fixture_dir="$(mktemp -d)"
-	trap 'rm -f "${fixture_dir}"/*.yaml; rmdir "${fixture_dir}"' EXIT
+	trap 'rm -rf "${fixture_dir}"' EXIT
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/check-core-ci-permissions.sh` around lines 333 - 334, Update the
cleanup trap associated with fixture_dir to use recursive forced removal of the
temporary directory instead of separately deleting YAML files and calling rmdir.
Keep cleanup unconditional and independent of the directory’s contents.

362-381: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add fixtures for the two uncovered validator branches, and derive the fixture count.

Two rejection paths have no fixture:

  • The invalid record at line 218, reached by an entry such as contents: readonly.
  • The duplicate-block check at lines 230 and 251, reached by two permissions: blocks on the same owner.

Line 381 also hardcodes 8. If a fixture is added and the literal is not updated, the summary reports a wrong count. Count the executed fixtures instead.

♻️ Proposed fixtures and derived count
 	off_indent="${valid_fixture/$'  ordinary:\n    runs-on: ubuntu-latest'/$'  ordinary:\n      permissions:\n        contents: read\n    runs-on: ubuntu-latest'}"
 	job_write_all="${valid_fixture/$'  ordinary:\n    runs-on: ubuntu-latest'/$'  ordinary:\n    permissions: write-all\n    runs-on: ubuntu-latest'}"
+	invalid_access="${valid_fixture/$'permissions:\n  contents: read'/$'permissions:\n  contents: readonly'}"
+	duplicate_block="${valid_fixture/$'permissions:\n  contents: read'/$'permissions:\n  contents: read\npermissions:\n  contents: read'}"
+	local fixture_count=0
 
-	run_fixture "${fixture_dir}" "complete" "pass" "" "${valid_fixture}" || failed=1
+	run_one() {
+		fixture_count=$((fixture_count + 1))
+		run_fixture "$@" || failed=1
+	}
+
+	run_one "${fixture_dir}" "complete" "pass" "" "${valid_fixture}"

Convert the remaining calls to run_one in the same way, add the two new cases, then report the counter:

+	run_one "${fixture_dir}" "invalid-access" "fail" \
+		"Invalid permission entry" "${invalid_access}"
+	run_one "${fixture_dir}" "duplicate-workflow-block" "fail" \
+		"Expected exactly one workflow permissions block" "${duplicate_block}"
 
 	if (( failed )); then
 		return 1
 	fi
 
-	printf 'Checked 8 Core CI permission fixtures.\n'
+	printf 'Checked %d Core CI permission fixtures.\n' "${fixture_count}"

Declare invalid_access, duplicate_block, and fixture_count beside the other locals at lines 322-331.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/check-core-ci-permissions.sh` around lines 362 - 381, Add fixtures
covering the invalid permission access path (for example, contents: readonly)
and duplicate permissions blocks for one owner, using the existing run_one
fixture pattern. In the fixture setup function, declare invalid_access,
duplicate_block, and fixture_count with the other locals, convert remaining
run_one calls consistently, increment fixture_count for each executed fixture,
and replace the hardcoded 8 in the final summary with that counter.

Source: Path instructions


93-163: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Restrict misplaced-permissions detection to YAML permission mappings.

The condition at line 149 runs for every nested permissions: line under the current job. A permissions: line in a run: | block or nested step mapping can therefore produce a false misplaced error and fail CI. Track block-scalar state or parse the YAML structurally with a parser available on the runner.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/check-core-ci-permissions.sh` around lines 93 - 163, Update the AWK
parsing logic around the misplaced-permissions check to recognize YAML
block-scalar content and nested step mappings, so only actual job-level YAML
permission mappings are reported as misplaced. Track and clear block-scalar
state based on indentation (or use an available YAML parser), and ensure literal
text such as permissions: inside run: | is ignored while genuine nested
permission mappings still emit misplaced.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@scripts/check-core-ci-permissions.sh`:
- Around line 333-334: Update the cleanup trap associated with fixture_dir to
use recursive forced removal of the temporary directory instead of separately
deleting YAML files and calling rmdir. Keep cleanup unconditional and
independent of the directory’s contents.
- Around line 362-381: Add fixtures covering the invalid permission access path
(for example, contents: readonly) and duplicate permissions blocks for one
owner, using the existing run_one fixture pattern. In the fixture setup
function, declare invalid_access, duplicate_block, and fixture_count with the
other locals, convert remaining run_one calls consistently, increment
fixture_count for each executed fixture, and replace the hardcoded 8 in the
final summary with that counter.
- Around line 93-163: Update the AWK parsing logic around the
misplaced-permissions check to recognize YAML block-scalar content and nested
step mappings, so only actual job-level YAML permission mappings are reported as
misplaced. Track and clear block-scalar state based on indentation (or use an
available YAML parser), and ensure literal text such as permissions: inside run:
| is ignored while genuine nested permission mappings still emit misplaced.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: fa0bc707-9f67-4431-969e-046d62f49ea0

📥 Commits

Reviewing files that changed from the base of the PR and between 4f0e11f and be1a230.

📒 Files selected for processing (2)
  • .github/workflows/ci.yaml
  • scripts/check-core-ci-permissions.sh

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
scripts/check-core-ci-permissions.sh (1)

366-389: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider pinning the over-indented entries case with a fixture.

The suite covers an over-indented permissions: key through off_indent. It does not cover the inverse shape: a correctly placed permissions: key at indent 4 whose entries sit at indent 8.

The parser records entries only at indent 6, at line 165. For that shape it emits the blocks record but no permission records. The check still fails, because lines 256-261 find an empty permission set for a reviewed job, and lines 272-280 reject the block on an unreviewed job. The behavior is fail-closed today. A fixture would lock that guarantee against future parser edits.

♻️ Proposed additional fixture
 	local job_write_all
+	local over_indent_entries
 	local trailing_root_permissions
 	job_write_all="${valid_fixture/$'  ordinary:\n    runs-on: ubuntu-latest'/$'  ordinary:\n    permissions: write-all\n    runs-on: ubuntu-latest'}"
+	over_indent_entries="${valid_fixture/$'      pull-requests: read'/$'        pull-requests: read'}"
 	trailing_root_permissions="${valid_fixture/$'permissions:\n  contents: read\n'/}"
 	run_fixture "${fixture_dir}" "job-write-all" "fail" \
 		"Inline permissions value write-all on ordinary is not allowed" "${job_write_all}" || failed=1
+	run_fixture "${fixture_dir}" "over-indent-entries" "fail" \
+		"Permissions for job lint-police must be" "${over_indent_entries}" || failed=1

Update the count at line 395 to match the new total.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/check-core-ci-permissions.sh` around lines 366 - 389, Add a fixture
in the permission-check test setup for a job with a correctly indented
permissions key but over-indented permission entries, and assert the existing
fail-closed behavior with the appropriate error outcome. Register the new
fixture with run_fixture alongside the other indentation cases, and update the
expected fixture count near the test summary to include it.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@scripts/check-core-ci-permissions.sh`:
- Around line 366-389: Add a fixture in the permission-check test setup for a
job with a correctly indented permissions key but over-indented permission
entries, and assert the existing fail-closed behavior with the appropriate error
outcome. Register the new fixture with run_fixture alongside the other
indentation cases, and update the expected fixture count near the test summary
to include it.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 2c2e55b5-dbef-430f-a9fa-23fecb5da2b6

📥 Commits

Reviewing files that changed from the base of the PR and between 4f0e11f and 815a5ea.

📒 Files selected for processing (2)
  • .github/workflows/ci.yaml
  • scripts/check-core-ci-permissions.sh
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/ci.yaml

@chet

chet commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full_review, thanks!

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

@chet: I will perform a complete review of PR #4656.

🐇

✅ Action performed

Full review finished.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (4)
scripts/check-core-ci-permissions.sh (3)

371-372: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Guard the positive fixtures against a silent substitution miss.

${var/pattern/replacement} returns the original string when the pattern misses. For the fail fixtures this is self-detecting, because the unchanged valid_fixture passes and the assertion expects a failure.

The pass fixtures have no such protection. If a future edit to valid_fixture changes this indentation, quoted_jobs silently degrades to valid_fixture. The test still passes, and the quoted-job-key paths at lines 78-79 stop being exercised.

Both substitutions match today. Add an assertion so they keep matching.

♻️ Proposed guard
 	quoted_jobs="${valid_fixture/$'  lint-police:'/$'  "lint-police":'}"
 	quoted_jobs="${quoted_jobs/$'  security-codeql-scan:'/$'  \047security-codeql-scan\047:'}"
+	if [[ "${quoted_jobs}" == "${valid_fixture}" ]]; then
+		printf 'Fixture quoted-job-keys did not diverge from the valid fixture.\n' >&2
+		return 1
+	fi

Apply the same check to step_input_permissions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/check-core-ci-permissions.sh` around lines 371 - 372, Add assertions
in the positive fixture setup around quoted_jobs and step_input_permissions to
verify each parameter substitution actually changed the source string before
running the existing assertions. Fail clearly when the expected indentation
pattern is absent, while preserving the current quoted-key transformations and
test flow.

396-397: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a fixture for a workflow-wide write in mapping form.

The workflow-write-all fixture replaces the whole block with permissions: write-all. That is the inline form, so it exits through the inline branch at line 219. It never reaches the workflow-wide write check at lines 210-212.

The remaining write fixtures, unreviewed-job-write and broadened-exception, both exercise the job branch at line 213.

The result is that lines 210-212 have no fixture coverage. That branch enforces the primary rule of this PR: no write scope at workflow level. Add a fixture in mapping form.

♻️ Proposed fixture

Add the fixture string beside the others:

 	invalid_access="${valid_fixture/$'permissions:\n  contents: read'/$'permissions:\n  contents: readonly'}"
+	workflow_mapping_write="${valid_fixture/$'permissions:\n  contents: read'/$'permissions:\n  contents: write'}"

Declare it with the other locals:

 	local workflow_write
+	local workflow_mapping_write

Register the assertion:

 	run_counted_fixture "${fixture_dir}" "workflow-write-all" "fail" \
 		"Inline permissions value write-all on workflow is not allowed" "${workflow_write}"
+	run_counted_fixture "${fixture_dir}" "workflow-mapping-write" "fail" \
+		"Workflow-wide write permission contents:write is not allowed" "${workflow_mapping_write}"

Update the count in the PR description from fifteen to sixteen fixtures.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/check-core-ci-permissions.sh` around lines 396 - 397, Add a separate
workflow-wide mapping-form write fixture alongside the existing fixture strings
and declare it with the other locals. Register it with run_counted_fixture to
exercise the workflow-level write validation branch, distinct from the inline
workflow-write-all fixture, and update the documented fixture count from fifteen
to sixteen.

194-233: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Reserve the workflow owner key so a job name cannot alias it.

The awk stage emits the literal owner workflow for the root block. A job named workflow in .github/workflows/ci.yaml would emit the same owner. The shell then merges both into actual_permissions[workflow] and permission_blocks[workflow], and the blocks record for the job overwrites the root count.

I traced every variant of this collision. Each one still fails the gate, because the merged value stops matching contents=read. The gate therefore remains fail-closed. The defect is diagnostic quality, not a bypass: the reported error names the workflow block while the real cause is the job.

Use a sentinel that a YAML job key cannot produce. A leading colon is not valid in the job-key regex ^[A-Za-z_][A-Za-z0-9_-]*$.

♻️ Proposed sentinel owner key

In the awk program, replace the three workflow owner literals:

-					printf "inline\tworkflow\t%s\n", remainder
+					printf "inline\t:root\t%s\n", remainder
-				emit_permission("workflow", content)
+				emit_permission(":root", content)
-			printf "blocks\tworkflow\t%d\n", root_blocks
+			printf "blocks\t:root\t%d\n", root_blocks

Then update the shell side:

-				if [[ "${owner}" == "workflow" ]]; then
+				if [[ "${owner}" == ":root" ]]; then
-	if [[ "${permission_blocks[workflow]:-0}" != "1" ]]; then
+	if [[ "${permission_blocks[:root]:-0}" != "1" ]]; then
-	actual="$(normalize_permissions "${actual_permissions[workflow]:-}")"
+	actual="$(normalize_permissions "${actual_permissions[:root]:-}")"
-		if [[ "${job}" == "workflow" ]]; then
+		if [[ "${job}" == ":root" ]]; then

Adjust the user-facing message text so it still reads on workflow.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/check-core-ci-permissions.sh` around lines 194 - 233, Reserve a
sentinel owner key beginning with a colon for the root workflow block in the awk
logic within extract_permission_records, replacing all three workflow owner
literals. Update the shell-side comparisons and permission_blocks handling to
use that sentinel, preventing a job named workflow from colliding with root
records. Keep user-facing diagnostics rendering the sentinel as “workflow”.
.github/workflows/ci.yaml (1)

1518-1523: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The scopes are correct and minimal. Consider making a scope failure diagnose itself.

Both scopes are justified. The API call at line 1547 reads .base.ref from the pulls endpoint, which requires pull-requests on a private repository. The checkout and the later git fetch require contents. The merge at line 1553 is --no-commit --no-ff and purely local, so the comment is accurate: the job never edits the pull request.

One rollout risk follows from the new declaration. This job now depends on an explicitly declared scope instead of an inherited default. If that scope is ever wrong, curl -sf exits non-zero and prints nothing, and jq still succeeds, so the pipeline status is jq's. TARGET becomes null, and the failure surfaces at git fetch as a missing ref rather than as a permission error.

A pipefail and an explicit guard would name the real cause.

♻️ Proposed diagnostic guard for the merge-validation step
         run: |
+          set -o pipefail
           git config --global --add safe.directory "$GITHUB_WORKSPACE"
           PR_NUMBER="${GITHUB_REF_NAME##pull-request/}"
           TARGET=$(curl -sf \
             -H "Authorization: Bearer $GITHUB_TOKEN" \
             -H "Accept: application/vnd.github+json" \
             "https://api.github.com/repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER" \
             | jq -r '.base.ref')
+          if [[ -z "${TARGET}" || "${TARGET}" == "null" ]]; then
+            echo "Could not resolve the base ref for PR ${PR_NUMBER}." >&2
+            echo "Confirm the lint-police job still declares 'pull-requests: read'." >&2
+            exit 1
+          fi
           git fetch --no-tags origin "${TARGET}:refs/remotes/origin/${TARGET}"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yaml around lines 1518 - 1523, Update the lint-police
workflow’s merge-validation step to enable pipefail and explicitly validate the
PR API response before using TARGET. Ensure curl failures or a null/missing base
ref fail immediately with a clear permission/API diagnostic, while preserving
the existing contents and pull-requests permission scopes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In @.github/workflows/ci.yaml:
- Around line 1518-1523: Update the lint-police workflow’s merge-validation step
to enable pipefail and explicitly validate the PR API response before using
TARGET. Ensure curl failures or a null/missing base ref fail immediately with a
clear permission/API diagnostic, while preserving the existing contents and
pull-requests permission scopes.

In `@scripts/check-core-ci-permissions.sh`:
- Around line 371-372: Add assertions in the positive fixture setup around
quoted_jobs and step_input_permissions to verify each parameter substitution
actually changed the source string before running the existing assertions. Fail
clearly when the expected indentation pattern is absent, while preserving the
current quoted-key transformations and test flow.
- Around line 396-397: Add a separate workflow-wide mapping-form write fixture
alongside the existing fixture strings and declare it with the other locals.
Register it with run_counted_fixture to exercise the workflow-level write
validation branch, distinct from the inline workflow-write-all fixture, and
update the documented fixture count from fifteen to sixteen.
- Around line 194-233: Reserve a sentinel owner key beginning with a colon for
the root workflow block in the awk logic within extract_permission_records,
replacing all three workflow owner literals. Update the shell-side comparisons
and permission_blocks handling to use that sentinel, preventing a job named
workflow from colliding with root records. Keep user-facing diagnostics
rendering the sentinel as “workflow”.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: e0505d5a-8649-4f8c-8fb0-bdf2de990be4

📥 Commits

Reviewing files that changed from the base of the PR and between 4f0e11f and 83ec411.

📒 Files selected for processing (2)
  • .github/workflows/ci.yaml
  • scripts/check-core-ci-permissions.sh

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@chet

chet commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full_review, thanks!

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

@chet: I will perform a complete review of PR #4656.

🐇

✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/check-core-ci-permissions.sh`:
- Around line 179-184: Reserve the job name workflow in the validator before
permission records are collected, rejecting workflows that define a job with
that name so it cannot overwrite the workflow-level entry in permission_blocks
or actual_permissions. Add the corresponding negative fixture alongside the
existing negative cases to verify the validator fails closed.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 4c3f153f-ee34-4e7e-94d2-c4ab2a319543

📥 Commits

Reviewing files that changed from the base of the PR and between 4f0e11f and 7e162c5.

📒 Files selected for processing (2)
  • .github/workflows/ci.yaml
  • scripts/check-core-ci-permissions.sh
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/ci.yaml

Comment thread scripts/check-core-ci-permissions.sh

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
scripts/check-core-ci-permissions.sh (1)

179-184: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Security Misconfiguration (CWE-1188): Insecure Default Initialization of Resource

Reachability: External

The workflow-level namespace collision remains unresolved.

The END block still emits the root count as blocks\tworkflow\t<root_blocks>, and the job loop emits blocks\t<job>\t<count> afterwards. Line 210 and line 216 store both record kinds in the same arrays, so a job named workflow overwrites the workflow-level entry and the baseline check at line 244 passes silently.

Reserve the name in validate_permissions, or emit workflow-level records under a sentinel owner that no YAML job key can produce.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/check-core-ci-permissions.sh` around lines 179 - 184, Prevent
workflow-level records from colliding with job records in validate_permissions
by reserving the workflow owner name or emitting root counts under a sentinel
owner that YAML job keys cannot produce. Update the END block’s root and job
output consistently, and ensure the baseline checks use the same non-colliding
key.
🧹 Nitpick comments (2)
.github/workflows/ci.yaml (1)

1548-1558: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Bound the GitHub API call with a timeout and a retry.

curl -sf has no time limit here. If api.github.com stalls, the request blocks until the job timeout and holds a linux-amd64-cpu16 runner. A transient 5xx or a connection reset also fails the whole lint job, although the operation is a read and is safe to retry.

Add --max-time, --connect-timeout, and a bounded retry. The change keeps the existing pipefail guard and the error branches intact.

♻️ Proposed change
           if ! TARGET="$(
             curl -sf \
+              --connect-timeout 10 \
+              --max-time 30 \
+              --retry 3 \
+              --retry-connrefused \
               -H "Authorization: Bearer $GITHUB_TOKEN" \
               -H "Accept: application/vnd.github+json" \
               "https://api.github.com/repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER" \
               | jq -r '.base.ref'
           )"; then
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yaml around lines 1548 - 1558, Update the curl
invocation in the PR base-ref lookup to include bounded connect and
total-request timeouts plus a finite retry policy for transient failures.
Preserve the existing pipefail guard, authorization and Accept headers, jq
extraction, and both error branches unchanged.
scripts/check-core-ci-permissions.sh (1)

378-407: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert every fixture substitution, not three of them.

Lines 384-387, 389-392, and 404-407 verify that the substitution changed the string. The other thirteen derived fixtures do not. If a pattern stops matching after an edit to valid_fixture, those fixtures silently degrade to a copy of valid_fixture. The suite still fails, because each of them expects fail, but the reported reason names the wrong defect.

Extract one helper that performs the substitution and asserts the change. It removes the duplicated guard blocks and makes the remaining cases self-checking.

♻️ Proposed helper
+	substitute_fixture() {
+		local __out="$1" source="$2" pattern="$3" replacement="$4"
+		local result="${source/"${pattern}"/"${replacement}"}"
+		if [[ "${result}" == "${source}" ]]; then
+			printf 'Fixture substitution for %s did not apply.\n' "${__out}" >&2
+			return 1
+		fi
+		printf -v "${__out}" '%s' "${result}"
+	}

Then replace each direct expansion, for example:

-	broadened_exception="${valid_fixture/$'      pull-requests: read'/$'      pull-requests: write'}"
+	substitute_fixture broadened_exception "${valid_fixture}" \
+		$'      pull-requests: read' $'      pull-requests: write' || failed=1
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/check-core-ci-permissions.sh` around lines 378 - 407, Extract a
helper for fixture substitutions that applies the replacement and fails when the
resulting value is unchanged. Use this helper for every derived fixture in the
surrounding test setup, including the existing guarded substitutions, removing
their duplicated manual checks; ensure all substitution patterns remain
self-validating.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@scripts/check-core-ci-permissions.sh`:
- Around line 179-184: Prevent workflow-level records from colliding with job
records in validate_permissions by reserving the workflow owner name or emitting
root counts under a sentinel owner that YAML job keys cannot produce. Update the
END block’s root and job output consistently, and ensure the baseline checks use
the same non-colliding key.

---

Nitpick comments:
In @.github/workflows/ci.yaml:
- Around line 1548-1558: Update the curl invocation in the PR base-ref lookup to
include bounded connect and total-request timeouts plus a finite retry policy
for transient failures. Preserve the existing pipefail guard, authorization and
Accept headers, jq extraction, and both error branches unchanged.

In `@scripts/check-core-ci-permissions.sh`:
- Around line 378-407: Extract a helper for fixture substitutions that applies
the replacement and fails when the resulting value is unchanged. Use this helper
for every derived fixture in the surrounding test setup, including the existing
guarded substitutions, removing their duplicated manual checks; ensure all
substitution patterns remain self-validating.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 76d5b105-472a-4bcc-b7e3-c12509179d85

📥 Commits

Reviewing files that changed from the base of the PR and between 4f0e11f and 7e162c5.

📒 Files selected for processing (2)
  • .github/workflows/ci.yaml
  • scripts/check-core-ci-permissions.sh

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@chet

chet commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full_review, thanks!

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

@chet: I will perform a complete review of PR #4656.

🐇

⚠️ Action not completed

Review rate limited.


Your included review limit is currently reached under our Fair Usage Limits Policy. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 6 minutes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
scripts/check-core-ci-permissions.sh (1)

205-251: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Propagate the parser exit status.

Process substitution discards the exit status of extract_permission_records. set -o pipefail does not apply to < <(...). If awk aborts, the loop reads no records and the run reports "No Core CI jobs found in ...", which points at the workflow instead of the parser.

Capture the records first, then feed the loop.

♻️ Proposed refactor: fail on parser errors
+	local records
+
+	if ! records="$(extract_permission_records "${workflow_path}")"; then
+		printf 'Failed to parse permissions from %s.\n' "${workflow_path}" >&2
+		return 1
+	fi
+
 	while IFS=$'\t' read -r record owner scope access; do
-	done < <(extract_permission_records "${workflow_path}")
+	done <<< "${records}"

Declare records with the other locals at lines 191-203.

As per path instructions: "Review shell scripts for quoting, strict-mode assumptions, error propagation, portability, idempotency, and secret-safe logging."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/check-core-ci-permissions.sh` around lines 205 - 251, Update the
permission-record processing around extract_permission_records to capture its
output in a records variable before iterating, and explicitly propagate a
nonzero parser exit status. Feed the loop from the captured records rather than
process substitution, while preserving the existing record handling and
diagnostics.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/check-core-ci-permissions.sh`:
- Around line 137-150: Update the jobs parsing logic around is_job_key so every
indent-2 key under jobs is recorded, including unrecognized or flow-style job
mappings, instead of being silently skipped. Add the corresponding diagnostic
handling to reject that record, and add the flow_style_job negative fixture with
its declaration alongside the existing fixture locals to verify validation fails
closed.

---

Nitpick comments:
In `@scripts/check-core-ci-permissions.sh`:
- Around line 205-251: Update the permission-record processing around
extract_permission_records to capture its output in a records variable before
iterating, and explicitly propagate a nonzero parser exit status. Feed the loop
from the captured records rather than process substitution, while preserving the
existing record handling and diagnostics.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 61f2da85-4e2b-4a5d-994f-74ad38cd8819

📥 Commits

Reviewing files that changed from the base of the PR and between 4f0e11f and 91589b0.

📒 Files selected for processing (2)
  • .github/workflows/ci.yaml
  • scripts/check-core-ci-permissions.sh
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/ci.yaml

Comment thread scripts/check-core-ci-permissions.sh
Core jobs without their own `permissions` block inherited the repository default for `GITHUB_TOKEN`, even when they only needed to read the checkout. This establishes `contents: read` for the whole workflow and keeps the two broader read-only jobs explicit.

- **Expected green-run effect:** None. This changes token authority, not the commands or build work on a successful run.
- **What it really buys us:** Ordinary Core jobs no longer inherit write authority they do not need, and a checked inventory makes a future permission increase visible in review.

`security-codeql-scan` keeps `actions: read`, `lint-police` keeps `pull-requests: read`, and the pinned image-promotion workflow already fits under the new default.

Tests added!

This supports NVIDIA#4650

Signed-off-by: Chet Nichols III <chetn@nvidia.com>
@chet

chet commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full_review, thanks!

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

@chet: I will perform a complete review of PR #4656.

🐇

✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
.github/workflows/ci.yaml (1)

1546-1559: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add a timeout and a retry policy to the GitHub API call.

The curl call has no --max-time and no retry. If the API stalls, the step blocks until the job timeout expires and holds a linux-amd64-cpu16 runner. A transient 5xx also fails the whole lint job. Bound the call and retry idempotent read failures.

Note: shell: bash already applies -e and -o pipefail, so line 1546 is redundant.

♻️ Proposed change
         run: |
-          set -o pipefail
           git config --global --add safe.directory "$GITHUB_WORKSPACE"
           PR_NUMBER="${GITHUB_REF_NAME##pull-request/}"
           if ! TARGET="$(
             curl -sf \
+              --connect-timeout 10 --max-time 30 \
+              --retry 3 --retry-delay 2 --retry-all-errors \
               -H "Authorization: Bearer $GITHUB_TOKEN" \
               -H "Accept: application/vnd.github+json" \
               "https://api.github.com/repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER" \
               | jq -r '.base.ref'
           )"; then

As per path instructions: "Review shell scripts for quoting, strict-mode assumptions, error propagation, portability, idempotency, and secret-safe logging."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yaml around lines 1546 - 1559, Update the curl
invocation in the TARGET assignment to enforce a bounded request duration with
--max-time and retry transient/idempotent read failures using curl’s retry
options, including suitable retry delay behavior. Remove the redundant set -o
pipefail line while preserving the existing error propagation and failure
message around the GitHub API lookup.

Source: Path instructions

scripts/check-core-ci-permissions.sh (1)

148-174: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Consider failing closed for a permissions: key at any unexpected indent inside a job.

The job_property_seen guard evaluates only the first property line of each job. If the first property is well indented, a later permissions: key at an indent other than 4 or 6 matches no rule and produces no record. Example:

  ordinary:
    runs-on: ubuntu-latest
        permissions:
          contents: write

The parser drops both lines and the validator exits 0. GitHub rejects that file as invalid YAML, so this is defense in depth rather than an exploitable bypass. A single guard keeps the parser fail-closed.

♻️ Proposed guard
 			if (in_jobs && current_job != "" && indent == 6 &&
 				content ~ /^permissions:/) {
 				printf "misplaced\t%s\t%d\n", current_job, indent
 				in_job_permissions = 0
 				next
 			}
+
+			if (in_jobs && current_job != "" && indent > 2 && indent != 4 &&
+				content ~ /^permissions:/) {
+				printf "misplaced\t%s\t%d\n", current_job, indent
+				in_job_permissions = 0
+				next
+			}

As per path instructions: "Review scripts for shell safety, quoting, idempotency, dependency checks, error handling, and avoiding secret leakage in logs."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/check-core-ci-permissions.sh` around lines 148 - 174, Update the
permissions parsing logic around job_property_seen and the existing
indent-4/indent-6 permissions checks so every permissions: key encountered
inside a job is classified fail-closed when its indentation is not an accepted
job-level indent. Emit the same misplaced record and reset in_job_permissions as
appropriate for unexpected indents, rather than allowing later malformed keys to
be silently ignored.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In @.github/workflows/ci.yaml:
- Around line 1546-1559: Update the curl invocation in the TARGET assignment to
enforce a bounded request duration with --max-time and retry
transient/idempotent read failures using curl’s retry options, including
suitable retry delay behavior. Remove the redundant set -o pipefail line while
preserving the existing error propagation and failure message around the GitHub
API lookup.

In `@scripts/check-core-ci-permissions.sh`:
- Around line 148-174: Update the permissions parsing logic around
job_property_seen and the existing indent-4/indent-6 permissions checks so every
permissions: key encountered inside a job is classified fail-closed when its
indentation is not an accepted job-level indent. Emit the same misplaced record
and reset in_job_permissions as appropriate for unexpected indents, rather than
allowing later malformed keys to be silently ignored.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: d36bb9f8-84f8-48b9-88bd-fc396b5bdae3

📥 Commits

Reviewing files that changed from the base of the PR and between 4f0e11f and 1f76be3.

📒 Files selected for processing (2)
  • .github/workflows/ci.yaml
  • scripts/check-core-ci-permissions.sh

@chet

chet commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

Thanks! I checked both remaining nitpicks and I'm leaving them out of this PR:

  • Retry/timeout behavior would be a separate change to the existing API call's reliability; this PR stays focused on token authority.
  • The unexpected-indent example is invalid YAML, and a blanket indent guard would reject the valid nested with.permissions case that already has a positive fixture.

The exact-head review has no open threads.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant