@peakflo/agent-harness 0.2.12 → 0.2.13-rc.1

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.
@@ -0,0 +1,73 @@
1
+ /**
2
+ * PRIVATE CREDENTIAL HANDOFF FROM pf-bootstrap — never through the environment.
3
+ *
4
+ * THE PROBLEM. pf-bootstrap used to hand the enrolment token (cold boot) and
5
+ * the LIVE runner credential (warm in-place upgrade) to the harness in
6
+ * PF_ENROLMENT_TOKEN / PF_RUNNER_CREDENTIAL. Deleting them from `process.env`
7
+ * afterwards (see `scrubCredentialEnv`) keeps them out of child environments,
8
+ * but `/proc/<pid>/environ` is the execve SNAPSHOT and keeps them for the
9
+ * life of the process. Pi and every agent subprocess run as the same uid as
10
+ * the harness, and the kernel lets a same-uid process read another's environ.
11
+ * Confirmed on staging: an agent could read the live runner credential.
12
+ *
13
+ * THE FIX. pf-bootstrap already spawns the harness with a private IPC channel
14
+ * (`stdio: [..., 'ipc']`, a socketpair only the two processes hold). It now
15
+ * sets the NON-secret marker `PF_CREDENTIAL_HANDOFF=ipc` and puts no secret in
16
+ * the environment at all. At startup the harness sends ONE request on that
17
+ * channel and pf-bootstrap answers ONCE with the credentials. They live only
18
+ * in this process's heap from then on.
19
+ *
20
+ * Request/response rather than "bootstrap pushes on spawn": a message that
21
+ * arrives before a listener exists is dropped, and asking first also means
22
+ * pf-bootstrap never writes a secret to a channel nobody is reading.
23
+ *
24
+ * COMPATIBILITY. Without the marker (an older pf-bootstrap image, a
25
+ * self-hosted runner, the desktop) nothing here runs and the environment path
26
+ * works exactly as before, followed by the same scrub. pf-bootstrap only sets
27
+ * the marker for a harness whose package.json declares
28
+ * `peakflo.credentialHandoff: "ipc"` (see package.json), so an older harness
29
+ * installed by a newer image still gets its token the old way.
30
+ */
31
+ /** The non-secret marker pf-bootstrap sets when it will answer over IPC. */
32
+ export declare const CREDENTIAL_HANDOFF_ENV = "PF_CREDENTIAL_HANDOFF";
33
+ export declare const CREDENTIAL_HANDOFF_IPC = "ipc";
34
+ /** harness -> bootstrap. */
35
+ export declare const CREDENTIAL_REQUEST_TYPE = "pf-harness-credentials-request";
36
+ /** bootstrap -> harness. */
37
+ export declare const CREDENTIAL_RESPONSE_TYPE = "pf-bootstrap-credentials";
38
+ export declare const CREDENTIAL_HANDOFF_TIMEOUT_MS = 15000;
39
+ /** The variables that may carry a credential. Never left in `process.env`. */
40
+ export declare const CREDENTIAL_ENV_KEYS: readonly ["PF_ENROLMENT_TOKEN", "PF_RUNNER_CREDENTIAL", "PF_RUNNER_ID", "PF_RUNNER_TENANT_ID", "PF_RUNNER_CREDENTIAL_EXPIRES_AT"];
41
+ export interface BootstrapCredentials {
42
+ /** Cold boot: the enrolment token. Warm upgrade: the live runner credential. */
43
+ enrolmentToken: string;
44
+ runnerId?: string;
45
+ runnerCredential?: string;
46
+ tenantId?: string | null;
47
+ credentialExpiresAt?: string | null;
48
+ }
49
+ /** The slice of `process` this needs, so tests can pass a fake. */
50
+ export interface HandoffProcess {
51
+ send?: (message: unknown) => boolean;
52
+ on(event: 'message', listener: (message: unknown) => void): unknown;
53
+ off(event: 'message', listener: (message: unknown) => void): unknown;
54
+ }
55
+ export declare class CredentialHandoffError extends Error {
56
+ constructor(message: string);
57
+ }
58
+ /** Remove every credential-bearing variable. Idempotent. */
59
+ export declare function scrubCredentialEnv(env: NodeJS.ProcessEnv): void;
60
+ /**
61
+ * Ask pf-bootstrap for the credentials over the private IPC channel.
62
+ *
63
+ * Returns null — and does nothing — when the marker is absent: the caller then
64
+ * uses the environment exactly as before. The marker itself is removed either
65
+ * way, so agent subprocesses do not inherit it.
66
+ */
67
+ export declare function receiveBootstrapCredentials(proc: HandoffProcess, env: NodeJS.ProcessEnv, timeoutMs?: number): Promise<BootstrapCredentials | null>;
68
+ /**
69
+ * The credentials as the environment-shaped variables `parseOptions` and
70
+ * `takeHandedOffCredential` already understand. The result is merged into a
71
+ * PLAIN OBJECT copy of the environment, never into `process.env`.
72
+ */
73
+ export declare function credentialsAsEnv(credentials: BootstrapCredentials): NodeJS.ProcessEnv;
@@ -0,0 +1,153 @@
1
+ "use strict";
2
+ /**
3
+ * PRIVATE CREDENTIAL HANDOFF FROM pf-bootstrap — never through the environment.
4
+ *
5
+ * THE PROBLEM. pf-bootstrap used to hand the enrolment token (cold boot) and
6
+ * the LIVE runner credential (warm in-place upgrade) to the harness in
7
+ * PF_ENROLMENT_TOKEN / PF_RUNNER_CREDENTIAL. Deleting them from `process.env`
8
+ * afterwards (see `scrubCredentialEnv`) keeps them out of child environments,
9
+ * but `/proc/<pid>/environ` is the execve SNAPSHOT and keeps them for the
10
+ * life of the process. Pi and every agent subprocess run as the same uid as
11
+ * the harness, and the kernel lets a same-uid process read another's environ.
12
+ * Confirmed on staging: an agent could read the live runner credential.
13
+ *
14
+ * THE FIX. pf-bootstrap already spawns the harness with a private IPC channel
15
+ * (`stdio: [..., 'ipc']`, a socketpair only the two processes hold). It now
16
+ * sets the NON-secret marker `PF_CREDENTIAL_HANDOFF=ipc` and puts no secret in
17
+ * the environment at all. At startup the harness sends ONE request on that
18
+ * channel and pf-bootstrap answers ONCE with the credentials. They live only
19
+ * in this process's heap from then on.
20
+ *
21
+ * Request/response rather than "bootstrap pushes on spawn": a message that
22
+ * arrives before a listener exists is dropped, and asking first also means
23
+ * pf-bootstrap never writes a secret to a channel nobody is reading.
24
+ *
25
+ * COMPATIBILITY. Without the marker (an older pf-bootstrap image, a
26
+ * self-hosted runner, the desktop) nothing here runs and the environment path
27
+ * works exactly as before, followed by the same scrub. pf-bootstrap only sets
28
+ * the marker for a harness whose package.json declares
29
+ * `peakflo.credentialHandoff: "ipc"` (see package.json), so an older harness
30
+ * installed by a newer image still gets its token the old way.
31
+ */
32
+ Object.defineProperty(exports, "__esModule", { value: true });
33
+ exports.CredentialHandoffError = exports.CREDENTIAL_ENV_KEYS = exports.CREDENTIAL_HANDOFF_TIMEOUT_MS = exports.CREDENTIAL_RESPONSE_TYPE = exports.CREDENTIAL_REQUEST_TYPE = exports.CREDENTIAL_HANDOFF_IPC = exports.CREDENTIAL_HANDOFF_ENV = void 0;
34
+ exports.scrubCredentialEnv = scrubCredentialEnv;
35
+ exports.receiveBootstrapCredentials = receiveBootstrapCredentials;
36
+ exports.credentialsAsEnv = credentialsAsEnv;
37
+ /** The non-secret marker pf-bootstrap sets when it will answer over IPC. */
38
+ exports.CREDENTIAL_HANDOFF_ENV = 'PF_CREDENTIAL_HANDOFF';
39
+ exports.CREDENTIAL_HANDOFF_IPC = 'ipc';
40
+ /** harness -> bootstrap. */
41
+ exports.CREDENTIAL_REQUEST_TYPE = 'pf-harness-credentials-request';
42
+ /** bootstrap -> harness. */
43
+ exports.CREDENTIAL_RESPONSE_TYPE = 'pf-bootstrap-credentials';
44
+ exports.CREDENTIAL_HANDOFF_TIMEOUT_MS = 15_000;
45
+ /** The variables that may carry a credential. Never left in `process.env`. */
46
+ exports.CREDENTIAL_ENV_KEYS = [
47
+ 'PF_ENROLMENT_TOKEN',
48
+ 'PF_RUNNER_CREDENTIAL',
49
+ 'PF_RUNNER_ID',
50
+ 'PF_RUNNER_TENANT_ID',
51
+ 'PF_RUNNER_CREDENTIAL_EXPIRES_AT'
52
+ ];
53
+ class CredentialHandoffError extends Error {
54
+ constructor(message) {
55
+ super(message);
56
+ this.name = 'CredentialHandoffError';
57
+ }
58
+ }
59
+ exports.CredentialHandoffError = CredentialHandoffError;
60
+ /** Remove every credential-bearing variable. Idempotent. */
61
+ function scrubCredentialEnv(env) {
62
+ for (const key of exports.CREDENTIAL_ENV_KEYS) {
63
+ delete env[key];
64
+ }
65
+ }
66
+ const nonEmpty = (value) => typeof value === 'string' && value.length > 0;
67
+ function parseResponse(message) {
68
+ if (typeof message !== 'object' || message === null)
69
+ return null;
70
+ const m = message;
71
+ if (m.type !== exports.CREDENTIAL_RESPONSE_TYPE || !nonEmpty(m.enrolmentToken)) {
72
+ return null;
73
+ }
74
+ const credentials = {
75
+ enrolmentToken: m.enrolmentToken
76
+ };
77
+ // A runner id without its credential (or the reverse) is not a handoff.
78
+ if (nonEmpty(m.runnerId) && nonEmpty(m.runnerCredential)) {
79
+ credentials.runnerId = m.runnerId;
80
+ credentials.runnerCredential = m.runnerCredential;
81
+ credentials.tenantId = nonEmpty(m.tenantId) ? m.tenantId : null;
82
+ credentials.credentialExpiresAt = nonEmpty(m.credentialExpiresAt)
83
+ ? m.credentialExpiresAt
84
+ : null;
85
+ }
86
+ return credentials;
87
+ }
88
+ /**
89
+ * Ask pf-bootstrap for the credentials over the private IPC channel.
90
+ *
91
+ * Returns null — and does nothing — when the marker is absent: the caller then
92
+ * uses the environment exactly as before. The marker itself is removed either
93
+ * way, so agent subprocesses do not inherit it.
94
+ */
95
+ async function receiveBootstrapCredentials(proc, env, timeoutMs = exports.CREDENTIAL_HANDOFF_TIMEOUT_MS) {
96
+ const mode = env[exports.CREDENTIAL_HANDOFF_ENV];
97
+ delete env[exports.CREDENTIAL_HANDOFF_ENV];
98
+ if (mode === undefined || mode === '')
99
+ return null;
100
+ if (mode !== exports.CREDENTIAL_HANDOFF_IPC) {
101
+ throw new CredentialHandoffError(`Unsupported ${exports.CREDENTIAL_HANDOFF_ENV} '${mode}'. Expected '${exports.CREDENTIAL_HANDOFF_IPC}'.`);
102
+ }
103
+ const send = proc.send;
104
+ if (typeof send !== 'function') {
105
+ throw new CredentialHandoffError(`${exports.CREDENTIAL_HANDOFF_ENV}=ipc is set but this process has no IPC channel`);
106
+ }
107
+ return new Promise((resolve, reject) => {
108
+ let timer;
109
+ const finish = (error, value) => {
110
+ proc.off('message', onMessage);
111
+ if (timer)
112
+ clearTimeout(timer);
113
+ if (error)
114
+ reject(error);
115
+ else
116
+ resolve(value);
117
+ };
118
+ const onMessage = (message) => {
119
+ const credentials = parseResponse(message);
120
+ if (credentials)
121
+ finish(null, credentials);
122
+ };
123
+ // Listen BEFORE asking, so the answer cannot arrive unheard.
124
+ proc.on('message', onMessage);
125
+ timer = setTimeout(() => finish(new CredentialHandoffError(`pf-bootstrap did not hand over the credentials within ${timeoutMs} ms`)), timeoutMs);
126
+ try {
127
+ send.call(proc, { type: exports.CREDENTIAL_REQUEST_TYPE });
128
+ }
129
+ catch (error) {
130
+ finish(new CredentialHandoffError(`Could not ask pf-bootstrap for the credentials: ${error instanceof Error ? error.message : String(error)}`));
131
+ }
132
+ });
133
+ }
134
+ /**
135
+ * The credentials as the environment-shaped variables `parseOptions` and
136
+ * `takeHandedOffCredential` already understand. The result is merged into a
137
+ * PLAIN OBJECT copy of the environment, never into `process.env`.
138
+ */
139
+ function credentialsAsEnv(credentials) {
140
+ const env = {
141
+ PF_ENROLMENT_TOKEN: credentials.enrolmentToken
142
+ };
143
+ if (credentials.runnerId && credentials.runnerCredential) {
144
+ env.PF_RUNNER_ID = credentials.runnerId;
145
+ env.PF_RUNNER_CREDENTIAL = credentials.runnerCredential;
146
+ if (credentials.tenantId)
147
+ env.PF_RUNNER_TENANT_ID = credentials.tenantId;
148
+ if (credentials.credentialExpiresAt) {
149
+ env.PF_RUNNER_CREDENTIAL_EXPIRES_AT = credentials.credentialExpiresAt;
150
+ }
151
+ }
152
+ return env;
153
+ }
package/dist/cli.js CHANGED
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  })();
36
36
  Object.defineProperty(exports, "__esModule", { value: true });
