@cdxgen/cdxgen 13.4.0 → 13.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -7
- package/bin/cdxgen-secure.js +15 -0
- package/bin/cdxgen.js +25 -5
- package/bin/evinse.js +5 -0
- package/bin/hbom.js +15 -7
- package/bin/repl.js +29 -0
- package/data/cbomosdb-queries.json +13 -1
- package/data/component-tags.json +8 -0
- package/data/queries-darwin.json +25 -1
- package/data/queries-win.json +19 -1
- package/data/queries.json +41 -0
- package/data/remediations.json +283 -1
- package/data/rules/cbom-security.yaml +235 -4
- package/data/rules/go-binaries.yaml +46 -0
- package/data/rules/golem-go.yaml +80 -0
- package/data/rules/hbom-compliance.yaml +40 -22
- package/data/rules/hbom-performance.yaml +7 -1
- package/data/rules/host-topology.yaml +1 -1
- package/data/rules/obom-runtime.yaml +31 -2
- package/data/rules/php-runtime.yaml +143 -0
- package/lib/audit/index.js +14 -0
- package/lib/cli/bomAssembly.js +28 -3
- package/lib/cli/cliOptions.js +110 -3
- package/lib/cli/index.js +73 -4
- package/lib/cli/jvmBom.js +126 -20
- package/lib/cli/managedBom.js +711 -71
- package/lib/cli/nativeBom.js +225 -28
- package/lib/core/env.js +30 -8
- package/lib/core/fs.js +28 -19
- package/lib/core/httpClient.js +74 -45
- package/lib/core/projectConfig.js +1 -0
- package/lib/core/projectTooling.js +97 -0
- package/lib/core/propertySanitizer.js +72 -0
- package/lib/ecosystems/golem.js +550 -6
- package/lib/ecosystems/gradleutils.js +30 -20
- package/lib/ecosystems/parsers-dotnet.js +117 -18
- package/lib/ecosystems/parsers-go.js +1090 -367
- package/lib/ecosystems/parsers-jvm.js +259 -17
- package/lib/ecosystems/parsers-misc.js +229 -44
- package/lib/ecosystems/parsers-python.js +11 -10
- package/lib/ecosystems/rubyutils.js +6 -3
- package/lib/ecosystems/rusi.js +10 -0
- package/lib/ecosystems/scalacliutils.js +2 -1
- package/lib/ecosystems/scalasem.js +3 -1
- package/lib/evinser/evinser.js +126 -18
- package/lib/evinser/swiftsem.js +68 -5
- package/lib/inventory/agentSkillSpec.js +17 -0
- package/lib/inventory/apkInstalledDb.js +243 -0
- package/lib/inventory/atomUtils.js +33 -0
- package/lib/inventory/auditCategories.js +3 -3
- package/lib/inventory/cacheDir.js +52 -9
- package/lib/inventory/certificates.js +2371 -0
- package/lib/inventory/communityAiConfigParser.js +4 -9
- package/lib/inventory/deps.js +6 -4
- package/lib/inventory/display.js +200 -3
- package/lib/inventory/dosai.js +500 -58
- package/lib/inventory/dynamic.js +45 -3
- package/lib/inventory/envcontext.js +168 -5
- package/lib/inventory/evidenceUtils.js +436 -23
- package/lib/inventory/formulationParsers.js +3 -2
- package/lib/inventory/goBuildInfo.js +140 -0
- package/lib/inventory/hbom.js +67 -7
- package/lib/inventory/hbomAnalysis.js +16 -0
- package/lib/inventory/hostTopology.js +21 -0
- package/lib/inventory/osPackagePurl.js +244 -0
- package/lib/inventory/osPackageResolver.js +89 -87
- package/lib/inventory/osinfo.js +79 -31
- package/lib/inventory/osqueryTransform.js +114 -5
- package/lib/inventory/phpBuildInfo.js +487 -0
- package/lib/inventory/phpHostProbe.js +641 -0
- package/lib/inventory/rootfsCerts.js +426 -0
- package/lib/inventory/source.js +207 -3
- package/lib/inventory/spdx.js +144 -0
- package/lib/inventory/traceRunner.js +5 -1
- package/lib/inventory/tracedCrypto.js +129 -0
- package/lib/managers/binary.js +529 -183
- package/lib/managers/docker.js +166 -12
- package/lib/parsers/email.js +277 -0
- package/lib/parsers/largeJson.js +34 -2
- package/lib/server/server.js +186 -1
- package/lib/stages/postgen/annotator.js +17 -1
- package/lib/stages/postgen/introspection/reflect.js +4 -1
- package/lib/stages/postgen/postgen.js +5 -5
- package/lib/stages/postgen/ruleEngine.js +58 -1
- package/lib/stages/postgen/specVersionCompat.js +30 -0
- package/lib/stages/pregen/pregen.js +117 -5
- package/lib/validator/bomValidator.js +26 -5
- package/lib/validator/generated/runtime.mjs +13 -0
- package/lib/validator/generated/validate-1.6.mjs +26 -7
- package/lib/validator/generated/validate-1.7.mjs +31 -12
- package/lib/validator/generated/validate-2.0.mjs +30 -11
- package/package.json +17 -17
- package/types/bin/cdxgen-secure.d.ts +3 -0
- package/types/bin/cdxgen-secure.d.ts.map +1 -0
- package/types/bin/repl.d.ts.map +1 -1
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts +9 -2
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +43 -0
- package/types/lib/cli/cliOptions.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts +20 -0
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/env.d.ts +18 -2
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/httpClient.d.ts.map +1 -1
- package/types/lib/core/projectConfig.d.ts.map +1 -1
- package/types/lib/core/projectTooling.d.ts +41 -0
- package/types/lib/core/projectTooling.d.ts.map +1 -0
- package/types/lib/core/propertySanitizer.d.ts +26 -0
- package/types/lib/core/propertySanitizer.d.ts.map +1 -1
- package/types/lib/ecosystems/cppEvidence.d.ts +1 -1
- package/types/lib/ecosystems/golem.d.ts +81 -1
- package/types/lib/ecosystems/golem.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +10 -8
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-dotnet.d.ts +40 -4
- package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-go.d.ts +85 -13
- package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-jvm.d.ts +28 -2
- package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-misc.d.ts +18 -58
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-python.d.ts +4 -3
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/pythonutils.d.ts +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -1
- package/types/lib/ecosystems/scalasem.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +25 -0
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/evinser/swiftsem.d.ts +10 -0
- package/types/lib/evinser/swiftsem.d.ts.map +1 -1
- package/types/lib/inventory/agentSkillSpec.d.ts +13 -0
- package/types/lib/inventory/agentSkillSpec.d.ts.map +1 -0
- package/types/lib/inventory/apkInstalledDb.d.ts +42 -0
- package/types/lib/inventory/apkInstalledDb.d.ts.map +1 -0
- package/types/lib/inventory/atomUtils.d.ts +14 -0
- package/types/lib/inventory/atomUtils.d.ts.map +1 -1
- package/types/lib/inventory/cacheDir.d.ts +12 -4
- package/types/lib/inventory/cacheDir.d.ts.map +1 -1
- package/types/lib/inventory/certificates.d.ts +205 -0
- package/types/lib/inventory/certificates.d.ts.map +1 -0
- package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/deps.d.ts +2 -1
- package/types/lib/inventory/deps.d.ts.map +1 -1
- package/types/lib/inventory/display.d.ts +18 -0
- package/types/lib/inventory/display.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +145 -6
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/dynamic.d.ts +6 -1
- package/types/lib/inventory/dynamic.d.ts.map +1 -1
- package/types/lib/inventory/envcontext.d.ts +40 -2
- package/types/lib/inventory/envcontext.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts +29 -10
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/formulationParsers.d.ts.map +1 -1
- package/types/lib/inventory/goBuildInfo.d.ts +10 -0
- package/types/lib/inventory/goBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/hbom.d.ts.map +1 -1
- package/types/lib/inventory/hbomAnalysis.d.ts +10 -0
- package/types/lib/inventory/hbomAnalysis.d.ts.map +1 -1
- package/types/lib/inventory/hostTopology.d.ts.map +1 -1
- package/types/lib/inventory/osPackagePurl.d.ts +99 -0
- package/types/lib/inventory/osPackagePurl.d.ts.map +1 -0
- package/types/lib/inventory/osPackageResolver.d.ts +6 -2
- package/types/lib/inventory/osPackageResolver.d.ts.map +1 -1
- package/types/lib/inventory/osinfo.d.ts +31 -6
- package/types/lib/inventory/osinfo.d.ts.map +1 -1
- package/types/lib/inventory/osqueryTransform.d.ts +51 -0
- package/types/lib/inventory/osqueryTransform.d.ts.map +1 -1
- package/types/lib/inventory/phpBuildInfo.d.ts +142 -0
- package/types/lib/inventory/phpBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/phpHostProbe.d.ts +35 -0
- package/types/lib/inventory/phpHostProbe.d.ts.map +1 -0
- package/types/lib/inventory/rootfsCerts.d.ts +27 -0
- package/types/lib/inventory/rootfsCerts.d.ts.map +1 -0
- package/types/lib/inventory/source.d.ts +28 -0
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/inventory/spdx.d.ts +15 -0
- package/types/lib/inventory/spdx.d.ts.map +1 -1
- package/types/lib/inventory/traceRunner.d.ts +4 -1
- package/types/lib/inventory/traceRunner.d.ts.map +1 -1
- package/types/lib/inventory/tracedCrypto.d.ts +22 -0
- package/types/lib/inventory/tracedCrypto.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts +10 -0
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts +12 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/parsers/email.d.ts +19 -0
- package/types/lib/parsers/email.d.ts.map +1 -0
- package/types/lib/parsers/largeJson.d.ts +13 -0
- package/types/lib/parsers/largeJson.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +8 -0
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
- package/types/lib/stages/postgen/introspection/reflect.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts +7 -4
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
- package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
- package/types/lib/stages/pregen/pregen.d.ts +14 -1
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
package/lib/core/httpClient.js
CHANGED
|
@@ -3,12 +3,7 @@ import { createHash } from "node:crypto";
|
|
|
3
3
|
import process from "node:process";
|
|
4
4
|
import { brotliDecompressSync, gunzipSync, inflateSync } from "node:zlib";
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
Agent,
|
|
8
|
-
EnvHttpProxyAgent,
|
|
9
|
-
interceptors,
|
|
10
|
-
request as undiciRequest,
|
|
11
|
-
} from "undici";
|
|
6
|
+
import { Agent, EnvHttpProxyAgent, request as undiciRequest } from "undici";
|
|
12
7
|
|
|
13
8
|
import { registerRunReset } from "./runState.js";
|
|
14
9
|
|
|
@@ -534,20 +529,13 @@ function cacheKeyFor(method, url, headers = {}, responseType = undefined) {
|
|
|
534
529
|
// The maximum number of redirects to follow when `followRedirect` is enabled.
|
|
535
530
|
const MAX_REDIRECTIONS = 10;
|
|
536
531
|
|
|
537
|
-
// Bun ships its own bundled implementation of the `undici` module which
|
|
538
|
-
//
|
|
539
|
-
// `interceptors.redirect` interceptor that undici v7 uses for redirect handling
|
|
540
|
-
// (see cdxgen issue #4245). Detect the Bun runtime so we can fall back to plain
|
|
541
|
-
// dispatchers and follow redirects manually instead of crashing at module load.
|
|
532
|
+
// Bun ships its own bundled implementation of the `undici` module, which lacks
|
|
533
|
+
// `EnvHttpProxyAgent` (see cdxgen issue #4245).
|
|
542
534
|
const isBun = typeof globalThis.Bun !== "undefined";
|
|
543
535
|
|
|
544
|
-
// Reusable pooled dispatchers
|
|
545
|
-
//
|
|
546
|
-
//
|
|
547
|
-
// allocating a fresh Agent per request (which would defeat connection pooling).
|
|
548
|
-
// On Bun, `compose`/`interceptors.redirect` are unavailable, so the redirect
|
|
549
|
-
// dispatchers are just the plain agents and redirects are handled in
|
|
550
|
-
// `requestWithRedirects` below.
|
|
536
|
+
// Reusable pooled dispatchers, one per TLS verification setting, so requests
|
|
537
|
+
// share connections instead of allocating a fresh Agent each time. Redirects
|
|
538
|
+
// are followed in `requestWithRedirects` below on every runtime.
|
|
551
539
|
// Whether standard proxy environment variables are set. When they are, requests
|
|
552
540
|
// must be routed through the proxy. Because this client always passes an
|
|
553
541
|
// explicit per-request `dispatcher`, it overrides Node's global (potentially
|
|
@@ -584,51 +572,72 @@ const defaultAgent = createBaseAgent();
|
|
|
584
572
|
const insecureAgent = createBaseAgent({
|
|
585
573
|
connect: { rejectUnauthorized: false },
|
|
586
574
|
});
|
|
587
|
-
const redirectDispatcher = isBun
|
|
588
|
-
? defaultAgent
|
|
589
|
-
: defaultAgent.compose(
|
|
590
|
-
interceptors.redirect({ maxRedirections: MAX_REDIRECTIONS }),
|
|
591
|
-
);
|
|
592
|
-
const insecureRedirectDispatcher = isBun
|
|
593
|
-
? insecureAgent
|
|
594
|
-
: insecureAgent.compose(
|
|
595
|
-
interceptors.redirect({ maxRedirections: MAX_REDIRECTIONS }),
|
|
596
|
-
);
|
|
597
575
|
|
|
598
576
|
/**
|
|
599
|
-
* Pick the undici dispatcher matching the request's
|
|
600
|
-
*
|
|
577
|
+
* Pick the undici dispatcher matching the request's TLS verification
|
|
578
|
+
* preference. Redirects are followed by `requestWithRedirects`, not by the
|
|
579
|
+
* dispatcher.
|
|
601
580
|
*
|
|
602
|
-
* @param {boolean} followRedirect Whether HTTP redirects should be followed.
|
|
603
581
|
* @param {boolean} insecure Whether TLS certificate verification is disabled.
|
|
604
582
|
* @returns {import("undici").Dispatcher} The dispatcher to use for the request.
|
|
605
583
|
*/
|
|
606
|
-
function selectDispatcher(
|
|
607
|
-
|
|
608
|
-
return followRedirect ? insecureRedirectDispatcher : insecureAgent;
|
|
609
|
-
}
|
|
610
|
-
return followRedirect ? redirectDispatcher : defaultAgent;
|
|
584
|
+
function selectDispatcher(insecure) {
|
|
585
|
+
return insecure ? insecureAgent : defaultAgent;
|
|
611
586
|
}
|
|
612
587
|
|
|
613
588
|
// HTTP status codes that represent a redirect with a `location` header.
|
|
614
589
|
const REDIRECT_STATUS_CODES = new Set([301, 302, 303, 307, 308]);
|
|
615
590
|
|
|
591
|
+
// Request headers that may follow a redirect to a different origin. Everything
|
|
592
|
+
// else, such as Authorization, cookies and registry API keys like X-Api-Key or
|
|
593
|
+
// PRIVATE-TOKEN, stays with the origin the caller addressed.
|
|
594
|
+
const CROSS_ORIGIN_REDIRECT_HEADERS = new Set([
|
|
595
|
+
"accept",
|
|
596
|
+
"accept-encoding",
|
|
597
|
+
"accept-language",
|
|
598
|
+
"user-agent",
|
|
599
|
+
]);
|
|
600
|
+
|
|
616
601
|
/**
|
|
617
|
-
*
|
|
602
|
+
* Keep only the request headers that may be sent to a different origin.
|
|
618
603
|
*
|
|
619
|
-
*
|
|
620
|
-
*
|
|
621
|
-
|
|
622
|
-
|
|
604
|
+
* @param {Object} headers Request headers.
|
|
605
|
+
* @returns {Object} Headers safe to send cross-origin.
|
|
606
|
+
*/
|
|
607
|
+
function crossOriginRedirectHeaders(headers = {}) {
|
|
608
|
+
const kept = {};
|
|
609
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
610
|
+
if (CROSS_ORIGIN_REDIRECT_HEADERS.has(name.toLowerCase())) {
|
|
611
|
+
kept[name] = value;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
return kept;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Issue an undici request and follow redirects one hop at a time.
|
|
619
|
+
*
|
|
620
|
+
* Redirects are followed here rather than by undici's redirect interceptor so
|
|
621
|
+
* that every hop goes through `beforeRedirect`, which runs the same host
|
|
622
|
+
* allowlist, protocol and dry-run checks as the first request, and so that
|
|
623
|
+
* credentials are not forwarded to another origin. Each intermediate body is
|
|
624
|
+
* drained and relative `location` headers are resolved, up to
|
|
623
625
|
* `MAX_REDIRECTIONS` hops.
|
|
624
626
|
*
|
|
625
627
|
* @param {URL} url Request URL.
|
|
626
628
|
* @param {Object} requestOptions Options passed to `undiciRequest`.
|
|
627
629
|
* @param {boolean} followRedirect Whether redirects should be followed.
|
|
630
|
+
* @param {function(URL): Promise<void>} beforeRedirect Called with the next URL
|
|
631
|
+
* before each hop; throws to stop the request.
|
|
628
632
|
* @returns {Promise<Object>} The final undici response.
|
|
629
633
|
*/
|
|
630
|
-
async function requestWithRedirects(
|
|
631
|
-
|
|
634
|
+
async function requestWithRedirects(
|
|
635
|
+
url,
|
|
636
|
+
requestOptions,
|
|
637
|
+
followRedirect,
|
|
638
|
+
beforeRedirect,
|
|
639
|
+
) {
|
|
640
|
+
if (!followRedirect) {
|
|
632
641
|
return await undiciRequest(url, requestOptions);
|
|
633
642
|
}
|
|
634
643
|
let currentUrl = url;
|
|
@@ -649,7 +658,14 @@ async function requestWithRedirects(url, requestOptions, followRedirect) {
|
|
|
649
658
|
} catch {
|
|
650
659
|
// Ignore drain failures; the socket will be discarded.
|
|
651
660
|
}
|
|
652
|
-
|
|
661
|
+
const nextUrl = new URL(location, currentUrl);
|
|
662
|
+
await beforeRedirect(nextUrl);
|
|
663
|
+
if (nextUrl.origin !== currentUrl.origin) {
|
|
664
|
+
requestOptions.headers = crossOriginRedirectHeaders(
|
|
665
|
+
requestOptions.headers,
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
currentUrl = nextUrl;
|
|
653
669
|
redirections += 1;
|
|
654
670
|
// 303, and 301/302 for anything other than GET/HEAD, must switch to GET
|
|
655
671
|
// without a body, matching how undici's redirect interceptor behaves.
|
|
@@ -738,9 +754,21 @@ async function doRequest(mergedOptions) {
|
|
|
738
754
|
method,
|
|
739
755
|
headers,
|
|
740
756
|
body: method === "GET" || method === "HEAD" ? undefined : body,
|
|
741
|
-
dispatcher: selectDispatcher(
|
|
757
|
+
dispatcher: selectDispatcher(insecure),
|
|
742
758
|
signal,
|
|
743
759
|
};
|
|
760
|
+
// A redirect target must pass the same beforeRequest checks, such as the
|
|
761
|
+
// host allowlist, as the URL the caller asked for.
|
|
762
|
+
const beforeRedirect = async (nextUrl) => {
|
|
763
|
+
const hopOptions = {
|
|
764
|
+
...options,
|
|
765
|
+
url: nextUrl,
|
|
766
|
+
context: { ...options.context },
|
|
767
|
+
};
|
|
768
|
+
for (const hook of options.hooks?.beforeRequest || []) {
|
|
769
|
+
await hook(hopOptions);
|
|
770
|
+
}
|
|
771
|
+
};
|
|
744
772
|
|
|
745
773
|
let undiciResponse;
|
|
746
774
|
try {
|
|
@@ -748,6 +776,7 @@ async function doRequest(mergedOptions) {
|
|
|
748
776
|
url,
|
|
749
777
|
requestOptions,
|
|
750
778
|
followRedirect,
|
|
779
|
+
beforeRedirect,
|
|
751
780
|
);
|
|
752
781
|
} catch (err) {
|
|
753
782
|
const requestError = new RequestError(err, options);
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Policy for running the scanned project's own build tooling.
|
|
3
|
+
*
|
|
4
|
+
* Build tools such as Maven, Gradle, Poetry, MSBuild, SwiftPM, Leiningen and
|
|
5
|
+
* CocoaPods read configuration or run code that ships with the project
|
|
6
|
+
* (`.mvn/jvm.config`, `.poetry/plugins`, `Directory.Build.rsp`, `Package.swift`,
|
|
7
|
+
* `project.clj`, podspecs). Running them on an untrusted project runs that
|
|
8
|
+
* project's code, so they belong to the same surface as dependency
|
|
9
|
+
* installation and are skipped whenever installation is disabled.
|
|
10
|
+
*/
|
|
11
|
+
import { isSecureMode, readEnvironmentVariable } from "./activity.js";
|
|
12
|
+
import { recordDegradation } from "./buildLedger.js";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Whether secure mode is on right now. `isSecureMode` is read once when the
|
|
16
|
+
* modules load, but the `cdxgen-secure` command turns secure mode on later,
|
|
17
|
+
* by setting CDXGEN_SECURE_MODE before the scan starts.
|
|
18
|
+
*
|
|
19
|
+
* @returns {boolean} true when secure mode is on
|
|
20
|
+
*/
|
|
21
|
+
export function isSecureModeActive() {
|
|
22
|
+
return (
|
|
23
|
+
isSecureMode ||
|
|
24
|
+
["true", "1"].includes(readEnvironmentVariable("CDXGEN_SECURE_MODE"))
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Whether the scan may run the scanned project's own build tooling, such as
|
|
30
|
+
* wrapper scripts or build definitions that Maven, Gradle, sbt, Mill, Poetry,
|
|
31
|
+
* PDM, MSBuild, SwiftPM, Leiningen and CocoaPods evaluate.
|
|
32
|
+
*
|
|
33
|
+
* Tooling follows `options.installDeps`, which defaults to off in secure mode
|
|
34
|
+
* when a caller does not set it. Introspection mode exists to observe the
|
|
35
|
+
* project's own toolchain, so it keeps that tooling on even when dependency
|
|
36
|
+
* installation is disabled, as long as the operator asked for it rather than
|
|
37
|
+
* a config file in the scanned project. Secure mode still wins:
|
|
38
|
+
* CDXGEN_INTROSPECT alone must not make a secure-mode scan run the scanned
|
|
39
|
+
* project's code.
|
|
40
|
+
*
|
|
41
|
+
* @param {Object} [options] CLI options
|
|
42
|
+
* @returns {boolean} true when project tooling may run
|
|
43
|
+
*/
|
|
44
|
+
export function isProjectToolingAllowed(options) {
|
|
45
|
+
const secureMode = isSecureModeActive();
|
|
46
|
+
const installDeps = options?.installDeps ?? !secureMode;
|
|
47
|
+
if (installDeps !== false) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
return (
|
|
51
|
+
options?.introspect === true &&
|
|
52
|
+
options?.introspectFromConfigFile !== true &&
|
|
53
|
+
!secureMode
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// The skips already reported for each scan, keyed by the scan's options.
|
|
58
|
+
// Callers without options share one key.
|
|
59
|
+
const reportedSkips = new WeakMap();
|
|
60
|
+
const noOptionsKey = {};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Tell the user that a tool was skipped because project tooling is not
|
|
64
|
+
* allowed, and record the matching degradation for the fidelity report.
|
|
65
|
+
* Each skip is reported once per scan, however many manifests it applies to.
|
|
66
|
+
* A skip that only costs evidence, not BOM content, passes no remediation id
|
|
67
|
+
* and is only shown.
|
|
68
|
+
*
|
|
69
|
+
* @param {Object} options CLI options of the scan
|
|
70
|
+
* @param {string|null} remediationId Remediation id from
|
|
71
|
+
* data/remediations.json, or null for a message only
|
|
72
|
+
* @param {string} message Console message shown to the user
|
|
73
|
+
* @param {Object} [fields] Degradation fields (ecosystem, tool, impact, detail)
|
|
74
|
+
* @returns {void}
|
|
75
|
+
*/
|
|
76
|
+
export function reportProjectToolingSkipped(
|
|
77
|
+
options,
|
|
78
|
+
remediationId,
|
|
79
|
+
message,
|
|
80
|
+
fields,
|
|
81
|
+
) {
|
|
82
|
+
const key = options && typeof options === "object" ? options : noOptionsKey;
|
|
83
|
+
let reported = reportedSkips.get(key);
|
|
84
|
+
if (!reported) {
|
|
85
|
+
reported = new Set();
|
|
86
|
+
reportedSkips.set(key, reported);
|
|
87
|
+
}
|
|
88
|
+
const skipKey = remediationId || message;
|
|
89
|
+
if (reported.has(skipKey)) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
reported.add(skipKey);
|
|
93
|
+
console.log(message);
|
|
94
|
+
if (remediationId) {
|
|
95
|
+
recordDegradation(remediationId, fields);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -26,6 +26,15 @@ const JSON_PROPERTY_NAMES = new Set([
|
|
|
26
26
|
]);
|
|
27
27
|
const URL_PATTERN = /https?:\/\/[^\s<>"'),\]}]+/giu;
|
|
28
28
|
const MAX_STRUCTURE_DEPTH = 6;
|
|
29
|
+
// What an RFC 3986 path may hold as it is: unreserved characters, sub-delims,
|
|
30
|
+
// ":", "@", "/" and well-formed percent-escapes. A query or fragment may also
|
|
31
|
+
// hold "?". Everything else is percent-encoded.
|
|
32
|
+
const URI_PATH_UNSAFE =
|
|
33
|
+
/%(?![0-9A-Fa-f]{2})|[^A-Za-z0-9\-._~!$&'()*+,;=:@/%]/gu;
|
|
34
|
+
const URI_QUERY_UNSAFE =
|
|
35
|
+
/%(?![0-9A-Fa-f]{2})|[^A-Za-z0-9\-._~!$&'()*+,;=:@/?%]/gu;
|
|
36
|
+
const URI_SCHEME_AUTHORITY = /^[A-Za-z][A-Za-z0-9+.-]*:\/\/[^/?#]*/u;
|
|
37
|
+
const UTF8_ENCODER = new TextEncoder();
|
|
29
38
|
|
|
30
39
|
function sanitizeUrlForBom(value) {
|
|
31
40
|
const input = String(value || "").trim();
|
|
@@ -146,6 +155,69 @@ export function sanitizeBomUrl(value) {
|
|
|
146
155
|
return sanitizeUrlForBom(value);
|
|
147
156
|
}
|
|
148
157
|
|
|
158
|
+
function percentEncodeUnsafe(text, unsafe) {
|
|
159
|
+
return text.replace(unsafe, (character) =>
|
|
160
|
+
Array.from(
|
|
161
|
+
UTF8_ENCODER.encode(character),
|
|
162
|
+
(byte) => `%${byte.toString(16).toUpperCase().padStart(2, "0")}`,
|
|
163
|
+
).join(""),
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Percent-encode what an RFC 3986 URI reference cannot hold as it is, so a
|
|
169
|
+
* route template or URL can be emitted where CycloneDX expects an
|
|
170
|
+
* iri-reference, such as `services[].endpoints[]`. The braces of a route
|
|
171
|
+
* template, spaces, `<>`, `[]` outside an IPv6 host, `|`, `^`, non-ASCII
|
|
172
|
+
* characters and a `%` that starts no escape are encoded:
|
|
173
|
+
* `/users/{id}` becomes `/users/%7Bid%7D`. The scheme and authority of an
|
|
174
|
+
* absolute URL are kept as written, the query and fragment stay a query and
|
|
175
|
+
* fragment, and existing escapes are kept, so encoding twice changes
|
|
176
|
+
* nothing. Nothing is removed; strip credentials with {@link sanitizeBomUrl}
|
|
177
|
+
* first.
|
|
178
|
+
*
|
|
179
|
+
* @param {unknown} value Route template, path or URL
|
|
180
|
+
* @returns {string} The encoded reference
|
|
181
|
+
*/
|
|
182
|
+
export function encodeUriReference(value) {
|
|
183
|
+
const text = String(value ?? "");
|
|
184
|
+
const authority = URI_SCHEME_AUTHORITY.exec(text)?.[0] ?? "";
|
|
185
|
+
const rest = text.slice(authority.length);
|
|
186
|
+
const queryStart = rest.indexOf("?");
|
|
187
|
+
const fragmentStart = rest.indexOf("#");
|
|
188
|
+
const pathEnd = Math.min(
|
|
189
|
+
queryStart < 0 ? rest.length : queryStart,
|
|
190
|
+
fragmentStart < 0 ? rest.length : fragmentStart,
|
|
191
|
+
);
|
|
192
|
+
let encoded =
|
|
193
|
+
authority + percentEncodeUnsafe(rest.slice(0, pathEnd), URI_PATH_UNSAFE);
|
|
194
|
+
if (pathEnd === queryStart) {
|
|
195
|
+
const queryEnd = fragmentStart > queryStart ? fragmentStart : rest.length;
|
|
196
|
+
encoded += `?${percentEncodeUnsafe(rest.slice(queryStart + 1, queryEnd), URI_QUERY_UNSAFE)}`;
|
|
197
|
+
}
|
|
198
|
+
if (fragmentStart >= 0) {
|
|
199
|
+
encoded += `#${percentEncodeUnsafe(rest.slice(fragmentStart + 1), URI_QUERY_UNSAFE)}`;
|
|
200
|
+
}
|
|
201
|
+
return encoded;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Whether a value carries a token shape the BOM sanitizer redacts: cloud
|
|
206
|
+
* access keys, bearer tokens, GitHub, Slack and npm tokens, JWTs and the
|
|
207
|
+
* like. Callers that cannot splice a `[redacted]` marker into the value, such
|
|
208
|
+
* as a URL path segment, use it to drop the value instead.
|
|
209
|
+
*
|
|
210
|
+
* @param {unknown} value Value to check
|
|
211
|
+
* @returns {boolean} true when the value holds an inline credential
|
|
212
|
+
*/
|
|
213
|
+
export function hasInlineCredential(value) {
|
|
214
|
+
const text = String(value ?? "");
|
|
215
|
+
return INLINE_CREDENTIAL_PATTERNS.some((pattern) => {
|
|
216
|
+
pattern.lastIndex = 0;
|
|
217
|
+
return pattern.test(text);
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
149
221
|
/**
|
|
150
222
|
* Sanitize a property value before serializing it into BOM properties.
|
|
151
223
|
*
|