@webpieces/http-client-node 0.4.700 → 0.4.702
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 +82 -51
- package/package.json +5 -5
- package/src/AddressResolver.d.ts +11 -1
- package/src/AddressResolver.js +12 -2
- package/src/AddressResolver.js.map +1 -1
- package/src/ClientConfig.d.ts +30 -36
- package/src/ClientConfig.js +21 -21
- package/src/ClientConfig.js.map +1 -1
- package/src/ClientHttpFactory.d.ts +30 -18
- package/src/ClientHttpFactory.js +29 -17
- package/src/ClientHttpFactory.js.map +1 -1
- package/src/ContextBaseUrlFilter.d.ts +80 -0
- package/src/ContextBaseUrlFilter.js +95 -0
- package/src/ContextBaseUrlFilter.js.map +1 -0
- package/src/CreateRpcClientCompileAssertions.d.ts +17 -0
- package/src/CreateRpcClientCompileAssertions.js +50 -0
- package/src/CreateRpcClientCompileAssertions.js.map +1 -0
- package/src/MissingRuntimeBaseUrlError.d.ts +20 -0
- package/src/MissingRuntimeBaseUrlError.js +28 -0
- package/src/MissingRuntimeBaseUrlError.js.map +1 -0
- package/src/NodeProxyClient.d.ts +26 -27
- package/src/NodeProxyClient.js +60 -47
- package/src/NodeProxyClient.js.map +1 -1
- package/src/OutboundAuthErrors.d.ts +42 -0
- package/src/OutboundAuthErrors.js +54 -0
- package/src/OutboundAuthErrors.js.map +1 -0
- package/src/OutboundAuthFilter.d.ts +56 -0
- package/src/OutboundAuthFilter.js +103 -0
- package/src/OutboundAuthFilter.js.map +1 -0
- package/src/SsrfGuardFilter.d.ts +11 -0
- package/src/SsrfGuardFilter.js +23 -4
- package/src/SsrfGuardFilter.js.map +1 -1
- package/src/SsrfPolicy.d.ts +39 -45
- package/src/SsrfPolicy.js +49 -34
- package/src/SsrfPolicy.js.map +1 -1
- package/src/WebhookSignerCallback.d.ts +114 -0
- package/src/WebhookSignerCallback.js +106 -0
- package/src/WebhookSignerCallback.js.map +1 -0
- package/src/index.d.ts +13 -10
- package/src/index.js +30 -24
- package/src/index.js.map +1 -1
- package/src/ContextBaseUrlOverrideFilter.d.ts +0 -38
- package/src/ContextBaseUrlOverrideFilter.js +0 -57
- package/src/ContextBaseUrlOverrideFilter.js.map +0 -1
- package/src/HostPolicy.d.ts +0 -118
- package/src/HostPolicy.js +0 -169
- package/src/HostPolicy.js.map +0 -1
- package/src/RuntimeHostErrors.d.ts +0 -42
- package/src/RuntimeHostErrors.js +0 -54
- package/src/RuntimeHostErrors.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ContextBaseUrlOverrideFilter.js","sourceRoot":"","sources":["../../../../../packages/http/http-client-node/src/ContextBaseUrlOverrideFilter.ts"],"names":[],"mappings":";;;AAAA,oDAA6E;AAC7E,0DAAyD;AAEzD,2DAAiE;AAEjE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAa,4BAA6B,SAAQ,kBAA+B;IACpE,KAAK,CAAC,MAAM,CAAC,OAAsB,EAAE,UAA4C;QACtF,MAAM,QAAQ,GAAG,6BAAc,CAAC,YAAY,CAAC,gCAAoB,CAAC,iBAAiB,CAAC,CAAC;QACrF,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,EAAE,EAAE,CAAC;YAC5C,MAAM,IAAI,8CAA0B,CAChC,GAAG,OAAO,CAAC,YAAY,IAAI,OAAO,CAAC,KAAK,CAAC,UAAU,oCAAoC;gBACnF,gDAAgD;gBAChD,wFAAwF;gBACxF,aAAa;gBACb,yFAAyF;gBACzF,qFAAqF;gBACrF,qEAAqE,EACzE,GAAG,OAAO,CAAC,YAAY,IAAI,OAAO,CAAC,KAAK,CAAC,UAAU,EAAE,CACxD,CAAC;QACN,CAAC;QACD,OAAO,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;QACjC,OAAO,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACtC,CAAC;CACJ;AAlBD,oEAkBC;AAED;;;;GAIG;AACU,QAAA,0BAA0B,GAAG,IAAI,CAAC;AAE/C,iGAAiG;AACpF,QAAA,mBAAmB,GAAG,GAAG,CAAC","sourcesContent":["import { Filter, Service, WebpiecesCoreHeaders } from '@webpieces/core-util';\nimport { RequestContext } from '@webpieces/core-context';\nimport { ClientRequest } from '@webpieces/http-client-core';\nimport { MissingRuntimeBaseUrlError } from './RuntimeHostErrors';\n\n/**\n * Reads {@link WebpiecesCoreHeaders.OVERRIDE_BASE_URL} out of the ambient RequestContext and points\n * THIS ONE CALL at it.\n *\n * This is the filter the whole runtime-base-URL feature is built out of, and expressing it as a\n * filter rather than as a special case inside the transport is the point: information from OUTSIDE\n * the call (a URL a partner registered, sitting in a database column) crosses into the send path\n * through the same seam an app's own signing filter uses, and everything below it in the chain —\n * the SSRF guard, the app's signature — sees the destination this filter chose rather than the one\n * `ClientConfig` bound at construction.\n *\n * ## Scope, and why it cannot leak\n *\n * It mutates the per-call {@link ClientRequest} and nothing else. The client is untouched, so the\n * next call through the same client starts from its configured host again; and the context entry is\n * scoped to whatever `RequestContext.run(...)` the caller established, so a fan-out loop that sets a\n * different URL per partner gets exactly the URL it set, per iteration.\n *\n * It is installed ONLY by the runtime host policies, so a client bound to a deployed service never\n * reads the key at all. That is what stops an ambient value set for a partner delivery from silently\n * re-pointing every other client in the same request at the partner's server.\n *\n * Priority {@link BASE_URL_OVERRIDE_PRIORITY} — the OUTERMOST framework filter, so the destination\n * is settled before anything else looks at it.\n */\nexport class ContextBaseUrlOverrideFilter extends Filter<ClientRequest, Response> {\n override async filter(request: ClientRequest, nextFilter: Service<ClientRequest, Response>): Promise<Response> {\n const override = RequestContext.getUntrusted(WebpiecesCoreHeaders.OVERRIDE_BASE_URL);\n if (override === undefined || override === '') {\n throw new MissingRuntimeBaseUrlError(\n `${request.contractName}.${request.route.methodName} is a RUNTIME-HOST client, so its ` +\n `destination must be supplied per call, but no ` +\n `WebpiecesCoreHeaders.OVERRIDE_BASE_URL was found in the RequestContext. Set it around ` +\n `the call:\\n` +\n ` RequestContext.putUntrusted(WebpiecesCoreHeaders.OVERRIDE_BASE_URL, webhook.url);\\n` +\n `Refusing rather than falling back to a derived service URL is deliberate: a silent ` +\n `fallback would send a partner's payload to one of our own services.`,\n `${request.contractName}.${request.route.methodName}`,\n );\n }\n request.pointAtBaseUrl(override);\n return nextFilter.invoke(request);\n }\n}\n\n/**\n * The priority the override filter runs at — OUTERMOST of everything, because every other filter's\n * job depends on knowing where the call is going. Exported so an app can see what it is ordering\n * against rather than guessing at a magic number.\n */\nexport const BASE_URL_OVERRIDE_PRIORITY = 1000;\n\n/** The SSRF guard's priority: immediately inside the override, so it judges the URL that won. */\nexport const SSRF_GUARD_PRIORITY = 900;\n"]}
|
package/src/HostPolicy.d.ts
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import { AuthMeta } from '@webpieces/core-util';
|
|
2
|
-
import { ClientFilterDefinition } from '@webpieces/http-client-core';
|
|
3
|
-
import { AddressResolver } from './AddressResolver';
|
|
4
|
-
/**
|
|
5
|
-
* WHERE a client's requests go — the second half of a {@link ClientConfig}, and a REQUIRED one.
|
|
6
|
-
*
|
|
7
|
-
* There are exactly two kinds of destination, and conflating them is what made outbound partner
|
|
8
|
-
* webhooks fall out of the typed world entirely:
|
|
9
|
-
*
|
|
10
|
-
* - a service WE DEPLOY, named by `svcName` and resolved through {@link ClientRegistry}
|
|
11
|
-
* → {@link DeployedServiceHost}
|
|
12
|
-
* - a host that is DATA — a URL a partner registered, an OAuth callback, a per-tenant or
|
|
13
|
-
* self-hosted instance — supplied per call through the RequestContext
|
|
14
|
-
* → {@link RuntimeHostFromContext}
|
|
15
|
-
*
|
|
16
|
-
* Naming one is not optional, because the two carry completely different risk. A deployed peer is
|
|
17
|
-
* an address we chose; a runtime host is attacker-influenced data, and the framework owes it an
|
|
18
|
-
* SSRF policy. Making the choice a class the caller writes down means
|
|
19
|
-
* `grep -rn RuntimeHostFromContext` enumerates every client in the codebase that can be re-pointed
|
|
20
|
-
* at all — which is the question a security review actually asks.
|
|
21
|
-
*/
|
|
22
|
-
export declare abstract class HostPolicy {
|
|
23
|
-
/**
|
|
24
|
-
* The base URL this call STARTS from, before any filter re-points it.
|
|
25
|
-
*
|
|
26
|
-
* For a deployed service that is the whole answer. For a runtime host it is the empty string:
|
|
27
|
-
* there is nothing to resolve at this point, and {@link ContextBaseUrlOverrideFilter} — which
|
|
28
|
-
* this policy also installs — supplies the real destination inside the chain, or refuses. The
|
|
29
|
-
* empty seed is never sent: the override filter throws when the context carries no URL, and the
|
|
30
|
-
* SSRF guard refuses anything that is not an absolute https URL.
|
|
31
|
-
*/
|
|
32
|
-
abstract resolveBaseUrl(svcName: string): Promise<string>;
|
|
33
|
-
/** The framework filters this policy installs, in addition to whatever the app passed. */
|
|
34
|
-
abstract builtInFilters(): ClientFilterDefinition[];
|
|
35
|
-
/**
|
|
36
|
-
* Reject, at BIND time, an endpoint this policy cannot honestly satisfy.
|
|
37
|
-
*
|
|
38
|
-
* @throws Error naming the endpoint and why. The default accepts everything.
|
|
39
|
-
*/
|
|
40
|
-
assertEndpointSupported(_authMeta: AuthMeta | undefined, _methodName: string, _contractName: string): void;
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* TODAY'S BEHAVIOUR, unchanged and byte for byte: the destination is a service we deploy, and its
|
|
44
|
-
* URL comes from {@link ClientRegistry} — a registered mapping, else the installed deriver, else a
|
|
45
|
-
* throw.
|
|
46
|
-
*
|
|
47
|
-
* It installs NO filters, reads no context key, and performs no DNS lookups, so a client written
|
|
48
|
-
* this way runs the exact code path it ran before the outbound chain existed. This is the one to
|
|
49
|
-
* reach for; the runtime ones are for the case where the address genuinely is not knowable at
|
|
50
|
-
* build time.
|
|
51
|
-
*/
|
|
52
|
-
export declare class DeployedServiceHost extends HostPolicy {
|
|
53
|
-
resolveBaseUrl(svcName: string): Promise<string>;
|
|
54
|
-
builtInFilters(): ClientFilterDefinition[];
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* The destination is supplied PER CALL, through
|
|
58
|
-
* `RequestContext.putUntrusted(WebpiecesCoreHeaders.OVERRIDE_BASE_URL, url)`, and the framework's
|
|
59
|
-
* SSRF policy is ON.
|
|
60
|
-
*
|
|
61
|
-
* ```ts
|
|
62
|
-
* const partner = factory.createRpcClient(
|
|
63
|
-
* PartnerWebhookApi,
|
|
64
|
-
* new ClientConfig('partner-webhooks', new RuntimeHostFromContext(new DnsAddressResolver())),
|
|
65
|
-
* [new ClientFilterDefinition(500, new HmacSigningFilter(secret))],
|
|
66
|
-
* );
|
|
67
|
-
*
|
|
68
|
-
* for (const webhook of webhooks) {
|
|
69
|
-
* await RequestContext.run(() => {
|
|
70
|
-
* RequestContext.putUntrusted(WebpiecesCoreHeaders.OVERRIDE_BASE_URL, webhook.url);
|
|
71
|
-
* return partner.deliver(envelope);
|
|
72
|
-
* });
|
|
73
|
-
* }
|
|
74
|
-
* ```
|
|
75
|
-
*
|
|
76
|
-
* `svcName` is still required, and still means something: it is the IDENTITY this client gets as a
|
|
77
|
-
* node on the runtime architecture graph. That is the whole point of routing partner deliveries
|
|
78
|
-
* back through a generated client — the most security-sensitive hop in the system stops being
|
|
79
|
-
* invisible.
|
|
80
|
-
*
|
|
81
|
-
* Endpoints whose auth mode is `@AuthOidc` or `@AuthSharedSecret` are REFUSED at bind time. Both
|
|
82
|
-
* mint a credential for a specific audience/peer that we chose, and there is no honest audience for
|
|
83
|
-
* a host we will not know until the call happens — minting one for a partner's URL would hand them
|
|
84
|
-
* a token. Authenticate a runtime-host call the way a webhook is actually authenticated: a signing
|
|
85
|
-
* filter over the exact bytes.
|
|
86
|
-
*/
|
|
87
|
-
export declare class RuntimeHostFromContext extends HostPolicy {
|
|
88
|
-
private readonly addressResolver;
|
|
89
|
-
constructor(addressResolver: AddressResolver);
|
|
90
|
-
resolveBaseUrl(_svcName: string): Promise<string>;
|
|
91
|
-
builtInFilters(): ClientFilterDefinition[];
|
|
92
|
-
assertEndpointSupported(authMeta: AuthMeta | undefined, methodName: string, contractName: string): void;
|
|
93
|
-
/** HTTPS only, no internal addresses, at most one redirect — each hop re-judged. */
|
|
94
|
-
private static readonly STRICT;
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* {@link RuntimeHostFromContext}, with the internal-address refusals SWITCHED OFF and plaintext
|
|
98
|
-
* http allowed — for a local emulator, an on-cluster service, or a test harness whose "partner" is
|
|
99
|
-
* `http://127.0.0.1:9123`.
|
|
100
|
-
*
|
|
101
|
-
* The long name is the feature. This is the permissive branch, so it is a NOUN a reviewer can grep
|
|
102
|
-
* (`grep -rn AllowingInternalAddresses` lists every client that can reach inside the network with a
|
|
103
|
-
* runtime-supplied URL) rather than a boolean, an omitted argument, or an empty allow-list — a
|
|
104
|
-
* widening that reads as an ABSENCE is invisible exactly where it matters most.
|
|
105
|
-
*
|
|
106
|
-
* The `reason` is required and is quoted back in this client's refusal messages, so the
|
|
107
|
-
* justification travels with the decision instead of living in a commit message.
|
|
108
|
-
*/
|
|
109
|
-
export declare class RuntimeHostFromContextAllowingInternalAddresses extends HostPolicy {
|
|
110
|
-
private readonly addressResolver;
|
|
111
|
-
private readonly policy;
|
|
112
|
-
constructor(
|
|
113
|
-
/** WHY this client may reach internal addresses, in prose. Required. */
|
|
114
|
-
reason: string, addressResolver: AddressResolver);
|
|
115
|
-
resolveBaseUrl(_svcName: string): Promise<string>;
|
|
116
|
-
builtInFilters(): ClientFilterDefinition[];
|
|
117
|
-
assertEndpointSupported(authMeta: AuthMeta | undefined, methodName: string, contractName: string): void;
|
|
118
|
-
}
|
package/src/HostPolicy.js
DELETED
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.RuntimeHostFromContextAllowingInternalAddresses = exports.RuntimeHostFromContext = exports.DeployedServiceHost = exports.HostPolicy = void 0;
|
|
4
|
-
const core_util_1 = require("@webpieces/core-util");
|
|
5
|
-
const http_client_core_1 = require("@webpieces/http-client-core");
|
|
6
|
-
const ContextBaseUrlOverrideFilter_1 = require("./ContextBaseUrlOverrideFilter");
|
|
7
|
-
const SsrfGuardFilter_1 = require("./SsrfGuardFilter");
|
|
8
|
-
const SsrfPolicy_1 = require("./SsrfPolicy");
|
|
9
|
-
const RuntimeHostErrors_1 = require("./RuntimeHostErrors");
|
|
10
|
-
/**
|
|
11
|
-
* WHERE a client's requests go — the second half of a {@link ClientConfig}, and a REQUIRED one.
|
|
12
|
-
*
|
|
13
|
-
* There are exactly two kinds of destination, and conflating them is what made outbound partner
|
|
14
|
-
* webhooks fall out of the typed world entirely:
|
|
15
|
-
*
|
|
16
|
-
* - a service WE DEPLOY, named by `svcName` and resolved through {@link ClientRegistry}
|
|
17
|
-
* → {@link DeployedServiceHost}
|
|
18
|
-
* - a host that is DATA — a URL a partner registered, an OAuth callback, a per-tenant or
|
|
19
|
-
* self-hosted instance — supplied per call through the RequestContext
|
|
20
|
-
* → {@link RuntimeHostFromContext}
|
|
21
|
-
*
|
|
22
|
-
* Naming one is not optional, because the two carry completely different risk. A deployed peer is
|
|
23
|
-
* an address we chose; a runtime host is attacker-influenced data, and the framework owes it an
|
|
24
|
-
* SSRF policy. Making the choice a class the caller writes down means
|
|
25
|
-
* `grep -rn RuntimeHostFromContext` enumerates every client in the codebase that can be re-pointed
|
|
26
|
-
* at all — which is the question a security review actually asks.
|
|
27
|
-
*/
|
|
28
|
-
class HostPolicy {
|
|
29
|
-
/**
|
|
30
|
-
* Reject, at BIND time, an endpoint this policy cannot honestly satisfy.
|
|
31
|
-
*
|
|
32
|
-
* @throws Error naming the endpoint and why. The default accepts everything.
|
|
33
|
-
*/
|
|
34
|
-
assertEndpointSupported(_authMeta, _methodName, _contractName) { }
|
|
35
|
-
}
|
|
36
|
-
exports.HostPolicy = HostPolicy;
|
|
37
|
-
/**
|
|
38
|
-
* TODAY'S BEHAVIOUR, unchanged and byte for byte: the destination is a service we deploy, and its
|
|
39
|
-
* URL comes from {@link ClientRegistry} — a registered mapping, else the installed deriver, else a
|
|
40
|
-
* throw.
|
|
41
|
-
*
|
|
42
|
-
* It installs NO filters, reads no context key, and performs no DNS lookups, so a client written
|
|
43
|
-
* this way runs the exact code path it ran before the outbound chain existed. This is the one to
|
|
44
|
-
* reach for; the runtime ones are for the case where the address genuinely is not knowable at
|
|
45
|
-
* build time.
|
|
46
|
-
*/
|
|
47
|
-
class DeployedServiceHost extends HostPolicy {
|
|
48
|
-
resolveBaseUrl(svcName) {
|
|
49
|
-
return core_util_1.ClientRegistry.resolve(svcName);
|
|
50
|
-
}
|
|
51
|
-
builtInFilters() {
|
|
52
|
-
return [];
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
exports.DeployedServiceHost = DeployedServiceHost;
|
|
56
|
-
/**
|
|
57
|
-
* The destination is supplied PER CALL, through
|
|
58
|
-
* `RequestContext.putUntrusted(WebpiecesCoreHeaders.OVERRIDE_BASE_URL, url)`, and the framework's
|
|
59
|
-
* SSRF policy is ON.
|
|
60
|
-
*
|
|
61
|
-
* ```ts
|
|
62
|
-
* const partner = factory.createRpcClient(
|
|
63
|
-
* PartnerWebhookApi,
|
|
64
|
-
* new ClientConfig('partner-webhooks', new RuntimeHostFromContext(new DnsAddressResolver())),
|
|
65
|
-
* [new ClientFilterDefinition(500, new HmacSigningFilter(secret))],
|
|
66
|
-
* );
|
|
67
|
-
*
|
|
68
|
-
* for (const webhook of webhooks) {
|
|
69
|
-
* await RequestContext.run(() => {
|
|
70
|
-
* RequestContext.putUntrusted(WebpiecesCoreHeaders.OVERRIDE_BASE_URL, webhook.url);
|
|
71
|
-
* return partner.deliver(envelope);
|
|
72
|
-
* });
|
|
73
|
-
* }
|
|
74
|
-
* ```
|
|
75
|
-
*
|
|
76
|
-
* `svcName` is still required, and still means something: it is the IDENTITY this client gets as a
|
|
77
|
-
* node on the runtime architecture graph. That is the whole point of routing partner deliveries
|
|
78
|
-
* back through a generated client — the most security-sensitive hop in the system stops being
|
|
79
|
-
* invisible.
|
|
80
|
-
*
|
|
81
|
-
* Endpoints whose auth mode is `@AuthOidc` or `@AuthSharedSecret` are REFUSED at bind time. Both
|
|
82
|
-
* mint a credential for a specific audience/peer that we chose, and there is no honest audience for
|
|
83
|
-
* a host we will not know until the call happens — minting one for a partner's URL would hand them
|
|
84
|
-
* a token. Authenticate a runtime-host call the way a webhook is actually authenticated: a signing
|
|
85
|
-
* filter over the exact bytes.
|
|
86
|
-
*/
|
|
87
|
-
class RuntimeHostFromContext extends HostPolicy {
|
|
88
|
-
addressResolver;
|
|
89
|
-
constructor(addressResolver) {
|
|
90
|
-
super();
|
|
91
|
-
this.addressResolver = addressResolver;
|
|
92
|
-
}
|
|
93
|
-
async resolveBaseUrl(_svcName) {
|
|
94
|
-
return '';
|
|
95
|
-
}
|
|
96
|
-
builtInFilters() {
|
|
97
|
-
return runtimeHostFilters(this.addressResolver, RuntimeHostFromContext.STRICT);
|
|
98
|
-
}
|
|
99
|
-
assertEndpointSupported(authMeta, methodName, contractName) {
|
|
100
|
-
assertNoServiceCredential(authMeta, methodName, contractName);
|
|
101
|
-
}
|
|
102
|
-
/** HTTPS only, no internal addresses, at most one redirect — each hop re-judged. */
|
|
103
|
-
static STRICT = new SsrfPolicy_1.SsrfPolicy(new Set(['https:']), false, 1, undefined);
|
|
104
|
-
}
|
|
105
|
-
exports.RuntimeHostFromContext = RuntimeHostFromContext;
|
|
106
|
-
/**
|
|
107
|
-
* {@link RuntimeHostFromContext}, with the internal-address refusals SWITCHED OFF and plaintext
|
|
108
|
-
* http allowed — for a local emulator, an on-cluster service, or a test harness whose "partner" is
|
|
109
|
-
* `http://127.0.0.1:9123`.
|
|
110
|
-
*
|
|
111
|
-
* The long name is the feature. This is the permissive branch, so it is a NOUN a reviewer can grep
|
|
112
|
-
* (`grep -rn AllowingInternalAddresses` lists every client that can reach inside the network with a
|
|
113
|
-
* runtime-supplied URL) rather than a boolean, an omitted argument, or an empty allow-list — a
|
|
114
|
-
* widening that reads as an ABSENCE is invisible exactly where it matters most.
|
|
115
|
-
*
|
|
116
|
-
* The `reason` is required and is quoted back in this client's refusal messages, so the
|
|
117
|
-
* justification travels with the decision instead of living in a commit message.
|
|
118
|
-
*/
|
|
119
|
-
class RuntimeHostFromContextAllowingInternalAddresses extends HostPolicy {
|
|
120
|
-
addressResolver;
|
|
121
|
-
policy;
|
|
122
|
-
constructor(
|
|
123
|
-
/** WHY this client may reach internal addresses, in prose. Required. */
|
|
124
|
-
reason, addressResolver) {
|
|
125
|
-
super();
|
|
126
|
-
this.addressResolver = addressResolver;
|
|
127
|
-
this.policy = new SsrfPolicy_1.SsrfPolicy(new Set(['https:', 'http:']), true, 1, reason);
|
|
128
|
-
}
|
|
129
|
-
async resolveBaseUrl(_svcName) {
|
|
130
|
-
return '';
|
|
131
|
-
}
|
|
132
|
-
builtInFilters() {
|
|
133
|
-
return runtimeHostFilters(this.addressResolver, this.policy);
|
|
134
|
-
}
|
|
135
|
-
assertEndpointSupported(authMeta, methodName, contractName) {
|
|
136
|
-
assertNoServiceCredential(authMeta, methodName, contractName);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
exports.RuntimeHostFromContextAllowingInternalAddresses = RuntimeHostFromContextAllowingInternalAddresses;
|
|
140
|
-
/**
|
|
141
|
-
* The two built-ins every runtime-host client gets: settle the destination from the context, then
|
|
142
|
-
* judge it. Shared by both runtime policies so the ORDER cannot drift between them — a guard that
|
|
143
|
-
* ran before the override would be judging the wrong URL.
|
|
144
|
-
*/
|
|
145
|
-
// webpieces-disable no-function-outside-class -- shared construction of two filter definitions; it holds no state a class could own
|
|
146
|
-
function runtimeHostFilters(addressResolver, policy) {
|
|
147
|
-
return [
|
|
148
|
-
new http_client_core_1.ClientFilterDefinition(ContextBaseUrlOverrideFilter_1.BASE_URL_OVERRIDE_PRIORITY, new ContextBaseUrlOverrideFilter_1.ContextBaseUrlOverrideFilter()),
|
|
149
|
-
new http_client_core_1.ClientFilterDefinition(ContextBaseUrlOverrideFilter_1.SSRF_GUARD_PRIORITY, new SsrfGuardFilter_1.SsrfGuardFilter(policy, addressResolver)),
|
|
150
|
-
];
|
|
151
|
-
}
|
|
152
|
-
/**
|
|
153
|
-
* @throws Error when the endpoint expects a credential minted for an audience we choose. Shared by
|
|
154
|
-
* both runtime policies for the same reason {@link runtimeHostFilters} is.
|
|
155
|
-
*/
|
|
156
|
-
// webpieces-disable no-function-outside-class -- shared bind-time assertion; it holds no state a class could own
|
|
157
|
-
function assertNoServiceCredential(authMeta, methodName, contractName) {
|
|
158
|
-
const kind = authMeta?.mode?.kind;
|
|
159
|
-
if (kind !== 'oidc' && kind !== 'shared-secret') {
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
throw new RuntimeHostErrors_1.RuntimeHostEndpointUnsupportedError(`${contractName}.${methodName} is authenticated with '${kind}', which cannot be used by a ` +
|
|
163
|
-
`runtime-host client. Both mint a credential for a peer WE chose — an OIDC token's audience is ` +
|
|
164
|
-
`the callee's base URL, and a shared secret is one we agreed with a named service — and the ` +
|
|
165
|
-
`destination here is not known until the call happens, so minting either one would hand our ` +
|
|
166
|
-
`credential to whoever registered the URL. Authenticate this hop the way a webhook actually is ` +
|
|
167
|
-
`authenticated: an app filter that signs the exact serialized bytes (ClientRequest.body).`, `${contractName}.${methodName}`);
|
|
168
|
-
}
|
|
169
|
-
//# sourceMappingURL=HostPolicy.js.map
|
package/src/HostPolicy.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"HostPolicy.js","sourceRoot":"","sources":["../../../../../packages/http/http-client-node/src/HostPolicy.ts"],"names":[],"mappings":"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","sourcesContent":["import { AuthMeta, ClientRegistry } from '@webpieces/core-util';\nimport { ClientFilterDefinition } from '@webpieces/http-client-core';\nimport { AddressResolver } from './AddressResolver';\nimport { BASE_URL_OVERRIDE_PRIORITY, ContextBaseUrlOverrideFilter, SSRF_GUARD_PRIORITY } from './ContextBaseUrlOverrideFilter';\nimport { SsrfGuardFilter } from './SsrfGuardFilter';\nimport { SsrfPolicy } from './SsrfPolicy';\nimport { RuntimeHostEndpointUnsupportedError } from './RuntimeHostErrors';\n\n/**\n * WHERE a client's requests go — the second half of a {@link ClientConfig}, and a REQUIRED one.\n *\n * There are exactly two kinds of destination, and conflating them is what made outbound partner\n * webhooks fall out of the typed world entirely:\n *\n * - a service WE DEPLOY, named by `svcName` and resolved through {@link ClientRegistry}\n * → {@link DeployedServiceHost}\n * - a host that is DATA — a URL a partner registered, an OAuth callback, a per-tenant or\n * self-hosted instance — supplied per call through the RequestContext\n * → {@link RuntimeHostFromContext}\n *\n * Naming one is not optional, because the two carry completely different risk. A deployed peer is\n * an address we chose; a runtime host is attacker-influenced data, and the framework owes it an\n * SSRF policy. Making the choice a class the caller writes down means\n * `grep -rn RuntimeHostFromContext` enumerates every client in the codebase that can be re-pointed\n * at all — which is the question a security review actually asks.\n */\nexport abstract class HostPolicy {\n /**\n * The base URL this call STARTS from, before any filter re-points it.\n *\n * For a deployed service that is the whole answer. For a runtime host it is the empty string:\n * there is nothing to resolve at this point, and {@link ContextBaseUrlOverrideFilter} — which\n * this policy also installs — supplies the real destination inside the chain, or refuses. The\n * empty seed is never sent: the override filter throws when the context carries no URL, and the\n * SSRF guard refuses anything that is not an absolute https URL.\n */\n abstract resolveBaseUrl(svcName: string): Promise<string>;\n\n /** The framework filters this policy installs, in addition to whatever the app passed. */\n abstract builtInFilters(): ClientFilterDefinition[];\n\n /**\n * Reject, at BIND time, an endpoint this policy cannot honestly satisfy.\n *\n * @throws Error naming the endpoint and why. The default accepts everything.\n */\n assertEndpointSupported(_authMeta: AuthMeta | undefined, _methodName: string, _contractName: string): void {}\n}\n\n/**\n * TODAY'S BEHAVIOUR, unchanged and byte for byte: the destination is a service we deploy, and its\n * URL comes from {@link ClientRegistry} — a registered mapping, else the installed deriver, else a\n * throw.\n *\n * It installs NO filters, reads no context key, and performs no DNS lookups, so a client written\n * this way runs the exact code path it ran before the outbound chain existed. This is the one to\n * reach for; the runtime ones are for the case where the address genuinely is not knowable at\n * build time.\n */\nexport class DeployedServiceHost extends HostPolicy {\n override resolveBaseUrl(svcName: string): Promise<string> {\n return ClientRegistry.resolve(svcName);\n }\n\n override builtInFilters(): ClientFilterDefinition[] {\n return [];\n }\n}\n\n/**\n * The destination is supplied PER CALL, through\n * `RequestContext.putUntrusted(WebpiecesCoreHeaders.OVERRIDE_BASE_URL, url)`, and the framework's\n * SSRF policy is ON.\n *\n * ```ts\n * const partner = factory.createRpcClient(\n * PartnerWebhookApi,\n * new ClientConfig('partner-webhooks', new RuntimeHostFromContext(new DnsAddressResolver())),\n * [new ClientFilterDefinition(500, new HmacSigningFilter(secret))],\n * );\n *\n * for (const webhook of webhooks) {\n * await RequestContext.run(() => {\n * RequestContext.putUntrusted(WebpiecesCoreHeaders.OVERRIDE_BASE_URL, webhook.url);\n * return partner.deliver(envelope);\n * });\n * }\n * ```\n *\n * `svcName` is still required, and still means something: it is the IDENTITY this client gets as a\n * node on the runtime architecture graph. That is the whole point of routing partner deliveries\n * back through a generated client — the most security-sensitive hop in the system stops being\n * invisible.\n *\n * Endpoints whose auth mode is `@AuthOidc` or `@AuthSharedSecret` are REFUSED at bind time. Both\n * mint a credential for a specific audience/peer that we chose, and there is no honest audience for\n * a host we will not know until the call happens — minting one for a partner's URL would hand them\n * a token. Authenticate a runtime-host call the way a webhook is actually authenticated: a signing\n * filter over the exact bytes.\n */\nexport class RuntimeHostFromContext extends HostPolicy {\n constructor(private readonly addressResolver: AddressResolver) {\n super();\n }\n\n override async resolveBaseUrl(_svcName: string): Promise<string> {\n return '';\n }\n\n override builtInFilters(): ClientFilterDefinition[] {\n return runtimeHostFilters(this.addressResolver, RuntimeHostFromContext.STRICT);\n }\n\n override assertEndpointSupported(\n authMeta: AuthMeta | undefined,\n methodName: string,\n contractName: string,\n ): void {\n assertNoServiceCredential(authMeta, methodName, contractName);\n }\n\n /** HTTPS only, no internal addresses, at most one redirect — each hop re-judged. */\n private static readonly STRICT = new SsrfPolicy(new Set(['https:']), false, 1, undefined);\n}\n\n/**\n * {@link RuntimeHostFromContext}, with the internal-address refusals SWITCHED OFF and plaintext\n * http allowed — for a local emulator, an on-cluster service, or a test harness whose \"partner\" is\n * `http://127.0.0.1:9123`.\n *\n * The long name is the feature. This is the permissive branch, so it is a NOUN a reviewer can grep\n * (`grep -rn AllowingInternalAddresses` lists every client that can reach inside the network with a\n * runtime-supplied URL) rather than a boolean, an omitted argument, or an empty allow-list — a\n * widening that reads as an ABSENCE is invisible exactly where it matters most.\n *\n * The `reason` is required and is quoted back in this client's refusal messages, so the\n * justification travels with the decision instead of living in a commit message.\n */\nexport class RuntimeHostFromContextAllowingInternalAddresses extends HostPolicy {\n private readonly policy: SsrfPolicy;\n\n constructor(\n /** WHY this client may reach internal addresses, in prose. Required. */\n reason: string,\n private readonly addressResolver: AddressResolver,\n ) {\n super();\n this.policy = new SsrfPolicy(new Set(['https:', 'http:']), true, 1, reason);\n }\n\n override async resolveBaseUrl(_svcName: string): Promise<string> {\n return '';\n }\n\n override builtInFilters(): ClientFilterDefinition[] {\n return runtimeHostFilters(this.addressResolver, this.policy);\n }\n\n override assertEndpointSupported(\n authMeta: AuthMeta | undefined,\n methodName: string,\n contractName: string,\n ): void {\n assertNoServiceCredential(authMeta, methodName, contractName);\n }\n}\n\n/**\n * The two built-ins every runtime-host client gets: settle the destination from the context, then\n * judge it. Shared by both runtime policies so the ORDER cannot drift between them — a guard that\n * ran before the override would be judging the wrong URL.\n */\n// webpieces-disable no-function-outside-class -- shared construction of two filter definitions; it holds no state a class could own\nfunction runtimeHostFilters(addressResolver: AddressResolver, policy: SsrfPolicy): ClientFilterDefinition[] {\n return [\n new ClientFilterDefinition(BASE_URL_OVERRIDE_PRIORITY, new ContextBaseUrlOverrideFilter()),\n new ClientFilterDefinition(SSRF_GUARD_PRIORITY, new SsrfGuardFilter(policy, addressResolver)),\n ];\n}\n\n/**\n * @throws Error when the endpoint expects a credential minted for an audience we choose. Shared by\n * both runtime policies for the same reason {@link runtimeHostFilters} is.\n */\n// webpieces-disable no-function-outside-class -- shared bind-time assertion; it holds no state a class could own\nfunction assertNoServiceCredential(\n authMeta: AuthMeta | undefined,\n methodName: string,\n contractName: string,\n): void {\n const kind = authMeta?.mode?.kind;\n if (kind !== 'oidc' && kind !== 'shared-secret') {\n return;\n }\n throw new RuntimeHostEndpointUnsupportedError(\n `${contractName}.${methodName} is authenticated with '${kind}', which cannot be used by a ` +\n `runtime-host client. Both mint a credential for a peer WE chose — an OIDC token's audience is ` +\n `the callee's base URL, and a shared secret is one we agreed with a named service — and the ` +\n `destination here is not known until the call happens, so minting either one would hand our ` +\n `credential to whoever registered the URL. Authenticate this hop the way a webhook actually is ` +\n `authenticated: an app filter that signs the exact serialized bytes (ClientRequest.body).`,\n `${contractName}.${methodName}`,\n );\n}\n"]}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The two ways a RUNTIME-HOST client refuses before any bytes leave, each its own type for the same
|
|
3
|
-
* reason {@link SsrfRefusedError} is: a delivery worker has to tell these apart, and they have
|
|
4
|
-
* different owners and different cures.
|
|
5
|
-
*
|
|
6
|
-
* - {@link RuntimeHostEndpointUnsupportedError} — BIND time, OUR bug. The contract declares an auth
|
|
7
|
-
* mode no runtime-host client can honestly satisfy. It fires when the client is built, so it takes
|
|
8
|
-
* the process down at startup rather than at the first delivery, which is the point.
|
|
9
|
-
* - {@link MissingRuntimeBaseUrlError} — CALL time, the CALLER's bug. This call was made with no
|
|
10
|
-
* destination in scope. Retrying is pointless; the fix is at the call site.
|
|
11
|
-
*
|
|
12
|
-
* Neither is an {@link SsrfRefusedError}: nothing about the destination was judged and found hostile.
|
|
13
|
-
* A worker that dead-letters on SSRF should usually PAGE on these instead — they mean the sender is
|
|
14
|
-
* misconfigured, not that a partner registered something dangerous.
|
|
15
|
-
*/
|
|
16
|
-
/**
|
|
17
|
-
* A runtime-host client was pointed at an endpoint whose auth mode mints a credential for an
|
|
18
|
-
* audience WE choose (`@AuthOidc`, `@AuthSharedSecret`). Thrown at BIND time, from
|
|
19
|
-
* `HostPolicy.assertEndpointSupported`.
|
|
20
|
-
*/
|
|
21
|
-
export declare class RuntimeHostEndpointUnsupportedError extends Error {
|
|
22
|
-
/** `Contract.method`, so a startup failure names the endpoint without reading a stack. */
|
|
23
|
-
readonly endpoint: string;
|
|
24
|
-
constructor(message: string,
|
|
25
|
-
/** `Contract.method`, so a startup failure names the endpoint without reading a stack. */
|
|
26
|
-
endpoint: string);
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* A runtime-host client was called with no `WebpiecesCoreHeaders.OVERRIDE_BASE_URL` in the ambient
|
|
30
|
-
* RequestContext, so there is no destination for this call.
|
|
31
|
-
*
|
|
32
|
-
* It is a THROW rather than a fallback to a derived service URL, deliberately: a silent fallback
|
|
33
|
-
* would send a partner's payload to one of our own services, which is a worse outcome than a loud
|
|
34
|
-
* failure by every measure.
|
|
35
|
-
*/
|
|
36
|
-
export declare class MissingRuntimeBaseUrlError extends Error {
|
|
37
|
-
/** `Contract.method`, so the log line names the call that had nowhere to go. */
|
|
38
|
-
readonly endpoint: string;
|
|
39
|
-
constructor(message: string,
|
|
40
|
-
/** `Contract.method`, so the log line names the call that had nowhere to go. */
|
|
41
|
-
endpoint: string);
|
|
42
|
-
}
|
package/src/RuntimeHostErrors.js
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* The two ways a RUNTIME-HOST client refuses before any bytes leave, each its own type for the same
|
|
4
|
-
* reason {@link SsrfRefusedError} is: a delivery worker has to tell these apart, and they have
|
|
5
|
-
* different owners and different cures.
|
|
6
|
-
*
|
|
7
|
-
* - {@link RuntimeHostEndpointUnsupportedError} — BIND time, OUR bug. The contract declares an auth
|
|
8
|
-
* mode no runtime-host client can honestly satisfy. It fires when the client is built, so it takes
|
|
9
|
-
* the process down at startup rather than at the first delivery, which is the point.
|
|
10
|
-
* - {@link MissingRuntimeBaseUrlError} — CALL time, the CALLER's bug. This call was made with no
|
|
11
|
-
* destination in scope. Retrying is pointless; the fix is at the call site.
|
|
12
|
-
*
|
|
13
|
-
* Neither is an {@link SsrfRefusedError}: nothing about the destination was judged and found hostile.
|
|
14
|
-
* A worker that dead-letters on SSRF should usually PAGE on these instead — they mean the sender is
|
|
15
|
-
* misconfigured, not that a partner registered something dangerous.
|
|
16
|
-
*/
|
|
17
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
-
exports.MissingRuntimeBaseUrlError = exports.RuntimeHostEndpointUnsupportedError = void 0;
|
|
19
|
-
/**
|
|
20
|
-
* A runtime-host client was pointed at an endpoint whose auth mode mints a credential for an
|
|
21
|
-
* audience WE choose (`@AuthOidc`, `@AuthSharedSecret`). Thrown at BIND time, from
|
|
22
|
-
* `HostPolicy.assertEndpointSupported`.
|
|
23
|
-
*/
|
|
24
|
-
class RuntimeHostEndpointUnsupportedError extends Error {
|
|
25
|
-
endpoint;
|
|
26
|
-
constructor(message,
|
|
27
|
-
/** `Contract.method`, so a startup failure names the endpoint without reading a stack. */
|
|
28
|
-
endpoint) {
|
|
29
|
-
super(message);
|
|
30
|
-
this.endpoint = endpoint;
|
|
31
|
-
this.name = 'RuntimeHostEndpointUnsupportedError';
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
exports.RuntimeHostEndpointUnsupportedError = RuntimeHostEndpointUnsupportedError;
|
|
35
|
-
/**
|
|
36
|
-
* A runtime-host client was called with no `WebpiecesCoreHeaders.OVERRIDE_BASE_URL` in the ambient
|
|
37
|
-
* RequestContext, so there is no destination for this call.
|
|
38
|
-
*
|
|
39
|
-
* It is a THROW rather than a fallback to a derived service URL, deliberately: a silent fallback
|
|
40
|
-
* would send a partner's payload to one of our own services, which is a worse outcome than a loud
|
|
41
|
-
* failure by every measure.
|
|
42
|
-
*/
|
|
43
|
-
class MissingRuntimeBaseUrlError extends Error {
|
|
44
|
-
endpoint;
|
|
45
|
-
constructor(message,
|
|
46
|
-
/** `Contract.method`, so the log line names the call that had nowhere to go. */
|
|
47
|
-
endpoint) {
|
|
48
|
-
super(message);
|
|
49
|
-
this.endpoint = endpoint;
|
|
50
|
-
this.name = 'MissingRuntimeBaseUrlError';
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
exports.MissingRuntimeBaseUrlError = MissingRuntimeBaseUrlError;
|
|
54
|
-
//# sourceMappingURL=RuntimeHostErrors.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"RuntimeHostErrors.js","sourceRoot":"","sources":["../../../../../packages/http/http-client-node/src/RuntimeHostErrors.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;GAcG;;;AAEH;;;;GAIG;AACH,MAAa,mCAAoC,SAAQ,KAAK;IAItC;IAHpB,YACI,OAAe;IACf,0FAA0F;IAC1E,QAAgB;QAEhC,KAAK,CAAC,OAAO,CAAC,CAAC;QAFC,aAAQ,GAAR,QAAQ,CAAQ;QAGhC,IAAI,CAAC,IAAI,GAAG,qCAAqC,CAAC;IACtD,CAAC;CACJ;AATD,kFASC;AAED;;;;;;;GAOG;AACH,MAAa,0BAA2B,SAAQ,KAAK;IAI7B;IAHpB,YACI,OAAe;IACf,gFAAgF;IAChE,QAAgB;QAEhC,KAAK,CAAC,OAAO,CAAC,CAAC;QAFC,aAAQ,GAAR,QAAQ,CAAQ;QAGhC,IAAI,CAAC,IAAI,GAAG,4BAA4B,CAAC;IAC7C,CAAC;CACJ;AATD,gEASC","sourcesContent":["/**\n * The two ways a RUNTIME-HOST client refuses before any bytes leave, each its own type for the same\n * reason {@link SsrfRefusedError} is: a delivery worker has to tell these apart, and they have\n * different owners and different cures.\n *\n * - {@link RuntimeHostEndpointUnsupportedError} — BIND time, OUR bug. The contract declares an auth\n * mode no runtime-host client can honestly satisfy. It fires when the client is built, so it takes\n * the process down at startup rather than at the first delivery, which is the point.\n * - {@link MissingRuntimeBaseUrlError} — CALL time, the CALLER's bug. This call was made with no\n * destination in scope. Retrying is pointless; the fix is at the call site.\n *\n * Neither is an {@link SsrfRefusedError}: nothing about the destination was judged and found hostile.\n * A worker that dead-letters on SSRF should usually PAGE on these instead — they mean the sender is\n * misconfigured, not that a partner registered something dangerous.\n */\n\n/**\n * A runtime-host client was pointed at an endpoint whose auth mode mints a credential for an\n * audience WE choose (`@AuthOidc`, `@AuthSharedSecret`). Thrown at BIND time, from\n * `HostPolicy.assertEndpointSupported`.\n */\nexport class RuntimeHostEndpointUnsupportedError extends Error {\n constructor(\n message: string,\n /** `Contract.method`, so a startup failure names the endpoint without reading a stack. */\n public readonly endpoint: string,\n ) {\n super(message);\n this.name = 'RuntimeHostEndpointUnsupportedError';\n }\n}\n\n/**\n * A runtime-host client was called with no `WebpiecesCoreHeaders.OVERRIDE_BASE_URL` in the ambient\n * RequestContext, so there is no destination for this call.\n *\n * It is a THROW rather than a fallback to a derived service URL, deliberately: a silent fallback\n * would send a partner's payload to one of our own services, which is a worse outcome than a loud\n * failure by every measure.\n */\nexport class MissingRuntimeBaseUrlError extends Error {\n constructor(\n message: string,\n /** `Contract.method`, so the log line names the call that had nowhere to go. */\n public readonly endpoint: string,\n ) {\n super(message);\n this.name = 'MissingRuntimeBaseUrlError';\n }\n}\n"]}
|