Skip to content

[Bug]: Over-long focus-manifest list entries bypass both de-duplication and the MAX_LIST_ITEMS safety cap #522

Description

@galuis116

Summary

normalizeStringList in src/signals/focus-manifest.ts normalizes the manifest
string lists (wantedPaths, blockedPaths, preferredLabels,
testExpectations). For an entry longer than MAX_ITEM_LENGTH (300), it
truncates, pushes, and continues — skipping both the de-duplication check
and the MAX_LIST_ITEMS (200) cap that the normal path enforces. Manifests are
fetched from untrusted, arbitrary repositories (.gittensory.yml /
.gittensory.json via loadRepoFocusManifest), so a crafted manifest defeats
both safety limits.

Evidence

// src/signals/focus-manifest.ts:95-114
const result: string[] = [];
for (const entry of value) {
  if (typeof entry !== "string") { /* warn + continue */ }
  const trimmed = entry.trim();
  if (!trimmed) continue;
  if (trimmed.length > MAX_ITEM_LENGTH) {
    warnings.push(`Manifest field "${field}" truncated an over-long entry.`);
    result.push(trimmed.slice(0, MAX_ITEM_LENGTH));
    continue;                                    // <-- skips dedup AND the cap below
  }
  if (!result.includes(trimmed)) result.push(trimmed);          // dedup (skipped above)
  if (result.length >= MAX_LIST_ITEMS) { /* warn */ break; }    // cap (skipped above)
}

Constants: MAX_LIST_ITEMS = 200, MAX_ITEM_LENGTH = 300,
MAX_FOCUS_MANIFEST_BYTES = 64 * 1024.

Concrete traces

(1) De-duplication bypass. Two distinct over-long entries that share a
300-char prefix:

wantedPaths:
  - "<300 'a' chars>X"     # length 301
  - "<300 'a' chars>Y"     # length 301

Both are truncated to the same "a"×300 and pushed without the !result.includes
check → wantedPaths = ["a…300", "a…300"] (a duplicate). The normal path
(de-duplicates repeated entries within the list cap, focus-manifest.test.ts:80)
would have collapsed them.

(2) MAX_LIST_ITEMS cap bypass (reachable under the byte gate). Because the
over-long branch continues before the cap check, a list composed entirely of
over-long entries never triggers the break. 201 entries of 301 chars fit under
the 64 KB pre-parse gate:

  • JSON: 201 × ("<301 chars>" + quotes + comma) ≈ 201 × 304 ≈ 61.1 KB < 65,536
  • YAML: 201 × (" - <301 chars>\n") ≈ 201 × 306 ≈ 61.5 KB < 65,536

So parseFocusManifestContent accepts it, and normalizeStringList returns a
201-entry list (up to ~214 fit), exceeding the 200-item cap that exists to
bound the list. The oversized list then flows into compileFocusManifestPolicy,
the onboarding-pack / entry-guidance output, and the
GET /v1/repos/:owner/:repo/focus-manifest response.

Why it's wrong

The truncation branch should still de-duplicate and respect the size cap — those
limits exist precisely to keep untrusted manifest input bounded and clean. As
written, the one input class the limits most need to handle (pathological,
over-long entries) is the one class that bypasses them.

Test status

Not locked in. focus-manifest.test.ts:73 (caps over-long lists and de-duplicates entries) uses short path-${index} entries, so it exercises
the normal dedup/cap path, never the over-long branch. No test asserts dedup or
the 200-cap for entries longer than MAX_ITEM_LENGTH.

Suggested fix

Truncate in place, then flow through the same de-dup and cap logic instead of
continue-ing past them:

let normalized = trimmed;
if (normalized.length > MAX_ITEM_LENGTH) {
  warnings.push(`Manifest field "${field}" truncated an over-long entry.`);
  normalized = normalized.slice(0, MAX_ITEM_LENGTH);
}
if (!result.includes(normalized)) result.push(normalized);
if (result.length >= MAX_LIST_ITEMS) {
  warnings.push(`Manifest field "${field}" exceeded ${MAX_LIST_ITEMS} entries; extra entries ignored.`);
  break;
}

Add tests: (a) two over-long entries sharing a 300-char prefix de-duplicate to a
single entry; (b) >200 over-long entries cap at 200 with the exceeded 200 entries warning.

Distinct from prior reports

Same family as the merged #464 (reward-risk next-actions deduped after capping),
but a different site and a different defect (a continue that bypasses dedup
and the list cap for over-long entries), in the newly-added focus-manifest
parser (#389 / #118). No existing issue covers focus-manifest list normalization.

Metadata

Metadata

Assignees

No one assigned

    Labels

    gittensor:bugGittensor-scored bug fix — scores a 0.05x multiplier.slopAI slop and/or attempts to game additional points via manipulation or alt profiles.

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions