@aegis-fluxion/core 0.7.1 → 0.8.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/README.md +140 -237
- package/dist/index.cjs +761 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +56 -1
- package/dist/index.d.ts +56 -1
- package/dist/index.js +762 -40
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
# @aegis-fluxion/core
|
|
2
2
|
|
|
3
|
-
Low-level
|
|
3
|
+
Low-level encrypted WebSocket primitives for the `aegis-fluxion` ecosystem.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Version: **0.7.1**
|
|
5
|
+
Version: **0.8.0**
|
|
8
6
|
|
|
9
7
|
---
|
|
10
8
|
|
|
@@ -12,16 +10,12 @@ Version: **0.7.1**
|
|
|
12
10
|
|
|
13
11
|
- Ephemeral ECDH handshake (`prime256v1`)
|
|
14
12
|
- AES-256-GCM encrypted envelopes
|
|
15
|
-
-
|
|
16
|
-
- Secure room routing (`join`, `leave`, `leaveAll`, `to(room).emit`)
|
|
17
|
-
- Heartbeat and zombie socket cleanup
|
|
18
|
-
- Auto-reconnect with fresh re-handshake
|
|
19
|
-
- **Binary payload support**: `Buffer`, `Uint8Array`, `Blob`
|
|
20
|
-
- **Server middleware pipeline** via `SecureServer.use(...)`
|
|
13
|
+
- ACK request/response (Promise + callback styles)
|
|
14
|
+
- Secure room routing (`join`, `leave`, `leaveAll`, `to(room).emit(...)`)
|
|
21
15
|
- Middleware phases: `connection`, `incoming`, `outgoing`
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
16
|
+
- Rate limiting and DDoS controls per connection and IP
|
|
17
|
+
- TLS 1.3-style session resumption with encrypted one-time tickets
|
|
18
|
+
- **Horizontal scaling hooks** via pluggable `SecureServerAdapter`
|
|
25
19
|
|
|
26
20
|
---
|
|
27
21
|
|
|
@@ -33,299 +27,208 @@ npm install @aegis-fluxion/core ws
|
|
|
33
27
|
|
|
34
28
|
---
|
|
35
29
|
|
|
36
|
-
##
|
|
37
|
-
|
|
38
|
-
`SecureServer` now supports phase-based middleware for auth, policy enforcement, and payload
|
|
39
|
-
normalization.
|
|
30
|
+
## Session resumption (TLS 1.3-style)
|
|
40
31
|
|
|
41
|
-
|
|
42
|
-
import { SecureClient, SecureServer } from "@aegis-fluxion/core";
|
|
32
|
+
`@aegis-fluxion/core@0.8.0` introduces secure resume-first reconnect behavior:
|
|
43
33
|
|
|
44
|
-
|
|
34
|
+
- Full handshake path uses ephemeral ECDH (`hello` frame).
|
|
35
|
+
- Resume path uses ticket-bound proofs (`resume` / `resume-ack` frames).
|
|
36
|
+
- Successful resumes derive fresh channel keys from ticket secret + client nonce.
|
|
37
|
+
- Servers enforce ticket TTL, bounded cache size, and one-time ticket consumption.
|
|
38
|
+
- Clients automatically fall back to full handshake when resume is rejected.
|
|
45
39
|
|
|
46
|
-
|
|
47
|
-
if (context.phase === "connection") {
|
|
48
|
-
const rawApiKey = context.request.headers["x-api-key"];
|
|
49
|
-
const apiKey = Array.isArray(rawApiKey) ? rawApiKey[0] : rawApiKey;
|
|
40
|
+
### Server configuration
|
|
50
41
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
42
|
+
```ts
|
|
43
|
+
import { SecureServer } from "@aegis-fluxion/core";
|
|
54
44
|
|
|
55
|
-
|
|
45
|
+
const server = new SecureServer({
|
|
46
|
+
host: "127.0.0.1",
|
|
47
|
+
port: 8080,
|
|
48
|
+
sessionResumption: {
|
|
49
|
+
enabled: true,
|
|
50
|
+
ticketTtlMs: 60_000,
|
|
51
|
+
maxCachedTickets: 10_000
|
|
56
52
|
}
|
|
57
|
-
|
|
58
|
-
await next();
|
|
59
53
|
});
|
|
54
|
+
```
|
|
60
55
|
|
|
61
|
-
|
|
62
|
-
if (
|
|
63
|
-
context.phase === "incoming" &&
|
|
64
|
-
context.event === "post:create" &&
|
|
65
|
-
typeof context.data === "object" &&
|
|
66
|
-
context.data !== null
|
|
67
|
-
) {
|
|
68
|
-
const payload = context.data as { title?: string };
|
|
69
|
-
context.data = { title: String(payload.title ?? "").trim() };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
await next();
|
|
56
|
+
### Client configuration
|
|
73
57
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
...(context.data as Record<string, unknown>),
|
|
77
|
-
middleware: true
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
server.on("post:create", async (payload, client) => {
|
|
83
|
-
return {
|
|
84
|
-
ok: true,
|
|
85
|
-
role: client.metadata.get("role"),
|
|
86
|
-
payload
|
|
87
|
-
};
|
|
88
|
-
});
|
|
58
|
+
```ts
|
|
59
|
+
import { SecureClient } from "@aegis-fluxion/core";
|
|
89
60
|
|
|
90
61
|
const client = new SecureClient("ws://127.0.0.1:8080", {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
62
|
+
reconnect: true,
|
|
63
|
+
sessionResumption: {
|
|
64
|
+
enabled: true,
|
|
65
|
+
maxAcceptedTicketTtlMs: 60_000
|
|
95
66
|
}
|
|
96
67
|
});
|
|
97
|
-
|
|
98
|
-
client.on("ready", async () => {
|
|
99
|
-
const response = await client.emit("post:create", { title: " Hello " }, { timeoutMs: 1500 });
|
|
100
|
-
console.log(response);
|
|
101
|
-
});
|
|
102
68
|
```
|
|
103
69
|
|
|
104
|
-
|
|
70
|
+
### Security model
|
|
105
71
|
|
|
106
|
-
-
|
|
107
|
-
-
|
|
72
|
+
- Resume proofs are validated with HMAC and constant-time comparison.
|
|
73
|
+
- Resume tickets are encrypted in transit (same channel protections as all payloads).
|
|
74
|
+
- Resume tickets are discarded if expired, policy-invalid, or already consumed.
|
|
75
|
+
- Reserved internal events (e.g., session-ticket transport) cannot be emitted by user code.
|
|
108
76
|
|
|
109
77
|
---
|
|
110
78
|
|
|
111
|
-
##
|
|
79
|
+
## SecureServer adapter API (horizontal scaling)
|
|
112
80
|
|
|
113
|
-
|
|
81
|
+
### Core types
|
|
114
82
|
|
|
115
83
|
```ts
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
disconnectReason: "Rate limit exceeded. Please retry later."
|
|
132
|
-
}
|
|
133
|
-
});
|
|
84
|
+
export interface SecureServerAdapterMessage {
|
|
85
|
+
version: 1;
|
|
86
|
+
originServerId: string;
|
|
87
|
+
scope: "broadcast" | "room";
|
|
88
|
+
event: string;
|
|
89
|
+
data: unknown;
|
|
90
|
+
emittedAt: number;
|
|
91
|
+
room?: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface SecureServerAdapter {
|
|
95
|
+
attach(server: SecureServer): void | Promise<void>;
|
|
96
|
+
publish(message: SecureServerAdapterMessage): void | Promise<void>;
|
|
97
|
+
detach?(server: SecureServer): void | Promise<void>;
|
|
98
|
+
}
|
|
134
99
|
```
|
|
135
100
|
|
|
136
|
-
|
|
101
|
+
### SecureServer hooks
|
|
137
102
|
|
|
138
|
-
-
|
|
139
|
-
-
|
|
140
|
-
-
|
|
141
|
-
-
|
|
103
|
+
- constructor option: `new SecureServer({ ..., adapter })`
|
|
104
|
+
- runtime binding: `await server.setAdapter(adapter)`
|
|
105
|
+
- inbound relay: `await server.handleAdapterMessage(message)`
|
|
106
|
+
- instance identity: `server.serverId`
|
|
142
107
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
## MCP Adapter Integration (0.7.0)
|
|
146
|
-
|
|
147
|
-
Use `@aegis-fluxion/mcp-adapter` to carry MCP JSON-RPC messages through your encrypted core
|
|
148
|
-
transport.
|
|
108
|
+
### Message normalization helper
|
|
149
109
|
|
|
150
110
|
```ts
|
|
151
|
-
import {
|
|
152
|
-
|
|
111
|
+
import { normalizeSecureServerAdapterMessage } from "@aegis-fluxion/core";
|
|
112
|
+
```
|
|
153
113
|
|
|
154
|
-
|
|
114
|
+
Use it in adapters before delivering inbound Pub/Sub payloads to `SecureServer`.
|
|
155
115
|
|
|
156
|
-
|
|
157
|
-
const mcpServerTransport = new SecureMCPTransport({
|
|
158
|
-
mode: "server",
|
|
159
|
-
server: secureServer,
|
|
160
|
-
clientId: client.id
|
|
161
|
-
});
|
|
116
|
+
---
|
|
162
117
|
|
|
163
|
-
|
|
164
|
-
// Forward into your MCP server runtime.
|
|
165
|
-
console.log("MCP request on server tunnel", message);
|
|
166
|
-
};
|
|
118
|
+
## Adapter integration example
|
|
167
119
|
|
|
168
|
-
|
|
169
|
-
|
|
120
|
+
```ts
|
|
121
|
+
import {
|
|
122
|
+
SecureServer,
|
|
123
|
+
type SecureServerAdapter,
|
|
124
|
+
type SecureServerAdapterMessage
|
|
125
|
+
} from "@aegis-fluxion/core";
|
|
126
|
+
|
|
127
|
+
class InMemoryAdapter implements SecureServerAdapter {
|
|
128
|
+
private static readonly peers = new Set<InMemoryAdapter>();
|
|
129
|
+
private server: SecureServer | null = null;
|
|
130
|
+
|
|
131
|
+
async attach(server: SecureServer): Promise<void> {
|
|
132
|
+
this.server = server;
|
|
133
|
+
InMemoryAdapter.peers.add(this);
|
|
134
|
+
}
|
|
170
135
|
|
|
171
|
-
|
|
136
|
+
async publish(message: SecureServerAdapterMessage): Promise<void> {
|
|
137
|
+
for (const peer of InMemoryAdapter.peers) {
|
|
138
|
+
if (peer === this || !peer.server) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
172
141
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
});
|
|
142
|
+
await peer.server.handleAdapterMessage(message);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
177
145
|
|
|
178
|
-
|
|
146
|
+
async detach(server: SecureServer): Promise<void> {
|
|
147
|
+
if (this.server !== server) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
179
150
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
151
|
+
this.server = null;
|
|
152
|
+
InMemoryAdapter.peers.delete(this);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const server = new SecureServer({
|
|
157
|
+
host: "127.0.0.1",
|
|
158
|
+
port: 8080,
|
|
159
|
+
adapter: new InMemoryAdapter()
|
|
185
160
|
});
|
|
186
161
|
```
|
|
187
162
|
|
|
188
163
|
---
|
|
189
164
|
|
|
190
|
-
##
|
|
191
|
-
|
|
192
|
-
`@aegis-fluxion/core` supports encrypted binary payload transfer while preserving type fidelity.
|
|
193
|
-
|
|
194
|
-
Supported send/receive types:
|
|
195
|
-
|
|
196
|
-
- `Buffer`
|
|
197
|
-
- `Uint8Array`
|
|
198
|
-
- `Blob`
|
|
199
|
-
|
|
200
|
-
Binary values can be nested in regular objects and arrays.
|
|
201
|
-
|
|
202
|
-
---
|
|
203
|
-
|
|
204
|
-
## Example: Encrypted Binary Event
|
|
165
|
+
## Middleware and ACK example
|
|
205
166
|
|
|
206
167
|
```ts
|
|
207
168
|
import { SecureClient, SecureServer } from "@aegis-fluxion/core";
|
|
208
169
|
|
|
209
170
|
const server = new SecureServer({ host: "127.0.0.1", port: 8080 });
|
|
210
|
-
const client = new SecureClient("ws://127.0.0.1:8080");
|
|
211
171
|
|
|
212
|
-
server.
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
if (!Buffer.isBuffer(chunk)) {
|
|
216
|
-
throw new Error("Expected Buffer payload.");
|
|
172
|
+
server.use(async (context, next) => {
|
|
173
|
+
if (context.phase === "connection") {
|
|
174
|
+
context.metadata.set("auth.role", "operator");
|
|
217
175
|
}
|
|
218
176
|
|
|
219
|
-
|
|
177
|
+
await next();
|
|
220
178
|
});
|
|
221
179
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
180
|
+
server.on("jobs:create", async (payload, client) => {
|
|
181
|
+
return {
|
|
182
|
+
ok: true,
|
|
183
|
+
role: client.metadata.get("auth.role"),
|
|
184
|
+
payload
|
|
185
|
+
};
|
|
225
186
|
});
|
|
226
187
|
|
|
227
|
-
client
|
|
228
|
-
|
|
229
|
-
|
|
188
|
+
const client = new SecureClient("ws://127.0.0.1:8080");
|
|
189
|
+
|
|
190
|
+
client.on("ready", async () => {
|
|
191
|
+
const response = await client.emit("jobs:create", { id: "job-42" }, { timeoutMs: 1200 });
|
|
192
|
+
console.log(response);
|
|
230
193
|
});
|
|
231
194
|
```
|
|
232
195
|
|
|
233
196
|
---
|
|
234
197
|
|
|
235
|
-
##
|
|
198
|
+
## Rate limiting and DDoS shield
|
|
236
199
|
|
|
237
200
|
```ts
|
|
238
|
-
|
|
239
|
-
const { file, bytes, blob } = payload as {
|
|
240
|
-
file: Buffer;
|
|
241
|
-
bytes: Uint8Array;
|
|
242
|
-
blob: Blob;
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
return {
|
|
246
|
-
fileBytes: file.byteLength,
|
|
247
|
-
bytesBytes: bytes.byteLength,
|
|
248
|
-
blobBytes: blob.size
|
|
249
|
-
};
|
|
250
|
-
});
|
|
201
|
+
import { SecureServer } from "@aegis-fluxion/core";
|
|
251
202
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
203
|
+
const server = new SecureServer({
|
|
204
|
+
host: "127.0.0.1",
|
|
205
|
+
port: 8080,
|
|
206
|
+
rateLimit: {
|
|
207
|
+
enabled: true,
|
|
208
|
+
windowMs: 1_000,
|
|
209
|
+
maxEventsPerConnection: 120,
|
|
210
|
+
maxEventsPerIp: 300,
|
|
211
|
+
action: "throttle", // or "disconnect"
|
|
212
|
+
throttleMs: 150,
|
|
213
|
+
maxThrottleMs: 2_000,
|
|
214
|
+
disconnectAfterViolations: 4,
|
|
215
|
+
disconnectCode: 1013,
|
|
216
|
+
disconnectReason: "Rate limit exceeded. Please retry later."
|
|
217
|
+
}
|
|
266
218
|
});
|
|
267
219
|
```
|
|
268
220
|
|
|
269
221
|
---
|
|
270
222
|
|
|
271
|
-
##
|
|
272
|
-
|
|
273
|
-
### `SecureServer`
|
|
274
|
-
|
|
275
|
-
- `on("connection" | "ready" | "disconnect" | "error", handler)`
|
|
276
|
-
- `on("custom:event", (data, client) => unknown | Promise<unknown>)`
|
|
277
|
-
- `use((context, next) => void | Promise<void>)`
|
|
278
|
-
- `emit(event, data): SecureServer`
|
|
279
|
-
- `emitTo(clientId, event, data): boolean`
|
|
280
|
-
- `emitTo(clientId, event, data, callback): boolean`
|
|
281
|
-
- `emitTo(clientId, event, data, options): Promise<unknown>`
|
|
282
|
-
- `emitTo(clientId, event, data, options, callback): boolean`
|
|
283
|
-
- `to(room).emit(event, data): SecureServer`
|
|
284
|
-
- `close(code?, reason?): void`
|
|
285
|
-
|
|
286
|
-
### `SecureServerClient`
|
|
287
|
-
|
|
288
|
-
- `id: string`
|
|
289
|
-
- `socket: WebSocket`
|
|
290
|
-
- `metadata: ReadonlyMap<string, unknown>`
|
|
291
|
-
- `emit(event, data, ...ackArgs): boolean | Promise<unknown>`
|
|
292
|
-
- `join(room): boolean`
|
|
293
|
-
- `leave(room): boolean`
|
|
294
|
-
- `leaveAll(): number`
|
|
295
|
-
|
|
296
|
-
### Middleware Types
|
|
297
|
-
|
|
298
|
-
- `SecureServerMiddleware`
|
|
299
|
-
- `SecureServerMiddlewareContext`
|
|
300
|
-
- `SecureServerConnectionMiddlewareContext`
|
|
301
|
-
- `SecureServerMessageMiddlewareContext`
|
|
302
|
-
- `SecureServerMiddlewareNext`
|
|
303
|
-
- `SecureServerRateLimitOptions`
|
|
304
|
-
- `SecureServerRateLimitAction`
|
|
305
|
-
|
|
306
|
-
### `SecureClient`
|
|
307
|
-
|
|
308
|
-
- `connect(): void`
|
|
309
|
-
- `disconnect(code?, reason?): void`
|
|
310
|
-
- `isConnected(): boolean`
|
|
311
|
-
- `readyState: number | null`
|
|
312
|
-
- `emit(event, data): boolean`
|
|
313
|
-
- `emit(event, data, callback): boolean`
|
|
314
|
-
- `emit(event, data, options): Promise<unknown>`
|
|
315
|
-
- `emit(event, data, options, callback): boolean`
|
|
316
|
-
- `on("connect" | "ready" | "disconnect" | "error", handler)`
|
|
317
|
-
- `on("custom:event", handler)`
|
|
223
|
+
## Binary payload support
|
|
318
224
|
|
|
319
|
-
|
|
225
|
+
Supported encrypted payload types:
|
|
320
226
|
|
|
321
|
-
|
|
227
|
+
- `Buffer`
|
|
228
|
+
- `Uint8Array`
|
|
229
|
+
- `Blob`
|
|
322
230
|
|
|
323
|
-
|
|
324
|
-
- Authentication tags are verified on every packet (tampered packets are dropped).
|
|
325
|
-
- Internal transport events are reserved (`__handshake`, `__rpc:req`, `__rpc:res`).
|
|
326
|
-
- Pending ACK requests are rejected on timeout/disconnect.
|
|
327
|
-
- Overload traffic can be throttled or disconnected before custom handlers are invoked.
|
|
328
|
-
- Middleware-level policy rejection uses WebSocket close code `1008`.
|
|
231
|
+
Binary fields can be nested in standard JSON objects and arrays.
|
|
329
232
|
|
|
330
233
|
---
|
|
331
234
|
|