@amalgm/core 0.1.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.
Files changed (87) hide show
  1. package/PURPOSE.md +94 -0
  2. package/README.md +30 -0
  3. package/dist/adapters/index.d.ts +13 -0
  4. package/dist/adapters/index.d.ts.map +1 -0
  5. package/dist/adapters/index.js +11 -0
  6. package/dist/adapters/index.js.map +1 -0
  7. package/dist/identity/auth-records.d.ts +147 -0
  8. package/dist/identity/auth-records.d.ts.map +1 -0
  9. package/dist/identity/auth-records.js +354 -0
  10. package/dist/identity/auth-records.js.map +1 -0
  11. package/dist/identity/ids.d.ts +57 -0
  12. package/dist/identity/ids.d.ts.map +1 -0
  13. package/dist/identity/ids.js +80 -0
  14. package/dist/identity/ids.js.map +1 -0
  15. package/dist/identity/index.d.ts +14 -0
  16. package/dist/identity/index.d.ts.map +1 -0
  17. package/dist/identity/index.js +14 -0
  18. package/dist/identity/index.js.map +1 -0
  19. package/dist/identity/labels.d.ts +115 -0
  20. package/dist/identity/labels.d.ts.map +1 -0
  21. package/dist/identity/labels.js +154 -0
  22. package/dist/identity/labels.js.map +1 -0
  23. package/dist/identity/layout.d.ts +188 -0
  24. package/dist/identity/layout.d.ts.map +1 -0
  25. package/dist/identity/layout.js +233 -0
  26. package/dist/identity/layout.js.map +1 -0
  27. package/dist/identity/ports.d.ts +31 -0
  28. package/dist/identity/ports.d.ts.map +1 -0
  29. package/dist/identity/ports.js +9 -0
  30. package/dist/identity/ports.js.map +1 -0
  31. package/dist/identity/product-state.d.ts +49 -0
  32. package/dist/identity/product-state.d.ts.map +1 -0
  33. package/dist/identity/product-state.js +55 -0
  34. package/dist/identity/product-state.js.map +1 -0
  35. package/dist/index.d.ts +19 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +19 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/supervision/app-health.d.ts +148 -0
  40. package/dist/supervision/app-health.d.ts.map +1 -0
  41. package/dist/supervision/app-health.js +165 -0
  42. package/dist/supervision/app-health.js.map +1 -0
  43. package/dist/supervision/app-restart.d.ts +105 -0
  44. package/dist/supervision/app-restart.d.ts.map +1 -0
  45. package/dist/supervision/app-restart.js +97 -0
  46. package/dist/supervision/app-restart.js.map +1 -0
  47. package/dist/supervision/health.d.ts +218 -0
  48. package/dist/supervision/health.d.ts.map +1 -0
  49. package/dist/supervision/health.js +227 -0
  50. package/dist/supervision/health.js.map +1 -0
  51. package/dist/supervision/index.d.ts +17 -0
  52. package/dist/supervision/index.d.ts.map +1 -0
  53. package/dist/supervision/index.js +17 -0
  54. package/dist/supervision/index.js.map +1 -0
  55. package/dist/supervision/service-registration.d.ts +343 -0
  56. package/dist/supervision/service-registration.d.ts.map +1 -0
  57. package/dist/supervision/service-registration.js +543 -0
  58. package/dist/supervision/service-registration.js.map +1 -0
  59. package/dist/supervision/update.d.ts +231 -0
  60. package/dist/supervision/update.d.ts.map +1 -0
  61. package/dist/supervision/update.js +336 -0
  62. package/dist/supervision/update.js.map +1 -0
  63. package/dist/transport/admission.d.ts +235 -0
  64. package/dist/transport/admission.d.ts.map +1 -0
  65. package/dist/transport/admission.js +313 -0
  66. package/dist/transport/admission.js.map +1 -0
  67. package/dist/transport/auth.d.ts +232 -0
  68. package/dist/transport/auth.d.ts.map +1 -0
  69. package/dist/transport/auth.js +266 -0
  70. package/dist/transport/auth.js.map +1 -0
  71. package/dist/transport/index.d.ts +21 -0
  72. package/dist/transport/index.d.ts.map +1 -0
  73. package/dist/transport/index.js +21 -0
  74. package/dist/transport/index.js.map +1 -0
  75. package/dist/transport/ingress.d.ts +94 -0
  76. package/dist/transport/ingress.d.ts.map +1 -0
  77. package/dist/transport/ingress.js +171 -0
  78. package/dist/transport/ingress.js.map +1 -0
  79. package/dist/transport/routing.d.ts +235 -0
  80. package/dist/transport/routing.d.ts.map +1 -0
  81. package/dist/transport/routing.js +288 -0
  82. package/dist/transport/routing.js.map +1 -0
  83. package/dist/transport/shipped-routes.d.ts +21 -0
  84. package/dist/transport/shipped-routes.d.ts.map +1 -0
  85. package/dist/transport/shipped-routes.js +53 -0
  86. package/dist/transport/shipped-routes.js.map +1 -0
  87. package/package.json +51 -0
@@ -0,0 +1,232 @@
1
+ /**
2
+ * The runtime auth boundary — every HTTP request and WebSocket upgrade
3
+ * that reaches a runtime service passes this gate first. Ported from
4
+ * the engine's runtime/scripts/runtime-auth.js, whose 162 lines are
5
+ * pure decision logic wearing Node clothing: the env read, the crypto
6
+ * compare, and the res writes are the host's; only the verdicts live
7
+ * here (axiom 6). Contracts — header names, the Bearer regex, the 401
8
+ * body, close codes — are byte-exact (axiom 8).
9
+ *
10
+ * The trust model in one line: possession of the runtime token IS
11
+ * authorization, and the token never leaves the machine except inside
12
+ * a tunnel frame the runtime itself stamped.
13
+ */
14
+ /**
15
+ * THE copy of the runtime token header name. The engine spells this
16
+ * string in four places (runtime-auth.js:5, local-gateway.js:72, and
17
+ * inline in both tunnels' localHeaders); cutover deletes the other
18
+ * three.
19
+ */
20
+ export declare const RUNTIME_TOKEN_HEADER = "x-amalgm-runtime-token";
21
+ /** An env-shaped bag of strings. The host passes process.env; tests pass literals. */
22
+ export interface EnvLike {
23
+ readonly [name: string]: string | undefined;
24
+ }
25
+ /**
26
+ * Header maps as Node hands them over: lowercased names, values that
27
+ * may be strings, repeated-header arrays, or (after a JSON round trip
28
+ * through a tunnel frame) numbers.
29
+ */
30
+ export type HeaderValue = string | number | readonly string[] | null | undefined;
31
+ export interface HeadersLike {
32
+ readonly [name: string]: HeaderValue;
33
+ }
34
+ /** runtime-auth.js:26-30 — first value wins for repeated headers; non-strings read as absent. */
35
+ export declare function headerValue(headers: HeadersLike | null | undefined, name: string): string;
36
+ /** runtime-auth.js:7-9 — the expected token is the env token, trimmed. Empty means "none configured". */
37
+ export declare function runtimeTokenFromEnv(env: EnvLike): string;
38
+ /**
39
+ * runtime-auth.js:11-18 — when is the gate armed? The explicit
40
+ * disable wins first, always; otherwise any of: a configured token,
41
+ * an npm-sourced runtime, or local mode. The npm/local clauses arm
42
+ * the gate even with no token configured — which the verdict law
43
+ * below turns into deny-everything, deliberately: a runtime that
44
+ * should have a token but lost it must fail closed, not open.
45
+ */
46
+ export declare function runtimeAuthRequired(env: EnvLike): boolean;
47
+ /** The one Bearer shape the boundary accepts — runtime-auth.js:47. */
48
+ export declare const BEARER_TOKEN_PATTERN: RegExp;
49
+ /**
50
+ * Browser WebSocket clients cannot set headers, so the token rides the
51
+ * subprotocol list as `amalgm-runtime-token.<token>` — runtime-auth.js:32-40.
52
+ */
53
+ export declare const RUNTIME_TOKEN_PROTOCOL_PREFIX = "amalgm-runtime-token.";
54
+ export declare function tokenFromProtocol(headers: HeadersLike): string;
55
+ /**
56
+ * Token extraction precedence — runtime-auth.js:42-54, in order:
57
+ * 1. the dedicated header,
58
+ * 2. `Authorization: Bearer <token>` (trimmed),
59
+ * 3. the WebSocket subprotocol entry,
60
+ * 4. nothing.
61
+ * First non-empty wins; a present-but-empty earlier source falls
62
+ * through rather than shadowing a later one.
63
+ */
64
+ export declare function tokenFromHeaders(headers: HeadersLike): string;
65
+ /**
66
+ * The constant-time byte compare is a host effect: node:crypto's
67
+ * timingSafeEqual, or the WebCrypto equivalent. Called only with
68
+ * equal-length inputs — the length gate lives in safeTokenEqual.
69
+ */
70
+ export type TimingSafeEqualPort = (left: Uint8Array, right: Uint8Array) => boolean;
71
+ /**
72
+ * runtime-auth.js:20-24 — UTF-8 encode both sides, length check
73
+ * first (node's timingSafeEqual throws on mismatched lengths; the
74
+ * early return also leaks nothing an attacker doesn't already know,
75
+ * since token length is not secret), then the injected constant-time
76
+ * compare.
77
+ */
78
+ export declare function safeTokenEqual(actual: unknown, expected: unknown, timingSafeEqual: TimingSafeEqualPort): boolean;
79
+ /** The three facts the verdict needs, resolved once by the host from env + adapters. */
80
+ export interface RuntimeAuthPolicy {
81
+ /** runtimeAuthRequired(env) — is the gate armed at all? */
82
+ readonly required: boolean;
83
+ /** runtimeTokenFromEnv(env) — '' means "armed but no token", which denies everything. */
84
+ readonly expectedToken: string;
85
+ readonly timingSafeEqual: TimingSafeEqualPort;
86
+ }
87
+ /**
88
+ * The verdict law — runtime-auth.js:56-61:
89
+ * - gate not armed → allow, unconditionally;
90
+ * - gate armed with no expected token → DENY, unconditionally
91
+ * (fail closed — see runtimeAuthRequired);
92
+ * - otherwise, constant-time compare of presented vs expected.
93
+ */
94
+ export declare function isAuthorizedRuntimeRequest(headers: HeadersLike, policy: RuntimeAuthPolicy): boolean;
95
+ /** The only hosts a browser origin may claim and still be "this machine" — runtime-auth.js:69. */
96
+ export declare const LOOPBACK_ORIGIN_HOSTNAMES: string[];
97
+ /**
98
+ * runtime-auth.js:63-74. An EMPTY origin returns TRUE, deliberately:
99
+ * no Origin header means no foreign web page — curl, native fetch,
100
+ * Electron's main process, and same-origin requests all omit it, and
101
+ * CORS is enforced by browsers alone, so a client that sends no
102
+ * Origin ignores these headers anyway. The gate exists to stop a
103
+ * hostile *page* on another origin from riding a user's browser into
104
+ * the runtime; absence of an origin is absence of that threat. An
105
+ * unparseable origin, by contrast, is a claim we cannot verify: false.
106
+ * ('::1' is unreachable through WHATWG parsing, which brackets IPv6
107
+ * hostnames — kept verbatim from the engine per axiom 8.)
108
+ */
109
+ export declare function isLoopbackOrigin(origin: string | null | undefined): boolean;
110
+ /** CORS contract defaults — runtime-auth.js:83-96, byte-exact including order. */
111
+ export declare const RUNTIME_CORS_METHODS = "GET, POST, DELETE, OPTIONS";
112
+ export declare const RUNTIME_CORS_ALLOWED_HEADERS: string;
113
+ export interface RuntimeCorsOptions {
114
+ readonly methods?: string;
115
+ readonly headers?: string;
116
+ readonly exposeHeaders?: string;
117
+ }
118
+ /**
119
+ * runtime-auth.js:76-100 as a header map instead of res.setHeader
120
+ * calls (insertion order preserved). Allow-Origin echoes the origin
121
+ * only when it is a loopback page — plus Vary so caches never serve
122
+ * one origin's grant to another. Methods and allowed headers are set
123
+ * unconditionally.
124
+ */
125
+ export declare function runtimeCorsHeaders(origin: string, options?: RuntimeCorsOptions): Record<string, string>;
126
+ /**
127
+ * The rejection contract — runtime-auth.js:115-121, byte-exact. The
128
+ * body is the frozen serialization, not a fresh stringify: parity
129
+ * tests pin the bytes, not the shape.
130
+ */
131
+ export declare const UNAUTHORIZED_RESPONSE: {
132
+ readonly status: 401;
133
+ readonly headers: {
134
+ readonly 'Content-Type': "application/json";
135
+ readonly 'Cache-Control': "no-store";
136
+ };
137
+ readonly body: "{\"error\":\"Missing or invalid Amalgm runtime token\"}";
138
+ };
139
+ /** runtime-auth.js:110-113 — health probes answer before auth, always. Pure over an already-parsed pathname. */
140
+ export declare function isHealthPath(pathname: string): boolean;
141
+ /** runtime-auth.js:138 — health paths plus caller-declared prefix exemptions (plain startsWith, not segment-aware). */
142
+ export declare function isExemptPath(pathname: string, exemptPrefixes?: readonly string[]): boolean;
143
+ export interface RuntimeHttpRequest {
144
+ readonly method: string;
145
+ /** Already parsed from req.url — see pathnameOf in routing.ts. */
146
+ readonly pathname: string;
147
+ readonly headers: HeadersLike;
148
+ }
149
+ export interface RuntimeHttpOptions {
150
+ readonly exemptPrefixes?: readonly string[];
151
+ }
152
+ export type RuntimeHttpDecision = {
153
+ readonly kind: 'preflight';
154
+ readonly status: 204 | 403;
155
+ } | {
156
+ readonly kind: 'allow';
157
+ } | {
158
+ readonly kind: 'reject';
159
+ readonly status: 401;
160
+ readonly headers: Record<string, string>;
161
+ readonly body: string;
162
+ };
163
+ /**
164
+ * The decision skeleton of authorizeRuntimeHttp — runtime-auth.js:123-143
165
+ * minus the side effects (the host applies runtimeCorsHeaders to every
166
+ * response and writes whichever verdict comes back):
167
+ * - OPTIONS is the preflight law (:126-134): 204 for loopback or
168
+ * absent origins, 403 for everyone else — preflights are never
169
+ * token-checked, because browsers refuse to attach credentials to
170
+ * them;
171
+ * - exempt paths and authorized requests pass;
172
+ * - everything else gets the byte-exact 401.
173
+ */
174
+ export declare function decideRuntimeHttp(request: RuntimeHttpRequest, policy: RuntimeAuthPolicy, options?: RuntimeHttpOptions): RuntimeHttpDecision;
175
+ /** The three facts the bearer guard needs: the request's headers, the
176
+ * token the product expects, and the injected constant-time compare. */
177
+ export interface BearerGuardInput {
178
+ readonly headers: HeadersLike;
179
+ readonly expectedToken: string;
180
+ readonly timingSafeEqual: TimingSafeEqualPort;
181
+ }
182
+ export type BearerGuardVerdict = {
183
+ readonly ok: true;
184
+ } | {
185
+ readonly ok: false;
186
+ readonly status: 401;
187
+ readonly headers: Record<string, string>;
188
+ readonly body: string;
189
+ };
190
+ /**
191
+ * The composed bearer guard a product calls at its HTTP boundary —
192
+ * hoisted from the four bricks (apps, tools, agents, browser) that each
193
+ * hand-rolled it, with drift including a non-timing-safe compare and a
194
+ * case-sensitive Bearer parse. This is those four guards as ONE law,
195
+ * assembled entirely from the primitives above (zero duplicated logic):
196
+ *
197
+ * - the token arrives by {@link tokenFromHeaders} — the dedicated
198
+ * header, `Authorization: Bearer` (case-insensitive scheme), or the
199
+ * WS subprotocol carrier, in that precedence;
200
+ * - the compare is {@link safeTokenEqual} over the injected
201
+ * constant-time port (axiom 6);
202
+ * - every failure — token missing, token wrong, no expected token
203
+ * configured (fail closed, as the verdict law) — is the SAME
204
+ * byte-exact {@link UNAUTHORIZED_RESPONSE}, so a caller never learns
205
+ * which check refused it.
206
+ */
207
+ export declare function guardBearerRequest(input: BearerGuardInput): BearerGuardVerdict;
208
+ /**
209
+ * runtime-auth.js:145-152 — outbound stamping: attach the runtime
210
+ * token to a header bag, or return the bag untouched when there is no
211
+ * token to attach (never an empty header).
212
+ */
213
+ export declare function withRuntimeToken(headers: Record<string, string>, token: string): Record<string, string>;
214
+ /**
215
+ * WebSocket close codes the gateway speaks — local-gateway.js:866,873,
216
+ * 1061,1074 and the tunnels' denial path. 4401/4404 are the private-use
217
+ * mirrors of 401/404 (the WS close-code space reserves 4000-4999 for
218
+ * applications); 1008 is the RFC 6455 policy-violation code, used for
219
+ * target-port denial and unknown routes; 1011 is unexpected-condition,
220
+ * used when the upstream leg fails (reason = the upstream error message).
221
+ */
222
+ export declare const WS_CLOSE_UNAUTHORIZED: {
223
+ readonly code: 4401;
224
+ readonly reason: "Unauthorized";
225
+ };
226
+ export declare const WS_CLOSE_PTY_SESSION_NOT_FOUND: {
227
+ readonly code: 4404;
228
+ readonly reason: "PTY session not found";
229
+ };
230
+ export declare const WS_CLOSE_POLICY_VIOLATION = 1008;
231
+ export declare const WS_CLOSE_UPSTREAM_FAILURE = 1011;
232
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/transport/auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,266 @@
1
+ /**
2
+ * The runtime auth boundary — every HTTP request and WebSocket upgrade
3
+ * that reaches a runtime service passes this gate first. Ported from
4
+ * the engine's runtime/scripts/runtime-auth.js, whose 162 lines are
5
+ * pure decision logic wearing Node clothing: the env read, the crypto
6
+ * compare, and the res writes are the host's; only the verdicts live
7
+ * here (axiom 6). Contracts — header names, the Bearer regex, the 401
8
+ * body, close codes — are byte-exact (axiom 8).
9
+ *
10
+ * The trust model in one line: possession of the runtime token IS
11
+ * authorization, and the token never leaves the machine except inside
12
+ * a tunnel frame the runtime itself stamped.
13
+ */
14
+ const utf8 = new TextEncoder();
15
+ /**
16
+ * THE copy of the runtime token header name. The engine spells this
17
+ * string in four places (runtime-auth.js:5, local-gateway.js:72, and
18
+ * inline in both tunnels' localHeaders); cutover deletes the other
19
+ * three.
20
+ */
21
+ export const RUNTIME_TOKEN_HEADER = 'x-amalgm-runtime-token';
22
+ /** runtime-auth.js:26-30 — first value wins for repeated headers; non-strings read as absent. */
23
+ export function headerValue(headers, name) {
24
+ const raw = headers?.[name.toLowerCase()];
25
+ if (Array.isArray(raw))
26
+ return raw[0] || '';
27
+ return typeof raw === 'string' ? raw : '';
28
+ }
29
+ /** runtime-auth.js:7-9 — the expected token is the env token, trimmed. Empty means "none configured". */
30
+ export function runtimeTokenFromEnv(env) {
31
+ return String(env.AMALGM_RUNTIME_TOKEN || '').trim();
32
+ }
33
+ /**
34
+ * runtime-auth.js:11-18 — when is the gate armed? The explicit
35
+ * disable wins first, always; otherwise any of: a configured token,
36
+ * an npm-sourced runtime, or local mode. The npm/local clauses arm
37
+ * the gate even with no token configured — which the verdict law
38
+ * below turns into deny-everything, deliberately: a runtime that
39
+ * should have a token but lost it must fail closed, not open.
40
+ */
41
+ export function runtimeAuthRequired(env) {
42
+ if (env.AMALGM_RUNTIME_AUTH === 'disabled')
43
+ return false;
44
+ return Boolean(runtimeTokenFromEnv(env)
45
+ || env.AMALGM_RUNTIME_SOURCE === 'npm'
46
+ || env.AMALGM_LOCAL_MODE === 'true');
47
+ }
48
+ /** The one Bearer shape the boundary accepts — runtime-auth.js:47. */
49
+ export const BEARER_TOKEN_PATTERN = /^Bearer\s+(.+)$/i;
50
+ /**
51
+ * Browser WebSocket clients cannot set headers, so the token rides the
52
+ * subprotocol list as `amalgm-runtime-token.<token>` — runtime-auth.js:32-40.
53
+ */
54
+ export const RUNTIME_TOKEN_PROTOCOL_PREFIX = 'amalgm-runtime-token.';
55
+ export function tokenFromProtocol(headers) {
56
+ const raw = headerValue(headers, 'sec-websocket-protocol');
57
+ for (const part of raw.split(',').map((value) => value.trim()).filter(Boolean)) {
58
+ if (part.startsWith(RUNTIME_TOKEN_PROTOCOL_PREFIX)) {
59
+ return part.slice(RUNTIME_TOKEN_PROTOCOL_PREFIX.length);
60
+ }
61
+ }
62
+ return '';
63
+ }
64
+ /**
65
+ * Token extraction precedence — runtime-auth.js:42-54, in order:
66
+ * 1. the dedicated header,
67
+ * 2. `Authorization: Bearer <token>` (trimmed),
68
+ * 3. the WebSocket subprotocol entry,
69
+ * 4. nothing.
70
+ * First non-empty wins; a present-but-empty earlier source falls
71
+ * through rather than shadowing a later one.
72
+ */
73
+ export function tokenFromHeaders(headers) {
74
+ const headerToken = headerValue(headers, RUNTIME_TOKEN_HEADER);
75
+ if (headerToken)
76
+ return headerToken;
77
+ const match = headerValue(headers, 'authorization').match(BEARER_TOKEN_PATTERN);
78
+ if (match)
79
+ return (match[1] ?? '').trim();
80
+ const protocolToken = tokenFromProtocol(headers);
81
+ if (protocolToken)
82
+ return protocolToken;
83
+ return '';
84
+ }
85
+ /**
86
+ * runtime-auth.js:20-24 — UTF-8 encode both sides, length check
87
+ * first (node's timingSafeEqual throws on mismatched lengths; the
88
+ * early return also leaks nothing an attacker doesn't already know,
89
+ * since token length is not secret), then the injected constant-time
90
+ * compare.
91
+ */
92
+ export function safeTokenEqual(actual, expected, timingSafeEqual) {
93
+ const left = utf8.encode(String(actual || ''));
94
+ const right = utf8.encode(String(expected || ''));
95
+ return left.length === right.length && timingSafeEqual(left, right);
96
+ }
97
+ /**
98
+ * The verdict law — runtime-auth.js:56-61:
99
+ * - gate not armed → allow, unconditionally;
100
+ * - gate armed with no expected token → DENY, unconditionally
101
+ * (fail closed — see runtimeAuthRequired);
102
+ * - otherwise, constant-time compare of presented vs expected.
103
+ */
104
+ export function isAuthorizedRuntimeRequest(headers, policy) {
105
+ if (!policy.required)
106
+ return true;
107
+ if (!policy.expectedToken)
108
+ return false;
109
+ return safeTokenEqual(tokenFromHeaders(headers), policy.expectedToken, policy.timingSafeEqual);
110
+ }
111
+ /** The only hosts a browser origin may claim and still be "this machine" — runtime-auth.js:69. */
112
+ export const LOOPBACK_ORIGIN_HOSTNAMES = ['localhost', '127.0.0.1', '[::1]', '::1'];
113
+ /**
114
+ * runtime-auth.js:63-74. An EMPTY origin returns TRUE, deliberately:
115
+ * no Origin header means no foreign web page — curl, native fetch,
116
+ * Electron's main process, and same-origin requests all omit it, and
117
+ * CORS is enforced by browsers alone, so a client that sends no
118
+ * Origin ignores these headers anyway. The gate exists to stop a
119
+ * hostile *page* on another origin from riding a user's browser into
120
+ * the runtime; absence of an origin is absence of that threat. An
121
+ * unparseable origin, by contrast, is a claim we cannot verify: false.
122
+ * ('::1' is unreachable through WHATWG parsing, which brackets IPv6
123
+ * hostnames — kept verbatim from the engine per axiom 8.)
124
+ */
125
+ export function isLoopbackOrigin(origin) {
126
+ if (!origin)
127
+ return true;
128
+ try {
129
+ const url = new URL(origin);
130
+ return ((url.protocol === 'http:' || url.protocol === 'https:')
131
+ && LOOPBACK_ORIGIN_HOSTNAMES.includes(url.hostname));
132
+ }
133
+ catch {
134
+ return false;
135
+ }
136
+ }
137
+ /** CORS contract defaults — runtime-auth.js:83-96, byte-exact including order. */
138
+ export const RUNTIME_CORS_METHODS = 'GET, POST, DELETE, OPTIONS';
139
+ export const RUNTIME_CORS_ALLOWED_HEADERS = [
140
+ 'Content-Type',
141
+ 'Authorization',
142
+ 'Accept',
143
+ 'Cache-Control',
144
+ 'Last-Event-ID',
145
+ 'Mcp-Session-Id',
146
+ 'X-Amalgm-Session-Id',
147
+ RUNTIME_TOKEN_HEADER,
148
+ ].join(', ');
149
+ /**
150
+ * runtime-auth.js:76-100 as a header map instead of res.setHeader
151
+ * calls (insertion order preserved). Allow-Origin echoes the origin
152
+ * only when it is a loopback page — plus Vary so caches never serve
153
+ * one origin's grant to another. Methods and allowed headers are set
154
+ * unconditionally.
155
+ */
156
+ export function runtimeCorsHeaders(origin, options = {}) {
157
+ const out = {};
158
+ if (origin && isLoopbackOrigin(origin)) {
159
+ out['Access-Control-Allow-Origin'] = origin;
160
+ out['Vary'] = 'Origin';
161
+ }
162
+ out['Access-Control-Allow-Methods'] = options.methods || RUNTIME_CORS_METHODS;
163
+ out['Access-Control-Allow-Headers'] = options.headers || RUNTIME_CORS_ALLOWED_HEADERS;
164
+ if (options.exposeHeaders)
165
+ out['Access-Control-Expose-Headers'] = options.exposeHeaders;
166
+ return out;
167
+ }
168
+ /**
169
+ * The rejection contract — runtime-auth.js:115-121, byte-exact. The
170
+ * body is the frozen serialization, not a fresh stringify: parity
171
+ * tests pin the bytes, not the shape.
172
+ */
173
+ export const UNAUTHORIZED_RESPONSE = {
174
+ status: 401,
175
+ headers: {
176
+ 'Content-Type': 'application/json',
177
+ 'Cache-Control': 'no-store',
178
+ },
179
+ body: '{"error":"Missing or invalid Amalgm runtime token"}',
180
+ };
181
+ /** runtime-auth.js:110-113 — health probes answer before auth, always. Pure over an already-parsed pathname. */
182
+ export function isHealthPath(pathname) {
183
+ return pathname === '/healthz' || pathname === '/';
184
+ }
185
+ /** runtime-auth.js:138 — health paths plus caller-declared prefix exemptions (plain startsWith, not segment-aware). */
186
+ export function isExemptPath(pathname, exemptPrefixes = []) {
187
+ return isHealthPath(pathname) || exemptPrefixes.some((prefix) => pathname.startsWith(prefix));
188
+ }
189
+ /**
190
+ * The decision skeleton of authorizeRuntimeHttp — runtime-auth.js:123-143
191
+ * minus the side effects (the host applies runtimeCorsHeaders to every
192
+ * response and writes whichever verdict comes back):
193
+ * - OPTIONS is the preflight law (:126-134): 204 for loopback or
194
+ * absent origins, 403 for everyone else — preflights are never
195
+ * token-checked, because browsers refuse to attach credentials to
196
+ * them;
197
+ * - exempt paths and authorized requests pass;
198
+ * - everything else gets the byte-exact 401.
199
+ */
200
+ export function decideRuntimeHttp(request, policy, options = {}) {
201
+ if (request.method === 'OPTIONS') {
202
+ const loopback = isLoopbackOrigin(headerValue(request.headers, 'origin'));
203
+ return { kind: 'preflight', status: loopback ? 204 : 403 };
204
+ }
205
+ const exempt = isExemptPath(request.pathname, options.exemptPrefixes ?? []);
206
+ if (exempt || isAuthorizedRuntimeRequest(request.headers, policy))
207
+ return { kind: 'allow' };
208
+ return {
209
+ kind: 'reject',
210
+ status: UNAUTHORIZED_RESPONSE.status,
211
+ headers: { ...UNAUTHORIZED_RESPONSE.headers },
212
+ body: UNAUTHORIZED_RESPONSE.body,
213
+ };
214
+ }
215
+ /**
216
+ * The composed bearer guard a product calls at its HTTP boundary —
217
+ * hoisted from the four bricks (apps, tools, agents, browser) that each
218
+ * hand-rolled it, with drift including a non-timing-safe compare and a
219
+ * case-sensitive Bearer parse. This is those four guards as ONE law,
220
+ * assembled entirely from the primitives above (zero duplicated logic):
221
+ *
222
+ * - the token arrives by {@link tokenFromHeaders} — the dedicated
223
+ * header, `Authorization: Bearer` (case-insensitive scheme), or the
224
+ * WS subprotocol carrier, in that precedence;
225
+ * - the compare is {@link safeTokenEqual} over the injected
226
+ * constant-time port (axiom 6);
227
+ * - every failure — token missing, token wrong, no expected token
228
+ * configured (fail closed, as the verdict law) — is the SAME
229
+ * byte-exact {@link UNAUTHORIZED_RESPONSE}, so a caller never learns
230
+ * which check refused it.
231
+ */
232
+ export function guardBearerRequest(input) {
233
+ const { headers, expectedToken, timingSafeEqual } = input;
234
+ if (expectedToken && safeTokenEqual(tokenFromHeaders(headers), expectedToken, timingSafeEqual)) {
235
+ return { ok: true };
236
+ }
237
+ return {
238
+ ok: false,
239
+ status: UNAUTHORIZED_RESPONSE.status,
240
+ headers: { ...UNAUTHORIZED_RESPONSE.headers },
241
+ body: UNAUTHORIZED_RESPONSE.body,
242
+ };
243
+ }
244
+ /**
245
+ * runtime-auth.js:145-152 — outbound stamping: attach the runtime
246
+ * token to a header bag, or return the bag untouched when there is no
247
+ * token to attach (never an empty header).
248
+ */
249
+ export function withRuntimeToken(headers, token) {
250
+ if (!token)
251
+ return headers;
252
+ return { ...headers, [RUNTIME_TOKEN_HEADER]: token };
253
+ }
254
+ /**
255
+ * WebSocket close codes the gateway speaks — local-gateway.js:866,873,
256
+ * 1061,1074 and the tunnels' denial path. 4401/4404 are the private-use
257
+ * mirrors of 401/404 (the WS close-code space reserves 4000-4999 for
258
+ * applications); 1008 is the RFC 6455 policy-violation code, used for
259
+ * target-port denial and unknown routes; 1011 is unexpected-condition,
260
+ * used when the upstream leg fails (reason = the upstream error message).
261
+ */
262
+ export const WS_CLOSE_UNAUTHORIZED = { code: 4401, reason: 'Unauthorized' };
263
+ export const WS_CLOSE_PTY_SESSION_NOT_FOUND = { code: 4404, reason: 'PTY session not found' };
264
+ export const WS_CLOSE_POLICY_VIOLATION = 1008;
265
+ export const WS_CLOSE_UPSTREAM_FAILURE = 1011;
266
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/transport/auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Transport — the pipe (axiom 3: it never reads the mail). Four
3
+ * layers, boundary-first:
4
+ *
5
+ * - auth: the runtime auth boundary — token extraction precedence,
6
+ * the verdict law, loopback CORS, the byte-exact 401, WS close
7
+ * codes.
8
+ * - routing: ONE routing table (axiom 4) — prefix classification,
9
+ * the port-mapping and target-port laws, denial contracts. The
10
+ * engine's shipped table contents live in shipped-routes.
11
+ * - admission: who gets routed to at all — app refs, the serving
12
+ * gate and the advertising breaker, preview-port admission, header
13
+ * hygiene, log redaction.
14
+ * - ingress: the event rail's receipt and signature laws.
15
+ */
16
+ export * from './auth.js';
17
+ export * from './routing.js';
18
+ export * from './shipped-routes.js';
19
+ export * from './admission.js';
20
+ export * from './ingress.js';
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/transport/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Transport — the pipe (axiom 3: it never reads the mail). Four
3
+ * layers, boundary-first:
4
+ *
5
+ * - auth: the runtime auth boundary — token extraction precedence,
6
+ * the verdict law, loopback CORS, the byte-exact 401, WS close
7
+ * codes.
8
+ * - routing: ONE routing table (axiom 4) — prefix classification,
9
+ * the port-mapping and target-port laws, denial contracts. The
10
+ * engine's shipped table contents live in shipped-routes.
11
+ * - admission: who gets routed to at all — app refs, the serving
12
+ * gate and the advertising breaker, preview-port admission, header
13
+ * hygiene, log redaction.
14
+ * - ingress: the event rail's receipt and signature laws.
15
+ */
16
+ export * from './auth.js';
17
+ export * from './routing.js';
18
+ export * from './shipped-routes.js';
19
+ export * from './admission.js';
20
+ export * from './ingress.js';
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/transport/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Event-rail ingress — the pipe half of the webhook rail. Success at
3
+ * this layer means exactly one thing: the immutable envelope (raw
4
+ * body + allowlisted headers) is durably queued and acknowledged.
5
+ * What a delivery TRIGGERS — the automation fan-out below
6
+ * events/processor.js:119 — is product logic and deliberately not
7
+ * here: the pipe stores and acknowledges the mail, it never reads it
8
+ * (axiom 3). The signature laws live at this layer because verifying
9
+ * WHO sent a payload is admission, not interpretation.
10
+ *
11
+ * Ported from amalgm-mcp/events/ingress.js (receipt contract) and
12
+ * events/matcher.js (signature laws — already pure in the engine).
13
+ */
14
+ import type { HeadersLike } from './auth.js';
15
+ /** ingress.js:12 — the body cap. Counted over raw bytes as they stream in, not after assembly. */
16
+ export declare const MAX_EVENT_BODY_BYTES: number;
17
+ /** The over-limit rejection — ingress.js:50-53 surfaced through the generic error path (:80): status + byte-exact message. */
18
+ export declare const EVENT_BODY_LIMIT_EXCEEDED: {
19
+ readonly status: 413;
20
+ readonly message: "Event body exceeds 2097152 bytes";
21
+ };
22
+ /**
23
+ * The 17-header storage allowlist — ingress.js:13-31. Everything else
24
+ * is dropped BEFORE the envelope is persisted: the envelope is
25
+ * immutable and replayed, so an unlisted header stored today is a
26
+ * secret leaked forever. The list is exactly: routing identity
27
+ * (x-amalgm-*), each supported sender's event/delivery markers, and
28
+ * every signature/secret header the matcher may need to re-verify.
29
+ */
30
+ export declare const STORED_HEADERS: ReadonlySet<string>;
31
+ /**
32
+ * ingress.js:33-42 — filter to the allowlist, lowercase the names,
33
+ * join repeated values with ', ', stringify the rest. The result is
34
+ * the envelope's header record — and the flattened shape every
35
+ * signature function below reads.
36
+ */
37
+ export declare function storedHeaders(headers: HeadersLike | null | undefined): Record<string, string>;
38
+ /** The receipt acknowledgment — ingress.js:73-78: 202, because acceptance is durable-queued, not processed. */
39
+ export declare const EVENT_ACCEPTED_STATUS = 202;
40
+ export declare function eventAcceptedBody(deliveryId: string, status: string): {
41
+ ok: true;
42
+ accepted: true;
43
+ deliveryId: string;
44
+ status: string;
45
+ };
46
+ /** Envelope-shaped headers: already lowercased and flattened by storedHeaders. */
47
+ export interface FlatHeaders {
48
+ readonly [name: string]: string | undefined;
49
+ }
50
+ /** matcher.js:13-17 — HMAC proof headers, in precedence order (generic first, then GitHub's sha256, then legacy sha1). */
51
+ export declare const HMAC_SIGNATURE_HEADERS: readonly ["x-webhook-signature", "x-hub-signature-256", "x-hub-signature"];
52
+ /** matcher.js:19-23 — plain shared-secret headers, in precedence order (ours, generic, GitLab's). */
53
+ export declare const TOKEN_HEADERS: readonly ["x-amalgm-webhook-secret", "x-webhook-secret", "x-gitlab-token"];
54
+ export interface ExtractedSignature {
55
+ readonly header: string;
56
+ readonly value: string;
57
+ }
58
+ /**
59
+ * The extraction order — matcher.js:28-43: every HMAC header first
60
+ * (an HMAC proof is stronger than a bearer secret, so it must win
61
+ * when a sender supplies both), then `Authorization: Bearer <secret>`
62
+ * (reported under header 'authorization'), then the plain token
63
+ * headers. First PRESENT header wins — an empty value reads as
64
+ * absent. Null when nothing proves anything.
65
+ */
66
+ export declare function extractSignature(headers: FlatHeaders): ExtractedSignature | null;
67
+ /** The HMAC effect: hex digest of rawBody keyed by secret. The host wraps node:crypto's createHmac. */
68
+ export type HmacHexPort = (algorithm: string, secret: string, rawBody: string) => string;
69
+ /**
70
+ * matcher.js:48-50 — the expected proof is `<algorithm>=<hex>`,
71
+ * sha256 unless the caller says otherwise (verification picks sha1
72
+ * only when the presented value announces it — GitHub's legacy
73
+ * x-hub-signature).
74
+ */
75
+ export declare function computeSignature(secret: string, rawBody: string, hmacHex: HmacHexPort, algorithm?: string): string;
76
+ /**
77
+ * matcher.js:122-128 — the human-readable source label, first
78
+ * recognized marker wins in this order: github → stripe → linear →
79
+ * gitlab → 'external'.
80
+ */
81
+ export declare function pickSourceLabel(headers: FlatHeaders): 'github' | 'stripe' | 'linear' | 'gitlab' | 'external';
82
+ /**
83
+ * The 11 headers worth handing to the agent run context —
84
+ * matcher.js:133-145. Note the deliberate asymmetry with
85
+ * STORED_HEADERS: secrets used only for admission (authorization,
86
+ * x-amalgm-webhook-secret, x-webhook-secret) are stored for
87
+ * re-verification but NOT passed through to agent context — except
88
+ * x-gitlab-token, which GitLab overloads as both auth and event
89
+ * metadata.
90
+ */
91
+ export declare const PASSTHROUGH_HEADERS: readonly ["x-github-event", "x-github-delivery", "x-hub-signature-256", "x-hub-signature", "stripe-signature", "x-webhook-signature", "content-type", "x-linear-event", "x-linear-signature", "x-gitlab-event", "x-gitlab-token"];
92
+ /** matcher.js:147-151 — project the passthrough subset, preserving list order. */
93
+ export declare function collectPassthroughHeaders(headers: FlatHeaders): Record<string, string>;
94
+ //# sourceMappingURL=ingress.d.ts.map