proofscan 0.6.10 → 0.7.1

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 (54) hide show
  1. package/dist/commands/config.d.ts.map +1 -1
  2. package/dist/commands/config.js +74 -25
  3. package/dist/commands/config.js.map +1 -1
  4. package/dist/commands/connectors.d.ts.map +1 -1
  5. package/dist/commands/connectors.js +9 -4
  6. package/dist/commands/connectors.js.map +1 -1
  7. package/dist/config/add.d.ts +23 -2
  8. package/dist/config/add.d.ts.map +1 -1
  9. package/dist/config/add.js +26 -4
  10. package/dist/config/add.js.map +1 -1
  11. package/dist/secrets/detection.d.ts +71 -0
  12. package/dist/secrets/detection.d.ts.map +1 -0
  13. package/dist/secrets/detection.js +186 -0
  14. package/dist/secrets/detection.js.map +1 -0
  15. package/dist/secrets/index.d.ts +12 -0
  16. package/dist/secrets/index.d.ts.map +1 -0
  17. package/dist/secrets/index.js +18 -0
  18. package/dist/secrets/index.js.map +1 -0
  19. package/dist/secrets/providers/dpapi.d.ts +22 -0
  20. package/dist/secrets/providers/dpapi.d.ts.map +1 -0
  21. package/dist/secrets/providers/dpapi.js +95 -0
  22. package/dist/secrets/providers/dpapi.js.map +1 -0
  23. package/dist/secrets/providers/index.d.ts +20 -0
  24. package/dist/secrets/providers/index.d.ts.map +1 -0
  25. package/dist/secrets/providers/index.js +47 -0
  26. package/dist/secrets/providers/index.js.map +1 -0
  27. package/dist/secrets/providers/plain.d.ts +21 -0
  28. package/dist/secrets/providers/plain.d.ts.map +1 -0
  29. package/dist/secrets/providers/plain.js +29 -0
  30. package/dist/secrets/providers/plain.js.map +1 -0
  31. package/dist/secrets/redaction.d.ts +76 -0
  32. package/dist/secrets/redaction.d.ts.map +1 -0
  33. package/dist/secrets/redaction.js +136 -0
  34. package/dist/secrets/redaction.js.map +1 -0
  35. package/dist/secrets/secretize.d.ts +107 -0
  36. package/dist/secrets/secretize.d.ts.map +1 -0
  37. package/dist/secrets/secretize.js +148 -0
  38. package/dist/secrets/secretize.js.map +1 -0
  39. package/dist/secrets/store.d.ts +42 -0
  40. package/dist/secrets/store.d.ts.map +1 -0
  41. package/dist/secrets/store.js +174 -0
  42. package/dist/secrets/store.js.map +1 -0
  43. package/dist/secrets/types.d.ts +135 -0
  44. package/dist/secrets/types.d.ts.map +1 -0
  45. package/dist/secrets/types.js +39 -0
  46. package/dist/secrets/types.js.map +1 -0
  47. package/dist/utils/output.d.ts +14 -0
  48. package/dist/utils/output.d.ts.map +1 -1
  49. package/dist/utils/output.js +16 -0
  50. package/dist/utils/output.js.map +1 -1
  51. package/dist/utils/sanitize-secrets.d.ts.map +1 -1
  52. package/dist/utils/sanitize-secrets.js +20 -3
  53. package/dist/utils/sanitize-secrets.js.map +1 -1
  54. package/package.json +1 -1
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Secret detection utilities (Phase 3.5)
3
+ *
4
+ * Detects sensitive keys and placeholder values in configuration.
5
+ * Used during config import/paste/set to auto-detect secrets.
6
+ */
7
+ /**
8
+ * Patterns that indicate a key is for sensitive data
9
+ * Case-insensitive matching
10
+ */
11
+ const SECRET_KEY_PATTERNS = [
12
+ /api[-_]?key/i,
13
+ /apikey/i,
14
+ /api[-_]?token/i,
15
+ /access[-_]?key/i,
16
+ /access[-_]?token/i,
17
+ /secret[-_]?key/i,
18
+ /secret/i,
19
+ /password/i,
20
+ /passwd/i,
21
+ /bearer/i,
22
+ /authorization/i,
23
+ /auth[-_]?token/i,
24
+ /private[-_]?key/i,
25
+ /credentials?/i,
26
+ /client[-_]?secret/i,
27
+ ];
28
+ /**
29
+ * Placeholder patterns that indicate a value should be replaced
30
+ * Case-insensitive matching
31
+ */
32
+ const PLACEHOLDER_PATTERNS = [
33
+ /^your[-_\s]?api[-_\s]?key$/i,
34
+ /^your[-_\s]?token$/i,
35
+ /^your[-_\s]?secret$/i,
36
+ /^your[-_\s]?password$/i,
37
+ /^your[-_\s]?.*[-_\s]?here$/i,
38
+ /^<.*>$/, // <YOUR_API_KEY>
39
+ /^\[.*\]$/, // [YOUR_API_KEY]
40
+ /^{.*}$/, // {YOUR_API_KEY}
41
+ /^changeme$/i,
42
+ /^change[-_\s]?me$/i,
43
+ /^xxx+$/i,
44
+ /^placeholder$/i,
45
+ /^replace[-_\s]?me$/i,
46
+ /^todo$/i,
47
+ /^fixme$/i,
48
+ /^insert[-_\s]?here$/i,
49
+ /^sk[-_]xxx/i, // sk-xxxxxxxx
50
+ /^pk[-_]xxx/i, // pk-xxxxxxxx
51
+ ];
52
+ /**
53
+ * Values that look like real secrets (for validation)
54
+ * These patterns help identify actual API keys vs. placeholders
55
+ */
56
+ const REAL_SECRET_PATTERNS = [
57
+ /^sk-[a-zA-Z0-9]{20,}$/, // OpenAI API key
58
+ /^pk-[a-zA-Z0-9]{20,}$/, // Some provider public key
59
+ /^ghp_[a-zA-Z0-9]{36,}$/, // GitHub PAT
60
+ /^gho_[a-zA-Z0-9]{36,}$/, // GitHub OAuth token
61
+ /^github_pat_[a-zA-Z0-9_]{22,}$/, // GitHub fine-grained PAT
62
+ /^[A-Za-z0-9+/=]{20,}$/, // Generic base64-ish token
63
+ ];
64
+ /**
65
+ * Check if a key name indicates a secret/sensitive value
66
+ *
67
+ * @param key - The key name to check (e.g., "OPENAI_API_KEY")
68
+ * @returns true if the key likely holds a secret
69
+ */
70
+ export function isSecretKey(key) {
71
+ return SECRET_KEY_PATTERNS.some(pattern => pattern.test(key));
72
+ }
73
+ /**
74
+ * Check if a value is a placeholder that needs to be replaced
75
+ *
76
+ * @param value - The value to check
77
+ * @returns true if the value is a placeholder
78
+ */
79
+ export function isPlaceholder(value) {
80
+ if (!value || typeof value !== 'string') {
81
+ return false;
82
+ }
83
+ const trimmed = value.trim();
84
+ // Empty or very short values are not placeholders
85
+ if (trimmed.length < 3) {
86
+ return false;
87
+ }
88
+ return PLACEHOLDER_PATTERNS.some(pattern => pattern.test(trimmed));
89
+ }
90
+ /**
91
+ * Check if a value looks like a real secret (not a placeholder)
92
+ *
93
+ * @param value - The value to check
94
+ * @returns true if the value appears to be a real secret
95
+ */
96
+ export function looksLikeRealSecret(value) {
97
+ if (!value || typeof value !== 'string') {
98
+ return false;
99
+ }
100
+ const trimmed = value.trim();
101
+ // Check for known secret formats
102
+ if (REAL_SECRET_PATTERNS.some(pattern => pattern.test(trimmed))) {
103
+ return true;
104
+ }
105
+ // Heuristic: long alphanumeric strings are likely secrets
106
+ // Minimum 20 chars, mostly alphanumeric with some special chars
107
+ if (trimmed.length >= 20 && /^[a-zA-Z0-9_\-+=/.]{20,}$/.test(trimmed)) {
108
+ return true;
109
+ }
110
+ return false;
111
+ }
112
+ /** Minimum length for a value to be considered a storable secret */
113
+ const MIN_SECRET_LENGTH = 8;
114
+ /**
115
+ * Detect if a key-value pair is a secret and determine action
116
+ *
117
+ * @param key - The key name
118
+ * @param value - The value
119
+ * @returns Detection result with recommended action
120
+ */
121
+ export function detectSecret(key, value) {
122
+ const secretKey = isSecretKey(key);
123
+ const placeholder = isPlaceholder(value);
124
+ const looksReal = looksLikeRealSecret(value);
125
+ const isTooShort = !value || value.length < MIN_SECRET_LENGTH;
126
+ let action;
127
+ if (!secretKey) {
128
+ // Not a secret key - skip
129
+ action = 'skip';
130
+ }
131
+ else if (placeholder) {
132
+ // Secret key with placeholder value - warn user
133
+ action = 'warn';
134
+ }
135
+ else if (isTooShort) {
136
+ // Secret key with very short/empty value - likely not a real secret, warn
137
+ action = 'warn';
138
+ }
139
+ else if (looksReal) {
140
+ // Secret key with real-looking value - store securely
141
+ action = 'store';
142
+ }
143
+ else {
144
+ // Secret key with value that's long enough but doesn't match known patterns - store to be safe
145
+ action = 'store';
146
+ }
147
+ return {
148
+ key,
149
+ value,
150
+ isSecretKey: secretKey,
151
+ isPlaceholder: placeholder,
152
+ looksLikeSecret: looksReal,
153
+ action,
154
+ };
155
+ }
156
+ /**
157
+ * Scan an env object for secrets
158
+ *
159
+ * @param env - Environment variables object
160
+ * @returns Array of detection results for secret keys
161
+ */
162
+ export function scanEnvForSecrets(env) {
163
+ const results = [];
164
+ for (const [key, value] of Object.entries(env)) {
165
+ const result = detectSecret(key, value);
166
+ if (result.isSecretKey) {
167
+ results.push(result);
168
+ }
169
+ }
170
+ return results;
171
+ }
172
+ /**
173
+ * Count secrets in an env object
174
+ *
175
+ * @param env - Environment variables object
176
+ * @returns Object with counts by action type
177
+ */
178
+ export function countSecrets(env) {
179
+ const results = scanEnvForSecrets(env);
180
+ return {
181
+ toStore: results.filter(r => r.action === 'store').length,
182
+ warnings: results.filter(r => r.action === 'warn').length,
183
+ total: results.length,
184
+ };
185
+ }
186
+ //# sourceMappingURL=detection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detection.js","sourceRoot":"","sources":["../../src/secrets/detection.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Secrets module (Phase 3.5)
3
+ *
4
+ * Provides secure storage and handling of API keys and sensitive configuration.
5
+ */
6
+ export * from './types.js';
7
+ export { isSecretKey, isPlaceholder, looksLikeRealSecret, detectSecret, scanEnvForSecrets, countSecrets, type SecretDetectionResult, } from './detection.js';
8
+ export { redactDeep, redactValue, redactEnv, redactionSummary, isRedacted, REDACTED, REDACTED_REF, type RedactionResult, type RedactionOptions, } from './redaction.js';
9
+ export { SqliteSecretStore, getSecretStore, closeSecretStore, } from './store.js';
10
+ export { PlainProvider, DpapiProvider, getBestProvider, getProvider, } from './providers/index.js';
11
+ export { secretizeEnv, formatSecretizeOutput, isSecretizeAvailable, type SecretizeKeyResult, type SecretizeResult, type SecretizeOptions, type FormatSecretizeOptions, } from './secretize.js';
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/secrets/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,cAAc,YAAY,CAAC;AAG3B,OAAO,EACL,WAAW,EACX,aAAa,EACb,mBAAmB,EACnB,YAAY,EACZ,iBAAiB,EACjB,YAAY,EACZ,KAAK,qBAAqB,GAC3B,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EACL,UAAU,EACV,WAAW,EACX,SAAS,EACT,gBAAgB,EAChB,UAAU,EACV,QAAQ,EACR,YAAY,EACZ,KAAK,eAAe,EACpB,KAAK,gBAAgB,GACtB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,gBAAgB,GACjB,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,aAAa,EACb,aAAa,EACb,eAAe,EACf,WAAW,GACZ,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EACL,YAAY,EACZ,qBAAqB,EACrB,oBAAoB,EACpB,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,GAC5B,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Secrets module (Phase 3.5)
3
+ *
4
+ * Provides secure storage and handling of API keys and sensitive configuration.
5
+ */
6
+ // Types and interfaces
7
+ export * from './types.js';
8
+ // Detection utilities
9
+ export { isSecretKey, isPlaceholder, looksLikeRealSecret, detectSecret, scanEnvForSecrets, countSecrets, } from './detection.js';
10
+ // Redaction utilities
11
+ export { redactDeep, redactValue, redactEnv, redactionSummary, isRedacted, REDACTED, REDACTED_REF, } from './redaction.js';
12
+ // Store
13
+ export { SqliteSecretStore, getSecretStore, closeSecretStore, } from './store.js';
14
+ // Providers
15
+ export { PlainProvider, DpapiProvider, getBestProvider, getProvider, } from './providers/index.js';
16
+ // Secretize
17
+ export { secretizeEnv, formatSecretizeOutput, isSecretizeAvailable, } from './secretize.js';
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/secrets/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,uBAAuB;AACvB,cAAc,YAAY,CAAC;AAE3B,sBAAsB;AACtB,OAAO,EACL,WAAW,EACX,aAAa,EACb,mBAAmB,EACnB,YAAY,EACZ,iBAAiB,EACjB,YAAY,GAEb,MAAM,gBAAgB,CAAC;AAExB,sBAAsB;AACtB,OAAO,EACL,UAAU,EACV,WAAW,EACX,SAAS,EACT,gBAAgB,EAChB,UAAU,EACV,QAAQ,EACR,YAAY,GAGb,MAAM,gBAAgB,CAAC;AAExB,QAAQ;AACR,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,gBAAgB,GACjB,MAAM,YAAY,CAAC;AAEpB,YAAY;AACZ,OAAO,EACL,aAAa,EACb,aAAa,EACb,eAAe,EACf,WAAW,GACZ,MAAM,sBAAsB,CAAC;AAE9B,YAAY;AACZ,OAAO,EACL,YAAY,EACZ,qBAAqB,EACrB,oBAAoB,GAKrB,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Windows DPAPI encryption provider (Phase 3.5)
3
+ *
4
+ * Uses Windows Data Protection API with CurrentUser scope.
5
+ * Secrets are encrypted using the user's Windows credentials.
6
+ *
7
+ * Implementation uses PowerShell to call .NET's ProtectedData class.
8
+ */
9
+ import type { IEncryptionProvider, ProviderType } from '../types.js';
10
+ /**
11
+ * DPAPI provider - Windows Data Protection API
12
+ *
13
+ * Encrypts data using the current Windows user's credentials.
14
+ * Data can only be decrypted by the same user on the same machine.
15
+ */
16
+ export declare class DpapiProvider implements IEncryptionProvider {
17
+ readonly type: ProviderType;
18
+ isAvailable(): boolean;
19
+ encrypt(plaintext: string): Promise<string>;
20
+ decrypt(ciphertext: string): Promise<string>;
21
+ }
22
+ //# sourceMappingURL=dpapi.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dpapi.d.ts","sourceRoot":"","sources":["../../../src/secrets/providers/dpapi.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAsBrE;;;;;GAKG;AACH,qBAAa,aAAc,YAAW,mBAAmB;IACvD,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAW;IAEtC,WAAW,IAAI,OAAO;IAKhB,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IA6B3C,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CA+BnD"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Windows DPAPI encryption provider (Phase 3.5)
3
+ *
4
+ * Uses Windows Data Protection API with CurrentUser scope.
5
+ * Secrets are encrypted using the user's Windows credentials.
6
+ *
7
+ * Implementation uses PowerShell to call .NET's ProtectedData class.
8
+ */
9
+ import { execSync } from 'child_process';
10
+ /** Maximum ciphertext length (100KB base64 encoded) */
11
+ const MAX_CIPHERTEXT_LENGTH = 100000;
12
+ /** Valid base64 pattern (strict validation for command injection prevention) */
13
+ const BASE64_PATTERN = /^[A-Za-z0-9+/]*={0,2}$/;
14
+ /**
15
+ * Validate that a string is valid base64 format
16
+ * Security: Prevents command injection by ensuring only safe characters
17
+ */
18
+ function isValidBase64(value) {
19
+ if (!value || value.length === 0) {
20
+ return false;
21
+ }
22
+ if (value.length > MAX_CIPHERTEXT_LENGTH) {
23
+ return false;
24
+ }
25
+ return BASE64_PATTERN.test(value);
26
+ }
27
+ /**
28
+ * DPAPI provider - Windows Data Protection API
29
+ *
30
+ * Encrypts data using the current Windows user's credentials.
31
+ * Data can only be decrypted by the same user on the same machine.
32
+ */
33
+ export class DpapiProvider {
34
+ type = 'dpapi';
35
+ isAvailable() {
36
+ // Only available on Windows
37
+ return process.platform === 'win32';
38
+ }
39
+ async encrypt(plaintext) {
40
+ if (!this.isAvailable()) {
41
+ throw new Error('DPAPI is only available on Windows');
42
+ }
43
+ // Convert plaintext to base64 for safe PowerShell handling
44
+ const base64Input = Buffer.from(plaintext, 'utf-8').toString('base64');
45
+ // PowerShell script to encrypt using DPAPI
46
+ const script = `
47
+ Add-Type -AssemblyName System.Security
48
+ $bytes = [System.Convert]::FromBase64String("${base64Input}")
49
+ $encrypted = [System.Security.Cryptography.ProtectedData]::Protect($bytes, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser)
50
+ [System.Convert]::ToBase64String($encrypted)
51
+ `.trim().replace(/\n/g, '; ');
52
+ try {
53
+ const result = execSync(`powershell -NoProfile -NonInteractive -Command "${script}"`, {
54
+ encoding: 'utf-8',
55
+ windowsHide: true,
56
+ timeout: 10000,
57
+ });
58
+ return result.trim();
59
+ }
60
+ catch (err) {
61
+ const message = err instanceof Error ? err.message : String(err);
62
+ throw new Error(`DPAPI encryption failed: ${message}`);
63
+ }
64
+ }
65
+ async decrypt(ciphertext) {
66
+ if (!this.isAvailable()) {
67
+ throw new Error('DPAPI is only available on Windows');
68
+ }
69
+ // Security: Validate ciphertext format to prevent command injection
70
+ if (!isValidBase64(ciphertext)) {
71
+ throw new Error('Invalid ciphertext format: must be valid base64');
72
+ }
73
+ // PowerShell script to decrypt using DPAPI
74
+ const script = `
75
+ Add-Type -AssemblyName System.Security
76
+ $encrypted = [System.Convert]::FromBase64String("${ciphertext}")
77
+ $bytes = [System.Security.Cryptography.ProtectedData]::Unprotect($encrypted, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser)
78
+ [System.Convert]::ToBase64String($bytes)
79
+ `.trim().replace(/\n/g, '; ');
80
+ try {
81
+ const result = execSync(`powershell -NoProfile -NonInteractive -Command "${script}"`, {
82
+ encoding: 'utf-8',
83
+ windowsHide: true,
84
+ timeout: 10000,
85
+ });
86
+ // Result is base64 encoded plaintext
87
+ return Buffer.from(result.trim(), 'base64').toString('utf-8');
88
+ }
89
+ catch (err) {
90
+ const message = err instanceof Error ? err.message : String(err);
91
+ throw new Error(`DPAPI decryption failed: ${message}`);
92
+ }
93
+ }
94
+ }
95
+ //# sourceMappingURL=dpapi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dpapi.js","sourceRoot":"","sources":["../../../src/secrets/providers/dpapi.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGzC,uDAAuD;AACvD,MAAM,qBAAqB,GAAG,MAAM,CAAC;AAErC,gFAAgF;AAChF,MAAM,cAAc,GAAG,wBAAwB,CAAC;AAEhD;;;GAGG;AACH,SAAS,aAAa,CAAC,KAAa;IAClC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjC,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,GAAG,qBAAqB,EAAE,CAAC;QACzC,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACpC,CAAC;AAED;;;;;GAKG;AACH,MAAM,OAAO,aAAa;IACf,IAAI,GAAiB,OAAO,CAAC;IAEtC,WAAW;QACT,4BAA4B;QAC5B,OAAO,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC;IACtC,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,SAAiB;QAC7B,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;QACxD,CAAC;QAED,2DAA2D;QAC3D,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAEvE,2CAA2C;QAC3C,MAAM,MAAM,GAAG;;qDAEkC,WAAW;;;KAG3D,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAE9B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,QAAQ,CAAC,mDAAmD,MAAM,GAAG,EAAE;gBACpF,QAAQ,EAAE,OAAO;gBACjB,WAAW,EAAE,IAAI;gBACjB,OAAO,EAAE,KAAK;aACf,CAAC,CAAC;YACH,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;QACvB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,MAAM,IAAI,KAAK,CAAC,4BAA4B,OAAO,EAAE,CAAC,CAAC;QACzD,CAAC;IACH,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,UAAkB;QAC9B,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;QACxD,CAAC;QAED,oEAAoE;QACpE,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,EAAE,CAAC;YAC/B,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;QACrE,CAAC;QAED,2CAA2C;QAC3C,MAAM,MAAM,GAAG;;yDAEsC,UAAU;;;KAG9D,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAE9B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,QAAQ,CAAC,mDAAmD,MAAM,GAAG,EAAE;gBACpF,QAAQ,EAAE,OAAO;gBACjB,WAAW,EAAE,IAAI;gBACjB,OAAO,EAAE,KAAK;aACf,CAAC,CAAC;YACH,qCAAqC;YACrC,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAChE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,MAAM,IAAI,KAAK,CAAC,4BAA4B,OAAO,EAAE,CAAC,CAAC;QACzD,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Encryption providers index
3
+ */
4
+ export { PlainProvider } from './plain.js';
5
+ export { DpapiProvider } from './dpapi.js';
6
+ import type { IEncryptionProvider, ProviderType } from '../types.js';
7
+ /**
8
+ * Get the best available encryption provider for the current platform
9
+ *
10
+ * Priority:
11
+ * 1. DPAPI on Windows
12
+ * 2. Keychain on macOS (future)
13
+ * 3. Plain fallback (no encryption, with warning)
14
+ */
15
+ export declare function getBestProvider(): IEncryptionProvider;
16
+ /**
17
+ * Get a specific provider by type
18
+ */
19
+ export declare function getProvider(type: ProviderType): IEncryptionProvider;
20
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/secrets/providers/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,OAAO,KAAK,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAIrE;;;;;;;GAOG;AACH,wBAAgB,eAAe,IAAI,mBAAmB,CAgBrD;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,YAAY,GAAG,mBAAmB,CAYnE"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Encryption providers index
3
+ */
4
+ export { PlainProvider } from './plain.js';
5
+ export { DpapiProvider } from './dpapi.js';
6
+ import { PlainProvider } from './plain.js';
7
+ import { DpapiProvider } from './dpapi.js';
8
+ /**
9
+ * Get the best available encryption provider for the current platform
10
+ *
11
+ * Priority:
12
+ * 1. DPAPI on Windows
13
+ * 2. Keychain on macOS (future)
14
+ * 3. Plain fallback (no encryption, with warning)
15
+ */
16
+ export function getBestProvider() {
17
+ // Try DPAPI first (Windows)
18
+ const dpapi = new DpapiProvider();
19
+ if (dpapi.isAvailable()) {
20
+ return dpapi;
21
+ }
22
+ // TODO: Add macOS Keychain support
23
+ // const keychain = new KeychainProvider();
24
+ // if (keychain.isAvailable()) {
25
+ // return keychain;
26
+ // }
27
+ // Fallback to plain (no encryption)
28
+ console.warn('Warning: No secure encryption provider available. Secrets will be stored without encryption.');
29
+ return new PlainProvider();
30
+ }
31
+ /**
32
+ * Get a specific provider by type
33
+ */
34
+ export function getProvider(type) {
35
+ switch (type) {
36
+ case 'dpapi':
37
+ return new DpapiProvider();
38
+ case 'plain':
39
+ return new PlainProvider();
40
+ case 'keychain':
41
+ // Not yet implemented
42
+ throw new Error('Keychain provider not yet implemented');
43
+ default:
44
+ throw new Error(`Unknown provider type: ${type}`);
45
+ }
46
+ }
47
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/secrets/providers/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAG3C,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe;IAC7B,4BAA4B;IAC5B,MAAM,KAAK,GAAG,IAAI,aAAa,EAAE,CAAC;IAClC,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;QACxB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,mCAAmC;IACnC,2CAA2C;IAC3C,gCAAgC;IAChC,qBAAqB;IACrB,IAAI;IAEJ,oCAAoC;IACpC,OAAO,CAAC,IAAI,CAAC,8FAA8F,CAAC,CAAC;IAC7G,OAAO,IAAI,aAAa,EAAE,CAAC;AAC7B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,IAAkB;IAC5C,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,OAAO;YACV,OAAO,IAAI,aAAa,EAAE,CAAC;QAC7B,KAAK,OAAO;YACV,OAAO,IAAI,aAAa,EAAE,CAAC;QAC7B,KAAK,UAAU;YACb,sBAAsB;YACtB,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;QAC3D;YACE,MAAM,IAAI,KAAK,CAAC,0BAA0B,IAAI,EAAE,CAAC,CAAC;IACtD,CAAC;AACH,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Plain encryption provider (no encryption)
3
+ *
4
+ * This is a fallback provider for testing and platforms without
5
+ * native encryption support. It provides NO security and should
6
+ * only be used for development/testing purposes.
7
+ */
8
+ import type { IEncryptionProvider, ProviderType } from '../types.js';
9
+ /**
10
+ * Plain provider - stores secrets as base64 without encryption
11
+ *
12
+ * WARNING: This provider offers NO security. Secrets are only
13
+ * base64 encoded, not encrypted. Use only for testing.
14
+ */
15
+ export declare class PlainProvider implements IEncryptionProvider {
16
+ readonly type: ProviderType;
17
+ isAvailable(): boolean;
18
+ encrypt(plaintext: string): Promise<string>;
19
+ decrypt(ciphertext: string): Promise<string>;
20
+ }
21
+ //# sourceMappingURL=plain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plain.d.ts","sourceRoot":"","sources":["../../../src/secrets/providers/plain.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAErE;;;;;GAKG;AACH,qBAAa,aAAc,YAAW,mBAAmB;IACvD,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAW;IAEtC,WAAW,IAAI,OAAO;IAKhB,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAK3C,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAInD"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Plain encryption provider (no encryption)
3
+ *
4
+ * This is a fallback provider for testing and platforms without
5
+ * native encryption support. It provides NO security and should
6
+ * only be used for development/testing purposes.
7
+ */
8
+ /**
9
+ * Plain provider - stores secrets as base64 without encryption
10
+ *
11
+ * WARNING: This provider offers NO security. Secrets are only
12
+ * base64 encoded, not encrypted. Use only for testing.
13
+ */
14
+ export class PlainProvider {
15
+ type = 'plain';
16
+ isAvailable() {
17
+ // Always available as fallback
18
+ return true;
19
+ }
20
+ async encrypt(plaintext) {
21
+ // Just base64 encode - NO ENCRYPTION
22
+ return Buffer.from(plaintext, 'utf-8').toString('base64');
23
+ }
24
+ async decrypt(ciphertext) {
25
+ // Just base64 decode
26
+ return Buffer.from(ciphertext, 'base64').toString('utf-8');
27
+ }
28
+ }
29
+ //# sourceMappingURL=plain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plain.js","sourceRoot":"","sources":["../../../src/secrets/providers/plain.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH;;;;;GAKG;AACH,MAAM,OAAO,aAAa;IACf,IAAI,GAAiB,OAAO,CAAC;IAEtC,WAAW;QACT,+BAA+B;QAC/B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,SAAiB;QAC7B,qCAAqC;QACrC,OAAO,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC5D,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,UAAkB;QAC9B,qBAAqB;QACrB,OAAO,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC7D,CAAC;CACF"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Secret redaction utilities (Phase 3.5)
3
+ *
4
+ * Provides deep redaction of secrets in config output.
5
+ * Used by config show, export, and snapshot commands.
6
+ */
7
+ /** Redacted placeholder for secrets */
8
+ export declare const REDACTED = "***REDACTED***";
9
+ /** Redacted placeholder for secret references */
10
+ export declare const REDACTED_REF = "***SECRET_REF***";
11
+ /**
12
+ * Result of redaction
13
+ */
14
+ export interface RedactionResult {
15
+ /** Redacted value */
16
+ value: unknown;
17
+ /** Number of values redacted */
18
+ count: number;
19
+ }
20
+ /**
21
+ * Options for redaction
22
+ */
23
+ export interface RedactionOptions {
24
+ /** Redact values for secret keys (default: true) */
25
+ redactSecretKeys?: boolean;
26
+ /** Redact secret references like "dpapi:xxx" (default: true) */
27
+ redactSecretRefs?: boolean;
28
+ /** Custom redaction string for values */
29
+ redactedValue?: string;
30
+ /** Custom redaction string for references */
31
+ redactedRef?: string;
32
+ }
33
+ /**
34
+ * Recursively redact secrets in a value
35
+ *
36
+ * Handles:
37
+ * - Secret references (dpapi:xxx, keychain:xxx)
38
+ * - Values for keys that match secret patterns
39
+ *
40
+ * @param value - Any JSON-serializable value
41
+ * @param options - Redaction options
42
+ * @returns Redacted value and count
43
+ */
44
+ export declare function redactDeep(value: unknown, options?: RedactionOptions): RedactionResult;
45
+ /**
46
+ * Redact a single value based on key and options
47
+ *
48
+ * @param key - The key name
49
+ * @param value - The value to potentially redact
50
+ * @param options - Redaction options
51
+ * @returns Redacted value if applicable, original otherwise
52
+ */
53
+ export declare function redactValue(key: string, value: string, options?: RedactionOptions): string;
54
+ /**
55
+ * Redact env variables object
56
+ *
57
+ * @param env - Environment variables
58
+ * @param options - Redaction options
59
+ * @returns Redacted env and count
60
+ */
61
+ export declare function redactEnv(env: Record<string, string>, options?: RedactionOptions): {
62
+ env: Record<string, string>;
63
+ count: number;
64
+ };
65
+ /**
66
+ * Create a summary of redacted values
67
+ *
68
+ * @param count - Number of values redacted
69
+ * @returns Human-readable summary
70
+ */
71
+ export declare function redactionSummary(count: number): string;
72
+ /**
73
+ * Check if a value has been redacted
74
+ */
75
+ export declare function isRedacted(value: string): boolean;
76
+ //# sourceMappingURL=redaction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redaction.d.ts","sourceRoot":"","sources":["../../src/secrets/redaction.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,uCAAuC;AACvC,eAAO,MAAM,QAAQ,mBAAmB,CAAC;AAEzC,iDAAiD;AACjD,eAAO,MAAM,YAAY,qBAAqB,CAAC;AAE/C;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,qBAAqB;IACrB,KAAK,EAAE,OAAO,CAAC;IACf,gCAAgC;IAChC,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,oDAAoD;IACpD,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,gEAAgE;IAChE,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,yCAAyC;IACzC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,6CAA6C;IAC7C,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AASD;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,gBAAqB,GAC7B,eAAe,CAkDjB;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,gBAAqB,GAC7B,MAAM,CAgBR;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CACvB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC3B,OAAO,GAAE,gBAAqB,GAC7B;IAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAchD;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQtD;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEjD"}