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.
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
-
The read job can authenticate and read
README.mdat the run's exact source SHA. - The read job cannot be assumed to mutate issues merely because authentication succeeded.
- The denied job's POST issue request will return 403 and create no issue.
-
The mutation job with
issues: writewill receive 201 and create exactly one issue. -
The created issue author may appear as
github-actions[bot], whilegithub.actoridentifies the workflow initiator. - 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: readdeclaration and 200 response; - denied job
issues: readdeclaration; - POST
/repos/OWNER/REPO/issuesstatus 403; -
X-GitHub-Request-Id,X-Accepted-GitHub-Permissionsand rate-limit headers when present; - proof no issue with the synthetic denied title exists.
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: readand 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
- Verify the synthetic issue is closed.
- Keep the closed issue and workflow runs until checkpoint review is complete; they are the audit trail.
- After review, delete the learner-owned disposable repository if desired. Do not attempt ambiguous “delete latest issue” cleanup.
- 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.
Knowledge check
Why must the checkpoint keep the initial 403 after Revision B succeeds?
It is the clean evidence that under-privileged authority was denied and that the later narrow permission change was causal.
Why is the final mutation job limited to
issues: write?
The only repository side effect is create/verify/close of one issue; unrelated write capabilities are unnecessary.
What is the cleanup guard?
Use the exact issue number returned by the create response and close only that resource; never select “latest issue” ambiguously.
Why does the checkpoint avoid a fork write test?
Core learning does not require granting write authority to untrusted fork execution; the safer mandatory path is a trusted disposable manual dispatch plus architecture analysis.
What production contract does Chapter 06 add?
A capability contract covering identity, trust context, permissions, resource boundary, token lifetime, requests, effects, evidence and cleanup.
Official references and version notes
- GITHUB_TOKEN concept — current token issuance, repository scope, lifetime, and workflow-trigger behavior.
- Use GITHUB_TOKEN for authentication in workflows — current examples for authenticating API/action requests and minimizing token permissions.
-
Workflow syntax — permissions
— current permission keys, workflow/job scope, calculation order,
fork adjustment, and
permissions: {}behavior. - Managing GitHub Actions settings for a repository — current repository default workflow-permission settings and organization inheritance.
- Authenticating to the REST API — current bearer authentication and versioned REST request guidance.
-
REST API — issues
— current requirements for reading/creating/updating issues; issue
creation requires Issues repository permission
writefor fine-grained/App-style tokens. -
Troubleshooting the REST API
— current 401/403/404 diagnostics and
X-Accepted-GitHub-Permissionsguidance. -
Triggering a workflow
— current recursion prevention when events are created with
GITHUB_TOKENand documented exceptions. - Events that trigger workflows — current fork pull-request token/secrets restrictions and event trust boundaries.
- Dependabot on GitHub Actions — current read-only token/secrets restrictions for Dependabot-triggered runs.
-
Secure use reference
— least-privilege guidance, untrusted-input boundaries, and the
fact that actions can access
github.token. -
About authentication to GitHub
— current recommendation to prefer built-in
GITHUB_TOKENin Actions and GitHub Apps for broader/cross-repository automation.
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.