@edspencer/paddock 0.0.1 → 0.56.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/README.md +69 -0
- package/install-notice.mjs +29 -0
- package/package.json +58 -5
- package/packages/server/dist/adoptable.d.ts +127 -0
- package/packages/server/dist/adoptable.js +315 -0
- package/packages/server/dist/app.d.ts +67 -0
- package/packages/server/dist/app.js +325 -0
- package/packages/server/dist/archive.d.ts +42 -0
- package/packages/server/dist/archive.js +125 -0
- package/packages/server/dist/attachments-config.d.ts +114 -0
- package/packages/server/dist/attachments-config.js +165 -0
- package/packages/server/dist/attachments-hint.d.ts +53 -0
- package/packages/server/dist/attachments-hint.js +140 -0
- package/packages/server/dist/attachments.d.ts +33 -0
- package/packages/server/dist/attachments.js +117 -0
- package/packages/server/dist/auth.d.ts +84 -0
- package/packages/server/dist/auth.js +247 -0
- package/packages/server/dist/background.d.ts +36 -0
- package/packages/server/dist/background.js +137 -0
- package/packages/server/dist/bind-safety.d.ts +56 -0
- package/packages/server/dist/bind-safety.js +51 -0
- package/packages/server/dist/brand.d.ts +44 -0
- package/packages/server/dist/brand.js +88 -0
- package/packages/server/dist/chat-dto.d.ts +210 -0
- package/packages/server/dist/chat-dto.js +308 -0
- package/packages/server/dist/cli/import-chats.d.ts +21 -0
- package/packages/server/dist/cli/import-chats.js +197 -0
- package/packages/server/dist/cli/paddock.d.ts +2 -0
- package/packages/server/dist/cli/paddock.js +247 -0
- package/packages/server/dist/concurrency.d.ts +57 -0
- package/packages/server/dist/concurrency.js +71 -0
- package/packages/server/dist/config.d.ts +512 -0
- package/packages/server/dist/config.js +593 -0
- package/packages/server/dist/curation-config.d.ts +55 -0
- package/packages/server/dist/curation-config.js +64 -0
- package/packages/server/dist/event-bus.d.ts +87 -0
- package/packages/server/dist/event-bus.js +48 -0
- package/packages/server/dist/git.d.ts +128 -0
- package/packages/server/dist/git.js +446 -0
- package/packages/server/dist/github-auth.d.ts +44 -0
- package/packages/server/dist/github-auth.js +169 -0
- package/packages/server/dist/herdctl-agent-config.d.ts +98 -0
- package/packages/server/dist/herdctl-agent-config.js +331 -0
- package/packages/server/dist/herdctl-agent-names.d.ts +172 -0
- package/packages/server/dist/herdctl-agent-names.js +228 -0
- package/packages/server/dist/herdctl-jobs-index.d.ts +65 -0
- package/packages/server/dist/herdctl-jobs-index.js +225 -0
- package/packages/server/dist/herdctl-jobs.d.ts +98 -0
- package/packages/server/dist/herdctl-jobs.js +225 -0
- package/packages/server/dist/herdctl.d.ts +572 -0
- package/packages/server/dist/herdctl.js +1218 -0
- package/packages/server/dist/hook-config.d.ts +217 -0
- package/packages/server/dist/hook-config.js +224 -0
- package/packages/server/dist/http-bytes.d.ts +24 -0
- package/packages/server/dist/http-bytes.js +52 -0
- package/packages/server/dist/index.d.ts +1 -0
- package/packages/server/dist/index.js +14 -0
- package/packages/server/dist/instance-config.d.ts +135 -0
- package/packages/server/dist/instance-config.js +345 -0
- package/packages/server/dist/localcommand.d.ts +23 -0
- package/packages/server/dist/localcommand.js +140 -0
- package/packages/server/dist/management-auth.d.ts +99 -0
- package/packages/server/dist/management-auth.js +165 -0
- package/packages/server/dist/management-config.d.ts +164 -0
- package/packages/server/dist/management-config.js +286 -0
- package/packages/server/dist/management-mcp-server.d.ts +20 -0
- package/packages/server/dist/management-mcp-server.js +95 -0
- package/packages/server/dist/management-metadata.d.ts +67 -0
- package/packages/server/dist/management-metadata.js +56 -0
- package/packages/server/dist/management-ops.d.ts +110 -0
- package/packages/server/dist/management-ops.js +449 -0
- package/packages/server/dist/management-policy.d.ts +172 -0
- package/packages/server/dist/management-policy.js +259 -0
- package/packages/server/dist/message-provenance.d.ts +112 -0
- package/packages/server/dist/message-provenance.js +260 -0
- package/packages/server/dist/models.d.ts +164 -0
- package/packages/server/dist/models.js +208 -0
- package/packages/server/dist/openapi.d.ts +66 -0
- package/packages/server/dist/openapi.js +183 -0
- package/packages/server/dist/parent-detach.d.ts +30 -0
- package/packages/server/dist/parent-detach.js +109 -0
- package/packages/server/dist/preload.d.ts +43 -0
- package/packages/server/dist/preload.js +55 -0
- package/packages/server/dist/project-files.d.ts +76 -0
- package/packages/server/dist/project-files.js +191 -0
- package/packages/server/dist/project-mime.d.ts +35 -0
- package/packages/server/dist/project-mime.js +55 -0
- package/packages/server/dist/project-paths.d.ts +88 -0
- package/packages/server/dist/project-paths.js +126 -0
- package/packages/server/dist/project-types.d.ts +316 -0
- package/packages/server/dist/project-types.js +48 -0
- package/packages/server/dist/projects.d.ts +202 -0
- package/packages/server/dist/projects.js +894 -0
- package/packages/server/dist/queued-message.d.ts +35 -0
- package/packages/server/dist/queued-message.js +116 -0
- package/packages/server/dist/read-state.d.ts +46 -0
- package/packages/server/dist/read-state.js +139 -0
- package/packages/server/dist/recovery-config.d.ts +111 -0
- package/packages/server/dist/recovery-config.js +105 -0
- package/packages/server/dist/recovery.d.ts +262 -0
- package/packages/server/dist/recovery.js +490 -0
- package/packages/server/dist/resume-drain.d.ts +122 -0
- package/packages/server/dist/resume-drain.js +158 -0
- package/packages/server/dist/route-context.d.ts +168 -0
- package/packages/server/dist/route-context.js +39 -0
- package/packages/server/dist/route-errors.d.ts +8 -0
- package/packages/server/dist/route-errors.js +13 -0
- package/packages/server/dist/routes/chats.d.ts +12 -0
- package/packages/server/dist/routes/chats.js +1406 -0
- package/packages/server/dist/routes/git.d.ts +13 -0
- package/packages/server/dist/routes/git.js +236 -0
- package/packages/server/dist/routes/mcp.d.ts +83 -0
- package/packages/server/dist/routes/mcp.js +250 -0
- package/packages/server/dist/routes/meta.d.ts +15 -0
- package/packages/server/dist/routes/meta.js +447 -0
- package/packages/server/dist/routes/projects.d.ts +25 -0
- package/packages/server/dist/routes/projects.js +736 -0
- package/packages/server/dist/routes/triggers.d.ts +21 -0
- package/packages/server/dist/routes/triggers.js +324 -0
- package/packages/server/dist/routes/workspace-mount.d.ts +48 -0
- package/packages/server/dist/routes/workspace-mount.js +32 -0
- package/packages/server/dist/routes.d.ts +26 -0
- package/packages/server/dist/routes.js +29 -0
- package/packages/server/dist/run-provenance.d.ts +109 -0
- package/packages/server/dist/run-provenance.js +206 -0
- package/packages/server/dist/runs.d.ts +92 -0
- package/packages/server/dist/runs.js +72 -0
- package/packages/server/dist/schedule-config.d.ts +68 -0
- package/packages/server/dist/schedule-config.js +75 -0
- package/packages/server/dist/schedule-session.d.ts +34 -0
- package/packages/server/dist/schedule-session.js +120 -0
- package/packages/server/dist/self-mcp-descriptions.d.ts +20 -0
- package/packages/server/dist/self-mcp-descriptions.js +145 -0
- package/packages/server/dist/self-mcp-read.d.ts +15 -0
- package/packages/server/dist/self-mcp-read.js +97 -0
- package/packages/server/dist/self-mcp-triggers.d.ts +30 -0
- package/packages/server/dist/self-mcp-triggers.js +144 -0
- package/packages/server/dist/self-mcp-types.d.ts +248 -0
- package/packages/server/dist/self-mcp-types.js +10 -0
- package/packages/server/dist/self-mcp-util.d.ts +73 -0
- package/packages/server/dist/self-mcp-util.js +157 -0
- package/packages/server/dist/self-mcp-write.d.ts +35 -0
- package/packages/server/dist/self-mcp-write.js +215 -0
- package/packages/server/dist/self-mcp.d.ts +95 -0
- package/packages/server/dist/self-mcp.js +448 -0
- package/packages/server/dist/send-file-mcp.d.ts +46 -0
- package/packages/server/dist/send-file-mcp.js +281 -0
- package/packages/server/dist/session-hub.d.ts +186 -0
- package/packages/server/dist/session-hub.js +291 -0
- package/packages/server/dist/spawn-capability.d.ts +99 -0
- package/packages/server/dist/spawn-capability.js +106 -0
- package/packages/server/dist/star.d.ts +26 -0
- package/packages/server/dist/star.js +94 -0
- package/packages/server/dist/start.d.ts +1 -0
- package/packages/server/dist/start.js +21 -0
- package/packages/server/dist/subagents.d.ts +337 -0
- package/packages/server/dist/subagents.js +626 -0
- package/packages/server/dist/sweep.d.ts +122 -0
- package/packages/server/dist/sweep.js +696 -0
- package/packages/server/dist/tooldetails.d.ts +33 -0
- package/packages/server/dist/tooldetails.js +466 -0
- package/packages/server/dist/transcribe.d.ts +69 -0
- package/packages/server/dist/transcribe.js +119 -0
- package/packages/server/dist/transcripts.d.ts +42 -0
- package/packages/server/dist/transcripts.js +164 -0
- package/packages/server/dist/trigger-config.d.ts +278 -0
- package/packages/server/dist/trigger-config.js +417 -0
- package/packages/server/dist/trigger-runtime.d.ts +94 -0
- package/packages/server/dist/trigger-runtime.js +82 -0
- package/packages/server/dist/trigger-session.d.ts +34 -0
- package/packages/server/dist/trigger-session.js +122 -0
- package/packages/server/dist/triggers.d.ts +83 -0
- package/packages/server/dist/triggers.js +124 -0
- package/packages/server/dist/trusted-proxy.d.ts +57 -0
- package/packages/server/dist/trusted-proxy.js +157 -0
- package/packages/server/dist/turn-notice.d.ts +154 -0
- package/packages/server/dist/turn-notice.js +271 -0
- package/packages/server/dist/unread.d.ts +36 -0
- package/packages/server/dist/unread.js +123 -0
- package/packages/server/dist/usage.d.ts +66 -0
- package/packages/server/dist/usage.js +243 -0
- package/packages/server/dist/wake-injection.d.ts +156 -0
- package/packages/server/dist/wake-injection.js +136 -0
- package/packages/server/dist/whisper-local.d.ts +9 -0
- package/packages/server/dist/whisper-local.js +122 -0
- package/packages/server/dist/ws-context.d.ts +158 -0
- package/packages/server/dist/ws-context.js +1 -0
- package/packages/server/dist/ws-protocol.d.ts +423 -0
- package/packages/server/dist/ws-protocol.js +251 -0
- package/packages/server/dist/ws-self-mcp.d.ts +52 -0
- package/packages/server/dist/ws-self-mcp.js +28 -0
- package/packages/server/dist/ws-triggers.d.ts +29 -0
- package/packages/server/dist/ws-triggers.js +261 -0
- package/packages/server/dist/ws-turn.d.ts +86 -0
- package/packages/server/dist/ws-turn.js +751 -0
- package/packages/server/dist/ws.d.ts +62 -0
- package/packages/server/dist/ws.js +1024 -0
- package/packages/server/package.json +6 -0
- package/packages/web/dist/assets/InstanceConfigPage-4cQGTj9Y.js +2 -0
- package/packages/web/dist/assets/MarkdownRenderer-CJZFLj_8.js +30 -0
- package/packages/web/dist/assets/ProjectRedirect-BHwVbbqB.js +2 -0
- package/packages/web/dist/assets/ProjectView-BOvuAITg.js +13 -0
- package/packages/web/dist/assets/ProjectsGrid-BCojZOGY.js +2 -0
- package/packages/web/dist/assets/arc-BVyxvnkl.js +2 -0
- package/packages/web/dist/assets/architectureDiagram-3BPJPVTR-Cd9Puqg1.js +37 -0
- package/packages/web/dist/assets/bash-I8pq0VWm.js +2 -0
- package/packages/web/dist/assets/blockDiagram-GPEHLZMM-B_AQt_Rx.js +133 -0
- package/packages/web/dist/assets/c-D2F1sQUL.js +2 -0
- package/packages/web/dist/assets/c4Diagram-AAUBKEIU-7e0TQtXB.js +11 -0
- package/packages/web/dist/assets/channel-C_1aISNn.js +2 -0
- package/packages/web/dist/assets/chunk-2J33WTMH-Dz4WZmUZ.js +2 -0
- package/packages/web/dist/assets/chunk-4BX2VUAB-DVWYldo6.js +2 -0
- package/packages/web/dist/assets/chunk-55IACEB6-BIMrizQd.js +2 -0
- package/packages/web/dist/assets/chunk-727SXJPM-CL2q9fF1.js +207 -0
- package/packages/web/dist/assets/chunk-AQP2D5EJ-BSq3HlQ9.js +232 -0
- package/packages/web/dist/assets/chunk-FMBD7UC4-Bgt64pLE.js +16 -0
- package/packages/web/dist/assets/chunk-ND2GUHAM-C8Pr2A38.js +2 -0
- package/packages/web/dist/assets/chunk-QZHKN3VN-BAOqh-MN.js +2 -0
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-B8JE7cHJ.js +2 -0
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-B8JE7cHJ.js +2 -0
- package/packages/web/dist/assets/core-lJeGACQg.js +4 -0
- package/packages/web/dist/assets/cose-bilkent-S5V4N54A-BF4tameA.js +2 -0
- package/packages/web/dist/assets/cpp-Bm3ii6kY.js +2 -0
- package/packages/web/dist/assets/csharp-BNpuYZMG.js +2 -0
- package/packages/web/dist/assets/css-DazXZka4.js +2 -0
- package/packages/web/dist/assets/cytoscape.esm-CUqq0XTU.js +332 -0
- package/packages/web/dist/assets/dagre-BM42HDAG-DdsLM6ql.js +5 -0
- package/packages/web/dist/assets/defaultLocale-DX6XiGOO.js +2 -0
- package/packages/web/dist/assets/diagram-2AECGRRQ-BQ78S_RM.js +44 -0
- package/packages/web/dist/assets/diagram-5GNKFQAL-CAKE6nHE.js +11 -0
- package/packages/web/dist/assets/diagram-KO2AKTUF-fEvHRXgr.js +4 -0
- package/packages/web/dist/assets/diagram-LMA3HP47-CtWsgeSm.js +25 -0
- package/packages/web/dist/assets/diagram-OG6HWLK6-Buog8k7_.js +25 -0
- package/packages/web/dist/assets/erDiagram-TEJ5UH35-CWvVVPZS.js +86 -0
- package/packages/web/dist/assets/flowDiagram-I6XJVG4X-DsCGXtp4.js +163 -0
- package/packages/web/dist/assets/format-DamJQ9LO.js +2 -0
- package/packages/web/dist/assets/ganttDiagram-6RSMTGT7-CsdwIvvC.js +293 -0
- package/packages/web/dist/assets/gitGraphDiagram-PVQCEYII-hZvrX1sD.js +107 -0
- package/packages/web/dist/assets/go-BPx_y7j8.js +2 -0
- package/packages/web/dist/assets/graph-BlwPajkw.js +2 -0
- package/packages/web/dist/assets/index-CGbZdrm8.js +71 -0
- package/packages/web/dist/assets/index-DJDQ210-.css +1 -0
- package/packages/web/dist/assets/infoDiagram-5YYISTIA-nE_Vtz_l.js +3 -0
- package/packages/web/dist/assets/ini-B3BVgEsG.js +2 -0
- package/packages/web/dist/assets/init-Gi6I4Gst.js +2 -0
- package/packages/web/dist/assets/ishikawaDiagram-YF4QCWOH-2Nlqs0oH.js +71 -0
- package/packages/web/dist/assets/java-B5VKqtH2.js +2 -0
- package/packages/web/dist/assets/javascript-BKRaQes9.js +2 -0
- package/packages/web/dist/assets/journeyDiagram-JHISSGLW-mjU1fMc4.js +140 -0
- package/packages/web/dist/assets/json-DIYVocXf.js +2 -0
- package/packages/web/dist/assets/kanban-definition-UN3LZRKU-C_9xNaa3.js +90 -0
- package/packages/web/dist/assets/katex-C5jXJg4s.js +258 -0
- package/packages/web/dist/assets/kotlin-Cw-eF_bm.js +3 -0
- package/packages/web/dist/assets/lastTab-CU1zsF0j.js +2 -0
- package/packages/web/dist/assets/layout-Boy8vhSP.js +2 -0
- package/packages/web/dist/assets/linear-BxKd91ho.js +2 -0
- package/packages/web/dist/assets/mermaid.core-C-CuhD8I.js +310 -0
- package/packages/web/dist/assets/mindmap-definition-RKZ34NQL-Bs6yfcZv.js +97 -0
- package/packages/web/dist/assets/ordinal-Cboi1Yqb.js +2 -0
- package/packages/web/dist/assets/php-CRdd4dfS.js +3 -0
- package/packages/web/dist/assets/pieDiagram-4H26LBE5-DNdqX7O5.js +31 -0
- package/packages/web/dist/assets/python-kSlZHhJb.js +2 -0
- package/packages/web/dist/assets/quadrantDiagram-W4KKPZXB-ZMJl9OdZ.js +8 -0
- package/packages/web/dist/assets/queued-LamEU0GH.js +2 -0
- package/packages/web/dist/assets/requirementDiagram-4Y6WPE33-RmxMdm2q.js +85 -0
- package/packages/web/dist/assets/ruby-7T4ktxyv.js +2 -0
- package/packages/web/dist/assets/rust-HC3MBUhY.js +2 -0
- package/packages/web/dist/assets/sankeyDiagram-5OEKKPKP-OAAhWcfK.js +41 -0
- package/packages/web/dist/assets/scss-DZLA7YXz.js +2 -0
- package/packages/web/dist/assets/sequenceDiagram-3UESZ5HK-BWF7rCyN.js +163 -0
- package/packages/web/dist/assets/sql-BC88IN-V.js +2 -0
- package/packages/web/dist/assets/stateDiagram-AJRCARHV-M3b_zqqo.js +2 -0
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-CI7saFDa.js +2 -0
- package/packages/web/dist/assets/swift-Dn8kKO_1.js +2 -0
- package/packages/web/dist/assets/timeline-definition-PNZ67QCA-BpOCh63N.js +121 -0
- package/packages/web/dist/assets/typescript-C2FFdlUC.js +2 -0
- package/packages/web/dist/assets/vennDiagram-CIIHVFJN-D_iPH45Q.js +35 -0
- package/packages/web/dist/assets/wardley-L42UT6IY-DVD9I2Om.js +174 -0
- package/packages/web/dist/assets/wardleyDiagram-YWT4CUSO-Bn1IKwjn.js +79 -0
- package/packages/web/dist/assets/xml-BXBhIUeX.js +2 -0
- package/packages/web/dist/assets/xychartDiagram-2RQKCTM6-DwtI0bkR.js +8 -0
- package/packages/web/dist/assets/yaml-drsDnIKT.js +2 -0
- package/packages/web/dist/favicon.ico +0 -0
- package/packages/web/dist/fonts/inter-latin.woff2 +0 -0
- package/packages/web/dist/fonts/jetbrains-mono-latin.woff2 +0 -0
- package/packages/web/dist/icons/apple-touch-icon.png +0 -0
- package/packages/web/dist/icons/favicon-16.png +0 -0
- package/packages/web/dist/icons/favicon-32.png +0 -0
- package/packages/web/dist/icons/icon-192.png +0 -0
- package/packages/web/dist/icons/icon-512.png +0 -0
- package/packages/web/dist/icons/maskable-512.png +0 -0
- package/packages/web/dist/index.html +56 -0
- package/packages/web/dist/manifest.webmanifest +32 -0
- package/packages/web/dist/sw.js +139 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { ManagementApiConfig } from "./management-config.js";
|
|
2
|
+
import type { ManagementPrincipal, ManagementDeniedError } from "./management-policy.js";
|
|
3
|
+
/** Why a credential was refused. Shapes the challenge; never leaks the secret. */
|
|
4
|
+
export type ManagementAuthFailure =
|
|
5
|
+
/** No credential presented at all. */
|
|
6
|
+
"missing"
|
|
7
|
+
/** An `Authorization` header we could not parse as a bearer token. */
|
|
8
|
+
| "malformed"
|
|
9
|
+
/** Parsed fine, but matched no configured client (or failed instance binding). */
|
|
10
|
+
| "invalid";
|
|
11
|
+
export type ManagementAuthResult = {
|
|
12
|
+
ok: true;
|
|
13
|
+
principal: ManagementPrincipal;
|
|
14
|
+
} | {
|
|
15
|
+
ok: false;
|
|
16
|
+
failure: ManagementAuthFailure;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Compare two secrets without leaking their relationship through timing.
|
|
20
|
+
*
|
|
21
|
+
* `timingSafeEqual` requires equal-length buffers and throws otherwise — which
|
|
22
|
+
* would itself leak length. Hashing both sides to a fixed-width digest first
|
|
23
|
+
* makes the comparison length-independent as well as time-independent.
|
|
24
|
+
*/
|
|
25
|
+
export declare function constantTimeEqual(a: string, b: string): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Extract a bearer token from an `Authorization` header value.
|
|
28
|
+
* Returns undefined when absent, and null when present but not a bearer token
|
|
29
|
+
* (so the caller can distinguish "missing" from "malformed").
|
|
30
|
+
*/
|
|
31
|
+
export declare function bearerToken(headerValue: string | string[] | undefined): string | null | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* Match a presented token against the configured clients.
|
|
34
|
+
*
|
|
35
|
+
* Scans EVERY client without early-exit so total work does not depend on which
|
|
36
|
+
* client matched (or on how far down the list a match sits). Instance binding is
|
|
37
|
+
* checked first: a token carrying an instance id that isn't ours is refused even
|
|
38
|
+
* if its secret would otherwise match, which is what makes a credential minted
|
|
39
|
+
* for another Paddock useless here.
|
|
40
|
+
*/
|
|
41
|
+
export declare function resolveTokenPrincipal(token: string, cfg: ManagementApiConfig): ManagementPrincipal | null;
|
|
42
|
+
/**
|
|
43
|
+
* Authenticate a request's `Authorization` header against the configured
|
|
44
|
+
* clients. The caller decides the HTTP shape (see {@link challengeHeader}).
|
|
45
|
+
*/
|
|
46
|
+
export declare function authenticateManagementRequest(authorization: string | string[] | undefined, cfg: ManagementApiConfig): ManagementAuthResult;
|
|
47
|
+
/**
|
|
48
|
+
* Build the `WWW-Authenticate` challenge for a refusal (RFC 6750 §3).
|
|
49
|
+
*
|
|
50
|
+
* `resourceMetadataUrl` is the RFC 9728 protected-resource-metadata location.
|
|
51
|
+
* M2 supplies it so an MCP client can discover how to obtain a token; M1 omits
|
|
52
|
+
* it (there is no OAuth path yet) and the header still forms a valid challenge.
|
|
53
|
+
*/
|
|
54
|
+
export declare function challengeHeader(failure: ManagementAuthFailure, resourceMetadataUrl?: string): string;
|
|
55
|
+
/**
|
|
56
|
+
* The challenge for an authenticated caller whose SCOPE doesn't cover what it
|
|
57
|
+
* asked for (MCP spec revision 2025-11-25 / RFC 6750 §3.1).
|
|
58
|
+
*
|
|
59
|
+
* Deliberately distinct from the 401 above: the credential was valid, so
|
|
60
|
+
* re-authenticating changes nothing — the caller needs a WIDER GRANT. The
|
|
61
|
+
* transport pairs this with **403**, and the `scope` parameter names what was
|
|
62
|
+
* required, which is what lets a client ask for incremental consent instead of
|
|
63
|
+
* failing opaquely.
|
|
64
|
+
*/
|
|
65
|
+
export declare function insufficientScopeChallenge(requiredScope: string, resourceMetadataUrl?: string): string;
|
|
66
|
+
/**
|
|
67
|
+
* The HTTP shape for a policy denial raised by the ops layer.
|
|
68
|
+
*
|
|
69
|
+
* Kept HERE, next to the other challenge builders, so that every transport maps
|
|
70
|
+
* a `ManagementDeniedError` the same way instead of inventing its own status.
|
|
71
|
+
* Both denial codes are scope problems, hence 403 rather than 401.
|
|
72
|
+
*/
|
|
73
|
+
export declare function denialResponse(error: ManagementDeniedError, resourceMetadataUrl?: string): {
|
|
74
|
+
status: 403;
|
|
75
|
+
challenge: string;
|
|
76
|
+
body: {
|
|
77
|
+
error: string;
|
|
78
|
+
code: string;
|
|
79
|
+
message: string;
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* The RFC 9728 protected-resource metadata URL to advertise in a challenge.
|
|
84
|
+
*
|
|
85
|
+
* Delegates to {@link metadataUrl} so there is ONE rule for when a document
|
|
86
|
+
* exists: advertising a `resource_metadata` URL that then 404s would send a
|
|
87
|
+
* client down a discovery path that cannot succeed.
|
|
88
|
+
*/
|
|
89
|
+
export declare function resourceMetadataUrl(cfg: ManagementApiConfig): string | undefined;
|
|
90
|
+
/**
|
|
91
|
+
* Whether the external management API is configured at all (drives the 404).
|
|
92
|
+
*
|
|
93
|
+
* Requires BOTH a client and a canonical public URL: without the latter, M2's
|
|
94
|
+
* discovery document could not be served correctly, so the surface stays off
|
|
95
|
+
* rather than half-working. `resolveManagementApiConfig` already empties the
|
|
96
|
+
* client list when `publicUrl` is missing or bad; this is the belt to that
|
|
97
|
+
* braces.
|
|
98
|
+
*/
|
|
99
|
+
export declare function isManagementApiEnabled(cfg: ManagementApiConfig | undefined): boolean;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Management API authentication (issue #312 M1) — the config-token authenticator.
|
|
3
|
+
*
|
|
4
|
+
* Turns a presented credential into a {@link ManagementPrincipal}, or refuses.
|
|
5
|
+
* Transport-agnostic: it takes a header value and returns a verdict, so the
|
|
6
|
+
* `/mcp` route, a future REST surface (#465), and tests all share one path.
|
|
7
|
+
*
|
|
8
|
+
* ── This is INDEPENDENT of `PADDOCK_AUTH_MODE` and of any reverse proxy ─────
|
|
9
|
+
* The non-negotiable invariant on the ticket: Paddock authenticates the
|
|
10
|
+
* management surface ITSELF. It must be possible to run with no proxy at all —
|
|
11
|
+
* even `auth.mode: none` — and still have a correctly authenticated `/mcp`. A
|
|
12
|
+
* proxy is an operator's choice, never a prerequisite. Consequently `/mcp` is
|
|
13
|
+
* exempted from the browser auth hook (auth.ts) and gated by THIS instead; the
|
|
14
|
+
* exemption is safe only because this runs in its place.
|
|
15
|
+
*
|
|
16
|
+
* ── Why 401 + WWW-Authenticate, never a 302 ────────────────────────────────
|
|
17
|
+
* An SSO proxy answers an unauthenticated request with an HTML login redirect.
|
|
18
|
+
* That breaks MCP clients outright: they cannot follow it, and OAuth discovery
|
|
19
|
+
* (M2) depends on reading a real `401` + `WWW-Authenticate` challenge. So this
|
|
20
|
+
* module owns the challenge header and always fails with a status an MCP client
|
|
21
|
+
* can act on.
|
|
22
|
+
*/
|
|
23
|
+
import { createHash, timingSafeEqual } from "node:crypto";
|
|
24
|
+
import { tokenInstanceId } from "./management-config.js";
|
|
25
|
+
import { metadataUrl } from "./management-metadata.js";
|
|
26
|
+
/**
|
|
27
|
+
* Compare two secrets without leaking their relationship through timing.
|
|
28
|
+
*
|
|
29
|
+
* `timingSafeEqual` requires equal-length buffers and throws otherwise — which
|
|
30
|
+
* would itself leak length. Hashing both sides to a fixed-width digest first
|
|
31
|
+
* makes the comparison length-independent as well as time-independent.
|
|
32
|
+
*/
|
|
33
|
+
export function constantTimeEqual(a, b) {
|
|
34
|
+
const ha = createHash("sha256").update(a, "utf8").digest();
|
|
35
|
+
const hb = createHash("sha256").update(b, "utf8").digest();
|
|
36
|
+
return timingSafeEqual(ha, hb);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Extract a bearer token from an `Authorization` header value.
|
|
40
|
+
* Returns undefined when absent, and null when present but not a bearer token
|
|
41
|
+
* (so the caller can distinguish "missing" from "malformed").
|
|
42
|
+
*/
|
|
43
|
+
export function bearerToken(headerValue) {
|
|
44
|
+
const raw = Array.isArray(headerValue) ? headerValue[0] : headerValue;
|
|
45
|
+
if (raw === undefined || raw.trim().length === 0)
|
|
46
|
+
return undefined;
|
|
47
|
+
const m = /^Bearer[ \t]+(.+)$/i.exec(raw.trim());
|
|
48
|
+
if (!m)
|
|
49
|
+
return null;
|
|
50
|
+
const token = m[1].trim();
|
|
51
|
+
return token.length > 0 ? token : null;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Match a presented token against the configured clients.
|
|
55
|
+
*
|
|
56
|
+
* Scans EVERY client without early-exit so total work does not depend on which
|
|
57
|
+
* client matched (or on how far down the list a match sits). Instance binding is
|
|
58
|
+
* checked first: a token carrying an instance id that isn't ours is refused even
|
|
59
|
+
* if its secret would otherwise match, which is what makes a credential minted
|
|
60
|
+
* for another Paddock useless here.
|
|
61
|
+
*/
|
|
62
|
+
export function resolveTokenPrincipal(token, cfg) {
|
|
63
|
+
const embedded = tokenInstanceId(token);
|
|
64
|
+
// A prefixed token names its instance; enforce it when we know our own.
|
|
65
|
+
if (embedded !== undefined && cfg.instanceId !== undefined && embedded !== cfg.instanceId) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
let matched = null;
|
|
69
|
+
for (const client of cfg.clients) {
|
|
70
|
+
if (constantTimeEqual(token, client.token))
|
|
71
|
+
matched = client;
|
|
72
|
+
}
|
|
73
|
+
if (!matched)
|
|
74
|
+
return null;
|
|
75
|
+
return { clientId: matched.clientId, kind: "token", scope: matched.scope };
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Authenticate a request's `Authorization` header against the configured
|
|
79
|
+
* clients. The caller decides the HTTP shape (see {@link challengeHeader}).
|
|
80
|
+
*/
|
|
81
|
+
export function authenticateManagementRequest(authorization, cfg) {
|
|
82
|
+
const token = bearerToken(authorization);
|
|
83
|
+
if (token === undefined)
|
|
84
|
+
return { ok: false, failure: "missing" };
|
|
85
|
+
if (token === null)
|
|
86
|
+
return { ok: false, failure: "malformed" };
|
|
87
|
+
const principal = resolveTokenPrincipal(token, cfg);
|
|
88
|
+
if (!principal)
|
|
89
|
+
return { ok: false, failure: "invalid" };
|
|
90
|
+
return { ok: true, principal };
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Build the `WWW-Authenticate` challenge for a refusal (RFC 6750 §3).
|
|
94
|
+
*
|
|
95
|
+
* `resourceMetadataUrl` is the RFC 9728 protected-resource-metadata location.
|
|
96
|
+
* M2 supplies it so an MCP client can discover how to obtain a token; M1 omits
|
|
97
|
+
* it (there is no OAuth path yet) and the header still forms a valid challenge.
|
|
98
|
+
*/
|
|
99
|
+
export function challengeHeader(failure, resourceMetadataUrl) {
|
|
100
|
+
const params = ['realm="paddock"'];
|
|
101
|
+
if (failure !== "missing") {
|
|
102
|
+
params.push('error="invalid_token"');
|
|
103
|
+
params.push(`error_description="${failure === "malformed" ? "malformed Authorization header" : "the access token is invalid"}"`);
|
|
104
|
+
}
|
|
105
|
+
if (resourceMetadataUrl)
|
|
106
|
+
params.push(`resource_metadata="${resourceMetadataUrl}"`);
|
|
107
|
+
return `Bearer ${params.join(", ")}`;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* The challenge for an authenticated caller whose SCOPE doesn't cover what it
|
|
111
|
+
* asked for (MCP spec revision 2025-11-25 / RFC 6750 §3.1).
|
|
112
|
+
*
|
|
113
|
+
* Deliberately distinct from the 401 above: the credential was valid, so
|
|
114
|
+
* re-authenticating changes nothing — the caller needs a WIDER GRANT. The
|
|
115
|
+
* transport pairs this with **403**, and the `scope` parameter names what was
|
|
116
|
+
* required, which is what lets a client ask for incremental consent instead of
|
|
117
|
+
* failing opaquely.
|
|
118
|
+
*/
|
|
119
|
+
export function insufficientScopeChallenge(requiredScope, resourceMetadataUrl) {
|
|
120
|
+
const params = [
|
|
121
|
+
'realm="paddock"',
|
|
122
|
+
'error="insufficient_scope"',
|
|
123
|
+
`error_description="the authenticated client's scope does not permit this operation"`,
|
|
124
|
+
`scope="${requiredScope}"`,
|
|
125
|
+
];
|
|
126
|
+
if (resourceMetadataUrl)
|
|
127
|
+
params.push(`resource_metadata="${resourceMetadataUrl}"`);
|
|
128
|
+
return `Bearer ${params.join(", ")}`;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* The HTTP shape for a policy denial raised by the ops layer.
|
|
132
|
+
*
|
|
133
|
+
* Kept HERE, next to the other challenge builders, so that every transport maps
|
|
134
|
+
* a `ManagementDeniedError` the same way instead of inventing its own status.
|
|
135
|
+
* Both denial codes are scope problems, hence 403 rather than 401.
|
|
136
|
+
*/
|
|
137
|
+
export function denialResponse(error, resourceMetadataUrl) {
|
|
138
|
+
return {
|
|
139
|
+
status: 403,
|
|
140
|
+
challenge: insufficientScopeChallenge(error.code === "project_denied" ? `project:${error.projectSlug}` : error.operation, resourceMetadataUrl),
|
|
141
|
+
body: { error: "forbidden", code: error.code, message: error.message },
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* The RFC 9728 protected-resource metadata URL to advertise in a challenge.
|
|
146
|
+
*
|
|
147
|
+
* Delegates to {@link metadataUrl} so there is ONE rule for when a document
|
|
148
|
+
* exists: advertising a `resource_metadata` URL that then 404s would send a
|
|
149
|
+
* client down a discovery path that cannot succeed.
|
|
150
|
+
*/
|
|
151
|
+
export function resourceMetadataUrl(cfg) {
|
|
152
|
+
return metadataUrl(cfg);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Whether the external management API is configured at all (drives the 404).
|
|
156
|
+
*
|
|
157
|
+
* Requires BOTH a client and a canonical public URL: without the latter, M2's
|
|
158
|
+
* discovery document could not be served correctly, so the surface stays off
|
|
159
|
+
* rather than half-working. `resolveManagementApiConfig` already empties the
|
|
160
|
+
* client list when `publicUrl` is missing or bad; this is the belt to that
|
|
161
|
+
* braces.
|
|
162
|
+
*/
|
|
163
|
+
export function isManagementApiEnabled(cfg) {
|
|
164
|
+
return (cfg?.clients.length ?? 0) > 0 && Boolean(cfg?.publicUrl);
|
|
165
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `managementApi.clients` config resolution (issue #312 M1).
|
|
3
|
+
*
|
|
4
|
+
* Turns the optional `managementApi` block of `paddock.config.yaml` into the
|
|
5
|
+
* resolved list of external clients the token authenticator will accept, each
|
|
6
|
+
* carrying its {@link ManagementScope}. Pure over an injected environment map so
|
|
7
|
+
* the whole validation matrix is unit-testable without touching `process.env`.
|
|
8
|
+
*
|
|
9
|
+
* ── Why token material is REFERENCED, never inlined ─────────────────────────
|
|
10
|
+
* `paddock.config.yaml` is git-tracked (and editable from the instance Settings
|
|
11
|
+
* screen, #385). A secret written there leaks into history the moment it is
|
|
12
|
+
* committed, and no later edit removes it. So a client's credential is given as
|
|
13
|
+
* an env REFERENCE (`ref: env:PADDOCK_MCP_TOKEN_MY_LAPTOP`) and an inline value
|
|
14
|
+
* is a hard config error — not a warning — because accepting one silently would
|
|
15
|
+
* teach operators the unsafe habit.
|
|
16
|
+
*
|
|
17
|
+
* ── Failure posture ─────────────────────────────────────────────────────────
|
|
18
|
+
* Two different kinds of problem, handled differently on purpose:
|
|
19
|
+
* - **Malformed config** (inline secret, unknown auth type, bad scope shape)
|
|
20
|
+
* is an ERROR. The operator wrote something meaningless; surface it.
|
|
21
|
+
* - **An unresolvable reference** (env var unset/blank/too short) DROPS THAT
|
|
22
|
+
* CLIENT with a loud warning, leaving other clients working. Dropping is the
|
|
23
|
+
* fail-closed direction: the credential simply doesn't exist, so nothing can
|
|
24
|
+
* authenticate as that client. If every client drops, `/mcp` reverts to its
|
|
25
|
+
* unconfigured 404 — the endpoint ceases to exist rather than opening up.
|
|
26
|
+
*/
|
|
27
|
+
import { type ManagementScope } from "./management-policy.js";
|
|
28
|
+
import { type TrustedProxies } from "./trusted-proxy.js";
|
|
29
|
+
/**
|
|
30
|
+
* Recommended token prefix. A prefixed token additionally carries the INSTANCE
|
|
31
|
+
* it was minted for (`pdk_<instanceId>_<secret>`), which is what makes a
|
|
32
|
+
* credential for instance A meaningless at instance B — see
|
|
33
|
+
* {@link tokenInstanceId}. Also gives secret scanners something to match on.
|
|
34
|
+
*/
|
|
35
|
+
export declare const TOKEN_PREFIX = "pdk_";
|
|
36
|
+
/**
|
|
37
|
+
* Minimum accepted token length. Not a strength guarantee — just a floor that
|
|
38
|
+
* rejects obvious placeholders ("changeme", "test") before they can ever
|
|
39
|
+
* authenticate a turn-spawning client.
|
|
40
|
+
*/
|
|
41
|
+
export declare const MIN_TOKEN_LENGTH = 24;
|
|
42
|
+
/** One resolved external client: an identity, a credential, and a scope. */
|
|
43
|
+
export interface ManagementClient {
|
|
44
|
+
/** The `managementApi.clients` map key — the stable, loggable identity. */
|
|
45
|
+
clientId: string;
|
|
46
|
+
/** The resolved secret. NEVER logged, never echoed in an API response. */
|
|
47
|
+
token: string;
|
|
48
|
+
scope: ManagementScope;
|
|
49
|
+
}
|
|
50
|
+
/** The resolved management-API config. */
|
|
51
|
+
export interface ManagementApiConfig {
|
|
52
|
+
/**
|
|
53
|
+
* Resolved external clients. EMPTY means the external management API is off
|
|
54
|
+
* entirely and `/mcp` must 404 (fail closed) — see routes/mcp.ts.
|
|
55
|
+
*/
|
|
56
|
+
clients: ManagementClient[];
|
|
57
|
+
/**
|
|
58
|
+
* This instance's identifier, used to bind prefixed tokens to it. Absent ⇒
|
|
59
|
+
* binding is not enforced (an unprefixed-token deployment still works).
|
|
60
|
+
*/
|
|
61
|
+
instanceId?: string;
|
|
62
|
+
/**
|
|
63
|
+
* The canonical PUBLIC origin clients reach this instance at, with no trailing
|
|
64
|
+
* slash (e.g. `https://paddock.example.com`).
|
|
65
|
+
*
|
|
66
|
+
* Required whenever clients are configured, because RFC 9728 §3.3 makes the
|
|
67
|
+
* `resource` value in the protected-resource metadata document a byte-for-byte
|
|
68
|
+
* match against the URL the client used — a mismatch means the client MUST
|
|
69
|
+
* discard the document. It cannot be derived from `Host`: behind a
|
|
70
|
+
* TLS-terminating proxy the scheme is wrong, and the header is
|
|
71
|
+
* attacker-controlled. So the operator states it once, here.
|
|
72
|
+
*/
|
|
73
|
+
publicUrl?: string;
|
|
74
|
+
/**
|
|
75
|
+
* Issuer URLs of the OAuth authorization servers that may issue tokens for
|
|
76
|
+
* this resource, advertised in the RFC 9728 discovery document.
|
|
77
|
+
*
|
|
78
|
+
* Optional and empty by default: with only config tokens configured there IS
|
|
79
|
+
* no authorization server, and advertising one would send clients into an
|
|
80
|
+
* OAuth flow that cannot complete. Populated when the OAuth authenticator is
|
|
81
|
+
* configured.
|
|
82
|
+
*/
|
|
83
|
+
authorizationServers?: string[];
|
|
84
|
+
/**
|
|
85
|
+
* Peers whose `X-Forwarded-Proto` the `/mcp` plaintext guard believes (#474).
|
|
86
|
+
* Absent ⇒ the compatibility default (loopback + private address space); see
|
|
87
|
+
* trusted-proxy.ts for what that default does and does not prove.
|
|
88
|
+
*/
|
|
89
|
+
trustedProxies?: TrustedProxies;
|
|
90
|
+
}
|
|
91
|
+
/** On-disk shape of the `managementApi` block. Every field optional. */
|
|
92
|
+
export interface ManagementApiConfigFile {
|
|
93
|
+
instanceId?: string;
|
|
94
|
+
publicUrl?: string;
|
|
95
|
+
authorizationServers?: string[] | string;
|
|
96
|
+
/** IPs / CIDRs / presets — see {@link parseTrustedProxies}. */
|
|
97
|
+
trustedProxies?: string[] | string;
|
|
98
|
+
clients?: Record<string, ManagementClientConfigFile | null | undefined>;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Validate the canonical public URL. Returns the normalised origin (no trailing
|
|
102
|
+
* slash) or an error string.
|
|
103
|
+
*
|
|
104
|
+
* Rejects plaintext for anything but loopback: `/mcp` carries bearer tokens, so
|
|
105
|
+
* advertising an `http://` origin to the world would publish a URL on which the
|
|
106
|
+
* credential is readable in transit. This is the config-time half of the epic's
|
|
107
|
+
* "refuse to serve `/mcp` over plaintext" rule (routes/mcp.ts enforces the
|
|
108
|
+
* per-request half).
|
|
109
|
+
*/
|
|
110
|
+
export declare function validatePublicUrl(raw: string): {
|
|
111
|
+
url: string;
|
|
112
|
+
} | {
|
|
113
|
+
error: string;
|
|
114
|
+
};
|
|
115
|
+
export interface ManagementClientConfigFile {
|
|
116
|
+
auth?: {
|
|
117
|
+
type?: string;
|
|
118
|
+
/** `env:VAR_NAME` — the ONLY supported form. An inline value is an error. */
|
|
119
|
+
ref?: string;
|
|
120
|
+
/** Present only in a MISCONFIGURED file; its presence is a hard error. */
|
|
121
|
+
token?: string;
|
|
122
|
+
secret?: string;
|
|
123
|
+
};
|
|
124
|
+
scope?: {
|
|
125
|
+
projects?: string[] | string;
|
|
126
|
+
allow?: string[] | string;
|
|
127
|
+
deny?: string[] | string;
|
|
128
|
+
denyProjects?: string[] | string;
|
|
129
|
+
maxSpawnDepth?: number | string;
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/** Outcome of resolving the block: the clients plus everything worth telling the operator. */
|
|
133
|
+
export interface ManagementConfigResolution {
|
|
134
|
+
config: ManagementApiConfig;
|
|
135
|
+
/** Fatal-to-that-client misconfigurations (logged at error level). */
|
|
136
|
+
errors: string[];
|
|
137
|
+
/** Non-fatal advice + dropped-client notices (logged at warn level). */
|
|
138
|
+
warnings: string[];
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* The instance id embedded in a prefixed token (`pdk_<instanceId>_<secret>`), or
|
|
142
|
+
* undefined for a token that carries none.
|
|
143
|
+
*
|
|
144
|
+
* This is the audience-binding hook the ticket asks for on day one: the
|
|
145
|
+
* authenticator refuses a prefixed token whose embedded instance doesn't match
|
|
146
|
+
* this instance, so copying a credential to a second Paddock does not make it
|
|
147
|
+
* work there — even though the secret bytes are identical.
|
|
148
|
+
*/
|
|
149
|
+
export declare function tokenInstanceId(token: string): string | undefined;
|
|
150
|
+
/**
|
|
151
|
+
* Resolve one client's scope, defaulting to READ-ONLY when the operator gave
|
|
152
|
+
* none. The default is the security posture of the whole feature: a client
|
|
153
|
+
* added without thinking about scope gets the risk class that cannot execute
|
|
154
|
+
* code (see the RCE framing in management-policy.ts).
|
|
155
|
+
*/
|
|
156
|
+
export declare function resolveClientScope(file: ManagementClientConfigFile["scope"]): ManagementScope;
|
|
157
|
+
/**
|
|
158
|
+
* Resolve the `managementApi` block against `env`.
|
|
159
|
+
*
|
|
160
|
+
* Never throws: a bad client is reported and dropped so one typo can't take the
|
|
161
|
+
* whole instance down (the browser UI and keepers are unaffected by management-
|
|
162
|
+
* API config). The caller logs `errors`/`warnings` at boot.
|
|
163
|
+
*/
|
|
164
|
+
export declare function resolveManagementApiConfig(file: ManagementApiConfigFile | undefined, env: Record<string, string | undefined>): ManagementConfigResolution;
|