@cyclonedx/cdxgen 11.4.4 → 11.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 +63 -63
- package/bin/cdxgen.js +31 -9
- package/lib/cli/index.js +334 -95
- package/lib/helpers/envcontext.js +20 -18
- package/lib/helpers/logger.js +0 -2
- package/lib/helpers/utils.js +743 -58
- package/lib/helpers/utils.test.js +444 -23
- package/lib/helpers/validator.js +10 -0
- package/lib/managers/binary.js +89 -11
- package/lib/managers/docker.js +47 -37
- package/lib/server/server.js +120 -6
- package/lib/server/server.test.js +235 -0
- package/package.json +30 -11
- package/types/lib/cli/index.d.ts +8 -1
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/helpers/envcontext.d.ts +3 -8
- package/types/lib/helpers/envcontext.d.ts.map +1 -1
- package/types/lib/helpers/logger.d.ts +0 -1
- package/types/lib/helpers/logger.d.ts.map +1 -1
- package/types/lib/helpers/utils.d.ts +67 -66
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/helpers/validator.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +22 -2
- package/types/lib/server/server.d.ts.map +1 -1
package/lib/managers/binary.js
CHANGED
|
@@ -604,15 +604,19 @@ export async function getOSPackages(src, imageConfig) {
|
|
|
604
604
|
}
|
|
605
605
|
comp.group = group;
|
|
606
606
|
comp.name = name;
|
|
607
|
+
try {
|
|
608
|
+
purlObj = PackageURL.fromString(comp.purl);
|
|
609
|
+
purlObj.qualifiers = purlObj.qualifiers || {};
|
|
610
|
+
} catch (_err) {
|
|
611
|
+
// continue regardless of error
|
|
612
|
+
}
|
|
607
613
|
if (group === "") {
|
|
608
614
|
try {
|
|
609
|
-
purlObj
|
|
610
|
-
if (purlObj.namespace && purlObj.namespace !== "") {
|
|
615
|
+
if (purlObj?.namespace && purlObj.namespace !== "") {
|
|
611
616
|
group = purlObj.namespace;
|
|
612
617
|
comp.group = group;
|
|
613
618
|
purlObj.namespace = group;
|
|
614
619
|
}
|
|
615
|
-
purlObj.qualifiers = purlObj.qualifiers || {};
|
|
616
620
|
if (distro_id?.length) {
|
|
617
621
|
purlObj.qualifiers["distro"] = distro_id;
|
|
618
622
|
}
|
|
@@ -621,7 +625,7 @@ export async function getOSPackages(src, imageConfig) {
|
|
|
621
625
|
}
|
|
622
626
|
// Bug fix for mageia and oracle linux
|
|
623
627
|
// Type is being returned as none for ubuntu as well!
|
|
624
|
-
if (purlObj
|
|
628
|
+
if (purlObj?.type === "none") {
|
|
625
629
|
purlObj["type"] = purl_type;
|
|
626
630
|
purlObj["namespace"] = "";
|
|
627
631
|
comp.group = "";
|
|
@@ -641,11 +645,11 @@ export async function getOSPackages(src, imageConfig) {
|
|
|
641
645
|
).toString();
|
|
642
646
|
comp["bom-ref"] = decodeURIComponent(comp.purl);
|
|
643
647
|
}
|
|
644
|
-
if (purlObj
|
|
648
|
+
if (purlObj?.type !== "none") {
|
|
645
649
|
allTypes.add(purlObj.type);
|
|
646
650
|
}
|
|
647
651
|
// Prefix distro codename for ubuntu
|
|
648
|
-
if (purlObj
|
|
652
|
+
if (purlObj?.qualifiers?.distro) {
|
|
649
653
|
allTypes.add(purlObj.qualifiers.distro);
|
|
650
654
|
if (OS_DISTRO_ALIAS[purlObj.qualifiers.distro]) {
|
|
651
655
|
distro_codename =
|
|
@@ -689,8 +693,28 @@ export async function getOSPackages(src, imageConfig) {
|
|
|
689
693
|
}
|
|
690
694
|
if (comp.purl.includes("epoch=")) {
|
|
691
695
|
try {
|
|
692
|
-
|
|
693
|
-
|
|
696
|
+
const epoch = purlObj.qualifiers?.epoch;
|
|
697
|
+
// trivy seems to be removing the epoch from the version and moving it to a qualifier
|
|
698
|
+
// let's fix this hack to improve confidence.
|
|
699
|
+
if (epoch) {
|
|
700
|
+
purlObj.version = `${epoch}:${purlObj.version}`;
|
|
701
|
+
comp.version = purlObj.version;
|
|
702
|
+
}
|
|
703
|
+
comp.evidence = {
|
|
704
|
+
identity: [
|
|
705
|
+
{
|
|
706
|
+
field: "purl",
|
|
707
|
+
confidence: 1,
|
|
708
|
+
methods: [
|
|
709
|
+
{
|
|
710
|
+
technique: "other",
|
|
711
|
+
confidence: 1,
|
|
712
|
+
value: comp.purl,
|
|
713
|
+
},
|
|
714
|
+
],
|
|
715
|
+
},
|
|
716
|
+
],
|
|
717
|
+
};
|
|
694
718
|
if (distro_id?.length) {
|
|
695
719
|
purlObj.qualifiers["distro"] = distro_id;
|
|
696
720
|
}
|
|
@@ -756,16 +780,35 @@ export async function getOSPackages(src, imageConfig) {
|
|
|
756
780
|
const compProperties = comp.properties;
|
|
757
781
|
let srcName;
|
|
758
782
|
let srcVersion;
|
|
783
|
+
let srcRelease;
|
|
784
|
+
let epoch;
|
|
759
785
|
if (compProperties && Array.isArray(compProperties)) {
|
|
760
786
|
for (const aprop of compProperties) {
|
|
787
|
+
// Property name: aquasecurity:trivy:SrcName
|
|
761
788
|
if (aprop.name.endsWith("SrcName")) {
|
|
762
789
|
srcName = aprop.value;
|
|
763
790
|
}
|
|
791
|
+
// Property name: aquasecurity:trivy:SrcVersion
|
|
764
792
|
if (aprop.name.endsWith("SrcVersion")) {
|
|
765
793
|
srcVersion = aprop.value;
|
|
766
794
|
}
|
|
795
|
+
// Property name: aquasecurity:trivy:SrcRelease
|
|
796
|
+
if (aprop.name.endsWith("SrcRelease")) {
|
|
797
|
+
srcRelease = aprop.value;
|
|
798
|
+
}
|
|
799
|
+
// Property name: aquasecurity:trivy:SrcEpoch
|
|
800
|
+
if (aprop.name.endsWith("SrcEpoch")) {
|
|
801
|
+
epoch = aprop.value;
|
|
802
|
+
}
|
|
767
803
|
}
|
|
768
804
|
}
|
|
805
|
+
// See issue #2067
|
|
806
|
+
if (srcVersion && srcRelease) {
|
|
807
|
+
srcVersion = `${srcVersion}-${srcRelease}`;
|
|
808
|
+
}
|
|
809
|
+
if (epoch) {
|
|
810
|
+
srcVersion = `${epoch}:${srcVersion}`;
|
|
811
|
+
}
|
|
769
812
|
delete comp.properties;
|
|
770
813
|
// Bug fix: We can get bom-ref like this: pkg:rpm/sles/libstdc%2B%2B6@14.2.0+git10526-150000.1.6.1?arch=x86_64&distro=sles-15.5
|
|
771
814
|
if (
|
|
@@ -785,18 +828,44 @@ export async function getOSPackages(src, imageConfig) {
|
|
|
785
828
|
if (compDeps) {
|
|
786
829
|
dependenciesList.push(compDeps);
|
|
787
830
|
}
|
|
788
|
-
//
|
|
831
|
+
// HACK: Many vulnerability databases, including vdb, track vulnerabilities based on source package names :(
|
|
832
|
+
// If there is a source package defined we include it as well to make such SCA scanners work.
|
|
833
|
+
// As a compromise, we reduce the confidence to zero so that there is a way to filter these out.
|
|
789
834
|
if (srcName && srcVersion && srcName !== comp.name) {
|
|
790
835
|
const newComp = Object.assign({}, comp);
|
|
791
836
|
newComp.name = srcName;
|
|
792
837
|
newComp.version = srcVersion;
|
|
838
|
+
newComp.tags = ["source"];
|
|
839
|
+
newComp.evidence = {
|
|
840
|
+
identity: [
|
|
841
|
+
{
|
|
842
|
+
field: "purl",
|
|
843
|
+
confidence: 0,
|
|
844
|
+
methods: [
|
|
845
|
+
{
|
|
846
|
+
technique: "filename",
|
|
847
|
+
confidence: 0,
|
|
848
|
+
value: comp.name,
|
|
849
|
+
},
|
|
850
|
+
],
|
|
851
|
+
},
|
|
852
|
+
],
|
|
853
|
+
};
|
|
854
|
+
// Track upstream and source versions as qualifiers
|
|
793
855
|
if (purlObj) {
|
|
856
|
+
const newCompQualifiers = {
|
|
857
|
+
...purlObj.qualifiers,
|
|
858
|
+
};
|
|
859
|
+
delete newCompQualifiers.epoch;
|
|
860
|
+
if (epoch) {
|
|
861
|
+
newCompQualifiers.epoch = epoch;
|
|
862
|
+
}
|
|
794
863
|
newComp.purl = new PackageURL(
|
|
795
864
|
purlObj.type,
|
|
796
865
|
purlObj.namespace,
|
|
797
866
|
srcName,
|
|
798
867
|
srcVersion,
|
|
799
|
-
|
|
868
|
+
newCompQualifiers,
|
|
800
869
|
purlObj.subpath,
|
|
801
870
|
).toString();
|
|
802
871
|
}
|
|
@@ -879,7 +948,8 @@ function detectSdksRuntimes(comp, bundledSdks, bundledRuntimes) {
|
|
|
879
948
|
|
|
880
949
|
const retrieveDependencies = (tmpDependencies, origBomRef, comp) => {
|
|
881
950
|
try {
|
|
882
|
-
const tmpDependsOn =
|
|
951
|
+
const tmpDependsOn =
|
|
952
|
+
tmpDependencies[origBomRef] || tmpDependencies[comp["bom-ref"]] || [];
|
|
883
953
|
const dependsOn = new Set();
|
|
884
954
|
tmpDependsOn.forEach((d) => {
|
|
885
955
|
try {
|
|
@@ -896,6 +966,14 @@ const retrieveDependencies = (tmpDependencies, origBomRef, comp) => {
|
|
|
896
966
|
tmpPurl.qualifiers.distro = compPurl.qualifiers.distro;
|
|
897
967
|
}
|
|
898
968
|
}
|
|
969
|
+
if (tmpPurl.qualifiers) {
|
|
970
|
+
if (
|
|
971
|
+
tmpPurl.qualifiers.epoch &&
|
|
972
|
+
!tmpPurl.version.startsWith(`${tmpPurl.qualifiers.epoch}:`)
|
|
973
|
+
) {
|
|
974
|
+
tmpPurl.version = `${tmpPurl.qualifiers.epoch}:${tmpPurl.version}`;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
899
977
|
dependsOn.add(decodeURIComponent(tmpPurl.toString()));
|
|
900
978
|
} catch (_e) {
|
|
901
979
|
// ignore
|
package/lib/managers/docker.js
CHANGED
|
@@ -474,7 +474,6 @@ export const getConnection = async (options, forRegistry) => {
|
|
|
474
474
|
if (_platform() === "win32") {
|
|
475
475
|
console.warn(
|
|
476
476
|
"Ensure Docker for Desktop is running as an administrator with 'Exposing daemon on TCP without TLS' setting turned on.",
|
|
477
|
-
opts,
|
|
478
477
|
);
|
|
479
478
|
} else if (_platform() === "darwin" && !isNerdctl) {
|
|
480
479
|
if (detectRancherDesktop() || detectColima()) {
|
|
@@ -488,7 +487,6 @@ export const getConnection = async (options, forRegistry) => {
|
|
|
488
487
|
} else {
|
|
489
488
|
console.warn(
|
|
490
489
|
"Ensure docker/podman service or Docker for Desktop is running.",
|
|
491
|
-
opts,
|
|
492
490
|
);
|
|
493
491
|
console.log(
|
|
494
492
|
"Check if the post-installation steps were performed correctly as per this documentation https://docs.docker.com/engine/install/linux-postinstall/",
|
|
@@ -693,7 +691,7 @@ export const getImage = async (fullImageName) => {
|
|
|
693
691
|
console.log(
|
|
694
692
|
"Set the environment variable DOCKER_CMD to use an alternative command such as nerdctl or podman.",
|
|
695
693
|
);
|
|
696
|
-
} else {
|
|
694
|
+
} else if (result.stderr) {
|
|
697
695
|
console.log(result.stderr);
|
|
698
696
|
}
|
|
699
697
|
}
|
|
@@ -702,7 +700,9 @@ export const getImage = async (fullImageName) => {
|
|
|
702
700
|
encoding: "utf-8",
|
|
703
701
|
});
|
|
704
702
|
if (result.status !== 0 || result.error) {
|
|
705
|
-
|
|
703
|
+
if (result.stderr) {
|
|
704
|
+
console.log(result.stderr);
|
|
705
|
+
}
|
|
706
706
|
return localData;
|
|
707
707
|
}
|
|
708
708
|
try {
|
|
@@ -847,6 +847,43 @@ function handleAbsolutePath(entry) {
|
|
|
847
847
|
}
|
|
848
848
|
}
|
|
849
849
|
|
|
850
|
+
// These paths are known to cause extract errors
|
|
851
|
+
const EXTRACT_EXCLUDE_PATHS = [
|
|
852
|
+
"etc/machine-id",
|
|
853
|
+
"etc/gshadow",
|
|
854
|
+
"etc/shadow",
|
|
855
|
+
"etc/passwd",
|
|
856
|
+
"etc/ssl/certs",
|
|
857
|
+
"etc/pki/ca-trust",
|
|
858
|
+
"usr/lib/systemd/",
|
|
859
|
+
"usr/lib64/libdevmapper.so",
|
|
860
|
+
"usr/sbin/",
|
|
861
|
+
"cacerts",
|
|
862
|
+
"ssl/certs",
|
|
863
|
+
"logs/",
|
|
864
|
+
"dev/",
|
|
865
|
+
"usr/share/zoneinfo/",
|
|
866
|
+
"usr/share/doc/",
|
|
867
|
+
"usr/share/i18n/",
|
|
868
|
+
"var/lib/ca-certificates",
|
|
869
|
+
"root/.gnupg",
|
|
870
|
+
"root/.dotnet",
|
|
871
|
+
"usr/share/licenses/device-mapper-libs",
|
|
872
|
+
];
|
|
873
|
+
|
|
874
|
+
// These device types are known to cause extract errors
|
|
875
|
+
const EXTRACT_EXCLUDE_TYPES = new Set([
|
|
876
|
+
"BlockDevice",
|
|
877
|
+
"CharacterDevice",
|
|
878
|
+
"FIFO",
|
|
879
|
+
"MultiVolume",
|
|
880
|
+
"TapeVolume",
|
|
881
|
+
"SymbolicLink",
|
|
882
|
+
"RenamedOrSymlinked",
|
|
883
|
+
"HardLink",
|
|
884
|
+
"Link",
|
|
885
|
+
]);
|
|
886
|
+
|
|
850
887
|
export const extractTar = async (fullImageName, dir, options) => {
|
|
851
888
|
try {
|
|
852
889
|
await stream.pipeline(
|
|
@@ -866,39 +903,11 @@ export const extractTar = async (fullImageName, dir, options) => {
|
|
|
866
903
|
},
|
|
867
904
|
onReadEntry: handleAbsolutePath,
|
|
868
905
|
filter: (path, entry) => {
|
|
869
|
-
|
|
906
|
+
const name = basename(path);
|
|
870
907
|
return !(
|
|
871
|
-
|
|
872
|
-
path.includes(
|
|
873
|
-
|
|
874
|
-
path.includes("etc/passwd") ||
|
|
875
|
-
path.includes("etc/ssl/certs") ||
|
|
876
|
-
path.includes("etc/pki/ca-trust") ||
|
|
877
|
-
path.includes("usr/lib/systemd/") ||
|
|
878
|
-
path.includes("usr/lib64/libdevmapper.so") ||
|
|
879
|
-
path.includes("usr/sbin/") ||
|
|
880
|
-
path.includes("cacerts") ||
|
|
881
|
-
path.includes("ssl/certs") ||
|
|
882
|
-
path.includes("logs/") ||
|
|
883
|
-
path.includes("dev/") ||
|
|
884
|
-
path.includes("usr/share/zoneinfo/") ||
|
|
885
|
-
path.includes("usr/share/doc/") ||
|
|
886
|
-
path.includes("usr/share/i18n/") ||
|
|
887
|
-
path.includes("var/lib/ca-certificates") ||
|
|
888
|
-
path.includes("root/.gnupg") ||
|
|
889
|
-
basename(path).startsWith(".") ||
|
|
890
|
-
path.includes("usr/share/licenses/device-mapper-libs") ||
|
|
891
|
-
[
|
|
892
|
-
"BlockDevice",
|
|
893
|
-
"CharacterDevice",
|
|
894
|
-
"FIFO",
|
|
895
|
-
"MultiVolume",
|
|
896
|
-
"TapeVolume",
|
|
897
|
-
"SymbolicLink",
|
|
898
|
-
"RenamedOrSymlinked",
|
|
899
|
-
"HardLink",
|
|
900
|
-
"Link",
|
|
901
|
-
].includes(entry.type)
|
|
908
|
+
name.startsWith(".") ||
|
|
909
|
+
EXTRACT_EXCLUDE_PATHS.some((p) => path.includes(p)) ||
|
|
910
|
+
EXTRACT_EXCLUDE_TYPES.has(entry.type)
|
|
902
911
|
);
|
|
903
912
|
},
|
|
904
913
|
}),
|
|
@@ -937,7 +946,8 @@ export const extractTar = async (fullImageName, dir, options) => {
|
|
|
937
946
|
} else if (["TAR_ENTRY_INFO", "TAR_ENTRY_INVALID"].includes(err.code)) {
|
|
938
947
|
if (
|
|
939
948
|
err?.header?.path?.includes("{") ||
|
|
940
|
-
err?.message?.includes("linkpath required")
|
|
949
|
+
err?.message?.includes("linkpath required") ||
|
|
950
|
+
err?.message?.includes("linkpath forbidden")
|
|
941
951
|
) {
|
|
942
952
|
return false;
|
|
943
953
|
}
|
package/lib/server/server.js
CHANGED
|
@@ -9,7 +9,14 @@ import compression from "compression";
|
|
|
9
9
|
import connect from "connect";
|
|
10
10
|
|
|
11
11
|
import { createBom, submitBom } from "../cli/index.js";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
getTmpDir,
|
|
14
|
+
hasDangerousUnicode,
|
|
15
|
+
isSecureMode,
|
|
16
|
+
isValidDriveRoot,
|
|
17
|
+
isWin,
|
|
18
|
+
safeSpawnSync,
|
|
19
|
+
} from "../helpers/utils.js";
|
|
13
20
|
import { postProcess } from "../stages/postgen/postgen.js";
|
|
14
21
|
|
|
15
22
|
// Timeout milliseconds. Default 10 mins
|
|
@@ -57,19 +64,90 @@ app.use(
|
|
|
57
64
|
);
|
|
58
65
|
app.use(compression());
|
|
59
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Checks the given hostname against the allowed list.
|
|
69
|
+
*
|
|
70
|
+
* @param {string} hostname Host name to check
|
|
71
|
+
* @returns {boolean} true if the hostname in its entirety is allowed. false otherwise.
|
|
72
|
+
*/
|
|
60
73
|
export function isAllowedHost(hostname) {
|
|
61
74
|
if (!process.env.CDXGEN_SERVER_ALLOWED_HOSTS) {
|
|
62
75
|
return true;
|
|
63
76
|
}
|
|
77
|
+
// Guard against dangerous Unicode characters
|
|
78
|
+
if (hasDangerousUnicode(hostname)) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
64
81
|
return (process.env.CDXGEN_SERVER_ALLOWED_HOSTS || "")
|
|
65
82
|
.split(",")
|
|
66
83
|
.includes(hostname);
|
|
67
84
|
}
|
|
68
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Checks the given path string to belong to a drive in Windows.
|
|
88
|
+
*
|
|
89
|
+
* @param {string} p Path string to check
|
|
90
|
+
* @returns {boolean} true if the windows path belongs to a drive. false otherwise (device names)
|
|
91
|
+
*/
|
|
92
|
+
export function isAllowedWinPath(p) {
|
|
93
|
+
if (typeof p !== "string") {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
if (p === "") {
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
// Guard against dangerous Unicode characters
|
|
100
|
+
if (hasDangerousUnicode(p)) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
const normalized = path.normalize(p);
|
|
105
|
+
// Check the entire normalized path for dangerous patterns
|
|
106
|
+
if (hasDangerousUnicode(normalized)) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
const { root } = path.parse(normalized);
|
|
110
|
+
// Both Relative paths and invalid windows device names are resulting in an empty root
|
|
111
|
+
// To keep things simple, we don't accept relative paths for Windows server-mode users at all
|
|
112
|
+
|
|
113
|
+
// Invocations with unix-style paths result in "\\" as the root on windows
|
|
114
|
+
// path.parse(path.normalize("/foo/bar"))
|
|
115
|
+
// { root: '\\', dir: '\\foo', base: 'bar', ext: '', name: 'bar' }
|
|
116
|
+
if (root === "\\") {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
// Check for device/UNC paths - these should always return false
|
|
120
|
+
if (root.startsWith("\\\\")) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
// Strict validation for drive letter format
|
|
124
|
+
return isValidDriveRoot(root);
|
|
125
|
+
} catch (_err) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Checks the given path against the allowed list.
|
|
132
|
+
*
|
|
133
|
+
* @param {string} p Path string to check
|
|
134
|
+
* @returns {boolean} true if the path is present in the allowed paths. false otherwise.
|
|
135
|
+
*/
|
|
69
136
|
export function isAllowedPath(p) {
|
|
137
|
+
if (typeof p !== "string") {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
// Guard against dangerous Unicode characters
|
|
141
|
+
if (hasDangerousUnicode(p)) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
70
144
|
if (!process.env.CDXGEN_SERVER_ALLOWED_PATHS) {
|
|
71
145
|
return true;
|
|
72
146
|
}
|
|
147
|
+
// Handle CVE-2025-27210 without relying entirely on node blocklists
|
|
148
|
+
if (isWin && !isAllowedWinPath(p)) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
73
151
|
return (process.env.CDXGEN_SERVER_ALLOWED_PATHS || "")
|
|
74
152
|
.split(",")
|
|
75
153
|
.some((ap) => p.startsWith(ap));
|
|
@@ -90,7 +168,8 @@ function gitClone(repoUrl, branch = null) {
|
|
|
90
168
|
tempDir,
|
|
91
169
|
];
|
|
92
170
|
if (branch) {
|
|
93
|
-
gitArgs.
|
|
171
|
+
const cloneIndex = gitArgs.indexOf("clone");
|
|
172
|
+
gitArgs.splice(cloneIndex + 1, 0, "--branch", branch);
|
|
94
173
|
}
|
|
95
174
|
console.log(
|
|
96
175
|
`Cloning Repo${branch ? ` with branch ${branch}` : ""} to ${tempDir}`,
|
|
@@ -174,6 +253,39 @@ export function parseQueryString(q, body = {}, options = {}) {
|
|
|
174
253
|
return options;
|
|
175
254
|
}
|
|
176
255
|
|
|
256
|
+
export function getQueryParams(req) {
|
|
257
|
+
try {
|
|
258
|
+
if (!req || !req.url) {
|
|
259
|
+
return {};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const protocol = req.protocol || "http";
|
|
263
|
+
const host = req.headers?.host || "localhost";
|
|
264
|
+
const baseUrl = `${protocol}://${host}`;
|
|
265
|
+
|
|
266
|
+
const fullUrl = new URL(req.url, baseUrl);
|
|
267
|
+
const params = {};
|
|
268
|
+
|
|
269
|
+
// Convert multiple values to an array
|
|
270
|
+
for (const [key, value] of fullUrl.searchParams) {
|
|
271
|
+
if (params[key]) {
|
|
272
|
+
if (Array.isArray(params[key])) {
|
|
273
|
+
params[key].push(value);
|
|
274
|
+
} else {
|
|
275
|
+
params[key] = [params[key], value];
|
|
276
|
+
}
|
|
277
|
+
} else {
|
|
278
|
+
params[key] = value;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return params;
|
|
283
|
+
} catch (error) {
|
|
284
|
+
console.error("Error parsing URL:", error);
|
|
285
|
+
return {};
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
177
289
|
const applyProfileOptions = (options) => {
|
|
178
290
|
switch (options.profile) {
|
|
179
291
|
case "appsec":
|
|
@@ -257,8 +369,7 @@ const start = (options) => {
|
|
|
257
369
|
}),
|
|
258
370
|
);
|
|
259
371
|
}
|
|
260
|
-
const
|
|
261
|
-
const q = Object.fromEntries(requestUrl.searchParams.entries());
|
|
372
|
+
const q = getQueryParams(req);
|
|
262
373
|
let cleanup = false;
|
|
263
374
|
let reqOptions = {};
|
|
264
375
|
try {
|
|
@@ -277,7 +388,7 @@ const start = (options) => {
|
|
|
277
388
|
}),
|
|
278
389
|
);
|
|
279
390
|
}
|
|
280
|
-
const filePath = q
|
|
391
|
+
const filePath = q?.path || q?.url || req?.body?.path || req?.body?.url;
|
|
281
392
|
if (!filePath) {
|
|
282
393
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
283
394
|
return res.end(
|
|
@@ -302,7 +413,10 @@ const start = (options) => {
|
|
|
302
413
|
srcDir = gitClone(filePath, reqOptions.gitBranch);
|
|
303
414
|
cleanup = true;
|
|
304
415
|
} else {
|
|
305
|
-
if (
|
|
416
|
+
if (
|
|
417
|
+
!isAllowedPath(path.resolve(srcDir)) ||
|
|
418
|
+
(isWin && !isAllowedWinPath(srcDir))
|
|
419
|
+
) {
|
|
306
420
|
res.writeHead(403, { "Content-Type": "application/json" });
|
|
307
421
|
return res.end(
|
|
308
422
|
JSON.stringify({
|