Chapter 06Lesson 05~210 minutes

Checkpoint Lab — GITHUB_TOKEN, Workflow Permissions, Least Privilege, and API Access

The checkpoint turns Chapter 06 into one auditable authorization experiment. You will begin with a deny-by-default workflow, prove a bounded source read, intentionally attempt one issue mutation with insufficient authority and preserve the resulting 403, then change only the mutation job to issues: write, create exactly one synthetic issue, verify it by number, close it, and explain why no broader scope or PAT was necessary.

CheckpointDeny then grantAuthorization evidenceIssue mutationCapability contract

Learning objectives

  • Predict the effective capability and repository effect for read, denied-write, and narrow-write jobs before execution.
  • Preserve the original 403 and distinguish authorization evidence from a failed pipeline implementation.
  • Demonstrate one tightly scoped same-repository mutation using only issues: write.
  • Verify and clean up the exact created issue while preserving run and API evidence.
  • Produce a complete least-privilege evidence packet and state the trust/plan limitations of the lab.

1. Checkpoint charter and authorization ceiling

Item Checkpoint contract
Repository Learner-owned disposable repository with Issues enabled and README.md committed.
Workflow .github/workflows/ch06-checkpoint.yml
Trigger workflow_dispatch only for mandatory execution.
Runner ubuntu-24.04.
Top-level capability permissions: {}.
Read job contents: read; GET exact README at GITHUB_SHA.
Denied job issues: read; POST issue must return 403.
Mutation job issues: write; create one issue, verify exact number, close same number.
External credentials/actions None.

2. Required predictions before execution

  1. The read job can authenticate and read README.md at the run's exact source SHA.
  2. The read job cannot be assumed to mutate issues merely because authentication succeeded.
  3. The denied job's POST issue request will return 403 and create no issue.
  4. The mutation job with issues: write will receive 201 and create exactly one issue.
  5. The created issue author may appear as github-actions[bot], while github.actor identifies the workflow initiator.
  6. Closing the exact issue number is cleanup; no other issue may be modified.

3. Revision A — preserve the under-privileged denial

Use the full guided workflow from Lesson 2, but stop after denied_write or temporarily omit the mutation job. Dispatch once and preserve:

  • workflow/source SHA;
  • event and actor;
  • run ID and attempt;
  • read job contents: read declaration and 200 response;
  • denied job issues: read declaration;
  • POST /repos/OWNER/REPO/issues status 403;
  • X-GitHub-Request-Id, X-Accepted-GitHub-Permissions and rate-limit headers when present;
  • proof no issue with the synthetic denied title exists.
Do not erase Revision A. The first 403 is required checkpoint evidence. Do not change to write-all, PAT, or pull_request_target.

4. Diagnose Revision A before repair

YAML parsed, the workflow run exists, the runner executed, authentication was valid enough for earlier API use, and the POST targeted the correct repository. The job requests issues: read, while issue creation requires write. The causal layer is therefore the issue permission.

If your evidence instead shows 410, stop: Issues may be disabled. If it shows rate limiting, address that. The checkpoint only proceeds to a permission repair when the preserved evidence supports that conclusion.

5. Revision B — change only the mutation capability

Add or enable the mutation job with:

mutate_issue:
  needs: denied_write
  runs-on: ubuntu-24.04
  permissions:
    issues: write

Reuse the same request shape, API version, repository, synthetic naming convention, and runner. The only security-relevant change is the requested issue capability on this one job.

6. Execute Revision B and verify the exact effect

Dispatch again. Require:

  • read request 200;
  • the intentionally denied job still proves 403 under issues: read;
  • mutation request 201 under issues: write;
  • issue title = ch06-write-<run-id>-<attempt>;
  • GET by returned issue number = 200/open;
  • close PATCH on that exact number = 200/closed.

The final workflow can contain both the denial-control job and the authorized mutation job. Their different results under different capability declarations are the core experiment.

7. Required evidence packet

Evidence Why it matters
Repository + workflow path + workflow SHA Identifies the authorization code that ran.
Event, actor, run ID, attempt, source SHA Identifies trigger/trust/execution state.
Repository default-policy note Record visible setting if authorized; otherwise “not queried.”
Per-job permission blocks Shows requested capability explicitly.
GET/POST/PATCH endpoint + API version Defines requested operations precisely.
200 / 403 / 201 / 200 / 200 statuses Shows read, denial, create, verify and close phases.
Request ID / accepted-permission hints Supports diagnosis without token disclosure.
Issue number + URL + author + final state Proves exact repository effect and cleanup.
Assumptions note No PAT/App key, cloud, deployment, package, external action or self-hosted runner.

