@cdxgen/cdxgen 0.0.1 → 13.0.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 +921 -87
- package/bin/audit.js +239 -0
- package/bin/cdxgen.js +2324 -0
- package/bin/convert.js +213 -0
- package/bin/evinse.js +353 -0
- package/bin/hbom.js +495 -0
- package/bin/repl.js +2224 -0
- package/bin/sign.js +195 -0
- package/bin/tracebom.js +295 -0
- package/bin/validate.js +268 -0
- package/bin/verify.js +189 -0
- package/data/README.md +307 -0
- package/data/bom-1.4.schema.json +1605 -0
- package/data/bom-1.5.schema.json +3660 -0
- package/data/bom-1.6.schema.json +5511 -0
- package/data/bom-1.7.schema.json +6700 -0
- package/data/cbomosdb-queries.json +68 -0
- package/data/component-tags.json +339 -0
- package/data/container-knowledge-index.json +125 -0
- package/data/cosdb-queries.json +17 -0
- package/data/crypto-oid.json +1998 -0
- package/data/cryptography-defs.json +3793 -0
- package/data/cryptography-defs.schema.json +576 -0
- package/data/cyclonedx-2.0-bundled.schema.json +7182 -0
- package/data/frameworks-list.json +219 -0
- package/data/glibc-stdlib.json +117 -0
- package/data/gtfobins-index.json +6296 -0
- package/data/helpers/init.gradle +71 -0
- package/data/jsf-0.82.schema.json +211 -0
- package/data/known-licenses.json +82 -0
- package/data/lic-mapping.json +338 -0
- package/data/license-aliases.json +6624 -0
- package/data/license-db.json +39059 -0
- package/data/license-deprecations.json +302 -0
- package/data/lolbas-index.json +150 -0
- package/data/predictive-audit-allowlist.json +11 -0
- package/data/pypi-pkg-aliases.json +1199 -0
- package/data/python-stdlib.json +308 -0
- package/data/queries-darwin.json +193 -0
- package/data/queries-win.json +257 -0
- package/data/queries.json +268 -0
- package/data/ruby-known-modules.json +281 -0
- package/data/rules/ai-agent-governance.yaml +323 -0
- package/data/rules/ai-bom.yaml +268 -0
- package/data/rules/ai-oversight.yaml +186 -0
- package/data/rules/ai-provenance.yaml +92 -0
- package/data/rules/asar-archives.yaml +150 -0
- package/data/rules/cbom-security.yaml +165 -0
- package/data/rules/chrome-extensions.yaml +248 -0
- package/data/rules/ci-permissions.yaml +802 -0
- package/data/rules/container-risk.yaml +353 -0
- package/data/rules/dependency-sources.yaml +236 -0
- package/data/rules/golem-go.yaml +229 -0
- package/data/rules/hbom-compliance.yaml +325 -0
- package/data/rules/hbom-performance.yaml +307 -0
- package/data/rules/hbom-security.yaml +248 -0
- package/data/rules/host-topology.yaml +165 -0
- package/data/rules/mcp-servers.yaml +388 -0
- package/data/rules/obom-runtime.yaml +1776 -0
- package/data/rules/package-integrity.yaml +364 -0
- package/data/rules/rootfs-hardening.yaml +179 -0
- package/data/rules/vscode-extensions.yaml +237 -0
- package/data/spdx-export.schema.json +6794 -0
- package/data/spdx-license-list.json +7449 -0
- package/data/spdx-licenses.json +759 -0
- package/data/spdx.schema.json +819 -0
- package/data/templates/README.md +3 -0
- package/data/templates/asvs-4.0.3.cdx.json +2943 -0
- package/data/templates/asvs-5.0.cdx.json +3046 -0
- package/data/templates/bsimm-v13.cdx.json +1008 -0
- package/data/templates/masvs-2.0.0.cdx.json +225 -0
- package/data/templates/nist-ssdf-1.1.cdx.json +828 -0
- package/data/templates/pcissc-secure-slc-1.1.cdx.json +1027 -0
- package/data/templates/scvs-1.0.0.cdx.json +813 -0
- package/data/templates/ssaf-DRAFT-2023-11.cdx.json +127 -0
- package/data/vendor-alias.json +41967 -0
- package/data/wrapdb-releases.json +4227 -0
- package/index.cjs +15 -0
- package/lib/audit/index.js +2800 -0
- package/lib/audit/progress.js +114 -0
- package/lib/audit/reporters.js +937 -0
- package/lib/audit/scoring.js +560 -0
- package/lib/audit/targets.js +798 -0
- package/lib/cli/bomAssembly.js +1833 -0
- package/lib/cli/cliOptions.js +513 -0
- package/lib/cli/index.js +2759 -0
- package/lib/cli/jsBom.js +2087 -0
- package/lib/cli/jvmBom.js +1858 -0
- package/lib/cli/managedBom.js +2083 -0
- package/lib/cli/nativeBom.js +2125 -0
- package/lib/core/activity.js +1087 -0
- package/lib/core/env.js +719 -0
- package/lib/core/fs.js +1058 -0
- package/lib/core/httpClient.js +730 -0
- package/lib/core/logger.js +142 -0
- package/lib/core/parallel.js +323 -0
- package/lib/core/paths.js +70 -0
- package/lib/core/projectConfig.js +122 -0
- package/lib/core/propertySanitizer.js +174 -0
- package/lib/core/spdx.js +5 -0
- package/lib/core/state.js +59 -0
- package/lib/core/text.js +207 -0
- package/lib/core/ui.js +876 -0
- package/lib/core/urls.js +84 -0
- package/lib/ecosystems/asarutils.js +1636 -0
- package/lib/ecosystems/bunutils.js +402 -0
- package/lib/ecosystems/caxa.js +77 -0
- package/lib/ecosystems/chromextutils.js +1174 -0
- package/lib/ecosystems/cmakeResolver.js +587 -0
- package/lib/ecosystems/cppEvidence.js +305 -0
- package/lib/ecosystems/denoutils.js +1094 -0
- package/lib/ecosystems/dotnetutils.js +132 -0
- package/lib/ecosystems/ecosystems.js +2772 -0
- package/lib/ecosystems/golem.js +1807 -0
- package/lib/ecosystems/gradleutils.js +1048 -0
- package/lib/ecosystems/jsEvidence.js +317 -0
- package/lib/ecosystems/npmutils.js +586 -0
- package/lib/ecosystems/parsers-bazel.js +875 -0
- package/lib/ecosystems/parsers-dotnet.js +1430 -0
- package/lib/ecosystems/parsers-gleam.js +302 -0
- package/lib/ecosystems/parsers-go.js +818 -0
- package/lib/ecosystems/parsers-js.js +4222 -0
- package/lib/ecosystems/parsers-jvm.js +948 -0
- package/lib/ecosystems/parsers-misc.js +3191 -0
- package/lib/ecosystems/parsers-python.js +2070 -0
- package/lib/ecosystems/parsers-rust.js +1294 -0
- package/lib/ecosystems/parsers-zig.js +861 -0
- package/lib/ecosystems/pep770.js +341 -0
- package/lib/ecosystems/pylockutils.js +427 -0
- package/lib/ecosystems/pythonutils.js +1281 -0
- package/lib/ecosystems/registryProvenance.js +1003 -0
- package/lib/ecosystems/remote/huggingface.js +1343 -0
- package/lib/ecosystems/rubyutils.js +1629 -0
- package/lib/ecosystems/rusi.js +820 -0
- package/lib/ecosystems/sbtutils.js +972 -0
- package/lib/ecosystems/tea.js +607 -0
- package/lib/ecosystems/utils.js +575 -0
- package/lib/ecosystems/zigResolver.js +410 -0
- package/lib/evinser/db.js +145 -0
- package/lib/evinser/evinser.js +2362 -0
- package/lib/evinser/scalasem.js +65 -0
- package/lib/evinser/swiftsem.js +767 -0
- package/lib/helpers/annotationFormatter.js +84 -0
- package/lib/helpers/bomSigner.js +312 -0
- package/lib/helpers/exportUtils.js +160 -0
- package/lib/helpers/remote/dependency-track.js +117 -0
- package/lib/helpers/versutils.js +202 -0
- package/lib/helpers/vsixutils.js +1069 -0
- package/lib/inventory/agentFormulationParser.js +322 -0
- package/lib/inventory/aiCollector.js +2140 -0
- package/lib/inventory/aiInventory.js +414 -0
- package/lib/inventory/aiModelVariants.js +121 -0
- package/lib/inventory/aiOversightCollector.js +661 -0
- package/lib/inventory/aiProvenanceCollector.js +822 -0
- package/lib/inventory/analyzer.js +6089 -0
- package/lib/inventory/analyzerScope.js +807 -0
- package/lib/inventory/astWorker.js +20 -0
- package/lib/inventory/atomUtils.js +1092 -0
- package/lib/inventory/auditCategories.js +192 -0
- package/lib/inventory/bomUtils.js +369 -0
- package/lib/inventory/cacheCommands.js +182 -0
- package/lib/inventory/cacheDir.js +104 -0
- package/lib/inventory/cbomutils.js +590 -0
- package/lib/inventory/cdxrs.js +434 -0
- package/lib/inventory/ciParsers/azurePipelines.js +295 -0
- package/lib/inventory/ciParsers/circleCi.js +286 -0
- package/lib/inventory/ciParsers/common.js +24 -0
- package/lib/inventory/ciParsers/githubActions.js +2434 -0
- package/lib/inventory/ciParsers/gitlabCi.js +213 -0
- package/lib/inventory/ciParsers/jenkins.js +180 -0
- package/lib/inventory/citations.js +234 -0
- package/lib/inventory/communityAiConfigParser.js +756 -0
- package/lib/inventory/containerRisk.js +204 -0
- package/lib/inventory/cryptoAlgorithmFamily.js +258 -0
- package/lib/inventory/deps.js +807 -0
- package/lib/inventory/depsUtils.js +588 -0
- package/lib/inventory/display.js +2128 -0
- package/lib/inventory/dosai.js +601 -0
- package/lib/inventory/dosaiParsers.js +154 -0
- package/lib/inventory/dynamic.js +145 -0
- package/lib/inventory/envcontext.js +1789 -0
- package/lib/inventory/evidenceUtils.js +774 -0
- package/lib/inventory/fetchBatch.js +779 -0
- package/lib/inventory/fetchRate.js +255 -0
- package/lib/inventory/forgeEnricher.js +300 -0
- package/lib/inventory/formulationParsers.js +422 -0
- package/lib/inventory/gtfobins.js +339 -0
- package/lib/inventory/hbom.js +719 -0
- package/lib/inventory/hbomAnalysis.js +310 -0
- package/lib/inventory/hbomLoader.js +35 -0
- package/lib/inventory/hostTopology.js +835 -0
- package/lib/inventory/inventoryStats.js +100 -0
- package/lib/inventory/jsonLike.js +121 -0
- package/lib/inventory/licenseEnhancer.js +918 -0
- package/lib/inventory/lolbas.js +285 -0
- package/lib/inventory/mcp.js +253 -0
- package/lib/inventory/mcpConfigParser.js +683 -0
- package/lib/inventory/mcpDiscovery.js +112 -0
- package/lib/inventory/mcpPinning.js +209 -0
- package/lib/inventory/osPackageResolver.js +493 -0
- package/lib/inventory/osinfo.js +277 -0
- package/lib/inventory/osqueryTransform.js +274 -0
- package/lib/inventory/plugins.js +466 -0
- package/lib/inventory/protobom.js +231 -0
- package/lib/inventory/protobomLoader.js +43 -0
- package/lib/inventory/provenanceUtils.js +224 -0
- package/lib/inventory/purl.js +749 -0
- package/lib/inventory/rustFormulationParser.js +339 -0
- package/lib/inventory/sbomDocument.js +340 -0
- package/lib/inventory/source.js +1488 -0
- package/lib/inventory/spdx.js +361 -0
- package/lib/inventory/spdxExpression.js +512 -0
- package/lib/inventory/spdxUtils.js +107 -0
- package/lib/inventory/table.js +263 -0
- package/lib/inventory/traceRunner.js +439 -0
- package/lib/inventory/unicodeScan.js +147 -0
- package/lib/managers/binary.js +3307 -0
- package/lib/managers/containerutils.js +125 -0
- package/lib/managers/docker.js +2145 -0
- package/lib/managers/dockerConnection.js +162 -0
- package/lib/managers/oci.js +704 -0
- package/lib/managers/piptree.js +267 -0
- package/lib/parsers/cmakeCache.js +199 -0
- package/lib/parsers/gguf.js +492 -0
- package/lib/parsers/gitAiNotes.js +233 -0
- package/lib/parsers/gitmodules.js +239 -0
- package/lib/parsers/htmlExtract.js +610 -0
- package/lib/parsers/huggingfaceManifest.js +551 -0
- package/lib/parsers/huggingfaceUtils.js +506 -0
- package/lib/parsers/iri.js +618 -0
- package/lib/parsers/npmrc.js +99 -0
- package/lib/parsers/ollama.js +101 -0
- package/lib/parsers/xml.js +841 -0
- package/lib/server/openapi.yaml +433 -0
- package/lib/server/server.js +616 -0
- package/lib/stages/postgen/annotator.js +977 -0
- package/lib/stages/postgen/auditBom.js +468 -0
- package/lib/stages/postgen/postgen.js +1239 -0
- package/lib/stages/postgen/ruleEngine.js +731 -0
- package/lib/stages/postgen/sortBom.js +195 -0
- package/lib/stages/postgen/spdxConverter.js +796 -0
- package/lib/stages/postgen/specVersionCompat.js +703 -0
- package/lib/stages/pregen/envAudit.js +388 -0
- package/lib/stages/pregen/pregen.js +637 -0
- package/lib/third-party/README.md +9 -0
- package/lib/third-party/arborist/CHANGELOG.md +1109 -0
- package/lib/third-party/arborist/LICENSE.md +20 -0
- package/lib/third-party/arborist/README.md +315 -0
- package/lib/third-party/arborist/lib/arborist/index.js +258 -0
- package/lib/third-party/arborist/lib/arborist/load-actual.js +605 -0
- package/lib/third-party/arborist/lib/arborist/load-virtual.js +300 -0
- package/lib/third-party/arborist/lib/bin-links.js +120 -0
- package/lib/third-party/arborist/lib/calc-dep-flags.js +130 -0
- package/lib/third-party/arborist/lib/case-insensitive-map.js +51 -0
- package/lib/third-party/arborist/lib/consistent-resolve.js +42 -0
- package/lib/third-party/arborist/lib/debug.js +50 -0
- package/lib/third-party/arborist/lib/dep-valid.js +190 -0
- package/lib/third-party/arborist/lib/edge.js +417 -0
- package/lib/third-party/arborist/lib/from-path.js +36 -0
- package/lib/third-party/arborist/lib/gather-dep-set.js +43 -0
- package/lib/third-party/arborist/lib/index.js +22 -0
- package/lib/third-party/arborist/lib/inventory.js +138 -0
- package/lib/third-party/arborist/lib/link.js +169 -0
- package/lib/third-party/arborist/lib/node.js +1673 -0
- package/lib/third-party/arborist/lib/npm-extension.js +45 -0
- package/lib/third-party/arborist/lib/override-resolves.js +11 -0
- package/lib/third-party/arborist/lib/override-set.js +319 -0
- package/lib/third-party/arborist/lib/package-extensions.js +289 -0
- package/lib/third-party/arborist/lib/printable.js +210 -0
- package/lib/third-party/arborist/lib/proc-log.js +30 -0
- package/lib/third-party/arborist/lib/realpath.js +100 -0
- package/lib/third-party/arborist/lib/relpath.js +4 -0
- package/lib/third-party/arborist/lib/shrinkwrap.js +1291 -0
- package/lib/third-party/arborist/lib/spec-from-lock.js +34 -0
- package/lib/third-party/arborist/lib/tree-check.js +165 -0
- package/lib/third-party/arborist/lib/validate-npm-package-name.js +107 -0
- package/lib/third-party/arborist/lib/version-from-tgz.js +51 -0
- package/lib/third-party/arborist/lib/yarn-lock.js +397 -0
- package/lib/validator/bomValidator.js +985 -0
- package/lib/validator/complianceEngine.js +241 -0
- package/lib/validator/complianceRules.js +1680 -0
- package/lib/validator/generated/runtime.mjs +17 -0
- package/lib/validator/generated/validate-1.6.mjs +21258 -0
- package/lib/validator/generated/validate-1.7.mjs +24803 -0
- package/lib/validator/generated/validate-2.0.mjs +22931 -0
- package/lib/validator/index.js +246 -0
- package/lib/validator/reporters/annotations.js +142 -0
- package/lib/validator/reporters/console.js +160 -0
- package/lib/validator/reporters/index.js +41 -0
- package/lib/validator/reporters/json.js +37 -0
- package/lib/validator/reporters/sarif.js +184 -0
- package/package.json +209 -26
- package/types/bin/audit.d.ts +3 -0
- package/types/bin/audit.d.ts.map +1 -0
- package/types/bin/cdxgen.d.ts +3 -0
- package/types/bin/cdxgen.d.ts.map +1 -0
- package/types/bin/convert.d.ts +3 -0
- package/types/bin/convert.d.ts.map +1 -0
- package/types/bin/evinse.d.ts +3 -0
- package/types/bin/evinse.d.ts.map +1 -0
- package/types/bin/hbom.d.ts +3 -0
- package/types/bin/hbom.d.ts.map +1 -0
- package/types/bin/repl.d.ts +3 -0
- package/types/bin/repl.d.ts.map +1 -0
- package/types/bin/sign.d.ts +3 -0
- package/types/bin/sign.d.ts.map +1 -0
- package/types/bin/tracebom.d.ts +3 -0
- package/types/bin/tracebom.d.ts.map +1 -0
- package/types/bin/validate.d.ts +14 -0
- package/types/bin/validate.d.ts.map +1 -0
- package/types/bin/verify.d.ts +3 -0
- package/types/bin/verify.d.ts.map +1 -0
- package/types/lib/audit/index.d.ts +153 -0
- package/types/lib/audit/index.d.ts.map +1 -0
- package/types/lib/audit/progress.d.ts +31 -0
- package/types/lib/audit/progress.d.ts.map +1 -0
- package/types/lib/audit/reporters.d.ts +62 -0
- package/types/lib/audit/reporters.d.ts.map +1 -0
- package/types/lib/audit/scoring.d.ts +36 -0
- package/types/lib/audit/scoring.d.ts.map +1 -0
- package/types/lib/audit/targets.d.ts +77 -0
- package/types/lib/audit/targets.d.ts.map +1 -0
- package/types/lib/cli/bomAssembly.d.ts +315 -0
- package/types/lib/cli/bomAssembly.d.ts.map +1 -0
- package/types/lib/cli/cliOptions.d.ts +156 -0
- package/types/lib/cli/cliOptions.d.ts.map +1 -0
- package/types/lib/cli/index.d.ts +107 -0
- package/types/lib/cli/index.d.ts.map +1 -0
- package/types/lib/cli/jsBom.d.ts +94 -0
- package/types/lib/cli/jsBom.d.ts.map +1 -0
- package/types/lib/cli/jvmBom.d.ts +54 -0
- package/types/lib/cli/jvmBom.d.ts.map +1 -0
- package/types/lib/cli/managedBom.d.ts +105 -0
- package/types/lib/cli/managedBom.d.ts.map +1 -0
- package/types/lib/cli/nativeBom.d.ts +143 -0
- package/types/lib/cli/nativeBom.d.ts.map +1 -0
- package/types/lib/core/activity.d.ts +178 -0
- package/types/lib/core/activity.d.ts.map +1 -0
- package/types/lib/core/env.d.ts +192 -0
- package/types/lib/core/env.d.ts.map +1 -0
- package/types/lib/core/fs.d.ts +158 -0
- package/types/lib/core/fs.d.ts.map +1 -0
- package/types/lib/core/httpClient.d.ts +149 -0
- package/types/lib/core/httpClient.d.ts.map +1 -0
- package/types/lib/core/logger.d.ts +28 -0
- package/types/lib/core/logger.d.ts.map +1 -0
- package/types/lib/core/parallel.d.ts +95 -0
- package/types/lib/core/parallel.d.ts.map +1 -0
- package/types/lib/core/paths.d.ts +36 -0
- package/types/lib/core/paths.d.ts.map +1 -0
- package/types/lib/core/projectConfig.d.ts +23 -0
- package/types/lib/core/projectConfig.d.ts.map +1 -0
- package/types/lib/core/propertySanitizer.d.ts +23 -0
- package/types/lib/core/propertySanitizer.d.ts.map +1 -0
- package/types/lib/core/spdx.d.ts +5 -0
- package/types/lib/core/spdx.d.ts.map +1 -0
- package/types/lib/core/state.d.ts +20 -0
- package/types/lib/core/state.d.ts.map +1 -0
- package/types/lib/core/text.d.ts +78 -0
- package/types/lib/core/text.d.ts.map +1 -0
- package/types/lib/core/ui.d.ts +87 -0
- package/types/lib/core/ui.d.ts.map +1 -0
- package/types/lib/core/urls.d.ts +43 -0
- package/types/lib/core/urls.d.ts.map +1 -0
- package/types/lib/ecosystems/asarutils.d.ts +71 -0
- package/types/lib/ecosystems/asarutils.d.ts.map +1 -0
- package/types/lib/ecosystems/bunutils.d.ts +38 -0
- package/types/lib/ecosystems/bunutils.d.ts.map +1 -0
- package/types/lib/ecosystems/caxa.d.ts +8 -0
- package/types/lib/ecosystems/caxa.d.ts.map +1 -0
- package/types/lib/ecosystems/chromextutils.d.ts +97 -0
- package/types/lib/ecosystems/chromextutils.d.ts.map +1 -0
- package/types/lib/ecosystems/cmakeResolver.d.ts +176 -0
- package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -0
- package/types/lib/ecosystems/cppEvidence.d.ts +35 -0
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -0
- package/types/lib/ecosystems/denoutils.d.ts +76 -0
- package/types/lib/ecosystems/denoutils.d.ts.map +1 -0
- package/types/lib/ecosystems/dotnetutils.d.ts +45 -0
- package/types/lib/ecosystems/dotnetutils.d.ts.map +1 -0
- package/types/lib/ecosystems/ecosystems.d.ts +279 -0
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -0
- package/types/lib/ecosystems/golem.d.ts +60 -0
- package/types/lib/ecosystems/golem.d.ts.map +1 -0
- package/types/lib/ecosystems/gradleutils.d.ts +139 -0
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -0
- package/types/lib/ecosystems/jsEvidence.d.ts +17 -0
- package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -0
- package/types/lib/ecosystems/npmutils.d.ts +183 -0
- package/types/lib/ecosystems/npmutils.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-bazel.d.ts +36 -0
- package/types/lib/ecosystems/parsers-bazel.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-dotnet.d.ts +116 -0
- package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-gleam.d.ts +33 -0
- package/types/lib/ecosystems/parsers-gleam.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-go.d.ts +92 -0
- package/types/lib/ecosystems/parsers-go.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-js.d.ts +242 -0
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-jvm.d.ts +100 -0
- package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-misc.d.ts +450 -0
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-python.d.ts +145 -0
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-rust.d.ts +56 -0
- package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-zig.d.ts +83 -0
- package/types/lib/ecosystems/parsers-zig.d.ts.map +1 -0
- package/types/lib/ecosystems/pep770.d.ts +38 -0
- package/types/lib/ecosystems/pep770.d.ts.map +1 -0
- package/types/lib/ecosystems/pylockutils.d.ts +68 -0
- package/types/lib/ecosystems/pylockutils.d.ts.map +1 -0
- package/types/lib/ecosystems/pythonutils.d.ts +70 -0
- package/types/lib/ecosystems/pythonutils.d.ts.map +1 -0
- package/types/lib/ecosystems/registryProvenance.d.ts +26 -0
- package/types/lib/ecosystems/registryProvenance.d.ts.map +1 -0
- package/types/lib/ecosystems/remote/huggingface.d.ts +31 -0
- package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -0
- package/types/lib/ecosystems/rubyutils.d.ts +182 -0
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -0
- package/types/lib/ecosystems/rusi.d.ts +40 -0
- package/types/lib/ecosystems/rusi.d.ts.map +1 -0
- package/types/lib/ecosystems/sbtutils.d.ts +179 -0
- package/types/lib/ecosystems/sbtutils.d.ts.map +1 -0
- package/types/lib/ecosystems/tea.d.ts +180 -0
- package/types/lib/ecosystems/tea.d.ts.map +1 -0
- package/types/lib/ecosystems/utils.d.ts +41 -0
- package/types/lib/ecosystems/utils.d.ts.map +1 -0
- package/types/lib/ecosystems/zigResolver.d.ts +70 -0
- package/types/lib/ecosystems/zigResolver.d.ts.map +1 -0
- package/types/lib/evinser/db.d.ts +17 -0
- package/types/lib/evinser/db.d.ts.map +1 -0
- package/types/lib/evinser/evinser.d.ts +354 -0
- package/types/lib/evinser/evinser.d.ts.map +1 -0
- package/types/lib/evinser/scalasem.d.ts +15 -0
- package/types/lib/evinser/scalasem.d.ts.map +1 -0
- package/types/lib/evinser/swiftsem.d.ts +103 -0
- package/types/lib/evinser/swiftsem.d.ts.map +1 -0
- package/types/lib/helpers/annotationFormatter.d.ts +23 -0
- package/types/lib/helpers/annotationFormatter.d.ts.map +1 -0
- package/types/lib/helpers/bomSigner.d.ts +28 -0
- package/types/lib/helpers/bomSigner.d.ts.map +1 -0
- package/types/lib/helpers/exportUtils.d.ts +52 -0
- package/types/lib/helpers/exportUtils.d.ts.map +1 -0
- package/types/lib/helpers/remote/dependency-track.d.ts +23 -0
- package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -0
- package/types/lib/helpers/versutils.d.ts +8 -0
- package/types/lib/helpers/versutils.d.ts.map +1 -0
- package/types/lib/helpers/vsixutils.d.ts +130 -0
- package/types/lib/helpers/vsixutils.d.ts.map +1 -0
- package/types/lib/inventory/agentFormulationParser.d.ts +21 -0
- package/types/lib/inventory/agentFormulationParser.d.ts.map +1 -0
- package/types/lib/inventory/aiCollector.d.ts +61 -0
- package/types/lib/inventory/aiCollector.d.ts.map +1 -0
- package/types/lib/inventory/aiInventory.d.ts +103 -0
- package/types/lib/inventory/aiInventory.d.ts.map +1 -0
- package/types/lib/inventory/aiModelVariants.d.ts +31 -0
- package/types/lib/inventory/aiModelVariants.d.ts.map +1 -0
- package/types/lib/inventory/aiOversightCollector.d.ts +28 -0
- package/types/lib/inventory/aiOversightCollector.d.ts.map +1 -0
- package/types/lib/inventory/aiProvenanceCollector.d.ts +13 -0
- package/types/lib/inventory/aiProvenanceCollector.d.ts.map +1 -0
- package/types/lib/inventory/analyzer.d.ts +157 -0
- package/types/lib/inventory/analyzer.d.ts.map +1 -0
- package/types/lib/inventory/analyzerScope.d.ts +106 -0
- package/types/lib/inventory/analyzerScope.d.ts.map +1 -0
- package/types/lib/inventory/astWorker.d.ts +9 -0
- package/types/lib/inventory/astWorker.d.ts.map +1 -0
- package/types/lib/inventory/atomUtils.d.ts +159 -0
- package/types/lib/inventory/atomUtils.d.ts.map +1 -0
- package/types/lib/inventory/auditCategories.d.ts +80 -0
- package/types/lib/inventory/auditCategories.d.ts.map +1 -0
- package/types/lib/inventory/bomUtils.d.ts +145 -0
- package/types/lib/inventory/bomUtils.d.ts.map +1 -0
- package/types/lib/inventory/cacheCommands.d.ts +28 -0
- package/types/lib/inventory/cacheCommands.d.ts.map +1 -0
- package/types/lib/inventory/cacheDir.d.ts +55 -0
- package/types/lib/inventory/cacheDir.d.ts.map +1 -0
- package/types/lib/inventory/cbomutils.d.ts +37 -0
- package/types/lib/inventory/cbomutils.d.ts.map +1 -0
- package/types/lib/inventory/cdxrs.d.ts +90 -0
- package/types/lib/inventory/cdxrs.d.ts.map +1 -0
- package/types/lib/inventory/ciParsers/azurePipelines.d.ts +27 -0
- package/types/lib/inventory/ciParsers/azurePipelines.d.ts.map +1 -0
- package/types/lib/inventory/ciParsers/circleCi.d.ts +27 -0
- package/types/lib/inventory/ciParsers/circleCi.d.ts.map +1 -0
- package/types/lib/inventory/ciParsers/common.d.ts +11 -0
- package/types/lib/inventory/ciParsers/common.d.ts.map +1 -0
- package/types/lib/inventory/ciParsers/githubActions.d.ts +38 -0
- package/types/lib/inventory/ciParsers/githubActions.d.ts.map +1 -0
- package/types/lib/inventory/ciParsers/gitlabCi.d.ts +26 -0
- package/types/lib/inventory/ciParsers/gitlabCi.d.ts.map +1 -0
- package/types/lib/inventory/ciParsers/jenkins.d.ts +26 -0
- package/types/lib/inventory/ciParsers/jenkins.d.ts.map +1 -0
- package/types/lib/inventory/citations.d.ts +90 -0
- package/types/lib/inventory/citations.d.ts.map +1 -0
- package/types/lib/inventory/communityAiConfigParser.d.ts +14 -0
- package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -0
- package/types/lib/inventory/containerRisk.d.ts +20 -0
- package/types/lib/inventory/containerRisk.d.ts.map +1 -0
- package/types/lib/inventory/cryptoAlgorithmFamily.d.ts +37 -0
- package/types/lib/inventory/cryptoAlgorithmFamily.d.ts.map +1 -0
- package/types/lib/inventory/deps.d.ts +140 -0
- package/types/lib/inventory/deps.d.ts.map +1 -0
- package/types/lib/inventory/depsUtils.d.ts +74 -0
- package/types/lib/inventory/depsUtils.d.ts.map +1 -0
- package/types/lib/inventory/display.d.ts +227 -0
- package/types/lib/inventory/display.d.ts.map +1 -0
- package/types/lib/inventory/dosai.d.ts +130 -0
- package/types/lib/inventory/dosai.d.ts.map +1 -0
- package/types/lib/inventory/dosaiParsers.d.ts +59 -0
- package/types/lib/inventory/dosaiParsers.d.ts.map +1 -0
- package/types/lib/inventory/dynamic.d.ts +25 -0
- package/types/lib/inventory/dynamic.d.ts.map +1 -0
- package/types/lib/inventory/envcontext.d.ts +377 -0
- package/types/lib/inventory/envcontext.d.ts.map +1 -0
- package/types/lib/inventory/evidenceUtils.d.ts +74 -0
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -0
- package/types/lib/inventory/fetchBatch.d.ts +127 -0
- package/types/lib/inventory/fetchBatch.d.ts.map +1 -0
- package/types/lib/inventory/fetchRate.d.ts +118 -0
- package/types/lib/inventory/fetchRate.d.ts.map +1 -0
- package/types/lib/inventory/forgeEnricher.d.ts +31 -0
- package/types/lib/inventory/forgeEnricher.d.ts.map +1 -0
- package/types/lib/inventory/formulationParsers.d.ts +50 -0
- package/types/lib/inventory/formulationParsers.d.ts.map +1 -0
- package/types/lib/inventory/gtfobins.d.ts +27 -0
- package/types/lib/inventory/gtfobins.d.ts.map +1 -0
- package/types/lib/inventory/hbom.d.ts +58 -0
- package/types/lib/inventory/hbom.d.ts.map +1 -0
- package/types/lib/inventory/hbomAnalysis.d.ts +56 -0
- package/types/lib/inventory/hbomAnalysis.d.ts.map +1 -0
- package/types/lib/inventory/hbomLoader.d.ts +7 -0
- package/types/lib/inventory/hbomLoader.d.ts.map +1 -0
- package/types/lib/inventory/hostTopology.d.ts +47 -0
- package/types/lib/inventory/hostTopology.d.ts.map +1 -0
- package/types/lib/inventory/inventoryStats.d.ts +42 -0
- package/types/lib/inventory/inventoryStats.d.ts.map +1 -0
- package/types/lib/inventory/jsonLike.d.ts +23 -0
- package/types/lib/inventory/jsonLike.d.ts.map +1 -0
- package/types/lib/inventory/licenseEnhancer.d.ts +70 -0
- package/types/lib/inventory/licenseEnhancer.d.ts.map +1 -0
- package/types/lib/inventory/lolbas.d.ts +16 -0
- package/types/lib/inventory/lolbas.d.ts.map +1 -0
- package/types/lib/inventory/mcp.d.ts +29 -0
- package/types/lib/inventory/mcp.d.ts.map +1 -0
- package/types/lib/inventory/mcpConfigParser.d.ts +20 -0
- package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -0
- package/types/lib/inventory/mcpDiscovery.d.ts +33 -0
- package/types/lib/inventory/mcpDiscovery.d.ts.map +1 -0
- package/types/lib/inventory/mcpPinning.d.ts +11 -0
- package/types/lib/inventory/mcpPinning.d.ts.map +1 -0
- package/types/lib/inventory/osPackageResolver.d.ts +49 -0
- package/types/lib/inventory/osPackageResolver.d.ts.map +1 -0
- package/types/lib/inventory/osinfo.d.ts +120 -0
- package/types/lib/inventory/osinfo.d.ts.map +1 -0
- package/types/lib/inventory/osqueryTransform.d.ts +84 -0
- package/types/lib/inventory/osqueryTransform.d.ts.map +1 -0
- package/types/lib/inventory/plugins.d.ts +58 -0
- package/types/lib/inventory/plugins.d.ts.map +1 -0
- package/types/lib/inventory/protobom.d.ts +71 -0
- package/types/lib/inventory/protobom.d.ts.map +1 -0
- package/types/lib/inventory/protobomLoader.d.ts +17 -0
- package/types/lib/inventory/protobomLoader.d.ts.map +1 -0
- package/types/lib/inventory/provenanceUtils.d.ts +98 -0
- package/types/lib/inventory/provenanceUtils.d.ts.map +1 -0
- package/types/lib/inventory/purl.d.ts +313 -0
- package/types/lib/inventory/purl.d.ts.map +1 -0
- package/types/lib/inventory/rustFormulationParser.d.ts +17 -0
- package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -0
- package/types/lib/inventory/sbomDocument.d.ts +59 -0
- package/types/lib/inventory/sbomDocument.d.ts.map +1 -0
- package/types/lib/inventory/source.d.ts +163 -0
- package/types/lib/inventory/source.d.ts.map +1 -0
- package/types/lib/inventory/spdx.d.ts +66 -0
- package/types/lib/inventory/spdx.d.ts.map +1 -0
- package/types/lib/inventory/spdxExpression.d.ts +58 -0
- package/types/lib/inventory/spdxExpression.d.ts.map +1 -0
- package/types/lib/inventory/spdxUtils.d.ts +12 -0
- package/types/lib/inventory/spdxUtils.d.ts.map +1 -0
- package/types/lib/inventory/table.d.ts +19 -0
- package/types/lib/inventory/table.d.ts.map +1 -0
- package/types/lib/inventory/traceRunner.d.ts +80 -0
- package/types/lib/inventory/traceRunner.d.ts.map +1 -0
- package/types/lib/inventory/unicodeScan.d.ts +46 -0
- package/types/lib/inventory/unicodeScan.d.ts.map +1 -0
- package/types/lib/managers/binary.d.ts +76 -0
- package/types/lib/managers/binary.d.ts.map +1 -0
- package/types/lib/managers/containerutils.d.ts +30 -0
- package/types/lib/managers/containerutils.d.ts.map +1 -0
- package/types/lib/managers/docker.d.ts +157 -0
- package/types/lib/managers/docker.d.ts.map +1 -0
- package/types/lib/managers/dockerConnection.d.ts +42 -0
- package/types/lib/managers/dockerConnection.d.ts.map +1 -0
- package/types/lib/managers/oci.d.ts +21 -0
- package/types/lib/managers/oci.d.ts.map +1 -0
- package/types/lib/managers/piptree.d.ts +13 -0
- package/types/lib/managers/piptree.d.ts.map +1 -0
- package/types/lib/parsers/cmakeCache.d.ts +70 -0
- package/types/lib/parsers/cmakeCache.d.ts.map +1 -0
- package/types/lib/parsers/gguf.d.ts +39 -0
- package/types/lib/parsers/gguf.d.ts.map +1 -0
- package/types/lib/parsers/gitAiNotes.d.ts +15 -0
- package/types/lib/parsers/gitAiNotes.d.ts.map +1 -0
- package/types/lib/parsers/gitmodules.d.ts +88 -0
- package/types/lib/parsers/gitmodules.d.ts.map +1 -0
- package/types/lib/parsers/htmlExtract.d.ts +34 -0
- package/types/lib/parsers/htmlExtract.d.ts.map +1 -0
- package/types/lib/parsers/huggingfaceManifest.d.ts +153 -0
- package/types/lib/parsers/huggingfaceManifest.d.ts.map +1 -0
- package/types/lib/parsers/huggingfaceUtils.d.ts +155 -0
- package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -0
- package/types/lib/parsers/iri.d.ts +68 -0
- package/types/lib/parsers/iri.d.ts.map +1 -0
- package/types/lib/parsers/npmrc.d.ts +33 -0
- package/types/lib/parsers/npmrc.d.ts.map +1 -0
- package/types/lib/parsers/ollama.d.ts +22 -0
- package/types/lib/parsers/ollama.d.ts.map +1 -0
- package/types/lib/parsers/xml.d.ts +47 -0
- package/types/lib/parsers/xml.d.ts.map +1 -0
- package/types/lib/server/server.d.ts +51 -0
- package/types/lib/server/server.d.ts.map +1 -0
- package/types/lib/stages/postgen/annotator.d.ts +36 -0
- package/types/lib/stages/postgen/annotator.d.ts.map +1 -0
- package/types/lib/stages/postgen/auditBom.d.ts +92 -0
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -0
- package/types/lib/stages/postgen/postgen.d.ts +59 -0
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -0
- package/types/lib/stages/postgen/ruleEngine.d.ts +18 -0
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -0
- package/types/lib/stages/postgen/sortBom.d.ts +34 -0
- package/types/lib/stages/postgen/sortBom.d.ts.map +1 -0
- package/types/lib/stages/postgen/spdxConverter.d.ts +11 -0
- package/types/lib/stages/postgen/spdxConverter.d.ts.map +1 -0
- package/types/lib/stages/postgen/specVersionCompat.d.ts +71 -0
- package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -0
- package/types/lib/stages/pregen/envAudit.d.ts +14 -0
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -0
- package/types/lib/stages/pregen/pregen.d.ts +59 -0
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/arborist/index.d.ts +47 -0
- package/types/lib/third-party/arborist/lib/arborist/index.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/arborist/load-actual.d.ts +4 -0
- package/types/lib/third-party/arborist/lib/arborist/load-actual.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/arborist/load-virtual.d.ts +4 -0
- package/types/lib/third-party/arborist/lib/arborist/load-virtual.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/bin-links.d.ts +18 -0
- package/types/lib/third-party/arborist/lib/bin-links.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/calc-dep-flags.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/calc-dep-flags.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/case-insensitive-map.d.ts +9 -0
- package/types/lib/third-party/arborist/lib/case-insensitive-map.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/consistent-resolve.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/consistent-resolve.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/debug.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/debug.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/dep-valid.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/dep-valid.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/edge.d.ts +46 -0
- package/types/lib/third-party/arborist/lib/edge.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/from-path.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/from-path.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/gather-dep-set.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/gather-dep-set.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/index.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/index.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/inventory.d.ts +14 -0
- package/types/lib/third-party/arborist/lib/inventory.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/link.d.ts +18 -0
- package/types/lib/third-party/arborist/lib/link.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/node.d.ts +112 -0
- package/types/lib/third-party/arborist/lib/node.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/npm-extension.d.ts +18 -0
- package/types/lib/third-party/arborist/lib/npm-extension.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/override-resolves.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/override-resolves.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/override-set.d.ts +27 -0
- package/types/lib/third-party/arborist/lib/override-set.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/package-extensions.d.ts +36 -0
- package/types/lib/third-party/arborist/lib/package-extensions.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/printable.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/printable.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/proc-log.d.ts +20 -0
- package/types/lib/third-party/arborist/lib/proc-log.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/realpath.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/realpath.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/relpath.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/relpath.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/shrinkwrap.d.ts +54 -0
- package/types/lib/third-party/arborist/lib/shrinkwrap.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/spec-from-lock.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/spec-from-lock.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/tree-check.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/tree-check.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/validate-npm-package-name.d.ts +13 -0
- package/types/lib/third-party/arborist/lib/validate-npm-package-name.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/version-from-tgz.d.ts +6 -0
- package/types/lib/third-party/arborist/lib/version-from-tgz.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/yarn-lock.d.ts +35 -0
- package/types/lib/third-party/arborist/lib/yarn-lock.d.ts.map +1 -0
- package/types/lib/validator/bomValidator.d.ts +40 -0
- package/types/lib/validator/bomValidator.d.ts.map +1 -0
- package/types/lib/validator/complianceEngine.d.ts +72 -0
- package/types/lib/validator/complianceEngine.d.ts.map +1 -0
- package/types/lib/validator/complianceRules.d.ts +61 -0
- package/types/lib/validator/complianceRules.d.ts.map +1 -0
- package/types/lib/validator/index.d.ts +101 -0
- package/types/lib/validator/index.d.ts.map +1 -0
- package/types/lib/validator/reporters/annotations.d.ts +37 -0
- package/types/lib/validator/reporters/annotations.d.ts.map +1 -0
- package/types/lib/validator/reporters/console.d.ts +34 -0
- package/types/lib/validator/reporters/console.d.ts.map +1 -0
- package/types/lib/validator/reporters/index.d.ts +30 -0
- package/types/lib/validator/reporters/index.d.ts.map +1 -0
- package/types/lib/validator/reporters/json.d.ts +11 -0
- package/types/lib/validator/reporters/json.d.ts.map +1 -0
- package/types/lib/validator/reporters/sarif.d.ts +22 -0
- package/types/lib/validator/reporters/sarif.d.ts.map +1 -0
- package/mod.d.ts +0 -7
- package/mod.js +0 -22
|
@@ -0,0 +1,2145 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import {
|
|
3
|
+
createReadStream,
|
|
4
|
+
lstatSync,
|
|
5
|
+
readdirSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
} from "node:fs";
|
|
8
|
+
import { platform as _platform, userInfo as _userInfo, homedir } from "node:os";
|
|
9
|
+
import { basename, join, resolve, win32 } from "node:path";
|
|
10
|
+
import process from "node:process";
|
|
11
|
+
import stream from "node:stream/promises";
|
|
12
|
+
import { URL } from "node:url";
|
|
13
|
+
|
|
14
|
+
import { x } from "tar";
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
DEBUG_MODE,
|
|
18
|
+
isDryRun,
|
|
19
|
+
readEnvironmentVariable,
|
|
20
|
+
recordActivity,
|
|
21
|
+
recordDecisionActivity,
|
|
22
|
+
recordSensitiveFileRead,
|
|
23
|
+
} from "../core/activity.js";
|
|
24
|
+
import { extractPathEnv } from "../core/env.js";
|
|
25
|
+
import {
|
|
26
|
+
getAllFiles,
|
|
27
|
+
getTmpDir,
|
|
28
|
+
safeExistsSync,
|
|
29
|
+
safeExtractArchive,
|
|
30
|
+
safeMkdirSync,
|
|
31
|
+
safeMkdtempSync,
|
|
32
|
+
safeRmSync,
|
|
33
|
+
safeSpawnSync,
|
|
34
|
+
safeWriteSync,
|
|
35
|
+
} from "../core/fs.js";
|
|
36
|
+
import { ui } from "../core/ui.js";
|
|
37
|
+
import { getDirs, getOnlyDirs, getOnlyDirsMulti } from "./containerutils.js";
|
|
38
|
+
import { createDaemonConnection } from "./dockerConnection.js";
|
|
39
|
+
|
|
40
|
+
/** True when the current operating system is Windows. */
|
|
41
|
+
export const isWin = _platform() === "win32";
|
|
42
|
+
/**
|
|
43
|
+
* Canonical Docker Hub registry hostname used as the default registry when
|
|
44
|
+
* parsing image names.
|
|
45
|
+
*
|
|
46
|
+
* @type {string}
|
|
47
|
+
*/
|
|
48
|
+
export const DOCKER_HUB_REGISTRY = "docker.io";
|
|
49
|
+
// Docker commonly stores Hub credentials under index.docker.io or
|
|
50
|
+
// registry-1.docker.io while pulls target docker.io.
|
|
51
|
+
const DOCKER_HUB_REGISTRY_ALIASES = new Set([
|
|
52
|
+
DOCKER_HUB_REGISTRY,
|
|
53
|
+
"index.docker.io",
|
|
54
|
+
"registry-1.docker.io",
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Encode a value as base64url (RFC 4648 §5) with padding.
|
|
59
|
+
* Docker Engine decodes X-Registry-Auth with Go's base64.URLEncoding,
|
|
60
|
+
* which uses the URL-safe alphabet (-_ instead of +/) and expects = padding.
|
|
61
|
+
* Node's "base64url" omits padding, so we convert from standard base64.
|
|
62
|
+
*/
|
|
63
|
+
const toBase64Url = (value) =>
|
|
64
|
+
Buffer.from(value).toString("base64").replace(/\+/g, "-").replace(/\//g, "_");
|
|
65
|
+
|
|
66
|
+
const normalizeRegistryPath = (registryPath) => {
|
|
67
|
+
if (!registryPath || registryPath === "/") {
|
|
68
|
+
return "";
|
|
69
|
+
}
|
|
70
|
+
let normalizedPath = registryPath.trim();
|
|
71
|
+
if (!normalizedPath.startsWith("/")) {
|
|
72
|
+
normalizedPath = `/${normalizedPath}`;
|
|
73
|
+
}
|
|
74
|
+
while (normalizedPath.endsWith("/")) {
|
|
75
|
+
normalizedPath = normalizedPath.slice(0, -1);
|
|
76
|
+
}
|
|
77
|
+
const lowerCasePath = normalizedPath.toLowerCase();
|
|
78
|
+
if (lowerCasePath.endsWith("/v1") || lowerCasePath.endsWith("/v2")) {
|
|
79
|
+
normalizedPath = normalizedPath.slice(0, -3);
|
|
80
|
+
}
|
|
81
|
+
return normalizedPath === "/" ? "" : normalizedPath;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const buildRegistryAuthority = (hostname, port) => {
|
|
85
|
+
if (!hostname) {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
hostname = hostname.toLowerCase();
|
|
89
|
+
if (hostname.includes(":") && !hostname.startsWith("[")) {
|
|
90
|
+
hostname = `[${hostname}]`;
|
|
91
|
+
}
|
|
92
|
+
if (port) {
|
|
93
|
+
return `${hostname}:${port}`;
|
|
94
|
+
}
|
|
95
|
+
return hostname;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const parseRawRegistryAuthority = (authority) => {
|
|
99
|
+
if (!authority?.trim()) {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
authority = authority.trim();
|
|
103
|
+
if (authority.startsWith("[")) {
|
|
104
|
+
const closingBracketIndex = authority.indexOf("]");
|
|
105
|
+
if (closingBracketIndex === -1) {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
const hostname = authority.slice(0, closingBracketIndex + 1);
|
|
109
|
+
const portSuffix = authority.slice(closingBracketIndex + 1);
|
|
110
|
+
if (!portSuffix) {
|
|
111
|
+
return buildRegistryAuthority(hostname);
|
|
112
|
+
}
|
|
113
|
+
if (!/^:\d+$/.test(portSuffix)) {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
return buildRegistryAuthority(hostname, portSuffix.slice(1));
|
|
117
|
+
}
|
|
118
|
+
const colonIndex = authority.lastIndexOf(":");
|
|
119
|
+
if (colonIndex > -1 && authority.indexOf(":") === colonIndex) {
|
|
120
|
+
const portCandidate = authority.slice(colonIndex + 1);
|
|
121
|
+
if (/^\d+$/.test(portCandidate)) {
|
|
122
|
+
return buildRegistryAuthority(
|
|
123
|
+
authority.slice(0, colonIndex),
|
|
124
|
+
portCandidate,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return buildRegistryAuthority(authority);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const parseRegistryReference = (registry) => {
|
|
132
|
+
if (!registry?.trim()) {
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
registry = registry.trim();
|
|
136
|
+
if (registry.includes("://")) {
|
|
137
|
+
if (!URL.canParse(registry)) {
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
const registryUrl = new URL(registry);
|
|
141
|
+
const authoritySource = registry
|
|
142
|
+
.slice(registry.indexOf("://") + 3)
|
|
143
|
+
.split("/")[0];
|
|
144
|
+
return {
|
|
145
|
+
authority: parseRawRegistryAuthority(authoritySource),
|
|
146
|
+
path: normalizeRegistryPath(registryUrl.pathname),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
const slashIndex = registry.indexOf("/");
|
|
150
|
+
const authority =
|
|
151
|
+
slashIndex === -1 ? registry : registry.slice(0, slashIndex);
|
|
152
|
+
const registryPath = slashIndex === -1 ? "" : registry.slice(slashIndex);
|
|
153
|
+
if (!authority) {
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
try {
|
|
157
|
+
// Raw registry references such as host:port are not absolute URLs, so we
|
|
158
|
+
// add an https scheme only to parse the authority and optional port.
|
|
159
|
+
return {
|
|
160
|
+
authority: parseRawRegistryAuthority(authority),
|
|
161
|
+
path: normalizeRegistryPath(registryPath),
|
|
162
|
+
};
|
|
163
|
+
} catch (_err) {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const looksLikeImageReference = (value) => {
|
|
169
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
value = value.trim();
|
|
173
|
+
if (value.includes("://")) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
if (!value.includes("/")) {
|
|
177
|
+
if (value.includes(":")) {
|
|
178
|
+
const tagOrPortSuffix = value.slice(value.lastIndexOf(":") + 1);
|
|
179
|
+
if (!/^\d+$/.test(tagOrPortSuffix)) {
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
return !parseRegistryReference(value)?.authority;
|
|
183
|
+
}
|
|
184
|
+
return !(
|
|
185
|
+
value.includes(".") ||
|
|
186
|
+
value === "localhost" ||
|
|
187
|
+
value.startsWith("[")
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
const firstSegment = value.split("/")[0];
|
|
191
|
+
return !(
|
|
192
|
+
parseRegistryReference(firstSegment)?.authority &&
|
|
193
|
+
(firstSegment.includes(".") ||
|
|
194
|
+
firstSegment.includes(":") ||
|
|
195
|
+
firstSegment === "localhost" ||
|
|
196
|
+
firstSegment.startsWith("["))
|
|
197
|
+
);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const resolveRequestedRegistryRef = (forRegistry, requestedRegistryRef) => {
|
|
201
|
+
const fallbackRegistry =
|
|
202
|
+
forRegistry ||
|
|
203
|
+
readEnvironmentVariable("DOCKER_SERVER_ADDRESS") ||
|
|
204
|
+
DOCKER_HUB_REGISTRY;
|
|
205
|
+
if (
|
|
206
|
+
typeof requestedRegistryRef !== "string" ||
|
|
207
|
+
!requestedRegistryRef.trim()
|
|
208
|
+
) {
|
|
209
|
+
return fallbackRegistry;
|
|
210
|
+
}
|
|
211
|
+
requestedRegistryRef = requestedRegistryRef.trim();
|
|
212
|
+
if (requestedRegistryRef.includes("://")) {
|
|
213
|
+
return requestedRegistryRef;
|
|
214
|
+
}
|
|
215
|
+
return looksLikeImageReference(requestedRegistryRef)
|
|
216
|
+
? fallbackRegistry
|
|
217
|
+
: requestedRegistryRef;
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const extractRequestedRegistryRefFromPath = (path, forRegistry) => {
|
|
221
|
+
if (!path?.includes("?")) {
|
|
222
|
+
return resolveRequestedRegistryRef(forRegistry, forRegistry);
|
|
223
|
+
}
|
|
224
|
+
const queryString = path.slice(path.indexOf("?") + 1);
|
|
225
|
+
const requestedImageRef = new URLSearchParams(queryString).get("fromImage");
|
|
226
|
+
return resolveRequestedRegistryRef(
|
|
227
|
+
forRegistry,
|
|
228
|
+
requestedImageRef || forRegistry,
|
|
229
|
+
);
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const normalizeRegistryReference = (registry) => {
|
|
233
|
+
const parsedRegistry = parseRegistryReference(registry);
|
|
234
|
+
if (!parsedRegistry?.authority) {
|
|
235
|
+
return undefined;
|
|
236
|
+
}
|
|
237
|
+
return parsedRegistry.path
|
|
238
|
+
? `${parsedRegistry.authority}${parsedRegistry.path}`
|
|
239
|
+
: parsedRegistry.authority;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const registriesMatch = (configuredRegistry, requestedRegistry) => {
|
|
243
|
+
if (!requestedRegistry) {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
const normalizedConfiguredRegistry =
|
|
247
|
+
parseRegistryReference(configuredRegistry);
|
|
248
|
+
const normalizedRequestedRegistry = parseRegistryReference(requestedRegistry);
|
|
249
|
+
if (
|
|
250
|
+
!normalizedConfiguredRegistry?.authority ||
|
|
251
|
+
!normalizedRequestedRegistry?.authority
|
|
252
|
+
) {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
const hostMatches =
|
|
256
|
+
normalizedConfiguredRegistry.authority ===
|
|
257
|
+
normalizedRequestedRegistry.authority ||
|
|
258
|
+
(DOCKER_HUB_REGISTRY_ALIASES.has(normalizedConfiguredRegistry.authority) &&
|
|
259
|
+
DOCKER_HUB_REGISTRY_ALIASES.has(normalizedRequestedRegistry.authority));
|
|
260
|
+
if (!hostMatches) {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
if (!normalizedConfiguredRegistry.path) {
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
return (
|
|
267
|
+
normalizedConfiguredRegistry.path === normalizedRequestedRegistry.path ||
|
|
268
|
+
normalizedRequestedRegistry.path.startsWith(
|
|
269
|
+
`${normalizedConfiguredRegistry.path}/`,
|
|
270
|
+
)
|
|
271
|
+
);
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
// Should we extract the tar image in non-strict mode
|
|
275
|
+
const NON_STRICT_TAR_EXTRACT = ["true", "1"].includes(
|
|
276
|
+
process?.env?.NON_STRICT_TAR_EXTRACT,
|
|
277
|
+
);
|
|
278
|
+
if (NON_STRICT_TAR_EXTRACT) {
|
|
279
|
+
console.log(
|
|
280
|
+
"Warning: Extracting container images and tar files in non-strict mode could lead to security risks!",
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
let dockerConn;
|
|
285
|
+
let isPodman = false;
|
|
286
|
+
let isPodmanRootless = true;
|
|
287
|
+
let isDockerRootless = false;
|
|
288
|
+
// https://github.com/containerd/containerd
|
|
289
|
+
let isContainerd = !!readEnvironmentVariable("CONTAINERD_ADDRESS");
|
|
290
|
+
const WIN_LOCAL_TLS = "http://localhost:2375";
|
|
291
|
+
let isWinLocalTLS = false;
|
|
292
|
+
let isNerdctl;
|
|
293
|
+
let isColima;
|
|
294
|
+
|
|
295
|
+
if (
|
|
296
|
+
!readEnvironmentVariable("DOCKER_HOST") &&
|
|
297
|
+
(readEnvironmentVariable("CONTAINERD_ADDRESS") ||
|
|
298
|
+
(readEnvironmentVariable("XDG_RUNTIME_DIR") &&
|
|
299
|
+
safeExistsSync(
|
|
300
|
+
join(
|
|
301
|
+
readEnvironmentVariable("XDG_RUNTIME_DIR"),
|
|
302
|
+
"containerd-rootless",
|
|
303
|
+
"api.sock",
|
|
304
|
+
),
|
|
305
|
+
)))
|
|
306
|
+
) {
|
|
307
|
+
isContainerd = true;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Strip absolute path prefixes from a path string, handling both Unix and
|
|
312
|
+
* Windows paths (including UNC and extended-length paths such as //?/C:/).
|
|
313
|
+
* Taken from https://github.com/isaacs/node-tar/blob/main/src/strip-absolute-path.ts
|
|
314
|
+
*
|
|
315
|
+
* @param {string} path The path to strip
|
|
316
|
+
* @returns {string} The path with its absolute root removed
|
|
317
|
+
*/
|
|
318
|
+
export const stripAbsolutePath = (path) => {
|
|
319
|
+
// This appears to be a most frequent case, so let's return quickly.
|
|
320
|
+
if (path === "/") {
|
|
321
|
+
return "";
|
|
322
|
+
}
|
|
323
|
+
let parsed = win32.parse(path);
|
|
324
|
+
while (win32.isAbsolute(path) || parsed.root) {
|
|
325
|
+
// windows will think that //x/y/z has a "root" of //x/y/
|
|
326
|
+
// but strip the //?/C:/ off of //?/C:/path
|
|
327
|
+
const root =
|
|
328
|
+
path.charAt(0) === "/" && path.slice(0, 4) !== "//?/" ? "/" : parsed.root;
|
|
329
|
+
path = path.slice(root.length);
|
|
330
|
+
parsed = win32.parse(path);
|
|
331
|
+
}
|
|
332
|
+
return path;
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Detect colima
|
|
337
|
+
*/
|
|
338
|
+
export function detectColima() {
|
|
339
|
+
if (isColima) {
|
|
340
|
+
return true;
|
|
341
|
+
}
|
|
342
|
+
if (_platform() === "darwin") {
|
|
343
|
+
const result = safeSpawnSync("colima", ["version"]);
|
|
344
|
+
if (result.status !== 0 || result.error) {
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
if (result?.stdout?.includes("colima version")) {
|
|
348
|
+
isColima = true;
|
|
349
|
+
console.log(
|
|
350
|
+
"Colima is known to have issues with volume mounts, which might result in incomplete BOM. Use it with caution!",
|
|
351
|
+
);
|
|
352
|
+
if (result?.stdout?.includes("runtime: containerd")) {
|
|
353
|
+
isNerdctl = true;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return isColima;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Detect if Rancher desktop is running on a mac.
|
|
362
|
+
*/
|
|
363
|
+
export function detectRancherDesktop() {
|
|
364
|
+
// Detect Rancher desktop and nerdctl on a mac
|
|
365
|
+
if (_platform() === "darwin") {
|
|
366
|
+
const limaHome = join(
|
|
367
|
+
homedir(),
|
|
368
|
+
"Library",
|
|
369
|
+
"Application Support",
|
|
370
|
+
"rancher-desktop",
|
|
371
|
+
"lima",
|
|
372
|
+
);
|
|
373
|
+
const limactl = join(
|
|
374
|
+
"/Applications",
|
|
375
|
+
"Rancher Desktop.app",
|
|
376
|
+
"Contents",
|
|
377
|
+
"Resources",
|
|
378
|
+
"resources",
|
|
379
|
+
"darwin",
|
|
380
|
+
"lima",
|
|
381
|
+
"bin",
|
|
382
|
+
"limactl",
|
|
383
|
+
);
|
|
384
|
+
// Is Rancher Desktop running
|
|
385
|
+
if (safeExistsSync(limactl) || safeExistsSync(limaHome)) {
|
|
386
|
+
const result = safeSpawnSync("rdctl", ["list-settings"]);
|
|
387
|
+
if (result.status !== 0 || result.error) {
|
|
388
|
+
if (
|
|
389
|
+
isNerdctl === undefined &&
|
|
390
|
+
result.stderr?.includes("connection refused")
|
|
391
|
+
) {
|
|
392
|
+
console.warn(
|
|
393
|
+
"Ensure Rancher Desktop is running prior to invoking cdxgen. To start from the command line, type the command 'rdctl start'",
|
|
394
|
+
);
|
|
395
|
+
isNerdctl = false;
|
|
396
|
+
}
|
|
397
|
+
} else {
|
|
398
|
+
if (DEBUG_MODE) {
|
|
399
|
+
console.log("Rancher Desktop found!");
|
|
400
|
+
}
|
|
401
|
+
isNerdctl = true;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return isNerdctl;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Cache the registry auth keys
|
|
409
|
+
const registry_auth_keys = {};
|
|
410
|
+
const REQUEST_TIMEOUT_SECS = 60000;
|
|
411
|
+
/**
|
|
412
|
+
* Build a `got` options object for Docker / registry API requests. Resolves
|
|
413
|
+
* authentication headers by consulting (in order) the DOCKER_AUTH_CONFIG
|
|
414
|
+
* environment variable, DOCKER_USER/DOCKER_PASSWORD/DOCKER_EMAIL environment
|
|
415
|
+
* variables, hardcoded tokens in ~/.docker/config.json, credential helpers
|
|
416
|
+
* listed in credHelpers/credsStore, and finally TLS certificate files pointed
|
|
417
|
+
* to by DOCKER_CERT_PATH.
|
|
418
|
+
*
|
|
419
|
+
* @param {string} [forRegistry] Registry hostname (e.g. "registry-1.docker.io").
|
|
420
|
+
* Defaults to DOCKER_SERVER_ADDRESS env var or "docker.io".
|
|
421
|
+
* @param {string} [requestedRegistryRef] Requested registry/image reference used
|
|
422
|
+
* to scope config.json auth matching. Unqualified images default to Docker Hub.
|
|
423
|
+
* @returns {Object} Options object suitable for passing to `got`
|
|
424
|
+
*/
|
|
425
|
+
const getDefaultOptions = (forRegistry, requestedRegistryRef = forRegistry) => {
|
|
426
|
+
let authTokenSet = false;
|
|
427
|
+
const credentialSourceEvaluations = [];
|
|
428
|
+
let selectedCredentialSource;
|
|
429
|
+
const noteCredentialSource = (source, outcome, detail = undefined) => {
|
|
430
|
+
credentialSourceEvaluations.push({
|
|
431
|
+
detail,
|
|
432
|
+
outcome,
|
|
433
|
+
source,
|
|
434
|
+
});
|
|
435
|
+
if (outcome === "selected") {
|
|
436
|
+
selectedCredentialSource = source;
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
const dockerServerAddress = readEnvironmentVariable("DOCKER_SERVER_ADDRESS");
|
|
440
|
+
const dockerConfig = readEnvironmentVariable("DOCKER_CONFIG");
|
|
441
|
+
const dockerAuthConfig = readEnvironmentVariable("DOCKER_AUTH_CONFIG", {
|
|
442
|
+
sensitive: true,
|
|
443
|
+
});
|
|
444
|
+
const dockerUser = readEnvironmentVariable("DOCKER_USER", {
|
|
445
|
+
sensitive: true,
|
|
446
|
+
});
|
|
447
|
+
const dockerPassword = readEnvironmentVariable("DOCKER_PASSWORD", {
|
|
448
|
+
sensitive: true,
|
|
449
|
+
});
|
|
450
|
+
const dockerEmail = readEnvironmentVariable("DOCKER_EMAIL", {
|
|
451
|
+
sensitive: true,
|
|
452
|
+
});
|
|
453
|
+
if (!forRegistry) {
|
|
454
|
+
forRegistry = dockerServerAddress ?? DOCKER_HUB_REGISTRY;
|
|
455
|
+
}
|
|
456
|
+
requestedRegistryRef = resolveRequestedRegistryRef(
|
|
457
|
+
forRegistry,
|
|
458
|
+
requestedRegistryRef,
|
|
459
|
+
);
|
|
460
|
+
const normalizedForRegistry =
|
|
461
|
+
parseRegistryReference(forRegistry)?.authority ?? forRegistry;
|
|
462
|
+
const authDecisionTarget =
|
|
463
|
+
requestedRegistryRef ||
|
|
464
|
+
normalizedForRegistry ||
|
|
465
|
+
forRegistry ||
|
|
466
|
+
DOCKER_HUB_REGISTRY;
|
|
467
|
+
const opts = {
|
|
468
|
+
enableUnixSockets: true,
|
|
469
|
+
throwHttpErrors: true,
|
|
470
|
+
method: "GET",
|
|
471
|
+
timeout: {
|
|
472
|
+
request: REQUEST_TIMEOUT_SECS,
|
|
473
|
+
socket: REQUEST_TIMEOUT_SECS,
|
|
474
|
+
},
|
|
475
|
+
retry: {
|
|
476
|
+
limit: 3,
|
|
477
|
+
methods: ["GET", "POST", "HEAD"],
|
|
478
|
+
statusCodes: [408, 413, 429, 500, 502, 503, 504, 521, 522, 524],
|
|
479
|
+
},
|
|
480
|
+
hooks: { beforeError: [] },
|
|
481
|
+
mutableDefaults: true,
|
|
482
|
+
};
|
|
483
|
+
const DOCKER_CONFIG = dockerConfig || join(homedir(), ".docker");
|
|
484
|
+
const dockerConfigFile = join(DOCKER_CONFIG, "config.json");
|
|
485
|
+
// Support for private registry
|
|
486
|
+
if (dockerAuthConfig) {
|
|
487
|
+
opts.headers = {
|
|
488
|
+
"X-Registry-Auth": dockerAuthConfig,
|
|
489
|
+
};
|
|
490
|
+
authTokenSet = true;
|
|
491
|
+
noteCredentialSource("DOCKER_AUTH_CONFIG", "selected");
|
|
492
|
+
} else {
|
|
493
|
+
noteCredentialSource("DOCKER_AUTH_CONFIG", "skipped", "not set");
|
|
494
|
+
}
|
|
495
|
+
if (
|
|
496
|
+
!authTokenSet &&
|
|
497
|
+
dockerUser &&
|
|
498
|
+
dockerPassword &&
|
|
499
|
+
dockerEmail &&
|
|
500
|
+
normalizedForRegistry
|
|
501
|
+
) {
|
|
502
|
+
const authPayload = {
|
|
503
|
+
username: dockerUser,
|
|
504
|
+
email: dockerEmail,
|
|
505
|
+
serveraddress: normalizedForRegistry,
|
|
506
|
+
};
|
|
507
|
+
if (dockerUser === "<token>") {
|
|
508
|
+
authPayload.IdentityToken = dockerPassword;
|
|
509
|
+
} else {
|
|
510
|
+
authPayload.password = dockerPassword;
|
|
511
|
+
}
|
|
512
|
+
opts.headers = {
|
|
513
|
+
"X-Registry-Auth": toBase64Url(JSON.stringify(authPayload)),
|
|
514
|
+
};
|
|
515
|
+
authTokenSet = true;
|
|
516
|
+
noteCredentialSource(
|
|
517
|
+
"DOCKER_USER/DOCKER_PASSWORD/DOCKER_EMAIL",
|
|
518
|
+
"selected",
|
|
519
|
+
);
|
|
520
|
+
} else if (!authTokenSet) {
|
|
521
|
+
noteCredentialSource(
|
|
522
|
+
"DOCKER_USER/DOCKER_PASSWORD/DOCKER_EMAIL",
|
|
523
|
+
"skipped",
|
|
524
|
+
dockerUser || dockerPassword || dockerEmail
|
|
525
|
+
? "incomplete environment credentials"
|
|
526
|
+
: "not set",
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
if (!authTokenSet && safeExistsSync(dockerConfigFile)) {
|
|
530
|
+
const configData = readFileSync(dockerConfigFile, "utf-8");
|
|
531
|
+
recordSensitiveFileRead(dockerConfigFile, {
|
|
532
|
+
label: "Docker credential file",
|
|
533
|
+
});
|
|
534
|
+
if (configData) {
|
|
535
|
+
try {
|
|
536
|
+
const configJson = JSON.parse(configData);
|
|
537
|
+
if (configJson.auths) {
|
|
538
|
+
// Check if there are hardcoded tokens
|
|
539
|
+
for (const serverAddress of Object.keys(configJson.auths)) {
|
|
540
|
+
if (!registriesMatch(serverAddress, requestedRegistryRef)) {
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
543
|
+
if (configJson.auths[serverAddress].auth) {
|
|
544
|
+
// The Docker config stores auth as base64("user:pass").
|
|
545
|
+
// The X-Registry-Auth header expects base64-encoded JSON.
|
|
546
|
+
const decoded = Buffer.from(
|
|
547
|
+
configJson.auths[serverAddress].auth,
|
|
548
|
+
"base64",
|
|
549
|
+
).toString();
|
|
550
|
+
const sepIdx = decoded.indexOf(":");
|
|
551
|
+
const authPayload = {
|
|
552
|
+
username: decoded.substring(0, sepIdx),
|
|
553
|
+
password: decoded.substring(sepIdx + 1),
|
|
554
|
+
serveraddress: serverAddress,
|
|
555
|
+
};
|
|
556
|
+
opts.headers = {
|
|
557
|
+
"X-Registry-Auth": toBase64Url(JSON.stringify(authPayload)),
|
|
558
|
+
};
|
|
559
|
+
authTokenSet = true;
|
|
560
|
+
noteCredentialSource(
|
|
561
|
+
"docker-config-auth",
|
|
562
|
+
"selected",
|
|
563
|
+
serverAddress,
|
|
564
|
+
);
|
|
565
|
+
break;
|
|
566
|
+
}
|
|
567
|
+
if (configJson.credsStore) {
|
|
568
|
+
const helperAuthToken = getCredsFromHelper(
|
|
569
|
+
configJson.credsStore,
|
|
570
|
+
serverAddress,
|
|
571
|
+
);
|
|
572
|
+
if (helperAuthToken) {
|
|
573
|
+
opts.headers = {
|
|
574
|
+
"X-Registry-Auth": helperAuthToken,
|
|
575
|
+
};
|
|
576
|
+
authTokenSet = true;
|
|
577
|
+
noteCredentialSource(
|
|
578
|
+
`docker-credential-helper:${configJson.credsStore}`,
|
|
579
|
+
"selected",
|
|
580
|
+
serverAddress,
|
|
581
|
+
);
|
|
582
|
+
break;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
} else if (configJson.credHelpers) {
|
|
587
|
+
// Support for credential helpers
|
|
588
|
+
for (const serverAddress of Object.keys(configJson.credHelpers)) {
|
|
589
|
+
if (!registriesMatch(serverAddress, requestedRegistryRef)) {
|
|
590
|
+
continue;
|
|
591
|
+
}
|
|
592
|
+
if (configJson.credHelpers[serverAddress]) {
|
|
593
|
+
const helperAuthToken = getCredsFromHelper(
|
|
594
|
+
configJson.credHelpers[serverAddress],
|
|
595
|
+
serverAddress,
|
|
596
|
+
);
|
|
597
|
+
if (helperAuthToken) {
|
|
598
|
+
opts.headers = {
|
|
599
|
+
"X-Registry-Auth": helperAuthToken,
|
|
600
|
+
};
|
|
601
|
+
authTokenSet = true;
|
|
602
|
+
noteCredentialSource(
|
|
603
|
+
`docker-credential-helper:${configJson.credHelpers[serverAddress]}`,
|
|
604
|
+
"selected",
|
|
605
|
+
serverAddress,
|
|
606
|
+
);
|
|
607
|
+
break;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
if (!authTokenSet) {
|
|
613
|
+
noteCredentialSource(
|
|
614
|
+
"docker-config",
|
|
615
|
+
"skipped",
|
|
616
|
+
"no matching config.json auth entry",
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
} catch (_err) {
|
|
620
|
+
// pass
|
|
621
|
+
noteCredentialSource("docker-config", "skipped", "config parse failed");
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
} else if (!authTokenSet) {
|
|
625
|
+
noteCredentialSource("docker-config", "skipped", "config.json not found");
|
|
626
|
+
}
|
|
627
|
+
const userInfo = _userInfo();
|
|
628
|
+
opts.podmanPrefixUrl = isWin ? "" : "http://unix:/run/podman/podman.sock:";
|
|
629
|
+
opts.podmanRootlessPrefixUrl = isWin
|
|
630
|
+
? ""
|
|
631
|
+
: `http://unix:/run/user/${userInfo.uid}/podman/podman.sock:`;
|
|
632
|
+
const dockerHost = readEnvironmentVariable("DOCKER_HOST");
|
|
633
|
+
const dockerCertPath = readEnvironmentVariable("DOCKER_CERT_PATH");
|
|
634
|
+
const dockerTlsVerify = readEnvironmentVariable("DOCKER_TLS_VERIFY");
|
|
635
|
+
if (!dockerHost) {
|
|
636
|
+
if (isPodman) {
|
|
637
|
+
opts.prefixUrl = isPodmanRootless
|
|
638
|
+
? opts.podmanRootlessPrefixUrl
|
|
639
|
+
: opts.podmanPrefixUrl;
|
|
640
|
+
} else {
|
|
641
|
+
if (isWinLocalTLS) {
|
|
642
|
+
opts.prefixUrl = WIN_LOCAL_TLS;
|
|
643
|
+
} else {
|
|
644
|
+
// Named pipes syntax for Windows doesn't work with got
|
|
645
|
+
// See: https://github.com/sindresorhus/got/issues/2178
|
|
646
|
+
/*
|
|
647
|
+
opts.prefixUrl = isWin
|
|
648
|
+
? "npipe//./pipe/docker_engine:"
|
|
649
|
+
: "unix:/var/run/docker.sock:";
|
|
650
|
+
*/
|
|
651
|
+
opts.prefixUrl = isWin
|
|
652
|
+
? WIN_LOCAL_TLS
|
|
653
|
+
: isDockerRootless
|
|
654
|
+
? `http://unix:${homedir()}/.docker/run/docker.sock:`
|
|
655
|
+
: "http://unix:/var/run/docker.sock:";
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
} else {
|
|
659
|
+
let hostStr = dockerHost;
|
|
660
|
+
if (hostStr.startsWith("unix:///")) {
|
|
661
|
+
hostStr = hostStr.replace("unix:///", "http://unix:/");
|
|
662
|
+
if (hostStr.includes("docker.sock")) {
|
|
663
|
+
hostStr = hostStr.replace("docker.sock", "docker.sock:");
|
|
664
|
+
isDockerRootless = true;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
opts.prefixUrl = hostStr;
|
|
668
|
+
if (dockerCertPath) {
|
|
669
|
+
const dockerCertFile = join(dockerCertPath, "cert.pem");
|
|
670
|
+
const dockerKeyFile = join(dockerCertPath, "key.pem");
|
|
671
|
+
const dockerCertificate = readFileSync(dockerCertFile, "utf8");
|
|
672
|
+
recordSensitiveFileRead(dockerCertFile, {
|
|
673
|
+
label: "Docker client certificate",
|
|
674
|
+
});
|
|
675
|
+
const dockerKey = readFileSync(dockerKeyFile, "utf8");
|
|
676
|
+
recordSensitiveFileRead(dockerKeyFile, {
|
|
677
|
+
label: "Docker client private key",
|
|
678
|
+
});
|
|
679
|
+
opts.https = {
|
|
680
|
+
certificate: dockerCertificate,
|
|
681
|
+
key: dockerKey,
|
|
682
|
+
};
|
|
683
|
+
// Disable tls on empty values
|
|
684
|
+
// From the docker docs: Setting the DOCKER_TLS_VERIFY environment variable to any value other than the empty string is equivalent to setting the --tlsverify flag
|
|
685
|
+
if (dockerTlsVerify === "") {
|
|
686
|
+
opts.https.rejectUnauthorized = false;
|
|
687
|
+
console.log("TLS Verification disabled for", hostStr);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
if (!selectedCredentialSource) {
|
|
693
|
+
noteCredentialSource(
|
|
694
|
+
"anonymous",
|
|
695
|
+
"selected",
|
|
696
|
+
"no credential source resolved",
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
const skippedSources = credentialSourceEvaluations
|
|
700
|
+
.filter((entry) => entry.outcome !== "selected")
|
|
701
|
+
.map((entry) =>
|
|
702
|
+
entry.detail ? `${entry.source} (${entry.detail})` : entry.source,
|
|
703
|
+
);
|
|
704
|
+
recordDecisionActivity(`docker-auth:${authDecisionTarget}`, {
|
|
705
|
+
metadata: {
|
|
706
|
+
decisionType: "credential-source-selection",
|
|
707
|
+
evaluatedSources: credentialSourceEvaluations.map(
|
|
708
|
+
({ detail, outcome, source }) =>
|
|
709
|
+
detail ? `${source}:${outcome}:${detail}` : `${source}:${outcome}`,
|
|
710
|
+
),
|
|
711
|
+
selectedSource: selectedCredentialSource,
|
|
712
|
+
},
|
|
713
|
+
reason: `Selected Docker auth source '${selectedCredentialSource}' for ${authDecisionTarget}. Skipped: ${skippedSources.length ? skippedSources.join(", ") : "none"}.`,
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
return opts;
|
|
717
|
+
};
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* Establish (or reuse) a `got` client connected to the local Docker or Podman
|
|
721
|
+
* daemon. Tries multiple socket / URL candidates in order: the default Docker
|
|
722
|
+
* socket, the rootless Docker socket, the Windows TCP endpoint, the rootless
|
|
723
|
+
* Podman socket, and the root Podman socket. Sets the module-level flags
|
|
724
|
+
* `isPodman`, `isPodmanRootless`, `isDockerRootless`, and `isWinLocalTLS` as a
|
|
725
|
+
* side-effect. Returns `undefined` when containerd / nerdctl is in use or no
|
|
726
|
+
* daemon could be reached.
|
|
727
|
+
*
|
|
728
|
+
* @param {Object} _options Additional options (currently unused; retained for
|
|
729
|
+
* backwards compatibility with existing callers)
|
|
730
|
+
* @param {string} [forRegistry] Registry hostname forwarded to `getDefaultOptions`
|
|
731
|
+
* @returns {Promise<Object|undefined>} A daemon connection object bound to the
|
|
732
|
+
* daemon base URL, or `undefined`
|
|
733
|
+
*/
|
|
734
|
+
export const getConnection = async (_options, forRegistry) => {
|
|
735
|
+
if (isContainerd || isNerdctl) {
|
|
736
|
+
return undefined;
|
|
737
|
+
}
|
|
738
|
+
if (isDryRun) {
|
|
739
|
+
try {
|
|
740
|
+
getDefaultOptions(forRegistry);
|
|
741
|
+
} catch (error) {
|
|
742
|
+
recordActivity({
|
|
743
|
+
kind: "read",
|
|
744
|
+
reason: `Dry run mode failed while tracing Docker credential inputs: ${error.message}`,
|
|
745
|
+
status: "failed",
|
|
746
|
+
target: error?.path || forRegistry || "container-daemon",
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
recordActivity({
|
|
750
|
+
kind: "network",
|
|
751
|
+
reason:
|
|
752
|
+
"Dry run mode blocks container daemon and registry HTTP requests.",
|
|
753
|
+
status: "blocked",
|
|
754
|
+
target: forRegistry || "container-daemon",
|
|
755
|
+
});
|
|
756
|
+
return undefined;
|
|
757
|
+
}
|
|
758
|
+
if (!dockerConn) {
|
|
759
|
+
const defaultOptions = getDefaultOptions(forRegistry);
|
|
760
|
+
const podmanRootlessUrl = defaultOptions.podmanRootlessPrefixUrl;
|
|
761
|
+
const podmanRootUrl = defaultOptions.podmanPrefixUrl;
|
|
762
|
+
// Probe a candidate daemon endpoint. Returns a live connection on success
|
|
763
|
+
// and closes/discards the connection (throwing) on failure so callers can
|
|
764
|
+
// try the next candidate.
|
|
765
|
+
const tryConnect = async (prefixUrl, pingPath) => {
|
|
766
|
+
const conn = createDaemonConnection(
|
|
767
|
+
prefixUrl,
|
|
768
|
+
defaultOptions.headers,
|
|
769
|
+
defaultOptions.https,
|
|
770
|
+
);
|
|
771
|
+
try {
|
|
772
|
+
await conn.request(pingPath, { method: "GET" });
|
|
773
|
+
return conn;
|
|
774
|
+
} catch (err) {
|
|
775
|
+
await conn.close();
|
|
776
|
+
throw err;
|
|
777
|
+
}
|
|
778
|
+
};
|
|
779
|
+
try {
|
|
780
|
+
dockerConn = await tryConnect(defaultOptions.prefixUrl, "_ping");
|
|
781
|
+
if (DEBUG_MODE) {
|
|
782
|
+
if (isDockerRootless) {
|
|
783
|
+
console.log("Docker service in rootless mode detected.");
|
|
784
|
+
} else {
|
|
785
|
+
console.log(
|
|
786
|
+
"Docker service in root mode detected. Consider switching to rootless mode to improve security. See https://docs.docker.com/engine/security/rootless/",
|
|
787
|
+
);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
} catch (_err) {
|
|
791
|
+
try {
|
|
792
|
+
dockerConn = await tryConnect(
|
|
793
|
+
`http://unix:${homedir()}/.docker/run/docker.sock:`,
|
|
794
|
+
"_ping",
|
|
795
|
+
);
|
|
796
|
+
isDockerRootless = true;
|
|
797
|
+
if (DEBUG_MODE) {
|
|
798
|
+
console.log("Docker service in rootless mode detected.");
|
|
799
|
+
}
|
|
800
|
+
return dockerConn;
|
|
801
|
+
} catch (_err) {
|
|
802
|
+
// ignore
|
|
803
|
+
}
|
|
804
|
+
try {
|
|
805
|
+
if (isWin) {
|
|
806
|
+
dockerConn = await tryConnect(WIN_LOCAL_TLS, "_ping");
|
|
807
|
+
isWinLocalTLS = true;
|
|
808
|
+
if (DEBUG_MODE) {
|
|
809
|
+
console.log("Docker desktop on Windows detected.");
|
|
810
|
+
}
|
|
811
|
+
} else {
|
|
812
|
+
dockerConn = await tryConnect(podmanRootlessUrl, "libpod/_ping");
|
|
813
|
+
isPodman = true;
|
|
814
|
+
isPodmanRootless = true;
|
|
815
|
+
if (DEBUG_MODE) {
|
|
816
|
+
console.log(
|
|
817
|
+
"Podman in rootless mode detected. Thank you for using podman!",
|
|
818
|
+
);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
} catch (_err) {
|
|
822
|
+
try {
|
|
823
|
+
dockerConn = await tryConnect(podmanRootUrl, "libpod/_ping");
|
|
824
|
+
isPodman = true;
|
|
825
|
+
isPodmanRootless = false;
|
|
826
|
+
console.log(
|
|
827
|
+
"Podman in root mode detected. Consider switching to rootless mode to improve security. See https://github.com/containers/podman/blob/main/docs/tutorials/rootless_tutorial.md",
|
|
828
|
+
);
|
|
829
|
+
} catch (_err) {
|
|
830
|
+
if (_platform() === "win32") {
|
|
831
|
+
console.warn(
|
|
832
|
+
"Ensure Docker for Desktop is running as an administrator with 'Exposing daemon on TCP without TLS' setting turned on.",
|
|
833
|
+
);
|
|
834
|
+
} else if (_platform() === "darwin" && !isNerdctl) {
|
|
835
|
+
if (detectRancherDesktop() || detectColima()) {
|
|
836
|
+
return undefined;
|
|
837
|
+
}
|
|
838
|
+
if (isNerdctl === undefined) {
|
|
839
|
+
console.warn(
|
|
840
|
+
"Ensure Podman Desktop (open-source) or Docker for Desktop (May require subscription) is running.",
|
|
841
|
+
);
|
|
842
|
+
}
|
|
843
|
+
} else {
|
|
844
|
+
console.warn(
|
|
845
|
+
"Ensure docker/podman service or Docker for Desktop is running.",
|
|
846
|
+
);
|
|
847
|
+
console.log(
|
|
848
|
+
"Check if the post-installation steps were performed correctly as per this documentation https://docs.docker.com/engine/install/linux-postinstall/",
|
|
849
|
+
);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
return dockerConn;
|
|
856
|
+
};
|
|
857
|
+
|
|
858
|
+
/**
|
|
859
|
+
* Send a single HTTP request to the Docker / Podman daemon via the connection
|
|
860
|
+
* returned by {@link getConnection}. GET requests are parsed as JSON; all other
|
|
861
|
+
* methods receive a Buffer response body.
|
|
862
|
+
*
|
|
863
|
+
* @param {string} path API path relative to the daemon base URL (e.g. "images/ubuntu:latest/json")
|
|
864
|
+
* @param {string} method HTTP method (e.g. "GET", "POST", "DELETE")
|
|
865
|
+
* @param {string} [forRegistry] Registry hostname forwarded to `getDefaultOptions` for auth headers
|
|
866
|
+
* @returns {Promise<Object|Buffer|undefined>} Parsed JSON object for GET
|
|
867
|
+
* requests, raw Buffer for other methods, or `undefined` if no client is available
|
|
868
|
+
*/
|
|
869
|
+
export const makeRequest = async (path, method, forRegistry) => {
|
|
870
|
+
if (isDryRun) {
|
|
871
|
+
recordActivity({
|
|
872
|
+
kind: "network",
|
|
873
|
+
reason:
|
|
874
|
+
"Dry run mode blocks container daemon and registry HTTP requests.",
|
|
875
|
+
status: "blocked",
|
|
876
|
+
target: `${method} ${forRegistry || "container-daemon"}/${path}`,
|
|
877
|
+
});
|
|
878
|
+
return undefined;
|
|
879
|
+
}
|
|
880
|
+
const client = await getConnection({}, forRegistry);
|
|
881
|
+
if (!client) {
|
|
882
|
+
return undefined;
|
|
883
|
+
}
|
|
884
|
+
// The connection is already bound to the correct daemon base URL. Only pass
|
|
885
|
+
// per-request auth headers and the method.
|
|
886
|
+
const defaultOptions = getDefaultOptions(
|
|
887
|
+
forRegistry,
|
|
888
|
+
extractRequestedRegistryRefFromPath(path, forRegistry),
|
|
889
|
+
);
|
|
890
|
+
const opts = {
|
|
891
|
+
responseType: method === "GET" ? "json" : "buffer",
|
|
892
|
+
method,
|
|
893
|
+
};
|
|
894
|
+
if (defaultOptions.headers) {
|
|
895
|
+
opts.headers = defaultOptions.headers;
|
|
896
|
+
}
|
|
897
|
+
return await client.request(path, opts);
|
|
898
|
+
};
|
|
899
|
+
|
|
900
|
+
/**
|
|
901
|
+
* Parse image name
|
|
902
|
+
*
|
|
903
|
+
* docker pull debian
|
|
904
|
+
* docker pull debian:jessie
|
|
905
|
+
* docker pull ubuntu@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2
|
|
906
|
+
* docker pull myregistry.local:5000/testing/test-image
|
|
907
|
+
*/
|
|
908
|
+
export const parseImageName = (fullImageName) => {
|
|
909
|
+
const nameObj = {
|
|
910
|
+
registry: "",
|
|
911
|
+
repo: "",
|
|
912
|
+
tag: "",
|
|
913
|
+
digest: "",
|
|
914
|
+
platform: "",
|
|
915
|
+
group: "",
|
|
916
|
+
name: "",
|
|
917
|
+
};
|
|
918
|
+
if (!fullImageName) {
|
|
919
|
+
return nameObj;
|
|
920
|
+
}
|
|
921
|
+
// ensure it's lowercased
|
|
922
|
+
fullImageName = fullImageName.toLowerCase().trim();
|
|
923
|
+
|
|
924
|
+
// Extract platform
|
|
925
|
+
if (fullImageName.startsWith("--platform=")) {
|
|
926
|
+
const tmpName = fullImageName.replace("--platform=", "").split(" ");
|
|
927
|
+
nameObj.platform = tmpName[0];
|
|
928
|
+
fullImageName = tmpName[1];
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// Extract registry name
|
|
932
|
+
if (
|
|
933
|
+
fullImageName.includes("/") &&
|
|
934
|
+
(fullImageName.includes(".") || fullImageName.includes(":"))
|
|
935
|
+
) {
|
|
936
|
+
let urlObj;
|
|
937
|
+
if (URL.canParse(fullImageName)) {
|
|
938
|
+
urlObj = new URL(fullImageName);
|
|
939
|
+
}
|
|
940
|
+
const tmpA = fullImageName.split("/");
|
|
941
|
+
if (
|
|
942
|
+
(urlObj && urlObj.pathname !== fullImageName) ||
|
|
943
|
+
tmpA[0].includes(".") ||
|
|
944
|
+
tmpA[0].includes(":")
|
|
945
|
+
) {
|
|
946
|
+
nameObj.registry = tmpA[0];
|
|
947
|
+
fullImageName = fullImageName.replace(`${tmpA[0]}/`, "");
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
// Extract digest name
|
|
952
|
+
if (fullImageName.includes("@sha256:")) {
|
|
953
|
+
const tmpA = fullImageName.split("@sha256:");
|
|
954
|
+
if (tmpA.length > 1) {
|
|
955
|
+
nameObj.digest = tmpA[tmpA.length - 1];
|
|
956
|
+
fullImageName = fullImageName.replace(`@sha256:${nameObj.digest}`, "");
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
// Extract tag name
|
|
961
|
+
if (fullImageName.includes(":")) {
|
|
962
|
+
const tmpA = fullImageName.split(":");
|
|
963
|
+
if (tmpA.length > 1) {
|
|
964
|
+
nameObj.tag = tmpA[tmpA.length - 1];
|
|
965
|
+
fullImageName = fullImageName.replace(`:${nameObj.tag}`, "");
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// The left over string is the repo name
|
|
970
|
+
nameObj.repo = fullImageName;
|
|
971
|
+
nameObj.name = fullImageName;
|
|
972
|
+
|
|
973
|
+
// extract group name
|
|
974
|
+
if (fullImageName.includes("/")) {
|
|
975
|
+
const tmpA = fullImageName.split("/");
|
|
976
|
+
if (tmpA.length > 1) {
|
|
977
|
+
nameObj.name = tmpA[tmpA.length - 1];
|
|
978
|
+
nameObj.group = fullImageName.replace(`/${tmpA[tmpA.length - 1]}`, "");
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
return nameObj;
|
|
983
|
+
};
|
|
984
|
+
|
|
985
|
+
/**
|
|
986
|
+
* Prefer cli on windows, nerdctl on mac, or when using tcp/ssh based host.
|
|
987
|
+
*
|
|
988
|
+
* @returns boolean true if we should use the cli. false otherwise
|
|
989
|
+
*/
|
|
990
|
+
const getContainerCliCmd = () => {
|
|
991
|
+
if (readEnvironmentVariable("DOCKER_CMD")?.trim()) {
|
|
992
|
+
return readEnvironmentVariable("DOCKER_CMD").trim();
|
|
993
|
+
}
|
|
994
|
+
detectRancherDesktop() || detectColima();
|
|
995
|
+
if (isNerdctl) {
|
|
996
|
+
return "nerdctl";
|
|
997
|
+
}
|
|
998
|
+
return "docker";
|
|
999
|
+
};
|
|
1000
|
+
|
|
1001
|
+
const needsCliFallback = () => {
|
|
1002
|
+
if (
|
|
1003
|
+
["true", "1"].includes(readEnvironmentVariable("DOCKER_USE_CLI")) ||
|
|
1004
|
+
readEnvironmentVariable("DOCKER_CMD")?.trim() ||
|
|
1005
|
+
(_platform() === "darwin" && (detectRancherDesktop() || detectColima()))
|
|
1006
|
+
) {
|
|
1007
|
+
return true;
|
|
1008
|
+
}
|
|
1009
|
+
return (
|
|
1010
|
+
isWin ||
|
|
1011
|
+
(readEnvironmentVariable("DOCKER_HOST") &&
|
|
1012
|
+
(readEnvironmentVariable("DOCKER_HOST").startsWith("tcp://") ||
|
|
1013
|
+
readEnvironmentVariable("DOCKER_HOST").startsWith("ssh://")))
|
|
1014
|
+
);
|
|
1015
|
+
};
|
|
1016
|
+
|
|
1017
|
+
/**
|
|
1018
|
+
* Method to get image to the local registry by pulling from the remote if required
|
|
1019
|
+
*/
|
|
1020
|
+
export const getImage = async (fullImageName) => {
|
|
1021
|
+
let localData;
|
|
1022
|
+
let pullData;
|
|
1023
|
+
const { registry, repo, tag, digest } = parseImageName(fullImageName);
|
|
1024
|
+
const repoWithTag =
|
|
1025
|
+
registry && registry !== DOCKER_HUB_REGISTRY
|
|
1026
|
+
? fullImageName
|
|
1027
|
+
: `${repo}:${tag !== "" ? tag : ":latest"}`;
|
|
1028
|
+
// Fetch only the latest tag if none is specified
|
|
1029
|
+
if (tag === "" && digest === "") {
|
|
1030
|
+
fullImageName = `${fullImageName}:latest`;
|
|
1031
|
+
}
|
|
1032
|
+
if (isContainerd && !needsCliFallback()) {
|
|
1033
|
+
console.log(
|
|
1034
|
+
"containerd/nerdctl is currently unsupported. Export the image manually and run cdxgen against the tar image.",
|
|
1035
|
+
);
|
|
1036
|
+
return undefined;
|
|
1037
|
+
}
|
|
1038
|
+
if (needsCliFallback()) {
|
|
1039
|
+
const dockerCmd = getContainerCliCmd();
|
|
1040
|
+
let needsPull = true;
|
|
1041
|
+
// Let's check the local cache first
|
|
1042
|
+
let result = safeSpawnSync(dockerCmd, ["images", "--format=json"]);
|
|
1043
|
+
if (result.status === 0 && result.stdout) {
|
|
1044
|
+
for (const imgLine of result.stdout.split("\n")) {
|
|
1045
|
+
try {
|
|
1046
|
+
const imgObj = JSON.parse(Buffer.from(imgLine).toString());
|
|
1047
|
+
if (`${imgObj.Repository}:${imgObj.Tag}` === fullImageName) {
|
|
1048
|
+
needsPull = false;
|
|
1049
|
+
break;
|
|
1050
|
+
}
|
|
1051
|
+
} catch (_err) {
|
|
1052
|
+
// continue regardless of error
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
if (needsPull) {
|
|
1057
|
+
result = safeSpawnSync(dockerCmd, ["pull", fullImageName]);
|
|
1058
|
+
if (result.status !== 0 || result.error) {
|
|
1059
|
+
if (result.stderr?.includes("docker daemon is not running")) {
|
|
1060
|
+
console.log(
|
|
1061
|
+
"Ensure Docker for Desktop is running as an administrator with 'Exposing daemon on TCP without TLS' setting turned on.",
|
|
1062
|
+
);
|
|
1063
|
+
} else if (result.stderr?.includes("not found")) {
|
|
1064
|
+
console.log(
|
|
1065
|
+
"Set the environment variable DOCKER_CMD to use an alternative command such as nerdctl or podman.",
|
|
1066
|
+
);
|
|
1067
|
+
} else if (result.stderr) {
|
|
1068
|
+
console.log(result.stderr);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
result = safeSpawnSync(dockerCmd, ["inspect", fullImageName]);
|
|
1073
|
+
if (result.status !== 0 || result.error) {
|
|
1074
|
+
if (result.stderr) {
|
|
1075
|
+
console.log(result.stderr);
|
|
1076
|
+
}
|
|
1077
|
+
// Continue with the daemon client when the CLI fallback is unavailable
|
|
1078
|
+
// or unable to inspect the image.
|
|
1079
|
+
} else {
|
|
1080
|
+
try {
|
|
1081
|
+
const stdout = result.stdout;
|
|
1082
|
+
if (stdout) {
|
|
1083
|
+
const inspectData = JSON.parse(Buffer.from(stdout).toString());
|
|
1084
|
+
if (inspectData && Array.isArray(inspectData)) {
|
|
1085
|
+
return inspectData[0];
|
|
1086
|
+
}
|
|
1087
|
+
return inspectData;
|
|
1088
|
+
}
|
|
1089
|
+
} catch (_err) {
|
|
1090
|
+
// continue regardless of error
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
try {
|
|
1095
|
+
localData = await makeRequest(
|
|
1096
|
+
`images/${repoWithTag}/json`,
|
|
1097
|
+
"GET",
|
|
1098
|
+
registry,
|
|
1099
|
+
);
|
|
1100
|
+
if (localData) {
|
|
1101
|
+
return localData;
|
|
1102
|
+
}
|
|
1103
|
+
} catch (_err) {
|
|
1104
|
+
// ignore
|
|
1105
|
+
}
|
|
1106
|
+
try {
|
|
1107
|
+
localData = await makeRequest(`images/${repo}/json`, "GET", registry);
|
|
1108
|
+
} catch (_err) {
|
|
1109
|
+
try {
|
|
1110
|
+
localData = await makeRequest(
|
|
1111
|
+
`images/${fullImageName}/json`,
|
|
1112
|
+
"GET",
|
|
1113
|
+
registry,
|
|
1114
|
+
);
|
|
1115
|
+
if (localData) {
|
|
1116
|
+
return localData;
|
|
1117
|
+
}
|
|
1118
|
+
} catch (_err) {
|
|
1119
|
+
// ignore
|
|
1120
|
+
}
|
|
1121
|
+
if (DEBUG_MODE) {
|
|
1122
|
+
console.log(
|
|
1123
|
+
`Trying to pull the image ${fullImageName} from registry. This might take a while ...`,
|
|
1124
|
+
);
|
|
1125
|
+
}
|
|
1126
|
+
// If the data is not available locally
|
|
1127
|
+
try {
|
|
1128
|
+
pullData = await makeRequest(
|
|
1129
|
+
`images/create?fromImage=${fullImageName}`,
|
|
1130
|
+
"POST",
|
|
1131
|
+
registry,
|
|
1132
|
+
);
|
|
1133
|
+
if (
|
|
1134
|
+
pullData &&
|
|
1135
|
+
(pullData.includes("no match for platform in manifest") ||
|
|
1136
|
+
pullData.includes("Error choosing an image from manifest list"))
|
|
1137
|
+
) {
|
|
1138
|
+
console.warn(
|
|
1139
|
+
"You may have to enable experimental settings in docker to support this platform!",
|
|
1140
|
+
);
|
|
1141
|
+
console.warn(
|
|
1142
|
+
"To scan windows images, run cdxgen on a windows server with hyper-v and docker installed. Switch to windows containers in your docker settings.",
|
|
1143
|
+
);
|
|
1144
|
+
return undefined;
|
|
1145
|
+
}
|
|
1146
|
+
} catch (_err) {
|
|
1147
|
+
try {
|
|
1148
|
+
if (DEBUG_MODE) {
|
|
1149
|
+
console.log(`Re-trying the pull with the name ${repoWithTag}.`);
|
|
1150
|
+
}
|
|
1151
|
+
await makeRequest(
|
|
1152
|
+
`images/create?fromImage=${repoWithTag}`,
|
|
1153
|
+
"POST",
|
|
1154
|
+
registry,
|
|
1155
|
+
);
|
|
1156
|
+
} catch (_err) {
|
|
1157
|
+
// continue regardless of error
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
try {
|
|
1161
|
+
if (DEBUG_MODE) {
|
|
1162
|
+
console.log(`Trying with ${repoWithTag}`);
|
|
1163
|
+
}
|
|
1164
|
+
localData = await makeRequest(
|
|
1165
|
+
`images/${repoWithTag}/json`,
|
|
1166
|
+
"GET",
|
|
1167
|
+
registry,
|
|
1168
|
+
);
|
|
1169
|
+
if (localData) {
|
|
1170
|
+
return localData;
|
|
1171
|
+
}
|
|
1172
|
+
} catch (_err) {
|
|
1173
|
+
try {
|
|
1174
|
+
if (DEBUG_MODE) {
|
|
1175
|
+
console.log(`Trying with ${repo}`);
|
|
1176
|
+
}
|
|
1177
|
+
localData = await makeRequest(`images/${repo}/json`, "GET", registry);
|
|
1178
|
+
if (localData) {
|
|
1179
|
+
return localData;
|
|
1180
|
+
}
|
|
1181
|
+
} catch (_err) {
|
|
1182
|
+
// continue regardless of error
|
|
1183
|
+
}
|
|
1184
|
+
try {
|
|
1185
|
+
if (DEBUG_MODE) {
|
|
1186
|
+
console.log(`Trying with ${fullImageName}`);
|
|
1187
|
+
}
|
|
1188
|
+
localData = await makeRequest(
|
|
1189
|
+
`images/${fullImageName}/json`,
|
|
1190
|
+
"GET",
|
|
1191
|
+
registry,
|
|
1192
|
+
);
|
|
1193
|
+
} catch (_err) {
|
|
1194
|
+
// continue regardless of error
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
if (!localData) {
|
|
1199
|
+
console.log(
|
|
1200
|
+
`Unable to pull ${fullImageName}. Check if the name is valid. Perform any authentication prior to invoking cdxgen.`,
|
|
1201
|
+
);
|
|
1202
|
+
console.log(
|
|
1203
|
+
`Try manually pulling this image using docker pull ${fullImageName}`,
|
|
1204
|
+
);
|
|
1205
|
+
}
|
|
1206
|
+
return localData;
|
|
1207
|
+
};
|
|
1208
|
+
|
|
1209
|
+
/**
|
|
1210
|
+
* @typedef {{ path: string }} TarReadEntryLike
|
|
1211
|
+
*/
|
|
1212
|
+
|
|
1213
|
+
/**
|
|
1214
|
+
* Warnings such as TAR_ENTRY_INFO are treated as errors in strict mode. While this is mostly desired, we can relax this
|
|
1215
|
+
* requirement for one particular warning related to absolute paths.
|
|
1216
|
+
* This callback function checks for absolute paths in the entry read from the archive and strips them using a custom
|
|
1217
|
+
* method.
|
|
1218
|
+
*
|
|
1219
|
+
* @param {TarReadEntryLike} entry ReadEntry object from node-tar
|
|
1220
|
+
*/
|
|
1221
|
+
function handleAbsolutePath(entry) {
|
|
1222
|
+
if (entry.path === "/" || win32.isAbsolute(entry.path)) {
|
|
1223
|
+
entry.path = stripAbsolutePath(entry.path);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
/**
|
|
1228
|
+
* Filter out problematic files, paths, and devices during tar extraction.
|
|
1229
|
+
*/
|
|
1230
|
+
function tarFilter(path, entry) {
|
|
1231
|
+
const name = basename(path);
|
|
1232
|
+
if (name.startsWith(".wh.")) {
|
|
1233
|
+
return false;
|
|
1234
|
+
}
|
|
1235
|
+
const ext = win32.extname(name).toLowerCase();
|
|
1236
|
+
if (MEDIA_EXTENSIONS.has(ext)) {
|
|
1237
|
+
return false;
|
|
1238
|
+
}
|
|
1239
|
+
return !(
|
|
1240
|
+
EXTRACT_EXCLUDE_PATHS.some((p) => path.includes(p)) ||
|
|
1241
|
+
EXTRACT_EXCLUDE_TYPES.has(entry.type)
|
|
1242
|
+
);
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
/**
|
|
1246
|
+
* Suppress low-signal tar warnings (TAR_ENTRY_INFO, TAR_LONGLINK) that are
|
|
1247
|
+
* expected when extracting container image layers. All other warning codes are
|
|
1248
|
+
* logged when DEBUG_MODE is enabled.
|
|
1249
|
+
*
|
|
1250
|
+
* @param {string} code Tar warning code (e.g. "TAR_ENTRY_INFO")
|
|
1251
|
+
* @param {string} message Human-readable warning message
|
|
1252
|
+
*/
|
|
1253
|
+
function handleTarWarning(code, message) {
|
|
1254
|
+
if (code === "TAR_ENTRY_INFO" || code === "TAR_LONGLINK") {
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1257
|
+
if (DEBUG_MODE) {
|
|
1258
|
+
console.log(code, message);
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
// These paths are known to cause extract errors
|
|
1263
|
+
const EXTRACT_EXCLUDE_PATHS = [
|
|
1264
|
+
"etc/machine-id",
|
|
1265
|
+
"etc/gshadow",
|
|
1266
|
+
"etc/shadow",
|
|
1267
|
+
"etc/passwd",
|
|
1268
|
+
"etc/ssl/certs",
|
|
1269
|
+
"etc/pki/ca-trust",
|
|
1270
|
+
"usr/lib/systemd/",
|
|
1271
|
+
"usr/lib64/libdevmapper.so",
|
|
1272
|
+
"usr/sbin/",
|
|
1273
|
+
"cacerts",
|
|
1274
|
+
"ssl/certs",
|
|
1275
|
+
"logs/",
|
|
1276
|
+
"dev/",
|
|
1277
|
+
"proc/",
|
|
1278
|
+
"sys/",
|
|
1279
|
+
"usr/share/zoneinfo/",
|
|
1280
|
+
"usr/share/doc/",
|
|
1281
|
+
"usr/share/man/",
|
|
1282
|
+
"usr/share/icons/",
|
|
1283
|
+
"usr/share/i18n/",
|
|
1284
|
+
"var/lib/ca-certificates",
|
|
1285
|
+
"root/.gnupg",
|
|
1286
|
+
"root/.dotnet",
|
|
1287
|
+
"usr/share/licenses/device-mapper-libs",
|
|
1288
|
+
];
|
|
1289
|
+
|
|
1290
|
+
const MEDIA_EXTENSIONS = new Set([
|
|
1291
|
+
".jpg",
|
|
1292
|
+
".jpeg",
|
|
1293
|
+
".png",
|
|
1294
|
+
".gif",
|
|
1295
|
+
".bmp",
|
|
1296
|
+
".tiff",
|
|
1297
|
+
".ico",
|
|
1298
|
+
".svg",
|
|
1299
|
+
".mp3",
|
|
1300
|
+
".wav",
|
|
1301
|
+
".mp4",
|
|
1302
|
+
".avi",
|
|
1303
|
+
".mov",
|
|
1304
|
+
".ttf",
|
|
1305
|
+
".woff",
|
|
1306
|
+
".woff2",
|
|
1307
|
+
".eot",
|
|
1308
|
+
]);
|
|
1309
|
+
|
|
1310
|
+
// These device types are known to cause extract errors
|
|
1311
|
+
const EXTRACT_EXCLUDE_TYPES = new Set([
|
|
1312
|
+
"BlockDevice",
|
|
1313
|
+
"CharacterDevice",
|
|
1314
|
+
"FIFO",
|
|
1315
|
+
"MultiVolume",
|
|
1316
|
+
"TapeVolume",
|
|
1317
|
+
"SymbolicLink",
|
|
1318
|
+
"RenamedOrSymlinked",
|
|
1319
|
+
"HardLink",
|
|
1320
|
+
"Link",
|
|
1321
|
+
]);
|
|
1322
|
+
|
|
1323
|
+
/**
|
|
1324
|
+
* Extract a container image tar archive into a destination directory.
|
|
1325
|
+
* Applies path sanitisation, ownership/permission preservation settings, and
|
|
1326
|
+
* an entry filter to skip problematic files and device nodes. Handles common
|
|
1327
|
+
* tar errors gracefully, logging only unexpected ones.
|
|
1328
|
+
*
|
|
1329
|
+
* @param {string} fullImageName Path to the source tar archive
|
|
1330
|
+
* @param {string} dir Destination directory to extract into
|
|
1331
|
+
* @param {Object} options CLI options (uses `options.failOnError`)
|
|
1332
|
+
* @returns {Promise<boolean>} `true` on success, `false` when the archive is
|
|
1333
|
+
* empty or a non-fatal error was encountered
|
|
1334
|
+
*/
|
|
1335
|
+
export const extractTar = async (fullImageName, dir, options) => {
|
|
1336
|
+
try {
|
|
1337
|
+
return await safeExtractArchive(
|
|
1338
|
+
fullImageName,
|
|
1339
|
+
dir,
|
|
1340
|
+
async () =>
|
|
1341
|
+
await stream.pipeline(
|
|
1342
|
+
createReadStream(fullImageName),
|
|
1343
|
+
x({
|
|
1344
|
+
sync: false,
|
|
1345
|
+
preserveOwner: false,
|
|
1346
|
+
noMtime: true,
|
|
1347
|
+
noChmod: true,
|
|
1348
|
+
strict: !NON_STRICT_TAR_EXTRACT,
|
|
1349
|
+
C: dir,
|
|
1350
|
+
portable: true,
|
|
1351
|
+
unlink: true,
|
|
1352
|
+
onwarn: handleTarWarning,
|
|
1353
|
+
onReadEntry: handleAbsolutePath,
|
|
1354
|
+
filter: tarFilter,
|
|
1355
|
+
}),
|
|
1356
|
+
),
|
|
1357
|
+
"untar",
|
|
1358
|
+
{
|
|
1359
|
+
blockedReason:
|
|
1360
|
+
"Dry run mode blocks untar and layer extraction operations because they create files on disk.",
|
|
1361
|
+
metadata: {
|
|
1362
|
+
archiveFormat: "tar",
|
|
1363
|
+
},
|
|
1364
|
+
},
|
|
1365
|
+
);
|
|
1366
|
+
} catch (err) {
|
|
1367
|
+
if (err.code === "EPERM" && err.syscall === "symlink") {
|
|
1368
|
+
console.log(
|
|
1369
|
+
"Please run cdxgen from a powershell terminal with admin privileges to create symlinks.",
|
|
1370
|
+
);
|
|
1371
|
+
console.log(err);
|
|
1372
|
+
} else if (
|
|
1373
|
+
![
|
|
1374
|
+
"TAR_BAD_ARCHIVE",
|
|
1375
|
+
"TAR_ENTRY_INFO",
|
|
1376
|
+
"TAR_ENTRY_INVALID",
|
|
1377
|
+
"TAR_ENTRY_ERROR",
|
|
1378
|
+
"TAR_ENTRY_UNSUPPORTED",
|
|
1379
|
+
"TAR_ABORT",
|
|
1380
|
+
"EACCES",
|
|
1381
|
+
].includes(err.code)
|
|
1382
|
+
) {
|
|
1383
|
+
console.log(
|
|
1384
|
+
`Error while extracting image ${fullImageName} to ${dir}. Please file this bug to the cdxgen repo. https://github.com/cdxgen/cdxgen/issues`,
|
|
1385
|
+
);
|
|
1386
|
+
console.log("------------");
|
|
1387
|
+
console.log(err);
|
|
1388
|
+
console.log("------------");
|
|
1389
|
+
} else if (err.code === "TAR_BAD_ARCHIVE") {
|
|
1390
|
+
if (DEBUG_MODE) {
|
|
1391
|
+
console.log(`Archive ${fullImageName} is empty. Skipping.`);
|
|
1392
|
+
}
|
|
1393
|
+
return false;
|
|
1394
|
+
} else if (["EACCES"].includes(err.code)) {
|
|
1395
|
+
console.log(err);
|
|
1396
|
+
} else if (["TAR_ENTRY_INFO", "TAR_ENTRY_INVALID"].includes(err.code)) {
|
|
1397
|
+
if (
|
|
1398
|
+
err?.header?.path?.includes("{") ||
|
|
1399
|
+
err?.message?.includes("linkpath required") ||
|
|
1400
|
+
err?.message?.includes("linkpath forbidden")
|
|
1401
|
+
) {
|
|
1402
|
+
return false;
|
|
1403
|
+
}
|
|
1404
|
+
if (DEBUG_MODE) {
|
|
1405
|
+
console.log(err);
|
|
1406
|
+
}
|
|
1407
|
+
} else if (DEBUG_MODE) {
|
|
1408
|
+
console.log(err.code, "is not handled yet in extractTar method.");
|
|
1409
|
+
}
|
|
1410
|
+
options.failOnError && process.exit(1);
|
|
1411
|
+
return false;
|
|
1412
|
+
}
|
|
1413
|
+
};
|
|
1414
|
+
|
|
1415
|
+
const readArchiveJson = (jsonFile) => {
|
|
1416
|
+
if (!jsonFile || !safeExistsSync(jsonFile)) {
|
|
1417
|
+
return undefined;
|
|
1418
|
+
}
|
|
1419
|
+
return JSON.parse(
|
|
1420
|
+
readFileSync(jsonFile, {
|
|
1421
|
+
encoding: "utf-8",
|
|
1422
|
+
}),
|
|
1423
|
+
);
|
|
1424
|
+
};
|
|
1425
|
+
|
|
1426
|
+
const tryReadArchiveJson = (jsonFile) => {
|
|
1427
|
+
try {
|
|
1428
|
+
return readArchiveJson(jsonFile);
|
|
1429
|
+
} catch (_err) {
|
|
1430
|
+
return undefined;
|
|
1431
|
+
}
|
|
1432
|
+
};
|
|
1433
|
+
|
|
1434
|
+
const digestToBlobPath = (digest) => {
|
|
1435
|
+
if (!digest?.startsWith("sha256:")) {
|
|
1436
|
+
return undefined;
|
|
1437
|
+
}
|
|
1438
|
+
return join("blobs", "sha256", digest.replace("sha256:", ""));
|
|
1439
|
+
};
|
|
1440
|
+
|
|
1441
|
+
const archiveBlobPath = (tempDir, digest) => {
|
|
1442
|
+
const blobPath = digestToBlobPath(digest);
|
|
1443
|
+
return blobPath ? join(tempDir, blobPath) : undefined;
|
|
1444
|
+
};
|
|
1445
|
+
|
|
1446
|
+
const toManifestEntry = (manifestBlob) => {
|
|
1447
|
+
const configBlob = digestToBlobPath(manifestBlob?.config?.digest);
|
|
1448
|
+
const layers =
|
|
1449
|
+
manifestBlob?.layers
|
|
1450
|
+
?.map((layer) => digestToBlobPath(layer?.digest))
|
|
1451
|
+
.filter(Boolean) || [];
|
|
1452
|
+
if (!configBlob && !layers.length) {
|
|
1453
|
+
return undefined;
|
|
1454
|
+
}
|
|
1455
|
+
return {
|
|
1456
|
+
Config: configBlob,
|
|
1457
|
+
Layers: layers,
|
|
1458
|
+
};
|
|
1459
|
+
};
|
|
1460
|
+
|
|
1461
|
+
const resolveArchiveManifest = (manifestData, tempDir) => {
|
|
1462
|
+
if (Array.isArray(manifestData)) {
|
|
1463
|
+
return manifestData;
|
|
1464
|
+
}
|
|
1465
|
+
if (!manifestData || typeof manifestData !== "object") {
|
|
1466
|
+
return [];
|
|
1467
|
+
}
|
|
1468
|
+
if (Array.isArray(manifestData.manifests)) {
|
|
1469
|
+
const resolvedManifests = manifestData.manifests
|
|
1470
|
+
.map((manifestEntry) => {
|
|
1471
|
+
if (manifestEntry?.Layers?.length || manifestEntry?.Config) {
|
|
1472
|
+
return manifestEntry;
|
|
1473
|
+
}
|
|
1474
|
+
const manifestBlob = tryReadArchiveJson(
|
|
1475
|
+
archiveBlobPath(tempDir, manifestEntry?.digest),
|
|
1476
|
+
);
|
|
1477
|
+
const resolvedEntry = toManifestEntry(manifestBlob);
|
|
1478
|
+
return resolvedEntry
|
|
1479
|
+
? {
|
|
1480
|
+
...manifestEntry,
|
|
1481
|
+
...resolvedEntry,
|
|
1482
|
+
}
|
|
1483
|
+
: manifestEntry;
|
|
1484
|
+
})
|
|
1485
|
+
.filter(Boolean);
|
|
1486
|
+
return resolvedManifests.length
|
|
1487
|
+
? resolvedManifests
|
|
1488
|
+
: manifestData.manifests;
|
|
1489
|
+
}
|
|
1490
|
+
const manifestEntry = toManifestEntry(manifestData);
|
|
1491
|
+
return manifestEntry ? [manifestEntry] : [];
|
|
1492
|
+
};
|
|
1493
|
+
|
|
1494
|
+
const discoverManifestFromBlobs = (tempDir) => {
|
|
1495
|
+
const blobsDir = join(tempDir, "blobs", "sha256");
|
|
1496
|
+
if (!safeExistsSync(blobsDir)) {
|
|
1497
|
+
return undefined;
|
|
1498
|
+
}
|
|
1499
|
+
const blobFiles = readdirSync(blobsDir);
|
|
1500
|
+
for (const blobFile of blobFiles) {
|
|
1501
|
+
const manifestBlob = tryReadArchiveJson(join(blobsDir, blobFile));
|
|
1502
|
+
const manifestEntry = toManifestEntry(manifestBlob);
|
|
1503
|
+
if (manifestEntry?.Layers?.length || manifestEntry?.Config) {
|
|
1504
|
+
return [manifestEntry];
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
return undefined;
|
|
1508
|
+
};
|
|
1509
|
+
|
|
1510
|
+
/**
|
|
1511
|
+
* Method to export a container image archive.
|
|
1512
|
+
* Returns the location of the layers with additional packages related metadata
|
|
1513
|
+
*/
|
|
1514
|
+
export const exportArchive = async (fullImageName, options = {}) => {
|
|
1515
|
+
if (isDryRun) {
|
|
1516
|
+
recordActivity({
|
|
1517
|
+
kind: "container",
|
|
1518
|
+
reason:
|
|
1519
|
+
"Dry run mode blocks container archive expansion and layer materialization.",
|
|
1520
|
+
status: "blocked",
|
|
1521
|
+
target: fullImageName,
|
|
1522
|
+
});
|
|
1523
|
+
return undefined;
|
|
1524
|
+
}
|
|
1525
|
+
if (!safeExistsSync(fullImageName)) {
|
|
1526
|
+
console.log(`Unable to find container image archive ${fullImageName}`);
|
|
1527
|
+
return undefined;
|
|
1528
|
+
}
|
|
1529
|
+
const manifest = {};
|
|
1530
|
+
const tempDir = safeMkdtempSync(join(getTmpDir(), "docker-images-"));
|
|
1531
|
+
const allLayersExplodedDir = join(tempDir, "all-layers");
|
|
1532
|
+
const blobsDir = join(tempDir, "blobs", "sha256");
|
|
1533
|
+
safeMkdirSync(allLayersExplodedDir);
|
|
1534
|
+
const manifestFile = join(tempDir, "manifest.json");
|
|
1535
|
+
const manifestIndexFile = join(tempDir, "index.json");
|
|
1536
|
+
const synthesizedManifestFile = join(tempDir, "synthetic-manifest.json");
|
|
1537
|
+
try {
|
|
1538
|
+
await extractTar(fullImageName, tempDir, options);
|
|
1539
|
+
if (safeExistsSync(manifestFile)) {
|
|
1540
|
+
// docker archive manifest file
|
|
1541
|
+
return await extractFromManifest(
|
|
1542
|
+
manifestFile,
|
|
1543
|
+
{},
|
|
1544
|
+
tempDir,
|
|
1545
|
+
allLayersExplodedDir,
|
|
1546
|
+
options,
|
|
1547
|
+
);
|
|
1548
|
+
}
|
|
1549
|
+
if (safeExistsSync(manifestIndexFile)) {
|
|
1550
|
+
return await extractFromManifest(
|
|
1551
|
+
manifestIndexFile,
|
|
1552
|
+
{},
|
|
1553
|
+
tempDir,
|
|
1554
|
+
allLayersExplodedDir,
|
|
1555
|
+
options,
|
|
1556
|
+
);
|
|
1557
|
+
}
|
|
1558
|
+
// podman use blobs dir
|
|
1559
|
+
if (safeExistsSync(blobsDir)) {
|
|
1560
|
+
const discoveredManifest = discoverManifestFromBlobs(tempDir);
|
|
1561
|
+
if (discoveredManifest?.length) {
|
|
1562
|
+
safeWriteSync(
|
|
1563
|
+
synthesizedManifestFile,
|
|
1564
|
+
JSON.stringify(discoveredManifest),
|
|
1565
|
+
"utf-8",
|
|
1566
|
+
);
|
|
1567
|
+
return await extractFromManifest(
|
|
1568
|
+
synthesizedManifestFile,
|
|
1569
|
+
{},
|
|
1570
|
+
tempDir,
|
|
1571
|
+
allLayersExplodedDir,
|
|
1572
|
+
options,
|
|
1573
|
+
);
|
|
1574
|
+
}
|
|
1575
|
+
if (DEBUG_MODE) {
|
|
1576
|
+
console.log(
|
|
1577
|
+
`Image archive ${fullImageName} successfully exported to directory ${tempDir}`,
|
|
1578
|
+
);
|
|
1579
|
+
}
|
|
1580
|
+
const allBlobs = getAllFiles(blobsDir, "*");
|
|
1581
|
+
for (const ablob of allBlobs) {
|
|
1582
|
+
if (DEBUG_MODE) {
|
|
1583
|
+
console.log(`Extracting ${ablob} to ${allLayersExplodedDir}`);
|
|
1584
|
+
}
|
|
1585
|
+
await extractTar(ablob, allLayersExplodedDir, options);
|
|
1586
|
+
}
|
|
1587
|
+
const lastLayerConfig = {};
|
|
1588
|
+
// Bug #3565. We may not know the work directory, so we have to try and detect them.
|
|
1589
|
+
const lastWorkingDir = "/";
|
|
1590
|
+
const exportData = {
|
|
1591
|
+
manifest,
|
|
1592
|
+
allLayersDir: tempDir,
|
|
1593
|
+
allLayersExplodedDir,
|
|
1594
|
+
lastLayerConfig,
|
|
1595
|
+
lastWorkingDir,
|
|
1596
|
+
};
|
|
1597
|
+
exportData.pkgPathList = getPkgPathList(exportData, lastWorkingDir);
|
|
1598
|
+
return exportData;
|
|
1599
|
+
}
|
|
1600
|
+
console.log(`Unable to extract image archive to ${tempDir}`);
|
|
1601
|
+
options.failOnError && process.exit(1);
|
|
1602
|
+
} catch (_err) {
|
|
1603
|
+
// ignore
|
|
1604
|
+
options.failOnError && process.exit(1);
|
|
1605
|
+
}
|
|
1606
|
+
return undefined;
|
|
1607
|
+
};
|
|
1608
|
+
|
|
1609
|
+
/**
|
|
1610
|
+
* Parse a Docker/containerd manifest file and extract all image layers into a
|
|
1611
|
+
* single merged directory. Resolves the last layer's config to determine the
|
|
1612
|
+
* container's working directory, and builds the package path list for
|
|
1613
|
+
* subsequent analysis.
|
|
1614
|
+
*
|
|
1615
|
+
* @param {string} manifestFile Path to the manifest.json (or index.json) file
|
|
1616
|
+
* @param {Object} localData Local image inspect data (e.g. from `docker inspect`)
|
|
1617
|
+
* @param {string} tempDir Temporary directory that holds the unpacked image
|
|
1618
|
+
* @param {string} allLayersExplodedDir Directory where all layers are merged
|
|
1619
|
+
* @param {Object} options CLI options (uses `options.failOnError`)
|
|
1620
|
+
* @returns {Promise<Object>} Export data object containing `manifest`,
|
|
1621
|
+
* `allLayersDir`, `allLayersExplodedDir`, `lastLayerConfig`,
|
|
1622
|
+
* `lastWorkingDir`, `binPaths`, and `pkgPathList`
|
|
1623
|
+
*/
|
|
1624
|
+
export const extractFromManifest = async (
|
|
1625
|
+
manifestFile,
|
|
1626
|
+
localData,
|
|
1627
|
+
tempDir,
|
|
1628
|
+
allLayersExplodedDir,
|
|
1629
|
+
options,
|
|
1630
|
+
) => {
|
|
1631
|
+
// Example of manifests
|
|
1632
|
+
// [{"Config":"blobs/sha256/dedc100afa8d6718f5ac537730dd4a5ceea3563e695c90f1a8ac6df32c4cb291","RepoTags":["shiftleft/core:latest"],"Layers":["blobs/sha256/eaead16dc43bb8811d4ff450935d607f9ba4baffda4fc110cc402fa43f601d83","blobs/sha256/2039af03c0e17a3025b989335e9414149577fa09e7d0dcbee80155333639d11f"]}]
|
|
1633
|
+
// {"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.docker.distribution.manifest.list.v2+json","digest":"sha256:7706ac20c7587081dc7a00e0ec65a6633b0bb3788e0048a3e971d3eae492db63","size":318,"annotations":{"io.containerd.image.name":"docker.io/shiftleft/scan-slim:latest","org.opencontainers.image.ref.name":"latest"}}]}
|
|
1634
|
+
let manifest = readArchiveJson(manifestFile);
|
|
1635
|
+
let lastLayerConfig = {};
|
|
1636
|
+
let lastLayerConfigFile = "";
|
|
1637
|
+
let selectedManifest;
|
|
1638
|
+
let lastWorkingDir = "";
|
|
1639
|
+
manifest = resolveArchiveManifest(manifest, tempDir);
|
|
1640
|
+
if (Array.isArray(manifest)) {
|
|
1641
|
+
selectedManifest = manifest[manifest.length - 1];
|
|
1642
|
+
if (manifest.length !== 1) {
|
|
1643
|
+
if (DEBUG_MODE) {
|
|
1644
|
+
console.log(
|
|
1645
|
+
"Multiple image tags was downloaded. Only the last one would be used",
|
|
1646
|
+
);
|
|
1647
|
+
console.log(selectedManifest);
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
const layers = selectedManifest?.Layers || [];
|
|
1651
|
+
if (!layers.length && safeExistsSync(tempDir)) {
|
|
1652
|
+
const blobFiles = readdirSync(join(tempDir, "blobs", "sha256"));
|
|
1653
|
+
if (blobFiles?.length) {
|
|
1654
|
+
for (const blobf of blobFiles) {
|
|
1655
|
+
layers.push(join("blobs", "sha256", blobf));
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
const lastLayer = layers[layers.length - 1];
|
|
1660
|
+
const layerPhase = ui.phase("Extracting image layers");
|
|
1661
|
+
let extractedLayers = 0;
|
|
1662
|
+
for (const layer of layers) {
|
|
1663
|
+
layerPhase.progress(extractedLayers, layers.length);
|
|
1664
|
+
layerPhase.detail(basename(layer));
|
|
1665
|
+
extractedLayers += 1;
|
|
1666
|
+
try {
|
|
1667
|
+
if (!lstatSync(join(tempDir, layer)).isFile()) {
|
|
1668
|
+
console.log(
|
|
1669
|
+
`Skipping layer ${layer} since it is not a readable file.`,
|
|
1670
|
+
);
|
|
1671
|
+
continue;
|
|
1672
|
+
}
|
|
1673
|
+
} catch (_e) {
|
|
1674
|
+
console.log(`Skipping layer ${layer} since it is not a readable file.`);
|
|
1675
|
+
continue;
|
|
1676
|
+
}
|
|
1677
|
+
if (DEBUG_MODE) {
|
|
1678
|
+
console.log(`Extracting layer ${layer} to ${allLayersExplodedDir}`);
|
|
1679
|
+
}
|
|
1680
|
+
try {
|
|
1681
|
+
await extractTar(join(tempDir, layer), allLayersExplodedDir, options);
|
|
1682
|
+
} catch (err) {
|
|
1683
|
+
if (err.code === "TAR_BAD_ARCHIVE") {
|
|
1684
|
+
if (DEBUG_MODE) {
|
|
1685
|
+
console.log(`Layer ${layer} is empty.`);
|
|
1686
|
+
}
|
|
1687
|
+
} else {
|
|
1688
|
+
console.log(err);
|
|
1689
|
+
options.failOnError && process.exit(1);
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
layerPhase.succeed(
|
|
1694
|
+
`${layers.length} layer${layers.length === 1 ? "" : "s"}`,
|
|
1695
|
+
);
|
|
1696
|
+
if (selectedManifest?.Config) {
|
|
1697
|
+
lastLayerConfigFile = join(tempDir, selectedManifest.Config);
|
|
1698
|
+
}
|
|
1699
|
+
if (!lastLayerConfigFile && lastLayer?.includes("layer.tar")) {
|
|
1700
|
+
lastLayerConfigFile = join(
|
|
1701
|
+
tempDir,
|
|
1702
|
+
lastLayer.replace("layer.tar", "json"),
|
|
1703
|
+
);
|
|
1704
|
+
}
|
|
1705
|
+
if (lastLayerConfigFile && safeExistsSync(lastLayerConfigFile)) {
|
|
1706
|
+
try {
|
|
1707
|
+
lastLayerConfig = JSON.parse(
|
|
1708
|
+
readFileSync(lastLayerConfigFile, {
|
|
1709
|
+
encoding: "utf-8",
|
|
1710
|
+
}),
|
|
1711
|
+
);
|
|
1712
|
+
lastWorkingDir = lastLayerConfig.config?.WorkingDir
|
|
1713
|
+
? join(allLayersExplodedDir, lastLayerConfig.config.WorkingDir)
|
|
1714
|
+
: "";
|
|
1715
|
+
} catch (_err) {
|
|
1716
|
+
options.failOnError && process.exit(1);
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
const inspectData = localData?.Config
|
|
1721
|
+
? localData
|
|
1722
|
+
: lastLayerConfig?.config
|
|
1723
|
+
? {
|
|
1724
|
+
...localData,
|
|
1725
|
+
Config: lastLayerConfig.config,
|
|
1726
|
+
}
|
|
1727
|
+
: localData;
|
|
1728
|
+
const binPaths = extractPathEnv(inspectData?.Config?.Env);
|
|
1729
|
+
const exportData = {
|
|
1730
|
+
inspectData,
|
|
1731
|
+
manifest,
|
|
1732
|
+
allLayersDir: tempDir,
|
|
1733
|
+
allLayersExplodedDir,
|
|
1734
|
+
lastLayerConfig,
|
|
1735
|
+
lastWorkingDir,
|
|
1736
|
+
binPaths,
|
|
1737
|
+
};
|
|
1738
|
+
exportData.pkgPathList = getPkgPathList(exportData, lastWorkingDir);
|
|
1739
|
+
return exportData;
|
|
1740
|
+
};
|
|
1741
|
+
|
|
1742
|
+
/**
|
|
1743
|
+
* Method to export a container image by using the export feature in docker or podman service.
|
|
1744
|
+
* Returns the location of the layers with additional packages related metadata
|
|
1745
|
+
*/
|
|
1746
|
+
export const exportImage = async (fullImageName, options) => {
|
|
1747
|
+
// Safely ignore local directories
|
|
1748
|
+
if (
|
|
1749
|
+
!fullImageName ||
|
|
1750
|
+
fullImageName === "." ||
|
|
1751
|
+
safeExistsSync(resolve(fullImageName))
|
|
1752
|
+
) {
|
|
1753
|
+
return undefined;
|
|
1754
|
+
}
|
|
1755
|
+
if (isDryRun) {
|
|
1756
|
+
const imageDetails = parseImageName(fullImageName);
|
|
1757
|
+
const requestedRegistryRef = imageDetails.registry
|
|
1758
|
+
? imageDetails.repo
|
|
1759
|
+
? `${imageDetails.registry}/${imageDetails.repo}`
|
|
1760
|
+
: imageDetails.registry
|
|
1761
|
+
: DOCKER_HUB_REGISTRY;
|
|
1762
|
+
await getConnection({}, requestedRegistryRef);
|
|
1763
|
+
recordActivity({
|
|
1764
|
+
kind: "container",
|
|
1765
|
+
reason:
|
|
1766
|
+
"Dry run mode blocks container image pull, save, and export operations.",
|
|
1767
|
+
status: "blocked",
|
|
1768
|
+
target: fullImageName,
|
|
1769
|
+
});
|
|
1770
|
+
return undefined;
|
|
1771
|
+
}
|
|
1772
|
+
// Try to get the data locally first
|
|
1773
|
+
const localData = await getImage(fullImageName);
|
|
1774
|
+
if (!localData) {
|
|
1775
|
+
return undefined;
|
|
1776
|
+
}
|
|
1777
|
+
const { registry, tag, digest } = parseImageName(fullImageName);
|
|
1778
|
+
// Fetch only the latest tag if none is specified
|
|
1779
|
+
if (tag === "" && digest === "") {
|
|
1780
|
+
fullImageName = `${fullImageName}:latest`;
|
|
1781
|
+
}
|
|
1782
|
+
const tempDir = safeMkdtempSync(join(getTmpDir(), "docker-images-"));
|
|
1783
|
+
const allLayersExplodedDir = join(tempDir, "all-layers");
|
|
1784
|
+
let manifestFile = join(tempDir, "manifest.json");
|
|
1785
|
+
// Windows containers use index.json
|
|
1786
|
+
const manifestIndexFile = join(tempDir, "index.json");
|
|
1787
|
+
// On Windows or on mac with Rancher Desktop, fallback to invoking cli
|
|
1788
|
+
if (needsCliFallback()) {
|
|
1789
|
+
const imageTarFile = join(tempDir, "image.tar");
|
|
1790
|
+
const dockerCmd = getContainerCliCmd();
|
|
1791
|
+
console.log(
|
|
1792
|
+
`About to export image ${fullImageName} to ${imageTarFile} using ${dockerCmd} cli`,
|
|
1793
|
+
);
|
|
1794
|
+
const result = safeSpawnSync(dockerCmd, [
|
|
1795
|
+
"save",
|
|
1796
|
+
"-o",
|
|
1797
|
+
imageTarFile,
|
|
1798
|
+
fullImageName,
|
|
1799
|
+
]);
|
|
1800
|
+
if (result.status !== 0 || result.error) {
|
|
1801
|
+
if (result.stdout || result.stderr) {
|
|
1802
|
+
console.log(result.stdout, result.stderr);
|
|
1803
|
+
}
|
|
1804
|
+
return localData;
|
|
1805
|
+
}
|
|
1806
|
+
await extractTar(imageTarFile, tempDir, options);
|
|
1807
|
+
if (DEBUG_MODE) {
|
|
1808
|
+
console.log(`Cleaning up ${imageTarFile}`);
|
|
1809
|
+
}
|
|
1810
|
+
safeRmSync(imageTarFile, { force: true });
|
|
1811
|
+
} else {
|
|
1812
|
+
const client = await getConnection({}, registry);
|
|
1813
|
+
try {
|
|
1814
|
+
if (DEBUG_MODE) {
|
|
1815
|
+
if (registry?.trim().length) {
|
|
1816
|
+
console.log(
|
|
1817
|
+
`About to export image ${fullImageName} from ${registry} to ${tempDir}`,
|
|
1818
|
+
);
|
|
1819
|
+
} else {
|
|
1820
|
+
console.log(`About to export image ${fullImageName} to ${tempDir}`);
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
await stream.pipeline(
|
|
1824
|
+
client.stream(`images/${fullImageName}/get`),
|
|
1825
|
+
x({
|
|
1826
|
+
sync: false,
|
|
1827
|
+
preserveOwner: false,
|
|
1828
|
+
noMtime: true,
|
|
1829
|
+
noChmod: true,
|
|
1830
|
+
strict: !NON_STRICT_TAR_EXTRACT,
|
|
1831
|
+
C: tempDir,
|
|
1832
|
+
portable: true,
|
|
1833
|
+
unlink: true,
|
|
1834
|
+
onwarn: handleTarWarning,
|
|
1835
|
+
onReadEntry: handleAbsolutePath,
|
|
1836
|
+
filter: tarFilter,
|
|
1837
|
+
}),
|
|
1838
|
+
);
|
|
1839
|
+
} catch (_err) {
|
|
1840
|
+
if (localData?.Id) {
|
|
1841
|
+
console.log(`Retrying with ${localData.Id}`);
|
|
1842
|
+
try {
|
|
1843
|
+
await stream.pipeline(
|
|
1844
|
+
client.stream(`images/${localData.Id}/get`),
|
|
1845
|
+
x({
|
|
1846
|
+
sync: true,
|
|
1847
|
+
preserveOwner: false,
|
|
1848
|
+
noMtime: true,
|
|
1849
|
+
noChmod: true,
|
|
1850
|
+
strict: !NON_STRICT_TAR_EXTRACT,
|
|
1851
|
+
C: tempDir,
|
|
1852
|
+
portable: true,
|
|
1853
|
+
onwarn: handleTarWarning,
|
|
1854
|
+
onReadEntry: handleAbsolutePath,
|
|
1855
|
+
filter: tarFilter,
|
|
1856
|
+
}),
|
|
1857
|
+
);
|
|
1858
|
+
} catch (_err) {
|
|
1859
|
+
// ignore
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
// Continue with extracting the layers
|
|
1865
|
+
if (safeExistsSync(tempDir)) {
|
|
1866
|
+
if (safeExistsSync(manifestFile)) {
|
|
1867
|
+
// This is fine
|
|
1868
|
+
} else if (safeExistsSync(manifestIndexFile)) {
|
|
1869
|
+
manifestFile = manifestIndexFile;
|
|
1870
|
+
} else {
|
|
1871
|
+
console.log(
|
|
1872
|
+
`Manifest file ${manifestFile} was not found after export at ${tempDir}`,
|
|
1873
|
+
);
|
|
1874
|
+
return undefined;
|
|
1875
|
+
}
|
|
1876
|
+
if (DEBUG_MODE) {
|
|
1877
|
+
console.log(
|
|
1878
|
+
`Image ${fullImageName} successfully exported to directory ${tempDir}`,
|
|
1879
|
+
);
|
|
1880
|
+
}
|
|
1881
|
+
safeMkdirSync(allLayersExplodedDir);
|
|
1882
|
+
return await extractFromManifest(
|
|
1883
|
+
manifestFile,
|
|
1884
|
+
localData,
|
|
1885
|
+
tempDir,
|
|
1886
|
+
allLayersExplodedDir,
|
|
1887
|
+
options,
|
|
1888
|
+
);
|
|
1889
|
+
}
|
|
1890
|
+
console.log(`Unable to export image to ${tempDir}`);
|
|
1891
|
+
return undefined;
|
|
1892
|
+
};
|
|
1893
|
+
|
|
1894
|
+
/**
|
|
1895
|
+
* Method to retrieve path list for system-level packages
|
|
1896
|
+
*/
|
|
1897
|
+
export const getPkgPathList = (exportData, lastWorkingDir) => {
|
|
1898
|
+
const allLayersExplodedDir = exportData.allLayersExplodedDir;
|
|
1899
|
+
const allLayersDir = exportData.allLayersDir;
|
|
1900
|
+
let pathList = [];
|
|
1901
|
+
let knownSysPaths = [];
|
|
1902
|
+
// Bug #3565. Try and detect the working directory
|
|
1903
|
+
if (lastWorkingDir === "/" || lastWorkingDir === "") {
|
|
1904
|
+
if (DEBUG_MODE) {
|
|
1905
|
+
console.log("Attempting to detect the work directories ...");
|
|
1906
|
+
}
|
|
1907
|
+
const possibleWorkDirs = getDirs(allLayersExplodedDir, "*", true, false);
|
|
1908
|
+
for (const adir of possibleWorkDirs) {
|
|
1909
|
+
if (safeExistsSync(adir) && !lstatSync(adir).isDirectory()) {
|
|
1910
|
+
break;
|
|
1911
|
+
}
|
|
1912
|
+
let ignoreDir = false;
|
|
1913
|
+
for (const nonWorkDirs of [
|
|
1914
|
+
"/var",
|
|
1915
|
+
"/usr",
|
|
1916
|
+
"/mnt",
|
|
1917
|
+
"/opt",
|
|
1918
|
+
"/root",
|
|
1919
|
+
"/home",
|
|
1920
|
+
]) {
|
|
1921
|
+
if (adir.endsWith(nonWorkDirs)) {
|
|
1922
|
+
ignoreDir = true;
|
|
1923
|
+
break;
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
if (!ignoreDir) {
|
|
1927
|
+
knownSysPaths.push(adir);
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
if (DEBUG_MODE) {
|
|
1931
|
+
console.log("Possible work directories", knownSysPaths);
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
if (allLayersExplodedDir && allLayersExplodedDir !== "") {
|
|
1935
|
+
knownSysPaths = knownSysPaths.concat([
|
|
1936
|
+
join(allLayersExplodedDir, "/usr/local/go"),
|
|
1937
|
+
join(allLayersExplodedDir, "/usr/local/lib"),
|
|
1938
|
+
join(allLayersExplodedDir, "/usr/local/lib64"),
|
|
1939
|
+
join(allLayersExplodedDir, "/opt"),
|
|
1940
|
+
join(allLayersExplodedDir, "/root"),
|
|
1941
|
+
join(allLayersExplodedDir, "/home"),
|
|
1942
|
+
join(allLayersExplodedDir, "/usr/share"),
|
|
1943
|
+
join(allLayersExplodedDir, "/usr/src"),
|
|
1944
|
+
join(allLayersExplodedDir, "/var/www/html"),
|
|
1945
|
+
join(allLayersExplodedDir, "/var/lib"),
|
|
1946
|
+
join(allLayersExplodedDir, "/mnt"),
|
|
1947
|
+
]);
|
|
1948
|
+
} else if (allLayersExplodedDir === "") {
|
|
1949
|
+
knownSysPaths = knownSysPaths.concat([
|
|
1950
|
+
join(allLayersExplodedDir, "/usr/local/go"),
|
|
1951
|
+
join(allLayersExplodedDir, "/usr/local/lib"),
|
|
1952
|
+
join(allLayersExplodedDir, "/usr/local/lib64"),
|
|
1953
|
+
join(allLayersExplodedDir, "/opt"),
|
|
1954
|
+
join(allLayersExplodedDir, "/root"),
|
|
1955
|
+
join(allLayersExplodedDir, "/usr/share"),
|
|
1956
|
+
join(allLayersExplodedDir, "/usr/src"),
|
|
1957
|
+
join(allLayersExplodedDir, "/var/www/html"),
|
|
1958
|
+
join(allLayersExplodedDir, "/var/lib"),
|
|
1959
|
+
]);
|
|
1960
|
+
}
|
|
1961
|
+
if (safeExistsSync(join(allLayersDir, "Files"))) {
|
|
1962
|
+
knownSysPaths.push(join(allLayersDir, "Files"));
|
|
1963
|
+
}
|
|
1964
|
+
/*
|
|
1965
|
+
// Too slow
|
|
1966
|
+
if (safeExistsSync(path.join(allLayersDir, "Users"))) {
|
|
1967
|
+
knownSysPaths.push(path.join(allLayersDir, "Users"));
|
|
1968
|
+
}
|
|
1969
|
+
*/
|
|
1970
|
+
if (safeExistsSync(join(allLayersDir, "ProgramData"))) {
|
|
1971
|
+
knownSysPaths.push(join(allLayersDir, "ProgramData"));
|
|
1972
|
+
}
|
|
1973
|
+
const pyInstalls = getDirs(allLayersDir, "Python*/", false, false);
|
|
1974
|
+
if (pyInstalls?.length) {
|
|
1975
|
+
for (const pyiPath of pyInstalls) {
|
|
1976
|
+
const pyDirs = getOnlyDirs(pyiPath, "site-packages");
|
|
1977
|
+
if (pyDirs?.length) {
|
|
1978
|
+
pathList = pathList.concat(pyDirs);
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
if (lastWorkingDir && lastWorkingDir !== "") {
|
|
1983
|
+
if (
|
|
1984
|
+
lastWorkingDir !== "/" &&
|
|
1985
|
+
!lastWorkingDir.includes("/opt/") &&
|
|
1986
|
+
!lastWorkingDir.includes("/home/") &&
|
|
1987
|
+
!lastWorkingDir.includes("/root/")
|
|
1988
|
+
) {
|
|
1989
|
+
knownSysPaths.push(lastWorkingDir);
|
|
1990
|
+
}
|
|
1991
|
+
// Some more common app dirs
|
|
1992
|
+
if (!lastWorkingDir.includes("/app/")) {
|
|
1993
|
+
knownSysPaths.push(join(allLayersExplodedDir, "/app"));
|
|
1994
|
+
}
|
|
1995
|
+
if (!lastWorkingDir.includes("/layers/")) {
|
|
1996
|
+
knownSysPaths.push(join(allLayersExplodedDir, "/layers"));
|
|
1997
|
+
}
|
|
1998
|
+
if (!lastWorkingDir.includes("/data/")) {
|
|
1999
|
+
knownSysPaths.push(join(allLayersExplodedDir, "/data"));
|
|
2000
|
+
}
|
|
2001
|
+
if (!lastWorkingDir.includes("/srv/")) {
|
|
2002
|
+
knownSysPaths.push(join(allLayersExplodedDir, "/srv"));
|
|
2003
|
+
}
|
|
2004
|
+
} else {
|
|
2005
|
+
// Bug #3426
|
|
2006
|
+
knownSysPaths.push(join(allLayersExplodedDir, "/app"));
|
|
2007
|
+
}
|
|
2008
|
+
// Known to cause EACCESS error
|
|
2009
|
+
knownSysPaths.push(join(allLayersExplodedDir, "/usr/lib"));
|
|
2010
|
+
knownSysPaths.push(join(allLayersExplodedDir, "/usr/lib64"));
|
|
2011
|
+
// Build path list
|
|
2012
|
+
const ecoDirNames = [
|
|
2013
|
+
"node_modules",
|
|
2014
|
+
"site-packages",
|
|
2015
|
+
"gems",
|
|
2016
|
+
".cargo",
|
|
2017
|
+
".composer",
|
|
2018
|
+
];
|
|
2019
|
+
for (const wpath of knownSysPaths) {
|
|
2020
|
+
pathList = pathList.concat(wpath);
|
|
2021
|
+
const ecoDirs = getOnlyDirsMulti(wpath, ecoDirNames);
|
|
2022
|
+
if (ecoDirs.node_modules.length) {
|
|
2023
|
+
pathList.push(ecoDirs.node_modules[0]);
|
|
2024
|
+
}
|
|
2025
|
+
if (ecoDirs["site-packages"].length) {
|
|
2026
|
+
pathList = pathList.concat(ecoDirs["site-packages"]);
|
|
2027
|
+
}
|
|
2028
|
+
if (ecoDirs.gems.length) {
|
|
2029
|
+
pathList.push(ecoDirs.gems[0]);
|
|
2030
|
+
}
|
|
2031
|
+
if (ecoDirs[".cargo"].length) {
|
|
2032
|
+
pathList = pathList.concat(ecoDirs[".cargo"]);
|
|
2033
|
+
}
|
|
2034
|
+
if (ecoDirs[".composer"].length) {
|
|
2035
|
+
pathList = pathList.concat(ecoDirs[".composer"]);
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
pathList = Array.from(new Set(pathList)).sort();
|
|
2039
|
+
if (DEBUG_MODE) {
|
|
2040
|
+
console.log("pathList", pathList);
|
|
2041
|
+
}
|
|
2042
|
+
return pathList;
|
|
2043
|
+
};
|
|
2044
|
+
|
|
2045
|
+
/**
|
|
2046
|
+
* Remove a container image from the local Docker / Podman daemon.
|
|
2047
|
+
*
|
|
2048
|
+
* @param {string} fullImageName Full image name including tag or digest (e.g. "ubuntu:22.04")
|
|
2049
|
+
* @param {boolean} [force=false] When `true`, force-remove the image even if it is in use
|
|
2050
|
+
* @returns {Promise<Buffer|undefined>} Raw response buffer from the daemon, or
|
|
2051
|
+
* `undefined` if no daemon connection is available
|
|
2052
|
+
*/
|
|
2053
|
+
export const removeImage = async (fullImageName, force = false) => {
|
|
2054
|
+
return await makeRequest(`images/${fullImageName}?force=${force}`, "DELETE");
|
|
2055
|
+
};
|
|
2056
|
+
|
|
2057
|
+
/**
|
|
2058
|
+
* Retrieve a base64url-encoded authentication token for a registry server by
|
|
2059
|
+
* invoking the `docker-credential-<exeSuffix>` credential helper binary.
|
|
2060
|
+
* Results are cached in `registry_auth_keys` to avoid redundant subprocess
|
|
2061
|
+
* calls.
|
|
2062
|
+
*
|
|
2063
|
+
* @param {string} exeSuffix Credential helper name suffix (e.g. "osxkeychain", "wincred", "pass")
|
|
2064
|
+
* @param {string} serverAddress Registry server address (e.g. "https://index.docker.io/v1/")
|
|
2065
|
+
* @returns {string|undefined} Base64url-encoded JSON auth token, or `undefined`
|
|
2066
|
+
* if the helper is unavailable or returns an error
|
|
2067
|
+
*/
|
|
2068
|
+
export const getCredsFromHelper = (exeSuffix, serverAddress) => {
|
|
2069
|
+
const cacheKey = `${exeSuffix}:${normalizeRegistryReference(serverAddress) ?? serverAddress}`;
|
|
2070
|
+
if (registry_auth_keys[cacheKey]) {
|
|
2071
|
+
return registry_auth_keys[cacheKey];
|
|
2072
|
+
}
|
|
2073
|
+
let credHelperExe = `docker-credential-${exeSuffix}`;
|
|
2074
|
+
if (isWin) {
|
|
2075
|
+
credHelperExe = `${credHelperExe}.exe`;
|
|
2076
|
+
}
|
|
2077
|
+
const result = safeSpawnSync(credHelperExe, ["get"], {
|
|
2078
|
+
input: serverAddress,
|
|
2079
|
+
});
|
|
2080
|
+
if (result.status !== 0 || result.error) {
|
|
2081
|
+
if (result.stdout || result.stderr) {
|
|
2082
|
+
console.log(result.stdout, result.stderr);
|
|
2083
|
+
}
|
|
2084
|
+
} else if (result.stdout) {
|
|
2085
|
+
const cmdOutput = Buffer.from(result.stdout).toString();
|
|
2086
|
+
try {
|
|
2087
|
+
const dockerUser = readEnvironmentVariable("DOCKER_USER", {
|
|
2088
|
+
sensitive: true,
|
|
2089
|
+
});
|
|
2090
|
+
const dockerPassword = readEnvironmentVariable("DOCKER_PASSWORD", {
|
|
2091
|
+
sensitive: true,
|
|
2092
|
+
});
|
|
2093
|
+
const authPayload = JSON.parse(cmdOutput);
|
|
2094
|
+
const fixedAuthPayload = {
|
|
2095
|
+
username: authPayload.username || authPayload.Username || dockerUser,
|
|
2096
|
+
password: authPayload.password || authPayload.Secret || dockerPassword,
|
|
2097
|
+
email: authPayload.email || authPayload.username || dockerUser,
|
|
2098
|
+
serveraddress: serverAddress,
|
|
2099
|
+
};
|
|
2100
|
+
const authKey = toBase64Url(JSON.stringify(fixedAuthPayload));
|
|
2101
|
+
registry_auth_keys[cacheKey] = authKey;
|
|
2102
|
+
return authKey;
|
|
2103
|
+
} catch (_err) {
|
|
2104
|
+
return undefined;
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
return undefined;
|
|
2108
|
+
};
|
|
2109
|
+
|
|
2110
|
+
/**
|
|
2111
|
+
* Append skipped source-file entries to the `internal:SrcFile` properties of matching
|
|
2112
|
+
* components. A component matches when its `oci:SrcImage` property value
|
|
2113
|
+
* equals the skipped image's `image` field and the source file path is not
|
|
2114
|
+
* already listed.
|
|
2115
|
+
*
|
|
2116
|
+
* @param {Array<{image: string, src: string}>} skippedImageSrcs List of skipped image/source pairs
|
|
2117
|
+
* @param {Array<Object>} components CycloneDX component objects to update in place
|
|
2118
|
+
*/
|
|
2119
|
+
export const addSkippedSrcFiles = (skippedImageSrcs, components) => {
|
|
2120
|
+
for (const skippedImage of skippedImageSrcs) {
|
|
2121
|
+
for (const co of components) {
|
|
2122
|
+
const srcFileValues = [];
|
|
2123
|
+
let srcImageValue;
|
|
2124
|
+
co.properties.forEach((property) => {
|
|
2125
|
+
if (property.name === "oci:SrcImage") {
|
|
2126
|
+
srcImageValue = property.value;
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
if (property.name === "internal:SrcFile") {
|
|
2130
|
+
srcFileValues.push(property.value);
|
|
2131
|
+
}
|
|
2132
|
+
});
|
|
2133
|
+
|
|
2134
|
+
if (
|
|
2135
|
+
srcImageValue === skippedImage.image &&
|
|
2136
|
+
!srcFileValues.includes(skippedImage.src)
|
|
2137
|
+
) {
|
|
2138
|
+
co.properties = co.properties.concat({
|
|
2139
|
+
name: "internal:SrcFile",
|
|
2140
|
+
value: skippedImage.src,
|
|
2141
|
+
});
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
};
|