---
name: upgrade-lambda-node
description: Upgrade a Lambda function to a new Node.js version, fix native addon incompatibilities, and remediate npm vulnerabilities
---

Upgrade this Lambda function to the Node.js version specified by the user (or ask which version if not specified). Follow these steps in order:

## 1. Bump the Node version references

Update all version references consistently:
- `.nvmrc` — set to the target Node major version (e.g. `24`)
- `Dockerfile` — update **both** the `base` stage AND the `runner` stage to the new `public.ecr.aws/lambda/nodejs:XX` image. These must match — a mismatch causes the build to use a different Node than the runtime.
- `Dockerfile.tests` — update the base image
- `package.json` `engines.node` field — update the minimum version (e.g. `>=24`)

## 2. Check for native addon incompatibilities

Native addons compiled against older V8 APIs break on new Node versions. Look for packages that use `nan` or `node-gyp` in the dependency tree:

```bash
yarn why nan
yarn why node-gyp
```

Common offenders and their fixes:
- `unix-dgram` (via `hot-shots` via `datadog-lambda-js`) → upgrade `datadog-lambda-js` to `^12.0.0` which drops `hot-shots` and `unix-dgram` entirely
- Any other `nan`-based addon → check for a newer version that migrated to N-API (`node-addon-api`)

## 3. Proactively fix known vulnerabilities

Do **not** run the vulnerability scan script — the user will paste scan results after the PR is created. Instead, use `yarn why` to check whether each known vulnerable package is present, and apply the fix if so.

**Strategy: always try upgrading the library first. Use `resolutions` only if a direct upgrade is not possible.**

For each package in the table below, run `yarn why <package>` to confirm it's present before applying a fix.

### Using yarn resolutions (when direct upgrade is not possible)

Add to `package.json` `resolutions` block:

```json
"resolutions": {
    "vulnerable-package": "safe-version"
}
```

Key rules for yarn 1.x resolutions:
- Use **direct package name** (e.g. `"minimatch": "10.2.3"`), not scoped paths like `"some-pkg/minimatch"` — scoped resolutions don't reliably propagate to deeply nested copies
- A resolution overrides **all** installs of that package across the entire tree
- After adding a resolution, run `yarn install` and verify with `find node_modules -path "*/package-name/package.json" | xargs grep '"version"'`
- Check for incompatible `brace-expansion` resolutions — forcing `brace-expansion` to a 2.x version breaks `minimatch@10.x` which requires `brace-expansion@^5.x`. If you add `"minimatch": "10.2.3"`, remove any existing `brace-expansion@2.x` resolution.

### Known vulnerability patterns and fixes

| Vulnerability source | Preferred fix | Fallback resolution |
|---|---|---|
| `minimatch@3.x` (from eslint 9.x) | No eslint@9 upgrade available | `"minimatch": "10.2.3"` — also removes brace-expansion@2.x (minimatch@10 needs brace-expansion@^5) |
| `minimatch@9.x` (from `@typescript-eslint` 7.x chain) | — | `"minimatch": "10.2.3"` + `"@typescript-eslint/typescript-estree": "8.58.0"` |
| `minimatch@9.x` or `10.x` (from npm bundled inside Docker image) | — | Add CVE to Jenkinsfile ignore list — npm's internal packages are not reachable via `resolutions` |
| `path-to-regexp@0.1.12` (from `dd-trace@5.43.x`) | Upgrade `dd-trace` to `^5.95.0` — it drops `path-to-regexp` entirely | `"path-to-regexp": "0.1.13"` resolution |
| `yaml@2.7.x` (from `lint-staged@15.x`) | Upgrade `lint-staged` to `^16.4.0` — brings `yaml@^2.8.2` | `"yaml": "2.8.3"` resolution |
| `picomatch@4.0.2` (from `jest@30.2.x` via `jest-util`) | Upgrade `jest` to `30.3.0` — `jest-util@30.3.0` uses `picomatch@^4.0.3` which resolves to 4.0.4 | `"picomatch": "4.0.4"` resolution |
| `flatted@3.3.x` (from `eslint` via `flat-cache`) | `flat-cache@^3.2.9` already covers 3.4.2 — a fresh `yarn install` resolves it naturally | `"flatted": "3.4.2"` resolution |
| `ajv@6/7` (from `@kafkajs/confluent-schema-registry@3.x`) | — | `"@kafkajs/confluent-schema-registry": "4.0.8"` resolution — v4 uses `ajv@^8`, also eliminates the `taffydb` CVE |
| `lodash@4.17.x` | Upgrade direct dep to `^4.18.1` | `"lodash": "4.18.1"` resolution for nested copies in `@theorchard` packages |
| `fast-xml-parser@4.4.x` (nested in AWS SDK via `@theorchard/lambda-apollo`) | No direct upgrade path | `"fast-xml-parser": "4.5.5"` resolution |
| `@smithy/config-resolver@4.1.x` (nested in AWS SDK) | No direct upgrade path | `"@smithy/config-resolver": "4.4.0"` resolution |
| `diff@4.0.2` (from `ts-node@10.9.2`) | `ts-node@10.9.2` is the latest — no upgrade available | `"diff": "4.0.4"` resolution |
| `@typescript-eslint` version conflicts (yarn nesting old versions) | — | Add 4 individual resolutions: `@typescript-eslint/typescript-estree`, `@typescript-eslint/utils`, `@typescript-eslint/eslint-plugin`, `@typescript-eslint/parser` all at the same version |

### Removing redundant resolutions

Before finalising, check which resolutions are redundant:
- If the package is already a **direct** `devDependency` at the same version → remove the resolution
- If the fixed version is already the **latest** in its semver range and all consumers use `^x.y.z` → remove the resolution
- Run `yarn install` and `yarn test` after removing each one to confirm

## 4. Handle scan results (after PR is created)

The user will paste vulnerability scan results from the CI pipeline. For each finding:

**Strategy: always try upgrading the library first. Use `resolutions` only if a direct upgrade is not possible.**

Use `yarn why <package>` to trace the dependency chain, then apply the appropriate fix from the table above.

For vulnerabilities that cannot be fixed via `package.json`:
- **OS-level packages** (RPM/deb, e.g. `openssl`) → add CVE to ignore list in Jenkinsfile
- **npm-bundled packages** (e.g. `tar`, `minimatch`, `picomatch` inside npm's own `node_modules`) → add CVE to ignore list in Jenkinsfile. These packages live inside npm's internal `node_modules` and cannot be overridden by `resolutions`. Verify first with `find node_modules -path "*/package-name/package.json" | xargs grep '"version"'` — if the vulnerable version only appears inside the Docker image (not in your app's `node_modules`), it is npm-bundled.
- **No fix released yet** (scanner reports "Has Fix: No") → add CVE to ignore list in Jenkinsfile with a comment explaining why

Add ignored CVEs to `VULNERABILITIES_TO_IGNORE` in the root `Jenkinsfile` with a comment:

```groovy
'CVE-2026-XXXXX',  // tar bundled in npm within Docker image, not a direct dependency
```

Do **not** add a CVE to the ignore list if you have fixed it via a library upgrade or resolution — only ignore what genuinely cannot be resolved.

## 5. Verify

```bash
yarn install
yarn test:unit
```

All tests must pass before committing.
