Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug?]: yarn npm audit --recursive produces incomplete output (yarn 4) #5781

Closed
1 task
nocive opened this issue Oct 10, 2023 · 11 comments
Closed
1 task

[Bug?]: yarn npm audit --recursive produces incomplete output (yarn 4) #5781

nocive opened this issue Oct 10, 2023 · 11 comments
Labels
bug Something isn't working external bug This issue highlights a bug in another project

Comments

@nocive
Copy link

nocive commented Oct 10, 2023

Self-service

  • I'd be willing to implement a fix

Describe the bug

I have a an application with a dependency tree that causes issues with yarn npm audit --recursive.

For yarn 3.x it errors out with a "bad request" error which I managed to narrow down to #4117 and, following the thread's advice, switched to yarn4 (rc53 at the time of writing).

With yarn 4.x I no longer receive the "bad request" error but receive incomplete output, the json structure is totally different than what would be expected (no summary is displayed, the advisory information is incomplete, etc):

The incomplete output returned by yarn4

$ yarn --version
4.0.0-rc.53

$ yarn npm audit -AR --json --no-deprecations
{"postcss":[{"id":1094280,"url":"https://github.com/advisories/GHSA-7fh5-64p2-3v2j","title":"PostCSS line return parsing error","severity":"moderate","vulnerable_versions":"<8.4.31","cwe":["CWE-144"],"cvss":{"score":0,"vectorString":null}}]}

An example of the expected output for yarn3

$ yarn npm audit -AR --json

