@nimbus-dev/sdk 1.1.2
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/LICENSE +21 -0
- package/README.md +34 -0
- package/dist/audit-logger.d.ts +6 -0
- package/dist/audit-logger.d.ts.map +1 -0
- package/dist/audit-logger.js +18 -0
- package/dist/audit-logger.js.map +1 -0
- package/dist/contract-tests.d.ts +45 -0
- package/dist/contract-tests.d.ts.map +1 -0
- package/dist/contract-tests.js +191 -0
- package/dist/contract-tests.js.map +1 -0
- package/dist/crypto/app-store-connect-jwt.d.ts +19 -0
- package/dist/crypto/app-store-connect-jwt.d.ts.map +1 -0
- package/dist/crypto/app-store-connect-jwt.js +30 -0
- package/dist/crypto/app-store-connect-jwt.js.map +1 -0
- package/dist/crypto/canonical-json.d.ts +36 -0
- package/dist/crypto/canonical-json.d.ts.map +1 -0
- package/dist/crypto/canonical-json.js +75 -0
- package/dist/crypto/canonical-json.js.map +1 -0
- package/dist/crypto/jwt.d.ts +30 -0
- package/dist/crypto/jwt.d.ts.map +1 -0
- package/dist/crypto/jwt.js +30 -0
- package/dist/crypto/jwt.js.map +1 -0
- package/dist/crypto/service-account-token.d.ts +36 -0
- package/dist/crypto/service-account-token.d.ts.map +1 -0
- package/dist/crypto/service-account-token.js +96 -0
- package/dist/crypto/service-account-token.js.map +1 -0
- package/dist/crypto/verify-signature.d.ts +57 -0
- package/dist/crypto/verify-signature.d.ts.map +1 -0
- package/dist/crypto/verify-signature.js +102 -0
- package/dist/crypto/verify-signature.js.map +1 -0
- package/dist/distribution-channel.d.ts +34 -0
- package/dist/distribution-channel.d.ts.map +1 -0
- package/dist/distribution-channel.js +73 -0
- package/dist/distribution-channel.js.map +1 -0
- package/dist/hitl-request.d.ts +7 -0
- package/dist/hitl-request.d.ts.map +1 -0
- package/dist/hitl-request.js +15 -0
- package/dist/hitl-request.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/ipc/index.d.ts +2 -0
- package/dist/ipc/index.d.ts.map +1 -0
- package/dist/ipc/index.js +2 -0
- package/dist/ipc/index.js.map +1 -0
- package/dist/ipc/ndjson-line-reader.d.ts +20 -0
- package/dist/ipc/ndjson-line-reader.d.ts.map +1 -0
- package/dist/ipc/ndjson-line-reader.js +56 -0
- package/dist/ipc/ndjson-line-reader.js.map +1 -0
- package/dist/server.d.ts +29 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +23 -0
- package/dist/server.js.map +1 -0
- package/dist/testing/index.d.ts +15 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +17 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/sandbox-contract.d.ts +83 -0
- package/dist/testing/sandbox-contract.d.ts.map +1 -0
- package/dist/testing/sandbox-contract.js +105 -0
- package/dist/testing/sandbox-contract.js.map +1 -0
- package/dist/testing/sandbox-probe.d.ts +23 -0
- package/dist/testing/sandbox-probe.d.ts.map +1 -0
- package/dist/testing/sandbox-probe.js +78 -0
- package/dist/testing/sandbox-probe.js.map +1 -0
- package/dist/types.d.ts +41 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +55 -0
- package/src/audit-logger.test.ts +33 -0
- package/src/audit-logger.ts +23 -0
- package/src/contract-tests.test.ts +203 -0
- package/src/contract-tests.ts +220 -0
- package/src/crypto/app-store-connect-jwt.test.ts +80 -0
- package/src/crypto/app-store-connect-jwt.ts +42 -0
- package/src/crypto/canonical-json.test.ts +121 -0
- package/src/crypto/canonical-json.ts +73 -0
- package/src/crypto/jwt.test.ts +62 -0
- package/src/crypto/jwt.ts +45 -0
- package/src/crypto/service-account-token.test.ts +128 -0
- package/src/crypto/service-account-token.ts +116 -0
- package/src/crypto/verify-signature.test.ts +118 -0
- package/src/crypto/verify-signature.ts +138 -0
- package/src/distribution-channel.test.ts +107 -0
- package/src/distribution-channel.ts +105 -0
- package/src/hitl-request.ts +22 -0
- package/src/index.ts +59 -0
- package/src/ipc/index.ts +5 -0
- package/src/ipc/ndjson-line-reader.test.ts +64 -0
- package/src/ipc/ndjson-line-reader.ts +70 -0
- package/src/plugin-api-v1.test.ts +50 -0
- package/src/sdk.test.ts +23 -0
- package/src/server.test.ts +96 -0
- package/src/server.ts +39 -0
- package/src/testing/index.ts +18 -0
- package/src/testing/sandbox-contract.test.ts +146 -0
- package/src/testing/sandbox-contract.ts +155 -0
- package/src/testing/sandbox-probe.ts +87 -0
- package/src/types.ts +42 -0
package/dist/server.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NimbusExtensionServer — base class for all Nimbus MCP extension servers
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* const server = new NimbusExtensionServer({ manifest, onAuth });
|
|
6
|
+
* server.registerTool("search", { description, inputSchema, handler });
|
|
7
|
+
* server.start();
|
|
8
|
+
*/
|
|
9
|
+
export class NimbusExtensionServer {
|
|
10
|
+
_options;
|
|
11
|
+
constructor(options) {
|
|
12
|
+
this._options = options;
|
|
13
|
+
}
|
|
14
|
+
registerTool(_name, _definition) {
|
|
15
|
+
// Roadmap Q3: register tool with MCP server
|
|
16
|
+
}
|
|
17
|
+
start() {
|
|
18
|
+
if (this._options.manifest.id.length === 0) {
|
|
19
|
+
throw new Error("NimbusExtensionServer: manifest.id is required");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAeH,MAAM,OAAO,qBAAqB;IACf,QAAQ,CAAkC;IAE3D,YAAY,OAAwC;QAClD,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;IAC1B,CAAC;IAED,YAAY,CAAS,KAAa,EAAE,WAA4C;QAC9E,4CAA4C;IAC9C,CAAC;IAED,KAAK;QACH,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3C,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;QACpE,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@nimbus-dev/sdk/testing` — utilities for extension authors and the
|
|
3
|
+
* gateway's own connector tests.
|
|
4
|
+
*
|
|
5
|
+
* Exports:
|
|
6
|
+
* - `MockGateway` — mock Gateway IPC for unit tests (Phase 4).
|
|
7
|
+
* - `runSandboxContractTests(manifestPath)` — fork the probe binary and
|
|
8
|
+
* verify the runtime sandbox enforces the manifest's declared
|
|
9
|
+
* `permissions.network` + `permissions.filesystem` (Phase 5 T2 PR 1).
|
|
10
|
+
*/
|
|
11
|
+
export { runSandboxContractTests } from "./sandbox-contract";
|
|
12
|
+
export declare class MockGateway {
|
|
13
|
+
callTool(_toolName: string, _input: Record<string, unknown>): Promise<unknown>;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAE7D,qBAAa,WAAW;IAChB,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;CAGrF"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@nimbus-dev/sdk/testing` — utilities for extension authors and the
|
|
3
|
+
* gateway's own connector tests.
|
|
4
|
+
*
|
|
5
|
+
* Exports:
|
|
6
|
+
* - `MockGateway` — mock Gateway IPC for unit tests (Phase 4).
|
|
7
|
+
* - `runSandboxContractTests(manifestPath)` — fork the probe binary and
|
|
8
|
+
* verify the runtime sandbox enforces the manifest's declared
|
|
9
|
+
* `permissions.network` + `permissions.filesystem` (Phase 5 T2 PR 1).
|
|
10
|
+
*/
|
|
11
|
+
export { runSandboxContractTests } from "./sandbox-contract";
|
|
12
|
+
export class MockGateway {
|
|
13
|
+
async callTool(_toolName, _input) {
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAE7D,MAAM,OAAO,WAAW;IACtB,KAAK,CAAC,QAAQ,CAAC,SAAiB,EAAE,MAA+B;QAC/D,OAAO,EAAE,CAAC;IACZ,CAAC;CACF"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `runSandboxContractTests` — verifies the declared sandbox permissions in a
|
|
3
|
+
* manifest match the runtime enforcement seen by a forked probe.
|
|
4
|
+
*
|
|
5
|
+
* Phase 5 T2 PR 1.
|
|
6
|
+
*
|
|
7
|
+
* Three probes run sequentially:
|
|
8
|
+
*
|
|
9
|
+
* 1. **network-listed** — for the first declared host in
|
|
10
|
+
* `permissions.network`, a HEAD fetch must succeed (2xx–4xx).
|
|
11
|
+
* Skipped if no hosts are declared.
|
|
12
|
+
*
|
|
13
|
+
* 2. **network-unlisted** — a fetch to `192.0.2.1` (TEST-NET-1, never
|
|
14
|
+
* routable) must fail with ECONNREFUSED / EPERM / EHOSTUNREACH /
|
|
15
|
+
* ENETUNREACH. Skipped on Windows because AppContainer network
|
|
16
|
+
* filtering is host-allow + deny-by-default at a different layer — the
|
|
17
|
+
* probe sees a generic socket failure that is indistinguishable from
|
|
18
|
+
* the unsandboxed case. See `docs/sandbox.md#platform-asymmetry`.
|
|
19
|
+
* Skipped on every platform if no hosts are declared (the
|
|
20
|
+
* sandbox-runner would deny network entirely in that case, and the
|
|
21
|
+
* probe's expectation collapses to "no network at all", which the
|
|
22
|
+
* first probe doesn't exercise).
|
|
23
|
+
*
|
|
24
|
+
* 3. **fs-denied** — a read of a known-protected path
|
|
25
|
+
* (`/etc/passwd` POSIX, `C:\Windows\System32\config\SAM` Windows)
|
|
26
|
+
* must fail with EACCES / EPERM. This probe always runs.
|
|
27
|
+
*
|
|
28
|
+
* Each probe is invoked via `child_process.spawnSync(process.execPath, …)`
|
|
29
|
+
* directly. **The probe is *not* sandbox-wrapped by the SDK harness alone.**
|
|
30
|
+
* For the SDK to assert real enforcement, a wrapping helper (typically
|
|
31
|
+
* `packages/gateway/test/helpers/sandbox-harness.ts`) needs to fork
|
|
32
|
+
* `runSandboxContractTests` inside a process that is itself sandbox-wrapped,
|
|
33
|
+
* or substitute a sandboxed `execPath`.
|
|
34
|
+
*
|
|
35
|
+
* Used by:
|
|
36
|
+
* - First-party connector contract tests via the gateway test harness.
|
|
37
|
+
* - Third-party extension authors invoking
|
|
38
|
+
* `import { runSandboxContractTests } from "@nimbus-dev/sdk/testing"`.
|
|
39
|
+
*
|
|
40
|
+
* When invoked **outside** a sandboxed harness on Linux/macOS, probes 2 and
|
|
41
|
+
* 3 will either succeed (return exit 2) — which the harness reports as a
|
|
42
|
+
* test failure — or fail with codes other than 10/11. Either way the harness
|
|
43
|
+
* throws. That's the third-party UX: "your contract test failed because the
|
|
44
|
+
* sandbox isn't enforcing the manifest", which is the correct signal even
|
|
45
|
+
* though the UX could be friendlier.
|
|
46
|
+
*/
|
|
47
|
+
export interface ProbeResult {
|
|
48
|
+
status: number;
|
|
49
|
+
stderr: string;
|
|
50
|
+
stdout: string;
|
|
51
|
+
}
|
|
52
|
+
/** Probe-runner shape — exposed for testability (see `__defaultRunProbe`). */
|
|
53
|
+
export type ProbeRunner = (probe: string, arg: string) => ProbeResult;
|
|
54
|
+
export interface RunSandboxContractTestsOptions {
|
|
55
|
+
/**
|
|
56
|
+
* Override the probe runner. Default is `__defaultRunProbe` (a
|
|
57
|
+
* `child_process.spawnSync` wrapper around the bundled probe binary).
|
|
58
|
+
* Tests inject a stub here; production callers leave this undefined.
|
|
59
|
+
*/
|
|
60
|
+
runProbe?: ProbeRunner;
|
|
61
|
+
/**
|
|
62
|
+
* Override `process.platform` for testability of the Windows skip
|
|
63
|
+
* branch. Default is the live `process.platform`.
|
|
64
|
+
*/
|
|
65
|
+
platform?: NodeJS.Platform;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Read the manifest at `manifestPath`, fork the probe binary for each
|
|
69
|
+
* declared capability + the FS-denied negative case, and throw if the
|
|
70
|
+
* observed enforcement does not match.
|
|
71
|
+
*
|
|
72
|
+
* Throws on first failure with a message that names the probe, the
|
|
73
|
+
* observed exit code, and the probe's stderr.
|
|
74
|
+
*/
|
|
75
|
+
export declare function runSandboxContractTests(manifestPath: string, opts?: RunSandboxContractTestsOptions): Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* Default probe runner — forks the bundled probe binary via
|
|
78
|
+
* `child_process.spawnSync`. Exported for direct use in test scaffolding
|
|
79
|
+
* that wants to assert the production wiring without re-implementing the
|
|
80
|
+
* `spawnSync` envelope.
|
|
81
|
+
*/
|
|
82
|
+
export declare function __defaultRunProbe(probe: string, arg: string): ProbeResult;
|
|
83
|
+
//# sourceMappingURL=sandbox-contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sandbox-contract.d.ts","sourceRoot":"","sources":["../../src/testing/sandbox-contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAmBH,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,8EAA8E;AAC9E,MAAM,MAAM,WAAW,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,WAAW,CAAC;AAEtE,MAAM,WAAW,8BAA8B;IAC7C;;;;OAIG;IACH,QAAQ,CAAC,EAAE,WAAW,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;CAC5B;AAED;;;;;;;GAOG;AACH,wBAAsB,uBAAuB,CAC3C,YAAY,EAAE,MAAM,EACpB,IAAI,GAAE,8BAAmC,GACxC,OAAO,CAAC,IAAI,CAAC,CAuCf;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,WAAW,CASzE"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `runSandboxContractTests` — verifies the declared sandbox permissions in a
|
|
3
|
+
* manifest match the runtime enforcement seen by a forked probe.
|
|
4
|
+
*
|
|
5
|
+
* Phase 5 T2 PR 1.
|
|
6
|
+
*
|
|
7
|
+
* Three probes run sequentially:
|
|
8
|
+
*
|
|
9
|
+
* 1. **network-listed** — for the first declared host in
|
|
10
|
+
* `permissions.network`, a HEAD fetch must succeed (2xx–4xx).
|
|
11
|
+
* Skipped if no hosts are declared.
|
|
12
|
+
*
|
|
13
|
+
* 2. **network-unlisted** — a fetch to `192.0.2.1` (TEST-NET-1, never
|
|
14
|
+
* routable) must fail with ECONNREFUSED / EPERM / EHOSTUNREACH /
|
|
15
|
+
* ENETUNREACH. Skipped on Windows because AppContainer network
|
|
16
|
+
* filtering is host-allow + deny-by-default at a different layer — the
|
|
17
|
+
* probe sees a generic socket failure that is indistinguishable from
|
|
18
|
+
* the unsandboxed case. See `docs/sandbox.md#platform-asymmetry`.
|
|
19
|
+
* Skipped on every platform if no hosts are declared (the
|
|
20
|
+
* sandbox-runner would deny network entirely in that case, and the
|
|
21
|
+
* probe's expectation collapses to "no network at all", which the
|
|
22
|
+
* first probe doesn't exercise).
|
|
23
|
+
*
|
|
24
|
+
* 3. **fs-denied** — a read of a known-protected path
|
|
25
|
+
* (`/etc/passwd` POSIX, `C:\Windows\System32\config\SAM` Windows)
|
|
26
|
+
* must fail with EACCES / EPERM. This probe always runs.
|
|
27
|
+
*
|
|
28
|
+
* Each probe is invoked via `child_process.spawnSync(process.execPath, …)`
|
|
29
|
+
* directly. **The probe is *not* sandbox-wrapped by the SDK harness alone.**
|
|
30
|
+
* For the SDK to assert real enforcement, a wrapping helper (typically
|
|
31
|
+
* `packages/gateway/test/helpers/sandbox-harness.ts`) needs to fork
|
|
32
|
+
* `runSandboxContractTests` inside a process that is itself sandbox-wrapped,
|
|
33
|
+
* or substitute a sandboxed `execPath`.
|
|
34
|
+
*
|
|
35
|
+
* Used by:
|
|
36
|
+
* - First-party connector contract tests via the gateway test harness.
|
|
37
|
+
* - Third-party extension authors invoking
|
|
38
|
+
* `import { runSandboxContractTests } from "@nimbus-dev/sdk/testing"`.
|
|
39
|
+
*
|
|
40
|
+
* When invoked **outside** a sandboxed harness on Linux/macOS, probes 2 and
|
|
41
|
+
* 3 will either succeed (return exit 2) — which the harness reports as a
|
|
42
|
+
* test failure — or fail with codes other than 10/11. Either way the harness
|
|
43
|
+
* throws. That's the third-party UX: "your contract test failed because the
|
|
44
|
+
* sandbox isn't enforcing the manifest", which is the correct signal even
|
|
45
|
+
* though the UX could be friendlier.
|
|
46
|
+
*/
|
|
47
|
+
import { spawnSync } from "node:child_process";
|
|
48
|
+
import { readFile } from "node:fs/promises";
|
|
49
|
+
import { dirname, resolve } from "node:path";
|
|
50
|
+
import { fileURLToPath } from "node:url";
|
|
51
|
+
const PROBE_PATH = resolve(dirname(fileURLToPath(import.meta.url)), "sandbox-probe.ts");
|
|
52
|
+
/**
|
|
53
|
+
* Read the manifest at `manifestPath`, fork the probe binary for each
|
|
54
|
+
* declared capability + the FS-denied negative case, and throw if the
|
|
55
|
+
* observed enforcement does not match.
|
|
56
|
+
*
|
|
57
|
+
* Throws on first failure with a message that names the probe, the
|
|
58
|
+
* observed exit code, and the probe's stderr.
|
|
59
|
+
*/
|
|
60
|
+
export async function runSandboxContractTests(manifestPath, opts = {}) {
|
|
61
|
+
const runProbe = opts.runProbe ?? __defaultRunProbe;
|
|
62
|
+
const platform = opts.platform ?? process.platform;
|
|
63
|
+
const raw = await readFile(manifestPath, "utf8");
|
|
64
|
+
const manifest = JSON.parse(raw);
|
|
65
|
+
const perms = manifest.permissions;
|
|
66
|
+
const objectForm = perms && typeof perms === "object" && !Array.isArray(perms) ? perms : null;
|
|
67
|
+
const hosts = objectForm?.network ?? [];
|
|
68
|
+
const firstHost = hosts[0];
|
|
69
|
+
if (firstHost !== undefined) {
|
|
70
|
+
const r = runProbe("network-listed", firstHost);
|
|
71
|
+
if (r.status !== 0) {
|
|
72
|
+
throw new Error(`network-listed probe failed for ${firstHost}: exit ${r.status}; stderr: ${r.stderr.trim()}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (platform !== "win32" && hosts.length > 0) {
|
|
76
|
+
const r = runProbe("network-unlisted", "");
|
|
77
|
+
if (r.status !== 11) {
|
|
78
|
+
throw new Error(`network-unlisted probe should have failed with ECONNREFUSED/EPERM/EHOSTUNREACH/ENETUNREACH; ` +
|
|
79
|
+
`got exit ${r.status}; stderr: ${r.stderr.trim()}. ` +
|
|
80
|
+
`See docs/sandbox.md#platform-asymmetry.`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const r3 = runProbe("fs-denied", "");
|
|
84
|
+
if (r3.status !== 10) {
|
|
85
|
+
throw new Error(`fs-denied probe should have returned EACCES (exit 10); got exit ${r3.status}; ` +
|
|
86
|
+
`stderr: ${r3.stderr.trim()}.`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Default probe runner — forks the bundled probe binary via
|
|
91
|
+
* `child_process.spawnSync`. Exported for direct use in test scaffolding
|
|
92
|
+
* that wants to assert the production wiring without re-implementing the
|
|
93
|
+
* `spawnSync` envelope.
|
|
94
|
+
*/
|
|
95
|
+
export function __defaultRunProbe(probe, arg) {
|
|
96
|
+
const result = spawnSync(process.execPath, [PROBE_PATH, `--probe=${probe}`, `--arg=${arg}`], {
|
|
97
|
+
encoding: "utf8",
|
|
98
|
+
});
|
|
99
|
+
return {
|
|
100
|
+
status: result.status ?? -1,
|
|
101
|
+
stderr: result.stderr ?? "",
|
|
102
|
+
stdout: result.stdout ?? "",
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=sandbox-contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sandbox-contract.js","sourceRoot":"","sources":["../../src/testing/sandbox-contract.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandbox probe binary — invoked by `runSandboxContractTests`.
|
|
3
|
+
*
|
|
4
|
+
* The probe is a tiny standalone program that the contract harness forks
|
|
5
|
+
* (typically wrapped by the gateway's sandbox runner) to exercise one
|
|
6
|
+
* specific capability and report the outcome via process exit code.
|
|
7
|
+
*
|
|
8
|
+
* Exit codes:
|
|
9
|
+
* 0 — expected pass (network reach to a listed host succeeded)
|
|
10
|
+
* 2 — unexpected outcome (test fails)
|
|
11
|
+
* 10 — expected EACCES/EPERM on filesystem read (sandbox enforced)
|
|
12
|
+
* 11 — expected ECONNREFUSED/EPERM/EHOSTUNREACH/ENETUNREACH on network
|
|
13
|
+
*
|
|
14
|
+
* Invocation:
|
|
15
|
+
* bun sandbox-probe.ts --probe=<name> --arg=<value>
|
|
16
|
+
*
|
|
17
|
+
* Probes:
|
|
18
|
+
* network-listed — HEAD-fetch https://<arg>/ ; succeed if 2xx-4xx
|
|
19
|
+
* network-unlisted — fetch http://192.0.2.1 (TEST-NET-1); succeed if blocked
|
|
20
|
+
* fs-denied — read a known-protected path; succeed if EACCES
|
|
21
|
+
*/
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=sandbox-probe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sandbox-probe.d.ts","sourceRoot":"","sources":["../../src/testing/sandbox-probe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAgEH,OAAO,EAAE,CAAC"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandbox probe binary — invoked by `runSandboxContractTests`.
|
|
3
|
+
*
|
|
4
|
+
* The probe is a tiny standalone program that the contract harness forks
|
|
5
|
+
* (typically wrapped by the gateway's sandbox runner) to exercise one
|
|
6
|
+
* specific capability and report the outcome via process exit code.
|
|
7
|
+
*
|
|
8
|
+
* Exit codes:
|
|
9
|
+
* 0 — expected pass (network reach to a listed host succeeded)
|
|
10
|
+
* 2 — unexpected outcome (test fails)
|
|
11
|
+
* 10 — expected EACCES/EPERM on filesystem read (sandbox enforced)
|
|
12
|
+
* 11 — expected ECONNREFUSED/EPERM/EHOSTUNREACH/ENETUNREACH on network
|
|
13
|
+
*
|
|
14
|
+
* Invocation:
|
|
15
|
+
* bun sandbox-probe.ts --probe=<name> --arg=<value>
|
|
16
|
+
*
|
|
17
|
+
* Probes:
|
|
18
|
+
* network-listed — HEAD-fetch https://<arg>/ ; succeed if 2xx-4xx
|
|
19
|
+
* network-unlisted — fetch http://192.0.2.1 (TEST-NET-1); succeed if blocked
|
|
20
|
+
* fs-denied — read a known-protected path; succeed if EACCES
|
|
21
|
+
*/
|
|
22
|
+
const probe = process.argv.find((a) => a.startsWith("--probe="))?.slice(8);
|
|
23
|
+
const arg = process.argv.find((a) => a.startsWith("--arg="))?.slice(6);
|
|
24
|
+
function errorCode(e) {
|
|
25
|
+
return (e.code ??
|
|
26
|
+
e.cause?.code);
|
|
27
|
+
}
|
|
28
|
+
async function probeNetworkListed() {
|
|
29
|
+
const url = `https://${arg ?? ""}/`;
|
|
30
|
+
try {
|
|
31
|
+
const res = await fetch(url, { method: "HEAD" });
|
|
32
|
+
return res.status >= 200 && res.status < 500 ? 0 : 2;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return 2;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async function probeNetworkUnlisted() {
|
|
39
|
+
try {
|
|
40
|
+
await fetch("http://192.0.2.1");
|
|
41
|
+
return 2;
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
const code = errorCode(e);
|
|
45
|
+
if (code === "ECONNREFUSED" ||
|
|
46
|
+
code === "EPERM" ||
|
|
47
|
+
code === "EHOSTUNREACH" ||
|
|
48
|
+
code === "ENETUNREACH") {
|
|
49
|
+
return 11;
|
|
50
|
+
}
|
|
51
|
+
return 2;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function probeFsDenied() {
|
|
55
|
+
const path = process.platform === "win32" ? String.raw `C:\Windows\System32\config\SAM` : "/etc/passwd";
|
|
56
|
+
try {
|
|
57
|
+
await Bun.file(path).text();
|
|
58
|
+
return 2;
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
const code = e.code;
|
|
62
|
+
if (code === "EACCES" || code === "EPERM" || code === "EBUSY")
|
|
63
|
+
return 10;
|
|
64
|
+
return 2;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async function main() {
|
|
68
|
+
if (probe === "network-listed")
|
|
69
|
+
process.exit(await probeNetworkListed());
|
|
70
|
+
if (probe === "network-unlisted")
|
|
71
|
+
process.exit(await probeNetworkUnlisted());
|
|
72
|
+
if (probe === "fs-denied")
|
|
73
|
+
process.exit(await probeFsDenied());
|
|
74
|
+
process.exit(2);
|
|
75
|
+
}
|
|
76
|
+
await main();
|
|
77
|
+
export {};
|
|
78
|
+
//# sourceMappingURL=sandbox-probe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sandbox-probe.js","sourceRoot":"","sources":["../../src/testing/sandbox-probe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;AAC3E,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;AAEvE,SAAS,SAAS,CAAC,CAAU;IAC3B,OAAO,CACJ,CAAkD,CAAC,IAAI;QACvD,CAAmC,CAAC,KAAK,EAAE,IAAI,CACjD,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,kBAAkB;IAC/B,MAAM,GAAG,GAAG,WAAW,GAAG,IAAI,EAAE,GAAG,CAAC;IACpC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QACjD,OAAO,GAAG,CAAC,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACvD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,CAAC;IACX,CAAC;AACH,CAAC;AAED,KAAK,UAAU,oBAAoB;IACjC,IAAI,CAAC;QACH,MAAM,KAAK,CAAC,kBAAkB,CAAC,CAAC;QAChC,OAAO,CAAC,CAAC;IACX,CAAC;IAAC,OAAO,CAAU,EAAE,CAAC;QACpB,MAAM,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;QAC1B,IACE,IAAI,KAAK,cAAc;YACvB,IAAI,KAAK,OAAO;YAChB,IAAI,KAAK,cAAc;YACvB,IAAI,KAAK,aAAa,EACtB,CAAC;YACD,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,OAAO,CAAC,CAAC;IACX,CAAC;AACH,CAAC;AAED,KAAK,UAAU,aAAa;IAC1B,MAAM,IAAI,GACR,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAA,gCAAgC,CAAC,CAAC,CAAC,aAAa,CAAC;IAC5F,IAAI,CAAC;QACH,MAAM,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;QAC5B,OAAO,CAAC,CAAC;IACX,CAAC;IAAC,OAAO,CAAU,EAAE,CAAC;QACpB,MAAM,IAAI,GAAI,CAAuB,CAAC,IAAI,CAAC;QAC3C,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,OAAO,IAAI,IAAI,KAAK,OAAO;YAAE,OAAO,EAAE,CAAC;QACzE,OAAO,CAAC,CAAC;IACX,CAAC;AACH,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,IAAI,KAAK,KAAK,gBAAgB;QAAE,OAAO,CAAC,IAAI,CAAC,MAAM,kBAAkB,EAAE,CAAC,CAAC;IACzE,IAAI,KAAK,KAAK,kBAAkB;QAAE,OAAO,CAAC,IAAI,CAAC,MAAM,oBAAoB,EAAE,CAAC,CAAC;IAC7E,IAAI,KAAK,KAAK,WAAW;QAAE,OAAO,CAAC,IAAI,CAAC,MAAM,aAAa,EAAE,CAAC,CAAC;IAC/D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAQD,MAAM,IAAI,EAAE,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for Nimbus extensions
|
|
3
|
+
*/
|
|
4
|
+
export interface NimbusItem {
|
|
5
|
+
id: string;
|
|
6
|
+
service: string;
|
|
7
|
+
itemType: "file" | "folder" | "email" | "event" | "photo" | "task";
|
|
8
|
+
name: string;
|
|
9
|
+
mimeType?: string;
|
|
10
|
+
sizeBytes?: number;
|
|
11
|
+
createdAt?: number;
|
|
12
|
+
modifiedAt?: number;
|
|
13
|
+
url?: string;
|
|
14
|
+
parentId?: string;
|
|
15
|
+
rawMeta?: Record<string, unknown>;
|
|
16
|
+
}
|
|
17
|
+
export interface ExtensionManifest {
|
|
18
|
+
$schema?: string;
|
|
19
|
+
id: string;
|
|
20
|
+
displayName: string;
|
|
21
|
+
version: string;
|
|
22
|
+
description: string;
|
|
23
|
+
author: string;
|
|
24
|
+
homepage?: string;
|
|
25
|
+
icon?: string;
|
|
26
|
+
entrypoint: string;
|
|
27
|
+
runtime: "bun" | "node";
|
|
28
|
+
permissions: Array<"read" | "write" | "delete">;
|
|
29
|
+
hitlRequired: Array<"write" | "delete">;
|
|
30
|
+
oauth?: {
|
|
31
|
+
provider: string;
|
|
32
|
+
scopes: string[];
|
|
33
|
+
authUrl: string;
|
|
34
|
+
tokenUrl: string;
|
|
35
|
+
pkce: boolean;
|
|
36
|
+
};
|
|
37
|
+
syncInterval?: number;
|
|
38
|
+
tags?: string[];
|
|
39
|
+
minNimbusVersion: string;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC;IACnE,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,KAAK,GAAG,MAAM,CAAC;IACxB,WAAW,EAAE,KAAK,CAAC,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC,CAAC;IAChD,YAAY,EAAE,KAAK,CAAC,OAAO,GAAG,QAAQ,CAAC,CAAC;IACxC,KAAK,CAAC,EAAE;QACN,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,EAAE,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,OAAO,CAAC;KACf,CAAC;IACF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,gBAAgB,EAAE,MAAM,CAAC;CAC1B"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nimbus-dev/sdk",
|
|
3
|
+
"version": "1.1.2",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/nimbus-agent/Nimbus.git",
|
|
8
|
+
"directory": "packages/sdk"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"main": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"bun": "./src/index.ts",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"import": "./dist/index.js",
|
|
18
|
+
"default": "./dist/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./testing": {
|
|
21
|
+
"bun": "./src/testing/index.ts",
|
|
22
|
+
"types": "./dist/testing/index.d.ts",
|
|
23
|
+
"import": "./dist/testing/index.js",
|
|
24
|
+
"default": "./dist/testing/index.js"
|
|
25
|
+
},
|
|
26
|
+
"./ipc": {
|
|
27
|
+
"bun": "./src/ipc/index.ts",
|
|
28
|
+
"types": "./dist/ipc/index.d.ts",
|
|
29
|
+
"import": "./dist/ipc/index.js",
|
|
30
|
+
"default": "./dist/ipc/index.js"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist",
|
|
35
|
+
"src"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"dev": "bun run --watch src/index.ts",
|
|
39
|
+
"build": "tsc --project tsconfig.build.json",
|
|
40
|
+
"typecheck": "tsc --noEmit",
|
|
41
|
+
"lint": "biome check src/",
|
|
42
|
+
"test": "bun test",
|
|
43
|
+
"clean": "rm -rf dist",
|
|
44
|
+
"prepublishOnly": "bun run build && bun run typecheck",
|
|
45
|
+
"test:coverage:sdk": "bun test --coverage src"
|
|
46
|
+
},
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public",
|
|
49
|
+
"registry": "https://registry.npmjs.org/"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@types/bun": "latest",
|
|
53
|
+
"typescript": "^6.0.3"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { createScopedAuditLogger } from "./audit-logger.ts";
|
|
3
|
+
|
|
4
|
+
describe("createScopedAuditLogger", () => {
|
|
5
|
+
test("prefixes action with extension ID", async () => {
|
|
6
|
+
const calls: Array<{ action: string; payload: Record<string, unknown> }> = [];
|
|
7
|
+
const emit = async (action: string, payload: Record<string, unknown>): Promise<void> => {
|
|
8
|
+
calls.push({ action, payload });
|
|
9
|
+
};
|
|
10
|
+
const logger = createScopedAuditLogger("ext.my-connector", emit);
|
|
11
|
+
await logger.log("sync.completed", { items: 42 });
|
|
12
|
+
expect(calls).toHaveLength(1);
|
|
13
|
+
expect(calls[0]?.action).toBe("ext.my-connector:sync.completed");
|
|
14
|
+
expect(calls[0]?.payload).toEqual({ items: 42 });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("rejects action IDs that already contain a colon", async () => {
|
|
18
|
+
const logger = createScopedAuditLogger("ext.foo", async () => {});
|
|
19
|
+
await expect(logger.log("already:scoped", {})).rejects.toThrow(/colon/);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("rejects empty action ID", async () => {
|
|
23
|
+
const logger = createScopedAuditLogger("ext.foo", async () => {});
|
|
24
|
+
await expect(logger.log("", {})).rejects.toThrow(/empty/);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("propagates emit errors unchanged", async () => {
|
|
28
|
+
const logger = createScopedAuditLogger("ext.foo", async () => {
|
|
29
|
+
throw new Error("downstream");
|
|
30
|
+
});
|
|
31
|
+
await expect(logger.log("x", {})).rejects.toThrow("downstream");
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type AuditEmit = (action: string, payload: Record<string, unknown>) => Promise<void>;
|
|
2
|
+
|
|
3
|
+
export interface AuditLogger {
|
|
4
|
+
log(action: string, payload: Record<string, unknown>): Promise<void>;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function createScopedAuditLogger(extensionId: string, emit: AuditEmit): AuditLogger {
|
|
8
|
+
if (!extensionId || extensionId.trim().length === 0) {
|
|
9
|
+
throw new Error("extensionId must be non-empty");
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
async log(action, payload) {
|
|
13
|
+
if (!action || action.length === 0) {
|
|
14
|
+
throw new Error("action must be non-empty");
|
|
15
|
+
}
|
|
16
|
+
if (action.includes(":")) {
|
|
17
|
+
throw new Error("action must not contain a colon (scoping prefix is added automatically)");
|
|
18
|
+
}
|
|
19
|
+
const scoped = `${extensionId}:${action}`;
|
|
20
|
+
await emit(scoped, payload);
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|