@proagentstore/cli 0.4.31 → 0.4.32
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.
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { startRunnerServer } from "./server.js";
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
5
6
|
// Resilience: a stray error in any runtime must NOT take the whole runner down —
|
|
6
7
|
// that drops the tunnel and forces the user to restart `pags up` (and lose their
|
|
7
8
|
// session). Log it and keep serving. Per-component handlers catch most things;
|
|
@@ -29,7 +30,11 @@ function configFromArgs() {
|
|
|
29
30
|
host: arg("--host", process.env.PAGS_RUNNER_HOST || "127.0.0.1") || "127.0.0.1",
|
|
30
31
|
port: Number(arg("--port", process.env.PAGS_RUNNER_PORT || "49171")),
|
|
31
32
|
dataDir,
|
|
32
|
-
|
|
33
|
+
// Never start unauthenticated (#245). This surface drives a coding CLI with permissions
|
|
34
|
+
// skipped, and `authorize` now fails closed — so a missing token would make the runner
|
|
35
|
+
// answer nothing rather than answer everyone. Generate one and print it, mirroring what
|
|
36
|
+
// `pags runner connect` has always done.
|
|
37
|
+
token: arg("--token", process.env.PAGS_RUNNER_TOKEN) || `pags_runner_${randomUUID()}`,
|
|
33
38
|
instanceId: arg("--instance-id", process.env.PAGS_INSTANCE_ID),
|
|
34
39
|
headless: flag("--headless") || process.env.PAGS_RUNNER_HEADLESS === "1",
|
|
35
40
|
};
|
|
@@ -391,13 +391,35 @@ async function route(runner, req, res) {
|
|
|
391
391
|
}
|
|
392
392
|
return json(res, 404, { error: "Not found" });
|
|
393
393
|
}
|
|
394
|
+
/**
|
|
395
|
+
* Authorize a request to the local runner (#245).
|
|
396
|
+
*
|
|
397
|
+
* This surface drives a coding CLI that `pags up` launches with
|
|
398
|
+
* `--dangerously-skip-permissions` / `--sandbox danger-full-access`, so "who may POST here" is
|
|
399
|
+
* the whole security boundary. Two things were the wrong way round:
|
|
400
|
+
*
|
|
401
|
+
* 1. **No token used to mean ALLOW.** `pags up` always generates one, so that path was safe —
|
|
402
|
+
* but `pags-browser-runner` run directly (its own --help documents this) passes
|
|
403
|
+
* `token: undefined`, and served the entire surface unauthenticated. Now it fails CLOSED;
|
|
404
|
+
* the standalone entrypoint generates a token instead of starting open.
|
|
405
|
+
*
|
|
406
|
+
* 2. **A browser could reach it.** Binding to loopback is not isolation: a page the user is
|
|
407
|
+
* visiting cannot READ a cross-origin response, but it can still SEND the POST, and the
|
|
408
|
+
* server sets no CORS headers and did no Origin check. The token already made that
|
|
409
|
+
* unguessable — but nothing legitimate that calls this runner is a browser (the cloud
|
|
410
|
+
* dispatches over the relay; the CLI calls it directly), and neither sends `Origin`. So the
|
|
411
|
+
* presence of that header is by itself proof the caller is a web page, and is refused before
|
|
412
|
+
* the token is even considered. Also closes DNS-rebinding, which loopback does not.
|
|
413
|
+
*/
|
|
394
414
|
function authorize(req, config) {
|
|
415
|
+
if (req.headers.origin)
|
|
416
|
+
return false;
|
|
395
417
|
const token = config.token;
|
|
396
418
|
if (config.instanceId && req.headers["x-pags-instance-id"] !== config.instanceId) {
|
|
397
419
|
return false;
|
|
398
420
|
}
|
|
399
421
|
if (!token)
|
|
400
|
-
return
|
|
422
|
+
return false;
|
|
401
423
|
const auth = req.headers.authorization || "";
|
|
402
424
|
const headerToken = req.headers["x-pags-runner-token"];
|
|
403
425
|
return auth === `Bearer ${token}` || headerToken === token;
|