This is the record of an abcd-managed repository

abcdBetav0.16.2 · 2026-11-06

Development record

A pinned-action bump syncs its scaffold template and lands re-authored

intentdraftsminoritd-2609301020001595

Typed links: builds_on itd-2609221842494980 (the shipped re-authoring of in-bound manifest bumps, whose bound leaves an Actions bump out); refines iss-209 (every pinned-action bump on a scaffolded workflow fails parity and is landed by a person); related_adrs adr-2609292116133348 (the owner authors and commits, the App only pushes, the bound, the residual, the Dependabot-secret placement).

Press Release

A dependabot bump of a pinned action in a scaffolded workflow carries its scaffold template with it and lands re-authored, so it goes green on its own. A read-only job checked out at the trusted base reads the bot branch's workflow pins as data and runs scaffold-sync from the base; a separate job pushes the template change with the GitHub App token, the person as author and committer, and refuses rather than fall back to GITHUB_TOKEN.

"Every action bump used to go red on the parity check, and the fix was always the same command run by hand," said a product thinker landing their third release-workflow bump of the week. "Now the template follows the pin on the bot's own branch, the commit is mine by the rule I already signed for, and a bump the workflow cannot prove safe is still left for me."

Why This Matters

abcd's own release.yml, auto-release.yml and dependency-reauthor.yml are rendered from the templates abcd launch scaffold lays in a managed repository, and TestSelfScaffoldParity holds each committed workflow byte-identical to its rendering. Dependabot's github-actions ecosystem edits only the committed workflow, never the template under internal/, so every pinned-action bump to one of those files fails parity by construction and can never go green on its own (iss-209, first seen on PR #211). The manual half ships: make scaffold-sync carries the workflow pins into the templates, and TestSyncRepoPinsIsCleanToday names that command when a bump lands on a workflow alone. What is left is a person running it and re-authoring the result on every such bump, and a permanently red bot pull request that trains the reader to discount red CI on exactly the changes where CI matters most.

The shipped re-authoring (itd-2609221842494980, under adr-2609292116133348) does not reach these bumps: its bound is a manifest and its lock file, and a workflow file is never a manifest, so the ADR's own consequences leave an Actions bump to a person. The product thinker's ruling M3 (2026-09-23) asked for this half to be automated as its own intent with a security review. Rulings H2 and CG1 settle the credential it pushes with: a GitHub App the person creates and installs, the person as the landed commit's author, and the token minted in shell as the shipped workflow already does.

Mechanism

We expect the bump to go green with no person in the loop because the one edit parity is missing is mechanical and already computed by a shipped tool (scaffold-sync carries a pin from workflow to template and touches nothing else), and because running that tool from the trusted base over the bot's pins read as data keeps the untrusted tree away from the write token; shown wrong if a synced branch still fails TestSelfScaffoldParity, if any step running under the App token executes a file from the bot's branch, or if a bump that edits anything beyond a pin is synced rather than left alone.

Scope Conditions

  • Holds for github-actions bumps dependabot opens against a workflow that scaffold-sync pairs with a template (release.yml, auto-release.yml and dependency-reauthor.yml in abcd today); a bump to any other workflow needs no template change and is out of scope.
  • Holds where the bump's diff changes only pinned uses: lines, the shape scaffold-sync propagates; any other edit to a workflow is a person's to land.
  • Holds where the person has created and installed the GitHub App and stored its credentials, and set the owner in the re-authoring declaration (H2); until then the workflow refuses by name and the manual make scaffold-sync stays the route.
  • Holds under adr-2609292116133348's authorship shape: the person is author and committer, the App only pushes, and the attribution gate is unchanged.

What's In Scope

  • The compute job, read-only: checked out at the pull request's trusted base with persist-credentials: false and no write permission; it reads the bot branch's workflow files as data (their bytes, never a checkout that executes them), lays the pins over the base's workflows, runs the base's scaffold-sync, and hands the resulting template change on as an artefact. No code from the bot's branch runs in any job.
  • The push job, separate: applies that template change onto the bot's branch and pushes it with the App token (H2, minted and revoked in shell per CG1), under a concurrency key that keeps the push-derived and pull-request-derived runs of one branch from cancelling each other (iss-209 names workflow_run.event in the key, which applies if the trigger is workflow_run; see Open Questions).
  • The authorship: the landed commits carry the person as author and committer, a message naming the bot, its commit and the workflow, and Assisted-by: None; the App appears in neither identity field, so scripts/check-attribution.sh is not edited.
  • Refusal, never a fallback: while the owner or either App credential is missing, or the bump fails any clause of the bound, the workflow refuses and names the clause; it never pushes with GITHUB_TOKEN and never keeps the bot as author.
  • The bound, on the record: widening adr-2609292116133348's bound to take in a pinned-action bump is a change to that record and to brief invariant 20 that cites it, made in the same change as the workflow.
  • The scaffold: whatever abcd's own repository runs, a managed repository that opted in to the re-authoring receives through abcd launch scaffold, held byte-identical by the parity test.
  • Security review before it merges (M3): the workflow writes to a bot's branch under a repository credential and asserts the person's authorship.

What's Out of Scope

  • Any change to the attribution gate's rules or refusals.
  • Bumps to workflows scaffold-sync does not pair with a template, and every ecosystem other than github-actions (the shipped re-authoring owns the manifest bumps).
  • Creating or installing the GitHub App and storing its credentials: the person's act (H2), owed under iss-2609292030070275.
  • Merging the bump, or judging what the new action version does.

Acceptance Criteria

  • Given a dependabot github-actions bump that changes only pinned uses: lines in a workflow scaffold-sync pairs with a template, when the workflow runs, then the template change lands on the bot's branch as the person, and TestSelfScaffoldParity and TestSyncRepoPinsIsCleanToday are green on the synced branch.
  • Given that workflow, when zizmor runs over it at the regular persona, then it reports no findings.
  • Given any job that holds the App token, when its steps are read, then none executes a file taken from the bot's branch: the bot's workflow files enter only as data read by code from the trusted base.
  • Given a bump that edits anything beyond pinned uses: lines, touches a workflow with no template, or comes from an undeclared bot or a person, when the workflow runs, then nothing is pushed and the run names the clause that failed.
  • Given the owner unset or an App credential absent, when an in-bound bump arrives, then the run refuses by name, nothing is pushed, and no step uses GITHUB_TOKEN to push.
  • Given a landed commit, when the attribution gate reads it, then the person is author and committer, the message names the bot and the workflow, it carries Assisted-by: None, and the gate's script is unchanged.
  • Given the intent is proposed for shipping, when its record is read, then it carries a security review of the workflow and its script (M3), and adr-2609292116133348 or its successor records the widened bound.

SOTA

Surveyed on 2026-10-23; sources, evidence tiers and the local fit risks are in the forge, CI and merge SOTA note.

  • The two-workflow split is the documented pattern, and the artefact boundary is the seam: GitHub Security Lab's "preventing pwn requests" runs untrusted code under pull_request, hands results on as artefacts that "are themselves untrusted", and lets a privileged workflow_run workflow consume them as data; GitHub's hardening guide says such workflows "must not explicitly check out untrusted code".
  • zizmor flags the privileged trigger regardless: dangerous-triggers covers pull_request_target, workflow_run and (since v1.31.0) issue_comment; artipacked wants persist-credentials: false. A finding on a deliberate privileged trigger is waived on the record, not designed away.
  • The credential's shape: actions/create-github-app-token mints a one-hour installation token, revokes it in its post step by default, and narrows it with permission-<name> inputs; the intent mints and revokes in shell per CG1, so the action is cited for the lifetime and narrowing the shell mint must match, not as a proposal to adopt it. GitHub states that apart from workflow_dispatch and repository_dispatch, "other GITHUB_TOKEN-triggered events do not create workflow runs at all", which is iss-209's dead end in the forge's words.
  • Where the secrets are decides the trigger, and one case is undocumented: "When a Dependabot event triggers a workflow, the only secrets available to the workflow are Dependabot secrets", with a read-only GITHUB_TOKEN; the same page names pull_request_target as its two-step route and never mentions workflow_run. Whether a workflow_run started by a Dependabot-triggered run counts as a Dependabot event (Dependabot secrets, read-only token) is undocumented in the pages read and is settled by a small experiment before the trigger is chosen (prefer the experiment); pull_request_target is rejected because zizmor and the Security Lab both name it as the dangerous trigger. Dependabot's github-actions ecosystem reaches .github/workflows and a root action.yml only.
  • Renovate reaches the template but lands as a bot: its regex custom manager matches any file through managerFilePatterns, so a .tmpl under internal/ is updatable in the same pull request; run as a GitHub App with platformCommit (the default on GitHub), "GitHub determines the git author string" and gitAuthor is ignored. So a hosted Renovate bump is bot-authored and still needs this repository's re-authoring; only a self-hosted Renovate with gitAuthor set to the person avoids it, at the cost of a new tool, runner and credential.

Path 2: a native workflow shaped to the Security Lab pattern, with the template diff crossing the artefact boundary as data and the push job holding a one-hour App token narrowed to contents: write and workflows: write, because the replayed commit touches .github/workflows/ and GitHub lists the Workflows permission as an additional permission on the refs and contents endpoints, so a push with contents: write alone is refused. Renovate stays the structural alternative the product thinker may still choose; it needs explicit sign-off as a new tool and app and does not remove the re-authoring step. The independent fit-challenge runs at plan time.

Open Questions

  • One workflow or two: extend the shipped dependency-reauthor.yml, which runs on pull_request in one job and reads Dependabot secrets, or add a second workflow. The answer sets the trigger (pull_request or workflow_run), whether the compute and push jobs are new jobs beside the shipped one, which secret store the App credentials must sit in for that trigger (to be confirmed against the platform's documentation), and so which concurrency key applies.
  • One commit or two on the bot's branch: the shipped bound admits exactly one bot commit; the pin half needs the bot's workflow edit re-authored as the person and the template change beside it, as one replayed commit carrying both or as two.
  • Renovate instead: Renovate's regex custom manager can update a pin in the template in the same pull request as the workflow, the simpler structural fix recorded in iss-209's remedy grounds and rejected for now because it is a new tool and app that needs the person's sign-off. Put to the person here: adopt it, or build the workflow above.
  • (SOTA 2026-10-23) If Renovate is chosen, who re-authors its bot-authored commit: the hosted app commits as the App (platformCommit), so the shipped re-authoring would still run, with the bot's name added to its declared set.
  • (SOTA 2026-10-23) Which zizmor finding the privileged trigger raises (dangerous-triggers on workflow_run) and where its waiver is recorded, given the acceptance criterion asks for no findings at the regular persona.

Audit Notes

Empty. Populated by intent-auditor when intent moves to shipped/.

Frontmatter

iditd-2609301020001595
typeintent
lifecycledrafts
severityminor

References2

Referenced by3

mentions1