owasp-dependency-check
Version:
A Node.js wrapper for the OWASP dependency-check-cli.
3 lines (2 loc) • 9.24 kB
JavaScript
import e from"extract-zip";import o from"node:fs/promises";import{Maybe as t}from"purify-ts";import n from"path";import r from"fs";import i from"os";import{fetch as a,ProxyAgent as s}from"undici";import{white as c,green as p}from"ansis";import d from"cross-spawn";import{program as l,Option as h,InvalidArgumentError as u}from"@commander-js/extra-typings";async function f(e,t){t.info(`Cleaning directory ${e}`),await y(e,!0,t),await o.mkdir(e,{recursive:!0})}async function y(e,t,n){try{await o.rm(e,{force:!0,recursive:t}),n.info(`Deleted "${e}"`)}catch(o){const t=w(o);n.warn(`Could not delete "${e}". Reason: ${t}`)}}function w(e){if(e instanceof Error)return e;let o;try{o=JSON.stringify(e)}catch{o="[Unable to stringify the thrown value]"}return new Error(`This value was thrown as is, not through an Error: ${o}`)}const m=/(-\S*(?:key|token|pass)[^\s=]*(?:=| +))(\S*)/gi;function g(e){return e.replace(m,"$1<secret value>")}function k(e,o,t){let n=0;o?0!=e&&t.warn(`Ignoring error code ${e.toString()}`):n=e,t.info(`Exit with code ${n.toString()}`),process.exitCode=n}function b(e,o,t){o.ifJust(o=>{t.info(`Setting environment variable ${e} to "${g(o)}"`),process.env[e]=o})}class v{prefix;constructor(e){this.prefix=e}info(...e){console.log([c.bgGreen` ${this.prefix}: `,...e].join(" "))}warn(...e){console.log([c.bgYellow` ${this.prefix} WARNING: `,...e].join(" "))}error(...e){console.error([c.bgRed` ${this.prefix} ERROR: `,...e].join(" "))}}function $(e){return new v(e)}const A="owasp-dependency-check",O=$(`${A} Installation`),D=/^dependency-check-\d+\.\d+\.\d+-release\.zip$/,x="win32"===i.platform();function E(e){const o=n.resolve(e,"dependency-check","bin","dependency-check."+(x?"bat":"sh"));return t.fromNullable(r.existsSync(o)?o:void 0)}function N(e,o){const t={};return e.ifJust(e=>{t.dispatcher=new s(e.toString())}),o.ifJust(e=>{t.headers={Authorization:`Bearer ${e}`}}),t}async function j(r,i,s){O.info(`Installing dependency check ${r.tag_name}...`),await f(i,O);const c=function(e){const o=e.assets.find(e=>D.test(e.name));if(!o)throw new Error(`Could not find asset for version ${e.tag_name}`);return o}(r),p=await async function(e,r,i,s){O.info(`Downloading dependency check from ${e}...`);const c=await a(e,N(s,t.empty())),p=n.resolve(i,r);if(c.body)return await o.writeFile(p,c.body),O.info("Download done."),p;throw new Error(`Download failed from ${e}`)}(c.browser_download_url,c.name,i,s);return await async function(o,t,n,r){await e(o,{dir:t}),await y(o,!1,r)}(p,i,0,O),E(i).orDefaultLazy(()=>{throw new Error(`Could not find Dependency-Check Core executable in ${i}`)})}async function S(e,o,t,r,i,s){const c=await async function(e,o,t){const n=e.mapOrDefault(e=>"https://api.github.com/repos/dependency-check/DependencyCheck/releases/tags/"+e,"https://api.github.com/repos/dependency-check/DependencyCheck/releases/latest");O.info(`Fetching release information from ${n}`);const r=await a(n,N(o,t));if(!r.ok)throw new Error(`Could not fetch release from GitHub: URL: ${n} Status: ${r.statusText}`);return await r.json()}(o,t,r);O.info(`Found release ${c.tag_name} on GitHub.`);const p=n.resolve(e,c.tag_name),d=E(p).filter(()=>!i);return d.isJust()?(O.info(`Using already installed dependency check at "${d.unsafeCoerce()}".`),d.unsafeCoerce()):(s||await f(e,O),await j(c,p,t))}const C=$(`${A} Analysis`);function T(e,o){const t=`${e} ${g(o.join(" "))}`;C.info("Running command:",t)}async function P(e,o,t,r,i,a){return C.info("Dependency-Check Core path:",e),await f(n.resolve(process.cwd(),t),C),b("JAVACMD",a,C),function(e){const o=["--version"];T(e,o);const t={cwd:n.resolve(process.cwd()),shell:!1,encoding:"utf-8"},r=d.sync(e,o,t);if(r.error)throw r.error;if(null===r.status)throw new Error("Version check did not complete with status code.");if(0!==r.status)throw new Error(r.stderr.toString());C.info(r.stdout.trimEnd())}(e),function(e,o,t,r){b("JAVA_OPTS",t.map(I),C);const i={cwd:n.resolve(process.cwd()),shell:!1,stdio:r?"ignore":"inherit"};T(e,o);const a=d.sync(e,o,i);if(a.error)throw a.error;if(null===a.status)throw new Error("Analysis did not complete with status code.");return C.info(p`Done.`),a.status}(e,o,r,i)}function I(e){let o=`-Dhttps.proxyHost=${e.hostname}`;return e.port&&(o+=` -Dhttps.proxyPort=${e.port}`),e.username&&(o+=` -Dhttps.proxyUser=${e.username}`),e.password&&(o+=` -Dhttps.proxyPassword=${e.password}`),o}const J=$(A),_=l.configureOutput({writeErr(e){J.error(e)}}).allowUnknownOption().name(A).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 h("--github-token <token>","GitHub token to authenticate against API").env("GITHUB_TOKEN")).optionsGroup("Execution options:").addOption(new h("--owasp-bin <binary>","the path to a preinstalled dependency-check-cli binary (.sh or .bat file)").env("OWASP_BIN").argParser(G)).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 h("--java-bin <binary>","the path to the Java binary").env("JAVACMD").argParser(G)).optionsGroup("Network options:").option("-p, --proxy <url>","the URL of a proxy server in the format: http(s)://[user]:[password]@<server>:[port]",function(e){const o=URL.parse(e);if(!o?.protocol||!o.hostname)throw new u("The proxy URL is invalid.");if("http:"!==o.protocol&&"https:"!==o.protocol)throw new u("The proxy URL is not HTTP(S).");return o}).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",n.join(i.tmpdir(),"dependency-check-data")).option("-s, --scan <path...>","the paths to scan ",["package-lock.json"]).option("-f, --format <format...>","the formats of the report to generate",["HTML","JSON"]).addOption(new h("--nvdApiKey <key>","NVD API key to authenticate against API").env("NVD_API_KEY")).addOption(new h("--project <name>","the name of the project to be scanned").env("PROJECT_NAME")).optionsGroup("General information:").version("0.8.1",void 0,`print the version of ${A}`).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-lock.json in working directory\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- 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').parse(),U={hideOwaspOutput:!!_.opts().hideOwaspOutput,owaspBinary:t.fromNullable(_.opts().owaspBin),proxyUrl:t.fromNullable(_.opts().proxy),githubToken:t.fromNullable(_.opts().githubToken),outDir:_.opts().out,forceInstall:!!_.opts().forceInstall,odcVersion:t.fromNullable(_.opts().odcVersion),binDir:n.resolve(_.opts().bin),cmdArguments:function(){const e=["--out",_.opts().out,"--data",_.opts().data,..._.args];return t.fromNullable(_.opts().nvdApiKey).ifJust(o=>{e.push("--nvdApiKey",o)}),_.opts().scan.forEach(o=>{e.push("--scan",o)}),e.push("--project",_.opts().project??V()),_.opts().format.forEach(o=>{e.push("--format",o)}),e}(),ignoreErrors:!!_.opts().ignoreErrors,keepOldVersions:!!_.opts().keepOldVersions,javaBinary:t.fromNullable(_.opts().javaBin)};function V(){let e="Unknown Project";try{const o=r.readFileSync(n.resolve("package.json")).toString();e=JSON.parse(o).name,J.info(`Found project name "${e}" in package.json`)}catch(e){const o=w(e);J.warn(o.message)}return e}function G(e){const o=n.resolve(e);if(!r.existsSync(o))throw new u("The binary does not exist.");if(!r.statSync(o).isFile())throw new u("The binary is not a file.");return o}const R=$(A);async function B(){let e=U.owaspBinary;if(e.isNothing()?e=t.of(await S(U.binDir,U.odcVersion,U.proxyUrl,U.githubToken,U.forceInstall,U.keepOldVersions)):R.info("Locally preinstalled (OWASP_BIN) Dependency-Check Core found."),e.isJust()){k(await P(e.unsafeCoerce(),U.cmdArguments,U.outDir,U.proxyUrl,U.hideOwaspOutput,U.javaBinary),U.ignoreErrors,R)}}B().catch(e=>{const o=w(e);R.error(o.message),k(1,U.ignoreErrors,R)});export{B as run};