@suveren/gateway 0.26.0 → 0.27.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/control-plane/index.mjs +294 -19
- package/dist/mcp-server/http.mjs +194 -40
- package/dist/ui/assets/index-CIR3mXNv.js +120 -0
- package/dist/ui/assets/index-DeEzJCqI.css +1 -0
- package/dist/ui/index.html +2 -2
- package/package.json +1 -1
- package/dist/ui/assets/index-46UkxmPE.css +0 -1
- package/dist/ui/assets/index-BC5hwcsC.js +0 -120
|
@@ -3578,6 +3578,106 @@ function createEvidenceExportRouter(spUrl, vault2, dataDir, lockAsTlsMismatch2)
|
|
|
3578
3578
|
// src/routes/proposal-preview.ts
|
|
3579
3579
|
import { Router as Router10 } from "express";
|
|
3580
3580
|
|
|
3581
|
+
// src/lib/content-crypto.ts
|
|
3582
|
+
import { createHash as createHash4 } from "crypto";
|
|
3583
|
+
import { canonicalize as canonicalize2 } from "@hap/core";
|
|
3584
|
+
var CONTENT_ALG = "HPKE-X25519-HKDF-SHA256-AES256GCM";
|
|
3585
|
+
function computeProposalContentHash(toolArgs) {
|
|
3586
|
+
return `sha256:${createHash4("sha256").update(canonicalize2(toolArgs)).digest("hex")}`;
|
|
3587
|
+
}
|
|
3588
|
+
function toB64(bytes) {
|
|
3589
|
+
return Buffer.from(bytes).toString("base64");
|
|
3590
|
+
}
|
|
3591
|
+
function keysToWire(encryptedKeys) {
|
|
3592
|
+
const keys = {};
|
|
3593
|
+
for (const [userId, wrap] of Object.entries(encryptedKeys)) {
|
|
3594
|
+
keys[userId] = { ct: toB64(wrap.ct), enc: toB64(wrap.enc) };
|
|
3595
|
+
}
|
|
3596
|
+
return keys;
|
|
3597
|
+
}
|
|
3598
|
+
async function fetchUserPubkey(userId, deps) {
|
|
3599
|
+
const pin = readPairing(deps.dataDir);
|
|
3600
|
+
const pinning = {
|
|
3601
|
+
enforce: resolvePinTls(deps.dataDir),
|
|
3602
|
+
pinnedSpkiHex: pin && pin.asUrl === deps.spUrl ? pin.tlsSpkiPinHex : void 0
|
|
3603
|
+
};
|
|
3604
|
+
try {
|
|
3605
|
+
const { res } = await fetchAs(
|
|
3606
|
+
`${deps.spUrl}/api/users/${encodeURIComponent(userId)}/pubkey`,
|
|
3607
|
+
{ headers: { Cookie: deps.cookie }, signal: AbortSignal.timeout(5e3) },
|
|
3608
|
+
pinning
|
|
3609
|
+
);
|
|
3610
|
+
if (!res.ok) return null;
|
|
3611
|
+
const data = await res.json().catch(() => ({}));
|
|
3612
|
+
return typeof data.pubkey === "string" ? data.pubkey : null;
|
|
3613
|
+
} catch {
|
|
3614
|
+
return null;
|
|
3615
|
+
}
|
|
3616
|
+
}
|
|
3617
|
+
async function resolveRecipientPubkeys(recipientUserIds, deps) {
|
|
3618
|
+
const ids = Array.from(/* @__PURE__ */ new Set([deps.selfUserId, ...recipientUserIds]));
|
|
3619
|
+
const resolved = [];
|
|
3620
|
+
const unreadableFor = [];
|
|
3621
|
+
for (const userId of ids) {
|
|
3622
|
+
const pubkeyB64 = await fetchUserPubkey(userId, deps);
|
|
3623
|
+
if (!pubkeyB64) {
|
|
3624
|
+
unreadableFor.push(userId);
|
|
3625
|
+
continue;
|
|
3626
|
+
}
|
|
3627
|
+
try {
|
|
3628
|
+
const bytes = new Uint8Array(Buffer.from(pubkeyB64, "base64"));
|
|
3629
|
+
if (bytes.length !== 32) {
|
|
3630
|
+
unreadableFor.push(userId);
|
|
3631
|
+
continue;
|
|
3632
|
+
}
|
|
3633
|
+
resolved.push({ userId, publicKey: bytes });
|
|
3634
|
+
} catch {
|
|
3635
|
+
unreadableFor.push(userId);
|
|
3636
|
+
}
|
|
3637
|
+
}
|
|
3638
|
+
return { resolved, unreadableFor };
|
|
3639
|
+
}
|
|
3640
|
+
async function encryptProposalContent(toolArgs, recipientUserIds, deps) {
|
|
3641
|
+
const canonical = canonicalize2(toolArgs);
|
|
3642
|
+
const hash = computeProposalContentHash(toolArgs);
|
|
3643
|
+
const { resolved, unreadableFor } = await resolveRecipientPubkeys(recipientUserIds, deps);
|
|
3644
|
+
if (resolved.length === 0) {
|
|
3645
|
+
throw new Error("No recipient (including the submitter) has a registered end-to-end key \u2014 cannot encrypt this proposal.");
|
|
3646
|
+
}
|
|
3647
|
+
const encrypted = await encryptForRecipients(canonical, resolved);
|
|
3648
|
+
return {
|
|
3649
|
+
content: { v: 1, alg: CONTENT_ALG, ciphertext: toB64(encrypted.intentCiphertext), keys: keysToWire(encrypted.encryptedKeys), hash },
|
|
3650
|
+
unreadableFor
|
|
3651
|
+
};
|
|
3652
|
+
}
|
|
3653
|
+
async function encryptProposalNote(note, recipientUserIds, deps) {
|
|
3654
|
+
const { resolved, unreadableFor } = await resolveRecipientPubkeys(recipientUserIds, deps);
|
|
3655
|
+
if (resolved.length === 0) {
|
|
3656
|
+
throw new Error("No recipient (including the submitter) has a registered end-to-end key \u2014 cannot encrypt this note.");
|
|
3657
|
+
}
|
|
3658
|
+
const encrypted = await encryptForRecipients(note, resolved);
|
|
3659
|
+
return {
|
|
3660
|
+
noteEncrypted: { ciphertext: toB64(encrypted.intentCiphertext), keys: keysToWire(encrypted.encryptedKeys) },
|
|
3661
|
+
unreadableFor
|
|
3662
|
+
};
|
|
3663
|
+
}
|
|
3664
|
+
async function decryptProposalContent(wire, myUserId, myPrivateKey) {
|
|
3665
|
+
const wrap = wire.keys[myUserId];
|
|
3666
|
+
if (!wrap) {
|
|
3667
|
+
throw new Error("No key wrap for this account in this content \u2014 this device cannot read it.");
|
|
3668
|
+
}
|
|
3669
|
+
const encrypted = {
|
|
3670
|
+
intentCiphertext: new Uint8Array(Buffer.from(wire.ciphertext, "base64")),
|
|
3671
|
+
encryptedKeys: {
|
|
3672
|
+
[myUserId]: {
|
|
3673
|
+
ct: new Uint8Array(Buffer.from(wrap.ct, "base64")),
|
|
3674
|
+
enc: new Uint8Array(Buffer.from(wrap.enc, "base64"))
|
|
3675
|
+
}
|
|
3676
|
+
}
|
|
3677
|
+
};
|
|
3678
|
+
return decryptIntent(encrypted, myUserId, myPrivateKey);
|
|
3679
|
+
}
|
|
3680
|
+
|
|
3581
3681
|
// src/lib/execution-journal-reader.ts
|
|
3582
3682
|
import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
|
|
3583
3683
|
import { join as join10 } from "path";
|
|
@@ -3644,8 +3744,25 @@ function createProposalStatusRouter(spUrl, vault2, dataDir, lockAsTlsMismatch2)
|
|
|
3644
3744
|
res.status(502).json({ error: `Authority Server unreachable: ${err instanceof Error ? err.message : String(err)}` });
|
|
3645
3745
|
return;
|
|
3646
3746
|
}
|
|
3747
|
+
let toolArgs;
|
|
3748
|
+
if (proposal.toolArgs) {
|
|
3749
|
+
toolArgs = proposal.toolArgs;
|
|
3750
|
+
} else if (proposal.content) {
|
|
3751
|
+
try {
|
|
3752
|
+
const kp = await loadOrGenerateKeyPair(vault2);
|
|
3753
|
+
const me = vault2.getSessionUser();
|
|
3754
|
+
const plaintext = await decryptProposalContent(proposal.content, me?.id ?? "", kp.privateKey);
|
|
3755
|
+
toolArgs = JSON.parse(plaintext);
|
|
3756
|
+
} catch (err) {
|
|
3757
|
+
res.json({ status: "unavailable", reason: "connector_error", message: `Could not decrypt this proposal's content on this device: ${err instanceof Error ? err.message : String(err)}` });
|
|
3758
|
+
return;
|
|
3759
|
+
}
|
|
3760
|
+
} else {
|
|
3761
|
+
res.status(502).json({ error: `Proposal ${proposalId} carries neither tool_args nor content.` });
|
|
3762
|
+
return;
|
|
3763
|
+
}
|
|
3647
3764
|
try {
|
|
3648
|
-
const result = await getToolPreview({ proposalId, tool: proposal.tool, toolArgs
|
|
3765
|
+
const result = await getToolPreview({ proposalId, tool: proposal.tool, toolArgs });
|
|
3649
3766
|
if (result.status !== "ok") {
|
|
3650
3767
|
res.json(result);
|
|
3651
3768
|
return;
|
|
@@ -3691,7 +3808,7 @@ function createProposalStatusRouter(spUrl, vault2, dataDir, lockAsTlsMismatch2)
|
|
|
3691
3808
|
// src/routes/evidence.ts
|
|
3692
3809
|
import { Router as Router11 } from "express";
|
|
3693
3810
|
import { createPublicKey as createPublicKey2, verify as verifyEd25519 } from "crypto";
|
|
3694
|
-
import { verifyTicketSignature, encodeDidKey, canonicalize as
|
|
3811
|
+
import { verifyTicketSignature, encodeDidKey, canonicalize as canonicalize3 } from "@hap/core";
|
|
3695
3812
|
function issuerFromPublicKeyHex(publicKeyHex) {
|
|
3696
3813
|
return encodeDidKey(Buffer.from(publicKeyHex, "hex"));
|
|
3697
3814
|
}
|
|
@@ -3710,7 +3827,7 @@ function verifyPreV07TicketSignature(receipt, asPublicKeyHex) {
|
|
|
3710
3827
|
});
|
|
3711
3828
|
const sig = Buffer.from(signature, "base64");
|
|
3712
3829
|
if (sig.length !== 64) return false;
|
|
3713
|
-
return verifyEd25519(null, Buffer.from(
|
|
3830
|
+
return verifyEd25519(null, Buffer.from(canonicalize3(signed), "utf8"), key, sig);
|
|
3714
3831
|
} catch {
|
|
3715
3832
|
return false;
|
|
3716
3833
|
}
|
|
@@ -3922,6 +4039,161 @@ function createDecryptIntentRouter(vault2) {
|
|
|
3922
4039
|
return router;
|
|
3923
4040
|
}
|
|
3924
4041
|
|
|
4042
|
+
// src/routes/decrypt-proposal-content.ts
|
|
4043
|
+
import { Router as Router14 } from "express";
|
|
4044
|
+
function createDecryptProposalContentRouter(vault2) {
|
|
4045
|
+
const router = Router14();
|
|
4046
|
+
router.post("/", async (req, res) => {
|
|
4047
|
+
const { ciphertext, keys } = req.body;
|
|
4048
|
+
if (typeof ciphertext !== "string" || ciphertext.length === 0) {
|
|
4049
|
+
res.status(400).json({ error: "Missing or invalid field: ciphertext (base64 string)" });
|
|
4050
|
+
return;
|
|
4051
|
+
}
|
|
4052
|
+
if (typeof keys !== "object" || keys === null) {
|
|
4053
|
+
res.status(400).json({ error: "Missing or invalid field: keys (object)" });
|
|
4054
|
+
return;
|
|
4055
|
+
}
|
|
4056
|
+
const me = vault2.getSessionUser();
|
|
4057
|
+
if (!me) {
|
|
4058
|
+
res.status(401).json({ error: "No Authority Server session." });
|
|
4059
|
+
return;
|
|
4060
|
+
}
|
|
4061
|
+
try {
|
|
4062
|
+
const kp = await loadOrGenerateKeyPair(vault2);
|
|
4063
|
+
const plaintext = await decryptProposalContent(
|
|
4064
|
+
{ ciphertext, keys },
|
|
4065
|
+
me.id,
|
|
4066
|
+
kp.privateKey
|
|
4067
|
+
);
|
|
4068
|
+
res.json({ plaintext });
|
|
4069
|
+
} catch (err) {
|
|
4070
|
+
console.error("[Control Plane] decrypt-proposal-content error:", err);
|
|
4071
|
+
const msg = err instanceof Error ? err.message : "Decryption failed";
|
|
4072
|
+
res.status(msg.includes("No key wrap") ? 403 : 500).json({ error: msg });
|
|
4073
|
+
}
|
|
4074
|
+
});
|
|
4075
|
+
return router;
|
|
4076
|
+
}
|
|
4077
|
+
|
|
4078
|
+
// src/routes/encrypt-proposal-note.ts
|
|
4079
|
+
import { Router as Router15 } from "express";
|
|
4080
|
+
var MAX_NOTE_LENGTH = 500;
|
|
4081
|
+
function createEncryptProposalNoteRouter(vault2, spUrl, dataDir) {
|
|
4082
|
+
const router = Router15();
|
|
4083
|
+
router.post("/", async (req, res) => {
|
|
4084
|
+
const { note, recipientUserIds } = req.body;
|
|
4085
|
+
if (typeof note !== "string" || note.length === 0 || note.length > MAX_NOTE_LENGTH) {
|
|
4086
|
+
res.status(400).json({ error: `note must be a non-empty string up to ${MAX_NOTE_LENGTH} characters` });
|
|
4087
|
+
return;
|
|
4088
|
+
}
|
|
4089
|
+
if (!Array.isArray(recipientUserIds) || !recipientUserIds.every((id) => typeof id === "string")) {
|
|
4090
|
+
res.status(400).json({ error: "recipientUserIds must be a string array" });
|
|
4091
|
+
return;
|
|
4092
|
+
}
|
|
4093
|
+
const me = vault2.getSessionUser();
|
|
4094
|
+
const cookie = vault2.getSpCookie();
|
|
4095
|
+
if (!me || !cookie) {
|
|
4096
|
+
res.status(401).json({ error: "No Authority Server session." });
|
|
4097
|
+
return;
|
|
4098
|
+
}
|
|
4099
|
+
try {
|
|
4100
|
+
const { noteEncrypted, unreadableFor } = await encryptProposalNote(
|
|
4101
|
+
note,
|
|
4102
|
+
recipientUserIds,
|
|
4103
|
+
{ spUrl, cookie, dataDir, selfUserId: me.id }
|
|
4104
|
+
);
|
|
4105
|
+
res.json({ noteEncrypted, unreadableFor });
|
|
4106
|
+
} catch (err) {
|
|
4107
|
+
console.error("[Control Plane] encrypt-proposal-note error:", err);
|
|
4108
|
+
res.status(500).json({ error: err instanceof Error ? err.message : "Encryption failed" });
|
|
4109
|
+
}
|
|
4110
|
+
});
|
|
4111
|
+
return router;
|
|
4112
|
+
}
|
|
4113
|
+
|
|
4114
|
+
// src/routes/internal-content-crypto.ts
|
|
4115
|
+
import { Router as Router16 } from "express";
|
|
4116
|
+
import { timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
4117
|
+
var MAX_RECIPIENTS = 50;
|
|
4118
|
+
function secretMatches(provided, expected) {
|
|
4119
|
+
if (!expected || !provided) return false;
|
|
4120
|
+
const a = Buffer.from(provided);
|
|
4121
|
+
const b = Buffer.from(expected);
|
|
4122
|
+
return a.length === b.length && timingSafeEqual2(a, b);
|
|
4123
|
+
}
|
|
4124
|
+
function createInternalContentCryptoRouter(getSecret, vault2, spUrl, dataDir) {
|
|
4125
|
+
const router = Router16();
|
|
4126
|
+
router.post("/encrypt-content", async (req, res) => {
|
|
4127
|
+
if (!secretMatches(req.header("X-Internal-Secret") ?? void 0, getSecret())) {
|
|
4128
|
+
res.status(401).json({ error: "unauthorized" });
|
|
4129
|
+
return;
|
|
4130
|
+
}
|
|
4131
|
+
const { toolArgs, recipientUserIds } = req.body ?? {};
|
|
4132
|
+
if (typeof toolArgs !== "object" || toolArgs === null || Array.isArray(toolArgs)) {
|
|
4133
|
+
res.status(400).json({ error: "toolArgs must be an object" });
|
|
4134
|
+
return;
|
|
4135
|
+
}
|
|
4136
|
+
if (!Array.isArray(recipientUserIds) || !recipientUserIds.every((id) => typeof id === "string")) {
|
|
4137
|
+
res.status(400).json({ error: "recipientUserIds must be a string array" });
|
|
4138
|
+
return;
|
|
4139
|
+
}
|
|
4140
|
+
if (recipientUserIds.length > MAX_RECIPIENTS) {
|
|
4141
|
+
res.status(400).json({ error: `too many recipients (max ${MAX_RECIPIENTS})` });
|
|
4142
|
+
return;
|
|
4143
|
+
}
|
|
4144
|
+
const me = vault2.getSessionUser();
|
|
4145
|
+
const cookie = vault2.getSpCookie();
|
|
4146
|
+
if (!me || !cookie) {
|
|
4147
|
+
res.status(401).json({ error: "no Authority Server session" });
|
|
4148
|
+
return;
|
|
4149
|
+
}
|
|
4150
|
+
try {
|
|
4151
|
+
const { content, unreadableFor } = await encryptProposalContent(
|
|
4152
|
+
toolArgs,
|
|
4153
|
+
recipientUserIds,
|
|
4154
|
+
{ spUrl, cookie, dataDir, selfUserId: me.id }
|
|
4155
|
+
);
|
|
4156
|
+
res.json({ content, unreadableFor });
|
|
4157
|
+
} catch (err) {
|
|
4158
|
+
console.error("[Control Plane] /internal/encrypt-content failed:", err);
|
|
4159
|
+
res.status(500).json({ error: err instanceof Error ? err.message : "encryption failed" });
|
|
4160
|
+
}
|
|
4161
|
+
});
|
|
4162
|
+
router.post("/decrypt-content", async (req, res) => {
|
|
4163
|
+
if (!secretMatches(req.header("X-Internal-Secret") ?? void 0, getSecret())) {
|
|
4164
|
+
res.status(401).json({ error: "unauthorized" });
|
|
4165
|
+
return;
|
|
4166
|
+
}
|
|
4167
|
+
const { ciphertext, keys } = req.body ?? {};
|
|
4168
|
+
if (typeof ciphertext !== "string" || ciphertext.length === 0) {
|
|
4169
|
+
res.status(400).json({ error: "ciphertext (base64 string) is required" });
|
|
4170
|
+
return;
|
|
4171
|
+
}
|
|
4172
|
+
if (typeof keys !== "object" || keys === null) {
|
|
4173
|
+
res.status(400).json({ error: "keys (object) is required" });
|
|
4174
|
+
return;
|
|
4175
|
+
}
|
|
4176
|
+
const me = vault2.getSessionUser();
|
|
4177
|
+
if (!me) {
|
|
4178
|
+
res.status(401).json({ error: "no Authority Server session" });
|
|
4179
|
+
return;
|
|
4180
|
+
}
|
|
4181
|
+
try {
|
|
4182
|
+
const kp = await loadOrGenerateKeyPair(vault2);
|
|
4183
|
+
const plaintext = await decryptProposalContent(
|
|
4184
|
+
{ ciphertext, keys },
|
|
4185
|
+
me.id,
|
|
4186
|
+
kp.privateKey
|
|
4187
|
+
);
|
|
4188
|
+
res.json({ plaintext });
|
|
4189
|
+
} catch (err) {
|
|
4190
|
+
console.error("[Control Plane] /internal/decrypt-content failed:", err);
|
|
4191
|
+
res.status(500).json({ error: err instanceof Error ? err.message : "decryption failed" });
|
|
4192
|
+
}
|
|
4193
|
+
});
|
|
4194
|
+
return router;
|
|
4195
|
+
}
|
|
4196
|
+
|
|
3925
4197
|
// src/lib/update-checker.ts
|
|
3926
4198
|
import { execSync } from "child_process";
|
|
3927
4199
|
import { dirname as dirname6 } from "path";
|
|
@@ -4060,7 +4332,7 @@ data: ${data}
|
|
|
4060
4332
|
}
|
|
4061
4333
|
|
|
4062
4334
|
// src/routes/gateway-settings.ts
|
|
4063
|
-
import { Router as
|
|
4335
|
+
import { Router as Router17 } from "express";
|
|
4064
4336
|
|
|
4065
4337
|
// src/lib/gateway-settings.ts
|
|
4066
4338
|
import { readFileSync as readFileSync11, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6 } from "fs";
|
|
@@ -4096,7 +4368,7 @@ function writeSettings(patch, dataDir) {
|
|
|
4096
4368
|
|
|
4097
4369
|
// src/routes/gateway-settings.ts
|
|
4098
4370
|
function createGatewaySettingsRouter() {
|
|
4099
|
-
const router =
|
|
4371
|
+
const router = Router17();
|
|
4100
4372
|
router.get("/", (_req, res) => {
|
|
4101
4373
|
res.json(readSettings());
|
|
4102
4374
|
});
|
|
@@ -4119,7 +4391,7 @@ function createGatewaySettingsRouter() {
|
|
|
4119
4391
|
}
|
|
4120
4392
|
|
|
4121
4393
|
// src/routes/archived-mandates.ts
|
|
4122
|
-
import { Router as
|
|
4394
|
+
import { Router as Router18 } from "express";
|
|
4123
4395
|
|
|
4124
4396
|
// src/lib/archived-mandates-store.ts
|
|
4125
4397
|
import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, mkdirSync as mkdirSync7, renameSync } from "fs";
|
|
@@ -4162,7 +4434,7 @@ function unarchiveMandate(id, dataDir) {
|
|
|
4162
4434
|
|
|
4163
4435
|
// src/routes/archived-mandates.ts
|
|
4164
4436
|
function createArchivedMandatesRouter(dataDir) {
|
|
4165
|
-
const router =
|
|
4437
|
+
const router = Router18();
|
|
4166
4438
|
router.get("/", (_req, res) => {
|
|
4167
4439
|
res.json({ archived: readArchived(dataDir) });
|
|
4168
4440
|
});
|
|
@@ -4184,24 +4456,24 @@ function createArchivedMandatesRouter(dataDir) {
|
|
|
4184
4456
|
}
|
|
4185
4457
|
|
|
4186
4458
|
// src/routes/internal-events.ts
|
|
4187
|
-
import { Router as
|
|
4188
|
-
import { timingSafeEqual as
|
|
4459
|
+
import { Router as Router19 } from "express";
|
|
4460
|
+
import { timingSafeEqual as timingSafeEqual3 } from "crypto";
|
|
4189
4461
|
var ALLOWED = ["proposal-added", "action-approval-needed"];
|
|
4190
4462
|
var SESSION_EXPIRED = "session-expired";
|
|
4191
4463
|
var AS_KEY_MISMATCH = "as-key-mismatch";
|
|
4192
4464
|
var AS_TLS_MISMATCH = "as-tls-mismatch";
|
|
4193
|
-
function
|
|
4465
|
+
function secretMatches2(provided, expected) {
|
|
4194
4466
|
if (!expected) return false;
|
|
4195
4467
|
if (!provided) return false;
|
|
4196
4468
|
const a = Buffer.from(provided);
|
|
4197
4469
|
const b = Buffer.from(expected);
|
|
4198
4470
|
if (a.length !== b.length) return false;
|
|
4199
|
-
return
|
|
4471
|
+
return timingSafeEqual3(a, b);
|
|
4200
4472
|
}
|
|
4201
4473
|
function createInternalEventsRouter(getSecret, onSessionExpired, onAsKeyMismatch, onAsTlsMismatch) {
|
|
4202
|
-
const router =
|
|
4474
|
+
const router = Router19();
|
|
4203
4475
|
router.post("/event", (req, res) => {
|
|
4204
|
-
if (!
|
|
4476
|
+
if (!secretMatches2(req.headers["x-internal-secret"], getSecret())) {
|
|
4205
4477
|
res.status(401).json({ error: "Unauthorized" });
|
|
4206
4478
|
return;
|
|
4207
4479
|
}
|
|
@@ -4233,8 +4505,8 @@ function createInternalEventsRouter(getSecret, onSessionExpired, onAsKeyMismatch
|
|
|
4233
4505
|
}
|
|
4234
4506
|
|
|
4235
4507
|
// src/routes/internal-mandate.ts
|
|
4236
|
-
import { Router as
|
|
4237
|
-
import { timingSafeEqual as
|
|
4508
|
+
import { Router as Router20 } from "express";
|
|
4509
|
+
import { timingSafeEqual as timingSafeEqual4, randomUUID } from "crypto";
|
|
4238
4510
|
|
|
4239
4511
|
// src/lib/mandate-ceremony.ts
|
|
4240
4512
|
import {
|
|
@@ -4408,16 +4680,16 @@ async function createMandate(req, deps) {
|
|
|
4408
4680
|
}
|
|
4409
4681
|
|
|
4410
4682
|
// src/routes/internal-mandate.ts
|
|
4411
|
-
function
|
|
4683
|
+
function secretMatches3(provided, expected) {
|
|
4412
4684
|
if (!expected || !provided) return false;
|
|
4413
4685
|
const a = Buffer.from(provided);
|
|
4414
4686
|
const b = Buffer.from(expected);
|
|
4415
|
-
return a.length === b.length &&
|
|
4687
|
+
return a.length === b.length && timingSafeEqual4(a, b);
|
|
4416
4688
|
}
|
|
4417
4689
|
function createInternalMandateRouter(getSecret, deps) {
|
|
4418
|
-
const router =
|
|
4690
|
+
const router = Router20();
|
|
4419
4691
|
router.post("/mandate", async (req, res) => {
|
|
4420
|
-
if (!
|
|
4692
|
+
if (!secretMatches3(req.header("X-Internal-Secret") ?? void 0, getSecret())) {
|
|
4421
4693
|
res.status(401).json({ error: "unauthorized" });
|
|
4422
4694
|
return;
|
|
4423
4695
|
}
|
|
@@ -4954,6 +5226,7 @@ app.use("/internal", jsonParser, createInternalMandateRouter(() => internalSecre
|
|
|
4954
5226
|
encrypt: encryptIntentForRecipients,
|
|
4955
5227
|
deliverGateContent: (args) => pushGateContent(args)
|
|
4956
5228
|
})));
|
|
5229
|
+
app.use("/internal", jsonParser, createInternalContentCryptoRouter(() => internalSecret2, vault, SP_URL, DATA_DIR));
|
|
4957
5230
|
app.use("/vault", jsonParser, authGuard, createVaultRouter(vault));
|
|
4958
5231
|
app.use("/ai", jsonParser, authGuard, createAIRouter(vault));
|
|
4959
5232
|
app.use("/ai-prompts", jsonParser, authGuard, createAIPromptsRouter());
|
|
@@ -4967,6 +5240,8 @@ app.use("/proposal-status", authGuard, createProposalStatusRouter(SP_URL, vault,
|
|
|
4967
5240
|
app.use("/api/evidence", authGuard, createEvidenceRouter());
|
|
4968
5241
|
app.use("/api/report", jsonParser, authGuard, createReportRouter(() => RUNNING_VERSION));
|
|
4969
5242
|
app.use("/api/decrypt-intent", jsonParser, authGuard, createDecryptIntentRouter(vault));
|
|
5243
|
+
app.use("/api/decrypt-proposal-content", jsonParser, authGuard, createDecryptProposalContentRouter(vault));
|
|
5244
|
+
app.use("/api/encrypt-proposal-note", jsonParser, authGuard, createEncryptProposalNoteRouter(vault, SP_URL, DATA_DIR));
|
|
4970
5245
|
app.use("/api/approved-intents", jsonParser, authGuard, createApprovedIntentsRouter(vault));
|
|
4971
5246
|
app.put("/integrations/:id/read-policy", jsonParser, authGuard, async (req, res) => {
|
|
4972
5247
|
const id = req.params.id;
|