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.
Files changed (87) hide show
  1. package/.cursor/skills/npm-publish/SKILL.md +58 -0
  2. package/.github/workflows/ci.yml +67 -0
  3. package/README.md +164 -0
  4. package/ROADMAP.md +986 -0
  5. package/dist/commands/config.d.ts +8 -0
  6. package/dist/commands/config.d.ts.map +1 -0
  7. package/dist/commands/config.js +67 -0
  8. package/dist/commands/config.js.map +1 -0
  9. package/dist/commands/delete.d.ts +7 -0
  10. package/dist/commands/delete.d.ts.map +1 -0
  11. package/dist/commands/delete.js +30 -0
  12. package/dist/commands/delete.js.map +1 -0
  13. package/dist/commands/login.d.ts +7 -0
  14. package/dist/commands/login.d.ts.map +1 -0
  15. package/dist/commands/login.js +37 -0
  16. package/dist/commands/login.js.map +1 -0
  17. package/dist/commands/register.d.ts +13 -0
  18. package/dist/commands/register.d.ts.map +1 -0
  19. package/dist/commands/register.js +160 -0
  20. package/dist/commands/register.js.map +1 -0
  21. package/dist/core/audit.d.ts +15 -0
  22. package/dist/core/audit.d.ts.map +1 -0
  23. package/dist/core/audit.js +36 -0
  24. package/dist/core/audit.js.map +1 -0
  25. package/dist/core/browser.d.ts +7 -0
  26. package/dist/core/browser.d.ts.map +1 -0
  27. package/dist/core/browser.js +104 -0
  28. package/dist/core/browser.js.map +1 -0
  29. package/dist/core/config.d.ts +9 -0
  30. package/dist/core/config.d.ts.map +1 -0
  31. package/dist/core/config.js +80 -0
  32. package/dist/core/config.js.map +1 -0
  33. package/dist/core/crypto.d.ts +17 -0
  34. package/dist/core/crypto.d.ts.map +1 -0
  35. package/dist/core/crypto.js +90 -0
  36. package/dist/core/crypto.js.map +1 -0
  37. package/dist/core/fields.d.ts +5 -0
  38. package/dist/core/fields.d.ts.map +1 -0
  39. package/dist/core/fields.js +54 -0
  40. package/dist/core/fields.js.map +1 -0
  41. package/dist/core/keychain.d.ts +5 -0
  42. package/dist/core/keychain.d.ts.map +1 -0
  43. package/dist/core/keychain.js +97 -0
  44. package/dist/core/keychain.js.map +1 -0
  45. package/dist/core/origin.d.ts +25 -0
  46. package/dist/core/origin.d.ts.map +1 -0
  47. package/dist/core/origin.js +73 -0
  48. package/dist/core/origin.js.map +1 -0
  49. package/dist/core/ratelimit.d.ts +10 -0
  50. package/dist/core/ratelimit.d.ts.map +1 -0
  51. package/dist/core/ratelimit.js +70 -0
  52. package/dist/core/ratelimit.js.map +1 -0
  53. package/dist/core/secure-memory.d.ts +39 -0
  54. package/dist/core/secure-memory.d.ts.map +1 -0
  55. package/dist/core/secure-memory.js +68 -0
  56. package/dist/core/secure-memory.js.map +1 -0
  57. package/dist/index.d.ts +3 -0
  58. package/dist/index.d.ts.map +1 -0
  59. package/dist/index.js +129 -0
  60. package/dist/index.js.map +1 -0
  61. package/dist/types/index.d.ts +27 -0
  62. package/dist/types/index.d.ts.map +1 -0
  63. package/dist/types/index.js +2 -0
  64. package/dist/types/index.js.map +1 -0
  65. package/package.json +58 -0
  66. package/src/commands/config.ts +84 -0
  67. package/src/commands/delete.ts +39 -0
  68. package/src/commands/login.ts +49 -0
  69. package/src/commands/register.ts +188 -0
  70. package/src/core/audit.ts +59 -0
  71. package/src/core/browser.ts +131 -0
  72. package/src/core/config.ts +91 -0
  73. package/src/core/crypto.ts +106 -0
  74. package/src/core/fields.ts +59 -0
  75. package/src/core/keychain.ts +110 -0
  76. package/src/core/origin.ts +90 -0
  77. package/src/core/ratelimit.ts +89 -0
  78. package/src/core/secure-memory.ts +78 -0
  79. package/src/index.ts +133 -0
  80. package/src/types/index.ts +31 -0
  81. package/tests/browser-password-manager.test.ts +1023 -0
  82. package/tests/crypto.test.ts +140 -0
  83. package/tests/e2e.test.ts +565 -0
  84. package/tests/fixtures/server.ts +59 -0
  85. package/tests/security.test.ts +113 -0
  86. package/tsconfig.json +20 -0
  87. 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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -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":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAE9C,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,OAAO,CAAC;KACb,WAAW,CAAC,2CAA2C,CAAC;KACxD,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO;KACJ,OAAO,CAAC,UAAU,CAAC;KACnB,WAAW,CAAC,qCAAqC,CAAC;KAClD,cAAc,CAAC,kBAAkB,EAAE,oDAAoD,CAAC;KACxF,cAAc,CAAC,gCAAgC,EAAE,uCAAuC,CAAC;KACzF,cAAc,CAAC,gCAAgC,EAAE,iCAAiC,CAAC;KACnF,MAAM,CAAC,8BAA8B,EAAE,gCAAgC,CAAC;KACxE,MAAM,CAAC,uBAAuB,EAAE,kCAAkC,CAAC;KACnE,MAAM,CAAC,uBAAuB,EAAE,4BAA4B,CAAC;KAC7D,MAAM,CAAC,qBAAqB,EAAE,8CAA8C,CAAC;KAC7E,MAAM,CAAC,aAAa,EAAE,2BAA2B,CAAC;KAClD,MAAM,CAAC,cAAc,EAAE,iDAAiD,CAAC;KACzE,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACxB,IAAI,CAAC;QACH,MAAM,QAAQ,CAAC;YACb,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,gBAAgB,EAAE,OAAO,CAAC,gBAAgB;YAC1C,gBAAgB,EAAE,OAAO,CAAC,gBAAgB;YAC1C,cAAc,EAAE,OAAO,CAAC,cAAc;YACtC,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,gBAAgB,EAAE,OAAO,CAAC,gBAAgB;YAC1C,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,SAAS,EAAE,OAAO,CAAC,SAAS;SAC7B,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QACxE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,mCAAmC,CAAC;KAChD,cAAc,CAAC,kBAAkB,EAAE,oDAAoD,CAAC;KACxF,MAAM,CAAC,UAAU,EAAE,+CAA+C,CAAC;KACnE,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACxB,IAAI,CAAC;QACH,MAAM,KAAK,CAAC;YACV,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,MAAM,EAAE,OAAO,CAAC,MAAM;SACvB,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QACxE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,+BAA+B,CAAC;KAC5C,cAAc,CAAC,gBAAgB,EAAE,6CAA6C,CAAC;KAC/E,MAAM,CAAC,aAAa,EAAE,0BAA0B,CAAC;KACjD,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACxB,IAAI,CAAC;QACH,MAAM,aAAa,CAAC;YAClB,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,KAAK,EAAE,OAAO,CAAC,KAAK;SACrB,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QACxE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,MAAM,SAAS,GAAG,OAAO;KACtB,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,4BAA4B,CAAC,CAAC;AAE7C,SAAS;KACN,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,+BAA+B,CAAC;KAC5C,MAAM,CAAC,KAAK,IAAI,EAAE;IACjB,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACnC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QACxE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,SAAS;KACN,OAAO,CAAC,WAAW,CAAC;KACpB,WAAW,CAAC,2BAA2B,CAAC;KACxC,MAAM,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;IACpB,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;IACvC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QACxE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,SAAS;KACN,OAAO,CAAC,mBAAmB,CAAC;KAC5B,WAAW,CAAC,2BAA2B,CAAC;KACxC,MAAM,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE;IAC3B,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC;IAC9C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QACxE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,SAAS;KACN,OAAO,CAAC,aAAa,CAAC;KACtB,WAAW,CAAC,8BAA8B,CAAC;KAC3C,MAAM,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;IACpB,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC;IACzC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QACxE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,OAAO,CAAC,KAAK,EAAE,CAAC"}
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.js.map
@@ -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
+ }