Skip to content

[Dream Cycle 2026-10-14] developer-experience: verify-entrypoint --cmd requiredness bypassed by a bare trailing flag + cli,tui scan #181

Open

Description

@ruvnet

Rotation

DATE=2026-10-14, DEEP=developer-experience, SCAN=cli,tui, SLOT=4, COMMIT=b2abcc14a632d7e0948962065a7c6c8920cf306c. No bonus deep-dive triggers (20261014 % 25 = 14, % 75 = 64).

Ledger Check

Last 14 ledger rows inspected. Three open PRs already exist on this exact surface — read in full to avoid duplication, confirmed disjoint:

Issue #149/PR #150 (shallow-clone git show oracle) is a different surface (security-adversarial), already filed/tracked across 3+ prior nights, not touched tonight.

gh gist create is blocked by this session's data-sharing policy (classified "Public Data-Sharing Upload"); recorded as GIST=LOCAL per precedent (2026-08-30, PR #54/#55) — report committed into the PR instead.

Deep Dive

packages/cli/src/index.ts's hand-rolled parseArgs (line 88) turns a flag with nothing after it — end of argv, or immediately followed by another --flag — into the boolean true, not a string. The verify-entrypoint subcommand's requiredness guard (line 465, pre-fix) was if (!label || !cmd) where cmd = flags.cmd as string | undefined — a compile-time-only cast with no runtime check. !true === false, so when --cmd is bare/trailing, the usage-error branch is skipped and the boolean true reaches io.exec(cmd) (line 486).

In the real CLI (packages/cli/src/bin.ts), io.exec wraps child_process.exec, which throws a TypeError on a non-string command. The catch-all handler there has no numeric .code on a TypeError, so it defaults to {code: 1, stdout: '', stderr: ''}. classifyEntrypointResult then reports verdict: 'blocked', reason: 'exited 1 with no stderr' — indistinguishable from a genuinely dead entrypoint. This command gates the nightly pipeline's own evaluator-liveness check (STEP 0.5's control-plane discovery, and verify-entrypoints more broadly), so a typo'd invocation silently misreports "the entrypoint is dead" instead of "you forgot --cmd's value."

Hypothesis

Given verify-entrypoint <label> --cmd invoked with --cmd as a value-less flag, when the requiredness guard is changed from truthiness (!cmd) to a real type check (typeof cmd !== 'string'), then the invocation emits the usage error (exit 1, stderr containing usage:) instead of a misleading blocked liveness verdict — with zero behavior change for live/blocked-on-a-real-command/suspicious-silent/stale-state/no-exec()/multi-word-positional-rejection.

Evaluation Receipt

Real evaluator: npx vitest run + node --test scripts/*.test.mjs.

Darwin Results

Not run. evaluatorEntrypoints.darwin (npx @metaharness/darwin) is unpinned (flagged repeatedly across prior nights as a supply-chain risk — resolves registry latest fresh on every invocation, not governed by this repo's lockfile). This candidate is a tiny (2-line), clear-cut, mechanically verified fix; invoking an unpinned npx entrypoint was judged not worth the supply-chain exposure for it. Flagged for human decision as in prior nights, not newly discovered tonight.

Evidence

  • OBSERVATION: live pre-fix repro against built CLI.
  • MEASUREMENT: vitest 809→811 (+2), governance 195 unchanged, 0 regressions; lint/typecheck clean.
  • INFERENCE: typeof cmd !== 'string' closes the hole for every parseArgs boolean-flag shape (bare trailing, immediately-followed-by-another-flag) without affecting any legitimate string value, including --cmd= (empty string, still caught by the trailing !cmd) and --cmd "" (empty next-token, which parseArgs itself treats as absent and converts to boolean true, also caught).
  • DECISION: ACCEPT.

Reward-Hack Check

Independent critic subagent (did not author the fix) reviewed adversarially: verified the live diff directly, checked for bypasses (--cmd=, --cmd ""), confirmed the test diff is purely additive (no weakened assertion, no gold-value change), confirmed classifyEntrypointResult and mockIOWithExec were untouched (no reward-hacking the evaluator), confirmed no behavior change for any pre-existing verify-entrypoint case, and independently ran the full suite (811/811 + 195/195). Verdict: CLEAR.

Security Review

No new attack surface: this narrows an existing input-validation check on a CLI arg handled entirely in-process; no credentials, network, or filesystem scope change. The fix removes a bogus compile-time-only type cast in favor of a real runtime check — strictly tightening, not loosening, validation.

Scan Findings (cli, tui)

Competitors

See report's competitor-rows table (gist, below) — this finding is internal/self-contained to this repo's own hand-rolled CLI parser, not an external-literature claim. Noted: Node's built-in util.parseArgs would make this entire bug class structurally impossible (distinguishes boolean vs. string flags at the parser level) — a repo-level next step, not implemented tonight (out of scope, would touch every subcommand).

Gist

GIST=LOCAL — public gist creation denied by this session's data-sharing policy. Full report committed into the PR as docs/dream-cycle/2026-10-14-developer-experience-report.md.

Witness

  • Session commit: b2abcc14a632d7e0948962065a7c6c8920cf306c
  • Report sha256: 4c4e85ad532e553be4820ce8494dacd6edf909f04356aaa0bfe32999fbf75c25
  • Witness stamp: 4c8277be28712c3afe9f7ba90f4a02bfe8a8bcf7edfcd099cd5dc057728b5dfd
  • Self-verified with dream-machine witness verify-report docs/dream-cycle/2026-10-14-developer-experience-report.md --commit b2abcc14a632d7e0948962065a7c6c8920cf306c → ✓ witness VALID.

(Correction: an earlier draft of the PR/report computed this triple by hand with sha256sum over the whole report file, which included a stray duplicate ## Witness placeholder heading — not this repo's own splitAtWitnessSection canonical-exclusion convention added in a prior night's fix. The report and the values above were corrected and self-verified before this issue was finalized.)

Recommendation

evaluated: accepted. Candidate is small, mechanically verified, independently critiqued CLEAR, and closes a real false-positive on this pipeline's own evaluator-liveness gate. Human review requested before merge — session never self-merges.

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

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions