@wingman-ai/gateway 0.4.0 → 0.4.1
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 +29 -111
- package/dist/agent/config/agentConfig.cjs +2 -0
- package/dist/agent/config/agentConfig.d.ts +6 -0
- package/dist/agent/config/agentConfig.js +2 -0
- package/dist/agent/config/agentLoader.cjs +21 -18
- package/dist/agent/config/agentLoader.js +22 -19
- package/dist/agent/config/toolRegistry.cjs +19 -0
- package/dist/agent/config/toolRegistry.d.ts +4 -0
- package/dist/agent/config/toolRegistry.js +17 -1
- package/dist/agent/middleware/additional-messages.cjs +115 -11
- package/dist/agent/middleware/additional-messages.d.ts +9 -0
- package/dist/agent/middleware/additional-messages.js +115 -11
- package/dist/agent/tests/agentLoader.test.cjs +45 -0
- package/dist/agent/tests/agentLoader.test.js +45 -0
- package/dist/agent/tests/toolRegistry.test.cjs +2 -0
- package/dist/agent/tests/toolRegistry.test.js +2 -0
- package/dist/agent/tools/node_invoke.cjs +146 -0
- package/dist/agent/tools/node_invoke.d.ts +86 -0
- package/dist/agent/tools/node_invoke.js +109 -0
- package/dist/cli/commands/gateway.cjs +1 -1
- package/dist/cli/commands/gateway.js +1 -1
- package/dist/cli/core/agentInvoker.cjs +21 -3
- package/dist/cli/core/agentInvoker.d.ts +10 -0
- package/dist/cli/core/agentInvoker.js +21 -3
- package/dist/gateway/http/nodes.cjs +247 -0
- package/dist/gateway/http/nodes.d.ts +20 -0
- package/dist/gateway/http/nodes.js +210 -0
- package/dist/gateway/node.cjs +10 -1
- package/dist/gateway/node.d.ts +10 -1
- package/dist/gateway/node.js +10 -1
- package/dist/gateway/server.cjs +414 -27
- package/dist/gateway/server.d.ts +34 -0
- package/dist/gateway/server.js +408 -27
- package/dist/gateway/types.d.ts +6 -1
- package/dist/gateway/validation.cjs +2 -0
- package/dist/gateway/validation.d.ts +4 -0
- package/dist/gateway/validation.js +2 -0
- package/dist/tests/additionalMessageMiddleware.test.cjs +92 -0
- package/dist/tests/additionalMessageMiddleware.test.js +92 -0
- package/dist/tests/gateway-http-security.test.cjs +277 -0
- package/dist/tests/gateway-http-security.test.d.ts +1 -0
- package/dist/tests/gateway-http-security.test.js +271 -0
- package/dist/tests/gateway-node-mode.test.cjs +174 -0
- package/dist/tests/gateway-node-mode.test.d.ts +1 -0
- package/dist/tests/gateway-node-mode.test.js +168 -0
- package/dist/tests/gateway-origin-policy.test.cjs +60 -0
- package/dist/tests/gateway-origin-policy.test.d.ts +1 -0
- package/dist/tests/gateway-origin-policy.test.js +54 -0
- package/dist/tests/gateway.test.cjs +1 -0
- package/dist/tests/gateway.test.js +1 -0
- package/dist/tests/node-tools.test.cjs +77 -0
- package/dist/tests/node-tools.test.d.ts +1 -0
- package/dist/tests/node-tools.test.js +71 -0
- package/dist/tests/nodes-api.test.cjs +86 -0
- package/dist/tests/nodes-api.test.d.ts +1 -0
- package/dist/tests/nodes-api.test.js +80 -0
- package/dist/webui/assets/{index-DHbfLOUR.js → index-BMekSELC.js} +106 -106
- package/dist/webui/index.html +1 -1
- package/package.json +1 -1
package/dist/gateway/types.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { ServerWebSocket } from "bun";
|
|
|
2
2
|
/**
|
|
3
3
|
* Message types for gateway communication
|
|
4
4
|
*/
|
|
5
|
-
export type MessageType = "connect" | "res" | "req:agent" | "req:agent:cancel" | "event:agent" | "session_subscribe" | "session_unsubscribe" | "register" | "registered" | "unregister" | "join_group" | "leave_group" | "broadcast" | "direct" | "ping" | "pong" | "error" | "ack" | "upgrade";
|
|
5
|
+
export type MessageType = "connect" | "res" | "req:agent" | "req:agent:cancel" | "event:agent" | "req:node" | "event:node" | "session_subscribe" | "session_unsubscribe" | "register" | "registered" | "unregister" | "join_group" | "leave_group" | "broadcast" | "direct" | "ping" | "pong" | "error" | "ack" | "upgrade";
|
|
6
6
|
/**
|
|
7
7
|
* Gateway message structure
|
|
8
8
|
*/
|
|
@@ -87,6 +87,7 @@ export interface RoutingInfo {
|
|
|
87
87
|
export interface NodeMetadata {
|
|
88
88
|
id: string;
|
|
89
89
|
name: string;
|
|
90
|
+
clientId?: string;
|
|
90
91
|
capabilities?: string[];
|
|
91
92
|
groups: Set<string>;
|
|
92
93
|
connectedAt: number;
|
|
@@ -102,6 +103,10 @@ export interface NodeMetadata {
|
|
|
102
103
|
export interface Node extends NodeMetadata {
|
|
103
104
|
ws: ServerWebSocket<{
|
|
104
105
|
nodeId: string;
|
|
106
|
+
clientId?: string;
|
|
107
|
+
clientType?: string;
|
|
108
|
+
authenticated?: boolean;
|
|
109
|
+
tailscaleUser?: string;
|
|
105
110
|
}>;
|
|
106
111
|
}
|
|
107
112
|
/**
|
|
@@ -10,6 +10,8 @@ export declare const MessageTypeSchema: z.ZodEnum<{
|
|
|
10
10
|
"req:agent": "req:agent";
|
|
11
11
|
"req:agent:cancel": "req:agent:cancel";
|
|
12
12
|
"event:agent": "event:agent";
|
|
13
|
+
"req:node": "req:node";
|
|
14
|
+
"event:node": "event:node";
|
|
13
15
|
session_subscribe: "session_subscribe";
|
|
14
16
|
session_unsubscribe: "session_unsubscribe";
|
|
15
17
|
register: "register";
|
|
@@ -34,6 +36,8 @@ export declare const GatewayMessageSchema: z.ZodObject<{
|
|
|
34
36
|
"req:agent": "req:agent";
|
|
35
37
|
"req:agent:cancel": "req:agent:cancel";
|
|
36
38
|
"event:agent": "event:agent";
|
|
39
|
+
"req:node": "req:node";
|
|
40
|
+
"event:node": "event:node";
|
|
37
41
|
session_subscribe: "session_subscribe";
|
|
38
42
|
session_unsubscribe: "session_unsubscribe";
|
|
39
43
|
register: "register";
|
|
@@ -117,6 +117,98 @@ const additional_messages_cjs_namespaceObject = require("../agent/middleware/add
|
|
|
117
117
|
}).length;
|
|
118
118
|
(0, external_vitest_namespaceObject.expect)(injectedCount).toBe(1);
|
|
119
119
|
});
|
|
120
|
+
(0, external_vitest_namespaceObject.it)("refreshes injected node context on each invocation", async ()=>{
|
|
121
|
+
let connectedIds = [
|
|
122
|
+
"node-a"
|
|
123
|
+
];
|
|
124
|
+
const middleware = (0, additional_messages_cjs_namespaceObject.additionalMessageMiddleware)({
|
|
125
|
+
nodeConnectedIdsProvider: ()=>connectedIds
|
|
126
|
+
});
|
|
127
|
+
const beforeAgent = "function" == typeof middleware.beforeAgent ? middleware.beforeAgent : middleware.beforeAgent?.hook;
|
|
128
|
+
if (!beforeAgent) throw new Error("beforeAgent hook not configured");
|
|
129
|
+
const first = await beforeAgent({
|
|
130
|
+
messages: [
|
|
131
|
+
new external_langchain_namespaceObject.HumanMessage("Hello")
|
|
132
|
+
]
|
|
133
|
+
}, {});
|
|
134
|
+
const firstContent = first.messages[0]?.content ?? "";
|
|
135
|
+
(0, external_vitest_namespaceObject.expect)(firstContent).toContain("Connected node IDs: node-a");
|
|
136
|
+
connectedIds = [
|
|
137
|
+
"node-b"
|
|
138
|
+
];
|
|
139
|
+
const second = await beforeAgent({
|
|
140
|
+
messages: first.messages
|
|
141
|
+
}, {});
|
|
142
|
+
const secondContent = second.messages[0]?.content ?? "";
|
|
143
|
+
(0, external_vitest_namespaceObject.expect)(secondContent).toContain("Connected node IDs: node-b");
|
|
144
|
+
(0, external_vitest_namespaceObject.expect)(secondContent).not.toContain("Connected node IDs: node-a");
|
|
145
|
+
});
|
|
146
|
+
(0, external_vitest_namespaceObject.it)("injects connected node IDs for node tool targeting when provided", async ()=>{
|
|
147
|
+
const middleware = (0, additional_messages_cjs_namespaceObject.additionalMessageMiddleware)({
|
|
148
|
+
nodeConnectedIdsProvider: ()=>[
|
|
149
|
+
"node-b",
|
|
150
|
+
"node-a",
|
|
151
|
+
"",
|
|
152
|
+
"node-b"
|
|
153
|
+
]
|
|
154
|
+
});
|
|
155
|
+
const input = {
|
|
156
|
+
messages: [
|
|
157
|
+
new external_langchain_namespaceObject.HumanMessage("Hello")
|
|
158
|
+
]
|
|
159
|
+
};
|
|
160
|
+
const beforeAgent = "function" == typeof middleware.beforeAgent ? middleware.beforeAgent : middleware.beforeAgent?.hook;
|
|
161
|
+
if (!beforeAgent) throw new Error("beforeAgent hook not configured");
|
|
162
|
+
const result = await beforeAgent(input, {});
|
|
163
|
+
const content = result.messages[0]?.content ?? "";
|
|
164
|
+
(0, external_vitest_namespaceObject.expect)(content).toContain("Connected Node Targets");
|
|
165
|
+
(0, external_vitest_namespaceObject.expect)(content).toContain("Connected node IDs: node-b, node-a");
|
|
166
|
+
(0, external_vitest_namespaceObject.expect)(content).toContain("target.nodeId or target.clientId");
|
|
167
|
+
});
|
|
168
|
+
(0, external_vitest_namespaceObject.it)("injects default node target clientId when present", async ()=>{
|
|
169
|
+
const middleware = (0, additional_messages_cjs_namespaceObject.additionalMessageMiddleware)({
|
|
170
|
+
nodeConnectedIdsProvider: ()=>[],
|
|
171
|
+
defaultNodeTargetClientId: "desktop-abc123"
|
|
172
|
+
});
|
|
173
|
+
const input = {
|
|
174
|
+
messages: [
|
|
175
|
+
new external_langchain_namespaceObject.HumanMessage("Hello")
|
|
176
|
+
]
|
|
177
|
+
};
|
|
178
|
+
const beforeAgent = "function" == typeof middleware.beforeAgent ? middleware.beforeAgent : middleware.beforeAgent?.hook;
|
|
179
|
+
if (!beforeAgent) throw new Error("beforeAgent hook not configured");
|
|
180
|
+
const result = await beforeAgent(input, {});
|
|
181
|
+
const content = result.messages[0]?.content ?? "";
|
|
182
|
+
(0, external_vitest_namespaceObject.expect)(content).toContain("Connected node IDs: (none currently connected)");
|
|
183
|
+
(0, external_vitest_namespaceObject.expect)(content).toContain("Default node target clientId for this request: desktop-abc123");
|
|
184
|
+
});
|
|
185
|
+
(0, external_vitest_namespaceObject.it)("injects connected node metadata when provided", async ()=>{
|
|
186
|
+
const middleware = (0, additional_messages_cjs_namespaceObject.additionalMessageMiddleware)({
|
|
187
|
+
nodeConnectedTargetsProvider: ()=>[
|
|
188
|
+
{
|
|
189
|
+
nodeId: "node-a",
|
|
190
|
+
clientId: "desktop-a",
|
|
191
|
+
name: "Russell MacBook",
|
|
192
|
+
capabilities: [
|
|
193
|
+
"system.notify",
|
|
194
|
+
"system.run"
|
|
195
|
+
]
|
|
196
|
+
}
|
|
197
|
+
]
|
|
198
|
+
});
|
|
199
|
+
const input = {
|
|
200
|
+
messages: [
|
|
201
|
+
new external_langchain_namespaceObject.HumanMessage("Hello")
|
|
202
|
+
]
|
|
203
|
+
};
|
|
204
|
+
const beforeAgent = "function" == typeof middleware.beforeAgent ? middleware.beforeAgent : middleware.beforeAgent?.hook;
|
|
205
|
+
if (!beforeAgent) throw new Error("beforeAgent hook not configured");
|
|
206
|
+
const result = await beforeAgent(input, {});
|
|
207
|
+
const content = result.messages[0]?.content ?? "";
|
|
208
|
+
(0, external_vitest_namespaceObject.expect)(content).toContain("Connected node metadata:");
|
|
209
|
+
(0, external_vitest_namespaceObject.expect)(content).toContain("node-a (clientId: desktop-a; name: Russell MacBook;");
|
|
210
|
+
(0, external_vitest_namespaceObject.expect)(content).toContain("capabilities: system.notify, system.run");
|
|
211
|
+
});
|
|
120
212
|
});
|
|
121
213
|
for(var __rspack_i in __webpack_exports__)exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
122
214
|
Object.defineProperty(exports, '__esModule', {
|
|
@@ -93,4 +93,96 @@ describe("additionalMessageMiddleware", ()=>{
|
|
|
93
93
|
}).length;
|
|
94
94
|
expect(injectedCount).toBe(1);
|
|
95
95
|
});
|
|
96
|
+
it("refreshes injected node context on each invocation", async ()=>{
|
|
97
|
+
let connectedIds = [
|
|
98
|
+
"node-a"
|
|
99
|
+
];
|
|
100
|
+
const middleware = additionalMessageMiddleware({
|
|
101
|
+
nodeConnectedIdsProvider: ()=>connectedIds
|
|
102
|
+
});
|
|
103
|
+
const beforeAgent = "function" == typeof middleware.beforeAgent ? middleware.beforeAgent : middleware.beforeAgent?.hook;
|
|
104
|
+
if (!beforeAgent) throw new Error("beforeAgent hook not configured");
|
|
105
|
+
const first = await beforeAgent({
|
|
106
|
+
messages: [
|
|
107
|
+
new HumanMessage("Hello")
|
|
108
|
+
]
|
|
109
|
+
}, {});
|
|
110
|
+
const firstContent = first.messages[0]?.content ?? "";
|
|
111
|
+
expect(firstContent).toContain("Connected node IDs: node-a");
|
|
112
|
+
connectedIds = [
|
|
113
|
+
"node-b"
|
|
114
|
+
];
|
|
115
|
+
const second = await beforeAgent({
|
|
116
|
+
messages: first.messages
|
|
117
|
+
}, {});
|
|
118
|
+
const secondContent = second.messages[0]?.content ?? "";
|
|
119
|
+
expect(secondContent).toContain("Connected node IDs: node-b");
|
|
120
|
+
expect(secondContent).not.toContain("Connected node IDs: node-a");
|
|
121
|
+
});
|
|
122
|
+
it("injects connected node IDs for node tool targeting when provided", async ()=>{
|
|
123
|
+
const middleware = additionalMessageMiddleware({
|
|
124
|
+
nodeConnectedIdsProvider: ()=>[
|
|
125
|
+
"node-b",
|
|
126
|
+
"node-a",
|
|
127
|
+
"",
|
|
128
|
+
"node-b"
|
|
129
|
+
]
|
|
130
|
+
});
|
|
131
|
+
const input = {
|
|
132
|
+
messages: [
|
|
133
|
+
new HumanMessage("Hello")
|
|
134
|
+
]
|
|
135
|
+
};
|
|
136
|
+
const beforeAgent = "function" == typeof middleware.beforeAgent ? middleware.beforeAgent : middleware.beforeAgent?.hook;
|
|
137
|
+
if (!beforeAgent) throw new Error("beforeAgent hook not configured");
|
|
138
|
+
const result = await beforeAgent(input, {});
|
|
139
|
+
const content = result.messages[0]?.content ?? "";
|
|
140
|
+
expect(content).toContain("Connected Node Targets");
|
|
141
|
+
expect(content).toContain("Connected node IDs: node-b, node-a");
|
|
142
|
+
expect(content).toContain("target.nodeId or target.clientId");
|
|
143
|
+
});
|
|
144
|
+
it("injects default node target clientId when present", async ()=>{
|
|
145
|
+
const middleware = additionalMessageMiddleware({
|
|
146
|
+
nodeConnectedIdsProvider: ()=>[],
|
|
147
|
+
defaultNodeTargetClientId: "desktop-abc123"
|
|
148
|
+
});
|
|
149
|
+
const input = {
|
|
150
|
+
messages: [
|
|
151
|
+
new HumanMessage("Hello")
|
|
152
|
+
]
|
|
153
|
+
};
|
|
154
|
+
const beforeAgent = "function" == typeof middleware.beforeAgent ? middleware.beforeAgent : middleware.beforeAgent?.hook;
|
|
155
|
+
if (!beforeAgent) throw new Error("beforeAgent hook not configured");
|
|
156
|
+
const result = await beforeAgent(input, {});
|
|
157
|
+
const content = result.messages[0]?.content ?? "";
|
|
158
|
+
expect(content).toContain("Connected node IDs: (none currently connected)");
|
|
159
|
+
expect(content).toContain("Default node target clientId for this request: desktop-abc123");
|
|
160
|
+
});
|
|
161
|
+
it("injects connected node metadata when provided", async ()=>{
|
|
162
|
+
const middleware = additionalMessageMiddleware({
|
|
163
|
+
nodeConnectedTargetsProvider: ()=>[
|
|
164
|
+
{
|
|
165
|
+
nodeId: "node-a",
|
|
166
|
+
clientId: "desktop-a",
|
|
167
|
+
name: "Russell MacBook",
|
|
168
|
+
capabilities: [
|
|
169
|
+
"system.notify",
|
|
170
|
+
"system.run"
|
|
171
|
+
]
|
|
172
|
+
}
|
|
173
|
+
]
|
|
174
|
+
});
|
|
175
|
+
const input = {
|
|
176
|
+
messages: [
|
|
177
|
+
new HumanMessage("Hello")
|
|
178
|
+
]
|
|
179
|
+
};
|
|
180
|
+
const beforeAgent = "function" == typeof middleware.beforeAgent ? middleware.beforeAgent : middleware.beforeAgent?.hook;
|
|
181
|
+
if (!beforeAgent) throw new Error("beforeAgent hook not configured");
|
|
182
|
+
const result = await beforeAgent(input, {});
|
|
183
|
+
const content = result.messages[0]?.content ?? "";
|
|
184
|
+
expect(content).toContain("Connected node metadata:");
|
|
185
|
+
expect(content).toContain("node-a (clientId: desktop-a; name: Russell MacBook;");
|
|
186
|
+
expect(content).toContain("capabilities: system.notify, system.run");
|
|
187
|
+
});
|
|
96
188
|
});
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __webpack_exports__ = {};
|
|
3
|
+
const external_node_fs_namespaceObject = require("node:fs");
|
|
4
|
+
const external_node_os_namespaceObject = require("node:os");
|
|
5
|
+
const external_node_path_namespaceObject = require("node:path");
|
|
6
|
+
const external_vitest_namespaceObject = require("vitest");
|
|
7
|
+
const server_cjs_namespaceObject = require("../gateway/server.cjs");
|
|
8
|
+
const tempWorkspaces = [];
|
|
9
|
+
function createGateway(config) {
|
|
10
|
+
const workspace = (0, external_node_fs_namespaceObject.mkdtempSync)((0, external_node_path_namespaceObject.join)((0, external_node_os_namespaceObject.tmpdir)(), "wingman-gateway-http-security-"));
|
|
11
|
+
tempWorkspaces.push(workspace);
|
|
12
|
+
return new server_cjs_namespaceObject.GatewayServer({
|
|
13
|
+
logLevel: "silent",
|
|
14
|
+
workspace,
|
|
15
|
+
configDir: ".wingman-http-security-config",
|
|
16
|
+
stateDir: ".wingman-http-security-state",
|
|
17
|
+
...config
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
function getGatewayInternals(server) {
|
|
21
|
+
return server;
|
|
22
|
+
}
|
|
23
|
+
function createTestSocket(initialData) {
|
|
24
|
+
const messages = [];
|
|
25
|
+
const ws = {
|
|
26
|
+
data: {
|
|
27
|
+
...initialData || {}
|
|
28
|
+
},
|
|
29
|
+
send: (serialized)=>{
|
|
30
|
+
messages.push(JSON.parse(serialized));
|
|
31
|
+
return 1;
|
|
32
|
+
},
|
|
33
|
+
close: ()=>{}
|
|
34
|
+
};
|
|
35
|
+
return {
|
|
36
|
+
ws,
|
|
37
|
+
messages
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
(0, external_vitest_namespaceObject.afterAll)(()=>{
|
|
41
|
+
for (const workspace of tempWorkspaces)(0, external_node_fs_namespaceObject.rmSync)(workspace, {
|
|
42
|
+
recursive: true,
|
|
43
|
+
force: true
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
(0, external_vitest_namespaceObject.describe)("gateway HTTP security", ()=>{
|
|
47
|
+
(0, external_vitest_namespaceObject.it)("requires auth for /api routes when token auth is enabled", async ()=>{
|
|
48
|
+
const server = createGateway({
|
|
49
|
+
host: "127.0.0.1",
|
|
50
|
+
port: 18789,
|
|
51
|
+
auth: {
|
|
52
|
+
mode: "token",
|
|
53
|
+
token: "test-token"
|
|
54
|
+
},
|
|
55
|
+
requireAuth: true
|
|
56
|
+
});
|
|
57
|
+
const internals = getGatewayInternals(server);
|
|
58
|
+
const unauthenticated = await internals.handleUiRequest(new Request("http://127.0.0.1:18789/api/providers"));
|
|
59
|
+
(0, external_vitest_namespaceObject.expect)(unauthenticated.status).toBe(401);
|
|
60
|
+
const authenticated = await internals.handleUiRequest(new Request("http://127.0.0.1:18789/api/providers", {
|
|
61
|
+
headers: {
|
|
62
|
+
Authorization: "Bearer test-token"
|
|
63
|
+
}
|
|
64
|
+
}));
|
|
65
|
+
(0, external_vitest_namespaceObject.expect)(authenticated.status).toBe(200);
|
|
66
|
+
});
|
|
67
|
+
(0, external_vitest_namespaceObject.it)("rejects disallowed origins and allows loopback development preflight", async ()=>{
|
|
68
|
+
const server = createGateway({
|
|
69
|
+
host: "127.0.0.1",
|
|
70
|
+
port: 18789,
|
|
71
|
+
auth: {
|
|
72
|
+
mode: "token",
|
|
73
|
+
token: "test-token"
|
|
74
|
+
},
|
|
75
|
+
requireAuth: true
|
|
76
|
+
});
|
|
77
|
+
const internals = getGatewayInternals(server);
|
|
78
|
+
const denied = await internals.handleUiRequest(new Request("http://127.0.0.1:18789/api/providers", {
|
|
79
|
+
method: "OPTIONS",
|
|
80
|
+
headers: {
|
|
81
|
+
Origin: "https://evil.example",
|
|
82
|
+
"Access-Control-Request-Method": "GET"
|
|
83
|
+
}
|
|
84
|
+
}));
|
|
85
|
+
(0, external_vitest_namespaceObject.expect)(denied.status).toBe(403);
|
|
86
|
+
const allowed = await internals.handleUiRequest(new Request("http://127.0.0.1:18789/api/providers", {
|
|
87
|
+
method: "OPTIONS",
|
|
88
|
+
headers: {
|
|
89
|
+
Origin: "http://localhost:5173",
|
|
90
|
+
"Access-Control-Request-Method": "GET"
|
|
91
|
+
}
|
|
92
|
+
}));
|
|
93
|
+
(0, external_vitest_namespaceObject.expect)(allowed.status).toBe(204);
|
|
94
|
+
(0, external_vitest_namespaceObject.expect)(allowed.headers.get("access-control-allow-origin")).toBe("http://localhost:5173");
|
|
95
|
+
});
|
|
96
|
+
(0, external_vitest_namespaceObject.it)("does not trust tailscale identity headers on non-loopback hosts", ()=>{
|
|
97
|
+
const server = createGateway({
|
|
98
|
+
host: "0.0.0.0",
|
|
99
|
+
port: 18789,
|
|
100
|
+
auth: {
|
|
101
|
+
mode: "token",
|
|
102
|
+
token: "tailscale-token",
|
|
103
|
+
allowTailscale: true
|
|
104
|
+
},
|
|
105
|
+
requireAuth: true
|
|
106
|
+
});
|
|
107
|
+
const internals = getGatewayInternals(server);
|
|
108
|
+
const bypassAttempt = internals.requireHttpAuth(new Request("http://127.0.0.1:18789/api/providers", {
|
|
109
|
+
headers: {
|
|
110
|
+
"tailscale-user-login": "attacker@example.com"
|
|
111
|
+
}
|
|
112
|
+
}));
|
|
113
|
+
(0, external_vitest_namespaceObject.expect)(bypassAttempt?.status).toBe(401);
|
|
114
|
+
const authenticated = internals.requireHttpAuth(new Request("http://127.0.0.1:18789/api/providers", {
|
|
115
|
+
headers: {
|
|
116
|
+
Authorization: "Bearer tailscale-token",
|
|
117
|
+
"tailscale-user-login": "attacker@example.com"
|
|
118
|
+
}
|
|
119
|
+
}));
|
|
120
|
+
(0, external_vitest_namespaceObject.expect)(authenticated).toBeNull();
|
|
121
|
+
});
|
|
122
|
+
(0, external_vitest_namespaceObject.it)("enforces bridge node capacity limits", async ()=>{
|
|
123
|
+
const server = createGateway({
|
|
124
|
+
host: "127.0.0.1",
|
|
125
|
+
port: 18789,
|
|
126
|
+
auth: {
|
|
127
|
+
mode: "none"
|
|
128
|
+
},
|
|
129
|
+
requireAuth: false,
|
|
130
|
+
maxNodes: 1
|
|
131
|
+
});
|
|
132
|
+
const internals = getGatewayInternals(server);
|
|
133
|
+
const registerBody = JSON.stringify({
|
|
134
|
+
type: "register",
|
|
135
|
+
payload: {
|
|
136
|
+
name: "bridge-node"
|
|
137
|
+
},
|
|
138
|
+
timestamp: Date.now()
|
|
139
|
+
});
|
|
140
|
+
const first = await internals.handleBridgeSend(new Request("http://127.0.0.1:18789/bridge/send", {
|
|
141
|
+
method: "POST",
|
|
142
|
+
headers: {
|
|
143
|
+
"Content-Type": "application/json"
|
|
144
|
+
},
|
|
145
|
+
body: registerBody
|
|
146
|
+
}));
|
|
147
|
+
(0, external_vitest_namespaceObject.expect)(first.status).toBe(200);
|
|
148
|
+
const second = await internals.handleBridgeSend(new Request("http://127.0.0.1:18789/bridge/send", {
|
|
149
|
+
method: "POST",
|
|
150
|
+
headers: {
|
|
151
|
+
"Content-Type": "application/json"
|
|
152
|
+
},
|
|
153
|
+
body: registerBody
|
|
154
|
+
}));
|
|
155
|
+
(0, external_vitest_namespaceObject.expect)(second.status).toBe(429);
|
|
156
|
+
});
|
|
157
|
+
(0, external_vitest_namespaceObject.it)("rejects malformed /api/nodes client IDs with 400", async ()=>{
|
|
158
|
+
const server = createGateway({
|
|
159
|
+
host: "127.0.0.1",
|
|
160
|
+
port: 18789,
|
|
161
|
+
auth: {
|
|
162
|
+
mode: "none"
|
|
163
|
+
},
|
|
164
|
+
requireAuth: false
|
|
165
|
+
});
|
|
166
|
+
const internals = getGatewayInternals(server);
|
|
167
|
+
const res = await internals.handleUiRequest(new Request("http://127.0.0.1:18789/api/nodes/%E0%A4%A", {
|
|
168
|
+
method: "PUT",
|
|
169
|
+
headers: {
|
|
170
|
+
"Content-Type": "application/json"
|
|
171
|
+
},
|
|
172
|
+
body: JSON.stringify({
|
|
173
|
+
enabled: true
|
|
174
|
+
})
|
|
175
|
+
}));
|
|
176
|
+
(0, external_vitest_namespaceObject.expect)(res.status).toBe(400);
|
|
177
|
+
});
|
|
178
|
+
(0, external_vitest_namespaceObject.it)("requires approved client identity for node execution capabilities", ()=>{
|
|
179
|
+
const server = createGateway({
|
|
180
|
+
host: "127.0.0.1",
|
|
181
|
+
port: 18789,
|
|
182
|
+
auth: {
|
|
183
|
+
mode: "none"
|
|
184
|
+
},
|
|
185
|
+
requireAuth: false
|
|
186
|
+
});
|
|
187
|
+
const internals = getGatewayInternals(server);
|
|
188
|
+
const blocked = createTestSocket({
|
|
189
|
+
authenticated: true
|
|
190
|
+
});
|
|
191
|
+
internals.handleRegister(blocked.ws, {
|
|
192
|
+
type: "register",
|
|
193
|
+
payload: {
|
|
194
|
+
name: "unpaired-node",
|
|
195
|
+
capabilities: [
|
|
196
|
+
"system.run"
|
|
197
|
+
]
|
|
198
|
+
},
|
|
199
|
+
timestamp: Date.now()
|
|
200
|
+
});
|
|
201
|
+
(0, external_vitest_namespaceObject.expect)(blocked.messages.some((message)=>"error" === message.type && message.payload?.code === "NODE_NOT_ENABLED")).toBe(true);
|
|
202
|
+
});
|
|
203
|
+
(0, external_vitest_namespaceObject.it)("rejects duplicate pending node request IDs", async ()=>{
|
|
204
|
+
const server = createGateway({
|
|
205
|
+
host: "127.0.0.1",
|
|
206
|
+
port: 18789,
|
|
207
|
+
auth: {
|
|
208
|
+
mode: "none"
|
|
209
|
+
},
|
|
210
|
+
requireAuth: false
|
|
211
|
+
});
|
|
212
|
+
const internals = getGatewayInternals(server);
|
|
213
|
+
const enableTarget = await internals.handleUiRequest(new Request("http://127.0.0.1:18789/api/nodes/desktop-target", {
|
|
214
|
+
method: "PUT",
|
|
215
|
+
headers: {
|
|
216
|
+
"Content-Type": "application/json"
|
|
217
|
+
},
|
|
218
|
+
body: JSON.stringify({
|
|
219
|
+
enabled: true,
|
|
220
|
+
name: "Desktop Target"
|
|
221
|
+
})
|
|
222
|
+
}));
|
|
223
|
+
(0, external_vitest_namespaceObject.expect)(enableTarget.status).toBe(200);
|
|
224
|
+
const target = createTestSocket({
|
|
225
|
+
authenticated: true,
|
|
226
|
+
clientId: "desktop-target",
|
|
227
|
+
clientType: "desktop"
|
|
228
|
+
});
|
|
229
|
+
internals.handleRegister(target.ws, {
|
|
230
|
+
type: "register",
|
|
231
|
+
payload: {
|
|
232
|
+
name: "target-node",
|
|
233
|
+
capabilities: [
|
|
234
|
+
"system.notify"
|
|
235
|
+
]
|
|
236
|
+
},
|
|
237
|
+
timestamp: Date.now()
|
|
238
|
+
});
|
|
239
|
+
const nodeId = target.messages.find((message)=>"ack" === message.type && "string" == typeof message.payload?.nodeId)?.payload?.nodeId;
|
|
240
|
+
(0, external_vitest_namespaceObject.expect)(nodeId).toBeTruthy();
|
|
241
|
+
const requester = createTestSocket({
|
|
242
|
+
authenticated: true,
|
|
243
|
+
clientId: "desktop-requester",
|
|
244
|
+
clientType: "desktop"
|
|
245
|
+
});
|
|
246
|
+
const duplicateRequestId = "dup-node-request-id";
|
|
247
|
+
internals.handleNodeRequest(requester.ws, {
|
|
248
|
+
type: "req:node",
|
|
249
|
+
id: duplicateRequestId,
|
|
250
|
+
targetNodeId: nodeId,
|
|
251
|
+
payload: {
|
|
252
|
+
tool: "system.notify",
|
|
253
|
+
args: {
|
|
254
|
+
title: "test"
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
timestamp: Date.now()
|
|
258
|
+
});
|
|
259
|
+
internals.handleNodeRequest(requester.ws, {
|
|
260
|
+
type: "req:node",
|
|
261
|
+
id: duplicateRequestId,
|
|
262
|
+
targetNodeId: nodeId,
|
|
263
|
+
payload: {
|
|
264
|
+
tool: "system.notify",
|
|
265
|
+
args: {
|
|
266
|
+
title: "test"
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
timestamp: Date.now()
|
|
270
|
+
});
|
|
271
|
+
(0, external_vitest_namespaceObject.expect)(requester.messages.some((message)=>"error" === message.type && message.payload?.code === "DUPLICATE_REQUEST_ID")).toBe(true);
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
for(var __rspack_i in __webpack_exports__)exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
275
|
+
Object.defineProperty(exports, '__esModule', {
|
|
276
|
+
value: true
|
|
277
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|