From ce650557d72eb2b3f2a21e4cfa9edf48f820df71 Mon Sep 17 00:00:00 2001 From: Dotta Date: Wed, 7 Oct 2026 09:43:18 -0500 Subject: [PATCH] Reserve Pi loader controls at both credential boundaries Co-Authored-By: Paperclip --- .../2026-10-02-pi-production-readiness.md | 44 ++++++++------- packages/paperclip-runner/acpx-profiles.json | 4 +- .../runner-core/src/acpx_sidecar_transport.rs | 54 ++++++++++++++++++- .../src/generated_acpx_profiles.rs | 2 +- .../tests/native_provider_backend.rs | 2 +- .../scripts/generate-acpx-profiles.mjs | 2 +- .../src/drivers/acpx/generated-profiles.ts | 4 +- .../src/drivers/acpx/pi-installation.test.ts | 38 +++++++------ .../drivers/acpx/pi-provider-config.test.ts | 12 ++++- .../src/drivers/acpx/pi-provider-config.ts | 46 +++++++++++++++- .../pi-acp/profile-v17-identity.json | 48 +++++++++++++++++ .../pi-acp/reserved-credential-names.json | 42 +++++++++++++++ scripts/pi-public-install-probe.mjs | 6 +-- 13 files changed, 258 insertions(+), 46 deletions(-) create mode 100644 packages/paperclip-runner/test-fixtures/pi-acp/profile-v17-identity.json create mode 100644 packages/paperclip-runner/test-fixtures/pi-acp/reserved-credential-names.json diff --git a/doc/plans/2026-10-02-pi-production-readiness.md b/doc/plans/2026-10-02-pi-production-readiness.md index 218695d3192..5c93f0e43a4 100644 --- a/doc/plans/2026-10-02-pi-production-readiness.md +++ b/doc/plans/2026-10-02-pi-production-readiness.md @@ -7,23 +7,31 @@ widgets, images, Cursor or Copilot qualification. The existing draft stack must ## Production qualification state — 2026-10-07 -The final target is native Linux with Pi profile 16. All 26 accepted Product -cells and seven Runner cases still require passes on the final source and -installed artifact set. Mac coverage is deferred. Historical passes below do -not transfer to the corrected source. +The final target is native Linux with Pi profile 17. All 26 accepted Product +cells and seven Runner cases require passes on the final source and installed +artifact set. Mac coverage is deferred. Historical passes do not transfer. -Review of `aa67de311` found that Rust rejected custom credential names such as -`LD_API_KEY` although the controller accepted them. The launcher now matches -`pi-provider-config.ts`: the same reserved names and prefixes are rejected, -while ordinary custom names are forwarded only through the authenticated Pi -session binding. Unit and real child-process tests cover `LD_API_KEY` and -`DYLD_API_KEY`, unrelated-secret exclusion and rejected reserved names. Pi's -provider closure is unchanged, so its profile remains 16. +Review found that Rust's initial broad `LD_` exclusion rejected valid custom +credentials; merely matching the controller then admitted real loader controls. +Both boundaries now reserve the same 40 loader, shell and process-control names +while permitting ordinary custom names such as `LD_API_KEY` and `DYLD_API_KEY`. +They share negative test inputs, and real child-process coverage verifies bound +credential forwarding and unrelated-secret exclusion. Profile 17 binds this +provider-configuration source correction. The Pi 1.0.0 native distributions, +wrapper, runtime extension and platform closure hashes remain unchanged. -The normal native Linux build of `aa67de311` passed. This credential-contract -correction requires a new source pin, normal package build/install and fresh -qualification. It introduces no model roster, fallback or default. The accepted -model and low thinking remain test inputs. +The normal native builds of `aa67de311` and `f817030f8` passed. Public Linux +package installation for `f817030f8` passed npm lifecycle checks but Pi setup +ran out of the disposable host's 10 GiB scratch disk. Preserve that failure and +superseded archives; reclaim only owned obsolete artifacts and completed caches +before rebuilding the corrected final source. No live attempt ran on `f817`. + +The full local Rust repeat at `f817` failed one Codex receipt-observation deadline; +the unchanged test passes in isolation. Linux CI passed its native Runner lane, +but browser shard 5 failed the signoff fixture's run-availability wait. Preserve +both observations and require new-head checks and review. The final source still +needs normal installed admission and all 33 live cases. No merge or release is +authorized. The fixture model and low thinking remain test inputs. ## Final fixture reconciliation — 2026-10-07 @@ -1035,9 +1043,9 @@ local tests and release review are still required. - Pi: `@earendil-works/pi-coding-agent@1.0.0`. - Wrapper: `pi-acp@0.0.33`; ACPX: `0.13.1`; Node: `24.21.0`. -- Candidate Pi profile: 16. Historical paid runtime profiles: 14 and 15. +- Candidate Pi profile: 17. Historical runtime profiles: 14, 15 and 16. Accepted fixture model: `openrouter/deepseek/deepseek-v4-flash-0731`. -- Pi/Copilot profile 16 and Cursor profile 15 bind the current shared ACPX patch. +- Pi profile 17, Copilot profile 16 and Cursor profile 15 bind the current shared ACPX patch. Earlier profiles remain historical evidence. Cursor and Copilot receive no new paid qualification in this work. - Reasoning: native-confirmed `low`. No silent model or thinking fallback. @@ -1052,7 +1060,7 @@ the dated history above and below. | Gate | Acceptance evidence | Current result | | --- | --- | --- | -| Runtime identity and admission | Exact runtime/profile/model; native thinking; startup below 60 seconds; unsupported configuration fails before prompting | Profile 16. Final corrected source needs normal installed Linux admission. Admission on `efd4` is historical. | +| Runtime identity and admission | Exact runtime/profile/model; native thinking; startup below 60 seconds; unsupported configuration fails before prompting | Profile 17. Final corrected source needs normal installed Linux admission. Admission on `efd4` is historical. | | Live lifecycle | Restart preserves the original pending request and process; stale answer rejected; Stop retires owned processes; three warm turns retain identity | Pending on the final artifact set. Preserve original failures; no unchanged paid retries. | | Product workflows | All 26 accepted cells, 13 local and 13 Daytona; public state, artifacts, terminal and cleanup evidence | Final set 0/26. The first `efd4` file-edit attempt failed before prompting with cleanup confirmed; the completed-cache disk correction is independently demonstrated. | | Runner protocol | All seven accepted cases through the normal packaged native Runner and authenticated mock control plane | Final set 0/7. Historical 5/7 belongs to the older profile-15 set. Definition source must bind the corrected shipping revision. | diff --git a/packages/paperclip-runner/acpx-profiles.json b/packages/paperclip-runner/acpx-profiles.json index d1a5aad93fb..2f6287283cc 100644 --- a/packages/paperclip-runner/acpx-profiles.json +++ b/packages/paperclip-runner/acpx-profiles.json @@ -14,12 +14,12 @@ "requiresProviderPolicy": false }, "pi": { - "agentProfileVersion": 16, + "agentProfileVersion": 17, "agentServerPackage": "pi-acp", "agentServerVersion": "0.0.33", "agentRuntimePackage": "@earendil-works/pi-coding-agent", "agentRuntimeVersion": "1.0.0", - "commandDigest": "sha256:28eefeccb2556d2668e20aee2f12a90d1fef50b9be954d5f6dd97c757e2e945e", + "commandDigest": "sha256:a1d976c437cb736c9cce8ebe8b8baf59e571761a8d00e8b1885e72dd906d8f21", "requiresProviderPolicy": true }, "cursor": { diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/acpx_sidecar_transport.rs b/packages/paperclip-runner/runner/crates/runner-core/src/acpx_sidecar_transport.rs index b1371ecf58f..2918f6c6411 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/acpx_sidecar_transport.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/acpx_sidecar_transport.rs @@ -138,7 +138,46 @@ fn pi_credential_environment_keys(binding: Option<&str>) -> Result, || name.starts_with("NPM_") || matches!( name.as_str(), - "PATH" | "HOME" | "SHELL" | "TMPDIR" | "LD_PRELOAD" | "DYLD_INSERT_LIBRARIES" + "PATH" + | "HOME" + | "SHELL" + | "TMPDIR" + | "BASH_ENV" + | "ENV" + | "ZDOTDIR" + | "LD_AUDIT" + | "LD_LIBRARY_PATH" + | "LD_PRELOAD" + | "LD_DEBUG" + | "LD_DEBUG_OUTPUT" + | "LD_PROFILE" + | "LD_PROFILE_OUTPUT" + | "LD_TRACE_LOADED_OBJECTS" + | "LD_ORIGIN_PATH" + | "LD_BIND_NOW" + | "LD_BIND_NOT" + | "LD_DYNAMIC_WEAK" + | "LD_HWCAP_MASK" + | "LD_SHOW_AUXV" + | "LD_USE_LOAD_BIAS" + | "LD_VERBOSE" + | "LD_WARN" + | "LD_ASSUME_KERNEL" + | "LD_PREFER_MAP_32BIT_EXEC" + | "DYLD_INSERT_LIBRARIES" + | "DYLD_LIBRARY_PATH" + | "DYLD_FRAMEWORK_PATH" + | "DYLD_FALLBACK_LIBRARY_PATH" + | "DYLD_FALLBACK_FRAMEWORK_PATH" + | "DYLD_VERSIONED_LIBRARY_PATH" + | "DYLD_VERSIONED_FRAMEWORK_PATH" + | "DYLD_ROOT_PATH" + | "DYLD_IMAGE_SUFFIX" + | "DYLD_SHARED_CACHE_DIR" + | "GLIBC_TUNABLES" + | "GCONV_PATH" + | "LOCPATH" + | "NLSPATH" ); if !valid_name || protected_name || !seen.insert(name) { return Err(invalid()); @@ -906,6 +945,19 @@ mod tests { assert!(pi_credential_environment_keys(Some(&"x".repeat(4_097))).is_err()); } + #[test] + fn pi_loader_and_shell_controls_match_the_controller_reservations() { + let names: Vec = serde_json::from_str(include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../../test-fixtures/pi-acp/reserved-credential-names.json" + ))) + .unwrap(); + for name in names { + let binding = json!({"schema":"paperclip.acpx_credential_binding.v1", "agent":"pi", "sessionId":"session-1", "names":[name]}); + assert!(pi_credential_environment_keys(Some(&binding.to_string())).is_err()); + } + } + #[test] fn pi_custom_credential_names_follow_the_controller_contract() { let names = vec!["LD_API_KEY", "DYLD_API_KEY", "MY_PI_SERVICE_KEY"]; diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/generated_acpx_profiles.rs b/packages/paperclip-runner/runner/crates/runner-core/src/generated_acpx_profiles.rs index 543e367e215..a04489675b6 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/generated_acpx_profiles.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/generated_acpx_profiles.rs @@ -29,7 +29,7 @@ pub(crate) fn acpx_release_profile(agent: &str) -> Option { agent_runtime_package: Some("@earendil-works/pi-coding-agent"), agent_runtime_version: Some("1.0.0"), command_digest: - "sha256:28eefeccb2556d2668e20aee2f12a90d1fef50b9be954d5f6dd97c757e2e945e", + "sha256:a1d976c437cb736c9cce8ebe8b8baf59e571761a8d00e8b1885e72dd906d8f21", requires_provider_policy: true, }, "cursor" => AcpxReleaseProfile { diff --git a/packages/paperclip-runner/runner/crates/runner-core/tests/native_provider_backend.rs b/packages/paperclip-runner/runner/crates/runner-core/tests/native_provider_backend.rs index adeaaee6a58..6a5c8a8ef50 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/tests/native_provider_backend.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/tests/native_provider_backend.rs @@ -17,7 +17,7 @@ use sha2::{Digest, Sha256}; const CODEX_ACPX_DIGEST: &str = "sha256:c4538599d1ab767db5dff50934f13bb5ba313a59d9c4a83e993fac4617ea63d3"; const PI_ACPX_DIGEST: &str = - "sha256:28eefeccb2556d2668e20aee2f12a90d1fef50b9be954d5f6dd97c757e2e945e"; + "sha256:a1d976c437cb736c9cce8ebe8b8baf59e571761a8d00e8b1885e72dd906d8f21"; fn temporary_directory(label: &str) -> PathBuf { let nonce = SystemTime::now() diff --git a/packages/paperclip-runner/scripts/generate-acpx-profiles.mjs b/packages/paperclip-runner/scripts/generate-acpx-profiles.mjs index bf04dddb544..2bdaf1d006a 100644 --- a/packages/paperclip-runner/scripts/generate-acpx-profiles.mjs +++ b/packages/paperclip-runner/scripts/generate-acpx-profiles.mjs @@ -39,7 +39,7 @@ const { commandDigest, ...attestation } = contract; assert.equal(commandDigest, `sha256:${createHash("sha256").update(canonicalJson(attestation)).digest("hex")}`); assert.equal(contract.acpxPatchSha256, createHash("sha256") .update(await readFile(new URL("../../patches/acpx@0.13.1.patch", root))).digest("hex")); -for (const [agent, path] of [["pi", "test-fixtures/pi-acp/profile-v16-identity.json"], ["copilot", "test/fixtures/copilot-profile-v16-identity.json"]]) { +for (const [agent, path] of [["pi", "test-fixtures/pi-acp/profile-v17-identity.json"], ["copilot", "test/fixtures/copilot-profile-v16-identity.json"]]) { const identity = await readJson(path); assert.equal(manifest.profiles[agent].agentProfileVersion, identity.declaration.agentProfileVersion); assert.equal(manifest.profiles[agent].commandDigest, identity.commandDigest); diff --git a/packages/paperclip-runner/src/drivers/acpx/generated-profiles.ts b/packages/paperclip-runner/src/drivers/acpx/generated-profiles.ts index 7fa1d7afb6a..1fa5f162a4d 100644 --- a/packages/paperclip-runner/src/drivers/acpx/generated-profiles.ts +++ b/packages/paperclip-runner/src/drivers/acpx/generated-profiles.ts @@ -22,12 +22,12 @@ export const QUALIFIED_ACPX_PROFILE_DATA = { "protocolVersion": 1, "acpxVersion": "0.13.1", "agent": "pi", - "agentProfileVersion": 16, + "agentProfileVersion": 17, "agentServerPackage": "pi-acp", "agentServerVersion": "0.0.33", "agentRuntimePackage": "@earendil-works/pi-coding-agent", "agentRuntimeVersion": "1.0.0", - "commandDigest": "sha256:28eefeccb2556d2668e20aee2f12a90d1fef50b9be954d5f6dd97c757e2e945e", + "commandDigest": "sha256:a1d976c437cb736c9cce8ebe8b8baf59e571761a8d00e8b1885e72dd906d8f21", "permissionPolicy": "interactive" }, "cursor": { diff --git a/packages/paperclip-runner/src/drivers/acpx/pi-installation.test.ts b/packages/paperclip-runner/src/drivers/acpx/pi-installation.test.ts index 24fcfd7fa5a..5fbe331af56 100644 --- a/packages/paperclip-runner/src/drivers/acpx/pi-installation.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/pi-installation.test.ts @@ -40,11 +40,19 @@ describe("Pi installation factory", () => { expect(current.commandDigest).not.toBe(prior.commandDigest); }); + it("preserves profile 16 and changes only the provider credential policy in profile 17", async () => { + const prior = JSON.parse(await readFile(new URL("../../../test-fixtures/pi-acp/profile-v16-identity.json", import.meta.url), "utf8")); + const current = JSON.parse(await readFile(new URL("../../../test-fixtures/pi-acp/profile-v17-identity.json", import.meta.url), "utf8")); + expect(Object.keys(current.declaration).filter(key => JSON.stringify(current.declaration[key]) !== JSON.stringify(prior.declaration[key])).sort()) + .toEqual(["agentProfileVersion", "providerConfigurationSourceSha256"]); + expect(current.commandDigest).not.toBe(prior.commandDigest); + }); + it("binds the profile declaration to the reviewed patch and platform closure pins", async () => { const hash = (bytes: string | Uint8Array) => createHash("sha256").update(bytes).digest("hex"); const canonical = (value: any): string => Array.isArray(value) ? `[${value.map(canonical).join(",")}]` : value && typeof value === "object" ? `{${Object.keys(value).sort().map(key => `${JSON.stringify(key)}:${canonical(value[key])}`).join(",")}}` : JSON.stringify(value); - const identity = JSON.parse(await readFile(new URL("../../../test-fixtures/pi-acp/profile-v16-identity.json", import.meta.url), "utf8")); + const identity = JSON.parse(await readFile(new URL("../../../test-fixtures/pi-acp/profile-v17-identity.json", import.meta.url), "utf8")); const declaration = identity.declaration; expect(declaration.sharedRuntimeContract).toBe("paperclip.acpx-runtime-contract.v1"); expect(identity.commandDigest).toBe(`sha256:${hash(canonical(declaration))}`); @@ -74,20 +82,20 @@ describe("Pi installation factory", () => { it("rejects legacy profiles and tampered executable identities", () => { expect(() => assertPiInstallationProfile(candidate())).not.toThrow(); - expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 1 })).toThrow("version 16"); - expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 2 })).toThrow("version 16"); - expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 3 })).toThrow("version 16"); - expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 4 })).toThrow("version 16"); - expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 5 })).toThrow("version 16"); - expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 6 })).toThrow("version 16"); - expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 7 })).toThrow("version 16"); - expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 8 })).toThrow("version 16"); - expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 9 })).toThrow("version 16"); - expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 10 })).toThrow("version 16"); - expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 11 })).toThrow("version 16"); - expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 12 })).toThrow("version 16"); - expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 13 })).toThrow("version 16"); - expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 14 })).toThrow("version 16"); + expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 1 })).toThrow("version 17"); + expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 2 })).toThrow("version 17"); + expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 3 })).toThrow("version 17"); + expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 4 })).toThrow("version 17"); + expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 5 })).toThrow("version 17"); + expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 6 })).toThrow("version 17"); + expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 7 })).toThrow("version 17"); + expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 8 })).toThrow("version 17"); + expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 9 })).toThrow("version 17"); + expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 10 })).toThrow("version 17"); + expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 11 })).toThrow("version 17"); + expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 12 })).toThrow("version 17"); + expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 13 })).toThrow("version 17"); + expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 14 })).toThrow("version 17"); for (const changed of [{ agentServerVersion: "latest" }, { commandDigest: `sha256:${"0".repeat(64)}` }, { agentRuntimePackage: "ambient-pi" }]) { expect(() => assertPiInstallationProfile({ ...candidate(), ...changed })).toThrow("trusted declaration"); } diff --git a/packages/paperclip-runner/src/drivers/acpx/pi-provider-config.test.ts b/packages/paperclip-runner/src/drivers/acpx/pi-provider-config.test.ts index 0a7bd7b065b..60c29152b95 100644 --- a/packages/paperclip-runner/src/drivers/acpx/pi-provider-config.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/pi-provider-config.test.ts @@ -1,4 +1,4 @@ -import { mkdtemp, rm } from "node:fs/promises"; +import { mkdtemp, readFile, rm } from "node:fs/promises"; import { join } from "node:path"; import { tmpdir } from "node:os"; import { describe, expect, it, vi } from "vitest"; @@ -42,6 +42,16 @@ describe("Pi caller-selected providers and models", () => { expect(() => piProviderConfiguration({ PAPERCLIP_PI_PROVIDERS: JSON.stringify({ custom: { apiKey } }) })).toThrow(); }); + it("reserves loader and shell controls at the controller boundary", async () => { + const names = JSON.parse(await readFile(new URL("../../../test-fixtures/pi-acp/reserved-credential-names.json", import.meta.url), "utf8")); + for (const apiKey of names) { + expect(() => piProviderConfiguration({ PAPERCLIP_PI_PROVIDERS: JSON.stringify({ custom: { apiKey } }) }), apiKey).toThrow(); + } + for (const apiKey of ["LD_API_KEY", "DYLD_API_KEY", "MY_PI_SERVICE_KEY"]) { + expect(piProviderConfiguration({ PAPERCLIP_PI_PROVIDERS: JSON.stringify({ custom: { apiKey } }) })?.credentialNames).toEqual([apiKey]); + } + }); + it("binds custom-provider configuration and model identity across recovery", async () => { const root = await mkdtemp(join(tmpdir(), "pi-model-recovery-")); try { diff --git a/packages/paperclip-runner/src/drivers/acpx/pi-provider-config.ts b/packages/paperclip-runner/src/drivers/acpx/pi-provider-config.ts index a32f23324cf..a8b5a56871b 100644 --- a/packages/paperclip-runner/src/drivers/acpx/pi-provider-config.ts +++ b/packages/paperclip-runner/src/drivers/acpx/pi-provider-config.ts @@ -17,6 +17,50 @@ export const PI_CREDENTIAL_NAMES = Object.freeze([ "PAPERCLIP_PI_PROVIDERS", ]); +// These names alter process startup rather than identify a provider credential. +const RESERVED_PI_CREDENTIAL_NAMES = new Set([ + "PATH", + "HOME", + "SHELL", + "TMPDIR", + "BASH_ENV", + "ENV", + "ZDOTDIR", + "LD_AUDIT", + "LD_LIBRARY_PATH", + "LD_PRELOAD", + "LD_DEBUG", + "LD_DEBUG_OUTPUT", + "LD_PROFILE", + "LD_PROFILE_OUTPUT", + "LD_TRACE_LOADED_OBJECTS", + "LD_ORIGIN_PATH", + "LD_BIND_NOW", + "LD_BIND_NOT", + "LD_DYNAMIC_WEAK", + "LD_HWCAP_MASK", + "LD_SHOW_AUXV", + "LD_USE_LOAD_BIAS", + "LD_VERBOSE", + "LD_WARN", + "LD_ASSUME_KERNEL", + "LD_PREFER_MAP_32BIT_EXEC", + "DYLD_INSERT_LIBRARIES", + "DYLD_LIBRARY_PATH", + "DYLD_FRAMEWORK_PATH", + "DYLD_FALLBACK_LIBRARY_PATH", + "DYLD_FALLBACK_FRAMEWORK_PATH", + "DYLD_VERSIONED_LIBRARY_PATH", + "DYLD_VERSIONED_FRAMEWORK_PATH", + "DYLD_ROOT_PATH", + "DYLD_IMAGE_SUFFIX", + "DYLD_SHARED_CACHE_DIR", + "GLIBC_TUNABLES", + "GCONV_PATH", + "LOCPATH", + "NLSPATH", +]); + export function piProviderConfiguration(environment?: NodeJS.ProcessEnv): { json: string; digest: string; credentialNames: readonly string[]; } | undefined { @@ -32,7 +76,7 @@ export function piProviderConfiguration(environment?: NodeJS.ProcessEnv): { if (typeof value !== "string" || value.trimStart().startsWith("!")) throw invalid(); if (/^[A-Z][A-Z0-9_]{0,127}$/.test(value)) { if (/^(?:PAPERCLIP_|NODE_|NPM_|npm_)/.test(value) - || ["PATH", "HOME", "SHELL", "TMPDIR", "LD_PRELOAD", "DYLD_INSERT_LIBRARIES"].includes(value)) throw invalid(); + || RESERVED_PI_CREDENTIAL_NAMES.has(value)) throw invalid(); names.add(value); } }; diff --git a/packages/paperclip-runner/test-fixtures/pi-acp/profile-v17-identity.json b/packages/paperclip-runner/test-fixtures/pi-acp/profile-v17-identity.json new file mode 100644 index 00000000000..85d8e82f8b0 --- /dev/null +++ b/packages/paperclip-runner/test-fixtures/pi-acp/profile-v17-identity.json @@ -0,0 +1,48 @@ +{ + "commandDigest": "sha256:a1d976c437cb736c9cce8ebe8b8baf59e571761a8d00e8b1885e72dd906d8f21", + "declaration": { + "schema": "paperclip.acpx_profile_declaration.v1", + "agent": "pi", + "agentProfileVersion": 17, + "acpxVersion": "0.13.1", + "agentServerVersion": "0.0.33", + "agentRuntimeVersion": "1.0.0", + "nodeVersion": "24.21.0", + "closure": { + "darwin-arm64": "729947513854dadc2d596a34186f7f19beaffead0fc97698d8eeac96b8479533", + "darwin-x64": "80de2913634f83e958792ddebc17e94f6dbbe5aa9aed3cea3ff5b40f44c90a59", + "linux-x64": "5fedea5f04f4f76d0e2ead0637b00322583d6c859f9df4f4dc8b332aca416eda" + }, + "wrapperSha256": "8ef777e9e6a3cef37775bab2b800e4931330c23a0b151ce07fdc5b943f28435d", + "helperSha256": "2191b1e5f281d24508ec7eaff39011ee863a8a70bf54f4aa0490ad0172b323da", + "extensionSha256": "71a85bf2e736d4108ea88a4aed01df3f547e8e4ccdeadea8251220e40cb05394", + "agentFilesBinding": "registered-runtime-context-v1", + "nativeQuestions": "select-confirm-input-editor-v3-method-specific-fields", + "pathPolicy": "sdk-normalization-v1", + "mcpToolNames": "provider-safe-exact-source-v1", + "acpxPatchSha256": "00d6af17216cb0cad902b48633eb19bf0ec6b05837a984cb24661fca593f09c1", + "nativeAssistantMessages": "pi1-rpc-deltas-v3-bound-message-and-tool-receipts", + "nativeNotices": "session-extension-v2-bounded-runtime-failure", + "messageProjectionSha256": "17d1e91b948dfa6a56e1905645add52b502041ad843c8d50a13b07956f22aadd", + "noticeProjectionSha256": "014485adc690998e395d334ea3ae67f72be6060a2fc78a1185df7b8d1da28fbf", + "sharedRuntimeContract": "paperclip.acpx-runtime-contract.v1", + "nativeInputDisposition": "queued-only-v1", + "cacheWarming": "disabled-owned-decision-v1", + "thinkingLevel": { + "supported": [ + "off", + "low", + "high", + "max" + ], + "newConfigurationDefault": "low", + "admission": "explicit-native-effective-before-prompt-v1", + "recovery": "mode-bound-v1" + }, + "modelSelection": "caller-selected-native-verified-v1", + "providerConfigurationSourceSha256": "2735ec4b46929f4f59c26cea42e623dfc81016aa7da7bd8399a24caa68a75d6b", + "credentialEnvironmentSourceSha256": "4ee0e9ac56fab8889a4d06b98634bb1797aeda64fcb31e0a404829018c7a12cb", + "runtimeSandboxSourceSha256": "21e1ed2d132886260cf17f956dc21824948bd428b8a0aa05518c508017e66213", + "recoveryIdentitySourceSha256": "f333a24b19ef1b5b5d23ea9778e753f7051690e835ef9e4476a962d8a231c375" + } +} diff --git a/packages/paperclip-runner/test-fixtures/pi-acp/reserved-credential-names.json b/packages/paperclip-runner/test-fixtures/pi-acp/reserved-credential-names.json new file mode 100644 index 00000000000..5f2dd1c718f --- /dev/null +++ b/packages/paperclip-runner/test-fixtures/pi-acp/reserved-credential-names.json @@ -0,0 +1,42 @@ +[ + "PATH", + "HOME", + "SHELL", + "TMPDIR", + "BASH_ENV", + "ENV", + "ZDOTDIR", + "LD_AUDIT", + "LD_LIBRARY_PATH", + "LD_PRELOAD", + "LD_DEBUG", + "LD_DEBUG_OUTPUT", + "LD_PROFILE", + "LD_PROFILE_OUTPUT", + "LD_TRACE_LOADED_OBJECTS", + "LD_ORIGIN_PATH", + "LD_BIND_NOW", + "LD_BIND_NOT", + "LD_DYNAMIC_WEAK", + "LD_HWCAP_MASK", + "LD_SHOW_AUXV", + "LD_USE_LOAD_BIAS", + "LD_VERBOSE", + "LD_WARN", + "LD_ASSUME_KERNEL", + "LD_PREFER_MAP_32BIT_EXEC", + "DYLD_INSERT_LIBRARIES", + "DYLD_LIBRARY_PATH", + "DYLD_FRAMEWORK_PATH", + "DYLD_FALLBACK_LIBRARY_PATH", + "DYLD_FALLBACK_FRAMEWORK_PATH", + "DYLD_VERSIONED_LIBRARY_PATH", + "DYLD_VERSIONED_FRAMEWORK_PATH", + "DYLD_ROOT_PATH", + "DYLD_IMAGE_SUFFIX", + "DYLD_SHARED_CACHE_DIR", + "GLIBC_TUNABLES", + "GCONV_PATH", + "LOCPATH", + "NLSPATH" +] diff --git a/scripts/pi-public-install-probe.mjs b/scripts/pi-public-install-probe.mjs index 0c7fc8714ab..b7cb62ea8b1 100644 --- a/scripts/pi-public-install-probe.mjs +++ b/scripts/pi-public-install-probe.mjs @@ -20,8 +20,8 @@ assert.equal(JSON.parse(await readFile(join(server, 'package.json'), 'utf8')).na const { resolvePaperclipRunnerBinary } = await import(pathToFileURL(join(server, 'dist/services/native-runtime/native-codex-runner.js'))); const { createCapabilityRunnerdCodexTransport } = await import(pathToFileURL(join(server, 'dist/vendor/paperclip-runner/live/runnerd-codex-transport.js'))); const { QUALIFIED_ACPX_PROFILES } = await import(pathToFileURL(join(server, 'dist/vendor/paperclip-runner/drivers/acpx/qualified-profiles.js'))); -assert.equal(QUALIFIED_ACPX_PROFILES.pi.agentProfileVersion, 16); -assert.equal(QUALIFIED_ACPX_PROFILES.pi.commandDigest, 'sha256:28eefeccb2556d2668e20aee2f12a90d1fef50b9be954d5f6dd97c757e2e945e'); +assert.equal(QUALIFIED_ACPX_PROFILES.pi.agentProfileVersion, 17); +assert.equal(QUALIFIED_ACPX_PROFILES.pi.commandDigest, 'sha256:a1d976c437cb736c9cce8ebe8b8baf59e571761a8d00e8b1885e72dd906d8f21'); assert.equal(Object.hasOwn(QUALIFIED_ACPX_PROFILES.pi, 'qualificationModel'), false); assert.equal(Object.hasOwn(QUALIFIED_ACPX_PROFILES.pi, 'reportedModelId'), false); const daemon = resolvePaperclipRunnerBinary(); @@ -64,7 +64,7 @@ try { assert.equal(state.activeTurnId, null); assert.equal(state.identity, null); assert.equal(state.providerExitUnconfirmed, false); - console.log(JSON.stringify({ schema: 'paperclip.pi.public-npm-install.v1', target: `${process.platform}-${process.arch}`, normalPackagedDaemon: true, exactPiProfile: 16, runtime: '1.0.0', credentials: 'none', promptCalls: 0, settledMs, cleanRunnerExit: true })); + console.log(JSON.stringify({ schema: 'paperclip.pi.public-npm-install.v1', target: `${process.platform}-${process.arch}`, normalPackagedDaemon: true, exactPiProfile: 17, runtime: '1.0.0', credentials: 'none', promptCalls: 0, settledMs, cleanRunnerExit: true })); } finally { try { await bundle?.transport.close(); } finally { clearTimeout(watchdog); await rm(root, { recursive: true, force: true }); }