@seekrit/cli 1.3.0 → 1.4.0
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.
- package/dist/index.js +88 -24
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import { createInterface } from "node:readline";
|
|
|
9
9
|
import { Writable } from "node:stream";
|
|
10
10
|
import { createHash } from "node:crypto";
|
|
11
11
|
import { fileURLToPath } from "node:url";
|
|
12
|
+
import { request } from "node:http";
|
|
12
13
|
/** Default lifetime of a published bundle (7 days), in seconds. */
|
|
13
14
|
const POLICY_DEFAULT_TTL_SECONDS = 10080 * 60;
|
|
14
15
|
/** Bounds on a bundle's lifetime: an hour at the short end, 90 days at the long. */
|
|
@@ -5051,7 +5052,7 @@ async function createAgentTaskToken() {
|
|
|
5051
5052
|
}
|
|
5052
5053
|
//#endregion
|
|
5053
5054
|
//#region package.json
|
|
5054
|
-
var version = "1.
|
|
5055
|
+
var version = "1.4.0";
|
|
5055
5056
|
//#endregion
|
|
5056
5057
|
//#region ../../packages/api-client/src/index.ts
|
|
5057
5058
|
var SeekritApiError = class extends Error {
|
|
@@ -11510,9 +11511,20 @@ function renderProxyConfig(plan) {
|
|
|
11510
11511
|
out.push("# agent. Requires SEEKRIT_PROXY_CONTROL_TOKEN in the environment, and that");
|
|
11511
11512
|
out.push("# token must not be readable by the agents.");
|
|
11512
11513
|
out.push("[control]");
|
|
11513
|
-
out.push(`
|
|
11514
|
+
if (plan.control.socket) out.push(`socket = ${tomlString(plan.control.socket)}`);
|
|
11515
|
+
else if (plan.control.listen) out.push(`listen = ${tomlString(plan.control.listen)}`);
|
|
11514
11516
|
out.push(`ttl = ${tomlString(plan.control.ttl)}`);
|
|
11515
11517
|
out.push(`max_ttl = ${tomlString(plan.control.maxTtl)}`);
|
|
11518
|
+
if (plan.control.socket) {
|
|
11519
|
+
out.push("# Only this program may mint tickets and decide held requests. Without it,");
|
|
11520
|
+
out.push("# any process running as your user and holding the control token can.");
|
|
11521
|
+
out.push("# allow_binary = [\"/usr/local/bin/your-orchestrator\"]");
|
|
11522
|
+
out.push("# allow_sha256 = [\"<sha256 of that binary>\"]");
|
|
11523
|
+
} else {
|
|
11524
|
+
out.push("# A TCP control listener carries no peer credentials, so the token is the");
|
|
11525
|
+
out.push("# only gate — and an agent spawned by the orchestrator inherits it. Use a");
|
|
11526
|
+
out.push("# Unix socket (--control /run/seekrit/control.sock) to attest the caller.");
|
|
11527
|
+
}
|
|
11516
11528
|
out.push("");
|
|
11517
11529
|
}
|
|
11518
11530
|
if (plan.tasks) {
|
|
@@ -12165,7 +12177,7 @@ function collect$2(value, acc) {
|
|
|
12165
12177
|
* release-please-config.json), so the pin follows the crate without anyone
|
|
12166
12178
|
* remembering to move it.
|
|
12167
12179
|
*/
|
|
12168
|
-
const PROXY_VERSION = "0.
|
|
12180
|
+
const PROXY_VERSION = "0.12.0";
|
|
12169
12181
|
const BIN = "seekrit-proxy";
|
|
12170
12182
|
/**
|
|
12171
12183
|
* Host → Rust target triple.
|
|
@@ -12201,7 +12213,7 @@ function versionPrefix(version) {
|
|
|
12201
12213
|
return version.startsWith("v") ? version : `v${version}`;
|
|
12202
12214
|
}
|
|
12203
12215
|
function resolveVersion(explicit) {
|
|
12204
|
-
return explicit ?? process.env.SEEKRIT_PROXY_VERSION ?? "0.
|
|
12216
|
+
return explicit ?? process.env.SEEKRIT_PROXY_VERSION ?? "0.12.0";
|
|
12205
12217
|
}
|
|
12206
12218
|
function resolveBaseUrl(explicit) {
|
|
12207
12219
|
return (explicit ?? process.env.SEEKRIT_PROXY_BASE_URL ?? "https://proxy.seekrit.dev").replace(/\/+$/, "");
|
|
@@ -12334,7 +12346,7 @@ function signalNumber(signal) {
|
|
|
12334
12346
|
* The control listener is loopback-bound by design, so the only interesting
|
|
12335
12347
|
* variable is the port — and one URL is easier to override than a host/port pair.
|
|
12336
12348
|
*/
|
|
12337
|
-
const DEFAULT_CONTROL_URL = "http://127.0.0.1:
|
|
12349
|
+
const DEFAULT_CONTROL_URL = "http://127.0.0.1:9090";
|
|
12338
12350
|
/** Header the control listener authenticates with (mirrors `src/tickets.rs`). */
|
|
12339
12351
|
const CONTROL_TOKEN_HEADER = "x-seekrit-control-token";
|
|
12340
12352
|
/**
|
|
@@ -12347,22 +12359,73 @@ const CONTROL_TOKEN_HEADER = "x-seekrit-control-token";
|
|
|
12347
12359
|
async function controlRequest(base, path, decision) {
|
|
12348
12360
|
const token = process.env.SEEKRIT_PROXY_CONTROL_TOKEN;
|
|
12349
12361
|
if (!token) fail("SEEKRIT_PROXY_CONTROL_TOKEN is not set — it is the same token the proxy was started with, and without it the listener will not answer");
|
|
12350
|
-
|
|
12362
|
+
const headers = {
|
|
12363
|
+
[CONTROL_TOKEN_HEADER]: token,
|
|
12364
|
+
...decision ? { "content-type": "application/json" } : {}
|
|
12365
|
+
};
|
|
12366
|
+
const method = decision ? "POST" : "GET";
|
|
12367
|
+
const body = decision ? JSON.stringify({ decision }) : void 0;
|
|
12368
|
+
let status;
|
|
12369
|
+
let text;
|
|
12351
12370
|
try {
|
|
12352
|
-
|
|
12353
|
-
method: decision ? "POST" : "GET",
|
|
12354
|
-
headers: {
|
|
12355
|
-
[CONTROL_TOKEN_HEADER]: token,
|
|
12356
|
-
...decision ? { "content-type": "application/json" } : {}
|
|
12357
|
-
},
|
|
12358
|
-
...decision ? { body: JSON.stringify({ decision }) } : {}
|
|
12359
|
-
});
|
|
12371
|
+
({status, text} = isSocketPath(base) ? await socketRequest(socketPathOf(base), path, method, headers, body) : await fetchRequest(base, path, method, headers, body));
|
|
12360
12372
|
} catch (e) {
|
|
12361
12373
|
fail(`could not reach the proxy's control listener at ${base}: ${e.message}\nIs the proxy running, and does its config have a [control] block?`);
|
|
12362
12374
|
throw e;
|
|
12363
12375
|
}
|
|
12364
|
-
if (
|
|
12365
|
-
return
|
|
12376
|
+
if (status < 200 || status >= 300) fail(`${status} from the control listener: ${text.trim()}`);
|
|
12377
|
+
return JSON.parse(text);
|
|
12378
|
+
}
|
|
12379
|
+
/** Is this `--control` value a Unix socket rather than a URL? */
|
|
12380
|
+
function isSocketPath(value) {
|
|
12381
|
+
return value.startsWith("unix:") || value.startsWith("/") || value.startsWith(".");
|
|
12382
|
+
}
|
|
12383
|
+
/** The filesystem path inside a `--control` value. */
|
|
12384
|
+
function socketPathOf(value) {
|
|
12385
|
+
return value.startsWith("unix:") ? value.slice(5).replace(/^\/\//, "") : value;
|
|
12386
|
+
}
|
|
12387
|
+
async function fetchRequest(base, path, method, headers, body) {
|
|
12388
|
+
const resp = await fetch(new URL(path, base), {
|
|
12389
|
+
method,
|
|
12390
|
+
headers,
|
|
12391
|
+
...body ? { body } : {}
|
|
12392
|
+
});
|
|
12393
|
+
return {
|
|
12394
|
+
status: resp.status,
|
|
12395
|
+
text: await resp.text()
|
|
12396
|
+
};
|
|
12397
|
+
}
|
|
12398
|
+
/**
|
|
12399
|
+
* The same request over a Unix socket.
|
|
12400
|
+
*
|
|
12401
|
+
* `fetch` cannot dial one without a custom dispatcher, and `node:http` can with
|
|
12402
|
+
* no dependency at all — which matters for a CLI shipped over npm.
|
|
12403
|
+
*/
|
|
12404
|
+
function socketRequest(socketPath, path, method, headers, body) {
|
|
12405
|
+
return new Promise((resolve, reject) => {
|
|
12406
|
+
const req = request({
|
|
12407
|
+
socketPath,
|
|
12408
|
+
path,
|
|
12409
|
+
method,
|
|
12410
|
+
headers: {
|
|
12411
|
+
...headers,
|
|
12412
|
+
...body ? { "content-length": Buffer.byteLength(body) } : {}
|
|
12413
|
+
}
|
|
12414
|
+
}, (res) => {
|
|
12415
|
+
let text = "";
|
|
12416
|
+
res.setEncoding("utf8");
|
|
12417
|
+
res.on("data", (chunk) => {
|
|
12418
|
+
text += chunk;
|
|
12419
|
+
});
|
|
12420
|
+
res.on("end", () => resolve({
|
|
12421
|
+
status: res.statusCode ?? 0,
|
|
12422
|
+
text
|
|
12423
|
+
}));
|
|
12424
|
+
});
|
|
12425
|
+
req.on("error", reject);
|
|
12426
|
+
if (body) req.write(body);
|
|
12427
|
+
req.end();
|
|
12428
|
+
});
|
|
12366
12429
|
}
|
|
12367
12430
|
const DEFAULT_CONFIG = "seekrit-proxy.toml";
|
|
12368
12431
|
/**
|
|
@@ -12386,7 +12449,8 @@ function planOptions(options) {
|
|
|
12386
12449
|
const listen = options.listen ?? PLAN_DEFAULTS.listen;
|
|
12387
12450
|
const forwardListen = options.forwardListen ?? PLAN_DEFAULTS.forwardListen;
|
|
12388
12451
|
if (mode === "both" && listen === forwardListen) fail(`--listen and --forward-listen cannot both be ${listen} — the two planes need separate ports`);
|
|
12389
|
-
|
|
12452
|
+
const controlIsSocket = options.control !== void 0 && isSocketPath(options.control);
|
|
12453
|
+
if (options.control && !controlIsSocket && (options.control === listen || options.control === forwardListen)) fail(`--control cannot share an address with a data plane (${options.control})`);
|
|
12390
12454
|
const refresh = duration(options.refresh, "--refresh");
|
|
12391
12455
|
return {
|
|
12392
12456
|
mode,
|
|
@@ -12398,7 +12462,7 @@ function planOptions(options) {
|
|
|
12398
12462
|
...options.cache || options.cacheMaxAge ? { cacheMaxAge: duration(options.cacheMaxAge, "--cache-max-age") ?? "24h" } : {},
|
|
12399
12463
|
...refresh ? { secretsRefresh: refresh } : {},
|
|
12400
12464
|
...options.control ? { control: {
|
|
12401
|
-
listen: options.control,
|
|
12465
|
+
...controlIsSocket ? { socket: options.control } : { listen: options.control },
|
|
12402
12466
|
ttl: "1h",
|
|
12403
12467
|
maxTtl: "12h"
|
|
12404
12468
|
} } : {},
|
|
@@ -12522,7 +12586,7 @@ async function buildPlan(options) {
|
|
|
12522
12586
|
}
|
|
12523
12587
|
/** Add the generation flags to a command, so `init` and `run` stay in step. */
|
|
12524
12588
|
function withGenerateOptions(cmd) {
|
|
12525
|
-
return cmd.option("--preset <name>", "gateway preset (repeatable; see `seekrit proxy presets`)", (value, acc = []) => [...acc, value]).option("--host <host[=SECRET,…]>", "ad-hoc rule: bare hostname, optionally the secrets it may receive (repeatable)", (value, acc = []) => [...acc, value]).option("--base-url <url>", "upstream base URL for an OpenAI-compatible gateway").option("--secret <NAME>", "override a preset's secret name").option("--prefix <path>", "override a preset's route prefix").option("--agent <slug>", "take the rules from published agent policy (server mode)").option("--agents <slug>", "additional identities this proxy may serve", (v, a = []) => [...a, v]).option("--org <slug>", "organization (for --agent)").option("--mode <reverse|forward|both>", "which data plane(s) to configure", "reverse").option("--listen <addr>", `reverse-proxy address (default: ${PLAN_DEFAULTS.listen})`).option("--forward-listen <addr>", `forward-proxy address (default: ${PLAN_DEFAULTS.forwardListen})`).option("--unmatched <tunnel|deny>", "what to do with an unruled host in forward mode").option("--ca-cert <path>", "interception CA certificate path (forward mode)").option("--ca-key <path>", "interception CA key path (forward mode)").option("--cache", "add a [cache] block so the proxy can start during an outage").option("--cache-max-age <dur>", "how stale a cached resolve may be (implies --cache)").option("--refresh <dur>", "re-resolve/re-fetch interval").option("--control <addr>", "add a [control] listener for per-agent session tickets").option("--tasks", "add a [tasks] block so this proxy honours runs dispatched with `seekrit agents dispatch`").option("--tasks-cache-ttl <dur>", "how long an introspected task is reused (implies --tasks)").option("--activity", "add an [activity] block so this proxy reports aggregate decisions for `seekrit agents review`").option("--activity-interval <dur>", "how often counts are flushed (implies --activity)");
|
|
12589
|
+
return cmd.option("--preset <name>", "gateway preset (repeatable; see `seekrit proxy presets`)", (value, acc = []) => [...acc, value]).option("--host <host[=SECRET,…]>", "ad-hoc rule: bare hostname, optionally the secrets it may receive (repeatable)", (value, acc = []) => [...acc, value]).option("--base-url <url>", "upstream base URL for an OpenAI-compatible gateway").option("--secret <NAME>", "override a preset's secret name").option("--prefix <path>", "override a preset's route prefix").option("--agent <slug>", "take the rules from published agent policy (server mode)").option("--agents <slug>", "additional identities this proxy may serve", (v, a = []) => [...a, v]).option("--org <slug>", "organization (for --agent)").option("--mode <reverse|forward|both>", "which data plane(s) to configure", "reverse").option("--listen <addr>", `reverse-proxy address (default: ${PLAN_DEFAULTS.listen})`).option("--forward-listen <addr>", `forward-proxy address (default: ${PLAN_DEFAULTS.forwardListen})`).option("--unmatched <tunnel|deny>", "what to do with an unruled host in forward mode").option("--ca-cert <path>", "interception CA certificate path (forward mode)").option("--ca-key <path>", "interception CA key path (forward mode)").option("--cache", "add a [cache] block so the proxy can start during an outage").option("--cache-max-age <dur>", "how stale a cached resolve may be (implies --cache)").option("--refresh <dur>", "re-resolve/re-fetch interval").option("--control <addr|path>", "add a [control] listener for per-agent session tickets; a path makes it a Unix socket, which is the only transport whose caller can be attested").option("--tasks", "add a [tasks] block so this proxy honours runs dispatched with `seekrit agents dispatch`").option("--tasks-cache-ttl <dur>", "how long an introspected task is reused (implies --tasks)").option("--activity", "add an [activity] block so this proxy reports aggregate decisions for `seekrit agents review`").option("--activity-interval <dur>", "how often counts are flushed (implies --activity)");
|
|
12526
12590
|
}
|
|
12527
12591
|
function registerProxyCommands(program) {
|
|
12528
12592
|
const proxy = program.command("proxy").description("run and configure the agent egress proxy (`seekrit proxy --help`)");
|
|
@@ -12589,10 +12653,10 @@ Next:
|
|
|
12589
12653
|
});
|
|
12590
12654
|
console.log(path);
|
|
12591
12655
|
const { target } = detectTarget();
|
|
12592
|
-
process.stderr.write(`seekrit-proxy ${versionPrefix(options.proxyVersion ?? "0.
|
|
12656
|
+
process.stderr.write(`seekrit-proxy ${versionPrefix(options.proxyVersion ?? "0.12.0")} (${target})\n`);
|
|
12593
12657
|
});
|
|
12594
12658
|
proxy.command("where").description("show which binary `seekrit proxy run` would use, without fetching it").option("--proxy-version <version>", `version to report (default: ${PROXY_VERSION})`).option("--json", "machine-readable output").action((options) => {
|
|
12595
|
-
const version = options.proxyVersion ?? process.env.SEEKRIT_PROXY_VERSION ?? "0.
|
|
12659
|
+
const version = options.proxyVersion ?? process.env.SEEKRIT_PROXY_VERSION ?? "0.12.0";
|
|
12596
12660
|
const override = process.env.SEEKRIT_PROXY_BIN;
|
|
12597
12661
|
const { target, exe } = detectTarget();
|
|
12598
12662
|
const path = override ?? proxyBinaryPath(version, target, exe);
|
|
@@ -12608,7 +12672,7 @@ Next:
|
|
|
12608
12672
|
process.stderr.write(`${info.version} · ${info.target} · ${info.source}\n`);
|
|
12609
12673
|
});
|
|
12610
12674
|
});
|
|
12611
|
-
proxy.command("approvals").description("list requests a running proxy is holding for approval").option("--control <url>", "control listener URL", DEFAULT_CONTROL_URL).option("--json", "machine-readable output").action(async (options) => {
|
|
12675
|
+
proxy.command("approvals").description("list requests a running proxy is holding for approval").option("--control <url|path>", "control listener URL, or a Unix socket path", DEFAULT_CONTROL_URL).option("--json", "machine-readable output").action(async (options) => {
|
|
12612
12676
|
const { pending } = await controlRequest(options.control, "/approvals");
|
|
12613
12677
|
emit(options, { pending }, () => {
|
|
12614
12678
|
printTable(pending, [
|
|
@@ -12636,7 +12700,7 @@ Next:
|
|
|
12636
12700
|
"always",
|
|
12637
12701
|
"let it through and stop asking about that host and method for this run"
|
|
12638
12702
|
]
|
|
12639
|
-
]) proxy.command(`${verb} <id>`).description(summary).option("--control <url>", "control listener URL", DEFAULT_CONTROL_URL).option("--json", "machine-readable output").action(async (id, options) => {
|
|
12703
|
+
]) proxy.command(`${verb} <id>`).description(summary).option("--control <url|path>", "control listener URL, or a Unix socket path", DEFAULT_CONTROL_URL).option("--json", "machine-readable output").action(async (id, options) => {
|
|
12640
12704
|
const result = await controlRequest(options.control, `/approvals/${encodeURIComponent(id)}`, decision);
|
|
12641
12705
|
emit(options, {
|
|
12642
12706
|
id,
|
|
@@ -12651,7 +12715,7 @@ Next:
|
|
|
12651
12715
|
process.stdout.write(renderComposeSnippet(plan, {
|
|
12652
12716
|
service: options.service,
|
|
12653
12717
|
workload: options.workload,
|
|
12654
|
-
image: options.image ?? `seekritdev/proxy:0.
|
|
12718
|
+
image: options.image ?? `seekritdev/proxy:0.12.0`,
|
|
12655
12719
|
publish: Boolean(options.publish)
|
|
12656
12720
|
}));
|
|
12657
12721
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seekrit/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "End-to-end encrypted secrets manager CLI — inject decrypted secrets into any command.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"@types/node": "^26.1.0",
|
|
31
31
|
"tsdown": "^0.22.3",
|
|
32
32
|
"vitest": "^4.1.11",
|
|
33
|
-
"@seekrit/core": "0.0.1",
|
|
34
33
|
"@seekrit/api-client": "0.0.1",
|
|
34
|
+
"@seekrit/core": "0.0.1",
|
|
35
35
|
"@seekrit/crypto": "0.0.1"
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|