@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.
- package/dist/runner/skills.js +52 -3
- package/package.json +1 -1
package/dist/runner/skills.js
CHANGED
|
@@ -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
|
-
//
|
|
1411
|
-
// (
|
|
1412
|
-
// code
|
|
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.
|
|
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",
|