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,68 @@
|
|
|
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 class SecureString {
|
|
10
|
+
buffer;
|
|
11
|
+
cleared = false;
|
|
12
|
+
constructor(value) {
|
|
13
|
+
this.buffer = Buffer.from(value, 'utf-8');
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Get the string value. Throws if already cleared.
|
|
17
|
+
*/
|
|
18
|
+
getValue() {
|
|
19
|
+
if (this.cleared) {
|
|
20
|
+
throw new Error('SecureString has been cleared');
|
|
21
|
+
}
|
|
22
|
+
return this.buffer.toString('utf-8');
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Securely clear the buffer by overwriting with zeros.
|
|
26
|
+
*/
|
|
27
|
+
clear() {
|
|
28
|
+
if (!this.cleared) {
|
|
29
|
+
this.buffer.fill(0);
|
|
30
|
+
this.cleared = true;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Check if the string has been cleared.
|
|
35
|
+
*/
|
|
36
|
+
isCleared() {
|
|
37
|
+
return this.cleared;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Get the length of the stored string.
|
|
41
|
+
*/
|
|
42
|
+
get length() {
|
|
43
|
+
return this.cleared ? 0 : this.buffer.length;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Execute a function with secure strings, ensuring cleanup on completion.
|
|
48
|
+
* @param values - Object with string values to protect
|
|
49
|
+
* @param fn - Function to execute with SecureString versions
|
|
50
|
+
* @returns Result of the function
|
|
51
|
+
*/
|
|
52
|
+
export async function withSecureStrings(values, fn) {
|
|
53
|
+
const secureValues = {};
|
|
54
|
+
// Create secure versions
|
|
55
|
+
for (const key of Object.keys(values)) {
|
|
56
|
+
secureValues[key] = new SecureString(values[key]);
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
return await fn(secureValues);
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
// Always clear secure strings
|
|
63
|
+
for (const key of Object.keys(secureValues)) {
|
|
64
|
+
secureValues[key].clear();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=secure-memory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secure-memory.js","sourceRoot":"","sources":["../../src/core/secure-memory.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;GAGG;AACH,MAAM,OAAO,YAAY;IACf,MAAM,CAAS;IACf,OAAO,GAAG,KAAK,CAAC;IAExB,YAAY,KAAa;QACvB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IAC5C,CAAC;IAED;;OAEG;IACH,QAAQ;QACN,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;QACnD,CAAC;QACD,OAAO,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IACvC,CAAC;IAED;;OAEG;IACH,KAAK;QACH,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAClB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACpB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACtB,CAAC;IACH,CAAC;IAED;;OAEG;IACH,SAAS;QACP,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED;;OAEG;IACH,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;IAC/C,CAAC;CACF;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,MAAS,EACT,EAA4D;IAE5D,MAAM,YAAY,GAAG,EAAsC,CAAC;IAE5D,yBAAyB;IACzB,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAgB,EAAE,CAAC;QACrD,YAAY,CAAC,GAAG,CAAC,GAAG,IAAI,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IACpD,CAAC;IAED,IAAI,CAAC;QACH,OAAO,MAAM,EAAE,CAAC,YAAY,CAAC,CAAC;IAChC,CAAC;YAAS,CAAC;QACT,8BAA8B;QAC9B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,YAAY,CAAgB,EAAE,CAAC;YAC3D,YAAY,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC;QAC5B,CAAC;IACH,CAAC;AACH,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { register } from './commands/register.js';
|
|
4
|
+
import { login } from './commands/login.js';
|
|
5
|
+
import { deleteCommand } from './commands/delete.js';
|
|
6
|
+
import { config } from './commands/config.js';
|
|
7
|
+
const program = new Command();
|
|
8
|
+
program
|
|
9
|
+
.name('vault')
|
|
10
|
+
.description('Secure credential vault CLI for AI agents')
|
|
11
|
+
.version('0.1.0');
|
|
12
|
+
program
|
|
13
|
+
.command('register')
|
|
14
|
+
.description('Register credentials for a new site')
|
|
15
|
+
.requiredOption('--cdp <endpoint>', 'CDP WebSocket endpoint (e.g., ws://localhost:9222)')
|
|
16
|
+
.requiredOption('--username-selector <selector>', 'CSS selector for username/email field')
|
|
17
|
+
.requiredOption('--password-selector <selector>', 'CSS selector for password field')
|
|
18
|
+
.option('--submit-selector <selector>', 'CSS selector for submit button')
|
|
19
|
+
.option('--username <username>', 'Username/email (non-interactive)')
|
|
20
|
+
.option('--password <password>', 'Password (non-interactive)')
|
|
21
|
+
.option('--generate-password', 'Generate a secure password (non-interactive)')
|
|
22
|
+
.option('-f, --force', 'Skip confirmation prompts')
|
|
23
|
+
.option('--allow-http', 'Allow HTTP origins (insecure - not recommended)')
|
|
24
|
+
.action(async (options) => {
|
|
25
|
+
try {
|
|
26
|
+
await register({
|
|
27
|
+
cdp: options.cdp,
|
|
28
|
+
usernameSelector: options.usernameSelector,
|
|
29
|
+
passwordSelector: options.passwordSelector,
|
|
30
|
+
submitSelector: options.submitSelector,
|
|
31
|
+
username: options.username,
|
|
32
|
+
password: options.password,
|
|
33
|
+
generatePassword: options.generatePassword,
|
|
34
|
+
force: options.force,
|
|
35
|
+
allowHttp: options.allowHttp,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
console.error('Error:', error instanceof Error ? error.message : error);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
program
|
|
44
|
+
.command('login')
|
|
45
|
+
.description('Fill credentials for a known site')
|
|
46
|
+
.requiredOption('--cdp <endpoint>', 'CDP WebSocket endpoint (e.g., ws://localhost:9222)')
|
|
47
|
+
.option('--submit', 'Click submit button after filling credentials')
|
|
48
|
+
.action(async (options) => {
|
|
49
|
+
try {
|
|
50
|
+
await login({
|
|
51
|
+
cdp: options.cdp,
|
|
52
|
+
submit: options.submit,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
console.error('Error:', error instanceof Error ? error.message : error);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
program
|
|
61
|
+
.command('delete')
|
|
62
|
+
.description('Delete credentials for a site')
|
|
63
|
+
.requiredOption('--origin <url>', 'Origin to delete (e.g., https://github.com)')
|
|
64
|
+
.option('-f, --force', 'Skip confirmation prompt')
|
|
65
|
+
.action(async (options) => {
|
|
66
|
+
try {
|
|
67
|
+
await deleteCommand({
|
|
68
|
+
origin: options.origin,
|
|
69
|
+
force: options.force,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
console.error('Error:', error instanceof Error ? error.message : error);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
const configCmd = program
|
|
78
|
+
.command('config')
|
|
79
|
+
.description('Manage vault configuration');
|
|
80
|
+
configCmd
|
|
81
|
+
.command('list')
|
|
82
|
+
.description('List all configuration values')
|
|
83
|
+
.action(async () => {
|
|
84
|
+
try {
|
|
85
|
+
await config({ action: 'list' });
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
console.error('Error:', error instanceof Error ? error.message : error);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
configCmd
|
|
93
|
+
.command('get <key>')
|
|
94
|
+
.description('Get a configuration value')
|
|
95
|
+
.action(async (key) => {
|
|
96
|
+
try {
|
|
97
|
+
await config({ action: 'get', key });
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
console.error('Error:', error instanceof Error ? error.message : error);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
configCmd
|
|
105
|
+
.command('set <key> <value>')
|
|
106
|
+
.description('Set a configuration value')
|
|
107
|
+
.action(async (key, value) => {
|
|
108
|
+
try {
|
|
109
|
+
await config({ action: 'set', key, value });
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
console.error('Error:', error instanceof Error ? error.message : error);
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
configCmd
|
|
117
|
+
.command('unset <key>')
|
|
118
|
+
.description('Remove a configuration value')
|
|
119
|
+
.action(async (key) => {
|
|
120
|
+
try {
|
|
121
|
+
await config({ action: 'unset', key });
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
console.error('Error:', error instanceof Error ? error.message : error);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
program.parse();
|
|
129
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface Selectors {
|
|
2
|
+
username: string;
|
|
3
|
+
password: string;
|
|
4
|
+
submit?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface Credentials {
|
|
7
|
+
username: string;
|
|
8
|
+
password: string;
|
|
9
|
+
}
|
|
10
|
+
export interface RPConfig {
|
|
11
|
+
origin: string;
|
|
12
|
+
selectors: Selectors;
|
|
13
|
+
credentials: Credentials;
|
|
14
|
+
}
|
|
15
|
+
export interface BrowserConnection {
|
|
16
|
+
browser: import('playwright').Browser;
|
|
17
|
+
page: import('playwright').Page;
|
|
18
|
+
}
|
|
19
|
+
export interface VaultConfig {
|
|
20
|
+
defaultUsername?: string;
|
|
21
|
+
/** Allow HTTP origins (insecure - not recommended) */
|
|
22
|
+
allowHttp?: string;
|
|
23
|
+
/** Comma-separated list of allowed CDP hostnames */
|
|
24
|
+
cdpAllowlist?: string;
|
|
25
|
+
}
|
|
26
|
+
export type ConfigKey = keyof VaultConfig;
|
|
27
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,SAAS,CAAC;IACrB,WAAW,EAAE,WAAW,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,YAAY,EAAE,OAAO,CAAC;IACtC,IAAI,EAAE,OAAO,YAAY,EAAE,IAAI,CAAC;CACjC;AAED,MAAM,WAAW,WAAW;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,MAAM,SAAS,GAAG,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agent-vault-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Secure credential vault CLI for AI agents - fills login forms via CDP without exposing credentials",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"vault": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "tsc --watch",
|
|
13
|
+
"start": "node dist/index.js",
|
|
14
|
+
"test": "vitest",
|
|
15
|
+
"test:headful": "HEADFUL=1 vitest --run",
|
|
16
|
+
"lint": "eslint src/",
|
|
17
|
+
"prepublishOnly": "npm run build",
|
|
18
|
+
"demo:auto": "tsx demo/automated-demo.ts",
|
|
19
|
+
"demo:interactive": "tsx demo/interactive-demo.ts",
|
|
20
|
+
"demo:manual": "chmod +x demo/manual-demo.sh && ./demo/manual-demo.sh",
|
|
21
|
+
"demo:video": "cd demo/remotion && npm install && npm run render",
|
|
22
|
+
"demo:preview": "cd demo/remotion && npm install && npm start",
|
|
23
|
+
"demo:verify": "tsx demo/verify-setup.ts"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"cli",
|
|
27
|
+
"vault",
|
|
28
|
+
"credentials",
|
|
29
|
+
"agent",
|
|
30
|
+
"ai",
|
|
31
|
+
"keychain",
|
|
32
|
+
"security",
|
|
33
|
+
"cdp",
|
|
34
|
+
"playwright"
|
|
35
|
+
],
|
|
36
|
+
"author": "",
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"commander": "^12.1.0",
|
|
40
|
+
"inquirer": "^9.2.23",
|
|
41
|
+
"keytar": "^7.9.0",
|
|
42
|
+
"nanoid": "^5.0.7",
|
|
43
|
+
"playwright": "^1.48.0",
|
|
44
|
+
"proper-lockfile": "^4.1.2",
|
|
45
|
+
"zod": "^4.3.6"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/inquirer": "^9.0.7",
|
|
49
|
+
"@types/node": "^22.0.0",
|
|
50
|
+
"@types/proper-lockfile": "^4.1.4",
|
|
51
|
+
"tsx": "^4.21.0",
|
|
52
|
+
"typescript": "^5.6.0",
|
|
53
|
+
"vitest": "^2.1.0"
|
|
54
|
+
},
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=18.0.0"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import {
|
|
2
|
+
loadConfig,
|
|
3
|
+
getConfigValue,
|
|
4
|
+
setConfigValue,
|
|
5
|
+
unsetConfigValue,
|
|
6
|
+
isValidConfigKey,
|
|
7
|
+
getValidConfigKeys,
|
|
8
|
+
} from '../core/config.js';
|
|
9
|
+
import type { ConfigKey } from '../types/index.js';
|
|
10
|
+
|
|
11
|
+
export type ConfigAction = 'get' | 'set' | 'unset' | 'list';
|
|
12
|
+
|
|
13
|
+
export interface ConfigOptions {
|
|
14
|
+
action: ConfigAction;
|
|
15
|
+
key?: string;
|
|
16
|
+
value?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function config(options: ConfigOptions): Promise<void> {
|
|
20
|
+
const { action, key, value } = options;
|
|
21
|
+
|
|
22
|
+
switch (action) {
|
|
23
|
+
case 'list': {
|
|
24
|
+
const cfg = await loadConfig();
|
|
25
|
+
const entries = Object.entries(cfg);
|
|
26
|
+
if (entries.length === 0) {
|
|
27
|
+
console.log('No configuration values set.');
|
|
28
|
+
console.log(`Available keys: ${getValidConfigKeys().join(', ')}`);
|
|
29
|
+
} else {
|
|
30
|
+
for (const [k, v] of entries) {
|
|
31
|
+
console.log(`${k}=${v}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
case 'get': {
|
|
38
|
+
if (!key) {
|
|
39
|
+
throw new Error('Key is required for get operation');
|
|
40
|
+
}
|
|
41
|
+
if (!isValidConfigKey(key)) {
|
|
42
|
+
throw new Error(`Invalid config key: ${key}. Valid keys: ${getValidConfigKeys().join(', ')}`);
|
|
43
|
+
}
|
|
44
|
+
const val = await getConfigValue(key as ConfigKey);
|
|
45
|
+
if (val !== undefined) {
|
|
46
|
+
console.log(val);
|
|
47
|
+
} else {
|
|
48
|
+
console.log(`(not set)`);
|
|
49
|
+
}
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
case 'set': {
|
|
54
|
+
if (!key) {
|
|
55
|
+
throw new Error('Key is required for set operation');
|
|
56
|
+
}
|
|
57
|
+
if (!isValidConfigKey(key)) {
|
|
58
|
+
throw new Error(`Invalid config key: ${key}. Valid keys: ${getValidConfigKeys().join(', ')}`);
|
|
59
|
+
}
|
|
60
|
+
if (!value) {
|
|
61
|
+
throw new Error('Value is required for set operation');
|
|
62
|
+
}
|
|
63
|
+
await setConfigValue(key as ConfigKey, value);
|
|
64
|
+
console.log(`✓ Set ${key}=${value}`);
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
case 'unset': {
|
|
69
|
+
if (!key) {
|
|
70
|
+
throw new Error('Key is required for unset operation');
|
|
71
|
+
}
|
|
72
|
+
if (!isValidConfigKey(key)) {
|
|
73
|
+
throw new Error(`Invalid config key: ${key}. Valid keys: ${getValidConfigKeys().join(', ')}`);
|
|
74
|
+
}
|
|
75
|
+
const deleted = await unsetConfigValue(key as ConfigKey);
|
|
76
|
+
if (deleted) {
|
|
77
|
+
console.log(`✓ Unset ${key}`);
|
|
78
|
+
} else {
|
|
79
|
+
console.log(`${key} was not set`);
|
|
80
|
+
}
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import { deleteRP, getRP } from '../core/keychain.js';
|
|
3
|
+
|
|
4
|
+
interface DeleteOptions {
|
|
5
|
+
origin: string;
|
|
6
|
+
force?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function deleteCommand(options: DeleteOptions): Promise<void> {
|
|
10
|
+
const config = await getRP(options.origin);
|
|
11
|
+
|
|
12
|
+
if (!config) {
|
|
13
|
+
throw new Error(`No credentials found for ${options.origin}`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (!options.force) {
|
|
17
|
+
const { confirm } = await inquirer.prompt<{ confirm: boolean }>([
|
|
18
|
+
{
|
|
19
|
+
type: 'confirm',
|
|
20
|
+
name: 'confirm',
|
|
21
|
+
message: `Delete credentials for ${options.origin}?`,
|
|
22
|
+
default: false,
|
|
23
|
+
},
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
if (!confirm) {
|
|
27
|
+
console.log('Deletion cancelled.');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const deleted = await deleteRP(options.origin);
|
|
33
|
+
|
|
34
|
+
if (deleted) {
|
|
35
|
+
console.log(`✓ Deleted credentials for ${options.origin}`);
|
|
36
|
+
} else {
|
|
37
|
+
throw new Error(`Failed to delete credentials for ${options.origin}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { connectToBrowser, fillField, clickElement } from '../core/browser.js';
|
|
2
|
+
import { extractOrigin, extractAndValidateOrigin } from '../core/origin.js';
|
|
3
|
+
import { getRP } from '../core/keychain.js';
|
|
4
|
+
|
|
5
|
+
interface LoginOptions {
|
|
6
|
+
cdp: string;
|
|
7
|
+
submit?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function login(options: LoginOptions): Promise<void> {
|
|
11
|
+
const { browser, page } = await connectToBrowser(options.cdp);
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const currentUrl = page.url();
|
|
15
|
+
const origin = extractAndValidateOrigin(currentUrl);
|
|
16
|
+
|
|
17
|
+
// Lookup stored config for this origin
|
|
18
|
+
const config = await getRP(origin);
|
|
19
|
+
|
|
20
|
+
if (!config) {
|
|
21
|
+
throw new Error('No credentials found for this site');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Verify origin hasn't changed before filling
|
|
25
|
+
const verifyOrigin = extractOrigin(page.url());
|
|
26
|
+
if (verifyOrigin !== origin) {
|
|
27
|
+
throw new Error('Page navigation detected - aborting for security');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Fill username and password using STORED selectors
|
|
31
|
+
await fillField(page, config.selectors.username, config.credentials.username);
|
|
32
|
+
await fillField(page, config.selectors.password, config.credentials.password);
|
|
33
|
+
|
|
34
|
+
// Verify again after filling
|
|
35
|
+
const postFillOrigin = extractOrigin(page.url());
|
|
36
|
+
if (postFillOrigin !== origin) {
|
|
37
|
+
throw new Error('Page changed during credential fill - possible attack');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Optionally click submit
|
|
41
|
+
if (options.submit && config.selectors.submit) {
|
|
42
|
+
await clickElement(page, config.selectors.submit);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
console.log('Login filled successfully');
|
|
46
|
+
} finally {
|
|
47
|
+
await browser.close();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import { connectToBrowser, fillField, validateSelector } from '../core/browser.js';
|
|
3
|
+
import {
|
|
4
|
+
extractAndValidateOrigin,
|
|
5
|
+
extractAndValidateOriginSecure,
|
|
6
|
+
type OriginValidationOptions,
|
|
7
|
+
} from '../core/origin.js';
|
|
8
|
+
import { storeRP, getRP } from '../core/keychain.js';
|
|
9
|
+
import { generatePassword } from '../core/crypto.js';
|
|
10
|
+
import { getConfigValue } from '../core/config.js';
|
|
11
|
+
import { SecureString } from '../core/secure-memory.js';
|
|
12
|
+
import type { Selectors } from '../types/index.js';
|
|
13
|
+
|
|
14
|
+
export interface RegisterOptions {
|
|
15
|
+
cdp: string;
|
|
16
|
+
usernameSelector: string;
|
|
17
|
+
passwordSelector: string;
|
|
18
|
+
submitSelector?: string;
|
|
19
|
+
// Non-interactive options
|
|
20
|
+
username?: string;
|
|
21
|
+
password?: string;
|
|
22
|
+
generatePassword?: boolean;
|
|
23
|
+
force?: boolean;
|
|
24
|
+
allowHttp?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function register(options: RegisterOptions): Promise<void> {
|
|
28
|
+
const { browser, page } = await connectToBrowser(options.cdp);
|
|
29
|
+
|
|
30
|
+
// Use SecureString for sensitive credential handling
|
|
31
|
+
let secureUsername: SecureString | null = null;
|
|
32
|
+
let securePassword: SecureString | null = null;
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const currentUrl = page.url();
|
|
36
|
+
|
|
37
|
+
// Use secure origin validation with HTTP blocking
|
|
38
|
+
const originOptions: OriginValidationOptions = {
|
|
39
|
+
allowHttp: options.allowHttp,
|
|
40
|
+
};
|
|
41
|
+
const origin = await extractAndValidateOriginSecure(currentUrl, originOptions);
|
|
42
|
+
|
|
43
|
+
// Validate selectors exist on page (don't expose selector in error)
|
|
44
|
+
const usernameValid = await validateSelector(page, options.usernameSelector);
|
|
45
|
+
const passwordValid = await validateSelector(page, options.passwordSelector);
|
|
46
|
+
|
|
47
|
+
if (!usernameValid) {
|
|
48
|
+
throw new Error('Username field selector not found on page');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!passwordValid) {
|
|
52
|
+
throw new Error('Password field selector not found on page');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (options.submitSelector) {
|
|
56
|
+
const submitValid = await validateSelector(page, options.submitSelector);
|
|
57
|
+
if (!submitValid) {
|
|
58
|
+
console.warn('Warning: Submit button selector not found on page');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Check if already registered
|
|
63
|
+
const existing = await getRP(origin);
|
|
64
|
+
if (existing) {
|
|
65
|
+
if (options.force) {
|
|
66
|
+
// Non-interactive: overwrite without prompting
|
|
67
|
+
} else {
|
|
68
|
+
const { overwrite } = await inquirer.prompt<{ overwrite: boolean }>([
|
|
69
|
+
{
|
|
70
|
+
type: 'confirm',
|
|
71
|
+
name: 'overwrite',
|
|
72
|
+
message: `Credentials already exist for this site. Overwrite?`,
|
|
73
|
+
default: false,
|
|
74
|
+
},
|
|
75
|
+
]);
|
|
76
|
+
|
|
77
|
+
if (!overwrite) {
|
|
78
|
+
console.log('Registration cancelled.');
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Confirm registration (skip if force or username provided non-interactively)
|
|
85
|
+
if (!options.force && !options.username) {
|
|
86
|
+
const { confirm } = await inquirer.prompt<{ confirm: boolean }>([
|
|
87
|
+
{
|
|
88
|
+
type: 'confirm',
|
|
89
|
+
name: 'confirm',
|
|
90
|
+
message: `Register credentials for this site?`,
|
|
91
|
+
default: true,
|
|
92
|
+
},
|
|
93
|
+
]);
|
|
94
|
+
|
|
95
|
+
if (!confirm) {
|
|
96
|
+
console.log('Registration cancelled.');
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Get username
|
|
102
|
+
let usernameValue: string;
|
|
103
|
+
if (options.username) {
|
|
104
|
+
usernameValue = options.username;
|
|
105
|
+
} else {
|
|
106
|
+
const defaultUsername = await getConfigValue('defaultUsername');
|
|
107
|
+
const response = await inquirer.prompt<{ username: string }>([
|
|
108
|
+
{
|
|
109
|
+
type: 'input',
|
|
110
|
+
name: 'username',
|
|
111
|
+
message: 'Enter username/email:',
|
|
112
|
+
default: defaultUsername,
|
|
113
|
+
validate: (input) => input.length > 0 || 'Username is required',
|
|
114
|
+
},
|
|
115
|
+
]);
|
|
116
|
+
usernameValue = response.username;
|
|
117
|
+
}
|
|
118
|
+
secureUsername = new SecureString(usernameValue);
|
|
119
|
+
|
|
120
|
+
// Get password
|
|
121
|
+
let passwordValue: string;
|
|
122
|
+
if (options.password) {
|
|
123
|
+
passwordValue = options.password;
|
|
124
|
+
} else if (options.generatePassword) {
|
|
125
|
+
passwordValue = generatePassword();
|
|
126
|
+
// Security: Don't preview any part of the password
|
|
127
|
+
console.log('Secure password generated (stored in keychain)');
|
|
128
|
+
} else {
|
|
129
|
+
const { passwordOption } = await inquirer.prompt<{ passwordOption: string }>([
|
|
130
|
+
{
|
|
131
|
+
type: 'list',
|
|
132
|
+
name: 'passwordOption',
|
|
133
|
+
message: 'Password:',
|
|
134
|
+
choices: [
|
|
135
|
+
{ name: 'Generate secure password', value: 'generate' },
|
|
136
|
+
{ name: 'Enter existing password', value: 'enter' },
|
|
137
|
+
],
|
|
138
|
+
},
|
|
139
|
+
]);
|
|
140
|
+
|
|
141
|
+
if (passwordOption === 'generate') {
|
|
142
|
+
passwordValue = generatePassword();
|
|
143
|
+
// Security: Don't preview any part of the password
|
|
144
|
+
console.log('Secure password generated (stored in keychain)');
|
|
145
|
+
} else {
|
|
146
|
+
const { enteredPassword } = await inquirer.prompt<{ enteredPassword: string }>([
|
|
147
|
+
{
|
|
148
|
+
type: 'password',
|
|
149
|
+
name: 'enteredPassword',
|
|
150
|
+
message: 'Enter password:',
|
|
151
|
+
mask: '*',
|
|
152
|
+
validate: (input) => input.length > 0 || 'Password is required',
|
|
153
|
+
},
|
|
154
|
+
]);
|
|
155
|
+
passwordValue = enteredPassword;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
securePassword = new SecureString(passwordValue);
|
|
159
|
+
|
|
160
|
+
const selectors: Selectors = {
|
|
161
|
+
username: options.usernameSelector,
|
|
162
|
+
password: options.passwordSelector,
|
|
163
|
+
submit: options.submitSelector,
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// Fill form fields using secure values
|
|
167
|
+
await fillField(page, options.usernameSelector, secureUsername.getValue());
|
|
168
|
+
await fillField(page, options.passwordSelector, securePassword.getValue());
|
|
169
|
+
|
|
170
|
+
// Store in keychain
|
|
171
|
+
await storeRP({
|
|
172
|
+
origin,
|
|
173
|
+
selectors,
|
|
174
|
+
credentials: {
|
|
175
|
+
username: secureUsername.getValue(),
|
|
176
|
+
password: securePassword.getValue(),
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
console.log('Credentials registered successfully');
|
|
181
|
+
console.log('Complete signup/login in browser.');
|
|
182
|
+
} finally {
|
|
183
|
+
// Securely clear sensitive data from memory
|
|
184
|
+
if (secureUsername) secureUsername.clear();
|
|
185
|
+
if (securePassword) securePassword.clear();
|
|
186
|
+
await browser.close();
|
|
187
|
+
}
|
|
188
|
+
}
|