security: verify sed-rewrite injection surface in bake_toolkit_ref (Sprint 5 security audit finding) #180
Labels
No labels
bump
major
bump
minor
bump
patch
kind/bug
kind/chore
kind/docs
kind/feature
priority/critical
priority/high
priority/low
priority/medium
size/L
size/M
size/S
size/XL
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit#180
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Audit finding
Sprint 5 security audit (#156) flagged a potential sed-injection surface in
scripts/lib/build_bake.sh:50:If
$new_refcontains a literal|(the sed delimiter), the sed expression splits into multiple commands, enabling sed-side injection. Agent's framing:tag_formatconfig could be poisoned to produce a$new_refcontaining|.Verify-at-source needed (Surveyor discipline)
The agent's finding may have conflated the immediate
new_refsource. Tracing:bake_toolkit_ref "$f" "$NEW_TAG"is invoked fromscripts/release-prep.sh(after section 8c)NEW_TAGis computed bysemver_bump(scripts/lib/semver.sh) — validated SemVer outputv[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.-]+)?If
NEW_TAGis genuinely SemVer-bounded, the|character can't appear → no immediate injection.The agent's referenced
tag_formatconfig (scripts/lib/config.sh:307) may be an indirect path — verify whethertag_formatinfluencesNEW_TAG's value at any step in the cut flow.Action items (in order of verification rigor)
NEW_TAG's provenance from semver_bump tobake_toolkit_ref's call site. Are there any intermediate string-templating steps that could inject special characters?tag_formatconfig handling: iftag_formatISN'T part ofNEW_TAG's computation, document the irrelevance + close. If it IS, confirm the injection feasibility.new_refinbake_toolkit_refitself via a[[ "$new_ref" =~ ^[A-Za-z0-9._-]+$ ]]check. Cheap defense-in-depth; no functional impact for legitimate refs (which are all SemVer-bounded).bake_toolkit_ref "$f" "v0.13.0|MALICIOUS"is REJECTED (not silently sed-injected).Composition
scripts/lib/build_bake.shis the file in question (shipped in v0.13.0 #173)toolkit_refinput — the bake mechanism (and this attack surface) PERSISTS post-Phase-2What this PR will NOT do
tag_formatconfig schema beyond audit findingsRefs
Correction per Surveyor b316 verify-at-source
My PR-body framing dismissed the agent's finding as "NEW_TAG comes from semver_bump's validated regex, not tag_format." That reasoning is WRONG. Surveyor traced the actual data flow at source:
NEW_TAG = config_render_tag("$NEW_VERSION", config)(release-prep.sh:266)config_render_tag=${fmt//\{version\}/$version}— substitutes the validated version INTO the tag_format TEMPLATEconfig.sh:307literally saystag_formatis unvalidatedSo:
{version}substitution input IS validated (semver) ✓tag_formatTEMPLATE (literal chars around{version}) is UNVALIDATED and flows verbatim into NEW_TAG → bake_toolkit_ref's sed'v{version}|evil'→NEW_TAG='v0.13.0|evil'→ sed delimiter collision / injectionThe agent's data-flow finding is correct. The discipline failure is mine: substrate-of-record verify failed (verified my mental model rather than reading config_render_tag).
Reachability bound (the LOW-risk framing — also from Surveyor b316)
bake_toolkit_refno-ops for external consumers via the[[ ! -f "$file" ]]file-guard (build_bake.sh:42). The injection is TOOLKIT-SELF-ONLY — only reachable where the maintainers controltag_format(the toolkit's ownrelease-toolkit.yml).External consumers'
tag_format→ bake is a no-op for them. So it's self-harm threat model = LOW risk, but REAL.Updated mitigation
Sanitize
new_refinbake_toolkit_ref(not at the config layer) — reject/escape sed-meta chars, especially the|delimiter. One guard closes the class regardless oftag_formatprovenance.Specifically:
Plus bats coverage: a mutation-verify fixture that passes
'v0.13.0|MALICIOUS'and asserts the function returns non-zero + leaves the file unchanged.Updated severity
LOW-RISK-BUT-REAL — not "validation-bounded" (it's NOT validated) but "reachability-bounded" (file-guard limits to toolkit-self). The defense-in-depth guard is the right fix because it closes the class regardless of the data-flow's upstream story.
Discipline lesson banked
The verify-at-source culture goes BOTH WAYS: my agent dispatched the audit, the agent found the issue, I second-guessed the agent based on my mental model of the data flow. Surveyor verified at source and the agent was right. The next agent-flag I'm tempted to dismiss as "agent overreach" needs the same trace-at-source rigor I apply to substrate claims I want to believe.
This is also the calibration banked earlier in the session: "detector-output that confirms what I want to believe gets the SAME independent-probe rigor as detector-output that contradicts it." Add a sibling: "detector-output that I'm tempted to DISMISS gets the same rigor too."
quartermaster referenced this issue2026-06-27 14:27:35 +02:00