@revenexx/integrations-node-sdk 1.11.0 → 1.14.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/dist/allowedTargets.d.ts +67 -0
- package/dist/{chunk-XO73F62E.js → chunk-CA6JVLLF.js} +52 -0
- package/dist/cli.cjs +49 -0
- package/dist/cli.js +4 -1
- package/dist/errors.d.ts +21 -0
- package/dist/index.cjs +374 -31
- package/dist/index.d.ts +3 -2
- package/dist/index.js +319 -32
- package/dist/manifest.d.ts +25 -0
- package/dist/ssrf.d.ts +54 -2
- package/dist/types.d.ts +64 -1
- package/package.json +3 -2
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The per-run allow-list of the SSRF guard (PO-828): the exact `ip:port` pairs a
|
|
3
|
+
* run may reach although the address is private — its own tenant's VPN routes,
|
|
4
|
+
* served by the relay at a private overlay address (an RFC 1918 or IPv6
|
|
5
|
+
* unique-local one — no other address may be listed).
|
|
6
|
+
*
|
|
7
|
+
* **The global symbol is the contract, not this module.** Every node bundle carries
|
|
8
|
+
* its own copy of this package, so a module-level setter would reach only the copy
|
|
9
|
+
* that happened to be imported by the host. The trusted host instead defines one
|
|
10
|
+
* property on `globalThis`, before any node bundle is imported, and every copy reads
|
|
11
|
+
* that same property:
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* Object.defineProperty(globalThis, Symbol.for('revenexx.ssrf.allowedTargets'), {
|
|
15
|
+
* value: Object.freeze([...targets]),
|
|
16
|
+
* writable: false, configurable: false, enumerable: false,
|
|
17
|
+
* });
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* The host may do that itself (the runtimes do, without depending on this package)
|
|
21
|
+
* or call {@link installAllowedTargets}, which does exactly that.
|
|
22
|
+
*
|
|
23
|
+
* A property that is not shaped that way is read as no list at all. Non-writable and
|
|
24
|
+
* non-configurable is what makes the list impossible to widen after the host set it;
|
|
25
|
+
* a property that lacks either was not set by a host following the contract, and a
|
|
26
|
+
* guard that honoured it would let whoever set it decide what is private.
|
|
27
|
+
*/
|
|
28
|
+
export declare const ALLOWED_TARGETS_SYMBOL: unique symbol;
|
|
29
|
+
/** Test seam: how many times a list has been validated. Not re-exported from the package. */
|
|
30
|
+
export declare const allowListStats: {
|
|
31
|
+
validations: number;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Read the list off `holder` — `globalThis` in production, a fresh object in the
|
|
35
|
+
* tests that need a shape the one-shot global cannot be given twice. Anything but a
|
|
36
|
+
* frozen array of valid entries on a non-writable, non-configurable data property
|
|
37
|
+
* reads as the empty list: fail closed, never partly.
|
|
38
|
+
*/
|
|
39
|
+
export declare function readAllowedTargets(holder: object): readonly string[];
|
|
40
|
+
/** {@link isAllowedTarget} against the list on `holder`. */
|
|
41
|
+
export declare function isAllowedTargetOn(holder: object, ip: string, port: number): boolean;
|
|
42
|
+
/** {@link installAllowedTargets} onto `holder`. */
|
|
43
|
+
export declare function installAllowedTargetsOn(holder: object, targets: readonly string[]): void;
|
|
44
|
+
/**
|
|
45
|
+
* Install the per-run allow-list on `globalThis`. For the trusted host, before any
|
|
46
|
+
* node bundle is imported; one-shot — a second call throws, as does an entry that is
|
|
47
|
+
* not `"<ip>:<port>"` (IPv4 dotted) or `"[v6]:<port>"`, or whose address is not an
|
|
48
|
+
* RFC 1918 or IPv6 unique-local one.
|
|
49
|
+
*
|
|
50
|
+
* Install it even when the list is empty. A process that never installs one leaves
|
|
51
|
+
* the property free for the first code that runs to define it — node code included.
|
|
52
|
+
*
|
|
53
|
+
* Promised behaviour: specs/ssrf-guard.md (AC-26 to AC-31).
|
|
54
|
+
*/
|
|
55
|
+
export declare function installAllowedTargets(targets: readonly string[]): void;
|
|
56
|
+
/**
|
|
57
|
+
* The per-run allow-list as the host installed it, or an empty list when none is
|
|
58
|
+
* installed or the property is malformed (see {@link ALLOWED_TARGETS_SYMBOL}).
|
|
59
|
+
*/
|
|
60
|
+
export declare function allowedTargets(): readonly string[];
|
|
61
|
+
/**
|
|
62
|
+
* Whether the run may reach `ip` on `port` although the address is private: `true`
|
|
63
|
+
* only when exactly that pair is on the allow-list. The port is part of the target —
|
|
64
|
+
* `10.31.0.7:8080` on the list does not allow `10.31.0.7:22`. For a caller that opens
|
|
65
|
+
* its own socket; `safeFetch` and `assertPublicUrl` already consult it.
|
|
66
|
+
*/
|
|
67
|
+
export declare function isAllowedTarget(ip: string, port: number): boolean;
|
|
@@ -92,6 +92,53 @@ function parsePackageMeta(raw) {
|
|
|
92
92
|
icon: icon !== "" ? icon : void 0
|
|
93
93
|
};
|
|
94
94
|
}
|
|
95
|
+
var SLUG_PATTERN = "^[a-z][a-z0-9-]{0,62}$";
|
|
96
|
+
var NAMESPACED_SLUG_PATTERN = "^[a-z][a-z0-9-]{0,62}:[a-z][a-z0-9-]{0,62}$";
|
|
97
|
+
var SEMVER_PATTERN = "^\\d+\\.\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$";
|
|
98
|
+
var CONFIG_KEY_PATTERN = "^[a-zA-Z][a-zA-Z0-9-]*$";
|
|
99
|
+
var PATTERN_HINT = {
|
|
100
|
+
[SLUG_PATTERN]: 'lowercase kebab-case, such as "not-found"',
|
|
101
|
+
[NAMESPACED_SLUG_PATTERN]: '"<namespace>:<name>" in lowercase kebab-case, such as "core:http-request"',
|
|
102
|
+
[SEMVER_PATTERN]: 'a semantic version, such as "1.0.0"',
|
|
103
|
+
[CONFIG_KEY_PATTERN]: 'a letter, then letters, digits or "-", such as "postalCode"'
|
|
104
|
+
};
|
|
105
|
+
function nodeManifestProblems(nodes) {
|
|
106
|
+
const problems = [];
|
|
107
|
+
for (const [index, node] of nodes.entries()) {
|
|
108
|
+
const who = `node ${index + 1} (${typeof node.slug === "string" ? node.slug : "no slug"})`;
|
|
109
|
+
const check = (path, value, pattern) => {
|
|
110
|
+
if (value === void 0 || typeof value === "string" && new RegExp(pattern).test(value)) return;
|
|
111
|
+
problems.push(
|
|
112
|
+
`${who}: ${path} ${JSON.stringify(value)} is refused by the registry \u2014 it must match ${pattern} (${PATTERN_HINT[pattern]}).`
|
|
113
|
+
);
|
|
114
|
+
};
|
|
115
|
+
check("slug", node.slug, NAMESPACED_SLUG_PATTERN);
|
|
116
|
+
check("version", node.version, SEMVER_PATTERN);
|
|
117
|
+
for (const [i, port] of (node.outputs ?? []).entries()) {
|
|
118
|
+
check(`outputs[${i}].name`, port.name, SLUG_PATTERN);
|
|
119
|
+
check(`outputs[${i}].sourceFromConfig`, port.sourceFromConfig, SLUG_PATTERN);
|
|
120
|
+
check(`outputs[${i}].fallback.name`, port.fallback?.name, SLUG_PATTERN);
|
|
121
|
+
}
|
|
122
|
+
const checkField = (field, at) => {
|
|
123
|
+
check(`${at}.key`, field.key, CONFIG_KEY_PATTERN);
|
|
124
|
+
for (const [j, key] of (field.dependsOn ?? []).entries()) check(`${at}.dependsOn[${j}]`, key, CONFIG_KEY_PATTERN);
|
|
125
|
+
check(`${at}.showIf.key`, field.showIf?.key, CONFIG_KEY_PATTERN);
|
|
126
|
+
const types = field.credentialType;
|
|
127
|
+
if (Array.isArray(types)) {
|
|
128
|
+
for (const [j, type] of types.entries()) check(`${at}.credentialType[${j}]`, type, NAMESPACED_SLUG_PATTERN);
|
|
129
|
+
} else {
|
|
130
|
+
check(`${at}.credentialType`, types, NAMESPACED_SLUG_PATTERN);
|
|
131
|
+
}
|
|
132
|
+
walk(field.properties, `${at}.properties`);
|
|
133
|
+
if (field.items) checkField(field.items, `${at}.items`);
|
|
134
|
+
};
|
|
135
|
+
const walk = (fields, path) => {
|
|
136
|
+
for (const [i, field] of (fields ?? []).entries()) checkField(field, `${path}[${i}]`);
|
|
137
|
+
};
|
|
138
|
+
walk(node.config, "config");
|
|
139
|
+
}
|
|
140
|
+
return problems;
|
|
141
|
+
}
|
|
95
142
|
function buildManifest(nodes, credentials = [], templates = []) {
|
|
96
143
|
const manifest = {
|
|
97
144
|
manifestVersion: MANIFEST_VERSION,
|
|
@@ -118,5 +165,10 @@ export {
|
|
|
118
165
|
MANIFEST_VERSION,
|
|
119
166
|
packageIconProblem,
|
|
120
167
|
parsePackageMeta,
|
|
168
|
+
SLUG_PATTERN,
|
|
169
|
+
NAMESPACED_SLUG_PATTERN,
|
|
170
|
+
SEMVER_PATTERN,
|
|
171
|
+
CONFIG_KEY_PATTERN,
|
|
172
|
+
nodeManifestProblems,
|
|
121
173
|
buildManifest
|
|
122
174
|
};
|
package/dist/cli.cjs
CHANGED
|
@@ -147,6 +147,53 @@ function parsePackageMeta(raw) {
|
|
|
147
147
|
icon: icon !== "" ? icon : void 0
|
|
148
148
|
};
|
|
149
149
|
}
|
|
150
|
+
var SLUG_PATTERN = "^[a-z][a-z0-9-]{0,62}$";
|
|
151
|
+
var NAMESPACED_SLUG_PATTERN = "^[a-z][a-z0-9-]{0,62}:[a-z][a-z0-9-]{0,62}$";
|
|
152
|
+
var SEMVER_PATTERN = "^\\d+\\.\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$";
|
|
153
|
+
var CONFIG_KEY_PATTERN = "^[a-zA-Z][a-zA-Z0-9-]*$";
|
|
154
|
+
var PATTERN_HINT = {
|
|
155
|
+
[SLUG_PATTERN]: 'lowercase kebab-case, such as "not-found"',
|
|
156
|
+
[NAMESPACED_SLUG_PATTERN]: '"<namespace>:<name>" in lowercase kebab-case, such as "core:http-request"',
|
|
157
|
+
[SEMVER_PATTERN]: 'a semantic version, such as "1.0.0"',
|
|
158
|
+
[CONFIG_KEY_PATTERN]: 'a letter, then letters, digits or "-", such as "postalCode"'
|
|
159
|
+
};
|
|
160
|
+
function nodeManifestProblems(nodes) {
|
|
161
|
+
const problems = [];
|
|
162
|
+
for (const [index, node] of nodes.entries()) {
|
|
163
|
+
const who = `node ${index + 1} (${typeof node.slug === "string" ? node.slug : "no slug"})`;
|
|
164
|
+
const check = (path, value, pattern) => {
|
|
165
|
+
if (value === void 0 || typeof value === "string" && new RegExp(pattern).test(value)) return;
|
|
166
|
+
problems.push(
|
|
167
|
+
`${who}: ${path} ${JSON.stringify(value)} is refused by the registry \u2014 it must match ${pattern} (${PATTERN_HINT[pattern]}).`
|
|
168
|
+
);
|
|
169
|
+
};
|
|
170
|
+
check("slug", node.slug, NAMESPACED_SLUG_PATTERN);
|
|
171
|
+
check("version", node.version, SEMVER_PATTERN);
|
|
172
|
+
for (const [i, port] of (node.outputs ?? []).entries()) {
|
|
173
|
+
check(`outputs[${i}].name`, port.name, SLUG_PATTERN);
|
|
174
|
+
check(`outputs[${i}].sourceFromConfig`, port.sourceFromConfig, SLUG_PATTERN);
|
|
175
|
+
check(`outputs[${i}].fallback.name`, port.fallback?.name, SLUG_PATTERN);
|
|
176
|
+
}
|
|
177
|
+
const checkField = (field, at) => {
|
|
178
|
+
check(`${at}.key`, field.key, CONFIG_KEY_PATTERN);
|
|
179
|
+
for (const [j, key] of (field.dependsOn ?? []).entries()) check(`${at}.dependsOn[${j}]`, key, CONFIG_KEY_PATTERN);
|
|
180
|
+
check(`${at}.showIf.key`, field.showIf?.key, CONFIG_KEY_PATTERN);
|
|
181
|
+
const types = field.credentialType;
|
|
182
|
+
if (Array.isArray(types)) {
|
|
183
|
+
for (const [j, type] of types.entries()) check(`${at}.credentialType[${j}]`, type, NAMESPACED_SLUG_PATTERN);
|
|
184
|
+
} else {
|
|
185
|
+
check(`${at}.credentialType`, types, NAMESPACED_SLUG_PATTERN);
|
|
186
|
+
}
|
|
187
|
+
walk(field.properties, `${at}.properties`);
|
|
188
|
+
if (field.items) checkField(field.items, `${at}.items`);
|
|
189
|
+
};
|
|
190
|
+
const walk = (fields, path) => {
|
|
191
|
+
for (const [i, field] of (fields ?? []).entries()) checkField(field, `${path}[${i}]`);
|
|
192
|
+
};
|
|
193
|
+
walk(node.config, "config");
|
|
194
|
+
}
|
|
195
|
+
return problems;
|
|
196
|
+
}
|
|
150
197
|
function buildManifest(nodes, credentials = [], templates = []) {
|
|
151
198
|
const manifest = {
|
|
152
199
|
manifestVersion: MANIFEST_VERSION,
|
|
@@ -213,6 +260,8 @@ async function runManifest() {
|
|
|
213
260
|
console.warn(`\u26A0 ${w} This will be an error in the next major version.`);
|
|
214
261
|
}
|
|
215
262
|
const manifest = buildManifest(mod.NODES, credentials, templates);
|
|
263
|
+
const nameProblems = nodeManifestProblems(manifest.nodes);
|
|
264
|
+
if (nameProblems.length > 0) fail(nameProblems.join("\n"));
|
|
216
265
|
const outDir = (0, import_node_path2.resolve)(projectRoot, "dist");
|
|
217
266
|
const outFile = (0, import_node_path2.resolve)(outDir, "manifest.json");
|
|
218
267
|
fs2.mkdirSync(outDir, { recursive: true });
|
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
buildManifest,
|
|
4
|
+
nodeManifestProblems,
|
|
4
5
|
outputWarnings,
|
|
5
6
|
packageIconProblem,
|
|
6
7
|
parsePackageMeta
|
|
7
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-CA6JVLLF.js";
|
|
8
9
|
|
|
9
10
|
// src/cli.ts
|
|
10
11
|
import * as fs2 from "fs";
|
|
@@ -112,6 +113,8 @@ async function runManifest() {
|
|
|
112
113
|
console.warn(`\u26A0 ${w} This will be an error in the next major version.`);
|
|
113
114
|
}
|
|
114
115
|
const manifest = buildManifest(mod.NODES, credentials, templates);
|
|
116
|
+
const nameProblems = nodeManifestProblems(manifest.nodes);
|
|
117
|
+
if (nameProblems.length > 0) fail(nameProblems.join("\n"));
|
|
115
118
|
const outDir = resolve2(projectRoot, "dist");
|
|
116
119
|
const outFile = resolve2(outDir, "manifest.json");
|
|
117
120
|
fs2.mkdirSync(outDir, { recursive: true });
|
package/dist/errors.d.ts
CHANGED
|
@@ -3,3 +3,24 @@ export declare class NodeError extends Error {
|
|
|
3
3
|
readonly meta?: Record<string, unknown>;
|
|
4
4
|
constructor(code: string, message: string, meta?: Record<string, unknown>);
|
|
5
5
|
}
|
|
6
|
+
/**
|
|
7
|
+
* The two codes a step fails with when the VPN route it was configured with
|
|
8
|
+
* cannot be used (PO-825). Both are failures of the world around the run, not of
|
|
9
|
+
* the data a record carried, and neither is retried by the platform.
|
|
10
|
+
*
|
|
11
|
+
* - `VPN_UNAVAILABLE` — the connection exists but is not `active`;
|
|
12
|
+
* message `VPN connection <name> is <status>`.
|
|
13
|
+
* - `VPN_ROUTE_MISSING` — the connection or the route no longer exists;
|
|
14
|
+
* message `VPN route <connection>/<route> no longer exists`.
|
|
15
|
+
*/
|
|
16
|
+
export declare const VPN_UNAVAILABLE: 'VPN_UNAVAILABLE';
|
|
17
|
+
export declare const VPN_ROUTE_MISSING: 'VPN_ROUTE_MISSING';
|
|
18
|
+
/** Every VPN failure code, for a reader that has to recognise one. */
|
|
19
|
+
export declare const VPN_ERROR_CODES: readonly ["VPN_UNAVAILABLE", "VPN_ROUTE_MISSING"];
|
|
20
|
+
/** One of {@link VPN_ERROR_CODES}. */
|
|
21
|
+
export type VpnErrorCode = (typeof VPN_ERROR_CODES)[number];
|
|
22
|
+
/**
|
|
23
|
+
* Whether `code` is a VPN failure code, narrowing it. `VPN_ERROR_CODES.includes`
|
|
24
|
+
* does not accept a plain `string`, which is what `NodeError.code` is.
|
|
25
|
+
*/
|
|
26
|
+
export declare function isVpnErrorCode(code: string): code is VpnErrorCode;
|