Skip to content

sweep: BUNDLE_NAME_RE accepts a trailing newline; Bundle.name is used raw as a path segment #330

Description

@pbean

Summary

BUNDLE_NAME_RE = re.compile(r"^[a-z0-9][a-z0-9-]{1,39}$") (sweep.py:37) is applied with
.match(), and $ matches before a trailing newline. So a bundle name of "fix-it\n" passes
validation:

>>> BUNDLE_NAME_RE.match("fix-it\n")     # truthy — accepted
>>> BUNDLE_NAME_RE.fullmatch("fix-it\n") # None

Confirmed end to end: validate_triage with bundles=[{"name": "fix-it\n", ...}] returns
errors == [] and plan.bundles[0].name == "fix-it\n".

Consequence

Bundle.name is used raw downstream:

  • as a filesystem segment — _run_bundle_write_intent, self.run_dir / "bundles" / dirname / "intent.md" (sweep.py:637, :1207), with no safe_segment
  • as story_key via _bundle_key (dw-fix-it\n), which lands in run state and the journal

On Linux this silently creates a directory whose name ends in a newline. On the native-Windows
target a path segment containing a control character is invalid, so the sweep fails at bundle start.
Branch names are safe — workspace.py:99-100 runs safe_ref_segment — but the directory path and
the story key are not.

Fix shape

Anchor with \Z (or switch the call sites to re.fullmatch):

BUNDLE_NAME_RE = re.compile(r"[a-z0-9][a-z0-9-]{1,39}\Z")

and add a negative case asserting plan is None for a trailing break in both bundles[].name and an
option's bundle_name. Worth auditing the other .match()-with-$ patterns in the same module at
the same time.

Note this is a validation hole, not a ledger-injection one — #305's sanitizer covers the ledger
route (_close_bundle_ledger_when_spec_status builds its note from task.story_key); this is the
path/story-key axis.

Found during the #305 review.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:deferred-workdeferred-work.md ledger and sweepbugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions