skip to content

You excluded a vulnerable transitive package and declared the fixed version directly — what can silently undo that fix?

level: middleimportance: should knowfreq 41%

answer

  1. it matches a coordinate, not a policy
  2. the day someone bumps the intermediate
  3. renamed, rerouted, or bundled
  4. nothing errors, the build stays green
  5. assert the resolved graph in CI

basics

~20 s

Upgrading the intermediate library. An exclusion is written against a specific coordinate on a specific path, so when that library changes how it pulls the package, the exclusion stops matching, the vulnerable version returns, and nothing in the build fails.

solid answer

~50 s

The exclusion is not a policy, it is a pattern match against a coordinate on a path through the graph. The day someone bumps the intermediate library, that path can change: the package arrives under a renamed coordinate, or through a second path I never excluded, or the intermediate now bundles it. The exclusion quietly matches nothing, my direct declaration may no longer be the winning version, and the build stays green because nothing about this is an error. That is why I treat this kind of remediation as decaying by default. I make it durable with three things: an assertion in CI on the resolved graph — the fixed version present, the vulnerable one absent — so a regression breaks the build instead of a quarterly report; a comment and a tracking issue recording why the workaround exists and what removes it; and continuous scanning of the built artifact, not just the manifest, so a reintroduction is caught even if the assertion is written too narrowly.

go deeper

for a junior

Know that excluding a package and declaring your own version is a workaround, not a permanent fix, and that it can stop working when an unrelated dependency is upgraded.

for a middle

Explain the mechanism of the decay: the exclusion matches a coordinate on a path, and a bump to the intermediate can change the path, the coordinate, or bundle the code where no exclusion reaches.

for a senior

Demonstrate that you convert a silent regression into a failing build with an assertion on the resolved graph, and that every workaround ships with an owner, a reason and a removal trigger.

for a principal

Own the aggregate: a rising count of these workarounds is evidence about which dependencies your organisation should stop taking, and it belongs in a dependency-strategy conversation rather than in per-service tickets.

## Why this remediation decays Excluding a vulnerable transitive package and declaring the fixed version yourself is a legitimate and common answer to a blocked upgrade. It has a property that overrides and version bumps do not: **it depends on the shape of the graph staying the same.** An exclusion says, in effect, "when resolving *this* requirement, do not follow the edge to *that* coordinate." Both halves are brittle: - **The path can change.** The intermediate library is upgraded and now reaches the package through a different requirement, or a second direct dependency starts pulling it too. Your exclusion still matches its original edge; the new edge is unexcluded. - **The coordinate can change.** Packages get renamed, split, republished under a new namespace or org, or absorbed into an umbrella package. The excluded name no longer appears, so the exclusion matches nothing — and the vulnerable code arrives under the new name. - **The package can be bundled.** If the intermediate vendors or bundles a copy of the library rather than declaring it, no exclusion in the world removes it, and manifest-level scanning cannot see it. - **Your direct declaration can go stale.** You pinned the fixed version at the time. Months later the graph would have brought something newer, and your declaration is now the thing holding the version *down* — the exact failure mode you were trying to fix, with your name on it. The common thread is that **none of these produce an error.** The build succeeds, the tests pass, and the only signal is a scanner finding that may reappear on a cadence nobody reads. ## The second failure mode: compatibility drift There is also a runtime risk that grows over time. When you declare the fixed version directly, the intermediate library is now running against a dependency version it never declared. On the day you do it, the delta is small and you have tested it. Six months and four bumps later, the delta may be much larger, and nobody re-evaluated it because the workaround has become invisible infrastructure. A workaround with no owner and no review date turns into unexamined divergence. ## Making it durable Treat the workaround as a temporary control with a lifecycle, not as a config line: 1. **Assert it in CI.** Add a check on the *resolved* dependency graph: the fixed version present, the vulnerable version absent, on every build. This converts a silent regression into a failing pipeline at the moment the intermediate is bumped — which is exactly when a human is available to think about it. This is the single highest-value step, and it is the one candidates most often omit. 2. **Scan the built artifact, not only the manifest.** A narrowly written assertion can miss a renamed coordinate or a bundled copy. Composition analysis over what actually ships catches reintroduction from a direction your assertion did not anticipate. 3. **Record why it exists.** A comment next to the exclusion and a linked tracking issue naming the upstream constraint that forced it. Without this, the next engineer sees an unexplained exclusion, cannot tell whether it is load-bearing, and either deletes it or — worse — copies the pattern. 4. **Give it a removal trigger, not a date.** The trigger is a fact about the world: "remove this when the intermediate declares a version allowing the fix." Watch for that release, then delete the exclusion and the direct declaration together. A calendar date generates a ticket nobody can action; a trigger tied to an upstream release can be checked automatically. 5. **Keep the count small and visible.** Every one of these is divergence you maintain. If the list is growing, that is a signal about your dependency choices, not just about one advisory. ## What interviewers listen for The weak answer is "we excluded it, so it is fixed." The strong answer names the regression path — a bump to the intermediate — states that it is silent, and pairs the workaround with a CI assertion and a removal trigger. It also treats the workaround as *temporary by construction*, with the upstream change as the thing that actually ends it. Candidates who have lived this can usually describe the specific moment they discovered a fix had quietly stopped applying, which is the most convincing answer available.

  • What exactly would you assert in CI, and why the resolved graph rather than the manifest?
    Two assertions on the graph the build actually resolved: the fixed version is present, and the vulnerable version appears nowhere. The manifest only records intent — it says what I asked for, not what the resolver produced, so an override that silently failed to apply still reads clean there. Asserting on the resolved output means the check fails at the moment the intermediate is bumped, in the pull request that caused it.
  • When would you prefer an override over excluding and declaring the package yourself?
    When I do not want to become the declaring owner of a package I have no business owning. An override keeps the dependency where it belongs, in the intermediate's requirements, and states only the version floor. Excluding and declaring is better when I want the package visible in my own manifest — for instance when several of my services need to converge on it deliberately, or when the exclusion also has to remove an unwanted second copy.
  • How do you stop these workarounds from accumulating across an estate?
    Make them countable and expiring. Each one carries a tracking issue linked to the upstream constraint that caused it, and I report the total as a number that is supposed to trend down. A growing list is a signal about dependency choice — usually one or two intermediates generating most of the entries — and that is an argument for replacing or contributing to those specific libraries rather than for more exclusions.

saying these in an interview costs you the question

  • Treats an exclusion as permanent once it is merged
  • Assumes the build will fail if the exclusion stops matching
  • Never records why the workaround exists
  • Scans only the manifest, never the built artifact
  • Leaves the pinned fixed version to go stale as a new blocker

context