mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-19 20:44:16 +02:00
Reject unrelated edits and command mentions, normalize the private workspace path, and replay retained paid evidence offline without another prompt. Co-Authored-By: Paperclip <noreply@paperclip.ing>
103 lines
8.2 KiB
JavaScript
103 lines
8.2 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
import { evaluateProbe, isExactMarkerWrite, permissionDecision } from "./qualify-copilot-acp.mjs";
|
|
const options = [{ kind: "allow_once", optionId: "native-approve-17" }, { kind: "allow_always", optionId: "native-session-3" }, { kind: "reject_once", optionId: "native-deny-0" }];
|
|
test("permission decisions preserve provider option identities and never select session-wide approval", () => {
|
|
const params = { sessionId: "owned", options, toolCall: { rawInput: { command: "exact" } } };
|
|
assert.deepEqual(permissionDecision("deny-write", params, "owned", "exact").outcome, { outcome: "selected", optionId: "native-deny-0" });
|
|
assert.deepEqual(permissionDecision("detached-shell", params, "owned", "exact").outcome, { outcome: "selected", optionId: "native-approve-17" });
|
|
for (const value of [{ ...params, sessionId: "other" }, { ...params, toolCall: { rawInput: { command: "changed" } } }]) assert.equal(permissionDecision("detached-shell", value, "owned", "exact").kind, "reject_once");
|
|
assert.deepEqual(permissionDecision("deny-write", { ...params, options: [] }, "owned", "exact").outcome, { outcome: "cancelled" });
|
|
});
|
|
test("denial needs an actual rejected write and no side effect even after terminal", () => {
|
|
const evidence = { scenario: "deny-write", stopReason: "end_turn", cleanupComplete: true, permissions: [{ exactSession: true, kind: "reject_once", writeAttempt: true, outcome: { outcome: "selected" } }], markerSamples: [{ exists: false }] };
|
|
assert.equal(evaluateProbe(evidence).passed, true);
|
|
assert.deepEqual(evaluateProbe({ ...evidence, permissions: [] }).failures, ["no_observed_denied_write_request"]);
|
|
assert.deepEqual(evaluateProbe({ ...evidence, markerSamples: [{ exists: false }, { exists: true }] }).failures, ["denied_write_had_side_effect"]);
|
|
});
|
|
test("denial evidence requires the native write target, not an unrelated edit or command mention", () => {
|
|
const marker = "/fixture/workspace/qualification-marker.txt";
|
|
for (const fileName of [marker, "qualification-marker.txt", "./qualification-marker.txt"]) {
|
|
assert.equal(isExactMarkerWrite({ kind: "edit", rawInput: { fileName } }, marker), true);
|
|
}
|
|
for (const toolCall of [
|
|
{ kind: "edit", rawInput: { fileName: "other.txt" } },
|
|
{ kind: "edit", rawInput: { fileName: "../qualification-marker.txt" } },
|
|
{ kind: "edit", rawInput: { fileName: `${marker}\0` } },
|
|
{ kind: "edit", rawInput: { command: "echo qualification-marker.txt" } },
|
|
{ kind: "execute", rawInput: { fileName: marker } },
|
|
{ kind: "edit" },
|
|
]) {
|
|
const writeAttempt = isExactMarkerWrite(toolCall, marker);
|
|
assert.equal(writeAttempt, false);
|
|
assert.equal(evaluateProbe({ scenario: "deny-write", stopReason: "end_turn", cleanupComplete: true,
|
|
permissions: [{ exactSession: true, kind: "reject_once", writeAttempt, outcome: { outcome: "selected" } }],
|
|
markerSamples: [{ exists: false }],
|
|
}).passed, false);
|
|
}
|
|
});
|
|
test("retained real denial still passes the exact target oracle without another provider call", async () => {
|
|
const { readFile } = await import("node:fs/promises");
|
|
const evidence = JSON.parse(await readFile(new URL("../test/fixtures/copilot-live-denial-2026-09-28.json", import.meta.url), "utf8"));
|
|
assert.equal(isExactMarkerWrite(evidence.nativeToolCall, "/fixture/workspace/qualification-marker.txt"), true);
|
|
});
|
|
test("detached settlement requires native mode, exact approval and output before terminal", () => {
|
|
const evidence = { scenario: "detached-shell", stopReason: "end_turn", cleanupComplete: true, detachedToolObserved: true, backgroundCompletionObservedBeforeTerminal: true, permissions: [{ exactSession: true, kind: "allow_once", exactCommand: true }], markerAtTerminal: { matches: true } };
|
|
assert.equal(evaluateProbe(evidence).passed, true);
|
|
for (const field of ["detachedToolObserved", "backgroundCompletionObservedBeforeTerminal", "cleanupComplete"]) assert.equal(evaluateProbe({ ...evidence, [field]: false }).passed, false);
|
|
assert.equal(evaluateProbe({ ...evidence, markerAtTerminal: { matches: false } }).passed, false);
|
|
assert.equal(evaluateProbe({ ...evidence, failureCode: "request_deadline:session/prompt" }).passed, false);
|
|
});
|
|
|
|
test("missing credentials reject before resolving a pack or launching anything", async () => {
|
|
const { runProbe } = await import("./qualify-copilot-acp.mjs");
|
|
await assert.rejects(runProbe("/does-not-exist", "/unused", "deny-write", undefined), /explicit Copilot token required/);
|
|
});
|
|
|
|
test("full credential-free protocol fixture preserves numeric-zero denial and reaps its process", async () => {
|
|
const { mkdtemp, mkdir, writeFile, readFile, rm } = await import("node:fs/promises");
|
|
const { tmpdir } = await import("node:os");
|
|
const { join } = await import("node:path");
|
|
const { runProbe } = await import("./qualify-copilot-acp.mjs");
|
|
const root = await mkdtemp(join(tmpdir(), "copilot-risk-probe-test-"));
|
|
const moduleRoot = join(root, "dist/drivers/acpx");
|
|
try {
|
|
await mkdir(moduleRoot, { recursive: true });
|
|
await writeFile(join(root, "package.json"), JSON.stringify({ type: "module" }));
|
|
await writeFile(join(root, "provider-pack.json"), JSON.stringify({ payload: { runnerSourceRevision: "fixture-only" }, digest: "fixture-only" }));
|
|
await writeFile(join(moduleRoot, "qualified-profiles.js"), 'export const resolveQualifiedAcpxProfile = () => ({});');
|
|
await writeFile(join(moduleRoot, "copilot-events.js"), 'export const COPILOT_ACP_CLIENT_CAPABILITIES = {};');
|
|
const childScript = join(root, "child.mjs");
|
|
await writeFile(childScript, `import { createInterface } from 'node:readline';
|
|
const send = m => process.stdout.write(JSON.stringify({jsonrpc:'2.0', ...m})+'\\n');
|
|
let promptId;
|
|
createInterface({input:process.stdin}).on('line', line => {
|
|
const m=JSON.parse(line);
|
|
if(!m.method){if(m.id!==0 || m.result?.outcome?.optionId!=='native-deny-0') process.exit(8); send({id:promptId,result:{stopReason:'end_turn'}});return;}
|
|
let result={};
|
|
if(m.method==='initialize')result={agentInfo:{version:'1.0.88'},agentCapabilities:{}};
|
|
if(m.method==='session/new')result={sessionId:'fixture-session',models:{availableModels:[{modelId:'gpt-5.6-luna'}]}};
|
|
if(m.method==='session/set_config_option')result={configOptions:[{id:'model',currentValue:'gpt-5.6-luna'}]};
|
|
if(m.method==='session/prompt'){promptId=m.id;send({id:0,method:'session/request_permission',params:{sessionId:'fixture-session',toolCall:{kind:'edit',rawInput:{fileName:process.cwd()+'/qualification-marker.txt'}},options:${JSON.stringify(options)}}});return;}
|
|
send({id:m.id,result});
|
|
}).on('close',()=>process.exit(0));`);
|
|
await writeFile(join(moduleRoot, "profile-installation.js"), `import { spawn } from 'node:child_process'; export const assertAcpxProfileEnvironment=()=>{}; export const verifyAcpxProfileInstallation=async()=>({commandDigest:'fixture-only',openCommand:async()=>({spawn:(_args,options)=>spawn(options.env.COPILOT_GITHUB_TOKEN === "fixture-spawn-failure" ? "/paperclip-fixture-missing-executable" : ${JSON.stringify(process.execPath)},[${JSON.stringify(childScript)}],options),close:async()=>{}})});`);
|
|
const output = join(root, "output.json");
|
|
const result = await runProbe(root, output, "deny-write", "fixture-token-never-used-for-network");
|
|
const evidence = JSON.parse(await readFile(output, "utf8"));
|
|
assert.equal(result.passed, true);
|
|
assert.equal(evidence.permissions[0].requestId, 0);
|
|
assert.deepEqual(evidence.permissions[0].outcome, { outcome: "selected", optionId: "native-deny-0" });
|
|
assert.equal(evidence.cleanupComplete, true);
|
|
assert.ok(evidence.markerSamples.every(sample => !sample.exists));
|
|
assert.equal(evidence.providerReportedCostUsd, null);
|
|
const failedOutput = join(root, "spawn-failure.json");
|
|
const failed = await runProbe(root, failedOutput, "deny-write", "fixture-spawn-failure");
|
|
const failedEvidence = JSON.parse(await readFile(failedOutput, "utf8"));
|
|
assert.equal(failed.passed, false);
|
|
assert.equal(failedEvidence.promptRequestsSent, 0);
|
|
assert.equal(failedEvidence.cleanupComplete, true);
|
|
assert.match(failedEvidence.failureCode, /provider_(spawn|stdin)_failure/);
|
|
} finally { await rm(root, { recursive: true, force: true }); }
|
|
});
|