欢迎光临
我们一直在努力

五、工程化与最佳实践——04-安全加固/02-依赖安全检查

在这里插入图片描述

05-工程化与最佳实践/04-安全加固/02-依赖安全检查

依赖安全检查

学习目标

  • 掌握 npm audit 漏洞扫描与修复
  • 学会使用 Snyk 进行依赖监控
  • 理解依赖更新策略和安全最佳实践

前置知识

  • npm/yarn 包管理
  • 语义化版本
  • CI/CD 基础

知识点列表

1. npm audit 漏洞扫描

1.1 基础使用

# 扫描项目依赖漏洞
npm audit

# 仅显示高严重性漏洞
npm audit –audit-level=high

# 生成 JSON 格式报告
npm audit –json > audit-report.json

# 自动修复可修复的漏洞
npm audit fix

# 强制更新到主要版本
npm audit fix –force

# 只更新生产依赖
npm audit fix –production

# 查看详细信息
npm audit –verbose

// scripts/audit.js
const { exec } = require('child_process');
const util = require('util');
const execPromise = util.promisify(exec);

class SecurityAudit {
async runAudit(options = {}) {
const cmd = `npm audit –json ${options.production ? '–production' : ''}`;

try {
const { stdout } = await execPromise(cmd);
const report = JSON.parse(stdout);

return this.analyzeReport(report);
} catch (error) {
// npm audit 在有漏洞时会返回非零退出码
if (error.stdout) {
return this.analyzeReport(JSON.parse(error.stdout));
}
throw error;
}
}

analyzeReport(report) {
const summary = {
totalVulnerabilities: report.metadata.vulnerabilities.total,
critical: report.metadata.vulnerabilities.critical || 0,
high: report.metadata.vulnerabilities.high || 0,
moderate: report.metadata.vulnerabilities.moderate || 0,
low: report.metadata.vulnerabilities.low || 0,
affectedPackages: [],
fixable: {
viaInstall: 0,
viaSemver: 0
}
};

for (const [pkg, data] of Object.entries(report.vulnerabilities || {})) {
summary.affectedPackages.push({
package: pkg,
severity: data.severity,
title: data.title,
version: data.via[0]?.version,
fixAvailable: data.fixAvailable !== false
});

if (data.fixAvailable !== false) {
if (typeof data.fixAvailable === 'object') {
summary.fixable.viaSemver++;
} else {
summary.fixable.viaInstall++;
}
}
}

return summary;
}

async generateReport() {
const audit = await this.runAudit();

console.log('\\n=== 安全审计报告 ===');
console.log(`总漏洞数: ${audit.totalVulnerabilities}`);
console.log(`严重: ${audit.critical} | 高危: ${audit.high} | 中危: ${audit.moderate} | 低危: ${audit.low}`);
console.log(`可修复: ${audit.fixable.viaInstall} (自动) + ${audit.fixable.viaSemver} (版本升级)`);

if (audit.affectedPackages.length > 0) {
console.log('\\n受影响包:');
for (const pkg of audit.affectedPackages) {
console.log(` – ${pkg.package}@${pkg.version}`);
console.log(` 严重性: ${pkg.severity}`);
console.log(` 问题: ${pkg.title}`);
console.log(` 可修复: ${pkg.fixAvailable ? '是' : '否'}`);
}
}

return audit;
}

async checkThreshold(threshold = 0) {
const audit = await this.runAudit();

if (audit.totalVulnerabilities > threshold) {
console.error(`❌ 安全审计失败: ${audit.totalVulnerabilities} 个漏洞超过阈值 ${threshold}`);
process.exit(1);
}

console.log(`✅ 安全审计通过: ${audit.totalVulnerabilities} 个漏洞`);
return true;
}
}

// 在 CI 中使用
if (require.main === module) {
const auditor = new SecurityAudit();
auditor.checkThreshold(0).catch(console.error);
}

1.2 自动化审计配置

// package.json
{
"scripts": {
"audit": "npm audit",
"audit:fix": "npm audit fix",
"audit:report": "npm audit –json > audit-report.json",
"security-check": "npm audit –audit-level=high"
},
"husky": {
"hooks": {
"pre-commit": "npm run security-check"
}
}
}

# .github/workflows/security-audit.yml
name: Security Audit

on:
schedule:
– cron: '0 0 * * *' # 每天运行
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
audit:
runs-on: ubuntu–latest
steps:
– uses: actions/checkout@v3

– uses: actions/setup–node@v3
with:
node-version: '18'
cache: 'npm'

– name: Install dependencies
run: npm ci

