UNPKG

owasp-dependency-check

Version:

A Node.js wrapper for the OWASP dependency-check-cli.

3 lines (2 loc) 10.4 kB
#!/usr/bin/env node import{Maybe as e,MaybeAsync as n}from"purify-ts";import t from"node:path";import o from"node:os";import{white as r,green as i}from"ansis";import*as a from"yup";import s,{ProxyAgent as c}from"undici";import p from"node:fs";import d from"extract-zip";import l from"cross-spawn";import{program as u,Option as f,InvalidArgumentError as h}from"@commander-js/extra-typings";function m(e){if(e instanceof Error)return e;let n;try{n=JSON.stringify(e)}catch{n="[Unable to stringify the thrown value]"}return new Error(`This value was thrown as is, not through an Error: ${n}`)}const y=/(-\S*(?:key|token|pass)[^\s=]*(?:=| +))(\S*)/gi;function g(e){return e.replace(y,"$1<secret value>")}function w(e,n){return e.toEither(new Error(n)).unsafeCoerce()}class k{prefix;constructor(e){this.prefix=e}info(...e){console.log([r.bgGreen` ${this.prefix}: `,...e].join(" "))}warn(...e){console.log([r.bgYellow` ${this.prefix} WARNING: `,...e].join(" "))}error(...e){console.error([r.bgRed` ${this.prefix} ERROR: `,...e].join(" "))}}function b(e){return new k(e)}const v="owasp-dependency-check";function S(e,t){return n(()=>s.fetch(e,t)).filter(e=>e.ok)}function O(e,n){n.info(`Cleaning directory ${e}`),A(e,!0,n),p.mkdirSync(e,{recursive:!0})}function A(e,n,t){try{p.rmSync(e,{force:!0,recursive:n}),t.info(`Deleted "${e}"`)}catch(n){const o=m(n);t.warn(`Could not delete "${e}". Reason: ${o}`)}}function I(...n){const o=t.resolve(...n);return e.encase(()=>p.statSync(o)).filter(e=>e.isFile()).map(()=>t.resolve(o))}const E=b(`${v} Installation`),$=/^dependency-check-\d+\.\d+\.\d+-release\.zip$/,x="win32"===o.platform();function D(e){return I(e,"dependency-check","bin","dependency-check."+(x?"bat":"sh"))}const N=a.object({tag_name:a.string().required(),assets:a.array().of(a.object({name:a.string().required(),browser_download_url:a.string().url().required()})).required()});function P(e,t,o){const r=e.mapOrDefault(e=>"https://api.github.com/repos/dependency-check/DependencyCheck/releases/tags/"+e,"https://api.github.com/repos/dependency-check/DependencyCheck/releases/latest");return E.info(`Fetching release information from ${r}`),function(e,t){return S(e,t).chain(e=>n(()=>e.json()))}(r,_(t,o)).chain(e=>function(e){return n(()=>N.validate(e,{strict:!0}))}(e))}async function j(o,r,i,a){E.info(`Downloading dependency check from ${o}...`);const s=t.resolve(i,r),c=await function(t,o,r){return S(t,o).chain(t=>n.liftMaybe(e.fromNullable(t.body))).chain(e=>n(()=>p.promises.writeFile(r,e))).map(()=>r)}(o,_(a,e.empty()),s);return w(c,`Download failed from ${o}`)}function _(e,n){return function(e,n){const t={};return e.ifJust(e=>{t.dispatcher=new c(e.toString())}),n.ifJust(e=>{t.headers=e}),t}(e,n.map(e=>({Authorization:`Bearer ${e}`})))}async function C(n,t,o){E.info(`Installing dependency check ${n.tag_name}...`),O(t,E);const r=w(function(n){return e.fromNullable(n.assets.find(e=>$.test(e.name)))}(n),`Could not find asset for version ${n.tag_name}`),i=await j(r.browser_download_url,r.name,t,o);return await async function(e,n,t,o){await d(e,{dir:n}),A(e,!1,o)}(i,t,0,E),w(D(t),`Could not find Dependency-Check Core executable in ${t}`)}function J(e,n,t){let o=0;n?0!=e&&t.warn(`Ignoring error code ${e.toString()}`):o=e,t.info(`Exit with code ${o.toString()}`),process.exitCode=o}function T(e,n,t,o){n.ifJust(n=>{t&&process.env[e]&&(n=`${process.env[e]} ${n}`),o.info(`Setting environment variable ${e} to "${g(n)}"`),process.env[e]=n})}const U=b(`${v} Analysis`);function R(e,n){const t=`${e} ${g(n.join(" "))}`;U.info("Running command:",t)}function V(e,n,t,o,r,a){return U.info("Dependency-Check Core path:",e),O(t,U),T("JAVACMD",a,!1,U),function(e){const n=["--version"];R(e,n);const t=l.sync(e,n,{shell:!1,encoding:"utf-8"});if(t.error)throw t.error;if(null===t.status)throw new Error("Version check did not complete with status code.");if(0!==t.status)throw new Error(t.stderr);U.info(t.stdout.trimEnd())}(e),function(e,n,t,o){T("JAVA_OPTS",t.map(G),!0,U);const r={shell:!1,stdio:o?"ignore":"inherit"};R(e,n);const a=l.sync(e,n,r);if(a.error)throw a.error;if(null===a.status)throw new Error("Analysis did not complete with status code.");return U.info(i`Done.`),a.status}(e,n,o,r)}function G(e){let n=`-Dhttps.proxyHost=${e.hostname}`;return e.port&&(n+=` -Dhttps.proxyPort=${e.port}`),e.username&&(n+=` -Dhttps.proxyUser=${e.username}`),e.password&&(n+=` -Dhttps.proxyPassword=${e.password}`),n}const B=b(v),H=u.configureOutput({writeErr(e){B.error(e)}}).allowUnknownOption().name(v).description("A Node.js wrapper for the OWASP dependency-check-cli.").argument("[args...]","additional arguments that will be passed to the dependency-check-cli").optionsGroup("Installation options:").option("--bin <binary>","the directory the dependency-check-cli will be installed into","dependency-check-bin").option("--force-install","install the dependency-check-cli even if the version is already present (will be overwritten)").option("--keep-old-versions","do not remove old versions of the dependency-check-cli").option("--odc-version <version>","the version of the dependency-check-cli to install in the format: v1.2.3").addOption(new f("--github-token <token>","GitHub token to authenticate against API").env("GITHUB_TOKEN")).optionsGroup("Execution options:").addOption(new f("--owasp-bin <binary>","the path to a preinstalled dependency-check-cli binary (.sh or .bat file)").env("OWASP_BIN").argParser(F)).option("--hide-owasp-output","do not display the output of the dependency-check-cli binary").option("--ignore-errors","always exit with code 0").addOption(new f("--java-bin <binary>","the path to the Java binary").env("JAVACMD").argParser(F)).optionsGroup("Network options:").option("-p, --proxy <url>","the URL of a proxy server in the format: http(s)://[user]:[password]@<server>:[port]",function(n){return(t=n,e.encase(()=>new URL(t))).filter(e=>"http:"===e.protocol||"https:"===e.protocol).toEither(new h("The proxy URL is invalid.")).unsafeCoerce();var t}).optionsGroup("OWASP dependency-check-cli options:").option("-o, --out <directory>","the directory the generated reports will be written into","dependency-check-reports").option("-d, --data <directory>","the location of the data directory used to store persistent data",t.join(o.tmpdir(),"dependency-check-data")).option("-s, --scan <file...>","the lock files of package managers to scan",[]).option("-f, --format <format...>","the formats of the report to generate",["HTML","JSON"]).addOption(new f("--nvdApiKey <key>","NVD API key to authenticate against API").env("NVD_API_KEY")).addOption(new f("--ossIndexUsername <user>","Sonatype OSS Index username to authenticate against API").env("OSS_INDEX_USERNAME")).addOption(new f("--ossIndexPassword <pass>","Sonatype OSS Index password to authenticate against API").env("OSS_INDEX_PASSWORD")).addOption(new f("--project <name>","the name of the project to be scanned").env("PROJECT_NAME")).optionsGroup("General information:").version("1.0.0",void 0,`print the version of ${v}`).helpOption("-h, --help","display this help information").addHelpText("afterAll",'\nYou can also use any arguments supported by the dependency-check-cli, see: https://jeremylong.github.io/DependencyCheck/dependency-check-cli/arguments.html\n\nSome defaults are provided:\n- project Default: "name" from package.json in working directory\n- data Default: dependency-check-data directory in system temp folder\n- format Default: HTML and JSON\n- scan Default: package managers\' lock files in working directory (package-lock.json, yarn.lock, pnpm-lock.yaml) \n\nThe following environment variables are supported:\n- OWASP_BIN: path to a local installation of the dependency-check-cli\n- NVD_API_KET: personal NVD API key to authenticate against API\n- OSS_INDEX_USERNAME: Sonatype OSS Index username to authenticate against API\n- OSS_INDEX_PASSWORD: Sonatype OSS Index password to authenticate against API\n- GITHUB_TOKEN: personal GitHub token to authenticate against API\n- PROJECT_NAME: the name of the project being scanned\n- JAVACMD: path to a Java binary');const M=["package-lock.json","npm-shrinkwrap.json","yarn.lock","pnpm-lock.yaml"];function W(){const n=["--out",H.opts().out,"--data",H.opts().data,...H.args];e.fromNullable(H.opts().nvdApiKey).ifJust(e=>{n.push("--nvdApiKey",e)}),e.fromNullable(H.opts().ossIndexUsername).ifJust(e=>{n.push("--ossIndexUsername",e)}),e.fromNullable(H.opts().ossIndexPassword).ifJust(e=>{n.push("--ossIndexPassword",e)});const t=H.opts().scan;return t.length>0?t.forEach(e=>{n.push("--scan",e)}):M.forEach(e=>{!function(e,n){I(n).ifJust(n=>{B.info(`Found "${n}" and adding it to --scan argument.`),e.push("--scan",n)})}(n,e)}),n.push("--project",H.opts().project??function(){let e="Unknown Project";try{const n=p.readFileSync("package.json").toString();e=JSON.parse(n).name,B.info(`Found project name "${e}" in package.json`)}catch(e){const n=m(e);B.warn(n.message)}return e}()),H.opts().format.forEach(e=>{n.push("--format",e)}),n}function F(e){return I(e).toEither(new h(`The file "${e}" does not exist.`)).unsafeCoerce()}const K=b(v);let L=!1;async function q(){const n=(H.parse(),{hideOwaspOutput:!!H.opts().hideOwaspOutput,owaspBinary:e.fromNullable(H.opts().owaspBin),proxyUrl:e.fromNullable(H.opts().proxy),githubToken:e.fromNullable(H.opts().githubToken),outDir:H.opts().out,forceInstall:!!H.opts().forceInstall,odcVersion:e.fromNullable(H.opts().odcVersion),binDir:t.resolve(H.opts().bin),cmdArguments:W(),ignoreErrors:!!H.opts().ignoreErrors,keepOldVersions:!!H.opts().keepOldVersions,javaBinary:e.fromNullable(H.opts().javaBin)});L=n.ignoreErrors;let o=n.owaspBinary;if(o.isNothing()?o=e.of(await async function(e,n,o,r,i,a){const s=w(await P(n,o,r),"Could not fetch release from GitHub.");E.info(`Found release ${s.tag_name} on GitHub.`);const c=t.resolve(e,s.tag_name),p=D(c).filter(()=>!i);return p.isJust()?(E.info(`Using already installed dependency check at "${p.unsafeCoerce()}".`),p.unsafeCoerce()):(a||O(e,E),C(s,c,o))}(n.binDir,n.odcVersion,n.proxyUrl,n.githubToken,n.forceInstall,n.keepOldVersions)):K.info("Locally preinstalled (OWASP_BIN) Dependency-Check Core found."),o.isJust()){J(V(o.unsafeCoerce(),n.cmdArguments,n.outDir,n.proxyUrl,n.hideOwaspOutput,n.javaBinary),L,K)}}q().catch(e=>{const n=m(e);K.error(n.message),J(1,L,K)});export{q as run};