Skip to content

main -> stable - #3230

Merged
jiridanek merged 15 commits into
stablefrom
main
Apr 1, 2026
Merged

jiridanek merged 15 commits into
stablefrom
main

Conversation

@jiridanek

@jiridanek jiridanek commented Apr 1, 2026 •

Copy link
Copy Markdown
Member

Description

How Has This Been Tested?

Self checklist (all need to be checked):

  • Ensure that you have run make test (gmake on macOS) before asking for review
  • Changes to everything except Dockerfile.konflux files should be done in odh/notebooks and automatically synced to rhds/notebooks. For Konflux-specific changes, modify Dockerfile.konflux files directly in rhds/notebooks as these require special attention in the downstream repository and flow to the upcoming RHOAI release.

Merge criteria:

  • The commits are squashed in a cohesive manner and have meaningful messages.
  • Testing instructions have been added in the PR body (for PRs involving changes that are not immediately obvious).
  • The developer has manually tested the changes and verified that the changes work

Summary by CodeRabbit

  • New Features

    • Hermetic build mode enabled; prefetch-dependencies task added to cache and prefetched resources before image builds
  • Improvements

    • Consolidated dependency management into meta-packages for simpler dependency sets
    • Python tooling bumped to uv 0.11.0; added protobuf runtime support across images
    • Multi-architecture build/prefetch support and fixed resource sizing for prefetch step
  • Testing

    • Stronger TypeScript typings and Playwright config; container tests skip unsupported s390x cases
  • Documentation

    • Added Go toolchain guidance to CONTRIBUTING.md