– name: Run security audit
run: npm audit ––json | tee audit–results.json

– name: Check for critical vulnerabilities
run: |
CRITICAL=$(jq '.metadata.vulnerabilities.critical // 0' audit-results.json)
HIGH=$(jq '.metadata.vulnerabilities.high // 0' audit-results.json)
if [ "$CRITICAL" -gt 0 ] || [ "$HIGH" -gt 0 ]; then
echo "❌ Critical/High vulnerabilities found!"
exit 1
fi
echo "✅ No critical vulnerabilities"

– name: Upload audit report
uses: actions/upload–artifact@v3
if: always()
with:
name: audit–report
path: audit–results.json

2. Snyk 依赖监控

2.1 安装与配置

# 安装 Snyk CLI
npm install -g snyk

# 认证
snyk auth

# 在项目中初始化
snyk monitor

// scripts/snyk-security.js
const { exec } = require('child_process');
const util = require('util');
const execPromise = util.promisify(exec);

class SnykSecurity {
constructor(projectName) {
this.projectName = projectName;
}

async test() {
console.log('🔍 运行 Snyk 安全测试…');

try {
const { stdout } = await execPromise('snyk test –json');
const result = JSON.parse(stdout);

return this.analyzeResult(result);
} catch (error) {
if (error.stdout) {
return this.analyzeResult(JSON.parse(error.stdout));
}
throw error;
}
}

analyzeResult(result) {
const summary = {
ok: result.ok,
vulnerabilities: {
total: result.vulnerabilities?.length || 0,
critical: 0,
high: 0,
medium: 0,
low: 0
},
licenses: {
total: result.licenses?.length || 0,
issues: []
},
dependencies: result.dependencyCount || 0,
path: result.path
};

// 统计漏洞严重性
for (const vuln of result.vulnerabilities || []) {
summary.vulnerabilities[vuln.severity]++;
summary.vulnerabilities[vuln.severity] =
(summary.vulnerabilities[vuln.severity] || 0) + 1;
}

// 许可证问题
for (const license of result.licenses || []) {
if (license.severity === 'high' || license.issues?.length) {
summary.licenses.issues.push({
package: license.name,
license: license.license,
issues: license.issues
});
}
}

return summary;
}

async monitor() {
console.log('📊 上传项目到 Snyk 监控…');

try {
const { stdout } = await execPromise('snyk monitor –json');
const result = JSON.parse(stdout);

console.log(`✅ 项目已添加到 Snyk: ${result.id}`);
console.log(`📈 查看报告: ${result.uri}`);

return result;
} catch (error) {
console.error('❌ Snyk 监控失败:', error.message);
throw error;
}
}

async testWithFix() {
console.log('🔧 尝试自动修复…');

try {
await execPromise('snyk wizard');
return true;
} catch (error) {
console.error('自动修复失败:', error.message);
return false;
}
}

async generateReport() {
const testResult = await this.test();

console.log('\\n=== Snyk 安全报告 ===');
console.log(`项目: ${this.projectName}`);
console.log(`状态: ${testResult.ok ? '✅ 安全' : '❌ 存在漏洞'}`);
console.log(`依赖总数: ${testResult.dependencies}`);
console.log(`漏洞总数: ${testResult.vulnerabilities.total}`);

if (testResult.vulnerabilities.total > 0) {
console.log('\\n漏洞详情:');
console.log(` 严重: ${testResult.vulnerabilities.critical || 0}`);
console.log(` 高危: ${testResult.vulnerabilities.high || 0}`);
console.log(` 中危: ${testResult.vulnerabilities.medium || 0}`);
console.log(` 低危: ${testResult.vulnerabilities.low || 0}`);
}

if (testResult.licenses.issues.length > 0) {
console.log(`\\n许可证问题: ${testResult.licenses.issues.length}`);
for (const issue of testResult.licenses.issues) {
console.log(` – ${issue.package} (${issue.license})`);
}
}

return testResult;
}
}

// 在 CI 中使用
async function securityCheck() {
const snyk = new SnykSecurity('my-app');

const result = await snyk.test();

if (!result.ok) {
console.error('安全测试失败');
process.exit(1);
}

// 可选:上传到 Snyk 监控
if (process.env.SNYK_MONITOR === 'true') {
await snyk.monitor();
}
}

2.2 Snyk GitHub Actions 集成

# .github/workflows/snyk-security.yml
name: Snyk Security

on:
push:
branches: [ main ]
schedule:
– cron: '0 */6 * * *' # 每6小时

