@desplega.ai/agent-swarm 1.165.0 → 1.166.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/{acp-adapter-w6zws6me.js → acp-adapter-yzfxn8vj.js} +4 -4
- package/dist/{actions-jna0b3wm.js → actions-qz3m692w.js} +20 -24
- package/dist/{amp-adapter-0eve81pz.js → amp-adapter-j1xbmsqz.js} +4 -4
- package/dist/{app-75hm5cfa.js → app-43vrdmv8.js} +6 -7
- package/dist/{assistant-yzd51j33.js → assistant-ertffmka.js} +21 -25
- package/dist/{boot-reembed-z158xq32.js → boot-reembed-998jtpdg.js} +9 -12
- package/dist/{boot-reembed-2ex2mcsh.js → boot-reembed-s7k7c6v2.js} +10 -12
- package/dist/boot-scrub-sweep-nepsk4t1.js +211 -0
- package/dist/{boot-seeds-2bmnh18p.js → boot-seeds-vvzs1zhk.js} +8 -8
- package/dist/{claude-adapter-jbj1mycz.js → claude-adapter-2zvmekrp.js} +9 -11
- package/dist/{claude-managed-adapter-17ggz09t.js → claude-managed-adapter-s49wbk8t.js} +3 -3
- package/dist/{claude-managed-setup-ptm1nzm2.js → claude-managed-setup-cvhqq8qn.js} +4 -4
- package/dist/{claude-sdk-session-9re1g7r2.js → claude-sdk-session-kkbxtw5q.js} +2618 -2600
- package/dist/{cli-k6xx3pve.js → cli-0nhdwxd5.js} +4 -4
- package/dist/{cli-n9ehc28t.js → cli-0qjcsxgz.js} +4 -4
- package/dist/{cli-zn2v3t8v.js → cli-3ddybbbx.js} +1 -1
- package/dist/{cli-5anb0ev3.js → cli-3qn4r334.js} +71 -14
- package/dist/{cli-1kzfvd1z.js → cli-4r1sjfc4.js} +9 -5
- package/dist/{cli-ba66wyhy.js → cli-66zh26sp.js} +6 -6
- package/dist/{cli-9xb89xca.js → cli-68r1j4sg.js} +6 -6
- package/dist/{cli-xnzrzets.js → cli-8kgvmkak.js} +2 -2
- package/dist/{cli-28smfvbn.js → cli-8t229cmr.js} +5 -5
- package/dist/{cli-9866bd07.js → cli-aqcey741.js} +1 -1
- package/dist/{cli-90v942q1.js → cli-cpfmqz3n.js} +8 -8
- package/dist/{cli-50eg13rj.js → cli-df56tqdw.js} +459 -282
- package/dist/{cli-8gpbwy09.js → cli-dw2q9f7g.js} +5 -5
- package/dist/{cli-0n4fasz0.js → cli-ecknp7p1.js} +15 -15
- package/dist/{cli-xb3c6aat.js → cli-f2n17zhp.js} +1 -1
- package/dist/{cli-ftry7975.js → cli-feh3dkah.js} +1 -1
- package/dist/{cli-amwhs8h2.js → cli-fn697bnt.js} +2 -2
- package/dist/{cli-prt9emct.js → cli-gt8azyet.js} +3 -3
- package/dist/{cli-59jn5twa.js → cli-h3x613ze.js} +2 -2
- package/dist/{cli-mmzwwptd.js → cli-hf5s9pgf.js} +1 -1
- package/dist/cli-j2jbr8af.js +516 -0
- package/dist/{cli-wfajtmf7.js → cli-jhm6hzjn.js} +593 -10
- package/dist/{cli-xtgtyafv.js → cli-jvj6a0cv.js} +190 -439
- package/dist/{cli-hpryr6em.js → cli-mb75wpxf.js} +1 -1
- package/dist/{cli-420bdgw0.js → cli-mv2520nm.js} +1 -1
- package/dist/{cli-pjxnr1jn.js → cli-mwegs4af.js} +3 -3
- package/dist/{cli-5b2fjjzh.js → cli-p4ztb80h.js} +2 -2
- package/dist/{cli-e7t4snpa.js → cli-rggdq8wq.js} +4 -4
- package/dist/{cli-bdv39kgj.js → cli-rsy933yz.js} +5 -5
- package/dist/{cli-gh5yqzpe.js → cli-v2a8x45d.js} +2 -2
- package/dist/{cli-bns5c8k9.js → cli-vzc73wet.js} +43 -47
- package/dist/cli-wt7ccy4r.js +2654 -0
- package/dist/{cli-j0pdrmwg.js → cli-x7kpr72a.js} +3 -3
- package/dist/{cli-x7afk5wm.js → cli-xvc3q9br.js} +1 -1
- package/dist/{cli-9s3h836g.js → cli-xvdgasgy.js} +1 -1
- package/dist/{cli-zvckv750.js → cli-y59h2pk9.js} +2 -2
- package/dist/{cli-qdv8kgz7.js → cli-yshaazs1.js} +13884 -3241
- package/dist/{cli-nfygw434.js → cli-ysnyhws9.js} +2 -2
- package/dist/{cli-fw6vdcq5.js → cli-zp67qg5x.js} +34 -1
- package/dist/{cli-ma3zvh39.js → cli-zy7vwsne.js} +6066 -5829
- package/dist/cli.js +24 -20
- package/dist/{codex-adapter-3qnxshje.js → codex-adapter-dw1wxqt8.js} +7 -7
- package/dist/{codex-login-brkxhhdg.js → codex-login-terbdsqq.js} +2 -2
- package/dist/{codex-session-runner-612wnapp.js → codex-session-runner-3cgb8y1g.js} +7 -7
- package/dist/{commands-2ybfcxkj.js → commands-mhxam6z2.js} +6 -6
- package/dist/{cursor-adapter-p3jgdehv.js → cursor-adapter-2qda3st7.js} +3 -3
- package/dist/{db-f4ans0jj.js → db-sjks418k.js} +8 -6
- package/dist/{devin-adapter-dhzt01ff.js → devin-adapter-bffjj5n4.js} +2 -2
- package/dist/{dispatcher-pk182apd.js → dispatcher-1es759rn.js} +18 -22
- package/dist/{dsh-adapter-7twaeps1.js → dsh-adapter-bek4cyw9.js} +3 -3
- package/dist/{e2b-y8t3jzyz.js → e2b-nd50eccg.js} +1 -1
- package/dist/{event-bus-w7ac4n3c.js → event-bus-vnfthafg.js} +2 -2
- package/dist/{grok-adapter-yt9pab3k.js → grok-adapter-ar9cw9jg.js} +5 -5
- package/dist/{handlers-g2wnr4t9.js → handlers-1gkbhktr.js} +21 -25
- package/dist/{hook-f7jt9vnm.js → hook-ds2zxq9v.js} +14 -16
- package/dist/{hook-bbdpnkb4.js → hook-vxa3tcsy.js} +8 -10
- package/dist/{http-w40q4w94.js → http-3h21z9md.js} +247 -164
- package/dist/{index-056j5kzn.js → index-9b65j1m5.js} +8 -8
- package/dist/{index-9bfssf54.js → index-bn6cy8ps.js} +18 -22
- package/dist/{index-cj1r9q83.js → index-dkfxpkmm.js} +19 -23
- package/dist/{index-7n70jwbb.js → index-f8xnqrye.js} +19 -21
- package/dist/{index-fdk1t4d6.js → index-n0rxa4ca.js} +11962 -9900
- package/dist/{index-k8xc083e.js → index-n99fycq7.js} +18 -22
- package/dist/{keepalive-1tj10tf4.js → keepalive-3xspa9jw.js} +11 -8
- package/dist/{lead-st0bg7v4.js → lead-6qphc124.js} +34 -38
- package/dist/{maintenance-nz71zats.js → maintenance-0teem1bb.js} +7 -9
- package/dist/{node-f0ykebbv.js → node-9sg7ajby.js} +25 -7
- package/dist/{oauth-refresh-sweep-aefzwvvq.js → oauth-refresh-sweep-sh9q25xp.js} +10 -7
- package/dist/{onboard-kgpr9mbn.js → onboard-54qmnqxe.js} +2 -2
- package/dist/{opencode-adapter-0c8e70dk.js → opencode-adapter-wjfa2wvd.js} +2 -2
- package/dist/{otel-impl-x8zp4y5a.js → otel-impl-ycqzp2a1.js} +2 -2
- package/dist/{pi-mono-adapter-t280ah5c.js → pi-mono-adapter-e77e9zst.js} +4 -4
- package/dist/{pricing-refresh-9e0q7cwk.js → pricing-refresh-atnkngqs.js} +7 -7
- package/dist/{rbac-roles-kcp6zge2.js → rbac-roles-9pjphh9q.js} +6 -6
- package/dist/{rbac-roles-790j3ga8.js → rbac-roles-j78wdksm.js} +6 -6
- package/dist/{render-v2-tch4nefj.js → render-v2-pxjs49fx.js} +6 -7
- package/dist/{setup-rcm303ns.js → setup-ymej0xtx.js} +2 -2
- package/dist/{work-objects-ctg7t70w.js → work-objects-3nh4ymzb.js} +22 -26
- package/dist/{worker-5dp0xm55.js → worker-qa9fkz5s.js} +34 -38
- package/dist/{x-v3yw66zs.js → x-ma4gnfgy.js} +2 -2
- package/openapi.json +5 -2
- package/package.json +9 -5
- package/src/be/automation-preflight.ts +9 -4
- package/src/be/boot-scrub-sweep.ts +302 -0
- package/src/be/crypto/key-bootstrap.ts +1 -1
- package/src/be/db/approvals.ts +15 -1
- package/src/be/db/codex-oauth-device-flows.ts +40 -0
- package/src/be/db/config-secrets.ts +31 -0
- package/src/be/db/runtime.ts +4 -0
- package/src/be/db/script-runs.ts +15 -0
- package/src/be/db/tasks/write.ts +1 -1
- package/src/be/db-queries/mcp-oauth.ts +5 -0
- package/src/be/db-queries/oauth.ts +77 -0
- package/src/be/db.ts +162 -59
- package/src/be/events.ts +17 -1
- package/src/be/memory/index-content.ts +2 -2
- package/src/be/memory/providers/sqlite-store.ts +63 -11
- package/src/be/memory/types.ts +14 -0
- package/src/be/migrations/201_workflow_replay_columns.sql +10 -0
- package/src/be/migrations/202_codex_oauth_device_flows.sql +24 -0
- package/src/be/scripts/db.ts +28 -4
- package/src/be/scripts/source-secrets.ts +37 -0
- package/src/be/scrub-json.ts +32 -0
- package/src/be/sealed-json.ts +59 -0
- package/src/be/secret-registry.ts +131 -0
- package/src/be/workflow-replay.ts +77 -0
- package/src/http/codex-oauth-device.ts +8 -10
- package/src/http/db-query-bounded.ts +3 -0
- package/src/http/db-query-shared.ts +107 -15
- package/src/http/db-query.ts +10 -2
- package/src/http/index.ts +23 -6
- package/src/http/script-runs.ts +9 -1
- package/src/http/scripts.ts +15 -0
- package/src/http/workflows.ts +3 -2
- package/src/oauth/mcp-wrapper.ts +1 -1
- package/src/providers/acp-adapter.ts +25 -11
- package/src/providers/acp-target-catalog.ts +35 -1
- package/src/providers/acp-targets.ts +69 -3
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/cursor-adapter.ts +1 -1
- package/src/script-workflows/supervisor.ts +11 -2
- package/src/scripts-runtime/eval-harness.ts +2 -12
- package/src/scripts-runtime/executors/quickjs-bundle.ts +152 -0
- package/src/scripts-runtime/executors/quickjs-prelude.ts +285 -0
- package/src/scripts-runtime/executors/quickjs-runner.ts +580 -0
- package/src/scripts-runtime/executors/quickjs-worker.ts +81 -0
- package/src/scripts-runtime/executors/quickjs.ts +196 -0
- package/src/scripts-runtime/executors/registry.ts +2 -0
- package/src/scripts-runtime/executors/sourcemap.ts +79 -0
- package/src/scripts-runtime/script-error-hints.ts +14 -0
- package/src/server.ts +3 -1
- package/src/tests/acp-adapter.test.ts +127 -2
- package/src/tests/boot-scrub-sweep.test.ts +390 -0
- package/src/tests/check-text-columns.test.ts +145 -0
- package/src/tests/claude-sdk-process.fixture.ts +13 -2
- package/src/tests/codex-oauth-device.test.ts +23 -37
- package/src/tests/db-query-denylist.test.ts +202 -0
- package/src/tests/db-task-read-contract.test.ts +1 -0
- package/src/tests/db-task-write-contract.test.ts +1 -0
- package/src/tests/entrypoint-acp-target-binary.test.ts +31 -1
- package/src/tests/events-scrub.test.ts +88 -0
- package/src/tests/fixtures/secret-corpus.ts +320 -0
- package/src/tests/script-connections.test.ts +5 -0
- package/src/tests/script-executor-conformance.test.ts +3 -0
- package/src/tests/script-executor-quickjs.test.ts +442 -0
- package/src/tests/script-executor-registry.test.ts +6 -1
- package/src/tests/script-runs-http.test.ts +88 -1
- package/src/tests/script-runs-scrub.test.ts +275 -0
- package/src/tests/scripts-http.test.ts +82 -1
- package/src/tests/scrubbed-text-12e.test.ts +289 -0
- package/src/tests/sealed-key-guard.test.ts +166 -0
- package/src/tests/secret-canary-sweep.test.ts +958 -0
- package/src/tests/secret-registry.test.ts +355 -0
- package/src/tests/secret-rules.test.ts +230 -0
- package/src/tests/secret-scrubber.test.ts +3 -2
- package/src/tests/workflow-replay.test.ts +518 -0
- package/src/tests/workflow-scrub.test.ts +242 -0
- package/src/tools/db-query.ts +1 -1
- package/src/tools/inject-learning.ts +1 -1
- package/src/tools/workflows/get-workflow-run.ts +3 -2
- package/src/types.ts +1 -1
- package/src/utils/secret-rules.generated.ts +2055 -0
- package/src/utils/secret-scrubber.ts +363 -29
- package/src/workflows/engine.ts +39 -4
- package/src/workflows/recovery.ts +10 -1
- package/src/workflows/resume.ts +62 -3
- package/src/workflows/retry-poller.ts +3 -0
- package/dist/boot-scrub-logs-246yn076.js +0 -99
- package/dist/cli-3a4m1aws.js +0 -572
- package/dist/cli-a87b04k8.js +0 -951
- package/dist/cli-cvk6yzw5.js +0 -40
- package/dist/cli-h1spv8v4.js +0 -427
- package/dist/cli-jqt9hrnz.js +0 -168
- package/dist/cli-yhmwmv0g.js +0 -9281
- package/src/be/boot-scrub-logs.ts +0 -132
|
@@ -116,7 +116,7 @@ export function resolveEncryptionKey(
|
|
|
116
116
|
|
|
117
117
|
if (!allowGenerate) {
|
|
118
118
|
throw new Error(
|
|
119
|
-
`Refusing to auto-generate ${KEY_FILENAME} for an existing database with encrypted secret rows. Restore ${ENV_KEY}, ${ENV_KEY_FILE}, or ${keyFilePath} before booting.`,
|
|
119
|
+
`Refusing to auto-generate ${KEY_FILENAME} for an existing database with encrypted secret rows or sealed replay values. Restore ${ENV_KEY}, ${ENV_KEY_FILE}, or ${keyFilePath} before booting.`,
|
|
120
120
|
);
|
|
121
121
|
}
|
|
122
122
|
|
package/src/be/db/approvals.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { type ScrubbedText, scrubObject, scrubSecrets } from "../../utils/secret-scrubber";
|
|
1
2
|
import { normalizeDate, normalizeDateRequired } from "../date-utils";
|
|
3
|
+
import { breaksJsonValidity } from "../scrub-json";
|
|
2
4
|
import { getDbClient } from "./runtime";
|
|
3
5
|
|
|
4
6
|
/**
|
|
@@ -126,7 +128,19 @@ export async function recordApprovalVotes(id: string, approvals: ApprovalVote[])
|
|
|
126
128
|
SET approvals = ?, updatedAt = ?
|
|
127
129
|
WHERE id = ? AND status = 'pending'
|
|
128
130
|
RETURNING id`,
|
|
129
|
-
[
|
|
131
|
+
[scrubApprovalVotesJson(approvals), new Date().toISOString(), id],
|
|
130
132
|
);
|
|
131
133
|
return row !== null && row !== undefined;
|
|
132
134
|
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Serializes votes for the `approvals` column with free-text answers scrubbed.
|
|
138
|
+
* Falls back to a per-value scrub when a redaction would break the JSON, so
|
|
139
|
+
* readers can always parse the column.
|
|
140
|
+
*/
|
|
141
|
+
export function scrubApprovalVotesJson(approvals: ApprovalVote[]): ScrubbedText {
|
|
142
|
+
const raw = JSON.stringify(approvals);
|
|
143
|
+
const scrubbed = scrubSecrets(raw);
|
|
144
|
+
if (!breaksJsonValidity(raw, scrubbed)) return scrubbed;
|
|
145
|
+
return JSON.stringify(scrubObject(approvals)) as ScrubbedText;
|
|
146
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { getDbClient } from "./runtime";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Encrypted Codex device-login flow state. Kept out of `kv_entries` so
|
|
5
|
+
* db-query can deny it by table. Returns null if missing or expired; an
|
|
6
|
+
* expired row is deleted inline.
|
|
7
|
+
*/
|
|
8
|
+
export async function getCodexDeviceFlow(
|
|
9
|
+
flowId: string,
|
|
10
|
+
): Promise<{ state: string; expiresAt: number } | null> {
|
|
11
|
+
const row = await getDbClient().get<{ state: string; expires_at: number }>(
|
|
12
|
+
"SELECT state, expires_at FROM codex_oauth_device_flows WHERE flow_id = ?",
|
|
13
|
+
[flowId],
|
|
14
|
+
);
|
|
15
|
+
if (!row) return null;
|
|
16
|
+
if (row.expires_at <= Date.now()) {
|
|
17
|
+
await getDbClient().run("DELETE FROM codex_oauth_device_flows WHERE flow_id = ?", [flowId]);
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
return { state: row.state, expiresAt: row.expires_at };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Upsert one device-login flow and drop every expired flow. */
|
|
24
|
+
export async function upsertCodexDeviceFlow(input: {
|
|
25
|
+
flowId: string;
|
|
26
|
+
state: string;
|
|
27
|
+
expiresAt: number;
|
|
28
|
+
}): Promise<void> {
|
|
29
|
+
const now = Date.now();
|
|
30
|
+
await getDbClient().run("DELETE FROM codex_oauth_device_flows WHERE expires_at <= ?", [now]);
|
|
31
|
+
await getDbClient().run(
|
|
32
|
+
`INSERT INTO codex_oauth_device_flows (flow_id, state, expires_at, created_at, updated_at)
|
|
33
|
+
VALUES (?, ?, ?, ?, ?)
|
|
34
|
+
ON CONFLICT(flow_id) DO UPDATE SET
|
|
35
|
+
state = excluded.state,
|
|
36
|
+
expires_at = excluded.expires_at,
|
|
37
|
+
updated_at = excluded.updated_at`,
|
|
38
|
+
[input.flowId, input.state, input.expiresAt, now, now],
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { decryptSecret, getEncryptionKey } from "../crypto";
|
|
2
|
+
import { isReservedConfigKey } from "../swarm-config-guard";
|
|
3
|
+
import { getDbClient } from "./runtime";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Every stored secret config value (all scopes, incl. derived
|
|
7
|
+
* `connection.<slug>.secret` rows), decrypted, for the secret registry
|
|
8
|
+
* (src/be/secret-registry.ts). Reserved env-only keys are skipped: their live
|
|
9
|
+
* value is in env. A row that fails to decrypt is counted, not thrown, so one
|
|
10
|
+
* bad row cannot hide the rest.
|
|
11
|
+
*/
|
|
12
|
+
export async function listStoredConfigSecrets(): Promise<{
|
|
13
|
+
secrets: { key: string; value: string }[];
|
|
14
|
+
failed: number;
|
|
15
|
+
}> {
|
|
16
|
+
const rows = await getDbClient().query<{ key: string; value: string; encrypted: number }>(
|
|
17
|
+
"SELECT key, value, encrypted FROM swarm_config WHERE isSecret = 1 OR encrypted = 1",
|
|
18
|
+
);
|
|
19
|
+
const secrets: { key: string; value: string }[] = [];
|
|
20
|
+
let failed = 0;
|
|
21
|
+
for (const row of rows) {
|
|
22
|
+
if (isReservedConfigKey(row.key)) continue;
|
|
23
|
+
try {
|
|
24
|
+
const value = row.encrypted === 1 ? decryptSecret(row.value, getEncryptionKey()) : row.value;
|
|
25
|
+
if (value) secrets.push({ key: row.key, value });
|
|
26
|
+
} catch {
|
|
27
|
+
failed++;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return { secrets, failed };
|
|
31
|
+
}
|
package/src/be/db/runtime.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { resolveEncryptionKey } from "../crypto";
|
|
|
7
7
|
import { createBunSqliteClient, type DbClient } from "../db-client";
|
|
8
8
|
import { runMigrations } from "../migrations/runner";
|
|
9
9
|
import { autoEncryptLegacyOAuthSecrets } from "../oauth-encryption-backfill";
|
|
10
|
+
import { sealedReplayExistsSql } from "../sealed-json";
|
|
10
11
|
|
|
11
12
|
let db: Database | null = null;
|
|
12
13
|
let sqliteVecAvailable = false;
|
|
@@ -233,6 +234,9 @@ export function initDb(dbPath = "./agent-swarm-db.sqlite"): Database {
|
|
|
233
234
|
WHERE clientSecretEncrypted = 1 AND clientSecret IS NOT NULL
|
|
234
235
|
UNION ALL
|
|
235
236
|
SELECT 1 FROM oauth_authorizations WHERE tokensEncrypted = 1
|
|
237
|
+
${sealedReplayExistsSql()
|
|
238
|
+
.map((arm) => `UNION ALL ${arm}`)
|
|
239
|
+
.join("\n ")}
|
|
236
240
|
) AS present`,
|
|
237
241
|
)
|
|
238
242
|
.get()?.present ?? 0) === 1;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { openSealedJson } from "../sealed-json";
|
|
2
|
+
import { getDbClient } from "./runtime";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The exact args a run executes with, or `undefined` when the run has no row.
|
|
6
|
+
* Only the supervisor may call this: everything else reads the redacted view
|
|
7
|
+
* on `ScriptRun.args`.
|
|
8
|
+
*/
|
|
9
|
+
export async function getScriptRunExecutionArgs(id: string): Promise<unknown> {
|
|
10
|
+
const row = await getDbClient().get<{ args: string }>(
|
|
11
|
+
"SELECT args FROM script_runs WHERE id = ?",
|
|
12
|
+
[id],
|
|
13
|
+
);
|
|
14
|
+
return row ? openSealedJson(row.args) : undefined;
|
|
15
|
+
}
|
package/src/be/db/tasks/write.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { scrubSecrets } from "../../utils/secret-scrubber";
|
|
|
2
2
|
import { decryptSecret, encryptSecret, getEncryptionKey } from "../crypto";
|
|
3
3
|
import { normalizeDateRequired } from "../date-utils";
|
|
4
4
|
import { getDbClient } from "../db";
|
|
5
|
+
import { oauthSecretName, registerStoredSecret } from "../secret-registry";
|
|
5
6
|
import {
|
|
6
7
|
type OAuthAuthorizationStatus,
|
|
7
8
|
updateAuthorizationTokens,
|
|
@@ -266,6 +267,10 @@ async function upsertMcpApp(input: {
|
|
|
266
267
|
input.dcrClientSecret == null || input.dcrClientSecret === ""
|
|
267
268
|
? (existing?.clientSecret ?? null)
|
|
268
269
|
: encryptSecret(input.dcrClientSecret, getEncryptionKey());
|
|
270
|
+
registerStoredSecret(
|
|
271
|
+
input.dcrClientSecret,
|
|
272
|
+
oauthSecretName(`mcp-${input.mcpServerId}`, "client_secret"),
|
|
273
|
+
);
|
|
269
274
|
// MCP OAuth applications are DCR-owned storage rows even when the client
|
|
270
275
|
// credentials were supplied manually or preregistered. Preserve that exact
|
|
271
276
|
// distinction in metadata for the legacy adapter boundary.
|
|
@@ -2,6 +2,7 @@ import type { OAuthApp, OAuthTokens } from "../../tracker/types";
|
|
|
2
2
|
import { decryptSecret, encryptSecret, getEncryptionKey } from "../crypto";
|
|
3
3
|
import { normalizeDateRequired } from "../date-utils";
|
|
4
4
|
import { getDbClient } from "../db";
|
|
5
|
+
import { oauthSecretName, registerStoredSecret } from "../secret-registry";
|
|
5
6
|
|
|
6
7
|
type OAuthAppRow = Omit<
|
|
7
8
|
OAuthApp,
|
|
@@ -241,6 +242,7 @@ async function writeOAuthApp(
|
|
|
241
242
|
const metadataProvided = data.metadata !== undefined;
|
|
242
243
|
const lifted = metadataProvided ? storageMetadata(data.metadata as string) : null;
|
|
243
244
|
const encryptedSecret = encryptSecret(data.clientSecret, getEncryptionKey());
|
|
245
|
+
registerStoredSecret(data.clientSecret, oauthSecretName(provider, "client_secret"));
|
|
244
246
|
const scopeSeparator =
|
|
245
247
|
data.scopeSeparator ?? existing?.scopeSeparator ?? (provider === "linear" ? "," : " ");
|
|
246
248
|
const rotation =
|
|
@@ -373,6 +375,79 @@ export async function getAuthorizationById(id: string): Promise<OAuthAuthorizati
|
|
|
373
375
|
return row ? normalizeAuthorization(row) : null;
|
|
374
376
|
}
|
|
375
377
|
|
|
378
|
+
/** Register an authorization's new plaintext tokens with the secret registry,
|
|
379
|
+
* named by the owning app's provider (`mcp-<serverId>` for MCP apps). */
|
|
380
|
+
async function registerAuthorizationSecrets(
|
|
381
|
+
appId: string,
|
|
382
|
+
accessToken: string,
|
|
383
|
+
refreshToken: string | null | undefined,
|
|
384
|
+
): Promise<void> {
|
|
385
|
+
const app = await getDbClient().get<{ provider: string }>(
|
|
386
|
+
"SELECT provider FROM oauth_apps WHERE id = ?",
|
|
387
|
+
[appId],
|
|
388
|
+
);
|
|
389
|
+
const provider = app?.provider ?? appId;
|
|
390
|
+
registerStoredSecret(accessToken, oauthSecretName(provider, "access_token"));
|
|
391
|
+
registerStoredSecret(refreshToken, oauthSecretName(provider, "refresh_token"));
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export type StoredOAuthSecretKind = "client_secret" | "access_token" | "refresh_token";
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Every stored OAuth credential, decrypted, for the secret registry
|
|
398
|
+
* (src/be/secret-registry.ts): client secrets of all apps (provider and
|
|
399
|
+
* MCP-managed) and the tokens of all authorizations, whatever their status.
|
|
400
|
+
* A row that fails to decrypt is counted, not thrown.
|
|
401
|
+
*/
|
|
402
|
+
export async function listStoredOAuthSecrets(): Promise<{
|
|
403
|
+
secrets: { provider: string; kind: StoredOAuthSecretKind; value: string }[];
|
|
404
|
+
failed: number;
|
|
405
|
+
}> {
|
|
406
|
+
const secrets: { provider: string; kind: StoredOAuthSecretKind; value: string }[] = [];
|
|
407
|
+
let failed = 0;
|
|
408
|
+
|
|
409
|
+
const apps = await getDbClient().query<OAuthAppRow>("SELECT * FROM oauth_apps");
|
|
410
|
+
for (const row of apps) {
|
|
411
|
+
try {
|
|
412
|
+
const app = normalizeOAuthApp(row);
|
|
413
|
+
if (app.clientSecret) {
|
|
414
|
+
secrets.push({ provider: app.provider, kind: "client_secret", value: app.clientSecret });
|
|
415
|
+
}
|
|
416
|
+
} catch {
|
|
417
|
+
failed++;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const authorizations = await getDbClient().query<OAuthAuthorizationRow & { provider: string }>(
|
|
422
|
+
`SELECT z.*, a.provider AS provider
|
|
423
|
+
FROM oauth_authorizations z
|
|
424
|
+
JOIN oauth_apps a ON a.id = z.appId`,
|
|
425
|
+
);
|
|
426
|
+
for (const row of authorizations) {
|
|
427
|
+
try {
|
|
428
|
+
const authorization = normalizeAuthorization(row);
|
|
429
|
+
if (authorization.accessToken) {
|
|
430
|
+
secrets.push({
|
|
431
|
+
provider: row.provider,
|
|
432
|
+
kind: "access_token",
|
|
433
|
+
value: authorization.accessToken,
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
if (authorization.refreshToken) {
|
|
437
|
+
secrets.push({
|
|
438
|
+
provider: row.provider,
|
|
439
|
+
kind: "refresh_token",
|
|
440
|
+
value: authorization.refreshToken,
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
} catch {
|
|
444
|
+
failed++;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return { secrets, failed };
|
|
449
|
+
}
|
|
450
|
+
|
|
376
451
|
export async function upsertAuthorization(data: {
|
|
377
452
|
id?: string;
|
|
378
453
|
appId: string;
|
|
@@ -402,6 +477,7 @@ export async function upsertAuthorization(data: {
|
|
|
402
477
|
: data.refreshToken == null
|
|
403
478
|
? null
|
|
404
479
|
: encryptSecret(data.refreshToken, key);
|
|
480
|
+
await registerAuthorizationSecrets(data.appId, data.accessToken, data.refreshToken);
|
|
405
481
|
|
|
406
482
|
if (existing) {
|
|
407
483
|
await getDbClient().run(
|
|
@@ -486,6 +562,7 @@ export async function updateAuthorizationTokens(
|
|
|
486
562
|
: data.refreshToken == null
|
|
487
563
|
? null
|
|
488
564
|
: encryptSecret(data.refreshToken, key);
|
|
565
|
+
await registerAuthorizationSecrets(existing.appId, data.accessToken, data.refreshToken);
|
|
489
566
|
const result = await getDbClient().run(
|
|
490
567
|
`UPDATE oauth_authorizations SET
|
|
491
568
|
accessToken = ?, refreshToken = ?, expiresAt = ?, scope = ?,
|