ai-cred 1.0.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 (64) hide show
  1. package/dist/cli/commands/add.d.ts +8 -0
  2. package/dist/cli/commands/add.js +145 -0
  3. package/dist/cli/commands/add.js.map +1 -0
  4. package/dist/cli/commands/get.d.ts +7 -0
  5. package/dist/cli/commands/get.js +53 -0
  6. package/dist/cli/commands/get.js.map +1 -0
  7. package/dist/cli/commands/list.d.ts +7 -0
  8. package/dist/cli/commands/list.js +118 -0
  9. package/dist/cli/commands/list.js.map +1 -0
  10. package/dist/cli/commands/remove.d.ts +7 -0
  11. package/dist/cli/commands/remove.js +53 -0
  12. package/dist/cli/commands/remove.js.map +1 -0
  13. package/dist/cli/commands/update.d.ts +8 -0
  14. package/dist/cli/commands/update.js +140 -0
  15. package/dist/cli/commands/update.js.map +1 -0
  16. package/dist/cli/index.d.ts +7 -0
  17. package/dist/cli/index.js +25 -0
  18. package/dist/cli/index.js.map +1 -0
  19. package/dist/cli/utils/display.d.ts +14 -0
  20. package/dist/cli/utils/display.js +31 -0
  21. package/dist/cli/utils/display.js.map +1 -0
  22. package/dist/cli/utils/prompt.d.ts +6 -0
  23. package/dist/cli/utils/prompt.js +66 -0
  24. package/dist/cli/utils/prompt.js.map +1 -0
  25. package/dist/core/audit-logger.d.ts +25 -0
  26. package/dist/core/audit-logger.js +41 -0
  27. package/dist/core/audit-logger.js.map +1 -0
  28. package/dist/core/audit-logger.test.d.ts +1 -0
  29. package/dist/core/audit-logger.test.js +89 -0
  30. package/dist/core/audit-logger.test.js.map +1 -0
  31. package/dist/core/errors.d.ts +23 -0
  32. package/dist/core/errors.js +78 -0
  33. package/dist/core/errors.js.map +1 -0
  34. package/dist/core/keychain-adapter.d.ts +65 -0
  35. package/dist/core/keychain-adapter.js +221 -0
  36. package/dist/core/keychain-adapter.js.map +1 -0
  37. package/dist/core/keychain-adapter.test.d.ts +1 -0
  38. package/dist/core/keychain-adapter.test.js +331 -0
  39. package/dist/core/keychain-adapter.test.js.map +1 -0
  40. package/dist/e2e/environment-isolation.test.d.ts +1 -0
  41. package/dist/e2e/environment-isolation.test.js +112 -0
  42. package/dist/e2e/environment-isolation.test.js.map +1 -0
  43. package/dist/server/index.d.ts +7 -0
  44. package/dist/server/index.js +26 -0
  45. package/dist/server/index.js.map +1 -0
  46. package/dist/server/tools.d.ts +10 -0
  47. package/dist/server/tools.js +230 -0
  48. package/dist/server/tools.js.map +1 -0
  49. package/dist/server/tools.test.d.ts +7 -0
  50. package/dist/server/tools.test.js +606 -0
  51. package/dist/server/tools.test.js.map +1 -0
  52. package/dist/types/credential.d.ts +77 -0
  53. package/dist/types/credential.js +46 -0
  54. package/dist/types/credential.js.map +1 -0
  55. package/dist/types/credential.test.d.ts +1 -0
  56. package/dist/types/credential.test.js +316 -0
  57. package/dist/types/credential.test.js.map +1 -0
  58. package/dist/utils/validation.d.ts +15 -0
  59. package/dist/utils/validation.js +34 -0
  60. package/dist/utils/validation.js.map +1 -0
  61. package/dist/utils/validation.test.d.ts +1 -0
  62. package/dist/utils/validation.test.js +139 -0
  63. package/dist/utils/validation.test.js.map +1 -0
  64. package/package.json +35 -0
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Display utilities for CLI output: secret masking and formatting.
3
+ */
4
+ import type { Credential } from '../../types/credential.js';
5
+ /**
6
+ * Mask a secret value for safe display.
7
+ * Short values (<=6 chars) become "****".
8
+ * Longer values show first 2 and last 2 characters: "ak...3f".
9
+ */
10
+ export declare function maskSecret(value: string): string;
11
+ /**
12
+ * Extract the primary secret field from a credential for masked display.
13
+ */
14
+ export declare function getPrimarySecret(credential: Credential): string;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Display utilities for CLI output: secret masking and formatting.
3
+ */
4
+ /**
5
+ * Mask a secret value for safe display.
6
+ * Short values (<=6 chars) become "****".
7
+ * Longer values show first 2 and last 2 characters: "ak...3f".
8
+ */
9
+ export function maskSecret(value) {
10
+ if (value.length <= 6)
11
+ return '****';
12
+ return `${value.slice(0, 2)}...${value.slice(-2)}`;
13
+ }
14
+ /**
15
+ * Extract the primary secret field from a credential for masked display.
16
+ */
17
+ export function getPrimarySecret(credential) {
18
+ switch (credential.type) {
19
+ case 'ssh':
20
+ return credential.password ?? credential.keyPath ?? '(key-based)';
21
+ case 'jenkins':
22
+ return credential.apiToken;
23
+ case 'portainer':
24
+ return credential.apiToken;
25
+ case 'aws':
26
+ return credential.secretAccessKey;
27
+ case 'api-key':
28
+ return credential.key;
29
+ }
30
+ }
31
+ //# sourceMappingURL=display.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"display.js","sourceRoot":"","sources":["../../../src/cli/utils/display.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC;QAAE,OAAO,MAAM,CAAC;IACrC,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AACrD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,UAAsB;IACrD,QAAQ,UAAU,CAAC,IAAI,EAAE,CAAC;QACxB,KAAK,KAAK;YACR,OAAO,UAAU,CAAC,QAAQ,IAAI,UAAU,CAAC,OAAO,IAAI,aAAa,CAAC;QACpE,KAAK,SAAS;YACZ,OAAO,UAAU,CAAC,QAAQ,CAAC;QAC7B,KAAK,WAAW;YACd,OAAO,UAAU,CAAC,QAAQ,CAAC;QAC7B,KAAK,KAAK;YACR,OAAO,UAAU,CAAC,eAAe,CAAC;QACpC,KAAK,SAAS;YACZ,OAAO,UAAU,CAAC,GAAG,CAAC;IAC1B,CAAC;AACH,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Masked password input for CLI secret prompts.
3
+ *
4
+ * Handles both TTY (interactive) and non-TTY (piped/CI) input streams.
5
+ */
6
+ export declare function promptSecret(prompt: string): Promise<string>;
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Masked password input for CLI secret prompts.
3
+ *
4
+ * Handles both TTY (interactive) and non-TTY (piped/CI) input streams.
5
+ */
6
+ export function promptSecret(prompt) {
7
+ return new Promise((resolve, reject) => {
8
+ process.stdout.write(prompt);
9
+ // Non-TTY: read a single line without masking (setRawMode unavailable)
10
+ if (!process.stdin.isTTY) {
11
+ let data = '';
12
+ process.stdin.setEncoding('utf8');
13
+ process.stdin.on('data', (chunk) => {
14
+ data += chunk;
15
+ const newlineIdx = data.indexOf('\n');
16
+ if (newlineIdx !== -1) {
17
+ process.stdin.pause();
18
+ resolve(data.slice(0, newlineIdx).replace(/\r$/, ''));
19
+ }
20
+ });
21
+ process.stdin.on('end', () => {
22
+ resolve(data.replace(/[\r\n]+$/, ''));
23
+ });
24
+ process.stdin.resume();
25
+ return;
26
+ }
27
+ // TTY: character-by-character reading with asterisk echo
28
+ const input = [];
29
+ process.stdin.setRawMode(true);
30
+ process.stdin.setEncoding('utf8');
31
+ process.stdin.resume();
32
+ const onData = (ch) => {
33
+ const code = ch.charCodeAt(0);
34
+ // Ctrl+C
35
+ if (ch === '\u0003') {
36
+ process.stdin.setRawMode(false);
37
+ process.stdin.pause();
38
+ process.stdin.removeListener('data', onData);
39
+ process.stdout.write('\n');
40
+ process.exit(1);
41
+ }
42
+ // Enter
43
+ if (ch === '\r' || ch === '\n') {
44
+ process.stdin.setRawMode(false);
45
+ process.stdin.pause();
46
+ process.stdin.removeListener('data', onData);
47
+ process.stdout.write('\n');
48
+ resolve(input.join(''));
49
+ return;
50
+ }
51
+ // Backspace / Delete
52
+ if (code === 127 || code === 8) {
53
+ if (input.length > 0) {
54
+ input.pop();
55
+ process.stdout.write('\b \b');
56
+ }
57
+ return;
58
+ }
59
+ // Regular character
60
+ input.push(ch);
61
+ process.stdout.write('*');
62
+ };
63
+ process.stdin.on('data', onData);
64
+ });
65
+ }
66
+ //# sourceMappingURL=prompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../../src/cli/utils/prompt.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Append-only audit logger for credential operations.
3
+ *
4
+ * Writes JSON Lines to ~/.ai-cred/audit.log.
5
+ * CRITICAL: Only operation metadata is logged -- never credential values.
6
+ * The AuditEntry type enforces this by design.
7
+ */
8
+ export interface AuditEntry {
9
+ timestamp: string;
10
+ operation: 'store' | 'get' | 'delete' | 'update' | 'list' | 'find';
11
+ service: string;
12
+ environment: string;
13
+ }
14
+ /**
15
+ * Returns the path to the audit log file.
16
+ * Exported for testing purposes.
17
+ */
18
+ export declare function getAuditLogPath(): string;
19
+ /**
20
+ * Append a single audit entry to the log file.
21
+ *
22
+ * Adds an ISO 8601 timestamp automatically.
23
+ * Silently fails on write errors -- audit logging must never break credential operations.
24
+ */
25
+ export declare function auditLog(entry: Omit<AuditEntry, 'timestamp'>, logPath?: string): Promise<void>;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Append-only audit logger for credential operations.
3
+ *
4
+ * Writes JSON Lines to ~/.ai-cred/audit.log.
5
+ * CRITICAL: Only operation metadata is logged -- never credential values.
6
+ * The AuditEntry type enforces this by design.
7
+ */
8
+ import { appendFileSync, mkdirSync, existsSync } from 'node:fs';
9
+ import { homedir } from 'node:os';
10
+ import { join, dirname } from 'node:path';
11
+ /**
12
+ * Returns the path to the audit log file.
13
+ * Exported for testing purposes.
14
+ */
15
+ export function getAuditLogPath() {
16
+ return join(homedir(), '.ai-cred', 'audit.log');
17
+ }
18
+ /**
19
+ * Append a single audit entry to the log file.
20
+ *
21
+ * Adds an ISO 8601 timestamp automatically.
22
+ * Silently fails on write errors -- audit logging must never break credential operations.
23
+ */
24
+ export async function auditLog(entry, logPath) {
25
+ try {
26
+ const resolvedPath = logPath ?? getAuditLogPath();
27
+ const dir = dirname(resolvedPath);
28
+ if (!existsSync(dir)) {
29
+ mkdirSync(dir, { recursive: true });
30
+ }
31
+ const fullEntry = {
32
+ timestamp: new Date().toISOString(),
33
+ ...entry,
34
+ };
35
+ appendFileSync(resolvedPath, JSON.stringify(fullEntry) + '\n');
36
+ }
37
+ catch {
38
+ // Silently fail -- audit must not break credential operations
39
+ }
40
+ }
41
+ //# sourceMappingURL=audit-logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-logger.js","sourceRoot":"","sources":["../../src/core/audit-logger.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAChE,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAS1C;;;GAGG;AACH,MAAM,UAAU,eAAe;IAC7B,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC;AAClD,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,KAAoC,EAAE,OAAgB;IACnF,IAAI,CAAC;QACH,MAAM,YAAY,GAAG,OAAO,IAAI,eAAe,EAAE,CAAC;QAClD,MAAM,GAAG,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;QAElC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACrB,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACtC,CAAC;QAED,MAAM,SAAS,GAAe;YAC5B,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,GAAG,KAAK;SACT,CAAC;QAEF,cAAc,CAAC,YAAY,EAAE,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,CAAC;IACjE,CAAC;IAAC,MAAM,CAAC;QACP,8DAA8D;IAChE,CAAC;AACH,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,89 @@
1
+ import { describe, it, expect, afterEach } from 'vitest';
2
+ import { mkdtempSync, readFileSync, rmSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { auditLog } from './audit-logger.js';
6
+ describe('auditLog', () => {
7
+ let tempDir;
8
+ let logPath;
9
+ // Create a fresh temp directory before each test via the setup in each test
10
+ // Clean up after each test
11
+ afterEach(() => {
12
+ if (tempDir) {
13
+ rmSync(tempDir, { recursive: true, force: true });
14
+ }
15
+ });
16
+ function setup() {
17
+ tempDir = mkdtempSync(join(tmpdir(), 'audit-test-'));
18
+ logPath = join(tempDir, 'audit.log');
19
+ }
20
+ it('writes audit entry with correct fields', async () => {
21
+ setup();
22
+ await auditLog({ operation: 'store', service: 'jenkins', environment: 'prod' }, logPath);
23
+ const content = readFileSync(logPath, 'utf-8').trim();
24
+ const entry = JSON.parse(content);
25
+ expect(entry).toHaveProperty('timestamp');
26
+ expect(entry.operation).toBe('store');
27
+ expect(entry.service).toBe('jenkins');
28
+ expect(entry.environment).toBe('prod');
29
+ });
30
+ it('appends multiple entries without overwriting', async () => {
31
+ setup();
32
+ await auditLog({ operation: 'store', service: 'jenkins', environment: 'prod' }, logPath);
33
+ await auditLog({ operation: 'get', service: 'aws', environment: 'dev' }, logPath);
34
+ await auditLog({ operation: 'delete', service: 'ssh', environment: 'staging' }, logPath);
35
+ const lines = readFileSync(logPath, 'utf-8').trim().split('\n');
36
+ expect(lines).toHaveLength(3);
37
+ // Each line is valid JSON
38
+ for (const line of lines) {
39
+ expect(() => JSON.parse(line)).not.toThrow();
40
+ }
41
+ });
42
+ it('never contains credential values', async () => {
43
+ setup();
44
+ // The audit logger only accepts operation metadata fields.
45
+ // Even if a credential with secrets exists, the AuditEntry type
46
+ // excludes credential values by design.
47
+ const secretPassword = 'super-secret-123';
48
+ const secretToken = 'tok-abc';
49
+ // Log an operation that would correspond to storing a credential with secrets
50
+ await auditLog({ operation: 'store', service: 'jenkins', environment: 'prod' }, logPath);
51
+ const content = readFileSync(logPath, 'utf-8');
52
+ expect(content).not.toContain(secretPassword);
53
+ expect(content).not.toContain(secretToken);
54
+ // Verify only expected fields are present
55
+ const entry = JSON.parse(content.trim());
56
+ const keys = Object.keys(entry);
57
+ expect(keys).toEqual(['timestamp', 'operation', 'service', 'environment']);
58
+ });
59
+ it('creates directory if it does not exist', async () => {
60
+ tempDir = mkdtempSync(join(tmpdir(), 'audit-test-'));
61
+ const nestedPath = join(tempDir, 'nested', 'deep', 'audit.log');
62
+ await auditLog({ operation: 'get', service: 'aws', environment: 'dev' }, nestedPath);
63
+ const content = readFileSync(nestedPath, 'utf-8').trim();
64
+ expect(content.length).toBeGreaterThan(0);
65
+ const entry = JSON.parse(content);
66
+ expect(entry.operation).toBe('get');
67
+ });
68
+ it('persists across separate calls (simulating restart)', async () => {
69
+ setup();
70
+ // First "session": write 2 entries
71
+ await auditLog({ operation: 'store', service: 'jenkins', environment: 'prod' }, logPath);
72
+ await auditLog({ operation: 'get', service: 'jenkins', environment: 'prod' }, logPath);
73
+ // Simulate process restart: no in-memory state carried over.
74
+ // Just call auditLog again with the same path -- it uses appendFileSync,
75
+ // so there is no in-memory state to carry.
76
+ await auditLog({ operation: 'delete', service: 'jenkins', environment: 'prod' }, logPath);
77
+ const lines = readFileSync(logPath, 'utf-8').trim().split('\n');
78
+ expect(lines).toHaveLength(3);
79
+ });
80
+ it('timestamp is valid ISO 8601', async () => {
81
+ setup();
82
+ await auditLog({ operation: 'list', service: 'all', environment: 'global' }, logPath);
83
+ const content = readFileSync(logPath, 'utf-8').trim();
84
+ const entry = JSON.parse(content);
85
+ // A valid ISO 8601 timestamp round-trips through Date
86
+ expect(new Date(entry.timestamp).toISOString()).toBe(entry.timestamp);
87
+ });
88
+ });
89
+ //# sourceMappingURL=audit-logger.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-logger.test.js","sourceRoot":"","sources":["../../src/core/audit-logger.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAC5D,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAmB,MAAM,mBAAmB,CAAC;AAE9D,QAAQ,CAAC,UAAU,EAAE,GAAG,EAAE;IACxB,IAAI,OAAe,CAAC;IACpB,IAAI,OAAe,CAAC;IAEpB,4EAA4E;IAC5E,2BAA2B;IAC3B,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACpD,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,SAAS,KAAK;QACZ,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,aAAa,CAAC,CAAC,CAAC;QACrD,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IACvC,CAAC;IAED,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;QACtD,KAAK,EAAE,CAAC;QACR,MAAM,QAAQ,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,CAAC;QAEzF,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;QACtD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAe,CAAC;QAEhD,MAAM,CAAC,KAAK,CAAC,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;QAC1C,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACtC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACtC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,KAAK,IAAI,EAAE;QAC5D,KAAK,EAAE,CAAC;QACR,MAAM,QAAQ,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,CAAC;QACzF,MAAM,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,OAAO,CAAC,CAAC;QAClF,MAAM,QAAQ,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,EAAE,OAAO,CAAC,CAAC;QAEzF,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAChE,MAAM,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAE9B,0BAA0B;QAC1B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QAC/C,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kCAAkC,EAAE,KAAK,IAAI,EAAE;QAChD,KAAK,EAAE,CAAC;QACR,2DAA2D;QAC3D,gEAAgE;QAChE,wCAAwC;QACxC,MAAM,cAAc,GAAG,kBAAkB,CAAC;QAC1C,MAAM,WAAW,GAAG,SAAS,CAAC;QAE9B,8EAA8E;QAC9E,MAAM,QAAQ,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,CAAC;QAEzF,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC/C,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;QAC9C,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QAC3C,0CAA0C;QAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,CAAe,CAAC;QACvD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChC,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,WAAW,EAAE,SAAS,EAAE,aAAa,CAAC,CAAC,CAAC;IAC7E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;QACtD,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,aAAa,CAAC,CAAC,CAAC;QACrD,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;QAEhE,MAAM,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,UAAU,CAAC,CAAC;QAErF,MAAM,OAAO,GAAG,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;QACzD,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAe,CAAC;QAChD,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,KAAK,IAAI,EAAE;QACnE,KAAK,EAAE,CAAC;QACR,mCAAmC;QACnC,MAAM,QAAQ,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,CAAC;QACzF,MAAM,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,CAAC;QAEvF,6DAA6D;QAC7D,yEAAyE;QACzE,2CAA2C;QAC3C,MAAM,QAAQ,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,CAAC;QAE1F,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAChE,MAAM,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6BAA6B,EAAE,KAAK,IAAI,EAAE;QAC3C,KAAK,EAAE,CAAC;QACR,MAAM,QAAQ,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,EAAE,OAAO,CAAC,CAAC;QAEtF,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;QACtD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAe,CAAC;QAEhD,sDAAsD;QACtD,MAAM,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * KeychainError: Security boundary for all keychain operation errors.
3
+ *
4
+ * CRITICAL (SEC-05): This class is designed to NEVER expose credential data,
5
+ * stderr output, or raw subprocess error messages. The `message` property and
6
+ * `toString()` return only pre-mapped safe messages derived from exit codes.
7
+ */
8
+ export declare class KeychainError extends Error {
9
+ readonly operation: string;
10
+ readonly service: string | undefined;
11
+ readonly exitCode: number;
12
+ readonly name = "KeychainError";
13
+ constructor(operation: string, exitCode: number, service?: string, _originalMessage?: string);
14
+ toString(): string;
15
+ }
16
+ /**
17
+ * Convert an unknown subprocess error into a KeychainError.
18
+ *
19
+ * CRITICAL (SEC-05): This function deliberately does NOT pass stderr, stdout,
20
+ * or any subprocess output into the KeychainError. Only the numeric exit code
21
+ * is forwarded; all textual subprocess data is discarded.
22
+ */
23
+ export declare function toKeychainError(error: unknown, operation: string, service?: string): KeychainError;
@@ -0,0 +1,78 @@
1
+ /**
2
+ * KeychainError: Security boundary for all keychain operation errors.
3
+ *
4
+ * CRITICAL (SEC-05): This class is designed to NEVER expose credential data,
5
+ * stderr output, or raw subprocess error messages. The `message` property and
6
+ * `toString()` return only pre-mapped safe messages derived from exit codes.
7
+ */
8
+ /**
9
+ * Known macOS security CLI exit codes mapped to safe user-facing messages.
10
+ */
11
+ const EXIT_CODE_MESSAGES = {
12
+ 0: 'Success',
13
+ 36: 'Keychain access was denied',
14
+ 44: 'Credential not found',
15
+ 45: 'Credential already exists',
16
+ 51: 'Keychain is locked',
17
+ };
18
+ function safeMessage(exitCode, service) {
19
+ if (exitCode === 44) {
20
+ const base = EXIT_CODE_MESSAGES[44];
21
+ return service ? `${base} for ${service}` : base;
22
+ }
23
+ if (exitCode === 45) {
24
+ const base = EXIT_CODE_MESSAGES[45];
25
+ return service ? `${base} for ${service}` : base;
26
+ }
27
+ if (exitCode in EXIT_CODE_MESSAGES) {
28
+ return EXIT_CODE_MESSAGES[exitCode];
29
+ }
30
+ return `Keychain operation failed (code ${exitCode})`;
31
+ }
32
+ export class KeychainError extends Error {
33
+ operation;
34
+ service;
35
+ exitCode;
36
+ name = 'KeychainError';
37
+ constructor(operation, exitCode, service,
38
+ // originalMessage is accepted but deliberately discarded -- never stored or exposed
39
+ _originalMessage) {
40
+ const msg = safeMessage(exitCode, service);
41
+ super(msg);
42
+ this.operation = operation;
43
+ this.exitCode = exitCode;
44
+ this.service = service;
45
+ // Ensure the prototype chain is set correctly for instanceof checks
46
+ Object.setPrototypeOf(this, KeychainError.prototype);
47
+ }
48
+ toString() {
49
+ return `KeychainError: ${this.message}`;
50
+ }
51
+ }
52
+ /**
53
+ * Convert an unknown subprocess error into a KeychainError.
54
+ *
55
+ * CRITICAL (SEC-05): This function deliberately does NOT pass stderr, stdout,
56
+ * or any subprocess output into the KeychainError. Only the numeric exit code
57
+ * is forwarded; all textual subprocess data is discarded.
58
+ */
59
+ export function toKeychainError(error, operation, service) {
60
+ let exitCode = -1;
61
+ if (error !== null && typeof error === 'object') {
62
+ const err = error;
63
+ // Node.js subprocess errors expose the exit code as a numeric `code` on
64
+ // ChildProcessError/Error objects -- but `code` may also be a string like
65
+ // 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER'. Only accept numeric codes.
66
+ if (typeof err['code'] === 'number') {
67
+ exitCode = err['code'];
68
+ }
69
+ // Some wrappers expose the numeric exit code under `exitCode`
70
+ if (exitCode === -1 && typeof err['exitCode'] === 'number') {
71
+ exitCode = err['exitCode'];
72
+ }
73
+ // If `code` is a non-numeric string signal/error-code, keep exitCode as -1
74
+ }
75
+ // Deliberately NOT forwarding any stderr/stdout/message content
76
+ return new KeychainError(operation, exitCode, service);
77
+ }
78
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;GAEG;AACH,MAAM,kBAAkB,GAA2B;IACjD,CAAC,EAAE,SAAS;IACZ,EAAE,EAAE,4BAA4B;IAChC,EAAE,EAAE,sBAAsB;IAC1B,EAAE,EAAE,2BAA2B;IAC/B,EAAE,EAAE,oBAAoB;CACzB,CAAC;AAEF,SAAS,WAAW,CAAC,QAAgB,EAAE,OAAgB;IACrD,IAAI,QAAQ,KAAK,EAAE,EAAE,CAAC;QACpB,MAAM,IAAI,GAAG,kBAAkB,CAAC,EAAE,CAAE,CAAC;QACrC,OAAO,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,QAAQ,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACnD,CAAC;IACD,IAAI,QAAQ,KAAK,EAAE,EAAE,CAAC;QACpB,MAAM,IAAI,GAAG,kBAAkB,CAAC,EAAE,CAAE,CAAC;QACrC,OAAO,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,QAAQ,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACnD,CAAC;IACD,IAAI,QAAQ,IAAI,kBAAkB,EAAE,CAAC;QACnC,OAAO,kBAAkB,CAAC,QAAQ,CAAE,CAAC;IACvC,CAAC;IACD,OAAO,mCAAmC,QAAQ,GAAG,CAAC;AACxD,CAAC;AAED,MAAM,OAAO,aAAc,SAAQ,KAAK;IAC7B,SAAS,CAAS;IAClB,OAAO,CAAqB;IAC5B,QAAQ,CAAS;IACR,IAAI,GAAG,eAAe,CAAC;IAEzC,YACE,SAAiB,EACjB,QAAgB,EAChB,OAAgB;IAChB,oFAAoF;IACpF,gBAAyB;QAEzB,MAAM,GAAG,GAAG,WAAW,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC3C,KAAK,CAAC,GAAG,CAAC,CAAC;QACX,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QAEvB,oEAAoE;QACpE,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;IACvD,CAAC;IAEQ,QAAQ;QACf,OAAO,kBAAkB,IAAI,CAAC,OAAO,EAAE,CAAC;IAC1C,CAAC;CACF;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAC7B,KAAc,EACd,SAAiB,EACjB,OAAgB;IAEhB,IAAI,QAAQ,GAAG,CAAC,CAAC,CAAC;IAElB,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAChD,MAAM,GAAG,GAAG,KAAgC,CAAC;QAE7C,wEAAwE;QACxE,0EAA0E;QAC1E,kEAAkE;QAClE,IAAI,OAAO,GAAG,CAAC,MAAM,CAAC,KAAK,QAAQ,EAAE,CAAC;YACpC,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC;QACzB,CAAC;QACD,8DAA8D;QAC9D,IAAI,QAAQ,KAAK,CAAC,CAAC,IAAI,OAAO,GAAG,CAAC,UAAU,CAAC,KAAK,QAAQ,EAAE,CAAC;YAC3D,QAAQ,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC;QAC7B,CAAC;QACD,2EAA2E;IAC7E,CAAC;IAED,gEAAgE;IAChE,OAAO,IAAI,aAAa,CAAC,SAAS,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;AACzD,CAAC"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * KeychainAdapter: Wraps the macOS `security` CLI for all credential operations.
3
+ *
4
+ * Security invariants enforced throughout this file:
5
+ * - SEC-01: Every exec() call targets this.keychainPath (dedicated keychain only)
6
+ * - SEC-02: All subprocess calls use execFile (not exec) with argument arrays --
7
+ * shell string interpolation is never used
8
+ * - SEC-04: list() returns metadata only -- no secret values, no -d/-w flags
9
+ * - SEC-05: No logging of subprocess arguments or output (they may contain credentials)
10
+ */
11
+ import type { Credential, Environment } from '../types/credential.js';
12
+ export { KeychainError } from './errors.js';
13
+ /**
14
+ * Metadata returned by list() -- NEVER includes secret values (SEC-04).
15
+ */
16
+ export interface CredentialMetadata {
17
+ service: string;
18
+ environment: Environment;
19
+ type: string;
20
+ createdAt: string;
21
+ modifiedAt: string;
22
+ }
23
+ export declare class KeychainAdapter {
24
+ private readonly keychainPath;
25
+ constructor(keychainPath?: string);
26
+ /**
27
+ * Internal helper: invoke the macOS `security` binary with the given args.
28
+ *
29
+ * SECURITY: Never log args (may include credentials in -w values).
30
+ * SECURITY: Errors are converted via toKeychainError which strips subprocess output.
31
+ */
32
+ private exec;
33
+ /**
34
+ * Initialize the dedicated keychain.
35
+ *
36
+ * First run: creates keychain, disables auto-lock, adds to search list.
37
+ * Every run: unlocks the keychain so operations can proceed.
38
+ */
39
+ initialize(): Promise<void>;
40
+ /**
41
+ * Store a credential in the dedicated keychain.
42
+ * Uses -U to update if the entry already exists.
43
+ */
44
+ store(env: Environment, service: string, credential: Credential): Promise<void>;
45
+ /**
46
+ * Retrieve a credential from the dedicated keychain.
47
+ * Validates the returned JSON against CredentialSchema before returning.
48
+ */
49
+ get(env: Environment, service: string): Promise<Credential>;
50
+ /**
51
+ * Delete a credential from the dedicated keychain.
52
+ */
53
+ delete(env: Environment, service: string): Promise<void>;
54
+ /**
55
+ * List all ai-cred credentials as metadata (no secret values) (SEC-04).
56
+ *
57
+ * Uses dump-keychain without -d flag so password data is NEVER retrieved.
58
+ * Filters entries by APP_MARKER in the description field.
59
+ */
60
+ list(): Promise<CredentialMetadata[]>;
61
+ /**
62
+ * Delete the entire keychain file. Intended for test cleanup only.
63
+ */
64
+ destroy(): Promise<void>;
65
+ }