jobs:
security:
runs-on: ubuntu–latest
steps:
– uses: actions/checkout@v3

– name: Setup Node.js
uses: actions/setup–node@v3
with:
node-version: '18'

– name: Install dependencies
run: npm ci

– name: Run Snyk to check for vulnerabilities
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: ––severity–threshold=high
command: test

– name: Upload result to GitHub Code Scanning
uses: github/codeql–action/upload–sarif@v2
with:
sarif_file: snyk.sarif

– name: Monitor project (main branch only)
if: github.ref == 'refs/heads/main'
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
command: monitor

3. 依赖更新策略

3.1 版本管理策略

// scripts/update-deps.js
const { exec } = require('child_process');
const util = require('util');
const fs = require('fs').promises;
const execPromise = util.promisify(exec);

class DependencyManager {
constructor() {
this.ignoredPackages = [];
this.updateStrategy = 'conservative'; // conservative, moderate, aggressive
}

async checkOutdated() {
const { stdout } = await execPromise('npm outdated –json');
return JSON.parse(stdout || '{}');
}

async getPackageInfo(packageName) {
try {
const { stdout } = await execPromise(`npm view ${packageName} –json`);
return JSON.parse(stdout);
} catch (error) {
return null;
}
}

async analyzeUpdates() {
const outdated = await this.checkOutdated();
const analysis = {};

for (const [pkg, info] of Object.entries(outdated)) {
const pkgInfo = await this.getPackageInfo(pkg);

analysis[pkg] = {
current: info.current,
wanted: info.wanted,
latest: info.latest,
type: this.getUpdateType(info.current, info.latest),
breaking: this.isBreakingUpdate(info.current, info.latest),
hasSecurityPatch: await this.hasSecurityPatch(pkg, info.current),
recommended: this.getRecommendedVersion(info)
};
}

return analysis;
}

getUpdateType(current, latest) {
const currentParts = current.split('.').map(Number);
const latestParts = latest.split('.').map(Number);

if (latestParts[0] > currentParts[0]) return 'major';
if (latestParts[1] > currentParts[1]) return 'minor';
if (latestParts[2] > currentParts[2]) return 'patch';
return 'none';
}

isBreakingUpdate(current, latest) {
const currentMajor = parseInt(current.split('.')[0]);
const latestMajor = parseInt(latest.split('.')[0]);
return latestMajor > currentMajor;
}

async hasSecurityPatch(packageName, currentVersion) {
try {
const { stdout } = await execPromise(`npm audit –json`);
const audit = JSON.parse(stdout);

for (const [pkg, data] of Object.entries(audit.vulnerabilities || {})) {
if (pkg === packageName) {
return data.fixAvailable !== false;
}
}
return false;
} catch {
return false;
}
}

getRecommendedVersion(info) {
// 保守策略: 只更新补丁版本
if (this.updateStrategy === 'conservative') {
return info.wanted;
}

// 中等策略: 更新次要版本
if (this.updateStrategy === 'moderate') {
const parts = info.latest.split('.');
parts[0] = info.current.split('.')[0];
return parts.join('.');
}

// 激进策略: 更新到最新
return info.latest;
}

async updatePackage(packageName, version) {
console.log(`📦 更新 ${packageName} -> ${version}`);

try {
await execPromise(`npm install ${packageName}@${version} –save`);
console.log(`✅ ${packageName} 更新成功`);
return true;
} catch (error) {
console.error(`❌ ${packageName} 更新失败:`, error.message);
return false;
}
}

async batchUpdate(analysis) {
const results = {
updated: [],
failed: [],
skipped: []
};

for (const [pkg, info] of Object.entries(analysis)) {
// 跳过忽略的包
if (this.ignoredPackages.includes(pkg)) {
results.skipped.push(pkg);
continue;
}

// 安全补丁优先
if (info.hasSecurityPatch) {
const success = await this.updatePackage(pkg, info.recommended);
if (success) results.updated.push(pkg);
else results.failed.push(pkg);
continue;
}

// 非主要版本更新自动更新
if (!info.breaking && this.updateStrategy !== 'conservative') {
const success = await this.updatePackage(pkg, info.recommended);
if (success) results.updated.push(pkg);
else results.failed.push(pkg);
continue;
}

results.skipped.push(pkg);
}

return results;
}

async generateUpdateReport() {
const analysis = await this.analyzeUpdates();

console.log('\\n=== 依赖更新报告 ===\\n');

const securityUpdates = [];
const majorUpdates = [];
const minorUpdates = [];
const patchUpdates = [];

for (const [pkg, info] of Object.entries(analysis)) {
if (info.hasSecurityPatch) {
securityUpdates.push(pkg);
} else if (info.type === 'major') {
majorUpdates.push(pkg);
} else if (info.type === 'minor') {
minorUpdates.push(pkg);
} else if (info.type === 'patch') {
patchUpdates.push(pkg);
}
}

console.log(`🔒 安全更新 (${securityUpdates.length}):`);
securityUpdates.forEach(pkg => console.log(` – ${pkg}`));

console.log(`\\n⚠️ 主要版本更新 (${majorUpdates.length}):`);
majorUpdates.forEach(pkg => console.log(` – ${pkg}`));

console.log(`\\n📈 次要版本更新 (${minorUpdates.length}):`);
minorUpdates.forEach(pkg => console.log(` – ${pkg}`));

console.log(`\\n🐛 补丁版本更新 (${patchUpdates.length}):`);
patchUpdates.forEach(pkg => console.log(` – ${pkg}`));

return analysis;
}
}

