mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-14 17:40:37 +02:00
Attest native Copilot permission targets through workspace aliases
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
e18f56edc9
commit
0526d8e9f4
9 files changed
+44
-14
No files matched your search
@@ -32,12 +32,12 @@
|
||||
"requiresProviderPolicy": true
|
||||
},
|
||||
"copilot": {
|
||||
"agentProfileVersion": 32,
|
||||
"agentProfileVersion": 33,
|
||||
"agentServerPackage": "@github/copilot",
|
||||
"agentServerVersion": "1.0.88",
|
||||
"agentRuntimePackage": null,
|
||||
"agentRuntimeVersion": null,
|
||||
"commandDigest": "sha256:3dcc8de3034e32001b0a819a614beca345d314266a70a09e0050a9f5f58275fc",
|
||||
"commandDigest": "sha256:7b459cc43771d4178898be8a362030ac2e4d8356c2a14a82334958959dd54e59",
|
||||
"requiresProviderPolicy": true
|
||||
},
|
||||
"claude": {
|
||||
|
||||
@@ -3620,7 +3620,7 @@ mod tests {
|
||||
descriptor_value["agentRuntimePackage"] = Value::Null;
|
||||
descriptor_value["agentRuntimeVersion"] = Value::Null;
|
||||
descriptor_value["commandDigest"] = serde_json::from_str::<Value>(include_str!(
|
||||
"../../../../test/fixtures/copilot-profile-v32-identity.json"
|
||||
"../../../../test/fixtures/copilot-profile-v33-identity.json"
|
||||
))
|
||||
.unwrap()["commandDigest"]
|
||||
.clone();
|
||||
|
||||
@@ -47,7 +47,7 @@ pub(crate) fn acpx_release_profile(agent: &str) -> Option<AcpxReleaseProfile> {
|
||||
agent_runtime_package: None,
|
||||
agent_runtime_version: None,
|
||||
command_digest:
|
||||
"sha256:3dcc8de3034e32001b0a819a614beca345d314266a70a09e0050a9f5f58275fc",
|
||||
"sha256:7b459cc43771d4178898be8a362030ac2e4d8356c2a14a82334958959dd54e59",
|
||||
requires_provider_policy: true,
|
||||
},
|
||||
"claude" => AcpxReleaseProfile {
|
||||
|
||||
+1
-1
@@ -1029,7 +1029,7 @@ fn provider_death_expires_input_and_commits_failure_without_querying_the_dead_pr
|
||||
let mut config = acpx_config(&directory, mode);
|
||||
let mut payload = prepare_payload_with_mode(&directory, "codex", mode);
|
||||
let identity: Value = serde_json::from_str(include_str!(
|
||||
"../../../../test/fixtures/copilot-profile-v32-identity.json"
|
||||
"../../../../test/fixtures/copilot-profile-v33-identity.json"
|
||||
))
|
||||
.unwrap();
|
||||
let digest = identity["commandDigest"].as_str().unwrap();
|
||||
|
||||
@@ -7,7 +7,7 @@ import { COPILOT_VERSION, materializePinnedCopilotBinary, resolveCopilotDistribu
|
||||
|
||||
const MAX_ARCHIVE_BYTES = 128 * 1024 * 1024;
|
||||
const MAX_EXPANDED_BYTES = 384 * 1024 * 1024;
|
||||
const PROFILE_DIGEST = "sha256:3dcc8de3034e32001b0a819a614beca345d314266a70a09e0050a9f5f58275fc";
|
||||
const PROFILE_DIGEST = "sha256:7b459cc43771d4178898be8a362030ac2e4d8356c2a14a82334958959dd54e59";
|
||||
|
||||
/** Build-time only; outputRoot is the exact runner-owned platform asset directory. */
|
||||
export async function buildPinnedCopilotDistribution({ outputRoot, platform = process.platform, architecture = process.arch }, { fetchImpl = fetch } = {}) {
|
||||
|
||||
@@ -7,9 +7,9 @@ import { fileURLToPath } from "node:url";
|
||||
import { resolve, dirname } from "node:path";
|
||||
|
||||
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const fixture = resolve(root, "test/fixtures/copilot-profile-v32-identity.json");
|
||||
const fixture = resolve(root, "test/fixtures/copilot-profile-v33-identity.json");
|
||||
const historical = JSON.parse(readFileSync(resolve(root, "test/fixtures/copilot-profile-v12-identity.json"), "utf8"));
|
||||
const declaration = { ...historical.declaration, agentProfileVersion: 32 };
|
||||
const declaration = { ...historical.declaration, agentProfileVersion: 33 };
|
||||
delete declaration.permissionIdentitySourceSha256;
|
||||
const sources = {
|
||||
displayEventSourceSha256: "src/drivers/acpx/copilot-events.ts",
|
||||
|
||||
@@ -1,10 +1,35 @@
|
||||
import { redactPaperclipSemanticValue } from "../../semantic-tools/redaction.js";
|
||||
import { safeAcpxLocations } from "./safe-locations.js";
|
||||
import { lstatSync, realpathSync } from "node:fs";
|
||||
import { basename, dirname, isAbsolute, join } from "node:path";
|
||||
|
||||
export const COPILOT_PERMISSION_CONTEXT_CONTRACT = "copilot-edit-permission-context-v1";
|
||||
const record = (value: unknown): Record<string, unknown> =>
|
||||
value !== null && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : {};
|
||||
|
||||
/** Native providers can spell an absolute workspace path through a host alias
|
||||
* (macOS /tmp and /private/tmp). Attest the real entry, or its existing parent
|
||||
* for a new file, before converting it to a display target. */
|
||||
function aliasedLocation(path: string, cwd: string): string | undefined {
|
||||
if (!isAbsolute(path)) return;
|
||||
try {
|
||||
const canonicalCwd = realpathSync(cwd);
|
||||
let canonicalPath: string;
|
||||
try {
|
||||
lstatSync(path);
|
||||
canonicalPath = realpathSync(path);
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.ErrnoException).code !== "ENOENT") return;
|
||||
// A dangling symlink must not become an apparently safe create target.
|
||||
try { lstatSync(path); return; } catch (missing) {
|
||||
if ((missing as NodeJS.ErrnoException).code !== "ENOENT") return;
|
||||
}
|
||||
canonicalPath = join(realpathSync(dirname(path)), basename(path));
|
||||
}
|
||||
return safeAcpxLocations([{ path: canonicalPath }], canonicalCwd, "edit")[0]?.path as string | undefined;
|
||||
} catch { return; }
|
||||
}
|
||||
|
||||
/** Display context only; this does not authorize filesystem access. */
|
||||
export function safeCopilotEditTarget(value: unknown, workingDirectory: string | undefined): string | undefined {
|
||||
const call = record(value);
|
||||
@@ -14,7 +39,12 @@ export function safeCopilotEditTarget(value: unknown, workingDirectory: string |
|
||||
if (Array.isArray(call.locations) && (call.locations.length > 16 || call.locations.some(x => typeof record(x).path !== "string"))) return;
|
||||
const paths = [input.path, input.fileName, ...(Array.isArray(call.locations) ? call.locations.map(x => record(x).path) : [])].filter(x => x !== undefined);
|
||||
if (!paths.length || paths.some(x => typeof x !== "string" || !x || x.trim() !== x || x.length > 2048 || /[\\\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u.test(x))) return;
|
||||
const safe = paths.flatMap(path => safeAcpxLocations([{ path }], workingDirectory, call.kind, call.title));
|
||||
const safe = paths.flatMap(path => {
|
||||
const locations = safeAcpxLocations([{ path }], workingDirectory, call.kind, call.title);
|
||||
if (locations.length) return locations;
|
||||
const aliased = aliasedLocation(path as string, workingDirectory);
|
||||
return aliased ? [{ path: aliased }] : [];
|
||||
});
|
||||
const names = safe.map(x => x.path).filter((x): x is string => typeof x === "string" && x.length <= 1024 && x.trim() === x && !x.includes(":"));
|
||||
if (names.length !== paths.length || new Set(names).size !== 1) return;
|
||||
const target = names[0];
|
||||
|
||||
@@ -48,12 +48,12 @@ export const QUALIFIED_ACPX_PROFILE_DATA = {
|
||||
"protocolVersion": 1,
|
||||
"acpxVersion": "0.13.1",
|
||||
"agent": "copilot",
|
||||
"agentProfileVersion": 32,
|
||||
"agentProfileVersion": 33,
|
||||
"agentServerPackage": "@github/copilot",
|
||||
"agentServerVersion": "1.0.88",
|
||||
"agentRuntimePackage": null,
|
||||
"agentRuntimeVersion": null,
|
||||
"commandDigest": "sha256:3dcc8de3034e32001b0a819a614beca345d314266a70a09e0050a9f5f58275fc",
|
||||
"commandDigest": "sha256:7b459cc43771d4178898be8a362030ac2e4d8356c2a14a82334958959dd54e59",
|
||||
"permissionPolicy": "interactive"
|
||||
},
|
||||
"claude": {
|
||||
|
||||
+3
-3
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"commandDigest": "sha256:3dcc8de3034e32001b0a819a614beca345d314266a70a09e0050a9f5f58275fc",
|
||||
"commandDigest": "sha256:7b459cc43771d4178898be8a362030ac2e4d8356c2a14a82334958959dd54e59",
|
||||
"declaration": {
|
||||
"schema": "paperclip.acpx_profile_declaration.v1",
|
||||
"agent": "copilot",
|
||||
"agentProfileVersion": 32,
|
||||
"agentProfileVersion": 33,
|
||||
"acpxVersion": "0.13.1",
|
||||
"agentServerVersion": "1.0.88",
|
||||
"protocolPolicyRevision": "copilot-agent-manual-v1",
|
||||
@@ -19,7 +19,7 @@
|
||||
"messageIdentityContract": "copilot-native-message-id-v1",
|
||||
"ownedDistributionDelivery": "COPILOT_CLI_DIST_DIR:lease-owned-guarded-inner-distribution:v1",
|
||||
"permissionContextContract": "copilot-edit-permission-context-v1",
|
||||
"permissionContextSourceSha256": "fe1bb611bea289d81b1c7c973fbb01b93de133a7caa8fe78b7ff4ee1f485b224",
|
||||
"permissionContextSourceSha256": "de2d38d702b3c574161af0c0c04790fb6f6347916eb3916ce7a131a8c229121b",
|
||||
"permissionAdapterSourceSha256": "eebcc1ec9bc8caa8d1edf0cbda74bd69f5c55cf8d65a0503e95798b9979a19dc",
|
||||
"permissionLocationsSourceSha256": "f39a81da395b6e23aa2a1a45572a28846e5e81bfd3d764d9a456f74a79c752e9",
|
||||
"permissionRedactionSourceSha256": "ad6770c69269087951790e2e87e22ac088e3d432769adf17ebc865ed512e6c4f",
|
||||
Reference in new issue
Block a user