@jini-ai/platform 0.1.2 → 0.5.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/CHANGELOG.md +146 -0
- package/README.md +207 -0
- package/dist/asset-cache.d.ts +0 -22
- package/dist/asset-cache.js +3 -138
- package/dist/download.js +2 -2
- package/dist/fetch-with-timeout.d.ts +105 -0
- package/dist/fetch-with-timeout.js +121 -0
- package/dist/fs/atomic-write.d.ts +103 -0
- package/dist/fs/atomic-write.js +201 -0
- package/dist/fs/durable-json.d.ts +89 -0
- package/dist/fs/durable-json.js +166 -0
- package/dist/fs/env-text.d.ts +11 -0
- package/dist/fs/env-text.js +22 -0
- package/dist/fs/file-lock.d.ts +126 -0
- package/dist/fs/file-lock.js +306 -0
- package/dist/fs/guarded-reader.d.ts +60 -0
- package/dist/fs/guarded-reader.js +310 -0
- package/dist/fs/index.d.ts +8 -0
- package/dist/fs/index.js +9 -0
- package/dist/fs/strict-containment.d.ts +5 -0
- package/dist/fs/strict-containment.js +13 -0
- package/dist/fs.js +2 -3
- package/dist/http/guarded/client.d.ts +13 -0
- package/dist/http/guarded/client.js +240 -0
- package/dist/http/guarded/errors.d.ts +21 -0
- package/dist/http/guarded/errors.js +20 -0
- package/dist/http/guarded/fetch-adapter.d.ts +23 -0
- package/dist/http/guarded/fetch-adapter.js +30 -0
- package/dist/http/guarded/index.d.ts +12 -0
- package/dist/http/guarded/index.js +7 -0
- package/dist/http/guarded/node-ports.d.ts +8 -0
- package/dist/http/guarded/node-ports.js +11 -0
- package/dist/http/guarded/ports.d.ts +51 -0
- package/dist/http/guarded/ports.js +2 -0
- package/dist/http/guarded/transport.fetch.d.ts +11 -0
- package/dist/http/guarded/transport.fetch.js +109 -0
- package/dist/http/guarded/types.d.ts +8 -0
- package/dist/http/guarded/types.js +2 -0
- package/dist/http.js +10 -1
- package/dist/index.d.ts +65 -1
- package/dist/index.js +64 -1
- package/dist/mail/adapter-module.d.ts +20 -0
- package/dist/mail/adapter-module.js +2 -0
- package/dist/mail/delivery-availability.d.ts +7 -0
- package/dist/mail/delivery-availability.js +6 -0
- package/dist/mail/index.d.ts +7 -0
- package/dist/mail/index.js +3 -0
- package/dist/mail/ports.d.ts +43 -0
- package/dist/mail/ports.js +2 -0
- package/dist/mail/purpose-scoped-mailer.d.ts +18 -0
- package/dist/mail/purpose-scoped-mailer.js +50 -0
- package/dist/mail/smtp.d.ts +79 -0
- package/dist/mail/smtp.js +102 -0
- package/dist/mail/types.d.ts +83 -0
- package/dist/mail/types.js +2 -0
- package/dist/messages.d.ts +22 -0
- package/dist/messages.js +15 -0
- package/dist/net/address.d.ts +38 -0
- package/dist/net/address.js +241 -0
- package/dist/net/index.d.ts +2 -0
- package/dist/net/index.js +2 -0
- package/dist/process.d.ts +2 -1
- package/dist/process.js +2 -1
- package/dist/proxy-env.js +1 -1
- package/dist/resource-paths.js +1 -1
- package/dist/sandbox-env.d.ts +1 -1
- package/dist/sandbox-env.js +1 -1
- package/dist/secrets/aad.d.ts +7 -0
- package/dist/secrets/aad.js +5 -0
- package/dist/secrets/credential-sets/aad.d.ts +7 -0
- package/dist/secrets/credential-sets/aad.js +7 -0
- package/dist/secrets/credential-sets/index.d.ts +4 -0
- package/dist/secrets/credential-sets/index.js +3 -0
- package/dist/secrets/credential-sets/repo.memory.d.ts +36 -0
- package/dist/secrets/credential-sets/repo.memory.js +72 -0
- package/dist/secrets/credential-sets/types.d.ts +67 -0
- package/dist/secrets/credential-sets/types.js +6 -0
- package/dist/secrets/index.d.ts +9 -0
- package/dist/secrets/index.js +5 -0
- package/dist/secrets/keyring.env.d.ts +176 -0
- package/dist/secrets/keyring.env.js +271 -0
- package/dist/secrets/keyring.memory.d.ts +10 -0
- package/dist/secrets/keyring.memory.js +9 -0
- package/dist/secrets/ports.d.ts +33 -0
- package/dist/secrets/ports.js +2 -0
- package/dist/secrets/secret-sealer.aesgcm.d.ts +22 -0
- package/dist/secrets/secret-sealer.aesgcm.js +64 -0
- package/dist/secrets/testing.d.ts +4 -0
- package/dist/secrets/testing.js +4 -0
- package/dist/secrets/types.d.ts +10 -0
- package/dist/secrets/types.js +2 -0
- package/dist/shell.d.ts +10 -6
- package/dist/shell.js +10 -6
- package/dist/terminal.d.ts +2 -2
- package/dist/terminal.js +9 -5
- package/package.json +85 -4
- package/dist/asset-cache.d.ts.map +0 -1
- package/dist/asset-cache.js.map +0 -1
- package/dist/aws-sigv4.d.ts.map +0 -1
- package/dist/aws-sigv4.js.map +0 -1
- package/dist/blob-storage.d.ts.map +0 -1
- package/dist/blob-storage.js.map +0 -1
- package/dist/command.d.ts.map +0 -1
- package/dist/command.js.map +0 -1
- package/dist/download.d.ts.map +0 -1
- package/dist/download.js.map +0 -1
- package/dist/fs.d.ts.map +0 -1
- package/dist/fs.js.map +0 -1
- package/dist/home-expansion.d.ts.map +0 -1
- package/dist/home-expansion.js.map +0 -1
- package/dist/http.d.ts.map +0 -1
- package/dist/http.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/process.d.ts.map +0 -1
- package/dist/process.js.map +0 -1
- package/dist/proxy-env.d.ts.map +0 -1
- package/dist/proxy-env.js.map +0 -1
- package/dist/resource-paths.d.ts.map +0 -1
- package/dist/resource-paths.js.map +0 -1
- package/dist/sandbox-env.d.ts.map +0 -1
- package/dist/sandbox-env.js.map +0 -1
- package/dist/shell.d.ts.map +0 -1
- package/dist/shell.js.map +0 -1
- package/dist/terminal.d.ts.map +0 -1
- package/dist/terminal.js.map +0 -1
- package/dist/toolchain.d.ts.map +0 -1
- package/dist/toolchain.js.map +0 -1
package/dist/messages.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const defaultPlatformMessages = {
|
|
2
|
+
atomicModeInvalid: () => "atomic file mode must be permission bits between 0000 and 0777",
|
|
3
|
+
atomicTempPathInvalid: () => "atomic temp path must be a different same-directory file",
|
|
4
|
+
atomicTempNameInvalid: () => "atomic temp name must be one safe segment",
|
|
5
|
+
atomicSymlinkRefused: () => "atomic write refused a symbolic-link destination",
|
|
6
|
+
atomicPermissionsRefused: () => "atomic write refused non-owner-only permissions",
|
|
7
|
+
atomicJsonUnserializable: () => "atomic JSON data must be serializable",
|
|
8
|
+
fileLockIntervalsInvalid: () => "file lock intervals must be positive safe integers (timeout may be zero; stale may be Infinity)",
|
|
9
|
+
fileLockWriteNoProgress: () => "file lock ownership write made no progress",
|
|
10
|
+
egressRefused: () => "egress to the requested host was refused by the outbound network policy",
|
|
11
|
+
rootKeySealedWarning: ({ subject }) => `IMPORTANT: anything sealed while ${subject} was in place was sealed under key material derived from these same bytes, `,
|
|
12
|
+
fileLockTimeout: ({ waitedMs }) => `exclusive file lock timed out after ${Math.round(waitedMs)}ms`,
|
|
13
|
+
fileLockLost: () => "exclusive file lock lost ownership before the critical section finished",
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=messages.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** Expand an IPv6 literal (with `::` compression and/or an embedded dotted
|
|
2
|
+
* IPv4 tail) into its eight 16-bit groups, or null if it does not parse.
|
|
3
|
+
* Node's URL parser normalizes mapped literals to hex (`::ffff:127.0.0.1` →
|
|
4
|
+
* `::ffff:7f00:1`), so a string regex can't reliably spot the mapped range —
|
|
5
|
+
* we have to canonicalize.
|
|
6
|
+
*
|
|
7
|
+
* Exported (previously module-private) so its parse-failure guards are
|
|
8
|
+
* directly unit-testable: the IP classifier and guarded client gate
|
|
9
|
+
* calls behind `net.isIP(addr) === 6`, and Node's `isIP` already fully
|
|
10
|
+
* validates IPv6 syntax (including embedded-IPv4-tail octet ranges) before
|
|
11
|
+
* returning 6 — empirically re-verified this session by fuzzing ~10k
|
|
12
|
+
* `isIP`-accepted v6 literals through this function's guards with zero
|
|
13
|
+
* hits — so none of these guards are reachable through that path. They stay
|
|
14
|
+
* as real defense-in-depth (fail closed with `null` rather than trust a
|
|
15
|
+
* platform primitive never changing behavior), matching the asset cache's
|
|
16
|
+
* exported-for-direct-testing precedent (`readBodyCapped`'s abort parameter). */
|
|
17
|
+
export declare function expandIpv6({ address: addr }: {
|
|
18
|
+
address: string;
|
|
19
|
+
}): number[] | null;
|
|
20
|
+
/** True for any address an external CDN must never resolve to: loopback,
|
|
21
|
+
* link-local, private, CGNAT, ULA, multicast, or unspecified. An unparseable
|
|
22
|
+
* input is treated as unsafe. */
|
|
23
|
+
export declare function isPrivateAddress({ address: addr }: {
|
|
24
|
+
address: string;
|
|
25
|
+
}): boolean;
|
|
26
|
+
/** True for `localhost`, `::1`, `127.0.0.0/8`, and their IPv4-mapped-IPv6 forms. */
|
|
27
|
+
export declare function isLoopbackApiHost({ hostname }: {
|
|
28
|
+
hostname: string;
|
|
29
|
+
}): boolean;
|
|
30
|
+
/** True for RFC1918/link-local/CGNAT/multicast/unspecified/unique-local-IPv6 addresses — private network space a public caller should never be steered into. */
|
|
31
|
+
export declare function isBlockedExternalApiHostname({ hostname }: {
|
|
32
|
+
hostname: string;
|
|
33
|
+
}): boolean;
|
|
34
|
+
/** True when `hostname` is `localhost`, an IPv6 loopback literal, or a `127.0.0.0/8` IPv4 literal. */
|
|
35
|
+
export declare function isLoopbackHostname({ hostname }: {
|
|
36
|
+
readonly hostname: unknown;
|
|
37
|
+
}, _optional?: Record<string, never>): boolean;
|
|
38
|
+
//# sourceMappingURL=address.d.ts.map
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { isIP } from 'node:net';
|
|
2
|
+
// SSRF peer classification is distinct from core's browser-development LAN origin allow-list.
|
|
3
|
+
/** Expand an IPv6 literal (with `::` compression and/or an embedded dotted
|
|
4
|
+
* IPv4 tail) into its eight 16-bit groups, or null if it does not parse.
|
|
5
|
+
* Node's URL parser normalizes mapped literals to hex (`::ffff:127.0.0.1` →
|
|
6
|
+
* `::ffff:7f00:1`), so a string regex can't reliably spot the mapped range —
|
|
7
|
+
* we have to canonicalize.
|
|
8
|
+
*
|
|
9
|
+
* Exported (previously module-private) so its parse-failure guards are
|
|
10
|
+
* directly unit-testable: the IP classifier and guarded client gate
|
|
11
|
+
* calls behind `net.isIP(addr) === 6`, and Node's `isIP` already fully
|
|
12
|
+
* validates IPv6 syntax (including embedded-IPv4-tail octet ranges) before
|
|
13
|
+
* returning 6 — empirically re-verified this session by fuzzing ~10k
|
|
14
|
+
* `isIP`-accepted v6 literals through this function's guards with zero
|
|
15
|
+
* hits — so none of these guards are reachable through that path. They stay
|
|
16
|
+
* as real defense-in-depth (fail closed with `null` rather than trust a
|
|
17
|
+
* platform primitive never changing behavior), matching the asset cache's
|
|
18
|
+
* exported-for-direct-testing precedent (`readBodyCapped`'s abort parameter). */
|
|
19
|
+
export function expandIpv6({ address: addr }) {
|
|
20
|
+
// `String#split` always returns a non-empty array, so index 0 is never
|
|
21
|
+
// `undefined` — the `!` narrows a `noUncheckedIndexedAccess` false
|
|
22
|
+
// positive, not a real fallback path.
|
|
23
|
+
let s = addr.toLowerCase().split('%')[0];
|
|
24
|
+
if (!s.includes(':'))
|
|
25
|
+
return null;
|
|
26
|
+
// Fold a trailing dotted IPv4 (`…:1.2.3.4`) into two hex groups.
|
|
27
|
+
const v4 = /^(.*:)(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/.exec(s);
|
|
28
|
+
if (v4) {
|
|
29
|
+
// `v4[2]` is the regex's second (mandatory, non-optional) capture group
|
|
30
|
+
// of a successful match — always defined.
|
|
31
|
+
const o = v4[2].split('.').map((n) => Number(n));
|
|
32
|
+
if (o.length !== 4 || o.some((n) => !Number.isInteger(n) || n < 0 || n > 255))
|
|
33
|
+
return null;
|
|
34
|
+
// `o` is now a validated 4-element array — indices 0-3 always defined.
|
|
35
|
+
const hi = (((o[0] << 8) | o[1])).toString(16);
|
|
36
|
+
const lo = (((o[2] << 8) | o[3])).toString(16);
|
|
37
|
+
s = `${v4[1]}${hi}:${lo}`;
|
|
38
|
+
}
|
|
39
|
+
const halves = s.split('::');
|
|
40
|
+
if (halves.length > 2)
|
|
41
|
+
return null;
|
|
42
|
+
const parse = (p) => p === '' ? [] : p.split(':').map((g) => (/^[0-9a-f]{1,4}$/.test(g) ? parseInt(g, 16) : Number.NaN));
|
|
43
|
+
// `halves` always has at least one element (split guarantee).
|
|
44
|
+
const left = parse(halves[0]);
|
|
45
|
+
let groups;
|
|
46
|
+
if (halves.length === 1) {
|
|
47
|
+
groups = left;
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
// `halves.length === 2` here (the `> 2` case already returned above).
|
|
51
|
+
const right = parse(halves[1]);
|
|
52
|
+
const fill = 8 - left.length - right.length;
|
|
53
|
+
if (fill < 1)
|
|
54
|
+
return null;
|
|
55
|
+
groups = [...left, ...new Array(fill).fill(0), ...right];
|
|
56
|
+
}
|
|
57
|
+
if (groups.length !== 8 || groups.some((g) => Number.isNaN(g) || g < 0 || g > 0xffff))
|
|
58
|
+
return null;
|
|
59
|
+
return groups;
|
|
60
|
+
}
|
|
61
|
+
/** True for any address an external CDN must never resolve to: loopback,
|
|
62
|
+
* link-local, private, CGNAT, ULA, multicast, or unspecified. An unparseable
|
|
63
|
+
* input is treated as unsafe. */
|
|
64
|
+
export function isPrivateAddress({ address: addr }) {
|
|
65
|
+
const fam = isIP(addr);
|
|
66
|
+
if (fam === 4) {
|
|
67
|
+
// `net.isIP(addr) === 4` already guarantees `addr` is a syntactically
|
|
68
|
+
// valid dotted-quad with four 0-255 decimal octets (no leading-zero
|
|
69
|
+
// octal ambiguity, no out-of-range component) — empirically re-verified
|
|
70
|
+
// this session by fuzzing 500k+ candidate strings through `isIP` and
|
|
71
|
+
// this same re-derivation with zero divergences. `o` is therefore always
|
|
72
|
+
// a validated 4-element array.
|
|
73
|
+
const o = addr.split('.').map((n) => Number(n));
|
|
74
|
+
const a = o[0];
|
|
75
|
+
const b = o[1];
|
|
76
|
+
if (a === 0)
|
|
77
|
+
return true; // "this" network / unspecified
|
|
78
|
+
if (a === 10)
|
|
79
|
+
return true; // private
|
|
80
|
+
if (a === 127)
|
|
81
|
+
return true; // loopback
|
|
82
|
+
if (a === 169 && b === 254)
|
|
83
|
+
return true; // link-local
|
|
84
|
+
if (a === 172 && b >= 16 && b <= 31)
|
|
85
|
+
return true; // private
|
|
86
|
+
if (a === 192 && b === 168)
|
|
87
|
+
return true; // private
|
|
88
|
+
if (a === 100 && b >= 64 && b <= 127)
|
|
89
|
+
return true; // CGNAT
|
|
90
|
+
if (a >= 224)
|
|
91
|
+
return true; // multicast + reserved
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
if (fam === 6) {
|
|
95
|
+
const groups = expandIpv6({ address: addr });
|
|
96
|
+
// `net.isIP(addr) === 6` already guarantees `addr` is syntactically valid
|
|
97
|
+
// IPv6 (Node validates this before classifying the family), and
|
|
98
|
+
// `expandIpv6` implements the same canonicalization Node's own parser
|
|
99
|
+
// does, so it never returns `null` for an `isIP`-accepted literal —
|
|
100
|
+
// empirically re-verified this session by fuzzing ~10k `isIP`-accepted
|
|
101
|
+
// v6 literals through `expandIpv6` with zero `null` results (see
|
|
102
|
+
// `expandIpv6`'s doc comment and `archived provenance ledger`'s 2026-07-22 entry).
|
|
103
|
+
// Kept as a real fail-closed guard (defense-in-depth against a future
|
|
104
|
+
// change to either parser) rather than asserted away, so it stays
|
|
105
|
+
// provably-unreachable-but-intentional instead of a forced test.
|
|
106
|
+
if (!groups)
|
|
107
|
+
return true; // can't canonicalize a "valid" v6 → refuse
|
|
108
|
+
// IPv4-mapped (`::ffff:0:0/96`) and the deprecated IPv4-compatible
|
|
109
|
+
// (`::0:0/96`, excluding :: / ::1) carry an embedded IPv4 — classify it by
|
|
110
|
+
// its real v4 range so `::ffff:7f00:1` (== 127.0.0.1) is caught.
|
|
111
|
+
const topZero = groups.slice(0, 5).every((g) => g === 0);
|
|
112
|
+
if (topZero && (groups[5] === 0xffff || groups[5] === 0)) {
|
|
113
|
+
// `groups` is a validated 8-element array once non-null — indices 6-7
|
|
114
|
+
// always defined.
|
|
115
|
+
const a = groups[6] >> 8;
|
|
116
|
+
const b = groups[6] & 0xff;
|
|
117
|
+
const c = groups[7] >> 8;
|
|
118
|
+
const d = groups[7] & 0xff;
|
|
119
|
+
if (groups[5] === 0xffff || a !== 0 || b !== 0) {
|
|
120
|
+
return isPrivateAddress({ address: `${a}.${b}.${c}.${d}` });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (groups.every((g) => g === 0))
|
|
124
|
+
return true; // :: (unspecified)
|
|
125
|
+
if (groups.slice(0, 7).every((g) => g === 0) && groups[7] === 1)
|
|
126
|
+
return true; // ::1 loopback
|
|
127
|
+
const first = groups[0];
|
|
128
|
+
if ((first & 0xffc0) === 0xfe80)
|
|
129
|
+
return true; // link-local fe80::/10 (fe80–febf)
|
|
130
|
+
if ((first & 0xfe00) === 0xfc00)
|
|
131
|
+
return true; // unique-local fc00::/7
|
|
132
|
+
if ((first & 0xff00) === 0xff00)
|
|
133
|
+
return true; // multicast ff00::/8
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
return true; // not a literal IP — caller must resolve before trusting it
|
|
137
|
+
}
|
|
138
|
+
function normalizeBracketedIpv6(hostname) {
|
|
139
|
+
const stripped = hostname.startsWith('[') && hostname.endsWith(']')
|
|
140
|
+
? hostname.slice(1, -1)
|
|
141
|
+
: hostname;
|
|
142
|
+
// FQDN trailing-dot form (RFC 1034) resolves identically to the dotless
|
|
143
|
+
// form, so `localhost.` must normalize to `localhost` before the equality
|
|
144
|
+
// check below — and `0.0.0.0.`, `10.0.0.1.`, etc. must normalize before
|
|
145
|
+
// isBlockedIpv4 parses them. Strips one or more trailing dots.
|
|
146
|
+
return stripped.toLowerCase().replace(/\.+$/, '');
|
|
147
|
+
}
|
|
148
|
+
function parseIpv4(hostname) {
|
|
149
|
+
const parts = hostname.split('.');
|
|
150
|
+
if (parts.length !== 4)
|
|
151
|
+
return null;
|
|
152
|
+
const parsed = parts.map((part) => {
|
|
153
|
+
if (!/^\d{1,3}$/.test(part))
|
|
154
|
+
return null;
|
|
155
|
+
const value = Number(part);
|
|
156
|
+
return value >= 0 && value <= 255 ? value : null;
|
|
157
|
+
});
|
|
158
|
+
if (parsed.some((part) => part === null))
|
|
159
|
+
return null;
|
|
160
|
+
return [parsed[0], parsed[1], parsed[2], parsed[3]];
|
|
161
|
+
}
|
|
162
|
+
function isLoopbackIpv4(hostname) {
|
|
163
|
+
const parts = parseIpv4(hostname);
|
|
164
|
+
return Boolean(parts && parts[0] === 127);
|
|
165
|
+
}
|
|
166
|
+
function isBlockedIpv4(hostname) {
|
|
167
|
+
const parts = parseIpv4(hostname);
|
|
168
|
+
if (!parts)
|
|
169
|
+
return false;
|
|
170
|
+
const [a, b] = parts;
|
|
171
|
+
return (a === 0 ||
|
|
172
|
+
(a === 100 && b >= 64 && b <= 127) ||
|
|
173
|
+
(a === 169 && b === 254) ||
|
|
174
|
+
a === 10 ||
|
|
175
|
+
(a === 192 && b === 168) ||
|
|
176
|
+
(a === 172 && b >= 16 && b <= 31) ||
|
|
177
|
+
a >= 224);
|
|
178
|
+
}
|
|
179
|
+
function ipv4MappedToDotted(hostname) {
|
|
180
|
+
const host = normalizeBracketedIpv6(hostname);
|
|
181
|
+
const mapped = /^::ffff:(.+)$/i.exec(host)?.[1];
|
|
182
|
+
if (!mapped)
|
|
183
|
+
return null;
|
|
184
|
+
if (parseIpv4(mapped.toLowerCase()))
|
|
185
|
+
return mapped.toLowerCase();
|
|
186
|
+
const hexParts = mapped.split(':');
|
|
187
|
+
if (hexParts.length !== 2 ||
|
|
188
|
+
!hexParts.every((part) => /^[0-9a-f]{1,4}$/i.test(part))) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
// Non-null assertions, not a runtime guard: the length/regex checks above
|
|
192
|
+
// already guarantee exactly two non-empty hex segments here.
|
|
193
|
+
const hi = hexParts[0];
|
|
194
|
+
const lo = hexParts[1];
|
|
195
|
+
const value = (Number.parseInt(hi, 16) << 16) | Number.parseInt(lo, 16);
|
|
196
|
+
return [
|
|
197
|
+
(value >>> 24) & 255,
|
|
198
|
+
(value >>> 16) & 255,
|
|
199
|
+
(value >>> 8) & 255,
|
|
200
|
+
value & 255,
|
|
201
|
+
].join('.');
|
|
202
|
+
}
|
|
203
|
+
/** True for `localhost`, `::1`, `127.0.0.0/8`, and their IPv4-mapped-IPv6 forms. */
|
|
204
|
+
export function isLoopbackApiHost({ hostname }) {
|
|
205
|
+
const host = normalizeBracketedIpv6(hostname);
|
|
206
|
+
if (host === 'localhost' || host === '::1')
|
|
207
|
+
return true;
|
|
208
|
+
if (isLoopbackIpv4(host))
|
|
209
|
+
return true;
|
|
210
|
+
const mapped = ipv4MappedToDotted(host);
|
|
211
|
+
return Boolean(mapped && isLoopbackIpv4(mapped));
|
|
212
|
+
}
|
|
213
|
+
/** True for RFC1918/link-local/CGNAT/multicast/unspecified/unique-local-IPv6 addresses — private network space a public caller should never be steered into. */
|
|
214
|
+
export function isBlockedExternalApiHostname({ hostname }) {
|
|
215
|
+
const host = normalizeBracketedIpv6(hostname);
|
|
216
|
+
if (host === '::')
|
|
217
|
+
return true;
|
|
218
|
+
if (isBlockedIpv4(host))
|
|
219
|
+
return true;
|
|
220
|
+
if (/^f[cd][0-9a-f]{2}:/i.test(host))
|
|
221
|
+
return true;
|
|
222
|
+
if (/^fe[89ab][0-9a-f]:/i.test(host))
|
|
223
|
+
return true;
|
|
224
|
+
const mapped = ipv4MappedToDotted(host);
|
|
225
|
+
return Boolean(mapped && isBlockedIpv4(mapped));
|
|
226
|
+
}
|
|
227
|
+
/** True when `hostname` is `localhost`, an IPv6 loopback literal, or a `127.0.0.0/8` IPv4 literal. */
|
|
228
|
+
export function isLoopbackHostname({ hostname }, _optional = {}) {
|
|
229
|
+
const normalized = String(hostname || '')
|
|
230
|
+
.toLowerCase()
|
|
231
|
+
.replace(/^\[|\]$/g, '')
|
|
232
|
+
.replace(/\.$/, '');
|
|
233
|
+
if (normalized === 'localhost')
|
|
234
|
+
return true;
|
|
235
|
+
if (normalized === '::1' || normalized === '0:0:0:0:0:0:0:1')
|
|
236
|
+
return true;
|
|
237
|
+
if (isIP(normalized) === 4)
|
|
238
|
+
return normalized === '127.0.0.1' || normalized.startsWith('127.');
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
//# sourceMappingURL=address.js.map
|
package/dist/process.d.ts
CHANGED
|
@@ -141,7 +141,8 @@ export declare function waitForProcessExit(pid: number | null | undefined, timeo
|
|
|
141
141
|
export declare function listProcessSnapshots(): Promise<ProcessSnapshot[]>;
|
|
142
142
|
/**
|
|
143
143
|
* Collect the transitive set of descendant PIDs (including the roots) from a
|
|
144
|
-
* process snapshot list, returned sorted descending
|
|
144
|
+
* process snapshot list, returned sorted by numeric PID descending for deterministic ordering.
|
|
145
|
+
* PID reuse and wraparound mean this does not guarantee children precede parents.
|
|
145
146
|
*
|
|
146
147
|
* @param processes - The full process snapshot list to walk.
|
|
147
148
|
* @param rootPids - The root PIDs whose subtrees to collect (non-numbers ignored).
|
package/dist/process.js
CHANGED
|
@@ -285,7 +285,8 @@ export async function listProcessSnapshots() {
|
|
|
285
285
|
}
|
|
286
286
|
/**
|
|
287
287
|
* Collect the transitive set of descendant PIDs (including the roots) from a
|
|
288
|
-
* process snapshot list, returned sorted descending
|
|
288
|
+
* process snapshot list, returned sorted by numeric PID descending for deterministic ordering.
|
|
289
|
+
* PID reuse and wraparound mean this does not guarantee children precede parents.
|
|
289
290
|
*
|
|
290
291
|
* @param processes - The full process snapshot list to walk.
|
|
291
292
|
* @param rootPids - The root PIDs whose subtrees to collect (non-numbers ignored).
|
package/dist/proxy-env.js
CHANGED
|
@@ -365,7 +365,7 @@ export function resolveSystemProxyEnv(options = {}) {
|
|
|
365
365
|
// `RegExp` construction from untrusted input. Empirically re-verified
|
|
366
366
|
// this session: 200k fuzzed `stdout`/registry-value strings (control
|
|
367
367
|
// characters, huge lengths, null bytes) through both parse functions,
|
|
368
|
-
// zero throws — see
|
|
368
|
+
// zero throws — see archived provenance ledger's 2026-07-22 entry.
|
|
369
369
|
return {};
|
|
370
370
|
}
|
|
371
371
|
return {};
|
package/dist/resource-paths.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* Generalized from an upstream flat daemon module: every hardcoded env-var
|
|
11
11
|
* name, the host CLI's own package specifier, the default data-directory
|
|
12
12
|
* name, and the Windows packaged-resources marker segment are now fields on
|
|
13
|
-
* {@link ResourcePathsConfig} — see `
|
|
13
|
+
* {@link ResourcePathsConfig} — see `archived provenance ledger` for the exact mapping.
|
|
14
14
|
*/
|
|
15
15
|
import fs from 'node:fs';
|
|
16
16
|
import { createRequire } from 'node:module';
|
package/dist/sandbox-env.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Names the environment variables and on-disk naming this port reads/writes.
|
|
3
3
|
* A consumer supplies its own env-var names and config-directory name — see
|
|
4
|
-
* `
|
|
4
|
+
* `archived provenance ledger` for the values a real adapter (e.g. the OD adapter) uses.
|
|
5
5
|
*/
|
|
6
6
|
export interface SandboxEnvConfig {
|
|
7
7
|
/** Env var gating whether sandboxing is on (truthy/falsy string). */
|
package/dist/sandbox-env.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* Generalized from an upstream flat daemon module: the origin hardcoded its
|
|
11
11
|
* host product's env-var names and a product-branded config sub-directory.
|
|
12
12
|
* Every one of those is now a field on {@link SandboxEnvConfig} so a
|
|
13
|
-
* consumer isn't branded — see `
|
|
13
|
+
* consumer isn't branded — see `archived provenance ledger` for the exact mapping.
|
|
14
14
|
*/
|
|
15
15
|
import fs from 'node:fs';
|
|
16
16
|
import path from 'node:path';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Formats an existing colon-separated AAD lineage without escaping or changing its bytes. */
|
|
2
|
+
export declare function formatAad({ kind, version, parts }: {
|
|
3
|
+
kind: string;
|
|
4
|
+
version: string;
|
|
5
|
+
parts: readonly string[];
|
|
6
|
+
}): string;
|
|
7
|
+
//# sourceMappingURL=aad.d.ts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { formatAad } from "../aad.js";
|
|
2
|
+
const AAD_VERSION = "v1";
|
|
3
|
+
/** Stable v1 AAD binding workspace, vendor, and credential identity. */
|
|
4
|
+
export function buildVendorCredentialAad(input) {
|
|
5
|
+
return formatAad({ kind: "vendor-credential-set", version: AAD_VERSION, parts: [input.workspaceId, input.vendorId, input.id] });
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=aad.js.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type { VendorCredentialSetRecord, VendorCredentialSetSummary, VendorCredentialSetRepoPort } from "./types.js";
|
|
2
|
+
export { buildVendorCredentialAad } from "./aad.js";
|
|
3
|
+
export { InMemoryVendorCredentialSetRepo } from "./repo.memory.js";
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { VendorCredentialSetRecord, VendorCredentialSetRepoPort } from "./types.js";
|
|
2
|
+
/** In-memory repository preserving label uniqueness and group default promotion. */
|
|
3
|
+
export declare class InMemoryVendorCredentialSetRepo implements VendorCredentialSetRepoPort {
|
|
4
|
+
constructor(_required: Record<string, never>);
|
|
5
|
+
private readonly rows;
|
|
6
|
+
private static rowKey;
|
|
7
|
+
private assertLabelAvailable;
|
|
8
|
+
private clearOtherDefaults;
|
|
9
|
+
insert(record: VendorCredentialSetRecord): Promise<void>;
|
|
10
|
+
update(record: VendorCredentialSetRecord): Promise<void>;
|
|
11
|
+
findById(input: {
|
|
12
|
+
workspaceId: string;
|
|
13
|
+
id: string;
|
|
14
|
+
}): Promise<VendorCredentialSetRecord | null>;
|
|
15
|
+
findDefaultByVendor(input: {
|
|
16
|
+
workspaceId: string;
|
|
17
|
+
vendorId: string;
|
|
18
|
+
}): Promise<VendorCredentialSetRecord | null>;
|
|
19
|
+
listByVendor(input: {
|
|
20
|
+
workspaceId: string;
|
|
21
|
+
vendorId: string;
|
|
22
|
+
}): Promise<VendorCredentialSetRecord[]>;
|
|
23
|
+
listByWorkspace(input: {
|
|
24
|
+
workspaceId: string;
|
|
25
|
+
}): Promise<VendorCredentialSetRecord[]>;
|
|
26
|
+
delete(input: {
|
|
27
|
+
workspaceId: string;
|
|
28
|
+
id: string;
|
|
29
|
+
}): Promise<void>;
|
|
30
|
+
updateAccountLabel(input: {
|
|
31
|
+
workspaceId: string;
|
|
32
|
+
id: string;
|
|
33
|
+
accountLabel: string;
|
|
34
|
+
}): Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=repo.memory.d.ts.map
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/** In-memory repository preserving label uniqueness and group default promotion. */
|
|
2
|
+
export class InMemoryVendorCredentialSetRepo {
|
|
3
|
+
constructor(_required) { }
|
|
4
|
+
rows = new Map();
|
|
5
|
+
static rowKey(workspaceId, id) {
|
|
6
|
+
return `${workspaceId}::${id}`;
|
|
7
|
+
}
|
|
8
|
+
assertLabelAvailable(workspaceId, vendorId, label, excludingId) {
|
|
9
|
+
for (const row of this.rows.values()) {
|
|
10
|
+
if (row.workspaceId !== workspaceId || row.vendorId !== vendorId || row.label !== label)
|
|
11
|
+
continue;
|
|
12
|
+
if (excludingId !== undefined && row.id === excludingId)
|
|
13
|
+
continue;
|
|
14
|
+
throw new Error(`UNIQUE constraint failed: vendor_credential_sets.workspace_id, vendor_credential_sets.vendor_id, vendor_credential_sets.label`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
clearOtherDefaults(workspaceId, vendorId, keepId) {
|
|
18
|
+
for (const [key, row] of this.rows) {
|
|
19
|
+
if (row.workspaceId !== workspaceId || row.vendorId !== vendorId || row.id === keepId || !row.isDefault)
|
|
20
|
+
continue;
|
|
21
|
+
this.rows.set(key, { ...row, isDefault: false });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async insert(record) {
|
|
25
|
+
this.assertLabelAvailable(record.workspaceId, record.vendorId, record.label);
|
|
26
|
+
this.rows.set(InMemoryVendorCredentialSetRepo.rowKey(record.workspaceId, record.id), { ...record });
|
|
27
|
+
if (record.isDefault)
|
|
28
|
+
this.clearOtherDefaults(record.workspaceId, record.vendorId, record.id);
|
|
29
|
+
}
|
|
30
|
+
async update(record) {
|
|
31
|
+
this.assertLabelAvailable(record.workspaceId, record.vendorId, record.label, record.id);
|
|
32
|
+
this.rows.set(InMemoryVendorCredentialSetRepo.rowKey(record.workspaceId, record.id), { ...record });
|
|
33
|
+
if (record.isDefault)
|
|
34
|
+
this.clearOtherDefaults(record.workspaceId, record.vendorId, record.id);
|
|
35
|
+
}
|
|
36
|
+
async findById(input) {
|
|
37
|
+
return this.rows.get(InMemoryVendorCredentialSetRepo.rowKey(input.workspaceId, input.id)) ?? null;
|
|
38
|
+
}
|
|
39
|
+
async findDefaultByVendor(input) {
|
|
40
|
+
for (const row of this.rows.values()) {
|
|
41
|
+
if (row.workspaceId === input.workspaceId && row.vendorId === input.vendorId && row.isDefault)
|
|
42
|
+
return row;
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
async listByVendor(input) {
|
|
47
|
+
return [...this.rows.values()].filter((row) => row.workspaceId === input.workspaceId && row.vendorId === input.vendorId);
|
|
48
|
+
}
|
|
49
|
+
async listByWorkspace(input) {
|
|
50
|
+
return [...this.rows.values()].filter((row) => row.workspaceId === input.workspaceId);
|
|
51
|
+
}
|
|
52
|
+
async delete(input) {
|
|
53
|
+
const key = InMemoryVendorCredentialSetRepo.rowKey(input.workspaceId, input.id);
|
|
54
|
+
const removed = this.rows.get(key);
|
|
55
|
+
this.rows.delete(key);
|
|
56
|
+
if (!removed?.isDefault)
|
|
57
|
+
return;
|
|
58
|
+
const remaining = [...this.rows.values()].filter((row) => row.workspaceId === removed.workspaceId && row.vendorId === removed.vendorId);
|
|
59
|
+
if (remaining.length === 0)
|
|
60
|
+
return;
|
|
61
|
+
const promoted = remaining.reduce((latest, row) => (row.updatedAt > latest.updatedAt ? row : latest));
|
|
62
|
+
this.rows.set(InMemoryVendorCredentialSetRepo.rowKey(promoted.workspaceId, promoted.id), { ...promoted, isDefault: true });
|
|
63
|
+
}
|
|
64
|
+
async updateAccountLabel(input) {
|
|
65
|
+
const key = InMemoryVendorCredentialSetRepo.rowKey(input.workspaceId, input.id);
|
|
66
|
+
const existing = this.rows.get(key);
|
|
67
|
+
if (!existing)
|
|
68
|
+
return;
|
|
69
|
+
this.rows.set(key, { ...existing, accountLabel: input.accountLabel });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=repo.memory.js.map
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/** Host-neutral credential records and workspace-scoped repository contracts.
|
|
2
|
+
* insert/update clear other defaults in the same vendor group; deleting a default promotes
|
|
3
|
+
* the most recently updated remaining row. Summaries never include sealed payloads.
|
|
4
|
+
*/
|
|
5
|
+
import type { SealedSecret } from "../types.js";
|
|
6
|
+
export interface VendorCredentialSetRecord {
|
|
7
|
+
readonly workspaceId: string;
|
|
8
|
+
readonly id: string;
|
|
9
|
+
/** Open string so plugin hosts can declare vendors beyond the built-in validators. */
|
|
10
|
+
readonly vendorId: string;
|
|
11
|
+
readonly label: string;
|
|
12
|
+
readonly sealed: SealedSecret;
|
|
13
|
+
/** Last four secret characters for recognition, stored in clear; a suffix alone is not the token. */
|
|
14
|
+
readonly tokenTail: string;
|
|
15
|
+
/** At most one default per (workspaceId, vendorId), maintained by the store/repository write path. */
|
|
16
|
+
readonly isDefault: boolean;
|
|
17
|
+
/** Only a verified public login belongs here; null until verification populates it. */
|
|
18
|
+
readonly accountLabel: string | null;
|
|
19
|
+
readonly createdAt: string;
|
|
20
|
+
readonly updatedAt: string;
|
|
21
|
+
}
|
|
22
|
+
export interface VendorCredentialSetSummary {
|
|
23
|
+
readonly id: string;
|
|
24
|
+
readonly vendorId: string;
|
|
25
|
+
readonly label: string;
|
|
26
|
+
readonly configured: true;
|
|
27
|
+
readonly isDefault: boolean;
|
|
28
|
+
readonly tokenTail: string;
|
|
29
|
+
readonly accountLabel: string | null;
|
|
30
|
+
readonly createdAt: string;
|
|
31
|
+
readonly updatedAt: string;
|
|
32
|
+
}
|
|
33
|
+
export interface VendorCredentialSetRepoPort {
|
|
34
|
+
insert(record: VendorCredentialSetRecord): Promise<void>;
|
|
35
|
+
/** Full-row replacement supports rename and secret rotation; label uniqueness can still reject it. */
|
|
36
|
+
update(record: VendorCredentialSetRecord): Promise<void>;
|
|
37
|
+
findById(input: {
|
|
38
|
+
workspaceId: string;
|
|
39
|
+
id: string;
|
|
40
|
+
}): Promise<VendorCredentialSetRecord | null>;
|
|
41
|
+
findDefaultByVendor(input: {
|
|
42
|
+
workspaceId: string;
|
|
43
|
+
vendorId: string;
|
|
44
|
+
}): Promise<VendorCredentialSetRecord | null>;
|
|
45
|
+
/** Lets the store recognize a group's first row and choose a promotion candidate before deletion. */
|
|
46
|
+
listByVendor(input: {
|
|
47
|
+
workspaceId: string;
|
|
48
|
+
vendorId: string;
|
|
49
|
+
}): Promise<VendorCredentialSetRecord[]>;
|
|
50
|
+
/** Intentionally unpaginated for a human-managed connection list, rather than a bulk credential inventory. */
|
|
51
|
+
listByWorkspace(input: {
|
|
52
|
+
workspaceId: string;
|
|
53
|
+
}): Promise<VendorCredentialSetRecord[]>;
|
|
54
|
+
/** Missing (workspaceId, id) is a no-op so repeated deletes remain idempotent. */
|
|
55
|
+
delete(input: {
|
|
56
|
+
workspaceId: string;
|
|
57
|
+
id: string;
|
|
58
|
+
}): Promise<void>;
|
|
59
|
+
/** Target only accountLabel: verification must not rewrite sealed/default/tail/updatedAt state.
|
|
60
|
+
* A missing row is a no-op, as with delete. */
|
|
61
|
+
updateAccountLabel(input: {
|
|
62
|
+
workspaceId: string;
|
|
63
|
+
id: string;
|
|
64
|
+
accountLabel: string;
|
|
65
|
+
}): Promise<void>;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Host-neutral credential records and workspace-scoped repository contracts.
|
|
2
|
+
* insert/update clear other defaults in the same vendor group; deleting a default promotes
|
|
3
|
+
* the most recently updated remaining row. Summaries never include sealed payloads.
|
|
4
|
+
*/
|
|
5
|
+
export {};
|
|
6
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type { SealedSecret } from "./types.js";
|
|
2
|
+
export type { RootKeyHandle, KeyringPort, SecretSealerPort } from "./ports.js";
|
|
3
|
+
export { AesGcmSecretSealer } from "./secret-sealer.aesgcm.js";
|
|
4
|
+
export { EnvOrFileKeyring, FixedRootKeyKeyring, UnusableRootKeyError, RootKeyFileAlreadyExistsError, parseRootKeyHex, fingerprintRootKeyHex, inspectRootKeyMaterial, revealRootKeyMaterial, generateFileRootKey, deriveFromRootKey, deriveSigningSecretFromRootKey, } from "./keyring.env.js";
|
|
5
|
+
export type { EnvOrFileKeyringRequired, RootKeyEnvironmentPort, EnvOrFileKeyringOptions, RootKeyRejection, ParsedRootKeyHex, RootKeyStatus, RootKeyReveal, InspectRootKeyMaterialOptions, GeneratedFileRootKey, } from "./keyring.env.js";
|
|
6
|
+
export { formatAad } from "./aad.js";
|
|
7
|
+
export { defaultPlatformMessages } from "../messages.js";
|
|
8
|
+
export type { PlatformMessages } from "../messages.js";
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { AesGcmSecretSealer } from "./secret-sealer.aesgcm.js";
|
|
2
|
+
export { EnvOrFileKeyring, FixedRootKeyKeyring, UnusableRootKeyError, RootKeyFileAlreadyExistsError, parseRootKeyHex, fingerprintRootKeyHex, inspectRootKeyMaterial, revealRootKeyMaterial, generateFileRootKey, deriveFromRootKey, deriveSigningSecretFromRootKey, } from "./keyring.env.js";
|
|
3
|
+
export { formatAad } from "./aad.js";
|
|
4
|
+
export { defaultPlatformMessages } from "../messages.js";
|
|
5
|
+
//# sourceMappingURL=index.js.map
|