dominds 1.25.11 → 1.25.13
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/README.md +1 -1
- package/README.zh.md +1 -1
- package/dist/access-control.js +9 -5
- package/dist/docs/team_mgmt-toolset.md +1 -1
- package/dist/docs/team_mgmt-toolset.zh.md +1 -1
- package/dist/llm/kernel-driver/sideDialog.d.ts +1 -0
- package/dist/llm/kernel-driver/sideDialog.js +29 -11
- package/dist/llm/kernel-driver/tellask-special.js +3 -0
- package/dist/persistence.d.ts +1 -0
- package/dist/persistence.js +23 -11
- package/dist/server/api-routes.js +65 -0
- package/dist/server/dialog-forensics-routes.d.ts +2 -0
- package/dist/server/dialog-forensics-routes.js +549 -0
- package/dist/server/dominds-self-update.js +54 -31
- package/dist/tools/builtins.js +14 -16
- package/dist/tools/os.d.ts +14 -0
- package/dist/tools/os.js +508 -101
- package/dist/tools/prompts/codex_inspect_and_patch_tools/en/tools.md +2 -1
- package/dist/tools/prompts/codex_inspect_and_patch_tools/zh/tools.md +2 -1
- package/package.json +3 -3
- package/webapp/dist/assets/{_basePickBy-BF9Zg9uq.js → _basePickBy-B1-brAPU.js} +3 -3
- package/webapp/dist/assets/{_basePickBy-BF9Zg9uq.js.map → _basePickBy-B1-brAPU.js.map} +1 -1
- package/webapp/dist/assets/{_baseUniq-CFjISsgz.js → _baseUniq-BuB0jmKD.js} +2 -2
- package/webapp/dist/assets/{_baseUniq-CFjISsgz.js.map → _baseUniq-BuB0jmKD.js.map} +1 -1
- package/webapp/dist/assets/{arc-BVyYGzE7.js → arc-CvsBvjnB.js} +2 -2
- package/webapp/dist/assets/{arc-BVyYGzE7.js.map → arc-CvsBvjnB.js.map} +1 -1
- package/webapp/dist/assets/{architectureDiagram-2XIMDMQ5-SEmNTU1b.js → architectureDiagram-2XIMDMQ5-B-xGuLbb.js} +7 -7
- package/webapp/dist/assets/{architectureDiagram-2XIMDMQ5-SEmNTU1b.js.map → architectureDiagram-2XIMDMQ5-B-xGuLbb.js.map} +1 -1
- package/webapp/dist/assets/{blockDiagram-WCTKOSBZ-BndD4gLF.js → blockDiagram-WCTKOSBZ-Bu53fwsa.js} +7 -7
- package/webapp/dist/assets/{blockDiagram-WCTKOSBZ-BndD4gLF.js.map → blockDiagram-WCTKOSBZ-Bu53fwsa.js.map} +1 -1
- package/webapp/dist/assets/{c4Diagram-IC4MRINW-fGAz7umu.js → c4Diagram-IC4MRINW-D9-FJ4LB.js} +3 -3
- package/webapp/dist/assets/{c4Diagram-IC4MRINW-fGAz7umu.js.map → c4Diagram-IC4MRINW-D9-FJ4LB.js.map} +1 -1
- package/webapp/dist/assets/{channel-Blt7S1Sn.js → channel-D1VPurpu.js} +2 -2
- package/webapp/dist/assets/{channel-Blt7S1Sn.js.map → channel-D1VPurpu.js.map} +1 -1
- package/webapp/dist/assets/{chunk-4BX2VUAB-C2FKcyob.js → chunk-4BX2VUAB-DB14wcWS.js} +2 -2
- package/webapp/dist/assets/{chunk-4BX2VUAB-C2FKcyob.js.map → chunk-4BX2VUAB-DB14wcWS.js.map} +1 -1
- package/webapp/dist/assets/{chunk-55IACEB6-CN8ZmdUP.js → chunk-55IACEB6-C5KCE85A.js} +2 -2
- package/webapp/dist/assets/{chunk-55IACEB6-CN8ZmdUP.js.map → chunk-55IACEB6-C5KCE85A.js.map} +1 -1
- package/webapp/dist/assets/{chunk-FMBD7UC4-B9Uq2tt2.js → chunk-FMBD7UC4-Bb6zm0iH.js} +2 -2
- package/webapp/dist/assets/{chunk-FMBD7UC4-B9Uq2tt2.js.map → chunk-FMBD7UC4-Bb6zm0iH.js.map} +1 -1
- package/webapp/dist/assets/{chunk-JSJVCQXG-vVrXi8LV.js → chunk-JSJVCQXG-CJPrv6fM.js} +2 -2
- package/webapp/dist/assets/{chunk-JSJVCQXG-vVrXi8LV.js.map → chunk-JSJVCQXG-CJPrv6fM.js.map} +1 -1
- package/webapp/dist/assets/{chunk-KX2RTZJC-DtZmdBq3.js → chunk-KX2RTZJC-8J1Swk7E.js} +2 -2
- package/webapp/dist/assets/{chunk-KX2RTZJC-DtZmdBq3.js.map → chunk-KX2RTZJC-8J1Swk7E.js.map} +1 -1
- package/webapp/dist/assets/{chunk-NQ4KR5QH-C3rU1XEw.js → chunk-NQ4KR5QH-DySHY4x9.js} +4 -4
- package/webapp/dist/assets/{chunk-NQ4KR5QH-C3rU1XEw.js.map → chunk-NQ4KR5QH-DySHY4x9.js.map} +1 -1
- package/webapp/dist/assets/{chunk-QZHKN3VN-CeHQK_vs.js → chunk-QZHKN3VN-CTO5Z-4P.js} +2 -2
- package/webapp/dist/assets/{chunk-QZHKN3VN-CeHQK_vs.js.map → chunk-QZHKN3VN-CTO5Z-4P.js.map} +1 -1
- package/webapp/dist/assets/{chunk-WL4C6EOR-Bb8GUwSo.js → chunk-WL4C6EOR-Ci_Ec8Ax.js} +6 -6
- package/webapp/dist/assets/{chunk-WL4C6EOR-Bb8GUwSo.js.map → chunk-WL4C6EOR-Ci_Ec8Ax.js.map} +1 -1
- package/webapp/dist/assets/{classDiagram-VBA2DB6C-CmP7X8Fj.js → classDiagram-VBA2DB6C-CxAxBvv7.js} +7 -7
- package/webapp/dist/assets/{classDiagram-VBA2DB6C-CmP7X8Fj.js.map → classDiagram-VBA2DB6C-CxAxBvv7.js.map} +1 -1
- package/webapp/dist/assets/{classDiagram-v2-RAHNMMFH-CmP7X8Fj.js → classDiagram-v2-RAHNMMFH-CxAxBvv7.js} +7 -7
- package/webapp/dist/assets/{classDiagram-v2-RAHNMMFH-CmP7X8Fj.js.map → classDiagram-v2-RAHNMMFH-CxAxBvv7.js.map} +1 -1
- package/webapp/dist/assets/{clone-CzGKO47U.js → clone-ePiNaiNY.js} +2 -2
- package/webapp/dist/assets/{clone-CzGKO47U.js.map → clone-ePiNaiNY.js.map} +1 -1
- package/webapp/dist/assets/{cose-bilkent-S5V4N54A-3wbordhk.js → cose-bilkent-S5V4N54A-j-Ex-Sef.js} +2 -2
- package/webapp/dist/assets/{cose-bilkent-S5V4N54A-3wbordhk.js.map → cose-bilkent-S5V4N54A-j-Ex-Sef.js.map} +1 -1
- package/webapp/dist/assets/{dagre-KLK3FWXG-DrM7hFR5.js → dagre-KLK3FWXG-ihZ2wOCM.js} +7 -7
- package/webapp/dist/assets/{dagre-KLK3FWXG-DrM7hFR5.js.map → dagre-KLK3FWXG-ihZ2wOCM.js.map} +1 -1
- package/webapp/dist/assets/{diagram-E7M64L7V-D-bQEVk2.js → diagram-E7M64L7V-Cp4GQGS7.js} +8 -8
- package/webapp/dist/assets/{diagram-E7M64L7V-D-bQEVk2.js.map → diagram-E7M64L7V-Cp4GQGS7.js.map} +1 -1
- package/webapp/dist/assets/{diagram-IFDJBPK2-MaU_xQfR.js → diagram-IFDJBPK2-B70cgyS5.js} +7 -7
- package/webapp/dist/assets/{diagram-IFDJBPK2-MaU_xQfR.js.map → diagram-IFDJBPK2-B70cgyS5.js.map} +1 -1
- package/webapp/dist/assets/{diagram-P4PSJMXO-BLklmc9h.js → diagram-P4PSJMXO-DMOv7eKE.js} +7 -7
- package/webapp/dist/assets/{diagram-P4PSJMXO-BLklmc9h.js.map → diagram-P4PSJMXO-DMOv7eKE.js.map} +1 -1
- package/webapp/dist/assets/{erDiagram-INFDFZHY-DHIVFsNv.js → erDiagram-INFDFZHY-BKpXWjIc.js} +5 -5
- package/webapp/dist/assets/{erDiagram-INFDFZHY-DHIVFsNv.js.map → erDiagram-INFDFZHY-BKpXWjIc.js.map} +1 -1
- package/webapp/dist/assets/{flowDiagram-PKNHOUZH-HbMxSdg1.js → flowDiagram-PKNHOUZH-DgrItj0h.js} +7 -7
- package/webapp/dist/assets/{flowDiagram-PKNHOUZH-HbMxSdg1.js.map → flowDiagram-PKNHOUZH-DgrItj0h.js.map} +1 -1
- package/webapp/dist/assets/{ganttDiagram-A5KZAMGK-CxBETPNC.js → ganttDiagram-A5KZAMGK-7-8hlYsT.js} +3 -3
- package/webapp/dist/assets/{ganttDiagram-A5KZAMGK-CxBETPNC.js.map → ganttDiagram-A5KZAMGK-7-8hlYsT.js.map} +1 -1
- package/webapp/dist/assets/{gitGraphDiagram-K3NZZRJ6-DPV-fFTC.js → gitGraphDiagram-K3NZZRJ6-cPSaCUUk.js} +8 -8
- package/webapp/dist/assets/{gitGraphDiagram-K3NZZRJ6-DPV-fFTC.js.map → gitGraphDiagram-K3NZZRJ6-cPSaCUUk.js.map} +1 -1
- package/webapp/dist/assets/{graph-C0MlCXJg.js → graph-CAlg3tEk.js} +3 -3
- package/webapp/dist/assets/{graph-C0MlCXJg.js.map → graph-CAlg3tEk.js.map} +1 -1
- package/webapp/dist/assets/{index-CzHjX_nj.js → index-DLTS_eOh.js} +123 -47
- package/webapp/dist/assets/{index-CzHjX_nj.js.map → index-DLTS_eOh.js.map} +1 -1
- package/webapp/dist/assets/{infoDiagram-LFFYTUFH-ChTC2kD-.js → infoDiagram-LFFYTUFH-CHJHvxMC.js} +6 -6
- package/webapp/dist/assets/{infoDiagram-LFFYTUFH-ChTC2kD-.js.map → infoDiagram-LFFYTUFH-CHJHvxMC.js.map} +1 -1
- package/webapp/dist/assets/{ishikawaDiagram-PHBUUO56--aJd3LM6.js → ishikawaDiagram-PHBUUO56-S8N-XZ8E.js} +2 -2
- package/webapp/dist/assets/{ishikawaDiagram-PHBUUO56--aJd3LM6.js.map → ishikawaDiagram-PHBUUO56-S8N-XZ8E.js.map} +1 -1
- package/webapp/dist/assets/{journeyDiagram-4ABVD52K-Bzd3cZTs.js → journeyDiagram-4ABVD52K-ChHNpMtH.js} +5 -5
- package/webapp/dist/assets/{journeyDiagram-4ABVD52K-Bzd3cZTs.js.map → journeyDiagram-4ABVD52K-ChHNpMtH.js.map} +1 -1
- package/webapp/dist/assets/{kanban-definition-K7BYSVSG-DiFHcs58.js → kanban-definition-K7BYSVSG-Cqxd99wZ.js} +3 -3
- package/webapp/dist/assets/{kanban-definition-K7BYSVSG-DiFHcs58.js.map → kanban-definition-K7BYSVSG-Cqxd99wZ.js.map} +1 -1
- package/webapp/dist/assets/{layout-B9Hsf17G.js → layout-uOLcVthp.js} +5 -5
- package/webapp/dist/assets/{layout-B9Hsf17G.js.map → layout-uOLcVthp.js.map} +1 -1
- package/webapp/dist/assets/{linear-6xqU78Yu.js → linear-Ga_f4H_w.js} +2 -2
- package/webapp/dist/assets/{linear-6xqU78Yu.js.map → linear-Ga_f4H_w.js.map} +1 -1
- package/webapp/dist/assets/{mindmap-definition-YRQLILUH-BUI6M5up.js → mindmap-definition-YRQLILUH-TSH7wOlZ.js} +4 -4
- package/webapp/dist/assets/{mindmap-definition-YRQLILUH-BUI6M5up.js.map → mindmap-definition-YRQLILUH-TSH7wOlZ.js.map} +1 -1
- package/webapp/dist/assets/{pieDiagram-SKSYHLDU-FCGp31Lg.js → pieDiagram-SKSYHLDU-DPXszqns.js} +8 -8
- package/webapp/dist/assets/{pieDiagram-SKSYHLDU-FCGp31Lg.js.map → pieDiagram-SKSYHLDU-DPXszqns.js.map} +1 -1
- package/webapp/dist/assets/{quadrantDiagram-337W2JSQ-bJcMGXM6.js → quadrantDiagram-337W2JSQ-BgA_GVhR.js} +3 -3
- package/webapp/dist/assets/{quadrantDiagram-337W2JSQ-bJcMGXM6.js.map → quadrantDiagram-337W2JSQ-BgA_GVhR.js.map} +1 -1
- package/webapp/dist/assets/{requirementDiagram-Z7DCOOCP-BghuL9nW.js → requirementDiagram-Z7DCOOCP-CM-47daj.js} +4 -4
- package/webapp/dist/assets/{requirementDiagram-Z7DCOOCP-BghuL9nW.js.map → requirementDiagram-Z7DCOOCP-CM-47daj.js.map} +1 -1
- package/webapp/dist/assets/{sankeyDiagram-WA2Y5GQK-DpuFpv6d.js → sankeyDiagram-WA2Y5GQK-CxDCwHAj.js} +2 -2
- package/webapp/dist/assets/{sankeyDiagram-WA2Y5GQK-DpuFpv6d.js.map → sankeyDiagram-WA2Y5GQK-CxDCwHAj.js.map} +1 -1
- package/webapp/dist/assets/{sequenceDiagram-2WXFIKYE-BM6rPANl.js → sequenceDiagram-2WXFIKYE-1UJP6Fff.js} +4 -4
- package/webapp/dist/assets/{sequenceDiagram-2WXFIKYE-BM6rPANl.js.map → sequenceDiagram-2WXFIKYE-1UJP6Fff.js.map} +1 -1
- package/webapp/dist/assets/{stateDiagram-RAJIS63D-C_jQSre0.js → stateDiagram-RAJIS63D-B2aqr0KQ.js} +9 -9
- package/webapp/dist/assets/{stateDiagram-RAJIS63D-C_jQSre0.js.map → stateDiagram-RAJIS63D-B2aqr0KQ.js.map} +1 -1
- package/webapp/dist/assets/{stateDiagram-v2-FVOUBMTO-BbQxj-LI.js → stateDiagram-v2-FVOUBMTO-DvmkevVb.js} +5 -5
- package/webapp/dist/assets/{stateDiagram-v2-FVOUBMTO-BbQxj-LI.js.map → stateDiagram-v2-FVOUBMTO-DvmkevVb.js.map} +1 -1
- package/webapp/dist/assets/{timeline-definition-YZTLITO2-qVPiYzDY.js → timeline-definition-YZTLITO2-CaOrqzT1.js} +3 -3
- package/webapp/dist/assets/{timeline-definition-YZTLITO2-qVPiYzDY.js.map → timeline-definition-YZTLITO2-CaOrqzT1.js.map} +1 -1
- package/webapp/dist/assets/{treemap-KZPCXAKY-CH_Gjw5E.js → treemap-KZPCXAKY-CWs_8GJm.js} +5 -5
- package/webapp/dist/assets/{treemap-KZPCXAKY-CH_Gjw5E.js.map → treemap-KZPCXAKY-CWs_8GJm.js.map} +1 -1
- package/webapp/dist/assets/{vennDiagram-LZ73GAT5-rpOhNWvx.js → vennDiagram-LZ73GAT5-BuBxFDz6.js} +2 -2
- package/webapp/dist/assets/{vennDiagram-LZ73GAT5-rpOhNWvx.js.map → vennDiagram-LZ73GAT5-BuBxFDz6.js.map} +1 -1
- package/webapp/dist/assets/{xychartDiagram-JWTSCODW-D1TcBJrI.js → xychartDiagram-JWTSCODW-ChjL-_2b.js} +3 -3
- package/webapp/dist/assets/{xychartDiagram-JWTSCODW-D1TcBJrI.js.map → xychartDiagram-JWTSCODW-ChjL-_2b.js.map} +1 -1
- package/webapp/dist/index.html +1 -1
package/dist/tools/os.js
CHANGED
|
@@ -15,6 +15,8 @@ exports.resolveShellCmdSpawnSpecForTests = resolveShellCmdSpawnSpecForTests;
|
|
|
15
15
|
exports.detectWindowsShellUsageWarningForTests = detectWindowsShellUsageWarningForTests;
|
|
16
16
|
exports.formatShellExecutionErrorForTests = formatShellExecutionErrorForTests;
|
|
17
17
|
exports.resolveReadonlyShellSpawnSpecForTests = resolveReadonlyShellSpawnSpecForTests;
|
|
18
|
+
exports.validateReadonlyShellCommandForTests = validateReadonlyShellCommandForTests;
|
|
19
|
+
exports.detectReadonlyShellForbiddenHiddenDirAccessForTests = detectReadonlyShellForbiddenHiddenDirAccessForTests;
|
|
18
20
|
const time_1 = require("@longrun-ai/kernel/utils/time");
|
|
19
21
|
const child_process_1 = require("child_process");
|
|
20
22
|
const crypto_1 = __importDefault(require("crypto"));
|
|
@@ -1194,7 +1196,7 @@ const readonlyShellSchema = {
|
|
|
1194
1196
|
properties: {
|
|
1195
1197
|
command: {
|
|
1196
1198
|
type: 'string',
|
|
1197
|
-
description: 'Read-only shell command (allowed prefixes: cat, rg, sed, ls, nl, wc, head, tail, stat, file, uname, whoami, id, echo, pwd, which, date, diff, realpath, readlink, printf, cut, sort, uniq, tr, awk, shasum, sha256sum, md5sum, uuid, git show, git status, git diff, git log, git blame, find, tree, jq, true; exact version probes: node --version|-v, python3 --version|-V; also allows: git -C <relative-path> <show|status|diff|log|blame> ...; also allows: cd <relative-path> && <allowed command...> (or ||); command chains via |/&&/|| are validated segment-by-segment)',
|
|
1199
|
+
description: 'Read-only shell command (common allowed prefixes: cat, rg, sed, ls, nl, wc, head, tail, stat, file, uname, whoami, id, echo, pwd, which, date, diff, realpath, readlink, printf, cut, sort, uniq, tr, awk, shasum, sha256sum, md5sum, uuid, git show, git status, git diff, git log, git blame, find, tree, jq, true; Windows also allows: where, fc, findstr, dir, type, more, ver; exact version probes: node --version|-v, python3/python/py --version|-V; also allows: git -C <relative-path> <show|status|diff|log|blame> ...; also allows: cd <relative-path> && <allowed command...> (or ||); command chains via |/&&/|| are validated segment-by-segment)',
|
|
1198
1200
|
},
|
|
1199
1201
|
timeout_ms: {
|
|
1200
1202
|
type: 'number',
|
|
@@ -1621,7 +1623,7 @@ exports.shellCmdTool = {
|
|
|
1621
1623
|
}
|
|
1622
1624
|
},
|
|
1623
1625
|
};
|
|
1624
|
-
const
|
|
1626
|
+
const readonlyShellCommonAllowedPrefixes = [
|
|
1625
1627
|
'cat',
|
|
1626
1628
|
'rg',
|
|
1627
1629
|
'sed',
|
|
@@ -1662,22 +1664,42 @@ const readonlyShellAllowedPrefixes = [
|
|
|
1662
1664
|
'jq',
|
|
1663
1665
|
'true',
|
|
1664
1666
|
];
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
+
const readonlyShellWindowsAllowedPrefixes = [
|
|
1668
|
+
'where',
|
|
1669
|
+
'fc',
|
|
1670
|
+
'findstr',
|
|
1671
|
+
'dir',
|
|
1672
|
+
'type',
|
|
1673
|
+
'more',
|
|
1674
|
+
'ver',
|
|
1675
|
+
];
|
|
1676
|
+
function getReadonlyShellAllowedPrefixes(platform = process.platform) {
|
|
1677
|
+
if (platform === 'win32') {
|
|
1678
|
+
return [...readonlyShellCommonAllowedPrefixes, ...readonlyShellWindowsAllowedPrefixes];
|
|
1679
|
+
}
|
|
1680
|
+
return readonlyShellCommonAllowedPrefixes;
|
|
1681
|
+
}
|
|
1682
|
+
function isAllowedReadonlyShellVersionProbe(command, platform = process.platform) {
|
|
1683
|
+
const tokens = splitShellTokens(command, platform);
|
|
1667
1684
|
if (tokens.length !== 2)
|
|
1668
1685
|
return false;
|
|
1669
|
-
const
|
|
1686
|
+
const cmdRaw = tokens[0]?.text ?? '';
|
|
1687
|
+
const cmd = platform === 'win32' ? cmdRaw.toLowerCase() : cmdRaw;
|
|
1670
1688
|
const flag = tokens[1]?.text ?? '';
|
|
1671
1689
|
if (cmd === 'node')
|
|
1672
1690
|
return flag === '--version' || flag === '-v';
|
|
1673
|
-
if (cmd === 'python3')
|
|
1691
|
+
if (cmd === 'python3' || cmd === 'python' || cmd === 'py') {
|
|
1674
1692
|
return flag === '--version' || flag === '-V';
|
|
1693
|
+
}
|
|
1675
1694
|
return false;
|
|
1676
1695
|
}
|
|
1677
|
-
function validateReadonlyShellCommand(command) {
|
|
1678
|
-
return validateReadonlyShellCommandInternal(command.trimStart(), 0);
|
|
1696
|
+
function validateReadonlyShellCommand(command, platform = process.platform) {
|
|
1697
|
+
return validateReadonlyShellCommandInternal(command.trimStart(), platform, 0);
|
|
1679
1698
|
}
|
|
1680
|
-
function
|
|
1699
|
+
function validateReadonlyShellCommandForTests(command, platform) {
|
|
1700
|
+
return validateReadonlyShellCommand(command, platform);
|
|
1701
|
+
}
|
|
1702
|
+
function validateReadonlyShellCommandInternal(command, platform, depth) {
|
|
1681
1703
|
if (depth > 8) {
|
|
1682
1704
|
return {
|
|
1683
1705
|
ok: false,
|
|
@@ -1688,8 +1710,8 @@ function validateReadonlyShellCommandInternal(command, depth) {
|
|
|
1688
1710
|
};
|
|
1689
1711
|
}
|
|
1690
1712
|
const trimmed = command.trimStart();
|
|
1691
|
-
if (trimmed
|
|
1692
|
-
const parsed = parseCdChain(trimmed);
|
|
1713
|
+
if (startsWithReadonlyShellCommand(trimmed, 'cd', platform)) {
|
|
1714
|
+
const parsed = parseCdChain(trimmed, platform);
|
|
1693
1715
|
if (!parsed) {
|
|
1694
1716
|
return { ok: false, failure: { reason: 'INVALID_CD_SYNTAX', rejectedSegment: trimmed } };
|
|
1695
1717
|
}
|
|
@@ -1703,9 +1725,9 @@ function validateReadonlyShellCommandInternal(command, depth) {
|
|
|
1703
1725
|
},
|
|
1704
1726
|
};
|
|
1705
1727
|
}
|
|
1706
|
-
return validateReadonlyShellCommandInternal(parsed.rest, depth + 1);
|
|
1728
|
+
return validateReadonlyShellCommandInternal(parsed.rest, platform, depth + 1);
|
|
1707
1729
|
}
|
|
1708
|
-
const chainParsed = splitTopLevelReadonlyShellChain(trimmed);
|
|
1730
|
+
const chainParsed = splitTopLevelReadonlyShellChain(trimmed, platform);
|
|
1709
1731
|
if (!chainParsed.ok) {
|
|
1710
1732
|
return {
|
|
1711
1733
|
ok: false,
|
|
@@ -1717,23 +1739,27 @@ function validateReadonlyShellCommandInternal(command, depth) {
|
|
|
1717
1739
|
}
|
|
1718
1740
|
if (chainParsed.segments.length > 1) {
|
|
1719
1741
|
for (const segment of chainParsed.segments) {
|
|
1720
|
-
const segmentValidation = validateReadonlyShellCommandInternal(segment, depth + 1);
|
|
1742
|
+
const segmentValidation = validateReadonlyShellCommandInternal(segment, platform, depth + 1);
|
|
1721
1743
|
if (!segmentValidation.ok) {
|
|
1722
1744
|
return segmentValidation;
|
|
1723
1745
|
}
|
|
1724
1746
|
}
|
|
1725
1747
|
return { ok: true };
|
|
1726
1748
|
}
|
|
1727
|
-
if (trimmed
|
|
1749
|
+
if (startsWithReadonlyShellCommand(trimmed, 'git', platform) && /^git\s+-C\s+/iu.test(trimmed)) {
|
|
1728
1750
|
// Allow a narrow, read-only subset of `git -C <dir> <subcommand> ...` as long as <dir> looks
|
|
1729
1751
|
// like a safe *relative* path (no absolute paths / parent traversal). This avoids accidentally
|
|
1730
1752
|
// inspecting outside the rtws with `git -C /...`.
|
|
1731
1753
|
const tokens = trimmed.split(/\s+/g);
|
|
1732
1754
|
// Expected: git -C <dir> <subcommand> ...
|
|
1733
|
-
|
|
1755
|
+
const gitCommand = tokens[0] ?? '';
|
|
1756
|
+
const gitFlag = tokens[1] ?? '';
|
|
1757
|
+
const isGitCommand = platform === 'win32' ? gitCommand.toLowerCase() === 'git' : gitCommand === 'git';
|
|
1758
|
+
if (tokens.length >= 4 && isGitCommand && gitFlag === '-C') {
|
|
1734
1759
|
const dirRaw = tokens[2] ?? '';
|
|
1735
1760
|
const dir = dirRaw.replace(/^["']|["']$/g, '');
|
|
1736
|
-
const
|
|
1761
|
+
const subcommandRaw = tokens[3] ?? '';
|
|
1762
|
+
const subcommand = platform === 'win32' ? subcommandRaw.toLowerCase() : subcommandRaw;
|
|
1737
1763
|
if (!isSafeRelativePath(dir)) {
|
|
1738
1764
|
return { ok: false, failure: { reason: 'GIT_C_UNSAFE_PATH', rejectedSegment: trimmed } };
|
|
1739
1765
|
}
|
|
@@ -1742,6 +1768,12 @@ function validateReadonlyShellCommandInternal(command, depth) {
|
|
|
1742
1768
|
subcommand === 'diff' ||
|
|
1743
1769
|
subcommand === 'log' ||
|
|
1744
1770
|
subcommand === 'blame') {
|
|
1771
|
+
if (hasUnsafeReadonlyShellCommandOptions(trimmed, platform)) {
|
|
1772
|
+
return {
|
|
1773
|
+
ok: false,
|
|
1774
|
+
failure: { reason: 'UNSAFE_SHELL_SYNTAX', rejectedSegment: trimmed },
|
|
1775
|
+
};
|
|
1776
|
+
}
|
|
1745
1777
|
return { ok: true };
|
|
1746
1778
|
}
|
|
1747
1779
|
return {
|
|
@@ -1751,17 +1783,93 @@ function validateReadonlyShellCommandInternal(command, depth) {
|
|
|
1751
1783
|
}
|
|
1752
1784
|
return { ok: false, failure: { reason: 'GIT_C_INVALID', rejectedSegment: trimmed } };
|
|
1753
1785
|
}
|
|
1754
|
-
if (isAllowedReadonlyShellVersionProbe(trimmed)) {
|
|
1786
|
+
if (isAllowedReadonlyShellVersionProbe(trimmed, platform)) {
|
|
1755
1787
|
return { ok: true };
|
|
1756
1788
|
}
|
|
1757
|
-
for (const prefix of
|
|
1758
|
-
if (trimmed
|
|
1789
|
+
for (const prefix of getReadonlyShellAllowedPrefixes(platform)) {
|
|
1790
|
+
if (matchesReadonlyShellPrefix(trimmed, prefix, platform)) {
|
|
1791
|
+
if (hasUnsafeReadonlyShellCommandOptions(trimmed, platform)) {
|
|
1792
|
+
return {
|
|
1793
|
+
ok: false,
|
|
1794
|
+
failure: { reason: 'UNSAFE_SHELL_SYNTAX', rejectedSegment: trimmed },
|
|
1795
|
+
};
|
|
1796
|
+
}
|
|
1759
1797
|
return { ok: true };
|
|
1760
1798
|
}
|
|
1761
1799
|
}
|
|
1762
1800
|
return { ok: false, failure: { reason: 'COMMAND_NOT_ALLOWLISTED', rejectedSegment: trimmed } };
|
|
1763
1801
|
}
|
|
1764
|
-
function
|
|
1802
|
+
function hasUnsafeReadonlyShellCommandOptions(command, platform) {
|
|
1803
|
+
const tokens = splitShellTokens(command, platform);
|
|
1804
|
+
const cmdRaw = tokens[0]?.text ?? '';
|
|
1805
|
+
const cmd = platform === 'win32' ? cmdRaw.toLowerCase() : cmdRaw;
|
|
1806
|
+
const args = tokens.slice(1).map((token) => token.text);
|
|
1807
|
+
if (cmd === 'awk') {
|
|
1808
|
+
return args.some((arg) => /\bsystem\s*\(/.test(arg) ||
|
|
1809
|
+
arg.includes('>') ||
|
|
1810
|
+
arg.includes('|') ||
|
|
1811
|
+
arg === '-i' ||
|
|
1812
|
+
arg.startsWith('-i') ||
|
|
1813
|
+
arg === '--include' ||
|
|
1814
|
+
arg.startsWith('--include=') ||
|
|
1815
|
+
arg === '-l' ||
|
|
1816
|
+
arg.startsWith('-l') ||
|
|
1817
|
+
arg === '--load' ||
|
|
1818
|
+
arg.startsWith('--load='));
|
|
1819
|
+
}
|
|
1820
|
+
if (cmd === 'sed') {
|
|
1821
|
+
return args.some((arg) => arg === '-i' ||
|
|
1822
|
+
arg.startsWith('-i') ||
|
|
1823
|
+
arg === '--in-place' ||
|
|
1824
|
+
arg.startsWith('--in-place=') ||
|
|
1825
|
+
looksLikeSedWriteScript(arg));
|
|
1826
|
+
}
|
|
1827
|
+
if (cmd === 'find') {
|
|
1828
|
+
return args.some((arg) => [
|
|
1829
|
+
'-delete',
|
|
1830
|
+
'-exec',
|
|
1831
|
+
'-execdir',
|
|
1832
|
+
'-ok',
|
|
1833
|
+
'-okdir',
|
|
1834
|
+
'-fprint',
|
|
1835
|
+
'-fprint0',
|
|
1836
|
+
'-fprintf',
|
|
1837
|
+
'-fls',
|
|
1838
|
+
].includes(arg));
|
|
1839
|
+
}
|
|
1840
|
+
if (cmd === 'git') {
|
|
1841
|
+
return args.some((arg) => arg === '-c' ||
|
|
1842
|
+
arg === '--config-env' ||
|
|
1843
|
+
arg.startsWith('--config-env=') ||
|
|
1844
|
+
arg === '--exec-path' ||
|
|
1845
|
+
arg.startsWith('--exec-path=') ||
|
|
1846
|
+
arg === '--paginate' ||
|
|
1847
|
+
arg === '--output' ||
|
|
1848
|
+
arg.startsWith('--output=') ||
|
|
1849
|
+
arg === '--ext-diff' ||
|
|
1850
|
+
arg === '--external-diff' ||
|
|
1851
|
+
arg === '--textconv');
|
|
1852
|
+
}
|
|
1853
|
+
if (cmd === 'sort') {
|
|
1854
|
+
return args.some((arg) => arg === '-o' || arg.startsWith('-o') || arg.startsWith('--output'));
|
|
1855
|
+
}
|
|
1856
|
+
if (cmd === 'rg') {
|
|
1857
|
+
return args.some((arg) => arg === '--pre' ||
|
|
1858
|
+
arg.startsWith('--pre=') ||
|
|
1859
|
+
arg === '--hostname-bin' ||
|
|
1860
|
+
arg.startsWith('--hostname-bin='));
|
|
1861
|
+
}
|
|
1862
|
+
if (cmd === 'date') {
|
|
1863
|
+
return args.some((arg) => arg === '-s' || arg.startsWith('-s') || arg === '--set' || arg.startsWith('--set='));
|
|
1864
|
+
}
|
|
1865
|
+
return false;
|
|
1866
|
+
}
|
|
1867
|
+
function looksLikeSedWriteScript(script) {
|
|
1868
|
+
const commandParts = script.split(/[;{}]/g);
|
|
1869
|
+
return commandParts.some((part) => /^\s*(?:[0-9,$!+~\/\\-]+|\/(?:\\.|[^/])*\/)?w(?:\s|$)/.test(part) ||
|
|
1870
|
+
/s(.)(?:\\.|(?!\1).)*\1(?:\\.|(?!\1).)*\1[0-9gIpMew]*w/.test(part));
|
|
1871
|
+
}
|
|
1872
|
+
function splitTopLevelReadonlyShellChain(command, platform = process.platform) {
|
|
1765
1873
|
const segments = [];
|
|
1766
1874
|
let quote = null;
|
|
1767
1875
|
let escape = false;
|
|
@@ -1775,6 +1883,7 @@ function splitTopLevelReadonlyShellChain(command) {
|
|
|
1775
1883
|
};
|
|
1776
1884
|
for (let i = 0; i < command.length; i++) {
|
|
1777
1885
|
const ch = command[i] ?? '';
|
|
1886
|
+
const next = command[i + 1] ?? '';
|
|
1778
1887
|
if (escape) {
|
|
1779
1888
|
escape = false;
|
|
1780
1889
|
continue;
|
|
@@ -1783,20 +1892,38 @@ function splitTopLevelReadonlyShellChain(command) {
|
|
|
1783
1892
|
if (ch === quote) {
|
|
1784
1893
|
quote = null;
|
|
1785
1894
|
}
|
|
1786
|
-
else if (
|
|
1895
|
+
else if (quote === '"' &&
|
|
1896
|
+
(ch === '`' || (ch === '$' && next === '(') || (platform === 'win32' && ch === '%'))) {
|
|
1897
|
+
return {
|
|
1898
|
+
ok: false,
|
|
1899
|
+
reason: 'UNSAFE_SHELL_SYNTAX',
|
|
1900
|
+
rejectedSegment: command.slice(segmentStart).trim() || command.trim(),
|
|
1901
|
+
};
|
|
1902
|
+
}
|
|
1903
|
+
else if (ch === '\\' && quote === '"' && platform !== 'win32') {
|
|
1787
1904
|
escape = true;
|
|
1788
1905
|
}
|
|
1789
1906
|
continue;
|
|
1790
1907
|
}
|
|
1791
|
-
if (ch === '\\') {
|
|
1908
|
+
if (ch === '\\' && platform !== 'win32') {
|
|
1792
1909
|
escape = true;
|
|
1793
1910
|
continue;
|
|
1794
1911
|
}
|
|
1795
|
-
if (ch === "'
|
|
1912
|
+
if (ch === '"' || (ch === "'" && platform !== 'win32')) {
|
|
1796
1913
|
quote = ch;
|
|
1797
1914
|
continue;
|
|
1798
1915
|
}
|
|
1799
|
-
|
|
1916
|
+
if (ch === '`' ||
|
|
1917
|
+
(ch === '$' && next === '(') ||
|
|
1918
|
+
ch === '<' ||
|
|
1919
|
+
ch === '>' ||
|
|
1920
|
+
(platform === 'win32' && ch === '%')) {
|
|
1921
|
+
return {
|
|
1922
|
+
ok: false,
|
|
1923
|
+
reason: 'UNSAFE_SHELL_SYNTAX',
|
|
1924
|
+
rejectedSegment: command.slice(segmentStart).trim() || command.trim(),
|
|
1925
|
+
};
|
|
1926
|
+
}
|
|
1800
1927
|
if ((ch === '&' && next === '&') || (ch === '|' && next === '|')) {
|
|
1801
1928
|
if (!pushSegment(i)) {
|
|
1802
1929
|
return {
|
|
@@ -1861,15 +1988,28 @@ function splitTopLevelReadonlyShellChain(command) {
|
|
|
1861
1988
|
function isSafeRelativePath(dir) {
|
|
1862
1989
|
const hasParentTraversal = /(^|[\\/])\.\.([\\/]|$)/.test(dir);
|
|
1863
1990
|
const isAbsoluteOrHome = dir.startsWith('/') ||
|
|
1991
|
+
dir.startsWith('\\') ||
|
|
1864
1992
|
dir.startsWith('~') ||
|
|
1865
|
-
/^[A-Za-z]
|
|
1993
|
+
/^[A-Za-z]:/.test(dir) ||
|
|
1866
1994
|
dir.startsWith('\\\\');
|
|
1867
1995
|
return !isAbsoluteOrHome && !hasParentTraversal && dir.trim() !== '';
|
|
1868
1996
|
}
|
|
1869
|
-
function
|
|
1997
|
+
function startsWithReadonlyShellCommand(command, executable, platform) {
|
|
1998
|
+
return matchesReadonlyShellPrefix(command, executable, platform);
|
|
1999
|
+
}
|
|
2000
|
+
function matchesReadonlyShellPrefix(command, prefix, platform) {
|
|
2001
|
+
const normalizedCommand = platform === 'win32' ? command.toLowerCase() : command;
|
|
2002
|
+
const normalizedPrefix = platform === 'win32' ? prefix.toLowerCase() : prefix;
|
|
2003
|
+
return (normalizedCommand === normalizedPrefix ||
|
|
2004
|
+
new RegExp(`^${escapeRegexLiteral(normalizedPrefix)}\\s`, 'u').test(normalizedCommand));
|
|
2005
|
+
}
|
|
2006
|
+
function escapeRegexLiteral(value) {
|
|
2007
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
2008
|
+
}
|
|
2009
|
+
function parseCdChain(command, platform) {
|
|
1870
2010
|
// Supports: cd <dir> && <rest> or cd <dir> || <rest>
|
|
1871
|
-
// `<dir>` may be
|
|
1872
|
-
if (
|
|
2011
|
+
// `<dir>` may be quoted; Windows cmd.exe only treats double quotes as quotes.
|
|
2012
|
+
if (!/^cd\s+/iu.test(command))
|
|
1873
2013
|
return null;
|
|
1874
2014
|
let i = 2;
|
|
1875
2015
|
while (i < command.length && /\s/.test(command[i] ?? ''))
|
|
@@ -1878,7 +2018,7 @@ function parseCdChain(command) {
|
|
|
1878
2018
|
return null;
|
|
1879
2019
|
const start = i;
|
|
1880
2020
|
const first = command[i] ?? '';
|
|
1881
|
-
if (first === '"' || first === "'") {
|
|
2021
|
+
if (first === '"' || (first === "'" && platform !== 'win32')) {
|
|
1882
2022
|
const quote = first;
|
|
1883
2023
|
i++;
|
|
1884
2024
|
while (i < command.length && command[i] !== quote)
|
|
@@ -1907,7 +2047,7 @@ function parseCdChain(command) {
|
|
|
1907
2047
|
return null;
|
|
1908
2048
|
return { dir, rest };
|
|
1909
2049
|
}
|
|
1910
|
-
function splitShellTokens(command) {
|
|
2050
|
+
function splitShellTokens(command, platform = process.platform) {
|
|
1911
2051
|
const out = [];
|
|
1912
2052
|
let buf = '';
|
|
1913
2053
|
let quote = null;
|
|
@@ -1929,7 +2069,7 @@ function splitShellTokens(command) {
|
|
|
1929
2069
|
buf += ch;
|
|
1930
2070
|
continue;
|
|
1931
2071
|
}
|
|
1932
|
-
if (ch === "'
|
|
2072
|
+
if (ch === '"' || (ch === "'" && platform !== 'win32')) {
|
|
1933
2073
|
quote = ch;
|
|
1934
2074
|
tokenQuoted = true;
|
|
1935
2075
|
continue;
|
|
@@ -1943,9 +2083,12 @@ function splitShellTokens(command) {
|
|
|
1943
2083
|
push();
|
|
1944
2084
|
return out;
|
|
1945
2085
|
}
|
|
2086
|
+
function normalizeReadonlyShellToken(token, platform) {
|
|
2087
|
+
return platform === 'win32' ? token.toLowerCase() : token;
|
|
2088
|
+
}
|
|
1946
2089
|
function firstReadonlyShellToken(segment) {
|
|
1947
2090
|
const tokens = splitShellTokens(segment.trim());
|
|
1948
|
-
return tokens[0]?.text ?? '';
|
|
2091
|
+
return normalizeReadonlyShellToken(tokens[0]?.text ?? '', process.platform);
|
|
1949
2092
|
}
|
|
1950
2093
|
function getReadonlyShellSuggestionEn(failure) {
|
|
1951
2094
|
const token = firstReadonlyShellToken(failure.rejectedSegment);
|
|
@@ -1957,6 +2100,9 @@ function getReadonlyShellSuggestionEn(failure) {
|
|
|
1957
2100
|
failure.reason === 'CHAIN_PARSE_TRAILING_ESCAPE') {
|
|
1958
2101
|
return 'Fix shell quoting first, then run an allowlisted segment (for example: `ls` or `rg <pattern>`).';
|
|
1959
2102
|
}
|
|
2103
|
+
if (failure.reason === 'UNSAFE_SHELL_SYNTAX') {
|
|
2104
|
+
return 'Do not use redirects, command substitution, or Windows environment expansion in `readonly_shell`; run a plain allowlisted inspection command.';
|
|
2105
|
+
}
|
|
1960
2106
|
if (failure.reason === 'INVALID_CD_SYNTAX' || failure.reason === 'UNSAFE_RELATIVE_PATH') {
|
|
1961
2107
|
return 'Use `cd <relative-path> && <allowed command...>`.';
|
|
1962
2108
|
}
|
|
@@ -1970,8 +2116,8 @@ function getReadonlyShellSuggestionEn(failure) {
|
|
|
1970
2116
|
}
|
|
1971
2117
|
if (token === 'node')
|
|
1972
2118
|
return 'Only version probes are allowed: `node --version || true`.';
|
|
1973
|
-
if (token === 'python3' || token === 'python') {
|
|
1974
|
-
return 'Only version probes are allowed: `python3 --version || true
|
|
2119
|
+
if (token === 'python3' || token === 'python' || token === 'py') {
|
|
2120
|
+
return 'Only version probes are allowed: `python3 --version || true` (or `python --version` / `py --version` on Windows).';
|
|
1975
2121
|
}
|
|
1976
2122
|
if (token === 'false')
|
|
1977
2123
|
return 'Use `true` as fallback (for example: `ls || true`).';
|
|
@@ -1992,6 +2138,9 @@ function getReadonlyShellSuggestionZh(failure) {
|
|
|
1992
2138
|
failure.reason === 'CHAIN_PARSE_TRAILING_ESCAPE') {
|
|
1993
2139
|
return '先修正引号/转义,再执行白名单子命令(例如:`ls` 或 `rg <pattern>`)。';
|
|
1994
2140
|
}
|
|
2141
|
+
if (failure.reason === 'UNSAFE_SHELL_SYNTAX') {
|
|
2142
|
+
return '请勿在 `readonly_shell` 中使用重定向或命令替换;请直接运行白名单检查命令。';
|
|
2143
|
+
}
|
|
1995
2144
|
if (failure.reason === 'INVALID_CD_SYNTAX' || failure.reason === 'UNSAFE_RELATIVE_PATH') {
|
|
1996
2145
|
return '请使用 `cd <相对路径> && <允许命令...>`。';
|
|
1997
2146
|
}
|
|
@@ -2005,8 +2154,8 @@ function getReadonlyShellSuggestionZh(failure) {
|
|
|
2005
2154
|
}
|
|
2006
2155
|
if (token === 'node')
|
|
2007
2156
|
return '仅允许版本探针:`node --version || true`。';
|
|
2008
|
-
if (token === 'python3' || token === 'python') {
|
|
2009
|
-
return '仅允许版本探针:`python3 --version || true
|
|
2157
|
+
if (token === 'python3' || token === 'python' || token === 'py') {
|
|
2158
|
+
return '仅允许版本探针:`python3 --version || true`(Windows 上也可用 `python --version` / `py --version`)。';
|
|
2010
2159
|
}
|
|
2011
2160
|
if (token === 'false')
|
|
2012
2161
|
return '兜底请用 `true`(例如:`ls || true`)。';
|
|
@@ -2020,8 +2169,9 @@ function getReadonlyShellSuggestionZh(failure) {
|
|
|
2020
2169
|
function normalizeRelFromRtwsRoot(relPath) {
|
|
2021
2170
|
return relPath.replace(/\\/g, '/').replace(/^\/+/, '');
|
|
2022
2171
|
}
|
|
2023
|
-
function detectForbiddenRtwsRootHiddenDir(relFromRoot) {
|
|
2024
|
-
const
|
|
2172
|
+
function detectForbiddenRtwsRootHiddenDir(relFromRoot, platform = process.platform) {
|
|
2173
|
+
const rawNormalized = normalizeRelFromRtwsRoot(relFromRoot);
|
|
2174
|
+
const normalized = platform === 'win32' ? rawNormalized.toLowerCase() : rawNormalized;
|
|
2025
2175
|
if (normalized === '.minds' || normalized.startsWith('.minds/'))
|
|
2026
2176
|
return '.minds';
|
|
2027
2177
|
if (normalized === '.dialogs' || normalized.startsWith('.dialogs/'))
|
|
@@ -2032,26 +2182,42 @@ function resolveRelFromRtwsRoot(workspaceRootAbs, baseDirRel, token) {
|
|
|
2032
2182
|
const abs = path_1.default.resolve(workspaceRootAbs, baseDirRel, token);
|
|
2033
2183
|
return path_1.default.relative(workspaceRootAbs, abs);
|
|
2034
2184
|
}
|
|
2035
|
-
function detectReadonlyShellForbiddenHiddenDirAccess(workspaceRootAbs, command) {
|
|
2185
|
+
function detectReadonlyShellForbiddenHiddenDirAccess(workspaceRootAbs, command, platform = process.platform) {
|
|
2036
2186
|
// Deny access to rtws-root `.minds/**` and `.dialogs/**` only.
|
|
2037
2187
|
// Nested rtws (e.g. `ux-rtws/.minds/**`, `ux-rtws/.dialogs/**`) remains allowed.
|
|
2038
2188
|
let baseDirRel = '.';
|
|
2039
2189
|
let rest = command.trimStart();
|
|
2040
2190
|
// Evaluate chained `cd ... && ...` prefixes and track base dir.
|
|
2041
|
-
while (rest
|
|
2042
|
-
const parsed = parseCdChain(rest);
|
|
2191
|
+
while (startsWithReadonlyShellCommand(rest, 'cd', platform)) {
|
|
2192
|
+
const parsed = parseCdChain(rest, platform);
|
|
2043
2193
|
if (!parsed)
|
|
2044
2194
|
break;
|
|
2045
2195
|
const dir = parsed.dir.replace(/^["']|["']$/g, '');
|
|
2046
2196
|
const relFromRoot = resolveRelFromRtwsRoot(workspaceRootAbs, baseDirRel, dir);
|
|
2047
|
-
const forbidden = detectForbiddenRtwsRootHiddenDir(relFromRoot);
|
|
2197
|
+
const forbidden = detectForbiddenRtwsRootHiddenDir(relFromRoot, platform);
|
|
2048
2198
|
if (forbidden)
|
|
2049
2199
|
return forbidden;
|
|
2050
2200
|
baseDirRel = path_1.default.join(baseDirRel, dir);
|
|
2051
2201
|
rest = parsed.rest.trimStart();
|
|
2052
2202
|
}
|
|
2053
|
-
const
|
|
2054
|
-
|
|
2203
|
+
const chainParsed = splitTopLevelReadonlyShellChain(rest, platform);
|
|
2204
|
+
if (chainParsed.ok && chainParsed.segments.length > 1) {
|
|
2205
|
+
for (const segment of chainParsed.segments) {
|
|
2206
|
+
const forbidden = detectReadonlyShellForbiddenHiddenDirAccessInSegment(workspaceRootAbs, baseDirRel, segment, platform);
|
|
2207
|
+
if (forbidden)
|
|
2208
|
+
return forbidden;
|
|
2209
|
+
}
|
|
2210
|
+
return null;
|
|
2211
|
+
}
|
|
2212
|
+
return detectReadonlyShellForbiddenHiddenDirAccessInSegment(workspaceRootAbs, baseDirRel, rest, platform);
|
|
2213
|
+
}
|
|
2214
|
+
function detectReadonlyShellForbiddenHiddenDirAccessForTests(workspaceRootAbs, command, platform) {
|
|
2215
|
+
return detectReadonlyShellForbiddenHiddenDirAccess(workspaceRootAbs, command, platform);
|
|
2216
|
+
}
|
|
2217
|
+
function detectReadonlyShellForbiddenHiddenDirAccessInSegment(workspaceRootAbs, baseDirRel, segment, platform) {
|
|
2218
|
+
const tokens = splitShellTokens(segment, platform);
|
|
2219
|
+
const cmdRaw = tokens[0]?.text ?? '';
|
|
2220
|
+
const cmd = platform === 'win32' ? cmdRaw.toLowerCase() : cmdRaw;
|
|
2055
2221
|
if (!cmd)
|
|
2056
2222
|
return null;
|
|
2057
2223
|
const tokenText = (i) => {
|
|
@@ -2060,101 +2226,332 @@ function detectReadonlyShellForbiddenHiddenDirAccess(workspaceRootAbs, command)
|
|
|
2060
2226
|
return null;
|
|
2061
2227
|
return v.text;
|
|
2062
2228
|
};
|
|
2063
|
-
// Handle the special allowed form: `git -C <dir> <subcommand> ...`
|
|
2064
|
-
if (cmd === 'git' && tokenText(1) === '-C') {
|
|
2065
|
-
const dirToken = tokenText(2);
|
|
2066
|
-
if (dirToken) {
|
|
2067
|
-
const relFromRoot = resolveRelFromRtwsRoot(workspaceRootAbs, baseDirRel, dirToken);
|
|
2068
|
-
const forbidden = detectForbiddenRtwsRootHiddenDir(relFromRoot);
|
|
2069
|
-
if (forbidden)
|
|
2070
|
-
return forbidden;
|
|
2071
|
-
}
|
|
2072
|
-
return null;
|
|
2073
|
-
}
|
|
2074
2229
|
const checkPathToken = (raw) => {
|
|
2075
2230
|
const trimmed = raw.trim();
|
|
2076
2231
|
if (trimmed === '' || trimmed === '-' || trimmed === '--')
|
|
2077
2232
|
return null;
|
|
2078
2233
|
const relFromRoot = resolveRelFromRtwsRoot(workspaceRootAbs, baseDirRel, trimmed);
|
|
2079
|
-
return detectForbiddenRtwsRootHiddenDir(relFromRoot);
|
|
2234
|
+
return detectForbiddenRtwsRootHiddenDir(relFromRoot, platform);
|
|
2080
2235
|
};
|
|
2236
|
+
const checkGitPathspecToken = (raw, gitBaseDirRel) => {
|
|
2237
|
+
let pathspec = raw.trim();
|
|
2238
|
+
if (pathspec === '' || pathspec === '-' || pathspec === '--')
|
|
2239
|
+
return null;
|
|
2240
|
+
if (pathspec.startsWith(':(')) {
|
|
2241
|
+
const magicEnd = pathspec.indexOf(')');
|
|
2242
|
+
if (magicEnd >= 0)
|
|
2243
|
+
pathspec = pathspec.slice(magicEnd + 1);
|
|
2244
|
+
}
|
|
2245
|
+
if (pathspec.startsWith(':/'))
|
|
2246
|
+
pathspec = pathspec.slice(2);
|
|
2247
|
+
if (pathspec.startsWith(':'))
|
|
2248
|
+
pathspec = pathspec.slice(1);
|
|
2249
|
+
const relFromRoot = resolveRelFromRtwsRoot(workspaceRootAbs, gitBaseDirRel, pathspec);
|
|
2250
|
+
return detectForbiddenRtwsRootHiddenDir(relFromRoot, platform);
|
|
2251
|
+
};
|
|
2252
|
+
if (cmd === 'git') {
|
|
2253
|
+
let gitBaseDirRel = baseDirRel;
|
|
2254
|
+
let argsStart = 2;
|
|
2255
|
+
if (tokenText(1) === '-C') {
|
|
2256
|
+
const dirToken = tokenText(2);
|
|
2257
|
+
if (dirToken) {
|
|
2258
|
+
const relFromRoot = resolveRelFromRtwsRoot(workspaceRootAbs, baseDirRel, dirToken);
|
|
2259
|
+
const forbidden = detectForbiddenRtwsRootHiddenDir(relFromRoot, platform);
|
|
2260
|
+
if (forbidden)
|
|
2261
|
+
return forbidden;
|
|
2262
|
+
gitBaseDirRel = path_1.default.join(baseDirRel, dirToken);
|
|
2263
|
+
}
|
|
2264
|
+
argsStart = 4;
|
|
2265
|
+
}
|
|
2266
|
+
for (let index = argsStart; index < tokens.length; index++) {
|
|
2267
|
+
const token = tokenText(index);
|
|
2268
|
+
if (!token || token === '--' || token.startsWith('-'))
|
|
2269
|
+
continue;
|
|
2270
|
+
const forbidden = checkGitPathspecToken(token, gitBaseDirRel);
|
|
2271
|
+
if (forbidden)
|
|
2272
|
+
return forbidden;
|
|
2273
|
+
}
|
|
2274
|
+
return null;
|
|
2275
|
+
}
|
|
2081
2276
|
// Command-specific parsing to avoid false-positives where `.minds` is just a pattern/filter.
|
|
2082
2277
|
if (cmd === 'rg') {
|
|
2083
|
-
// `rg [OPTIONS] PATTERN [PATH ...]`
|
|
2084
|
-
let
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2278
|
+
// `rg [OPTIONS] PATTERN [PATH ...]`; `rg --files [PATH ...]` has no pattern.
|
|
2279
|
+
let index = 1;
|
|
2280
|
+
let filesMode = false;
|
|
2281
|
+
let patternConsumed = false;
|
|
2282
|
+
while (index < tokens.length) {
|
|
2283
|
+
const token = tokenText(index);
|
|
2284
|
+
if (!token)
|
|
2088
2285
|
break;
|
|
2089
|
-
if (
|
|
2090
|
-
|
|
2286
|
+
if (token === '--') {
|
|
2287
|
+
index += 1;
|
|
2091
2288
|
break;
|
|
2092
2289
|
}
|
|
2093
|
-
if (
|
|
2094
|
-
|
|
2290
|
+
if (token === '--files') {
|
|
2291
|
+
filesMode = true;
|
|
2292
|
+
index += 1;
|
|
2095
2293
|
continue;
|
|
2096
2294
|
}
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2295
|
+
if (token === '-g' || token === '--glob' || token === '--iglob' || token === '-f') {
|
|
2296
|
+
const optionValue = tokenText(index + 1);
|
|
2297
|
+
if (optionValue) {
|
|
2298
|
+
const forbidden = checkPathToken(optionValue);
|
|
2299
|
+
if (forbidden)
|
|
2300
|
+
return forbidden;
|
|
2301
|
+
}
|
|
2302
|
+
index += 2;
|
|
2303
|
+
continue;
|
|
2304
|
+
}
|
|
2305
|
+
if ((token.startsWith('-g') || token.startsWith('-f')) && token.length > 2) {
|
|
2306
|
+
const optionValue = token.slice(2);
|
|
2307
|
+
const forbidden = checkPathToken(optionValue);
|
|
2308
|
+
if (forbidden)
|
|
2309
|
+
return forbidden;
|
|
2310
|
+
index += 1;
|
|
2311
|
+
continue;
|
|
2312
|
+
}
|
|
2313
|
+
if (token.startsWith('--glob=') ||
|
|
2314
|
+
token.startsWith('--iglob=') ||
|
|
2315
|
+
token.startsWith('--file=')) {
|
|
2316
|
+
const optionValue = token.slice(token.indexOf('=') + 1);
|
|
2317
|
+
const forbidden = checkPathToken(optionValue);
|
|
2318
|
+
if (forbidden)
|
|
2319
|
+
return forbidden;
|
|
2320
|
+
index += 1;
|
|
2321
|
+
continue;
|
|
2322
|
+
}
|
|
2323
|
+
if (token === '-e' || token === '--regexp') {
|
|
2324
|
+
index += 2;
|
|
2325
|
+
continue;
|
|
2326
|
+
}
|
|
2327
|
+
if (token.startsWith('--regexp=')) {
|
|
2328
|
+
index += 1;
|
|
2329
|
+
continue;
|
|
2330
|
+
}
|
|
2331
|
+
if (token.startsWith('-')) {
|
|
2332
|
+
index += 1;
|
|
2333
|
+
continue;
|
|
2334
|
+
}
|
|
2335
|
+
if (!filesMode && !patternConsumed) {
|
|
2336
|
+
patternConsumed = true;
|
|
2337
|
+
index += 1;
|
|
2338
|
+
continue;
|
|
2339
|
+
}
|
|
2340
|
+
const forbidden = checkPathToken(token);
|
|
2341
|
+
if (forbidden)
|
|
2342
|
+
return forbidden;
|
|
2343
|
+
index += 1;
|
|
2100
2344
|
}
|
|
2101
|
-
for (;
|
|
2102
|
-
const
|
|
2103
|
-
if (!
|
|
2345
|
+
for (; index < tokens.length; index++) {
|
|
2346
|
+
const token = tokenText(index);
|
|
2347
|
+
if (!token)
|
|
2104
2348
|
continue;
|
|
2105
|
-
const forbidden = checkPathToken(
|
|
2349
|
+
const forbidden = checkPathToken(token);
|
|
2106
2350
|
if (forbidden)
|
|
2107
2351
|
return forbidden;
|
|
2108
2352
|
}
|
|
2109
2353
|
return null;
|
|
2110
2354
|
}
|
|
2111
2355
|
if (cmd === 'jq') {
|
|
2112
|
-
// `jq [OPTIONS] FILTER [FILE ...]
|
|
2113
|
-
let
|
|
2114
|
-
while (
|
|
2115
|
-
const
|
|
2116
|
-
if (!
|
|
2356
|
+
// `jq [OPTIONS] FILTER [FILE ...]`; some options read files before FILTER.
|
|
2357
|
+
let index = 1;
|
|
2358
|
+
while (index < tokens.length) {
|
|
2359
|
+
const token = tokenText(index);
|
|
2360
|
+
if (!token)
|
|
2117
2361
|
break;
|
|
2118
|
-
if (
|
|
2119
|
-
|
|
2362
|
+
if (token === '--') {
|
|
2363
|
+
index += 1;
|
|
2120
2364
|
break;
|
|
2121
2365
|
}
|
|
2122
|
-
if (
|
|
2123
|
-
|
|
2366
|
+
if (token === '-f' || token === '--from-file') {
|
|
2367
|
+
const optionValue = tokenText(index + 1);
|
|
2368
|
+
if (optionValue) {
|
|
2369
|
+
const forbidden = checkPathToken(optionValue);
|
|
2370
|
+
if (forbidden)
|
|
2371
|
+
return forbidden;
|
|
2372
|
+
}
|
|
2373
|
+
index += 2;
|
|
2374
|
+
continue;
|
|
2375
|
+
}
|
|
2376
|
+
if (token.startsWith('--from-file=')) {
|
|
2377
|
+
const optionValue = token.slice(token.indexOf('=') + 1);
|
|
2378
|
+
const forbidden = checkPathToken(optionValue);
|
|
2379
|
+
if (forbidden)
|
|
2380
|
+
return forbidden;
|
|
2381
|
+
index += 1;
|
|
2382
|
+
continue;
|
|
2383
|
+
}
|
|
2384
|
+
if (token === '--slurpfile' || token === '--rawfile' || token === '--argfile') {
|
|
2385
|
+
const fileValue = tokenText(index + 2);
|
|
2386
|
+
if (fileValue) {
|
|
2387
|
+
const forbidden = checkPathToken(fileValue);
|
|
2388
|
+
if (forbidden)
|
|
2389
|
+
return forbidden;
|
|
2390
|
+
}
|
|
2391
|
+
index += 3;
|
|
2392
|
+
continue;
|
|
2393
|
+
}
|
|
2394
|
+
if (token === '--arg' || token === '--argjson') {
|
|
2395
|
+
index += 3;
|
|
2396
|
+
continue;
|
|
2397
|
+
}
|
|
2398
|
+
if (token.startsWith('-')) {
|
|
2399
|
+
index += 1;
|
|
2124
2400
|
continue;
|
|
2125
2401
|
}
|
|
2126
2402
|
// First non-flag token is FILTER (do not treat as a file path).
|
|
2127
|
-
|
|
2403
|
+
index += 1;
|
|
2128
2404
|
break;
|
|
2129
2405
|
}
|
|
2130
|
-
for (;
|
|
2131
|
-
const
|
|
2132
|
-
if (!
|
|
2406
|
+
for (; index < tokens.length; index++) {
|
|
2407
|
+
const token = tokenText(index);
|
|
2408
|
+
if (!token)
|
|
2133
2409
|
continue;
|
|
2134
|
-
const forbidden = checkPathToken(
|
|
2410
|
+
const forbidden = checkPathToken(token);
|
|
2135
2411
|
if (forbidden)
|
|
2136
2412
|
return forbidden;
|
|
2137
2413
|
}
|
|
2138
2414
|
return null;
|
|
2139
2415
|
}
|
|
2416
|
+
if (cmd === 'where') {
|
|
2417
|
+
// Windows `where /r <dir> <pattern>` recursively searches a directory.
|
|
2418
|
+
for (let index = 1; index < tokens.length; index++) {
|
|
2419
|
+
const token = tokenText(index);
|
|
2420
|
+
if (!token)
|
|
2421
|
+
continue;
|
|
2422
|
+
if (token.toLowerCase() === '/r' || token.toLowerCase() === '-r') {
|
|
2423
|
+
const optionValue = tokenText(index + 1);
|
|
2424
|
+
if (optionValue) {
|
|
2425
|
+
const forbidden = checkPathToken(optionValue);
|
|
2426
|
+
if (forbidden)
|
|
2427
|
+
return forbidden;
|
|
2428
|
+
}
|
|
2429
|
+
index += 1;
|
|
2430
|
+
}
|
|
2431
|
+
}
|
|
2432
|
+
return null;
|
|
2433
|
+
}
|
|
2434
|
+
if (cmd === 'findstr') {
|
|
2435
|
+
// `findstr [OPTIONS] STRINGS [FILE ...]` — treat the first non-option as the pattern.
|
|
2436
|
+
let index = 1;
|
|
2437
|
+
while (index < tokens.length) {
|
|
2438
|
+
const token = tokenText(index);
|
|
2439
|
+
if (!token)
|
|
2440
|
+
break;
|
|
2441
|
+
const findstrOptionPath = /^[/\-](?:f|g|d):(.+)$/iu.exec(token);
|
|
2442
|
+
if (findstrOptionPath) {
|
|
2443
|
+
const optionValue = findstrOptionPath[1] ?? '';
|
|
2444
|
+
const optionPaths = /^[/\-]d:/iu.test(token) ? optionValue.split(';') : [optionValue];
|
|
2445
|
+
for (const optionPath of optionPaths) {
|
|
2446
|
+
const forbidden = checkPathToken(optionPath);
|
|
2447
|
+
if (forbidden)
|
|
2448
|
+
return forbidden;
|
|
2449
|
+
}
|
|
2450
|
+
index += 1;
|
|
2451
|
+
continue;
|
|
2452
|
+
}
|
|
2453
|
+
if (token.startsWith('/') || token.startsWith('-')) {
|
|
2454
|
+
index += 1;
|
|
2455
|
+
continue;
|
|
2456
|
+
}
|
|
2457
|
+
index += 1;
|
|
2458
|
+
break;
|
|
2459
|
+
}
|
|
2460
|
+
for (; index < tokens.length; index++) {
|
|
2461
|
+
const token = tokenText(index);
|
|
2462
|
+
if (!token)
|
|
2463
|
+
continue;
|
|
2464
|
+
const forbidden = checkPathToken(token);
|
|
2465
|
+
if (forbidden)
|
|
2466
|
+
return forbidden;
|
|
2467
|
+
}
|
|
2468
|
+
return null;
|
|
2469
|
+
}
|
|
2470
|
+
if (cmd === 'awk') {
|
|
2471
|
+
let index = 1;
|
|
2472
|
+
let programConsumed = false;
|
|
2473
|
+
while (index < tokens.length) {
|
|
2474
|
+
const token = tokenText(index);
|
|
2475
|
+
if (!token)
|
|
2476
|
+
break;
|
|
2477
|
+
if (token === '-f' || token === '--file') {
|
|
2478
|
+
const optionValue = tokenText(index + 1);
|
|
2479
|
+
if (optionValue) {
|
|
2480
|
+
const forbidden = checkPathToken(optionValue);
|
|
2481
|
+
if (forbidden)
|
|
2482
|
+
return forbidden;
|
|
2483
|
+
}
|
|
2484
|
+
programConsumed = true;
|
|
2485
|
+
index += 2;
|
|
2486
|
+
continue;
|
|
2487
|
+
}
|
|
2488
|
+
if (token.startsWith('-f') && token.length > 2) {
|
|
2489
|
+
const optionValue = token.slice(2);
|
|
2490
|
+
const forbidden = checkPathToken(optionValue);
|
|
2491
|
+
if (forbidden)
|
|
2492
|
+
return forbidden;
|
|
2493
|
+
programConsumed = true;
|
|
2494
|
+
index += 1;
|
|
2495
|
+
continue;
|
|
2496
|
+
}
|
|
2497
|
+
if (token.startsWith('--file=')) {
|
|
2498
|
+
const optionValue = token.slice(token.indexOf('=') + 1);
|
|
2499
|
+
const forbidden = checkPathToken(optionValue);
|
|
2500
|
+
if (forbidden)
|
|
2501
|
+
return forbidden;
|
|
2502
|
+
programConsumed = true;
|
|
2503
|
+
index += 1;
|
|
2504
|
+
continue;
|
|
2505
|
+
}
|
|
2506
|
+
if (token === '-v' || token === '-F') {
|
|
2507
|
+
index += 2;
|
|
2508
|
+
continue;
|
|
2509
|
+
}
|
|
2510
|
+
if (token.startsWith('-F') && token.length > 2) {
|
|
2511
|
+
index += 1;
|
|
2512
|
+
continue;
|
|
2513
|
+
}
|
|
2514
|
+
if (token.startsWith('-')) {
|
|
2515
|
+
index += 1;
|
|
2516
|
+
continue;
|
|
2517
|
+
}
|
|
2518
|
+
if (!programConsumed) {
|
|
2519
|
+
programConsumed = true;
|
|
2520
|
+
index += 1;
|
|
2521
|
+
continue;
|
|
2522
|
+
}
|
|
2523
|
+
const forbidden = checkPathToken(token);
|
|
2524
|
+
if (forbidden)
|
|
2525
|
+
return forbidden;
|
|
2526
|
+
index += 1;
|
|
2527
|
+
}
|
|
2528
|
+
return null;
|
|
2529
|
+
}
|
|
2140
2530
|
if (cmd === 'find') {
|
|
2141
|
-
// `find [path ...] [expression]` —
|
|
2142
|
-
for (let
|
|
2143
|
-
const
|
|
2144
|
-
if (!
|
|
2531
|
+
// `find [global-options] [path ...] [expression]` — treat only initial roots as paths.
|
|
2532
|
+
for (let index = 1; index < tokens.length; index++) {
|
|
2533
|
+
const token = tokenText(index);
|
|
2534
|
+
if (!token)
|
|
2145
2535
|
continue;
|
|
2146
|
-
if (
|
|
2536
|
+
if (token === '-H' || token === '-L' || token === '-P' || /^-O[0-9]$/.test(token)) {
|
|
2537
|
+
continue;
|
|
2538
|
+
}
|
|
2539
|
+
if (token === '-D') {
|
|
2540
|
+
index += 1;
|
|
2541
|
+
continue;
|
|
2542
|
+
}
|
|
2543
|
+
if (token.startsWith('-'))
|
|
2147
2544
|
break;
|
|
2148
|
-
if (
|
|
2545
|
+
if (token === '!' || token === '(' || token === ')')
|
|
2149
2546
|
break;
|
|
2150
|
-
const forbidden = checkPathToken(
|
|
2547
|
+
const forbidden = checkPathToken(token);
|
|
2151
2548
|
if (forbidden)
|
|
2152
2549
|
return forbidden;
|
|
2153
2550
|
}
|
|
2154
2551
|
return null;
|
|
2155
2552
|
}
|
|
2156
2553
|
// Default conservative: treat non-flag args as potential paths for common file-inspection commands.
|
|
2157
|
-
// This intentionally does NOT block `echo/printf
|
|
2554
|
+
// This intentionally does NOT block `echo/printf/...` where args are data, not paths.
|
|
2158
2555
|
const pathLikeCommands = new Set([
|
|
2159
2556
|
'cat',
|
|
2160
2557
|
'ls',
|
|
@@ -2167,8 +2564,18 @@ function detectReadonlyShellForbiddenHiddenDirAccess(workspaceRootAbs, command)
|
|
|
2167
2564
|
'diff',
|
|
2168
2565
|
'realpath',
|
|
2169
2566
|
'readlink',
|
|
2567
|
+
'cut',
|
|
2568
|
+
'sort',
|
|
2569
|
+
'uniq',
|
|
2570
|
+
'shasum',
|
|
2571
|
+
'sha256sum',
|
|
2572
|
+
'md5sum',
|
|
2170
2573
|
'tree',
|
|
2171
2574
|
'sed',
|
|
2575
|
+
'dir',
|
|
2576
|
+
'type',
|
|
2577
|
+
'more',
|
|
2578
|
+
'fc',
|
|
2172
2579
|
]);
|
|
2173
2580
|
if (pathLikeCommands.has(cmd)) {
|
|
2174
2581
|
for (let i = 1; i < tokens.length; i++) {
|
|
@@ -2187,10 +2594,10 @@ function detectReadonlyShellForbiddenHiddenDirAccess(workspaceRootAbs, command)
|
|
|
2187
2594
|
exports.readonlyShellTool = {
|
|
2188
2595
|
type: 'func',
|
|
2189
2596
|
name: 'readonly_shell',
|
|
2190
|
-
description: 'Execute a read-only shell command from a small allowlist. Only exact version probes are allowed for node/python (no scripts such as `node -e` or `python3 -c`). Command chains via |/&&/|| are validated segment-by-segment. Commands outside the allowlist are rejected.',
|
|
2597
|
+
description: 'Execute a read-only shell command from a small allowlist. On Windows this runs through cmd.exe, so use allowlisted cmd/PATH commands such as `rg`, `git`, `dir`, `type`, or `where`. Only exact version probes are allowed for node/python (no scripts such as `node -e` or `python3 -c`). Command chains via |/&&/|| are validated segment-by-segment. Commands outside the allowlist are rejected.',
|
|
2191
2598
|
descriptionI18n: {
|
|
2192
|
-
en: 'Execute a read-only shell command from a small allowlist. Only exact version probes are allowed for node/python (no scripts such as `node -e` or `python3 -c`). Command chains via |/&&/|| are validated segment-by-segment. You are explicitly authorized to call this tool yourself (no delegation). Commands outside the allowlist are rejected.',
|
|
2193
|
-
zh: '执行只读 shell
|
|
2599
|
+
en: 'Execute a read-only shell command from a small allowlist. On Windows this runs through cmd.exe, so use allowlisted cmd/PATH commands such as `rg`, `git`, `dir`, `type`, or `where`. Only exact version probes are allowed for node/python (no scripts such as `node -e` or `python3 -c`). Command chains via |/&&/|| are validated segment-by-segment. You are explicitly authorized to call this tool yourself (no delegation). Commands outside the allowlist are rejected.',
|
|
2600
|
+
zh: '执行只读 shell 命令,仅允许少量白名单命令前缀。Windows 上通过 cmd.exe 执行,请使用白名单内且 cmd/PATH 可用的命令,例如 `rg`、`git`、`dir`、`type` 或 `where`。对 node/python 仅允许版本探针(不允许 `node -e` / `python3 -c` 这类脚本)。通过 |/&&/|| 串联时会按子命令逐段校验。你已被明确授权自行调用该工具(无需委派)。不在允许列表内的命令会被拒绝。',
|
|
2194
2601
|
},
|
|
2195
2602
|
parameters: readonlyShellSchema,
|
|
2196
2603
|
async call(dlg, caller, args) {
|
|
@@ -2206,15 +2613,15 @@ exports.readonlyShellTool = {
|
|
|
2206
2613
|
}
|
|
2207
2614
|
const validation = validateReadonlyShellCommand(command);
|
|
2208
2615
|
if (!validation.ok) {
|
|
2209
|
-
const allowedList =
|
|
2616
|
+
const allowedList = getReadonlyShellAllowedPrefixes().join(', ');
|
|
2210
2617
|
const rejectedSegment = validation.failure.rejectedSegment.trim();
|
|
2211
2618
|
const rejectedSegmentOrCommand = rejectedSegment === '' ? command : rejectedSegment;
|
|
2212
2619
|
const suggestion = language === 'zh'
|
|
2213
2620
|
? getReadonlyShellSuggestionZh(validation.failure)
|
|
2214
2621
|
: getReadonlyShellSuggestionEn(validation.failure);
|
|
2215
2622
|
return (0, tool_1.toolFailure)(prependShellWarning(language === 'zh'
|
|
2216
|
-
? `❌ readonly_shell 仅允许以下命令前缀:${allowedList}\n另外允许(仅版本探针):node --version|-v、python3 --version|-V\n脚本执行(如 node -e / python3 -c)一律拒绝。\n另外允许:git -C <相对路径> <show|status|diff|log|blame> ...\n另外允许:cd <相对路径> && <允许命令...>(或 ||)\n说明:通过 |/&&/|| 串联时会按子命令逐段校验。\n被拒子命令段:${rejectedSegmentOrCommand}\n允许的等价写法:${suggestion}\n收到:${command}`
|
|
2217
|
-
: `❌ readonly_shell only allows these command prefixes: ${allowedList}\nAlso allowed (exact version probes only): node --version|-v, python3 --version|-V\nNode/python scripts (for example: node -e, python3 -c) are rejected.\nAlso allowed: git -C <relative-path> <show|status|diff|log|blame> ...\nAlso allowed: cd <relative-path> && <allowed command...> (or ||)\nNote: chains via |/&&/|| are validated segment-by-segment.\nRejected segment: ${rejectedSegmentOrCommand}\nAllowed equivalent: ${suggestion}\nGot: ${command}`, warning));
|
|
2623
|
+
? `❌ readonly_shell 仅允许以下命令前缀:${allowedList}\n另外允许(仅版本探针):node --version|-v、python3/python/py --version|-V\n脚本执行(如 node -e / python3 -c)一律拒绝。\n另外允许:git -C <相对路径> <show|status|diff|log|blame> ...\n另外允许:cd <相对路径> && <允许命令...>(或 ||)\nWindows 上通过 cmd.exe 执行;请使用该 shell/PATH 中可用的白名单命令。\n说明:通过 |/&&/|| 串联时会按子命令逐段校验。\n被拒子命令段:${rejectedSegmentOrCommand}\n允许的等价写法:${suggestion}\n收到:${command}`
|
|
2624
|
+
: `❌ readonly_shell only allows these command prefixes: ${allowedList}\nAlso allowed (exact version probes only): node --version|-v, python3/python/py --version|-V\nNode/python scripts (for example: node -e, python3 -c) are rejected.\nAlso allowed: git -C <relative-path> <show|status|diff|log|blame> ...\nAlso allowed: cd <relative-path> && <allowed command...> (or ||)\nOn Windows this runs through cmd.exe; use allowlisted commands available in that shell/PATH.\nNote: chains via |/&&/|| are validated segment-by-segment.\nRejected segment: ${rejectedSegmentOrCommand}\nAllowed equivalent: ${suggestion}\nGot: ${command}`, warning));
|
|
2218
2625
|
}
|
|
2219
2626
|
const forbiddenHiddenDir = detectReadonlyShellForbiddenHiddenDirAccess(path_1.default.resolve(process.cwd()), command);
|
|
2220
2627
|
if (forbiddenHiddenDir) {
|