@claude-flow/security 3.0.0-alpha.9 → 3.0.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.
Files changed (116) hide show
  1. package/dist/CVE-REMEDIATION.d.ts +17 -6
  2. package/dist/CVE-REMEDIATION.d.ts.map +1 -1
  3. package/dist/CVE-REMEDIATION.js +328 -12
  4. package/dist/CVE-REMEDIATION.js.map +1 -1
  5. package/dist/index.d.ts +8 -0
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +26 -0
  8. package/dist/index.js.map +1 -1
  9. package/dist/keychain-adapter.d.ts +48 -0
  10. package/dist/keychain-adapter.d.ts.map +1 -0
  11. package/dist/keychain-adapter.js +0 -0
  12. package/dist/keychain-adapter.js.map +1 -0
  13. package/dist/mcp-caller-identity.d.ts +101 -0
  14. package/dist/mcp-caller-identity.d.ts.map +1 -0
  15. package/dist/mcp-caller-identity.js +0 -0
  16. package/dist/mcp-caller-identity.js.map +1 -0
  17. package/dist/mcp-composition-inspector.d.ts +133 -0
  18. package/dist/mcp-composition-inspector.d.ts.map +1 -0
  19. package/dist/mcp-composition-inspector.js +254 -0
  20. package/dist/mcp-composition-inspector.js.map +1 -0
  21. package/dist/oauth/browser.d.ts +31 -0
  22. package/dist/oauth/browser.d.ts.map +1 -0
  23. package/dist/oauth/browser.js +55 -0
  24. package/dist/oauth/browser.js.map +1 -0
  25. package/dist/oauth/callback-server.d.ts +37 -0
  26. package/dist/oauth/callback-server.d.ts.map +1 -0
  27. package/dist/oauth/callback-server.js +86 -0
  28. package/dist/oauth/callback-server.js.map +1 -0
  29. package/dist/oauth/client.d.ts +57 -0
  30. package/dist/oauth/client.d.ts.map +1 -0
  31. package/dist/oauth/client.js +120 -0
  32. package/dist/oauth/client.js.map +1 -0
  33. package/dist/oauth/pkce.d.ts +25 -0
  34. package/dist/oauth/pkce.d.ts.map +1 -0
  35. package/dist/oauth/pkce.js +36 -0
  36. package/dist/oauth/pkce.js.map +1 -0
  37. package/dist/path-validator.d.ts +23 -0
  38. package/dist/path-validator.d.ts.map +1 -1
  39. package/dist/path-validator.js +128 -5
  40. package/dist/path-validator.js.map +1 -1
  41. package/dist/policy/canonical.d.ts +5 -0
  42. package/dist/policy/canonical.d.ts.map +1 -0
  43. package/dist/policy/canonical.js +30 -0
  44. package/dist/policy/canonical.js.map +1 -0
  45. package/dist/policy/engine.d.ts +30 -0
  46. package/dist/policy/engine.d.ts.map +1 -0
  47. package/dist/policy/engine.js +319 -0
  48. package/dist/policy/engine.js.map +1 -0
  49. package/dist/policy/envelope.d.ts +9 -0
  50. package/dist/policy/envelope.d.ts.map +1 -0
  51. package/dist/policy/envelope.js +90 -0
  52. package/dist/policy/envelope.js.map +1 -0
  53. package/dist/policy/evaluator.d.ts +4 -0
  54. package/dist/policy/evaluator.d.ts.map +1 -0
  55. package/dist/policy/evaluator.js +119 -0
  56. package/dist/policy/evaluator.js.map +1 -0
  57. package/dist/policy/index.d.ts +7 -0
  58. package/dist/policy/index.d.ts.map +1 -0
  59. package/dist/policy/index.js +7 -0
  60. package/dist/policy/index.js.map +1 -0
  61. package/dist/policy/product-plane.d.ts +238 -0
  62. package/dist/policy/product-plane.d.ts.map +1 -0
  63. package/dist/policy/product-plane.js +1022 -0
  64. package/dist/policy/product-plane.js.map +1 -0
  65. package/dist/policy/types.d.ts +187 -0
  66. package/dist/policy/types.d.ts.map +1 -0
  67. package/dist/policy/types.js +2 -0
  68. package/dist/policy/types.js.map +1 -0
  69. package/dist/safe-git.d.ts +22 -0
  70. package/dist/safe-git.d.ts.map +1 -0
  71. package/dist/safe-git.js +63 -0
  72. package/dist/safe-git.js.map +1 -0
  73. package/package.json +17 -5
  74. package/.claude-flow/data/pending-insights.jsonl +0 -6
  75. package/.claude-flow/neural/stats.json +0 -6
  76. package/__tests__/acceptance/security-compliance.test.ts +0 -674
  77. package/__tests__/authorization-propagator.test.ts +0 -155
  78. package/__tests__/credential-generator.test.ts +0 -310
  79. package/__tests__/fixtures/configurations.ts +0 -425
  80. package/__tests__/fixtures/index.ts +0 -21
  81. package/__tests__/helpers/create-mock.ts +0 -469
  82. package/__tests__/helpers/index.ts +0 -32
  83. package/__tests__/input-validator.test.ts +0 -382
  84. package/__tests__/integration/security-flow.test.ts +0 -606
  85. package/__tests__/password-hasher.test.ts +0 -239
  86. package/__tests__/path-validator.test.ts +0 -302
  87. package/__tests__/plugin-integrity-verifier.test.ts +0 -199
  88. package/__tests__/safe-executor.test.ts +0 -292
  89. package/__tests__/token-generator.test.ts +0 -371
  90. package/__tests__/tool-output-guardrail.test.ts +0 -256
  91. package/__tests__/unit/credential-generator.test.ts +0 -182
  92. package/__tests__/unit/password-hasher.test.ts +0 -359
  93. package/__tests__/unit/path-validator.test.ts +0 -509
  94. package/__tests__/unit/safe-executor.test.ts +0 -667
  95. package/__tests__/unit/token-generator.test.ts +0 -310
  96. package/ruvector.db +0 -0
  97. package/src/CVE-REMEDIATION.ts +0 -251
  98. package/src/application/index.ts +0 -10
  99. package/src/application/services/security-application-service.ts +0 -193
  100. package/src/authorization/propagator.ts +0 -279
  101. package/src/credential-generator.ts +0 -368
  102. package/src/domain/entities/security-context.ts +0 -173
  103. package/src/domain/index.ts +0 -17
  104. package/src/domain/services/security-domain-service.ts +0 -296
  105. package/src/index.ts +0 -314
  106. package/src/input-validator.ts +0 -466
  107. package/src/password-hasher.ts +0 -277
  108. package/src/path-validator.ts +0 -525
  109. package/src/plugins/integrity-verifier.ts +0 -265
  110. package/src/safe-executor.ts +0 -546
  111. package/src/token-generator.ts +0 -463
  112. package/src/tool-output-guardrail.ts +0 -360
  113. package/tmp.json +0 -0
  114. package/tsconfig.json +0 -9
  115. package/tsconfig.tsbuildinfo +0 -1
  116. package/vitest.config.ts +0 -20
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keychain-adapter.js","sourceRoot":"","sources":["../src/keychain-adapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * McpCallerIdentity — ADR-377 Phase 3 (ruvnet/ruflo#2516, #2873).
3
+ *
4
+ * Per-invocation, Ed25519-signed capability tokens binding an MCP tool call
5
+ * to a specific caller. Closes the gap identified in arXiv:2603.07473
6
+ * (Caller Identity Confusion in MCP): authorization state today persists
7
+ * across callers with no re-verification, and `AgentAuthorizationPropagator`
8
+ * (ADR-144)'s `checkToolCall` validates a *scope* but not that the caller
9
+ * presenting it is who they claim to be for *this* call.
10
+ *
11
+ * Signing implementation follows the same pattern already used in this repo
12
+ * for Ed25519 signing (`@claude-flow/browser`'s `witness-signer.ts`): native
13
+ * `node:crypto`, zero new dependencies, sign+verify both supported (unlike
14
+ * `@claude-flow/security`'s own `plugins/integrity-verifier.ts`, which uses
15
+ * `@noble/ed25519` for verify-only). Reimplemented here rather than imported
16
+ * from `@claude-flow/browser` because a security-primitives module
17
+ * depending on the browser package would be a backwards layering
18
+ * dependency; the pattern is ~15 lines and not worth a cross-package import
19
+ * for that.
20
+ *
21
+ * Scope and honest limitations (per the ADR's own text)
22
+ * --------------------------------------------------------
23
+ * This module implements token issuance/verification only. It does NOT
24
+ * wire itself into any MCP tool dispatcher — this repo currently has at
25
+ * least two distinct `ToolRegistry` implementations
26
+ * (`v3/mcp/tool-registry.ts`, `v3/@claude-flow/mcp/src/tool-registry.ts`)
27
+ * plus a third under `@claude-flow/shared`, and it was not safe to guess
28
+ * which one is authoritative without risking silently wiring the wrong
29
+ * dispatch path. `AgentAuthorizationPropagator.checkToolCall` (ADR-144) is
30
+ * the natural composition point — call `verifyInvocationToken` first and
31
+ * only proceed to `checkToolCall` if it passes — but that composition is
32
+ * left to the caller rather than baked into `checkToolCall` itself, to
33
+ * avoid changing ADR-144's already-shipped, already-tested behavior.
34
+ *
35
+ * Key distribution (how callers obtain a private key to sign with in the
36
+ * first place) is explicitly out of scope — the ADR calls for "a follow-on
37
+ * ADR for key distribution" before this ships enabled by default, which is
38
+ * why `CLAUDE_FLOW_MCP_CALLER_AUTH` defaults to disabled.
39
+ */
40
+ import type { KeyObject } from 'node:crypto';
41
+ export interface CallerIdentityKey {
42
+ privateKey: KeyObject;
43
+ publicKey: KeyObject;
44
+ publicKeyHex: string;
45
+ }
46
+ export interface InvocationToken {
47
+ /** Agent/principal id of the caller this token was issued to. */
48
+ callerId: string;
49
+ /** Tool this token authorizes — a token issued for one tool cannot be
50
+ * replayed against another. */
51
+ toolName: string;
52
+ /** Unix ms the token was issued. */
53
+ issuedAt: number;
54
+ /** Unix ms after which the token is no longer valid. */
55
+ expiresAt: number;
56
+ /** Random per-token value; prevents replay of an otherwise-identical
57
+ * token issued in the same millisecond. */
58
+ nonce: string;
59
+ /** Hex-encoded Ed25519 signature over the canonicalized fields above. */
60
+ signature: string;
61
+ }
62
+ export interface TokenVerificationResult {
63
+ valid: boolean;
64
+ reason?: 'expired' | 'bad-signature' | 'tool-mismatch';
65
+ }
66
+ /** Generate a fresh Ed25519 keypair. */
67
+ export declare function generateCallerIdentityKey(): CallerIdentityKey;
68
+ /** Load a caller-identity key from a PEM-encoded private key string. */
69
+ export declare function loadCallerIdentityKey(privateKeyPem: string): CallerIdentityKey;
70
+ /**
71
+ * Issue a fresh, signed `InvocationToken` for `callerId` to call `toolName`.
72
+ * Callers must present a *new* token per invocation — tokens are
73
+ * single-tool-bound and short-lived (`ttlMs`, default 30s) by design; there
74
+ * is no renewal API on purpose.
75
+ */
76
+ export declare function issueInvocationToken(callerId: string, toolName: string, key: CallerIdentityKey, opts?: {
77
+ ttlMs?: number;
78
+ now?: number;
79
+ }): InvocationToken;
80
+ /**
81
+ * Verify an `InvocationToken` against the issuing caller's public key and
82
+ * (if provided) the tool the caller is about to invoke. Never throws.
83
+ */
84
+ export declare function verifyInvocationToken(token: InvocationToken, publicKey: KeyObject, opts?: {
85
+ toolName?: string;
86
+ now?: number;
87
+ }): TokenVerificationResult;
88
+ /** Off by default — see the file header re: key distribution being out of scope pending a follow-on ADR. Only the literal `'true'` enables enforcement. */
89
+ export declare function isMcpCallerAuthEnabled(): boolean;
90
+ /** Inverse of `extractPublicKeyHex`: rebuild a `KeyObject` from the raw hex it produced. */
91
+ export declare function publicKeyFromHex(hex: string): KeyObject;
92
+ /**
93
+ * Pack an `InvocationToken` into one env-var-safe string (JSON + base64url).
94
+ * The issuer's public key travels in its own env var (set alongside this
95
+ * one, hex is already env-var-safe) rather than being folded in here — kept
96
+ * separate to mirror how the two values are set independently at spawn time.
97
+ */
98
+ export declare function encodeTokenEnvelope(token: InvocationToken): string;
99
+ /** Inverse of `encodeTokenEnvelope`. Never throws — returns `undefined` on any malformed input. */
100
+ export declare function decodeTokenEnvelope(encoded: string): InvocationToken | undefined;
101
+ //# sourceMappingURL=mcp-caller-identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-caller-identity.d.ts","sourceRoot":"","sources":["../src/mcp-caller-identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,SAAS,CAAC;IACtB,SAAS,EAAE,SAAS,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,iEAAiE;IACjE,QAAQ,EAAE,MAAM,CAAC;IACjB;mCAC+B;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,oCAAoC;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,wDAAwD;IACxD,SAAS,EAAE,MAAM,CAAC;IAClB;+CAC2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,SAAS,GAAG,eAAe,GAAG,eAAe,CAAC;CACxD;AAeD,wCAAwC;AACxC,wBAAgB,yBAAyB,IAAI,iBAAiB,CAG7D;AAED,wEAAwE;AACxE,wBAAgB,qBAAqB,CAAC,aAAa,EAAE,MAAM,GAAG,iBAAiB,CAI9E;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,iBAAiB,EACtB,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAO,GAC1C,eAAe,CAWjB;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,eAAe,EACtB,SAAS,EAAE,SAAS,EACpB,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAO,GAC7C,uBAAuB,CAgBzB;AAQD,2JAA2J;AAC3J,wBAAgB,sBAAsB,IAAI,OAAO,CAEhD;AAoBD,4FAA4F;AAC5F,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAMvD;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,CAElE;AAED,mGAAmG;AACnG,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAmBhF"}
Binary file
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-caller-identity.js","sourceRoot":"","sources":["../src/mcp-caller-identity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,133 @@
1
+ /**
2
+ * MCP Tool Composition Inspector — ADR-320 (this ADR; ruvnet/ruflo dream-cycle
3
+ * proposal, arXiv:2606.27027 "ShareLock").
4
+ *
5
+ * The attack: an adversary registers N seemingly-benign MCP tools whose
6
+ * individual descriptions look fine in isolation, but whose CONCATENATION
7
+ * (or pairwise overlap) forms an injection payload targeting a downstream
8
+ * agent — a Shamir-secret-sharing-style split across tool descriptions.
9
+ * Per-tool inspection misses this because each fragment sits under any
10
+ * single-tool detection threshold.
11
+ *
12
+ * Relationship to prior art in this repo
13
+ * ---------------------------------------
14
+ * A CLI-only v1 of this idea already shipped directly to `main`
15
+ * (dream-cycle #2783, commits 381b7ebcc/581cd2bf3) as
16
+ * `v3/@claude-flow/cli/src/security/mcp-composition-inspector.ts` — an
17
+ * on-demand `ruflo security composition-scan` command using exact
18
+ * common-substring matching plus an injection-phrase catalog and
19
+ * typosquat check. That module's own header explicitly scopes itself as
20
+ * "a bounded engineering fix rather than an ADR-scope subsystem" and lists
21
+ * "Future v2: SimHash + LSH for scale" as deliberately deferred. THIS
22
+ * module is that v2: a reusable `@claude-flow/security` library primitive
23
+ * (not a CLI-only tool) built around a real SimHash fingerprint, so it can
24
+ * be called from a pre-task/pre-tool-use hook before a multi-tool chain
25
+ * executes, per this ADR's implementation target. It does not replace or
26
+ * import the CLI v1 — the two are independent, complementary detectors
27
+ * (exact-substring vs. hashed-shingle-overlap) that a caller may run
28
+ * together.
29
+ *
30
+ * Detection method (deterministic, no LLM call)
31
+ * -----------------------------------------------
32
+ * 1. Each tool description is normalized and split into overlapping
33
+ * word-shingles (default: 5-word windows). Shingling — rather than
34
+ * whole-description comparison — is what lets us find a SMALL embedded
35
+ * fragment inside an otherwise unrelated, benign description.
36
+ * 2. Each tool gets a 64-bit SimHash fingerprint: every shingle is hashed
37
+ * (FNV-1a, 64-bit) and the fingerprint bits are set by majority vote
38
+ * across all of a tool's shingle hashes. Two tools whose descriptions
39
+ * share enough content have a SMALL Hamming distance between
40
+ * fingerprints — this is the O(1)-per-pair coarse filter that avoids an
41
+ * LLM call and stays cheap even at hundreds of registered tools (fast
42
+ * 64-bit XOR + popcount per pair).
43
+ * 3. An inverted shingle-hash index (hash -> set of tool indices) is built
44
+ * once, in O(total shingles). For each pair flagged by the SimHash
45
+ * filter (or found in the index) the SAME index is used to compute the
46
+ * actual overlapping shingle text — this is what makes the finding
47
+ * reportable (the SimHash score alone can't tell an operator *what*
48
+ * matched).
49
+ * 4. A fragment's "population" (how many distinct tools carry it) caps
50
+ * false positives: shared shingles that appear in more than
51
+ * `maxFragmentPopulation` (default 3) tools are template language
52
+ * (e.g. every ruflo `memory_*` tool's boilerplate), not an attack —
53
+ * Shamir-split attacks concentrate a fragment in a small conspiracy of
54
+ * 2-3 tools. Same population-cap idea as the CLI v1, reimplemented here
55
+ * against the hash index instead of raw substrings.
56
+ *
57
+ * This is a heuristic detector, not a security boundary: it reports
58
+ * SUSPECTS. Default action is warn + log; blocking is opt-in via
59
+ * `CLAUDE_FLOW_MCP_COMPOSITION_BLOCK=1` (see {@link evaluateToolComposition}).
60
+ *
61
+ * @module @claude-flow/security/mcp-composition-inspector
62
+ */
63
+ export interface McpToolDescriptor {
64
+ name: string;
65
+ description: string;
66
+ }
67
+ export interface CompositionInspectorOptions {
68
+ /** Words per shingle window. Default 5. */
69
+ shingleSize?: number;
70
+ /** SimHash similarity (1 - Hamming/64) at/above which a pair is a candidate. Default 0.6. */
71
+ simhashThreshold?: number;
72
+ /** Fraction of a tool's shingles shared with a peer (low-population only) at/above which a pair is flagged. Default 0.25. */
73
+ fragmentOverlapThreshold?: number;
74
+ /** Max number of distinct tools a shingle may appear in before it's treated as template language, not an attack fragment. Default 3. */
75
+ maxFragmentPopulation?: number;
76
+ /** Minimum character length for a shingle to be indexed (filters trivial/common short phrases). Default 12. */
77
+ minShingleChars?: number;
78
+ /** Max sample shared fragments recorded per finding (display only). Default 5. */
79
+ maxSampleFragments?: number;
80
+ }
81
+ export interface CompositionFinding {
82
+ toolA: string;
83
+ toolB: string;
84
+ /** 1 - (Hamming distance / 64) between the two tools' SimHash fingerprints. */
85
+ simhashSimilarity: number;
86
+ /** sharedLowPopulationShingles / min(shingleCount(A), shingleCount(B)). */
87
+ fragmentOverlapScore: number;
88
+ /** Sample of the actual shared shingle text driving the finding (truncated). */
89
+ sharedFragments: string[];
90
+ /** Max population (distinct tools) observed among the shared shingles that triggered this finding. */
91
+ fragmentPopulation: number;
92
+ reason: string;
93
+ }
94
+ export interface CompositionInspectionStats {
95
+ toolsScanned: number;
96
+ pairsCompared: number;
97
+ shinglesIndexed: number;
98
+ /** Pairs whose SimHash similarity alone crossed simhashThreshold (diagnostic — includes pairs later excluded for having no low-population shared shingle). */
99
+ candidatePairsFromSimhash: number;
100
+ }
101
+ export interface CompositionInspectionResult {
102
+ findings: CompositionFinding[];
103
+ stats: CompositionInspectionStats;
104
+ }
105
+ export interface CompositionGuardResult {
106
+ result: CompositionInspectionResult;
107
+ action: 'none' | 'warn' | 'block';
108
+ blocked: boolean;
109
+ message?: string;
110
+ }
111
+ /** FNV-1a, 64-bit. Deterministic, dependency-free, good avalanche for short strings. */
112
+ export declare function fnv1a64(input: string): bigint;
113
+ /**
114
+ * Classic SimHash: bit-vote a 64-bit fingerprint across a list of token
115
+ * hashes. Near-duplicate token sets (even with some fragments differing)
116
+ * produce fingerprints with a small Hamming distance.
117
+ */
118
+ export declare function simhash64(shingles: readonly string[]): bigint;
119
+ /** Hamming distance between two 64-bit fingerprints (0..64). */
120
+ export declare function hammingDistance64(a: bigint, b: bigint): number;
121
+ export declare function inspectToolComposition(tools: readonly McpToolDescriptor[], options?: CompositionInspectorOptions): CompositionInspectionResult;
122
+ /** Reads `CLAUDE_FLOW_MCP_COMPOSITION_BLOCK` fresh on every call. '1' or 'true' (case-insensitive) enables blocking; unset/anything else keeps the default warn+log posture. */
123
+ export declare function isCompositionBlockEnabled(): boolean;
124
+ /**
125
+ * Runs {@link inspectToolComposition} and applies the ADR-320 default
126
+ * posture: warn + log (via `console.warn`) unless
127
+ * `CLAUDE_FLOW_MCP_COMPOSITION_BLOCK=1`, in which case the chain is marked
128
+ * `blocked: true` — the caller (the pre-task hook / MCP dispatcher) decides
129
+ * what "blocked" means operationally; this function does not throw or abort
130
+ * anything itself.
131
+ */
132
+ export declare function evaluateToolComposition(tools: readonly McpToolDescriptor[], options?: CompositionInspectorOptions): CompositionGuardResult;
133
+ //# sourceMappingURL=mcp-composition-inspector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-composition-inspector.d.ts","sourceRoot":"","sources":["../src/mcp-composition-inspector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;AAMH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,2BAA2B;IAC1C,2CAA2C;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6FAA6F;IAC7F,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,6HAA6H;IAC7H,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,wIAAwI;IACxI,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,+GAA+G;IAC/G,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kFAAkF;IAClF,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,+EAA+E;IAC/E,iBAAiB,EAAE,MAAM,CAAC;IAC1B,2EAA2E;IAC3E,oBAAoB,EAAE,MAAM,CAAC;IAC7B,gFAAgF;IAChF,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,sGAAsG;IACtG,kBAAkB,EAAE,MAAM,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,0BAA0B;IACzC,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,8JAA8J;IAC9J,yBAAyB,EAAE,MAAM,CAAC;CACnC;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,EAAE,kBAAkB,EAAE,CAAC;IAC/B,KAAK,EAAE,0BAA0B,CAAC;CACnC;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,2BAA2B,CAAC;IACpC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAUD,wFAAwF;AACxF,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAO7C;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAe7D;AAED,gEAAgE;AAChE,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAQ9D;AAkCD,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,SAAS,iBAAiB,EAAE,EACnC,OAAO,GAAE,2BAAgC,GACxC,2BAA2B,CAyF7B;AAQD,gLAAgL;AAChL,wBAAgB,yBAAyB,IAAI,OAAO,CAGnD;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,SAAS,iBAAiB,EAAE,EACnC,OAAO,GAAE,2BAAgC,GACxC,sBAAsB,CAwBxB"}
@@ -0,0 +1,254 @@
1
+ /**
2
+ * MCP Tool Composition Inspector — ADR-320 (this ADR; ruvnet/ruflo dream-cycle
3
+ * proposal, arXiv:2606.27027 "ShareLock").
4
+ *
5
+ * The attack: an adversary registers N seemingly-benign MCP tools whose
6
+ * individual descriptions look fine in isolation, but whose CONCATENATION
7
+ * (or pairwise overlap) forms an injection payload targeting a downstream
8
+ * agent — a Shamir-secret-sharing-style split across tool descriptions.
9
+ * Per-tool inspection misses this because each fragment sits under any
10
+ * single-tool detection threshold.
11
+ *
12
+ * Relationship to prior art in this repo
13
+ * ---------------------------------------
14
+ * A CLI-only v1 of this idea already shipped directly to `main`
15
+ * (dream-cycle #2783, commits 381b7ebcc/581cd2bf3) as
16
+ * `v3/@claude-flow/cli/src/security/mcp-composition-inspector.ts` — an
17
+ * on-demand `ruflo security composition-scan` command using exact
18
+ * common-substring matching plus an injection-phrase catalog and
19
+ * typosquat check. That module's own header explicitly scopes itself as
20
+ * "a bounded engineering fix rather than an ADR-scope subsystem" and lists
21
+ * "Future v2: SimHash + LSH for scale" as deliberately deferred. THIS
22
+ * module is that v2: a reusable `@claude-flow/security` library primitive
23
+ * (not a CLI-only tool) built around a real SimHash fingerprint, so it can
24
+ * be called from a pre-task/pre-tool-use hook before a multi-tool chain
25
+ * executes, per this ADR's implementation target. It does not replace or
26
+ * import the CLI v1 — the two are independent, complementary detectors
27
+ * (exact-substring vs. hashed-shingle-overlap) that a caller may run
28
+ * together.
29
+ *
30
+ * Detection method (deterministic, no LLM call)
31
+ * -----------------------------------------------
32
+ * 1. Each tool description is normalized and split into overlapping
33
+ * word-shingles (default: 5-word windows). Shingling — rather than
34
+ * whole-description comparison — is what lets us find a SMALL embedded
35
+ * fragment inside an otherwise unrelated, benign description.
36
+ * 2. Each tool gets a 64-bit SimHash fingerprint: every shingle is hashed
37
+ * (FNV-1a, 64-bit) and the fingerprint bits are set by majority vote
38
+ * across all of a tool's shingle hashes. Two tools whose descriptions
39
+ * share enough content have a SMALL Hamming distance between
40
+ * fingerprints — this is the O(1)-per-pair coarse filter that avoids an
41
+ * LLM call and stays cheap even at hundreds of registered tools (fast
42
+ * 64-bit XOR + popcount per pair).
43
+ * 3. An inverted shingle-hash index (hash -> set of tool indices) is built
44
+ * once, in O(total shingles). For each pair flagged by the SimHash
45
+ * filter (or found in the index) the SAME index is used to compute the
46
+ * actual overlapping shingle text — this is what makes the finding
47
+ * reportable (the SimHash score alone can't tell an operator *what*
48
+ * matched).
49
+ * 4. A fragment's "population" (how many distinct tools carry it) caps
50
+ * false positives: shared shingles that appear in more than
51
+ * `maxFragmentPopulation` (default 3) tools are template language
52
+ * (e.g. every ruflo `memory_*` tool's boilerplate), not an attack —
53
+ * Shamir-split attacks concentrate a fragment in a small conspiracy of
54
+ * 2-3 tools. Same population-cap idea as the CLI v1, reimplemented here
55
+ * against the hash index instead of raw substrings.
56
+ *
57
+ * This is a heuristic detector, not a security boundary: it reports
58
+ * SUSPECTS. Default action is warn + log; blocking is opt-in via
59
+ * `CLAUDE_FLOW_MCP_COMPOSITION_BLOCK=1` (see {@link evaluateToolComposition}).
60
+ *
61
+ * @module @claude-flow/security/mcp-composition-inspector
62
+ */
63
+ // ============================================================================
64
+ // SimHash primitives (plain TS, no dependency)
65
+ // ============================================================================
66
+ const FNV_OFFSET_BASIS_64 = 0xcbf29ce484222325n;
67
+ const FNV_PRIME_64 = 0x100000001b3n;
68
+ const MASK_64 = (1n << 64n) - 1n;
69
+ /** FNV-1a, 64-bit. Deterministic, dependency-free, good avalanche for short strings. */
70
+ export function fnv1a64(input) {
71
+ let hash = FNV_OFFSET_BASIS_64;
72
+ for (let i = 0; i < input.length; i++) {
73
+ hash ^= BigInt(input.charCodeAt(i));
74
+ hash = (hash * FNV_PRIME_64) & MASK_64;
75
+ }
76
+ return hash;
77
+ }
78
+ /**
79
+ * Classic SimHash: bit-vote a 64-bit fingerprint across a list of token
80
+ * hashes. Near-duplicate token sets (even with some fragments differing)
81
+ * produce fingerprints with a small Hamming distance.
82
+ */
83
+ export function simhash64(shingles) {
84
+ if (shingles.length === 0)
85
+ return 0n;
86
+ const bitVotes = new Array(64).fill(0);
87
+ for (const shingle of shingles) {
88
+ const h = fnv1a64(shingle);
89
+ for (let bit = 0; bit < 64; bit++) {
90
+ const set = (h >> BigInt(bit)) & 1n;
91
+ bitVotes[bit] += set === 1n ? 1 : -1;
92
+ }
93
+ }
94
+ let fingerprint = 0n;
95
+ for (let bit = 0; bit < 64; bit++) {
96
+ if (bitVotes[bit] > 0)
97
+ fingerprint |= 1n << BigInt(bit);
98
+ }
99
+ return fingerprint;
100
+ }
101
+ /** Hamming distance between two 64-bit fingerprints (0..64). */
102
+ export function hammingDistance64(a, b) {
103
+ let x = (a ^ b) & MASK_64;
104
+ let count = 0;
105
+ while (x > 0n) {
106
+ count += Number(x & 1n);
107
+ x >>= 1n;
108
+ }
109
+ return count;
110
+ }
111
+ // ============================================================================
112
+ // Shingling
113
+ // ============================================================================
114
+ function normalize(text) {
115
+ return text.toLowerCase().replace(/\s+/g, ' ').trim();
116
+ }
117
+ /** Overlapping word-shingles of `k` words each. Falls back to the whole (short) text as a single shingle. */
118
+ function wordShingles(text, k) {
119
+ const words = normalize(text).split(' ').filter(Boolean);
120
+ if (words.length === 0)
121
+ return [];
122
+ if (words.length < k)
123
+ return [words.join(' ')];
124
+ const shingles = [];
125
+ for (let i = 0; i + k <= words.length; i++) {
126
+ shingles.push(words.slice(i, i + k).join(' '));
127
+ }
128
+ return shingles;
129
+ }
130
+ export function inspectToolComposition(tools, options = {}) {
131
+ const shingleSize = options.shingleSize ?? 5;
132
+ const simhashThreshold = options.simhashThreshold ?? 0.6;
133
+ const fragmentOverlapThreshold = options.fragmentOverlapThreshold ?? 0.25;
134
+ const maxFragmentPopulation = options.maxFragmentPopulation ?? 3;
135
+ const minShingleChars = options.minShingleChars ?? 12;
136
+ const maxSampleFragments = options.maxSampleFragments ?? 5;
137
+ const indexed = tools.map((t, idx) => {
138
+ const shingles = wordShingles(t.description, shingleSize).filter((s) => s.length >= minShingleChars);
139
+ const hashKeys = new Set(shingles.map((s) => fnv1a64(s).toString(36)));
140
+ return {
141
+ idx,
142
+ name: t.name,
143
+ shingles,
144
+ hashKeys,
145
+ fingerprint: simhash64(shingles),
146
+ };
147
+ });
148
+ // Inverted index: shingle-hash -> {tools that carry it, sample text}.
149
+ const shingleToTools = new Map();
150
+ const shingleSampleText = new Map();
151
+ for (const t of indexed) {
152
+ for (const shingle of t.shingles) {
153
+ const key = fnv1a64(shingle).toString(36);
154
+ const set = shingleToTools.get(key) ?? new Set();
155
+ set.add(t.idx);
156
+ shingleToTools.set(key, set);
157
+ if (!shingleSampleText.has(key))
158
+ shingleSampleText.set(key, shingle);
159
+ }
160
+ }
161
+ const findings = [];
162
+ const stats = {
163
+ toolsScanned: tools.length,
164
+ pairsCompared: 0,
165
+ shinglesIndexed: shingleToTools.size,
166
+ candidatePairsFromSimhash: 0,
167
+ };
168
+ for (let i = 0; i < indexed.length; i++) {
169
+ for (let j = i + 1; j < indexed.length; j++) {
170
+ stats.pairsCompared++;
171
+ const a = indexed[i];
172
+ const b = indexed[j];
173
+ const hamming = hammingDistance64(a.fingerprint, b.fingerprint);
174
+ const simhashSimilarity = a.shingles.length > 0 && b.shingles.length > 0 ? 1 - hamming / 64 : 0;
175
+ if (simhashSimilarity >= simhashThreshold)
176
+ stats.candidatePairsFromSimhash++;
177
+ // Low-population shared shingles between this pair, via the inverted index.
178
+ let sharedCount = 0;
179
+ let maxPopulationSeen = 0;
180
+ const sample = [];
181
+ for (const key of a.hashKeys) {
182
+ if (!b.hashKeys.has(key))
183
+ continue;
184
+ const population = shingleToTools.get(key)?.size ?? 0;
185
+ if (population > maxFragmentPopulation)
186
+ continue; // template language, not an attack signature
187
+ sharedCount++;
188
+ maxPopulationSeen = Math.max(maxPopulationSeen, population);
189
+ if (sample.length < maxSampleFragments) {
190
+ const text = shingleSampleText.get(key);
191
+ if (text)
192
+ sample.push(text);
193
+ }
194
+ }
195
+ const minShingleCount = Math.max(1, Math.min(a.shingles.length, b.shingles.length));
196
+ const fragmentOverlapScore = sharedCount / minShingleCount;
197
+ const flagBySimhash = simhashSimilarity >= simhashThreshold && sharedCount > 0;
198
+ const flagByFragment = fragmentOverlapScore >= fragmentOverlapThreshold && sharedCount > 0;
199
+ if (!flagBySimhash && !flagByFragment)
200
+ continue;
201
+ findings.push({
202
+ toolA: a.name,
203
+ toolB: b.name,
204
+ simhashSimilarity: Number(simhashSimilarity.toFixed(3)),
205
+ fragmentOverlapScore: Number(fragmentOverlapScore.toFixed(3)),
206
+ sharedFragments: sample,
207
+ fragmentPopulation: maxPopulationSeen,
208
+ reason: flagBySimhash
209
+ ? `SimHash similarity ${simhashSimilarity.toFixed(2)} (Hamming distance ${hamming}/64) with ${sharedCount} low-population shared shingle(s) (population <= ${maxFragmentPopulation}) — possible cross-tool instruction fragment`
210
+ : `Fragment overlap ${fragmentOverlapScore.toFixed(2)} (${sharedCount}/${minShingleCount} shingles shared, population <= ${maxFragmentPopulation}) — possible Shamir-split instruction fragment`,
211
+ });
212
+ }
213
+ }
214
+ return { findings, stats };
215
+ }
216
+ // ============================================================================
217
+ // Guard: env-var-gated warn/block decision
218
+ // ============================================================================
219
+ const COMPOSITION_BLOCK_ENV = 'CLAUDE_FLOW_MCP_COMPOSITION_BLOCK';
220
+ /** Reads `CLAUDE_FLOW_MCP_COMPOSITION_BLOCK` fresh on every call. '1' or 'true' (case-insensitive) enables blocking; unset/anything else keeps the default warn+log posture. */
221
+ export function isCompositionBlockEnabled() {
222
+ const raw = process.env[COMPOSITION_BLOCK_ENV];
223
+ return raw === '1' || raw?.toLowerCase() === 'true';
224
+ }
225
+ /**
226
+ * Runs {@link inspectToolComposition} and applies the ADR-320 default
227
+ * posture: warn + log (via `console.warn`) unless
228
+ * `CLAUDE_FLOW_MCP_COMPOSITION_BLOCK=1`, in which case the chain is marked
229
+ * `blocked: true` — the caller (the pre-task hook / MCP dispatcher) decides
230
+ * what "blocked" means operationally; this function does not throw or abort
231
+ * anything itself.
232
+ */
233
+ export function evaluateToolComposition(tools, options = {}) {
234
+ const result = inspectToolComposition(tools, options);
235
+ if (result.findings.length === 0) {
236
+ return { result, action: 'none', blocked: false };
237
+ }
238
+ const summary = `MCP Composition Inspector: ${result.findings.length} suspicious cross-tool fragment(s) ` +
239
+ `across ${result.stats.toolsScanned} tools (${result.stats.pairsCompared} pairs compared)`;
240
+ if (isCompositionBlockEnabled()) {
241
+ const message = `${summary} — blocking chain (${COMPOSITION_BLOCK_ENV}=1)`;
242
+ console.warn(`[mcp-composition-inspector] ${message}`);
243
+ for (const f of result.findings) {
244
+ console.warn(` - ${f.toolA} <-> ${f.toolB}: ${f.reason}`);
245
+ }
246
+ return { result, action: 'block', blocked: true, message };
247
+ }
248
+ console.warn(`[mcp-composition-inspector] ${summary}`);
249
+ for (const f of result.findings) {
250
+ console.warn(` - ${f.toolA} <-> ${f.toolB}: ${f.reason}`);
251
+ }
252
+ return { result, action: 'warn', blocked: false, message: summary };
253
+ }
254
+ //# sourceMappingURL=mcp-composition-inspector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-composition-inspector.js","sourceRoot":"","sources":["../src/mcp-composition-inspector.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Opens the system default browser for the `ruflo auth login` flow. A
3
+ * TypeScript port of meta-proxy's `src/oauth/browser.rs`.
4
+ *
5
+ * Deliberately does NOT go through `SafeExecutor.execute()`: `SafeExecutor`'s
6
+ * blocked-argument patterns (`safe-executor.ts`'s `DEFAULT_BLOCKED_PATTERNS`)
7
+ * include a bare `&`, which is meant to catch shell-metacharacter injection in
8
+ * command-line-style arguments — but it also appears in every real OAuth
9
+ * authorize URL's query string (`?a=1&b=2&...`), so passing one through would
10
+ * false-positive-reject on every real invocation. That blocklist is the wrong
11
+ * tool for a URL argument: with `shell: false` (used here, same as
12
+ * `SafeExecutor`), a single argv element containing `&` is inert — there's no
13
+ * shell to interpret it. The actual safety property that matters is "the URL
14
+ * was constructed by us from validated components, never from raw external
15
+ * input" (this module's `authorizeUrl()` in `client.ts` is the only caller),
16
+ * which `assertSafeUrl` below checks directly instead.
17
+ *
18
+ * @module v3/security/oauth/browser
19
+ */
20
+ /**
21
+ * Attempts to open `url` in the system default browser. Resolves whether or
22
+ * not a browser window actually appeared — this cannot be confirmed in
23
+ * general, which is why the caller always also prints the URL as a fallback.
24
+ */
25
+ export declare function openBrowser(url: string): Promise<void>;
26
+ /** Pure command selection, exported for platform regression tests. */
27
+ export declare function browserCommand(url: string, platform?: NodeJS.Platform): {
28
+ cmd: string;
29
+ args: string[];
30
+ };
31
+ //# sourceMappingURL=browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../../src/oauth/browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAYH;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQtD;AAED,sEAAsE;AACtE,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAWzH"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Opens the system default browser for the `ruflo auth login` flow. A
3
+ * TypeScript port of meta-proxy's `src/oauth/browser.rs`.
4
+ *
5
+ * Deliberately does NOT go through `SafeExecutor.execute()`: `SafeExecutor`'s
6
+ * blocked-argument patterns (`safe-executor.ts`'s `DEFAULT_BLOCKED_PATTERNS`)
7
+ * include a bare `&`, which is meant to catch shell-metacharacter injection in
8
+ * command-line-style arguments — but it also appears in every real OAuth
9
+ * authorize URL's query string (`?a=1&b=2&...`), so passing one through would
10
+ * false-positive-reject on every real invocation. That blocklist is the wrong
11
+ * tool for a URL argument: with `shell: false` (used here, same as
12
+ * `SafeExecutor`), a single argv element containing `&` is inert — there's no
13
+ * shell to interpret it. The actual safety property that matters is "the URL
14
+ * was constructed by us from validated components, never from raw external
15
+ * input" (this module's `authorizeUrl()` in `client.ts` is the only caller),
16
+ * which `assertSafeUrl` below checks directly instead.
17
+ *
18
+ * @module v3/security/oauth/browser
19
+ */
20
+ import { execFile } from 'node:child_process';
21
+ /** Throws if `url` isn't a well-formed https/http URL — the one check that matters here. */
22
+ function assertSafeUrl(url) {
23
+ const parsed = new URL(url); // throws on malformed input
24
+ if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
25
+ throw new Error(`refusing to open non-http(s) URL scheme: ${parsed.protocol}`);
26
+ }
27
+ }
28
+ /**
29
+ * Attempts to open `url` in the system default browser. Resolves whether or
30
+ * not a browser window actually appeared — this cannot be confirmed in
31
+ * general, which is why the caller always also prints the URL as a fallback.
32
+ */
33
+ export function openBrowser(url) {
34
+ assertSafeUrl(url);
35
+ const { cmd, args } = browserCommand(url);
36
+ return new Promise((resolve) => {
37
+ execFile(cmd, args, { shell: false, windowsHide: true }, () => resolve());
38
+ });
39
+ }
40
+ /** Pure command selection, exported for platform regression tests. */
41
+ export function browserCommand(url, platform = process.platform) {
42
+ assertSafeUrl(url);
43
+ if (platform === 'darwin') {
44
+ return { cmd: 'open', args: [url] };
45
+ }
46
+ else if (platform === 'win32') {
47
+ // cmd.exe treats OAuth query-string `&` separators as command operators,
48
+ // truncating the URL before client_id. Use the URL handler without a shell.
49
+ return { cmd: 'rundll32.exe', args: ['url.dll,FileProtocolHandler', url] };
50
+ }
51
+ else {
52
+ return { cmd: 'xdg-open', args: [url] };
53
+ }
54
+ }
55
+ //# sourceMappingURL=browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.js","sourceRoot":"","sources":["../../src/oauth/browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE9C,4FAA4F;AAC5F,SAAS,aAAa,CAAC,GAAW;IAChC,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,4BAA4B;IACzD,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QAChE,MAAM,IAAI,KAAK,CAAC,4CAA4C,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;IACjF,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,aAAa,CAAC,GAAG,CAAC,CAAC;IAEnB,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;IAE1C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;IAC5E,CAAC,CAAC,CAAC;AACL,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,cAAc,CAAC,GAAW,EAAE,WAA4B,OAAO,CAAC,QAAQ;IACtF,aAAa,CAAC,GAAG,CAAC,CAAC;IACnB,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC1B,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;IACtC,CAAC;SAAM,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QAChC,yEAAyE;QACzE,4EAA4E;QAC5E,OAAO,EAAE,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,6BAA6B,EAAE,GAAG,CAAC,EAAE,CAAC;IAC7E,CAAC;SAAM,CAAC;QACN,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;IAC1C,CAAC;AACH,CAAC"}