8. Trust-context simulation without privileged execution

Do not run the mutation workflow from an untrusted fork for the mandatory lab. Instead, add an architecture note predicting that fork/Dependabot context can downgrade write permissions. Explain why the appropriate design is to keep untrusted CI read-only and move trusted mutations behind a separate reviewed boundary.

If the learner has a safe disposable fork and wants an optional observation, use a read-only workflow only. Do not enable “send write tokens” or introduce pull_request_target merely for this exercise.

9. Record the recursion boundary

The issue create/close actions are repository events caused by GITHUB_TOKEN. Current GitHub behavior generally prevents such events from recursively starting new workflows, subject to documented exceptions. Record this as a limitation: the checkpoint proves issue authorization, not event-chaining behavior.

10. Verification checklist

  • Exactly one disposable repository is targeted.
  • Top-level permissions: {} remains.
  • Read job has only contents: read.
  • Denied job has only issues: read and preserves 403.
  • Mutation job has only issues: write.
  • No token/header/context dump appears in logs.
  • Issue title contains run ID + attempt.
  • Returned issue number is independently verified.
  • Cleanup closes only that issue number.
  • No PAT/GitHub App secret/external action/cloud/deployment/self-hosted runner is introduced.

11. Cleanup / rollback

  1. Verify the synthetic issue is closed.
  2. Keep the closed issue and workflow runs until checkpoint review is complete; they are the audit trail.
  3. After review, delete the learner-owned disposable repository if desired. Do not attempt ambiguous “delete latest issue” cleanup.
  4. If a repository setting was changed manually outside this checkpoint, restore it explicitly; the mandatory lab itself requires no settings mutation.

12. What Chapter 06 adds to the production operating model

Chapter 06 adds the capability contract: every job has an explicit automation identity, trust context, permission set, resource boundary, token lifetime, API/action request set, expected denial/success behavior, side-effect guard, evidence record, and cleanup/compensation path. Authentication success is not authorization success, and a green job is not proof of the intended repository effect.

Chapter 07 builds on this foundation by separating secrets, environments, variables, redaction, and credential hygiene—where protected values come from and how they should be exposed to only the jobs that need them.

Next lesson

Secrets, Environments, Variables, Redaction, and Credential Hygiene

Move from the automatic repository token to explicit sensitive-value handling and protected environment boundaries.

Knowledge check

Why must the checkpoint keep the initial 403 after Revision B succeeds?

Why is the final mutation job limited to issues: write?

What is the cleanup guard?

Why does the checkpoint avoid a fork write test?

What production contract does Chapter 06 add?

Official references and version notes

Version and compatibility note

Version-sensitive token/permission/API behavior was rechecked against current primary GitHub documentation on 2026-09-09. Mandatory labs use a learner-owned disposable repository, ubuntu-24.04, built-in Bash/cURL/Python 3 only, and no third-party action, PAT, GitHub App private key, cloud credential, package, deployment, environment secret, self-hosted runner, or enterprise-only feature. At verification time, GitHub creates a repository-scoped GitHub App installation access token for each job; on GitHub-hosted runners its effective lifetime is bounded by the job (maximum hosted job duration 6 hours), and it is available through both secrets.GITHUB_TOKEN and github.token. Current workflow syntax supports explicit permissions at workflow or job scope; after any permission is specified, unspecified configurable scopes become none. Current configurable keys include actions, artifact-metadata, attestations, checks, code-quality, contents, deployments, id-token, issues, discussions, packages, pages, pull-requests, security-events, statuses, and vulnerability-alerts; re-check the live syntax before future generation because GitHub is continuously delivered. Permission calculation begins from enterprise/organization/repository defaults, is adjusted by workflow and then job configuration, and can be downgraded again for fork pull-request execution. Dependabot-triggered runs are treated with fork-like restrictions. The current REST API examples use X-GitHub-Api-Version: 2026-03-10. The lab never prints authorization headers or token values; it records endpoint, method, status, response request ID/permission hints, run ID/attempt, source SHA, and the exact disposable issue created/closed. The checkpoint mutation is deliberately limited to one synthetic issue in a learner-owned disposable repository. It preserves both the expected 403 and successful narrow-write evidence.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.