@maintainabilityai/research-runner 0.1.45 → 0.1.47
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 +84 -3
- package/package.json +1 -1
package/dist/runner/skills.js
CHANGED
|
@@ -1338,6 +1338,27 @@ 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 + Bug-S / S6 (Codex round-4 hardening) — persist
|
|
1342
|
+
// inventory to the clone cache so knowledge-code-read can strict-
|
|
1343
|
+
// mode validate requested paths against the same list that lands
|
|
1344
|
+
// in the audit chain. Round-3 caught the inventory-persist write
|
|
1345
|
+
// being non-fatal; round-4 flagged that as fail-open — without
|
|
1346
|
+
// a written inventory, knowledge-code-read's path-not-in-inventory
|
|
1347
|
+
// check silently falls through (the file just isn't there to
|
|
1348
|
+
// check against). S6 fix: persist failure is now FATAL. The
|
|
1349
|
+
// brownfield grounding contract requires the inventory to exist;
|
|
1350
|
+
// a skill_call that succeeded without writing it is a coverage lie.
|
|
1351
|
+
try {
|
|
1352
|
+
fs.writeFileSync(path.join(cloneTarget, '.knowledge-code-inventory.json'), JSON.stringify({ inventory_paths: inventoryPaths, sha, cachedAt: new Date().toISOString() }), 'utf8');
|
|
1353
|
+
}
|
|
1354
|
+
catch (err) {
|
|
1355
|
+
return {
|
|
1356
|
+
ok: false,
|
|
1357
|
+
reason: `inventory-persist-failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
1358
|
+
repo: repoSlug,
|
|
1359
|
+
remediation: "knowledge-code MUST persist its file inventory so knowledge-code-read can strict-mode validate file reads against it. Check tmpdir permissions and disk space.",
|
|
1360
|
+
};
|
|
1361
|
+
}
|
|
1341
1362
|
return {
|
|
1342
1363
|
ok: true,
|
|
1343
1364
|
mode: 'brownfield',
|
|
@@ -1407,9 +1428,26 @@ const handleKnowledgeCodeRead = async (input) => {
|
|
|
1407
1428
|
if (normalized.startsWith('..') || normalized === '..' || normalized.includes(`${path.sep}..${path.sep}`)) {
|
|
1408
1429
|
return { ok: false, reason: `path-rejected: path-traversal segments forbidden (${filePath} -> ${normalized})` };
|
|
1409
1430
|
}
|
|
1410
|
-
//
|
|
1411
|
-
// (
|
|
1412
|
-
// code
|
|
1431
|
+
// Bug-R / R6 (Codex round-3) — auth tightening. A prior knowledge-
|
|
1432
|
+
// code call for this (runId, owner, name) MUST have populated the
|
|
1433
|
+
// cache before knowledge-code-read can return content. Closes two
|
|
1434
|
+
// gaps Codex flagged: (1) skill could read any public GitHub repo
|
|
1435
|
+
// by URL alone, (2) audit chain didn't prove the standard
|
|
1436
|
+
// brownfield-grounding pipeline ran before the file read. Test
|
|
1437
|
+
// mode (KNOWLEDGE_CODE_READ_ALLOW_UNCACHED=1) bypasses this for
|
|
1438
|
+
// unit tests that drive the skill directly.
|
|
1439
|
+
const cacheDir = knowledgeCodeCacheDir(runId, gh.owner, gh.name);
|
|
1440
|
+
const metaPath = path.join(cacheDir, '.cache-meta.json');
|
|
1441
|
+
const allowUncached = process.env.KNOWLEDGE_CODE_READ_ALLOW_UNCACHED === '1';
|
|
1442
|
+
if (!allowUncached && !fs.existsSync(metaPath)) {
|
|
1443
|
+
return {
|
|
1444
|
+
ok: false,
|
|
1445
|
+
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.`,
|
|
1446
|
+
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.",
|
|
1447
|
+
};
|
|
1448
|
+
}
|
|
1449
|
+
// Reuse the cached clone from knowledge-code; clone fresh only in
|
|
1450
|
+
// test mode (allowUncached).
|
|
1413
1451
|
const cloneResult = ensureClone(runId, repoUrl, ref ?? 'HEAD', gh.owner, gh.name);
|
|
1414
1452
|
if (!cloneResult.ok) {
|
|
1415
1453
|
return {
|
|
@@ -1419,6 +1457,49 @@ const handleKnowledgeCodeRead = async (input) => {
|
|
|
1419
1457
|
remediation: `Could not access clone for ${repoUrl}. Underlying error: ${cloneResult.error ?? 'unknown'}`,
|
|
1420
1458
|
};
|
|
1421
1459
|
}
|
|
1460
|
+
// Bug-R / R6 + Bug-S / S6 (Codex round-4 hardening) — validate
|
|
1461
|
+
// the requested path against the inventory persisted by knowledge-
|
|
1462
|
+
// code. Only paths knowledge-code advertised in `inventory_paths`
|
|
1463
|
+
// are readable. Round-3 left missing/malformed inventory as a
|
|
1464
|
+
// fall-through (silent pass); round-4 caught that as fail-open.
|
|
1465
|
+
// S6: missing inventory file or malformed JSON is now a HARD
|
|
1466
|
+
// FAILURE in non-test mode. The cache exists (we passed the
|
|
1467
|
+
// earlier no-cache check), so the inventory file MUST exist —
|
|
1468
|
+
// its absence means knowledge-code didn't complete normally.
|
|
1469
|
+
if (!allowUncached) {
|
|
1470
|
+
const inventoryPath = path.join(cloneResult.path, '.knowledge-code-inventory.json');
|
|
1471
|
+
if (!fs.existsSync(inventoryPath)) {
|
|
1472
|
+
return {
|
|
1473
|
+
ok: false,
|
|
1474
|
+
reason: `inventory-missing: ${gh.owner}/${gh.name}'s clone exists but .knowledge-code-inventory.json is absent. knowledge-code did not complete normally — re-run it before invoking knowledge-code-read.`,
|
|
1475
|
+
};
|
|
1476
|
+
}
|
|
1477
|
+
let inv;
|
|
1478
|
+
try {
|
|
1479
|
+
inv = JSON.parse(fs.readFileSync(inventoryPath, 'utf8'));
|
|
1480
|
+
}
|
|
1481
|
+
catch (err) {
|
|
1482
|
+
return {
|
|
1483
|
+
ok: false,
|
|
1484
|
+
reason: `inventory-malformed: .knowledge-code-inventory.json could not be parsed: ${err instanceof Error ? err.message : String(err)}`,
|
|
1485
|
+
remediation: "Re-run knowledge-code to regenerate the inventory.",
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1488
|
+
const allowed = new Set(inv.inventory_paths ?? []);
|
|
1489
|
+
if (allowed.size === 0) {
|
|
1490
|
+
return {
|
|
1491
|
+
ok: false,
|
|
1492
|
+
reason: `inventory-empty: .knowledge-code-inventory.json carries no inventory_paths. knowledge-code likely walked zero files (empty repo? maxFiles=0?). Cannot validate file reads.`,
|
|
1493
|
+
};
|
|
1494
|
+
}
|
|
1495
|
+
if (!allowed.has(normalized)) {
|
|
1496
|
+
return {
|
|
1497
|
+
ok: false,
|
|
1498
|
+
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.`,
|
|
1499
|
+
remediation: "If the file is real but missed by the bounded walk (default maxFiles=200), call knowledge-code with a higher maxFiles before retrying.",
|
|
1500
|
+
};
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1422
1503
|
const absPath = path.join(cloneResult.path, normalized);
|
|
1423
1504
|
// Final paranoia check — resolve the real path and verify it's still
|
|
1424
1505
|
// 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.47",
|
|
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",
|