Seguridad de la cadena de suministro para dependencias de software
Protege tu cadena de suministro con versiones fijadas, verificación de lockfiles, generación de SBOM, escaneo automático y control del riesgo externo.

Tu aplicación es 5% tu código y 95% dependencias. Un solo paquete comprometido —como event-stream, ua-parser-js o colors.js— puede inyectar malware en cada aplicación que dependa de él. Los ataques a la cadena de suministro apuntan al eslabón más débil: la relación de confianza entre los desarrolladores y los paquetes de código abierto que instalan sin revisar.
Defenderse de esto requiere controles en capas: fijar versiones exactas, verificar la integridad, escanear en busca de vulnerabilidades conocidas, generar Listas de Materiales de Software (SBOM) y monitorear continuamente los problemas recién divulgados en tu árbol de dependencias.
Fijación de dependencias e integridad del lockfile
Los lockfiles garantizan instalaciones reproducibles. Pero un lockfile solo es tan confiable como el proceso que lo genera.
// ❌ package.json with loose version ranges
{
"dependencies": {
"express": "^4.18.0",
"lodash": "~4.17.0",
"axios": "*"
}
}
// Any install could pull different versions
// A compromised patch release gets pulled automatically// ✅ Exact versions + lockfile verification
{
"dependencies": {
"express": "4.18.2",
"lodash": "4.17.21",
"axios": "1.6.2"
}
}# CI pipeline: verify lockfile integrity
# .github/workflows/security.yml
name: Supply Chain Security
on: [push, pull_request]
jobs:
verify-dependencies:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Fail if lockfile doesn't match package.json
- name: Verify lockfile integrity
run: npm ci --ignore-scripts
# npm ci fails if lockfile is out of sync
# --ignore-scripts prevents postinstall attacks
# Verify package integrity checksums
- name: Audit dependencies
run: npm audit --audit-level=high
# Check for known malicious packages
- name: Socket security check
uses: SocketDev/socket-security-action@v1
with:
api_key: ${{ secrets.SOCKET_API_KEY }}La bandera --ignore-scripts en CI es fundamental. Los scripts de post-instalación son el principal vector de ataque para paquetes comprometidos: se ejecutan como código arbitrario durante npm install. Desactívalos en CI y permítelos solo para paquetes explícitamente confiables.
Generación de SBOM
Una Lista de Materiales de Software (SBOM) cataloga cada componente de tu aplicación. Muchos marcos de cumplimiento la exigen, y es esencial para responder rápidamente cuando se divulga una nueva vulnerabilidad.
// SBOM generation as part of the build pipeline
interface SBOMComponent {
name: string;
version: string;
type: "library" | "framework" | "application";
purl: string; // Package URL - universal identifier
licenses: string[];
supplier: string;
hashes: {
algorithm: string;
value: string;
}[];
dependencies: string[]; // PURLs of direct dependencies
}
interface SBOM {
format: "CycloneDX" | "SPDX";
version: string;
metadata: {
timestamp: string;
component: {
name: string;
version: string;
};
tools: string[];
};
components: SBOMComponent[];
}# Generate SBOM in CI/CD
- name: Generate CycloneDX SBOM
run: npx @cyclonedx/cyclonedx-npm --output-file sbom.json
# Store SBOM as build artifact
- name: Upload SBOM
uses: actions/upload-artifact@v4
with:
name: sbom
path: sbom.json
# Scan SBOM for vulnerabilities
- name: Scan SBOM with Grype
run: |
grype sbom:sbom.json --fail-on highEvaluación del riesgo de las dependencias
No todas las dependencias implican el mismo riesgo. Un framework con 10,000 estrellas en GitHub y respaldo corporativo es muy distinto de una utilidad mantenida por una sola persona con 3 descargas por semana.
// Dependency risk evaluation framework
interface DependencyRisk {
name: string;
version: string;
riskScore: number; // 0-100
factors: RiskFactor[];
}
interface RiskFactor {
category: string;
description: string;
severity: "low" | "medium" | "high" | "critical";
}
function evaluateDependencyRisk(
pkg: PackageMetadata
): DependencyRisk {
const factors: RiskFactor[] = [];
// Maintainer risk
if (pkg.maintainers.length === 1) {
factors.push({
category: "maintainer",
description:
"Single maintainer — bus factor of 1",
severity: "medium",
});
}
// Activity risk
const daysSinceLastPublish = dateDiffDays(
pkg.lastPublished,
new Date()
);
if (daysSinceLastPublish > 365) {
factors.push({
category: "activity",
description:
`No updates in ${daysSinceLastPublish} days`,
severity: "high",
});
}
// Dependency depth risk
if (pkg.transitiveDepCount > 100) {
factors.push({
category: "supply-chain",
description:
`${pkg.transitiveDepCount} transitive dependencies`,
severity: "high",
});
}
// Permission risk — postinstall scripts
if (pkg.hasInstallScript) {
factors.push({
category: "permissions",
description: "Has postinstall script",
severity: "high",
});
}
// Typosquatting risk
if (pkg.weeklyDownloads < 100 && pkg.ageInDays < 30) {
factors.push({
category: "typosquatting",
description:
"New package with low downloads — potential typosquat",
severity: "critical",
});
}
const riskScore = calculateRiskScore(factors);
return {
name: pkg.name,
version: pkg.version,
riskScore,
factors,
};
}
function calculateRiskScore(
factors: RiskFactor[]
): number {
const weights = {
low: 5,
medium: 15,
high: 30,
critical: 50,
};
const total = factors.reduce(
(sum, f) => sum + weights[f.severity],
0
);
return Math.min(100, total);
}Monitoreo automatizado de vulnerabilidades
Las vulnerabilidades se divulgan después de que ya hiciste el despliegue. El monitoreo continuo detecta nuevos CVE frente a tu árbol de dependencias actual.
# Scheduled vulnerability scanning
name: Dependency Vulnerability Monitor
on:
schedule:
- cron: '0 8 * * 1-5' # Weekdays at 8 AM
workflow_dispatch:
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci --ignore-scripts
- name: Scan with npm audit
id: audit
continue-on-error: true
run: |
npm audit --json > audit-results.json
echo "vulnerabilities=$(jq '.metadata.vulnerabilities.high + .metadata.vulnerabilities.critical' audit-results.json)" >> $GITHUB_OUTPUT
- name: Create alert issue
if: steps.audit.outputs.vulnerabilities > 0
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const audit = JSON.parse(
fs.readFileSync('audit-results.json', 'utf8')
);
const vulns = audit.vulnerabilities || {};
const critical = Object.entries(vulns)
.filter(([, v]) =>
v.severity === 'high' || v.severity === 'critical'
);
const body = critical.map(([name, v]) =>
`- **${name}** (${v.severity}): ${v.via?.[0]?.title || 'Unknown'}`
).join('\n');
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: `🔒 ${critical.length} high/critical vulnerability(ies) found`,
body: `## Dependency Vulnerabilities\n\n${body}\n\nRun \`npm audit\` locally for details.`,
labels: ['security', 'dependencies']
});Lista blanca de scripts de instalación
En lugar de desactivar globalmente los scripts de instalación, mantén una lista blanca explícita de los paquetes autorizados a ejecutar scripts durante la instalación.
{
"scripts": {
"preinstall": "npx only-allow npm"
},
"overrides": {},
"npm": {
"ignore-scripts": true
}
}// .npmrc — disable scripts globally, allowlist specific packages
ignore-scripts=true
// scripts-allow.json — explicit allowlist
{
"allowedPackages": [
"esbuild",
"sharp",
"better-sqlite3"
],
"reason": {
"esbuild": "Requires platform-specific binary download",
"sharp": "Native image processing bindings",
"better-sqlite3": "Native SQLite bindings"
}
}// Dependency addition review checklist
interface DependencyReviewChecklist {
packageName: string;
reviewer: string;
checks: {
question: string;
answer: boolean;
notes: string;
}[];
}
const checklist: DependencyReviewChecklist = {
packageName: "new-package",
reviewer: "team-lead",
checks: [
{
question: "Is this functionality available in the stdlib or existing deps?",
answer: false,
notes: "Checked: no equivalent in existing deps",
},
{
question: "Does it have regular maintenance activity?",
answer: true,
notes: "Last commit 2 weeks ago, 15 releases this year",
},
{
question: "Does it have postinstall scripts?",
answer: false,
notes: "Verified: no install scripts",
},
{
question: "Is the transitive dependency count acceptable?",
answer: true,
notes: "3 transitive deps, all well-known",
},
{
question: "Are there known vulnerabilities?",
answer: false,
notes: "npm audit clean, no open CVEs",
},
],
};Puntos clave
La verificación de lockfiles con npm ci --ignore-scripts en CI garantiza builds reproducibles y evita ataques mediante scripts de post-instalación, el principal vector de los compromisos de la cadena de suministro en el ecosistema npm. La generación de SBOM con CycloneDX o SPDX cataloga cada componente de tu aplicación, lo que permite responder rápidamente cuando se divulgan nuevas vulnerabilidades en las dependencias que usas. La evaluación del riesgo de las dependencias debe considerar el número de mantenedores, la frecuencia de actualización, la profundidad de las dependencias transitivas, los scripts de post-instalación y los patrones de descarga, ya que no todos los paquetes implican el mismo riesgo. El escaneo programado de vulnerabilidades con creación automática de issues detecta CVE recién divulgados en tu árbol de dependencias existente, no solo en el momento de la instalación. Una lista blanca explícita para los paquetes autorizados a ejecutar scripts de instalación es más segura que permitir o desactivar los scripts de forma global: los paquetes que necesitan binarios nativos obtienen scripts, y todos los demás se ejecutan sin ellos. Cada nueva dependencia que se agregue debe pasar por una lista de verificación que confirme que ninguna dependencia existente ofrece la misma funcionalidad, que el paquete tiene mantenimiento activo y que el impacto de sus dependencias transitivas es aceptable.


