@floegence/flowersec-core 2.3.8 → 2.3.10
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 +2 -0
- package/dist/interop/serverParityPeer.js +48 -6
- package/dist/node/nativeTransportAddon.d.ts +1 -0
- package/dist/node/nativeTransportAddon.js +51 -9
- package/dist/node/tunnelRuntime.js +11 -4
- package/dist/proxy/runtime.js +6 -1
- package/dist/proxy/serviceWorker.js +23 -12
- package/dist/transport/webTransportAdapter.js +2 -2
- package/dist/v2/carrier.d.ts +2 -0
- package/dist/v2/carrier.js +8 -2
- package/dist/v2/session.d.ts +11 -0
- package/dist/v2/session.js +147 -17
- package/package.json +3 -3
- package/sbom/cyclonedx.json +6 -6
- package/sbom/spdx.json +11 -11
package/README.md
CHANGED
|
@@ -41,6 +41,8 @@ The controller has one scheduler and one in-flight attempt. Its states are `idle
|
|
|
41
41
|
|
|
42
42
|
Node `SessionHandlers` accept application stream kinds whose UTF-8 encoding is 1 through 255 bytes and reserve `flowersec.rpc.v2` for Flowersec RPC. `AcceptedSession.serve(...)` half-closes successfully handled streams. A rejected handler Promise resets only that stream; the accept loop and unrelated streams continue.
|
|
43
43
|
|
|
44
|
+
Reliable streams apply bounded per-stream receive backpressure instead of buffering application data without limit. A slow consumer pauses carrier progress until reads release capacity; records retain carrier order, so a rekey behind backpressured DATA completes after the consumer resumes. `closeWrite()` sends the graceful FIN and keeps reads available. `reset()` and `close()` abort both directions. If a write is canceled or fails after its wire commit may have started, only that stream becomes terminal and cannot be reused.
|
|
45
|
+
|
|
44
46
|
Source failures return a structured `terminal`, `retryable`, or `retry_after` disposition. Thrown or malformed source failures are terminal. Retry delay is deterministic exponential backoff from 250 ms, doubling to a 30-second maximum with no jitter; `retry_after` is never attempted before its specified Unix-millisecond boundary. Attempts are unlimited unless `maximumAttempts` is explicitly set.
|
|
45
47
|
|
|
46
48
|
A newly established session replaces `currentSession` atomically. The controller never migrates or replays streams, RPC calls, or writes from a terminated session; callers start new application operations on the new session.
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createInterface } from "node:readline";
|
|
2
|
+
import { createPrivateKey, createPublicKey, X509Certificate } from "node:crypto";
|
|
2
3
|
import { createAcceptor, createArtifactLease, createEndpointSet, createStreamMetadata, createTunnelRuntime, connect, Issuer, parseArtifact, SessionError, SessionHandlers, } from "../node/index.js";
|
|
3
4
|
const RUNTIME = "node-typescript";
|
|
4
|
-
const ORIGIN = "https://client.example";
|
|
5
|
+
const ORIGIN = process.env.FLOWERSEC_PARITY_ORIGIN ?? "https://client.example";
|
|
5
6
|
const ECHO_RPC = 7001;
|
|
6
7
|
const NOTIFY_RPC = 7002;
|
|
7
8
|
const COMPLETE_RPC = 7003;
|
|
@@ -10,8 +11,10 @@ const ECHO_KIND = "parity.echo";
|
|
|
10
11
|
const RESET_KIND = "parity.reset";
|
|
11
12
|
const encoder = new TextEncoder();
|
|
12
13
|
const decoder = new TextDecoder();
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
// Static test-only P-256 material keeps browser parity hermetic and is never
|
|
15
|
+
// exposed by a production package entrypoint.
|
|
16
|
+
const TEST_CERT_DER_B64 = "MIIB0DCCAXWgAwIBAgIUN1vflbzlJfrU4ZKED+4S+7sCtiYwCgYIKoZIzj0EAwIwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI2MDgxNDAyNDM0MloXDTM2MDgxMTAyNDM0MlowFDESMBAGA1UEAwwJbG9jYWxob3N0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEajwb7qy6VxUFH+WjP/RG8LabjthsjlqZweN2NAkwClGhWdp5XI5HEsP7p5pOpYjjD4kLsvjnBISRRf0CJIW2GKOBpDCBoTAdBgNVHQ4EFgQUe4BGlwqmqkqzEKuW7ACsFr6Dk4YwHwYDVR0jBBgwFoAUe4BGlwqmqkqzEKuW7ACsFr6Dk4YwLAYDVR0RBCUwI4IJbG9jYWxob3N0hwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAoGCCqGSM49BAMCA0kAMEYCIQCYzxx1Zev7TI4aHaXKrj7uV4F8wkJ2kEJtogyGlMJOFwIhAOAEs50N+UKa+0B9JK6xRACX82a6bFBZCY+H9nUKikV9";
|
|
17
|
+
const TEST_KEY_DER_B64 = "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgGw0KRCu5rtYpQtqfTVTSz97sUToj2S3UhuA/cxsDa5KhRANCAARqPBvurLpXFQUf5aM/9EbwtpuO2GyOWpnB43Y0CTAKUaFZ2nlcjkcSw/unmk6liOMPiQuy+OcEhJFF/QIkhbYY";
|
|
15
18
|
class SignalQueue {
|
|
16
19
|
#values = [];
|
|
17
20
|
#waiters = [];
|
|
@@ -271,16 +274,41 @@ async function exchangeDatagram(session, carrier, initiator) {
|
|
|
271
274
|
}
|
|
272
275
|
function fixture() {
|
|
273
276
|
const certificate = pem("CERTIFICATE", TEST_CERT_DER_B64);
|
|
277
|
+
const privateKey = pem("PRIVATE KEY", TEST_KEY_DER_B64);
|
|
278
|
+
validateTestTLSFixture(certificate, privateKey);
|
|
274
279
|
return {
|
|
275
280
|
tls: {
|
|
276
281
|
certificate_chain_pem: certificate,
|
|
277
|
-
private_key_pem:
|
|
282
|
+
private_key_pem: privateKey,
|
|
278
283
|
root_certificate_pem: certificate,
|
|
279
284
|
root_certificate_der_base64: TEST_CERT_DER_B64,
|
|
280
285
|
leaf_certificate_der_base64: TEST_CERT_DER_B64,
|
|
281
286
|
},
|
|
282
287
|
};
|
|
283
288
|
}
|
|
289
|
+
function validateTestTLSFixture(certificatePEM, privateKeyPEM) {
|
|
290
|
+
const certificate = new X509Certificate(certificatePEM);
|
|
291
|
+
const validFrom = Date.parse(certificate.validFrom);
|
|
292
|
+
const validTo = Date.parse(certificate.validTo);
|
|
293
|
+
const maximumValidityMs = 11 * 366 * 24 * 60 * 60 * 1_000;
|
|
294
|
+
const certificatePublicKey = certificate.publicKey.export({ type: "spki", format: "der" });
|
|
295
|
+
const privateKeyPublicKey = createPublicKey(createPrivateKey(privateKeyPEM)).export({ type: "spki", format: "der" });
|
|
296
|
+
if (certificate.ca ||
|
|
297
|
+
certificate.publicKey.asymmetricKeyType !== "ec" ||
|
|
298
|
+
certificate.publicKey.asymmetricKeyDetails?.namedCurve !== "prime256v1" ||
|
|
299
|
+
certificate.checkHost("localhost") !== "localhost" ||
|
|
300
|
+
certificate.checkIP("127.0.0.1") !== "127.0.0.1" ||
|
|
301
|
+
certificate.checkIP("::1") !== "::1" ||
|
|
302
|
+
!certificate.keyUsage.includes("1.3.6.1.5.5.7.3.1") ||
|
|
303
|
+
!Number.isFinite(validFrom) ||
|
|
304
|
+
!Number.isFinite(validTo) ||
|
|
305
|
+
Date.now() < validFrom ||
|
|
306
|
+
Date.now() >= validTo ||
|
|
307
|
+
validTo - validFrom > maximumValidityMs ||
|
|
308
|
+
!certificatePublicKey.equals(privateKeyPublicKey)) {
|
|
309
|
+
throw new Error("browser parity TLS fixture is invalid");
|
|
310
|
+
}
|
|
311
|
+
}
|
|
284
312
|
function pem(label, encoded) {
|
|
285
313
|
const lines = encoded.match(/.{1,64}/g);
|
|
286
314
|
if (lines === null)
|
|
@@ -334,7 +362,12 @@ async function runServer(tls, carrier) {
|
|
|
334
362
|
const accepted = await accepting;
|
|
335
363
|
state.executed.record("admission");
|
|
336
364
|
const serving = accepted.serve().catch((error) => error);
|
|
337
|
-
|
|
365
|
+
if (process.env.FLOWERSEC_PARITY_CLIENT_PROFILE !== undefined) {
|
|
366
|
+
await externalServer(accepted.session, state);
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
await exerciseServer(accepted.session, state, "direct", true, carrier);
|
|
370
|
+
}
|
|
338
371
|
await serving;
|
|
339
372
|
await accepted.close().catch(() => undefined);
|
|
340
373
|
if (state.activeStreams.value !== 0)
|
|
@@ -481,7 +514,12 @@ async function runTunnelEndpointB(input, carrier) {
|
|
|
481
514
|
const state = createHandlers("tunnel");
|
|
482
515
|
const session = await connectArtifact(secondJSON, relay, state.handlers);
|
|
483
516
|
state.executed.record("admission");
|
|
484
|
-
|
|
517
|
+
if (process.env.FLOWERSEC_PARITY_CLIENT_PROFILE !== undefined) {
|
|
518
|
+
await externalServer(session, state);
|
|
519
|
+
}
|
|
520
|
+
else {
|
|
521
|
+
await exerciseServer(session, state, "tunnel", false, carrier);
|
|
522
|
+
}
|
|
485
523
|
await session.close().catch(() => undefined);
|
|
486
524
|
state.executed.record("close");
|
|
487
525
|
if (state.activeStreams.value !== 0)
|
|
@@ -489,6 +527,10 @@ async function runTunnelEndpointB(input, carrier) {
|
|
|
489
527
|
state.executed.record("cleanup");
|
|
490
528
|
writeJSON(result("endpoint-b-result", "tunnel", state.executed.snapshot(), carrier));
|
|
491
529
|
}
|
|
530
|
+
async function externalServer(session, state) {
|
|
531
|
+
await session.waitTermination();
|
|
532
|
+
state.executed.record("close");
|
|
533
|
+
}
|
|
492
534
|
async function runTunnelEndpointA(input, carrier) {
|
|
493
535
|
const envelope = await input.next();
|
|
494
536
|
validateTunnelDimensions(envelope.topology, envelope.endpoint_b.relay, "endpoint_a", carrier);
|
|
@@ -2,6 +2,7 @@ import { createRequire } from "node:module";
|
|
|
2
2
|
import { CarrierError, } from "../v2/carrier.js";
|
|
3
3
|
export const NATIVE_TRANSPORT_CONTRACT_VERSION = 1;
|
|
4
4
|
const NATIVE_PACKAGE = "@floegence/flowersec-node-native";
|
|
5
|
+
const SERVER_PARITY_NATIVE_ADDON = "FLOWERSEC_SERVER_PARITY_NATIVE_ADDON";
|
|
5
6
|
export class NativeTransportUnavailableError extends Error {
|
|
6
7
|
code = "native_transport_unavailable";
|
|
7
8
|
constructor() {
|
|
@@ -12,7 +13,7 @@ export class NativeTransportUnavailableError extends Error {
|
|
|
12
13
|
export function loadNativeTransportAddon(requireFunction = createRequire(import.meta.url)) {
|
|
13
14
|
let candidate;
|
|
14
15
|
try {
|
|
15
|
-
candidate = requireFunction(
|
|
16
|
+
candidate = requireFunction(nativePackageSpecifier());
|
|
16
17
|
}
|
|
17
18
|
catch {
|
|
18
19
|
throw new NativeTransportUnavailableError();
|
|
@@ -22,6 +23,14 @@ export function loadNativeTransportAddon(requireFunction = createRequire(import.
|
|
|
22
23
|
}
|
|
23
24
|
return candidate;
|
|
24
25
|
}
|
|
26
|
+
function nativePackageSpecifier() {
|
|
27
|
+
if (process.env.FLOWERSEC_SERVER_PARITY_PEER === "1") {
|
|
28
|
+
const override = process.env[SERVER_PARITY_NATIVE_ADDON];
|
|
29
|
+
if (override !== undefined && override !== "")
|
|
30
|
+
return override;
|
|
31
|
+
}
|
|
32
|
+
return NATIVE_PACKAGE;
|
|
33
|
+
}
|
|
25
34
|
export function tryLoadNativeTransportAddon(requireFunction = createRequire(import.meta.url)) {
|
|
26
35
|
try {
|
|
27
36
|
return loadNativeTransportAddon(requireFunction);
|
|
@@ -117,21 +126,54 @@ function wrapSession(native) {
|
|
|
117
126
|
}
|
|
118
127
|
function wrapStream(native) {
|
|
119
128
|
return Object.freeze({
|
|
120
|
-
read: async () => await native.read(),
|
|
121
|
-
write: async (data) => await native.write(data),
|
|
122
|
-
closeWrite: async () => await native.closeWrite(),
|
|
123
|
-
stopSending: async () => await native.stopSending(),
|
|
124
|
-
reset: async () => await native.reset(),
|
|
129
|
+
read: async () => await nativeStreamCall(native.read()),
|
|
130
|
+
write: async (data) => await nativeStreamCall(native.write(data)),
|
|
131
|
+
closeWrite: async () => await nativeStreamCall(native.closeWrite()),
|
|
132
|
+
stopSending: async () => await nativeStreamCall(native.stopSending()),
|
|
133
|
+
reset: async () => await nativeStreamCall(native.reset()),
|
|
134
|
+
cancelPending: () => native.cancelPending?.(),
|
|
125
135
|
abort: () => native.abort(),
|
|
126
136
|
});
|
|
127
137
|
}
|
|
138
|
+
async function nativeStreamCall(operation) {
|
|
139
|
+
try {
|
|
140
|
+
return await operation;
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
const reason = error instanceof Error ? error.message : "";
|
|
144
|
+
switch (reason) {
|
|
145
|
+
case "reset": throw new CarrierError("reset", "raw QUIC stream reset", error);
|
|
146
|
+
case "canceled": throw new CarrierError("aborted", "raw QUIC stream operation canceled", error);
|
|
147
|
+
case "closed": throw new CarrierError("closed", "raw QUIC stream closed", error);
|
|
148
|
+
case "stream_failed": throw new CarrierError("closed", "raw QUIC stream operation closed", error);
|
|
149
|
+
default: throw error;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function nativeOperationError(error) {
|
|
154
|
+
const reason = error instanceof Error ? error.message : "";
|
|
155
|
+
switch (reason) {
|
|
156
|
+
case "canceled": return new CarrierError("aborted", "native transport operation canceled", error);
|
|
157
|
+
case "closed":
|
|
158
|
+
case "listener_closed":
|
|
159
|
+
case "stream_failed":
|
|
160
|
+
return new CarrierError("closed", "native transport operation closed", error);
|
|
161
|
+
default: return error;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
128
164
|
async function settle(operation, signal) {
|
|
129
165
|
if (signal?.aborted === true) {
|
|
130
166
|
operation.cancel();
|
|
131
167
|
throw new CarrierError("aborted", "native transport operation aborted");
|
|
132
168
|
}
|
|
133
|
-
if (signal === undefined)
|
|
134
|
-
|
|
169
|
+
if (signal === undefined) {
|
|
170
|
+
try {
|
|
171
|
+
return await operation.result();
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
throw nativeOperationError(error);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
135
177
|
return await new Promise((resolve, reject) => {
|
|
136
178
|
let settled = false;
|
|
137
179
|
const cleanup = () => signal.removeEventListener("abort", abort);
|
|
@@ -155,7 +197,7 @@ async function settle(operation, signal) {
|
|
|
155
197
|
return;
|
|
156
198
|
settled = true;
|
|
157
199
|
cleanup();
|
|
158
|
-
reject(error);
|
|
200
|
+
reject(nativeOperationError(error));
|
|
159
201
|
});
|
|
160
202
|
});
|
|
161
203
|
}
|
|
@@ -301,9 +301,11 @@ async function bridgeStreams(source, target, slots, signal, start) {
|
|
|
301
301
|
}
|
|
302
302
|
catch (error) {
|
|
303
303
|
release();
|
|
304
|
-
|
|
305
|
-
|
|
304
|
+
if (signal.aborted) {
|
|
305
|
+
incoming.abort(asError(error));
|
|
306
306
|
throw error;
|
|
307
|
+
}
|
|
308
|
+
await incoming.reset().catch(() => undefined);
|
|
307
309
|
continue;
|
|
308
310
|
}
|
|
309
311
|
start(spliceStreams(incoming, outgoing, signal, false).finally(release), false);
|
|
@@ -321,8 +323,13 @@ async function spliceStreams(left, right, signal, closePair) {
|
|
|
321
323
|
throw new Error("Flowersec tunnel control stream closed");
|
|
322
324
|
}
|
|
323
325
|
catch (error) {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
+
if (closePair || signal.aborted) {
|
|
327
|
+
left.abort(asError(error));
|
|
328
|
+
right.abort(asError(error));
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
await Promise.allSettled([left.reset(), right.reset()]);
|
|
332
|
+
}
|
|
326
333
|
throw error;
|
|
327
334
|
}
|
|
328
335
|
finally {
|
package/dist/proxy/runtime.js
CHANGED
|
@@ -352,8 +352,13 @@ export function createProxyRuntime(options) {
|
|
|
352
352
|
}
|
|
353
353
|
const headersOut = filterHeaders(response.headers ?? [], BASE_RESPONSE_HEADERS, responseExtra);
|
|
354
354
|
port.postMessage({ type: "flowersec-proxy:response_meta", status: response.status, headers: headersOut });
|
|
355
|
-
|
|
355
|
+
const chunks = readChunks(reader, maxChunkBytes, maxBodyBytes)[Symbol.asyncIterator]();
|
|
356
|
+
for (;;) {
|
|
356
357
|
await waitForCredit();
|
|
358
|
+
const next = await chunks.next();
|
|
359
|
+
if (next.done)
|
|
360
|
+
break;
|
|
361
|
+
const chunk = next.value;
|
|
357
362
|
const data = chunk.slice().buffer;
|
|
358
363
|
port.postMessage({ type: "flowersec-proxy:response_chunk", data }, [data]);
|
|
359
364
|
}
|
|
@@ -121,8 +121,11 @@ function serviceWorkerMain(config) {
|
|
|
121
121
|
const target = config.windowTarget === "request_client"
|
|
122
122
|
? source
|
|
123
123
|
: runtimeClientId === "" ? null : await worker.clients.get(runtimeClientId);
|
|
124
|
-
if (target === null)
|
|
124
|
+
if (target === null) {
|
|
125
|
+
if (config.windowTarget === "registered_runtime")
|
|
126
|
+
runtimeClientId = "";
|
|
125
127
|
return new Response("proxy runtime unavailable", { status: 503 });
|
|
128
|
+
}
|
|
126
129
|
let body;
|
|
127
130
|
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
128
131
|
const requestBody = await request.clone().arrayBuffer();
|
|
@@ -188,17 +191,25 @@ function serviceWorkerMain(config) {
|
|
|
188
191
|
};
|
|
189
192
|
channel.port1.onmessageerror = () => finishError(502, "proxy request failed");
|
|
190
193
|
const headers = Array.from(request.headers.entries()).map(([name, value]) => ({ name, value }));
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
194
|
+
try {
|
|
195
|
+
target.postMessage({
|
|
196
|
+
type: config.windowClientMessageType,
|
|
197
|
+
req: {
|
|
198
|
+
id: `${Date.now()}-${Math.random().toString(16).slice(2)}`,
|
|
199
|
+
method: request.method,
|
|
200
|
+
path,
|
|
201
|
+
headers,
|
|
202
|
+
response_flow_control: "chunk_credit_v2",
|
|
203
|
+
...(body === undefined ? {} : { body }),
|
|
204
|
+
},
|
|
205
|
+
}, [channel.port2, ...(body === undefined ? [] : [body])]);
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
if (config.windowTarget === "registered_runtime")
|
|
209
|
+
runtimeClientId = "";
|
|
210
|
+
channel.port1.close();
|
|
211
|
+
resolve(new Response("proxy runtime unavailable", { status: 503 }));
|
|
212
|
+
}
|
|
202
213
|
});
|
|
203
214
|
const injection = config.injectHTML;
|
|
204
215
|
if (injection === null || pathMatches(url.pathname, injection.excludePathPrefixes ?? []))
|
|
@@ -490,9 +490,9 @@ async function raceAbort(promise, signal) {
|
|
|
490
490
|
if (signal === undefined)
|
|
491
491
|
return await promise;
|
|
492
492
|
if (signal.aborted)
|
|
493
|
-
throw signal.reason;
|
|
493
|
+
throw abortedCarrierError(signal.reason);
|
|
494
494
|
return await new Promise((resolve, reject) => {
|
|
495
|
-
const abort = () => reject(signal.reason);
|
|
495
|
+
const abort = () => reject(abortedCarrierError(signal.reason));
|
|
496
496
|
signal.addEventListener("abort", abort, { once: true });
|
|
497
497
|
void promise.then(resolve, reject).finally(() => signal.removeEventListener("abort", abort));
|
|
498
498
|
});
|
package/dist/v2/carrier.d.ts
CHANGED
|
@@ -52,6 +52,8 @@ export type NativeCarrierStreamV2 = Readonly<{
|
|
|
52
52
|
closeWrite(): Promise<void>;
|
|
53
53
|
stopSending(): Promise<void>;
|
|
54
54
|
reset(): Promise<void>;
|
|
55
|
+
/** Cancels pending local primitives without initiating peer-visible teardown. */
|
|
56
|
+
cancelPending?(): void;
|
|
55
57
|
/** See {@link CarrierStreamV2.abort}. */
|
|
56
58
|
abort(error?: Error): void;
|
|
57
59
|
}>;
|
package/dist/v2/carrier.js
CHANGED
|
@@ -353,11 +353,11 @@ class NativeCarrierStreamAdapter {
|
|
|
353
353
|
this.native = native;
|
|
354
354
|
}
|
|
355
355
|
async read(options = {}) {
|
|
356
|
-
return await abortable(this.native.read(), options.signal, () => this.
|
|
356
|
+
return await abortable(this.native.read(), options.signal, () => this.cancelPending());
|
|
357
357
|
}
|
|
358
358
|
async write(data, options = {}) {
|
|
359
359
|
throwIfAborted(options.signal);
|
|
360
|
-
return await abortable(this.native.write(data), options.signal, () => this.
|
|
360
|
+
return await abortable(this.native.write(data), options.signal, () => this.cancelPending());
|
|
361
361
|
}
|
|
362
362
|
async closeWrite() {
|
|
363
363
|
await this.native.closeWrite();
|
|
@@ -371,6 +371,12 @@ class NativeCarrierStreamAdapter {
|
|
|
371
371
|
abort(error) {
|
|
372
372
|
this.native.abort(error);
|
|
373
373
|
}
|
|
374
|
+
cancelPending() {
|
|
375
|
+
if (this.native.cancelPending !== undefined)
|
|
376
|
+
this.native.cancelPending();
|
|
377
|
+
else
|
|
378
|
+
this.native.abort();
|
|
379
|
+
}
|
|
374
380
|
}
|
|
375
381
|
async function abortable(promise, signal, onAbort) {
|
|
376
382
|
if (signal === undefined)
|
package/dist/v2/session.d.ts
CHANGED
|
@@ -104,12 +104,15 @@ export declare class SessionV2 implements SessionV2Contract {
|
|
|
104
104
|
private sessionCloseCommitted;
|
|
105
105
|
private readonly peerSessionClose;
|
|
106
106
|
private readonly streams;
|
|
107
|
+
private readonly releasedStreams;
|
|
108
|
+
private readonly releasedStreamCleanup;
|
|
107
109
|
private readonly peerLedger;
|
|
108
110
|
private readonly outboundLedger;
|
|
109
111
|
private readonly incoming;
|
|
110
112
|
private readonly outboundPermits;
|
|
111
113
|
private readonly inboundPermits;
|
|
112
114
|
private readonly pings;
|
|
115
|
+
private readonly backgroundTasks;
|
|
113
116
|
private nextPing;
|
|
114
117
|
private readonly rpcActivation;
|
|
115
118
|
private rpcStreamPromise;
|
|
@@ -153,6 +156,7 @@ export declare class SessionV2 implements SessionV2Contract {
|
|
|
153
156
|
}>>;
|
|
154
157
|
localReset(stream: EncryptedStreamV2, error: Error): Promise<void>;
|
|
155
158
|
releaseStream(stream: EncryptedStreamV2): void;
|
|
159
|
+
private confirmControlDelivery;
|
|
156
160
|
private openLogicalStream;
|
|
157
161
|
private ensureRPCStream;
|
|
158
162
|
private acceptCarrierLoop;
|
|
@@ -184,6 +188,7 @@ export declare class SessionV2 implements SessionV2Contract {
|
|
|
184
188
|
private validGoAwayBoundary;
|
|
185
189
|
private sendGoAway;
|
|
186
190
|
private receiveGoAway;
|
|
191
|
+
private trackBackground;
|
|
187
192
|
private localOpeningAllowedAfterGoAway;
|
|
188
193
|
private acceptsPeerStreamAfterGoAway;
|
|
189
194
|
private cleanupEpochRoots;
|
|
@@ -224,6 +229,7 @@ declare class EncryptedStreamV2 implements ByteStreamV2 {
|
|
|
224
229
|
private remoteFIN;
|
|
225
230
|
private pumpStarted;
|
|
226
231
|
private permitReleased;
|
|
232
|
+
private closeWriteTask;
|
|
227
233
|
private pendingSendRekey;
|
|
228
234
|
private lastSendRekeyACK;
|
|
229
235
|
private receiveRekey;
|
|
@@ -235,6 +241,7 @@ declare class EncryptedStreamV2 implements ByteStreamV2 {
|
|
|
235
241
|
close(): Promise<void>;
|
|
236
242
|
send(type: InnerTypeV2, payload: Uint8Array, signal?: AbortSignal): Promise<void>;
|
|
237
243
|
canRekeySend(): boolean;
|
|
244
|
+
hasRemoteFIN(): boolean;
|
|
238
245
|
startSendRekey(transition: bigint, epoch: number): Readonly<{
|
|
239
246
|
armed: Deferred<void>;
|
|
240
247
|
done: Deferred<void>;
|
|
@@ -245,6 +252,7 @@ declare class EncryptedStreamV2 implements ByteStreamV2 {
|
|
|
245
252
|
markOpen(): void;
|
|
246
253
|
markTerminal(error: Error): boolean;
|
|
247
254
|
peerReset(error: Error): void;
|
|
255
|
+
carrierReset(): void;
|
|
248
256
|
abort(error?: Error): void;
|
|
249
257
|
releasePermit(): void;
|
|
250
258
|
private pump;
|
|
@@ -274,12 +282,15 @@ declare class ByteQueue {
|
|
|
274
282
|
private closed;
|
|
275
283
|
private error;
|
|
276
284
|
private readonly waiters;
|
|
285
|
+
private readonly capacityWaiters;
|
|
277
286
|
constructor(limit: number);
|
|
278
287
|
push(chunk: Uint8Array): void;
|
|
279
288
|
close(): void;
|
|
280
289
|
fail(error: Error): void;
|
|
290
|
+
waitForCapacity(required: number): Promise<void>;
|
|
281
291
|
read(signal?: AbortSignal): Promise<Uint8Array | null>;
|
|
282
292
|
private wake;
|
|
293
|
+
private wakeCapacity;
|
|
283
294
|
}
|
|
284
295
|
type Deferred<T> = Readonly<{
|
|
285
296
|
promise: Promise<T>;
|
package/dist/v2/session.js
CHANGED
|
@@ -12,6 +12,7 @@ const MAX_BUFFERED_STREAM_BYTES = 4 * 1024 * 1024;
|
|
|
12
12
|
const RESERVED_RPC_KIND = "flowersec.rpc.v2";
|
|
13
13
|
const DEFAULT_IDLE_TIMEOUT_MS = 60_000;
|
|
14
14
|
const DEFAULT_CLOSE_TIMEOUT_MS = 2_000;
|
|
15
|
+
const DEFAULT_CONTROL_CONFIRM_TIMEOUT_MS = 2_000;
|
|
15
16
|
export class SessionV2Error extends Error {
|
|
16
17
|
code;
|
|
17
18
|
constructor(code, message) {
|
|
@@ -108,12 +109,15 @@ export class SessionV2 {
|
|
|
108
109
|
sessionCloseCommitted = false;
|
|
109
110
|
peerSessionClose = deferred();
|
|
110
111
|
streams = new Map();
|
|
112
|
+
releasedStreams = new Map();
|
|
113
|
+
releasedStreamCleanup = releasedStreamCleanup(this.releasedStreams);
|
|
111
114
|
peerLedger;
|
|
112
115
|
outboundLedger;
|
|
113
116
|
incoming = new AsyncQueue();
|
|
114
117
|
outboundPermits;
|
|
115
118
|
inboundPermits;
|
|
116
119
|
pings = new Map();
|
|
120
|
+
backgroundTasks = new Set();
|
|
117
121
|
nextPing = 1n;
|
|
118
122
|
rpcActivation = deferred();
|
|
119
123
|
rpcStreamPromise;
|
|
@@ -305,23 +309,51 @@ export class SessionV2 {
|
|
|
305
309
|
async localReset(stream, error) {
|
|
306
310
|
if (!stream.markTerminal(error))
|
|
307
311
|
return;
|
|
312
|
+
this.releaseStream(stream);
|
|
313
|
+
let controlConfirmed = false;
|
|
308
314
|
try {
|
|
309
315
|
await this.sendControl(InnerTypeV2.StreamReset, idReason(stream.id, 6));
|
|
310
316
|
this.commitLocalReset(stream.id);
|
|
317
|
+
if (stream.hasRemoteFIN())
|
|
318
|
+
controlConfirmed = await this.confirmControlDelivery();
|
|
311
319
|
}
|
|
312
320
|
catch (cause) {
|
|
313
321
|
this.fail(asError(cause));
|
|
314
322
|
}
|
|
315
|
-
|
|
316
|
-
|
|
323
|
+
if (controlConfirmed) {
|
|
324
|
+
await stream.carrier.closeWrite().catch(async () => await stream.carrier.reset().catch(() => undefined));
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
await stream.carrier.reset().catch(() => undefined);
|
|
328
|
+
}
|
|
317
329
|
}
|
|
318
330
|
releaseStream(stream) {
|
|
331
|
+
if (stream.terminalError !== undefined) {
|
|
332
|
+
this.releasedStreams.delete(stream.id);
|
|
333
|
+
this.releasedStreamCleanup.unregister(stream);
|
|
334
|
+
}
|
|
319
335
|
if (this.streams.get(stream.id) !== stream)
|
|
320
336
|
return;
|
|
321
337
|
this.streams.delete(stream.id);
|
|
338
|
+
if (stream.terminalError === undefined) {
|
|
339
|
+
this.releasedStreams.set(stream.id, new WeakRef(stream));
|
|
340
|
+
this.releasedStreamCleanup.register(stream, stream.id, stream);
|
|
341
|
+
}
|
|
322
342
|
stream.releasePermit();
|
|
323
343
|
this.cleanupEpochRoots();
|
|
324
344
|
}
|
|
345
|
+
async confirmControlDelivery() {
|
|
346
|
+
const nonce = this.nextPing++;
|
|
347
|
+
const pending = deferred();
|
|
348
|
+
this.pings.set(nonce, pending);
|
|
349
|
+
try {
|
|
350
|
+
await this.sendControl(InnerTypeV2.Ping, u64(nonce));
|
|
351
|
+
return await resolveWithin(pending.promise, DEFAULT_CONTROL_CONFIRM_TIMEOUT_MS);
|
|
352
|
+
}
|
|
353
|
+
finally {
|
|
354
|
+
this.pings.delete(nonce);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
325
357
|
async openLogicalStream(kind, options, internal) {
|
|
326
358
|
this.assertOpen();
|
|
327
359
|
throwIfAborted(options.signal);
|
|
@@ -599,7 +631,21 @@ export class SessionV2 {
|
|
|
599
631
|
const { id, reason } = parseIDReason(record.payload);
|
|
600
632
|
if (id === 0n || reason === 0)
|
|
601
633
|
throw protocolError("invalid STREAM_RESET");
|
|
602
|
-
this.streams.get(id)
|
|
634
|
+
const stream = this.streams.get(id);
|
|
635
|
+
if (stream !== undefined) {
|
|
636
|
+
stream.peerReset(new SessionV2Error("stream_reset", "logical stream reset by peer"));
|
|
637
|
+
}
|
|
638
|
+
else {
|
|
639
|
+
const released = this.releasedStreams.get(id);
|
|
640
|
+
if (released !== undefined) {
|
|
641
|
+
this.releasedStreams.delete(id);
|
|
642
|
+
const releasedStream = released.deref();
|
|
643
|
+
if (releasedStream !== undefined) {
|
|
644
|
+
this.releasedStreamCleanup.unregister(releasedStream);
|
|
645
|
+
releasedStream.markTerminal(new SessionV2Error("stream_reset", "logical stream reset by peer"));
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
603
649
|
if (this.isLocalLogicalID(id)) {
|
|
604
650
|
this.outboundLedger.peerReset(id);
|
|
605
651
|
this.notifyOutboundFrontierChanged();
|
|
@@ -907,9 +953,16 @@ export class SessionV2 {
|
|
|
907
953
|
this.receivedGoAwayLastAccepted = lastAccepted;
|
|
908
954
|
this.receivedGoAwayReason = reason;
|
|
909
955
|
const excluded = [...this.streams.values()].filter((stream) => this.isLocalLogicalID(stream.id) && stream.id > lastAccepted);
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
}
|
|
956
|
+
for (const stream of excluded) {
|
|
957
|
+
this.trackBackground(this.localReset(stream, new SessionV2Error("going_away", "logical stream exceeds peer GOAWAY boundary")));
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
trackBackground(task) {
|
|
961
|
+
this.backgroundTasks.add(task);
|
|
962
|
+
void task.catch((error) => {
|
|
963
|
+
if (this.lifecycle !== "closed")
|
|
964
|
+
this.fail(asError(error));
|
|
965
|
+
}).finally(() => this.backgroundTasks.delete(task));
|
|
913
966
|
}
|
|
914
967
|
localOpeningAllowedAfterGoAway(id) {
|
|
915
968
|
return !this.receivedGoAway || id <= this.receivedGoAwayLastAccepted;
|
|
@@ -995,6 +1048,12 @@ export class SessionV2 {
|
|
|
995
1048
|
this.rpc.close();
|
|
996
1049
|
for (const stream of [...this.streams.values()])
|
|
997
1050
|
stream.peerReset(error);
|
|
1051
|
+
for (const released of this.releasedStreams.values()) {
|
|
1052
|
+
const stream = released.deref();
|
|
1053
|
+
if (stream !== undefined)
|
|
1054
|
+
this.releasedStreamCleanup.unregister(stream);
|
|
1055
|
+
}
|
|
1056
|
+
this.releasedStreams.clear();
|
|
998
1057
|
this.wipeAllRoots();
|
|
999
1058
|
this.h3.fill(0);
|
|
1000
1059
|
if (abortCarrier && !normalPeerCarrierClose)
|
|
@@ -1032,6 +1091,7 @@ class EncryptedStreamV2 {
|
|
|
1032
1091
|
remoteFIN = false;
|
|
1033
1092
|
pumpStarted = false;
|
|
1034
1093
|
permitReleased = false;
|
|
1094
|
+
closeWriteTask;
|
|
1035
1095
|
pendingSendRekey;
|
|
1036
1096
|
lastSendRekeyACK;
|
|
1037
1097
|
receiveRekey;
|
|
@@ -1064,9 +1124,16 @@ class EncryptedStreamV2 {
|
|
|
1064
1124
|
throw this.terminalError;
|
|
1065
1125
|
const copy = payload.slice();
|
|
1066
1126
|
await this.enqueueSend(async () => {
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1127
|
+
try {
|
|
1128
|
+
for (let offset = 0; offset < copy.length; offset += MAX_DATA_BYTES) {
|
|
1129
|
+
throwIfAborted(options.signal);
|
|
1130
|
+
await this.session.sendStreamRecord(this, InnerTypeV2.Data, copy.subarray(offset, Math.min(copy.length, offset + MAX_DATA_BYTES)), options.signal);
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
catch (error) {
|
|
1134
|
+
const normalized = asError(error);
|
|
1135
|
+
void this.session.localReset(this, normalized);
|
|
1136
|
+
throw normalized;
|
|
1070
1137
|
}
|
|
1071
1138
|
});
|
|
1072
1139
|
return copy.length;
|
|
@@ -1074,16 +1141,26 @@ class EncryptedStreamV2 {
|
|
|
1074
1141
|
async closeWrite() {
|
|
1075
1142
|
await this.opened.promise;
|
|
1076
1143
|
await this.waitSendRekey();
|
|
1077
|
-
if (this.localFIN
|
|
1144
|
+
if (this.localFIN)
|
|
1078
1145
|
return;
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1146
|
+
if (this.terminalError !== undefined)
|
|
1147
|
+
throw this.terminalError;
|
|
1148
|
+
this.closeWriteTask ??= this.enqueueSend(async () => {
|
|
1149
|
+
try {
|
|
1150
|
+
if (this.localFIN)
|
|
1151
|
+
return;
|
|
1152
|
+
await this.session.sendStreamRecord(this, InnerTypeV2.FIN, new Uint8Array());
|
|
1153
|
+
await this.carrier.closeWrite();
|
|
1154
|
+
this.localFIN = true;
|
|
1155
|
+
this.releaseIfClean();
|
|
1156
|
+
}
|
|
1157
|
+
catch (error) {
|
|
1158
|
+
const normalized = asError(error);
|
|
1159
|
+
void this.session.localReset(this, normalized);
|
|
1160
|
+
throw normalized;
|
|
1161
|
+
}
|
|
1086
1162
|
});
|
|
1163
|
+
await this.closeWriteTask;
|
|
1087
1164
|
}
|
|
1088
1165
|
async reset() {
|
|
1089
1166
|
await this.session.localReset(this, new SessionV2Error("stream_reset", "logical stream reset"));
|
|
@@ -1097,6 +1174,9 @@ class EncryptedStreamV2 {
|
|
|
1097
1174
|
canRekeySend() {
|
|
1098
1175
|
return this.terminalError === undefined && this.opened.settled && !this.localFIN;
|
|
1099
1176
|
}
|
|
1177
|
+
hasRemoteFIN() {
|
|
1178
|
+
return this.remoteFIN;
|
|
1179
|
+
}
|
|
1100
1180
|
startSendRekey(transition, epoch) {
|
|
1101
1181
|
if (this.pendingSendRekey !== undefined)
|
|
1102
1182
|
throw protocolError("overlapping stream rekey");
|
|
@@ -1157,6 +1237,11 @@ class EncryptedStreamV2 {
|
|
|
1157
1237
|
this.carrier.abort(error);
|
|
1158
1238
|
this.session.releaseStream(this);
|
|
1159
1239
|
}
|
|
1240
|
+
carrierReset() {
|
|
1241
|
+
if (!this.markTerminal(new SessionV2Error("stream_reset", "logical stream reset by peer")))
|
|
1242
|
+
return;
|
|
1243
|
+
this.session.releaseStream(this);
|
|
1244
|
+
}
|
|
1160
1245
|
abort(error = new SessionV2Error("closed", "logical stream aborted")) {
|
|
1161
1246
|
this.peerReset(error);
|
|
1162
1247
|
}
|
|
@@ -1196,6 +1281,7 @@ class EncryptedStreamV2 {
|
|
|
1196
1281
|
case InnerTypeV2.Data:
|
|
1197
1282
|
if (this.remoteFIN)
|
|
1198
1283
|
throw protocolError("DATA after FIN");
|
|
1284
|
+
await this.data.waitForCapacity(record.payload.length);
|
|
1199
1285
|
this.data.push(record.payload);
|
|
1200
1286
|
break;
|
|
1201
1287
|
case InnerTypeV2.FIN:
|
|
@@ -1218,6 +1304,10 @@ class EncryptedStreamV2 {
|
|
|
1218
1304
|
}
|
|
1219
1305
|
catch (error) {
|
|
1220
1306
|
const normalized = asError(error);
|
|
1307
|
+
if (normalized instanceof CarrierError && normalized.code === "reset") {
|
|
1308
|
+
this.carrierReset();
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1221
1311
|
if (this.remoteFIN && /closed|EOF/i.test(normalized.message)) {
|
|
1222
1312
|
this.releaseIfClean();
|
|
1223
1313
|
return;
|
|
@@ -1461,6 +1551,7 @@ class ByteQueue {
|
|
|
1461
1551
|
closed = false;
|
|
1462
1552
|
error;
|
|
1463
1553
|
waiters = new Set();
|
|
1554
|
+
capacityWaiters = new Set();
|
|
1464
1555
|
constructor(limit) {
|
|
1465
1556
|
this.limit = limit;
|
|
1466
1557
|
}
|
|
@@ -1485,6 +1576,26 @@ class ByteQueue {
|
|
|
1485
1576
|
this.head = 0;
|
|
1486
1577
|
this.bytes = 0;
|
|
1487
1578
|
this.wake(error);
|
|
1579
|
+
this.wakeCapacity(error);
|
|
1580
|
+
}
|
|
1581
|
+
async waitForCapacity(required) {
|
|
1582
|
+
if (!Number.isInteger(required) || required < 0 || required > this.limit) {
|
|
1583
|
+
throw new RangeError("invalid stream receive capacity request");
|
|
1584
|
+
}
|
|
1585
|
+
while (this.bytes + required > this.limit) {
|
|
1586
|
+
if (this.error !== undefined)
|
|
1587
|
+
throw this.error;
|
|
1588
|
+
if (this.closed)
|
|
1589
|
+
return;
|
|
1590
|
+
const waiter = deferred();
|
|
1591
|
+
this.capacityWaiters.add(waiter);
|
|
1592
|
+
try {
|
|
1593
|
+
await waiter.promise;
|
|
1594
|
+
}
|
|
1595
|
+
finally {
|
|
1596
|
+
this.capacityWaiters.delete(waiter);
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1488
1599
|
}
|
|
1489
1600
|
async read(signal) {
|
|
1490
1601
|
while (true) {
|
|
@@ -1493,6 +1604,7 @@ class ByteQueue {
|
|
|
1493
1604
|
if (this.head < this.chunks.length) {
|
|
1494
1605
|
const chunk = this.chunks[this.head++];
|
|
1495
1606
|
this.bytes -= chunk.length;
|
|
1607
|
+
this.wakeCapacity();
|
|
1496
1608
|
if (this.head > 1_024 && this.head * 2 > this.chunks.length) {
|
|
1497
1609
|
this.chunks.splice(0, this.head);
|
|
1498
1610
|
this.head = 0;
|
|
@@ -1516,6 +1628,11 @@ class ByteQueue {
|
|
|
1516
1628
|
error === undefined ? waiter.resolve() : waiter.reject(error);
|
|
1517
1629
|
this.waiters.clear();
|
|
1518
1630
|
}
|
|
1631
|
+
wakeCapacity(error) {
|
|
1632
|
+
for (const waiter of [...this.capacityWaiters])
|
|
1633
|
+
error === undefined ? waiter.resolve() : waiter.reject(error);
|
|
1634
|
+
this.capacityWaiters.clear();
|
|
1635
|
+
}
|
|
1519
1636
|
}
|
|
1520
1637
|
class AsyncSemaphore {
|
|
1521
1638
|
available;
|
|
@@ -1742,6 +1859,12 @@ async function settleWithin(promise, timeoutMs) {
|
|
|
1742
1859
|
void promise.then(() => { clearTimeout(timer); resolve(true); }, () => { clearTimeout(timer); resolve(true); });
|
|
1743
1860
|
});
|
|
1744
1861
|
}
|
|
1862
|
+
async function resolveWithin(promise, timeoutMs) {
|
|
1863
|
+
return await new Promise((resolve) => {
|
|
1864
|
+
const timer = setTimeout(() => resolve(false), timeoutMs);
|
|
1865
|
+
void promise.then(() => { clearTimeout(timer); resolve(true); }, () => { clearTimeout(timer); resolve(false); });
|
|
1866
|
+
});
|
|
1867
|
+
}
|
|
1745
1868
|
function sessionIdleTimeoutMs(config) {
|
|
1746
1869
|
const timeoutMs = config.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
|
|
1747
1870
|
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 0) {
|
|
@@ -1845,6 +1968,13 @@ function abortedError() {
|
|
|
1845
1968
|
function abortReason(signal) {
|
|
1846
1969
|
return signal.reason instanceof Error ? signal.reason : abortedError();
|
|
1847
1970
|
}
|
|
1971
|
+
function releasedStreamCleanup(streams) {
|
|
1972
|
+
return new FinalizationRegistry((id) => {
|
|
1973
|
+
const stream = streams.get(id);
|
|
1974
|
+
if (stream !== undefined && stream.deref() === undefined)
|
|
1975
|
+
streams.delete(id);
|
|
1976
|
+
});
|
|
1977
|
+
}
|
|
1848
1978
|
function createSessionDeadline(config, phase) {
|
|
1849
1979
|
const deadlines = config.deadlines;
|
|
1850
1980
|
const timeoutMs = phase === "establish"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@floegence/flowersec-core",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.10",
|
|
4
4
|
"description": "Flowersec core TypeScript library for carrier-neutral encrypted sessions and multiplexed streams.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"ws": "^8.21.2"
|
|
78
78
|
},
|
|
79
79
|
"optionalDependencies": {
|
|
80
|
-
"@floegence/flowersec-node-native": "2.3.
|
|
80
|
+
"@floegence/flowersec-node-native": "2.3.10"
|
|
81
81
|
},
|
|
82
82
|
"devDependencies": {
|
|
83
83
|
"@playwright/test": "1.62.1",
|
|
@@ -95,5 +95,5 @@
|
|
|
95
95
|
"vite": "^8.2.1",
|
|
96
96
|
"vitest": "4.1.10"
|
|
97
97
|
},
|
|
98
|
-
"flowersecSourceCommit": "
|
|
98
|
+
"flowersecSourceCommit": "67e5ff521e77982e4f57e8ba5eb858df3e48c886"
|
|
99
99
|
}
|
package/sbom/cyclonedx.json
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"bomFormat": "CycloneDX",
|
|
3
3
|
"specVersion": "1.5",
|
|
4
|
-
"serialNumber": "urn:uuid:
|
|
4
|
+
"serialNumber": "urn:uuid:2ff806a3-4e32-5ae7-867b-3dc94826ed55",
|
|
5
5
|
"version": 1,
|
|
6
6
|
"metadata": {
|
|
7
7
|
"component": {
|
|
8
8
|
"type": "library",
|
|
9
9
|
"name": "@floegence/flowersec-core",
|
|
10
|
-
"version": "2.3.
|
|
11
|
-
"purl": "pkg:npm/%40floegence/flowersec-core@2.3.
|
|
12
|
-
"bom-ref": "pkg:npm/%40floegence/flowersec-core@2.3.
|
|
10
|
+
"version": "2.3.10",
|
|
11
|
+
"purl": "pkg:npm/%40floegence/flowersec-core@2.3.10",
|
|
12
|
+
"bom-ref": "pkg:npm/%40floegence/flowersec-core@2.3.10"
|
|
13
13
|
},
|
|
14
14
|
"properties": [
|
|
15
15
|
{
|
|
16
16
|
"name": "flowersec:source-inventory-sha256",
|
|
17
|
-
"value": "
|
|
17
|
+
"value": "36475d4f06355a764285462713f086777546ed34fe2ea5911961d9bb9a3037f1"
|
|
18
18
|
}
|
|
19
19
|
]
|
|
20
20
|
},
|
|
@@ -190,7 +190,7 @@
|
|
|
190
190
|
],
|
|
191
191
|
"dependencies": [
|
|
192
192
|
{
|
|
193
|
-
"ref": "pkg:npm/%40floegence/flowersec-core@2.3.
|
|
193
|
+
"ref": "pkg:npm/%40floegence/flowersec-core@2.3.10",
|
|
194
194
|
"dependsOn": [
|
|
195
195
|
"pkg:npm/%40noble/ciphers@2.3.0",
|
|
196
196
|
"pkg:npm/%40noble/curves@2.3.0",
|
package/sbom/spdx.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"dataLicense": "CC0-1.0",
|
|
4
4
|
"SPDXID": "SPDXRef-DOCUMENT",
|
|
5
5
|
"name": "flowersec-ts",
|
|
6
|
-
"documentNamespace": "https://github.com/floegence/flowersec/sbom/flowersec-ts/
|
|
6
|
+
"documentNamespace": "https://github.com/floegence/flowersec/sbom/flowersec-ts/36475d4f06355a764285462713f086777546ed34fe2ea5911961d9bb9a3037f1",
|
|
7
7
|
"creationInfo": {
|
|
8
8
|
"created": "1970-01-01T00:00:00Z",
|
|
9
9
|
"creators": [
|
|
@@ -13,19 +13,19 @@
|
|
|
13
13
|
"packages": [
|
|
14
14
|
{
|
|
15
15
|
"name": "@floegence/flowersec-core",
|
|
16
|
-
"SPDXID": "SPDXRef-Package-
|
|
17
|
-
"versionInfo": "2.3.
|
|
16
|
+
"SPDXID": "SPDXRef-Package-bd5ff7d2005890830d32",
|
|
17
|
+
"versionInfo": "2.3.10",
|
|
18
18
|
"downloadLocation": "NOASSERTION",
|
|
19
19
|
"filesAnalyzed": false,
|
|
20
20
|
"licenseConcluded": "NOASSERTION",
|
|
21
21
|
"licenseDeclared": "NOASSERTION",
|
|
22
22
|
"copyrightText": "NOASSERTION",
|
|
23
|
-
"comment": "Flowersec source inventory SHA-256:
|
|
23
|
+
"comment": "Flowersec source inventory SHA-256: 36475d4f06355a764285462713f086777546ed34fe2ea5911961d9bb9a3037f1",
|
|
24
24
|
"externalRefs": [
|
|
25
25
|
{
|
|
26
26
|
"referenceCategory": "PACKAGE-MANAGER",
|
|
27
27
|
"referenceType": "purl",
|
|
28
|
-
"referenceLocator": "pkg:npm/%40floegence/flowersec-core@2.3.
|
|
28
|
+
"referenceLocator": "pkg:npm/%40floegence/flowersec-core@2.3.10"
|
|
29
29
|
}
|
|
30
30
|
]
|
|
31
31
|
},
|
|
@@ -136,30 +136,30 @@
|
|
|
136
136
|
{
|
|
137
137
|
"spdxElementId": "SPDXRef-DOCUMENT",
|
|
138
138
|
"relationshipType": "DESCRIBES",
|
|
139
|
-
"relatedSpdxElement": "SPDXRef-Package-
|
|
139
|
+
"relatedSpdxElement": "SPDXRef-Package-bd5ff7d2005890830d32"
|
|
140
140
|
},
|
|
141
141
|
{
|
|
142
|
-
"spdxElementId": "SPDXRef-Package-
|
|
142
|
+
"spdxElementId": "SPDXRef-Package-bd5ff7d2005890830d32",
|
|
143
143
|
"relationshipType": "DEPENDS_ON",
|
|
144
144
|
"relatedSpdxElement": "SPDXRef-Package-5ce913a03239b02770fb"
|
|
145
145
|
},
|
|
146
146
|
{
|
|
147
|
-
"spdxElementId": "SPDXRef-Package-
|
|
147
|
+
"spdxElementId": "SPDXRef-Package-bd5ff7d2005890830d32",
|
|
148
148
|
"relationshipType": "DEPENDS_ON",
|
|
149
149
|
"relatedSpdxElement": "SPDXRef-Package-01009adf60db02c13634"
|
|
150
150
|
},
|
|
151
151
|
{
|
|
152
|
-
"spdxElementId": "SPDXRef-Package-
|
|
152
|
+
"spdxElementId": "SPDXRef-Package-bd5ff7d2005890830d32",
|
|
153
153
|
"relationshipType": "DEPENDS_ON",
|
|
154
154
|
"relatedSpdxElement": "SPDXRef-Package-8815b117c8a1d5f7eeb0"
|
|
155
155
|
},
|
|
156
156
|
{
|
|
157
|
-
"spdxElementId": "SPDXRef-Package-
|
|
157
|
+
"spdxElementId": "SPDXRef-Package-bd5ff7d2005890830d32",
|
|
158
158
|
"relationshipType": "DEPENDS_ON",
|
|
159
159
|
"relatedSpdxElement": "SPDXRef-Package-f8b45a289df643042b94"
|
|
160
160
|
},
|
|
161
161
|
{
|
|
162
|
-
"spdxElementId": "SPDXRef-Package-
|
|
162
|
+
"spdxElementId": "SPDXRef-Package-bd5ff7d2005890830d32",
|
|
163
163
|
"relationshipType": "DEPENDS_ON",
|
|
164
164
|
"relatedSpdxElement": "SPDXRef-Package-b779412f685822663496"
|
|
165
165
|
},
|