Lambda functions still executing on runtimes AWS has deprecated
What does ZopNight detect here?
Deprecation is decided by the discoverer's is_deprecated_runtime flag, not a hardcoded version list that goes stale, so a runtime AWS deprecates next quarter is caught without a rule change. Deprecated runtimes stop receiving security patches first, then lose the ability to create or update functions in 2 later enforcement phases.
Signal and threshold
| Field | Value |
|---|---|
| Rule IDs | RC-155 |
| Category | compliance |
| Severity | critical |
| Metric | none — pure configuration read |
| Source | lambda_runtime_deprecated.go |
Where it applies
Deprecation is a countdown, not a label
AWS retires Lambda runtimes in phases. First the runtime stops receiving security patches, so your function keeps running on an interpreter that will never see another CVE fix. Then AWS blocks creating new functions on it, then blocks updates to existing ones, and eventually invocations themselves can be disabled. A function on a deprecated runtime is therefore not “working fine”; it is at a known point on a published schedule toward not working at all, carrying unpatched-interpreter risk the whole way. That is why this finding is rated critical despite costing nothing.
The determination lives upstream, deliberately
An earlier implementation kept its own list of deprecated runtime strings, which is a design
that rots: every AWS deprecation announcement required a code change nobody remembered to
make. The current rule reads a per-function is_deprecated_runtime flag the discoverer
computes, plus the runtime string for display. If the flag is absent the rule abstains, and if
the runtime name is missing there is nothing meaningful to report, so it abstains then too.
One source of truth, no stale allow-list.
Enumerate your runtime spread
aws lambda list-functions \ --query 'Functions[].[FunctionName,Runtime]' --output tableCross-check anything old-looking against the runtime deprecation table in the Lambda documentation. python3.8, nodejs16.x, go1.x and their contemporaries are the usual finds.
Migration is mostly testing, rarely rewriting
Within a language, runtime upgrades are usually mechanical: bump the runtime setting, run the test suite, watch for removed stdlib behaviour and updated SDK defaults. The genuinely painful cases are functions whose dependencies pin dead versions, and functions nobody owns, which this finding tends to surface for the first time in years. Batch the mechanical upgrades, schedule the painful ones, and treat any function with no owner and no traffic as a deletion candidate instead of a migration.
Keep the class from coming back
Runtime lifetimes are finite by design, so build the upgrade into cadence: pin runtimes in IaC, subscribe to the deprecation schedule, and prefer container-image functions where you control the base image lifecycle when a team genuinely cannot track managed-runtime timelines.