Development record
A pinned-action bump syncs its scaffold template and lands re-authored
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-syncpairs with a template (release.yml,auto-release.ymlanddependency-reauthor.ymlin 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 shapescaffold-syncpropagates; 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-syncstays 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: falseand 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'sscaffold-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.eventin the key, which applies if the trigger isworkflow_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, soscripts/check-attribution.shis 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_TOKENand 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-syncdoes 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 workflowscaffold-syncpairs with a template, when the workflow runs, then the template change lands on the bot's branch as the person, andTestSelfScaffoldParityandTestSyncRepoPinsIsCleanTodayare 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_TOKENto 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 privilegedworkflow_runworkflow 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-triggerscoverspull_request_target,workflow_runand (since v1.31.0)issue_comment;artipackedwantspersist-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-tokenmints a one-hour installation token, revokes it in its post step by default, and narrows it withpermission-<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 fromworkflow_dispatchandrepository_dispatch, "otherGITHUB_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 namespull_request_targetas its two-step route and never mentionsworkflow_run. Whether aworkflow_runstarted 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_targetis rejected because zizmor and the Security Lab both name it as the dangerous trigger. Dependabot's github-actions ecosystem reaches.github/workflowsand a rootaction.ymlonly. - Renovate reaches the template but lands as a bot: its regex custom
manager matches any file through
managerFilePatterns, so a.tmplunderinternal/is updatable in the same pull request; run as a GitHub App withplatformCommit(the default on GitHub), "GitHub determines the git author string" andgitAuthoris ignored. So a hosted Renovate bump is bot-authored and still needs this repository's re-authoring; only a self-hosted Renovate withgitAuthorset 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 onpull_requestin one job and reads Dependabot secrets, or add a second workflow. The answer sets the trigger (pull_requestorworkflow_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-triggersonworkflow_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
| id | itd-2609301020001595 |
|---|---|
| type | intent |
| lifecycle | drafts |
| severity | minor |
.abcd/development/intents/drafts/itd-2609301020001595-a-dependabot-bump-of-a-pinned-action-in.md
open on GitHub ↗ · commit history ↗
References2
- adr-2609292116133348A dependency bump inside the bound is re-authored as the owner and only pushed by an Apprelated
- itd-2609221842494980A dependency bump lands without a person re-authoring it, and no machine enters the contributor graphbuilds on
Referenced by3
- iss-209Every dependabot PR that bumps a pinned action in .github/workflows/release.yml or auto-release.yml fails TestSelfScaffoldParity and can never go green on its own. Observed on PR #211 (actions/attest 4.2.0 -> 4.2.2): 'release.yml: abcd rendering is not byte-identical to the committed workflow', failing check on BOTH platforms while gitleaks, record-lint, smoke and zizmor all pass. Root cause: the committed workflow is a GENERATED artefact. internal/core/launch/scaffold/templates/release.yml.tmpl:270 and .github/workflows/release.yml:248 both pin actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 (v4.2.0), and TestSelfScaffoldParity asserts the two are byte-identical so that abcd's own release dogfoods the machinery a managed repo receives. Dependabot edits only the derived file, so parity breaks by construction on every such bump — this is recurring and structural, not a one-off. The obvious fix (bring the scaffold template into dependabot's scope) is NOT AVAILABLE: the github-actions ecosystem discovers only .github/workflows/*.yml and composite action.yml manifests under the configured directory, and no package-ecosystem scans an arbitrary .tmpl under internal/. .github/dependabot.yml today declares gomod and github-actions, both at directory /. Note also the wrong-direction trap: auto-rendering the scaffold on a dependabot PR would REVERT the bump, since render flows template -> workflow. Options, none adopted: (a) make the parity failure self-explaining — the message says 'not byte-identical' and prints the first diff but never names the template path or says 'an action bump must be applied to the template too', which is the cheapest change and matches the loud-staging principle; (b) propagate the bump backwards — a job on dependabot PRs that applies the changed pin to the template and commits to the bot branch, which is the durable fix but is custom automation writing to a bot branch; (c) drop these two workflows from dependabot's scope and manage their pins by hand from the template, trading automation for consistency. Secondary risk worth weighing: a permanently-red dependabot PR trains the maintainer to discount red CI on exactly the PRs where CI most needs to be trusted.related
- iss-2610262031409019itd-2609301020001595 quotes an unnamed product thinker, while the personas principle forbids generic attributionrelated
- iss-2610262037198530The bump re-authoring workflow stamps a commit message a machine wrote with Assisted-by: None; 34-build treats None as meaning human-only, which is false for machine-written text.related
mentions1
- iss-2609292030070275The dependency-bump re-authoring (itd-2609221842494980, spc-2609221843355061) has never run against the live forge, so its first acceptance criterion is proven only offline, against a stand-in App API and a local remote. The person's steps are owed: create a GitHub App with Contents read and write, install it on this repository, store its id and private key as the Dependabot secrets DEPENDENCY_REAUTHOR_APP_ID and DEPENDENCY_REAUTHOR_APP_KEY, and set owner_name and owner_email in .abcd/config/dependency-reauthor.conf. Until then every in-bound bump is refused by name and stays a person's to land. Done when one real dependabot go_modules or pip bump is watched re-authored as the owner, re-checked green by the attribution gate and merged; then the spec closes and the intent ships (Refs: iss-2609221820487644).