// Dependabot 配置
// .github/dependabot.yml
/*
version: 2
updates:
– package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
time: "09:00"
timezone: "Asia/Shanghai"
open-pull-requests-limit: 10
versioning-strategy: "increase"
labels:
– "dependencies"
– "security"
commit-message:
prefix: "chore(deps)"
prefix-development: "chore(dev-deps)"
ignore:
– dependency-name: "express"
versions: ["5.x"]
groups:
production-dependencies:
dependency-type: "production"
development-dependencies:
dependency-type: "development"
patterns:
– "eslint*"
– "jest*"
reviewers:
– "team-backend"
assignees:
– "maintainer"
*/

3.2 依赖锁定与验证

// scripts/verify-deps.js
const crypto = require('crypto');
const fs = require('fs').promises;
const path = require('path');

class DependencyVerifier {
constructor() {
this.integrityHashes = new Map();
this.signatures = new Map();
}

async generateIntegrityHash(packageName, version) {
const packagePath = path.join('node_modules', packageName);
const files = await this.getAllFiles(packagePath);

const hash = crypto.createHash('sha256');

for (const file of files) {
const content = await fs.readFile(file);
hash.update(content);
}

const integrity = hash.digest('base64');
this.integrityHashes.set(`${packageName}@${version}`, integrity);

return integrity;
}

async getAllFiles(dir) {
const files = [];
const entries = await fs.readdir(dir, { withFileTypes: true });

for (const entry of entries) {
const fullPath = path.join(dir, entry.name);

if (entry.isDirectory()) {
if (!['node_modules', '.git'].includes(entry.name)) {
files.push(…await this.getAllFiles(fullPath));
}
} else if (entry.isFile() && !entry.name.endsWith('.map')) {
files.push(fullPath);
}
}

return files;
}

async lockIntegrity() {
const packageJson = JSON.parse(await fs.readFile('package.json', 'utf8'));
const lockfile = {};

const allDeps = {
…packageJson.dependencies,
…packageJson.devDependencies
};

for (const [name, version] of Object.entries(allDeps)) {
const integrity = await this.generateIntegrityHash(name, version);
lockfile[`${name}@${version}`] = { integrity, version };
}

await fs.writeFile('integrity.lock', JSON.stringify(lockfile, null, 2));
console.log('✅ 完整性锁定文件已生成');
}

async verifyIntegrity() {
const lockfile = JSON.parse(await fs.readFile('integrity.lock', 'utf8'));
let isValid = true;

for (const [pkg, expected] of Object.entries(lockfile)) {
const actual = await this.generateIntegrityHash(
pkg.split('@')[0],
expected.version
);

if (actual !== expected.integrity) {
console.error(`❌ 完整性验证失败: ${pkg}`);
isValid = false;
}
}

if (isValid) {
console.log('✅ 所有依赖完整性验证通过');
}

return isValid;
}
}

练习题

基础题

  • 运行 npm audit 并修复可修复的漏洞
  • 配置 Snyk 并扫描项目依赖
  • 使用 npm outdated 检查过期依赖
  • 进阶题

  • 在 CI 中集成安全审计
  • 配置 Dependabot 自动更新
  • 实现依赖完整性验证
  • 挑战题

  • 搭建完整的依赖安全监控系统
  • 实现自动化依赖更新流程
  • 练习题参考答案

    基础题1

    npm audit
    npm audit fix

    基础题2

    snyk test
    snyk monitor

    基础题3

    npm outdated


    赞(0)
    未经允许不得转载:171主机测评 » 五、工程化与最佳实践——04-安全加固/02-依赖安全检查
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址