Skip to content

task-done: parse .info file via awk instead of source (defends against opaque non-gh identifiers) #149

Description

@martin-conur

Context

Follow-up from PR #145 (closes #144) — reviewer's clean-with-nits finding.

After #144 lands, non-gh loadouts (claude-jira / claude-notion / claude-local) can pass any opaque string as the spec identifier. task-reviewer writes it unquoted into the worktree's .info file:

printf 'ISSUE_NUMBER=%s\n' "$ISSUE_NUMBER"

Producing lines like:

ISSUE_NUMBER=PROJ-$(date)

task-done then reads the file via source "$INFO_FILE" (claude-gh/bin/task-done:54). The shell re-parses the value — $(...) executes, $VAR expands, backticks execute.

Practical risk: zero

  • Real Jira keys: PROJ-123 — no metacharacters.
  • Real Notion URLs: https://notion.so/...?p=... — %-encoded special chars.
  • Real local slugs: kebab-case-string — no metacharacters.

The vector requires explicitly crafted identifiers and would be a self-inflicted local operation. Filed for completeness and code-hygiene, not because exploitation is realistic.

Why fix anyway

Proposed fix

Two options:

Option A — quote on write (defensive at source):

- printf 'ISSUE_NUMBER=%s\n' "$ISSUE_NUMBER"
+ printf 'ISSUE_NUMBER=%q\n' "$ISSUE_NUMBER"

…in task-reviewer. task-done's source then sees a quoted assignment that bash parses safely.

Option B — replace source with awk parser in task-done (defensive at read):

task-done already uses an awk-based read for TAB_ID (claude-gh/bin/task-done:116, 121):

_WORKER_TAB_ID=$(awk -F= '/^TAB_ID=/ { print $2; exit }' "$INFO_FILE")

Extend the same pattern to every field task-done currently relies on from $INFO_FILE:

Drop the source "$INFO_FILE" line. Each field gets a parser-side awk read; nothing in the file is ever eval'd.

Recommended: Option B. Defends regardless of write-side discipline (a future loadout adding a new field can't accidentally introduce the vector). Mirrors the existing TAB_ID pattern.

Files (expected touchpoints)

File Action
claude-gh/bin/task-done (+ all 4 claude + 3 kiro variants in drift groups task-done-std / task-done-local) MODIFY — replace source with field-wise awk reads
tests/task_done.bats EXTEND — add a test that passes a .info containing ISSUE_NUMBER=PROJ-$(date) and asserts task-done completes without $(date) evaluating
claude-gh/bin/task-reviewer (and 3 sibling drift members) (Option A only) — write printf %q quoted value

tools/check-drift.sh should stay green — the changes are inside existing confirm-and-cleanup / worktree-context regions.

Verification

  1. bats tests/task_done.bats — green, including the new injection-attempt test.
  2. tools/check-drift.sh — green.
  3. End-to-end: PM dispatches task-reviewer 42 'PROJ-$(date)' in a claude-jira scratch project; later task-done --remove-worktree in that reviewer's worktree completes without $(date) being substituted.

Why P2 / no milestone

References

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions