@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.
- package/PURPOSE.md +94 -0
- package/README.md +30 -0
- package/dist/adapters/index.d.ts +13 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +11 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/identity/auth-records.d.ts +147 -0
- package/dist/identity/auth-records.d.ts.map +1 -0
- package/dist/identity/auth-records.js +354 -0
- package/dist/identity/auth-records.js.map +1 -0
- package/dist/identity/ids.d.ts +57 -0
- package/dist/identity/ids.d.ts.map +1 -0
- package/dist/identity/ids.js +80 -0
- package/dist/identity/ids.js.map +1 -0
- package/dist/identity/index.d.ts +14 -0
- package/dist/identity/index.d.ts.map +1 -0
- package/dist/identity/index.js +14 -0
- package/dist/identity/index.js.map +1 -0
- package/dist/identity/labels.d.ts +115 -0
- package/dist/identity/labels.d.ts.map +1 -0
- package/dist/identity/labels.js +154 -0
- package/dist/identity/labels.js.map +1 -0
- package/dist/identity/layout.d.ts +188 -0
- package/dist/identity/layout.d.ts.map +1 -0
- package/dist/identity/layout.js +233 -0
- package/dist/identity/layout.js.map +1 -0
- package/dist/identity/ports.d.ts +31 -0
- package/dist/identity/ports.d.ts.map +1 -0
- package/dist/identity/ports.js +9 -0
- package/dist/identity/ports.js.map +1 -0
- package/dist/identity/product-state.d.ts +49 -0
- package/dist/identity/product-state.d.ts.map +1 -0
- package/dist/identity/product-state.js +55 -0
- package/dist/identity/product-state.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/supervision/app-health.d.ts +148 -0
- package/dist/supervision/app-health.d.ts.map +1 -0
- package/dist/supervision/app-health.js +165 -0
- package/dist/supervision/app-health.js.map +1 -0
- package/dist/supervision/app-restart.d.ts +105 -0
- package/dist/supervision/app-restart.d.ts.map +1 -0
- package/dist/supervision/app-restart.js +97 -0
- package/dist/supervision/app-restart.js.map +1 -0
- package/dist/supervision/health.d.ts +218 -0
- package/dist/supervision/health.d.ts.map +1 -0
- package/dist/supervision/health.js +227 -0
- package/dist/supervision/health.js.map +1 -0
- package/dist/supervision/index.d.ts +17 -0
- package/dist/supervision/index.d.ts.map +1 -0
- package/dist/supervision/index.js +17 -0
- package/dist/supervision/index.js.map +1 -0
- package/dist/supervision/service-registration.d.ts +343 -0
- package/dist/supervision/service-registration.d.ts.map +1 -0
- package/dist/supervision/service-registration.js +543 -0
- package/dist/supervision/service-registration.js.map +1 -0
- package/dist/supervision/update.d.ts +231 -0
- package/dist/supervision/update.d.ts.map +1 -0
- package/dist/supervision/update.js +336 -0
- package/dist/supervision/update.js.map +1 -0
- package/dist/transport/admission.d.ts +235 -0
- package/dist/transport/admission.d.ts.map +1 -0
- package/dist/transport/admission.js +313 -0
- package/dist/transport/admission.js.map +1 -0
- package/dist/transport/auth.d.ts +232 -0
- package/dist/transport/auth.d.ts.map +1 -0
- package/dist/transport/auth.js +266 -0
- package/dist/transport/auth.js.map +1 -0
- package/dist/transport/index.d.ts +21 -0
- package/dist/transport/index.d.ts.map +1 -0
- package/dist/transport/index.js +21 -0
- package/dist/transport/index.js.map +1 -0
- package/dist/transport/ingress.d.ts +94 -0
- package/dist/transport/ingress.d.ts.map +1 -0
- package/dist/transport/ingress.js +171 -0
- package/dist/transport/ingress.js.map +1 -0
- package/dist/transport/routing.d.ts +235 -0
- package/dist/transport/routing.d.ts.map +1 -0
- package/dist/transport/routing.js +288 -0
- package/dist/transport/routing.js.map +1 -0
- package/dist/transport/shipped-routes.d.ts +21 -0
- package/dist/transport/shipped-routes.d.ts.map +1 -0
- package/dist/transport/shipped-routes.js +53 -0
- package/dist/transport/shipped-routes.js.map +1 -0
- 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
|