jiridanek and others added 14 commits March 31, 2026 13:35
…cience deps into subprojects (#2796)

Add 3 new dependency subprojects to reduce duplication across pyproject.toml files:
- odh-notebooks-meta-db-connectors-deps (pymongo, psycopg, pyodbc, mysql-connector-python)
- odh-notebooks-meta-jupyterlab-deps (jupyterlab, jupyter-server, extensions)
- odh-notebooks-meta-jupyterlab-datascience-deps (odh-elyra, jupyter-bokeh, jupyterlab-lsp, etc.)

Existing subprojects are unchanged. Updated 14 pyproject.toml files and regenerated lock files.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dockerfile2LLB now returns (*Result, error) instead of 5 values.
Access the LLB state via result.State.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix FAILED tests/containers/runtimes/runtime_test.py::TestRuntimeImage::test_feast_version[ghcr.io/opendatahub-io/notebooks/workbench-images:runtime-datascience-ubi9-python-3.12-3217_merge_2f8909d91201e3197d82286ad72206b98e5f9ee9] - AssertionError: 'feast version' failed: qemu: uncaught target signal 11 (Segmentation fault) - core dumped
- Remove collect_ignore from pytest.ini (unsupported there; pytest 9+ warns)
- Set collect_ignore in tests/conftest.py relative to tests/
- Skip feast version runtime test on s390x (PyArrow/Feast flaky under CI QEMU)
- jupyter/minimal: extend RPM prefetch (protobuf, unixODBC-devel, s390x build
  toolchain) and refresh locks / pylocks
- jupyter/pytorch+llmcompressor: uv bump, protobuf in image, prefetch refresh
- runtimes/datascience: Dockerfile.cpu / konflux.cpu updates for hermetic base
test: fix pytest collect_ignore; skip Feast CLI on s390x
chore: minimal RPM prefetch, pytorch CUDA alignment, runtime datascience
Build Jupyter datascience heretically, by prefetch the rpms, pip and gomod. Install openshift-clients from rpm, build mongocli from shared submodule sources. Also update .tekton files.
…mertic-datascience

RHAIENG-2856: Hermetic build for Jupyter datascience CPU
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add tsconfig.json with nodenext module resolution, excluding cypress/.
Fix type errors surfaced by strict mode:
- k8s client-node 1.x object-param API for getNamespacedCustomObject
- Add Page/TestInfo type annotations in utils.ts
- Type custom Playwright fixtures in defineConfig<MyFixtures>()
- Fix Page | undefined in start_browser.ts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Throw a descriptive error if the selector doesn't match,
instead of a generic TypeError from the non-null assertion.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use `{ url: string; image?: never } | { image: string; url?: never }`
to make invalid states (empty object, both undefined) unrepresentable
at compile time. Fix CI config to only override fixture default when
TEST_TARGET env var is set.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
chore(tests/browser): update all pnpm dependencies and adopt more of typescript
…edora builds (#3223)

* chore: update Go to 1.26 and fix Makefile for Fedora builds (#3026)

Update go directive to 1.26.0 in all Go modules.
Add GOTOOLCHAIN=auto and GONOSUMCHECK=golang.org/toolchain to
Makefile go commands so Fedora users (who have GOTOOLCHAIN=local
and GOSUMDB=off by default) can build without manual env setup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add Go toolchain section to CONTRIBUTING.md

Document Go 1.26 requirement and Fedora GOSUMDB=off workaround.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: use GONOSUMDB (not GONOSUMCHECK) and pin go 1.26.1

GONOSUMCHECK is not a real Go env var — GONOSUMDB is the correct one.
Pin go 1.26.1 since go 1.26.5 doesn't exist yet.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@openshift-ci
openshift-ci Bot requested review from ayush17 and dibryant April 1, 2026 13:15
@openshift-ci

openshift-ci Bot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign ysok for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci Bot added the size/xxl label Apr 1, 2026
@github-actions github-actions Bot added the review-requested GitHub Bot creates notification on #pr-review-ai-ide-team slack channel label Apr 1, 2026
@jiridanek jiridanek changed the title main <- stable main -> stable Apr 1, 2026
@coderabbitai

coderabbitai Bot commented Apr 1, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This pull request shifts many images and pipelines to hermetic builds by introducing prefetch-input parameters, adding a prefetch-dependencies task with fixed resources, and wiring prefetched RPM/pip/gomod sources into Dockerfiles (via /cachi2/output/deps) guarded by a LOCAL_BUILD flag. It consolidates explicit DB-connector and JupyterLab pins into three local meta-packages under dependencies/, bumps uv to 0.11.0 and Go to 1.26.1, regenerates numerous RPM lockfiles (adds build/toolchain packages and protobuf), updates Tekton PipelineRun triggers/params for prefetching, and tightens several TypeScript test typings.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes


Detailed Findings

Architecture & Design

Hermetic Build Introduction: Dockerfiles and Tekton now depend on prefetched artifacts and a prefetch task. Verify:

  • Repo precedence and correct replacement when LOCAL_BUILD=true.
  • Go build uses GOMODCACHE=/cachi2/output/deps/gomod and intended proxy/offline semantics.
  • Pip --no-index --find-links /cachi2/output/deps/pip covers all wheels/sources needed; missing artifacts will break builds.

Actionable: validate prefetch artifact coverage against lockfiles and the Dockerfile install commands.

Meta-Package Consolidation: Many pyproject.toml files now depend on three local meta-packages. Risk: ensure meta-packages are acyclic and their uv lockfiles include full transitive closure for offline builds.

Actionable: run uv resolution for each meta-package and confirm resulting uv.lock contains all transitive packages.

RPM Prefetch Manifests: Large additions (protobuf, build toolchain, openblas-devel, etc.) and many lockfile deltas across arches.

Actionable: verify s390x entries are complete and that added build-tool RPMs are available in the targeted repos for all arches.

Code Quality Issues

Dockerfile Variable Scope: PYLOCK_FLAVOR referenced in pip install—ensure it’s defined in build context/stages to avoid ambiguous behavior.

Actionable: confirm BuildKit/CI invocation sets PYLOCK_FLAVOR or stage ARGs propagate correctly.

Go nil-check: scripts/buildinputs/dockerfile.go now calls result.State.Marshal(ctx). If result can be nil or State nil, this may panic.

Actionable: add nil-checks on result and result.State before Marshal to avoid runtime panics (CWE-688: Functionality not protected against nil dereference).

TypeScript fixture changes: Discriminated union for codeServerSource and stricter signatures for test helpers require update of all callers.

Actionable: run TypeScript compilation for tests and update any legacy usages that pass both url and image or omit typed parameters.

Security & Operations

Removed network fetches: curl/dnf upgrade and runtime downloads removed—reduces attack surface (positive). No praise; factual.

GPG key imports: Multiple GPG keys imported with || true fallback. Silent continuation if keys missing may mask integrity issues.

Actionable: ensure keys are included in prefetch artifacts; consider failing fast if critical keys are absent (CWE-295: Improper Certificate Validation risk if keys missing).

Offline mode dependency risk: GOPROXY=off and pip --no-index require complete offline artifact sets. Missing indirect deps cause build/runtime failures.

Actionable: audit uv.lock.* and rpms.lock.yaml files for completeness. Add CI validation step that runs a hermetic build using only prefetched artifacts and fails on missing items (CWE-110: Missing Release of Resource? more precisely dependency integrity risk).

Go toolchain bump: go 1.26.1 in go.mod without go.sum changes shown.

Actionable: run go mod tidy / go test in scripts modules to ensure go.sum and module integrity are updated and committed.

Tests & CI

s390x test skip: tests/containers/... now skips feast version on s390x. Actionable: confirm other tests with arch-sensitive behavior also handle s390x consistently.

pytest collection: collect_ignore moved from pytest.ini to conftest.py. Actionable: verify pytest version compatibility and that collection exclusion is effective in CI.

Playwright/test updates: Type changes in fixtures and helper signatures require updating test callers.

Actionable: run test typechecks and Playwright runs in CI to catch mismatches.

Actionable Security Citations

  • Nil-dereference risk: add checks to avoid panics (CWE-671 / CWE-688).
  • Silent GPG import failures: ensure key presence and fail fast if missing (CWE-295).
  • Offline dependency completeness: missing dependencies may yield insecure fallback or build breakage—validate locks and prefetch artifacts (supply audit step).

Only actionable issues flagged above; address those before merging.

🚥 Pre-merge checks | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning Title 'main -> stable' does not follow imperative mood requirement; uses branch names instead of imperative verbs (add, remove, fix, update). Use imperative mood and include a ticket reference. Example: 'RHAIENG-XXXX: chore: prepare main->stable release' or equivalent with proper formatting.
Description check ⚠️ Warning Description is entirely empty boilerplate template with all sections unfilled and all merge criteria unchecked. Fill in the Description section with detailed change summary, add How Has This Been Tested section with test results, and check off applicable merge criteria items to demonstrate compliance.
Branch Prefix Policy ⚠️ Warning PR targets 'stable' branch requiring '[stable] ' prefix; title 'main -> stable (PR #3230)' lacks required prefix and matches no exemptions. Update PR title to '[stable] main -> stable (PR #3230)' to comply with branch prefix policy for non-main targets.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@openshift-ci openshift-ci Bot added size/xxl and removed size/xxl labels Apr 1, 2026
@codecov-commenter

Copy link
Copy Markdown

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
97 1 96 2
View the top 1 failed test(s) by shortest run time
tests.test_main::test_rhds_pipelines_use_rhds_args
Stack Traces | 0.726s run time
contains 1 failed subtest

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@openshift-ci openshift-ci Bot added size/xxl and removed size/xxl labels Apr 1, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
tests/browser/tests/utils.ts (2)

31-45: ⚠️ Potential issue | 🔴 Critical

Critical bug: setInterval inside loop creates exponential interval spawning.

Line 42 uses setInterval inside loop(). Each call to loop() schedules another repeating interval, causing exponential growth in callbacks, memory exhaustion, and likely browser tab crash.

Fix: use `setTimeout` for single delayed callback
         return new Promise<void>((resolve, reject) => {
             let loop = () => {
                 let now = performance.now();
                 if (now - mutated > checkPeriod) {
                     observer.disconnect();
                     resolve();
                 }
                 if (now - started > timeout) {
                     observer.disconnect();
                     reject();
                 }
-                window.setInterval(loop, checkPeriod);
+                window.setTimeout(loop, checkPeriod);
             };
             loop()
         })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/browser/tests/utils.ts` around lines 31 - 45, The loop inside the
Promise schedules a repeating setInterval on every invocation causing
exponential intervals; change the scheduling call in the loop function to use
window.setTimeout(loop, checkPeriod) (single-shot) so only one delayed callback
is queued each iteration, keep the existing
observer.disconnect()/resolve()/reject() logic intact, and ensure the initial
loop() call still starts the cycle; reference the loop function and the observer
variable in tests/browser/tests/utils.ts when making this change.

38-41: ⚠️ Potential issue | 🟡 Minor

Promise never resolves if timeout triggers after stability check passes.

If now - mutated > checkPeriod resolves the promise, the subsequent timeout check on lines 38-41 still executes and may call reject() on an already-resolved promise. Add return statements after resolve()/reject().

Add early returns
             let loop = () => {
                 let now = performance.now();
                 if (now - mutated > checkPeriod) {
                     observer.disconnect();
                     resolve();
+                    return;
                 }
                 if (now - started > timeout) {
                     observer.disconnect();
                     reject();
+                    return;
                 }
                 window.setTimeout(loop, checkPeriod);
             };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/browser/tests/utils.ts` around lines 38 - 41, The timeout branch can
call reject() after the promise was already resolved by the stability check;
update the check handling so you exit immediately after resolving or rejecting:
after the stability branch that calls resolve() when (now - mutated >
checkPeriod) and after the timeout branch that calls reject() when (now -
started > timeout), add immediate returns (ensuring observer.disconnect() is
still called before the return) so no further checks run and the promise cannot
be settled twice; look for the uses of resolve(), reject(),
observer.disconnect(), now - mutated > checkPeriod and now - started > timeout
to locate the exact spots to update.
runtimes/datascience/ubi9-python-3.12/Dockerfile.konflux.cpu (2)

123-124: ⚠️ Potential issue | 🟠 Major

Network access to GitHub in Konflux build.

Line 123-124 downloads bootstrapper.py from raw.githubusercontent.com at build time. This breaks hermetic build requirements for Konflux. The Elyra bootstrapper should be prefetched or vendored.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@runtimes/datascience/ubi9-python-3.12/Dockerfile.konflux.cpu` around lines
123 - 124, The Dockerfile currently fetches the Elyra bootstrapper at build time
via the RUN curl line (downloading bootstrapper.py), which violates hermetic
build rules; replace that network fetch by vendoring or prefetching the file
into the build context and change the Dockerfile to use COPY/ADD to bring
./utils/bootstrapper.py into the image instead of the curl command. Ensure the
repository contains the exact bootstrapper.py (or a pinned vendor snapshot) and
update the Dockerfile location that referenced the bootstrapper (the RUN curl
line) to a COPY ./utils/bootstrapper.py ./utils/bootstrapper.py so the image
build no longer requires external network access.

97-101: ⚠️ Potential issue | 🟠 Major

Network access in Konflux Dockerfile may break hermetic build guarantees.

Lines 97-100 use curl to download the OpenShift client from mirror.openshift.com at build time. For a Konflux (hermetic) build, external network access is typically disallowed or should use prefetched artifacts like the openshift-clients RPM in the manifests.

The RPM prefetch manifests already include openshift-clients. Consider installing via dnf from prefetched RPMs instead of curl.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@runtimes/datascience/ubi9-python-3.12/Dockerfile.konflux.cpu` around lines 97
- 101, The Dockerfile currently fetches the OpenShift client with the curl/tar
sequence (curl ... -o /tmp/openshift-client-linux.tar.gz; tar -xzvf
/tmp/openshift-client-linux.tar.gz; rm -f /tmp/openshift-client-linux.tar.gz),
which breaks hermetic builds; replace that block by installing the prefetched
openshift-clients RPM (package name openshift-clients) via dnf (e.g., dnf
install -y /path/to/prefetched/openshift-clients-*.rpm or dnf install -y
openshift-clients if the manifests repo is enabled) and remove the curl/tar
lines so the Dockerfile uses the pre-fetched RPM artifact instead of downloading
from mirror.openshift.com.
jupyter/pytorch+llmcompressor/ubi9-python-3.12/Dockerfile.konflux.cuda (1)

191-196: ⚠️ Potential issue | 🟡 Minor

--no-verify-hashes disables integrity verification.

Line 194 uses --no-verify-hashes while installing from prefetched dependencies. For hermetic builds, hash verification should typically be enforced to ensure reproducibility and detect tampering. The lockfile (pylock.toml) contains SHA256 hashes.

If hashes are intentionally skipped due to Cachi2 prefetch mechanics (e.g., wheels renamed or reprocessed), document this in a comment. Otherwise, consider using --require-hashes for stronger supply chain security.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jupyter/pytorch`+llmcompressor/ubi9-python-3.12/Dockerfile.konflux.cuda
around lines 191 - 196, The pip install invocation using the UV wrapper (the
command starting with "UV_NO_CACHE=true UV_LINK_MODE=copy
UV_PREVIEW_FEATURES=pylock uv pip install ... --no-verify-hashes") disables
package hash verification; either enable strict hash checking by replacing
"--no-verify-hashes" with "--require-hashes" (and ensure ./requirements.txt /
pylock.toml contains hashes) or, if Cachi2 prefetching prevents exact hashes,
add a clear comment above this UV pip install line explaining why hashes are
intentionally omitted (describe the Cachi2 renaming/reprocessing behavior) so
future maintainers understand the tradeoff.
🧹 Nitpick comments (10)
tests/browser/tests/utils.ts (1)

13-14: Use let instead of var for block-scoped variables.

var hoists to function scope and is discouraged in modern TypeScript.

-        var started = performance.now();
-        var mutated = started;
+        let started = performance.now();
+        let mutated = started;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/browser/tests/utils.ts` around lines 13 - 14, Replace the
function-scoped declarations using "var" for the timing variables with
block-scoped "let": change "var started = performance.now();" and "var mutated =
started;" to use "let" so that started and mutated are block-scoped (update both
declarations in tests/browser/tests/utils.ts where these variables are defined).
tests/browser/tests/codeserver.spec.ts (2)

40-46: Unnecessary optional chaining and verbose error throw.

codeServerSource is always provided by the fixture system. The ?. on line 41 is redundant. The IIFE-throw pattern on lines 44-46 works but is verbose.

Simplified fixture
   codeServer: [async ({ page, codeServerSource }, use) => {
-    if (codeServerSource?.url) {
+    if ('url' in codeServerSource) {
       await use(new CodeServer(page, codeServerSource.url))
     } else {
-      const image = codeServerSource.image ?? (() => {
-        throw new Error("invalid config: codeserver image not specified")
-      })()
+      // Type guarantees `image` exists when `url` is absent
+      const image = codeServerSource.image;
       const container = await new GenericContainer(image)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/browser/tests/codeserver.spec.ts` around lines 40 - 46, Remove the
unnecessary optional chaining on codeServerSource (it is always provided) and
replace the verbose IIFE throw with a simple guard: in the codeServer fixture
block, change "if (codeServerSource?.url)" to "if (codeServerSource.url)" and
replace the "const image = codeServerSource.image ?? (() => { throw new
Error('invalid config: codeserver image not specified') })()" pattern with
"const image = codeServerSource.image; if (!image) throw new Error('invalid
config: codeserver image not specified')", keeping the rest of the logic using
new CodeServer(page, codeServerSource.url) intact.

16-22: MyFixtures type duplicated in playwright.config.ts.

The same MyFixtures type is defined in both files. Extract to a shared module to avoid drift.

Extract to shared types file

Create tests/browser/tests/types.ts:

export type MyFixtures = {
  connectCDP: false | number;
  codeServerSource:
    | { url: string; image?: never }
    | { image: string; url?: never };
};

Then import in both files:

import type { MyFixtures } from './types';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/browser/tests/codeserver.spec.ts` around lines 16 - 22, The MyFixtures
type is duplicated; extract it into a single shared types module named types.ts
that exports the MyFixtures type (keeping the connectCDP and codeServerSource
union but removing the per-test-only codeServer property), then remove the local
MyFixtures definitions in both files and import the shared type (import type {
MyFixtures } from './types') in each place where MyFixtures was previously
declared so both tests reference the single canonical type.
tests/browser/scripts/start_browser.ts (1)

62-70: Redundant null checks—page can only be Page | undefined.

The type is Page | undefined, and context?.pages()[0] returns undefined (not null) when absent. The === null checks on lines 63 and 66 are dead code.

Simplified condition
-    while ((page === null || page === undefined) && Date.now() - started < timeout) {
+    while (page === undefined && Date.now() - started < timeout) {
         const context = browser.contexts()[0];
         page = context?.pages()[0];
-        if (page !== null && page !== undefined) {
+        if (page !== undefined) {
             return page;
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/browser/scripts/start_browser.ts` around lines 62 - 70, The loop
contains redundant null checks for the local variable page (declared as Page |
undefined) and for context?.pages()[0], which can only be undefined; remove the
=== null checks and simplify the conditions to check only undefined (e.g., use
page === undefined or page !== undefined) and likewise rely on optional chaining
result being undefined, updating the while condition and the if that returns
page (references: page, started, timeout, browser.contexts(),
context?.pages()[0]).
jupyter/minimal/ubi9-python-3.12/prefetch-input/odh/rpms.lock.yaml (1)

28-41: Build toolchain packages increase attack surface for "minimal" image.

New additions include autoconf, automake, cmake, libtool, m4, openblas-devel, pybind11-devel, and gcc runtime libraries. For a "minimal" Jupyter image, consider whether these belong in a separate build stage or multi-stage build to reduce the final image attack surface. If these are required at runtime for pip-installing packages with native extensions, document this requirement.

Also applies to: 2240-2295, 2289-2295, 2331-2351

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jupyter/minimal/ubi9-python-3.12/prefetch-input/odh/rpms.lock.yaml` around
lines 28 - 41, The rpms.lock.yaml adds build-toolchain packages (autoconf,
automake, cmake, libtool, m4, openblas-devel, pybind11-devel, gcc runtime libs)
which increase the attack surface of the "minimal" Jupyter image; either move
these packages out of the final runtime package list into a separate
build-stage/multi-stage image (so they are installed only during build and not
present in the final image) or, if they are required at runtime for
pip-installing native extensions, explicitly document that requirement in the
repo (e.g., in README or image documentation) and mark these entries as
build-time dependencies—update the entries for autoconf, automake, cmake,
libtool, m4, openblas-devel, pybind11-devel, and gcc-related packages
accordingly across the referenced sections (around lines 2240-2351) to reflect
the chosen approach.
runtimes/datascience/ubi9-python-3.12/Dockerfile.cpu (1)

91-91: uv version inconsistency: 0.10.8 here vs 0.11.0 in Konflux Dockerfiles.

This file pins uv==0.10.8 while jupyter/pytorch+llmcompressor/ubi9-python-3.12/Dockerfile.cuda and Dockerfile.konflux.cuda use uv==0.11.0. Drift between non-Konflux and Konflux builds may cause subtle differences.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@runtimes/datascience/ubi9-python-3.12/Dockerfile.cpu` at line 91, The RUN pip
install line currently pins "uv==0.10.8" causing version drift; update that
package pin in the RUN command (the pip install statement that includes
"micropipenv[toml]==1.10.0" and "uv==0.10.8") to match the Konflux Dockerfiles
by changing "uv==0.10.8" to "uv==0.11.0" so all builds use the same uv version.
Makefile (1)

144-145: GONOSUMDB is ignored for toolchain downloads.

Go treats golang.org/toolchain specially: GONOSUMDB does not apply here, and GOSUMDB=off still blocks GOTOOLCHAIN=auto from fetching the toolchain. Keeping this env var is misleading because it suggests a compatibility path that Go will ignore. Drop it, and if you need checksum-db-disabled environments to work, handle that with an explicit guard or documentation instead. (go.dev)

Do `GONOSUMDB` patterns apply to `golang.org/toolchain` downloads when using `GOTOOLCHAIN=auto`, and what happens if `GOSUMDB=off`?
Proposed fix
 bin/buildinputs: scripts/buildinputs/buildinputs.go scripts/buildinputs/go.mod scripts/buildinputs/go.sum
 	$(info Building a Go helper for Dockerfile dependency analysis...)
-	GOTOOLCHAIN=auto GONOSUMDB=golang.org/toolchain \
+	GOTOOLCHAIN=auto \
 	  go build -C "scripts/buildinputs" -o "$(ROOT_DIR)/$@" ./...

 test-unit:
 	`@echo` "Running Python unit tests"
 	./uv run pytest -m 'not buildonlytest' --ignore=tests/containers tests/ ntb/
 	`@echo` "Running Go unit tests"
-	GOTOOLCHAIN=auto GONOSUMDB=golang.org/toolchain \
+	GOTOOLCHAIN=auto \
 	  go test -C scripts/buildinputs -cover ./...

Also applies to: 503-504

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Makefile` around lines 144 - 145, The Makefile sets GONOSUMDB when invoking
go build for the toolchain (GOTOOLCHAIN=auto) but GONOSUMDB is ignored for
golang.org/toolchain downloads and is misleading; remove the
GONOSUMDB=golang.org/toolchain assignment from the go build invocation (the line
that exports/uses GOTOOLCHAIN=auto and runs go build -C "scripts/buildinputs" -o
"$(ROOT_DIR)/$@" ./...), and if you need to support environments with
GOSUMDB=off, add an explicit guard or documented check that handles GOSUMDB
rather than relying on GONOSUMDB.
jupyter/datascience/ubi9-python-3.12/Dockerfile.cpu (1)

29-32: Go cache directories overlap may cause build issues.

GOCACHE, GOMODCACHE, and GOPATH all point to /cachi2/output/deps/gomod or subdirectories thereof. While GOMODCACHE under $GOPATH/pkg/mod is standard, GOCACHE (build cache) sharing the same root as GOPATH is unconventional and may cause cache pollution between module downloads and build artifacts.

Consider separating GOCACHE
-    export GOCACHE=/cachi2/output/deps/gomod
+    export GOCACHE=/tmp/go-build-cache
     export GOMODCACHE=/cachi2/output/deps/gomod/pkg/mod
     export GOPATH=/cachi2/output/deps/gomod
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jupyter/datascience/ubi9-python-3.12/Dockerfile.cpu` around lines 29 - 32,
The current Dockerfile sets GOCACHE, GOMODCACHE and GOPATH to overlapping
locations which can cause cache pollution; change GOCACHE to a separate
directory (e.g., /cachi2/output/deps/gocache) while leaving GOMODCACHE as
/cachi2/output/deps/gomod/pkg/mod and GOPATH as /cachi2/output/deps/gomod, and
update GOPROXY only if needed; modify the export of GOCACHE in the
Dockerfile.cpu so it points to a distinct path from GOPATH/GOMODCACHE to isolate
build cache from module cache.
jupyter/datascience/ubi9-python-3.12/Dockerfile.konflux.cpu (2)

52-53: Duplicated GPG key import with silent failure.

Same || true pattern in multiple stages. The repeated imports (lines 52-53, 95-97) could be consolidated into a single reusable script, and the silent failure handling should be reconsidered per the comment on Dockerfile.cpu.

Also applies to: 95-97

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jupyter/datascience/ubi9-python-3.12/Dockerfile.konflux.cpu` around lines 52
- 53, The Dockerfile duplicates GPG key imports with a silent-failure "|| true"
(seen in the RUN lines importing
/cachi2/output/deps/generic/RPM-GPG-KEY-CentOS-Official and
/etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release and again at lines ~95-97); extract
these import commands into a small reusable script (e.g., import-gpg-keys.sh)
and call it from each stage, and remove the blanket "|| true" so the script
performs explicit checks and logs or exits on failure (return non-zero) to
surface errors instead of silencing them. Ensure the script idempotently checks
for an existing key before importing to avoid duplicate failures.

1-187: Reduce Dockerfile duplication: consolidate with build-arg-controlled labels.

These two Dockerfiles are 92% identical with only the LABEL section differing:

  • Dockerfile.cpu: 189 lines
  • Dockerfile.konflux.cpu: 187 lines
  • 173 lines unchanged; 16 lines changed (all in LABEL block)

Both use identical Konflux/hermetic build logic (12 cachi2 references each, same [HERMETIC] patterns, identical LOCAL_BUILD behavior). The only variation is image naming/metadata:

  • Dockerfile.cpu uses odh-notebook-jupyter-datascience-ubi9-python-3.12 and Quay.io registry
  • Dockerfile.konflux.cpu uses rhoai/odh-workbench-jupyter-datascience-cpu-py312-rhel9 and Red Hat license terms

Consolidate into a single Dockerfile with a BUILD_VARIANT or REGISTRY_TARGET build-arg to switch label values. This eliminates maintenance drift and avoids future misalignment between variants.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jupyter/datascience/ubi9-python-3.12/Dockerfile.konflux.cpu` around lines 1 -
187, The Dockerfiles are nearly identical except for LABEL metadata; consolidate
into one Dockerfile and drive variant-specific LABEL values via a build-arg
(e.g., BUILD_VARIANT or REGISTRY_TARGET). Edit the existing
Dockerfile.konflux.cpu/jupyter-datascience stage: introduce ARG BUILD_VARIANT
(or REGISTRY_TARGET) near the top, replace the hard-coded LABEL block in the
jupyter-datascience stage with conditional value selection based on that arg
(set descriptive name, component, display-name, summary, description,
license_terms) and default to the current konflux values; ensure Docker build
invocation can pass the alternative variant to produce the other image. Keep all
hermetic LOCAL_BUILD logic, mongocli-builder and stage names (mongocli-builder,
cpu-base, jupyter-minimal, jupyter-datascience) unchanged so only LABELs are
parameterized.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
@.tekton/odh-workbench-jupyter-datascience-cpu-py312-ubi9-odh-main-pull-request.yaml:
- Line 16: The CEL filter in the PR trigger is missing the shared meta-project
paths used by the datascience image; update the condition inside
".tekton/odh-workbench-jupyter-datascience-cpu-py312-ubi9-odh-main-pull-request.yaml"
so the OR list includes .pathChanged() checks for the three new projects:
"dependencies/odh-notebooks-meta-db-connectors-deps",
"dependencies/odh-notebooks-meta-jupyterlab-deps", and
"dependencies/odh-notebooks-meta-jupyterlab-datascience-deps" (add each as e.g.
"dependencies/odh-notebooks-meta-db-connectors-deps".pathChanged()) so a PR
touching any of those shared inputs will trigger the PipelineRun.

In @.tekton/odh-workbench-jupyter-datascience-cpu-py312-ubi9-odh-main-push.yaml:
- Line 11: The trigger's CEL expression for
pipelinesascode.tekton.dev/on-cel-expression omits the shared meta-project
directories that now affect jupyter/datascience/ubi9-python-3.12/pyproject.toml,
so add OR conditions to the existing pathChanged() list to include the
dependency projects (e.g.,
"dependencies/odh-notebooks-meta-db-connectors-deps".pathChanged(),
"dependencies/odh-notebooks-meta-jupyterlab-deps".pathChanged(), and
"dependencies/odh-notebooks-meta-jupyterlab-datascience-deps".pathChanged()) so
changes in those shared projects will also cause the PipelineRun to execute;
update the expression that currently lists the
jupyter/datascience/ubi9-python-3.12/** paths under the same OR group.

In `@jupyter/datascience/ubi9-python-3.12/Dockerfile.cpu`:
- Around line 52-53: The RUN command that imports the CentOS GPG key ("RUN rpm
--import /cachi2/output/deps/generic/RPM-GPG-KEY-CentOS-Official || true") is
silencing failures with "|| true"; remove the silent suppression and instead
ensure the build fails or logs a clear warning when the key is missing or
invalid. Replace the silent suppression by checking the key import result for
the "rpm --import /cachi2/output/deps/generic/RPM-GPG-KEY-CentOS-Official" step
(or test for file existence first) and emit a descriptive error/warning or exit
non‑zero so unsigned packages are not allowed; keep the second import ("RUN rpm
--import /etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release") consistent with this
behavior.

In `@jupyter/datascience/ubi9-python-3.12/uv.lock.d/pylock.cpu.toml`:
- Around line 1874-1883: The "uv" package entry declares a broad marker
(implementation_name == 'cpython' and sys_platform == 'linux') but only supplies
wheels for linux_aarch64, linux_s390x and linux_x86_64; narrow the marker for
the uv package or add the missing ppc64le wheel: either change the uv
[[packages]] marker to explicitly restrict architectures (e.g. include
platform_machine in ("x86_64","aarch64","s390x")) so the resolver won't attempt
ppc64le, or add a wheels entry for linux_ppc64le with the correct URL,
upload-time, size and sha256 hash to match the other wheel entries. Ensure you
update the package block for name = "uv" and its wheels list accordingly.

In `@jupyter/minimal/ubi9-python-3.12/prefetch-input/odh/rpms.lock.yaml`:
- Around line 1428-1434: The python3-pip entry (name: python3-pip, evr:
21.3.1-1.el9) in the rpms.lock.yaml is vulnerable to CVE-2023-5752 for Mercurial
(hg+) installs; verify whether your build or downstreams actually perform
Mercurial VCS installs and, if they do, either (a) replace or override this
package with a non-vulnerable pip (version >=23.3 or vendor-supplied RHEL
backport when available) or (b) document and mitigate the risk by disabling VCS
installs or blocking hg+ URLs in your pipelines; update the lock entry or add a
clear comment/mitigation note referencing python3-pip 21.3.1 and CVE-2023-5752
accordingly.

In `@jupyter/minimal/ubi9-python-3.12/prefetch-input/rhds/rpms.in.yaml`:
- Line 43: The shared RPM manifest currently lists compiler and -devel packages
(e.g., unixODBC-devel) and an s390x toolchain block; remove those entries from
this shared runtime manifest and place them into an s390x-only builder manifest
or a dedicated build-stage manifest so they are only installed for the s390x
builder path; update the manifests referenced around the same section (also
applies to the package group in lines ~91-102) to ensure minimal runtime images
no longer include compilers/headers.

In `@jupyter/minimal/ubi9-python-3.12/prefetch-input/rhds/rpms.lock.yaml`:
- Around line 56-57: The lockfile rpms.lock.yaml contains many entries where the
URL architecture (e.g., "aarch64", "ppc64le", "s390x") does not match the
hardcoded repoid value "rhel-9-for-x86_64-appstream-rpms"; regenerate the
lockfile or update the repoid fields so each package entry's repoid corresponds
to the URL architecture (or remove repoid entirely if your consumer resolves
strictly from URL). Locate and fix the repoid key entries (search for "repoid"
and sample URLs like the autoconf URL) so that for each architecture block you
set the correct repository id (e.g., rhel-9-for-aarch64-appstream-rpms,
rhel-9-for-ppc64le-appstream-rpms, rhel-9-for-s390x-appstream-rpms) or strip
repoid generation in the producing script to avoid mismatches.
- Around line 17547-17560: The lockfile pins unversioned RHEL 9 Python packages
(entries named python3-devel and python3-pip with sourcerpm python3.9-...) into
the ubi9-python-3.12 variant; update the rpms.lock.yaml entries so they
reference version-matched Python 3.12 RPMs (e.g., replace
python3-devel/python3-pip entries whose sourcerpm starts with "python3.9-" or
"python-pip-" with the corresponding python3.12-devel/python3.12-pip RPMs and
correct repoid/checksum/evr), or remove them if they are only transitive
artifacts and must not be installed; after changing the lockfile, re-run the
provided verification script and grep the manifests/Dockerfiles (e.g.,
odh/rpms.in.yaml, Dockerfile.cpu, Dockerfile.konflux.cpu) to ensure no
references remain to python3-devel -> python3.9 or unversioned python3-pip so
native extensions will use Python 3.12 headers.

In `@jupyter/pytorch`+llmcompressor/ubi9-python-3.12/uv.lock.d/pylock.cuda.toml:
- Line 2: The lock-generation command uses the flag
--index-strategy=unsafe-best-match which can cause cross-index package selection
when mixing CUDA and CPU indexes; update the invocation that builds
uv.lock.d/pylock.cuda.toml to use --index-strategy=first-index (or alternatively
split CUDA/CPU index sets) so the resolver prefers the primary CUDA index
deterministically; locate the command string containing
--index-strategy=unsafe-best-match and replace it with
--index-strategy=first-index in the pylock generation invocation.

In `@jupyter/rocm/pytorch/ubi9-python-3.12/uv.lock.d/pylock.rocm.toml`:
- Line 2: The commented pip-compile command in pylock.rocm.toml uses
--index-strategy=unsafe-best-match which enables cross-index version selection;
update that flag to --index-strategy=first-index in the commented command (the
long uv pip compile invocation) so the resolver uses the first index containing
a package instead of unsafe-best-match.

In `@tests/browser/tests/openshift_console.spec.ts`:
- Around line 14-16: The call to customApi.getNamespacedCustomObject is using an
options object but the `@kubernetes/client-node` API expects positional arguments;
update the invocation of getNamespacedCustomObject (where route is assigned) to
pass the five parameters in order: group, version, namespace, plural, name
(i.e., 'route.openshift.io', 'v1', 'openshift-console', 'routes', 'console')
instead of the object form so the function signature matches the client library.

In `@tests/browser/tsconfig.json`:
- Line 4: The tsconfig setting "target": "es2025" is incompatible with the
Playwright/base Node.js 22 runtime; update the tsconfig.json target to "es2024"
(replace the "target": "es2025" entry) so TypeScript type-checks against the
actual runtime, or alternatively update the CI/Playwright base image to one that
includes Node.js 24+; edit the "target" field in tests/browser/tsconfig.json
(the "target" property) to ensure it matches the chosen runtime.

---

Outside diff comments:
In `@jupyter/pytorch`+llmcompressor/ubi9-python-3.12/Dockerfile.konflux.cuda:
- Around line 191-196: The pip install invocation using the UV wrapper (the
command starting with "UV_NO_CACHE=true UV_LINK_MODE=copy
UV_PREVIEW_FEATURES=pylock uv pip install ... --no-verify-hashes") disables
package hash verification; either enable strict hash checking by replacing
"--no-verify-hashes" with "--require-hashes" (and ensure ./requirements.txt /
pylock.toml contains hashes) or, if Cachi2 prefetching prevents exact hashes,
add a clear comment above this UV pip install line explaining why hashes are
intentionally omitted (describe the Cachi2 renaming/reprocessing behavior) so
future maintainers understand the tradeoff.

In `@runtimes/datascience/ubi9-python-3.12/Dockerfile.konflux.cpu`:
- Around line 123-124: The Dockerfile currently fetches the Elyra bootstrapper
at build time via the RUN curl line (downloading bootstrapper.py), which
violates hermetic build rules; replace that network fetch by vendoring or
prefetching the file into the build context and change the Dockerfile to use
COPY/ADD to bring ./utils/bootstrapper.py into the image instead of the curl
command. Ensure the repository contains the exact bootstrapper.py (or a pinned
vendor snapshot) and update the Dockerfile location that referenced the
bootstrapper (the RUN curl line) to a COPY ./utils/bootstrapper.py
./utils/bootstrapper.py so the image build no longer requires external network
access.
- Around line 97-101: The Dockerfile currently fetches the OpenShift client with
the curl/tar sequence (curl ... -o /tmp/openshift-client-linux.tar.gz; tar -xzvf
/tmp/openshift-client-linux.tar.gz; rm -f /tmp/openshift-client-linux.tar.gz),
which breaks hermetic builds; replace that block by installing the prefetched
openshift-clients RPM (package name openshift-clients) via dnf (e.g., dnf
install -y /path/to/prefetched/openshift-clients-*.rpm or dnf install -y
openshift-clients if the manifests repo is enabled) and remove the curl/tar
lines so the Dockerfile uses the pre-fetched RPM artifact instead of downloading
from mirror.openshift.com.

In `@tests/browser/tests/utils.ts`:
- Around line 31-45: The loop inside the Promise schedules a repeating
setInterval on every invocation causing exponential intervals; change the
scheduling call in the loop function to use window.setTimeout(loop, checkPeriod)
(single-shot) so only one delayed callback is queued each iteration, keep the
existing observer.disconnect()/resolve()/reject() logic intact, and ensure the
initial loop() call still starts the cycle; reference the loop function and the
observer variable in tests/browser/tests/utils.ts when making this change.
- Around line 38-41: The timeout branch can call reject() after the promise was
already resolved by the stability check; update the check handling so you exit
immediately after resolving or rejecting: after the stability branch that calls
resolve() when (now - mutated > checkPeriod) and after the timeout branch that
calls reject() when (now - started > timeout), add immediate returns (ensuring
observer.disconnect() is still called before the return) so no further checks
run and the promise cannot be settled twice; look for the uses of resolve(),
reject(), observer.disconnect(), now - mutated > checkPeriod and now - started >
timeout to locate the exact spots to update.

---

Nitpick comments:
In `@jupyter/datascience/ubi9-python-3.12/Dockerfile.cpu`:
- Around line 29-32: The current Dockerfile sets GOCACHE, GOMODCACHE and GOPATH
to overlapping locations which can cause cache pollution; change GOCACHE to a
separate directory (e.g., /cachi2/output/deps/gocache) while leaving GOMODCACHE
as /cachi2/output/deps/gomod/pkg/mod and GOPATH as /cachi2/output/deps/gomod,
and update GOPROXY only if needed; modify the export of GOCACHE in the
Dockerfile.cpu so it points to a distinct path from GOPATH/GOMODCACHE to isolate
build cache from module cache.

In `@jupyter/datascience/ubi9-python-3.12/Dockerfile.konflux.cpu`:
- Around line 52-53: The Dockerfile duplicates GPG key imports with a
silent-failure "|| true" (seen in the RUN lines importing
/cachi2/output/deps/generic/RPM-GPG-KEY-CentOS-Official and
/etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release and again at lines ~95-97); extract
these import commands into a small reusable script (e.g., import-gpg-keys.sh)
and call it from each stage, and remove the blanket "|| true" so the script
performs explicit checks and logs or exits on failure (return non-zero) to
surface errors instead of silencing them. Ensure the script idempotently checks
for an existing key before importing to avoid duplicate failures.
- Around line 1-187: The Dockerfiles are nearly identical except for LABEL
metadata; consolidate into one Dockerfile and drive variant-specific LABEL
values via a build-arg (e.g., BUILD_VARIANT or REGISTRY_TARGET). Edit the
existing Dockerfile.konflux.cpu/jupyter-datascience stage: introduce ARG
BUILD_VARIANT (or REGISTRY_TARGET) near the top, replace the hard-coded LABEL
block in the jupyter-datascience stage with conditional value selection based on
that arg (set descriptive name, component, display-name, summary, description,
license_terms) and default to the current konflux values; ensure Docker build
invocation can pass the alternative variant to produce the other image. Keep all
hermetic LOCAL_BUILD logic, mongocli-builder and stage names (mongocli-builder,
cpu-base, jupyter-minimal, jupyter-datascience) unchanged so only LABELs are
parameterized.

In `@jupyter/minimal/ubi9-python-3.12/prefetch-input/odh/rpms.lock.yaml`:
- Around line 28-41: The rpms.lock.yaml adds build-toolchain packages (autoconf,
automake, cmake, libtool, m4, openblas-devel, pybind11-devel, gcc runtime libs)
which increase the attack surface of the "minimal" Jupyter image; either move
these packages out of the final runtime package list into a separate
build-stage/multi-stage image (so they are installed only during build and not
present in the final image) or, if they are required at runtime for
pip-installing native extensions, explicitly document that requirement in the
repo (e.g., in README or image documentation) and mark these entries as
build-time dependencies—update the entries for autoconf, automake, cmake,
libtool, m4, openblas-devel, pybind11-devel, and gcc-related packages
accordingly across the referenced sections (around lines 2240-2351) to reflect
the chosen approach.

In `@Makefile`:
- Around line 144-145: The Makefile sets GONOSUMDB when invoking go build for
the toolchain (GOTOOLCHAIN=auto) but GONOSUMDB is ignored for
golang.org/toolchain downloads and is misleading; remove the
GONOSUMDB=golang.org/toolchain assignment from the go build invocation (the line
that exports/uses GOTOOLCHAIN=auto and runs go build -C "scripts/buildinputs" -o
"$(ROOT_DIR)/$@" ./...), and if you need to support environments with
GOSUMDB=off, add an explicit guard or documented check that handles GOSUMDB
rather than relying on GONOSUMDB.

In `@runtimes/datascience/ubi9-python-3.12/Dockerfile.cpu`:
- Line 91: The RUN pip install line currently pins "uv==0.10.8" causing version
drift; update that package pin in the RUN command (the pip install statement
that includes "micropipenv[toml]==1.10.0" and "uv==0.10.8") to match the Konflux
Dockerfiles by changing "uv==0.10.8" to "uv==0.11.0" so all builds use the same
uv version.

In `@tests/browser/scripts/start_browser.ts`:
- Around line 62-70: The loop contains redundant null checks for the local
variable page (declared as Page | undefined) and for context?.pages()[0], which
can only be undefined; remove the === null checks and simplify the conditions to
check only undefined (e.g., use page === undefined or page !== undefined) and
likewise rely on optional chaining result being undefined, updating the while
condition and the if that returns page (references: page, started, timeout,
browser.contexts(), context?.pages()[0]).

In `@tests/browser/tests/codeserver.spec.ts`:
- Around line 40-46: Remove the unnecessary optional chaining on
codeServerSource (it is always provided) and replace the verbose IIFE throw with
a simple guard: in the codeServer fixture block, change "if
(codeServerSource?.url)" to "if (codeServerSource.url)" and replace the "const
image = codeServerSource.image ?? (() => { throw new Error('invalid config:
codeserver image not specified') })()" pattern with "const image =
codeServerSource.image; if (!image) throw new Error('invalid config: codeserver
image not specified')", keeping the rest of the logic using new CodeServer(page,
codeServerSource.url) intact.
- Around line 16-22: The MyFixtures type is duplicated; extract it into a single
shared types module named types.ts that exports the MyFixtures type (keeping the
connectCDP and codeServerSource union but removing the per-test-only codeServer
property), then remove the local MyFixtures definitions in both files and import
the shared type (import type { MyFixtures } from './types') in each place where
MyFixtures was previously declared so both tests reference the single canonical
type.

In `@tests/browser/tests/utils.ts`:
- Around line 13-14: Replace the function-scoped declarations using "var" for
the timing variables with block-scoped "let": change "var started =
performance.now();" and "var mutated = started;" to use "let" so that started
and mutated are block-scoped (update both declarations in
tests/browser/tests/utils.ts where these variables are defined).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited), Repository UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: d59c8083-5a7e-461d-8ad4-da1ee75f0374

📥 Commits

Reviewing files that changed from the base of the PR and between 992b39e and e524f1d.

⛔ Files ignored due to path filters (2)
  • scripts/buildinputs/go.sum is excluded by !**/*.sum, !**/*.sum
  • tests/browser/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (74)
  • .tekton/odh-workbench-jupyter-datascience-cpu-py312-ubi9-odh-main-pull-request.yaml
  • .tekton/odh-workbench-jupyter-datascience-cpu-py312-ubi9-odh-main-push.yaml
  • CONTRIBUTING.md
  • Makefile
  • codeserver/ubi9-python-3.12/uv.lock.d/pylock.cpu.toml
  • dependencies/odh-notebooks-meta-db-connectors-deps/pyproject.toml
  • dependencies/odh-notebooks-meta-jupyterlab-datascience-deps/pyproject.toml
  • dependencies/odh-notebooks-meta-jupyterlab-deps/pyproject.toml
  • jupyter/datascience/ubi9-python-3.12/Dockerfile.cpu
  • jupyter/datascience/ubi9-python-3.12/Dockerfile.konflux.cpu
  • jupyter/datascience/ubi9-python-3.12/prefetch-input
  • jupyter/datascience/ubi9-python-3.12/pyproject.toml
  • jupyter/datascience/ubi9-python-3.12/requirements.cpu.txt
  • jupyter/datascience/ubi9-python-3.12/uv.lock.d/pylock.cpu.toml
  • jupyter/minimal/ubi9-python-3.12/prefetch-input/odh/rpms.in.yaml
  • jupyter/minimal/ubi9-python-3.12/prefetch-input/odh/rpms.lock.yaml
  • jupyter/minimal/ubi9-python-3.12/prefetch-input/rhds/rpms.in.yaml
  • jupyter/minimal/ubi9-python-3.12/prefetch-input/rhds/rpms.lock.yaml
  • jupyter/minimal/ubi9-python-3.12/pyproject.toml
  • jupyter/minimal/ubi9-python-3.12/uv.lock.d/pylock.cpu.toml
  • jupyter/minimal/ubi9-python-3.12/uv.lock.d/pylock.cuda.toml
  • jupyter/minimal/ubi9-python-3.12/uv.lock.d/pylock.rocm.toml
  • jupyter/pytorch+llmcompressor/ubi9-python-3.12/Dockerfile.cuda
  • jupyter/pytorch+llmcompressor/ubi9-python-3.12/Dockerfile.konflux.cuda
  • jupyter/pytorch+llmcompressor/ubi9-python-3.12/prefetch-input/odh/rpms.in.yaml
  • jupyter/pytorch+llmcompressor/ubi9-python-3.12/prefetch-input/odh/rpms.lock.yaml
  • jupyter/pytorch+llmcompressor/ubi9-python-3.12/prefetch-input/rhds/rpms.in.yaml
  • jupyter/pytorch+llmcompressor/ubi9-python-3.12/prefetch-input/rhds/rpms.lock.yaml
  • jupyter/pytorch+llmcompressor/ubi9-python-3.12/pyproject.toml
  • jupyter/pytorch+llmcompressor/ubi9-python-3.12/requirements.cuda.txt
  • jupyter/pytorch+llmcompressor/ubi9-python-3.12/uv.lock.d/pylock.cuda.toml
  • jupyter/pytorch/ubi9-python-3.12/pyproject.toml
  • jupyter/pytorch/ubi9-python-3.12/uv.lock.d/pylock.cuda.toml
  • jupyter/rocm/pytorch/ubi9-python-3.12/pyproject.toml
  • jupyter/rocm/pytorch/ubi9-python-3.12/uv.lock.d/pylock.rocm.toml
  • jupyter/rocm/tensorflow/ubi9-python-3.12/pylock.toml
  • jupyter/rocm/tensorflow/ubi9-python-3.12/pyproject.toml
  • jupyter/tensorflow/ubi9-python-3.12/pyproject.toml
  • jupyter/tensorflow/ubi9-python-3.12/uv.lock.d/pylock.cuda.toml
  • jupyter/trustyai/ubi9-python-3.12/pyproject.toml
  • jupyter/trustyai/ubi9-python-3.12/uv.lock.d/pylock.cpu.toml
  • pytest.ini
  • rstudio/c9s-python-3.12/uv.lock.d/pylock.cpu.toml
  • rstudio/c9s-python-3.12/uv.lock.d/pylock.cuda.toml
  • rstudio/rhel9-python-3.12/uv.lock.d/pylock.cpu.toml
  • rstudio/rhel9-python-3.12/uv.lock.d/pylock.cuda.toml
  • runtimes/datascience/ubi9-python-3.12/Dockerfile.cpu
  • runtimes/datascience/ubi9-python-3.12/Dockerfile.konflux.cpu
  • runtimes/datascience/ubi9-python-3.12/pyproject.toml
  • runtimes/datascience/ubi9-python-3.12/uv.lock.d/pylock.cpu.toml
  • runtimes/minimal/ubi9-python-3.12/uv.lock.d/pylock.cpu.toml
  • runtimes/pytorch+llmcompressor/ubi9-python-3.12/pyproject.toml
  • runtimes/pytorch+llmcompressor/ubi9-python-3.12/uv.lock.d/pylock.cuda.toml
  • runtimes/pytorch/ubi9-python-3.12/pyproject.toml
  • runtimes/pytorch/ubi9-python-3.12/uv.lock.d/pylock.cuda.toml
  • runtimes/rocm-pytorch/ubi9-python-3.12/pyproject.toml
  • runtimes/rocm-pytorch/ubi9-python-3.12/uv.lock.d/pylock.rocm.toml
  • runtimes/rocm-tensorflow/ubi9-python-3.12/pylock.toml
  • runtimes/rocm-tensorflow/ubi9-python-3.12/pyproject.toml
  • runtimes/tensorflow/ubi9-python-3.12/pyproject.toml
  • runtimes/tensorflow/ubi9-python-3.12/uv.lock.d/pylock.cuda.toml
  • scripts/buildinputs/dockerfile.go
  • scripts/buildinputs/go.mod
  • scripts/check-payload/go.mod
  • scripts/pylocks_generator.py
  • tests/browser/package.json5
  • tests/browser/playwright.config.ts
  • tests/browser/scripts/start_browser.ts
  • tests/browser/tests/codeserver.spec.ts
  • tests/browser/tests/openshift_console.spec.ts
  • tests/browser/tests/utils.ts
  • tests/browser/tsconfig.json
  • tests/conftest.py
  • tests/containers/runtimes/runtime_test.py
👮 Files not reviewed due to content moderation or server errors (2)
  • runtimes/rocm-tensorflow/ubi9-python-3.12/pylock.toml
  • jupyter/rocm/tensorflow/ubi9-python-3.12/pylock.toml

pipelinesascode.tekton.dev/on-comment: ^/kfbuild\s+(all|odh\-workbench\-jupyter\-datascience\-cpu\-py312\-ubi9|jupyter/datascience/ubi9\-python\-3\.12)
pipelinesascode.tekton.dev/on-cel-expression: |
event == "pull_request" && target_branch == "main" && !("manifests/base/params-latest.env".pathChanged()) && ( ".tekton/odh-workbench-jupyter-datascience-cpu-py312-ubi9-odh-main-pull-request.yaml".pathChanged() || "jupyter/utils/**".pathChanged() || "jupyter/datascience/ubi9-python-3.12/**".pathChanged() || "jupyter/datascience/ubi9-python-3.12/start-notebook.sh".pathChanged() || "jupyter/datascience/ubi9-python-3.12/build-args/cpu.conf".pathChanged() )
event == "pull_request" && target_branch == "main" && !("manifests/base/params-latest.env".pathChanged()) && ( ".tekton/odh-workbench-jupyter-datascience-cpu-py312-ubi9-odh-main-pull-request.yaml".pathChanged() || "jupyter/utils/**".pathChanged() || "jupyter/datascience/ubi9-python-3.12/**".pathChanged() || "jupyter/datascience/ubi9-python-3.12/start-notebook.sh".pathChanged() || "jupyter/datascience/ubi9-python-3.12/build-args/cpu.conf".pathChanged() || "jupyter/minimal/ubi9-python-3.12/prefetch-input/odh/**".pathChanged() || "jupyter/pytorch+llmcompressor/ubi9-python-3.12/prefetch-input/mongocli/**".pathChanged() )

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add the new shared meta-project paths to this PR trigger.

The datascience image now consumes local dependencies/odh-notebooks-meta-{db-connectors,jupyterlab,jupyterlab-datascience}-deps projects, but this PR CEL filter still ignores those paths. A PR that only changes one of those shared inputs will not run this PipelineRun for the image that depends on it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
@.tekton/odh-workbench-jupyter-datascience-cpu-py312-ubi9-odh-main-pull-request.yaml
at line 16, The CEL filter in the PR trigger is missing the shared meta-project
paths used by the datascience image; update the condition inside
".tekton/odh-workbench-jupyter-datascience-cpu-py312-ubi9-odh-main-pull-request.yaml"
so the OR list includes .pathChanged() checks for the three new projects:
"dependencies/odh-notebooks-meta-db-connectors-deps",
"dependencies/odh-notebooks-meta-jupyterlab-deps", and
"dependencies/odh-notebooks-meta-jupyterlab-datascience-deps" (add each as e.g.
"dependencies/odh-notebooks-meta-db-connectors-deps".pathChanged()) so a PR
touching any of those shared inputs will trigger the PipelineRun.

pipelinesascode.tekton.dev/max-keep-runs: '3'
build.appstudio.openshift.io/build-nudge-files: manifests/base/params-latest.env
pipelinesascode.tekton.dev/on-cel-expression: event == "push" && target_branch == "main" && !("manifests/base/params-latest.env".pathChanged()) && ( ".tekton/odh-workbench-jupyter-datascience-cpu-py312-ubi9-odh-main-push.yaml".pathChanged() || "jupyter/utils/**".pathChanged() || "jupyter/datascience/ubi9-python-3.12/**".pathChanged() || "jupyter/datascience/ubi9-python-3.12/start-notebook.sh".pathChanged() || "jupyter/datascience/ubi9-python-3.12/build-args/cpu.conf".pathChanged() )
pipelinesascode.tekton.dev/on-cel-expression: event == "push" && target_branch == "main" && !("manifests/base/params-latest.env".pathChanged()) && ( ".tekton/odh-workbench-jupyter-datascience-cpu-py312-ubi9-odh-main-push.yaml".pathChanged() || "jupyter/utils/**".pathChanged() || "jupyter/datascience/ubi9-python-3.12/**".pathChanged() || "jupyter/datascience/ubi9-python-3.12/start-notebook.sh".pathChanged() || "jupyter/datascience/ubi9-python-3.12/build-args/cpu.conf".pathChanged() || "jupyter/minimal/ubi9-python-3.12/prefetch-input/odh/**".pathChanged() || "jupyter/pytorch+llmcompressor/ubi9-python-3.12/prefetch-input/mongocli/**".pathChanged() )

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add the new shared meta-project paths to this trigger.

jupyter/datascience/ubi9-python-3.12/pyproject.toml now resolves through local dependencies/odh-notebooks-meta-{db-connectors,jupyterlab,jupyterlab-datascience}-deps projects, but this CEL filter still ignores those directories. If one of those shared projects changes without a matching edit under jupyter/datascience/ubi9-python-3.12/**, this PipelineRun never executes for the consuming image.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.tekton/odh-workbench-jupyter-datascience-cpu-py312-ubi9-odh-main-push.yaml
at line 11, The trigger's CEL expression for
pipelinesascode.tekton.dev/on-cel-expression omits the shared meta-project
directories that now affect jupyter/datascience/ubi9-python-3.12/pyproject.toml,
so add OR conditions to the existing pathChanged() list to include the
dependency projects (e.g.,
"dependencies/odh-notebooks-meta-db-connectors-deps".pathChanged(),
"dependencies/odh-notebooks-meta-jupyterlab-deps".pathChanged(), and
"dependencies/odh-notebooks-meta-jupyterlab-datascience-deps".pathChanged()) so
changes in those shared projects will also cause the PipelineRun to execute;
update the expression that currently lists the
jupyter/datascience/ubi9-python-3.12/** paths under the same OR group.

Comment on lines +52 to +53
RUN rpm --import /cachi2/output/deps/generic/RPM-GPG-KEY-CentOS-Official || true
RUN rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Silent failure on GPG key import may mask integrity issues.

|| true suppresses errors if the CentOS GPG key is missing or corrupt. This could allow unsigned packages to be installed without detection. Consider logging a warning or failing explicitly in production builds.

Proposed fix
-RUN rpm --import /cachi2/output/deps/generic/RPM-GPG-KEY-CentOS-Official || true
+RUN rpm --import /cachi2/output/deps/generic/RPM-GPG-KEY-CentOS-Official || echo "Warning: CentOS GPG key not found, skipping"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
RUN rpm --import /cachi2/output/deps/generic/RPM-GPG-KEY-CentOS-Official || true
RUN rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release
RUN rpm --import /cachi2/output/deps/generic/RPM-GPG-KEY-CentOS-Official || echo "Warning: CentOS GPG key not found, skipping"
RUN rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jupyter/datascience/ubi9-python-3.12/Dockerfile.cpu` around lines 52 - 53,
The RUN command that imports the CentOS GPG key ("RUN rpm --import
/cachi2/output/deps/generic/RPM-GPG-KEY-CentOS-Official || true") is silencing
failures with "|| true"; remove the silent suppression and instead ensure the
build fails or logs a clear warning when the key is missing or invalid. Replace
the silent suppression by checking the key import result for the "rpm --import
/cachi2/output/deps/generic/RPM-GPG-KEY-CentOS-Official" step (or test for file
existence first) and emit a descriptive error/warning or exit non‑zero so
unsigned packages are not allowed; keep the second import ("RUN rpm --import
/etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release") consistent with this behavior.

Comment on lines +1874 to +1883
[[packages]]
name = "uv"
version = "0.11.0"
marker = "implementation_name == 'cpython' and sys_platform == 'linux'"
wheels = [
{ url = "https://packages.redhat.com/api/pulp-content/public-rhai/rhoai/3.4/cpu-ubi9/uv-0.11.0-2-py3-none-linux_aarch64.whl", upload-time = 2026-03-24T11:53:57Z, size = 22442657, hashes = { sha256 = "f902f3d954de9e0e39c7eeb058c2da250c7d809b173130ff087bfe99a137edb2" } },
{ url = "https://packages.redhat.com/api/pulp-content/public-rhai/rhoai/3.4/cpu-ubi9/uv-0.11.0-2-py3-none-linux_s390x.whl", upload-time = 2026-03-24T12:35:51Z, size = 27179975, hashes = { sha256 = "a259a9095423a02688578766e27ea749eb675f3f5ca762468384413e2fd8393e" } },
{ url = "https://packages.redhat.com/api/pulp-content/public-rhai/rhoai/3.4/cpu-ubi9/uv-0.11.0-2-py3-none-linux_x86_64.whl", upload-time = 2026-03-24T11:54:56Z, size = 23969786, hashes = { sha256 = "85055bffcaef68f91a7965d2ad014e0557107e452cc432dcd855c715084f6715" } },
]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify lock intent vs uv wheel coverage in this file.
# Expected: if ppc64le is a target in this lock, uv should either provide ppc64le wheel
# or its marker should explicitly exclude ppc64le.

python - <<'PY'
from pathlib import Path
import re

p = Path("jupyter/datascience/ubi9-python-3.12/uv.lock.d/pylock.cpu.toml")
txt = p.read_text()

# Check if lock appears multi-arch (ppc64le wheels present at all)
multi_arch_ppc = "linux_ppc64le.whl" in txt

# Extract uv package block
m = re.search(r"\[\[packages\]\]\nname = \"uv\".*?(?=\n\[\[packages\]\]|\Z)", txt, re.S)
if not m:
    print("uv package block not found")
    raise SystemExit(1)

block = m.group(0)
has_uv_ppc = "linux_ppc64le.whl" in block
marker = re.search(r"marker = \"([^\"]+)\"", block)
marker = marker.group(1) if marker else "<missing>"

print("lock_has_ppc64le_anywhere:", multi_arch_ppc)
print("uv_has_ppc64le_wheel:", has_uv_ppc)
print("uv_marker:", marker)

if multi_arch_ppc and (not has_uv_ppc) and ("ppc64le" not in marker):
    print("FAIL: uv marker is too broad for declared wheel set.")
    raise SystemExit(2)

print("PASS")
PY

Repository: opendatahub-io/notebooks

Length of output: 252


Constrain uv marker to supported arches or add missing wheel coverage

Line 1877 declares uv for all Linux CPython targets, but wheels are provided only for linux_aarch64, linux_s390x, and linux_x86_64 (Lines 1879–1881). Since the lock file includes linux_ppc64le wheels for other packages, installation on ppc64le will fail during dependency resolution.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jupyter/datascience/ubi9-python-3.12/uv.lock.d/pylock.cpu.toml` around lines
1874 - 1883, The "uv" package entry declares a broad marker (implementation_name
== 'cpython' and sys_platform == 'linux') but only supplies wheels for
linux_aarch64, linux_s390x and linux_x86_64; narrow the marker for the uv
package or add the missing ppc64le wheel: either change the uv [[packages]]
marker to explicitly restrict architectures (e.g. include platform_machine in
("x86_64","aarch64","s390x")) so the resolver won't attempt ppc64le, or add a
wheels entry for linux_ppc64le with the correct URL, upload-time, size and
sha256 hash to match the other wheel entries. Ensure you update the package
block for name = "uv" and its wheels list accordingly.

Comment on lines +1428 to +1434
- url: https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/aarch64/appstream/os/Packages/p/python3-pip-21.3.1-1.el9.noarch.rpm
repoid: ubi-9-for-aarch64-appstream-rpms
size: 2133958
checksum: sha256:2ff41d5bbfb5bf09378a499b56d9854e9389e3a8648897426d144f4b385f8730
name: python3-pip
evr: 21.3.1-1.el9
sourcerpm: python-pip-21.3.1-1.el9.src.rpm

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check pip security advisories
gh api graphql -f query='
{
  securityVulnerabilities(first: 10, ecosystem: PIP, package: "pip") {
    nodes {
      advisory {
        summary
        severity
        publishedAt
        identifiers {
          type
          value
        }
      }
      vulnerableVersionRange
      firstPatchedVersion {
        identifier
      }
    }
  }
}'

Repository: opendatahub-io/notebooks

Length of output: 228


🌐 Web query:

CVE-2023-5752 pip wheel command injection vulnerability

💡 Result:

CVE-2023-5752 is a command injection vulnerability in pip versions prior to 23.3. It affects users installing packages from Mercurial VCS URLs (e.g., "pip install hg+..."). The specified Mercurial revision can inject arbitrary "--config" options into the "hg clone" command, allowing control over the repository cloned and installed. This does not impact users not using Mercurial installs. CVSS v3.1 base scores: NIST 3.3 (Low, AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N); PSF 5.5 (Medium, AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N). Fixed in pip 23.3 via pypa/pip#12306. Upgrade to pip 23.3 or later. Official advisory: https://mail.python.org/archives/list/security-announce@python.org/thread/F4PL35U6X4VVHZ5ILJU3PWUWN7H7LZXL/. No public exploits known; low real-world risk as Mercurial usage is rare.

Citations:


🏁 Script executed:

#!/bin/bash
# Get context about the lock file and check if RHEL packages mention backported patches
head -20 jupyter/minimal/ubi9-python-3.12/prefetch-input/odh/rpms.lock.yaml

Repository: opendatahub-io/notebooks

Length of output: 988


🏁 Script executed:

#!/bin/bash
# Search for any documentation about this lock file and its purpose
find . -type f \( -name "*.md" -o -name "*.txt" -o -name "README*" \) | xargs grep -l "rpms.lock" 2>/dev/null | head -5

Repository: opendatahub-io/notebooks

Length of output: 172


🌐 Web query:

RHEL 9 python3-pip 21.3.1 security patches backport CVE

💡 Result:

In RHEL 9, the python3-pip package provides pip version 21.3.1, which is backpatched with security fixes following Red Hat's standard backporting practice for maintaining older versions with isolated security patches. Confirmed security backports include RHSA-2023:6694 for CVE-2007-4559 (tarfile directory traversal vulnerability), where Red Hat backported the fix to the pip 21.x series used in RHEL 9. The RHBA-2024:9321 advisory confirms python3-pip remains at version 21.3.1-1.el9 as of RHEL 9.5. Amazon Linux 2023 (RHEL 9 derivative) also backported CVE-2025-8869 to python3-pip 21.3.1-2.amzn2023.0.14, demonstrating the approach for this version. Red Hat does not upgrade pip to newer upstream versions to preserve stability and compatibility; instead, they backport fixes as needed throughout the RHEL 9 lifecycle (until May 2032). No comprehensive list of all backported CVEs specific to python3-pip 21.3.1 was found in public errata up to 2026-04-01, but users should apply all relevant RHSA updates via dnf update python3-pip to ensure the latest backports are installed. Check https://access.redhat.com/errata/search/?q=python3-pip for the latest advisories.

Citations:


🏁 Script executed:

#!/bin/bash
# Check the lock file's README to understand its purpose and update mechanism
cat scripts/lockfile-generators/README.md

Repository: opendatahub-io/notebooks

Length of output: 39644


🌐 Web query:

RHEL 9 python3-pip CVE-2023-5752 RHSA errata backport fix

💡 Result:

No, there is no RHSA errata providing a backport fix for CVE-2023-5752 in the RHEL 9 python3-pip package. The CVE is tracked in Red Hat Bugzilla (ID 2250765) with status NEW (as of 2025-09-15), indicating it remains unaddressed in RHEL products. It is fixed upstream in pip 23.3 and backported in Fedora (e.g., python-pip-23.3.2), but no specific RHSA for RHEL 9 python3-pip addresses this CVE. RHSA-2024:3781 addresses it in Red Hat Ansible Automation Platform 2.4 for RHEL 9 (BZ 2250765 listed in fixes), but not the base OS python3-pip package. As of 2026-04-01, RHEL 9 users should avoid installing packages from Mercurial VCS URLs with pip or upgrade pip via other means if possible, pending an official fix.

Citations:


CVE-2023-5752 is unfixed in RHEL 9's python3-pip 21.3.1; verify necessity of Mercurial VCS installs.

CVE-2023-5752 (Mercurial VCS command injection, not wheel injection as originally stated) affects pip <23.3 and remains unpatched in RHEL 9 (Bugzilla 2250765: status NEW). However, the attack surface is narrow: only users installing packages directly from Mercurial URLs (e.g., pip install hg+...) are at risk, and no public exploits exist (CVSS 3.3 Low).

This lock file is RHEL-vendor-controlled; pip 21.3.1 is intentionally maintained via selective backports for stability. If Mercurial installs are not used in your build or downstream, this is a low-risk issue. If they are needed, consider evaluating whether RHEL 9.6+ includes a future backport or if an alternate package source can mitigate the risk.

Also applies to: 6479-6485, 11558-11564, 16602-16608

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jupyter/minimal/ubi9-python-3.12/prefetch-input/odh/rpms.lock.yaml` around
lines 1428 - 1434, The python3-pip entry (name: python3-pip, evr: 21.3.1-1.el9)
in the rpms.lock.yaml is vulnerable to CVE-2023-5752 for Mercurial (hg+)
installs; verify whether your build or downstreams actually perform Mercurial
VCS installs and, if they do, either (a) replace or override this package with a
non-vulnerable pip (version >=23.3 or vendor-supplied RHEL backport when
available) or (b) document and mitigate the risk by disabling VCS installs or
blocking hg+ URLs in your pipelines; update the lock entry or add a clear
comment/mitigation note referencing python3-pip 21.3.1 and CVE-2023-5752
accordingly.

Comment on lines +17547 to +17560
- url: https://cdn.redhat.com/content/dist/rhel9/9.6/x86_64/appstream/os/Packages/p/python3-devel-3.9.21-2.el9_6.2.x86_64.rpm
repoid: rhel-9-for-x86_64-appstream-rpms
size: 249990
checksum: sha256:11f8ba100e932d42a3204b0ee08c51fbdbc5b9434df07ebb156e369ea2979df4
name: python3-devel
evr: 3.9.21-2.el9_6.2
sourcerpm: python3.9-3.9.21-2.el9_6.2.src.rpm
- url: https://cdn.redhat.com/content/dist/rhel9/9.6/x86_64/appstream/os/Packages/p/python3-pip-21.3.1-1.el9.noarch.rpm
repoid: rhel-9-for-x86_64-appstream-rpms
size: 2133958
checksum: sha256:2ff41d5bbfb5bf09378a499b56d9854e9389e3a8648897426d144f4b385f8730
name: python3-pip
evr: 21.3.1-1.el9
sourcerpm: python-pip-21.3.1-1.el9.src.rpm

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don’t pin the unversioned Python 3.9 RPM stack in a ubi9-python-3.12 lockfile.

This variant is for Python 3.12, but these additions resolve to the unversioned RHEL 9 Python stack, including python3-devel from python3.9-3.9.21-2.el9_6.2.src.rpm. If these RPMs are installed during build, native extensions and tooling can pick up 3.9 headers/site-packages instead of 3.12. Use version-matched 3.12 RPMs, or verify these are only prefetched transitive artifacts that never get installed.

Run this to confirm the lock is pulling 3.9 RPMs and to inspect the related input manifests/Dockerfiles for versioned 3.12 package usage:

#!/bin/bash
set -euo pipefail

python - <<'PY'
from pathlib import Path

path = Path("jupyter/minimal/ubi9-python-3.12/prefetch-input/rhds/rpms.lock.yaml")
targets = {
    "name: python3-devel",
    "name: python3-pip",
    "name: python3-setuptools",
}

print("lockfile hits:")
text = path.read_text().splitlines()
for lineno, raw in enumerate(text, 1):
    line = raw.strip()
    if (
        line in targets
        or line.startswith("sourcerpm: python3.9-")
        or line.startswith("sourcerpm: python-pip-")
        or line.startswith("sourcerpm: python-setuptools-")
    ):
        print(f"{lineno}: {line}")

full = "\n".join(text)
print(f"\ncontains python3.12-devel in lockfile: {'python3.12-devel' in full}")
PY

for p in \
  "jupyter/minimal/ubi9-python-3.12/prefetch-input/odh/rpms.in.yaml" \
  "runtimes/datascience/ubi9-python-3.12/Dockerfile.cpu" \
  "jupyter/pytorch+llmcompressor/ubi9-python-3.12/Dockerfile.konflux.cpu"
do
  if [ -f "$p" ]; then
    echo "== $p =="
    rg -n -C2 'python3-devel|python3\.12-devel|python3-pip|python3\.12-pip|python3-setuptools|python3\.12-setuptools|pybind11-devel' "$p" || true
  fi
done

Also applies to: 19934-19940

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jupyter/minimal/ubi9-python-3.12/prefetch-input/rhds/rpms.lock.yaml` around
lines 17547 - 17560, The lockfile pins unversioned RHEL 9 Python packages
(entries named python3-devel and python3-pip with sourcerpm python3.9-...) into
the ubi9-python-3.12 variant; update the rpms.lock.yaml entries so they
reference version-matched Python 3.12 RPMs (e.g., replace
python3-devel/python3-pip entries whose sourcerpm starts with "python3.9-" or
"python-pip-" with the corresponding python3.12-devel/python3.12-pip RPMs and
correct repoid/checksum/evr), or remove them if they are only transitive
artifacts and must not be installed; after changing the lockfile, re-run the
provided verification script and grep the manifests/Dockerfiles (e.g.,
odh/rpms.in.yaml, Dockerfile.cpu, Dockerfile.konflux.cpu) to ensure no
references remain to python3-devel -> python3.9 or unversioned python3-pip so
native extensions will use Python 3.12 headers.

@@ -1,5 +1,5 @@
# This file was autogenerated by uv via the following command:
# uv pip compile pyproject.toml --output-file uv.lock.d/pylock.cuda.toml --format pylock.toml --generate-hashes --emit-index-url --python-version=3.12 --universal --no-annotate --no-emit-package odh-notebooks-meta-llmcompressor-deps --no-emit-package odh-notebooks-meta-runtime-elyra-deps --no-emit-package odh-notebooks-meta-runtime-datascience-deps --no-emit-package odh-notebooks-meta-workbench-datascience-deps --constraints ../../../dependencies/cve-constraints.txt --exclude-newer=2026-03-31T09:07:59Z --default-index=https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4/cuda12.9-ubi9/simple/?format=json --index=https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4/cuda12.9-ubi9/simple/?format=json --index=https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4/cpu-ubi9/simple/?format=json --index-strategy=unsafe-best-match
# uv pip compile pyproject.toml --output-file uv.lock.d/pylock.cuda.toml --format pylock.toml --generate-hashes --emit-index-url --python-version=3.12 --universal --no-annotate --no-emit-package odh-notebooks-meta-db-connectors-deps --no-emit-package odh-notebooks-meta-jupyterlab-datascience-deps --no-emit-package odh-notebooks-meta-jupyterlab-deps --no-emit-package odh-notebooks-meta-llmcompressor-deps --no-emit-package odh-notebooks-meta-runtime-elyra-deps --no-emit-package odh-notebooks-meta-runtime-datascience-deps --no-emit-package odh-notebooks-meta-workbench-datascience-deps --constraints ../../../dependencies/cve-constraints.txt --exclude-newer=2026-03-31T14:27:32Z --default-index=https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4/cuda12.9-ubi9/simple/?format=json --index=https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4/cuda12.9-ubi9/simple/?format=json --index=https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4/cpu-ubi9/simple/?format=json --index-strategy=unsafe-best-match

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Find CUDA lockfiles that still use unsafe index strategy
rg -n --fixed-strings -- '--index-strategy=unsafe-best-match' '**/uv.lock.d/pylock.cuda.toml' || true

# 2) Detect whether any CUDA lock actually resolved packages from cpu index
rg -n '/cpu-ubi9/' '**/uv.lock.d/pylock.cuda.toml' || true

# Expected:
# - No unsafe-best-match in CUDA lock generation commands.
# - No /cpu-ubi9/ package URLs in CUDA locks.

Repository: opendatahub-io/notebooks

Length of output: 207


🏁 Script executed:

#!/bin/bash

# Find actual lock file paths
fd -t f "pylock.cuda.toml" || echo "Pattern: no pylock.cuda.toml found"

# Also check the directory structure
fd -t d "uv.lock.d" | head -20

# Check if the directory mentioned exists
[ -d "jupyter/pytorch+llmcompressor/ubi9-python-3.12/uv.lock.d" ] && echo "DIR EXISTS" || echo "DIR DOES NOT EXIST"

Repository: opendatahub-io/notebooks

Length of output: 1381


🏁 Script executed:

#!/bin/bash

# Read the first 5 lines of the specific file to see line 2
echo "=== Target file line 2 ==="
head -n 5 jupyter/pytorch+llmcompressor/ubi9-python-3.12/uv.lock.d/pylock.cuda.toml | cat -n

# Search for unsafe-best-match in ALL cuda lock files
echo -e "\n=== Searching for unsafe-best-match in CUDA locks ==="
rg -l 'unsafe-best-match' '**/uv.lock.d/pylock.cuda.toml' || echo "No matches found"

# Count how many CUDA locks have this pattern
echo -e "\n=== Count of unsafe-best-match in CUDA locks ==="
rg 'unsafe-best-match' '**/uv.lock.d/pylock.cuda.toml' 2>/dev/null | wc -l

# Search for cpu-ubi9 references in the target CUDA lock
echo -e "\n=== Checking for cpu-ubi9 references in target CUDA lock ==="
rg '/cpu-ubi9/' jupyter/pytorch+llmcompressor/ubi9-python-3.12/uv.lock.d/pylock.cuda.toml | head -5 || echo "No cpu-ubi9 found"

Repository: opendatahub-io/notebooks

Length of output: 2577


🏁 Script executed:

#!/bin/bash

# Check if any actual package URLs in the lock file use /cpu-ubi9/
echo "=== Actual packages from cpu-ubi9 in target CUDA lock ==="
rg '/cpu-ubi9/' jupyter/pytorch+llmcompressor/ubi9-python-3.12/uv.lock.d/pylock.cuda.toml --invert-match '^\s*#' | head -10 || echo "No packages from cpu-ubi9 index found"

# Get line count of the file
wc -l jupyter/pytorch+llmcompressor/ubi9-python-3.12/uv.lock.d/pylock.cuda.toml

# Count packages from each index
echo -e "\n=== Package sources in CUDA lock ==="
echo "CUDA packages:"
rg 'url = "https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4/cuda' jupyter/pytorch+llmcompressor/ubi9-python-3.12/uv.lock.d/pylock.cuda.toml | wc -l
echo "CPU packages:"
rg 'url = "https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4/cpu' jupyter/pytorch+llmcompressor/ubi9-python-3.12/uv.lock.d/pylock.cuda.toml | wc -l

Repository: opendatahub-io/notebooks

Length of output: 1640


🏁 Script executed:

#!/bin/bash

# More careful search for CPU vs CUDA packages
echo "=== Checking package URLs in lock file ==="
rg 'url = "' jupyter/pytorch+llmcompressor/ubi9-python-3.12/uv.lock.d/pylock.cuda.toml | head -20

# Check if any packages are actually from cpu-ubi9
echo -e "\n=== CPU packages found? ==="
grep -c 'cpu-ubi9' jupyter/pytorch+llmcompressor/ubi9-python-3.12/uv.lock.d/pylock.cuda.toml || echo "0"

# Check CUDA packages
echo -e "\n=== CUDA packages found? ==="
grep -c 'cuda12.9' jupyter/pytorch+llmcompressor/ubi9-python-3.12/uv.lock.d/pylock.cuda.toml || echo "0"

# Check if this is a recent change or pre-existing
echo -e "\n=== Check git history if available ==="
cd jupyter/pytorch+llmcompressor/ubi9-python-3.12/uv.lock.d && git log -1 --oneline pylock.cuda.toml 2>/dev/null || echo "No git history available"

Repository: opendatahub-io/notebooks

Length of output: 5974


🏁 Script executed:

#!/bin/bash

# Check git status and recent changes to this file
git diff HEAD~1 jupyter/pytorch+llmcompressor/ubi9-python-3.12/uv.lock.d/pylock.cuda.toml --stat 2>/dev/null || echo "No diff available"

# Check if file is in the current PR changes
git status jupyter/pytorch+llmcompressor/ubi9-python-3.12/uv.lock.d/pylock.cuda.toml 2>/dev/null || echo "Git status check failed"

# Check last few commits that touch this file
git log --oneline -10 -- jupyter/pytorch+llmcompressor/ubi9-python-3.12/uv.lock.d/pylock.cuda.toml 2>/dev/null | head -5 || echo "No history"

# Check if unsafe-best-match appears anywhere in LOCK files (not just CUDA)
echo -e "\n=== unsafe-best-match in all pylock files ==="
find . -name 'pylock.*.toml' -exec grep -l 'unsafe-best-match' {} \; | head -20 || echo "None found"

Repository: opendatahub-io/notebooks

Length of output: 1073


Replace unsafe-best-match with first-index in lock generation to prevent cross-index dependency confusion (CWE-829).

Line 2 uses --index-strategy=unsafe-best-match while mixing CUDA and CPU indexes. With unsafe-best-match, if a higher-version package appears on the secondary CPU index during future regeneration, the resolver will select it over the CUDA variant, poisoning the lock. Use --index-strategy=first-index to enforce deterministic CUDA-first resolution, or split locks by index class. This pattern affects 12+ lock files system-wide (CUDA and ROCM variants).

Suggested change
-#    uv pip compile pyproject.toml --output-file uv.lock.d/pylock.cuda.toml --format pylock.toml --generate-hashes --emit-index-url --python-version=3.12 --universal --no-annotate --no-emit-package odh-notebooks-meta-db-connectors-deps --no-emit-package odh-notebooks-meta-jupyterlab-datascience-deps --no-emit-package odh-notebooks-meta-jupyterlab-deps --no-emit-package odh-notebooks-meta-llmcompressor-deps --no-emit-package odh-notebooks-meta-runtime-elyra-deps --no-emit-package odh-notebooks-meta-runtime-datascience-deps --no-emit-package odh-notebooks-meta-workbench-datascience-deps --constraints ../../../dependencies/cve-constraints.txt --exclude-newer=2026-03-31T14:27:32Z --default-index=https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4/cuda12.9-ubi9/simple/?format=json --index=https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4/cuda12.9-ubi9/simple/?format=json --index=https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4/cpu-ubi9/simple/?format=json --index-strategy=unsafe-best-match
+#    uv pip compile pyproject.toml --output-file uv.lock.d/pylock.cuda.toml --format pylock.toml --generate-hashes --emit-index-url --python-version=3.12 --universal --no-annotate --no-emit-package odh-notebooks-meta-db-connectors-deps --no-emit-package odh-notebooks-meta-jupyterlab-datascience-deps --no-emit-package odh-notebooks-meta-jupyterlab-deps --no-emit-package odh-notebooks-meta-llmcompressor-deps --no-emit-package odh-notebooks-meta-runtime-elyra-deps --no-emit-package odh-notebooks-meta-runtime-datascience-deps --no-emit-package odh-notebooks-meta-workbench-datascience-deps --constraints ../../../dependencies/cve-constraints.txt --exclude-newer=2026-03-31T14:27:32Z --default-index=https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4/cuda12.9-ubi9/simple/?format=json --index=https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4/cuda12.9-ubi9/simple/?format=json --index=https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4/cpu-ubi9/simple/?format=json --index-strategy=first-index
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# uv pip compile pyproject.toml --output-file uv.lock.d/pylock.cuda.toml --format pylock.toml --generate-hashes --emit-index-url --python-version=3.12 --universal --no-annotate --no-emit-package odh-notebooks-meta-db-connectors-deps --no-emit-package odh-notebooks-meta-jupyterlab-datascience-deps --no-emit-package odh-notebooks-meta-jupyterlab-deps --no-emit-package odh-notebooks-meta-llmcompressor-deps --no-emit-package odh-notebooks-meta-runtime-elyra-deps --no-emit-package odh-notebooks-meta-runtime-datascience-deps --no-emit-package odh-notebooks-meta-workbench-datascience-deps --constraints ../../../dependencies/cve-constraints.txt --exclude-newer=2026-03-31T14:27:32Z --default-index=https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4/cuda12.9-ubi9/simple/?format=json --index=https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4/cuda12.9-ubi9/simple/?format=json --index=https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4/cpu-ubi9/simple/?format=json --index-strategy=unsafe-best-match
# uv pip compile pyproject.toml --output-file uv.lock.d/pylock.cuda.toml --format pylock.toml --generate-hashes --emit-index-url --python-version=3.12 --universal --no-annotate --no-emit-package odh-notebooks-meta-db-connectors-deps --no-emit-package odh-notebooks-meta-jupyterlab-datascience-deps --no-emit-package odh-notebooks-meta-jupyterlab-deps --no-emit-package odh-notebooks-meta-llmcompressor-deps --no-emit-package odh-notebooks-meta-runtime-elyra-deps --no-emit-package odh-notebooks-meta-runtime-datascience-deps --no-emit-package odh-notebooks-meta-workbench-datascience-deps --constraints ../../../dependencies/cve-constraints.txt --exclude-newer=2026-03-31T14:27:32Z --default-index=https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4/cuda12.9-ubi9/simple/?format=json --index=https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4/cuda12.9-ubi9/simple/?format=json --index=https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4/cpu-ubi9/simple/?format=json --index-strategy=first-index
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jupyter/pytorch`+llmcompressor/ubi9-python-3.12/uv.lock.d/pylock.cuda.toml at
line 2, The lock-generation command uses the flag
--index-strategy=unsafe-best-match which can cause cross-index package selection
when mixing CUDA and CPU indexes; update the invocation that builds
uv.lock.d/pylock.cuda.toml to use --index-strategy=first-index (or alternatively
split CUDA/CPU index sets) so the resolver prefers the primary CUDA index
deterministically; locate the command string containing
--index-strategy=unsafe-best-match and replace it with
--index-strategy=first-index in the pylock generation invocation.

@@ -1,5 +1,5 @@
# This file was autogenerated by uv via the following command:
# uv pip compile pyproject.toml --output-file uv.lock.d/pylock.rocm.toml --format pylock.toml --generate-hashes --emit-index-url --python-version=3.12 --universal --no-annotate --no-emit-package odh-notebooks-meta-llmcompressor-deps --no-emit-package odh-notebooks-meta-runtime-elyra-deps --no-emit-package odh-notebooks-meta-runtime-datascience-deps --no-emit-package odh-notebooks-meta-workbench-datascience-deps --constraints ../../../../dependencies/cve-constraints.txt --exclude-newer=2026-03-31T09:07:59Z --default-index=https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4/rocm6.4-ubi9/simple/?format=json --index=https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4/rocm6.4-ubi9/simple/?format=json --index=https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4/cpu-ubi9/simple/?format=json --index-strategy=unsafe-best-match
# uv pip compile pyproject.toml --output-file uv.lock.d/pylock.rocm.toml --format pylock.toml --generate-hashes --emit-index-url --python-version=3.12 --universal --no-annotate --no-emit-package odh-notebooks-meta-db-connectors-deps --no-emit-package odh-notebooks-meta-jupyterlab-datascience-deps --no-emit-package odh-notebooks-meta-jupyterlab-deps --no-emit-package odh-notebooks-meta-llmcompressor-deps --no-emit-package odh-notebooks-meta-runtime-elyra-deps --no-emit-package odh-notebooks-meta-runtime-datascience-deps --no-emit-package odh-notebooks-meta-workbench-datascience-deps --constraints ../../../../dependencies/cve-constraints.txt --exclude-newer=2026-03-31T11:02:33Z --default-index=https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4/rocm6.4-ubi9/simple/?format=json --index=https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4/rocm6.4-ubi9/simple/?format=json --index=https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4/cpu-ubi9/simple/?format=json --index-strategy=unsafe-best-match

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In uv pip compile, what are the exact semantics and recommended secure default for --index-strategy values (first-index, unsafe-first-match, unsafe-best-match) when multiple indexes are configured?

💡 Result:

In uv pip compile, the --index-strategy option controls how packages are resolved when multiple indexes (e.g., via --index, --extra-index-url, or [[tool.uv.index]] in pyproject.toml) are configured. Indexes are prioritized in declaration order, with configured indexes before the default (PyPI). The values are: - first-index (default, recommended secure default): For each package, search all indexes in order. Use only versions from the first index containing the package name, ignoring versions from later indexes. This prevents dependency confusion attacks (e.g., malicious PyPI packages shadowing internal ones) by ensuring packages from trusted indexes aren't superseded. - unsafe-first-match: Search all indexes. Prefer the first index with any compatible version, even if later indexes have newer compatible versions. Allows some cross-index resolution but still limits to first compatible index. - unsafe-best-match: Search all indexes and consider all versions across them. Select the highest compatible version from the combined set (pip-like). If the same version appears in multiple indexes, prefer the first index. Exposes to dependency confusion risks, as malicious newer versions from untrusted indexes could be chosen. The secure default is first-index, prioritizing safety over pip compatibility. Use unsafe-* only when needed (e.g., pip workflows), preferring explicit package-index pinning via tool.uv.sources for security.

Citations:


Use --index-strategy=first-index instead of unsafe-best-match to prevent dependency confusion (CWE-829).

The unsafe-best-match strategy resolves to the highest compatible version across all indexes, allowing an attacker to publish a malicious package to any configured index and have it selected over legitimate versions. The secure default is first-index, which restricts each package to versions from the first index containing it, eliminating cross-index shadowing.

Suggested fix
-#    uv pip compile pyproject.toml --output-file uv.lock.d/pylock.rocm.toml --format pylock.toml --generate-hashes --emit-index-url --python-version=3.12 --universal --no-annotate --no-emit-package odh-notebooks-meta-db-connectors-deps --no-emit-package odh-notebooks-meta-jupyterlab-datascience-deps --no-emit-package odh-notebooks-meta-jupyterlab-deps --no-emit-package odh-notebooks-meta-llmcompressor-deps --no-emit-package odh-notebooks-meta-runtime-elyra-deps --no-emit-package odh-notebooks-meta-runtime-datascience-deps --no-emit-package odh-notebooks-meta-workbench-datascience-deps --constraints ../../../../dependencies/cve-constraints.txt --exclude-newer=2026-03-31T11:02:33Z --default-index=https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4/rocm6.4-ubi9/simple/?format=json --index=https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4/rocm6.4-ubi9/simple/?format=json --index=https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4/cpu-ubi9/simple/?format=json --index-strategy=unsafe-best-match
+#    uv pip compile pyproject.toml --output-file uv.lock.d/pylock.rocm.toml --format pylock.toml --generate-hashes --emit-index-url --python-version=3.12 --universal --no-annotate --no-emit-package odh-notebooks-meta-db-connectors-deps --no-emit-package odh-notebooks-meta-jupyterlab-datascience-deps --no-emit-package odh-notebooks-meta-jupyterlab-deps --no-emit-package odh-notebooks-meta-llmcompressor-deps --no-emit-package odh-notebooks-meta-runtime-elyra-deps --no-emit-package odh-notebooks-meta-runtime-datascience-deps --no-emit-package odh-notebooks-meta-workbench-datascience-deps --constraints ../../../../dependencies/cve-constraints.txt --exclude-newer=2026-03-31T11:02:33Z --default-index=https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4/rocm6.4-ubi9/simple/?format=json --index=https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4/rocm6.4-ubi9/simple/?format=json --index=https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4/cpu-ubi9/simple/?format=json --index-strategy=first-index
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# uv pip compile pyproject.toml --output-file uv.lock.d/pylock.rocm.toml --format pylock.toml --generate-hashes --emit-index-url --python-version=3.12 --universal --no-annotate --no-emit-package odh-notebooks-meta-db-connectors-deps --no-emit-package odh-notebooks-meta-jupyterlab-datascience-deps --no-emit-package odh-notebooks-meta-jupyterlab-deps --no-emit-package odh-notebooks-meta-llmcompressor-deps --no-emit-package odh-notebooks-meta-runtime-elyra-deps --no-emit-package odh-notebooks-meta-runtime-datascience-deps --no-emit-package odh-notebooks-meta-workbench-datascience-deps --constraints ../../../../dependencies/cve-constraints.txt --exclude-newer=2026-03-31T11:02:33Z --default-index=https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4/rocm6.4-ubi9/simple/?format=json --index=https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4/rocm6.4-ubi9/simple/?format=json --index=https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4/cpu-ubi9/simple/?format=json --index-strategy=unsafe-best-match
# uv pip compile pyproject.toml --output-file uv.lock.d/pylock.rocm.toml --format pylock.toml --generate-hashes --emit-index-url --python-version=3.12 --universal --no-annotate --no-emit-package odh-notebooks-meta-db-connectors-deps --no-emit-package odh-notebooks-meta-jupyterlab-datascience-deps --no-emit-package odh-notebooks-meta-jupyterlab-deps --no-emit-package odh-notebooks-meta-llmcompressor-deps --no-emit-package odh-notebooks-meta-runtime-elyra-deps --no-emit-package odh-notebooks-meta-runtime-datascience-deps --no-emit-package odh-notebooks-meta-workbench-datascience-deps --constraints ../../../../dependencies/cve-constraints.txt --exclude-newer=2026-03-31T11:02:33Z --default-index=https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4/rocm6.4-ubi9/simple/?format=json --index=https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4/rocm6.4-ubi9/simple/?format=json --index=https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4/cpu-ubi9/simple/?format=json --index-strategy=first-index
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jupyter/rocm/pytorch/ubi9-python-3.12/uv.lock.d/pylock.rocm.toml` at line 2,
The commented pip-compile command in pylock.rocm.toml uses
--index-strategy=unsafe-best-match which enables cross-index version selection;
update that flag to --index-strategy=first-index in the commented command (the
long uv pip compile invocation) so the resolver uses the first index containing
a package instead of unsafe-best-match.

Comment on lines +14 to +16
route = await customApi.getNamespacedCustomObject({
group: 'route.openshift.io', version: 'v1', namespace: 'openshift-console', plural: 'routes', name: 'console'
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the actual API signature in `@kubernetes/client-node`

echo "=== Checking CustomObjectsApi method signatures ==="
npm view `@kubernetes/client-node`@1.4.0 repository.url 2>/dev/null

# Search for getNamespacedCustomObject signature in type definitions
cat << 'EOF'
Manual verification needed: Check `@kubernetes/client-node` changelog or TypeScript definitions
for getNamespacedCustomObject to confirm it accepts { group, version, namespace, plural, name } object.
EOF

Repository: opendatahub-io/notebooks

Length of output: 373


🏁 Script executed:

#!/bin/bash
# Find and check the `@kubernetes/client-node` version
echo "=== Searching for package files ==="
find . -name "package.json*" -type f | head -20

echo ""
echo "=== Checking for `@kubernetes/client-node` dependency ==="
rg '@kubernetes/client-node' -A 1 || true

Repository: opendatahub-io/notebooks

Length of output: 1712


🏁 Script executed:

#!/bin/bash
# Read the test file to see the actual implementation
echo "=== Reading the test file ==="
cat -n tests/browser/tests/openshift_console.spec.ts | head -40

Repository: opendatahub-io/notebooks

Length of output: 1839


🌐 Web query:

@kubernetes/client-node CustomObjectsApi getNamespacedCustomObject signature options object vs positional

💡 Result:

In the @kubernetes/client-node JavaScript/TypeScript Kubernetes client library, the CustomObjectsApi.getNamespacedCustomObject method uses positional arguments for the required parameters: getNamespacedCustomObject(group: string, version: string, namespace: string, plural: string, name: string, options?: any): Promise<V1KubernetesObject>. The core required parameters (group, version, namespace, plural, name) are passed positionally. Optional query parameters and request configuration (such as pretty, dryRun, fieldSelector, labelSelector, resourceVersion, timeoutSeconds, etc.) are passed in an optional trailing options object. This follows the standard pattern seen across Kubernetes client libraries and confirmed in usage examples from StackOverflow and GitHub issues. For example: k8sApi.getNamespacedCustomObject('monitoring.coreos.com','v1','default', 'prometheusrules', 'prometheus-kube-prometheus-etcd-rules') No sources show an options-object-only signature without positional args; positional is primary for the 5 required params, with options for extras. The generated API code (from kubernetes-client/javascript repo) uses this signature, matching other generated methods like CoreV1Api.listNamespacedPod({namespace: 'default'}), but CustomObjectsApi methods use positional for core params per examples and Java/Python analogs.

Citations:


Fix API call: getNamespacedCustomObject requires positional arguments, not an options object.

The @kubernetes/client-node 1.4.0 API signature requires the 5 core parameters (group, version, namespace, plural, name) to be passed as positional arguments, not as a named options object. Change line 14–16 to:

route = await customApi.getNamespacedCustomObject(
  'route.openshift.io', 'v1', 'openshift-console', 'routes', 'console'
);

The current code will fail at runtime with a type/argument mismatch error.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/browser/tests/openshift_console.spec.ts` around lines 14 - 16, The call
to customApi.getNamespacedCustomObject is using an options object but the
`@kubernetes/client-node` API expects positional arguments; update the invocation
of getNamespacedCustomObject (where route is assigned) to pass the five
parameters in order: group, version, namespace, plural, name (i.e.,
'route.openshift.io', 'v1', 'openshift-console', 'routes', 'console') instead of
the object form so the function signature matches the client library.

{
// tsconfig.json natively supports comments (JSONC format)
"compilerOptions": {
"target": "es2025",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

What ECMAScript features are included in ES2025 and does Node.js 22+ support them?

💡 Result:

ES2025 (ECMAScript 2025, the 16th edition) includes the following new features: - New Iterator global with static and prototype methods (e.g., Iterator.prototype.map, filter, flatMap) - New Set.prototype methods: intersection(other), union(other), difference(other), symmetricDifference(other), isSubsetOf(other), isSupersetOf(other) - Import attributes (with { type: 'json' }) for JSON modules and syntax for module import attributes - RegExp.escape(str) static method - RegExp inline modifier flags (e.g., /foo(?i:bar)/ for inline case-insensitive) - Promise.try(fn) static method - Float16Array typed array + DataView.prototype.getFloat16/setFloat16 + Math.f16round Node.js 22+ (specifically Node.js 24+, released May 2025 with V8 13.6) supports several ES2025 features via V8, including: - RegExp.escape - Float16Array (enabled on global object) - Explicit resource management (using / await using declarations, though primarily Stage 4 in 2025, aligned with ES2025) Node.js 22 (V8 12.4) supports up to ES2024 features like Array.fromAsync but lacks the ES2025-specific ones listed above. Full ES2025 support requires Node.js 24+. As of 2026-04-01, Node.js 24 is LTS ("Krypton"), ensuring ongoing support.

Citations:


🏁 Script executed:

#!/bin/bash
# Check Node.js version constraints in package.json
cat package.json | jq '.engines' 2>/dev/null || cat package.json | grep -A 5 '"engines"' || echo "No engines field found"

Repository: opendatahub-io/notebooks

Length of output: 112


🏁 Script executed:

#!/bin/bash
# Look for Node.js version specification files
fd -E '.git' '\.node-version|\.nvmrc|\.tool-versions' -t f

Repository: opendatahub-io/notebooks

Length of output: 50


🏁 Script executed:

#!/bin/bash
# Check CI workflows for Node.js version specifications
find .github/workflows -name '*.yml' -o -name '*.yaml' 2>/dev/null | head -20

Repository: opendatahub-io/notebooks

Length of output: 890


🏁 Script executed:

#!/bin/bash
# Check TypeScript version in package.json
cat package.json | jq '.devDependencies.typescript, .dependencies.typescript' 2>/dev/null || grep -i 'typescript' package.json | head -5

Repository: opendatahub-io/notebooks

Length of output: 112


🏁 Script executed:

#!/bin/bash
# Explore repository structure
ls -la

Repository: opendatahub-io/notebooks

Length of output: 3097


🏁 Script executed:

#!/bin/bash
# Check tests/browser directory
ls -la tests/browser/ 2>/dev/null || echo "Directory not found"

Repository: opendatahub-io/notebooks

Length of output: 805


🏁 Script executed:

#!/bin/bash
# Look for all package.json files
fd 'package.json' -t f

Repository: opendatahub-io/notebooks

Length of output: 899


🏁 Script executed:

#!/bin/bash
# Check the build-browser-tests workflow
cat .github/workflows/build-browser-tests.yaml

Repository: opendatahub-io/notebooks

Length of output: 3047


🏁 Script executed:

#!/bin/bash
# Read package.json5
cat tests/browser/package.json5

Repository: opendatahub-io/notebooks

Length of output: 847


🏁 Script executed:

#!/bin/bash
# Read Dockerfile
cat tests/browser/Dockerfile

Repository: opendatahub-io/notebooks

Length of output: 1022


🏁 Script executed:

#!/bin/bash
# Read complete tsconfig.json
cat tests/browser/tsconfig.json

Repository: opendatahub-io/notebooks

Length of output: 715


🏁 Script executed:

#!/bin/bash
# Check if there are any references to Node.js version constraints or Playwright compatibility
grep -r "node" tests/browser/ --include="*.md" --include="*.txt" -i || echo "No Node.js docs found"

Repository: opendatahub-io/notebooks

Length of output: 712


🏁 Script executed:

#!/bin/bash
# Check the README for Node.js/Playwright version info
cat tests/browser/README.md

Repository: opendatahub-io/notebooks

Length of output: 3454


Node.js 22 (likely in Playwright base image) does not support ES2025; verify image or change target to es2024.

TypeScript 6.0.2 supports es2025, but the Dockerfile uses mcr.microsoft.com/playwright:v1.58.1-noble (Ubuntu 24.04 LTS), which typically includes Node.js 22. According to the ES2025 specification, Node.js 22 (V8 12.4) only supports ES2024 features. Full ES2025 support (RegExp.escape, Promise.try, Float16Array, Iterator methods) requires Node.js 24+ (V8 13.6).

Even with "noEmit": true, TypeScript will type-check code assuming the runtime supports ES2025 features. If test code uses ES2025-specific features, runtime errors will occur on Node.js 22.

Either upgrade the Playwright base image to include Node.js 24+, or change "target" to "es2024" to match the actual runtime environment.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/browser/tsconfig.json` at line 4, The tsconfig setting "target":
"es2025" is incompatible with the Playwright/base Node.js 22 runtime; update the
tsconfig.json target to "es2024" (replace the "target": "es2025" entry) so
TypeScript type-checks against the actual runtime, or alternatively update the
CI/Playwright base image to one that includes Node.js 24+; edit the "target"
field in tests/browser/tsconfig.json (the "target" property) to ensure it
matches the chosen runtime.

Co-authored-by: GitHub Actions <github-actions[bot]@users.noreply.github.com>
@openshift-ci openshift-ci Bot added size/xxl and removed size/xxl labels Apr 1, 2026
@openshift-ci openshift-ci Bot added size/xxl and removed size/xxl labels Apr 1, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
jupyter/rocm/pytorch/ubi9-python-3.12/uv.lock.d/pylock.rocm.toml (1)

2-2: ⚠️ Potential issue | 🟠 Major

Replace unsafe-best-match to prevent dependency confusion (CWE-829).

Line 2 still uses --index-strategy=unsafe-best-match. With multiple indexes, a malicious higher-version package on a less-trusted index can be selected during lock regeneration and poison the supply chain.

Suggested fix
-#    ... --index-strategy=unsafe-best-match
+#    ... --index-strategy=first-index
In the latest uv documentation, what is the recommended secure `--index-strategy` for multi-index resolution, and does `unsafe-best-match` increase dependency-confusion risk?

As per coding guidelines "REVIEW PRIORITIES: 1. Security vulnerabilities (provide severity, exploit scenario, and remediation code)".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jupyter/rocm/pytorch/ubi9-python-3.12/uv.lock.d/pylock.rocm.toml` at line 2,
The command line uses the insecure flag --index-strategy=unsafe-best-match which
can enable dependency-confusion; update the uv pip compile invocation to use a
secure, deterministic index strategy instead (replace
--index-strategy=unsafe-best-match with a safer strategy such as
--index-strategy=prefer-index or another non-unsafe option supported by uv) and
regenerate the lockfile so resolution always favors the intended trusted index;
ensure you update the invocation that includes --index-strategy in the
pylock.rocm.toml comment line.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@jupyter/rocm/pytorch/ubi9-python-3.12/uv.lock.d/pylock.rocm.toml`:
- Line 2: The command line uses the insecure flag
--index-strategy=unsafe-best-match which can enable dependency-confusion; update
the uv pip compile invocation to use a secure, deterministic index strategy
instead (replace --index-strategy=unsafe-best-match with a safer strategy such
as --index-strategy=prefer-index or another non-unsafe option supported by uv)
and regenerate the lockfile so resolution always favors the intended trusted
index; ensure you update the invocation that includes --index-strategy in the
pylock.rocm.toml comment line.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited), Repository UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 6722ccc4-ea7a-450a-9b75-105e09c3dd34

📥 Commits

Reviewing files that changed from the base of the PR and between e524f1d and 62eca26.

📒 Files selected for processing (22)
  • codeserver/ubi9-python-3.12/uv.lock.d/pylock.cpu.toml
  • jupyter/datascience/ubi9-python-3.12/uv.lock.d/pylock.cpu.toml
  • jupyter/minimal/ubi9-python-3.12/uv.lock.d/pylock.cpu.toml
  • jupyter/minimal/ubi9-python-3.12/uv.lock.d/pylock.cuda.toml
  • jupyter/minimal/ubi9-python-3.12/uv.lock.d/pylock.rocm.toml
  • jupyter/pytorch+llmcompressor/ubi9-python-3.12/uv.lock.d/pylock.cuda.toml
  • jupyter/pytorch/ubi9-python-3.12/uv.lock.d/pylock.cuda.toml
  • jupyter/rocm/pytorch/ubi9-python-3.12/uv.lock.d/pylock.rocm.toml
  • jupyter/rocm/tensorflow/ubi9-python-3.12/pylock.toml
  • jupyter/tensorflow/ubi9-python-3.12/uv.lock.d/pylock.cuda.toml
  • jupyter/trustyai/ubi9-python-3.12/uv.lock.d/pylock.cpu.toml
  • rstudio/c9s-python-3.12/uv.lock.d/pylock.cpu.toml
  • rstudio/c9s-python-3.12/uv.lock.d/pylock.cuda.toml
  • rstudio/rhel9-python-3.12/uv.lock.d/pylock.cpu.toml
  • rstudio/rhel9-python-3.12/uv.lock.d/pylock.cuda.toml
  • runtimes/datascience/ubi9-python-3.12/uv.lock.d/pylock.cpu.toml
  • runtimes/minimal/ubi9-python-3.12/uv.lock.d/pylock.cpu.toml
  • runtimes/pytorch+llmcompressor/ubi9-python-3.12/uv.lock.d/pylock.cuda.toml
  • runtimes/pytorch/ubi9-python-3.12/uv.lock.d/pylock.cuda.toml
  • runtimes/rocm-pytorch/ubi9-python-3.12/uv.lock.d/pylock.rocm.toml
  • runtimes/rocm-tensorflow/ubi9-python-3.12/pylock.toml
  • runtimes/tensorflow/ubi9-python-3.12/uv.lock.d/pylock.cuda.toml
✅ Files skipped from review due to trivial changes (7)
  • rstudio/rhel9-python-3.12/uv.lock.d/pylock.cuda.toml
  • jupyter/minimal/ubi9-python-3.12/uv.lock.d/pylock.rocm.toml
  • runtimes/rocm-pytorch/ubi9-python-3.12/uv.lock.d/pylock.rocm.toml
  • runtimes/minimal/ubi9-python-3.12/uv.lock.d/pylock.cpu.toml
  • jupyter/minimal/ubi9-python-3.12/uv.lock.d/pylock.cpu.toml
  • rstudio/rhel9-python-3.12/uv.lock.d/pylock.cpu.toml
  • runtimes/tensorflow/ubi9-python-3.12/uv.lock.d/pylock.cuda.toml
🚧 Files skipped from review as they are similar to previous changes (9)
  • rstudio/c9s-python-3.12/uv.lock.d/pylock.cuda.toml
  • runtimes/pytorch+llmcompressor/ubi9-python-3.12/uv.lock.d/pylock.cuda.toml
  • rstudio/c9s-python-3.12/uv.lock.d/pylock.cpu.toml
  • runtimes/pytorch/ubi9-python-3.12/uv.lock.d/pylock.cuda.toml
  • jupyter/datascience/ubi9-python-3.12/uv.lock.d/pylock.cpu.toml
  • jupyter/tensorflow/ubi9-python-3.12/uv.lock.d/pylock.cuda.toml
  • codeserver/ubi9-python-3.12/uv.lock.d/pylock.cpu.toml
  • jupyter/pytorch+llmcompressor/ubi9-python-3.12/uv.lock.d/pylock.cuda.toml
  • runtimes/datascience/ubi9-python-3.12/uv.lock.d/pylock.cpu.toml
👮 Files not reviewed due to content moderation or server errors (2)
  • jupyter/rocm/tensorflow/ubi9-python-3.12/pylock.toml
  • runtimes/rocm-tensorflow/ubi9-python-3.12/pylock.toml

@jiridanek
jiridanek merged commit 7308816 into stable Apr 1, 2026
77 of 144 checks passed
dibryant pushed a commit to dibryant/notebooks that referenced this pull request Oct 27, 2026
)

Signed-off-by: konflux-internal-p02 <170854209+konflux-internal-p02[bot]@users.noreply.github.com>
Co-authored-by: konflux-internal-p02[bot] <170854209+konflux-internal-p02[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review-requested GitHub Bot creates notification on #pr-review-ai-ide-team slack channel size/xxl

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants