@floegence/flowersec-core 2.1.0 → 2.3.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 CHANGED
@@ -2,19 +2,19 @@
2
2
 
3
3
  `@floegence/flowersec-core` is the ESM-only Flowersec v2 SDK for browsers and Node.js. Its public package surface is limited to the root, `/browser`, `/node`, and `/proxy` entrypoints.
4
4
 
5
- Flowersec 2.1.0 is the coordinated TypeScript SDK release.
5
+ Flowersec 2.3.0 is the coordinated TypeScript SDK release.
6
6
 
7
7
  ## Install
8
8
 
9
9
  ```bash
10
- npm install @floegence/flowersec-core@2.1.0
10
+ npm install @floegence/flowersec-core@2.3.0
11
11
  ```
12
12
 
13
13
  ## Public API
14
14
 
15
15
  - `@floegence/flowersec-core` exports the portable artifact, lease, session, stream, RPC, stream-metadata, and connection-controller API, plus profile-owned unreliable messages when negotiated.
16
16
  - `@floegence/flowersec-core/browser` adds `connect(...)`, `createConnectionController(...)`, and their options.
17
- - `@floegence/flowersec-core/node` adds `connect(...)`, `createConnectionController(...)`, and their options.
17
+ - `@floegence/flowersec-core/node` adds `connect(...)`, `createConnectionController(...)`, `createAcceptor(...)`, `SessionHandlers`, and `AcceptedSession`.
18
18
  - `@floegence/flowersec-core/proxy` adds the `Session`-based HTTP/WebSocket runtime, Service Worker and controller/app-window bridges, strict `proxy.runtime@2` validation, and `connectProxyBrowser(...)` composition.
19
19
 
20
20
  The root type exports are:
@@ -52,8 +52,10 @@ carrier-neutral sessions, RPC, reliable streams, redacted public errors, and
52
52
  the optional single-owner `ConnectionController`. Callers should not compare
53
53
  raw TypeScript error-code strings with other SDKs.
54
54
 
55
- Only this portable core is required to align across languages. Complete SDK
56
- profiles and language conveniences intentionally differ by runtime.
55
+ Portable core, connection control, session/RPC/stream lifecycle, accepted-session
56
+ workflows, and published consumer workflows align across every applicable SDK.
57
+ Platform-limited profiles are unsupported only with an explicit alternative
58
+ boundary and executable test ID in `stability/language_capabilities.json`.
57
59
 
58
60
  The TypeScript SDK profile is split by entrypoint: browsers own WebSocket and
59
61
  WebTransport dialing, while Node.js owns WebSocket and WebTransport dialing through
@@ -1,6 +1,7 @@
1
1
  import { AdmissionStatusV2, type ArtifactV2, type DecodedFSB2RequestV2 } from "../v2/artifact.js";
2
2
  import { type NativeCarrierSessionV2 } from "../v2/carrier.js";
3
3
  import type { SessionProtocolRuntimeV2, SessionV2 as InternalSessionV2 } from "../v2/session.js";
4
+ import type { RpcRouter } from "../rpc/server.js";
4
5
  export type AdmissionDecisionV2 = Readonly<{
5
6
  accepted: true;
6
7
  artifact: ArtifactV2;
@@ -12,5 +13,6 @@ export type AdmissionDecisionV2 = Readonly<{
12
13
  export type AdmissionAuthorizerV2 = (request: DecodedFSB2RequestV2, signal?: AbortSignal) => Promise<AdmissionDecisionV2>;
13
14
  export declare function acceptNativeSessionV2(carrier: NativeCarrierSessionV2, authorize: AdmissionAuthorizerV2, options: Readonly<{
14
15
  runtime: SessionProtocolRuntimeV2;
16
+ rpcRouter?: RpcRouter;
15
17
  signal?: AbortSignal;
16
18
  }>): Promise<InternalSessionV2>;
@@ -1,4 +1,4 @@
1
- import { AdmissionStatusV2, decodeFSB2RequestV2, encodeFSA2ResponseV2, } from "../v2/artifact.js";
1
+ import { AdmissionStatusV2, buildFSB2RequestV2, decodeFSB2RequestV2, encodeFSB2RequestV2, encodeFSA2ResponseV2, } from "../v2/artifact.js";
2
2
  import { adaptNativeCarrierSessionV2 } from "../v2/carrier.js";
3
3
  import { establishSessionV2 } from "../v2/session.js";
4
4
  import { AdmissionSessionV2Error } from "../v2/admissionError.js";
@@ -11,6 +11,11 @@ export async function acceptNativeSessionV2(carrier, authorize, options) {
11
11
  const rawFSB2 = await readFSB2(admission, options.signal);
12
12
  const decoded = decodeFSB2RequestV2(rawFSB2);
13
13
  const decision = await authorize(decoded, options.signal);
14
+ if (decision.accepted) {
15
+ const expected = encodeFSB2RequestV2(buildFSB2RequestV2(decision.artifact, decoded.request.chosen_candidate_id));
16
+ if (!equalBytes(expected, rawFSB2))
17
+ throw new Error("authorized artifact does not match admission");
18
+ }
14
19
  const response = decision.accepted
15
20
  ? { status: AdmissionStatusV2.Success, reason: "" }
16
21
  : { status: decision.status, reason: decision.reason };
@@ -19,7 +24,8 @@ export async function acceptNativeSessionV2(carrier, authorize, options) {
19
24
  if (!decision.accepted) {
20
25
  throw new AdmissionSessionV2Error(decision.reason, `Flowersec v2 admission rejected: ${decision.reason}`);
21
26
  }
22
- return await establishSessionV2(adaptNativeCarrierSessionV2(carrier), sessionConfigFromArtifactV2(decision.artifact, rawFSB2, options.runtime, undefined, "server"), signalOptions(options.signal));
27
+ const config = sessionConfigFromArtifactV2(decision.artifact, rawFSB2, options.runtime, undefined, "server");
28
+ return await establishSessionV2(adaptNativeCarrierSessionV2(carrier), options.rpcRouter === undefined ? config : { ...config, rpcRouter: options.rpcRouter }, signalOptions(options.signal));
23
29
  }
24
30
  catch (error) {
25
31
  admission.abort(asError(error));
@@ -27,6 +33,14 @@ export async function acceptNativeSessionV2(carrier, authorize, options) {
27
33
  throw error;
28
34
  }
29
35
  }
36
+ function equalBytes(left, right) {
37
+ if (left.length !== right.length)
38
+ return false;
39
+ let difference = 0;
40
+ for (let index = 0; index < left.length; index += 1)
41
+ difference |= left[index] ^ right[index];
42
+ return difference === 0;
43
+ }
30
44
  async function readFSB2(stream, signal) {
31
45
  const reader = new NativeReader(stream);
32
46
  const header = await reader.readExactly(12, signal);
@@ -0,0 +1,64 @@
1
+ import { type Artifact } from "../public/artifact.js";
2
+ import { type IncomingStream, type JsonValue, type OperationOptions, type Session } from "../public/contract.js";
3
+ export declare class RuntimeAuthorizationRequest {
4
+ readonly lookupKey: string;
5
+ private readonly requestBrand;
6
+ private constructor();
7
+ }
8
+ export type AuthorizationDecision = Readonly<{
9
+ decision: "allow";
10
+ artifact: Artifact;
11
+ }> | Readonly<{
12
+ decision: "reject" | "retry";
13
+ reason: string;
14
+ }>;
15
+ export type RPCHandlerResult = Readonly<{
16
+ payload: JsonValue;
17
+ }> | Readonly<{
18
+ error: Readonly<{
19
+ code: number;
20
+ message?: string;
21
+ }>;
22
+ }>;
23
+ export type RPCHandler = (payload: JsonValue, request: Readonly<{
24
+ typeId: number;
25
+ }>) => Promise<RPCHandlerResult>;
26
+ export type StreamHandler = (incoming: IncomingStream, options: OperationOptions) => Promise<void>;
27
+ export type SessionHandlerOptions = Readonly<{
28
+ maxConcurrentStreams?: number;
29
+ }>;
30
+ export declare class SessionHandlersError extends Error {
31
+ readonly code: "invalid_handler" | "already_registered" | "frozen";
32
+ constructor(code: "invalid_handler" | "already_registered" | "frozen");
33
+ }
34
+ export declare class SessionHandlers {
35
+ constructor(options?: SessionHandlerOptions);
36
+ handleRPC(typeId: number, handler: RPCHandler): void;
37
+ handleStream(kind: string, handler: StreamHandler): void;
38
+ }
39
+ export type AcceptorOptions = Readonly<{
40
+ host: string;
41
+ port: number;
42
+ path: string;
43
+ certificate: string;
44
+ privateKey: string;
45
+ maxInboundStreams: number;
46
+ authorize(request: RuntimeAuthorizationRequest, options: OperationOptions): Promise<AuthorizationDecision>;
47
+ resolveHandlers?(request: RuntimeAuthorizationRequest, options: OperationOptions): Promise<SessionHandlers> | SessionHandlers;
48
+ }>;
49
+ export declare class AcceptedSession {
50
+ private constructor();
51
+ get session(): Session;
52
+ serve(options?: OperationOptions): Promise<void>;
53
+ close(): Promise<void>;
54
+ }
55
+ export declare class Acceptor {
56
+ private constructor();
57
+ address(): Readonly<{
58
+ host: string;
59
+ port: number;
60
+ }>;
61
+ accept(operation?: OperationOptions): Promise<AcceptedSession>;
62
+ close(): Promise<void>;
63
+ }
64
+ export declare function createAcceptor(options: AcceptorOptions): Promise<Acceptor>;
@@ -0,0 +1,208 @@
1
+ import { sha256 } from "@noble/hashes/sha256";
2
+ import { RpcRouter } from "../rpc/server.js";
3
+ import { acceptNativeSessionV2 } from "../connector/sessionAcceptor.js";
4
+ import { nodeSessionRuntimeV2 } from "./sessionRuntime.js";
5
+ import { startNodeWebTransportServerV2, } from "./webTransportServer.js";
6
+ import { unwrapArtifact } from "../public/artifact.js";
7
+ import { SessionError, } from "../public/contract.js";
8
+ import { projectSessionV2 } from "../v2/publicSession.js";
9
+ import { base64urlEncode } from "../utils/base64url.js";
10
+ const DEFAULT_MAX_CONCURRENT_STREAMS = 64;
11
+ const MAX_CONCURRENT_STREAMS = 128;
12
+ const encoder = new TextEncoder();
13
+ export class RuntimeAuthorizationRequest {
14
+ lookupKey;
15
+ constructor(lookupKey) {
16
+ this.lookupKey = lookupKey;
17
+ }
18
+ }
19
+ export class SessionHandlersError extends Error {
20
+ code;
21
+ constructor(code) {
22
+ super(`Flowersec session handler registration failed (code=${code})`);
23
+ this.code = code;
24
+ this.name = "SessionHandlersError";
25
+ }
26
+ }
27
+ export class SessionHandlers {
28
+ constructor(options = {}) {
29
+ const maximum = options.maxConcurrentStreams ?? DEFAULT_MAX_CONCURRENT_STREAMS;
30
+ if (!Number.isSafeInteger(maximum) || maximum < 1 || maximum > MAX_CONCURRENT_STREAMS) {
31
+ throw new SessionHandlersError("invalid_handler");
32
+ }
33
+ sessionHandlerStates.set(this, {
34
+ maxConcurrentStreams: maximum,
35
+ rpc: new Map(),
36
+ streams: new Map(),
37
+ frozen: false,
38
+ });
39
+ }
40
+ handleRPC(typeId, handler) {
41
+ const state = mutableHandlerState(this);
42
+ if (!Number.isSafeInteger(typeId) || typeId < 1 || typeId > 0xffff_ffff || typeof handler !== "function") {
43
+ throw new SessionHandlersError("invalid_handler");
44
+ }
45
+ if (state.rpc.has(typeId))
46
+ throw new SessionHandlersError("already_registered");
47
+ state.rpc.set(typeId, handler);
48
+ }
49
+ handleStream(kind, handler) {
50
+ const state = mutableHandlerState(this);
51
+ if (kind.length < 1 || encoder.encode(kind).length > 255 || kind === "flowersec.rpc.v2" || typeof handler !== "function") {
52
+ throw new SessionHandlersError("invalid_handler");
53
+ }
54
+ if (state.streams.has(kind))
55
+ throw new SessionHandlersError("already_registered");
56
+ state.streams.set(kind, handler);
57
+ }
58
+ }
59
+ const sessionHandlerStates = new WeakMap();
60
+ function mutableHandlerState(handlers) {
61
+ const state = sessionHandlerStates.get(handlers);
62
+ if (state === undefined)
63
+ throw new SessionHandlersError("invalid_handler");
64
+ if (state.frozen)
65
+ throw new SessionHandlersError("frozen");
66
+ return state;
67
+ }
68
+ function freezeHandlers(handlers, router) {
69
+ const state = mutableHandlerState(handlers);
70
+ state.frozen = true;
71
+ for (const [typeId, handler] of state.rpc) {
72
+ router.register(typeId, async (payload) => {
73
+ const result = await handler(payload, Object.freeze({ typeId }));
74
+ if ("error" in result)
75
+ return { payload: null, error: validRPCError(result.error) };
76
+ return { payload: result.payload };
77
+ });
78
+ }
79
+ return Object.freeze({
80
+ maxConcurrentStreams: state.maxConcurrentStreams,
81
+ streams: new Map(state.streams),
82
+ });
83
+ }
84
+ export class AcceptedSession {
85
+ constructor() { }
86
+ get session() {
87
+ return acceptedSessionState(this).session;
88
+ }
89
+ async serve(options = {}) {
90
+ const active = new Set();
91
+ const state = acceptedSessionState(this);
92
+ try {
93
+ while (true) {
94
+ if (options.signal?.aborted)
95
+ throw new SessionError("canceled");
96
+ const incoming = await this.session.acceptStream(options);
97
+ const handler = state.handlers.streams.get(incoming.kind);
98
+ if (handler === undefined || active.size >= state.handlers.maxConcurrentStreams) {
99
+ await incoming.stream.reset();
100
+ continue;
101
+ }
102
+ const task = handler(incoming, options)
103
+ .finally(() => incoming.stream.close())
104
+ .finally(() => active.delete(task));
105
+ active.add(task);
106
+ }
107
+ }
108
+ finally {
109
+ await this.session.close().catch(() => undefined);
110
+ await Promise.allSettled(active);
111
+ }
112
+ }
113
+ async close() {
114
+ await this.session.close();
115
+ }
116
+ }
117
+ const acceptedSessionStates = new WeakMap();
118
+ function acceptedSessionState(accepted) {
119
+ const state = acceptedSessionStates.get(accepted);
120
+ if (state === undefined)
121
+ throw new SessionError("operation_failed");
122
+ return state;
123
+ }
124
+ function createAcceptedSession(session, handlers) {
125
+ const accepted = new AcceptedSession();
126
+ acceptedSessionStates.set(accepted, { session, handlers });
127
+ return Object.freeze(accepted);
128
+ }
129
+ export class Acceptor {
130
+ constructor() { }
131
+ address() {
132
+ return acceptorState(this).server.address();
133
+ }
134
+ async accept(operation = {}) {
135
+ const state = acceptorState(this);
136
+ const carrier = await state.server.accept(operation);
137
+ const router = new RpcRouter();
138
+ let handlers;
139
+ const internal = await acceptNativeSessionV2(carrier, async (decoded, signal) => {
140
+ const request = runtimeAuthorizationRequest(decoded);
141
+ const decision = await state.options.authorize(request, signal === undefined ? {} : { signal });
142
+ if (decision.decision !== "allow") {
143
+ return {
144
+ accepted: false,
145
+ status: decision.decision === "retry" ? 2 : 1,
146
+ reason: decision.reason,
147
+ };
148
+ }
149
+ const registry = state.options.resolveHandlers === undefined
150
+ ? new SessionHandlers()
151
+ : await state.options.resolveHandlers(request, signal === undefined ? {} : { signal });
152
+ handlers = freezeHandlers(registry, router);
153
+ return { accepted: true, artifact: unwrapArtifact(decision.artifact) };
154
+ }, {
155
+ runtime: nodeSessionRuntimeV2,
156
+ rpcRouter: router,
157
+ ...(operation.signal === undefined ? {} : { signal: operation.signal }),
158
+ });
159
+ if (handlers === undefined) {
160
+ await internal.close().catch(() => undefined);
161
+ throw new SessionError("operation_failed");
162
+ }
163
+ return createAcceptedSession(projectSessionV2(internal), handlers);
164
+ }
165
+ async close() {
166
+ await acceptorState(this).server.close();
167
+ }
168
+ }
169
+ const acceptorStates = new WeakMap();
170
+ function acceptorState(acceptor) {
171
+ const state = acceptorStates.get(acceptor);
172
+ if (state === undefined)
173
+ throw new SessionError("operation_failed");
174
+ return state;
175
+ }
176
+ export async function createAcceptor(options) {
177
+ if (!Number.isSafeInteger(options.maxInboundStreams) || options.maxInboundStreams < 1 || options.maxInboundStreams > 128) {
178
+ throw new TypeError("invalid Flowersec Acceptor options");
179
+ }
180
+ const server = await startNodeWebTransportServerV2({
181
+ host: options.host,
182
+ port: options.port,
183
+ path: options.path,
184
+ certificate: options.certificate,
185
+ privateKey: options.privateKey,
186
+ carrierPath: "direct",
187
+ inboundBidirectionalStreamCapacity: options.maxInboundStreams + 2,
188
+ });
189
+ const acceptor = new Acceptor();
190
+ acceptorStates.set(acceptor, { server, options });
191
+ return Object.freeze(acceptor);
192
+ }
193
+ function runtimeAuthorizationRequest(decoded) {
194
+ const credential = decoded.request.pathKind === "direct"
195
+ ? decoded.request.routing_token
196
+ : decoded.request.attach_token;
197
+ const lookupKey = base64urlEncode(sha256(encoder.encode(credential)));
198
+ return new RuntimeAuthorizationRequest(lookupKey);
199
+ }
200
+ function validRPCError(error) {
201
+ if (!Number.isSafeInteger(error.code) || error.code < 1 || error.code > 0xffff_ffff) {
202
+ return { code: 500, message: "handler failed" };
203
+ }
204
+ if (error.message !== undefined && encoder.encode(error.message).length > 1024) {
205
+ return { code: 500, message: "handler failed" };
206
+ }
207
+ return error.message === undefined ? { code: error.code } : { code: error.code, message: error.message };
208
+ }
@@ -1,3 +1,5 @@
1
1
  export { connect, createConnectionController } from "./connectSession.js";
2
+ export { AcceptedSession, Acceptor, RuntimeAuthorizationRequest, SessionHandlers, SessionHandlersError, createAcceptor, } from "./acceptor.js";
3
+ export type { AcceptorOptions, AuthorizationDecision, RPCHandler, RPCHandlerResult, SessionHandlerOptions, StreamHandler, } from "./acceptor.js";
2
4
  export type { ConnectionControllerOptions, SessionOptions, SessionTLSOptions, } from "./connectSession.js";
3
5
  export * from "../facade.js";
@@ -1,2 +1,3 @@
1
1
  export { connect, createConnectionController } from "./connectSession.js";
2
+ export { AcceptedSession, Acceptor, RuntimeAuthorizationRequest, SessionHandlers, SessionHandlersError, createAcceptor, } from "./acceptor.js";
2
3
  export * from "../facade.js";
@@ -454,12 +454,17 @@ function normalizeCandidateURL(kind, carrier, raw) {
454
454
  default:
455
455
  throw invalidCandidate("carrier registry");
456
456
  }
457
- if (scheme !== expectedScheme)
457
+ const loopbackPlaintext = carrier === "websocket" && scheme === "ws" && kind === "direct" && isLoopbackHost(normalizedAuthority);
458
+ if (scheme !== expectedScheme && !loopbackPlaintext)
458
459
  throw invalidCandidate("carrier scheme");
459
460
  if (carrier !== "raw_quic" && path !== expectedPath)
460
461
  throw invalidCandidate("carrier URL path");
461
462
  return `${scheme}://${normalizedAuthority}${path}`;
462
463
  }
464
+ function isLoopbackHost(authority) {
465
+ const host = authority.startsWith("[") ? authority.slice(1, authority.indexOf("]")) : authority.split(":")[0];
466
+ return host === "127.0.0.1" || host === "::1";
467
+ }
463
468
  function normalizeAuthority(authority) {
464
469
  let host;
465
470
  let portText = "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floegence/flowersec-core",
3
- "version": "2.1.0",
3
+ "version": "2.3.0",
4
4
  "description": "Flowersec core TypeScript library for carrier-neutral encrypted sessions and multiplexed streams.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "bomFormat": "CycloneDX",
3
3
  "specVersion": "1.5",
4
- "serialNumber": "urn:uuid:3a3de657-a6c7-5262-8788-686263f18f4a",
4
+ "serialNumber": "urn:uuid:cf8e8ff3-da93-5d33-8d7d-3757e60b3f70",
5
5
  "version": 1,
6
6
  "metadata": {
7
7
  "component": {
8
8
  "type": "library",
9
9
  "name": "@floegence/flowersec-core",
10
- "version": "2.1.0",
11
- "purl": "pkg:npm/%40floegence/flowersec-core@2.1.0",
12
- "bom-ref": "pkg:npm/%40floegence/flowersec-core@2.1.0"
10
+ "version": "2.3.0",
11
+ "purl": "pkg:npm/%40floegence/flowersec-core@2.3.0",
12
+ "bom-ref": "pkg:npm/%40floegence/flowersec-core@2.3.0"
13
13
  },
14
14
  "properties": [
15
15
  {
16
16
  "name": "flowersec:source-inventory-sha256",
17
- "value": "212330df6f0cfcf64ebc4710f73833ffa6d31e4701a246e8b1f4a6a6f74418f8"
17
+ "value": "ff5c9ce47b84ad4cc8a7e9ca5d1605dbaaeaae54743cb071442e146cbc1ca912"
18
18
  }
19
19
  ]
20
20
  },
@@ -2290,7 +2290,7 @@
2290
2290
  ],
2291
2291
  "dependencies": [
2292
2292
  {
2293
- "ref": "pkg:npm/%40floegence/flowersec-core@2.1.0",
2293
+ "ref": "pkg:npm/%40floegence/flowersec-core@2.3.0",
2294
2294
  "dependsOn": [
2295
2295
  "pkg:npm/%40fails-components/webtransport-transport-http3-quiche@1.6.7",
2296
2296
  "pkg:npm/%40fails-components/webtransport@1.6.7",
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/212330df6f0cfcf64ebc4710f73833ffa6d31e4701a246e8b1f4a6a6f74418f8",
6
+ "documentNamespace": "https://github.com/floegence/flowersec/sbom/flowersec-ts/ff5c9ce47b84ad4cc8a7e9ca5d1605dbaaeaae54743cb071442e146cbc1ca912",
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-e11fb24ba960cd06eb83",
17
- "versionInfo": "2.1.0",
16
+ "SPDXID": "SPDXRef-Package-7a598ff0897002980d8f",
17
+ "versionInfo": "2.3.0",
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: 212330df6f0cfcf64ebc4710f73833ffa6d31e4701a246e8b1f4a6a6f74418f8",
23
+ "comment": "Flowersec source inventory SHA-256: ff5c9ce47b84ad4cc8a7e9ca5d1605dbaaeaae54743cb071442e146cbc1ca912",
24
24
  "externalRefs": [
25
25
  {
26
26
  "referenceCategory": "PACKAGE-MANAGER",
27
27
  "referenceType": "purl",
28
- "referenceLocator": "pkg:npm/%40floegence/flowersec-core@2.1.0"
28
+ "referenceLocator": "pkg:npm/%40floegence/flowersec-core@2.3.0"
29
29
  }
30
30
  ]
31
31
  },
@@ -1411,7 +1411,7 @@
1411
1411
  {
1412
1412
  "spdxElementId": "SPDXRef-DOCUMENT",
1413
1413
  "relationshipType": "DESCRIBES",
1414
- "relatedSpdxElement": "SPDXRef-Package-e11fb24ba960cd06eb83"
1414
+ "relatedSpdxElement": "SPDXRef-Package-7a598ff0897002980d8f"
1415
1415
  },
1416
1416
  {
1417
1417
  "spdxElementId": "SPDXRef-Package-789c0f508771e385c5e8",
@@ -1459,37 +1459,37 @@
1459
1459
  "relatedSpdxElement": "SPDXRef-Package-1a7f123f21a1eaf4fd69"
1460
1460
  },
1461
1461
  {
1462
- "spdxElementId": "SPDXRef-Package-e11fb24ba960cd06eb83",
1462
+ "spdxElementId": "SPDXRef-Package-7a598ff0897002980d8f",
1463
1463
  "relationshipType": "DEPENDS_ON",
1464
1464
  "relatedSpdxElement": "SPDXRef-Package-789c0f508771e385c5e8"
1465
1465
  },
1466
1466
  {
1467
- "spdxElementId": "SPDXRef-Package-e11fb24ba960cd06eb83",
1467
+ "spdxElementId": "SPDXRef-Package-7a598ff0897002980d8f",
1468
1468
  "relationshipType": "DEPENDS_ON",
1469
1469
  "relatedSpdxElement": "SPDXRef-Package-4b35e4821b43dc47a168"
1470
1470
  },
1471
1471
  {
1472
- "spdxElementId": "SPDXRef-Package-e11fb24ba960cd06eb83",
1472
+ "spdxElementId": "SPDXRef-Package-7a598ff0897002980d8f",
1473
1473
  "relationshipType": "DEPENDS_ON",
1474
1474
  "relatedSpdxElement": "SPDXRef-Package-24f128c8779cacb70e46"
1475
1475
  },
1476
1476
  {
1477
- "spdxElementId": "SPDXRef-Package-e11fb24ba960cd06eb83",
1477
+ "spdxElementId": "SPDXRef-Package-7a598ff0897002980d8f",
1478
1478
  "relationshipType": "DEPENDS_ON",
1479
1479
  "relatedSpdxElement": "SPDXRef-Package-6a42d288421aaee354ff"
1480
1480
  },
1481
1481
  {
1482
- "spdxElementId": "SPDXRef-Package-e11fb24ba960cd06eb83",
1482
+ "spdxElementId": "SPDXRef-Package-7a598ff0897002980d8f",
1483
1483
  "relationshipType": "DEPENDS_ON",
1484
1484
  "relatedSpdxElement": "SPDXRef-Package-d976986d5d79eddc7789"
1485
1485
  },
1486
1486
  {
1487
- "spdxElementId": "SPDXRef-Package-e11fb24ba960cd06eb83",
1487
+ "spdxElementId": "SPDXRef-Package-7a598ff0897002980d8f",
1488
1488
  "relationshipType": "DEPENDS_ON",
1489
1489
  "relatedSpdxElement": "SPDXRef-Package-f8b45a289df643042b94"
1490
1490
  },
1491
1491
  {
1492
- "spdxElementId": "SPDXRef-Package-e11fb24ba960cd06eb83",
1492
+ "spdxElementId": "SPDXRef-Package-7a598ff0897002980d8f",
1493
1493
  "relationshipType": "DEPENDS_ON",
1494
1494
  "relatedSpdxElement": "SPDXRef-Package-38fe7b92bdac2a86fb61"
1495
1495
  },