We recently ran into a minor problem that got me frustrated. Everything we run happens inside containers built with Docker, including our frontend. For security, we use Aqua’s Trivy tool to scan any outgoing containers before they’re pushed to our repository, where they could then be deployed. Typically, these are easy enough to fix. We read the scan report, up the version with a patch, update our package configuration, and the build passes the security scan. This time, though, I ran into a problem with our frontend that wasn’t so easy to resolve.

The Problem

A recent CVE emerged detailing a potential issue with the SSRI package. Our frontend code uses this indirectly. It’s part of the process that bakes out the files we ultimately serve our users. While technically not deployed, the idea of running code anywhere in our system — even if only during a build, and even a relatively minor bug — isn’t something we let go. It needed to be patched.

Unfortunately, the dependency was buried in Nuxt.js, the Vue.js-JavaScript framework we use. The naïve path to resolving the vulnerability thus wasn’t an option. Just as we don’t want to run code with known vulnerabilities, we don’t want to run the bleeding-edge unreleased versions of Nuxt.js that used newer packages.

Coming to an Answer

Eventually, someone pointed out that Yarn, our JavaScript package manager, has a clever feature called selective dependency resolution designed for exactly this situation.

The idea is simple: by specifying a new dependency resolution, you can override the explicit dependency in a package. In our case, to make sure you’re running a patched version of an underlying package. Without casting too many aspersions, it’s good this exists given at least a few well-known débâcles within the universe of JavaScript packages.

In our case, the culprit was the cacache package. (Another lesson: the yarn why command does not accept version hints!) The latest stable release of Nuxt.js uses a version of Webpack that, if you dig down enough, relies on cacache version 12.0.2, which uses SSRI 6.0.2. The solution was as simple as adding a dependency override for that package as part of our package.json file:

  "resolutions": {
    "cacache": "^15.0.6"
  },

That is, by including this in our package specification, we force anything that might use any version of cacache to use 15.0.6, the latest one. This is easy enough to see by inspecting the updated yarn.lock, which then looks like this:

cacache@^12.0.2, cacache@^15.0.5, cacache@^15.0.6:
  version "15.0.6"
  resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.0.6.tgz#65a8c580fda15b59150fb76bf3f3a8e45d583099"
  integrity sha512-g1WYDMct/jzW+JdWEyjaX2zoBkZ6ZT9VpOyp2I/VMtDsNLffNat3kqPFfi1eDRSK9/SuKGyORDHcQMcPF8sQ/w==
  dependencies:
    "@npmcli/move-file" "^1.0.1"
    chownr "^2.0.0"
    fs-minipass "^2.0.0"
    glob "^7.1.4"
    infer-owner "^1.0.4"
    lru-cache "^6.0.0"
    minipass "^3.1.1"
    minipass-collect "^1.0.2"
    minipass-flush "^1.0.5"
    minipass-pipeline "^1.2.2"
    mkdirp "^1.0.3"
    p-map "^4.0.0"
    promise-inflight "^1.0.1"
    rimraf "^3.0.2"
    ssri "^8.0.1"
    tar "^6.0.2"
    unique-filename "^1.1.1"

After a commit and a push, our frontend passed the container check. No more build errors and, more importantly, our frontend code now has that security issue fixed.

Maybe this isn’t that revelatory. As a non-expert, I didn’t think the solution was at all obvious, and this certainly didn’t emerge from my attempts at finding an answer. It seemed only right to share.