affaan-m/ECC · error · Error

Invalid ECC repo root: missing package.json at ${packageJson

Error message

Invalid ECC repo root: missing package.json at ${packageJsonPath}

What it means

`changedFiles` is an optional field in the orch-review payload, but when present it must be an array of path strings — it is joined into the security-trigger haystack. This throw fires when changedFiles is supplied but is not an array (e.g. a single string, a comma-separated CSV string, or an object). Non-array values would not join correctly and would distort the security scan.

Source

Thrown at scripts/auto-update.js:136

    return path.dirname(record.state.target.root);
  }

  return repoRoot;
}

// Recognized ECC package names. A repo root is only trusted to run its
// install-apply.js if its package.json identifies it as ECC — otherwise a
// cloned project that ships a nested `evil/{package.json,scripts/install-apply.js}`
// could drive auto-update into executing attacker code (GHSA-hfpv-w6mp-5g95).
const ECC_PACKAGE_NAMES = new Set(['ecc-universal', 'everything-claude-code']);

function validateRepoRoot(repoRoot) {
  const normalized = path.resolve(repoRoot);
  const packageJsonPath = path.join(normalized, 'package.json');
  const installApplyPath = path.join(normalized, 'scripts', 'install-apply.js');

  if (!fs.existsSync(packageJsonPath)) {
    throw new Error(`Invalid ECC repo root: missing package.json at ${packageJsonPath}`);
  }

  if (!fs.existsSync(installApplyPath)) {
    throw new Error(`Invalid ECC repo root: missing install script at ${installApplyPath}`);
  }

  let pkgName = null;
  try {
    pkgName = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')).name;
  } catch {
    throw new Error(`Invalid ECC repo root: unreadable package.json at ${packageJsonPath}`);
  }
  if (!ECC_PACKAGE_NAMES.has(pkgName)) {
    throw new Error(`Refusing to run install from untrusted repo root ${normalized}: package.json name '${pkgName}' is not an official ECC package.`);
  }

  return normalized;
}

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Pass an array even for one file: changedFiles: ['src/foo.js'].
  2. If you have a CSV or Set, convert first: changedFiles: Array.from(fileSet) or csv.split(',').
  3. If you do not need file-level security scanning, omit changedFiles entirely.

Example fix

// before
orchReview({ diff, changedFiles: 'src/foo.js' }); // string, not array

// after
orchReview({ diff, changedFiles: ['src/foo.js'] });
Defensive patterns

Strategy: validation

Validate before calling

// Normalize changedFiles to an array before calling.
if (changedFiles != null && !Array.isArray(changedFiles)) {
  changedFiles = [String(changedFiles)]; // or csv.split(',')
}
orchReview({ diff, changedFiles });

Type guard

function isValidChangedFiles(v) {
  return v == null || (Array.isArray(v) && v.every(x => typeof x === 'string'));
}

Prevention

When it happens

Trigger: Passing changedFiles: 'src/foo.js' (single string); changedFiles: 'a.js,b.js' (CSV); changedFiles: { path: 'a.js' } (object).

Common situations: Caller has a single changed file and passes it bare instead of wrapping in an array; caller joins file paths into a CSV string; a mapping step returns the wrong shape.

Related errors


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/3c9980d0e5503fcd. Report an issue: GitHub.