@maintainabilityai/research-runner 0.1.45 → 0.1.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1338,6 +1338,15 @@ const handleKnowledgeCode = async (input) => {
1338
1338
  // Workflow gate consumes this to validate cited paths.
1339
1339
  inventory_paths: inventoryPaths,
1340
1340
  };
1341
+ // Bug-R / R6 (Codex round-3) — persist inventory to the clone
1342
+ // cache so knowledge-code-read can strict-mode validate requested
1343
+ // paths against the same list that lands in the audit chain.
1344
+ // Without this, the agent could ask knowledge-code-read for
1345
+ // arbitrary paths inside the clone that the chain never advertised.
1346
+ try {
1347
+ fs.writeFileSync(path.join(cloneTarget, '.knowledge-code-inventory.json'), JSON.stringify({ inventory_paths: inventoryPaths, sha, cachedAt: new Date().toISOString() }), 'utf8');
1348
+ }
1349
+ catch { /* inventory persist failure is non-fatal — read skill will fall back to cache-only check */ }
1341
1350
  return {
1342
1351
  ok: true,
1343
1352
  mode: 'brownfield',
@@ -1407,9 +1416,26 @@ const handleKnowledgeCodeRead = async (input) => {
1407
1416
  if (normalized.startsWith('..') || normalized === '..' || normalized.includes(`${path.sep}..${path.sep}`)) {
1408
1417
  return { ok: false, reason: `path-rejected: path-traversal segments forbidden (${filePath} -> ${normalized})` };
1409
1418
  }
1410
- // Reuse the cached clone from knowledge-code; clone fresh if missing
1411
- // (e.g. agent called knowledge-code-read without calling knowledge-
1412
- // code first supported but slower).
1419
+ // Bug-R / R6 (Codex round-3) auth tightening. A prior knowledge-
1420
+ // code call for this (runId, owner, name) MUST have populated the
1421
+ // cache before knowledge-code-read can return content. Closes two
1422
+ // gaps Codex flagged: (1) skill could read any public GitHub repo
1423
+ // by URL alone, (2) audit chain didn't prove the standard
1424
+ // brownfield-grounding pipeline ran before the file read. Test
1425
+ // mode (KNOWLEDGE_CODE_READ_ALLOW_UNCACHED=1) bypasses this for
1426
+ // unit tests that drive the skill directly.
1427
+ const cacheDir = knowledgeCodeCacheDir(runId, gh.owner, gh.name);
1428
+ const metaPath = path.join(cacheDir, '.cache-meta.json');
1429
+ const allowUncached = process.env.KNOWLEDGE_CODE_READ_ALLOW_UNCACHED === '1';
1430
+ if (!allowUncached && !fs.existsSync(metaPath)) {
1431
+ return {
1432
+ ok: false,
1433
+ reason: `no-prior-knowledge-code: knowledge-code-read requires a prior knowledge-code call for ${gh.owner}/${gh.name} in run ${runId}. Call knowledge-code first to clone + classify the repo, then knowledge-code-read can return file contents from the cached clone.`,
1434
+ remediation: "Call `knowledge-code` with the same repoUrl + runId before invoking knowledge-code-read. The audit chain then proves the agent went through brownfield grounding before reading files.",
1435
+ };
1436
+ }
1437
+ // Reuse the cached clone from knowledge-code; clone fresh only in
1438
+ // test mode (allowUncached).
1413
1439
  const cloneResult = ensureClone(runId, repoUrl, ref ?? 'HEAD', gh.owner, gh.name);
1414
1440
  if (!cloneResult.ok) {
1415
1441
  return {
@@ -1419,6 +1445,29 @@ const handleKnowledgeCodeRead = async (input) => {
1419
1445
  remediation: `Could not access clone for ${repoUrl}. Underlying error: ${cloneResult.error ?? 'unknown'}`,
1420
1446
  };
1421
1447
  }
1448
+ // Bug-R / R6 (strict mode part 2) — validate the requested path
1449
+ // against the inventory persisted by knowledge-code. Only paths
1450
+ // that knowledge-code already advertised in `inventory_paths` are
1451
+ // readable — closes the gap where the agent could ask for any
1452
+ // file inside the clone, including files not visible in the
1453
+ // bounded walk. Test mode bypasses (see allowUncached).
1454
+ if (!allowUncached) {
1455
+ const inventoryPath = path.join(cloneResult.path, '.knowledge-code-inventory.json');
1456
+ if (fs.existsSync(inventoryPath)) {
1457
+ try {
1458
+ const inv = JSON.parse(fs.readFileSync(inventoryPath, 'utf8'));
1459
+ const allowed = new Set(inv.inventory_paths ?? []);
1460
+ if (allowed.size > 0 && !allowed.has(normalized)) {
1461
+ return {
1462
+ ok: false,
1463
+ reason: `path-not-in-inventory: ${normalized} is not in the knowledge-code inventory_paths for ${gh.owner}/${gh.name}. The agent can only read files knowledge-code advertised in the chain.`,
1464
+ remediation: "If the file is real but missed by the bounded walk (default maxFiles=200), call knowledge-code with a higher maxFiles before retrying.",
1465
+ };
1466
+ }
1467
+ }
1468
+ catch { /* malformed inventory; fall through (cache-only check still applied) */ }
1469
+ }
1470
+ }
1422
1471
  const absPath = path.join(cloneResult.path, normalized);
1423
1472
  // Final paranoia check — resolve the real path and verify it's still
1424
1473
  // a child of the clone root. Defends against symlink-shaped escapes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maintainabilityai/research-runner",
3
- "version": "0.1.45",
3
+ "version": "0.1.46",
4
4
  "description": "Research + PRD agent runner — orchestrates the Archeologist and PRD pipelines for the MaintainabilityAI governance mesh",
5
5
  "license": "MIT",
6
6
  "author": "MaintainabilityAI",