Files
2025-11-29 18:22:35 +08:00

9.6 KiB

name, description
name description
auditing-dependencies Auditing and updating npm dependencies to prevent security vulnerabilities in TypeScript projects

Security: Dependency Management

Purpose: Prevent security vulnerabilities through proper npm dependency auditing, updating, and monitoring.

When to use: Before adding new dependencies, during security reviews, when setting up CI/CD pipelines, or when package.json changes.

Critical Security Principle

Dependencies are attack vectors. Each package you add introduces potential vulnerabilities:

  • Direct vulnerabilities in the package code
  • Transitive dependencies (dependencies of dependencies)
  • Supply chain attacks (malicious package updates)
  • Unmaintained packages with known CVEs

Default stance: Minimize dependencies. Every package is a liability.

Dependency Audit Workflow

1. Check for Known Vulnerabilities

Before installing any package:

npm audit

This shows:

  • Severity levels (critical, high, moderate, low)
  • Vulnerable packages and versions
  • Recommended fixes
  • Dependency path showing how vulnerability entered

Read the output carefully. Not all vulnerabilities affect your code:

  • Check if vulnerable code path is used
  • Assess actual risk vs theoretical risk
  • Prioritize fixes by severity and exploitability

2. Update Vulnerable Packages

Automatic fixes (use with caution):

npm audit fix

This updates packages to non-breaking versions that patch vulnerabilities.

Breaking changes:

npm audit fix --force

This updates to latest versions, potentially breaking your code. Use only after:

  • Reading breaking change notes
  • Having comprehensive test coverage
  • Being prepared to fix broken code

Manual selective updates:

npm update package-name

Update specific packages after reviewing their changelogs.

3. Prevent Vulnerable Installations

Block installations with vulnerabilities:

Create .npmrc in project root:

audit-level=moderate

This fails npm install if moderate or higher severity vulnerabilities exist.

In CI/CD:

- name: Security audit
  run: |
    npm audit --audit-level=moderate
    if [ $? -ne 0 ]; then
      echo "Security vulnerabilities found!"
      exit 1
    fi

4. Monitor Dependencies Continuously

GitHub Dependabot:

Enable in repository settings → Security → Dependabot alerts.

Automatically:

  • Scans dependencies daily
  • Creates PRs for security updates
  • Provides vulnerability details

npm-check-updates:

npx npm-check-updates

Shows available updates for all dependencies.

npx npm-check-updates -u

Updates package.json (still need to run npm install).

Dependency Selection Best Practices

Before Adding Any Dependency

Ask these questions:

  1. Do I actually need this?

    • Can I write the functionality myself in < 100 lines?
    • Am I using 10% of the package's features?
    • Is this adding significant bundle size for trivial functionality?
  2. Is this package trustworthy?

    • Check npm weekly downloads (high is better)
    • Check GitHub stars and recent activity
    • Check last publish date (recent is better, but stable packages may be older)
    • Look for security track record
  3. Is this package maintained?

    • When was last commit?
    • Are issues being responded to?
    • Are security issues addressed quickly?
    • Is there a clear maintenance policy?
  4. What are the transitive dependencies?

    npm ls package-name
    

    Each transitive dependency is another attack vector.

Red Flags

Avoid packages with:

  • No TypeScript types (requires @types/ package or no types at all)
  • Abandoned for > 2 years with no successor
  • Known security vulnerabilities with no fix available
  • Excessive transitive dependencies (> 50 packages)
  • Requires postinstall scripts (potential supply chain attack vector)
  • Very small packages doing trivial things (left-pad scenario)

Safer Alternatives

Use built-in Node.js/browser features when possible:

import { randomBytes } from 'crypto';
const id = randomBytes(16).toString('hex');

Better than installing uuid package if you just need random IDs.

const url = new URL('/api/users', 'https://api.example.com');
url.searchParams.set('limit', '10');

Better than installing query string builder packages.

Lock Files and Reproducible Builds

Always Commit Lock Files

package-lock.json (npm) or yarn.lock (Yarn) must be committed:

  • Ensures exact same dependency versions across environments
  • Prevents supply chain attacks via dependency version updates
  • Makes builds reproducible

