Loading...
Loading...
Compare original and translation side by side
undefinedundefinedundefinedundefined
```bash
```bashundefinedundefinedundefinedundefined - name: Run Gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }} - name: TruffleHog OSS
uses: trufflesecurity/trufflehog@main
with:
path: ./
base: ${{ github.event.repository.default_branch }}
head: HEAD
extra_args: --debug --only-verifiedundefined - name: Run Gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }} - name: TruffleHog OSS
uses: trufflesecurity/trufflehog@main
with:
path: ./
base: ${{ github.event.repository.default_branch }}
head: HEAD
extra_args: --debug --only-verifiedundefined// scripts/scan-secrets.ts
import * as fs from "fs";
import * as path from "path";
interface SecretPattern {
name: string;
regex: RegExp;
severity: "critical" | "high" | "medium";
}
const SECRET_PATTERNS: SecretPattern[] = [
{
name: "AWS Access Key",
regex: /(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}/g,
severity: "critical",
},
{
name: "Private Key",
regex: /-----BEGIN (RSA|OPENSSH|DSA|EC|PGP) PRIVATE KEY-----/g,
severity: "critical",
},
{
name: "Generic API Key",
regex:
/['"]?[a-zA-Z0-9_-]*api[_-]?key['"]?\s*[:=]\s*['"][a-zA-Z0-9]{32,}['"]/gi,
severity: "high",
},
{
name: "Database URL",
regex: /(postgresql|mysql|mongodb):\/\/[^\s:]+:[^\s@]+@[^\s\/]+/gi,
severity: "critical",
},
{
name: "JWT Token",
regex: /eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]*/g,
severity: "high",
},
];
interface SecretFinding {
file: string;
line: number;
column: number;
pattern: string;
match: string;
severity: string;
}
function scanFile(filePath: string): SecretFinding[] {
const findings: SecretFinding[] = [];
const content = fs.readFileSync(filePath, "utf-8");
const lines = content.split("\n");
lines.forEach((line, lineIndex) => {
SECRET_PATTERNS.forEach((pattern) => {
const matches = line.matchAll(pattern.regex);
for (const match of matches) {
findings.push({
file: filePath,
line: lineIndex + 1,
column: match.index || 0,
pattern: pattern.name,
match: match[0].substring(0, 50) + "...",
severity: pattern.severity,
});
}
});
});
return findings;
}
function scanDirectory(dir: string): SecretFinding[] {
const findings: SecretFinding[] = [];
const files = fs.readdirSync(dir, { withFileTypes: true });
const ignorePaths = ["node_modules", ".git", "dist", "build"];
files.forEach((file) => {
const fullPath = path.join(dir, file.name);
if (file.isDirectory() && !ignorePaths.includes(file.name)) {
findings.push(...scanDirectory(fullPath));
} else if (file.isFile()) {
findings.push(...scanFile(fullPath));
}
});
return findings;
}
// Run scan
const findings = scanDirectory("./src");
if (findings.length > 0) {
console.error("🚨 Secrets detected!\n");
findings.forEach((f) => {
console.error(
`[${f.severity.toUpperCase()}] ${f.file}:${f.line}:${f.column}`
);
console.error(` Pattern: ${f.pattern}`);
console.error(` Match: ${f.match}\n`);
});
process.exit(1);
} else {
console.log("✅ No secrets detected");
}// scripts/scan-secrets.ts
import * as fs from "fs";
import * as path from "path";
interface SecretPattern {
name: string;
regex: RegExp;
severity: "critical" | "high" | "medium";
}
const SECRET_PATTERNS: SecretPattern[] = [
{
name: "AWS Access Key",
regex: /(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}/g,
severity: "critical",
},
{
name: "Private Key",
regex: /-----BEGIN (RSA|OPENSSH|DSA|EC|PGP) PRIVATE KEY-----/g,
severity: "critical",
},
{
name: "Generic API Key",
regex:
/['"]?[a-zA-Z0-9_-]*api[_-]?key['"]?\s*[:=]\s*['"][a-zA-Z0-9]{32,}['"]/gi,
severity: "high",
},
{
name: "Database URL",
regex: /(postgresql|mysql|mongodb):\/\/[^\s:]+:[^\s@]+@[^\s\/]+/gi,
severity: "critical",
},
{
name: "JWT Token",
regex: /eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]*/g,
severity: "high",
},
];
interface SecretFinding {
file: string;
line: number;
column: number;
pattern: string;
match: string;
severity: string;
}
function scanFile(filePath: string): SecretFinding[] {
const findings: SecretFinding[] = [];
const content = fs.readFileSync(filePath, "utf-8");
const lines = content.split("\n");
lines.forEach((line, lineIndex) => {
SECRET_PATTERNS.forEach((pattern) => {
const matches = line.matchAll(pattern.regex);
for (const match of matches) {
findings.push({
file: filePath,
line: lineIndex + 1,
column: match.index || 0,
pattern: pattern.name,
match: match[0].substring(0, 50) + "...",
severity: pattern.severity,
});
}
});
});
return findings;
}
function scanDirectory(dir: string): SecretFinding[] {
const findings: SecretFinding[] = [];
const files = fs.readdirSync(dir, { withFileTypes: true });
const ignorePaths = ["node_modules", ".git", "dist", "build"];
files.forEach((file) => {
const fullPath = path.join(dir, file.name);
if (file.isDirectory() && !ignorePaths.includes(file.name)) {
findings.push(...scanDirectory(fullPath));
} else if (file.isFile()) {
findings.push(...scanFile(fullPath));
}
});
return findings;
}
// Run scan
const findings = scanDirectory("./src");
if (findings.length > 0) {
console.error("🚨 Secrets detected!\n");
findings.forEach((f) => {
console.error(
`[${f.severity.toUpperCase()}] ${f.file}:${f.line}:${f.column}`
);
console.error(` Pattern: ${f.pattern}`);
console.error(` Match: ${f.match}\n`);
});
process.exit(1);
} else {
console.log("✅ No secrets detected");
}undefinedundefined# Using BFG Repo-Cleaner
bfg --replace-text secrets.txt repo.git
git reflog expire --expire=now --all
git gc --prune=now --aggressive
# Force push (requires team coordination)
git push --force --all
3. **Notify stakeholders**
- [ ] Security team
- [ ] DevOps team
- [ ] Service owners
- [ ] Management (if public repo)# Using BFG Repo-Cleaner
bfg --replace-text secrets.txt repo.git
git reflog expire --expire=now --all
git gc --prune=now --aggressive
# Force push (requires team coordination)
git push --force --all
3. **通知相关人员**
- [ ] 安全团队
- [ ] DevOps团队
- [ ] 服务负责人
- [ ] 管理层(若为公开仓库)undefinedundefined// ❌ BAD: Hardcoded secrets
const API_KEY = 'sk_live_abc123xyz789';
const db = connect('mongodb://admin:password@localhost');
// ✅ GOOD: Environment variables
const API_KEY = process.env.API_KEY;
const db = connect(process.env.DATABASE_URL);
// ✅ BETTER: Secret management service
import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';
async function getSecret(secretName: string): Promise<string> {
const client = new SecretsManagerClient({ region: 'us-east-1' });
const response = await client.send(
new GetSecretValueCommand({ SecretId: secretName })
);
return response.SecretString!;
}
const apiKey = await getSecret('prod/api/stripe-key');// ❌ BAD: Hardcoded secrets
const API_KEY = 'sk_live_abc123xyz789';
const db = connect('mongodb://admin:password@localhost');
// ✅ GOOD: Environment variables
const API_KEY = process.env.API_KEY;
const db = connect(process.env.DATABASE_URL);
// ✅ BETTER: Secret management service
import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';
async function getSecret(secretName: string): Promise<string> {
const client = new SecretsManagerClient({ region: 'us-east-1' });
const response = await client.send(
new GetSecretValueCommand({ SecretId: secretName })
);
return response.SecretString!;
}
const apiKey = await getSecret('prod/api/stripe-key');undefinedundefinedundefinedundefined// config/env-validation.ts
import { z } from "zod";
const envSchema = z
.object({
NODE_ENV: z.enum(["development", "production", "test"]),
DATABASE_URL: z.string().url(),
API_KEY: z.string().min(32),
JWT_SECRET: z.string().min(64),
// Never allow default/example values in production
})
.refine((env) => {
if (env.NODE_ENV === "production") {
const invalidValues = ["example", "test", "localhost", "changeme"];
return !invalidValues.some((val) =>
Object.values(env).some((envVal) =>
String(envVal).toLowerCase().includes(val)
)
);
}
return true;
}, "Production environment cannot use example/test values");
// Validate on startup
try {
envSchema.parse(process.env);
} catch (error) {
console.error("❌ Invalid environment configuration:", error);
process.exit(1);
}// config/env-validation.ts
import { z } from "zod";
const envSchema = z
.object({
NODE_ENV: z.enum(["development", "production", "test"]),
DATABASE_URL: z.string().url(),
API_KEY: z.string().min(32),
JWT_SECRET: z.string().min(64),
// Never allow default/example values in production
})
.refine((env) => {
if (env.NODE_ENV === "production") {
const invalidValues = ["example", "test", "localhost", "changeme"];
return !invalidValues.some((val) =>
Object.values(env).some((envVal) =>
String(envVal).toLowerCase().includes(val)
)
);
}
return true;
}, "Production environment cannot use example/test values");
// Validate on startup
try {
envSchema.parse(process.env);
} catch (error) {
console.error("❌ Invalid environment configuration:", error);
process.exit(1);
}// monitoring/secret-monitoring.ts
import {
CloudWatchClient,
PutMetricDataCommand,
} from "@aws-sdk/client-cloudwatch";
async function monitorSecretUsage(secretName: string) {
const cloudwatch = new CloudWatchClient();
await cloudwatch.send(
new PutMetricDataCommand({
Namespace: "Security/Secrets",
MetricData: [
{
MetricName: "SecretAccess",
Value: 1,
Unit: "Count",
Dimensions: [
{
Name: "SecretName",
Value: secretName,
},
],
},
],
})
);
}
// Alert on unusual secret access patterns// monitoring/secret-monitoring.ts
import {
CloudWatchClient,
PutMetricDataCommand,
} from "@aws-sdk/client-cloudwatch";
async function monitorSecretUsage(secretName: string) {
const cloudwatch = new CloudWatchClient();
await cloudwatch.send(
new PutMetricDataCommand({
Namespace: "Security/Secrets",
MetricData: [
{
MetricName: "SecretAccess",
Value: 1,
Unit: "Count",
Dimensions: [
{
Name: "SecretName",
Value: secretName,
},
],
},
],
})
);
}
// Alert on unusual secret access patterns