37
37
  const adapter_resolver_1 = require("./adapter-resolver");
38
+ const bootstrap_handoff_1 = require("./bootstrap-handoff");
38
39
  const contract_1 = require("./contract");
39
40
  const credential_store_1 = require("./credential-store");
40
41
  const harness_1 = require("./harness");
@@ -97,8 +98,8 @@ Options:
97
98
  Needs Node.js 22.19 or newer; start stops with one line on an older one.
98
99
  PF_ALLOW_UNSUPPORTED_NODE=1 starts anyway, at your own risk.
99
100
 
100
- In a managed sandbox pf-bootstrap supplies these through the environment, so
101
- the enrolment token never appears in /proc/<pid>/cmdline.
101
+ In a managed sandbox pf-bootstrap hands the token over a private IPC channel,
102
+ so it never appears in /proc/<pid>/cmdline or /proc/<pid>/environ.
102
103
  `;
103
104
  const serviceStart = (0, service_1.isServiceStart)(process.argv.slice(2));
104
105
  function stopPermanentServiceFailure(reason, linuxCode) {
@@ -224,8 +225,22 @@ async function main() {
224
225
  */
225
226
  const startArgs = argv.slice(1);
226
227
  const wantsLogin = startArgs.includes('--login');
228
+ /*
229
+ * CREDENTIALS NEVER STAY IN process.env. A current pf-bootstrap hands them
230
+ * over its private IPC channel (bootstrap-handoff.ts); an older one, a
231
+ * self-hosted install or a developer still uses the environment. Either way
232
+ * they are merged into `startEnv`, a PLAIN OBJECT copy that lives only in
233
+ * this heap, and every credential variable is scrubbed from process.env
234
+ * before anything else runs, so no child can inherit one.
235
+ */
236
+ const handoff = await (0, bootstrap_handoff_1.receiveBootstrapCredentials)(process, process.env);
237
+ const startEnv = {
238
+ ...process.env,
239
+ ...(handoff ? (0, bootstrap_handoff_1.credentialsAsEnv)(handoff) : {})
240
+ };
241
+ (0, bootstrap_handoff_1.scrubCredentialEnv)(process.env);
227
242
  // `--token=x` counts as a token, hence readOptionValue rather than indexOf.
228
- const hasToken = Boolean(readOptionValue(startArgs, 'token') ?? process.env.PF_ENROLMENT_TOKEN);
243
+ const hasToken = Boolean(readOptionValue(startArgs, 'token') ?? startEnv.PF_ENROLMENT_TOKEN);
229
244
  const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
230
245
  if (!serviceStart && !hasToken && (wantsLogin || interactive)) {
231
246
  const { enrolViaBrowser } = await Promise.resolve().then(() => __importStar(require('./enrol/browser-enrol')));
@@ -239,17 +254,17 @@ async function main() {
239
254
  appUrl: readOptionValue(startArgs, 'app-url') ?? process.env.PF_APP_URL,
240
255
  label: readOptionValue(startArgs, 'label') ?? process.env.PF_RUNNER_LABEL
241
256
  });
242
- // Handed on through the environment rather than argv, for the same reason
243
- // pf-bootstrap does: argv is world-readable via /proc/<pid>/cmdline.
244
- process.env.PF_ENROLMENT_TOKEN = token;
257
+ // Handed on in memory rather than argv: argv is world-readable via
258
+ // /proc/<pid>/cmdline. Never written back to process.env.
259
+ startEnv.PF_ENROLMENT_TOKEN = token;
245
260
  }
246
- const options = (0, options_1.parseOptions)(startArgs, process.env);
261
+ const options = (0, options_1.parseOptions)(startArgs, startEnv);
247
262
  new service_1.ServiceManager({
248
263
  nodePath: process.execPath,
249
264
  cliPath: __filename,
250
265
  env: process.env
251
266
  }).assertNoDuplicateRunner(options);
252
- const handedOffCredential = (0, credential_store_1.takeHandedOffCredential)(process.env);
267
+ const handedOffCredential = (0, credential_store_1.takeHandedOffCredential)(startEnv);
253
268
  const resolveAdapter = (0, adapter_resolver_1.createAdapterResolver)(options.runtime, options.workspaceRoot);
254
269
  const harness = new harness_1.Harness(options, {
255
270
  getAdapter: (backend, ports) => resolveAdapter(backend, ports),
@@ -81,8 +81,9 @@ export declare class MemoryCredentialStore implements CredentialStore {
81
81
  clear(): void;
82
82
  }
83
83
  /**
84
- * Take the private bootstrap handoff and remove every credential-bearing
85
- * variable before an adapter can copy the process environment to a child.
84
+ * Take the bootstrap handoff out of an environment-shaped object and remove
85
+ * every credential-bearing variable from it, before an adapter can copy the
86
+ * process environment to a child.
86
87
  */
87
88
  export declare function takeHandedOffCredential(env: NodeJS.ProcessEnv): StoredCredential | null;
88
89
  /**
@@ -39,6 +39,7 @@ exports.takeHandedOffCredential = takeHandedOffCredential;
39
39
  exports.createCredentialStore = createCredentialStore;
40
40
  const node_fs_1 = require("node:fs");
41
41
  const node_path_1 = require("node:path");
42
+ const bootstrap_handoff_1 = require("./bootstrap-handoff");
42
43
  /** The file name under the workspace root. */
43
44
  exports.CREDENTIAL_FILE_NAME = 'runner-credential.json';
44
45
  /**
@@ -80,8 +81,9 @@ class MemoryCredentialStore {
80
81
  }
81
82
  exports.MemoryCredentialStore = MemoryCredentialStore;
82
83
  /**
83
- * Take the private bootstrap handoff and remove every credential-bearing
84
- * variable before an adapter can copy the process environment to a child.
84
+ * Take the bootstrap handoff out of an environment-shaped object and remove
85
+ * every credential-bearing variable from it, before an adapter can copy the
86
+ * process environment to a child.
85
87
  */
86
88
  function takeHandedOffCredential(env) {
87
89
  const runnerId = env.PF_RUNNER_ID;
@@ -98,11 +100,9 @@ function takeHandedOffCredential(env) {
98
100
  // On warm upgrade PF_ENROLMENT_TOKEN contains the live runner credential.
99
101
  // On cold boot it contains a single-use enrolment token. Neither belongs in
100
102
  // an agent subprocess environment after parseOptions() has consumed it.
101
- delete env.PF_ENROLMENT_TOKEN;
102
- delete env.PF_RUNNER_CREDENTIAL;
103
- delete env.PF_RUNNER_ID;
104
- delete env.PF_RUNNER_TENANT_ID;
105
- delete env.PF_RUNNER_CREDENTIAL_EXPIRES_AT;
103
+ // Scrubbing process.env does NOT clear /proc/<pid>/environ, which is why a
104
+ // current pf-bootstrap hands these over IPC instead (bootstrap-handoff.ts).
105
+ (0, bootstrap_handoff_1.scrubCredentialEnv)(env);
106
106
  return stored;
107
107
  }
108
108
  function isStoredCredential(value) {
package/dist/options.d.ts CHANGED
@@ -7,9 +7,10 @@ export declare class OptionsError extends Error {
7
7
  *
8
8
  * NOTE ON THE ENROLMENT TOKEN: it is accepted on the command line for local
9
9
  * development and self-hosted installs, which is what the design calls for.
10
- * In a managed sandbox pf-bootstrap passes it through PF_ENROLMENT_TOKEN
11
- * instead, so it never appears in the process arguments — /proc/<pid>/cmdline
12
- * is world-readable, while /proc/<pid>/environ is readable only by the
13
- * process owner.
10
+ * In a managed sandbox pf-bootstrap hands it over its private IPC channel
11
+ * (bootstrap-handoff.ts) and cli.ts passes it here in a plain in-memory copy
12
+ * of the environment, so it never appears in /proc/<pid>/cmdline (world-
13
+ * readable) or /proc/<pid>/environ (readable by the same uid, which Pi
14
+ * shares). An older pf-bootstrap still sets PF_ENROLMENT_TOKEN itself.
14
15
  */
15
16
  export declare function parseOptions(argv: string[], env: NodeJS.ProcessEnv): HarnessOptions;
package/dist/options.js CHANGED
@@ -182,10 +182,11 @@ function parseSwitch(raw, fallback, name) {
182
182
  *
183
183
  * NOTE ON THE ENROLMENT TOKEN: it is accepted on the command line for local
184
184
  * development and self-hosted installs, which is what the design calls for.
185
- * In a managed sandbox pf-bootstrap passes it through PF_ENROLMENT_TOKEN
186
- * instead, so it never appears in the process arguments — /proc/<pid>/cmdline
187
- * is world-readable, while /proc/<pid>/environ is readable only by the
188
- * process owner.
185
+ * In a managed sandbox pf-bootstrap hands it over its private IPC channel
186
+ * (bootstrap-handoff.ts) and cli.ts passes it here in a plain in-memory copy
187
+ * of the environment, so it never appears in /proc/<pid>/cmdline (world-
188
+ * readable) or /proc/<pid>/environ (readable by the same uid, which Pi
189
+ * shares). An older pf-bootstrap still sets PF_ENROLMENT_TOKEN itself.
189
190
  */
190
191
  function parseOptions(argv, env) {
191
192
  const controlPlaneUrl = readFlag(argv, 'control-plane') ?? env.PF_CONTROL_PLANE_URL;
package/dist/types.d.ts CHANGED
@@ -27,11 +27,15 @@
27
27
  * can use. `POST /api/coding-agents/sessions/:id/ingest` in particular is
28
28
  * identical for all three; that is the whole payoff of the extraction.
29
29
  *
30
- * 4. TOKENS TRAVEL IN THE ENVIRONMENT OR IN HEADERS, NEVER IN ARGV.
31
- * `/proc/<pid>/cmdline` is world-readable; `/proc/<pid>/environ` is readable
32
- * only by the process owner. Every credential below is either an
33
- * `Authorization: Bearer` header or an environment variable handed to a
34
- * child process. Do not "improve" this by adding a `--token` flag.
30
+ * 4. TOKENS TRAVEL IN HEADERS OR IN MEMORY, NEVER IN ARGV.
31
+ * `/proc/<pid>/cmdline` is world-readable. `/proc/<pid>/environ` is
32
+ * readable by every process of the same uid — and in a sandbox Pi shares
33
+ * the harness's uid — and keeps the execve snapshot even after
34
+ * `delete process.env.X`. So the RUNNER's own credentials reach a managed
35
+ * sandbox harness over pf-bootstrap's private IPC channel
36
+ * (bootstrap-handoff.ts), not the environment; the environment remains
37
+ * only for a self-hosted or desktop start and an older pf-bootstrap.
38
+ * Do not "improve" this by adding a `--token` flag.
35
39
  */
36
40
  export interface HarnessOptions {
37
41
  /** workflow-api base url, e.g. https://stage-api.peakflo.ai */
package/dist/types.js CHANGED
@@ -28,11 +28,15 @@
28
28
  * can use. `POST /api/coding-agents/sessions/:id/ingest` in particular is
29
29
  * identical for all three; that is the whole payoff of the extraction.
30
30
  *
31
- * 4. TOKENS TRAVEL IN THE ENVIRONMENT OR IN HEADERS, NEVER IN ARGV.
32
- * `/proc/<pid>/cmdline` is world-readable; `/proc/<pid>/environ` is readable
33
- * only by the process owner. Every credential below is either an
34
- * `Authorization: Bearer` header or an environment variable handed to a
35
- * child process. Do not "improve" this by adding a `--token` flag.
31
+ * 4. TOKENS TRAVEL IN HEADERS OR IN MEMORY, NEVER IN ARGV.
32
+ * `/proc/<pid>/cmdline` is world-readable. `/proc/<pid>/environ` is
33
+ * readable by every process of the same uid — and in a sandbox Pi shares
34
+ * the harness's uid — and keeps the execve snapshot even after
35
+ * `delete process.env.X`. So the RUNNER's own credentials reach a managed
36
+ * sandbox harness over pf-bootstrap's private IPC channel
37
+ * (bootstrap-handoff.ts), not the environment; the environment remains
38
+ * only for a self-hosted or desktop start and an older pf-bootstrap.
39
+ * Do not "improve" this by adding a `--token` flag.
36
40
  */
37
41
  Object.defineProperty(exports, "__esModule", { value: true });
38
42
  /* -------------------------------------------------------------------------- */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peakflo/agent-harness",
3
- "version": "0.2.12",
3
+ "version": "0.2.13-rc.1",
4
4
  "description": "Headless Peakflo coding-agent harness. One codebase, three hosting modes: a Daytona cloud sandbox, a customer VM, and the 20x desktop.",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
@@ -14,6 +14,9 @@
14
14
  "README.md",
15
15
  "LICENSE"
16
16
  ],
17
+ "peakflo": {
18
+ "credentialHandoff": "ipc"
19
+ },
17
20
  "engines": {
18
21
  "node": ">=22.19"
19
22
  },