Skip to content

fix(engine)!: bound miner-goal-spec list scanning, remove the orphaned duplicate parser#4318

Merged
JSONbored merged 1 commit into
mainfrom
fix/miner-goal-spec-dedupe
Jul 8, 2026
Merged

fix(engine)!: bound miner-goal-spec list scanning, remove the orphaned duplicate parser#4318
JSONbored merged 1 commit into
mainfrom
fix/miner-goal-spec-dedupe

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Motivation

Found while filing Wave 2 Phase 2 miner issues (2026-07-08): packages/gittensory-engine/src/miner-goal-spec-parse.ts (110 lines) is a second, independent implementation of parseMinerGoalSpec that duplicates and had drifted from the canonical one in miner-goal-spec.ts (278 lines) -- different return types, different validation limits (100/256 vs 200/300).

Git history explains why two exist: two independently-filed issues (#2652, a follow-up to #2293's type scaffold, and #2658/closes-#2301) both specified building this parser, landing around the same time. miner-goal-spec.ts became canonical -- re-exported from the package barrel, and the only one packages/gittensory-miner's real caller (opportunity-ranker.js:57) actually imports. miner-goal-spec-parse.ts's own PR description explicitly avoided the barrel "while #2628 is open" as a temporary conflict-avoidance measure that was never reconciled -- it sat published as its own npm subpath export (@jsonbored/gittensory-engine/miner-goal-spec-parse) with confirmed zero real callers anywhere in the repo, in-tree or via the subpath.

More importantly, investigating the drift surfaced a real, live security gap: the canonical file has the exact resource-exhaustion bug that #2896 patched only in its orphaned sibling. normalizeStringList's cap check fired only after a candidate was accepted, so an array of entries that always take the continue path (non-string, duplicate, or empty-after-trim) never hit the cap and got scanned in full -- unbounded CPU/memory work and unbounded warning growth on a hostile .gittensory-miner.yml, which this parser reads from untrusted per-repo config.

Changes

  • Bound normalizeStringList's iteration to the raw array index, checked before any per-entry work -- mirroring #2896's fix, but without reproducing its own latent bug: a trailing result.length >= cap break (kept there for "defense in depth") fires silently, with no warning, whenever the input has exactly cap+1 unique valid entries. A pre-existing test ("caps oversized string lists and ignores extra entries") caught this regression during review -- the index-based check alone is both necessary and sufficient to bound work, so the trailing check is removed rather than mirrored.
  • Delete the orphaned miner-goal-spec-parse.ts and its dedicated test.
  • Remove the ./miner-goal-spec-parse subpath from package.json's exports map.

Breaking change

@jsonbored/gittensory-engine's published ./miner-goal-spec-parse subpath export is removed. The package is one week old (bootstrap-published 2026-07-08) with a single in-repo consumer that never used this subpath -- no known external impact.

Testing

  • npm --workspace @jsonbored/gittensory-engine test -- 275/275 passing.
  • Full vitest sweep of every root-level test touching this module or its consumers (miner-goal-spec-*, opportunity-ranker, miner-discovery-pipeline, metadata/ranked-opportunity suites) -- 164/164 passing.
  • npm run typecheck -- clean.
  • Coverage check scoped to the changed file: 100% branch coverage on the actual diff (the only uncovered lines are discoverMinerGoalSpecPath, untouched by this change).
  • Added hostile-input regression tests (all-non-string, all-duplicate, all-empty, all-overlong) to both the package-level and root-level test suites, mirroring the coverage #2896 added for the now-deleted sibling.

…d duplicate parser

Two independently-filed issues (#2652's follow-up to #2293, and #2301)
both specified a MinerGoalSpec parser. Two implementations landed
around the same time: miner-goal-spec.ts became canonical (re-exported
from the package barrel, the only one packages/gittensory-miner's real
caller actually imports), while miner-goal-spec-parse.ts's own PR
explicitly avoided the barrel "while #2628 is open" as a temporary
conflict-avoidance measure that was never reconciled -- it sat
published as its own npm subpath export with zero real callers
anywhere in the repo.

More importantly: the canonical file has the exact resource-exhaustion
bug that #2896 patched in its orphaned sibling and nowhere else.
normalizeStringList's cap check only fired after a candidate was
accepted, so an array of entries that always take the `continue` path
(non-string, duplicate, or empty-after-trim) never hit the cap and got
scanned in full -- a hostile `.gittensory-miner.yml` with thousands of
duplicate/invalid list entries could force unbounded CPU/memory work
and unbounded warning growth on every repo a miner considers, since
this parser runs on untrusted per-repo config.

- Bound normalizeStringList's iteration to the raw array index, before
  any per-entry work, mirroring #2896's fix -- but WITHOUT that fix's
  own latent bug: a trailing `result.length >= cap` break (kept for
  "defense in depth") fires silently, with no warning, whenever the
  input has exactly cap+1 unique valid entries. A pre-existing test
  ("caps oversized string lists and ignores extra entries") caught
  this regression during review. The index-based check alone is both
  necessary and sufficient to bound work; the trailing check is
  removed rather than mirrored.
- Delete the orphaned miner-goal-spec-parse.ts and its dedicated test
  (confirmed zero real callers, in-repo or via its published subpath).
- Remove the ./miner-goal-spec-parse subpath from package.json's
  exports map. BREAKING CHANGE: @jsonbored/gittensory-engine's
  published subpath export is removed. The package is one week old
  with a single in-repo consumer that never used this subpath; no
  known external impact.
- Add hostile-input regression tests (all-non-string, all-duplicate,
  all-empty, all-overlong) to both the package-level and root-level
  test suites for the canonical parser, mirroring the coverage #2896
  added for its now-deleted sibling.
@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
gittensory-ui a903dc6 Commit Preview URL

Branch Preview URL
Jul 08 2026, 08:27 PM

@JSONbored JSONbored self-assigned this Jul 8, 2026
@JSONbored
JSONbored merged commit d329591 into main Jul 8, 2026
8 of 9 checks passed
@JSONbored
JSONbored deleted the fix/miner-goal-spec-dedupe branch July 8, 2026 20:31
@loopover-orb loopover-orb Bot added the gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier. label Jul 8, 2026
@codecov

codecov Bot commented Jul 8, 2026

Copy link
Copy Markdown

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
12295 1 12294 12
View the full list of 1 ❄️ flaky test(s)
test/unit/mcp-cli-tools.test.ts > gittensory-mcp CLI — tools > lists every registered stdio tool with a non-empty description

Flake rate in main: 100.00% (Passed 0 times, Failed 1 times)

Stack Traces | 1.29s run time
AssertionError: expected 33 to be 34 // Object.is equality

- Expected
+ Received

- 34
+ 33

 ❯ test/unit/mcp-cli-tools.test.ts:47:27

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

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

Labels

gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant