Skip to content

sec(2157): scope the add-to-kanban App token to least privilege - #533

Open
LukasWodka wants to merge 1 commit into
developfrom
sec/2157-add-to-kanban-least-privilege
Open

sec(2157): scope the add-to-kanban App token to least privilege#533
LukasWodka wants to merge 1 commit into
developfrom
sec/2157-add-to-kanban-least-privilege

Conversation

@LukasWodka

@LukasWodka LukasWodka commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Refs backend#2157. One of 18 — add-to-kanban.yml is a byte-compared copy, so least privilege cannot be applied to one repo without splitting the fleet.

Why this exists

Without any permission-*, the mint carried the App's full installation grant — contents + pull-requests write across every installed repo. And the App holds bypass_reviews on staging and prod fleet-wide, so the blast radius was merge past review, not merely write.

repositories: ${{ github.event.repository.name }}   # reads stop being org-wide
permission-issues: read                             # add-to-project must RESOLVE
permission-pull-requests: read                      #   the node before adding it
permission-organization-projects: write
permissions: {}                                     # the job needs no GITHUB_TOKEN

Byte-identical to backend's copy, deliberately

That copy is the one that survived review. @saadqbal caught that owner: narrows nothing — the run log says it verbatim, Input 'repositories' is not set. Creating token for all repositories owned by tracebloc — and @aptracebloc caught a run I had cited as evidence that was not one. Copying the corrected version instead of re-deriving it is the whole point of a byte-compared file.

Verified, not assumed

The open question was whether repositories: scoping clips organization_projects (an org-level permission). It does not: run 32255581084 on backend#2181's head exercised these exact scopes and landed the cardStatus=Code review. A landed card, not a passing mint, is the assertion.

The drift window

caller-drift compares copies against .github's, so findings rise as this sweep lands and reach 0 only when .github goes last. That is deliberate and is the same ordering the App migration used. Do not dispatch caller-drift remediation during the window — it would propose reverting these files.

Measured before starting: the fleet was split three ways, and the audit was already red at 2 findings (run 32268082510) — backend and frontend-app carried different partial fixes. frontend-app's was missing repositories: entirely, so it looked scoped and its reads were still org-wide.


Note

Low Risk
CI-only workflow change that reduces token scope; board behavior was validated on backend with the same scopes.

Overview
Tightens add-to-kanban so the release-train GitHub App installation token is no longer minted with the App’s full grant. The job now sets permissions: {} so the default GITHUB_TOKEN is unused, and the mint step adds repositories: (current repo only) plus explicit permission-issues, permission-pull-requests, and permission-organization-projects: write for actions/add-to-project.

Inline comments document why each knob exists (repo scoping vs owner:, required read scopes to resolve issue/PR nodes, and what was actually verified in CI vs assumed). This file is a byte-compared fleet copy aligned with backend’s reviewed version.

Reviewed by Cursor Bugbot for commit a807517. Bugbot is set up for automated code reviews on this repo. Configure here.

Brings this repo's copy to the fleet canonical form. `add-to-kanban.yml` is a
byte-compared copy, so this is one pass over every repo rather than a fix here.

WHAT CHANGES

  repositories: ${{ github.event.repository.name }}   the two content reads stop
                                                      being org-wide
  permission-issues: read                             add-to-project must RESOLVE
  permission-pull-requests: read                      the triggering node before
  permission-organization-projects: write              it can add it
  permissions: {}                                     the job needs no GITHUB_TOKEN

Without any `permission-*` the mint carried the App's FULL installation grant --
contents+PR write across every installed repo -- and the App holds bypass_reviews
on staging and prod fleet-wide, so the blast radius was merge-past-review rather
than merely write.

WHY THE FILE IS BYTE-IDENTICAL TO backend's. That copy is the one that survived
review: saadqbal caught that `owner:` narrows nothing ("Input 'repositories' is not
set. Creating token for all repositories owned by tracebloc"), and aptracebloc
caught a run cited as evidence that was not one. Copying the corrected version
rather than re-deriving it is the point of a byte-compared file.

VERIFIED, not assumed: run 32255581084 on backend#2181's head exercised these exact
scopes and landed the card (Status=Code review), which settles the one open
question -- `repositories:` scoping does not clip `organization_projects`.

Refs backend#2157.
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