@ggui-ai/mcp-server 0.1.0-rc.3 → 0.2.0-alpha.3
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 +1 -1
- package/dist/build-mcp.d.ts +8 -8
- package/dist/build-mcp.d.ts.map +1 -1
- package/dist/build-mcp.js +53 -12
- package/dist/console-auth.d.ts +7 -7
- package/dist/console-auth.d.ts.map +1 -1
- package/dist/console-auth.js +4 -4
- package/dist/console-cache.d.ts +2 -2
- package/dist/console-cache.d.ts.map +1 -1
- package/dist/console-cache.js +10 -10
- package/dist/console-headers.d.ts +2 -2
- package/dist/console-headers.js +2 -2
- package/dist/console-payloads.d.ts +3 -3
- package/dist/console-payloads.d.ts.map +1 -1
- package/dist/console-payloads.js +10 -10
- package/dist/console-timeline.d.ts +12 -30
- package/dist/console-timeline.d.ts.map +1 -1
- package/dist/console-timeline.js +44 -45
- package/dist/index.d.ts +7 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/instructions-presets.d.ts +1 -1
- package/dist/instructions-presets.d.ts.map +1 -1
- package/dist/instructions-presets.js +40 -22
- package/dist/llm-backed-negotiator.d.ts +6 -6
- package/dist/llm-backed-negotiator.d.ts.map +1 -1
- package/dist/llm-backed-negotiator.js +92 -91
- package/dist/mcp-apps-inbound.d.ts +3 -3
- package/dist/mcp-apps-inbound.d.ts.map +1 -1
- package/dist/mcp-apps-inbound.js +28 -23
- package/dist/mcp-apps-outbound.d.ts +136 -121
- package/dist/mcp-apps-outbound.d.ts.map +1 -1
- package/dist/mcp-apps-outbound.js +384 -414
- package/dist/mcp-mounts.d.ts +21 -20
- package/dist/mcp-mounts.d.ts.map +1 -1
- package/dist/mcp-mounts.js +25 -29
- package/dist/{session-channel.d.ts → render-channel.d.ts} +145 -102
- package/dist/render-channel.d.ts.map +1 -0
- package/dist/{session-channel.js → render-channel.js} +481 -462
- package/dist/schema-compat.d.ts +11 -11
- package/dist/schema-compat.d.ts.map +1 -1
- package/dist/schema-compat.js +6 -6
- package/dist/server.d.ts +224 -219
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1498 -1963
- package/dist/storage.d.ts +7 -7
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +12 -12
- package/package.json +12 -11
- package/dist/render-gate.d.ts +0 -87
- package/dist/render-gate.d.ts.map +0 -1
- package/dist/render-gate.js +0 -77
- package/dist/render-rate-limit.d.ts +0 -59
- package/dist/render-rate-limit.d.ts.map +0 -1
- package/dist/render-rate-limit.js +0 -73
- package/dist/render-signing.d.ts +0 -98
- package/dist/render-signing.d.ts.map +0 -1
- package/dist/render-signing.js +0 -113
- package/dist/session-channel.d.ts.map +0 -1
package/dist/storage.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { RenderStore, ThreadStore, VectorStore } from '@ggui-ai/mcp-server-core';
|
|
2
2
|
import type { StorageConfig } from '@ggui-ai/project-config';
|
|
3
3
|
export interface ResolveStorageFromConfigOptions {
|
|
4
4
|
/**
|
|
5
5
|
* Directory used to resolve relative `path` values in the storage
|
|
6
6
|
* config. Typically the directory containing `ggui.json` so a
|
|
7
|
-
* manifest saying `"path": "./ggui-
|
|
7
|
+
* manifest saying `"path": "./ggui-renders.sqlite"` lands next to
|
|
8
8
|
* the manifest (not CWD, which would silently create a file wherever
|
|
9
9
|
* the process happened to be started).
|
|
10
10
|
*
|
|
@@ -16,9 +16,9 @@ export interface ResolveStorageFromConfigOptions {
|
|
|
16
16
|
readonly baseDir?: string;
|
|
17
17
|
}
|
|
18
18
|
export interface ResolvedStorageStores {
|
|
19
|
-
/** Concrete
|
|
19
|
+
/** Concrete RenderStore, iff the config declared one. Undefined =
|
|
20
20
|
* caller falls back to createGguiServer's in-memory default. */
|
|
21
|
-
readonly
|
|
21
|
+
readonly renderStore?: RenderStore;
|
|
22
22
|
/** Concrete VectorStore, iff the config declared one. Undefined =
|
|
23
23
|
* caller falls back to createGguiServer's in-memory default. */
|
|
24
24
|
readonly vectors?: VectorStore;
|
|
@@ -33,7 +33,7 @@ export interface ResolvedStorageStores {
|
|
|
33
33
|
* real; routes mount and work until restart).
|
|
34
34
|
* - `driver: 'sqlite'` → `SqliteThreadStore` (durable).
|
|
35
35
|
*
|
|
36
|
-
* This is the one semantic deviation from
|
|
36
|
+
* This is the one semantic deviation from renders/vectors, which
|
|
37
37
|
* treat `driver: 'memory'` as a no-op because `createGguiServer`
|
|
38
38
|
* already defaults those to in-memory stores internally. Threads
|
|
39
39
|
* don't have an implicit default — the whole route family is
|
|
@@ -60,13 +60,13 @@ export interface ResolvedStorageStores {
|
|
|
60
60
|
* case of `threads:`, no thread
|
|
61
61
|
* routes at all).
|
|
62
62
|
*
|
|
63
|
-
* - `
|
|
63
|
+
* - `renders` / `vectors`:
|
|
64
64
|
* - `driver: 'memory'` → omitted from the bundle
|
|
65
65
|
* (same fallback — declaring
|
|
66
66
|
* memory is the same outcome
|
|
67
67
|
* as omitting it; present
|
|
68
68
|
* for intent visibility).
|
|
69
|
-
* - `driver: 'sqlite'` → `
|
|
69
|
+
* - `driver: 'sqlite'` → `SqliteRenderStore` /
|
|
70
70
|
* `SqliteVectorStore`.
|
|
71
71
|
*
|
|
72
72
|
* - `threads`:
|
package/dist/storage.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAqDA,OAAO,KAAK,EACV,
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAqDA,OAAO,KAAK,EACV,WAAW,EACX,WAAW,EACX,WAAW,EACZ,MAAM,0BAA0B,CAAC;AAQlC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAE7D,MAAM,WAAW,+BAA+B;IAC9C;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,qBAAqB;IACpC;oEACgE;IAChE,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC;IACnC;oEACgE;IAChE,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC;IAC/B;;;;;;;;;;;;;;;;;yCAiBqC;IACrC,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC;IACnC;;;;;;OAMG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,SAAS,GAAG,WAAW,CAAC;CACrD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,aAAa,GAAG,SAAS,EACjC,IAAI,GAAE,+BAAoC,GACzC,OAAO,CAAC,qBAAqB,CAAC,CAgDhC"}
|
package/dist/storage.js
CHANGED
|
@@ -27,12 +27,12 @@
|
|
|
27
27
|
* ## Why `better-sqlite3` gets imported dynamically
|
|
28
28
|
*
|
|
29
29
|
* `better-sqlite3` is an optional peer dep of `@ggui-ai/mcp-server-core`.
|
|
30
|
-
* If we `import {
|
|
30
|
+
* If we `import { SqliteRenderStore } from '@ggui-ai/mcp-server-core/sqlite'`
|
|
31
31
|
* at the top of this file, any consumer that doesn't opt into SQLite
|
|
32
32
|
* storage still pays the peer-dep cost (the module graph resolves the
|
|
33
33
|
* subpath at import time, which tries to load better-sqlite3's N-API
|
|
34
34
|
* binary). Dynamic `await import(...)` keeps the cost truly optional:
|
|
35
|
-
* SQLite is loaded only when `storage.
|
|
35
|
+
* SQLite is loaded only when `storage.renders.driver === 'sqlite'` or
|
|
36
36
|
* `storage.vectors.driver === 'sqlite'`.
|
|
37
37
|
*
|
|
38
38
|
* ## Why this is async + returns a bundle instead of augmenting createGguiServer
|
|
@@ -41,9 +41,9 @@
|
|
|
41
41
|
* who want storage from config write:
|
|
42
42
|
*
|
|
43
43
|
* ```ts
|
|
44
|
-
* const {
|
|
44
|
+
* const { renderStore, vectors } =
|
|
45
45
|
* await resolveStorageFromConfig(manifest.storage, { baseDir: projectRoot });
|
|
46
|
-
* const server = createGguiServer({
|
|
46
|
+
* const server = createGguiServer({ renderStore, vectors });
|
|
47
47
|
* ```
|
|
48
48
|
*
|
|
49
49
|
* Explicit instances passed to `createGguiServer` still win — this
|
|
@@ -68,13 +68,13 @@ import { InMemoryThreadStore } from '@ggui-ai/mcp-server-core/in-memory';
|
|
|
68
68
|
* case of `threads:`, no thread
|
|
69
69
|
* routes at all).
|
|
70
70
|
*
|
|
71
|
-
* - `
|
|
71
|
+
* - `renders` / `vectors`:
|
|
72
72
|
* - `driver: 'memory'` → omitted from the bundle
|
|
73
73
|
* (same fallback — declaring
|
|
74
74
|
* memory is the same outcome
|
|
75
75
|
* as omitting it; present
|
|
76
76
|
* for intent visibility).
|
|
77
|
-
* - `driver: 'sqlite'` → `
|
|
77
|
+
* - `driver: 'sqlite'` → `SqliteRenderStore` /
|
|
78
78
|
* `SqliteVectorStore`.
|
|
79
79
|
*
|
|
80
80
|
* - `threads`:
|
|
@@ -96,7 +96,7 @@ import { InMemoryThreadStore } from '@ggui-ai/mcp-server-core/in-memory';
|
|
|
96
96
|
export async function resolveStorageFromConfig(config, opts = {}) {
|
|
97
97
|
if (!config)
|
|
98
98
|
return {};
|
|
99
|
-
const
|
|
99
|
+
const rendersSqlite = config.renders?.driver === 'sqlite';
|
|
100
100
|
const vectorsSqlite = config.vectors?.driver === 'sqlite';
|
|
101
101
|
const threadsSqlite = config.threads?.driver === 'sqlite';
|
|
102
102
|
const threadsMemory = config.threads?.driver === 'memory';
|
|
@@ -108,16 +108,16 @@ export async function resolveStorageFromConfig(config, opts = {}) {
|
|
|
108
108
|
result.threadStore = new InMemoryThreadStore();
|
|
109
109
|
result.threadDurability = 'ephemeral';
|
|
110
110
|
}
|
|
111
|
-
if (!
|
|
111
|
+
if (!rendersSqlite && !vectorsSqlite && !threadsSqlite)
|
|
112
112
|
return result;
|
|
113
113
|
// Single dynamic import serves every sqlite adapter — better-sqlite3's
|
|
114
114
|
// N-API binding loads once even if the import lands twice; the
|
|
115
115
|
// subpath barrel is cached by Node's module loader.
|
|
116
|
-
const {
|
|
117
|
-
if (config.
|
|
118
|
-
const filename = resolveStoragePath(config.
|
|
116
|
+
const { SqliteRenderStore, SqliteVectorStore, SqliteThreadStore } = await import('@ggui-ai/mcp-server-core/sqlite');
|
|
117
|
+
if (config.renders && config.renders.driver === 'sqlite') {
|
|
118
|
+
const filename = resolveStoragePath(config.renders.path, opts.baseDir);
|
|
119
119
|
ensureParentDir(filename);
|
|
120
|
-
result.
|
|
120
|
+
result.renderStore = new SqliteRenderStore({ filename });
|
|
121
121
|
}
|
|
122
122
|
if (config.vectors && config.vectors.driver === 'sqlite') {
|
|
123
123
|
const filename = resolveStoragePath(config.vectors.path, opts.baseDir);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ggui-ai/mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0-alpha.3",
|
|
4
4
|
"description": "Self-hosted MCP server for the ggui protocol. An HTTP/MCP binding layer over @ggui-ai/mcp-server-handlers and the reference adapters from @ggui-ai/mcp-server-core — run it via `ggui serve`.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ggui",
|
|
@@ -25,22 +25,23 @@
|
|
|
25
25
|
}
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
+
"@modelcontextprotocol/ext-apps": "^1.7.2",
|
|
28
29
|
"@modelcontextprotocol/sdk": "^1.27.0",
|
|
29
30
|
"express": "^4.21.0",
|
|
30
31
|
"nodemailer": "^8.0.7",
|
|
31
32
|
"resend": "^6.12.2",
|
|
32
33
|
"ws": "^8.20.1",
|
|
33
34
|
"zod": "^4.3.6",
|
|
34
|
-
"@ggui-ai/
|
|
35
|
-
"@ggui-ai/mcp-server-core": "0.
|
|
36
|
-
"@ggui-ai/mcp-server-handlers": "0.
|
|
37
|
-
"@ggui-ai/
|
|
38
|
-
"@ggui-ai/negotiator": "0.
|
|
39
|
-
"@ggui-ai/
|
|
40
|
-
"@ggui-ai/
|
|
41
|
-
"@ggui-ai/protocol": "0.
|
|
42
|
-
"@ggui-ai/ui-gen": "0.
|
|
43
|
-
"@ggui-ai/ui-registry": "0.
|
|
35
|
+
"@ggui-ai/console": "0.2.0-alpha.3",
|
|
36
|
+
"@ggui-ai/mcp-server-core": "0.2.0-alpha.3",
|
|
37
|
+
"@ggui-ai/mcp-server-handlers": "0.2.0-alpha.3",
|
|
38
|
+
"@ggui-ai/iframe-runtime": "0.2.0-alpha.3",
|
|
39
|
+
"@ggui-ai/negotiator": "0.2.0-alpha.3",
|
|
40
|
+
"@ggui-ai/preview-a2ui": "0.2.0-alpha.3",
|
|
41
|
+
"@ggui-ai/project-config": "0.2.0-alpha.3",
|
|
42
|
+
"@ggui-ai/protocol": "0.2.0-alpha.3",
|
|
43
|
+
"@ggui-ai/ui-gen": "0.2.0-alpha.3",
|
|
44
|
+
"@ggui-ai/ui-registry": "0.2.0-alpha.3"
|
|
44
45
|
},
|
|
45
46
|
"devDependencies": {
|
|
46
47
|
"@types/express": "^4.17.21",
|
package/dist/render-gate.d.ts
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `gateShortCode` — the single chokepoint for capability-URL routes.
|
|
3
|
-
*
|
|
4
|
-
* `/r/<code>` and `/api/bootstrap/<code>` are sibling surfaces that
|
|
5
|
-
* expose the same render state via different content types (HTML
|
|
6
|
-
* shell vs JSON bootstrap envelope). Before this gate, each route
|
|
7
|
-
* implemented its own lookup + session resolve + 404 mapping, which
|
|
8
|
-
* meant: hardening one route (revoke checks, sig verify, rate limit,
|
|
9
|
-
* audit log) silently left the other unhardened. Capability URLs are
|
|
10
|
-
* the credential — drift across the two routes is a defense gap.
|
|
11
|
-
*
|
|
12
|
-
* This module centralizes the gate logic. Routes ask the gate for
|
|
13
|
-
* an outcome; content-type rendering stays on the route.
|
|
14
|
-
*
|
|
15
|
-
* Outcomes today:
|
|
16
|
-
* - `ok` — binding lookup + session resolve both succeeded
|
|
17
|
-
* - `not_found` — shortCode unknown OR revoked (lookup returned null)
|
|
18
|
-
* - `session_missing` — binding present but session record absent
|
|
19
|
-
*
|
|
20
|
-
* Outcomes reserved for future slices (sig verify, rate limit, etc.):
|
|
21
|
-
* - `invalid_signature` (HMAC slice)
|
|
22
|
-
* - `expired` (TTL slice)
|
|
23
|
-
* - `rate_limited` (per-code throttle slice)
|
|
24
|
-
*
|
|
25
|
-
* Each future slice extends the union; routes can map new codes to
|
|
26
|
-
* appropriate status codes (403, 410, 429) without re-implementing
|
|
27
|
-
* the lookup.
|
|
28
|
-
*/
|
|
29
|
-
import type { Session } from '@ggui-ai/protocol';
|
|
30
|
-
import type { ShortCodeIndex } from '@ggui-ai/mcp-server-core';
|
|
31
|
-
import type { RenderSigner } from './render-signing.js';
|
|
32
|
-
/** Minimal SessionStore surface the gate uses — narrower than the
|
|
33
|
-
* full interface so test fakes stay tiny. */
|
|
34
|
-
interface GateSessionStore {
|
|
35
|
-
get(id: string): Promise<Session | null>;
|
|
36
|
-
}
|
|
37
|
-
/** The single failure surface the gate emits. New codes added here
|
|
38
|
-
* grow the discriminated union; consumers exhaustively match. */
|
|
39
|
-
export type RenderGateFailureCode = 'not_found' | 'session_missing' | 'invalid_signature' | 'expired' | 'malformed_signature';
|
|
40
|
-
export type RenderGateOutcome = {
|
|
41
|
-
readonly ok: true;
|
|
42
|
-
readonly binding: NonNullable<Awaited<ReturnType<ShortCodeIndex['lookup']>>>;
|
|
43
|
-
readonly session: Session;
|
|
44
|
-
} | {
|
|
45
|
-
readonly ok: false;
|
|
46
|
-
readonly code: RenderGateFailureCode;
|
|
47
|
-
/** Optional context that's safe to pass to a structured logger
|
|
48
|
-
* but MUST NOT leak to the wire — error responses stay generic
|
|
49
|
-
* ("shortCode not recognised") to avoid info-leak amplification
|
|
50
|
-
* on brute-force attempts. */
|
|
51
|
-
readonly logContext?: Record<string, unknown>;
|
|
52
|
-
};
|
|
53
|
-
export interface GateShortCodeInput {
|
|
54
|
-
readonly shortCode: string;
|
|
55
|
-
readonly shortCodeIndex: ShortCodeIndex;
|
|
56
|
-
readonly sessionStore: GateSessionStore;
|
|
57
|
-
/**
|
|
58
|
-
* Optional render-URL signer. When wired, the gate verifies the
|
|
59
|
-
* incoming `sig`+`exp` query pair BEFORE lookup; an invalid or
|
|
60
|
-
* expired signature short-circuits with a tagged failure code so
|
|
61
|
-
* the route can map to an appropriate status (403/410). Absent =
|
|
62
|
-
* signing disabled (legacy or `--no-render-signing` boot).
|
|
63
|
-
*/
|
|
64
|
-
readonly signer?: RenderSigner;
|
|
65
|
-
/**
|
|
66
|
-
* Raw `sig`+`exp` from the request query string. When `signer` is
|
|
67
|
-
* wired but these are missing, the gate fails with
|
|
68
|
-
* `malformed_signature`. When `signer` is absent, both fields are
|
|
69
|
-
* ignored.
|
|
70
|
-
*/
|
|
71
|
-
readonly signedQuery?: {
|
|
72
|
-
readonly sig?: string | undefined;
|
|
73
|
-
readonly exp?: string | undefined;
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Look up a shortCode + resolve its session. Idempotent + side-
|
|
78
|
-
* effect-free; safe to call from any HTTP handler. Returns a tagged
|
|
79
|
-
* outcome the caller maps to its content-type-appropriate response.
|
|
80
|
-
*
|
|
81
|
-
* Empty input MUST be caught upstream (path-param validation) — the
|
|
82
|
-
* gate assumes a non-empty shortCode arrives because the route
|
|
83
|
-
* pattern requires it.
|
|
84
|
-
*/
|
|
85
|
-
export declare function gateShortCode(input: GateShortCodeInput): Promise<RenderGateOutcome>;
|
|
86
|
-
export {};
|
|
87
|
-
//# sourceMappingURL=render-gate.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"render-gate.d.ts","sourceRoot":"","sources":["../src/render-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD;8CAC8C;AAC9C,UAAU,gBAAgB;IACxB,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;CAC1C;AAED;kEACkE;AAClE,MAAM,MAAM,qBAAqB,GAC7B,WAAW,GACX,iBAAiB,GACjB,mBAAmB,GACnB,SAAS,GACT,qBAAqB,CAAC;AAE1B,MAAM,MAAM,iBAAiB,GACzB;IACE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAClB,QAAQ,CAAC,OAAO,EAAE,WAAW,CAC3B,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAC,CAC9C,CAAC;IACF,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B,GACD;IACE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;IACrC;;;mCAG+B;IAC/B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/C,CAAC;AAEN,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC,QAAQ,CAAC,YAAY,EAAE,gBAAgB,CAAC;IACxC;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC;IAC/B;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE;QACrB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QAClC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;KACnC,CAAC;CACH;AAED;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CACjC,KAAK,EAAE,kBAAkB,GACxB,OAAO,CAAC,iBAAiB,CAAC,CAwC5B"}
|
package/dist/render-gate.js
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `gateShortCode` — the single chokepoint for capability-URL routes.
|
|
3
|
-
*
|
|
4
|
-
* `/r/<code>` and `/api/bootstrap/<code>` are sibling surfaces that
|
|
5
|
-
* expose the same render state via different content types (HTML
|
|
6
|
-
* shell vs JSON bootstrap envelope). Before this gate, each route
|
|
7
|
-
* implemented its own lookup + session resolve + 404 mapping, which
|
|
8
|
-
* meant: hardening one route (revoke checks, sig verify, rate limit,
|
|
9
|
-
* audit log) silently left the other unhardened. Capability URLs are
|
|
10
|
-
* the credential — drift across the two routes is a defense gap.
|
|
11
|
-
*
|
|
12
|
-
* This module centralizes the gate logic. Routes ask the gate for
|
|
13
|
-
* an outcome; content-type rendering stays on the route.
|
|
14
|
-
*
|
|
15
|
-
* Outcomes today:
|
|
16
|
-
* - `ok` — binding lookup + session resolve both succeeded
|
|
17
|
-
* - `not_found` — shortCode unknown OR revoked (lookup returned null)
|
|
18
|
-
* - `session_missing` — binding present but session record absent
|
|
19
|
-
*
|
|
20
|
-
* Outcomes reserved for future slices (sig verify, rate limit, etc.):
|
|
21
|
-
* - `invalid_signature` (HMAC slice)
|
|
22
|
-
* - `expired` (TTL slice)
|
|
23
|
-
* - `rate_limited` (per-code throttle slice)
|
|
24
|
-
*
|
|
25
|
-
* Each future slice extends the union; routes can map new codes to
|
|
26
|
-
* appropriate status codes (403, 410, 429) without re-implementing
|
|
27
|
-
* the lookup.
|
|
28
|
-
*/
|
|
29
|
-
/**
|
|
30
|
-
* Look up a shortCode + resolve its session. Idempotent + side-
|
|
31
|
-
* effect-free; safe to call from any HTTP handler. Returns a tagged
|
|
32
|
-
* outcome the caller maps to its content-type-appropriate response.
|
|
33
|
-
*
|
|
34
|
-
* Empty input MUST be caught upstream (path-param validation) — the
|
|
35
|
-
* gate assumes a non-empty shortCode arrives because the route
|
|
36
|
-
* pattern requires it.
|
|
37
|
-
*/
|
|
38
|
-
export async function gateShortCode(input) {
|
|
39
|
-
// Signature verification runs BEFORE the lookup so a flood of
|
|
40
|
-
// requests with garbage shortCodes can be rejected without hitting
|
|
41
|
-
// the index. The signer also gates information-leak: revealing
|
|
42
|
-
// "this code doesn't exist" vs "your sig is wrong" lets an
|
|
43
|
-
// attacker probe shortCodes via timing differences. Rejecting on
|
|
44
|
-
// signature first keeps the response timing uniform for any code
|
|
45
|
-
// an attacker hasn't seen a valid sig for.
|
|
46
|
-
if (input.signer) {
|
|
47
|
-
const verify = input.signer.verify({
|
|
48
|
-
shortCode: input.shortCode,
|
|
49
|
-
sig: input.signedQuery?.sig,
|
|
50
|
-
exp: input.signedQuery?.exp,
|
|
51
|
-
});
|
|
52
|
-
if (!verify.ok) {
|
|
53
|
-
return {
|
|
54
|
-
ok: false,
|
|
55
|
-
code: verify.code === 'malformed'
|
|
56
|
-
? 'malformed_signature'
|
|
57
|
-
: verify.code,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
const binding = await input.shortCodeIndex.lookup(input.shortCode);
|
|
62
|
-
if (!binding) {
|
|
63
|
-
return { ok: false, code: 'not_found' };
|
|
64
|
-
}
|
|
65
|
-
const session = await input.sessionStore.get(binding.sessionId);
|
|
66
|
-
if (!session) {
|
|
67
|
-
return {
|
|
68
|
-
ok: false,
|
|
69
|
-
code: 'session_missing',
|
|
70
|
-
logContext: {
|
|
71
|
-
shortCode: input.shortCode,
|
|
72
|
-
sessionId: binding.sessionId,
|
|
73
|
-
},
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
return { ok: true, binding, session };
|
|
77
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Per-shortCode rate limiter.
|
|
3
|
-
*
|
|
4
|
-
* Brute-force attempts on `/r/<code>` or `/api/bootstrap/<code>` are
|
|
5
|
-
* trivially detected at the wire by per-shortCode call counts. Even
|
|
6
|
-
* with high-entropy shortCodes and HMAC-signed render URLs, the gate
|
|
7
|
-
* still hits a real backend on every request — so DoS resistance +
|
|
8
|
-
* abuse-signal logging matter independently of entropy.
|
|
9
|
-
*
|
|
10
|
-
* **Design choice — rate-limit by shortCode, not by peer.**
|
|
11
|
-
* Cross-origin iframes (claude.ai) NAT every user through the host's
|
|
12
|
-
* outbound proxy, so per-peer limits would either rate-limit the whole
|
|
13
|
-
* host (false-positive flood) or accept every peer (no signal). Per-
|
|
14
|
-
* shortCode is the right granularity: 30 hits/minute on a single
|
|
15
|
-
* code is abuse no matter who sent them; 30 hits/minute spread across
|
|
16
|
-
* 30 unique codes is normal traffic.
|
|
17
|
-
*
|
|
18
|
-
* In-memory + per-process by default. Operator-grade deployments
|
|
19
|
-
* back this with a shared store (Redis bucket counter) — wire via the
|
|
20
|
-
* {@link RenderRateLimiter} interface; the OSS reference uses a
|
|
21
|
-
* `Map<shortCode, {windowStart, count}>` with periodic cleanup.
|
|
22
|
-
*/
|
|
23
|
-
/** Configuration knobs. Operators tune via boot options. */
|
|
24
|
-
export interface RenderRateLimiterConfig {
|
|
25
|
-
/** Window size in seconds. Each shortCode gets `limit` hits per
|
|
26
|
-
* rolling window. Default 60s. */
|
|
27
|
-
readonly windowSeconds?: number;
|
|
28
|
-
/** Per-shortCode hit cap inside the window. Default 30. */
|
|
29
|
-
readonly limit?: number;
|
|
30
|
-
/** Clock seam for tests. */
|
|
31
|
-
readonly now?: () => number;
|
|
32
|
-
}
|
|
33
|
-
export type RenderRateLimitResult = {
|
|
34
|
-
readonly allowed: true;
|
|
35
|
-
} | {
|
|
36
|
-
readonly allowed: false;
|
|
37
|
-
readonly retryAfterSeconds: number;
|
|
38
|
-
};
|
|
39
|
-
export interface RenderRateLimiter {
|
|
40
|
-
/** Record a hit on `shortCode`. Returns the gate decision + (when
|
|
41
|
-
* rejected) a Retry-After hint in seconds. */
|
|
42
|
-
check(shortCode: string): RenderRateLimitResult;
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* In-memory reference implementation. Cleanup runs lazily on each
|
|
46
|
-
* call — entries past their window-end are reaped before the count is
|
|
47
|
-
* read. Worst-case heap: bounded by the number of distinct shortCodes
|
|
48
|
-
* hit within the last `windowSeconds`; pre-launch OSS workloads stay
|
|
49
|
-
* tiny enough that the periodic pass is sufficient.
|
|
50
|
-
*/
|
|
51
|
-
export declare function createInMemoryRenderRateLimiter(cfg?: RenderRateLimiterConfig): RenderRateLimiter;
|
|
52
|
-
/**
|
|
53
|
-
* Mask a shortCode for log output. The full code is the credential —
|
|
54
|
-
* log it verbatim and a leaked log line becomes a leaked URL. Show
|
|
55
|
-
* just enough (first 3 chars) to correlate within a session without
|
|
56
|
-
* giving the credential away.
|
|
57
|
-
*/
|
|
58
|
-
export declare function maskShortCode(shortCode: string): string;
|
|
59
|
-
//# sourceMappingURL=render-rate-limit.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"render-rate-limit.d.ts","sourceRoot":"","sources":["../src/render-rate-limit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,4DAA4D;AAC5D,MAAM,WAAW,uBAAuB;IACtC;uCACmC;IACnC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,2DAA2D;IAC3D,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,4BAA4B;IAC5B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CAC7B;AAED,MAAM,MAAM,qBAAqB,GAC7B;IAAE,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAA;CAAE,GAC1B;IAAE,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpE,MAAM,WAAW,iBAAiB;IAChC;mDAC+C;IAC/C,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,qBAAqB,CAAC;CACjD;AAED;;;;;;GAMG;AACH,wBAAgB,+BAA+B,CAC7C,GAAG,GAAE,uBAA4B,GAChC,iBAAiB,CAmCnB;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAIvD"}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Per-shortCode rate limiter.
|
|
3
|
-
*
|
|
4
|
-
* Brute-force attempts on `/r/<code>` or `/api/bootstrap/<code>` are
|
|
5
|
-
* trivially detected at the wire by per-shortCode call counts. Even
|
|
6
|
-
* with high-entropy shortCodes and HMAC-signed render URLs, the gate
|
|
7
|
-
* still hits a real backend on every request — so DoS resistance +
|
|
8
|
-
* abuse-signal logging matter independently of entropy.
|
|
9
|
-
*
|
|
10
|
-
* **Design choice — rate-limit by shortCode, not by peer.**
|
|
11
|
-
* Cross-origin iframes (claude.ai) NAT every user through the host's
|
|
12
|
-
* outbound proxy, so per-peer limits would either rate-limit the whole
|
|
13
|
-
* host (false-positive flood) or accept every peer (no signal). Per-
|
|
14
|
-
* shortCode is the right granularity: 30 hits/minute on a single
|
|
15
|
-
* code is abuse no matter who sent them; 30 hits/minute spread across
|
|
16
|
-
* 30 unique codes is normal traffic.
|
|
17
|
-
*
|
|
18
|
-
* In-memory + per-process by default. Operator-grade deployments
|
|
19
|
-
* back this with a shared store (Redis bucket counter) — wire via the
|
|
20
|
-
* {@link RenderRateLimiter} interface; the OSS reference uses a
|
|
21
|
-
* `Map<shortCode, {windowStart, count}>` with periodic cleanup.
|
|
22
|
-
*/
|
|
23
|
-
/**
|
|
24
|
-
* In-memory reference implementation. Cleanup runs lazily on each
|
|
25
|
-
* call — entries past their window-end are reaped before the count is
|
|
26
|
-
* read. Worst-case heap: bounded by the number of distinct shortCodes
|
|
27
|
-
* hit within the last `windowSeconds`; pre-launch OSS workloads stay
|
|
28
|
-
* tiny enough that the periodic pass is sufficient.
|
|
29
|
-
*/
|
|
30
|
-
export function createInMemoryRenderRateLimiter(cfg = {}) {
|
|
31
|
-
const windowMs = (cfg.windowSeconds ?? 60) * 1000;
|
|
32
|
-
const limit = cfg.limit ?? 30;
|
|
33
|
-
const now = cfg.now ?? (() => Date.now());
|
|
34
|
-
const buckets = new Map();
|
|
35
|
-
return {
|
|
36
|
-
check(shortCode) {
|
|
37
|
-
if (!shortCode) {
|
|
38
|
-
// Empty shortCode is upstream's path-validation problem; we
|
|
39
|
-
// still return allowed:true to keep the contract uniform.
|
|
40
|
-
return { allowed: true };
|
|
41
|
-
}
|
|
42
|
-
const t = now();
|
|
43
|
-
const existing = buckets.get(shortCode);
|
|
44
|
-
if (!existing || t - existing.windowStart >= windowMs) {
|
|
45
|
-
// Window expired or never opened — start a fresh window.
|
|
46
|
-
buckets.set(shortCode, { windowStart: t, count: 1 });
|
|
47
|
-
return { allowed: true };
|
|
48
|
-
}
|
|
49
|
-
if (existing.count >= limit) {
|
|
50
|
-
const retryMs = windowMs - (t - existing.windowStart);
|
|
51
|
-
return {
|
|
52
|
-
allowed: false,
|
|
53
|
-
retryAfterSeconds: Math.max(1, Math.ceil(retryMs / 1000)),
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
existing.count += 1;
|
|
57
|
-
return { allowed: true };
|
|
58
|
-
},
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Mask a shortCode for log output. The full code is the credential —
|
|
63
|
-
* log it verbatim and a leaked log line becomes a leaked URL. Show
|
|
64
|
-
* just enough (first 3 chars) to correlate within a session without
|
|
65
|
-
* giving the credential away.
|
|
66
|
-
*/
|
|
67
|
-
export function maskShortCode(shortCode) {
|
|
68
|
-
if (!shortCode || shortCode.length === 0)
|
|
69
|
-
return '<empty>';
|
|
70
|
-
if (shortCode.length <= 3)
|
|
71
|
-
return `${shortCode}***`;
|
|
72
|
-
return `${shortCode.slice(0, 3)}***`;
|
|
73
|
-
}
|
package/dist/render-signing.d.ts
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HMAC-signed render URL helpers (capability-URL hardening).
|
|
3
|
-
*
|
|
4
|
-
* The shortCode IS the credential under the capability-URL model.
|
|
5
|
-
* Signed render URLs add three properties on top of the base model
|
|
6
|
-
* (entropy + revoke + parity gate):
|
|
7
|
-
*
|
|
8
|
-
* 1. **Time-bound.** Each minted URL carries `?exp=<unix>`. Past
|
|
9
|
-
* that timestamp, the gate refuses even if the binding still
|
|
10
|
-
* exists. A leaked URL stops working without operator action.
|
|
11
|
-
* 2. **Tamper-evident.** Each minted URL carries `?sig=<hmac>` over
|
|
12
|
-
* `(shortCode, exp)`. Changing either invalidates the sig.
|
|
13
|
-
* Attackers who learn ONE valid shortCode can't generate URLs
|
|
14
|
-
* for shortCodes they're guessing.
|
|
15
|
-
* 3. **Nuclear revoke (operational).** Rotating the signing secret
|
|
16
|
-
* invalidates every outstanding URL across the whole server in
|
|
17
|
-
* one move. Useful for incident response.
|
|
18
|
-
*
|
|
19
|
-
* **Threat model — what this is NOT.** The signature does not bind
|
|
20
|
-
* the URL to a user, IP, or session. Anyone with the URL still has
|
|
21
|
-
* full access until it expires. That's the Google-Docs-share-link
|
|
22
|
-
* model. It's the only viable model when the URL must be embeddable
|
|
23
|
-
* in cross-origin iframes (claude.ai), which can't attach auth
|
|
24
|
-
* headers — see plan audit dated 2026-05-15.
|
|
25
|
-
*
|
|
26
|
-
* **Key lifecycle.** Operators set `--render-signing-secret <hex>` to
|
|
27
|
-
* pin a stable key (URLs survive restarts; rotation is opt-in). When
|
|
28
|
-
* omitted, the secret is auto-generated at boot — restart = every
|
|
29
|
-
* outstanding URL dies. Document both behaviors so operators choose
|
|
30
|
-
* deliberately.
|
|
31
|
-
*
|
|
32
|
-
* **Opt-out.** `--no-render-signing` (operator boots without a signer)
|
|
33
|
-
* disables the layer entirely. The gate skips the verify step and
|
|
34
|
-
* behavior reverts to plain (unsigned) capability URLs. Useful for
|
|
35
|
-
* legacy hosts that strip query strings or tooling that pre-records
|
|
36
|
-
* URLs.
|
|
37
|
-
*/
|
|
38
|
-
export type RenderSignerVerifyResult = {
|
|
39
|
-
readonly ok: true;
|
|
40
|
-
} | {
|
|
41
|
-
readonly ok: false;
|
|
42
|
-
readonly code: 'invalid_signature' | 'expired' | 'malformed';
|
|
43
|
-
};
|
|
44
|
-
export interface RenderSigner {
|
|
45
|
-
/**
|
|
46
|
-
* Mint a `{sig, exp}` pair for `shortCode`. `expSeconds` overrides
|
|
47
|
-
* the configured TTL. The returned `exp` is a unix-epoch second
|
|
48
|
-
* timestamp so URLs stay short.
|
|
49
|
-
*/
|
|
50
|
-
sign(shortCode: string, expSecondsOverride?: number): {
|
|
51
|
-
readonly sig: string;
|
|
52
|
-
readonly exp: number;
|
|
53
|
-
};
|
|
54
|
-
/**
|
|
55
|
-
* Verify a `sig` + `exp` against `shortCode`. Returns `{ok: true}`
|
|
56
|
-
* on success; tagged failure otherwise. `malformed` covers missing
|
|
57
|
-
* fields, non-numeric `exp`, sig-length mismatches — distinguishing
|
|
58
|
-
* those from `invalid_signature` keeps the audit log informative
|
|
59
|
-
* without leaking detail to the wire (the response code stays
|
|
60
|
-
* uniform).
|
|
61
|
-
*/
|
|
62
|
-
verify(args: {
|
|
63
|
-
readonly shortCode: string;
|
|
64
|
-
readonly sig: string | undefined;
|
|
65
|
-
readonly exp: string | undefined;
|
|
66
|
-
}): RenderSignerVerifyResult;
|
|
67
|
-
/** Encode `{sig, exp}` as a `?sig=...&exp=...` query string suitable
|
|
68
|
-
* for appending to a render URL. Returns the suffix WITHOUT a
|
|
69
|
-
* leading `?` or `&` — callers join with whatever separator their
|
|
70
|
-
* URL already has. */
|
|
71
|
-
toQuerySuffix(args: {
|
|
72
|
-
readonly sig: string;
|
|
73
|
-
readonly exp: number;
|
|
74
|
-
}): string;
|
|
75
|
-
}
|
|
76
|
-
export interface CreateRenderSignerInput {
|
|
77
|
-
/**
|
|
78
|
-
* 32-byte hex key. When omitted, a fresh random key is minted —
|
|
79
|
-
* stable for the lifetime of this process, lost on restart.
|
|
80
|
-
* Operators wanting URLs that survive restarts MUST pass an
|
|
81
|
-
* explicit key (typically from a sealed-secret store).
|
|
82
|
-
*/
|
|
83
|
-
readonly secret?: string;
|
|
84
|
-
/**
|
|
85
|
-
* Default URL lifetime in seconds. Override per-call via
|
|
86
|
-
* {@link RenderSigner.sign}'s `expSecondsOverride`.
|
|
87
|
-
*/
|
|
88
|
-
readonly ttlSeconds?: number;
|
|
89
|
-
/** Clock seam for tests. Defaults to `Date.now()`. */
|
|
90
|
-
readonly now?: () => number;
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Build a {@link RenderSigner}. Memoize the result on the server-boot
|
|
94
|
-
* scope; the secret rotates only when the operator restarts (or sets
|
|
95
|
-
* a new explicit `--render-signing-secret`).
|
|
96
|
-
*/
|
|
97
|
-
export declare function createRenderSigner(input?: CreateRenderSignerInput): RenderSigner;
|
|
98
|
-
//# sourceMappingURL=render-signing.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"render-signing.d.ts","sourceRoot":"","sources":["../src/render-signing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAUH,MAAM,MAAM,wBAAwB,GAChC;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAA;CAAE,GACrB;IACE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,IAAI,EAAE,mBAAmB,GAAG,SAAS,GAAG,WAAW,CAAC;CAC9D,CAAC;AAEN,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,kBAAkB,CAAC,EAAE,MAAM,GAAG;QACpD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;KACtB,CAAC;IAEF;;;;;;;OAOG;IACH,MAAM,CAAC,IAAI,EAAE;QACX,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;QAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;QACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;KAClC,GAAG,wBAAwB,CAAC;IAE7B;;;2BAGuB;IACvB,aAAa,CAAC,IAAI,EAAE;QAClB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;KACtB,GAAG,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,uBAAuB;IACtC;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,sDAAsD;IACtD,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CAC7B;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,GAAE,uBAA4B,GAClC,YAAY,CA0Ed"}
|