Never:

  • Add lock files to .gitignore
  • Delete lock files to "fix" problems
  • Run npm install with --no-package-lock

Use Specific Version Ranges

In package.json, prefer exact versions for critical dependencies:

{
  "dependencies": {
    "express": "4.18.2",
    "zod": "3.22.4"
  }
}

Not:

{
  "dependencies": {
    "express": "^4.18.2",
    "zod": "~3.22.4"
  }
}

Rationale: Caret (^) and tilde (~) allow automatic updates that could introduce breaking changes or vulnerabilities.

Exception: Development dependencies can use ranges if you regularly update them.

CI/CD Integration

Required Security Checks

Every CI pipeline must:

  1. Run audit on every build:

    - run: npm audit --audit-level=moderate
    
  2. Check for outdated dependencies weekly:

    schedule:
      - cron: '0 0 * * 1'
    jobs:
      update-check:
        - run: npx npm-check-updates
    
  3. Prevent merging PRs with vulnerabilities:

    - name: Security gate
      run: npm audit --production --audit-level=moderate
    

Example GitHub Actions Workflow

name: Security Audit

on:
  push:
    branches: [main]
  pull_request:
  schedule:
    - cron: '0 0 * * 1'

jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npm audit --audit-level=moderate
      - name: Check for outdated packages
        run: npx npm-check-updates

Handling Vulnerabilities That Can't Be Fixed

Scenario: Dependency has vulnerability, no fix available

Options:

  1. Find alternative package:

    • Research similar packages without the vulnerability
    • Consider rewriting functionality if simple
  2. Assess actual risk:

    • Is vulnerable code path used in your application?
    • Is vulnerability exploitable in your context?
    • Document risk assessment
  3. Audit exception (last resort):

    npm audit --json > audit-baseline.json
    

    Document why exception is acceptable:

    • What is the vulnerability?
    • Why can't it be fixed?
    • What mitigations are in place?
    • When will this be reviewed again?

    Never ignore vulnerabilities permanently.

Common Mistakes

Mistake 1: Installing packages without checking

npm install some-random-package

Correct approach:

  1. Check package on npm registry
  2. Review GitHub repository
  3. Check bundle size: bundlephobia.com
  4. Run npm audit after installation
  5. Review lock file changes in git diff

Mistake 2: Ignoring audit warnings

"It's just a moderate severity in a dev dependency, doesn't matter."

Wrong. Development dependencies:

  • Can be compromised and steal secrets
  • Run with same permissions as your code
  • Can modify source files during build

Mistake 3: Using --force without understanding

npm install --force

This bypasses dependency resolution and can install incompatible versions.

Only use when:

  • You understand exactly what it does
  • You've read the conflict details
  • You have tests to verify nothing broke

Maintenance Schedule

Weekly:

  • Review Dependabot PRs
  • Run npm audit

Monthly:

  • Run npx npm-check-updates
  • Update non-breaking dependencies
  • Test thoroughly

Quarterly:

  • Plan major version updates
  • Review all dependencies for continued need
  • Remove unused packages

TypeScript-Specific Considerations

Type Definition Security

Check if types match runtime:

import { z } from 'zod';

const APIResponseSchema = z.object({
  data: z.array(z.string()),
});

type APIResponse = z.infer<typeof APIResponseSchema>;

This ensures types and runtime validation stay synchronized.

Type-Only Imports for Tree-Shaking

import type { User } from 'huge-library';

This imports only types, not runtime code, reducing bundle size.

Resources

Tools:

  • npm audit - Built-in vulnerability scanner
  • npm-check-updates - Dependency update checker
  • Snyk - Commercial vulnerability scanning
  • GitHub Dependabot - Automated security updates

References:

Summary Checklist

Before merging any dependency changes:

  • npm audit passes at moderate level or higher
  • Reviewed what the package does and alternatives considered
  • Checked package maintenance status and security history
  • Lock file committed with changes
  • CI pipeline includes security audit
  • Transitive dependencies reviewed (not excessive)
  • Bundle size impact assessed for frontend projects
  • Types available and trustworthy

Remember: The best dependency is the one you don't add. The second best is one that's actively maintained with a strong security track record.