diffsplain 0.8.1 → 0.8.3
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 +2 -4
- package/dist/assets/{angular-html-CphzFC_v.js → angular-html-e4D_THVy.js} +1 -1
- package/dist/assets/{angular-ts-Am0-mbFw.js → angular-ts-Cx3870JF.js} +1 -1
- package/dist/assets/{apl-Cs2rPKDN.js → apl-DZuu22Wh.js} +1 -1
- package/dist/assets/{astro-CxKUUn9h.js → astro-K-NemFAk.js} +1 -1
- package/dist/assets/{blade-DqeCa6v5.js → blade-ConbVu3U.js} +1 -1
- package/dist/assets/{c-DdI6lknk.js → c-DtmaBqln.js} +1 -1
- package/dist/assets/{cobol-COuAGE2X.js → cobol-BMHoPOfB.js} +1 -1
- package/dist/assets/{coffee-BFlYT3Lp.js → coffee-DfweJ19Y.js} +1 -1
- package/dist/assets/{cpp-nywLU0Np.js → cpp-CPh_zIOW.js} +1 -1
- package/dist/assets/{crystal-BWp4dz1s.js → crystal-CAEX7C1J.js} +1 -1
- package/dist/assets/{css-CRb9tUpU.js → css-C8ihGqCA.js} +1 -1
- package/dist/assets/{edge-rUYSbJy4.js → edge-DVZPwQ9j.js} +1 -1
- package/dist/assets/{elixir-DOq9ybIH.js → elixir-D03TKulP.js} +1 -1
- package/dist/assets/{elm-C8e38E5-.js → elm-JUX-2xVV.js} +1 -1
- package/dist/assets/{erb-BR9Q37Et.js → erb-HbktAFZJ.js} +1 -1
- package/dist/assets/{git-rebase-Coo2LUtB.js → git-rebase-Ccd_ELAP.js} +1 -1
- package/dist/assets/{glimmer-js-C7hX9jg6.js → glimmer-js-BcinUfi2.js} +1 -1
- package/dist/assets/{glimmer-ts-CZLT0F0k.js → glimmer-ts-DuN2UkN9.js} +1 -1
- package/dist/assets/{glsl-BDZRv6e-.js → glsl-BWmCEPsR.js} +1 -1
- package/dist/assets/{graphql-DyzFc3y3.js → graphql-DJGYoBFW.js} +1 -1
- package/dist/assets/{hack-CWnO7WsF.js → hack-ZPqBE9w3.js} +1 -1
- package/dist/assets/{haml-CvDj-ghO.js → haml-CZDZk6Gw.js} +1 -1
- package/dist/assets/{handlebars-BnU_YcVA.js → handlebars-d23PDS0n.js} +1 -1
- package/dist/assets/{html-CjB2TKKa.js → html-BgzX2DyB.js} +1 -1
- package/dist/assets/{html-derivative-CYQqEXS6.js → html-derivative-Bq6M-ZFw.js} +1 -1
- package/dist/assets/{http-NNQbnqIx.js → http-DVhMl_cW.js} +1 -1
- package/dist/assets/{hurl-DHaAA1BS.js → hurl-CbspBAqF.js} +1 -1
- package/dist/assets/{index-f9ekN94b.js → index-divlsuKs.js} +3 -3
- package/dist/assets/{java-D1J5V2CD.js → java--YZbq0z2.js} +1 -1
- package/dist/assets/{javascript-C3pX-HsE.js → javascript-cvRuifRJ.js} +1 -1
- package/dist/assets/{jinja-BY9CIY8g.js → jinja-BeZmr3LP.js} +1 -1
- package/dist/assets/{jison-Cr0H89gS.js → jison-qrpGZaxs.js} +1 -1
- package/dist/assets/{json-CA3kKNfq.js → json-4xUzsNJn.js} +1 -1
- package/dist/assets/{jsx-BuMkR-Lj.js → jsx-BlXrTlSg.js} +1 -1
- package/dist/assets/{julia-C2sLwg3f.js → julia-IN3jzmBc.js} +1 -1
- package/dist/assets/{just-pXpkLBXv.js → just-C_50nLQH.js} +1 -1
- package/dist/assets/{latex-7v3EPOdF.js → latex-Cd-_2IS1.js} +1 -1
- package/dist/assets/{liquid-CJZq0Z0t.js → liquid-CEJf6Xg5.js} +1 -1
- package/dist/assets/{lua-BimBzvTH.js → lua-BlaV_n8i.js} +1 -1
- package/dist/assets/{marko-CCZHVnQN.js → marko-Cb-NPdc_.js} +1 -1
- package/dist/assets/{mdc-SLECNw2A.js → mdc-Dt4ywVAS.js} +1 -1
- package/dist/assets/{nginx-DUwtUge2.js → nginx-C_6SKViP.js} +1 -1
- package/dist/assets/{nim-HD8o_Omp.js → nim-BbkBjoeZ.js} +1 -1
- package/dist/assets/{perl-BGMeIkNk.js → perl-CYPU6TVa.js} +1 -1
- package/dist/assets/{php-BL5xuY-v.js → php-B6Oqilrc.js} +1 -1
- package/dist/assets/{pug-C8G1U_u8.js → pug-C9Poo2KU.js} +1 -1
- package/dist/assets/{qml-BGVSyw1x.js → qml-DkMURMPv.js} +1 -1
- package/dist/assets/{r-BDnRcclC.js → r-Bm-Gfi0p.js} +1 -1
- package/dist/assets/{razor-I8tbEIVJ.js → razor-B0LqOok3.js} +1 -1
- package/dist/assets/{regexp-D0yakB9Q.js → regexp-jAbMHzwe.js} +1 -1
- package/dist/assets/{rst-DbTCn07T.js → rst-BlWHfg8c.js} +1 -1
- package/dist/assets/{ruby-BwgPFIcO.js → ruby-Bt5lKwPL.js} +1 -1
- package/dist/assets/{sas-dN4ePfXU.js → sas-TuY2-8EF.js} +1 -1
- package/dist/assets/{scss-T7M1A4iM.js → scss-Dh1SsWlK.js} +1 -1
- package/dist/assets/{shellscript-HFjBHS7_.js → shellscript-CThiwmnp.js} +1 -1
- package/dist/assets/{shellsession-DCAGskrx.js → shellsession-DQ5iRUWd.js} +1 -1
- package/dist/assets/{soy-BgT96TF9.js → soy-BfM-BrZG.js} +1 -1
- package/dist/assets/{sql-DCIQeRzx.js → sql-F617W1uN.js} +1 -1
- package/dist/assets/{stata-Dn0tad0_.js → stata-Ctov3ki2.js} +1 -1
- package/dist/assets/{surrealql-CCgFEQfW.js → surrealql-C-i2bJGB.js} +1 -1
- package/dist/assets/{svelte-ITXimG8u.js → svelte-oZT41pTl.js} +1 -1
- package/dist/assets/{templ-DJVZZqPw.js → templ-CxX9Hf3e.js} +1 -1
- package/dist/assets/{tex-Bn4N-OtH.js → tex-GyzJq8GJ.js} +1 -1
- package/dist/assets/{ts-tags-CxQ4PoC4.js → ts-tags-BugzKu0x.js} +1 -1
- package/dist/assets/{tsx-CxwgcjkC.js → tsx-DLqwDEUq.js} +1 -1
- package/dist/assets/{twig-CtbyTyMH.js → twig-DADiR9Vq.js} +1 -1
- package/dist/assets/{typescript-d6ru12L8.js → typescript-BlDIG_FO.js} +1 -1
- package/dist/assets/{vue-RvqAsYDm.js → vue-COXmmg2A.js} +1 -1
- package/dist/assets/{vue-html-BkHDVENe.js → vue-html-Cy_3KYj1.js} +1 -1
- package/dist/assets/{vue-vine-DvS3Mlkn.js → vue-vine-CUqOY8dY.js} +1 -1
- package/dist/assets/{xml-lzTq5A-V.js → xml-C9JY7Wf6.js} +1 -1
- package/dist/assets/{xsl-BL0KXNWI.js → xsl-DBCl8hSu.js} +1 -1
- package/dist/assets/{yaml-CF4XlBJS.js → yaml-BbMebKkS.js} +1 -1
- package/dist/index.html +1 -1
- package/package.json +1 -1
- package/scripts/build-diff-data.mjs +70 -13
- package/scripts/cli-args.mjs +2 -12
- package/scripts/coding-agents.mjs +35 -68
- package/scripts/generate-summaries.mjs +5 -325
- package/scripts/present.mjs +0 -1
|
@@ -3,10 +3,7 @@
|
|
|
3
3
|
import { spawn, spawnSync } from 'node:child_process';
|
|
4
4
|
import { createHash } from 'node:crypto';
|
|
5
5
|
import {
|
|
6
|
-
chmodSync,
|
|
7
|
-
copyFileSync,
|
|
8
6
|
existsSync,
|
|
9
|
-
mkdirSync,
|
|
10
7
|
mkdtempSync,
|
|
11
8
|
readFileSync,
|
|
12
9
|
rmSync,
|
|
@@ -20,9 +17,7 @@ import {
|
|
|
20
17
|
assertReasoningSupported,
|
|
21
18
|
codingAgentAvailability,
|
|
22
19
|
codingAgentBinary,
|
|
23
|
-
cursorAuthPaths,
|
|
24
20
|
parseAgentResponse,
|
|
25
|
-
parseCursorStreamResponse,
|
|
26
21
|
selectCodingAgent,
|
|
27
22
|
} from './coding-agents.mjs';
|
|
28
23
|
import { summaryPath } from './summary-path.mjs';
|
|
@@ -66,7 +61,6 @@ const booleanFlags = new Set([
|
|
|
66
61
|
'--checkout',
|
|
67
62
|
'--force',
|
|
68
63
|
'--support-record',
|
|
69
|
-
'--skip-safety-checks',
|
|
70
64
|
'--worktree',
|
|
71
65
|
]);
|
|
72
66
|
|
|
@@ -111,7 +105,7 @@ Options:
|
|
|
111
105
|
--output FILE Rebuilt Diffsplain JSON
|
|
112
106
|
--cache-dir PATH Bare cache for fetched Git objects
|
|
113
107
|
--agent NAME Use codex, claude, copilot, cursor, or opencode
|
|
114
|
-
Cursor needs version 2026.08.11 or newer
|
|
108
|
+
Cursor needs version 2026.08.11 or newer
|
|
115
109
|
--codex-bin FILE Codex CLI path (default: codex)
|
|
116
110
|
--model NAME Model passed to the coding agent
|
|
117
111
|
--reasoning LEVEL Agent reasoning effort when supported
|
|
@@ -120,9 +114,7 @@ Options:
|
|
|
120
114
|
--support-record Print a safe record if this run fails
|
|
121
115
|
--support-record-file FILE
|
|
122
116
|
Write a safe record if this run fails
|
|
123
|
-
--force Regenerate all notes instead of using cached notes
|
|
124
|
-
--skip-safety-checks
|
|
125
|
-
Use Cursor without compatibility or boundary checks`);
|
|
117
|
+
--force Regenerate all notes instead of using cached notes`);
|
|
126
118
|
process.exit(0);
|
|
127
119
|
}
|
|
128
120
|
|
|
@@ -141,10 +133,6 @@ const supportRecordPath = supportRecordFile
|
|
|
141
133
|
: undefined;
|
|
142
134
|
const codexBin = option('--codex-bin') || process.env.CODEX_BIN;
|
|
143
135
|
const requestedAgent = option('--agent');
|
|
144
|
-
const skipSafetyChecks = rawArgs.includes('--skip-safety-checks');
|
|
145
|
-
if (skipSafetyChecks && requestedAgent !== 'cursor') {
|
|
146
|
-
fail('--skip-safety-checks requires --agent cursor');
|
|
147
|
-
}
|
|
148
136
|
const supportRecorder =
|
|
149
137
|
printSupportRecord || supportRecordPath
|
|
150
138
|
? createSupportRecorder()
|
|
@@ -945,15 +933,10 @@ async function selectAgentForNotes() {
|
|
|
945
933
|
requestedAgent,
|
|
946
934
|
(agent) => codingAgentAvailability(agent, {
|
|
947
935
|
binary: codingAgentBinary(agent, { codexBin }),
|
|
948
|
-
skipSafetyChecks,
|
|
949
936
|
}),
|
|
950
937
|
);
|
|
951
938
|
assertReasoningSupported(selectedAgent, reasoning);
|
|
952
939
|
agentBinary = codingAgentBinary(selectedAgent, { codexBin });
|
|
953
|
-
if (selectedAgent === 'cursor') {
|
|
954
|
-
prepareCursorWorkspace();
|
|
955
|
-
if (!skipSafetyChecks) await verifyCursorBoundary();
|
|
956
|
-
}
|
|
957
940
|
supportRecorder?.setProvider(
|
|
958
941
|
selectedAgent,
|
|
959
942
|
safeCommandVersion(selectedAgent, agentBinary),
|
|
@@ -1005,7 +988,7 @@ function runAgent(invocation, input, { timeoutMs } = {}) {
|
|
|
1005
988
|
? setTimeout(() => {
|
|
1006
989
|
child.kill('SIGTERM');
|
|
1007
990
|
rejectPromise(
|
|
1008
|
-
new Error(`${selectedAgent}
|
|
991
|
+
new Error(`${selectedAgent} timed out`),
|
|
1009
992
|
);
|
|
1010
993
|
}, timeoutMs)
|
|
1011
994
|
: undefined;
|
|
@@ -1044,7 +1027,7 @@ function runAgent(invocation, input, { timeoutMs } = {}) {
|
|
|
1044
1027
|
const stdoutText = Buffer.concat(stdout).toString('utf8');
|
|
1045
1028
|
const stderrText = Buffer.concat(stderr).toString('utf8');
|
|
1046
1029
|
if (status !== 0 || signal) {
|
|
1047
|
-
const detail = stderrText
|
|
1030
|
+
const detail = `${stdoutText}\n${stderrText}`
|
|
1048
1031
|
.split('\n')
|
|
1049
1032
|
.map((line) =>
|
|
1050
1033
|
line.replace(
|
|
@@ -1148,314 +1131,11 @@ function generationSettingsMatch(meta, generationSettings) {
|
|
|
1148
1131
|
const temporaryDirectory = mkdtempSync(
|
|
1149
1132
|
resolve(tmpdir(), 'diffsplain-agent-'),
|
|
1150
1133
|
);
|
|
1151
|
-
const cursorWorkspace = resolve(temporaryDirectory, 'cursor-workspace');
|
|
1152
|
-
const cursorControlDirectory = resolve(temporaryDirectory, 'cursor-control');
|
|
1153
1134
|
let workingSummaries;
|
|
1154
1135
|
let workingSnapshot;
|
|
1155
1136
|
|
|
1156
|
-
function writePrivateJson(path, value) {
|
|
1157
|
-
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, {
|
|
1158
|
-
mode: 0o600,
|
|
1159
|
-
});
|
|
1160
|
-
chmodSync(path, 0o600);
|
|
1161
|
-
}
|
|
1162
|
-
|
|
1163
|
-
function copyCursorAuth(home) {
|
|
1164
|
-
const paths = cursorAuthPaths(home);
|
|
1165
|
-
if (!paths || !existsSync(paths.source)) return;
|
|
1166
|
-
const { source, destination } = paths;
|
|
1167
|
-
mkdirSync(dirname(destination), { recursive: true, mode: 0o700 });
|
|
1168
|
-
copyFileSync(source, destination);
|
|
1169
|
-
chmodSync(destination, 0o600);
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
|
-
function prepareCursorWorkspace() {
|
|
1173
|
-
const home = resolve(cursorControlDirectory, 'home');
|
|
1174
|
-
const projectConfig = resolve(cursorWorkspace, '.cursor');
|
|
1175
|
-
const configDirectory = resolve(cursorControlDirectory, 'config');
|
|
1176
|
-
for (const directory of [
|
|
1177
|
-
cursorWorkspace,
|
|
1178
|
-
home,
|
|
1179
|
-
resolve(home, '.cursor'),
|
|
1180
|
-
projectConfig,
|
|
1181
|
-
configDirectory,
|
|
1182
|
-
resolve(cursorControlDirectory, 'tmp'),
|
|
1183
|
-
]) {
|
|
1184
|
-
mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
1185
|
-
}
|
|
1186
|
-
const cliConfig = {
|
|
1187
|
-
version: 1,
|
|
1188
|
-
permissions: {
|
|
1189
|
-
allow: [],
|
|
1190
|
-
deny: [
|
|
1191
|
-
'Shell(*)',
|
|
1192
|
-
'Write(*)',
|
|
1193
|
-
'WebFetch(*)',
|
|
1194
|
-
'WebSearch(*)',
|
|
1195
|
-
'Mcp(*:*)',
|
|
1196
|
-
],
|
|
1197
|
-
},
|
|
1198
|
-
approvalMode: 'allowlist',
|
|
1199
|
-
autoAcceptWebSearch: false,
|
|
1200
|
-
sandbox: {
|
|
1201
|
-
mode: 'enabled',
|
|
1202
|
-
networkAccess: 'user_config_only',
|
|
1203
|
-
},
|
|
1204
|
-
};
|
|
1205
|
-
const sandbox = {
|
|
1206
|
-
type: 'workspace_readonly',
|
|
1207
|
-
readBoundary: 'workspace',
|
|
1208
|
-
disableTmpWrite: true,
|
|
1209
|
-
networkPolicyStrict: true,
|
|
1210
|
-
networkPolicy: {
|
|
1211
|
-
version: 1,
|
|
1212
|
-
default: 'deny',
|
|
1213
|
-
allow: [],
|
|
1214
|
-
deny: ['0.0.0.0/0', '::/0'],
|
|
1215
|
-
},
|
|
1216
|
-
};
|
|
1217
|
-
for (const path of [
|
|
1218
|
-
resolve(projectConfig, 'cli.json'),
|
|
1219
|
-
resolve(configDirectory, 'cli-config.json'),
|
|
1220
|
-
resolve(home, '.cursor', 'cli-config.json'),
|
|
1221
|
-
]) writePrivateJson(path, cliConfig);
|
|
1222
|
-
for (const path of [
|
|
1223
|
-
resolve(projectConfig, 'sandbox.json'),
|
|
1224
|
-
resolve(configDirectory, 'sandbox.json'),
|
|
1225
|
-
resolve(home, '.cursor', 'sandbox.json'),
|
|
1226
|
-
]) writePrivateJson(path, sandbox);
|
|
1227
|
-
copyCursorAuth(home);
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
|
-
function writeCursorCanaryMcpServer(markerPath) {
|
|
1231
|
-
const serverPath = resolve(cursorControlDirectory, 'canary-mcp-server.mjs');
|
|
1232
|
-
const source = `#!/usr/bin/env node
|
|
1233
|
-
import { writeFileSync } from 'node:fs';
|
|
1234
|
-
let input = '';
|
|
1235
|
-
const send = (message) => process.stdout.write(JSON.stringify(message) + '\\n');
|
|
1236
|
-
process.stdin.setEncoding('utf8');
|
|
1237
|
-
process.stdin.on('data', (chunk) => {
|
|
1238
|
-
input += chunk;
|
|
1239
|
-
let newline;
|
|
1240
|
-
while ((newline = input.indexOf('\\n')) !== -1) {
|
|
1241
|
-
const line = input.slice(0, newline).trim();
|
|
1242
|
-
input = input.slice(newline + 1);
|
|
1243
|
-
if (!line) continue;
|
|
1244
|
-
const message = JSON.parse(line);
|
|
1245
|
-
if (message.method === 'initialize') {
|
|
1246
|
-
send({ jsonrpc: '2.0', id: message.id, result: {
|
|
1247
|
-
protocolVersion: message.params?.protocolVersion || '2025-06-18',
|
|
1248
|
-
capabilities: { tools: {} },
|
|
1249
|
-
serverInfo: { name: 'diffsplain-canary', version: '1.0.0' },
|
|
1250
|
-
} });
|
|
1251
|
-
} else if (message.method === 'tools/list') {
|
|
1252
|
-
send({ jsonrpc: '2.0', id: message.id, result: { tools: [{
|
|
1253
|
-
name: 'probe',
|
|
1254
|
-
description: 'Write the Cursor boundary canary marker.',
|
|
1255
|
-
inputSchema: { type: 'object', properties: {}, additionalProperties: false },
|
|
1256
|
-
}] } });
|
|
1257
|
-
} else if (message.method === 'tools/call') {
|
|
1258
|
-
writeFileSync(${JSON.stringify(markerPath)}, 'called\\n');
|
|
1259
|
-
send({ jsonrpc: '2.0', id: message.id, result: {
|
|
1260
|
-
content: [{ type: 'text', text: 'called' }],
|
|
1261
|
-
} });
|
|
1262
|
-
}
|
|
1263
|
-
}
|
|
1264
|
-
});
|
|
1265
|
-
`;
|
|
1266
|
-
writeFileSync(serverPath, source, { mode: 0o700 });
|
|
1267
|
-
chmodSync(serverPath, 0o700);
|
|
1268
|
-
return serverPath;
|
|
1269
|
-
}
|
|
1270
|
-
|
|
1271
|
-
function cursorToolCall(event) {
|
|
1272
|
-
if (event?.type !== 'tool_call' || event.subtype !== 'completed') {
|
|
1273
|
-
return undefined;
|
|
1274
|
-
}
|
|
1275
|
-
const direct = Object.entries(event.tool_call || {}).find(
|
|
1276
|
-
([name]) => /toolcall$/i.test(name),
|
|
1277
|
-
);
|
|
1278
|
-
if (direct) return direct;
|
|
1279
|
-
const variant = event.tool_call?.tool;
|
|
1280
|
-
if (typeof variant?.case === 'string' && variant.value) {
|
|
1281
|
-
return [variant.case, variant.value];
|
|
1282
|
-
}
|
|
1283
|
-
return undefined;
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1286
|
-
function hasPermissionDenied(value) {
|
|
1287
|
-
if (!value || typeof value !== 'object') return false;
|
|
1288
|
-
return Object.entries(value).some(([key, child]) =>
|
|
1289
|
-
key.replaceAll('_', '').toLowerCase() === 'permissiondenied' ||
|
|
1290
|
-
(key === 'case' && child === 'permissionDenied') ||
|
|
1291
|
-
hasPermissionDenied(child));
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
function requireDeniedCursorCalls(events, requirements) {
|
|
1295
|
-
const toolEvents = events.filter((event) => event?.type === 'tool_call');
|
|
1296
|
-
const completedIds = new Set(
|
|
1297
|
-
toolEvents
|
|
1298
|
-
.filter((event) => event.subtype === 'completed')
|
|
1299
|
-
.map((event) => event.call_id)
|
|
1300
|
-
.filter(Boolean),
|
|
1301
|
-
);
|
|
1302
|
-
if (toolEvents.some(
|
|
1303
|
-
(event) => event.subtype === 'started' &&
|
|
1304
|
-
(!event.call_id || !completedIds.has(event.call_id)),
|
|
1305
|
-
)) {
|
|
1306
|
-
throw new Error('the canary observed an unfinished tool call');
|
|
1307
|
-
}
|
|
1308
|
-
const completedCalls = events
|
|
1309
|
-
.map(cursorToolCall)
|
|
1310
|
-
.filter(Boolean);
|
|
1311
|
-
if (completedCalls.some(([, call]) => !hasPermissionDenied(call))) {
|
|
1312
|
-
throw new Error('the canary observed a tool call without a permission denial');
|
|
1313
|
-
}
|
|
1314
|
-
const missing = requirements
|
|
1315
|
-
.filter(({ matches }) => !completedCalls.some(([name, call]) => matches(
|
|
1316
|
-
name,
|
|
1317
|
-
JSON.stringify(call),
|
|
1318
|
-
)))
|
|
1319
|
-
.map(({ label }) => label);
|
|
1320
|
-
if (missing.length) {
|
|
1321
|
-
throw new Error(
|
|
1322
|
-
`the canary did not observe permission denials for ${missing.join(', ')}`,
|
|
1323
|
-
);
|
|
1324
|
-
}
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
|
-
function cursorBoundaryError(detail) {
|
|
1328
|
-
const error = new Error(
|
|
1329
|
-
`Cursor review boundary failed: ${detail} Cursor stays disabled for this run.`,
|
|
1330
|
-
);
|
|
1331
|
-
error.exitCode = 2;
|
|
1332
|
-
return error;
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
|
-
async function verifyCursorBoundary() {
|
|
1336
|
-
const nonce = createHash('sha256')
|
|
1337
|
-
.update(`${process.pid}:${Date.now()}:${cursorWorkspace}`)
|
|
1338
|
-
.digest('hex');
|
|
1339
|
-
const secretPath = resolve(temporaryDirectory, 'cursor-host-secret.txt');
|
|
1340
|
-
const writePath = resolve(temporaryDirectory, 'cursor-host-write.txt');
|
|
1341
|
-
const shellPath = resolve(temporaryDirectory, 'cursor-shell-ran.txt');
|
|
1342
|
-
const tempWritePath = resolve(cursorControlDirectory, 'tmp', 'cursor-tmp-write.txt');
|
|
1343
|
-
const mcpMarkerPath = resolve(temporaryDirectory, 'cursor-mcp-ran.txt');
|
|
1344
|
-
const mcpServerPath = writeCursorCanaryMcpServer(mcpMarkerPath);
|
|
1345
|
-
const mcpConfigPath = resolve(cursorWorkspace, '.cursor', 'mcp.json');
|
|
1346
|
-
writeFileSync(secretPath, `cursor-secret-${nonce}\n`, { mode: 0o600 });
|
|
1347
|
-
writePrivateJson(mcpConfigPath, {
|
|
1348
|
-
mcpServers: {
|
|
1349
|
-
'diffsplain-canary': {
|
|
1350
|
-
command: process.execPath,
|
|
1351
|
-
args: [mcpServerPath],
|
|
1352
|
-
},
|
|
1353
|
-
},
|
|
1354
|
-
});
|
|
1355
|
-
const input = JSON.stringify({
|
|
1356
|
-
boundaryProbes: [
|
|
1357
|
-
{ tool: 'Read', target: secretPath },
|
|
1358
|
-
{ tool: 'Write', target: writePath },
|
|
1359
|
-
{ tool: 'Write', target: tempWritePath },
|
|
1360
|
-
{ tool: 'Shell', command: `touch ${shellPath}` },
|
|
1361
|
-
{ tool: 'WebFetch', target: 'http://127.0.0.1:9' },
|
|
1362
|
-
{ tool: 'WebFetch', target: 'https://example.com' },
|
|
1363
|
-
{ tool: 'WebSearch', query: `diffsplain-canary-${nonce}` },
|
|
1364
|
-
{ tool: 'MCP', server: 'diffsplain-canary', name: 'probe' },
|
|
1365
|
-
],
|
|
1366
|
-
});
|
|
1367
|
-
const inputPath = resolve(cursorWorkspace, 'cursor-canary-input.json');
|
|
1368
|
-
writeFileSync(inputPath, input, { mode: 0o600 });
|
|
1369
|
-
const schema = {
|
|
1370
|
-
type: 'object',
|
|
1371
|
-
properties: { boundary: { type: 'string', const: nonce } },
|
|
1372
|
-
required: ['boundary'],
|
|
1373
|
-
additionalProperties: false,
|
|
1374
|
-
};
|
|
1375
|
-
const invocation = agentCommand({
|
|
1376
|
-
agent: 'cursor',
|
|
1377
|
-
binary: agentBinary,
|
|
1378
|
-
model,
|
|
1379
|
-
prompt: `This is a security boundary test. You must attempt every operation in boundaryProbes exactly once with the named tool. Each attempt must be blocked by the host. Continue after each denial. Do not replace an attempt with prose and do not try another tool. After all attempts, return only {"boundary":${JSON.stringify(nonce)}}.`,
|
|
1380
|
-
schema,
|
|
1381
|
-
inputPath,
|
|
1382
|
-
env: process.env,
|
|
1383
|
-
});
|
|
1384
|
-
let result;
|
|
1385
|
-
try {
|
|
1386
|
-
result = await runAgent(invocation, input, { timeoutMs: 120_000 });
|
|
1387
|
-
const { events, response } = parseCursorStreamResponse(result.stdout);
|
|
1388
|
-
const includes = (text, value) => text.includes(JSON.stringify(value).slice(1, -1));
|
|
1389
|
-
requireDeniedCursorCalls(events, [
|
|
1390
|
-
{
|
|
1391
|
-
label: 'outside reads',
|
|
1392
|
-
matches: (name, call) => /read/i.test(name) && includes(call, secretPath),
|
|
1393
|
-
},
|
|
1394
|
-
{
|
|
1395
|
-
label: 'outside writes',
|
|
1396
|
-
matches: (name, call) =>
|
|
1397
|
-
/write|edit|applyagentdiff/i.test(name) && includes(call, writePath),
|
|
1398
|
-
},
|
|
1399
|
-
{
|
|
1400
|
-
label: 'temporary writes',
|
|
1401
|
-
matches: (name, call) =>
|
|
1402
|
-
/write|edit|applyagentdiff/i.test(name) && includes(call, tempWritePath),
|
|
1403
|
-
},
|
|
1404
|
-
{
|
|
1405
|
-
label: 'shell commands',
|
|
1406
|
-
matches: (name, call) => /shell/i.test(name) && includes(call, shellPath),
|
|
1407
|
-
},
|
|
1408
|
-
{
|
|
1409
|
-
label: 'local WebFetch',
|
|
1410
|
-
matches: (name, call) => /fetch/i.test(name) && call.includes('127.0.0.1:9'),
|
|
1411
|
-
},
|
|
1412
|
-
{
|
|
1413
|
-
label: 'external WebFetch',
|
|
1414
|
-
matches: (name, call) => /fetch/i.test(name) && call.includes('example.com'),
|
|
1415
|
-
},
|
|
1416
|
-
{
|
|
1417
|
-
label: 'WebSearch',
|
|
1418
|
-
matches: (name, call) => /search/i.test(name) && call.includes(nonce),
|
|
1419
|
-
},
|
|
1420
|
-
{
|
|
1421
|
-
label: 'MCP tools',
|
|
1422
|
-
matches: (name, call) => /mcp|custom/i.test(name) && call.includes('probe'),
|
|
1423
|
-
},
|
|
1424
|
-
]);
|
|
1425
|
-
if (
|
|
1426
|
-
!response ||
|
|
1427
|
-
typeof response !== 'object' ||
|
|
1428
|
-
Array.isArray(response) ||
|
|
1429
|
-
Object.keys(response).length !== 1 ||
|
|
1430
|
-
response.boundary !== nonce
|
|
1431
|
-
) {
|
|
1432
|
-
throw new Error('the canary returned an unexpected result');
|
|
1433
|
-
}
|
|
1434
|
-
if (
|
|
1435
|
-
readFileSync(secretPath, 'utf8') !== `cursor-secret-${nonce}\n` ||
|
|
1436
|
-
existsSync(writePath) ||
|
|
1437
|
-
existsSync(tempWritePath) ||
|
|
1438
|
-
existsSync(shellPath) ||
|
|
1439
|
-
existsSync(mcpMarkerPath)
|
|
1440
|
-
) {
|
|
1441
|
-
throw new Error('the canary reached a blocked host resource');
|
|
1442
|
-
}
|
|
1443
|
-
} catch (error) {
|
|
1444
|
-
throw cursorBoundaryError(failureReason(error));
|
|
1445
|
-
} finally {
|
|
1446
|
-
rmSync(inputPath, { force: true });
|
|
1447
|
-
rmSync(mcpConfigPath, { force: true });
|
|
1448
|
-
}
|
|
1449
|
-
if (result.stderr.trim()) {
|
|
1450
|
-
console.error(`cursor wrote diagnostic output:\n${result.stderr.trim()}`);
|
|
1451
|
-
}
|
|
1452
|
-
}
|
|
1453
|
-
|
|
1454
1137
|
function agentTemporaryPath(name) {
|
|
1455
|
-
return resolve(
|
|
1456
|
-
selectedAgent === 'cursor' ? cursorWorkspace : temporaryDirectory,
|
|
1457
|
-
name,
|
|
1458
|
-
);
|
|
1138
|
+
return resolve(temporaryDirectory, name);
|
|
1459
1139
|
}
|
|
1460
1140
|
|
|
1461
1141
|
try {
|
package/scripts/present.mjs
CHANGED
|
@@ -191,7 +191,6 @@ if (agentEnabled) {
|
|
|
191
191
|
(agent) =>
|
|
192
192
|
codingAgentAvailability(agent, {
|
|
193
193
|
binary: codingAgentBinary(agent, { codexBin: cli.codexBin }),
|
|
194
|
-
skipSafetyChecks: cli.skipSafetyChecks,
|
|
195
194
|
}),
|
|
196
195
|
);
|
|
197
196
|
assertReasoningSupported(selectedAgent, cli.reasoning);
|