youmd 0.11.0 → 0.13.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/commands/brain.d.ts.map +1 -1
- package/dist/commands/brain.js +77 -28
- package/dist/commands/brain.js.map +1 -1
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +6 -1
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/connect.d.ts +113 -0
- package/dist/commands/connect.d.ts.map +1 -0
- package/dist/commands/connect.js +849 -0
- package/dist/commands/connect.js.map +1 -0
- package/dist/commands/conversations.d.ts +5 -0
- package/dist/commands/conversations.d.ts.map +1 -0
- package/dist/commands/conversations.js +116 -0
- package/dist/commands/conversations.js.map +1 -0
- package/dist/commands/founder.d.ts +16 -0
- package/dist/commands/founder.d.ts.map +1 -0
- package/dist/commands/founder.js +446 -0
- package/dist/commands/founder.js.map +1 -0
- package/dist/commands/handoff.d.ts +5 -0
- package/dist/commands/handoff.d.ts.map +1 -0
- package/dist/commands/handoff.js +432 -0
- package/dist/commands/handoff.js.map +1 -0
- package/dist/commands/orchestrate.d.ts +15 -0
- package/dist/commands/orchestrate.d.ts.map +1 -1
- package/dist/commands/orchestrate.js +294 -0
- package/dist/commands/orchestrate.js.map +1 -1
- package/dist/commands/project.d.ts.map +1 -1
- package/dist/commands/project.js +8 -1
- package/dist/commands/project.js.map +1 -1
- package/dist/commands/reconcile.d.ts +15 -0
- package/dist/commands/reconcile.d.ts.map +1 -0
- package/dist/commands/reconcile.js +260 -0
- package/dist/commands/reconcile.js.map +1 -0
- package/dist/commands/session.d.ts +28 -0
- package/dist/commands/session.d.ts.map +1 -0
- package/dist/commands/session.js +895 -0
- package/dist/commands/session.js.map +1 -0
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +11 -0
- package/dist/commands/sync.js.map +1 -1
- package/dist/commands/tasks.d.ts +20 -0
- package/dist/commands/tasks.d.ts.map +1 -1
- package/dist/commands/tasks.js +90 -19
- package/dist/commands/tasks.js.map +1 -1
- package/dist/generated/capability-registry.d.ts +444 -76
- package/dist/generated/capability-registry.d.ts.map +1 -1
- package/dist/generated/capability-registry.js +497 -76
- package/dist/generated/capability-registry.js.map +1 -1
- package/dist/index.js +122 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/api.d.ts +393 -2
- package/dist/lib/api.d.ts.map +1 -1
- package/dist/lib/api.js +195 -2
- package/dist/lib/api.js.map +1 -1
- package/dist/lib/brain/adapters.d.ts +22 -1
- package/dist/lib/brain/adapters.d.ts.map +1 -1
- package/dist/lib/brain/adapters.js +184 -2
- package/dist/lib/brain/adapters.js.map +1 -1
- package/dist/lib/brain/approval-key-trust.d.ts +29 -0
- package/dist/lib/brain/approval-key-trust.d.ts.map +1 -0
- package/dist/lib/brain/approval-key-trust.js +139 -0
- package/dist/lib/brain/approval-key-trust.js.map +1 -0
- package/dist/lib/brain/content.d.ts +10 -3
- package/dist/lib/brain/content.d.ts.map +1 -1
- package/dist/lib/brain/content.js +133 -30
- package/dist/lib/brain/content.js.map +1 -1
- package/dist/lib/brain/descriptor-validator.d.ts +11 -0
- package/dist/lib/brain/descriptor-validator.d.ts.map +1 -0
- package/dist/lib/brain/descriptor-validator.js +185 -0
- package/dist/lib/brain/descriptor-validator.js.map +1 -0
- package/dist/lib/brain/discovery.d.ts.map +1 -1
- package/dist/lib/brain/discovery.js +77 -50
- package/dist/lib/brain/discovery.js.map +1 -1
- package/dist/lib/brain/index.d.ts +2 -0
- package/dist/lib/brain/index.d.ts.map +1 -1
- package/dist/lib/brain/index.js +14 -4
- package/dist/lib/brain/index.js.map +1 -1
- package/dist/lib/brain/proposal.d.ts +53 -5
- package/dist/lib/brain/proposal.d.ts.map +1 -1
- package/dist/lib/brain/proposal.js +330 -94
- package/dist/lib/brain/proposal.js.map +1 -1
- package/dist/lib/brain/safe-file.d.ts +25 -1
- package/dist/lib/brain/safe-file.d.ts.map +1 -1
- package/dist/lib/brain/safe-file.js +250 -30
- package/dist/lib/brain/safe-file.js.map +1 -1
- package/dist/lib/brain/safety.d.ts.map +1 -1
- package/dist/lib/brain/safety.js +5 -0
- package/dist/lib/brain/safety.js.map +1 -1
- package/dist/lib/brain/staged-apply.d.ts +2 -1
- package/dist/lib/brain/staged-apply.d.ts.map +1 -1
- package/dist/lib/brain/staged-apply.js +6 -3
- package/dist/lib/brain/staged-apply.js.map +1 -1
- package/dist/lib/brain/store.d.ts +3 -0
- package/dist/lib/brain/store.d.ts.map +1 -1
- package/dist/lib/brain/store.js +68 -0
- package/dist/lib/brain/store.js.map +1 -1
- package/dist/lib/brain/sync.d.ts +48 -0
- package/dist/lib/brain/sync.d.ts.map +1 -1
- package/dist/lib/brain/sync.js +102 -0
- package/dist/lib/brain/sync.js.map +1 -1
- package/dist/lib/brain/types.d.ts +23 -0
- package/dist/lib/brain/types.d.ts.map +1 -1
- package/dist/lib/brain/vision.d.ts +35 -0
- package/dist/lib/brain/vision.d.ts.map +1 -0
- package/dist/lib/brain/vision.js +65 -0
- package/dist/lib/brain/vision.js.map +1 -0
- package/dist/lib/businessScaffold.d.ts +42 -0
- package/dist/lib/businessScaffold.d.ts.map +1 -0
- package/dist/lib/businessScaffold.js +150 -0
- package/dist/lib/businessScaffold.js.map +1 -0
- package/dist/lib/codex-device-auth.d.ts +94 -0
- package/dist/lib/codex-device-auth.d.ts.map +1 -0
- package/dist/lib/codex-device-auth.js +295 -0
- package/dist/lib/codex-device-auth.js.map +1 -0
- package/dist/lib/codex-job-control-plane.d.ts +67 -0
- package/dist/lib/codex-job-control-plane.d.ts.map +1 -0
- package/dist/lib/codex-job-control-plane.js +105 -0
- package/dist/lib/codex-job-control-plane.js.map +1 -0
- package/dist/lib/codex-readonly-job.d.ts +76 -0
- package/dist/lib/codex-readonly-job.d.ts.map +1 -0
- package/dist/lib/codex-readonly-job.js +196 -0
- package/dist/lib/codex-readonly-job.js.map +1 -0
- package/dist/lib/config.d.ts +7 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +3 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/connected-access-native.d.ts +111 -0
- package/dist/lib/connected-access-native.d.ts.map +1 -0
- package/dist/lib/connected-access-native.js +403 -0
- package/dist/lib/connected-access-native.js.map +1 -0
- package/dist/lib/connected-context.d.ts +82 -0
- package/dist/lib/connected-context.d.ts.map +1 -0
- package/dist/lib/connected-context.js +610 -0
- package/dist/lib/connected-context.js.map +1 -0
- package/dist/lib/goalRegistryFile.d.ts +169 -0
- package/dist/lib/goalRegistryFile.d.ts.map +1 -0
- package/dist/lib/goalRegistryFile.js +518 -0
- package/dist/lib/goalRegistryFile.js.map +1 -0
- package/dist/lib/goalsFile.d.ts +101 -0
- package/dist/lib/goalsFile.d.ts.map +1 -0
- package/dist/lib/goalsFile.js +413 -0
- package/dist/lib/goalsFile.js.map +1 -0
- package/dist/lib/handoff.d.ts +170 -0
- package/dist/lib/handoff.d.ts.map +1 -0
- package/dist/lib/handoff.js +316 -0
- package/dist/lib/handoff.js.map +1 -0
- package/dist/lib/machine-identity.d.ts +2 -0
- package/dist/lib/machine-identity.d.ts.map +1 -1
- package/dist/lib/machine-identity.js +13 -6
- package/dist/lib/machine-identity.js.map +1 -1
- package/dist/lib/onboarding.d.ts +4 -1
- package/dist/lib/onboarding.d.ts.map +1 -1
- package/dist/lib/onboarding.js +11 -3
- package/dist/lib/onboarding.js.map +1 -1
- package/dist/lib/orchestrator/loop.d.ts.map +1 -1
- package/dist/lib/orchestrator/loop.js +8 -3
- package/dist/lib/orchestrator/loop.js.map +1 -1
- package/dist/lib/orchestrator/run-journal.d.ts +68 -3
- package/dist/lib/orchestrator/run-journal.d.ts.map +1 -1
- package/dist/lib/orchestrator/run-journal.js +391 -37
- package/dist/lib/orchestrator/run-journal.js.map +1 -1
- package/dist/lib/orchestrator/supervisor.d.ts +11 -0
- package/dist/lib/orchestrator/supervisor.d.ts.map +1 -1
- package/dist/lib/orchestrator/supervisor.js +304 -116
- package/dist/lib/orchestrator/supervisor.js.map +1 -1
- package/dist/lib/orchestrator/tools.d.ts.map +1 -1
- package/dist/lib/orchestrator/tools.js +76 -3
- package/dist/lib/orchestrator/tools.js.map +1 -1
- package/dist/lib/redact.d.ts.map +1 -1
- package/dist/lib/redact.js +40 -7
- package/dist/lib/redact.js.map +1 -1
- package/dist/lib/remote-agent-event.d.ts +19 -0
- package/dist/lib/remote-agent-event.d.ts.map +1 -0
- package/dist/lib/remote-agent-event.js +85 -0
- package/dist/lib/remote-agent-event.js.map +1 -0
- package/dist/lib/remote-command.d.ts.map +1 -1
- package/dist/lib/remote-command.js +19 -0
- package/dist/lib/remote-command.js.map +1 -1
- package/dist/lib/remote-executor.d.ts +7 -1
- package/dist/lib/remote-executor.d.ts.map +1 -1
- package/dist/lib/remote-executor.js +391 -0
- package/dist/lib/remote-executor.js.map +1 -1
- package/dist/lib/sessionCheckpoint.d.ts +104 -0
- package/dist/lib/sessionCheckpoint.d.ts.map +1 -0
- package/dist/lib/sessionCheckpoint.js +191 -0
- package/dist/lib/sessionCheckpoint.js.map +1 -0
- package/dist/lib/sessionHandoff.d.ts +47 -0
- package/dist/lib/sessionHandoff.d.ts.map +1 -0
- package/dist/lib/sessionHandoff.js +305 -0
- package/dist/lib/sessionHandoff.js.map +1 -0
- package/dist/lib/stream.d.ts +3 -1
- package/dist/lib/stream.d.ts.map +1 -1
- package/dist/lib/stream.js +8 -3
- package/dist/lib/stream.js.map +1 -1
- package/dist/lib/taskLedgerProposal.d.ts +2 -2
- package/dist/lib/taskLedgerProposal.d.ts.map +1 -1
- package/dist/lib/taskLedgerProposal.js +16 -8
- package/dist/lib/taskLedgerProposal.js.map +1 -1
- package/dist/lib/tasksFile.d.ts +14 -2
- package/dist/lib/tasksFile.d.ts.map +1 -1
- package/dist/lib/tasksFile.js +39 -5
- package/dist/lib/tasksFile.js.map +1 -1
- package/dist/mcp/registry.d.ts +4 -1
- package/dist/mcp/registry.d.ts.map +1 -1
- package/dist/mcp/registry.js +230 -1
- package/dist/mcp/registry.js.map +1 -1
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +18 -9
- package/dist/mcp/server.js.map +1 -1
- package/native/prebuilds/darwin-universal/youmd_safe_open.node +0 -0
- package/package.json +4 -2
- package/scripts/env-vault/README.md +16 -20
- package/scripts/env-vault/backup.sh +22 -57
- package/scripts/env-vault/restore.sh +18 -96
- package/scripts/smoke-safe-open-node.cjs +50 -0
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.CONNECTED_CONTEXT_RESOURCE_URI = exports.CONNECTED_CONTEXT_SCOPES = exports.CONNECTED_CONTEXT_AUDIENCE = exports.CONNECTED_CONTEXT_RUNTIME_BINDING = exports.CONNECTED_CONTEXT_APPLICATION_ID = void 0;
|
|
37
|
+
exports.createNativeConnectedContext = createNativeConnectedContext;
|
|
38
|
+
exports.bootstrapConnectedContext = bootstrapConnectedContext;
|
|
39
|
+
exports.getConnectedContextStatus = getConnectedContextStatus;
|
|
40
|
+
exports.verifyConnectedContext = verifyConnectedContext;
|
|
41
|
+
exports.revokeConnectedContext = revokeConnectedContext;
|
|
42
|
+
exports.readConnectedContextResource = readConnectedContextResource;
|
|
43
|
+
exports.listConnectedContextMcpResource = listConnectedContextMcpResource;
|
|
44
|
+
exports.readConnectedContextMcpResource = readConnectedContextMcpResource;
|
|
45
|
+
const crypto = __importStar(require("crypto"));
|
|
46
|
+
const fs = __importStar(require("fs"));
|
|
47
|
+
const os = __importStar(require("os"));
|
|
48
|
+
const path = __importStar(require("path"));
|
|
49
|
+
const canonical_json_1 = require("./canonical-json");
|
|
50
|
+
const machine_identity_1 = require("./machine-identity");
|
|
51
|
+
const connected_access_native_1 = require("./connected-access-native");
|
|
52
|
+
exports.CONNECTED_CONTEXT_APPLICATION_ID = "app:youmd-local-helper";
|
|
53
|
+
exports.CONNECTED_CONTEXT_RUNTIME_BINDING = "com.youmd.connected-access-bootstrap";
|
|
54
|
+
exports.CONNECTED_CONTEXT_AUDIENCE = "youmd:personal";
|
|
55
|
+
exports.CONNECTED_CONTEXT_SCOPES = ["read:private"];
|
|
56
|
+
exports.CONNECTED_CONTEXT_RESOURCE_URI = "youmd://connected-context/current";
|
|
57
|
+
const PROD_ISSUER = "https://kindly-cassowary-600.convex.site";
|
|
58
|
+
const ROOT_SCHEMA = "you-md/connected-agent-access/owner-workspace-manifest/v1";
|
|
59
|
+
const DIGEST = /^sha256:[a-f0-9]{64}$/;
|
|
60
|
+
const B64URL_43 = /^[A-Za-z0-9_-]{43}$/;
|
|
61
|
+
const SECRET_CANARY = /(?:\b(?:yg|ym)_[A-Za-z0-9._-]{3,}|\bbearer\s+[A-Za-z0-9._-]+|file:\/{1,3}|(?:^|[\s'"=])(?:~\/|\/Users\/|\/home\/)|\b(?:access|refresh|id)?[_-]?(?:token|cookie|totp|password|api[_-]?key|secret|authorization|client[_-]?secret)\s*[:=])/im;
|
|
62
|
+
function bootstrapNativeLoaded(deps) {
|
|
63
|
+
return {
|
|
64
|
+
// Bootstrap CLI control is bound by the request vocabulary below, not by
|
|
65
|
+
// a Chrome extension identity. The native host ignores this legacy config
|
|
66
|
+
// field in CLI-control mode and independently validates the executable.
|
|
67
|
+
config: { extensionId: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", bindingClass: "owner_registered_development" },
|
|
68
|
+
paths: (0, connected_access_native_1.nativeHostPaths)((deps.homedir ?? os.homedir)()),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function exactNativeFields(value, expected) {
|
|
72
|
+
if (Object.keys(value).sort().join(",") !== [...expected].sort().join(",") || value.ok !== true || value.authorityGranted !== false || value.secretValuesExposed !== false)
|
|
73
|
+
throw new Error("native_bootstrap_response_invalid");
|
|
74
|
+
}
|
|
75
|
+
function parseNativeBootstrapKey(value, expectedType, expectedStatus) {
|
|
76
|
+
exactNativeFields(value, ["type", "ok", "status", "publicJwk", "keyThumbprint", "keyStorage", "authorityGranted", "secretValuesExposed"]);
|
|
77
|
+
if (value.type !== expectedType || value.status !== expectedStatus || value.keyStorage !== "macos_keychain_backed")
|
|
78
|
+
throw new Error("native_bootstrap_key_invalid");
|
|
79
|
+
const publicJwk = exactJwk(value.publicJwk);
|
|
80
|
+
const keyThumbprint = typeof value.keyThumbprint === "string" ? value.keyThumbprint : "";
|
|
81
|
+
if (keyThumbprint !== thumbprint(publicJwk))
|
|
82
|
+
throw new Error("native_bootstrap_key_invalid");
|
|
83
|
+
return { publicJwk, keyThumbprint };
|
|
84
|
+
}
|
|
85
|
+
function parseNativeDpop(value, expectedStatus) {
|
|
86
|
+
exactNativeFields(value, ["type", "ok", "status", "dpopProof", "publicJwk", "keyThumbprint", "iat", "jti", "keyStorage", "authorityGranted", "secretValuesExposed"]);
|
|
87
|
+
const key = parseNativeBootstrapKey({ type: value.type, ok: value.ok, status: "key_ready", publicJwk: value.publicJwk, keyThumbprint: value.keyThumbprint, keyStorage: value.keyStorage, authorityGranted: value.authorityGranted, secretValuesExposed: value.secretValuesExposed }, "connected_access.dpop_proof_result.v1", "key_ready");
|
|
88
|
+
if (value.type !== "connected_access.dpop_proof_result.v1" || value.status !== expectedStatus || typeof value.dpopProof !== "string" || typeof value.iat !== "number" || !Number.isInteger(value.iat) || typeof value.jti !== "string")
|
|
89
|
+
throw new Error("native_bootstrap_dpop_invalid");
|
|
90
|
+
return { ...key, dpopProof: value.dpopProof, iat: value.iat, jti: value.jti };
|
|
91
|
+
}
|
|
92
|
+
/** Strict adapter over the installed Swift native helper's CLI-control ABI. */
|
|
93
|
+
function createNativeConnectedContext(deps = {}) {
|
|
94
|
+
const common = { applicationId: exports.CONNECTED_CONTEXT_APPLICATION_ID, runtimeBinding: exports.CONNECTED_CONTEXT_RUNTIME_BINDING };
|
|
95
|
+
const call = (request) => (0, connected_access_native_1.nativeHostCall)(request, bootstrapNativeLoaded(deps), deps);
|
|
96
|
+
return {
|
|
97
|
+
keyInfo: async () => {
|
|
98
|
+
const value = await call({ type: "connected_access.bootstrap_key_info_request.v1", ...common });
|
|
99
|
+
if (value.status === "key_absent") {
|
|
100
|
+
exactNativeFields(value, ["type", "ok", "status", "keyStorage", "authorityGranted", "secretValuesExposed"]);
|
|
101
|
+
if (value.type !== "connected_access.bootstrap_key_info_result.v1" || value.keyStorage !== "macos_keychain_backed")
|
|
102
|
+
throw new Error("native_bootstrap_key_invalid");
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
return parseNativeBootstrapKey(value, "connected_access.bootstrap_key_info_result.v1", "key_present");
|
|
106
|
+
},
|
|
107
|
+
ensureKey: async () => parseNativeBootstrapKey(await call({ type: "connected_access.ensure_bootstrap_key_request.v1", ...common }), "connected_access.ensure_bootstrap_key_result.v1", "key_ready"),
|
|
108
|
+
signExchange: async ({ nonce }) => parseNativeDpop(await call({ type: "connected_access.dpop_exchange_proof_request.v1", ...common, nonce }), "signed_dpop_exchange_proof"),
|
|
109
|
+
signArtifact: async ({ artifactId, accessTokenDigest }) => parseNativeDpop(await call({ type: "connected_access.dpop_artifact_proof_request.v1", ...common, artifactId, accessTokenDigest }), "signed_dpop_artifact_proof"),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function now(deps) { return (deps.now ?? Date.now)(); }
|
|
113
|
+
function hashBytes(value) { return `sha256:${crypto.createHash("sha256").update(value).digest("hex")}`; }
|
|
114
|
+
function b64urlHash(value) { return crypto.createHash("sha256").update(value, "utf8").digest("base64url"); }
|
|
115
|
+
function exactObject(value, keys, code) {
|
|
116
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
117
|
+
throw new Error(code);
|
|
118
|
+
const row = value;
|
|
119
|
+
if (Object.keys(row).sort().join(",") !== [...keys].sort().join(","))
|
|
120
|
+
throw new Error(code);
|
|
121
|
+
return row;
|
|
122
|
+
}
|
|
123
|
+
function exactJwk(value) {
|
|
124
|
+
const row = exactObject(value, ["crv", "kty", "x"], "bootstrap_key_invalid");
|
|
125
|
+
if (row.kty !== "OKP" || row.crv !== "Ed25519" || typeof row.x !== "string" || !B64URL_43.test(row.x))
|
|
126
|
+
throw new Error("bootstrap_key_invalid");
|
|
127
|
+
return { kty: "OKP", crv: "Ed25519", x: row.x };
|
|
128
|
+
}
|
|
129
|
+
function thumbprint(jwk) { return crypto.createHash("sha256").update((0, canonical_json_1.canonicalJsonString)(jwk)).digest("base64url"); }
|
|
130
|
+
function safeId(value, pattern, code) { if (typeof value !== "string" || !pattern.test(value))
|
|
131
|
+
throw new Error(code); return value; }
|
|
132
|
+
function isoMs(value, code) { if (typeof value !== "string")
|
|
133
|
+
throw new Error(code); const parsed = Date.parse(value); if (!Number.isFinite(parsed))
|
|
134
|
+
throw new Error(code); return parsed; }
|
|
135
|
+
function without(value, key) { const copy = { ...value }; delete copy[key]; return copy; }
|
|
136
|
+
function verifyEd25519(jwk, message, signature, code) {
|
|
137
|
+
if (typeof signature !== "string" || !/^[A-Za-z0-9+/]{86}==$/.test(signature))
|
|
138
|
+
throw new Error(code);
|
|
139
|
+
let key;
|
|
140
|
+
try {
|
|
141
|
+
key = crypto.createPublicKey({ key: jwk, format: "jwk" });
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
throw new Error(code);
|
|
145
|
+
}
|
|
146
|
+
if (!crypto.verify(null, Buffer.from(message, "utf8"), key, Buffer.from(signature, "base64")))
|
|
147
|
+
throw new Error(code);
|
|
148
|
+
}
|
|
149
|
+
function verifySignature(payload, signatureValue, jwk, expectedKeyId, code) {
|
|
150
|
+
const signature = exactObject(signatureValue, ["algorithm", "canonicalization", "keyId", "canonicalDigest", "value"], code);
|
|
151
|
+
if (signature.algorithm !== "ed25519" || signature.canonicalization !== "RFC8785" || signature.keyId !== expectedKeyId)
|
|
152
|
+
throw new Error(code);
|
|
153
|
+
const digest = hashBytes((0, canonical_json_1.canonicalJsonString)(payload));
|
|
154
|
+
if (signature.canonicalDigest !== digest)
|
|
155
|
+
throw new Error(code);
|
|
156
|
+
verifyEd25519(jwk, (0, canonical_json_1.canonicalJsonString)({ payload, canonicalization: "RFC8785" }), signature.value, code);
|
|
157
|
+
}
|
|
158
|
+
function parseJwks(value, keyId) {
|
|
159
|
+
const root = exactObject(value, ["keys"], "bootstrap_jwks_invalid");
|
|
160
|
+
if (!Array.isArray(root.keys) || root.keys.length < 1 || root.keys.length > 2)
|
|
161
|
+
throw new Error("bootstrap_jwks_invalid");
|
|
162
|
+
const matches = root.keys.filter((candidate) => candidate && typeof candidate === "object" && !Array.isArray(candidate) && candidate.kid === keyId);
|
|
163
|
+
if (matches.length !== 1)
|
|
164
|
+
throw new Error("bootstrap_issuer_key_not_found");
|
|
165
|
+
const key = exactObject(matches[0], ["alg", "crv", "kid", "kty", "use", "x"], "bootstrap_jwks_invalid");
|
|
166
|
+
if (key.alg !== "EdDSA" || key.use !== "sig" || key.kid !== keyId || "d" in key)
|
|
167
|
+
throw new Error("bootstrap_jwks_invalid");
|
|
168
|
+
return exactJwk({ kty: key.kty, crv: key.crv, x: key.x });
|
|
169
|
+
}
|
|
170
|
+
function validateManifestShape(value) {
|
|
171
|
+
const keys = ["schemaVersion", "manifestId", "issuer", "ownerRef", "workspaceRef", "tenantRef", "audience", "generation", "notBefore", "issuedAt", "expiresAt", "devices", "applications", "capabilityAllowlist", "toolAllowlist", "resourceAllowlist", "defaultBudget", "mcpProtectedResources", "artifacts", "bootstrap", "rollback", "keyGovernance", "secretValuesExposed", "signature"];
|
|
172
|
+
const row = exactObject(value, keys, "bootstrap_manifest_schema_invalid");
|
|
173
|
+
if (row.schemaVersion !== ROOT_SCHEMA || row.secretValuesExposed !== false)
|
|
174
|
+
throw new Error("bootstrap_manifest_schema_invalid");
|
|
175
|
+
safeId(row.manifestId, /^manifest:[a-z0-9][a-z0-9_-]{7,127}$/, "bootstrap_manifest_schema_invalid");
|
|
176
|
+
safeId(row.ownerRef, /^user:[A-Za-z0-9._-]{3,128}$/, "bootstrap_manifest_schema_invalid");
|
|
177
|
+
safeId(row.workspaceRef, /^workspace:[a-f0-9]{64}$/, "bootstrap_manifest_schema_invalid");
|
|
178
|
+
if (row.tenantRef !== "personal" || row.audience !== exports.CONNECTED_CONTEXT_AUDIENCE || !Number.isInteger(row.generation) || Number(row.generation) < 1)
|
|
179
|
+
throw new Error("bootstrap_manifest_binding_invalid");
|
|
180
|
+
for (const field of ["notBefore", "issuedAt", "expiresAt"])
|
|
181
|
+
isoMs(row[field], "bootstrap_manifest_time_invalid");
|
|
182
|
+
if (!Array.isArray(row.devices) || row.devices.length !== 1 || !Array.isArray(row.applications) || row.applications.length !== 1 || !Array.isArray(row.artifacts) || row.artifacts.length !== 1)
|
|
183
|
+
throw new Error("bootstrap_manifest_schema_invalid");
|
|
184
|
+
return row;
|
|
185
|
+
}
|
|
186
|
+
function verifyManifest(value, manifestDigest, jwk, expected) {
|
|
187
|
+
const manifest = validateManifestShape(value);
|
|
188
|
+
if (manifest.issuer !== expected.issuer || expected.issuer !== PROD_ISSUER)
|
|
189
|
+
throw new Error("bootstrap_manifest_issuer_invalid");
|
|
190
|
+
const notBefore = isoMs(manifest.notBefore, "bootstrap_manifest_time_invalid");
|
|
191
|
+
const issuedAt = isoMs(manifest.issuedAt, "bootstrap_manifest_time_invalid");
|
|
192
|
+
const expiresAt = isoMs(manifest.expiresAt, "bootstrap_manifest_time_invalid");
|
|
193
|
+
if (notBefore > expected.now + 60000 || issuedAt > expected.now + 60000 || expiresAt <= expected.now || expiresAt - issuedAt > 10 * 60000)
|
|
194
|
+
throw new Error("bootstrap_manifest_expired");
|
|
195
|
+
const generation = Number(manifest.generation);
|
|
196
|
+
if (generation < expected.floor)
|
|
197
|
+
throw new Error("bootstrap_manifest_rollback");
|
|
198
|
+
const device = manifest.devices.find((item) => item && typeof item === "object" && item.deviceId === expected.deviceId);
|
|
199
|
+
if (!device || device.status !== "active" || device.revokedAt !== null || !Array.isArray(device.keyThumbprints) || device.keyThumbprints.length !== 1 || device.keyThumbprints[0] !== expected.keyThumbprint)
|
|
200
|
+
throw new Error("bootstrap_manifest_device_mismatch");
|
|
201
|
+
const application = manifest.applications.find((item) => item && typeof item === "object" && item.applicationId === exports.CONNECTED_CONTEXT_APPLICATION_ID);
|
|
202
|
+
if (!application || application.status !== "active" || application.revokedAt !== null)
|
|
203
|
+
throw new Error("bootstrap_manifest_application_mismatch");
|
|
204
|
+
if ((0, canonical_json_1.canonicalJsonString)(manifest.capabilityAllowlist) !== (0, canonical_json_1.canonicalJsonString)([{ capabilityRef: "capability:context-read", scopes: [...exports.CONNECTED_CONTEXT_SCOPES] }])
|
|
205
|
+
|| (0, canonical_json_1.canonicalJsonString)(manifest.toolAllowlist) !== (0, canonical_json_1.canonicalJsonString)([{ toolRef: "tool:context-reader", capabilityRef: "capability:context-read", actionKinds: ["read"] }])
|
|
206
|
+
|| (0, canonical_json_1.canonicalJsonString)(manifest.resourceAllowlist) !== (0, canonical_json_1.canonicalJsonString)([{ resourceRef: "resource:you-context", origin: expected.issuer, actionKinds: ["read"] }])
|
|
207
|
+
|| (0, canonical_json_1.canonicalJsonString)(manifest.defaultBudget) !== (0, canonical_json_1.canonicalJsonString)({ maxSteps: 1, maxInputTokens: 1, maxOutputTokens: 1, maxCostUsd: 0.01, maxDurationSeconds: 60 })
|
|
208
|
+
|| !Array.isArray(manifest.mcpProtectedResources) || manifest.mcpProtectedResources.length !== 0)
|
|
209
|
+
throw new Error("bootstrap_manifest_capability_invalid");
|
|
210
|
+
const bootstrap = exactObject(manifest.bootstrap, ["contextArtifactRef", "contextDigest", "exchange", "fetchUri"], "bootstrap_manifest_schema_invalid");
|
|
211
|
+
const exchange = exactObject(bootstrap.exchange, ["audience", "dpopKeyThumbprint", "dpopRequired", "expiresAt", "nonceRef", "scopes"], "bootstrap_manifest_schema_invalid");
|
|
212
|
+
if (exchange.audience !== exports.CONNECTED_CONTEXT_AUDIENCE || exchange.dpopRequired !== true || exchange.dpopKeyThumbprint !== expected.keyThumbprint || JSON.stringify(exchange.scopes) !== JSON.stringify(exports.CONNECTED_CONTEXT_SCOPES) || isoMs(exchange.expiresAt, "bootstrap_manifest_time_invalid") <= expected.now)
|
|
213
|
+
throw new Error("bootstrap_exchange_binding_invalid");
|
|
214
|
+
safeId(exchange.nonceRef, /^nonce:[A-Za-z0-9_-]{16,128}$/, "bootstrap_exchange_binding_invalid");
|
|
215
|
+
const artifact = manifest.artifacts[0];
|
|
216
|
+
const artifactUnsigned = without(exactObject(artifact, ["artifactRef", "digest", "kind", "signature", "uri"], "bootstrap_artifact_binding_invalid"), "signature");
|
|
217
|
+
if (artifact.kind !== "youstack" || artifact.artifactRef !== bootstrap.contextArtifactRef || artifact.digest !== bootstrap.contextDigest || !DIGEST.test(String(artifact.digest)) || artifact.uri !== bootstrap.fetchUri || artifact.uri !== `${expected.issuer}/api/v1/agent-access/bootstrap/artifacts/${artifact.artifactRef}`)
|
|
218
|
+
throw new Error("bootstrap_artifact_binding_invalid");
|
|
219
|
+
const rollback = exactObject(manifest.rollback, ["minimumAcceptedGeneration", "previousManifestHash", "rollbackAuthorized"], "bootstrap_rollback_invalid");
|
|
220
|
+
if (rollback.minimumAcceptedGeneration !== generation || rollback.rollbackAuthorized !== false || (rollback.previousManifestHash !== null && !DIGEST.test(String(rollback.previousManifestHash))))
|
|
221
|
+
throw new Error("bootstrap_rollback_invalid");
|
|
222
|
+
const governance = exactObject(manifest.keyGovernance, ["activeKeyIds", "allowedAlgorithms", "issuer", "keySetUri", "rotationMaxAgeDays"], "bootstrap_key_governance_invalid");
|
|
223
|
+
if (governance.issuer !== expected.issuer || governance.keySetUri !== `${expected.issuer}/api/v1/agent-access/bootstrap/jwks` || JSON.stringify(governance.allowedAlgorithms) !== '["ed25519"]' || !Array.isArray(governance.activeKeyIds) || governance.activeKeyIds.length < 1)
|
|
224
|
+
throw new Error("bootstrap_key_governance_invalid");
|
|
225
|
+
const signature = manifest.signature;
|
|
226
|
+
const keyId = safeId(signature?.keyId, /^key:[A-Za-z0-9._-]{3,128}$/, "bootstrap_signature_invalid");
|
|
227
|
+
if (!governance.activeKeyIds.includes(keyId))
|
|
228
|
+
throw new Error("bootstrap_signature_invalid");
|
|
229
|
+
verifySignature(artifactUnsigned, artifact.signature, jwk, keyId, "bootstrap_artifact_signature_invalid");
|
|
230
|
+
verifySignature(without(manifest, "signature"), manifest.signature, jwk, keyId, "bootstrap_manifest_signature_invalid");
|
|
231
|
+
if (manifestDigest !== hashBytes((0, canonical_json_1.canonicalJsonString)(manifest)))
|
|
232
|
+
throw new Error("bootstrap_manifest_digest_invalid");
|
|
233
|
+
return { manifest, artifact, bootstrap, generation };
|
|
234
|
+
}
|
|
235
|
+
function verifyDpopResult(proof, expected) {
|
|
236
|
+
if ((0, canonical_json_1.canonicalJsonString)(proof.publicJwk) !== (0, canonical_json_1.canonicalJsonString)(expected.jwk) || proof.keyThumbprint !== expected.thumbprint || thumbprint(proof.publicJwk) !== expected.thumbprint)
|
|
237
|
+
throw new Error("bootstrap_dpop_key_mismatch");
|
|
238
|
+
const parts = proof.dpopProof.split(".");
|
|
239
|
+
if (parts.length !== 3)
|
|
240
|
+
throw new Error("bootstrap_dpop_invalid");
|
|
241
|
+
let header;
|
|
242
|
+
let payload;
|
|
243
|
+
try {
|
|
244
|
+
header = JSON.parse(Buffer.from(parts[0], "base64url").toString("utf8"));
|
|
245
|
+
payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
throw new Error("bootstrap_dpop_invalid");
|
|
249
|
+
}
|
|
250
|
+
if (header.typ !== "dpop+jwt" || header.alg !== "EdDSA" || (0, canonical_json_1.canonicalJsonString)(header.jwk) !== (0, canonical_json_1.canonicalJsonString)(expected.jwk))
|
|
251
|
+
throw new Error("bootstrap_dpop_invalid");
|
|
252
|
+
const allowed = new Set(["htu", "htm", "iat", "jti", ...(expected.nonce ? ["nonce"] : []), ...(expected.ath ? ["ath"] : [])]);
|
|
253
|
+
if (Object.keys(payload).some((key) => !allowed.has(key)) || payload.htu !== expected.htu || payload.htm !== expected.htm || payload.iat !== proof.iat || payload.jti !== proof.jti || (expected.nonce ? payload.nonce !== expected.nonce : "nonce" in payload) || (expected.ath ? payload.ath !== expected.ath : "ath" in payload) || typeof proof.iat !== "number" || Math.abs(expected.now - proof.iat * 1000) > 60000 || typeof proof.jti !== "string" || proof.jti.length < 16)
|
|
254
|
+
throw new Error("bootstrap_dpop_claims_invalid");
|
|
255
|
+
let key;
|
|
256
|
+
try {
|
|
257
|
+
key = crypto.createPublicKey({ key: expected.jwk, format: "jwk" });
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
throw new Error("bootstrap_dpop_invalid");
|
|
261
|
+
}
|
|
262
|
+
if (!crypto.verify(null, Buffer.from(`${parts[0]}.${parts[1]}`), key, Buffer.from(parts[2], "base64url")))
|
|
263
|
+
throw new Error("bootstrap_dpop_signature_invalid");
|
|
264
|
+
}
|
|
265
|
+
function connectedRoot(homedir = os.homedir) { return path.join(homedir(), ".you", "connected-access"); }
|
|
266
|
+
function secureDirectory(dir) { const stat = fs.lstatSync(dir); if (!stat.isDirectory() || stat.isSymbolicLink() || (typeof process.getuid === "function" && stat.uid !== process.getuid()))
|
|
267
|
+
throw new Error("connected_context_store_unsafe"); fs.chmodSync(dir, 0o700); }
|
|
268
|
+
function secureRegularFile(file) { const stat = fs.lstatSync(file); if (!stat.isFile() || stat.isSymbolicLink() || (typeof process.getuid === "function" && stat.uid !== process.getuid()) || (stat.mode & 0o077) !== 0)
|
|
269
|
+
throw new Error("connected_context_store_unsafe"); }
|
|
270
|
+
function ensureRoot(root) { fs.mkdirSync(path.join(root, "snapshots"), { recursive: true, mode: 0o700 }); secureDirectory(root); secureDirectory(path.join(root, "snapshots")); }
|
|
271
|
+
function atomicJson(file, value, random) { const temp = `${file}.tmp-${random(8).toString("hex")}`; fs.writeFileSync(temp, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 }); fs.chmodSync(temp, 0o600); fs.renameSync(temp, file); }
|
|
272
|
+
function readJson(file) { secureRegularFile(file); return JSON.parse(fs.readFileSync(file, "utf8")); }
|
|
273
|
+
function optionalJson(file) { try {
|
|
274
|
+
return readJson(file);
|
|
275
|
+
}
|
|
276
|
+
catch (error) {
|
|
277
|
+
if (error.code === "ENOENT")
|
|
278
|
+
return null;
|
|
279
|
+
throw error;
|
|
280
|
+
} }
|
|
281
|
+
function floorAt(root) { const value = optionalJson(path.join(root, "rollback-floor.json")); if (value === null)
|
|
282
|
+
return 0; const row = exactObject(value, ["minimumAcceptedGeneration"], "connected_context_floor_invalid"); if (!Number.isInteger(row.minimumAcceptedGeneration) || Number(row.minimumAcceptedGeneration) < 1)
|
|
283
|
+
throw new Error("connected_context_floor_invalid"); return Number(row.minimumAcceptedGeneration); }
|
|
284
|
+
function markerAt(root) { const value = optionalJson(path.join(root, "revoked.json")); if (value === null)
|
|
285
|
+
return null; const row = value && typeof value === "object" && !Array.isArray(value) ? value : undefined; const keys = row ? Object.keys(row).sort().join(",") : ""; if (!row || (keys !== "applicationRevoked,keyRevoked,manifestId,revokedAt" && keys !== "applicationRevoked,keyRevoked,manifestId,reason,revokedAt") || typeof row.manifestId !== "string" || !/^manifest:[a-z0-9][a-z0-9_-]{7,127}$/.test(row.manifestId) || typeof row.revokedAt !== "string" || !Number.isFinite(Date.parse(row.revokedAt)) || typeof row.applicationRevoked !== "boolean" || typeof row.keyRevoked !== "boolean" || ("reason" in row && row.reason !== "remote_manifest_not_active"))
|
|
286
|
+
throw new Error("connected_context_revocation_invalid"); return row; }
|
|
287
|
+
function priorAccepted(root) {
|
|
288
|
+
const pointer = optionalJson(path.join(root, "current.json"));
|
|
289
|
+
if (pointer === null)
|
|
290
|
+
return null;
|
|
291
|
+
const row = exactObject(pointer, ["snapshot"], "connected_context_pointer_invalid");
|
|
292
|
+
if (typeof row.snapshot !== "string" || !/^[1-9][0-9]*-[a-f0-9]{16}$/.test(row.snapshot))
|
|
293
|
+
throw new Error("connected_context_pointer_invalid");
|
|
294
|
+
const metadata = readJson(path.join(root, "snapshots", row.snapshot, "manifest.json"));
|
|
295
|
+
if (!Number.isInteger(metadata.generation) || !DIGEST.test(metadata.manifestDigest))
|
|
296
|
+
throw new Error("connected_context_metadata_invalid");
|
|
297
|
+
return { generation: metadata.generation, manifestDigest: metadata.manifestDigest };
|
|
298
|
+
}
|
|
299
|
+
function hasAnyLocalTrustState(root) {
|
|
300
|
+
for (const name of ["rollback-floor.json", "current.json", "revoked.json"]) {
|
|
301
|
+
try {
|
|
302
|
+
fs.lstatSync(path.join(root, name));
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
if (error.code !== "ENOENT")
|
|
307
|
+
throw error;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
try {
|
|
311
|
+
return fs.readdirSync(path.join(root, "snapshots")).length > 0;
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
if (error.code === "ENOENT")
|
|
315
|
+
return false;
|
|
316
|
+
throw error;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
function loadSnapshotUnchecked(dir, floor, clock) {
|
|
320
|
+
secureDirectory(dir);
|
|
321
|
+
secureRegularFile(path.join(dir, "manifest.json"));
|
|
322
|
+
secureRegularFile(path.join(dir, "context.md"));
|
|
323
|
+
const metadata = readJson(path.join(dir, "manifest.json"));
|
|
324
|
+
if (!metadata || metadata.schemaVersion !== "you-md/connected-context-snapshot/v1" || metadata.secretValuesExposed !== false || !Number.isInteger(metadata.generation) || metadata.generation < floor || !DIGEST.test(metadata.artifactDigest) || !DIGEST.test(metadata.manifestDigest) || metadata.applicationId !== exports.CONNECTED_CONTEXT_APPLICATION_ID || !/^dak_[A-Za-z0-9]{32}$/.test(metadata.keyId))
|
|
325
|
+
throw new Error("connected_context_metadata_invalid");
|
|
326
|
+
const bytes = fs.readFileSync(path.join(dir, "context.md"));
|
|
327
|
+
if (hashBytes(bytes) !== metadata.artifactDigest || bytes.length > 512 * 1024)
|
|
328
|
+
throw new Error("connected_context_digest_invalid");
|
|
329
|
+
const context = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
330
|
+
if (SECRET_CANARY.test(context))
|
|
331
|
+
throw new Error("connected_context_forbidden_material");
|
|
332
|
+
const raw = metadata.issuerKey;
|
|
333
|
+
const issuerJwk = exactJwk({ kty: raw.kty, crv: raw.crv, x: raw.x });
|
|
334
|
+
const deviceJwk = exactJwk(metadata.devicePublicJwk);
|
|
335
|
+
const verified = verifyManifest(metadata.manifest, metadata.manifestDigest, issuerJwk, { issuer: PROD_ISSUER, deviceId: metadata.deviceId, keyThumbprint: thumbprint(deviceJwk), now: clock, floor });
|
|
336
|
+
if (verified.generation !== metadata.generation || verified.manifest.expiresAt !== metadata.expiresAt || verified.artifact.digest !== metadata.artifactDigest)
|
|
337
|
+
throw new Error("connected_context_metadata_invalid");
|
|
338
|
+
return { metadata, context };
|
|
339
|
+
}
|
|
340
|
+
function loadCurrent(root, clock) {
|
|
341
|
+
try {
|
|
342
|
+
secureDirectory(root);
|
|
343
|
+
secureDirectory(path.join(root, "snapshots"));
|
|
344
|
+
}
|
|
345
|
+
catch (error) {
|
|
346
|
+
if (error.code === "ENOENT")
|
|
347
|
+
throw new Error("connected_context_missing");
|
|
348
|
+
throw error;
|
|
349
|
+
}
|
|
350
|
+
const floor = floorAt(root);
|
|
351
|
+
let selected;
|
|
352
|
+
try {
|
|
353
|
+
const pointer = readJson(path.join(root, "current.json"));
|
|
354
|
+
if (typeof pointer.snapshot === "string" && /^[1-9][0-9]*-[a-f0-9]{16}$/.test(pointer.snapshot))
|
|
355
|
+
selected = pointer.snapshot;
|
|
356
|
+
}
|
|
357
|
+
catch { /* recover below */ }
|
|
358
|
+
const candidates = [selected, ...(() => { try {
|
|
359
|
+
return fs.readdirSync(path.join(root, "snapshots")).filter((name) => /^[1-9][0-9]*-[a-f0-9]{16}$/.test(name)).sort((a, b) => Number(b.split("-")[0]) - Number(a.split("-")[0]));
|
|
360
|
+
}
|
|
361
|
+
catch {
|
|
362
|
+
return [];
|
|
363
|
+
} })()].filter((value, index, all) => Boolean(value) && all.indexOf(value) === index);
|
|
364
|
+
let sawExpired = false;
|
|
365
|
+
for (const name of candidates) {
|
|
366
|
+
try {
|
|
367
|
+
const loaded = loadSnapshotUnchecked(path.join(root, "snapshots", name), floor, clock);
|
|
368
|
+
if (isoMs(loaded.metadata.expiresAt, "connected_context_expired") <= clock) {
|
|
369
|
+
sawExpired = true;
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
const recovered = name !== selected;
|
|
373
|
+
if (recovered)
|
|
374
|
+
atomicJson(path.join(root, "current.json"), { snapshot: name }, crypto.randomBytes);
|
|
375
|
+
return { ...loaded, recovered };
|
|
376
|
+
}
|
|
377
|
+
catch (error) {
|
|
378
|
+
if (error instanceof Error && error.message.includes("expired"))
|
|
379
|
+
sawExpired = true;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
throw new Error(sawExpired ? "connected_context_expired" : candidates.length ? "connected_context_invalid" : "connected_context_missing");
|
|
383
|
+
}
|
|
384
|
+
function persistSnapshot(root, metadata, context, random) {
|
|
385
|
+
ensureRoot(root);
|
|
386
|
+
const name = `${metadata.generation}-${metadata.manifestDigest.slice(7, 23)}`;
|
|
387
|
+
const finalDir = path.join(root, "snapshots", name);
|
|
388
|
+
const tempDir = path.join(root, "snapshots", `.tmp-${random(8).toString("hex")}`);
|
|
389
|
+
fs.mkdirSync(tempDir, { mode: 0o700 });
|
|
390
|
+
fs.writeFileSync(path.join(tempDir, "manifest.json"), `${JSON.stringify(metadata, null, 2)}\n`, { mode: 0o600 });
|
|
391
|
+
fs.writeFileSync(path.join(tempDir, "context.md"), context, { mode: 0o600 });
|
|
392
|
+
fs.chmodSync(tempDir, 0o700);
|
|
393
|
+
if (!fs.existsSync(finalDir))
|
|
394
|
+
fs.renameSync(tempDir, finalDir);
|
|
395
|
+
else
|
|
396
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
397
|
+
atomicJson(path.join(root, "rollback-floor.json"), { minimumAcceptedGeneration: metadata.generation }, random);
|
|
398
|
+
atomicJson(path.join(root, "current.json"), { snapshot: name }, random);
|
|
399
|
+
try {
|
|
400
|
+
const marker = markerAt(root);
|
|
401
|
+
if (marker?.manifestId !== metadata.manifest.manifestId)
|
|
402
|
+
fs.unlinkSync(path.join(root, "revoked.json"));
|
|
403
|
+
}
|
|
404
|
+
catch { /* marker absent */ }
|
|
405
|
+
}
|
|
406
|
+
async function requestJson(deps, url, init, owner = false) {
|
|
407
|
+
const headers = { Accept: "application/json", ...(init.body ? { "Content-Type": "application/json" } : {}) };
|
|
408
|
+
if (owner) {
|
|
409
|
+
const token = deps.authToken();
|
|
410
|
+
if (!token)
|
|
411
|
+
throw new Error("authentication_required");
|
|
412
|
+
headers.Authorization = `Bearer ${token}`;
|
|
413
|
+
}
|
|
414
|
+
const response = await deps.fetch(url, { ...init, headers: { ...headers, ...init.headers }, redirect: "error" });
|
|
415
|
+
let data = {};
|
|
416
|
+
try {
|
|
417
|
+
data = await response.json();
|
|
418
|
+
}
|
|
419
|
+
catch { /* handled by status */ }
|
|
420
|
+
if (!response.ok)
|
|
421
|
+
throw new Error(`connected_context_http_${response.status}`);
|
|
422
|
+
return { response, data };
|
|
423
|
+
}
|
|
424
|
+
async function bootstrapConnectedContext(deps, options = {}) {
|
|
425
|
+
if (!deps.authToken())
|
|
426
|
+
throw new Error("authentication_required");
|
|
427
|
+
if (options.trustCurrentGeneration !== undefined && options.trustCurrentGeneration !== true)
|
|
428
|
+
throw new Error("bootstrap_trust_option_invalid");
|
|
429
|
+
const issuer = deps.apiUrl().replace(/\/$/, "");
|
|
430
|
+
if (issuer !== PROD_ISSUER)
|
|
431
|
+
throw new Error("bootstrap_issuer_not_allowed");
|
|
432
|
+
const clock = now(deps);
|
|
433
|
+
const root = connectedRoot(deps.homedir);
|
|
434
|
+
const hadLocalTrustState = hasAnyLocalTrustState(root);
|
|
435
|
+
const floor = floorAt(root);
|
|
436
|
+
const prior = priorAccepted(root);
|
|
437
|
+
const deviceId = `device:${(deps.machineKey ?? machine_identity_1.getPhysicalMachineKey)()}`;
|
|
438
|
+
if (!/^device:[A-Za-z0-9._-]{8,120}$/.test(deviceId))
|
|
439
|
+
throw new Error("invalid_local_machine_identity");
|
|
440
|
+
const key = await deps.native.keyInfo?.() ?? await deps.native.ensureKey();
|
|
441
|
+
const jwk = exactJwk(key.publicJwk);
|
|
442
|
+
if (key.keyThumbprint !== thumbprint(jwk))
|
|
443
|
+
throw new Error("bootstrap_key_thumbprint_mismatch");
|
|
444
|
+
const enrolled = (await requestJson(deps, `${issuer}/api/v1/me/agent-access/device-keys/enroll`, { method: "POST", body: JSON.stringify({ deviceId, publicJwk: (0, canonical_json_1.canonicalJsonString)(jwk) }) }, true)).data.deviceKey;
|
|
445
|
+
const keyId = safeId(enrolled?.keyId, /^dak_[A-Za-z0-9]{32}$/, "bootstrap_key_enroll_invalid");
|
|
446
|
+
if (enrolled.keyThumbprint !== key.keyThumbprint || !Number.isInteger(enrolled.generation) || Number(enrolled.generation) < 1 || enrolled.status !== "active")
|
|
447
|
+
throw new Error("bootstrap_key_enroll_invalid");
|
|
448
|
+
const app = (await requestJson(deps, `${issuer}/api/v1/me/agent-access/applications/register`, { method: "POST", body: JSON.stringify({ applicationId: exports.CONNECTED_CONTEXT_APPLICATION_ID, runtime: "native_helper", runtimeBinding: exports.CONNECTED_CONTEXT_RUNTIME_BINDING, allowedOrigins: ["https://www.you.md"] }) }, true)).data.application;
|
|
449
|
+
if (app?.applicationId !== exports.CONNECTED_CONTEXT_APPLICATION_ID || app.runtimeBinding !== exports.CONNECTED_CONTEXT_RUNTIME_BINDING || !Number.isInteger(app.generation) || app.status !== "active")
|
|
450
|
+
throw new Error("bootstrap_application_register_invalid");
|
|
451
|
+
const prepared = (await requestJson(deps, `${issuer}/api/v1/me/agent-access/bootstrap/prepare`, { method: "POST", body: JSON.stringify({ deviceId, keyId, applicationId: exports.CONNECTED_CONTEXT_APPLICATION_ID, audience: exports.CONNECTED_CONTEXT_AUDIENCE, requestedScopes: [...exports.CONNECTED_CONTEXT_SCOPES] }) }, true)).data;
|
|
452
|
+
const rawManifest = validateManifestShape(prepared.manifest);
|
|
453
|
+
const rawSignature = rawManifest.signature;
|
|
454
|
+
const signingKeyId = safeId(rawSignature.keyId, /^key:[A-Za-z0-9._-]{3,128}$/, "bootstrap_signature_invalid");
|
|
455
|
+
const jwks = (await requestJson(deps, `${issuer}/api/v1/agent-access/bootstrap/jwks`, { method: "GET" })).data;
|
|
456
|
+
const issuerKey = parseJwks(jwks, signingKeyId);
|
|
457
|
+
const verified = verifyManifest(rawManifest, prepared.manifestDigest, issuerKey, { issuer, deviceId, keyThumbprint: key.keyThumbprint, now: clock, floor });
|
|
458
|
+
const rollback = verified.manifest.rollback;
|
|
459
|
+
const proposedDigest = String(prepared.manifestDigest);
|
|
460
|
+
if (prior) {
|
|
461
|
+
if (verified.generation < prior.generation)
|
|
462
|
+
throw new Error("bootstrap_manifest_rollback");
|
|
463
|
+
if (verified.generation === prior.generation && proposedDigest !== prior.manifestDigest)
|
|
464
|
+
throw new Error("bootstrap_manifest_generation_collision");
|
|
465
|
+
if (verified.generation > prior.generation + 1)
|
|
466
|
+
throw new Error("bootstrap_manifest_chain_gap");
|
|
467
|
+
if (verified.generation === prior.generation + 1 && rollback.previousManifestHash !== prior.manifestDigest)
|
|
468
|
+
throw new Error("bootstrap_manifest_chain_discontinuous");
|
|
469
|
+
}
|
|
470
|
+
else if (verified.generation !== 1 || rollback.previousManifestHash !== null) {
|
|
471
|
+
if (!options.trustCurrentGeneration)
|
|
472
|
+
throw new Error("bootstrap_manifest_trust_reset_required");
|
|
473
|
+
if (hadLocalTrustState)
|
|
474
|
+
throw new Error("bootstrap_manifest_trust_reset_denied");
|
|
475
|
+
const listed = (await requestJson(deps, `${issuer}/api/v1/me/agent-access/bootstrap/manifests`, { method: "GET" }, true)).data.manifests;
|
|
476
|
+
const exact = Array.isArray(listed) ? listed.find((entry) => entry && typeof entry === "object" && entry.manifestId === verified.manifest.manifestId) : undefined;
|
|
477
|
+
if (!exact || exact.generation !== verified.generation || !["active", "consumed"].includes(String(exact.status)) || exact.artifactId !== verified.artifact.artifactRef || exact.audience !== exports.CONNECTED_CONTEXT_AUDIENCE || JSON.stringify(exact.scopes) !== JSON.stringify(exports.CONNECTED_CONTEXT_SCOPES) || Number(exact.expiresAt) <= now(deps))
|
|
478
|
+
throw new Error("bootstrap_manifest_trust_reset_denied");
|
|
479
|
+
}
|
|
480
|
+
const exchange = verified.bootstrap.exchange;
|
|
481
|
+
const nonce = String(exchange.nonceRef);
|
|
482
|
+
const tokenHtu = `${issuer}/api/v1/agent-access/bootstrap/token`;
|
|
483
|
+
const exchangeProof = await deps.native.signExchange({ nonce });
|
|
484
|
+
verifyDpopResult(exchangeProof, { jwk, thumbprint: key.keyThumbprint, htu: tokenHtu, htm: "POST", nonce, now: now(deps) });
|
|
485
|
+
const tokenData = (await requestJson(deps, tokenHtu, { method: "POST", body: JSON.stringify({ nonce }), headers: { DPoP: exchangeProof.dpopProof } })).data;
|
|
486
|
+
const accessToken = typeof tokenData.access_token === "string" && /^dpop_[A-Za-z0-9_-]{32,256}$/.test(tokenData.access_token) ? tokenData.access_token : undefined;
|
|
487
|
+
if (!accessToken || tokenData.token_type !== "DPoP" || tokenData.scope !== exports.CONNECTED_CONTEXT_SCOPES[0] || tokenData.cnf?.jkt !== key.keyThumbprint)
|
|
488
|
+
throw new Error("bootstrap_token_response_invalid");
|
|
489
|
+
const artifactId = String(verified.artifact.artifactRef);
|
|
490
|
+
const fetchUri = String(verified.bootstrap.fetchUri);
|
|
491
|
+
const ath = b64urlHash(accessToken);
|
|
492
|
+
const artifactProof = await deps.native.signArtifact({ artifactId, accessTokenDigest: ath });
|
|
493
|
+
verifyDpopResult(artifactProof, { jwk, thumbprint: key.keyThumbprint, htu: fetchUri, htm: "GET", ath, now: now(deps) });
|
|
494
|
+
const artifactResponse = await deps.fetch(fetchUri, { method: "GET", headers: { Accept: "text/markdown", Authorization: `DPoP ${accessToken}`, DPoP: artifactProof.dpopProof }, redirect: "error" });
|
|
495
|
+
if (!artifactResponse.ok)
|
|
496
|
+
throw new Error(`connected_context_http_${artifactResponse.status}`);
|
|
497
|
+
const bytes = new Uint8Array(await artifactResponse.arrayBuffer());
|
|
498
|
+
if (bytes.byteLength < 1 || bytes.byteLength > 512 * 1024)
|
|
499
|
+
throw new Error("bootstrap_artifact_size_invalid");
|
|
500
|
+
const artifactDigest = hashBytes(bytes);
|
|
501
|
+
if (artifactDigest !== verified.artifact.digest || artifactDigest !== artifactResponse.headers.get("x-content-digest"))
|
|
502
|
+
throw new Error("bootstrap_artifact_digest_invalid");
|
|
503
|
+
let context;
|
|
504
|
+
try {
|
|
505
|
+
context = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
506
|
+
}
|
|
507
|
+
catch {
|
|
508
|
+
throw new Error("bootstrap_artifact_utf8_invalid");
|
|
509
|
+
}
|
|
510
|
+
if (SECRET_CANARY.test(context))
|
|
511
|
+
throw new Error("bootstrap_artifact_forbidden_material");
|
|
512
|
+
const metadata = { schemaVersion: "you-md/connected-context-snapshot/v1", storedAt: new Date(now(deps)).toISOString(), manifestDigest: String(prepared.manifestDigest), artifactDigest, issuerKey: { kid: signingKeyId, ...issuerKey, alg: "EdDSA", use: "sig" }, devicePublicJwk: jwk, manifest: verified.manifest, keyId, applicationId: exports.CONNECTED_CONTEXT_APPLICATION_ID, deviceId, generation: verified.generation, expiresAt: String(verified.manifest.expiresAt), secretValuesExposed: false };
|
|
513
|
+
persistSnapshot(root, metadata, context, deps.randomBytes ?? crypto.randomBytes);
|
|
514
|
+
return { connected: true, state: "ready", manifestId: String(verified.manifest.manifestId), generation: verified.generation, expiresAt: metadata.expiresAt, artifactDigest, authorityGranted: false, secretValuesExposed: false };
|
|
515
|
+
}
|
|
516
|
+
function getConnectedContextStatus(deps = {}) {
|
|
517
|
+
const root = connectedRoot(deps.homedir);
|
|
518
|
+
try {
|
|
519
|
+
const marker = markerAt(root);
|
|
520
|
+
const clock = now(deps);
|
|
521
|
+
const loaded = loadCurrent(root, clock);
|
|
522
|
+
const manifestId = String(loaded.metadata.manifest.manifestId);
|
|
523
|
+
if (marker?.manifestId === manifestId)
|
|
524
|
+
return { connected: false, state: "revoked", manifestId, generation: loaded.metadata.generation, expiresAt: loaded.metadata.expiresAt, authorityGranted: false, secretValuesExposed: false };
|
|
525
|
+
return { connected: true, state: "ready", manifestId, generation: loaded.metadata.generation, expiresAt: loaded.metadata.expiresAt, artifactDigest: loaded.metadata.artifactDigest, recovered: loaded.recovered || undefined, authorityGranted: false, secretValuesExposed: false };
|
|
526
|
+
}
|
|
527
|
+
catch (error) {
|
|
528
|
+
const code = error instanceof Error ? error.message : "connected_context_invalid";
|
|
529
|
+
return { connected: false, state: code.includes("missing") ? "missing" : code.includes("expired") ? "expired" : "invalid", authorityGranted: false, secretValuesExposed: false };
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
async function verifyConnectedContext(deps) {
|
|
533
|
+
const root = connectedRoot(deps.homedir);
|
|
534
|
+
const loaded = loadCurrent(root, now(deps));
|
|
535
|
+
const manifest = loaded.metadata.manifest;
|
|
536
|
+
const signature = manifest.signature;
|
|
537
|
+
const keyId = String(signature.keyId);
|
|
538
|
+
const issuerKey = parseJwks((await requestJson(deps, `${PROD_ISSUER}/api/v1/agent-access/bootstrap/jwks`, { method: "GET" })).data, keyId);
|
|
539
|
+
const storedKey = loaded.metadata.issuerKey;
|
|
540
|
+
if ((0, canonical_json_1.canonicalJsonString)(issuerKey) !== (0, canonical_json_1.canonicalJsonString)({ kty: storedKey.kty, crv: storedKey.crv, x: storedKey.x }))
|
|
541
|
+
throw new Error("connected_context_issuer_key_changed");
|
|
542
|
+
verifySignature(without(manifest, "signature"), manifest.signature, issuerKey, keyId, "connected_context_signature_invalid");
|
|
543
|
+
const listed = (await requestJson(deps, `${PROD_ISSUER}/api/v1/me/agent-access/bootstrap/manifests`, { method: "GET" }, true)).data.manifests;
|
|
544
|
+
const exact = Array.isArray(listed) ? listed.find((entry) => entry && typeof entry === "object" && entry.manifestId === manifest.manifestId) : undefined;
|
|
545
|
+
if (!exact || !["active", "consumed"].includes(String(exact.status)) || exact.generation !== loaded.metadata.generation || exact.artifactId !== manifest.bootstrap.contextArtifactRef || exact.audience !== exports.CONNECTED_CONTEXT_AUDIENCE || JSON.stringify(exact.scopes) !== JSON.stringify(exports.CONNECTED_CONTEXT_SCOPES) || Number(exact.expiresAt) <= now(deps) || exact.dependenciesActive !== true) {
|
|
546
|
+
ensureRoot(root);
|
|
547
|
+
atomicJson(path.join(root, "revoked.json"), { manifestId: manifest.manifestId, revokedAt: new Date(now(deps)).toISOString(), reason: "remote_manifest_not_active", applicationRevoked: false, keyRevoked: false }, deps.randomBytes ?? crypto.randomBytes);
|
|
548
|
+
throw new Error("connected_context_revoked");
|
|
549
|
+
}
|
|
550
|
+
return getConnectedContextStatus(deps);
|
|
551
|
+
}
|
|
552
|
+
async function revokeConnectedContext(deps, options = {}) {
|
|
553
|
+
const root = connectedRoot(deps.homedir);
|
|
554
|
+
const loaded = loadCurrent(root, now(deps));
|
|
555
|
+
const manifestId = options.manifestId ?? String(loaded.metadata.manifest.manifestId);
|
|
556
|
+
if (manifestId !== loaded.metadata.manifest.manifestId)
|
|
557
|
+
throw new Error("connected_context_manifest_mismatch");
|
|
558
|
+
const manifestAck = (await requestJson(deps, `${PROD_ISSUER}/api/v1/me/agent-access/bootstrap/manifests/revoke`, { method: "POST", body: JSON.stringify({ manifestId }) }, true)).data;
|
|
559
|
+
if (manifestAck.manifestId !== manifestId || manifestAck.revoked !== true || manifestAck.authorityGranted !== false || manifestAck.secretValuesExposed !== false)
|
|
560
|
+
throw new Error("connected_context_revoke_ack_invalid");
|
|
561
|
+
const random = deps.randomBytes ?? crypto.randomBytes;
|
|
562
|
+
ensureRoot(root);
|
|
563
|
+
const marker = { manifestId, revokedAt: new Date(now(deps)).toISOString(), applicationRevoked: false, keyRevoked: false };
|
|
564
|
+
// The local read path must fail closed as soon as the manifest revoke lands,
|
|
565
|
+
// even when a separately requested app/key revocation later fails.
|
|
566
|
+
atomicJson(path.join(root, "revoked.json"), marker, random);
|
|
567
|
+
if (options.revokeApplication) {
|
|
568
|
+
const ack = (await requestJson(deps, `${PROD_ISSUER}/api/v1/me/agent-access/applications/revoke`, { method: "POST", body: JSON.stringify({ applicationId: exports.CONNECTED_CONTEXT_APPLICATION_ID }) }, true)).data.application;
|
|
569
|
+
if (ack?.applicationId !== exports.CONNECTED_CONTEXT_APPLICATION_ID || ack.status !== "revoked")
|
|
570
|
+
throw new Error("connected_context_revoke_ack_invalid");
|
|
571
|
+
marker.applicationRevoked = true;
|
|
572
|
+
atomicJson(path.join(root, "revoked.json"), marker, random);
|
|
573
|
+
}
|
|
574
|
+
if (options.revokeKey) {
|
|
575
|
+
const listed = (await requestJson(deps, `${PROD_ISSUER}/api/v1/me/agent-access/device-keys`, { method: "GET" }, true)).data.deviceKeys;
|
|
576
|
+
const expectedThumbprint = thumbprint(exactJwk(loaded.metadata.devicePublicJwk));
|
|
577
|
+
const exact = Array.isArray(listed) ? listed.find((entry) => entry && typeof entry === "object" && entry.keyId === loaded.metadata.keyId) : undefined;
|
|
578
|
+
if (!exact || exact.deviceId !== loaded.metadata.deviceId || exact.keyThumbprint !== expectedThumbprint || exact.status !== "active")
|
|
579
|
+
throw new Error("connected_context_key_binding_invalid");
|
|
580
|
+
const ack = (await requestJson(deps, `${PROD_ISSUER}/api/v1/me/agent-access/device-keys/revoke`, { method: "POST", body: JSON.stringify({ keyId: exact.keyId }) }, true)).data.deviceKey;
|
|
581
|
+
if (ack?.keyId !== exact.keyId || ack.status !== "revoked")
|
|
582
|
+
throw new Error("connected_context_revoke_ack_invalid");
|
|
583
|
+
marker.keyRevoked = true;
|
|
584
|
+
atomicJson(path.join(root, "revoked.json"), marker, random);
|
|
585
|
+
}
|
|
586
|
+
return getConnectedContextStatus(deps);
|
|
587
|
+
}
|
|
588
|
+
function readConnectedContextResource(deps = {}) {
|
|
589
|
+
const root = connectedRoot(deps.homedir);
|
|
590
|
+
const loaded = loadCurrent(root, now(deps));
|
|
591
|
+
if (markerAt(root)?.manifestId === loaded.metadata.manifest.manifestId)
|
|
592
|
+
throw new Error("connected_context_revoked");
|
|
593
|
+
return loaded.context;
|
|
594
|
+
}
|
|
595
|
+
async function listConnectedContextMcpResource(deps) {
|
|
596
|
+
try {
|
|
597
|
+
await verifyConnectedContext(deps);
|
|
598
|
+
return { uri: exports.CONNECTED_CONTEXT_RESOURCE_URI, name: "connected-context/current", description: "Locally verified signed owner context. Read-only; every MCP discovery/read rechecks authoritative owner manifest status and fails closed offline.", mimeType: "text/markdown" };
|
|
599
|
+
}
|
|
600
|
+
catch {
|
|
601
|
+
return null;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
async function readConnectedContextMcpResource(uri, deps) {
|
|
605
|
+
if (uri !== exports.CONNECTED_CONTEXT_RESOURCE_URI)
|
|
606
|
+
throw new Error("connected_context_resource_unknown");
|
|
607
|
+
await verifyConnectedContext(deps);
|
|
608
|
+
return { contents: [{ uri, mimeType: "text/markdown", text: readConnectedContextResource(deps) }] };
|
|
609
|
+
}
|
|
610
|
+
//# sourceMappingURL=connected-context.js.map
|