Back

SAS-6: Insecure Third-Party Dependencies - Code Examples

Each pair below shows a vulnerable setup and the secure version of the same thing—package configuration, lockfiles and hashes, CI scanning, and Lambda layer/dependency handling. The focus is the choices that decide whether a bad package can enter, and what it can do once it does.

1. Package Configuration (package.json / requirements.txt)

Vulnerable

# package.json - floating ranges + a build-time script that runs anything
{
  "name": "order-fn",
  "dependencies": {
    "left-pad": "*",              // whatever resolves - no ceiling
    "some-parser": "^1.0.0",      // silently accepts a hijacked 1.9.9
    "aws-sdk": "latest"           // non-deterministic across builds
  },
  "scripts": {
    "postinstall": "node ./setup.js"   // arbitrary code on every install
  }
}

# requirements.txt - unpinned, no hashes
requests
pyyaml>=3
some-utils            # name never verified; a typo installs an impostor

Secure

# package.json - exact-ish, no floating "latest", scripts controlled in CI
{
  "name": "order-fn",
  "dependencies": {
    "some-parser": "1.4.2",       // exact version, chosen deliberately
    "@aws-sdk/client-dynamodb": "3.658.1"
  }
  // no lifecycle scripts here; CI installs with --ignore-scripts
}

# requirements.txt - pinned AND hash-verified
requests==2.32.3 \
    --hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6
pyyaml==6.0.2 \
    --hash=sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425
# Every package pinned; install fails if a hash does not match.

2. Lockfiles and Integrity Hashes

Vulnerable

# CI installs with the mutable command and no lockfile discipline
npm install                     # may update the lockfile / pull new versions
# (package-lock.json not committed, or ignored in CI)

# Python
pip install -r requirements.txt # no --require-hashes; any matching version

Secure

# Node - deterministic install straight from the committed lockfile
npm ci                          # fails if package-lock.json is missing/out of sync
# package-lock.json pins each dep with an integrity hash:
#   "some-parser": {
#     "version": "1.4.2",
#     "integrity": "sha512-Xa9...=="   // artifact is verified on install
#   }

# Python - require a hash for every package (direct and transitive)
pip install --require-hashes -r requirements.txt
# A swapped or tampered artifact fails the hash check and aborts the build.

3. CI Dependency Scanning (SCA)

Vulnerable

# .github/workflows/deploy.yml - no scanning; ship whatever installs
jobs:
  deploy:
    steps:
      - run: npm install
      - run: npx serverless deploy      // known CVEs ride along, unnoticed

Secure

# .github/workflows/deploy.yml - scan gates the deploy
jobs:
  deploy:
    steps:
      - run: npm ci --ignore-scripts

      # Fail the build on known-vulnerable dependencies
      - run: npm audit --audit-level=high
      - run: npx snyk test --severity-threshold=high

      # Generate + scan an SBOM of what will actually ship
      - run: npx @cyclonedx/cyclonedx-npm --output-file sbom.json
      - run: grype sbom:sbom.json --fail-on high

      # Only deploy if every gate passed
      - run: npx serverless deploy

# Also run the SAME scans on a schedule against deployed artifacts,
# so a CVE disclosed after release still raises an alarm.

Python equivalent gate:

- run: pip install --require-hashes -r requirements.txt
- run: pip-audit -r requirements.txt --strict     # non-zero exit fails the job

4. Lambda Layer / Bundled Dependencies

Vulnerable

# A shared layer built once, never re-scanned, pinned by many functions
build-layer.sh:
  pip install -r layer-requirements.txt -t python/   # unpinned, unscanned
  zip -r layer.zip python/
  aws lambda publish-layer-version --layer-name common-deps \
      --zip-file fileb://layer.zip

# serverless.yml - 40 functions attach the same stale layer
functions:
  orders:
    handler: orders.handler
    layers:
      - arn:aws:lambda:us-east-1:123456789012:layer:common-deps:7   # frozen, vulnerable
    runtime: python3.7        # deprecated / unpatched runtime

Secure

# Build the layer deterministically, scan its CONTENTS, rebuild on a cadence
build-layer.sh:
  pip install --require-hashes -r layer-requirements.txt -t python/
  # Scan the built layer directory before publishing
  grype dir:./python --fail-on high
  trivy fs ./python --severity HIGH,CRITICAL
  zip -r layer.zip python/
  aws lambda publish-layer-version --layer-name common-deps \
      --zip-file fileb://layer.zip

# serverless.yml - supported runtime, layer version bumped as it is rebuilt
functions:
  orders:
    handler: orders.handler
    layers:
      - arn:aws:lambda:us-east-1:123456789012:layer:common-deps:12  # freshly scanned
    runtime: python3.12       # current, patched runtime

5. Bounding the Blast Radius (the Serverless Twist)

Even a perfectly scanned pipeline can be beaten by a zero-day. The final control is ensuring a compromised dependency inherits almost nothing.

Vulnerable

# serverless.yml - the function role is a skeleton key
provider:
  name: aws
  iam:
    role:
      statements:
        - Effect: Allow
          Action: "*"           # a bad dep can do ANYTHING
          Resource: "*"
  environment:
    DB_PASSWORD: ${env:DB_PASSWORD}   # secret sits in plaintext env for any module to read

Secure

# serverless.yml - least privilege + secrets out of plaintext env
provider:
  name: aws
  iam:
    role:
      statements:
        - Effect: Allow
          Action: ["dynamodb:GetItem"]                 # only what the handler needs
          Resource: "arn:aws:dynamodb:us-east-1:123456789012:table/Orders"
# Pull secrets at runtime from a secrets manager (short-lived), not plaintext env,
# and restrict egress so exfiltration has nowhere to go.
functions:
  orders:
    handler: orders.handler
    # install scripts disabled at build; deps pinned + scanned in CI (sections 1-4)

What Changed, and Why

AreaVulnerableSecure
Package configFloating ranges (*, latest), postinstall onDeliberate exact versions; scripts controlled in CI
Lockfiles / hashesnpm install, no committed lockfile, no hashesnpm ci / --require-hashes from a committed, hashed lockfile
CI scanningNone — CVEs ship silentlySCA + SBOM scan gate the deploy, and run on a schedule
Layers / runtimeStale, unscanned layer; deprecated runtimeScanned, rebuilt layer; current, patched runtime
Blast radius* role, secret in plaintext envLeast-privilege role, secrets manager, egress limits

Next Steps