Skip to content

feat(2036): add-to-kanban authenticates as the App — the last of 19 - #284

Merged
LukasWodka merged 1 commit into
developfrom
feat/2036-add-to-kanban-app
Aug 19, 2026
Merged

feat(2036): add-to-kanban authenticates as the App — the last of 19#284
LukasWodka merged 1 commit into
developfrom
feat/2036-add-to-kanban-app

Conversation

@LukasWodka

@LukasWodkaLukasWodka commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Refs backend#2036. The last of 19. Board writes stop running on one human's PAT.

Why this repo went last

add-to-kanban.yml is a byte-compared copy, and .github's copy is the source of truth caller-drift measures every other repo against. Migrating this one first would have made all 18 others read as drifted the moment the contract changed and before any of them could follow. Migrating it last puts the drift window on develop, where it is a finding against work already in flight rather than against the fleet's main.

The window was real, and it is measured

Dispatched the audit read-only just now — run 32227732563:

17 repo-conformance drift finding(s).
- averaging-service: add-to-kanban.yml has DRIFTED from the copy in
tracebloc/.github (blob 07283e30b80b vs 603751a070de). It is a copy,
not a caller, so nothing else would ever notice.
… 16 more, byte-for-byte the same finding

Its last scheduled run was 2026-08-18 07:18Z — before most of the sweep merged — so the fleet has been red for about a day with nothing reporting it. That is the cost of the ordering, and it was the cheaper of the two costs.

This commit closes all 17 at once.release-train is correctly absent from that list: its copy is divergent in the inventory with a written reason (a repo-specific header, and it carries no Dependabot config), so it was never compared.

Byte-identity by construction, not transcription

The file is the canonical fleet blob copied verbatim, then verified:

$ git hash-object .github/workflows/add-to-kanban.yml
07283e30b80b… # fleet blob (17 repos): 07283e30b80b… MATCH

Worth doing that way round rather than hand-editing: the earlier sweep PRs each had their transform applied independently, and a copy guard that compares bytes will reject a single stray space. Checking the hash is one command; discovering the mismatch from an audit run a day later is not.

What this does NOT do

No permission-* downscoping. saadqbal's finding is real and is filed as backend#2157 — every mint added by this sweep requests the App's full permission set. It is deliberately not fixed here, for two reasons:

  1. A byte-compared copy cannot be fixed piecemeal. Downscoping only this one re-opens the same 18-way drift the PR is closing.
  2. Downscoping does not close the escalation vector as described. RELEASE_TRAIN_APP_ID and ..._PRIVATE_KEY are org secrets at visibility=all, and a pull_request run uses the workflow file from the PR head — so branch-push access is enough to mint a token requesting whatever it likes. #2157 keeps least-privilege (real defence-in-depth: it bounds a compromised action or a bad pin) separate from the posture question, so closing the cheap half doesn't read as closing the expensive one.

Verification

make check: 41 selftests, green · blob hash matches the fleet · the audit's 17 findings are all this one file, so this is the whole remaining set.


Note

Low Risk
Single workflow auth swap for project board writes; behavior unchanged aside from credential source, with explicit failure if App secrets are missing.

Overview
Kanban automation no longer uses PROJECTS_KANBAN_TOKEN for org project writes. It mints an org-scoped installation token for the tracebloc-release-train GitHub App (actions/create-github-app-token with owner: ${{ github.repository_owner }}), then passes that token to actions/add-to-project.

Inline comments document no PAT fallback (so a broken App path fails visibly) and that Dependabot-triggered runs need the same App secrets in Dependabot’s secret scope.

This is the canonicaladd-to-kanban.yml copy the fleet audits for byte drift; aligning it closes the remaining drift findings from the backend#2036 sweep.

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

The last of 19. Board writes stop running on one human's PAT.
WHY THIS REPO WENT LAST. add-to-kanban.yml is a byte-compared COPY and
.github's own copy is the source of truth caller-drift measures every other
repo against. Migrating this one first would have made all 18 others read as
drifted the moment the contract changed and before any of them could follow;
migrating it last puts the drift window on develop, where it is a finding
against work already in flight rather than against the fleet's main.
The window is real and it is measured, not assumed. Read-only audit run
32227732563, dispatched just now:
17 repo-conformance drift finding(s).
- averaging-service: add-to-kanban.yml has DRIFTED from the copy in
tracebloc/.github (blob 07283e3 vs 603751a).
... 16 more, all the same finding
This commit closes all 17 at once. release-train is correctly absent from that
list -- its copy is `divergent` in the inventory with a written reason, so it
was never compared.
Byte-identical to the fleet by construction rather than by transcription: the
file is the canonical blob copied verbatim, verified 07283e3 == 07283e3.
Two known follow-ups, both already filed, so neither is discovered later:
backend#2157 every mint added by this sweep requests the App's full
permission set. It has to be one pass over all 19, since a
byte-compared copy cannot be fixed piecemeal -- which is why
this PR does NOT downscope. Raised by saadqbal.
backend#2164 unrelated to this file; listed only because it is the other
open item from the same review pass.
make check: 41 selftests, green.
Refs backend#2036.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@saadqbalsaadqbal left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Byte-for-byte the fleet blob — same 603751a → 07283e3 pair as cli#525 and backend#2116, with the same secret names, the same owner: input and the same trigger. Also checked: the action SHA really is what v3.2.0 points at, the trigger is pull_request (not pull_request_target), there are no run: blocks at all so no github.event.* reaches a shell, the token only ever flows into add-to-project's input, and a failed mint fails the job rather than no-opping. And add-to-kanban.yml is a copies entry in repo-inventory.yml, not a reusable — no uses: tracebloc/.github/... callers anywhere, so nothing downstream sees this. Approving.

