guardrail-security 1.0.2 → 2.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.
- package/dist/sbom/generator.d.ts +42 -0
- package/dist/sbom/generator.d.ts.map +1 -1
- package/dist/sbom/generator.js +168 -7
- package/dist/secrets/allowlist.d.ts +38 -0
- package/dist/secrets/allowlist.d.ts.map +1 -0
- package/dist/secrets/allowlist.js +131 -0
- package/dist/secrets/config-loader.d.ts +25 -0
- package/dist/secrets/config-loader.d.ts.map +1 -0
- package/dist/secrets/config-loader.js +103 -0
- package/dist/secrets/contextual-risk.d.ts +19 -0
- package/dist/secrets/contextual-risk.d.ts.map +1 -0
- package/dist/secrets/contextual-risk.js +88 -0
- package/dist/secrets/git-scanner.d.ts +29 -0
- package/dist/secrets/git-scanner.d.ts.map +1 -0
- package/dist/secrets/git-scanner.js +109 -0
- package/dist/secrets/guardian.d.ts +70 -57
- package/dist/secrets/guardian.d.ts.map +1 -1
- package/dist/secrets/guardian.js +531 -258
- package/dist/secrets/index.d.ts +4 -0
- package/dist/secrets/index.d.ts.map +1 -1
- package/dist/secrets/index.js +11 -1
- package/dist/secrets/patterns.d.ts +39 -10
- package/dist/secrets/patterns.d.ts.map +1 -1
- package/dist/secrets/patterns.js +129 -71
- package/dist/secrets/pre-commit.d.ts.map +1 -1
- package/dist/secrets/pre-commit.js +1 -1
- package/dist/secrets/vault-integration.d.ts.map +1 -1
- package/dist/secrets/vault-integration.js +1 -0
- package/dist/supply-chain/vulnerability-db.d.ts +89 -16
- package/dist/supply-chain/vulnerability-db.d.ts.map +1 -1
- package/dist/supply-chain/vulnerability-db.js +404 -115
- package/dist/utils/semver.d.ts +37 -0
- package/dist/utils/semver.d.ts.map +1 -0
- package/dist/utils/semver.js +109 -0
- package/package.json +17 -3
- package/src/__tests__/license/engine.test.ts +0 -250
- package/src/__tests__/supply-chain/typosquat.test.ts +0 -191
- package/src/attack-surface/analyzer.ts +0 -153
- package/src/attack-surface/index.ts +0 -5
- package/src/index.ts +0 -21
- package/src/languages/index.ts +0 -91
- package/src/languages/java-analyzer.ts +0 -490
- package/src/languages/python-analyzer.ts +0 -498
- package/src/license/compatibility-matrix.ts +0 -366
- package/src/license/engine.ts +0 -346
- package/src/license/index.ts +0 -6
- package/src/sbom/generator.ts +0 -355
- package/src/sbom/index.ts +0 -5
- package/src/secrets/guardian.ts +0 -468
- package/src/secrets/index.ts +0 -10
- package/src/secrets/patterns.ts +0 -186
- package/src/secrets/pre-commit.ts +0 -158
- package/src/secrets/vault-integration.ts +0 -360
- package/src/secrets/vault-providers.ts +0 -446
- package/src/supply-chain/detector.ts +0 -253
- package/src/supply-chain/index.ts +0 -11
- package/src/supply-chain/malicious-db.ts +0 -103
- package/src/supply-chain/script-analyzer.ts +0 -194
- package/src/supply-chain/typosquat.ts +0 -302
- package/src/supply-chain/vulnerability-db.ts +0 -386
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import { secretsGuardian } from './guardian';
|
|
2
|
-
import { execSync } from 'child_process';
|
|
3
|
-
import { writeFileSync } from 'fs';
|
|
4
|
-
import { join } from 'path';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Staged file info
|
|
8
|
-
*/
|
|
9
|
-
export interface StagedFile {
|
|
10
|
-
path: string;
|
|
11
|
-
content: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Pre-commit scan result
|
|
16
|
-
*/
|
|
17
|
-
export interface PreCommitScanResult {
|
|
18
|
-
passed: boolean;
|
|
19
|
-
detections: number;
|
|
20
|
-
files: string[];
|
|
21
|
-
message: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Pre-Commit Hook for Secrets Detection
|
|
26
|
-
*/
|
|
27
|
-
export class PreCommitHook {
|
|
28
|
-
/**
|
|
29
|
-
* Generate pre-commit hook script
|
|
30
|
-
*/
|
|
31
|
-
generateHookScript(): string {
|
|
32
|
-
return `#!/bin/bash
|
|
33
|
-
# Guardrail Secrets Detection Pre-Commit Hook
|
|
34
|
-
|
|
35
|
-
echo "🔍 Scanning for secrets..."
|
|
36
|
-
|
|
37
|
-
# Run secrets scan via API or CLI
|
|
38
|
-
npx Guardrail secrets scan-staged
|
|
39
|
-
|
|
40
|
-
if [ $? -ne 0 ]; then
|
|
41
|
-
echo "❌ Secrets detected! Commit blocked."
|
|
42
|
-
echo "Please remove secrets or add to .gitignore"
|
|
43
|
-
exit 1
|
|
44
|
-
fi
|
|
45
|
-
|
|
46
|
-
echo "✅ No secrets detected"
|
|
47
|
-
exit 0
|
|
48
|
-
`;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Scan staged files
|
|
53
|
-
*/
|
|
54
|
-
async scanStagedFiles(gitRoot: string): Promise<PreCommitScanResult> {
|
|
55
|
-
try {
|
|
56
|
-
// Get staged files
|
|
57
|
-
const stagedFiles = this.getStagedFiles(gitRoot);
|
|
58
|
-
|
|
59
|
-
if (stagedFiles.length === 0) {
|
|
60
|
-
return {
|
|
61
|
-
passed: true,
|
|
62
|
-
detections: 0,
|
|
63
|
-
files: [],
|
|
64
|
-
message: 'No files to scan',
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Scan each staged file
|
|
69
|
-
const allDetections: string[] = [];
|
|
70
|
-
|
|
71
|
-
for (const file of stagedFiles) {
|
|
72
|
-
const detections = await secretsGuardian.scanContent(
|
|
73
|
-
file.content,
|
|
74
|
-
file.path,
|
|
75
|
-
{
|
|
76
|
-
excludeTests: true,
|
|
77
|
-
minConfidence: 0.7,
|
|
78
|
-
}
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
if (detections.length > 0) {
|
|
82
|
-
allDetections.push(file.path);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const passed = allDetections.length === 0;
|
|
87
|
-
|
|
88
|
-
return {
|
|
89
|
-
passed,
|
|
90
|
-
detections: allDetections.length,
|
|
91
|
-
files: allDetections,
|
|
92
|
-
message: passed
|
|
93
|
-
? 'No secrets detected in staged files'
|
|
94
|
-
: `Secrets detected in ${allDetections.length} file(s)`,
|
|
95
|
-
};
|
|
96
|
-
} catch (error) {
|
|
97
|
-
throw new Error(`Pre-commit scan failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Get staged files from git
|
|
103
|
-
*/
|
|
104
|
-
private getStagedFiles(gitRoot: string): StagedFile[] {
|
|
105
|
-
try {
|
|
106
|
-
// Get list of staged files
|
|
107
|
-
const output = execSync('git diff --cached --name-only --diff-filter=ACM', {
|
|
108
|
-
cwd: gitRoot,
|
|
109
|
-
encoding: 'utf-8',
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
const filePaths = output.trim().split('\n').filter((f) => f);
|
|
113
|
-
|
|
114
|
-
// Read content of each staged file
|
|
115
|
-
const stagedFiles: StagedFile[] = [];
|
|
116
|
-
|
|
117
|
-
for (const filePath of filePaths) {
|
|
118
|
-
try {
|
|
119
|
-
// Get staged content (not working directory)
|
|
120
|
-
const content = execSync(`git show :${filePath}`, {
|
|
121
|
-
cwd: gitRoot,
|
|
122
|
-
encoding: 'utf-8',
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
stagedFiles.push({
|
|
126
|
-
path: filePath,
|
|
127
|
-
content,
|
|
128
|
-
});
|
|
129
|
-
} catch (error) {
|
|
130
|
-
// File might be deleted or binary, skip
|
|
131
|
-
continue;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return stagedFiles;
|
|
136
|
-
} catch (error) {
|
|
137
|
-
throw new Error(`Failed to get staged files: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Install pre-commit hook
|
|
143
|
-
*/
|
|
144
|
-
installHook(gitRoot: string): void {
|
|
145
|
-
const hookPath = join(gitRoot, '.git', 'hooks', 'pre-commit');
|
|
146
|
-
const hookScript = this.generateHookScript();
|
|
147
|
-
|
|
148
|
-
try {
|
|
149
|
-
writeFileSync(hookPath, hookScript, { mode: 0o755 });
|
|
150
|
-
console.log('✅ Pre-commit hook installed successfully');
|
|
151
|
-
} catch (error) {
|
|
152
|
-
throw new Error(`Failed to install hook: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Export singleton
|
|
158
|
-
export const preCommitHook = new PreCommitHook();
|
|
@@ -1,360 +0,0 @@
|
|
|
1
|
-
import { SecretDetection } from './guardian';
|
|
2
|
-
import { SecretType } from './patterns';
|
|
3
|
-
import { createVaultProvider, VaultProvider, VaultProviderConfig, LocalEnvProvider } from './vault-providers';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Vault configuration
|
|
7
|
-
*/
|
|
8
|
-
export interface VaultConfig {
|
|
9
|
-
type: 'aws_secrets_manager' | 'hashicorp_vault' | 'azure_keyvault' | 'gcp_secret_manager';
|
|
10
|
-
endpoint?: string;
|
|
11
|
-
region?: string;
|
|
12
|
-
credentials?: {
|
|
13
|
-
accessKeyId?: string;
|
|
14
|
-
secretAccessKey?: string;
|
|
15
|
-
token?: string;
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Migration result
|
|
21
|
-
*/
|
|
22
|
-
export interface VaultMigrationResult {
|
|
23
|
-
secretId: string;
|
|
24
|
-
vaultId: string;
|
|
25
|
-
envVarName: string;
|
|
26
|
-
migrated: boolean;
|
|
27
|
-
error?: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Vault Integration for Secrets
|
|
32
|
-
*
|
|
33
|
-
* Helps migrate hardcoded secrets to secure vaults.
|
|
34
|
-
* Supports AWS Secrets Manager, HashiCorp Vault, Azure Key Vault, and GCP Secret Manager.
|
|
35
|
-
*/
|
|
36
|
-
export class VaultIntegration {
|
|
37
|
-
private providerCache: Map<string, VaultProvider> = new Map();
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Get or create a vault provider
|
|
41
|
-
*/
|
|
42
|
-
private getProvider(vaultConfig: VaultConfig): VaultProvider {
|
|
43
|
-
const cacheKey = `${vaultConfig.type}_${vaultConfig.endpoint || 'default'}`;
|
|
44
|
-
|
|
45
|
-
if (!this.providerCache.has(cacheKey)) {
|
|
46
|
-
const providerConfig: VaultProviderConfig = {
|
|
47
|
-
type: vaultConfig.type,
|
|
48
|
-
region: vaultConfig.region,
|
|
49
|
-
endpoint: vaultConfig.endpoint,
|
|
50
|
-
credentials: vaultConfig.credentials,
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
try {
|
|
54
|
-
const provider = createVaultProvider(providerConfig);
|
|
55
|
-
this.providerCache.set(cacheKey, provider);
|
|
56
|
-
} catch (error) {
|
|
57
|
-
console.warn(`Failed to create vault provider, falling back to local: ${error}`);
|
|
58
|
-
this.providerCache.set(cacheKey, new LocalEnvProvider());
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return this.providerCache.get(cacheKey)!;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Test vault connection
|
|
67
|
-
*/
|
|
68
|
-
async testConnection(vaultConfig: VaultConfig): Promise<{ connected: boolean; error?: string }> {
|
|
69
|
-
try {
|
|
70
|
-
const provider = this.getProvider(vaultConfig);
|
|
71
|
-
const connected = await provider.testConnection();
|
|
72
|
-
return { connected };
|
|
73
|
-
} catch (error) {
|
|
74
|
-
return {
|
|
75
|
-
connected: false,
|
|
76
|
-
error: error instanceof Error ? error.message : 'Unknown error'
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Migrate secrets to vault
|
|
82
|
-
* Now uses real vault providers instead of simulation
|
|
83
|
-
*/
|
|
84
|
-
async migrateToVault(
|
|
85
|
-
detections: SecretDetection[],
|
|
86
|
-
vaultConfig: VaultConfig
|
|
87
|
-
): Promise<VaultMigrationResult[]> {
|
|
88
|
-
const results: VaultMigrationResult[] = [];
|
|
89
|
-
const provider = this.getProvider(vaultConfig);
|
|
90
|
-
|
|
91
|
-
// Test connection first
|
|
92
|
-
const connectionTest = await this.testConnection(vaultConfig);
|
|
93
|
-
if (!connectionTest.connected) {
|
|
94
|
-
return detections.map(detection => ({
|
|
95
|
-
secretId: detection.id || '',
|
|
96
|
-
vaultId: '',
|
|
97
|
-
envVarName: this.generateEnvVarName(detection),
|
|
98
|
-
migrated: false,
|
|
99
|
-
error: `Vault connection failed: ${connectionTest.error}`,
|
|
100
|
-
}));
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
for (const detection of detections) {
|
|
104
|
-
try {
|
|
105
|
-
const envVarName = this.generateEnvVarName(detection);
|
|
106
|
-
|
|
107
|
-
// Extract the actual secret value from detection
|
|
108
|
-
// Note: For security, the actual value should be extracted during scan
|
|
109
|
-
// maskedValue contains the masked version, snippet contains context
|
|
110
|
-
const secretValue = (detection as any).rawValue || detection.location.snippet || '';
|
|
111
|
-
|
|
112
|
-
if (!secretValue) {
|
|
113
|
-
results.push({
|
|
114
|
-
secretId: detection.id || '',
|
|
115
|
-
vaultId: '',
|
|
116
|
-
envVarName,
|
|
117
|
-
migrated: false,
|
|
118
|
-
error: 'No secret value found in detection',
|
|
119
|
-
});
|
|
120
|
-
continue;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Upload to real vault
|
|
124
|
-
const vaultId = await provider.createSecret(envVarName, secretValue);
|
|
125
|
-
|
|
126
|
-
results.push({
|
|
127
|
-
secretId: detection.id || '',
|
|
128
|
-
vaultId,
|
|
129
|
-
envVarName,
|
|
130
|
-
migrated: true,
|
|
131
|
-
});
|
|
132
|
-
} catch (error) {
|
|
133
|
-
results.push({
|
|
134
|
-
secretId: detection.id || '',
|
|
135
|
-
vaultId: '',
|
|
136
|
-
envVarName: this.generateEnvVarName(detection),
|
|
137
|
-
migrated: false,
|
|
138
|
-
error: error instanceof Error ? error.message : 'Unknown error',
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return results;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Retrieve a secret from vault
|
|
148
|
-
*/
|
|
149
|
-
async getSecret(vaultConfig: VaultConfig, secretName: string): Promise<string | null> {
|
|
150
|
-
const provider = this.getProvider(vaultConfig);
|
|
151
|
-
return provider.getSecret(secretName);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* List all secrets in vault
|
|
156
|
-
*/
|
|
157
|
-
async listSecrets(vaultConfig: VaultConfig): Promise<string[]> {
|
|
158
|
-
const provider = this.getProvider(vaultConfig);
|
|
159
|
-
return provider.listSecrets();
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Delete a secret from vault
|
|
164
|
-
*/
|
|
165
|
-
async deleteSecret(vaultConfig: VaultConfig, secretName: string): Promise<boolean> {
|
|
166
|
-
const provider = this.getProvider(vaultConfig);
|
|
167
|
-
return provider.deleteSecret(secretName);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Generate environment variable name
|
|
172
|
-
*/
|
|
173
|
-
generateEnvVarName(detection: SecretDetection): string {
|
|
174
|
-
const typeMap: Record<SecretType, string> = {
|
|
175
|
-
[SecretType.AWS_ACCESS_KEY]: 'AWS_ACCESS_KEY_ID',
|
|
176
|
-
[SecretType.AWS_SECRET_KEY]: 'AWS_SECRET_ACCESS_KEY',
|
|
177
|
-
[SecretType.GITHUB_TOKEN]: 'GITHUB_TOKEN',
|
|
178
|
-
[SecretType.GOOGLE_API_KEY]: 'GOOGLE_API_KEY',
|
|
179
|
-
[SecretType.STRIPE_KEY]: 'STRIPE_SECRET_KEY',
|
|
180
|
-
[SecretType.JWT_TOKEN]: 'JWT_SECRET',
|
|
181
|
-
[SecretType.PRIVATE_KEY]: 'PRIVATE_KEY',
|
|
182
|
-
[SecretType.DATABASE_URL]: 'DATABASE_URL',
|
|
183
|
-
[SecretType.SLACK_TOKEN]: 'SLACK_TOKEN',
|
|
184
|
-
[SecretType.API_KEY_GENERIC]: 'API_KEY',
|
|
185
|
-
[SecretType.API_KEY]: 'API_KEY',
|
|
186
|
-
[SecretType.TOKEN]: 'TOKEN',
|
|
187
|
-
[SecretType.CERTIFICATE]: 'CERTIFICATE',
|
|
188
|
-
[SecretType.JWT_SECRET]: 'JWT_SECRET',
|
|
189
|
-
[SecretType.PASSWORD]: 'PASSWORD',
|
|
190
|
-
[SecretType.OTHER]: 'SECRET'
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
const baseName = typeMap[detection.secretType as SecretType] || 'SECRET';
|
|
194
|
-
|
|
195
|
-
// Add file-based suffix if multiple of same type
|
|
196
|
-
const fileName = detection.filePath.split('/').pop()?.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase();
|
|
197
|
-
|
|
198
|
-
return `${baseName}_${fileName}`;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Generate code snippet for accessing secret from vault
|
|
203
|
-
*/
|
|
204
|
-
generateCodeSnippet(vaultConfig: VaultConfig, envVarName: string): string {
|
|
205
|
-
switch (vaultConfig.type) {
|
|
206
|
-
case 'aws_secrets_manager':
|
|
207
|
-
return `
|
|
208
|
-
// AWS Secrets Manager
|
|
209
|
-
import { SecretsManagerClient, GetSecretValueCommand } from "@aws-sdk/client-secrets-manager";
|
|
210
|
-
|
|
211
|
-
const client = new SecretsManagerClient({ region: "${vaultConfig.region || 'us-east-1'}" });
|
|
212
|
-
const response = await client.send(
|
|
213
|
-
new GetSecretValueCommand({ SecretId: "${envVarName}" })
|
|
214
|
-
);
|
|
215
|
-
const secret = response.SecretString;
|
|
216
|
-
`;
|
|
217
|
-
|
|
218
|
-
case 'hashicorp_vault':
|
|
219
|
-
return `
|
|
220
|
-
// HashiCorp Vault
|
|
221
|
-
import vault from "node-vault";
|
|
222
|
-
|
|
223
|
-
const vaultClient = vault({
|
|
224
|
-
endpoint: "${vaultConfig.endpoint || 'http://127.0.0.1:8200'}",
|
|
225
|
-
token: process.env.VAULT_TOKEN
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
const { data } = await vaultClient.read("secret/data/${envVarName}");
|
|
229
|
-
const secret = data.data.value;
|
|
230
|
-
`;
|
|
231
|
-
|
|
232
|
-
case 'azure_keyvault':
|
|
233
|
-
return `
|
|
234
|
-
// Azure Key Vault
|
|
235
|
-
import { SecretClient } from "@azure/keyvault-secrets";
|
|
236
|
-
import { DefaultAzureCredential } from "@azure/identity";
|
|
237
|
-
|
|
238
|
-
const credential = new DefaultAzureCredential();
|
|
239
|
-
const client = new SecretClient("${vaultConfig.endpoint}", credential);
|
|
240
|
-
const secret = await client.getSecret("${envVarName}");
|
|
241
|
-
const value = secret.value;
|
|
242
|
-
`;
|
|
243
|
-
|
|
244
|
-
case 'gcp_secret_manager':
|
|
245
|
-
return `
|
|
246
|
-
// GCP Secret Manager
|
|
247
|
-
import { SecretManagerServiceClient } from '@google-cloud/secret-manager';
|
|
248
|
-
|
|
249
|
-
const client = new SecretManagerServiceClient();
|
|
250
|
-
const [version] = await client.accessSecretVersion({
|
|
251
|
-
name: 'projects/PROJECT_ID/secrets/${envVarName}/versions/latest',
|
|
252
|
-
});
|
|
253
|
-
const secret = version.payload?.data?.toString();
|
|
254
|
-
`;
|
|
255
|
-
|
|
256
|
-
default:
|
|
257
|
-
return `// Use environment variable: process.env.${envVarName}`;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Generate migration guide
|
|
263
|
-
*/
|
|
264
|
-
generateMigrationGuide(results: VaultMigrationResult[]): string {
|
|
265
|
-
let guide = '# Secrets Migration Guide\n\n';
|
|
266
|
-
guide += '## Detected Secrets\n\n';
|
|
267
|
-
|
|
268
|
-
for (const result of results) {
|
|
269
|
-
guide += `### ${result.envVarName}\n`;
|
|
270
|
-
guide += `- Vault ID: ${result.vaultId}\n`;
|
|
271
|
-
guide += `- Status: ${result.migrated ? '✅ Migrated' : '❌ Failed'}\n`;
|
|
272
|
-
|
|
273
|
-
if (result.error) {
|
|
274
|
-
guide += `- Error: ${result.error}\n`;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
guide += '\n';
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
guide += '## Next Steps\n\n';
|
|
281
|
-
guide += '1. Update your code to fetch secrets from vault\n';
|
|
282
|
-
guide += '2. Remove hardcoded secrets from source code\n';
|
|
283
|
-
guide += '3. Update CI/CD pipelines to use vault credentials\n';
|
|
284
|
-
guide += '4. Test the integration\n';
|
|
285
|
-
guide += '5. Commit and push changes\n';
|
|
286
|
-
|
|
287
|
-
return guide;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* Verify a secret exists in vault
|
|
292
|
-
*/
|
|
293
|
-
async verifySecret(vaultConfig: VaultConfig, secretName: string): Promise<boolean> {
|
|
294
|
-
const secret = await this.getSecret(vaultConfig, secretName);
|
|
295
|
-
return secret !== null;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* Batch migrate with progress callback
|
|
300
|
-
*/
|
|
301
|
-
async migrateWithProgress(
|
|
302
|
-
detections: SecretDetection[],
|
|
303
|
-
vaultConfig: VaultConfig,
|
|
304
|
-
onProgress?: (completed: number, total: number, current: string) => void
|
|
305
|
-
): Promise<VaultMigrationResult[]> {
|
|
306
|
-
const results: VaultMigrationResult[] = [];
|
|
307
|
-
const provider = this.getProvider(vaultConfig);
|
|
308
|
-
const total = detections.length;
|
|
309
|
-
|
|
310
|
-
for (let i = 0; i < detections.length; i++) {
|
|
311
|
-
const detection = detections[i];
|
|
312
|
-
if (!detection) continue;
|
|
313
|
-
|
|
314
|
-
const envVarName = this.generateEnvVarName(detection);
|
|
315
|
-
|
|
316
|
-
if (onProgress) {
|
|
317
|
-
onProgress(i, total, envVarName);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
try {
|
|
321
|
-
const secretValue = (detection as any).rawValue || detection.location.snippet || '';
|
|
322
|
-
if (!secretValue) {
|
|
323
|
-
results.push({
|
|
324
|
-
secretId: detection.id || '',
|
|
325
|
-
vaultId: '',
|
|
326
|
-
envVarName,
|
|
327
|
-
migrated: false,
|
|
328
|
-
error: 'No secret value found',
|
|
329
|
-
});
|
|
330
|
-
continue;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
const vaultId = await provider.createSecret(envVarName, secretValue);
|
|
334
|
-
results.push({
|
|
335
|
-
secretId: detection.id || '',
|
|
336
|
-
vaultId,
|
|
337
|
-
envVarName,
|
|
338
|
-
migrated: true,
|
|
339
|
-
});
|
|
340
|
-
} catch (error) {
|
|
341
|
-
results.push({
|
|
342
|
-
secretId: detection.id || '',
|
|
343
|
-
vaultId: '',
|
|
344
|
-
envVarName,
|
|
345
|
-
migrated: false,
|
|
346
|
-
error: error instanceof Error ? error.message : 'Unknown error',
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
if (onProgress) {
|
|
352
|
-
onProgress(total, total, 'Complete');
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
return results;
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// Export singleton
|
|
360
|
-
export const vaultIntegration = new VaultIntegration();
|