GitHub Actions / Reusable automation and scoped authority / Lesson 8
GITHUB_TOKEN permissions and secret scope
Give each job only the authority it needs and understand what secret redaction cannot protect.
On this page
Lesson goals
- Review workflow and job permission declarations.
- Separate repository, environment, and organization secret access.
The built-in token is still authority
GitHub supplies GITHUB_TOKEN for workflow authentication within its documented scope and lifetime. An action can access the token through github.token even when the workflow does not explicitly pass it as an input. Therefore not seeing a token argument does not prove an action has no repository authority. Parcel’s formatter check requires no issue creation or release publication. Set the narrow permissions that the job actually needs, review organization defaults, and remember that event-specific restrictions can further limit effective access.
Use workflow defaults and job exceptions
A restrictive workflow permission declaration gives a clear starting point, and a job can declare the specific capabilities required by its responsibility. When permission names are specified, unspecified permissions generally become none under the documented rules, with documented exceptions such as metadata behavior. Do not solve every API denial with write-all. Identify the operation, resource, token type, and effective event policy. A repository content read is different from package publication, pull-request modification, or requesting an OIDC identity token.
permissions:
contents: read
jobs:
inspect:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- run: printf "%s\n" "read-only fixture review"
The fragment grants no write capability in its declared permission set and performs no API request. Actual effective permissions depend on repository policy and event. It is not enabled and no token is obtained.
A secret’s location controls availability
Repository, organization, and environment secrets have different access and selection rules. Environment secrets are associated with jobs referencing that environment and its configured protections. Fork and other low-trust event behavior can withhold secrets, while reusable workflows need deliberate secret passing under their supported rules. A missing secret reference can yield an empty string, so validate presence without printing the value. Do not move a secret to a broader scope merely to make a failed job work before reviewing why the original boundary withheld it.
Redaction is a last visibility layer
Masking attempts to hide recognized secret values in logs; transformations, encoding, fragmentation, or artifact inclusion can bypass that visibility protection. Avoid printing full contexts or environments and avoid placing credentials in report files. A base64 representation is still sensitive, not encryption that makes disclosure safe. If a value is exposed, the credential owner must handle revocation or rotation through the approved process. Editing a log does not invalidate a copied token or erase an external action already performed with it.
Check permission requirements offline
The complete local model compares a declared task requirement with a granted capability set. It illustrates least-privilege review without reading a real token or querying an API. Real GitHub authorization also depends on resource ownership, token type, event restrictions, repository settings, and organization policy, so this set calculation cannot predict every API result. Use a denial to investigate those layers. Separate a report publication failure from application-test failure so adding report authority does not accidentally expand the entire validation job.
granted = {"contents:read"}
tasks = {"inspect-source": {"contents:read"}, "publish-release": {"contents:write"}}
for task, required in tasks.items():
print(task, required <= granted)
inspect-source True
publish-release False
The strings are synthetic capability labels, not token inspection output. No secret is created, printed, retrieved, or used.
Common errors and fixes
An action is assumed token-free because no token input is passed.
- Why it happens
- The github.token access path was overlooked.
- How to fix it
- Review action trust and effective job permissions.
- Prevent it next time
- Limit built-in token authority before invoking dependencies.
An API denial leads to write-all on every job.
- Why it happens
- The required operation and resource were not identified.
- How to fix it
- Grant the smallest capability to the responsible trusted job.
- Prevent it next time
- Keep a permission justification with each privileged task.
A base64-encoded credential is uploaded as harmless diagnostics.
- Why it happens
- Encoding was confused with confidentiality.
- How to fix it
- Remove the exposure and coordinate credential rotation with its owner.
- Prevent it next time
- Keep sensitive values out of logs and artifacts in every representation.
Check your understanding
1. A formatter job needs source read access and a separate job publishes a release. Should both have write permissions?
Show worked solution
No. Keep validation authority minimal and grant publication capability only to the trusted publication job after its required evidence and environment checks. Also review whether that job executes any untrusted source or dependency.
2. A fork test sees an empty secret value. Is broadening secret scope the first fix?
Show worked solution
No. Establish event trust and documented secret availability first. An untrusted test may correctly be denied that authority. Use a controlled fixture or separate trusted path rather than crossing the intended boundary for convenience.
Key takeaways
- No explicit token input does not mean no token authority.
- Secret scope, permissions, execution trust, and redaction are separate controls.