@tinycloud/cli 1.3.5-beta.4 → 1.3.5-beta.6
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/courier-entry.js +352 -40
- package/dist/courier-entry.js.map +1 -1
- package/package.json +4 -4
package/dist/courier-entry.js
CHANGED
|
@@ -4,10 +4,10 @@ var EXT = "https://tinycloud.xyz/ext/courier/v0";
|
|
|
4
4
|
var DEFAULT_HOST = "https://tee.node.tinycloud.xyz";
|
|
5
5
|
var COURIER_SPACE = "agents";
|
|
6
6
|
var AGENT_GRANT_EXPIRY = "30d";
|
|
7
|
-
var kv = (
|
|
7
|
+
var kv = (path22, actions) => ({
|
|
8
8
|
service: "tinycloud.kv",
|
|
9
9
|
space: COURIER_SPACE,
|
|
10
|
-
path: `${PREFIX}/${
|
|
10
|
+
path: `${PREFIX}/${path22}`,
|
|
11
11
|
actions: actions.map((action) => `tinycloud.kv/${action}`)
|
|
12
12
|
});
|
|
13
13
|
var AGENT_GRANT = [
|
|
@@ -826,7 +826,7 @@ function authorityTuples(entries) {
|
|
|
826
826
|
return tuples;
|
|
827
827
|
}
|
|
828
828
|
function renderTuples(tuples) {
|
|
829
|
-
const shown = tuples.slice(0, 3).map(({ service, space, path:
|
|
829
|
+
const shown = tuples.slice(0, 3).map(({ service, space, path: path22, action }) => `${service} ${space} ${path22 || '""'} ${action}`);
|
|
830
830
|
if (tuples.length > 3)
|
|
831
831
|
shown.push(`(+${tuples.length - 3} more)`);
|
|
832
832
|
return shown.join("; ");
|
|
@@ -899,9 +899,10 @@ import { TinyCloudNode, DelegatedAccess, NodeWasmBindings, activateValidatedRunt
|
|
|
899
899
|
// ../courier/dist/profile.js
|
|
900
900
|
import fs from "fs";
|
|
901
901
|
import path from "path";
|
|
902
|
+
import os from "os";
|
|
902
903
|
import { randomBytes } from "crypto";
|
|
903
904
|
function profileDir() {
|
|
904
|
-
return process.env.COURIER_HOME || path.join(
|
|
905
|
+
return process.env.COURIER_HOME || path.join(os.homedir(), ".courier");
|
|
905
906
|
}
|
|
906
907
|
function profilePath(name2, home = profileDir()) {
|
|
907
908
|
return path.join(home, name2);
|
|
@@ -909,11 +910,13 @@ function profilePath(name2, home = profileDir()) {
|
|
|
909
910
|
function profileExists() {
|
|
910
911
|
return fs.existsSync(profilePath("profile.json"));
|
|
911
912
|
}
|
|
912
|
-
function
|
|
913
|
-
const dir = profileDir();
|
|
913
|
+
function mkdirOwnerOnly(dir) {
|
|
914
914
|
fs.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
915
915
|
fs.chmodSync(dir, 448);
|
|
916
916
|
}
|
|
917
|
+
function ensureSecureDir() {
|
|
918
|
+
mkdirOwnerOnly(profileDir());
|
|
919
|
+
}
|
|
917
920
|
function writeSecure(file, obj) {
|
|
918
921
|
ensureSecureDir();
|
|
919
922
|
const temp = `${file}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
|
|
@@ -1103,7 +1106,7 @@ async function peerHooks(node, profile, peer) {
|
|
|
1103
1106
|
// ../courier/dist/args.js
|
|
1104
1107
|
function parseArgs(argv) {
|
|
1105
1108
|
const positional = [];
|
|
1106
|
-
const BOOLS = { json: true, help: true, force: true, stdin: true, quiet: true, "rotate-key": true, paste: true, request: true, peek: true, resume: true, once: true, unguarded: true, "install-launchd": true, "install-systemd": true, "dry-run": true };
|
|
1109
|
+
const BOOLS = { json: true, help: true, force: true, stdin: true, "code-stdin": true, quiet: true, "rotate-key": true, paste: true, request: true, peek: true, resume: true, once: true, unguarded: true, "install-launchd": true, "install-systemd": true, "dry-run": true };
|
|
1107
1110
|
const flags = /* @__PURE__ */ new Map();
|
|
1108
1111
|
for (let i = 0; i < argv.length; i++) {
|
|
1109
1112
|
const a = argv[i];
|
|
@@ -2255,7 +2258,7 @@ import path3 from "path";
|
|
|
2255
2258
|
|
|
2256
2259
|
// ../courier/dist/guard/paths.js
|
|
2257
2260
|
import fs2 from "fs";
|
|
2258
|
-
import
|
|
2261
|
+
import os2 from "os";
|
|
2259
2262
|
import path2 from "path";
|
|
2260
2263
|
var CREDENTIAL_SUBSTRINGS = [
|
|
2261
2264
|
".config/gh",
|
|
@@ -2404,7 +2407,7 @@ function installRoots(entry) {
|
|
|
2404
2407
|
function protectedSet(home, entry, extra = {}) {
|
|
2405
2408
|
return {
|
|
2406
2409
|
roots: [path2.resolve(home), ...installRoots(entry)],
|
|
2407
|
-
homes: [.../* @__PURE__ */ new Set([
|
|
2410
|
+
homes: [.../* @__PURE__ */ new Set([os2.homedir(), ...extra.agentHome ? [extra.agentHome] : []])]
|
|
2408
2411
|
};
|
|
2409
2412
|
}
|
|
2410
2413
|
function isProtectedRead(file, cwd, set) {
|
|
@@ -4940,7 +4943,7 @@ function analyzeScript(text, parent, inexact) {
|
|
|
4940
4943
|
|
|
4941
4944
|
// ../courier/dist/guard/install.js
|
|
4942
4945
|
import fs4 from "fs";
|
|
4943
|
-
import
|
|
4946
|
+
import os3 from "os";
|
|
4944
4947
|
import path6 from "path";
|
|
4945
4948
|
import { createHash } from "crypto";
|
|
4946
4949
|
var CREDENTIAL_FILES = [".config/gh/**", ".npmrc", ".netrc", ".ssh/**", ".aws/**", ".config/gcloud/**", ".docker/config.json", ".config/wrangler/**", ".config/.wrangler/**", ".git-credentials"];
|
|
@@ -4982,7 +4985,7 @@ var GATED_DENY = [
|
|
|
4982
4985
|
].map((prefix) => `Bash(${prefix}:*)`);
|
|
4983
4986
|
var PRIVILEGE_DENY = ["sudo", "su", "doas", "pkexec", "tmux", "screen", "crontab", "systemctl", "ssh", "gh auth", "git credential", "secret-tool"].map((prefix) => `Bash(${prefix}:*)`);
|
|
4984
4987
|
var shq = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
|
|
4985
|
-
function hookCommand(argv, home, realHome =
|
|
4988
|
+
function hookCommand(argv, home, realHome = os3.homedir()) {
|
|
4986
4989
|
return `COURIER_HOME=${shq(home)} COURIER_REAL_HOME=${shq(realHome)} ${argv.map(shq).join(" ")} || exit 2`;
|
|
4987
4990
|
}
|
|
4988
4991
|
var guardDir = (home) => path6.join(home, "guard");
|
|
@@ -5005,7 +5008,7 @@ function guardSettings(options) {
|
|
|
5005
5008
|
for (const f of ["key.json", "grant.json"])
|
|
5006
5009
|
deny2.push(`Read(${rulePath(path6.join(home, f))})`);
|
|
5007
5010
|
for (const rel of CREDENTIAL_FILES)
|
|
5008
|
-
deny2.push(`Read(${rulePath(path6.join(
|
|
5011
|
+
deny2.push(`Read(${rulePath(path6.join(os3.homedir(), rel))})`);
|
|
5009
5012
|
for (const prog of programs)
|
|
5010
5013
|
for (const sub2 of ["pair", "revoke", "init", "pause", "resume", "watch"])
|
|
5011
5014
|
deny2.push(`Bash(${prog} ${sub2}:*)`);
|
|
@@ -5185,7 +5188,7 @@ function courierEnvOverrides(base, home, extra = []) {
|
|
|
5185
5188
|
// ../courier/dist/wake.js
|
|
5186
5189
|
import { spawn } from "child_process";
|
|
5187
5190
|
import fs7 from "fs";
|
|
5188
|
-
import
|
|
5191
|
+
import os4 from "os";
|
|
5189
5192
|
import path9 from "path";
|
|
5190
5193
|
|
|
5191
5194
|
// ../courier/dist/guard/cleanhome.js
|
|
@@ -5564,7 +5567,7 @@ function createWaker(config, deps) {
|
|
|
5564
5567
|
if (cwd === courier || cwd.startsWith(`${courier}${path9.sep}`))
|
|
5565
5568
|
throw new Error("agent cwd must be outside the Courier tree");
|
|
5566
5569
|
prepareCleanHome({
|
|
5567
|
-
realHome: config.realHome ?? env.HOME ??
|
|
5570
|
+
realHome: config.realHome ?? env.HOME ?? os4.homedir(),
|
|
5568
5571
|
cleanHome: agentHome(config.home),
|
|
5569
5572
|
claude: config.adapter !== "exec"
|
|
5570
5573
|
});
|
|
@@ -5656,7 +5659,7 @@ ${UNGUARDED_NOTICE}.`
|
|
|
5656
5659
|
}
|
|
5657
5660
|
const existing = agents.find((a) => typeof a.id === "string" && typeof a.provider === "string" && isClaudeProvider(a.provider) && typeof a.cwd === "string" && sameDir(a.cwd, config.cwd))?.id;
|
|
5658
5661
|
if (existing) {
|
|
5659
|
-
const dir = fs7.mkdtempSync(path9.join(
|
|
5662
|
+
const dir = fs7.mkdtempSync(path9.join(os4.tmpdir(), "courier-wake-"));
|
|
5660
5663
|
const file = path9.join(dir, "prompt.txt");
|
|
5661
5664
|
try {
|
|
5662
5665
|
fs7.writeFileSync(file, prompt(request), { mode: 384 });
|
|
@@ -5800,7 +5803,7 @@ function createNotifier(config, deps) {
|
|
|
5800
5803
|
|
|
5801
5804
|
// ../courier/dist/commands.js
|
|
5802
5805
|
import fs9 from "fs";
|
|
5803
|
-
import
|
|
5806
|
+
import os5 from "os";
|
|
5804
5807
|
import path11 from "path";
|
|
5805
5808
|
import { NodeWasmBindings as NodeWasmBindings2, activateValidatedRuntimeDelegation as activateValidatedRuntimeDelegation2, deserializeDelegation as deserializeDelegation2, serializeDelegation as serializeDelegation2 } from "@tinycloud/node-sdk";
|
|
5806
5809
|
|
|
@@ -6270,7 +6273,7 @@ async function cmdInit(args) {
|
|
|
6270
6273
|
version: 3,
|
|
6271
6274
|
host,
|
|
6272
6275
|
openkeyHost,
|
|
6273
|
-
label: labelFlag ?? `courier \xB7 ${
|
|
6276
|
+
label: labelFlag ?? `courier \xB7 ${os5.hostname()}`,
|
|
6274
6277
|
...handleFlag ? { selfHandle: handleFlag } : {},
|
|
6275
6278
|
courierDid: bareDid(key.did),
|
|
6276
6279
|
status: "awaiting-grant",
|
|
@@ -6844,7 +6847,7 @@ ${DEFAULT_ORDERS}`);
|
|
|
6844
6847
|
|
|
6845
6848
|
// ../courier/dist/watch-cmd.js
|
|
6846
6849
|
import fs13 from "fs";
|
|
6847
|
-
import
|
|
6850
|
+
import os8 from "os";
|
|
6848
6851
|
import path16 from "path";
|
|
6849
6852
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
6850
6853
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
@@ -7395,8 +7398,8 @@ var Watcher = class {
|
|
|
7395
7398
|
const prefix = `${PREFIX}/to/${peer.handle}/threads/`;
|
|
7396
7399
|
const keys = await listAll(this.deps.selfKv, prefix);
|
|
7397
7400
|
const known = /* @__PURE__ */ new Set();
|
|
7398
|
-
for (const
|
|
7399
|
-
const match = /^([^/]+)\/(?:meta\.json|(?:msg|task)\/[^/]+\.json)$/.exec(
|
|
7401
|
+
for (const path22 of keys) {
|
|
7402
|
+
const match = /^([^/]+)\/(?:meta\.json|(?:msg|task)\/[^/]+\.json)$/.exec(path22.slice(prefix.length));
|
|
7400
7403
|
if (match && SAFE_ID.test(match[1]))
|
|
7401
7404
|
known.add(`${peer.handle}/${match[1]}`);
|
|
7402
7405
|
}
|
|
@@ -8273,19 +8276,19 @@ var Watcher = class {
|
|
|
8273
8276
|
|
|
8274
8277
|
// ../courier/dist/guard/command.js
|
|
8275
8278
|
import fs11 from "fs";
|
|
8276
|
-
import
|
|
8279
|
+
import os7 from "os";
|
|
8277
8280
|
import path14 from "path";
|
|
8278
8281
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
8279
8282
|
|
|
8280
8283
|
// ../courier/dist/broker-config.js
|
|
8281
8284
|
import fs10 from "fs";
|
|
8282
|
-
import
|
|
8285
|
+
import os6 from "os";
|
|
8283
8286
|
import path12 from "path";
|
|
8284
8287
|
var TEST_TOKEN = `ghp_${"d".repeat(36)}`;
|
|
8285
8288
|
var ID = /^[a-f0-9]{24}$/;
|
|
8286
8289
|
var PINNED = ["host", "openkeyHost", "spaceId", "ownerDid", "courierDid"];
|
|
8287
8290
|
function brokerRoot(testOwnerRoot) {
|
|
8288
|
-
const owner =
|
|
8291
|
+
const owner = os6.userInfo().homedir;
|
|
8289
8292
|
if (testOwnerRoot !== void 0) {
|
|
8290
8293
|
const root = realpathBest(testOwnerRoot);
|
|
8291
8294
|
const scratch = realpathBest(path12.join(owner, "scratch"));
|
|
@@ -8294,7 +8297,7 @@ function brokerRoot(testOwnerRoot) {
|
|
|
8294
8297
|
}
|
|
8295
8298
|
if (fs10.existsSync(root)) {
|
|
8296
8299
|
const st = fs10.statSync(root);
|
|
8297
|
-
if (st.uid !==
|
|
8300
|
+
if (st.uid !== os6.userInfo().uid || st.mode & 63)
|
|
8298
8301
|
throw new CourierError("usage", "--test-owner-root must be owner-private (0700)");
|
|
8299
8302
|
}
|
|
8300
8303
|
const broker = realpathBest(path12.join(root, ".config/courier-broker"));
|
|
@@ -8350,7 +8353,7 @@ function loadBrokerConfig(id, testOwnerRoot) {
|
|
|
8350
8353
|
try {
|
|
8351
8354
|
for (const f of [path12.dirname(file), file]) {
|
|
8352
8355
|
const st2 = fs10.lstatSync(f);
|
|
8353
|
-
if (st2.isSymbolicLink() || st2.uid !==
|
|
8356
|
+
if (st2.isSymbolicLink() || st2.uid !== os6.userInfo().uid || st2.mode & 63)
|
|
8354
8357
|
throw Error("not owner-private");
|
|
8355
8358
|
}
|
|
8356
8359
|
const config = JSON.parse(fs10.readFileSync(file, "utf8"));
|
|
@@ -8503,8 +8506,8 @@ function realEnv(cwd, home = profileDir(), entry = process.argv[1] ?? "") {
|
|
|
8503
8506
|
binaries: installed,
|
|
8504
8507
|
resolveProgram: (word, dir) => binaryName(word, dir, searchPath, installed),
|
|
8505
8508
|
literals: [...installLiterals(home, entry), brokerRoot()],
|
|
8506
|
-
protectedRoots: [home, brokerRoot(), path14.join(
|
|
8507
|
-
realHome: process.env.COURIER_REAL_HOME ||
|
|
8509
|
+
protectedRoots: [home, brokerRoot(), path14.join(os7.homedir(), ".courier")],
|
|
8510
|
+
realHome: process.env.COURIER_REAL_HOME || os7.userInfo().homedir,
|
|
8508
8511
|
courierEntries: installed.filter((b) => b.path === realpathBest(entry) && ["courier", "tc"].includes(b.name)).map((b) => b.path),
|
|
8509
8512
|
readFile(file) {
|
|
8510
8513
|
try {
|
|
@@ -8575,10 +8578,10 @@ async function cmdGuard(args) {
|
|
|
8575
8578
|
const input = JSON.parse(readStdin());
|
|
8576
8579
|
const home = profileDir();
|
|
8577
8580
|
const entry = process.argv[1] ?? "";
|
|
8578
|
-
const realHome = process.env.COURIER_REAL_HOME ||
|
|
8581
|
+
const realHome = process.env.COURIER_REAL_HOME || os7.homedir();
|
|
8579
8582
|
const deps = {
|
|
8580
8583
|
env: realEnv(process.cwd(), home, entry),
|
|
8581
|
-
protectedSet: { ...protectedSet(home, entry, { agentHome:
|
|
8584
|
+
protectedSet: { ...protectedSet(home, entry, { agentHome: os7.homedir() }), roots: [...protectedSet(home, entry).roots, brokerRoot(), path14.join(os7.homedir(), ".courier")], homes: [.../* @__PURE__ */ new Set([realHome, os7.homedir()])], realHome }
|
|
8582
8585
|
};
|
|
8583
8586
|
const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error("timed out")), DEADLINE_MS).unref());
|
|
8584
8587
|
const decision = await Promise.race([decide(input, deps), timeout]);
|
|
@@ -8947,7 +8950,7 @@ async function installService(args, config) {
|
|
|
8947
8950
|
logFile: path16.join(profileDir(), `watch${name2 ? `-${name2}` : ""}.log`),
|
|
8948
8951
|
name: name2
|
|
8949
8952
|
};
|
|
8950
|
-
const plan = launchd ? launchdPlan(spec,
|
|
8953
|
+
const plan = launchd ? launchdPlan(spec, os8.homedir(), process.getuid?.() ?? 0) : systemdPlan(spec, os8.homedir());
|
|
8951
8954
|
if (dry) {
|
|
8952
8955
|
process.stdout.write(renderPlan(plan));
|
|
8953
8956
|
return;
|
|
@@ -9054,36 +9057,36 @@ function parseEntry(value2) {
|
|
|
9054
9057
|
return void 0;
|
|
9055
9058
|
return { tier, ratePerHour, outPerHour };
|
|
9056
9059
|
}
|
|
9057
|
-
function loadPeers(
|
|
9060
|
+
function loadPeers(path22, ownerUid2 = process.getuid?.()) {
|
|
9058
9061
|
let fd;
|
|
9059
9062
|
try {
|
|
9060
|
-
fd = fs14.openSync(
|
|
9063
|
+
fd = fs14.openSync(path22, fs14.constants.O_RDONLY | fs14.constants.O_NOFOLLOW);
|
|
9061
9064
|
} catch (error) {
|
|
9062
9065
|
const code = error.code;
|
|
9063
9066
|
if (code === "ENOENT")
|
|
9064
9067
|
return /* @__PURE__ */ Object.create(null);
|
|
9065
9068
|
if (code === "ELOOP")
|
|
9066
|
-
throw new CourierError("insecure-peers", `${
|
|
9069
|
+
throw new CourierError("insecure-peers", `${path22} is a symbolic link; point the flag at the file itself.`);
|
|
9067
9070
|
throw error;
|
|
9068
9071
|
}
|
|
9069
9072
|
try {
|
|
9070
9073
|
const stat = fs14.fstatSync(fd);
|
|
9071
9074
|
if (!stat.isFile())
|
|
9072
|
-
throw new CourierError("insecure-peers", `${
|
|
9075
|
+
throw new CourierError("insecure-peers", `${path22} is not a regular file.`);
|
|
9073
9076
|
if (ownerUid2 !== void 0 && stat.uid !== ownerUid2) {
|
|
9074
|
-
throw new CourierError("insecure-peers", `${
|
|
9077
|
+
throw new CourierError("insecure-peers", `${path22} is not owned by the peers owner (uid ${ownerUid2}); pass --peers-owner or chown the file.`);
|
|
9075
9078
|
}
|
|
9076
9079
|
if ((stat.mode & 18) !== 0) {
|
|
9077
|
-
throw new CourierError("insecure-peers", `${
|
|
9080
|
+
throw new CourierError("insecure-peers", `${path22} is writable by group or others; restrict it to the owner (chmod 600).`);
|
|
9078
9081
|
}
|
|
9079
9082
|
const parsed = JSON.parse(fs14.readFileSync(fd, "utf8"));
|
|
9080
9083
|
if (!isRecord(parsed))
|
|
9081
|
-
throw new CourierError("bad-peers", `${
|
|
9084
|
+
throw new CourierError("bad-peers", `${path22} must be a JSON object keyed by peer id.`);
|
|
9082
9085
|
const peers = /* @__PURE__ */ Object.create(null);
|
|
9083
9086
|
for (const [peerId, value2] of Object.entries(parsed)) {
|
|
9084
9087
|
const policy = parseEntry(value2);
|
|
9085
9088
|
if (!policy)
|
|
9086
|
-
throw new CourierError("bad-peers", `${
|
|
9089
|
+
throw new CourierError("bad-peers", `${path22} has an invalid policy for peer "${peerId}"; fix the entry. Nothing was loaded.`);
|
|
9087
9090
|
peers[peerId] = policy;
|
|
9088
9091
|
}
|
|
9089
9092
|
return peers;
|
|
@@ -10749,7 +10752,7 @@ ${targetHelp()}`);
|
|
|
10749
10752
|
// ../courier/dist/broker.js
|
|
10750
10753
|
import { execFile } from "child_process";
|
|
10751
10754
|
import fs18 from "fs";
|
|
10752
|
-
import
|
|
10755
|
+
import os9 from "os";
|
|
10753
10756
|
import path20 from "path";
|
|
10754
10757
|
var MAX_OUTPUT = 4e3;
|
|
10755
10758
|
var EXEC_TIMEOUT_MS = 12e4;
|
|
@@ -10758,7 +10761,7 @@ var validToken = (token2) => TOKEN.test(token2);
|
|
|
10758
10761
|
async function loadBrokerCredential(config, exec = execFileRunner) {
|
|
10759
10762
|
if (config.testToken === TEST_TOKEN)
|
|
10760
10763
|
return { ghBin: config.ghBin, token: TEST_TOKEN };
|
|
10761
|
-
const ownerHome =
|
|
10764
|
+
const ownerHome = os9.userInfo().homedir;
|
|
10762
10765
|
const result = await exec(config.ghBin, ["auth", "token", "--hostname", "github.com"], {
|
|
10763
10766
|
cwd: ownerHome,
|
|
10764
10767
|
timeoutMs: 1e4,
|
|
@@ -10968,6 +10971,308 @@ async function cmdBroker(args) {
|
|
|
10968
10971
|
`);
|
|
10969
10972
|
}
|
|
10970
10973
|
|
|
10974
|
+
// ../courier/dist/credential.js
|
|
10975
|
+
import { createPrivateKey, sign as cryptoSign } from "crypto";
|
|
10976
|
+
import { createInterface as createInterface2 } from "readline";
|
|
10977
|
+
import { completeEmailProof, createEd25519HolderSigner, EmailProofError, emailProofRequirement, requestEmailProofCode } from "@tinycloud/node-sdk/credentials";
|
|
10978
|
+
import { createFileEmailProofPendingStore, PendingStoreError } from "@tinycloud/node-sdk/credential-pending-store";
|
|
10979
|
+
import { credentialRequirementDigest } from "@tinycloud/sdk-core";
|
|
10980
|
+
|
|
10981
|
+
// ../courier/dist/credential-source.js
|
|
10982
|
+
import fs19 from "fs";
|
|
10983
|
+
import path21 from "path";
|
|
10984
|
+
import { createHash as createHash4, randomBytes as randomBytes5 } from "crypto";
|
|
10985
|
+
import { createStoredCredentialRecord, credentialMatchesRequirement } from "@tinycloud/sdk-core";
|
|
10986
|
+
var DIR_NAME = "credentials";
|
|
10987
|
+
function credentialsDir(home) {
|
|
10988
|
+
return path21.join(home, DIR_NAME);
|
|
10989
|
+
}
|
|
10990
|
+
function sha256Hex(text) {
|
|
10991
|
+
return createHash4("sha256").update(text).digest("hex");
|
|
10992
|
+
}
|
|
10993
|
+
function credentialFile(home, holderDid, requirementDigest) {
|
|
10994
|
+
return path21.join(credentialsDir(home), `credential-${sha256Hex(`${holderDid}
|
|
10995
|
+
${requirementDigest}`)}.json`);
|
|
10996
|
+
}
|
|
10997
|
+
function lstatOrUndefined2(file) {
|
|
10998
|
+
try {
|
|
10999
|
+
return fs19.lstatSync(file);
|
|
11000
|
+
} catch (error) {
|
|
11001
|
+
if (error.code === "ENOENT")
|
|
11002
|
+
return void 0;
|
|
11003
|
+
throw error;
|
|
11004
|
+
}
|
|
11005
|
+
}
|
|
11006
|
+
function assertOwnerOnly(st, label2, expectedMode) {
|
|
11007
|
+
const mode = st.mode & 511;
|
|
11008
|
+
if ((mode & 63) !== 0) {
|
|
11009
|
+
throw new CourierError("credential-permissions", `${label2} must be owner-only (mode ${expectedMode}); found mode ${mode.toString(8)}.`);
|
|
11010
|
+
}
|
|
11011
|
+
assertOwnedByCurrentUser(st, label2);
|
|
11012
|
+
}
|
|
11013
|
+
function assertOwnedByCurrentUser(st, label2) {
|
|
11014
|
+
if (typeof process.getuid === "function" && st.uid !== process.getuid()) {
|
|
11015
|
+
throw new CourierError("credential-permissions", `${label2} is not owned by the current user.`);
|
|
11016
|
+
}
|
|
11017
|
+
}
|
|
11018
|
+
function ensureSecureDir2(home) {
|
|
11019
|
+
const dir = credentialsDir(home);
|
|
11020
|
+
mkdirOwnerOnly(dir);
|
|
11021
|
+
const st = fs19.lstatSync(dir);
|
|
11022
|
+
if (!st.isDirectory())
|
|
11023
|
+
throw new CourierError("credential-permissions", `${dir} must be a real directory, not a symlink or file.`);
|
|
11024
|
+
assertOwnedByCurrentUser(st, dir);
|
|
11025
|
+
fs19.chmodSync(dir, 448);
|
|
11026
|
+
}
|
|
11027
|
+
function checkSecureDir(home) {
|
|
11028
|
+
const dir = credentialsDir(home);
|
|
11029
|
+
const st = lstatOrUndefined2(dir);
|
|
11030
|
+
if (!st)
|
|
11031
|
+
return false;
|
|
11032
|
+
if (!st.isDirectory())
|
|
11033
|
+
throw new CourierError("credential-permissions", `${dir} must be a real directory, not a symlink or file.`);
|
|
11034
|
+
assertOwnerOnly(st, dir, "0700");
|
|
11035
|
+
return true;
|
|
11036
|
+
}
|
|
11037
|
+
function writeOwnerOnly(home, file, obj) {
|
|
11038
|
+
ensureSecureDir2(home);
|
|
11039
|
+
const existing = lstatOrUndefined2(file);
|
|
11040
|
+
if (existing && !existing.isFile()) {
|
|
11041
|
+
throw new CourierError("credential-permissions", `${path21.basename(file)} exists and is not a regular file; refusing to replace it.`);
|
|
11042
|
+
}
|
|
11043
|
+
const temp = `${file}.${process.pid}.${randomBytes5(6).toString("hex")}.tmp`;
|
|
11044
|
+
let fd;
|
|
11045
|
+
try {
|
|
11046
|
+
fd = fs19.openSync(temp, fs19.constants.O_WRONLY | fs19.constants.O_CREAT | fs19.constants.O_EXCL | fs19.constants.O_NOFOLLOW, 384);
|
|
11047
|
+
fs19.writeFileSync(fd, JSON.stringify(obj, null, 2) + "\n");
|
|
11048
|
+
fs19.fchmodSync(fd, 384);
|
|
11049
|
+
fs19.fsyncSync(fd);
|
|
11050
|
+
fs19.closeSync(fd);
|
|
11051
|
+
fd = void 0;
|
|
11052
|
+
fs19.renameSync(temp, file);
|
|
11053
|
+
} finally {
|
|
11054
|
+
if (fd !== void 0)
|
|
11055
|
+
fs19.closeSync(fd);
|
|
11056
|
+
try {
|
|
11057
|
+
fs19.unlinkSync(temp);
|
|
11058
|
+
} catch {
|
|
11059
|
+
}
|
|
11060
|
+
}
|
|
11061
|
+
}
|
|
11062
|
+
function readOwnerOnly(home, file) {
|
|
11063
|
+
if (!checkSecureDir(home))
|
|
11064
|
+
return void 0;
|
|
11065
|
+
const label2 = path21.basename(file);
|
|
11066
|
+
const before = lstatOrUndefined2(file);
|
|
11067
|
+
if (!before)
|
|
11068
|
+
return void 0;
|
|
11069
|
+
if (!before.isFile())
|
|
11070
|
+
throw new CourierError("credential-permissions", `${label2} must be a regular file; symlinks are refused.`);
|
|
11071
|
+
assertOwnerOnly(before, label2, "0600");
|
|
11072
|
+
const fd = fs19.openSync(file, fs19.constants.O_RDONLY | fs19.constants.O_NOFOLLOW);
|
|
11073
|
+
let text;
|
|
11074
|
+
try {
|
|
11075
|
+
const opened = fs19.fstatSync(fd);
|
|
11076
|
+
if (!opened.isFile() || opened.dev !== before.dev || opened.ino !== before.ino) {
|
|
11077
|
+
throw new CourierError("credential-permissions", `${label2} changed while it was being opened.`);
|
|
11078
|
+
}
|
|
11079
|
+
if (opened.nlink !== 1)
|
|
11080
|
+
throw new CourierError("credential-permissions", `${label2} has multiple hard links; refusing to read it.`);
|
|
11081
|
+
assertOwnerOnly(opened, label2, "0600");
|
|
11082
|
+
text = fs19.readFileSync(fd, "utf8");
|
|
11083
|
+
} finally {
|
|
11084
|
+
fs19.closeSync(fd);
|
|
11085
|
+
}
|
|
11086
|
+
try {
|
|
11087
|
+
return JSON.parse(text);
|
|
11088
|
+
} catch {
|
|
11089
|
+
throw new CourierError("credential-store-invalid", `Invalid credential store file ${label2}.`);
|
|
11090
|
+
}
|
|
11091
|
+
}
|
|
11092
|
+
function removeFile(file) {
|
|
11093
|
+
fs19.rmSync(file, { force: true });
|
|
11094
|
+
}
|
|
11095
|
+
var StoredCredentialSource = class {
|
|
11096
|
+
home;
|
|
11097
|
+
constructor(home = profileDir()) {
|
|
11098
|
+
this.home = home;
|
|
11099
|
+
}
|
|
11100
|
+
async find(input) {
|
|
11101
|
+
const file = credentialFile(this.home, input.holderDid, input.requirementDigest);
|
|
11102
|
+
const record = readOwnerOnly(this.home, file);
|
|
11103
|
+
if (!record)
|
|
11104
|
+
return void 0;
|
|
11105
|
+
const now = input.now ?? /* @__PURE__ */ new Date();
|
|
11106
|
+
if (!credentialMatchesRequirement(input.requirement, record, input.holderDid, now)) {
|
|
11107
|
+
removeFile(file);
|
|
11108
|
+
return void 0;
|
|
11109
|
+
}
|
|
11110
|
+
return record;
|
|
11111
|
+
}
|
|
11112
|
+
async store(input) {
|
|
11113
|
+
const record = await createStoredCredentialRecord({
|
|
11114
|
+
verified: input.verified,
|
|
11115
|
+
ownerDid: input.holderDid,
|
|
11116
|
+
requirement: input.requirement,
|
|
11117
|
+
requirementDigest: input.requirementDigest,
|
|
11118
|
+
storedAt: (input.now ?? /* @__PURE__ */ new Date()).toISOString()
|
|
11119
|
+
});
|
|
11120
|
+
writeOwnerOnly(this.home, credentialFile(this.home, input.holderDid, input.requirementDigest), record);
|
|
11121
|
+
return record;
|
|
11122
|
+
}
|
|
11123
|
+
};
|
|
11124
|
+
|
|
11125
|
+
// ../courier/dist/credential.js
|
|
11126
|
+
var acquireUsage = () => `Usage: ${cmd("credential acquire --email <addr> [--code-stdin]")}`;
|
|
11127
|
+
var showUsage = () => `Usage: ${cmd("credential show --email <addr>")}`;
|
|
11128
|
+
var credentialUsage = () => `${acquireUsage()}
|
|
11129
|
+
${showUsage()}`;
|
|
11130
|
+
function holderFor(key) {
|
|
11131
|
+
let privateKey;
|
|
11132
|
+
try {
|
|
11133
|
+
privateKey = createPrivateKey({ key: key.jwk, format: "jwk" });
|
|
11134
|
+
} catch {
|
|
11135
|
+
throw new CourierError("credential.request.HOLDER_INVALID", "Agent signing key is unusable.");
|
|
11136
|
+
}
|
|
11137
|
+
try {
|
|
11138
|
+
return createEd25519HolderSigner(key.did, async (bytes) => new Uint8Array(cryptoSign(null, bytes, privateKey)));
|
|
11139
|
+
} catch (error) {
|
|
11140
|
+
throw mapEmailProofError(error, "request");
|
|
11141
|
+
}
|
|
11142
|
+
}
|
|
11143
|
+
function isEmailProofError(error) {
|
|
11144
|
+
return error instanceof EmailProofError;
|
|
11145
|
+
}
|
|
11146
|
+
function mapEmailProofError(error, fallbackStep) {
|
|
11147
|
+
if (isEmailProofError(error))
|
|
11148
|
+
return new CourierError(`credential.${error.step}.${error.code}`, error.message);
|
|
11149
|
+
return new CourierError(`credential.${fallbackStep}.FAILED`, "Email verification failed.");
|
|
11150
|
+
}
|
|
11151
|
+
function keepsPendingAfterFailure(error) {
|
|
11152
|
+
return isEmailProofError(error) && error.recoverable;
|
|
11153
|
+
}
|
|
11154
|
+
async function withStoreErrors(run2) {
|
|
11155
|
+
try {
|
|
11156
|
+
return await run2();
|
|
11157
|
+
} catch (error) {
|
|
11158
|
+
if (error instanceof PendingStoreError)
|
|
11159
|
+
throw new CourierError(error.code, error.message);
|
|
11160
|
+
throw error;
|
|
11161
|
+
}
|
|
11162
|
+
}
|
|
11163
|
+
async function readStdinFirstLine() {
|
|
11164
|
+
const rl = createInterface2({ input: process.stdin, crlfDelay: Infinity });
|
|
11165
|
+
for await (const line of rl) {
|
|
11166
|
+
rl.close();
|
|
11167
|
+
return line;
|
|
11168
|
+
}
|
|
11169
|
+
return "";
|
|
11170
|
+
}
|
|
11171
|
+
async function acquire(args, deps) {
|
|
11172
|
+
const address = requireFlag(args, "email", acquireUsage()).trim();
|
|
11173
|
+
if (args.has("code"))
|
|
11174
|
+
throw new CourierError("usage", `--code is refused: it puts the code on argv. Enter the code with --code-stdin. ${acquireUsage()}`);
|
|
11175
|
+
const key = deps.loadKey(deps.home);
|
|
11176
|
+
const holder = holderFor(key);
|
|
11177
|
+
const store = createFileEmailProofPendingStore({ home: deps.home });
|
|
11178
|
+
if (!args.has("code-stdin")) {
|
|
11179
|
+
const older = await withStoreErrors(() => store.findLive(address, deps.now()));
|
|
11180
|
+
let pending2;
|
|
11181
|
+
try {
|
|
11182
|
+
pending2 = await deps.requestCode({ address, holder, fetch: deps.fetch, now: deps.now });
|
|
11183
|
+
} catch (error) {
|
|
11184
|
+
throw mapEmailProofError(error, "request");
|
|
11185
|
+
}
|
|
11186
|
+
await withStoreErrors(async () => {
|
|
11187
|
+
await store.put(pending2);
|
|
11188
|
+
if (older && older.requestId !== pending2.requestId)
|
|
11189
|
+
await store.delete(older.requestId);
|
|
11190
|
+
});
|
|
11191
|
+
deps.out(`Verification code sent to ${address}. Run \`${cmd(`credential acquire --email ${address} --code-stdin`)}\` and enter the code within 15 minutes.
|
|
11192
|
+
`);
|
|
11193
|
+
return;
|
|
11194
|
+
}
|
|
11195
|
+
const code = (await deps.readStdinLine()).trim();
|
|
11196
|
+
if (!/^\d{8}$/.test(code))
|
|
11197
|
+
throw new CourierError("credential.code.CODE_INVALID", "Verification code must be 8 digits.");
|
|
11198
|
+
const pending = await withStoreErrors(() => store.findLive(address, deps.now()));
|
|
11199
|
+
if (!pending) {
|
|
11200
|
+
throw new CourierError("credential-request-missing", `No pending verification for ${address}. Run \`${cmd(`credential acquire --email ${address}`)}\` first.`);
|
|
11201
|
+
}
|
|
11202
|
+
if (pending.holderDid !== holder.did) {
|
|
11203
|
+
await withStoreErrors(() => store.delete(pending.requestId));
|
|
11204
|
+
throw new CourierError("credential.session.SESSION_INVALID", "Pending verification belongs to a different agent key. Request a new code.");
|
|
11205
|
+
}
|
|
11206
|
+
let result;
|
|
11207
|
+
try {
|
|
11208
|
+
result = await deps.completeProof({ pending, code, holder, fetch: deps.fetch, now: deps.now });
|
|
11209
|
+
} catch (error) {
|
|
11210
|
+
if (keepsPendingAfterFailure(error)) {
|
|
11211
|
+
const mapped = mapEmailProofError(error, "credential");
|
|
11212
|
+
throw new CourierError(mapped.code, `${mapped.message} Retry with --code-stdin before ${pending.expiresAt}.`);
|
|
11213
|
+
}
|
|
11214
|
+
await withStoreErrors(() => store.delete(pending.requestId));
|
|
11215
|
+
throw mapEmailProofError(error, "credential");
|
|
11216
|
+
}
|
|
11217
|
+
const requirement = emailProofRequirement(address);
|
|
11218
|
+
const requirementDigest = await credentialRequirementDigest(requirement);
|
|
11219
|
+
await new StoredCredentialSource(deps.home).store({
|
|
11220
|
+
verified: result.verified,
|
|
11221
|
+
requirement,
|
|
11222
|
+
requirementDigest,
|
|
11223
|
+
holderDid: holder.did,
|
|
11224
|
+
now: deps.now()
|
|
11225
|
+
});
|
|
11226
|
+
await withStoreErrors(() => store.delete(pending.requestId));
|
|
11227
|
+
deps.out(`Email credential stored for ${address}. It is valid for up to 1 hour from issuance.
|
|
11228
|
+
`);
|
|
11229
|
+
}
|
|
11230
|
+
async function show(args, deps) {
|
|
11231
|
+
const address = requireFlag(args, "email", showUsage()).trim();
|
|
11232
|
+
const holder = holderFor(deps.loadKey(deps.home));
|
|
11233
|
+
const requirement = emailProofRequirement(address);
|
|
11234
|
+
const requirementDigest = await credentialRequirementDigest(requirement);
|
|
11235
|
+
const record = await new StoredCredentialSource(deps.home).find({
|
|
11236
|
+
requirement,
|
|
11237
|
+
requirementDigest,
|
|
11238
|
+
holderDid: holder.did,
|
|
11239
|
+
now: deps.now()
|
|
11240
|
+
});
|
|
11241
|
+
if (!record) {
|
|
11242
|
+
throw new CourierError("credential.show.NOT_FOUND", `No valid stored email credential for ${address}. Run \`${cmd(`credential acquire --email ${address}`)}\`.`);
|
|
11243
|
+
}
|
|
11244
|
+
deps.out([
|
|
11245
|
+
`email: ${record.claims.email}`,
|
|
11246
|
+
`credentialId: ${record.credentialId}`,
|
|
11247
|
+
`issuedAt: ${record.issuedAt}`,
|
|
11248
|
+
`expiresAt: ${record.expiresAt}`,
|
|
11249
|
+
`status: ${record.status.method}`,
|
|
11250
|
+
""
|
|
11251
|
+
].join("\n"));
|
|
11252
|
+
}
|
|
11253
|
+
async function cmdCredential(args, deps = {}) {
|
|
11254
|
+
const resolved = {
|
|
11255
|
+
home: deps.home ?? profileDir(),
|
|
11256
|
+
now: deps.now ?? (() => /* @__PURE__ */ new Date()),
|
|
11257
|
+
fetch: deps.fetch,
|
|
11258
|
+
loadKey: deps.loadKey ?? ((home) => loadKey(home)),
|
|
11259
|
+
requestCode: deps.requestCode ?? requestEmailProofCode,
|
|
11260
|
+
completeProof: deps.completeProof ?? completeEmailProof,
|
|
11261
|
+
readStdinLine: deps.readStdinLine ?? readStdinFirstLine,
|
|
11262
|
+
out: deps.out ?? ((text) => {
|
|
11263
|
+
process.stdout.write(text);
|
|
11264
|
+
})
|
|
11265
|
+
};
|
|
11266
|
+
switch (args.positional[0]) {
|
|
11267
|
+
case "acquire":
|
|
11268
|
+
return acquire(args, resolved);
|
|
11269
|
+
case "show":
|
|
11270
|
+
return show(args, resolved);
|
|
11271
|
+
default:
|
|
11272
|
+
throw new CourierError("usage", credentialUsage());
|
|
11273
|
+
}
|
|
11274
|
+
}
|
|
11275
|
+
|
|
10971
11276
|
// ../courier/dist/main.js
|
|
10972
11277
|
function usage() {
|
|
10973
11278
|
const name2 = programName();
|
|
@@ -11066,6 +11371,11 @@ Delivery (one watcher per person per machine; never calls a model):
|
|
|
11066
11371
|
Sender: accept one-line item requests over a Unix socket and send the
|
|
11067
11372
|
named outbox item. Replies are ok or refused:<code>.
|
|
11068
11373
|
|
|
11374
|
+
credential acquire --email <addr> [--code-stdin]
|
|
11375
|
+
Email proof: send a verification code to the address, then enter it with
|
|
11376
|
+
--code-stdin (reads one line from stdin; keeps the code off argv).
|
|
11377
|
+
credential show --email <addr> Show the stored email credential for this agent key (no secret printed).
|
|
11378
|
+
|
|
11069
11379
|
pause <peer> <threadId> [--reason "\u2026"] Humans: stop waking your agent for a thread; tells the peer
|
|
11070
11380
|
resume <peer> <threadId> Humans: wake it again (the wake budget starts over)
|
|
11071
11381
|
|
|
@@ -11123,6 +11433,8 @@ async function run(argv) {
|
|
|
11123
11433
|
return cmdPause(sub2);
|
|
11124
11434
|
case "resume":
|
|
11125
11435
|
return cmdResume(sub2);
|
|
11436
|
+
case "credential":
|
|
11437
|
+
return cmdCredential(sub2);
|
|
11126
11438
|
case "guard":
|
|
11127
11439
|
return cmdGuard(sub2);
|
|
11128
11440
|
case "run-approved":
|