{"actions":[],"advisories":{"1094280":{"findings":[{"version":"7.0.39","paths":["react-scripts>postcss","depcheck>@vue/compiler-sfc>postcss","react-scripts>css-loader>icss-utils>postcss","react-scripts>css-loader>postcss-modules-local-by-default>icss-utils>postcss","react-scripts>css-minimizer-webpack-plugin>cssnano>cssnano-preset-default>css-declaration-sorter>postcss","react-scripts>css-minimizer-webpack-plugin>cssnano>cssnano-preset-default>postcss-merge-rules>cssnano-utils>postcss"]}],"metadata":null,"vulnerable_versions":"<8.4.31","module_name":"postcss","severity":"moderate","github_advisory_id":"GHSA-7fh5-64p2-3v2j","cves":["CVE-2023-44270"],"access":"public","patched_versions":">=8.4.31","cvss":{"score":0,"vectorString":null},"updated":"2023-10-09T20:06:54.000Z","recommendation":"Upgrade to version 8.4.31 or later","cwe":["CWE-144"],"found_by":null,"deleted":null,"id":1094280,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2023-44270\n- https://github.com/postcss/postcss/commit/58cc860b4c1707510c9cd1bc1fa30b423a9ad6c5\n- https://github.com/postcss/postcss/blob/main/lib/tokenize.js#L25\n- https://github.com/postcss/postcss/releases/tag/8.4.31\n- https://github.com/advisories/GHSA-7fh5-64p2-3v2j","created":"2023-09-30T00:31:10.000Z","reported_by":null,"title":"PostCSS line return parsing error","npm_advisory_id":null,"overview":"An issue was discovered in PostCSS before 8.4.31. It affects linters using PostCSS to parse external Cascading Style Sheets (CSS). There may be `\\r` discrepancies, as demonstrated by `@font-face{ font:(\\r/*);}` in a rule.\n\nThis vulnerability affects linters using PostCSS to parse external untrusted CSS. An attacker can prepare CSS in such a way that it will contains parts parsed by PostCSS as a CSS comment. After processing by PostCSS, it will be included in the PostCSS output in CSS nodes (rules, properties) despite being originally included in a comment.","url":"https://github.com/advisories/GHSA-7fh5-64p2-3v2j"}},"muted":[],"metadata":{"vulnerabilities":{"info":0,"low":0,"moderate":6,"high":0,"critical":0},"dependencies":1215,"devDependencies":0,"optionalDependencies":0,"totalDependencies":1215}}

To reproduce

dependencies (yarn info --json --name-only -A | jq -r .):

@emotion/css@npm:11.11.2
@emotion/react@npm:11.11.1
@emotion/styled@npm:11.11.0
@sentry/browser@npm:7.72.0
@testing-library/jest-dom@npm:6.1.3
@testing-library/react@npm:14.0.0
@types/jest@npm:29.5.5
@types/node@npm:20.7.0
@types/react-dom@npm:18.2.7
audit-ci@npm:6.6.1
depcheck@npm:1.4.6
eslint-config-react-app@npm:7.0.1
eslint@npm:8.50.0
html-webpack-plugin@npm:5.5.3
i18next@npm:23.5.1
msw@npm:1.3.1
prettier@npm:3.0.3
process@npm:0.11.10
react-app-polyfill@npm:3.0.0
react-app-rewired@npm:2.2.1
react-dom@npm:18.2.0
react-i18next@npm:13.2.2
react-router-dom@npm:6.16.0
react-scripts@npm:5.0.1
react@npm:18.2.0
subscription-preferences-ui@workspace:.
ts-loader@npm:9.4.4
typescript@patch:typescript@npm%3A5.2.2#optional!builtin<compat/typescript>::version=5.2.2&hash=f3b441
whatwg-fetch@npm:3.6.19

Environment

System:
    OS: Linux 6.1 Manjaro Linux
    CPU: (12) x64 13th Gen Intel(R) Core(TM) i7-1365U
  Binaries:
    Node: 20.6.1 - /tmp/xfs-ea368b29/node
    Yarn: 4.0.0-rc.53 - /tmp/xfs-ea368b29/yarn

Additional context

No response

@nocive nocive added the bug Something isn't working label Oct 10, 2023
@arcanis
Copy link
Member

arcanis commented Oct 10, 2023

I'm afraid that more a bug report for the npm registry. The "modern" npm audit endpoint sends fewer data than the "legacy" one (but it's more stable, whereas the legacy one requires to send a full dependency tree that doesn't work well w/ Yarn).

@arcanis arcanis added the external bug This issue highlights a bug in another project label Oct 10, 2023
@arcanis
Copy link
Member

arcanis commented Oct 10, 2023

As an example, this is everything we get in response:

❯ curl -X POST https://registry.npmjs.org/-/npm/v1/security/advisories/bulk -H 'Content-Type: application/json' -d '{"postcss":["8.4.30"]}'

{"postcss":[{"id":1094280,"url":"https://github.com/advisories/GHSA-7fh5-64p2-3v2j","title":"PostCSS line return parsing error","severity":"moderate","vulnerable_versions":"<8.4.31","cwe":["CWE-144"],"cvss":{"score":0,"vectorString":null}}]}

@nocive
Copy link
Author

nocive commented Oct 10, 2023

@arcanis thanks for the quick reply!

Just for my own understanding, is this discrepancy something that yarn plans to deal with internally or do you suggest developers implement userland tooling to deal with it (at least for the time being)?

We've quickly put together something that could help us cope with this issue for the time being, but thought it would be worth getting some feedback from upstream before committing to such a hacky solution! 🙈

audit.js
#!/usr/bin/env node

const util = require('util')
const exec = require('child_process').exec

// --no-deprecations is only supported by yarn 4.x
cmd = 'yarn npm audit --json --recursive --no-deprecations'
console.log(`Execing: ${cmd}`)

exec(cmd, function (err, stdout, stderr) {
  vulnerabilities = {}
  metadata = {}

  const addVulnerability = (key, data) => {
      vulnerabilities[key] = {
        id: data.id,
        url: data.url,
        title: data.title,
        severity: data.severity,
        vulnerable_versions: data.vulnerable_versions,
        github_advisory_id: data.github_advisory_id,
        cves: data.cves,
      }
  }

  console.log(`Exit code: ${err != null ? err.code : 0}`)

  try {
    parsed = JSON.parse(stdout)
  } catch (e) {
    console.log(`Error parsing returned json from command: ${e}`)
    console.log(stdout)
    process.exit(1)
  }

  if (err !== null) {
    // The full advisory information is not available when an error happens.
    // This looks like a bug in the data returned by npm registry
    // which we have to work around for now.
    for (const [name, data] of Object.entries(parsed)) {
      if (typeof data[0] === 'undefined' || typeof data[0].id === 'undefined') {
        console.log('debug: ignored invalid entry')
        continue
      }

      console.log(`debug: found ${name} - ${data[0].url}`)
      addVulnerability(name, data[0])
    }
  } else {
    for (const [name, data] of Object.entries(parsed.advisories || {})) {
      console.log(`debug: found ${name} - ${data.url}`)
      addVulnerability(name, data)
    }
    metadata = parsed.metadata
  }

  found = Object.keys(vulnerabilities).length !== 0

  if (found) {
    console.log('\nVulnerabilities')
    console.log('-----------------')
    console.log(util.inspect(vulnerabilities, {showHidden: false, depth: null, colors: true}))
  }
  if (metadata) {
    console.log('\nSummary')
    console.log('-----------------')
    console.log(util.inspect(metadata, {showHidden: false, depth: null, colors: true}))
  }

  if (!found) {
    console.log('\nHooray! No vulnerabilities found!')
  } else {
    console.log('\nUh-oh! Found vulnerabilities, check the output above for details.')
  }

  // We purposely return 0 even in the even of error !== null because that's a valid state when
  // the advisory information is not returned by `yarn npm audit`.
  process.exit(found ? 2 : 0)
})

@arcanis
Copy link
Member

arcanis commented Oct 10, 2023

I think the best we could do would be to change this line to be if (this.json || hasError) { instead, which prints a format similar to what other Yarn commands output with --json - does that match what you have in mind?

{"value":"@actions/core","children":{"ID":1089254,"Issue":"@actions/core has Delimiter Injection Vulnerability in exportVariable","URL":"https://github.com/advisories/GHSA-7r3h-m5j6-3q42","Severity":"moderate","Vulnerable Versions":"<=1.9.0","Tree Versions":["1.2.6"],"Dependents":["@arcanis/sherlock@npm:2.0.3"]}}
{"value":"@babel/plugin-proposal-async-generator-functions","children":{"ID":"@babel/plugin-proposal-async-generator-functions (deprecation)","Issue":"This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-async-generator-functions instead.","Severity":"moderate","Vulnerable Versions":"7.19.1","Tree Versions":["7.19.1"],"Dependents":["@babel/preset-env@virtual:e470d99b1e4fdf4c5db5d090ff5472cdeba0404b7ffd31cd2efab3493dd184c67bc45f60c2ef1c040e2c41afe38c6280bffc5df2fbe3aefaa2b6eacf685ab07c#npm:7.19.1"]}}
{"value":"@babel/plugin-proposal-class-properties","children":{"ID":"@babel/plugin-proposal-class-properties (deprecation)","Issue":"This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.","Severity":"moderate","Vulnerable Versions":"7.18.6","Tree Versions":["7.18.6"],"Dependents":["@babel/preset-env@virtual:e470d99b1e4fdf4c5db5d090ff5472cdeba0404b7ffd31cd2efab3493dd184c67bc45f60c2ef1c040e2c41afe38c6280bffc5df2fbe3aefaa2b6eacf685ab07c#npm:7.19.1"]}}

(You can run yarn set version from sources --no-minify and directly update the resulting .yarn/releases/*.js file to check)

@nocive
Copy link
Author

nocive commented Oct 10, 2023

I think for external tool integrations it would be great to always have the same expected structure returned, but I'm in no position to assess who's responsibility it is to "fix that" :)

In our usage case, we are using audit-ci together with yarn3 and the new output breaks the integration with some nasty side effects as it returns 0 even when vulnerabilities are found.

yarn3

$ yarn npm audit -AR --json; echo $?
{"actions":[],"advisories":{"1094280":{"findings":[{"version":"7.0.39","paths":["react-scripts>postcss","depcheck>@vue/compiler-sfc>postcss","react-scripts>css-loader>icss-utils>postcss","react-scripts>css-loader>postcss-modules-local-by-default>icss-utils>postcss","react-scripts>css-minimizer-webpack-plugin>cssnano>cssnano-preset-default>css-declaration-sorter>postcss","react-scripts>css-minimizer-webpack-plugin>cssnano>cssnano-preset-default>postcss-merge-rules>cssnano-utils>postcss"]}],"metadata":null,"vulnerable_versions":"<8.4.31","module_name":"postcss","severity":"moderate","github_advisory_id":"GHSA-7fh5-64p2-3v2j","cves":["CVE-2023-44270"],"access":"public","patched_versions":">=8.4.31","cvss":{"score":0,"vectorString":null},"updated":"2023-10-09T20:06:54.000Z","recommendation":"Upgrade to version 8.4.31 or later","cwe":["CWE-144"],"found_by":null,"deleted":null,"id":1094280,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2023-44270\n- https://github.com/postcss/postcss/commit/58cc860b4c1707510c9cd1bc1fa30b423a9ad6c5\n- https://github.com/postcss/postcss/blob/main/lib/tokenize.js#L25\n- https://github.com/postcss/postcss/releases/tag/8.4.31\n- https://github.com/advisories/GHSA-7fh5-64p2-3v2j","created":"2023-09-30T00:31:10.000Z","reported_by":null,"title":"PostCSS line return parsing error","npm_advisory_id":null,"overview":"An issue was discovered in PostCSS before 8.4.31. It affects linters using PostCSS to parse external Cascading Style Sheets (CSS). There may be `\\r` discrepancies, as demonstrated by `@font-face{ font:(\\r/*);}` in a rule.\n\nThis vulnerability affects linters using PostCSS to parse external untrusted CSS. An attacker can prepare CSS in such a way that it will contains parts parsed by PostCSS as a CSS comment. After processing by PostCSS, it will be included in the PostCSS output in CSS nodes (rules, properties) despite being originally included in a comment.","url":"https://github.com/advisories/GHSA-7fh5-64p2-3v2j"}},"muted":[],"metadata":{"vulnerabilities":{"info":0,"low":0,"moderate":6,"high":0,"critical":0},"dependencies":1215,"devDependencies":0,"optionalDependencies":0,"totalDependencies":1215}}
0
$ yarn audit; echo $?
audit-ci version: 6.6.1
Yarn Berry audit report results:
{
  "vulnerabilities": {
    "info": 0,
    "low": 0,
    "moderate": 6,
    "high": 0,
    "critical": 0
  },
  "dependencies": 1215,
  "devDependencies": 0,
  "optionalDependencies": 0,
  "totalDependencies": 1215
}
Found vulnerable advisory paths:
GHSA-7fh5-64p2-3v2j|react-scripts>postcss
GHSA-7fh5-64p2-3v2j|depcheck>@vue/compiler-sfc>postcss
GHSA-7fh5-64p2-3v2j|react-scripts>css-loader>icss-utils>postcss
GHSA-7fh5-64p2-3v2j|react-scripts>css-loader>postcss-modules-local-by-default>icss-utils>postcss
GHSA-7fh5-64p2-3v2j|react-scripts>css-minimizer-webpack-plugin>cssnano>cssnano-preset-default>css-declaration-sorter>postcss
GHSA-7fh5-64p2-3v2j|react-scripts>css-minimizer-webpack-plugin>cssnano>cssnano-preset-default>postcss-merge-rules>cssnano-utils>postcss
Failed security audit due to moderate vulnerabilities.
Vulnerable advisories are:
https://github.com/advisories/GHSA-7fh5-64p2-3v2j
Exiting...
1

yarn4 (with proposed change)

$ yarn npm audit -AR --json; echo $?
{"value":"@babel/plugin-proposal-class-properties","children":{"ID":"@babel/plugin-proposal-class-properties (deprecation)","Issue":"This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.","Severity":"moderate","Vulnerable Versions":"7.18.6","Tree Versions":["7.18.6"],"Dependents":["babel-preset-react-app@npm:10.0.1"]}}
{"value":"@babel/plugin-proposal-nullish-coalescing-operator","children":{"ID":"@babel/plugin-proposal-nullish-coalescing-operator (deprecation)","Issue":"This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead.","Severity":"moderate","Vulnerable Versions":"7.18.6","Tree Versions":["7.18.6"],"Dependents":["babel-preset-react-app@npm:10.0.1"]}}
{"value":"@babel/plugin-proposal-numeric-separator","children":{"ID":"@babel/plugin-proposal-numeric-separator (deprecation)","Issue":"This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead.","Severity":"moderate","Vulnerable Versions":"7.18.6","Tree Versions":["7.18.6"],"Dependents":["babel-preset-react-app@npm:10.0.1"]}}
{"value":"@babel/plugin-proposal-optional-chaining","children":{"ID":"@babel/plugin-proposal-optional-chaining (deprecation)","Issue":"This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.","Severity":"moderate","Vulnerable Versions":"7.21.0","Tree Versions":["7.21.0"],"Dependents":["babel-preset-react-app@npm:10.0.1"]}}
{"value":"@babel/plugin-proposal-private-methods","children":{"ID":"@babel/plugin-proposal-private-methods (deprecation)","Issue":"This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead.","Severity":"moderate","Vulnerable Versions":"7.18.6","Tree Versions":["7.18.6"],"Dependents":["babel-preset-react-app@npm:10.0.1"]}}
{"value":"@babel/plugin-proposal-private-property-in-object","children":{"ID":"@babel/plugin-proposal-private-property-in-object (deprecation)","Issue":"This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-property-in-object instead.","Severity":"moderate","Vulnerable Versions":"7.21.11","Tree Versions":["7.21.11"],"Dependents":["babel-preset-react-app@npm:10.0.1"]}}
{"value":"rollup-plugin-terser","children":{"ID":"rollup-plugin-terser (deprecation)","Issue":"This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser","Severity":"moderate","Vulnerable Versions":"7.0.2","Tree Versions":["7.0.2"],"Dependents":["workbox-build@npm:6.6.0"]}}
{"value":"sourcemap-codec","children":{"ID":"sourcemap-codec (deprecation)","Issue":"Please use @jridgewell/sourcemap-codec instead","Severity":"moderate","Vulnerable Versions":"1.4.8","Tree Versions":["1.4.8"],"Dependents":["magic-string@npm:0.25.9"]}}
{"value":"stable","children":{"ID":"stable (deprecation)","Issue":"Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility","Severity":"moderate","Vulnerable Versions":"0.1.8","Tree Versions":["0.1.8"],"Dependents":["svgo@npm:1.3.2"]}}
{"value":"svgo","children":{"ID":"svgo (deprecation)","Issue":"This SVGO version is no longer supported. Upgrade to v2.x.x.","Severity":"moderate","Vulnerable Versions":"1.3.2","Tree Versions":["1.3.2"],"Dependents":["@svgr/plugin-svgo@npm:5.5.0"]}}
{"value":"w3c-hr-time","children":{"ID":"w3c-hr-time (deprecation)","Issue":"Use your platform's native performance.now() and performance.timeOrigin.","Severity":"moderate","Vulnerable Versions":"1.0.2","Tree Versions":["1.0.2"],"Dependents":["jsdom@virtual:de33b7f3967bc3d4e1b65a36474e1f3fe3baee525bcc768364cb7d2a670e909d0520585e1e3eefbe6e1e8973b0a9471bddf92500148da425f9a867a5090771fb#npm:16.7.0"]}}
{"value":"workbox-cacheable-response","children":{"ID":"workbox-cacheable-response (deprecation)","Issue":"workbox-background-sync@6.6.0","Severity":"moderate","Vulnerable Versions":"6.6.0","Tree Versions":["6.6.0"],"Dependents":["workbox-build@npm:6.6.0"]}}
1
$ yarn audit; echo $?
audit-ci version: 6.6.1
Yarn Berry audit report results:
undefined
Passed yarn security audit.
0

@nocive
Copy link
Author

nocive commented Oct 27, 2023

We are switching to yarn4 and ditching audit-ci.
Ended up keeping the wrapper script just to get prettier and colorized output, would be great to actually have that functionality with yarn npm audit.

I think we can close this ticket though since the output is the expected one, it's just radically different from what yarn3 produces, which might make the migration path harder for some people.

For those who might be interested:

#!/usr/bin/env node

const util = require('util')
const exec = require('child_process').exec

cmd = 'yarn npm audit --json --recursive --no-deprecations'
console.log(`Execing: ${cmd}`)

exec(cmd, { timeout: 15000 }, function (err, stdout, stderr) {
  let vulnerabilities = {}
  let found = 0
  let parsed

  const useColors = (process.env.NO_COLOR || "0") === "0"

  const addVulnerability = (key, data) => {
      vulnerabilities[key] = data
  }

  console.log(`Exit code: ${err != null ? err.code : 0}`)

  try {
    parsed = JSON.parse(stdout)
  } catch (e) {
    console.log(`Error parsing returned json from command: ${e}`)
    console.log(stdout)
    process.exit(1)
  }

  if (err !== null) {
    for (const [name, data] of Object.entries(parsed)) {
      if (typeof data[0] === 'undefined' || typeof data[0].id === 'undefined') {
        console.log('debug: ignored invalid entry (possibly a deprecation)')
        continue
      }

      addVulnerability(name, data[0])
    }
  }

  found = Object.keys(vulnerabilities).length

  if (found !== 0) {
    console.log('\nyarn npm audit report')
    console.log('---------------------------')
    console.log(util.inspect(vulnerabilities, {showHidden: false, depth: null, colors: useColors}))
  }

  if (found === 0) {
    console.log('\nHooray! No vulnerabilities found!')
  } else {
    console.log(`\nUh-oh! Found ${found} vulnerabilities, check the output above for details.`)
  }

  process.exit(found === 0 ? 0 : 2)
})

@nocive nocive closed this as completed Oct 27, 2023
@arcanis
Copy link
Member

arcanis commented Oct 27, 2023

Note that there's a bug in the output which I fixed in #5833. The format will be slightly different in 4.0.1 (will release in a couple of hours). Can you double-check it works well enough for you? You can use this command line to try the master build:

yarn set version from sources

@nocive
Copy link
Author

nocive commented Oct 27, 2023

@arcanis

That sadly breaks because I'm expecting valid json and now nothing is returned if no vulnerabilities are found.
Would it be possible to always return valid json and keep the {} empty return as previously?

@arcanis
Copy link
Member

arcanis commented Oct 27, 2023

The --json flag (for yarn npm audit but also all other commands) is supposed to return an NDJSON stream, so we can't do that. NDJSON is basically a line-separated stream of JSON objects (\n isn't valid in JSON strings, so it's a convenient way to send data over time).

@nocive
Copy link
Author

nocive commented Oct 27, 2023

I see, that makes sense. Thanks.

No worries, I'll adapt my script accordingly.
It should be fine as long as the 0 return code can be used reliably, I'll skip parsing the output entirely if that's the case.

@nocive
Copy link
Author

nocive commented Oct 29, 2023

With yarn 4.0.1, I'm able to narrow down my wrapper to an inline script:

yarn npm audit -R --json --no-deprecations | node -r util -e "i = fs.readFileSync(0, 'utf-8'); try { o = JSON.parse(i || '{}'); } catch (e) { console.log(e); process.exit(1); }; console.log(util.inspect(o, { showHidden: false, depth: null, colors: (process.env.NO_COLOR || '0') !== '1' })); process.exit(Object.keys(o).length === 0 ? 0 : 2);"

EDIT: or just use yarn npm audit --recursive --no-deprecations natively, since the error code is now reliable and the output nicer, thanks @arcanis :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working external bug This issue highlights a bug in another project
Projects
None yet
Development

No branches or pull requests

2 participants