To determine whether a vulnerable package is deployed, join three records: the advisory's exact package and affected range, the contents of an immutable artifact, and the instances actually running that artifact at a stated time. A merged dependency update proves none of those runtime facts. A fixed image proves only that a fixed image exists.
The awkward cases are usually mixed states. The API rollout has started, an old replica still receives requests, and a worker has its own release schedule. “Production is on the new version” can be true of the desired configuration while being false of the processes handling customer work.
This article follows one such investigation to a bounded conclusion. Presence is its subject. Whether the affected operation is reachable, whether an attack succeeded, and whether a build dependency ran with sensitive credentials require separate evidence.
The release looks fixed; the inventory disagrees
Everything in this case is synthetic: Harbor, its package, its advisory, its release records and its observations. The timestamps describe the example, not an investigation performed by the author.
Harbor runs a billing API and an export worker in Ireland. Fictional advisory DEMO-ADV-01 affects npm @harbor-demo/archive-reader versions before 2.1.1. Release R42 updates the package from 2.1.0 to 2.1.1. At 14:00 UTC, the release dashboard shows R42 and the attached SBOM lists the fixed version. The security ticket says “deployed; close.”
A platform engineer changes the question to:
At 14:00 UTC, which running Ireland API containers and export workers contained the affected version, and what must change before that answer becomes “none found in the assessed workloads”?
The API and worker are explicitly included. The US region is outside this investigation. Stored rollback images are recorded separately from running workloads. This prevents a later API-only success from silently becoming a fleet-wide claim.
Start by copying the advisory identity, ecosystem, range and exceptions into the record. The OSV schema separates package identity from affected versions and ranges. A similar package name, a patched fork or a distribution package with different provenance needs its own reconciliation; a string match is not enough.
Join the artifact identities before counting instances
The R42 lockfile is useful evidence that the project resolved the fixed dependency. npm describes package-lock.json as a record of the generated dependency tree. It does not establish which output was built, which layers were copied, or which output is running.
The engineer retrieves build records and artifact inventories. The following labels stand for full immutable identifiers retained in the example's evidence record; they are not usable hashes. A real record needs the full digest and algorithm, not M42-amd or a shortened display value.
| Release/build | Published image index | Platform manifest | SBOM's exact subject | Package found |
|---|---|---|---|---|
| R41 / build 741 | I41 | M41-amd | M41-amd | 2.1.0, affected |
| R42 / build 756 | I42 | M42-amd | M42-amd | 2.1.1, outside the fictional affected range |
| R42 / build 756 | I42 | M42-arm | M42-arm | 2.1.1, outside the fictional affected range |
The index-to-manifest relationship matters. A multi-architecture image index can refer to distinct platform manifests; Kubernetes' image documentation explains that structure and the difference between moving tags and content-addressed digests. An SBOM for M42-amd does not, by itself, describe M42-arm. Nor should a runtime identifier be assumed to be an index digest simply because both values contain sha256.
For each join, retain the build or registry record that establishes the relationship. If the runtime supplies an identifier in another form, resolve it using the platform and registry records rather than stripping a prefix and assuming equality. A missing mapping is an unresolved row, not a fixed row.
The inventories here describe the finished images and cover the package locations relevant to this application, including the copied worker bundle. That is an explicit assumption of the synthetic case. In a real review, check how the inventory was produced, duplicate installed versions, bundled libraries and opaque layers. CycloneDX can express composition completeness; merely having a valid SBOM does not establish complete capture of an artifact.
Also check whether startup scripts install packages or mounted volumes replace application libraries. A digest identifies image contents, while the running process can see additional or changed files; Kubernetes documents that container filesystems include mounted volumes and permitted writes. This case assumes no such changes affect the package inventory. When they do, inventory those runtime inputs as well before inferring the loaded package from the image alone.
Count running work, not the desired release
The initial inventory produces this result:
| Instance at 14:00 | Work observed | Resolved artifact | Package conclusion |
|---|---|---|---|
API billing-a |
Serving requests | M41-amd | Affected version present |
API billing-b |
Serving requests | M42-amd | Fixed version present |
API billing-c |
Serving requests | M42-arm | Fixed version present |
Worker export-07 |
Processing export queue | M41-amd | Affected version present |
There are four observed running application containers, two containing the affected version. Of the three serving API containers, one is affected. “Two of four” describes this inventory; it is not an estimate of traffic share or exploitation probability. A worker can process more sensitive input than the entire API, and request distribution need not be equal.
The API Deployment's desired image is I42. That does not contradict the table. Kubernetes rolling updates can run old and new replicas during a transition. For Kubernetes, retain the Pod UID, container name, observed status, status.containerStatuses[].imageID, namespace and collection time, then establish the artifact mapping. The Pod API also warns that reported status can lag actual state. Reconcile stale or unreachable nodes instead of declaring absence from an incomplete control-plane view.
For ECS, collect each relevant running task through DescribeTasks and retain task identity, container identity and containers[].imageDigest. AWS defines that field as the container image manifest digest. A missing field or failed task lookup needs follow-up. The latest task definition alone does not identify every running task.
Inventory scope also needs a workload map: API replicas, canaries, scheduled jobs, queue consumers and relevant sidecars. Routing evidence helps distinguish serving API replicas from draining ones, but removal from a route does not prove process termination or completion of existing work. Record those states separately. Do not execute unfamiliar application code to discover package presence; use controlled artifact and platform evidence.
The first “done” is only half a rollout
At 14:20, the release owner supplies a new observation: all three API containers now use R42 artifacts, and the old API container has terminated. The worker still uses M41-amd. The API package rollout is complete for the observed scope; the Ireland service rollout is not.
The worker was omitted because its deployment was not driven by the API release job. The owner updates its launch configuration, stops it accepting new work, lets the current export finish under the chosen operational procedure, and replaces it. These are proposed actions inside the synthetic case, not universally safe instructions: some workloads require cancellation or immediate containment instead.
At 15:00, the example's follow-up inventory records the replacement worker, the terminated old worker, and the API instances. It also checks the worker's scheduling source so that the next run will not recreate M41-amd.
| Observation | API | Export worker | Supported decision |
|---|---|---|---|
| 14:00 | One affected, two fixed | One affected | Affected package deployed in both workload types |
| 14:20 | Three fixed; old API instance terminated | Still affected | Close API rollout task; keep worker task open |
| 15:00 | Three fixed | Replacement fixed; old worker terminated; launch configuration updated | No affected version found in the assessed running Ireland API and worker artifacts |
The old artifact remains in the registry for rollback. That is not a running deployment, but it creates a recurrence route. The release owner marks it unsuitable for routine rollback and records the approved alternative. If it is deployed again, the presence conclusion must be reopened.
A current snapshot cannot establish when the last affected request ran or whether exploitation occurred. For an incident window, preserve rollout events, instance lifetimes and historical artifact inventories. Do not overwrite the 14:00 record with the cleaner 15:00 state.
The decision that goes back into the ticket
A useful closure note contains enough information to challenge its conclusion:
Scope: Ireland billing API and export worker; observations at 14:00, 14:20 and 15:00 UTC. Identity chain: advisory
DEMO-ADV-01→ npm package/version → finished-image inventories for M41-amd, M42-amd and M42-arm → observed instance identities. Full identifiers and collection records attached. Initial result: affected version present in one serving API container and one active worker. Final result: no affected version found in the inventoried running artifacts at 15:00; old instances terminated and the worker launch configuration updated. Remaining limits: US region unassessed; no conclusion about vulnerable-function reachability or historical exploitation. Follow-through: release owner owns rollback restriction; AppSec owns the separate applicability assessment.
If the SBOM and running identity cannot be joined, the same note should say exactly which link is missing. If only the lockfile is known, the conclusion stops at project resolution. If one architecture is unassessed, it stays visible beside the assessed one.
Handle a contradictory or incomplete observation
Extend Harbor's case with a fifth worker whose task record exists but whose artifact identity cannot be resolved. The correct result is not “two affected out of five” followed by an implication that the other three are fixed. Record two affected, two fixed and one unresolved at the observation time. The unknown worker may change the operational decision even when it does not change the number of confirmed affected instances.
Assign the missing join to the owner who can supply it. A build engineer can establish which manifest a build published; a platform engineer can establish which runtime instance was observed; an application owner can explain a mounted package directory. Asking any one of them to certify the whole chain without the other records encourages a conclusion broader than their evidence.
If two records disagree, retain both collection times before choosing a winner. A worker may have been replaced between observations. Alternatively, one record may describe desired configuration while another describes a running instance. Reconciliation should explain the difference and identify the latest supported state. It should not discard the older record merely because the new one matches the intended release.
Make future launches part of the closure request
The 15:00 snapshot closes a question about observed running work. Harbor also needs assurance that its next scheduled worker will use the intended fixed artifact. Separate these deliverables in the rollout task: current instances reconciled, and future launch sources corrected. That distinction catches a repair that replaces today's worker while leaving tomorrow's schedule on R41.
Look at the actual sources used by the included workloads: the worker schedule, the release configuration, and any approved operational procedure that can launch a one-off copy. This is a bounded review of the application being assessed, not a claim to discover every possible deployment in the organization. Name any environment or launch route that remains outside the investigation.
Finally, define what should reopen the record. A rollback to an affected artifact, a newly discovered worker, a changed mounted dependency, or evidence that the inventory omitted a bundled copy all change the premise. The ticket can then say exactly why a previous “none found” result no longer answers the current question. Presence is a dated observation supported by an identity chain; its usefulness depends on keeping that chain and its scope visible.
The practical output is a small chain of evidence with an honest endpoint. For inventory interpretation, continue with how to read an SBOM. Once presence is established, use dependency prioritization to decide what the affected operation means for this application.