agent-vault-cli 0.1.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/.cursor/skills/npm-publish/SKILL.md +58 -0
- package/.github/workflows/ci.yml +67 -0
- package/README.md +164 -0
- package/ROADMAP.md +986 -0
- package/dist/commands/config.d.ts +8 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +67 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/delete.d.ts +7 -0
- package/dist/commands/delete.d.ts.map +1 -0
- package/dist/commands/delete.js +30 -0
- package/dist/commands/delete.js.map +1 -0
- package/dist/commands/login.d.ts +7 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +37 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/register.d.ts +13 -0
- package/dist/commands/register.d.ts.map +1 -0
- package/dist/commands/register.js +160 -0
- package/dist/commands/register.js.map +1 -0
- package/dist/core/audit.d.ts +15 -0
- package/dist/core/audit.d.ts.map +1 -0
- package/dist/core/audit.js +36 -0
- package/dist/core/audit.js.map +1 -0
- package/dist/core/browser.d.ts +7 -0
- package/dist/core/browser.d.ts.map +1 -0
- package/dist/core/browser.js +104 -0
- package/dist/core/browser.js.map +1 -0
- package/dist/core/config.d.ts +9 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +80 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/crypto.d.ts +17 -0
- package/dist/core/crypto.d.ts.map +1 -0
- package/dist/core/crypto.js +90 -0
- package/dist/core/crypto.js.map +1 -0
- package/dist/core/fields.d.ts +5 -0
- package/dist/core/fields.d.ts.map +1 -0
- package/dist/core/fields.js +54 -0
- package/dist/core/fields.js.map +1 -0
- package/dist/core/keychain.d.ts +5 -0
- package/dist/core/keychain.d.ts.map +1 -0
- package/dist/core/keychain.js +97 -0
- package/dist/core/keychain.js.map +1 -0
- package/dist/core/origin.d.ts +25 -0
- package/dist/core/origin.d.ts.map +1 -0
- package/dist/core/origin.js +73 -0
- package/dist/core/origin.js.map +1 -0
- package/dist/core/ratelimit.d.ts +10 -0
- package/dist/core/ratelimit.d.ts.map +1 -0
- package/dist/core/ratelimit.js +70 -0
- package/dist/core/ratelimit.js.map +1 -0
- package/dist/core/secure-memory.d.ts +39 -0
- package/dist/core/secure-memory.d.ts.map +1 -0
- package/dist/core/secure-memory.js +68 -0
- package/dist/core/secure-memory.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +129 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts +27 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +58 -0
- package/src/commands/config.ts +84 -0
- package/src/commands/delete.ts +39 -0
- package/src/commands/login.ts +49 -0
- package/src/commands/register.ts +188 -0
- package/src/core/audit.ts +59 -0
- package/src/core/browser.ts +131 -0
- package/src/core/config.ts +91 -0
- package/src/core/crypto.ts +106 -0
- package/src/core/fields.ts +59 -0
- package/src/core/keychain.ts +110 -0
- package/src/core/origin.ts +90 -0
- package/src/core/ratelimit.ts +89 -0
- package/src/core/secure-memory.ts +78 -0
- package/src/index.ts +133 -0
- package/src/types/index.ts +31 -0
- package/tests/browser-password-manager.test.ts +1023 -0
- package/tests/crypto.test.ts +140 -0
- package/tests/e2e.test.ts +565 -0
- package/tests/fixtures/server.ts +59 -0
- package/tests/security.test.ts +113 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +17 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { readFile, writeFile, mkdir, chmod, access, constants } from 'node:fs/promises';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { lock } from 'proper-lockfile';
|
|
5
|
+
import { logAuditEvent } from './audit.js';
|
|
6
|
+
const CONFIG_DIR = join(homedir(), '.agent-vault');
|
|
7
|
+
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
8
|
+
// Secure file permissions: owner read/write only
|
|
9
|
+
const SECURE_DIR_MODE = 0o700;
|
|
10
|
+
const SECURE_FILE_MODE = 0o600;
|
|
11
|
+
async function ensureConfigDir() {
|
|
12
|
+
await mkdir(CONFIG_DIR, { recursive: true, mode: SECURE_DIR_MODE });
|
|
13
|
+
}
|
|
14
|
+
async function fileExists(path) {
|
|
15
|
+
try {
|
|
16
|
+
await access(path, constants.F_OK);
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export async function loadConfig() {
|
|
24
|
+
try {
|
|
25
|
+
const data = await readFile(CONFIG_FILE, 'utf-8');
|
|
26
|
+
return JSON.parse(data);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return {};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export async function saveConfig(config) {
|
|
33
|
+
await ensureConfigDir();
|
|
34
|
+
const exists = await fileExists(CONFIG_FILE);
|
|
35
|
+
// Create empty file if it doesn't exist (for locking)
|
|
36
|
+
if (!exists) {
|
|
37
|
+
await writeFile(CONFIG_FILE, '{}', { mode: SECURE_FILE_MODE });
|
|
38
|
+
}
|
|
39
|
+
// Use file locking to prevent race conditions
|
|
40
|
+
let release = null;
|
|
41
|
+
try {
|
|
42
|
+
release = await lock(CONFIG_FILE, { retries: 3 });
|
|
43
|
+
await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), { mode: SECURE_FILE_MODE });
|
|
44
|
+
// Ensure permissions are set correctly
|
|
45
|
+
await chmod(CONFIG_FILE, SECURE_FILE_MODE);
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
if (release) {
|
|
49
|
+
await release();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export async function getConfigValue(key) {
|
|
54
|
+
const config = await loadConfig();
|
|
55
|
+
return config[key];
|
|
56
|
+
}
|
|
57
|
+
export async function setConfigValue(key, value) {
|
|
58
|
+
const config = await loadConfig();
|
|
59
|
+
config[key] = value;
|
|
60
|
+
await saveConfig(config);
|
|
61
|
+
await logAuditEvent('config_changed', { details: `Key: ${key}` });
|
|
62
|
+
}
|
|
63
|
+
export async function unsetConfigValue(key) {
|
|
64
|
+
const config = await loadConfig();
|
|
65
|
+
if (key in config) {
|
|
66
|
+
delete config[key];
|
|
67
|
+
await saveConfig(config);
|
|
68
|
+
await logAuditEvent('config_changed', { details: `Key removed: ${key}` });
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
export function isValidConfigKey(key) {
|
|
74
|
+
const validKeys = ['defaultUsername', 'allowHttp', 'cdpAllowlist'];
|
|
75
|
+
return validKeys.includes(key);
|
|
76
|
+
}
|
|
77
|
+
export function getValidConfigKeys() {
|
|
78
|
+
return ['defaultUsername', 'allowHttp', 'cdpAllowlist'];
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a cryptographically secure password using Node.js crypto.randomInt
|
|
3
|
+
* Guarantees at least one character from each class (lowercase, uppercase, digit, special)
|
|
4
|
+
*
|
|
5
|
+
* @param length - Password length (minimum 12, default 24)
|
|
6
|
+
* @throws Error if length is less than minimum
|
|
7
|
+
*/
|
|
8
|
+
export declare function generatePassword(length?: number): string;
|
|
9
|
+
/**
|
|
10
|
+
* Alternative password generator using nanoid's customAlphabet
|
|
11
|
+
* Regenerates until complexity requirements are met
|
|
12
|
+
*
|
|
13
|
+
* @param length - Password length (minimum 12, default 24)
|
|
14
|
+
* @throws Error if length is less than minimum
|
|
15
|
+
*/
|
|
16
|
+
export declare function generatePasswordNanoid(length?: number): string;
|
|
17
|
+
//# sourceMappingURL=crypto.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../src/core/crypto.ts"],"names":[],"mappings":"AA8CA;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,GAAE,MAAwB,GAAG,MAAM,CAoBzE;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,GAAE,MAAwB,GAAG,MAAM,CAuB/E"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { randomInt } from 'node:crypto';
|
|
2
|
+
import { customAlphabet } from 'nanoid';
|
|
3
|
+
const PASSWORD_LENGTH = 24;
|
|
4
|
+
const MIN_PASSWORD_LENGTH = 12;
|
|
5
|
+
const PASSWORD_CHARSET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*';
|
|
6
|
+
const CHAR_CLASSES = {
|
|
7
|
+
lowercase: 'abcdefghijklmnopqrstuvwxyz',
|
|
8
|
+
uppercase: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
|
9
|
+
digits: '0123456789',
|
|
10
|
+
special: '!@#$%^&*',
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Cryptographically secure random character selection
|
|
14
|
+
*/
|
|
15
|
+
function secureRandomChar(charset) {
|
|
16
|
+
return charset[randomInt(charset.length)];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Cryptographically secure Fisher-Yates shuffle
|
|
20
|
+
*/
|
|
21
|
+
function secureShuffleArray(array) {
|
|
22
|
+
const result = [...array];
|
|
23
|
+
for (let i = result.length - 1; i > 0; i--) {
|
|
24
|
+
const j = randomInt(i + 1);
|
|
25
|
+
[result[i], result[j]] = [result[j], result[i]];
|
|
26
|
+
}
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Check if password meets complexity requirements
|
|
31
|
+
*/
|
|
32
|
+
function meetsComplexityRequirements(password) {
|
|
33
|
+
return (/[a-z]/.test(password) &&
|
|
34
|
+
/[A-Z]/.test(password) &&
|
|
35
|
+
/[0-9]/.test(password) &&
|
|
36
|
+
/[!@#$%^&*]/.test(password));
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Generate a cryptographically secure password using Node.js crypto.randomInt
|
|
40
|
+
* Guarantees at least one character from each class (lowercase, uppercase, digit, special)
|
|
41
|
+
*
|
|
42
|
+
* @param length - Password length (minimum 12, default 24)
|
|
43
|
+
* @throws Error if length is less than minimum
|
|
44
|
+
*/
|
|
45
|
+
export function generatePassword(length = PASSWORD_LENGTH) {
|
|
46
|
+
if (length < MIN_PASSWORD_LENGTH) {
|
|
47
|
+
throw new Error(`Password length must be at least ${MIN_PASSWORD_LENGTH} characters`);
|
|
48
|
+
}
|
|
49
|
+
// Start with one guaranteed character from each class
|
|
50
|
+
const chars = [
|
|
51
|
+
secureRandomChar(CHAR_CLASSES.lowercase),
|
|
52
|
+
secureRandomChar(CHAR_CLASSES.uppercase),
|
|
53
|
+
secureRandomChar(CHAR_CLASSES.digits),
|
|
54
|
+
secureRandomChar(CHAR_CLASSES.special),
|
|
55
|
+
];
|
|
56
|
+
// Fill remaining length with random characters from full charset
|
|
57
|
+
while (chars.length < length) {
|
|
58
|
+
chars.push(secureRandomChar(PASSWORD_CHARSET));
|
|
59
|
+
}
|
|
60
|
+
// Cryptographically secure shuffle to randomize position of guaranteed chars
|
|
61
|
+
return secureShuffleArray(chars).join('');
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Alternative password generator using nanoid's customAlphabet
|
|
65
|
+
* Regenerates until complexity requirements are met
|
|
66
|
+
*
|
|
67
|
+
* @param length - Password length (minimum 12, default 24)
|
|
68
|
+
* @throws Error if length is less than minimum
|
|
69
|
+
*/
|
|
70
|
+
export function generatePasswordNanoid(length = PASSWORD_LENGTH) {
|
|
71
|
+
if (length < MIN_PASSWORD_LENGTH) {
|
|
72
|
+
throw new Error(`Password length must be at least ${MIN_PASSWORD_LENGTH} characters`);
|
|
73
|
+
}
|
|
74
|
+
const generate = customAlphabet(PASSWORD_CHARSET, length);
|
|
75
|
+
// Generate until we meet complexity requirements
|
|
76
|
+
// With 24 chars from a 70-char alphabet including all classes,
|
|
77
|
+
// probability of missing a class is extremely low (~0.01%)
|
|
78
|
+
let password;
|
|
79
|
+
let attempts = 0;
|
|
80
|
+
const maxAttempts = 100;
|
|
81
|
+
do {
|
|
82
|
+
password = generate();
|
|
83
|
+
attempts++;
|
|
84
|
+
if (attempts >= maxAttempts) {
|
|
85
|
+
throw new Error('Failed to generate compliant password after maximum attempts');
|
|
86
|
+
}
|
|
87
|
+
} while (!meetsComplexityRequirements(password));
|
|
88
|
+
return password;
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=crypto.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/core/crypto.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Page } from 'playwright';
|
|
2
|
+
export declare function detectUsernameField(page: Page): Promise<string | null>;
|
|
3
|
+
export declare function detectPasswordField(page: Page): Promise<string | null>;
|
|
4
|
+
export declare function detectSubmitButton(page: Page): Promise<string | null>;
|
|
5
|
+
//# sourceMappingURL=fields.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fields.d.ts","sourceRoot":"","sources":["../../src/core/fields.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AA0BvC,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAQ5E;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAQ5E;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAY3E"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const USERNAME_SELECTORS = [
|
|
2
|
+
'input[type="email"]',
|
|
3
|
+
'input[name="email"]',
|
|
4
|
+
'input[autocomplete="username"]',
|
|
5
|
+
'input[autocomplete="email"]',
|
|
6
|
+
'input[name="username"]',
|
|
7
|
+
'input[id="username"]',
|
|
8
|
+
'input[id="email"]',
|
|
9
|
+
];
|
|
10
|
+
const PASSWORD_SELECTORS = [
|
|
11
|
+
'input[type="password"]',
|
|
12
|
+
'input[autocomplete="current-password"]',
|
|
13
|
+
'input[autocomplete="new-password"]',
|
|
14
|
+
];
|
|
15
|
+
const SUBMIT_SELECTORS = [
|
|
16
|
+
'button[type="submit"]',
|
|
17
|
+
'input[type="submit"]',
|
|
18
|
+
'button:has-text("Log in")',
|
|
19
|
+
'button:has-text("Sign in")',
|
|
20
|
+
'button:has-text("Login")',
|
|
21
|
+
];
|
|
22
|
+
export async function detectUsernameField(page) {
|
|
23
|
+
for (const selector of USERNAME_SELECTORS) {
|
|
24
|
+
const element = await page.$(selector);
|
|
25
|
+
if (element && await element.isVisible()) {
|
|
26
|
+
return selector;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
export async function detectPasswordField(page) {
|
|
32
|
+
for (const selector of PASSWORD_SELECTORS) {
|
|
33
|
+
const element = await page.$(selector);
|
|
34
|
+
if (element && await element.isVisible()) {
|
|
35
|
+
return selector;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
export async function detectSubmitButton(page) {
|
|
41
|
+
for (const selector of SUBMIT_SELECTORS) {
|
|
42
|
+
try {
|
|
43
|
+
const element = await page.$(selector);
|
|
44
|
+
if (element && await element.isVisible()) {
|
|
45
|
+
return selector;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// Some selectors might not be valid, continue
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=fields.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fields.js","sourceRoot":"","sources":["../../src/core/fields.ts"],"names":[],"mappings":"AAEA,MAAM,kBAAkB,GAAG;IACzB,qBAAqB;IACrB,qBAAqB;IACrB,gCAAgC;IAChC,6BAA6B;IAC7B,wBAAwB;IACxB,sBAAsB;IACtB,mBAAmB;CACpB,CAAC;AAEF,MAAM,kBAAkB,GAAG;IACzB,wBAAwB;IACxB,wCAAwC;IACxC,oCAAoC;CACrC,CAAC;AAEF,MAAM,gBAAgB,GAAG;IACvB,uBAAuB;IACvB,sBAAsB;IACtB,2BAA2B;IAC3B,4BAA4B;IAC5B,0BAA0B;CAC3B,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,IAAU;IAClD,KAAK,MAAM,QAAQ,IAAI,kBAAkB,EAAE,CAAC;QAC1C,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QACvC,IAAI,OAAO,IAAI,MAAM,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC;YACzC,OAAO,QAAQ,CAAC;QAClB,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,IAAU;IAClD,KAAK,MAAM,QAAQ,IAAI,kBAAkB,EAAE,CAAC;QAC1C,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QACvC,IAAI,OAAO,IAAI,MAAM,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC;YACzC,OAAO,QAAQ,CAAC;QAClB,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,IAAU;IACjD,KAAK,MAAM,QAAQ,IAAI,gBAAgB,EAAE,CAAC;QACxC,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;YACvC,IAAI,OAAO,IAAI,MAAM,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC;gBACzC,OAAO,QAAQ,CAAC;YAClB,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,8CAA8C;QAChD,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { RPConfig } from '../types/index.js';
|
|
2
|
+
export declare function storeRP(config: RPConfig): Promise<void>;
|
|
3
|
+
export declare function getRP(origin: string): Promise<RPConfig | null>;
|
|
4
|
+
export declare function deleteRP(origin: string): Promise<boolean>;
|
|
5
|
+
//# sourceMappingURL=keychain.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keychain.d.ts","sourceRoot":"","sources":["../../src/core/keychain.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAmClD,wBAAsB,OAAO,CAAC,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAY7D;AAED,wBAAsB,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAiDpE;AAED,wBAAsB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAO/D"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import keytar from 'keytar';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { logAuditEvent } from './audit.js';
|
|
4
|
+
import { checkRateLimit } from './ratelimit.js';
|
|
5
|
+
const SERVICE_NAME = 'agent-vault';
|
|
6
|
+
// Zod schema for strict validation of stored credentials
|
|
7
|
+
const SelectorsSchema = z.object({
|
|
8
|
+
username: z.string().min(1),
|
|
9
|
+
password: z.string().min(1),
|
|
10
|
+
submit: z.string().optional(),
|
|
11
|
+
});
|
|
12
|
+
const CredentialsSchema = z.object({
|
|
13
|
+
username: z.string(),
|
|
14
|
+
password: z.string(),
|
|
15
|
+
});
|
|
16
|
+
const RPConfigSchema = z.object({
|
|
17
|
+
origin: z.string().url(),
|
|
18
|
+
selectors: SelectorsSchema,
|
|
19
|
+
credentials: CredentialsSchema,
|
|
20
|
+
});
|
|
21
|
+
/**
|
|
22
|
+
* Validate RPConfig data against schema
|
|
23
|
+
*/
|
|
24
|
+
function validateRPConfig(data) {
|
|
25
|
+
const result = RPConfigSchema.safeParse(data);
|
|
26
|
+
if (!result.success) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
return result.data;
|
|
30
|
+
}
|
|
31
|
+
export async function storeRP(config) {
|
|
32
|
+
// Rate limit credential storage
|
|
33
|
+
await checkRateLimit('store_credentials');
|
|
34
|
+
// Validate config before storing
|
|
35
|
+
const validated = validateRPConfig(config);
|
|
36
|
+
if (!validated) {
|
|
37
|
+
throw new Error('Invalid credential configuration');
|
|
38
|
+
}
|
|
39
|
+
await keytar.setPassword(SERVICE_NAME, config.origin, JSON.stringify(validated));
|
|
40
|
+
await logAuditEvent('credential_stored', { origin: config.origin });
|
|
41
|
+
}
|
|
42
|
+
export async function getRP(origin) {
|
|
43
|
+
try {
|
|
44
|
+
// Rate limit credential retrieval
|
|
45
|
+
await checkRateLimit('get_credentials');
|
|
46
|
+
const data = await keytar.getPassword(SERVICE_NAME, origin);
|
|
47
|
+
if (!data) {
|
|
48
|
+
await logAuditEvent('credential_retrieved', { origin, success: false });
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
let parsed;
|
|
52
|
+
try {
|
|
53
|
+
parsed = JSON.parse(data);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// Corrupted JSON data
|
|
57
|
+
await logAuditEvent('credential_retrieved', {
|
|
58
|
+
origin,
|
|
59
|
+
details: 'Corrupted data',
|
|
60
|
+
success: false,
|
|
61
|
+
});
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
// Validate with zod schema
|
|
65
|
+
const validated = validateRPConfig(parsed);
|
|
66
|
+
if (!validated) {
|
|
67
|
+
await logAuditEvent('credential_retrieved', {
|
|
68
|
+
origin,
|
|
69
|
+
details: 'Schema validation failed',
|
|
70
|
+
success: false,
|
|
71
|
+
});
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
await logAuditEvent('credential_retrieved', { origin, success: true });
|
|
75
|
+
return validated;
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
// Don't expose internal errors - use generic message
|
|
79
|
+
if (error instanceof Error && error.message.includes('Rate limit')) {
|
|
80
|
+
throw error; // Re-throw rate limit errors
|
|
81
|
+
}
|
|
82
|
+
await logAuditEvent('credential_retrieved', {
|
|
83
|
+
origin,
|
|
84
|
+
details: 'Internal error',
|
|
85
|
+
success: false,
|
|
86
|
+
});
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
export async function deleteRP(origin) {
|
|
91
|
+
// Rate limit credential deletion
|
|
92
|
+
await checkRateLimit('delete_credentials');
|
|
93
|
+
const result = await keytar.deletePassword(SERVICE_NAME, origin);
|
|
94
|
+
await logAuditEvent('credential_deleted', { origin, success: result });
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=keychain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keychain.js","sourceRoot":"","sources":["../../src/core/keychain.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export declare function extractOrigin(url: string): string;
|
|
2
|
+
/**
|
|
3
|
+
* Check if origin uses HTTPS (secure)
|
|
4
|
+
*/
|
|
5
|
+
export declare function isSecureProtocol(origin: string): boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Check if origin uses HTTP (insecure)
|
|
8
|
+
*/
|
|
9
|
+
export declare function isHttpProtocol(origin: string): boolean;
|
|
10
|
+
export declare function isValidOrigin(origin: string): boolean;
|
|
11
|
+
export interface OriginValidationOptions {
|
|
12
|
+
allowHttp?: boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Validate an origin with security checks.
|
|
16
|
+
* By default, requires HTTPS to prevent credentials from being sent in plaintext.
|
|
17
|
+
*/
|
|
18
|
+
export declare function validateOriginSecurity(origin: string, options?: OriginValidationOptions): Promise<void>;
|
|
19
|
+
export declare function extractAndValidateOrigin(url: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Extract and validate origin with full security checks.
|
|
22
|
+
* Use this for operations that store or retrieve credentials.
|
|
23
|
+
*/
|
|
24
|
+
export declare function extractAndValidateOriginSecure(url: string, options?: OriginValidationOptions): Promise<string>;
|
|
25
|
+
//# sourceMappingURL=origin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"origin.d.ts","sourceRoot":"","sources":["../../src/core/origin.ts"],"names":[],"mappings":"AAEA,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGjD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAOxD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAOtD;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAOrD;AAED,MAAM,WAAW,uBAAuB;IACtC,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,IAAI,CAAC,CAiBf;AAED,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAM5D;AAED;;;GAGG;AACH,wBAAsB,8BAA8B,CAClD,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,MAAM,CAAC,CAIjB"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { getConfigValue } from './config.js';
|
|
2
|
+
export function extractOrigin(url) {
|
|
3
|
+
const parsed = new URL(url);
|
|
4
|
+
return parsed.origin;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Check if origin uses HTTPS (secure)
|
|
8
|
+
*/
|
|
9
|
+
export function isSecureProtocol(origin) {
|
|
10
|
+
try {
|
|
11
|
+
const url = new URL(origin);
|
|
12
|
+
return url.protocol === 'https:';
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Check if origin uses HTTP (insecure)
|
|
20
|
+
*/
|
|
21
|
+
export function isHttpProtocol(origin) {
|
|
22
|
+
try {
|
|
23
|
+
const url = new URL(origin);
|
|
24
|
+
return url.protocol === 'http:';
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function isValidOrigin(origin) {
|
|
31
|
+
try {
|
|
32
|
+
const url = new URL(origin);
|
|
33
|
+
return url.protocol === 'https:' || url.protocol === 'http:';
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Validate an origin with security checks.
|
|
41
|
+
* By default, requires HTTPS to prevent credentials from being sent in plaintext.
|
|
42
|
+
*/
|
|
43
|
+
export async function validateOriginSecurity(origin, options = {}) {
|
|
44
|
+
// Check config for allowHttp setting
|
|
45
|
+
const configAllowHttp = await getConfigValue('allowHttp');
|
|
46
|
+
const allowHttp = options.allowHttp ?? configAllowHttp === 'true';
|
|
47
|
+
// Check HTTPS requirement
|
|
48
|
+
if (!allowHttp && isHttpProtocol(origin)) {
|
|
49
|
+
throw new Error('HTTP origins are not allowed by default. Credentials would be sent in plaintext. ' +
|
|
50
|
+
'Use --allow-http flag or set config allowHttp=true to override.');
|
|
51
|
+
}
|
|
52
|
+
// Validate basic origin format
|
|
53
|
+
if (!isValidOrigin(origin)) {
|
|
54
|
+
throw new Error('Invalid origin format');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export function extractAndValidateOrigin(url) {
|
|
58
|
+
const origin = extractOrigin(url);
|
|
59
|
+
if (!isValidOrigin(origin)) {
|
|
60
|
+
throw new Error('Invalid page origin');
|
|
61
|
+
}
|
|
62
|
+
return origin;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Extract and validate origin with full security checks.
|
|
66
|
+
* Use this for operations that store or retrieve credentials.
|
|
67
|
+
*/
|
|
68
|
+
export async function extractAndValidateOriginSecure(url, options = {}) {
|
|
69
|
+
const origin = extractOrigin(url);
|
|
70
|
+
await validateOriginSecurity(origin, options);
|
|
71
|
+
return origin;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=origin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"origin.js","sourceRoot":"","sources":["../../src/core/origin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,UAAU,aAAa,CAAC,GAAW;IACvC,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IAC5B,OAAO,MAAM,CAAC,MAAM,CAAC;AACvB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAc;IAC7C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;QAC5B,OAAO,GAAG,CAAC,QAAQ,KAAK,QAAQ,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,MAAc;IAC3C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;QAC5B,OAAO,GAAG,CAAC,QAAQ,KAAK,OAAO,CAAC;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,MAAc;IAC1C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;QAC5B,OAAO,GAAG,CAAC,QAAQ,KAAK,QAAQ,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,CAAC;IAC/D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAMD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,MAAc,EACd,UAAmC,EAAE;IAErC,qCAAqC;IACrC,MAAM,eAAe,GAAG,MAAM,cAAc,CAAC,WAAW,CAAC,CAAC;IAC1D,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,eAAe,KAAK,MAAM,CAAC;IAElE,0BAA0B;IAC1B,IAAI,CAAC,SAAS,IAAI,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC;QACzC,MAAM,IAAI,KAAK,CACb,mFAAmF;YACjF,iEAAiE,CACpE,CAAC;IACJ,CAAC;IAED,+BAA+B;IAC/B,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;IAC3C,CAAC;AACH,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,GAAW;IAClD,MAAM,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;IAClC,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;IACzC,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAClD,GAAW,EACX,UAAmC,EAAE;IAErC,MAAM,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;IAClC,MAAM,sBAAsB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9C,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if an operation is rate limited.
|
|
3
|
+
* Returns true if allowed, throws if rate limited.
|
|
4
|
+
*/
|
|
5
|
+
export declare function checkRateLimit(operation: string): Promise<void>;
|
|
6
|
+
/**
|
|
7
|
+
* Reset rate limit state (for testing or admin purposes)
|
|
8
|
+
*/
|
|
9
|
+
export declare function resetRateLimit(): Promise<void>;
|
|
10
|
+
//# sourceMappingURL=ratelimit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ratelimit.d.ts","sourceRoot":"","sources":["../../src/core/ratelimit.ts"],"names":[],"mappings":"AAoCA;;;GAGG;AACH,wBAAsB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyCrE;AAED;;GAEG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAEpD"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { logAuditEvent } from './audit.js';
|
|
5
|
+
const RATE_LIMIT_DIR = join(homedir(), '.agent-vault');
|
|
6
|
+
const RATE_LIMIT_FILE = join(RATE_LIMIT_DIR, '.ratelimit');
|
|
7
|
+
// Rate limit configuration
|
|
8
|
+
const MAX_ATTEMPTS = 5;
|
|
9
|
+
const WINDOW_MS = 60 * 1000; // 1 minute window
|
|
10
|
+
const LOCKOUT_MS = 5 * 60 * 1000; // 5 minute lockout after exceeding
|
|
11
|
+
async function ensureRateLimitDir() {
|
|
12
|
+
await mkdir(RATE_LIMIT_DIR, { recursive: true, mode: 0o700 });
|
|
13
|
+
}
|
|
14
|
+
async function loadRateLimitState() {
|
|
15
|
+
try {
|
|
16
|
+
const data = await readFile(RATE_LIMIT_FILE, 'utf-8');
|
|
17
|
+
return JSON.parse(data);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return { attempts: [] };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async function saveRateLimitState(state) {
|
|
24
|
+
await ensureRateLimitDir();
|
|
25
|
+
await writeFile(RATE_LIMIT_FILE, JSON.stringify(state), { mode: 0o600 });
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Check if an operation is rate limited.
|
|
29
|
+
* Returns true if allowed, throws if rate limited.
|
|
30
|
+
*/
|
|
31
|
+
export async function checkRateLimit(operation) {
|
|
32
|
+
const now = Date.now();
|
|
33
|
+
const state = await loadRateLimitState();
|
|
34
|
+
// Check if currently locked out
|
|
35
|
+
if (state.lockedUntil && now < state.lockedUntil) {
|
|
36
|
+
const remainingSeconds = Math.ceil((state.lockedUntil - now) / 1000);
|
|
37
|
+
await logAuditEvent('rate_limit_exceeded', {
|
|
38
|
+
details: `Operation: ${operation}, locked for ${remainingSeconds}s`,
|
|
39
|
+
success: false,
|
|
40
|
+
});
|
|
41
|
+
throw new Error(`Rate limit exceeded. Please wait ${remainingSeconds} seconds before trying again.`);
|
|
42
|
+
}
|
|
43
|
+
// Clear lockout if expired
|
|
44
|
+
if (state.lockedUntil && now >= state.lockedUntil) {
|
|
45
|
+
state.lockedUntil = undefined;
|
|
46
|
+
state.attempts = [];
|
|
47
|
+
}
|
|
48
|
+
// Filter attempts within the window
|
|
49
|
+
state.attempts = state.attempts.filter((ts) => now - ts < WINDOW_MS);
|
|
50
|
+
// Check if exceeding limit
|
|
51
|
+
if (state.attempts.length >= MAX_ATTEMPTS) {
|
|
52
|
+
state.lockedUntil = now + LOCKOUT_MS;
|
|
53
|
+
await saveRateLimitState(state);
|
|
54
|
+
await logAuditEvent('rate_limit_exceeded', {
|
|
55
|
+
details: `Operation: ${operation}, lockout initiated`,
|
|
56
|
+
success: false,
|
|
57
|
+
});
|
|
58
|
+
throw new Error(`Too many attempts. Please wait ${Math.ceil(LOCKOUT_MS / 1000)} seconds before trying again.`);
|
|
59
|
+
}
|
|
60
|
+
// Record this attempt
|
|
61
|
+
state.attempts.push(now);
|
|
62
|
+
await saveRateLimitState(state);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Reset rate limit state (for testing or admin purposes)
|
|
66
|
+
*/
|
|
67
|
+
export async function resetRateLimit() {
|
|
68
|
+
await saveRateLimitState({ attempts: [] });
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=ratelimit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ratelimit.js","sourceRoot":"","sources":["../../src/core/ratelimit.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secure memory utilities for handling sensitive data.
|
|
3
|
+
* Uses Buffer to allow explicit zeroing of memory.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* A secure string container that can be explicitly cleared from memory.
|
|
7
|
+
* Uses Buffer internally for memory that can be overwritten.
|
|
8
|
+
*/
|
|
9
|
+
export declare class SecureString {
|
|
10
|
+
private buffer;
|
|
11
|
+
private cleared;
|
|
12
|
+
constructor(value: string);
|
|
13
|
+
/**
|
|
14
|
+
* Get the string value. Throws if already cleared.
|
|
15
|
+
*/
|
|
16
|
+
getValue(): string;
|
|
17
|
+
/**
|
|
18
|
+
* Securely clear the buffer by overwriting with zeros.
|
|
19
|
+
*/
|
|
20
|
+
clear(): void;
|
|
21
|
+
/**
|
|
22
|
+
* Check if the string has been cleared.
|
|
23
|
+
*/
|
|
24
|
+
isCleared(): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Get the length of the stored string.
|
|
27
|
+
*/
|
|
28
|
+
get length(): number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Execute a function with secure strings, ensuring cleanup on completion.
|
|
32
|
+
* @param values - Object with string values to protect
|
|
33
|
+
* @param fn - Function to execute with SecureString versions
|
|
34
|
+
* @returns Result of the function
|
|
35
|
+
*/
|
|
36
|
+
export declare function withSecureStrings<T extends Record<string, string>, R>(values: T, fn: (secure: {
|
|
37
|
+
[K in keyof T]: SecureString;
|
|
38
|
+
}) => Promise<R>): Promise<R>;
|
|
39
|
+
//# sourceMappingURL=secure-memory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secure-memory.d.ts","sourceRoot":"","sources":["../../src/core/secure-memory.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;GAGG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAAS;gBAEZ,KAAK,EAAE,MAAM;IAIzB;;OAEG;IACH,QAAQ,IAAI,MAAM;IAOlB;;OAEG;IACH,KAAK,IAAI,IAAI;IAOb;;OAEG;IACH,SAAS,IAAI,OAAO;IAIpB;;OAEG;IACH,IAAI,MAAM,IAAI,MAAM,CAEnB;CACF;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,EACzE,MAAM,EAAE,CAAC,EACT,EAAE,EAAE,CAAC,MAAM,EAAE;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,YAAY;CAAE,KAAK,OAAO,CAAC,CAAC,CAAC,GAC3D,OAAO,CAAC,CAAC,CAAC,CAgBZ"}
|