Repository navigation
Check affect of changing default workflow permissions to match GitHub's security recommendations #8178
Description
Activity
- addedrole: back end/devOpsTasks for back-end developersTasks for back-end developersFeature: Refactor GHARefactoring GitHub actions to fit latest architectural normsRefactoring GitHub actions to fit latest architectural normssize: 5ptCan be done in 19-30 hoursCan be done in 19-30 hoursLang: GHAGitHub ActionsGitHub Actions
on Jun 10, 2025 - moved this to New Issue Approval in P: HfLA Website: Project Board
on Jun 10, 2025 - addedDraftIssue is still in the process of being createdIssue is still in the process of being created
on Jun 10, 2025 Hi @t-will-gillis, thank you for taking up this issue! Hfla appreciates you :)
Do let fellow developers know about your:-
i. Availability: (When are you available to work on the issue/answer questions other programmers might have about your issue?)
ii. ETA: (When do you expect this issue to be completed?)You're awesome!
P.S. - You may not take up another issue until this issue gets merged (or closed). Thanks again :)
Tokens, Secrets, Scopes, & Permissions
Explanation of terms
- Tokens
- Tokens are credentials used to authenticate the actor (here 'actor' refers to the user, workflow, or bot) when that actor interacts with GitHub's REST API or GraphQL.
- In addition to authenticating the actor, tokens are granted specific, defined 'scopes' - more about this in "Scopes" following.
- Examples include the
GITHUB_TOKENand Personal Access Tokens (PATs). PATs themselves are either 'classic' and have broad scopes, or are 'fine-grained' and have targeted scopes. HfLA currently uses the classic PATs, though GitHub recommends that we transition to using fine-grained PATs for better security through "Least Privilege". - ⚠Important note: the
GITHUB_TOKENalways exists by default in a workflow unless it is preempted bypermissions:(see "Permissions" following) or by a PAT.
- Secrets
- Secrets are encrypted values stored in GitHub at the repo level. (They can also be stored at the org or environment levels)
- Secrets securely hold the value of the Tokens (or other credentials) so we don't expose sensitive info in the workflow by hardcoding the token itself.
- Scopes
- Scopes represent the capabilities granted to the Token that limit what the token is permitted to do.
- 'Classic' tokens use broad scopes while 'fine-grained' tokens use narrow, targeted scopes.
- For example, a token might have a 'classic' scope of
read:org, which means that a workflow using this token (via a secret) is given a specific permission. In the case of theread:orgscope, the workflow is permitted to "Read org and team membership, read org projects".
- Permissions
- Permissions are temporary restrictions applied specifically to the
GITHUB_TOKEN. - They can be set at the overall level or at the job level by adding
permissions:to the workflow's YAML.
- Permissions are temporary restrictions applied specifically to the
To summarize then:
- A "Token" is the credential (e.g.,
GITHUB_TOKENor a PAT). - A "Secret" is how you store and inject a token into your workflow securely.
- "Scopes" define the potential reach of the token (what it could ever do).
- "Permissions" are the actual allowed actions that the
GITHUB_TOKENis permitted at different stages of the workflow.
Additional notes
- Each step of a workflow has at a minimum the permissions given by the default
GITHUB_TOKEN. These permissions apply by default unless overridden or disabled in the workflow itself. - These default permissions are defined in the "Workflow permissions" section on the /settings/actions page in the master account, and include either:
- a.) read and write permissions in the repo for all scopes supported by the
GITHUB_TOKEN, or - b.) read permissions in the repo for
contentsandpackages. - ⚠GitHub recommends using read permissions as the default, and then granting explicit permissions as needed in the workflows.
- a.) read and write permissions in the repo for all scopes supported by the
- Certain workflow activities (such as pulling teams information, querying project info, and doing GraphQL mutations) require additional permissions/ scopes that cannot be granted to the default
GITHUB_TOKENusing thepermissions:entry in the workflow's YML. In these cases, each workflow step doing one of these activities must explicitly declare a PAT that is scoped with 'extended' permissions. - The fine-grained PATs are preferable over the broadly-defined 'classic' PATs; however, there may be some API calls that are not covered by the fine-grained tokens.
- The default "Workflow permissions" defined for the
GITHUB_TOKENapply if no other permissions are declared. Otherwise,permissions:defined at the workflow level override the default repo/org defaults,permissions:defined at the workflow level preempt those at the workflow level (or the default repo/org), and PATs referenced at the step level preempt all others. - These permissions are not additive. Whatever is declared in the governing permissions: (i.e. default, workflow level, job level, step level) are the exact permissions that apply. (Example: if workflow says
permissions: contents: read, then during that workflow run theGITHUB_TOKENwill only havecontents:readpermission, even if the default was broader. - ⚠It is not required to specific add
github-token: ${{ secrets.github.token }}to a step- but this can be done for clarity and/or to avoid ambiguity if a PAT is not used. - It is not required to add workflow- or job-level
permissions:if these permissions only repeat the default "Workflow permissions"- but again explicitly stating these permissions can be done for clarity and/or to avoid ambiguity. (And arguably should be done for self-documentation) - In the near future we will change the repo-wide
GITHUB_TOKENpermissions to read only.- ⚠ Until then, we will not change the default "Workflow permissions" setting. Instead for every workflow we only need to state at the workflow-level:
permissions: contents: read. This statement disables the write permission by omitting it.
- ⚠ Until then, we will not change the default "Workflow permissions" setting. Instead for every workflow we only need to state at the workflow-level:
- Tokens
5 remaining items
Workflow
permissions:auditAlready has a
permissions:blockactivity-trigger.yml— no change neededpermissions: contents: read issues: write pull-requests: write
codeql-scan-job.yml— no change needed (set at job level)permissions: actions: read contents: read security-events: write
Needs a
permissions:block addedadd-update-label-weekly.ymlpermissions: contents: read
All meaningful operations use a GitHub App token. Default token is only exposed at checkout.
check-closed-issue-for-linked-pr.ymlpermissions: contents: read issues: read pull-requests: read
Default token runs
check-issue-labels-and-linked-prs.js, which calls the GraphQL API viaclosedByPullRequestsReferenceson an Issue — reads issue and linked PR data only. Reopen, label, and comment operations useHACKFORLA_GRAPHQL_TOKEN.
codeql-create-issues.ymlpermissions: contents: read security-events: read issues: read
GITHUB_TOKENis used forGET /repos/{owner}/{repo}/code-scanning/alerts(security-events: read) andGET /search/issues(issues: read). Issue creation usesHACKFORLA_ADMIN_TOKEN.
codeql.ymlpermissions: actions: read contents: read security-events: write
Delegates entirely to
codeql-scan-job.ymlviauses:. The caller's token is what the reusable workflow receives — it must grant at least what the reusable workflow needs.
flag-issues-unlabeled-after-deletion.ymlpermissions: contents: read
All operations use
HACKFORLA_GRAPHQL_TOKENandHACKFORLA_BOT_PA_TOKEN. Default token is only exposed at checkout.
issue-trigger.ymlpermissions: contents: read issues: write
Default token is used by:
check-labels.js—issues.setLabelspost-labels-comment.js—issues.createCommentadd-feature-branch-comment.js—issues.createCommenthide-feature-branch-comment.js— GraphQLminimizeCommentmutation
All require
issues: write.check-label-preliminary-update.jsmakes no API calls (reads only from the event context payload). Team membership check and preliminary comment useHACKFORLA_GRAPHQL_TOKEN.
lint-scss.ymlpermissions: contents: read statuses: write
GITHUB_TOKENis passed to super-linter v4, which posts commit status checks. If PR-level annotations fail in future,checks: writemay also be required.
move-closed-issues.yamlpermissions: contents: read
Default token runs
sort-closed-issues.js, which reads only from the event context payload — no API calls. Project card operations useHACKFORLA_GRAPHQL_TOKEN.
pr-instructions.ymlpermissions: contents: read pull-requests: read
Default token runs
create-instruction.js, which callspulls.listFilesto determine which files were modified. No writes — the instruction is saved to an artifact.
pr-verification.ymlpermissions: contents: read
All verification logic uses
HACKFORLA_ADMIN_TOKEN. Default token is only exposed at checkout.
pull-request-trigger.ymlpermissions: contents: read issues: write
Default token runs
check-linked-issue.js, which callsissues.getto verify the linked issue exists and conditionally callsissues.createComment(viapost-issue-comment.js) to notify the PR author. Both use the Issues API;issues: writecovers both.
schedule-daily-1100.ymlpermissions: contents: read
GITHUB_TOKENis passed asenv.tokentoget-project-data.js, which reads public data only: repo search, repo languages, commit contributors, issue comment contributors. Push operations useHACKFORLA_BOT_PA_TOKENvia checkout.
schedule-monthly.ymlpermissions: contents: read
All operations (checkout, contributor queries, team membership changes, auto-commit) use
HACKFORLA_ADMIN_TOKEN. Default token has no meaningful exposure.
set-pr-labels.yamlpermissions: contents: read issues: read
Default token runs
listIssuesFromPRBody(no API calls — parsescontext.payload.pull_request.body) andlistLabelsFromIssues(issues.listLabelsOnIssue). Labels are written to an artifact, not to the repo.
update-label-directory.ymlpermissions: contents: read
All operations (label directory update, Google Sheets POST, auto-commit) use
HACKFORLA_BOT_PA_TOKEN. Default token has no meaningful exposure.
vrms-data.ymlpermissions: contents: read
Data is fetched from
vrms.iovia curl — no GitHub API calls use the default token. Push usesHACKFORLA_BOT_PA_TOKENvia checkout.
wr-pr-instructions.ymlpermissions: contents: read actions: read issues: write
Default token calls
actions.listWorkflowRunArtifactsandactions.downloadArtifactto retrieve the artifact from the triggering workflow run (actions: read), then callsissues.createComment(viapost-issue-comment.js) to post the PR instructions (issues: write).
wr-pull-request-trigger.ymlpermissions: contents: read
Only echoes a string. No GitHub API calls.
wr-schedule-monthly.ymlpermissions: contents: read issues: read
Default token calls
issues.listForRepoto retrieve the latest issue number (issues: read). Issue creation and closing both useHACKFORLA_BOT_PA_TOKEN.
wr-set-pr-labels.yamlpermissions: contents: read actions: read issues: write
Default token calls
actions.listWorkflowRunArtifactsandactions.downloadArtifact(actions: read), then callsissues.setLabelson the PR number (issues: write— GitHub's Labels API uses the Issues endpoint for both issues and PRs).
Secondary flags (PAT scopes)
admin:org_hookonHACKFORLA_BOT_PA_TOKENandHACKFORLA_ADMIN_TOKEN— No workflow creates, updates, or deletes webhooks. This scope appears unused on both tokens and can likely be removed after testing.repovspublic_repoonHACKFORLA_GRAPHQL_TOKEN—repogrants full access including private repos. If all targeted repos are public, this could be narrowed topublic_repo. Requires testing to confirm no operation depends on the broader scope.pr-verification.yml—pull_request_targetwithout a repository guard — This is the onlypull_request_targetworkflow and has noif: github.repository == 'hackforla/website'guard. Practical risk is low (HACKFORLA_ADMIN_TOKENis not available in fork secret stores), but the pattern is inconsistent with other workflows.- added sub-issues
on Mar 28, 2026
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsIn progress (actively working)
Overview
We need to change the permissions for the default
GITHUB_TOKENfrom read/write to read only per GitHub's recommendation for security best practice.Details
Before proceeding, read the explainer below.
We need to audit each of our workflows to identify exactly what permissions are needed at each level of the workflow, i.e. overall, job-level, and step-level.
This issue has three objectives:
GITHUB_TOKEN.TODO
Action Items
check-closed-issue-for-linked-pr.yml#8579codeql-create-issues.yml#8580codeql.yml#8581flag-issues-unlabeled-after-deletion.yml#8582lint-scss.yml#8583move-closed-issues.yaml#8584pr-verification.yml#8585schedule-daily-1100.yml#8586update-label-directory.ymlandflag-issues-unlabeled-after-deletion.yml#8587vrms-data.yml#8588pr-instructions.ymlandwr-pr-instructions.yml#8589pull-request-trigger.ymlandwr-pull-request-trigger.yml#8590schedule-monthly.ymlandwr-schedule-monthly.yml#8591set-pr-labels.yamlandwr-set-pr-labels.yaml#8592Resources/Instructions