One thing worth sequencing before this lands: backend#2181 is open and adds three permission-* lines to backend's copy of this exact file. It's green now — it needed permission-issues: read and permission-pull-requests: read alongside the projects write, or add-to-project can't resolve the node it's adding. The moment it merges, backend drifts from the canonical this PR is establishing and one of the 17 findings you're closing re-opens. Either land those three lines across all 19 in one go, or mark backend divergent in repo-inventory.yml until the rest follow.

Nits, none blocking:

  • caller-drift's push paths don't include add-to-kanban.yml, so merging this won't re-run the audit. Worth a manual dispatch afterwards to show 17 → 0 rather than waiting for Monday's cron.
  • The added comment says this workflow fires on Dependabot PRs. True for the fleet, but this repo has no dependabot.yml and has never had a Dependabot PR. Byte-identity wins so leave it as-is — just worth knowing a future reader shouldn't take it as evidence about this repo's secret scope.
  • workflows/add-to-kanban.yml at the repo root (not .github/workflows/, not workflow-templates/) is a dead copy still on actions/add-to-project@v1.0.2 and PROJECTS_KANBAN_TOKEN. GitHub never runs it and nothing references it, but after this it's the last PAT-to-the-board reference in the repo. Worth deleting separately.
  • app-id carries deprecationMessage: "Use 'client-id' instead." at the pinned SHA, so every run warns. Fleet-wide (8 other mints in this repo too), not this PR's job.

On #2157: you're right that downscoping doesn't close the head-rewrite vector. pull_request_target would — this job never checks out PR code, it only reads event metadata, so taking the workflow file from the base branch does the same work. Worth putting on that thread as the actual remediation.

@LukasWodka
LukasWodka merged commit 7db97fc into developAug 19, 2026
12 checks passed
@LukasWodka
LukasWodka deleted the feat/2036-add-to-kanban-app branch August 19, 2026 12:08

@aptraceblocaptracebloc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving. The code is correct and I verified the central claims rather than reading them.

Byte-identity is empirically confirmed. PR head blob is 07283e30b80bddab05300d77b1f59cf719ac1b38; fetching add-to-kanban.yml from averaging-service, backend, frontend-app and tracebloc-engine on develop returns that exact sha. The "17" is right too: 18 required inventory entries minus .github itself, with release-train correctly excluded as divergent. Both action pins resolve to their claimed tags (bcd2ba49 = create-github-app-token v3.2.0, 5afcf98f = add-to-project v2.0.0).

The mint is proven working on this exact head. Because pull_request runs the workflow from the PR head, the add-to-project check is this new file executing — run 32228094633 shows the mint and the board write both green. No PAT fallback, no continue-on-error, so missing App secrets fail red. Trigger is pull_request (not pull_request_target) on a public repo with 0 forks, and GitHub withholds secrets from fork pull_request runs — so no fork exposure path. There is no run: block at all, so nothing can be spliced into a shell, and no permissions: block was widened.

Merge order is load-bearing

Blobs in play: A = 603751a0 (PAT, current .github/develop), B = 07283e30 (this PR's output, and all 17 copies today), C = 7830f7e0 (backend#2181's current output).

  • This PR first: canonical B, 17 copies B0 findings, fleet green. Then #2181 lands → 1 finding, which is its own deliberate validation window.
  • #2181 first: canonical still Astill 17 findings, one with a new reason.

So this merges before tracebloc/backend#2181. Worth flagging explicitly because #2181's body says "the .github canonical … merges last" — that refers to a laterBC change which has no PR yet, not to this one. Reading it as "hold #284" inverts the correct order and keeps all 17 findings red for nothing.

One caution for the window after #2181 lands and before the fan-out completes: caller-drift's create-prs dispatch would propose rewriting backend's file back to canonical, reverting the least-privilege change. Worth not dispatching remediation until the fan-out is done.

Non-blocking

  • "The last of 19" is stale on arrival — backend#2166 already plans a 20th change to this same file (BC), and its first step is open as #2181. Also worth knowing this PR does not retire the PAT: caller-drift.yml:166,178,231 still uses PROJECTS_KANBAN_TOKEN live, for a documented reason. "Last of 19" ≠ "the PAT is gone."
  • The mint requests the App's full installation grant. Deferring is right — fixing it here re-opens 18-way drift, and set-pr-status.yml and kanban-reconcile.yml carry the identical unscoped mint, so it's pre-existing fleet posture rather than something introduced here. Being fixed in #2181 / #2166.
  • GITHUB_TOKEN is now unused by every step, so permissions: {} would be ideal — byte-identity-blocked, belongs in the fan-out.
  • The comment says the workflow "also fires on DEPENDABOT PRs", but .github has no dependabot config at any path. Inherent to the byte-identical copy model and accurate for the repos that do have one; noting it only so it isn't mistaken for a live case here.

One correction to something I wrote on #2181: I said a typo'd permission-* input is silently ignored, leaving full scope. @saadqbal established that's wrong — the action reads raw INPUT_PERMISSION-* env vars, so a misspelling 422s the mint. The fan-out does not need that spot-check.

— drafted with Claude Code

@LukasWodka

Copy link
Copy Markdown
ContributorAuthor

/fr-pass

Best-effort FR passed (triage; behavioral evidence limited while e2e journey red — backend#2206). Advancing to Ready for prod.

Sign up for freeto 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.

3 participants

@LukasWodka@saadqbal@aptracebloc