circle-ir 3.80.0 → 3.81.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.
Files changed (31) hide show
  1. package/configs/sinks/xss.yaml +2 -1
  2. package/dist/analysis/config-loader.d.ts.map +1 -1
  3. package/dist/analysis/config-loader.js +6 -1
  4. package/dist/analysis/config-loader.js.map +1 -1
  5. package/dist/analysis/passes/_credential-helpers.d.ts +40 -0
  6. package/dist/analysis/passes/_credential-helpers.d.ts.map +1 -0
  7. package/dist/analysis/passes/_credential-helpers.js +152 -0
  8. package/dist/analysis/passes/_credential-helpers.js.map +1 -0
  9. package/dist/analysis/passes/cleartext-credential-transport-pass.d.ts +42 -0
  10. package/dist/analysis/passes/cleartext-credential-transport-pass.d.ts.map +1 -0
  11. package/dist/analysis/passes/cleartext-credential-transport-pass.js +196 -0
  12. package/dist/analysis/passes/cleartext-credential-transport-pass.js.map +1 -0
  13. package/dist/analysis/passes/plaintext-password-storage-pass.d.ts +47 -0
  14. package/dist/analysis/passes/plaintext-password-storage-pass.d.ts.map +1 -0
  15. package/dist/analysis/passes/plaintext-password-storage-pass.js +159 -0
  16. package/dist/analysis/passes/plaintext-password-storage-pass.js.map +1 -0
  17. package/dist/analysis/passes/weak-password-encoding-pass.d.ts +40 -0
  18. package/dist/analysis/passes/weak-password-encoding-pass.d.ts.map +1 -0
  19. package/dist/analysis/passes/weak-password-encoding-pass.js +157 -0
  20. package/dist/analysis/passes/weak-password-encoding-pass.js.map +1 -0
  21. package/dist/analysis/passes/weak-password-hash-pass.d.ts +49 -0
  22. package/dist/analysis/passes/weak-password-hash-pass.d.ts.map +1 -0
  23. package/dist/analysis/passes/weak-password-hash-pass.js +225 -0
  24. package/dist/analysis/passes/weak-password-hash-pass.js.map +1 -0
  25. package/dist/analyzer.d.ts.map +1 -1
  26. package/dist/analyzer.js +12 -0
  27. package/dist/analyzer.js.map +1 -1
  28. package/dist/browser/circle-ir.js +564 -1
  29. package/dist/core/circle-ir-core.cjs +6 -1
  30. package/dist/core/circle-ir-core.js +6 -1
  31. package/package.json +1 -1
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Pass: plaintext-password-storage (CWE-256, category: security)
3
+ *
4
+ * Detects writing a credential-named identifier to a persistent store
5
+ * (file, KV store, cookie, database) without first passing it through a
6
+ * cryptographic hash / KDF.
7
+ *
8
+ * Detection per language:
9
+ * Python:
10
+ * - `open(...).write(password)` / `f.write(password)`
11
+ * - `pickle.dump(password, ...)` / `json.dump(...)` / `yaml.dump(...)`
12
+ * - `redis.set(key, password)`
13
+ * JS/TS:
14
+ * - `fs.writeFile|writeFileSync|appendFile(path, password)`
15
+ * - `localStorage.setItem(key, password)` / `sessionStorage.setItem`
16
+ * - `redis.set(key, password)`
17
+ * Java:
18
+ * - `Files.write|writeString(path, password)`
19
+ * - `FileWriter.write(password)`
20
+ * Go:
21
+ * - `os.WriteFile(name, []byte(password), ...)`
22
+ * - `f.WriteString(password)` / `f.Write([]byte(password))`
23
+ *
24
+ * Heuristic for "not hashed": intraprocedural — walk all calls earlier
25
+ * in the same `in_method` scope; if any of them is a known hash/KDF
26
+ * (see _credential-helpers `isHashFunctionCall`) and consumes the
27
+ * credential identifier, suppress.
28
+ *
29
+ * This is intentionally lightweight (no full DFG); FP risk skewed toward
30
+ * recall loss for cross-function hashing (controller → service.hash →
31
+ * repo.store), which is acceptable for v1.
32
+ */
33
+ import { argLooksLikeCredential, priorHashOf, } from './_credential-helpers.js';
34
+ function isWriteStorageCall(call, language) {
35
+ const method = call.method_name ?? '';
36
+ const receiver = call.receiver ?? '';
37
+ const recvLower = receiver.toLowerCase();
38
+ if (language === 'python') {
39
+ // open(...).write(pw) — receiver is a file handle; we approximate by
40
+ // method name `write` and check arg credential below.
41
+ if (method === 'write' || method === 'writelines') {
42
+ return { credPos: 0, api: `<file>.${method}` };
43
+ }
44
+ if ((recvLower === 'pickle' || recvLower === 'json' || recvLower === 'yaml') &&
45
+ (method === 'dump' || method === 'dumps')) {
46
+ return { credPos: 0, api: `${receiver}.${method}` };
47
+ }
48
+ if (recvLower === 'redis' && (method === 'set' || method === 'setex' || method === 'hset')) {
49
+ return { credPos: 1, api: `redis.${method}` };
50
+ }
51
+ }
52
+ if (language === 'javascript' || language === 'typescript') {
53
+ if ((recvLower === 'fs' || recvLower.endsWith('.fs')) &&
54
+ (method === 'writeFile' || method === 'writeFileSync' ||
55
+ method === 'appendFile' || method === 'appendFileSync')) {
56
+ return { credPos: 1, api: `fs.${method}` };
57
+ }
58
+ if ((recvLower === 'localstorage' || recvLower === 'sessionstorage') &&
59
+ method === 'setItem') {
60
+ return { credPos: 1, api: `${receiver}.setItem` };
61
+ }
62
+ if (recvLower === 'redis' && (method === 'set' || method === 'setex' || method === 'hset')) {
63
+ return { credPos: 1, api: `redis.${method}` };
64
+ }
65
+ }
66
+ if (language === 'java') {
67
+ if ((receiver === 'Files' || receiver.endsWith('.Files')) &&
68
+ (method === 'write' || method === 'writeString')) {
69
+ return { credPos: 1, api: `Files.${method}` };
70
+ }
71
+ // FileWriter.write(pw) — instance call, single arg.
72
+ if (method === 'write') {
73
+ // Heuristic: receiver name contains "writer" / "file" / "stream".
74
+ const lc = (receiver ?? '').toLowerCase();
75
+ if (lc.includes('writer') || lc.includes('file') || lc.includes('stream')) {
76
+ return { credPos: 0, api: `${receiver}.write` };
77
+ }
78
+ }
79
+ }
80
+ if (language === 'go') {
81
+ if (receiver === 'os' || receiver.endsWith('/os')) {
82
+ if (method === 'WriteFile')
83
+ return { credPos: 1, api: 'os.WriteFile' };
84
+ }
85
+ if (receiver === 'ioutil' || receiver.endsWith('/ioutil')) {
86
+ if (method === 'WriteFile')
87
+ return { credPos: 1, api: 'ioutil.WriteFile' };
88
+ }
89
+ if (method === 'WriteString' || method === 'Write') {
90
+ return { credPos: 0, api: `<file>.${method}` };
91
+ }
92
+ }
93
+ return null;
94
+ }
95
+ export class PlaintextPasswordStoragePass {
96
+ name = 'plaintext-password-storage';
97
+ category = 'security';
98
+ run(ctx) {
99
+ const { graph, language } = ctx;
100
+ const file = graph.ir.meta.file;
101
+ const findings = [];
102
+ // Group calls by in_method for cheap prior-hash lookup.
103
+ const callsByScope = new Map();
104
+ for (const call of graph.ir.calls) {
105
+ const scope = call.in_method ?? '<top>';
106
+ const arr = callsByScope.get(scope) ?? [];
107
+ arr.push(call);
108
+ callsByScope.set(scope, arr);
109
+ }
110
+ for (const call of graph.ir.calls) {
111
+ const spec = isWriteStorageCall(call, language);
112
+ if (!spec)
113
+ continue;
114
+ const credArg = call.arguments.find((a) => a.position === spec.credPos);
115
+ if (!credArg)
116
+ continue;
117
+ if (!argLooksLikeCredential(credArg))
118
+ continue;
119
+ // Resolve the credential identifier name.
120
+ const identExpr = (credArg.expression ?? '').trim();
121
+ const head = identExpr.split(/[.\s(]/, 1)[0] ?? '';
122
+ const identifier = credArg.variable ?? head;
123
+ if (!identifier)
124
+ continue;
125
+ // Suppress if the identifier was hashed earlier in the same scope.
126
+ const scope = call.in_method ?? '<top>';
127
+ const scopeCalls = callsByScope.get(scope) ?? [];
128
+ const prior = scopeCalls.filter((c) => c.location.line < call.location.line);
129
+ if (priorHashOf(identifier, prior))
130
+ continue;
131
+ // Suppress if the credArg expression itself contains a hash call
132
+ // inline: `f.write(bcrypt.hashpw(pw))`.
133
+ if (/\b(?:hashpw|hash|sha\d+|md5|bcrypt|argon2|pbkdf2|digest)\b/i
134
+ .test(credArg.expression ?? '')) {
135
+ continue;
136
+ }
137
+ const line = call.location.line;
138
+ findings.push({ line, language, api: spec.api, identifier });
139
+ ctx.addFinding({
140
+ id: `${this.name}-${file}-${line}`,
141
+ pass: this.name,
142
+ category: this.category,
143
+ rule_id: this.name,
144
+ cwe: 'CWE-256',
145
+ severity: 'high',
146
+ level: 'warning',
147
+ message: `Credential \`${identifier}\` written in plaintext via \`${spec.api}\`. ` +
148
+ 'Passwords / secrets must be hashed (Argon2id, bcrypt) before storage.',
149
+ file,
150
+ line,
151
+ fix: 'Hash the credential with Argon2id / bcrypt before writing it to ' +
152
+ 'disk, cookie, KV store, or database.',
153
+ evidence: { identifier, api: spec.api, language },
154
+ });
155
+ }
156
+ return { findings };
157
+ }
158
+ }
159
+ //# sourceMappingURL=plaintext-password-storage-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plaintext-password-storage-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/plaintext-password-storage-pass.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Pass: weak-password-encoding (CWE-261, category: security)
3
+ *
4
+ * Detects use of an encoding (base64 / hex) on a credential-named identifier.
5
+ * Encoding is NOT encryption — base64-encoding a password before storing or
6
+ * transmitting it provides no confidentiality. Common anti-pattern.
7
+ *
8
+ * Detection per language:
9
+ * Python:
10
+ * - `base64.b64encode(password)` / `.urlsafe_b64encode(...)`
11
+ * - `binascii.hexlify(password)`
12
+ * JS/TS:
13
+ * - `Buffer.from(password).toString('base64')` / `.toString('hex')`
14
+ * - `btoa(password)`
15
+ * Java:
16
+ * - `Base64.getEncoder().encodeToString(passwordBytes)`
17
+ * - `Base64.getUrlEncoder().encodeToString(...)`
18
+ * - `Hex.encodeHexString(passwordBytes)`
19
+ * Go:
20
+ * - `base64.StdEncoding.EncodeToString(passwordBytes)`
21
+ * - `hex.EncodeToString(...)`
22
+ *
23
+ * FP-guard: skip when the encoded value is part of an HTTP Basic auth
24
+ * header construction (`"Basic " + base64(...)`) — that IS the spec.
25
+ */
26
+ import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
27
+ export interface WeakPasswordEncodingResult {
28
+ findings: Array<{
29
+ line: number;
30
+ language: string;
31
+ api: string;
32
+ }>;
33
+ }
34
+ export declare class WeakPasswordEncodingPass implements AnalysisPass<WeakPasswordEncodingResult> {
35
+ readonly name = "weak-password-encoding";
36
+ readonly category: "security";
37
+ run(ctx: PassContext): WeakPasswordEncodingResult;
38
+ private detect;
39
+ }
40
+ //# sourceMappingURL=weak-password-encoding-pass.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"weak-password-encoding-pass.d.ts","sourceRoot":"","sources":["../../../src/analysis/passes/weak-password-encoding-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAO9E,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,KAAK,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,GAAG,EAAE,MAAM,CAAC;KACb,CAAC,CAAC;CACJ;AAcD,qBAAa,wBAAyB,YAAW,YAAY,CAAC,0BAA0B,CAAC;IACvF,QAAQ,CAAC,IAAI,4BAA4B;IACzC,QAAQ,CAAC,QAAQ,EAAG,UAAU,CAAU;IAExC,GAAG,CAAC,GAAG,EAAE,WAAW,GAAG,0BAA0B;IAoCjD,OAAO,CAAC,MAAM;CAsFf"}
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Pass: weak-password-encoding (CWE-261, category: security)
3
+ *
4
+ * Detects use of an encoding (base64 / hex) on a credential-named identifier.
5
+ * Encoding is NOT encryption — base64-encoding a password before storing or
6
+ * transmitting it provides no confidentiality. Common anti-pattern.
7
+ *
8
+ * Detection per language:
9
+ * Python:
10
+ * - `base64.b64encode(password)` / `.urlsafe_b64encode(...)`
11
+ * - `binascii.hexlify(password)`
12
+ * JS/TS:
13
+ * - `Buffer.from(password).toString('base64')` / `.toString('hex')`
14
+ * - `btoa(password)`
15
+ * Java:
16
+ * - `Base64.getEncoder().encodeToString(passwordBytes)`
17
+ * - `Base64.getUrlEncoder().encodeToString(...)`
18
+ * - `Hex.encodeHexString(passwordBytes)`
19
+ * Go:
20
+ * - `base64.StdEncoding.EncodeToString(passwordBytes)`
21
+ * - `hex.EncodeToString(...)`
22
+ *
23
+ * FP-guard: skip when the encoded value is part of an HTTP Basic auth
24
+ * header construction (`"Basic " + base64(...)`) — that IS the spec.
25
+ */
26
+ import { argLooksLikeCredential, literalAt, } from './_credential-helpers.js';
27
+ function isBasicAuthContext(call, code) {
28
+ // Look at the source line for "Basic " literal nearby — heuristic
29
+ // for HTTP Basic auth construction where base64 is part of the spec.
30
+ const line = call.location.line;
31
+ if (line < 1)
32
+ return false;
33
+ const lines = code.split('\n');
34
+ const start = Math.max(0, line - 2);
35
+ const end = Math.min(lines.length, line + 1);
36
+ const window = lines.slice(start, end).join('\n');
37
+ return /["'`]Basic\s/i.test(window);
38
+ }
39
+ export class WeakPasswordEncodingPass {
40
+ name = 'weak-password-encoding';
41
+ category = 'security';
42
+ run(ctx) {
43
+ const { graph, language, code } = ctx;
44
+ const file = graph.ir.meta.file;
45
+ const findings = [];
46
+ for (const call of graph.ir.calls) {
47
+ const api = this.detect(call, language);
48
+ if (!api)
49
+ continue;
50
+ if (isBasicAuthContext(call, code))
51
+ continue;
52
+ const line = call.location.line;
53
+ findings.push({ line, language, api });
54
+ ctx.addFinding({
55
+ id: `${this.name}-${file}-${line}`,
56
+ pass: this.name,
57
+ category: this.category,
58
+ rule_id: this.name,
59
+ cwe: 'CWE-261',
60
+ severity: 'medium',
61
+ level: 'warning',
62
+ message: `Credential encoded via \`${api}\` — encoding is NOT encryption. ` +
63
+ 'Base64/hex provide no confidentiality; anyone with the encoded value can decode it.',
64
+ file,
65
+ line,
66
+ fix: 'For storage, use a password hash (Argon2id / bcrypt). ' +
67
+ 'For transport, use TLS. For symmetric secrecy, use authenticated encryption (AES-GCM).',
68
+ evidence: { api, language },
69
+ });
70
+ }
71
+ return { findings };
72
+ }
73
+ detect(call, language) {
74
+ const method = call.method_name ?? '';
75
+ const receiver = call.receiver ?? '';
76
+ const recvLower = receiver.toLowerCase();
77
+ const arg0 = call.arguments.find((a) => a.position === 0);
78
+ if (language === 'python') {
79
+ // base64.b64encode(password)
80
+ if (recvLower === 'base64' &&
81
+ (method === 'b64encode' || method === 'urlsafe_b64encode' ||
82
+ method === 'standard_b64encode')) {
83
+ if (argLooksLikeCredential(arg0))
84
+ return `base64.${method}`;
85
+ }
86
+ // binascii.hexlify(password)
87
+ if (recvLower === 'binascii' && method === 'hexlify') {
88
+ if (argLooksLikeCredential(arg0))
89
+ return 'binascii.hexlify';
90
+ }
91
+ }
92
+ if (language === 'javascript' || language === 'typescript') {
93
+ // Buffer.from(password).toString('base64')
94
+ if (method === 'toString') {
95
+ const encoding = literalAt(call, 0);
96
+ if (encoding === 'base64' || encoding === 'hex' || encoding === 'base64url') {
97
+ // Receiver expression should look like `Buffer.from(<credential>)`.
98
+ // Conservative: check if receiver text contains "Buffer.from" and a
99
+ // credential keyword.
100
+ const recv = (receiver ?? '').toLowerCase();
101
+ if (recv.includes('buffer.from') &&
102
+ /(?:password|passwd|pwd|secret|api[_-]?key|auth[_-]?token|private[_-]?key|access[_-]?key|credential)/i
103
+ .test(receiver ?? '')) {
104
+ return `Buffer.from().toString('${encoding}')`;
105
+ }
106
+ }
107
+ }
108
+ // btoa(password)
109
+ if (method === 'btoa' && receiver === '') {
110
+ if (argLooksLikeCredential(arg0))
111
+ return 'btoa';
112
+ }
113
+ }
114
+ if (language === 'java') {
115
+ // Base64.getEncoder().encodeToString(passwordBytes)
116
+ // Or Base64.getUrlEncoder().encodeToString(...).
117
+ if (method === 'encodeToString') {
118
+ const recv = (receiver ?? '').toLowerCase();
119
+ if (recv.includes('encoder') || recv.includes('base64')) {
120
+ // arg[0] expr typically looks like `password.getBytes()`.
121
+ const expr = (arg0?.expression ?? '').trim();
122
+ const head = expr.split(/[.\s(]/, 1)[0] ?? '';
123
+ if (argLooksLikeCredential({ position: 0, expression: head, variable: head })) {
124
+ return 'Base64.encodeToString';
125
+ }
126
+ }
127
+ }
128
+ // Hex.encodeHexString(passwordBytes)
129
+ if (method === 'encodeHexString' &&
130
+ (receiver === 'Hex' || receiver.endsWith('.Hex'))) {
131
+ const expr = (arg0?.expression ?? '').trim();
132
+ const head = expr.split(/[.\s(]/, 1)[0] ?? '';
133
+ if (argLooksLikeCredential({ position: 0, expression: head, variable: head })) {
134
+ return 'Hex.encodeHexString';
135
+ }
136
+ }
137
+ }
138
+ if (language === 'go') {
139
+ // base64.StdEncoding.EncodeToString(passwordBytes)
140
+ if (method === 'EncodeToString') {
141
+ const recv = (receiver ?? '').toLowerCase();
142
+ if (recv.includes('base64') || recv.includes('hex') ||
143
+ recv.includes('encoding')) {
144
+ const expr = (arg0?.expression ?? '').trim();
145
+ // Strip `[]byte(...)` wrapper.
146
+ const inner = expr.replace(/^\[\]byte\s*\(\s*/, '').replace(/\s*\)\s*$/, '');
147
+ const head = inner.split(/[.\s(]/, 1)[0] ?? '';
148
+ if (argLooksLikeCredential({ position: 0, expression: head, variable: head })) {
149
+ return recv.includes('hex') ? 'hex.EncodeToString' : 'base64.EncodeToString';
150
+ }
151
+ }
152
+ }
153
+ }
154
+ return null;
155
+ }
156
+ }
157
+ //# sourceMappingURL=weak-password-encoding-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"weak-password-encoding-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/weak-password-encoding-pass.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Pass: weak-password-hash (CWE-916, category: security)
3
+ *
4
+ * Detects use of a fast / unsalted hash, or a KDF with insufficient
5
+ * computational cost, applied to a credential-named identifier.
6
+ *
7
+ * Distinct from `weak-hash` (CWE-328):
8
+ * - `weak-hash` flags broken algorithms (MD2/MD4/MD5/SHA-1) at any call site.
9
+ * - `weak-password-hash` flags algorithm/cost choices that are SAFE for
10
+ * general digests but UNSAFE for password storage (e.g. plain SHA-256
11
+ * of a password, bcrypt cost < 10, PBKDF2 iterations < 100k).
12
+ *
13
+ * Detection per language:
14
+ * Python:
15
+ * - `hashlib.sha256(password)` / `.sha512(...)` / etc. where the
16
+ * argument is a credential-named identifier.
17
+ * - `bcrypt.hashpw(pw, bcrypt.gensalt(rounds=N))` where N < 10.
18
+ * - `PBKDF2HMAC(..., iterations=N).derive(pw)` where N < 100000.
19
+ * JS/TS:
20
+ * - `crypto.createHash('sha256').update(password).digest()`.
21
+ * - `bcrypt.hash(pw, N)` / `bcrypt.hashSync(pw, N)` where N < 10.
22
+ * - `crypto.pbkdf2Sync(pw, salt, N, ...)` where N < 100000.
23
+ * Java:
24
+ * - `MessageDigest.getInstance("SHA-256")` followed by `.update(pw)` —
25
+ * conservative: detect `MessageDigest.getInstance` + `.update(credIdent)`
26
+ * on any non-broken algorithm. (Broken algos already flagged by weak-hash.)
27
+ * - `PBEKeySpec(pw, salt, N, ...)` where N < 100000.
28
+ * Go:
29
+ * - `sha256.Sum256([]byte(password))`, `sha512.Sum512([]byte(password))`.
30
+ * - `bcrypt.GenerateFromPassword(pw, cost)` where cost < 10.
31
+ *
32
+ * Aligned with: OWASP ASVS 2.4.1, NIST SP 800-63B §5.1.1.2, gosec G401-style.
33
+ */
34
+ import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
35
+ export interface WeakPasswordHashResult {
36
+ findings: Array<{
37
+ line: number;
38
+ language: string;
39
+ kind: 'fast-unsalted-hash' | 'low-bcrypt-cost' | 'low-pbkdf2-iterations';
40
+ api: string;
41
+ }>;
42
+ }
43
+ export declare class WeakPasswordHashPass implements AnalysisPass<WeakPasswordHashResult> {
44
+ readonly name = "weak-password-hash";
45
+ readonly category: "security";
46
+ run(ctx: PassContext): WeakPasswordHashResult;
47
+ private detect;
48
+ }
49
+ //# sourceMappingURL=weak-password-hash-pass.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"weak-password-hash-pass.d.ts","sourceRoot":"","sources":["../../../src/analysis/passes/weak-password-hash-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAmB9E,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,KAAK,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,oBAAoB,GAAG,iBAAiB,GAAG,uBAAuB,CAAC;QACzE,GAAG,EAAE,MAAM,CAAC;KACb,CAAC,CAAC;CACJ;AAsBD,qBAAa,oBAAqB,YAAW,YAAY,CAAC,sBAAsB,CAAC;IAC/E,QAAQ,CAAC,IAAI,wBAAwB;IACrC,QAAQ,CAAC,QAAQ,EAAG,UAAU,CAAU;IAExC,GAAG,CAAC,GAAG,EAAE,WAAW,GAAG,sBAAsB;IA0C7C,OAAO,CAAC,MAAM;CAyIf"}