From 810de3e0ec38052c874b2de3c979929212c0e8ca Mon Sep 17 00:00:00 2001 From: dervoeti Date: Fri, 21 Aug 2026 07:30:41 +0000 Subject: [PATCH 01/11] feat: Add a shared generator for vendored JavaScript SBOMs --- shared/sbom/vendored_js.py | 189 +++++++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100755 shared/sbom/vendored_js.py diff --git a/shared/sbom/vendored_js.py b/shared/sbom/vendored_js.py new file mode 100755 index 000000000..6b7f9e5e0 --- /dev/null +++ b/shared/sbom/vendored_js.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python3 +"""Generates a CycloneDX SBOM for third-party JavaScript that is checked into a product's source +tree as pre-built (usually minified) files. + +Such files carry no package manifest and no lockfile, so cdxgen, syft and trivy are all blind to +them: the libraries are shipped in our images but appear in no SBOM. We found no tool that +identifies a minified bundle reliably. retire.js recognises only about half of the libraries we +ship and gets some versions wrong. So the components have to be recorded by hand, in a manifest +per product version: + + /stackable/vendored-js/.json + +An entry may also declare the libraries that a bundle inlines, which is how a library that is +shipped without a file of its own still ends up in the SBOM. Trino's vendored vis bundle for +example inlines a copy of moment that nothing else would report. + +A hand-written manifest goes stale the moment a product version is bumped, so every entry pins the +SHA-256 of the file it describes. Both commands fail on a changed file, on a file that is listed +nowhere and on an entry whose file has disappeared. Recording a version is therefore a one-time +cost per file, and the build tells us when to revisit it. + +shared/sbom/identify_js.py helps with writing and updating a manifest. +""" + +import argparse +import hashlib +import json +import re +import sys +from pathlib import Path +from urllib.parse import unquote + +# A purl always carries the name and the version, so they are not repeated in the manifest. +PURL = re.compile(r"^pkg:[^/]+/(?P.+)@(?P[^@?#]+)$") + + +def scan(manifest, source_root): + """Every JavaScript file below the scanned directories, keyed by its path relative to the + source root. The manifest uses those relative paths because they stay unambiguous even when a + product has several scanned directories.""" + return { + str(path.relative_to(source_root)): path + for directory in manifest["scan-dirs"] + for path in sorted((source_root / directory).rglob("*.js")) + } + + +def verify(manifest, source_root): + """Every disagreement between the manifest and the source tree that would make the generated SBOM wrong.""" + own = set(manifest.get("own", [])) + listed = {} + problems = [] + + for library in manifest["libraries"]: + if library["file"] in listed or library["file"] in own: + problems.append(f"DUPLICATE {library['file']}\n Listed more than once in the manifest.") + listed[library["file"]] = library + + found = scan(manifest, source_root) + for file, path in found.items(): + library = listed.get(file) + if library is None: + if file not in own: + problems.append( + f'UNLISTED {file}\n Add it to "libraries" with a purl if it is third-party,' + ' or to "own" if the product wrote it.' + ) + continue + + actual = hashlib.sha256(path.read_bytes()).hexdigest() + if library["sha256"] != actual: + problems.append( + f"CHANGED {file}\n manifest {library['sha256']}\n actual {actual}\n" + " The file was updated upstream, so re-check the version it records." + ) + + for file in listed: + if file not in found: + problems.append(f"GONE {file}\n Listed in the manifest but no longer in the source tree.") + for file in sorted(own - found.keys()): + problems.append(f'GONE {file}\n Listed in "own" but no longer in the source tree.') + + return found, problems + + +def identity(entry): + """A component's name and version, plus its purl if it has one. A purl is the preferred + identity because that is what vulnerability scanners match on, but libraries that were never + published to a package registry cannot have one and are recorded by name only. The "note" + field of such an entry says why.""" + purl = entry.get("purl") + if not purl: + if not entry.get("name"): + raise SystemExit(f"Entry without a purl and without a name: {entry}") + return None, entry["name"], entry.get("version") + + match = PURL.match(purl) + if not match: + raise SystemExit(f"Cannot parse the purl {purl}") + return purl, unquote(match["name"]), unquote(match["version"]) + + +def build_bom(manifest, component_version, spec_version): + components = {} + + def add(entry, location, sha256): + purl, name, version = identity(entry) + # Some libraries carry no version anywhere, so the name alone identifies them. + key = purl or (f"{name}@{version}" if version else name) + if key not in components: + component = {"type": "library", "name": name} + if version: + component["version"] = version + component["bom-ref"] = key + if purl: + component["purl"] = purl + if entry.get("license"): + component["licenses"] = [{"expression": entry["license"]}] + if sha256: + component["hashes"] = [] + component["evidence"] = {"occurrences": []} + components[key] = component + + component = components[key] + if sha256: + component.setdefault("hashes", []).append({"alg": "SHA-256", "content": sha256}) + component["evidence"]["occurrences"].append({"location": location}) + + for library in manifest["libraries"]: + # A library can be shipped as several files, for example a minified and a plain build, so + # the files are collapsed into one component that records each of them as evidence. + add(library, library["file"], library["sha256"]) + # Bundles inline their own dependencies, which are shipped without a file of their own. + # Their hash would be the hash of the bundle, so it is deliberately not recorded. + for bundled in library.get("bundles", []): + add(bundled, library["file"], None) + + # No timestamp and no serial number, so that repeated runs produce the same file. + return { + "bomFormat": "CycloneDX", + "specVersion": spec_version, + "version": 1, + "metadata": { + "component": { + "type": "application", + "bom-ref": f"{manifest['name']}@{component_version}", + "name": manifest["name"], + "version": component_version, + }, + "tools": {"components": [{"type": "application", "name": "vendored_js.py", "group": "tech.stackable"}]}, + }, + "components": list(components.values()), + } + + +def main(): + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + commands = parser.add_subparsers(dest="command", required=True) + + check_command = commands.add_parser("check", help="report every mismatch between the manifest and the source tree") + bom_command = commands.add_parser("bom", help="write the SBOM the manifest describes") + for command in (check_command, bom_command): + command.add_argument("manifest", type=Path) + command.add_argument("source_root", type=Path) + bom_command.add_argument("output", type=Path) + bom_command.add_argument("component_version") + bom_command.add_argument("spec_version") + + arguments = parser.parse_args() + manifest = json.loads(arguments.manifest.read_text()) + + # Never generate an SBOM that we know to be wrong, so this also gates "bom". + found, problems = verify(manifest, arguments.source_root) + if problems: + print(f"{arguments.manifest} does not match the source tree ({len(problems)} problem(s)):\n", file=sys.stderr) + print("\n".join(problems), file=sys.stderr) + raise SystemExit(1) + + if arguments.command == "check": + print(f"{arguments.manifest}: {len(found)} JavaScript files, all accounted for") + return + + bom = build_bom(manifest, arguments.component_version, arguments.spec_version) + arguments.output.write_text(json.dumps(bom, indent=2) + "\n") + print(f"Wrote {arguments.output} with {len(bom['components'])} components") + + +if __name__ == "__main__": + main() From da2a2287df27230c3f6f049d2b604c545443b22a Mon Sep 17 00:00:00 2001 From: dervoeti Date: Fri, 21 Aug 2026 07:30:50 +0000 Subject: [PATCH 02/11] feat(hbase): Add an SBOM for the web UI dependencies --- CHANGELOG.md | 2 + boil.toml | 6 ++ hbase/hbase/Dockerfile | 47 ++++++++++- hbase/hbase/boil-config.toml | 8 ++ hbase/hbase/stackable/hbase_webapps_deps.py | 91 +++++++++++++++++++++ 5 files changed, 153 insertions(+), 1 deletion(-) create mode 100755 hbase/hbase/stackable/hbase_webapps_deps.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ea959918..65d557bcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file. - airflow, superset, druid, nifi: Add SBOMs for the frontend (npm) dependencies ([#1600]). - nifi: Backport NIFI-15958 to log periodic progress while waiting for the content archive scan and provenance re-index, for `2.6.0`, `2.7.2`, and `2.9.0` ([#1611]). +- hbase: Add an SBOM for the web UI (npm) dependencies, which are unpacked from webjars and therefore not covered by the CycloneDX Maven plugin ([#1620]). ### Changed @@ -29,6 +30,7 @@ All notable changes to this project will be documented in this file. [#1600]: https://github.com/stackabletech/docker-images/pull/1600 [#1611]: https://github.com/stackabletech/docker-images/pull/1611 [#1616]: https://github.com/stackabletech/docker-images/pull/1616 +[#1620]: https://github.com/stackabletech/docker-images/pull/1620 ## [26.7.0] - 2026-07-21 diff --git a/boil.toml b/boil.toml index c7ec34d11..f5ac03bb1 100644 --- a/boil.toml +++ b/boil.toml @@ -6,6 +6,12 @@ DELETE_CACHES = "true" # CycloneDX specification version used for the SBOMs generated by cdxgen. # 1.6 is the lowest version cdxgen 13 accepts as a generation target. CDXGEN_SPEC_VERSION = "1.6" +# Node version used to run cdxgen in the builders that need it. It is unrelated to any product +# and to the Node version that a product uses to build its frontend, so it is configured once +# here instead of per product version. +# Find the latest release here: https://github.com/nodejs/node/releases +# renovate: datasource=node-version packageName=node +SBOM_NODEJS_VERSION = "24.19.0" [metadata] documentation = "https://docs.stackable.tech/home/stable/" diff --git a/hbase/hbase/Dockerfile b/hbase/hbase/Dockerfile index 84ed95311..fe8045efa 100644 --- a/hbase/hbase/Dockerfile +++ b/hbase/hbase/Dockerfile @@ -11,6 +11,9 @@ ENV HADOOP_VERSION=${HADOOP_HADOOP_VERSION} ARG TARGETARCH ARG TARGETOS ARG STACKABLE_USER_UID +ARG SBOM_NODEJS_VERSION +ARG CDXGEN_SPEC_VERSION +ARG CDXGEN_VERSION # Setting this to anything other than "true" will keep the cache folders around (e.g. for Maven, NPM etc.) # This can be used to speed up builds when disk space is of no concern. @@ -18,11 +21,31 @@ ARG DELETE_CACHES="true" COPY hbase/licenses /licenses +RUN <= 24, so it gets its own Node installation in /opt/node-sbom and +# is invoked with that prepended to PATH. +# -fsSL is not needed: the shared /root/.curlrc sets location, fail, silent and show-error. +ARCH="${TARGETARCH/amd64/x64}" +mkdir -p /opt/node-sbom +curl "https://repo.stackable.tech/repository/packages/node/node-v${SBOM_NODEJS_VERSION}-linux-${ARCH}.tar.xz" | \ + tar --extract --xz --directory=/opt/node-sbom --strip-components=1 +PATH="/opt/node-sbom/bin:$PATH" npm install --global "@cdxgen/cdxgen@${CDXGEN_VERSION}" + +microdnf update +microdnf install python3 +microdnf clean all +rm -rf /var/cache/yum +EOF + USER ${STACKABLE_USER_UID} WORKDIR /stackable COPY --chown=${STACKABLE_USER_UID}:0 hbase/hbase/stackable/patches/patchable.toml /stackable/src/hbase/hbase/stackable/patches/patchable.toml COPY --chown=${STACKABLE_USER_UID}:0 hbase/hbase/stackable/patches/${PRODUCT_VERSION} /stackable/src/hbase/hbase/stackable/patches/${PRODUCT_VERSION} +COPY --chown=${STACKABLE_USER_UID}:0 hbase/hbase/stackable/hbase_webapps_deps.py /stackable/hbase_webapps_deps.py COPY --from=hadoop-builder --chown=${STACKABLE_USER_UID}:0 /stackable/patched-libs /stackable/patched-libs # Cache mounts are owned by root by default @@ -36,7 +59,9 @@ COPY --from=hadoop-builder --chown=${STACKABLE_USER_UID}:0 /stackable/patched-li # builder containers will share the same cache and the `rm -rf` commands will fail # with a "directory not empty" error on the first builder to finish, as other builders # are still working in the cache directory. -RUN --mount=type=cache,id=maven-hbase-${PRODUCT_VERSION},uid=${STACKABLE_USER_UID},target=/stackable/.m2/repository <s of a plugin and not as project dependencies, the CycloneDX Maven plugin does not +pick them up, so they are missing from the HBase SBOM. + +The generated package.json is only an intermediate artifact: cdxgen turns it into the actual +CycloneDX SBOM. npm coordinates are used rather than the Maven ones, because vulnerability scanners +match advisories against pkg:npm and largely fail to match pkg:maven/org.webjars purls. +""" + +import argparse +import json +import re +from pathlib import Path +from xml.etree import ElementTree + +PROPERTY = re.compile(r"\$\{([\w.-]+)\}") + + +def tag(element): + """The tag of an element without the Maven POM namespace.""" + # ElementTree keeps the namespace in the tag itself, so the root element of a pom is called + # "{http://maven.apache.org/POM/4.0.0}project". + return element.tag.rpartition("}")[2] + + +def properties(pom): + """Every entry of a pom, for example 3.7.1.""" + # The whole tree is walked because is not only a top-level element: HBase declares + # most of its properties inside profiles. + entries = {} + for block in pom.iter(): + if tag(block) == "properties": + for entry in block: + entries[tag(entry)] = (entry.text or "").strip() + return entries + + +def webjars(server_pom, versions): + """The org.webjars artifacts that the maven-dependency-plugin unpacks, as npm dependencies. + The webjar artifact IDs match their npm package names, so they can be used verbatim.""" + dependencies = {} + for item in server_pom.iter(): + if tag(item) != "artifactItem": + continue + + fields = {tag(field): (field.text or "").strip() for field in item} + if fields.get("groupId") != "org.webjars": + continue + if not fields.get("artifactId") or not fields.get("version"): + raise SystemExit(f" without an artifactId or version: {fields}") + + version = PROPERTY.sub(lambda match: versions.get(match[1], match[0]), fields["version"]) + if "${" in version: + raise SystemExit(f"Cannot resolve the version of {fields['artifactId']} from the root pom: {version}") + + # bootstrap is unpacked twice, once for its JavaScript and once for its CSS. + dependencies[fields["artifactId"]] = version + + # Guard against upstream restructuring the pom, which would otherwise silently produce an SBOM + # without any components. + if not dependencies: + raise SystemExit("No org.webjars found in hbase-server/pom.xml, did the pom layout change?") + return dependencies + + +def main(): + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("source_root", type=Path, help="the HBase source tree") + parser.add_argument("version", help="the HBase version, used as the version of the generated package") + parser.add_argument("output", type=Path, help="the package.json to write") + arguments = parser.parse_args() + + dependencies = webjars( + ElementTree.parse(arguments.source_root / "hbase-server/pom.xml"), + properties(ElementTree.parse(arguments.source_root / "pom.xml")), + ) + package = {"name": "hbase-webapps", "version": arguments.version, "private": True, "dependencies": dependencies} + arguments.output.write_text(json.dumps(package, indent=2) + "\n") + + summary = ", ".join(f"{name}@{version}" for name, version in dependencies.items()) + print(f"Wrote {arguments.output}: {summary}") + + +if __name__ == "__main__": + main() From d7039cedc174580891793501d1cddd0b6a4130fc Mon Sep 17 00:00:00 2001 From: dervoeti Date: Fri, 21 Aug 2026 07:30:57 +0000 Subject: [PATCH 03/11] feat(hadoop, spark, trino): Add SBOMs for the web UI dependencies --- CHANGELOG.md | 2 + hadoop/hadoop/Dockerfile | 20 ++- .../hadoop/stackable/vendored-js/3.3.6.json | 121 ++++++++++++++++ .../hadoop/stackable/vendored-js/3.4.2.json | 122 ++++++++++++++++ .../hadoop/stackable/vendored-js/3.4.3.json | 122 ++++++++++++++++ .../hadoop/stackable/vendored-js/3.5.0.json | 123 ++++++++++++++++ spark-k8s/Dockerfile.3 | 31 +++++ spark-k8s/Dockerfile.4 | 31 +++++ spark-k8s/stackable/vendored-js/3.5.8.json | 122 ++++++++++++++++ spark-k8s/stackable/vendored-js/4.1.1.json | 131 ++++++++++++++++++ spark-k8s/stackable/vendored-js/4.1.2.json | 131 ++++++++++++++++++ trino/trino/Dockerfile | 83 +++++++++++ trino/trino/boil-config.toml | 18 +++ trino/trino/stackable/vendored-js/477.json | 112 +++++++++++++++ trino/trino/stackable/vendored-js/479.json | 112 +++++++++++++++ trino/trino/stackable/vendored-js/481.json | 112 +++++++++++++++ 16 files changed, 1391 insertions(+), 2 deletions(-) create mode 100644 hadoop/hadoop/stackable/vendored-js/3.3.6.json create mode 100644 hadoop/hadoop/stackable/vendored-js/3.4.2.json create mode 100644 hadoop/hadoop/stackable/vendored-js/3.4.3.json create mode 100644 hadoop/hadoop/stackable/vendored-js/3.5.0.json create mode 100644 spark-k8s/stackable/vendored-js/3.5.8.json create mode 100644 spark-k8s/stackable/vendored-js/4.1.1.json create mode 100644 spark-k8s/stackable/vendored-js/4.1.2.json create mode 100644 trino/trino/stackable/vendored-js/477.json create mode 100644 trino/trino/stackable/vendored-js/479.json create mode 100644 trino/trino/stackable/vendored-js/481.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 65d557bcd..6597ec766 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ All notable changes to this project will be documented in this file. - airflow, superset, druid, nifi: Add SBOMs for the frontend (npm) dependencies ([#1600]). - nifi: Backport NIFI-15958 to log periodic progress while waiting for the content archive scan and provenance re-index, for `2.6.0`, `2.7.2`, and `2.9.0` ([#1611]). - hbase: Add an SBOM for the web UI (npm) dependencies, which are unpacked from webjars and therefore not covered by the CycloneDX Maven plugin ([#1620]). +- trino: Add SBOMs for the web UI, both for the two npm projects behind it and for the pre-built JavaScript vendored into the source tree ([#1620]). +- hadoop, spark: Add SBOMs for the pre-built JavaScript that is vendored into the source tree for the HDFS and Spark web UIs ([#1620]). ### Changed diff --git a/hadoop/hadoop/Dockerfile b/hadoop/hadoop/Dockerfile index b8387410e..a336c1858 100644 --- a/hadoop/hadoop/Dockerfile +++ b/hadoop/hadoop/Dockerfile @@ -12,6 +12,7 @@ ARG AZURE_STORAGE_VERSION ARG AZURE_KEYVAULT_CORE_VERSION ARG ANALYTICSACCELERATOR_S3_VERSION ARG STACKABLE_USER_UID +ARG CDXGEN_SPEC_VERSION WORKDIR /stackable @@ -21,8 +22,9 @@ COPY --chown=${STACKABLE_USER_UID}:0 shared/protobuf/stackable/patches/${PROTOBU RUN <>> Build spark RUN <>> Build spark RUN <= 24, which is unrelated to the Node version that the +# frontend-maven-plugin downloads for the actual build, so it gets its own Node installation in +# /opt/node-sbom and is invoked with that prepended to PATH. +# -fsSL is not needed: the shared /root/.curlrc sets location, fail, silent and show-error. +ARCH="${TARGETARCH/amd64/x64}" +mkdir -p /opt/node-sbom +curl "https://repo.stackable.tech/repository/packages/node/node-v${SBOM_NODEJS_VERSION}-linux-${ARCH}.tar.xz" | \ + tar --extract --xz --directory=/opt/node-sbom --strip-components=1 +PATH="/opt/node-sbom/bin:$PATH" npm install --global "@cdxgen/cdxgen@${CDXGEN_VERSION}" + +microdnf update +microdnf install python3 +microdnf clean all +rm -rf /var/cache/yum +EOF + +COPY --chown=${STACKABLE_USER_UID}:0 shared/sbom/vendored_js.py /stackable/vendored_js.py +COPY --chown=${STACKABLE_USER_UID}:0 trino/trino/stackable/vendored-js/${PRODUCT_VERSION}.json /stackable/vendored-js.json + # adding a hadolint ignore for SC2215, due to https://github.com/hadolint/hadolint/issues/980 # hadolint ignore=SC2215 RUN --mount=type=cache,id=maven-${PRODUCT_VERSION},target=/root/.m2/repository < Date: Fri, 21 Aug 2026 07:31:00 +0000 Subject: [PATCH 04/11] feat: Add a tool for identifying vendored JavaScript --- shared/sbom/identify_js.py | 189 +++++++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100755 shared/sbom/identify_js.py diff --git a/shared/sbom/identify_js.py b/shared/sbom/identify_js.py new file mode 100755 index 000000000..e0d5b87ec --- /dev/null +++ b/shared/sbom/identify_js.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python3 +"""Authoring aid for the manifests that shared/sbom/vendored_js.py consumes. Not used by any build. + +Writing such a manifest means naming a library and a version for a pre-built, usually minified +JavaScript file. The file name is not evidence: Hadoop ships d3 4.1.0 as "d3-v4.1.1.min.js" and +mustache.js as "jquery.mustache.js". Neither is a header comment always present. + +This tool provides the two things that turn writing a manifest into reading off facts: + + inspect lists every .js file under the given directories with its SHA-256 and any version + string found near the top of the file, which is the starting point for a new + manifest and for seeing what a version bump changed. + + identify downloads every published version of the given npm packages and hashes every file in + them, looking for one that is byte-identical to ours. A match is proof, and it is + what established that Hadoop's "d3-v4.1.1.min.js" really is d3 4.1.0. + +A file is also compared with its surrounding whitespace stripped, because vendoring a file through +an editor or a shell redirection commonly appends a trailing newline. Trino's clipboard.min.js is +the published clipboard 2.0.11 plus exactly one such byte. That is the same code and the same +advisories apply, so it counts as a match, and the manifest entry says which kind it was. + +When several releases match, the file was shipped unchanged across them and hashing cannot tell +them apart. Record the lowest one: it is the earliest release the code appeared in, and it keeps +the widest set of advisories applicable, which is the safe direction. Note the ambiguity in the +manifest entry. + +When nothing matches, the library either predates its npm releases or the product modified or +rebuilt it. Fall back to the version the file states and say so in the entry. + +Examples: + identify_js.py inspect . hadoop-hdfs-project/hadoop-hdfs/src/main/webapps + identify_js.py identify webapps/static/d3-v4.1.1.min.js d3 --prefix 4. + +Tarballs are cached, override the location with IDENTIFY_JS_CACHE. +""" + +import argparse +import hashlib +import json +import os +import re +import tarfile +import tempfile +from pathlib import Path +from urllib.error import URLError +from urllib.request import urlopen + +CACHE = Path(os.environ.get("IDENTIFY_JS_CACHE", Path(tempfile.gettempdir()) / "stackable-identify-js-cache")) + +# Version strings are written in every conceivable way, so cast a wide net over the top of the file +# and let the caller judge. Matching on bytes keeps minified files with odd encodings readable. +HINTS = [ + re.compile(rb"@version\s+v?([0-9]+\.[0-9][\w.-]*)"), + re.compile(rb"\bversion\s*[:=]\s*['\"]?v?([0-9]+\.[0-9][\w.-]*)", re.IGNORECASE), + # Banners such as "// https://d3js.org Version 4.1.0." separate with a space. All three + # components are required here, otherwise every "Apache License, Version 2.0" header matches. + re.compile(rb"\bversion\s+v?([0-9]+\.[0-9]+\.[0-9][\w.-]*)", re.IGNORECASE), + re.compile(rb"^/\*!?\s*([A-Za-z][\w.\- ]*?)\s+v?([0-9]+\.[0-9][\w.-]*)", re.MULTILINE), + re.compile(rb"\bv([0-9]+\.[0-9]+\.[0-9][\w.-]*)"), +] + + +def sha256(contents): + return hashlib.sha256(contents).hexdigest() + + +def digests(contents): + """The SHA-256 of the file and of the same file without its surrounding whitespace. The second + one identifies a copy that a vendoring step gave a trailing newline, which happens often enough + that comparing only the first would report the library as modified.""" + return sha256(contents), sha256(contents.strip()) + + +def version_hints(contents): + hints = [] + for pattern in HINTS: + for match in pattern.finditer(contents[:3000]): + # Rstrip because a version at the end of a sentence swallows the full stop. + hint = b" ".join(group for group in match.groups() if group).decode("latin1").rstrip(".-") + if hint not in hints: + hints.append(hint) + return hints + + +def published_versions(package, prefix): + """Every non-prerelease version of a package, ascending. The registry lists them in publication + order, which is not always ascending, and the advice to record the lowest match depends on the + order being right.""" + # The scope separator has to stay encoded, otherwise the registry sees two path segments. + with urlopen(f"https://registry.npmjs.org/{package.replace('/', '%2f')}") as response: + metadata = json.load(response) + + releases = [ + (version, release["dist"]["tarball"]) + for version, release in metadata.get("versions", {}).items() + # A prerelease is never what a product vendored. + if "-" not in version and version.startswith(prefix) and release.get("dist", {}).get("tarball") + ] + return sorted(releases, key=lambda release: [int(part) if part.isdigit() else 0 for part in release[0].split(".")]) + + +def tarball_hashes(package, version, url): + """The SHA-256 of every file in a release and of its stripped contents, keyed by its path inside + the package.""" + archive_path = CACHE / f"{package.replace('/', '_')}-{version}.tgz" + if not archive_path.exists(): + CACHE.mkdir(parents=True, exist_ok=True) + with urlopen(url) as response: + archive_path.write_bytes(response.read()) + + try: + with tarfile.open(archive_path) as archive: + # Every member is below a "package/" directory that is of no interest here. + return { + member.name.split("/", 1)[-1]: digests(archive.extractfile(member).read()) + for member in archive + if member.isfile() + } + except tarfile.TarError: + # A handful of very old releases have broken tarballs, skip them. + return {} + + +def inspect(source_root, directories): + for directory in directories: + for path in sorted((source_root / directory).rglob("*.js")): + contents = path.read_bytes() + hints = " | ".join(version_hints(contents)[:3]) or "-" + print("\t".join([str(path.relative_to(source_root)), sha256(contents), hints])) + + +def identify(target, packages, prefix): + wanted, wanted_stripped = digests(target.read_bytes()) + print(f"{target}\n sha256 {wanted}\n") + + matches = [] + for package in packages: + try: + releases = published_versions(package, prefix) + except URLError as error: + print(f"{package}: {error}") + continue + print(f"{package}: checking {len(releases)} version(s)") + for version, url in releases: + for name, (digest, stripped) in tarball_hashes(package, version, url).items(): + if digest == wanted: + matches.append((package, version, "identical")) + print(f" MATCH {package}@{version} {name}") + elif stripped == wanted_stripped: + matches.append((package, version, "whitespace")) + print(f" MATCH {package}@{version} {name} (differs only in surrounding whitespace)") + + if not matches: + print("\nNo match. Fall back to the version the file states and note that in the manifest.") + return + + if any(kind == "whitespace" for _, _, kind in matches): + print("\nThe code is identical, only the surrounding whitespace differs, so the release applies.") + print("Record it and say in the manifest that the copy carries extra whitespace.") + + if len(matches) > 1: + package, version, _ = matches[0] + print(f"\nThe file is the same in {len(matches)} releases, so record the lowest,") + print(f"{package}@{version}, and note the ambiguity in the manifest.") + + +def main(): + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + commands = parser.add_subparsers(dest="command", required=True) + + inspect_command = commands.add_parser("inspect", help="list the JavaScript files below the given directories") + inspect_command.add_argument("source_root", type=Path) + inspect_command.add_argument("directories", nargs="+") + + identify_command = commands.add_parser("identify", help="find the npm release a file came from") + identify_command.add_argument("file", type=Path) + identify_command.add_argument("packages", nargs="+", help="npm packages the file might come from") + identify_command.add_argument("--prefix", default="", help='only check versions starting with this, e.g. "4."') + + arguments = parser.parse_args() + if arguments.command == "inspect": + inspect(arguments.source_root, arguments.directories) + else: + identify(arguments.file, arguments.packages, arguments.prefix) + + +if __name__ == "__main__": + main() From 0ecf31260a8d34ff0117a297707f1e94db1094df Mon Sep 17 00:00:00 2001 From: dervoeti Date: Mon, 24 Aug 2026 14:53:04 +0000 Subject: [PATCH 05/11] chore: Unify the name of the Node version used to run cdxgen --- boil.toml | 5 +++-- hbase/hbase/Dockerfile | 14 +++++++------- trino/trino/Dockerfile | 23 +++++++++-------------- 3 files changed, 19 insertions(+), 23 deletions(-) diff --git a/boil.toml b/boil.toml index f5ac03bb1..881adcdfc 100644 --- a/boil.toml +++ b/boil.toml @@ -8,10 +8,11 @@ DELETE_CACHES = "true" CDXGEN_SPEC_VERSION = "1.6" # Node version used to run cdxgen in the builders that need it. It is unrelated to any product # and to the Node version that a product uses to build its frontend, so it is configured once -# here instead of per product version. +# here instead of per product version. Products that pin `cdxgen-nodejs-version` in their own +# boil-config.toml override this value. # Find the latest release here: https://github.com/nodejs/node/releases # renovate: datasource=node-version packageName=node -SBOM_NODEJS_VERSION = "24.19.0" +CDXGEN_NODEJS_VERSION = "24.19.0" [metadata] documentation = "https://docs.stackable.tech/home/stable/" diff --git a/hbase/hbase/Dockerfile b/hbase/hbase/Dockerfile index fe8045efa..ffe6ff214 100644 --- a/hbase/hbase/Dockerfile +++ b/hbase/hbase/Dockerfile @@ -11,7 +11,7 @@ ENV HADOOP_VERSION=${HADOOP_HADOOP_VERSION} ARG TARGETARCH ARG TARGETOS ARG STACKABLE_USER_UID -ARG SBOM_NODEJS_VERSION +ARG CDXGEN_NODEJS_VERSION ARG CDXGEN_SPEC_VERSION ARG CDXGEN_VERSION @@ -25,14 +25,14 @@ RUN <= 24, so it gets its own Node installation in /opt/node-sbom and +# cdxgen requires Node >= 24, so it gets its own Node installation in /opt/node-cdxgen and # is invoked with that prepended to PATH. # -fsSL is not needed: the shared /root/.curlrc sets location, fail, silent and show-error. ARCH="${TARGETARCH/amd64/x64}" -mkdir -p /opt/node-sbom -curl "https://repo.stackable.tech/repository/packages/node/node-v${SBOM_NODEJS_VERSION}-linux-${ARCH}.tar.xz" | \ - tar --extract --xz --directory=/opt/node-sbom --strip-components=1 -PATH="/opt/node-sbom/bin:$PATH" npm install --global "@cdxgen/cdxgen@${CDXGEN_VERSION}" +mkdir -p /opt/node-cdxgen +curl "https://repo.stackable.tech/repository/packages/node/node-v${CDXGEN_NODEJS_VERSION}-linux-${ARCH}.tar.xz" | \ + tar --extract --xz --directory=/opt/node-cdxgen --strip-components=1 +PATH="/opt/node-cdxgen/bin:$PATH" npm install --global "@cdxgen/cdxgen@${CDXGEN_VERSION}" microdnf update microdnf install python3 @@ -100,7 +100,7 @@ mv hbase-assembly/target/bom.json /stackable/hbase-${NEW_VERSION}/hbase-${NEW_VE # by the maven-dependency-plugin instead of being declared as project dependencies, so the # CycloneDX Maven plugin does not cover them, see hbase_webapps_deps.py. ( - export PATH="/opt/node-sbom/bin:$PATH" + export PATH="/opt/node-cdxgen/bin:$PATH" WEBAPPS_SBOM_DIR="$(mktemp --directory)" python3 /stackable/hbase_webapps_deps.py . "${ORIGINAL_VERSION}" "${WEBAPPS_SBOM_DIR}/package.json" cd "${WEBAPPS_SBOM_DIR}" diff --git a/trino/trino/Dockerfile b/trino/trino/Dockerfile index 6acf55b15..1e234adc3 100644 --- a/trino/trino/Dockerfile +++ b/trino/trino/Dockerfile @@ -9,7 +9,7 @@ ARG RELEASE_VERSION ARG STACKABLE_USER_UID ARG TRINO_AIRLIFT_VERSION ARG TARGETARCH -ARG SBOM_NODEJS_VERSION +ARG CDXGEN_NODEJS_VERSION ARG CDXGEN_SPEC_VERSION ARG CDXGEN_VERSION @@ -24,13 +24,13 @@ RUN <= 24, which is unrelated to the Node version that the # frontend-maven-plugin downloads for the actual build, so it gets its own Node installation in -# /opt/node-sbom and is invoked with that prepended to PATH. +# /opt/node-cdxgen and is invoked with that prepended to PATH. # -fsSL is not needed: the shared /root/.curlrc sets location, fail, silent and show-error. ARCH="${TARGETARCH/amd64/x64}" -mkdir -p /opt/node-sbom -curl "https://repo.stackable.tech/repository/packages/node/node-v${SBOM_NODEJS_VERSION}-linux-${ARCH}.tar.xz" | \ - tar --extract --xz --directory=/opt/node-sbom --strip-components=1 -PATH="/opt/node-sbom/bin:$PATH" npm install --global "@cdxgen/cdxgen@${CDXGEN_VERSION}" +mkdir -p /opt/node-cdxgen +curl "https://repo.stackable.tech/repository/packages/node/node-v${CDXGEN_NODEJS_VERSION}-linux-${ARCH}.tar.xz" | \ + tar --extract --xz --directory=/opt/node-cdxgen --strip-components=1 +PATH="/opt/node-cdxgen/bin:$PATH" npm install --global "@cdxgen/cdxgen@${CDXGEN_VERSION}" microdnf update microdnf install python3 @@ -105,20 +105,15 @@ mv core/trino-server/target/bom.json /stackable/trino-server-${NEW_VERSION}/trin # so this needs revisiting when such a version is added. The paths below fail the build rather # than silently producing nothing. ( - export PATH="/opt/node-sbom/bin:$PATH" + export PATH="/opt/node-cdxgen/bin:$PATH" WEB_UI="core/trino-web-ui/src/main/resources" # cdxgen resolves the dependency tree from the lockfile, and returns nothing at all when a # project has both a package-lock.json and a yarn.lock, which webapp/src does. Copying just the # manifest and the npm lockfile into a scratch directory avoids that. - # --required-only keeps what the lockfile marks as non-dev. Note that this is wider than what - # ends up in the bundle: upstream declares packages such as happy-dom and js-yaml as runtime - # dependencies even though the bundler drops them, so they are reported here as well. That is - # the same trade-off the other frontend SBOMs make, and over-reporting is preferred over - # missing a component. + # --required-only keeps what the lockfile marks as non-dev. # --no-babel disables the usage analysis, which would otherwise mark every package that is not - # imported directly as optional and thereby drop genuine transitive runtime dependencies. It - # has nothing to look at here anyway, because only the manifest and the lockfile are copied. + # imported directly as optional and thereby drop genuine transitive runtime dependencies. # --project-version is passed because the frontends declare a placeholder version upstream, and # --project-name because cdxgen otherwise names the root component after the scratch directory. for FRONTEND in "webapp/src:trino-web-ui" "webapp-preview:trino-web-ui-preview"; do From 8d864288f7854674cc9d6cb0f4758a14b720be6d Mon Sep 17 00:00:00 2001 From: dervoeti Date: Mon, 24 Aug 2026 15:24:15 +0000 Subject: [PATCH 06/11] fix(hbase): Correct the comment explaining the webjar SBOM --- hbase/hbase/Dockerfile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/hbase/hbase/Dockerfile b/hbase/hbase/Dockerfile index ffe6ff214..8bdc0f3fb 100644 --- a/hbase/hbase/Dockerfile +++ b/hbase/hbase/Dockerfile @@ -104,9 +104,11 @@ mv hbase-assembly/target/bom.json /stackable/hbase-${NEW_VERSION}/hbase-${NEW_VE WEBAPPS_SBOM_DIR="$(mktemp --directory)" python3 /stackable/hbase_webapps_deps.py . "${ORIGINAL_VERSION}" "${WEBAPPS_SBOM_DIR}/package.json" cd "${WEBAPPS_SBOM_DIR}" - # cdxgen needs a lockfile to resolve the dependency tree. The webjars are pre-built browser - # bundles that inline their dependencies, so the transitive packages are shipped as well and - # belong in the SBOM. + # cdxgen reads the components from a lockfile, so one is generated for the package.json above. + # Usually a lockfile also resolves the npm dependencies of the listed packages, while a + # webjar only ever ships the files of the library itself. Such a dependency would therefore + # show up in the SBOM without being shipped. In this case it is not a problem, because the libraries + # HBase unpacks (jquery, moment and bootstrap) have no npm dependencies. npm install --package-lock-only --no-audit --no-fund cdxgen \ --type js \ From b7d1539a4d9f78298f4af2c9eae96d6b4453ea45 Mon Sep 17 00:00:00 2001 From: dervoeti Date: Mon, 24 Aug 2026 15:24:16 +0000 Subject: [PATCH 07/11] fix(hadoop): Name the vendored JavaScript SBOM after its component --- hadoop/hadoop/Dockerfile | 2 +- hadoop/hadoop/stackable/vendored-js/3.3.6.json | 2 +- hadoop/hadoop/stackable/vendored-js/3.4.2.json | 2 +- hadoop/hadoop/stackable/vendored-js/3.4.3.json | 2 +- hadoop/hadoop/stackable/vendored-js/3.5.0.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/hadoop/hadoop/Dockerfile b/hadoop/hadoop/Dockerfile index a336c1858..de701628a 100644 --- a/hadoop/hadoop/Dockerfile +++ b/hadoop/hadoop/Dockerfile @@ -127,7 +127,7 @@ mv hadoop-dist/target/bom.json /stackable/hadoop-${NEW_VERSION}/hadoop-${NEW_VER python3 /build/vendored_js.py bom \ /build/vendored-js.json \ . \ - "/stackable/hadoop-${NEW_VERSION}/hadoop-vendored-js-${NEW_VERSION}.cdx.json" \ + "/stackable/hadoop-${NEW_VERSION}/hadoop-webapps-${NEW_VERSION}.cdx.json" \ "${ORIGINAL_VERSION}" \ "${CDXGEN_SPEC_VERSION}" diff --git a/hadoop/hadoop/stackable/vendored-js/3.3.6.json b/hadoop/hadoop/stackable/vendored-js/3.3.6.json index 09e7c5a72..0e2c034ab 100644 --- a/hadoop/hadoop/stackable/vendored-js/3.3.6.json +++ b/hadoop/hadoop/stackable/vendored-js/3.3.6.json @@ -1,5 +1,5 @@ { - "name": "hadoop-hdfs-webapps", + "name": "hadoop-webapps", "scan-dirs": [ "hadoop-hdfs-project/hadoop-hdfs/src/main/webapps", "hadoop-hdfs-project/hadoop-hdfs-rbf/src/main/webapps", diff --git a/hadoop/hadoop/stackable/vendored-js/3.4.2.json b/hadoop/hadoop/stackable/vendored-js/3.4.2.json index 2ad1dea02..140e9fcfa 100644 --- a/hadoop/hadoop/stackable/vendored-js/3.4.2.json +++ b/hadoop/hadoop/stackable/vendored-js/3.4.2.json @@ -1,5 +1,5 @@ { - "name": "hadoop-hdfs-webapps", + "name": "hadoop-webapps", "scan-dirs": [ "hadoop-hdfs-project/hadoop-hdfs/src/main/webapps", "hadoop-hdfs-project/hadoop-hdfs-rbf/src/main/webapps", diff --git a/hadoop/hadoop/stackable/vendored-js/3.4.3.json b/hadoop/hadoop/stackable/vendored-js/3.4.3.json index 2ad1dea02..140e9fcfa 100644 --- a/hadoop/hadoop/stackable/vendored-js/3.4.3.json +++ b/hadoop/hadoop/stackable/vendored-js/3.4.3.json @@ -1,5 +1,5 @@ { - "name": "hadoop-hdfs-webapps", + "name": "hadoop-webapps", "scan-dirs": [ "hadoop-hdfs-project/hadoop-hdfs/src/main/webapps", "hadoop-hdfs-project/hadoop-hdfs-rbf/src/main/webapps", diff --git a/hadoop/hadoop/stackable/vendored-js/3.5.0.json b/hadoop/hadoop/stackable/vendored-js/3.5.0.json index c95ca12bd..9fc4ede4f 100644 --- a/hadoop/hadoop/stackable/vendored-js/3.5.0.json +++ b/hadoop/hadoop/stackable/vendored-js/3.5.0.json @@ -1,5 +1,5 @@ { - "name": "hadoop-hdfs-webapps", + "name": "hadoop-webapps", "scan-dirs": [ "hadoop-hdfs-project/hadoop-hdfs/src/main/webapps", "hadoop-hdfs-project/hadoop-hdfs-rbf/src/main/webapps", From c6be1e3e52b3566b20eb390c817fc7ce65b48c2a Mon Sep 17 00:00:00 2001 From: dervoeti Date: Mon, 24 Aug 2026 15:24:17 +0000 Subject: [PATCH 08/11] chore: Drop the redundant hashes initialization in vendored_js.py --- shared/sbom/vendored_js.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/shared/sbom/vendored_js.py b/shared/sbom/vendored_js.py index 6b7f9e5e0..065ad8531 100755 --- a/shared/sbom/vendored_js.py +++ b/shared/sbom/vendored_js.py @@ -116,8 +116,6 @@ def add(entry, location, sha256): component["purl"] = purl if entry.get("license"): component["licenses"] = [{"expression": entry["license"]}] - if sha256: - component["hashes"] = [] component["evidence"] = {"occurrences": []} components[key] = component From f52f46836abd198c3d8d03dc2a7810552ce811f6 Mon Sep 17 00:00:00 2001 From: dervoeti Date: Mon, 24 Aug 2026 19:54:47 +0000 Subject: [PATCH 09/11] chore: Format the SBOM helper scripts with ruff --- hbase/hbase/stackable/hbase_webapps_deps.py | 32 ++++++-- shared/sbom/identify_js.py | 83 ++++++++++++++++----- shared/sbom/vendored_js.py | 43 ++++++++--- 3 files changed, 123 insertions(+), 35 deletions(-) diff --git a/hbase/hbase/stackable/hbase_webapps_deps.py b/hbase/hbase/stackable/hbase_webapps_deps.py index ac51bcf04..74e69a0da 100755 --- a/hbase/hbase/stackable/hbase_webapps_deps.py +++ b/hbase/hbase/stackable/hbase_webapps_deps.py @@ -53,11 +53,17 @@ def webjars(server_pom, versions): if fields.get("groupId") != "org.webjars": continue if not fields.get("artifactId") or not fields.get("version"): - raise SystemExit(f" without an artifactId or version: {fields}") + raise SystemExit( + f" without an artifactId or version: {fields}" + ) - version = PROPERTY.sub(lambda match: versions.get(match[1], match[0]), fields["version"]) + version = PROPERTY.sub( + lambda match: versions.get(match[1], match[0]), fields["version"] + ) if "${" in version: - raise SystemExit(f"Cannot resolve the version of {fields['artifactId']} from the root pom: {version}") + raise SystemExit( + f"Cannot resolve the version of {fields['artifactId']} from the root pom: {version}" + ) # bootstrap is unpacked twice, once for its JavaScript and once for its CSS. dependencies[fields["artifactId"]] = version @@ -65,14 +71,21 @@ def webjars(server_pom, versions): # Guard against upstream restructuring the pom, which would otherwise silently produce an SBOM # without any components. if not dependencies: - raise SystemExit("No org.webjars found in hbase-server/pom.xml, did the pom layout change?") + raise SystemExit( + "No org.webjars found in hbase-server/pom.xml, did the pom layout change?" + ) return dependencies def main(): - parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + parser = argparse.ArgumentParser( + description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter + ) parser.add_argument("source_root", type=Path, help="the HBase source tree") - parser.add_argument("version", help="the HBase version, used as the version of the generated package") + parser.add_argument( + "version", + help="the HBase version, used as the version of the generated package", + ) parser.add_argument("output", type=Path, help="the package.json to write") arguments = parser.parse_args() @@ -80,7 +93,12 @@ def main(): ElementTree.parse(arguments.source_root / "hbase-server/pom.xml"), properties(ElementTree.parse(arguments.source_root / "pom.xml")), ) - package = {"name": "hbase-webapps", "version": arguments.version, "private": True, "dependencies": dependencies} + package = { + "name": "hbase-webapps", + "version": arguments.version, + "private": True, + "dependencies": dependencies, + } arguments.output.write_text(json.dumps(package, indent=2) + "\n") summary = ", ".join(f"{name}@{version}" for name, version in dependencies.items()) diff --git a/shared/sbom/identify_js.py b/shared/sbom/identify_js.py index e0d5b87ec..2c9c0c1c3 100755 --- a/shared/sbom/identify_js.py +++ b/shared/sbom/identify_js.py @@ -46,7 +46,11 @@ from urllib.error import URLError from urllib.request import urlopen -CACHE = Path(os.environ.get("IDENTIFY_JS_CACHE", Path(tempfile.gettempdir()) / "stackable-identify-js-cache")) +CACHE = Path( + os.environ.get( + "IDENTIFY_JS_CACHE", Path(tempfile.gettempdir()) / "stackable-identify-js-cache" + ) +) # Version strings are written in every conceivable way, so cast a wide net over the top of the file # and let the caller judge. Matching on bytes keeps minified files with odd encodings readable. @@ -56,7 +60,9 @@ # Banners such as "// https://d3js.org Version 4.1.0." separate with a space. All three # components are required here, otherwise every "Apache License, Version 2.0" header matches. re.compile(rb"\bversion\s+v?([0-9]+\.[0-9]+\.[0-9][\w.-]*)", re.IGNORECASE), - re.compile(rb"^/\*!?\s*([A-Za-z][\w.\- ]*?)\s+v?([0-9]+\.[0-9][\w.-]*)", re.MULTILINE), + re.compile( + rb"^/\*!?\s*([A-Za-z][\w.\- ]*?)\s+v?([0-9]+\.[0-9][\w.-]*)", re.MULTILINE + ), re.compile(rb"\bv([0-9]+\.[0-9]+\.[0-9][\w.-]*)"), ] @@ -77,7 +83,11 @@ def version_hints(contents): for pattern in HINTS: for match in pattern.finditer(contents[:3000]): # Rstrip because a version at the end of a sentence swallows the full stop. - hint = b" ".join(group for group in match.groups() if group).decode("latin1").rstrip(".-") + hint = ( + b" ".join(group for group in match.groups() if group) + .decode("latin1") + .rstrip(".-") + ) if hint not in hints: hints.append(hint) return hints @@ -88,16 +98,25 @@ def published_versions(package, prefix): order, which is not always ascending, and the advice to record the lowest match depends on the order being right.""" # The scope separator has to stay encoded, otherwise the registry sees two path segments. - with urlopen(f"https://registry.npmjs.org/{package.replace('/', '%2f')}") as response: + with urlopen( + f"https://registry.npmjs.org/{package.replace('/', '%2f')}" + ) as response: metadata = json.load(response) releases = [ (version, release["dist"]["tarball"]) for version, release in metadata.get("versions", {}).items() # A prerelease is never what a product vendored. - if "-" not in version and version.startswith(prefix) and release.get("dist", {}).get("tarball") + if "-" not in version + and version.startswith(prefix) + and release.get("dist", {}).get("tarball") ] - return sorted(releases, key=lambda release: [int(part) if part.isdigit() else 0 for part in release[0].split(".")]) + return sorted( + releases, + key=lambda release: [ + int(part) if part.isdigit() else 0 for part in release[0].split(".") + ], + ) def tarball_hashes(package, version, url): @@ -113,7 +132,9 @@ def tarball_hashes(package, version, url): with tarfile.open(archive_path) as archive: # Every member is below a "package/" directory that is of no interest here. return { - member.name.split("/", 1)[-1]: digests(archive.extractfile(member).read()) + member.name.split("/", 1)[-1]: digests( + archive.extractfile(member).read() + ) for member in archive if member.isfile() } @@ -127,7 +148,9 @@ def inspect(source_root, directories): for path in sorted((source_root / directory).rglob("*.js")): contents = path.read_bytes() hints = " | ".join(version_hints(contents)[:3]) or "-" - print("\t".join([str(path.relative_to(source_root)), sha256(contents), hints])) + print( + "\t".join([str(path.relative_to(source_root)), sha256(contents), hints]) + ) def identify(target, packages, prefix): @@ -143,40 +166,62 @@ def identify(target, packages, prefix): continue print(f"{package}: checking {len(releases)} version(s)") for version, url in releases: - for name, (digest, stripped) in tarball_hashes(package, version, url).items(): + for name, (digest, stripped) in tarball_hashes( + package, version, url + ).items(): if digest == wanted: matches.append((package, version, "identical")) print(f" MATCH {package}@{version} {name}") elif stripped == wanted_stripped: matches.append((package, version, "whitespace")) - print(f" MATCH {package}@{version} {name} (differs only in surrounding whitespace)") + print( + f" MATCH {package}@{version} {name} (differs only in surrounding whitespace)" + ) if not matches: - print("\nNo match. Fall back to the version the file states and note that in the manifest.") + print( + "\nNo match. Fall back to the version the file states and note that in the manifest." + ) return if any(kind == "whitespace" for _, _, kind in matches): - print("\nThe code is identical, only the surrounding whitespace differs, so the release applies.") - print("Record it and say in the manifest that the copy carries extra whitespace.") + print( + "\nThe code is identical, only the surrounding whitespace differs, so the release applies." + ) + print( + "Record it and say in the manifest that the copy carries extra whitespace." + ) if len(matches) > 1: package, version, _ = matches[0] - print(f"\nThe file is the same in {len(matches)} releases, so record the lowest,") + print( + f"\nThe file is the same in {len(matches)} releases, so record the lowest," + ) print(f"{package}@{version}, and note the ambiguity in the manifest.") def main(): - parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + parser = argparse.ArgumentParser( + description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter + ) commands = parser.add_subparsers(dest="command", required=True) - inspect_command = commands.add_parser("inspect", help="list the JavaScript files below the given directories") + inspect_command = commands.add_parser( + "inspect", help="list the JavaScript files below the given directories" + ) inspect_command.add_argument("source_root", type=Path) inspect_command.add_argument("directories", nargs="+") - identify_command = commands.add_parser("identify", help="find the npm release a file came from") + identify_command = commands.add_parser( + "identify", help="find the npm release a file came from" + ) identify_command.add_argument("file", type=Path) - identify_command.add_argument("packages", nargs="+", help="npm packages the file might come from") - identify_command.add_argument("--prefix", default="", help='only check versions starting with this, e.g. "4."') + identify_command.add_argument( + "packages", nargs="+", help="npm packages the file might come from" + ) + identify_command.add_argument( + "--prefix", default="", help='only check versions starting with this, e.g. "4."' + ) arguments = parser.parse_args() if arguments.command == "inspect": diff --git a/shared/sbom/vendored_js.py b/shared/sbom/vendored_js.py index 065ad8531..2dc943e63 100755 --- a/shared/sbom/vendored_js.py +++ b/shared/sbom/vendored_js.py @@ -53,7 +53,9 @@ def verify(manifest, source_root): for library in manifest["libraries"]: if library["file"] in listed or library["file"] in own: - problems.append(f"DUPLICATE {library['file']}\n Listed more than once in the manifest.") + problems.append( + f"DUPLICATE {library['file']}\n Listed more than once in the manifest." + ) listed[library["file"]] = library found = scan(manifest, source_root) @@ -76,9 +78,13 @@ def verify(manifest, source_root): for file in listed: if file not in found: - problems.append(f"GONE {file}\n Listed in the manifest but no longer in the source tree.") + problems.append( + f"GONE {file}\n Listed in the manifest but no longer in the source tree." + ) for file in sorted(own - found.keys()): - problems.append(f'GONE {file}\n Listed in "own" but no longer in the source tree.') + problems.append( + f'GONE {file}\n Listed in "own" but no longer in the source tree.' + ) return found, problems @@ -121,7 +127,9 @@ def add(entry, location, sha256): component = components[key] if sha256: - component.setdefault("hashes", []).append({"alg": "SHA-256", "content": sha256}) + component.setdefault("hashes", []).append( + {"alg": "SHA-256", "content": sha256} + ) component["evidence"]["occurrences"].append({"location": location}) for library in manifest["libraries"]: @@ -145,18 +153,32 @@ def add(entry, location, sha256): "name": manifest["name"], "version": component_version, }, - "tools": {"components": [{"type": "application", "name": "vendored_js.py", "group": "tech.stackable"}]}, + "tools": { + "components": [ + { + "type": "application", + "name": "vendored_js.py", + "group": "tech.stackable", + } + ] + }, }, "components": list(components.values()), } def main(): - parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + parser = argparse.ArgumentParser( + description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter + ) commands = parser.add_subparsers(dest="command", required=True) - check_command = commands.add_parser("check", help="report every mismatch between the manifest and the source tree") - bom_command = commands.add_parser("bom", help="write the SBOM the manifest describes") + check_command = commands.add_parser( + "check", help="report every mismatch between the manifest and the source tree" + ) + bom_command = commands.add_parser( + "bom", help="write the SBOM the manifest describes" + ) for command in (check_command, bom_command): command.add_argument("manifest", type=Path) command.add_argument("source_root", type=Path) @@ -170,7 +192,10 @@ def main(): # Never generate an SBOM that we know to be wrong, so this also gates "bom". found, problems = verify(manifest, arguments.source_root) if problems: - print(f"{arguments.manifest} does not match the source tree ({len(problems)} problem(s)):\n", file=sys.stderr) + print( + f"{arguments.manifest} does not match the source tree ({len(problems)} problem(s)):\n", + file=sys.stderr, + ) print("\n".join(problems), file=sys.stderr) raise SystemExit(1) From 937865d6bb7e2437e590edf4976e949f0b0c6db1 Mon Sep 17 00:00:00 2001 From: dervoeti Date: Tue, 25 Aug 2026 19:30:41 +0000 Subject: [PATCH 10/11] fix: Recognise vendored JavaScript that only differs in comments or formatting --- shared/sbom/identify_js.py | 129 +++++++++++++++++++++++++++++-------- 1 file changed, 102 insertions(+), 27 deletions(-) diff --git a/shared/sbom/identify_js.py b/shared/sbom/identify_js.py index 2c9c0c1c3..6c3c92568 100755 --- a/shared/sbom/identify_js.py +++ b/shared/sbom/identify_js.py @@ -15,15 +15,35 @@ them, looking for one that is byte-identical to ours. A match is proof, and it is what established that Hadoop's "d3-v4.1.1.min.js" really is d3 4.1.0. -A file is also compared with its surrounding whitespace stripped, because vendoring a file through -an editor or a shell redirection commonly appends a trailing newline. Trino's clipboard.min.js is -the published clipboard 2.0.11 plus exactly one such byte. That is the same code and the same -advisories apply, so it counts as a match, and the manifest entry says which kind it was. +A file is compared in four ways, because vendoring rarely copies a release byte for byte: + + identical the file as it is. Proof. + whitespace the file with its line endings normalised, its trailing spaces removed and its + surrounding whitespace stripped. Copying through an editor or a shell redirection + commonly appends a trailing newline or strips trailing spaces. Trino's + clipboard.min.js is the published clipboard 2.0.11 plus one newline, and Hadoop's + d3.v3.js is the published d3 3.2.7 with one trailing space gone. + comments the same, and additionally without the comment banner at the top of the file and + without the source-map link at the bottom. Vendoring often drops or rewrites the + copyright header, or adds one where the release has none, which is the other half + of what makes d3.v3.js look modified. Dropping the source-map link is just as + common, because the .map file is not vendored along with it. + formatting the same, and with every run of whitespace collapsed, which makes the comparison + blind to how the file is indented. Trino's jquery-3.7.1.js is the published release + re-indented with spaces instead of tabs. This is the weakest of the four: a + difference inside a multi-line string literal would be collapsed away with it, so + it says the code is the same up to formatting rather than proving it byte for byte. + +All four mean the same code, so the same advisories apply, and all four count as a match. The +manifest entry says which kind it was. Only the banner and the source-map link are ignored, never +a comment inside the code, so two releases that differ in the code itself can still be told apart. When several releases match, the file was shipped unchanged across them and hashing cannot tell them apart. Record the lowest one: it is the earliest release the code appeared in, and it keeps the widest set of advisories applicable, which is the safe direction. Note the ambiguity in the -manifest entry. +manifest entry. Evidence outside the code beats that rule, because it names one release instead of +a range: Spark's d3-flamegraph.min.js is identical in 4.1.2 and 4.1.3, and its banner is the +jsDelivr URL it was downloaded from, so 4.1.3 is recorded. When nothing matches, the library either predates its npm releases or the product modified or rebuilt it. Fall back to the version the file states and say so in the entry. @@ -67,15 +87,57 @@ ] +# The digest of an empty file, which is what a file that holds nothing but a comment strips down to. +EMPTY = hashlib.sha256(b"").hexdigest() + + def sha256(contents): return hashlib.sha256(contents).hexdigest() +def normalize_whitespace(contents): + """The file with its line endings normalised, its trailing spaces removed and its surrounding + whitespace stripped. None of that changes what the code does, and all of it happens to a file + on the way into a source tree.""" + lines = contents.replace(b"\r\n", b"\n").split(b"\n") + return b"\n".join(line.rstrip() for line in lines).strip() + + +def strip_comments(contents): + """The file without the comment banner at the top of it, without the source-map link at the + bottom, and without its surrounding whitespace. Only comments outside the code are removed, so + the code itself is compared in full.""" + rest = contents.strip() + if rest.rsplit(b"\n", 1)[-1].startswith( + (b"//# sourceMappingURL=", b"//@ sourceMappingURL=") + ): + rest = rest.rsplit(b"\n", 1)[0].strip() + while True: + if rest.startswith(b"/*"): + end = rest.find(b"*/") + if end < 0: + break + rest = rest[end + 2 :].strip() + elif rest.startswith(b"//"): + end = rest.find(b"\n") + if end < 0: + break + rest = rest[end + 1 :].strip() + else: + break + return rest + + def digests(contents): - """The SHA-256 of the file and of the same file without its surrounding whitespace. The second - one identifies a copy that a vendoring step gave a trailing newline, which happens often enough - that comparing only the first would report the library as modified.""" - return sha256(contents), sha256(contents.strip()) + """The SHA-256 of the file and of its three normalised forms, keyed by the kind of match each + one is evidence for. Comparing only the first would report a copy that merely lost a trailing + space or a copyright header as a modified library.""" + return { + "identical": sha256(contents), + "whitespace": sha256(normalize_whitespace(contents)), + "comments": sha256(normalize_whitespace(strip_comments(contents))), + "formatting": sha256(re.sub(rb"\s+", b" ", strip_comments(contents)).strip()), + } def version_hints(contents): @@ -120,8 +182,7 @@ def published_versions(package, prefix): def tarball_hashes(package, version, url): - """The SHA-256 of every file in a release and of its stripped contents, keyed by its path inside - the package.""" + """The digests of every file in a release, keyed by its path inside the package.""" archive_path = CACHE / f"{package.replace('/', '_')}-{version}.tgz" if not archive_path.exists(): CACHE.mkdir(parents=True, exist_ok=True) @@ -153,9 +214,28 @@ def inspect(source_root, directories): ) +# What a match of each kind means, and how the manifest entry should describe it. Ordered from the +# strongest evidence to the weakest, and that is also the order they are reported in. +KINDS = { + "identical": "byte-identical", + "whitespace": "differs only in whitespace", + "comments": "differs only in whitespace and in the banner or the source-map link", + "formatting": "differs only in formatting, the banner or the source-map link", +} + + +def match_kind(wanted, published): + """How a published file matches ours, or None if it does not. A file that is nothing but a + comment is empty once the banner is gone, which would match anything, so that is not a match.""" + for kind in KINDS: + if wanted[kind] == published[kind] and published[kind] != EMPTY: + return kind + return None + + def identify(target, packages, prefix): - wanted, wanted_stripped = digests(target.read_bytes()) - print(f"{target}\n sha256 {wanted}\n") + wanted = digests(target.read_bytes()) + print(f"{target}\n sha256 {wanted['identical']}\n") matches = [] for package in packages: @@ -166,17 +246,11 @@ def identify(target, packages, prefix): continue print(f"{package}: checking {len(releases)} version(s)") for version, url in releases: - for name, (digest, stripped) in tarball_hashes( - package, version, url - ).items(): - if digest == wanted: - matches.append((package, version, "identical")) - print(f" MATCH {package}@{version} {name}") - elif stripped == wanted_stripped: - matches.append((package, version, "whitespace")) - print( - f" MATCH {package}@{version} {name} (differs only in surrounding whitespace)" - ) + for name, published in tarball_hashes(package, version, url).items(): + kind = match_kind(wanted, published) + if kind: + matches.append((package, version, kind)) + print(f" MATCH {package}@{version} {name} ({KINDS[kind]})") if not matches: print( @@ -184,12 +258,13 @@ def identify(target, packages, prefix): ) return - if any(kind == "whitespace" for _, _, kind in matches): + kinds = {kind for _, _, kind in matches} + if kinds != {"identical"}: print( - "\nThe code is identical, only the surrounding whitespace differs, so the release applies." + "\nThe code is identical, so the release applies. Record it and say in the manifest" ) print( - "Record it and say in the manifest that the copy carries extra whitespace." + f"what differs: {', '.join(KINDS[kind] for kind in KINDS if kind in kinds)}." ) if len(matches) > 1: From 8c40af9d65bfcea9ebdf8035f29ed87873aabdd0 Mon Sep 17 00:00:00 2001 From: dervoeti Date: Tue, 25 Aug 2026 19:30:42 +0000 Subject: [PATCH 11/11] fix(hadoop, spark, trino): Correct the notes in the vendored JavaScript manifests --- .../hadoop/stackable/vendored-js/3.3.6.json | 19 +++++++++++-------- .../hadoop/stackable/vendored-js/3.4.2.json | 19 +++++++++++-------- .../hadoop/stackable/vendored-js/3.4.3.json | 19 +++++++++++-------- .../hadoop/stackable/vendored-js/3.5.0.json | 19 +++++++++++-------- spark-k8s/stackable/vendored-js/3.5.8.json | 8 ++++---- spark-k8s/stackable/vendored-js/4.1.1.json | 15 ++++++++------- spark-k8s/stackable/vendored-js/4.1.2.json | 15 ++++++++------- trino/trino/stackable/vendored-js/477.json | 13 ++++++++----- trino/trino/stackable/vendored-js/479.json | 13 ++++++++----- trino/trino/stackable/vendored-js/481.json | 13 ++++++++----- 10 files changed, 88 insertions(+), 65 deletions(-) diff --git a/hadoop/hadoop/stackable/vendored-js/3.3.6.json b/hadoop/hadoop/stackable/vendored-js/3.3.6.json index 0e2c034ab..696ef7287 100644 --- a/hadoop/hadoop/stackable/vendored-js/3.3.6.json +++ b/hadoop/hadoop/stackable/vendored-js/3.3.6.json @@ -22,14 +22,14 @@ "file": "hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/static/bootstrap-3.4.1/js/npm.js", "purl": "pkg:npm/bootstrap@3.4.1", "license": "MIT", - "note": "Part of the Bootstrap distribution, it only requires the other Bootstrap files.", + "note": "Part of the Bootstrap distribution, it only requires the other Bootstrap files. It is unchanged from 3.3.0 to 3.4.1, so hashing cannot tell those releases apart and the version comes from the directory it sits in.", "sha256": "c7aa82a1aa7d45224a38d926d2adaff7fe4aef5bcdafa2a47bdac057f4422c2d" }, { "file": "hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/static/bootstrap-3.4.1/js/bootstrap-editable.min.js", "purl": "pkg:npm/x-editable@1.5.0", "license": "MIT", - "note": "Version taken from the file header. 1.5.0 was never published to npm, but advisories are matched against version ranges, so the npm purl is still the useful identity.", + "note": "Version taken from the file header. x-editable has a single npm release, 1.5.1, so 1.5.0 was never published there, but advisories are matched against version ranges, so the npm purl is still the useful identity.", "sha256": "8e4041866b100f3afe72c70c1dd5d6405729ba0e327f07e14c4023d9d657753c" }, { @@ -50,20 +50,22 @@ "file": "hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/static/dust-helpers-1.1.1.min.js", "purl": "pkg:npm/dustjs-helpers@1.1.1", "license": "MIT", - "note": "Version taken from the file name. It matches no published dustjs-helpers tarball, so it is either modified or predates the npm releases.", + "note": "Version taken from the file name. The 1.1.1 release ships only the unminified dist/dust-helpers-1.1.1.js, so there is no published minified file to compare this copy with.", "sha256": "ff65ffc9e919f9ab7922d82db9ea9d7840a7543001ccba2a8c4f11195a08a7f6" }, { "file": "hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/static/jquery-3.6.0.min.js", "purl": "pkg:npm/jquery@3.6.0", "license": "MIT", - "sha256": "80f04717f32ea0320c5e8618fbacedd1fee3a8775ad8292140a6113551d4b5b0" + "sha256": "80f04717f32ea0320c5e8618fbacedd1fee3a8775ad8292140a6113551d4b5b0", + "note": "The file is dist/jquery.min.js of the 3.6.0 release minus its trailing newline, so the code is that release unchanged." }, { "file": "hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/static/jquery.dataTables.min.js", "purl": "pkg:npm/datatables.net@1.10.7", "license": "MIT", - "sha256": "7a101ba1668e04321dd15acb478546de82bea05c8887749c8532427577e5df7a" + "sha256": "7a101ba1668e04321dd15acb478546de82bea05c8887749c8532427577e5df7a", + "note": "Version taken from the file header. datatables.net on npm starts at 1.10.9, so there is no 1.10.7 tarball to compare with." }, { "file": "hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/static/dataTables.bootstrap.js", @@ -76,7 +78,8 @@ "file": "hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/static/moment.min.js", "purl": "pkg:npm/moment@2.22.1", "license": "MIT", - "sha256": "853e11d64268a12da71524bc7e1bb1f960243f3eee045f7839f796f1d23670e0" + "sha256": "853e11d64268a12da71524bc7e1bb1f960243f3eee045f7839f796f1d23670e0", + "note": "The file is min/moment.min.js of the 2.22.1 release with a \"//! moment.js\" banner added, so the code is that release unchanged." }, { "file": "hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/static/json-bignum.js", @@ -95,7 +98,7 @@ "file": "hadoop-tools/hadoop-sls/src/main/html/js/thirdparty/d3.v3.js", "purl": "pkg:npm/d3@3.2.7", "license": "BSD-3-Clause", - "note": "Version taken from the file header. It matches no published d3 tarball, so it is probably a custom build.", + "note": "The file is d3.js of the 3.2.7 release with a BSD-3-Clause banner added and one trailing space removed, so the code is that release unchanged.", "sha256": "2085bd03d15690b448e136c590d6982ed7397e730407fed1d414304c81761315" }, { @@ -103,7 +106,7 @@ "purl": null, "name": "bootstrap", "license": "Apache-2.0", - "note": "Bootstrap 2.x, which was Apache-2.0 licensed. The file carries no version and matches no published tarball, so no version is recorded. Its header says \"Copyright 2012 Twitter, Inc.\".", + "note": "Bootstrap 2.x, which was Apache-2.0 licensed. The file carries no version and npm has no 2.x release at all, jumping from 0.0.2 to 3.1.1, so no version is recorded. Its header says \"Copyright 2012 Twitter, Inc.\".", "sha256": "eabb9d96942adad6cbfbf964a4fe53c5bc585dd330cb829665ee15bbf2ca4f1d" } ], diff --git a/hadoop/hadoop/stackable/vendored-js/3.4.2.json b/hadoop/hadoop/stackable/vendored-js/3.4.2.json index 140e9fcfa..f8f762399 100644 --- a/hadoop/hadoop/stackable/vendored-js/3.4.2.json +++ b/hadoop/hadoop/stackable/vendored-js/3.4.2.json @@ -22,14 +22,14 @@ "file": "hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/static/bootstrap-3.4.1/js/npm.js", "purl": "pkg:npm/bootstrap@3.4.1", "license": "MIT", - "note": "Part of the Bootstrap distribution, it only requires the other Bootstrap files.", + "note": "Part of the Bootstrap distribution, it only requires the other Bootstrap files. It is unchanged from 3.3.0 to 3.4.1, so hashing cannot tell those releases apart and the version comes from the directory it sits in.", "sha256": "c7aa82a1aa7d45224a38d926d2adaff7fe4aef5bcdafa2a47bdac057f4422c2d" }, { "file": "hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/static/bootstrap-3.4.1/js/bootstrap-editable.min.js", "purl": "pkg:npm/x-editable@1.5.0", "license": "MIT", - "note": "Version taken from the file header. 1.5.0 was never published to npm, but advisories are matched against version ranges, so the npm purl is still the useful identity.", + "note": "Version taken from the file header. x-editable has a single npm release, 1.5.1, so 1.5.0 was never published there, but advisories are matched against version ranges, so the npm purl is still the useful identity.", "sha256": "8e4041866b100f3afe72c70c1dd5d6405729ba0e327f07e14c4023d9d657753c" }, { @@ -50,20 +50,22 @@ "file": "hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/static/dust-helpers-1.1.1.min.js", "purl": "pkg:npm/dustjs-helpers@1.1.1", "license": "MIT", - "note": "Version taken from the file name. It matches no published dustjs-helpers tarball, so it is either modified or predates the npm releases.", + "note": "Version taken from the file name. The 1.1.1 release ships only the unminified dist/dust-helpers-1.1.1.js, so there is no published minified file to compare this copy with.", "sha256": "ff65ffc9e919f9ab7922d82db9ea9d7840a7543001ccba2a8c4f11195a08a7f6" }, { "file": "hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/static/jquery-3.6.0.min.js", "purl": "pkg:npm/jquery@3.6.0", "license": "MIT", - "sha256": "80f04717f32ea0320c5e8618fbacedd1fee3a8775ad8292140a6113551d4b5b0" + "sha256": "80f04717f32ea0320c5e8618fbacedd1fee3a8775ad8292140a6113551d4b5b0", + "note": "The file is dist/jquery.min.js of the 3.6.0 release minus its trailing newline, so the code is that release unchanged." }, { "file": "hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/static/jquery.dataTables.min.js", "purl": "pkg:npm/datatables.net@1.11.5", "license": "MIT", - "sha256": "2e288f534e4f2a5b1f4d17cc62149068ab6ef1a8dac45832b387e000719f28a3" + "sha256": "2e288f534e4f2a5b1f4d17cc62149068ab6ef1a8dac45832b387e000719f28a3", + "note": "Version taken from the file header. The npm release ships a jquery.dataTables.min.js of the same version, but compiled by Closure with different symbol names, so the two are not byte-identical." }, { "file": "hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/static/dataTables.bootstrap.js", @@ -76,7 +78,8 @@ "file": "hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/static/moment.min.js", "purl": "pkg:npm/moment@2.29.4", "license": "MIT", - "sha256": "9aec203698e15111ceda22d99911f578505e16d9dd92f17be8d31ca4e29f6990" + "sha256": "9aec203698e15111ceda22d99911f578505e16d9dd92f17be8d31ca4e29f6990", + "note": "Version taken from the file header. The npm release ships a min/moment.min.js of the same version, but minified with different variable names, so the two are not byte-identical." }, { "file": "hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/static/json-bignum.js", @@ -95,7 +98,7 @@ "file": "hadoop-tools/hadoop-sls/src/main/html/js/thirdparty/d3.v3.js", "purl": "pkg:npm/d3@3.2.7", "license": "BSD-3-Clause", - "note": "Version taken from the file header. It matches no published d3 tarball, so it is probably a custom build.", + "note": "The file is d3.js of the 3.2.7 release with a BSD-3-Clause banner added and one trailing space removed, so the code is that release unchanged.", "sha256": "2085bd03d15690b448e136c590d6982ed7397e730407fed1d414304c81761315" }, { @@ -103,7 +106,7 @@ "purl": null, "name": "bootstrap", "license": "Apache-2.0", - "note": "Bootstrap 2.x, which was Apache-2.0 licensed. The file carries no version and matches no published tarball, so no version is recorded. Its header says \"Copyright 2012 Twitter, Inc.\".", + "note": "Bootstrap 2.x, which was Apache-2.0 licensed. The file carries no version and npm has no 2.x release at all, jumping from 0.0.2 to 3.1.1, so no version is recorded. Its header says \"Copyright 2012 Twitter, Inc.\".", "sha256": "eabb9d96942adad6cbfbf964a4fe53c5bc585dd330cb829665ee15bbf2ca4f1d" } ], diff --git a/hadoop/hadoop/stackable/vendored-js/3.4.3.json b/hadoop/hadoop/stackable/vendored-js/3.4.3.json index 140e9fcfa..f8f762399 100644 --- a/hadoop/hadoop/stackable/vendored-js/3.4.3.json +++ b/hadoop/hadoop/stackable/vendored-js/3.4.3.json @@ -22,14 +22,14 @@ "file": "hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/static/bootstrap-3.4.1/js/npm.js", "purl": "pkg:npm/bootstrap@3.4.1", "license": "MIT", - "note": "Part of the Bootstrap distribution, it only requires the other Bootstrap files.", + "note": "Part of the Bootstrap distribution, it only requires the other Bootstrap files. It is unchanged from 3.3.0 to 3.4.1, so hashing cannot tell those releases apart and the version comes from the directory it sits in.", "sha256": "c7aa82a1aa7d45224a38d926d2adaff7fe4aef5bcdafa2a47bdac057f4422c2d" }, { "file": "hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/static/bootstrap-3.4.1/js/bootstrap-editable.min.js", "purl": "pkg:npm/x-editable@1.5.0", "license": "MIT", - "note": "Version taken from the file header. 1.5.0 was never published to npm, but advisories are matched against version ranges, so the npm purl is still the useful identity.", + "note": "Version taken from the file header. x-editable has a single npm release, 1.5.1, so 1.5.0 was never published there, but advisories are matched against version ranges, so the npm purl is still the useful identity.", "sha256": "8e4041866b100f3afe72c70c1dd5d6405729ba0e327f07e14c4023d9d657753c" }, { @@ -50,20 +50,22 @@ "file": "hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/static/dust-helpers-1.1.1.min.js", "purl": "pkg:npm/dustjs-helpers@1.1.1", "license": "MIT", - "note": "Version taken from the file name. It matches no published dustjs-helpers tarball, so it is either modified or predates the npm releases.", + "note": "Version taken from the file name. The 1.1.1 release ships only the unminified dist/dust-helpers-1.1.1.js, so there is no published minified file to compare this copy with.", "sha256": "ff65ffc9e919f9ab7922d82db9ea9d7840a7543001ccba2a8c4f11195a08a7f6" }, { "file": "hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/static/jquery-3.6.0.min.js", "purl": "pkg:npm/jquery@3.6.0", "license": "MIT", - "sha256": "80f04717f32ea0320c5e8618fbacedd1fee3a8775ad8292140a6113551d4b5b0" + "sha256": "80f04717f32ea0320c5e8618fbacedd1fee3a8775ad8292140a6113551d4b5b0", + "note": "The file is dist/jquery.min.js of the 3.6.0 release minus its trailing newline, so the code is that release unchanged." }, { "file": "hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/static/jquery.dataTables.min.js", "purl": "pkg:npm/datatables.net@1.11.5", "license": "MIT", - "sha256": "2e288f534e4f2a5b1f4d17cc62149068ab6ef1a8dac45832b387e000719f28a3" + "sha256": "2e288f534e4f2a5b1f4d17cc62149068ab6ef1a8dac45832b387e000719f28a3", + "note": "Version taken from the file header. The npm release ships a jquery.dataTables.min.js of the same version, but compiled by Closure with different symbol names, so the two are not byte-identical." }, { "file": "hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/static/dataTables.bootstrap.js", @@ -76,7 +78,8 @@ "file": "hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/static/moment.min.js", "purl": "pkg:npm/moment@2.29.4", "license": "MIT", - "sha256": "9aec203698e15111ceda22d99911f578505e16d9dd92f17be8d31ca4e29f6990" + "sha256": "9aec203698e15111ceda22d99911f578505e16d9dd92f17be8d31ca4e29f6990", + "note": "Version taken from the file header. The npm release ships a min/moment.min.js of the same version, but minified with different variable names, so the two are not byte-identical." }, { "file": "hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/static/json-bignum.js", @@ -95,7 +98,7 @@ "file": "hadoop-tools/hadoop-sls/src/main/html/js/thirdparty/d3.v3.js", "purl": "pkg:npm/d3@3.2.7", "license": "BSD-3-Clause", - "note": "Version taken from the file header. It matches no published d3 tarball, so it is probably a custom build.", + "note": "The file is d3.js of the 3.2.7 release with a BSD-3-Clause banner added and one trailing space removed, so the code is that release unchanged.", "sha256": "2085bd03d15690b448e136c590d6982ed7397e730407fed1d414304c81761315" }, { @@ -103,7 +106,7 @@ "purl": null, "name": "bootstrap", "license": "Apache-2.0", - "note": "Bootstrap 2.x, which was Apache-2.0 licensed. The file carries no version and matches no published tarball, so no version is recorded. Its header says \"Copyright 2012 Twitter, Inc.\".", + "note": "Bootstrap 2.x, which was Apache-2.0 licensed. The file carries no version and npm has no 2.x release at all, jumping from 0.0.2 to 3.1.1, so no version is recorded. Its header says \"Copyright 2012 Twitter, Inc.\".", "sha256": "eabb9d96942adad6cbfbf964a4fe53c5bc585dd330cb829665ee15bbf2ca4f1d" } ], diff --git a/hadoop/hadoop/stackable/vendored-js/3.5.0.json b/hadoop/hadoop/stackable/vendored-js/3.5.0.json index 9fc4ede4f..a7b56a7bc 100644 --- a/hadoop/hadoop/stackable/vendored-js/3.5.0.json +++ b/hadoop/hadoop/stackable/vendored-js/3.5.0.json @@ -22,14 +22,14 @@ "file": "hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/static/bootstrap-3.4.1/js/npm.js", "purl": "pkg:npm/bootstrap@3.4.1", "license": "MIT", - "note": "Part of the Bootstrap distribution, it only requires the other Bootstrap files.", + "note": "Part of the Bootstrap distribution, it only requires the other Bootstrap files. It is unchanged from 3.3.0 to 3.4.1, so hashing cannot tell those releases apart and the version comes from the directory it sits in.", "sha256": "c7aa82a1aa7d45224a38d926d2adaff7fe4aef5bcdafa2a47bdac057f4422c2d" }, { "file": "hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/static/bootstrap-3.4.1/js/bootstrap-editable.min.js", "purl": "pkg:npm/x-editable@1.5.0", "license": "MIT", - "note": "Version taken from the file header. 1.5.0 was never published to npm, but advisories are matched against version ranges, so the npm purl is still the useful identity.", + "note": "Version taken from the file header. x-editable has a single npm release, 1.5.1, so 1.5.0 was never published there, but advisories are matched against version ranges, so the npm purl is still the useful identity.", "sha256": "8e4041866b100f3afe72c70c1dd5d6405729ba0e327f07e14c4023d9d657753c" }, { @@ -50,20 +50,22 @@ "file": "hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/static/dust-helpers-1.1.1.min.js", "purl": "pkg:npm/dustjs-helpers@1.1.1", "license": "MIT", - "note": "Version taken from the file name. It matches no published dustjs-helpers tarball, so it is either modified or predates the npm releases.", + "note": "Version taken from the file name. The 1.1.1 release ships only the unminified dist/dust-helpers-1.1.1.js, so there is no published minified file to compare this copy with.", "sha256": "ff65ffc9e919f9ab7922d82db9ea9d7840a7543001ccba2a8c4f11195a08a7f6" }, { "file": "hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/static/jquery-3.6.0.min.js", "purl": "pkg:npm/jquery@3.6.0", "license": "MIT", - "sha256": "80f04717f32ea0320c5e8618fbacedd1fee3a8775ad8292140a6113551d4b5b0" + "sha256": "80f04717f32ea0320c5e8618fbacedd1fee3a8775ad8292140a6113551d4b5b0", + "note": "The file is dist/jquery.min.js of the 3.6.0 release minus its trailing newline, so the code is that release unchanged." }, { "file": "hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/static/jquery.dataTables.min.js", "purl": "pkg:npm/datatables.net@1.11.5", "license": "MIT", - "sha256": "2e288f534e4f2a5b1f4d17cc62149068ab6ef1a8dac45832b387e000719f28a3" + "sha256": "2e288f534e4f2a5b1f4d17cc62149068ab6ef1a8dac45832b387e000719f28a3", + "note": "Version taken from the file header. The npm release ships a jquery.dataTables.min.js of the same version, but compiled by Closure with different symbol names, so the two are not byte-identical." }, { "file": "hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/static/dataTables.bootstrap.js", @@ -76,7 +78,8 @@ "file": "hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/static/moment.min.js", "purl": "pkg:npm/moment@2.29.4", "license": "MIT", - "sha256": "9aec203698e15111ceda22d99911f578505e16d9dd92f17be8d31ca4e29f6990" + "sha256": "9aec203698e15111ceda22d99911f578505e16d9dd92f17be8d31ca4e29f6990", + "note": "Version taken from the file header. The npm release ships a min/moment.min.js of the same version, but minified with different variable names, so the two are not byte-identical." }, { "file": "hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/static/json-bignum.js", @@ -95,7 +98,7 @@ "file": "hadoop-tools/hadoop-sls/src/main/html/js/thirdparty/d3.v3.js", "purl": "pkg:npm/d3@3.2.7", "license": "BSD-3-Clause", - "note": "Version taken from the file header. It matches no published d3 tarball, so it is probably a custom build.", + "note": "The file is d3.js of the 3.2.7 release with a BSD-3-Clause banner added and one trailing space removed, so the code is that release unchanged.", "sha256": "2085bd03d15690b448e136c590d6982ed7397e730407fed1d414304c81761315" }, { @@ -103,7 +106,7 @@ "purl": null, "name": "bootstrap", "license": "Apache-2.0", - "note": "Bootstrap 2.x, which was Apache-2.0 licensed. The file carries no version and matches no published tarball, so no version is recorded. Its header says \"Copyright 2012 Twitter, Inc.\".", + "note": "Bootstrap 2.x, which was Apache-2.0 licensed. The file carries no version and npm has no 2.x release at all, jumping from 0.0.2 to 3.1.1, so no version is recorded. Its header says \"Copyright 2012 Twitter, Inc.\".", "sha256": "eabb9d96942adad6cbfbf964a4fe53c5bc585dd330cb829665ee15bbf2ca4f1d" } ], diff --git a/spark-k8s/stackable/vendored-js/3.5.8.json b/spark-k8s/stackable/vendored-js/3.5.8.json index 628081d7f..1e1cb2925 100644 --- a/spark-k8s/stackable/vendored-js/3.5.8.json +++ b/spark-k8s/stackable/vendored-js/3.5.8.json @@ -29,7 +29,7 @@ "file": "core/src/main/resources/org/apache/spark/ui/static/jquery.mustache.js", "purl": "pkg:npm/mustache@3.0.1", "license": "MIT", - "note": "Despite the file name this is mustache.js itself, not a jQuery plugin. Version taken from the file header. It matches no published tarball, so the copy shipped here is built differently or modified.", + "note": "Despite the file name this is mustache.js itself, not a jQuery plugin. The file is mustache.js of the 3.0.1 release minus its trailing newline, so the code is that release unchanged.", "sha256": "520f7bf7d54c8dde783aaf6f0dac66c9edc55da01716ccabca6888686822e7d6" }, { @@ -60,7 +60,7 @@ "file": "core/src/main/resources/org/apache/spark/ui/static/d3.min.js", "purl": "pkg:npm/d3@3.5.5", "license": "BSD-3-Clause", - "note": "Version taken from the file header. The 3.5.5 release declares no license field, its bundled LICENSE file is BSD-3-Clause. It matches no published tarball, so the copy shipped here is built differently or modified.", + "note": "The 3.5.5 release declares no license field, its bundled LICENSE file is BSD-3-Clause. The file is d3.min.js of that release with a \"/*v3.5.5*/\" banner added and a trailing newline appended, so the code is that release unchanged.", "sha256": "3d4c7c277efd3bb019ed0aba5d2dfbe575ded9b9055b842997774bee02f2b76a" }, { @@ -74,14 +74,14 @@ "file": "core/src/main/resources/org/apache/spark/ui/static/graphlib-dot.min.js", "purl": "pkg:npm/graphlib-dot@0.5.2", "license": "MIT", - "note": "Version taken from the file header. It matches no published tarball, so the copy shipped here is built differently or modified.", + "note": "Version taken from the file header. The npm release ships only the CommonJS sources under lib/, not the browser bundle vendored here, so there is no published file to compare it with.", "sha256": "668584b1ed5fe082dc65c895d7cf4b4b3f0868758b1bdbaf056905418594a556" }, { "file": "core/src/main/resources/org/apache/spark/ui/static/jquery.dataTables.1.13.5.min.js", "purl": "pkg:npm/datatables.net@1.13.5", "license": "MIT", - "note": "Version taken from the file header. It matches no published tarball, so the copy shipped here is built differently or modified.", + "note": "Version taken from the file header, which the npm build carries as well. The npm release ships a jquery.dataTables.min.js of the same version, but minified with different settings (extra parentheses, assignments ordered differently), so the two are not byte-identical.", "sha256": "4a20199d45c7b3b9180461baa8f93a383e0438ac921a8bbcef0c3ab5c986c1c3" }, { diff --git a/spark-k8s/stackable/vendored-js/4.1.1.json b/spark-k8s/stackable/vendored-js/4.1.1.json index 4d08c9c41..0b6113dd7 100644 --- a/spark-k8s/stackable/vendored-js/4.1.1.json +++ b/spark-k8s/stackable/vendored-js/4.1.1.json @@ -8,7 +8,8 @@ "file": "core/src/main/resources/org/apache/spark/ui/static/bootstrap.bundle.min.js", "purl": "pkg:npm/bootstrap@4.4.1", "license": "MIT", - "sha256": "4d371899aba195b1f0cba3a70de300fb5b327a322cfbe3a30d77af8456d8494e" + "sha256": "4d371899aba195b1f0cba3a70de300fb5b327a322cfbe3a30d77af8456d8494e", + "note": "The file is dist/js/bootstrap.bundle.min.js of the 4.4.1 release without the trailing source-map link, so the code is that release unchanged." }, { "file": "core/src/main/resources/org/apache/spark/ui/static/dataTables.rowsGroup.js", @@ -29,7 +30,7 @@ "file": "core/src/main/resources/org/apache/spark/ui/static/jquery.mustache.js", "purl": "pkg:npm/mustache@3.0.1", "license": "MIT", - "note": "Despite the file name this is mustache.js itself, not a jQuery plugin. Version taken from the file header. It matches no published tarball, so the copy shipped here is built differently or modified.", + "note": "Despite the file name this is mustache.js itself, not a jQuery plugin. The file is mustache.js of the 3.0.1 release minus its trailing newline, so the code is that release unchanged.", "sha256": "520f7bf7d54c8dde783aaf6f0dac66c9edc55da01716ccabca6888686822e7d6" }, { @@ -66,21 +67,21 @@ "file": "core/src/main/resources/org/apache/spark/ui/static/d3-flamegraph.min.js", "purl": "pkg:npm/d3-flame-graph@4.1.3", "license": "Apache-2.0", - "note": "Version taken from the file header, which is the jsDelivr URL it was downloaded from.", + "note": "Version taken from the file header, which is the jsDelivr URL it was downloaded from. Ignoring that banner the file is identical in 4.1.2 and 4.1.3, so the banner is the only thing that dates it.", "sha256": "10c2ac45bb9ad73e12fdebf434fbb29aa05b85b7bab660c2751e885c51bd5356" }, { "file": "core/src/main/resources/org/apache/spark/ui/static/dagre-d3.min.js", "purl": "pkg:npm/dagre-d3@0.6.4", "license": "MIT", - "note": "Version taken from the file header. It matches no published tarball, so the copy shipped here is built differently or modified.", + "note": "The file is dist/dagre-d3.min.js of the 0.6.4 release with a banner added that names the Git tag it was taken from, so the code is that release unchanged.", "sha256": "b1e946243fc2aa7ba500f7b8fd68c6c8a6fcbbb0329e00389b6a05e20e39130e" }, { "file": "core/src/main/resources/org/apache/spark/ui/static/graphlib-dot.min.js", "purl": "pkg:npm/graphlib-dot@1.0.2", "license": "MIT", - "note": "Version taken from the file header. It matches no published tarball, so the copy shipped here is built differently or modified.", + "note": "The banner names https://github.com/dagrejs/graphlib-dot/blob/v1.0.2/dist/graphlib-dot.min.js. npm stops at 0.6.4, so 1.0.2 exists only as a Git tag and there is no published tarball to compare with.", "sha256": "ae68701d5a547f47eb65ea79a4e2f3179d23c1b8280b2856e16e68555231a409" }, { @@ -93,14 +94,14 @@ "file": "core/src/main/resources/org/apache/spark/ui/static/dataTables.bootstrap4.min.js", "purl": "pkg:npm/datatables.net-bs4@1.13.7", "license": "MIT", - "note": "The file is byte-identical in 1.13.7, 1.13.8 and 1.13.10, so the release it was taken from cannot be determined by hashing. The lowest one is recorded.", + "note": "The file is byte-identical in 1.13.7, 1.13.8, 1.13.10 and 1.13.11, so the release it was taken from cannot be determined by hashing. The lowest one is recorded.", "sha256": "e8dd8ff4b7568aa33170d6750c480e8cac29999da8a3a3e78dea13a4dfd8c8ef" }, { "file": "core/src/main/resources/org/apache/spark/ui/static/vis-timeline-graph2d.min.js", "purl": "pkg:npm/vis-timeline@7.7.2", "license": "(Apache-2.0 OR MIT)", - "note": "Version taken from the file header. It matches no published tarball, so the copy shipped here is built differently or modified.", + "note": "The file is standalone/umd/vis-timeline-graph2d.min.js of the 7.7.2 release without the trailing source-map link, so the code is that release unchanged.", "bundles": [ { "purl": "pkg:npm/moment@2.29.4", diff --git a/spark-k8s/stackable/vendored-js/4.1.2.json b/spark-k8s/stackable/vendored-js/4.1.2.json index 4d08c9c41..0b6113dd7 100644 --- a/spark-k8s/stackable/vendored-js/4.1.2.json +++ b/spark-k8s/stackable/vendored-js/4.1.2.json @@ -8,7 +8,8 @@ "file": "core/src/main/resources/org/apache/spark/ui/static/bootstrap.bundle.min.js", "purl": "pkg:npm/bootstrap@4.4.1", "license": "MIT", - "sha256": "4d371899aba195b1f0cba3a70de300fb5b327a322cfbe3a30d77af8456d8494e" + "sha256": "4d371899aba195b1f0cba3a70de300fb5b327a322cfbe3a30d77af8456d8494e", + "note": "The file is dist/js/bootstrap.bundle.min.js of the 4.4.1 release without the trailing source-map link, so the code is that release unchanged." }, { "file": "core/src/main/resources/org/apache/spark/ui/static/dataTables.rowsGroup.js", @@ -29,7 +30,7 @@ "file": "core/src/main/resources/org/apache/spark/ui/static/jquery.mustache.js", "purl": "pkg:npm/mustache@3.0.1", "license": "MIT", - "note": "Despite the file name this is mustache.js itself, not a jQuery plugin. Version taken from the file header. It matches no published tarball, so the copy shipped here is built differently or modified.", + "note": "Despite the file name this is mustache.js itself, not a jQuery plugin. The file is mustache.js of the 3.0.1 release minus its trailing newline, so the code is that release unchanged.", "sha256": "520f7bf7d54c8dde783aaf6f0dac66c9edc55da01716ccabca6888686822e7d6" }, { @@ -66,21 +67,21 @@ "file": "core/src/main/resources/org/apache/spark/ui/static/d3-flamegraph.min.js", "purl": "pkg:npm/d3-flame-graph@4.1.3", "license": "Apache-2.0", - "note": "Version taken from the file header, which is the jsDelivr URL it was downloaded from.", + "note": "Version taken from the file header, which is the jsDelivr URL it was downloaded from. Ignoring that banner the file is identical in 4.1.2 and 4.1.3, so the banner is the only thing that dates it.", "sha256": "10c2ac45bb9ad73e12fdebf434fbb29aa05b85b7bab660c2751e885c51bd5356" }, { "file": "core/src/main/resources/org/apache/spark/ui/static/dagre-d3.min.js", "purl": "pkg:npm/dagre-d3@0.6.4", "license": "MIT", - "note": "Version taken from the file header. It matches no published tarball, so the copy shipped here is built differently or modified.", + "note": "The file is dist/dagre-d3.min.js of the 0.6.4 release with a banner added that names the Git tag it was taken from, so the code is that release unchanged.", "sha256": "b1e946243fc2aa7ba500f7b8fd68c6c8a6fcbbb0329e00389b6a05e20e39130e" }, { "file": "core/src/main/resources/org/apache/spark/ui/static/graphlib-dot.min.js", "purl": "pkg:npm/graphlib-dot@1.0.2", "license": "MIT", - "note": "Version taken from the file header. It matches no published tarball, so the copy shipped here is built differently or modified.", + "note": "The banner names https://github.com/dagrejs/graphlib-dot/blob/v1.0.2/dist/graphlib-dot.min.js. npm stops at 0.6.4, so 1.0.2 exists only as a Git tag and there is no published tarball to compare with.", "sha256": "ae68701d5a547f47eb65ea79a4e2f3179d23c1b8280b2856e16e68555231a409" }, { @@ -93,14 +94,14 @@ "file": "core/src/main/resources/org/apache/spark/ui/static/dataTables.bootstrap4.min.js", "purl": "pkg:npm/datatables.net-bs4@1.13.7", "license": "MIT", - "note": "The file is byte-identical in 1.13.7, 1.13.8 and 1.13.10, so the release it was taken from cannot be determined by hashing. The lowest one is recorded.", + "note": "The file is byte-identical in 1.13.7, 1.13.8, 1.13.10 and 1.13.11, so the release it was taken from cannot be determined by hashing. The lowest one is recorded.", "sha256": "e8dd8ff4b7568aa33170d6750c480e8cac29999da8a3a3e78dea13a4dfd8c8ef" }, { "file": "core/src/main/resources/org/apache/spark/ui/static/vis-timeline-graph2d.min.js", "purl": "pkg:npm/vis-timeline@7.7.2", "license": "(Apache-2.0 OR MIT)", - "note": "Version taken from the file header. It matches no published tarball, so the copy shipped here is built differently or modified.", + "note": "The file is standalone/umd/vis-timeline-graph2d.min.js of the 7.7.2 release without the trailing source-map link, so the code is that release unchanged.", "bundles": [ { "purl": "pkg:npm/moment@2.29.4", diff --git a/trino/trino/stackable/vendored-js/477.json b/trino/trino/stackable/vendored-js/477.json index 092476724..f3db76f20 100644 --- a/trino/trino/stackable/vendored-js/477.json +++ b/trino/trino/stackable/vendored-js/477.json @@ -9,13 +9,15 @@ "file": "core/trino-web-ui/src/main/resources/webapp/vendor/bootstrap/js/bootstrap.js", "purl": "pkg:npm/bootstrap@3.4.1", "license": "MIT", - "sha256": "7c5a5562cb5d2b03c237b97b7f98f8302b9c04f9ef9f287b1d7093af16949b60" + "sha256": "7c5a5562cb5d2b03c237b97b7f98f8302b9c04f9ef9f287b1d7093af16949b60", + "note": "A Bootstrap customizer build, which the config.json next to it was generated for. It carries the same 3.4.1 modules as the published dist/js/bootstrap.js, only concatenated in a different order, so it is not byte-identical." }, { "file": "core/trino-web-ui/src/main/resources/webapp/vendor/bootstrap/js/bootstrap.min.js", "purl": "pkg:npm/bootstrap@3.4.1", "license": "MIT", - "sha256": "d518de485d8f2accc3acbce4c1be9f67c041d01cf4b43747a20e764b396cc526" + "sha256": "d518de485d8f2accc3acbce4c1be9f67c041d01cf4b43747a20e764b396cc526", + "note": "See the unminified file." }, { "file": "core/trino-web-ui/src/main/resources/webapp/vendor/clipboardjs/clipboard.min.js", @@ -28,7 +30,8 @@ "file": "core/trino-web-ui/src/main/resources/webapp/vendor/jquery/jquery-3.7.1.js", "purl": "pkg:npm/jquery@3.7.1", "license": "MIT", - "sha256": "5e6769f1e58b34bec6a37d52a0a242a3069e67a8364f7bff414a7fe8b083f1b9" + "sha256": "5e6769f1e58b34bec6a37d52a0a242a3069e67a8364f7bff414a7fe8b083f1b9", + "note": "The file is dist/jquery.js of the 3.7.1 release indented with spaces instead of tabs, so the code is that release unchanged." }, { "file": "core/trino-web-ui/src/main/resources/webapp/vendor/jquery/jquery-3.7.1.min.js", @@ -58,7 +61,7 @@ "file": "core/trino-web-ui/src/main/resources/webapp/vendor/vis/vis.js", "purl": "pkg:npm/vis-timeline@7.7.3", "license": "(Apache-2.0 OR MIT)", - "note": "Version taken from the file header. It matches no published tarball, so the copy shipped here is built differently or modified.", + "note": "The file is standalone/umd/vis-timeline-graph2d.js of the 7.7.3 release indented differently, so the code is that release unchanged.", "bundles": [ { "purl": "pkg:npm/moment@2.29.4", @@ -71,7 +74,7 @@ "file": "core/trino-web-ui/src/main/resources/webapp/vendor/vis/vis.min.js", "purl": "pkg:npm/vis-timeline@7.7.3", "license": "(Apache-2.0 OR MIT)", - "note": "See the unminified file.", + "note": "The file is standalone/umd/vis-timeline-graph2d.min.js of the 7.7.3 release, indented differently and missing one non-breaking space inside a string literal, so it is that release apart from that one character.", "bundles": [ { "purl": "pkg:npm/moment@2.29.4", diff --git a/trino/trino/stackable/vendored-js/479.json b/trino/trino/stackable/vendored-js/479.json index 092476724..f3db76f20 100644 --- a/trino/trino/stackable/vendored-js/479.json +++ b/trino/trino/stackable/vendored-js/479.json @@ -9,13 +9,15 @@ "file": "core/trino-web-ui/src/main/resources/webapp/vendor/bootstrap/js/bootstrap.js", "purl": "pkg:npm/bootstrap@3.4.1", "license": "MIT", - "sha256": "7c5a5562cb5d2b03c237b97b7f98f8302b9c04f9ef9f287b1d7093af16949b60" + "sha256": "7c5a5562cb5d2b03c237b97b7f98f8302b9c04f9ef9f287b1d7093af16949b60", + "note": "A Bootstrap customizer build, which the config.json next to it was generated for. It carries the same 3.4.1 modules as the published dist/js/bootstrap.js, only concatenated in a different order, so it is not byte-identical." }, { "file": "core/trino-web-ui/src/main/resources/webapp/vendor/bootstrap/js/bootstrap.min.js", "purl": "pkg:npm/bootstrap@3.4.1", "license": "MIT", - "sha256": "d518de485d8f2accc3acbce4c1be9f67c041d01cf4b43747a20e764b396cc526" + "sha256": "d518de485d8f2accc3acbce4c1be9f67c041d01cf4b43747a20e764b396cc526", + "note": "See the unminified file." }, { "file": "core/trino-web-ui/src/main/resources/webapp/vendor/clipboardjs/clipboard.min.js", @@ -28,7 +30,8 @@ "file": "core/trino-web-ui/src/main/resources/webapp/vendor/jquery/jquery-3.7.1.js", "purl": "pkg:npm/jquery@3.7.1", "license": "MIT", - "sha256": "5e6769f1e58b34bec6a37d52a0a242a3069e67a8364f7bff414a7fe8b083f1b9" + "sha256": "5e6769f1e58b34bec6a37d52a0a242a3069e67a8364f7bff414a7fe8b083f1b9", + "note": "The file is dist/jquery.js of the 3.7.1 release indented with spaces instead of tabs, so the code is that release unchanged." }, { "file": "core/trino-web-ui/src/main/resources/webapp/vendor/jquery/jquery-3.7.1.min.js", @@ -58,7 +61,7 @@ "file": "core/trino-web-ui/src/main/resources/webapp/vendor/vis/vis.js", "purl": "pkg:npm/vis-timeline@7.7.3", "license": "(Apache-2.0 OR MIT)", - "note": "Version taken from the file header. It matches no published tarball, so the copy shipped here is built differently or modified.", + "note": "The file is standalone/umd/vis-timeline-graph2d.js of the 7.7.3 release indented differently, so the code is that release unchanged.", "bundles": [ { "purl": "pkg:npm/moment@2.29.4", @@ -71,7 +74,7 @@ "file": "core/trino-web-ui/src/main/resources/webapp/vendor/vis/vis.min.js", "purl": "pkg:npm/vis-timeline@7.7.3", "license": "(Apache-2.0 OR MIT)", - "note": "See the unminified file.", + "note": "The file is standalone/umd/vis-timeline-graph2d.min.js of the 7.7.3 release, indented differently and missing one non-breaking space inside a string literal, so it is that release apart from that one character.", "bundles": [ { "purl": "pkg:npm/moment@2.29.4", diff --git a/trino/trino/stackable/vendored-js/481.json b/trino/trino/stackable/vendored-js/481.json index 092476724..f3db76f20 100644 --- a/trino/trino/stackable/vendored-js/481.json +++ b/trino/trino/stackable/vendored-js/481.json @@ -9,13 +9,15 @@ "file": "core/trino-web-ui/src/main/resources/webapp/vendor/bootstrap/js/bootstrap.js", "purl": "pkg:npm/bootstrap@3.4.1", "license": "MIT", - "sha256": "7c5a5562cb5d2b03c237b97b7f98f8302b9c04f9ef9f287b1d7093af16949b60" + "sha256": "7c5a5562cb5d2b03c237b97b7f98f8302b9c04f9ef9f287b1d7093af16949b60", + "note": "A Bootstrap customizer build, which the config.json next to it was generated for. It carries the same 3.4.1 modules as the published dist/js/bootstrap.js, only concatenated in a different order, so it is not byte-identical." }, { "file": "core/trino-web-ui/src/main/resources/webapp/vendor/bootstrap/js/bootstrap.min.js", "purl": "pkg:npm/bootstrap@3.4.1", "license": "MIT", - "sha256": "d518de485d8f2accc3acbce4c1be9f67c041d01cf4b43747a20e764b396cc526" + "sha256": "d518de485d8f2accc3acbce4c1be9f67c041d01cf4b43747a20e764b396cc526", + "note": "See the unminified file." }, { "file": "core/trino-web-ui/src/main/resources/webapp/vendor/clipboardjs/clipboard.min.js", @@ -28,7 +30,8 @@ "file": "core/trino-web-ui/src/main/resources/webapp/vendor/jquery/jquery-3.7.1.js", "purl": "pkg:npm/jquery@3.7.1", "license": "MIT", - "sha256": "5e6769f1e58b34bec6a37d52a0a242a3069e67a8364f7bff414a7fe8b083f1b9" + "sha256": "5e6769f1e58b34bec6a37d52a0a242a3069e67a8364f7bff414a7fe8b083f1b9", + "note": "The file is dist/jquery.js of the 3.7.1 release indented with spaces instead of tabs, so the code is that release unchanged." }, { "file": "core/trino-web-ui/src/main/resources/webapp/vendor/jquery/jquery-3.7.1.min.js", @@ -58,7 +61,7 @@ "file": "core/trino-web-ui/src/main/resources/webapp/vendor/vis/vis.js", "purl": "pkg:npm/vis-timeline@7.7.3", "license": "(Apache-2.0 OR MIT)", - "note": "Version taken from the file header. It matches no published tarball, so the copy shipped here is built differently or modified.", + "note": "The file is standalone/umd/vis-timeline-graph2d.js of the 7.7.3 release indented differently, so the code is that release unchanged.", "bundles": [ { "purl": "pkg:npm/moment@2.29.4", @@ -71,7 +74,7 @@ "file": "core/trino-web-ui/src/main/resources/webapp/vendor/vis/vis.min.js", "purl": "pkg:npm/vis-timeline@7.7.3", "license": "(Apache-2.0 OR MIT)", - "note": "See the unminified file.", + "note": "The file is standalone/umd/vis-timeline-graph2d.min.js of the 7.7.3 release, indented differently and missing one non-breaking space inside a string literal, so it is that release apart from that one character.", "bundles": [ { "purl": "pkg:npm/moment@2.29.4",