mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-10 20:46:28 +01:00
Integrate verified lowercase Pi setup networking
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
commit
a85f840b16
7 files changed
+39
-13
No files matched your search
@@ -50,3 +50,9 @@ it("passes explicit setup network settings without provider keys or ambient Node
|
||||
expect(environment).toEqual({ ...network, LANG: "C.UTF-8" });
|
||||
expect(buildPiSetupEnvironment({})).toEqual({ PATH: "/usr/bin:/bin", LANG: "C.UTF-8" });
|
||||
});
|
||||
|
||||
it("preserves lowercase proxy settings and leaves precedence to Node/npm", () => {
|
||||
const lower = { http_proxy: "http://lower-proxy.example:8080", https_proxy: "http://lower-proxy.example:8080", no_proxy: "localhost" };
|
||||
expect(buildPiSetupEnvironment(lower)).toEqual({ PATH: "/usr/bin:/bin", LANG: "C.UTF-8", ...lower });
|
||||
expect(buildPiSetupEnvironment({ ...lower, HTTPS_PROXY: "http://upper-proxy.example:8080", OPENROUTER_API_KEY: "must-not-forward" })).toEqual({ PATH: "/usr/bin:/bin", LANG: "C.UTF-8", ...lower, HTTPS_PROXY: "http://upper-proxy.example:8080" });
|
||||
});
|
||||
@@ -30,7 +30,7 @@ export async function resolveRemoteCompanionImporter(serverUrl: string): Promise
|
||||
/** Public downloads may use operator network settings, never application secrets. */
|
||||
export function buildPiSetupEnvironment(source: NodeJS.ProcessEnv = process.env): NodeJS.ProcessEnv {
|
||||
const environment: NodeJS.ProcessEnv = { PATH: source.PATH ?? "/usr/bin:/bin", LANG: "C.UTF-8" };
|
||||
for (const key of ["LC_ALL", "HTTPS_PROXY", "HTTP_PROXY", "NO_PROXY", "SSL_CERT_FILE", "SSL_CERT_DIR", "NODE_EXTRA_CA_CERTS"]) {
|
||||
for (const key of ["LC_ALL", "HTTPS_PROXY", "HTTP_PROXY", "NO_PROXY", "https_proxy", "http_proxy", "no_proxy", "SSL_CERT_FILE", "SSL_CERT_DIR", "NODE_EXTRA_CA_CERTS"]) {
|
||||
if (typeof source[key] === "string") environment[key] = source[key];
|
||||
}
|
||||
return environment;
|
||||
|
||||
@@ -1130,7 +1130,8 @@ perform it automatically. It installs only this host's supported platform
|
||||
lock, wrapper patch and complete Pi closure. Node 24, npm, git, tar and the normal
|
||||
platform dependency inspector (`otool` or `ldd`) must be available. The server
|
||||
package must be writable by the installing account. Explicit setup preserves
|
||||
`HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY`, `SSL_CERT_FILE`, `SSL_CERT_DIR` and
|
||||
`HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY`, their lowercase equivalents,
|
||||
`SSL_CERT_FILE`, `SSL_CERT_DIR` and
|
||||
`NODE_EXTRA_CA_CERTS` for public downloads, and enables Node's environment proxy
|
||||
handling. The provisioner keeps the same closed allowlist. Instance settings,
|
||||
provider credentials, user npm configuration, `HOME`, `NODE_OPTIONS` and
|
||||
|
||||
@@ -27,7 +27,7 @@ qualification. The existing draft stack must be reviewed in dependency order.
|
||||
| Runner protocol | Pi roster passes through the native packaged runner and authenticated mock control plane; lifecycle/denial/control cases remain distinct from Product tests | All seven cases are attempted at `0bd040093`: five pass; context-before-action retains its 120-second terminal timeout, and finish-task retains use of the advisory report without the required task mutation. One context/document workflow retry passes only after verified protection against the recorded host sleep; its original failure stays unchanged. The seven `b148b73ea` passes remain historical. |
|
||||
| Installed distribution | Public CLI/server tars on ARM Mac, Intel Mac and Linux; normal Pi setup; exact Linux companion and immutable Daytona image imported without binary override | Public CLI/server installation and normal Pi setup pass on all three platforms at original execution source `0bd040093`. The installer correction at `fcef1eae9` changes CLI and provisioner bytes; fresh normal public artifact/install proof must bind the updated source. Old artifact proofs keep their original labels. Public immutable image `ghcr.io/paperclipai/paperclip-daytona-runner@sha256:342f1fd5cb8cabfa2242f38f4f686608b28b6536aa879c54b0cb0da6fba9ef47` imports into native Linux Daytona and passes closed admission. Temporary sandbox cleanup passes. Image jobs 37102904889, 37106313185 and 37110683910 are terminal/cancelled. |
|
||||
| Governance and spend | Company isolation, human-only permission, duplicate/stale answers, Stop and budget hard stop; pricing estimates never become claimed bills | 58 focused governance/cost/session invariants pass at `f5f57e380`. Live pending-permission Stop passes at `03dd6ef93`. The qualification key cap is not proof of Paperclip budget enforcement. |
|
||||
| Integration and rollout | Review each prerequisite; final-head typecheck, tests, build and CI pass; no unresolved review; exact artifacts; rollback recorded | Last verified readiness head `146f578c0` has 53 successful CI checks, two skips, current-head Greptile 5/5 and no unresolved root threads, including full build and typecheck. The new installer integration must receive its own completed current-head CI, review and updated public artifact/install proof. All 116 targeted native Linux fixture tests and 13 browser-case collections pass at `04f639eeb`; the new descriptor correction separately passes all 17 free native Linux fault tests. Full typecheck, complete test and build evidence remains recorded below; original failed commands remain failed. Frozen Pi execution inputs remain equivalent to `0bd040093`; the explicit installer has two changed executable inputs. Prerequisite #14921 passes every test/build/typecheck check but its valid admission review blocks merge. The retained SDK and setup network findings receive scoped corrections with focused verification; their new heads require fresh CI and review. Production stays held for eight failed gates and prerequisite disposition. No merge or release. |
|
||||
| Integration and rollout | Review each prerequisite; final-head typecheck, tests, build and CI pass; no unresolved review; exact artifacts; rollback recorded | Last verified readiness head `146f578c0` has 53 successful CI checks, two skips, current-head Greptile 5/5 and no unresolved root threads, including full build and typecheck. The new installer integration must receive its own completed current-head CI, review and updated public artifact/install proof. All 116 targeted native Linux fixture tests and 13 browser-case collections pass at `04f639eeb`; the new descriptor correction separately passes all 17 free native Linux fault tests. Full typecheck, complete test and build evidence remains recorded below; original failed commands remain failed. Frozen Pi execution inputs remain equivalent to `0bd040093`; the explicit installer has three changed executable inputs. Prerequisite #14921 passes every test/build/typecheck check but its valid admission review blocks merge. The retained SDK and setup network findings receive scoped corrections with focused verification; their new heads require fresh CI and review. Production stays held for eight failed gates and prerequisite disposition. No merge or release. |
|
||||
|
||||
## Bounded execution
|
||||
|
||||
@@ -82,8 +82,8 @@ success label is corrected by its strict verdict. All three owned sandboxes are
|
||||
deleted. The original paid rejection stage remains unproven.
|
||||
|
||||
Prerequisite fixes preserve every previous commit. The latest heads are
|
||||
#14921 `7f452cbd7`, #14922 `fcef1eae9`, #14923 `5aff21d77`, and #14924
|
||||
`5120ddc8f`. They carry the scoped UI/profile assertions and notice-display
|
||||
#14921 `7f452cbd7`, #14922 `b7e692f48`, #14923 `cca38f29a`, and #14924
|
||||
`c2e2e39db`. They carry the scoped UI/profile assertions and notice-display
|
||||
corrections. The first prerequisite also receives the existing package-local
|
||||
Runner probe import repair and remaining admission assertions already present
|
||||
downstream. The complete repair includes both public probe export surfaces.
|
||||
@@ -132,7 +132,7 @@ canonical roots and all existing ambient-Node negative fixtures stay enforced.
|
||||
|
||||
The explicit CLI setup child and its bundled provisioner now preserve the same
|
||||
closed allowlist: `PATH`, fixed `LANG`, optional `LC_ALL`, `HTTP_PROXY`,
|
||||
`HTTPS_PROXY`, `NO_PROXY`, `SSL_CERT_FILE`, `SSL_CERT_DIR` and
|
||||
`HTTPS_PROXY`, `NO_PROXY`, their lowercase equivalents, `SSL_CERT_FILE`, `SSL_CERT_DIR` and
|
||||
`NODE_EXTRA_CA_CERTS`. The CLI enables Node's environment proxy handling before
|
||||
the helper starts. Provider keys, `HOME`, npm configuration, `NODE_OPTIONS`,
|
||||
`NODE_PATH` and TLS-validation bypass remain excluded. These settings apply to
|
||||
@@ -150,6 +150,25 @@ failure; the exact correction delta and preserved negative tests then pass.
|
||||
Local missing-Commander and incomplete Product source-alias setup failures are
|
||||
retained separately; no repository test configuration changes occur.
|
||||
|
||||
Fresh review at `5120ddc8f` identifies omitted lowercase proxies. The scoped
|
||||
follow-up at #14922 `b7e692f48` preserves `http_proxy`, `https_proxy` and
|
||||
`no_proxy` through the CLI, provisioner and npm-download allowlists. All seven
|
||||
CLI tests, eight SDK fixtures and two bundled checks pass there. Additive merges
|
||||
preserve the later prerequisites' distinct runtime/build pins; their bundled
|
||||
checks also pass. The overly broad whole-materializer equality guard remains a
|
||||
local verification failure; exact six-file correction deltas pass afterward.
|
||||
|
||||
A separate loopback-only calibration at #14924 `c2e2e39db` exercises the actual
|
||||
bundled CLI and generated provisioner. Without its owned custom CA, the TLS
|
||||
certificate is rejected before any archive request. With that CA, lowercase
|
||||
proxies carry exactly one GET for the pinned Node archive; the owned endpoint
|
||||
returns a deliberate 503 before any package is installed. Both children retire,
|
||||
setup staging/locks disappear, the owned proxy/certificate fixture is removed
|
||||
and no outside networking, real credentials or model calls occur. This is a
|
||||
limited setup-boundary calibration with a private egress-denial prefix, not a
|
||||
normal public artifact/install proof. The two earlier private calibration
|
||||
startup failures remain retained after their shebang/guard repairs.
|
||||
|
||||
Normal updated CLI/server tar installation and setup on ARM Mac, Intel Mac and
|
||||
Linux remain required before releasing these installer bytes. Original
|
||||
`0bd040093` artifacts and all 33 paid grades retain their exact source labels.
|
||||
|
||||
@@ -174,7 +174,7 @@ export async function materializePiDistribution({ outputRoot, nodeExecutable, np
|
||||
const buildHome = join(staging, "build-home"); await mkdir(buildHome);
|
||||
// Do not inherit NPM_TOKEN, npm_config_*, NODE_OPTIONS, provider keys or user
|
||||
// .npmrc. Public registry downloads need no private application credential.
|
||||
const environment = Object.fromEntries(["PATH", "LANG", "LC_ALL", "HTTPS_PROXY", "HTTP_PROXY", "NO_PROXY", "SSL_CERT_FILE", "SSL_CERT_DIR", "NODE_EXTRA_CA_CERTS"].flatMap((key) => typeof process.env[key] === "string" ? [[key, process.env[key]]] : []));
|
||||
const environment = Object.fromEntries(["PATH", "LANG", "LC_ALL", "HTTPS_PROXY", "HTTP_PROXY", "NO_PROXY", "https_proxy", "http_proxy", "no_proxy", "SSL_CERT_FILE", "SSL_CERT_DIR", "NODE_EXTRA_CA_CERTS"].flatMap((key) => typeof process.env[key] === "string" ? [[key, process.env[key]]] : []));
|
||||
environment.HOME = buildHome;
|
||||
// npm 10 prunes non-host packages bundled by upstream Pi, unlike the npm
|
||||
// 11.19.0 used to qualify this complete closure. Public setup must use the
|
||||
|
||||
@@ -34,14 +34,14 @@ test("public server tar layout carries a self-contained host provisioner and exa
|
||||
const installedPackage = join(installed, "package"); const entry = join(installedPackage, "dist/vendor/paperclip-runner/cli/provision-pi.cjs");
|
||||
const api = createRequire(import.meta.url)(entry);
|
||||
assert.equal((await api.provisionPackageRoot(entry)).root, installedPackage);
|
||||
const network = { PATH: "/usr/bin:/bin", HTTPS_PROXY: "http://proxy.example:8080", HTTP_PROXY: "http://proxy.example:8080", NO_PROXY: "localhost", SSL_CERT_FILE: "/public/ca.pem", SSL_CERT_DIR: "/public/certs", NODE_EXTRA_CA_CERTS: "/public/extra-ca.pem" };
|
||||
const network = { PATH: "/usr/bin:/bin", HTTPS_PROXY: "http://proxy.example:8080", HTTP_PROXY: "http://proxy.example:8080", NO_PROXY: "localhost", http_proxy: "http://127.0.0.1:9", https_proxy: "http://127.0.0.1:9", no_proxy: "localhost", SSL_CERT_FILE: "/public/ca.pem", SSL_CERT_DIR: "/public/certs", NODE_EXTRA_CA_CERTS: "/public/extra-ca.pem" };
|
||||
assert.deepEqual(api.provisionEnvironment({ ...network, HOME: "/private/home", OPENROUTER_API_KEY: "sensitive-canary", NPM_TOKEN: "sensitive-canary", NODE_OPTIONS: "--require /foreign.js", NODE_PATH: "/foreign", NODE_TLS_REJECT_UNAUTHORIZED: "0" }), { ...network, LANG: "C.UTF-8" });
|
||||
// Real, unmocked installation verifier rejects a corrupt cache before any
|
||||
// download/process. The positive full-closure proof uses real platform packs.
|
||||
await mkdir(join(installedPackage, "provider-assets/pi", `${process.platform}-${process.arch}`, "runtime"), { recursive: true });
|
||||
const deny = join(root, "deny.cjs");
|
||||
await writeFile(deny, `process.nextTick(()=>{const assert=require('node:assert/strict');assert.equal(process.env.HTTP_PROXY,'http://127.0.0.1:9');assert.equal(process.env.HTTPS_PROXY,'http://127.0.0.1:9');assert.equal(process.env.NO_PROXY,'localhost');assert.equal(process.env.NODE_EXTRA_CA_CERTS,'/dev/null');for(const key of ['OPENROUTER_API_KEY','NPM_TOKEN','HOME','NODE_OPTIONS','NODE_PATH','NODE_TLS_REJECT_UNAUTHORIZED'])assert.equal(process.env[key],undefined,key);});const fail=()=>{throw Error('UNEXPECTED_NETWORK_OR_CHILD')}; globalThis.fetch=fail; for(const m of ['node:net','node:tls','node:http','node:https']){const x=require(m);for(const k of ['connect','createConnection','request','get'])if(k in x)x[k]=fail;}const c=require('node:child_process');for(const k of ['spawn','execFile','exec'])c[k]=fail;`);
|
||||
const result = spawnSync(process.execPath, ["--require", deny, entry], { encoding: "utf8", env: { PATH: "/usr/bin:/bin", HTTP_PROXY: "http://127.0.0.1:9", HTTPS_PROXY: "http://127.0.0.1:9", NO_PROXY: "localhost", NODE_EXTRA_CA_CERTS: "/dev/null", OPENROUTER_API_KEY: "sensitive-canary", NPM_TOKEN: "sensitive-canary", HOME: "/private/home", NODE_OPTIONS: "", NODE_PATH: "/foreign", NODE_TLS_REJECT_UNAUTHORIZED: "0" }, timeout: 10_000 });
|
||||
await writeFile(deny, `process.nextTick(()=>{const assert=require('node:assert/strict');assert.equal(process.env.HTTP_PROXY,'http://127.0.0.1:9');assert.equal(process.env.HTTPS_PROXY,'http://127.0.0.1:9');assert.equal(process.env.NO_PROXY,'localhost');assert.equal(process.env.http_proxy,'http://127.0.0.1:9');assert.equal(process.env.https_proxy,'http://127.0.0.1:9');assert.equal(process.env.no_proxy,'localhost');assert.equal(process.env.NODE_EXTRA_CA_CERTS,'/dev/null');for(const key of ['OPENROUTER_API_KEY','NPM_TOKEN','HOME','NODE_OPTIONS','NODE_PATH','NODE_TLS_REJECT_UNAUTHORIZED'])assert.equal(process.env[key],undefined,key);});const fail=()=>{throw Error('UNEXPECTED_NETWORK_OR_CHILD')}; globalThis.fetch=fail; for(const m of ['node:net','node:tls','node:http','node:https']){const x=require(m);for(const k of ['connect','createConnection','request','get'])if(k in x)x[k]=fail;}const c=require('node:child_process');for(const k of ['spawn','execFile','exec'])c[k]=fail;`);
|
||||
const result = spawnSync(process.execPath, ["--require", deny, entry], { encoding: "utf8", env: { PATH: "/usr/bin:/bin", HTTP_PROXY: "http://127.0.0.1:9", HTTPS_PROXY: "http://127.0.0.1:9", NO_PROXY: "localhost", http_proxy: "http://127.0.0.1:9", https_proxy: "http://127.0.0.1:9", no_proxy: "localhost", NODE_EXTRA_CA_CERTS: "/dev/null", OPENROUTER_API_KEY: "sensitive-canary", NPM_TOKEN: "sensitive-canary", HOME: "/private/home", NODE_OPTIONS: "", NODE_PATH: "/foreign", NODE_TLS_REJECT_UNAUTHORIZED: "0" }, timeout: 10_000 });
|
||||
assert.ifError(result.error); assert.equal(result.status, 1); assert.match(result.stderr, /Pi setup failed/);
|
||||
assert.doesNotMatch(result.stderr, /UNEXPECTED_NETWORK_OR_CHILD|sensitive-canary/);
|
||||
assert.deepEqual(await readdir(join(installedPackage, "provider-assets/pi")), [`${process.platform}-${process.arch}`]);
|
||||
@@ -60,7 +60,7 @@ test("explicit CLI setup passes only network settings to its real child and enab
|
||||
assert.deepEqual(process.execArgv,['--use-env-proxy']);
|
||||
assert.equal(process.env.HTTP_PROXY,'http://127.0.0.1:9');
|
||||
assert.equal(process.env.HTTPS_PROXY,'http://127.0.0.1:9');
|
||||
assert.equal(process.env.NO_PROXY,'localhost');
|
||||
assert.equal(process.env.NO_PROXY,'localhost');assert.equal(process.env.http_proxy,'http://127.0.0.1:9');assert.equal(process.env.https_proxy,'http://127.0.0.1:9');assert.equal(process.env.no_proxy,'localhost');
|
||||
assert.equal(process.env.SSL_CERT_FILE,'/public/ca.pem');
|
||||
assert.equal(process.env.SSL_CERT_DIR,'/public/certs');
|
||||
assert.equal(process.env.NODE_EXTRA_CA_CERTS,'/dev/null');
|
||||
@@ -69,7 +69,7 @@ test("explicit CLI setup passes only network settings to its real child and enab
|
||||
const modulePath = join(root, "runtime.mjs");
|
||||
await build({ entryPoints: [resolve(dirname(new URL(import.meta.url).pathname), "../../../cli/src/commands/runtime.ts")], outfile: modulePath, bundle: true, platform: "node", format: "esm", target: "node24", logLevel: "silent" });
|
||||
const result = spawnSync(process.execPath, ["--input-type=module", "-e", "const runtime=await import(process.argv[1]);await runtime.setupPiRuntime();", modulePath], {
|
||||
encoding: "utf8", timeout: 10_000, env: { PATH: "/usr/bin:/bin", HTTP_PROXY: "http://127.0.0.1:9", HTTPS_PROXY: "http://127.0.0.1:9", NO_PROXY: "localhost", SSL_CERT_FILE: "/public/ca.pem", SSL_CERT_DIR: "/public/certs", NODE_EXTRA_CA_CERTS: "/dev/null", OPENROUTER_API_KEY: "sensitive-canary", NPM_TOKEN: "sensitive-canary", HOME: "/private/home", NODE_PATH: "/foreign", NODE_TLS_REJECT_UNAUTHORIZED: "0" },
|
||||
encoding: "utf8", timeout: 10_000, env: { PATH: "/usr/bin:/bin", HTTP_PROXY: "http://127.0.0.1:9", HTTPS_PROXY: "http://127.0.0.1:9", NO_PROXY: "localhost", http_proxy: "http://127.0.0.1:9", https_proxy: "http://127.0.0.1:9", no_proxy: "localhost", SSL_CERT_FILE: "/public/ca.pem", SSL_CERT_DIR: "/public/certs", NODE_EXTRA_CA_CERTS: "/dev/null", OPENROUTER_API_KEY: "sensitive-canary", NPM_TOKEN: "sensitive-canary", HOME: "/private/home", NODE_PATH: "/foreign", NODE_TLS_REJECT_UNAUTHORIZED: "0" },
|
||||
});
|
||||
assert.ifError(result.error); assert.equal(result.status, 0, result.stderr);
|
||||
assert.match(result.stdout, /SETUP_NETWORK_BOUNDARY_PASS/);
|
||||
|
||||
@@ -11,7 +11,7 @@ import { PI_DISTRIBUTION_CLOSURE_SHA256 } from "../src/drivers/acpx/pi-closure-p
|
||||
|
||||
export function provisionEnvironment(source = process.env) {
|
||||
const environment = { PATH: source.PATH ?? "/usr/bin:/bin", LANG: "C.UTF-8" };
|
||||
for (const key of ["LC_ALL", "HTTPS_PROXY", "HTTP_PROXY", "NO_PROXY", "SSL_CERT_FILE", "SSL_CERT_DIR", "NODE_EXTRA_CA_CERTS"]) {
|
||||
for (const key of ["LC_ALL", "HTTPS_PROXY", "HTTP_PROXY", "NO_PROXY", "https_proxy", "http_proxy", "no_proxy", "SSL_CERT_FILE", "SSL_CERT_DIR", "NODE_EXTRA_CA_CERTS"]) {
|
||||
if (typeof source[key] === "string") environment[key] = source[key];
|
||||
}
|
||||
return environment;
|
||||
|
||||
Reference in new issue
Block a user