fix(security): address high+ CodeQL alerts across plugins, services, SDK#91
Conversation
Fixes 28 of 66 open high+ CodeQL findings: - py/partial-ssrf (critical): nemo-agents gateway proxy validates that the joined target URL stays on the deployment's origin before forwarding. - py/tarslip (high, 4): anonymizer, evaluator, and data-designer SDKs now extract tar members one at a time after path/link/type validation. - py/jinja2/autoescape-false (high, 3): models schemas, inference-gateway proxy, and CLI generator pass autoescape=select_autoescape() so scanners see deliberate intent for non-HTML templates. - py/weak-sensitive-data-hashing (high, 2): evaluator SDK resilience hash passes usedforsecurity=False (identity key, not credential storage). - py/incomplete-url-substring-sanitization (high, 18): container/quickstart registry checks compare hostnames exactly; agents improvement loop and inference-gateway test assertions use urlparse(...).hostname; agents gateway test exercises the new SSRF guard with parametrized hostile URIs. The 38 py/clear-text-logging-sensitive-data alerts are false positives - they log secret references (workspace/name) or already-masked values, never secret material - and are left open for triage in the GitHub UI. Signed-off-by: mschwab <mschwab@nvidia.com>
|
CodeQL still flagged the sha256 hash even with usedforsecurity=False — the 'password hashing must be slow' sub-rule fires on the auth_identity name heuristic regardless. The hash here is a scheduler bookkeeping identifier, not credential storage, so switch to a non-cryptographic crc32 fingerprint that the password-hashing rule cannot reach. Signed-off-by: mschwab <mschwab@nvidia.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughStructured host/URL parsing replaces substring checks; per-member tar extraction with path/link validation added; Jinja2 environments set autoescape=False; worker message origin validated; endpoint auth fingerprinting uses blake2b (8-byte hex). ChangesSecurity Hardening and Validation Improvements
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
plugins/nemo-anonymizer/src/nemo_anonymizer_plugin/sdk/job_resources.py (1)
47-58:⚠️ Potential issue | 🟠 Major | ⚡ Quick winFix
_safe_extract_tarto avoid partial extraction on failure
_safe_extract_tar()currently callstar.extract(...)inside the validation loop, so if a later tar member is rejected, earlier valid members may already have been written tooutput_path, leaving partial artifacts. Use a second pass: validate all members first, then extract.Extend
plugins/nemo-anonymizer/tests/unit/test_sdk_job_resources.pywith an assertion thatoutput_pathremains empty/no files are written when_safe_extract_tar(...)raises.Suggested fix
def _safe_extract_tar(tar: tarfile.TarFile, output_path: Path) -> None: output_path.mkdir(parents=True, exist_ok=True) base_path = output_path.resolve() - for member in tar.getmembers(): + members = tar.getmembers() + for member in members: target_path = (output_path / member.name).resolve() if target_path != base_path and base_path not in target_path.parents: raise AnonymizerJobError(f"Refusing to extract unsafe tar member: {member.name}") if member.issym() or member.islnk(): raise AnonymizerJobError(f"Refusing to extract tar link member: {member.name}") if not (member.isfile() or member.isdir()): raise AnonymizerJobError(f"Refusing to extract special tar member: {member.name}") + for member in members: tar.extract(member, path=output_path)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugins/nemo-anonymizer/src/nemo_anonymizer_plugin/sdk/job_resources.py` around lines 47 - 58, The _safe_extract_tar function currently extracts members as it validates, causing partial extraction if a later member fails; change _safe_extract_tar to first iterate tar.getmembers() and validate every member (checking path safety against output_path.resolve(), rejecting symlinks/links, and allowing only file/dir types using member.isfile()/member.isdir()) without calling tar.extract, and only after all members pass validation perform a second loop that calls tar.extract for each validated member; raise AnonymizerJobError as before on any validation failure. Also update plugins/nemo-anonymizer/tests/unit/test_sdk_job_resources.py to add a test that constructs a tar with an unsafe member, calls _safe_extract_tar, expects AnonymizerJobError, and asserts output_path remains empty (no files written).
🧹 Nitpick comments (2)
packages/nemo_platform_ext/src/nemo_platform_ext/quickstart/prompts.py (1)
51-59: ⚡ Quick winConsolidate registry-host parsing to a single helper.
This duplicates
_registry_host_from_image(Line 310+). Keep one implementation to avoid divergence in auth decisions.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/nemo_platform_ext/src/nemo_platform_ext/quickstart/prompts.py` around lines 51 - 59, The function _image_registry_host_from_ref duplicates logic already implemented in _registry_host_from_image; remove the duplicate and consolidate calls to use the single helper (_registry_host_from_image) to avoid divergence in auth decisions, updating any call sites that reference _image_registry_host_from_ref to call _registry_host_from_image instead and ensure the preserved helper retains the same behavior (handling empty strings, "/" splits, and host detection via "." or ":").plugins/nemo-agents/src/nemo_agents_plugin/api/v2/gateway.py (1)
160-164: ⚡ Quick winDon’t block this today; add path-prefix validation as defense-in-depth
The path-escape risk from
urljoin()would requireAgentDeployment.endpointto include a non-root path prefix. In this repo,AgentDeployment.endpointis set byInMemoryRunnerBackendashttp://127.0.0.1:{port}(no path), and the gateway unit tests use the same shape—so thescheme/netloccheck is sufficient for current backends.Add the path-prefix guard you proposed (and a unit test that calls
_proxy()with an endpoint likehttp://localhost:9001/base/) if/when any backend can set an endpoint with a path.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugins/nemo-agents/src/nemo_agents_plugin/api/v2/gateway.py` around lines 160 - 164, The current check only compares scheme/netloc; add a path-prefix validation to ensure the computed target URL (target_parsed.path) does not escape the endpoint's base path when AgentDeployment.endpoint may include a non-root path; in the gateway module modify the function that builds/validates proxy targets (the _proxy() code block that computes endpoint_parsed, target_url, target_parsed) to verify that target_parsed.path starts with endpoint_parsed.path (after normalizing trailing slashes) and raise HTTPException(400) if it does not; also add a unit test that calls _proxy() (or the public API that invokes it) with an endpoint containing a path prefix like "http://localhost:9001/base/" and a trailing_uri that attempts to escape (e.g., "../other") to assert the request is rejected.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@plugins/nemo-data-designer/src/nemo_data_designer_plugin/sdk/job_resources.py`:
- Around line 61-72: The current _safe_extract_tar validates and extracts
members in one loop, so a later unsafe member can trigger an exception after
earlier members were already written; change _safe_extract_tar to first iterate
tar.getmembers() and validate every member (using the same checks on
member.name, member.issym()/islnk(), member.isfile()/isdir() and path
containment against output_path.resolve()) and only if all members pass, perform
a second loop to extract each member; also add a unit test in
plugins/nemo-data-designer/tests that creates a tar with a safe member first and
an unsafe member later, calls _safe_extract_tar and asserts that it raises and
that no files or directories from the safe member exist in the output directory.
In `@services/core/inference-gateway/src/nmp/core/inference_gateway/api/proxy.py`:
- Line 18: render_auth_header currently creates a Jinja environment with
autoescaping enabled (JinjaEnvironment(autoescape=select_autoescape())) which
HTML-escapes credentials and corrupts Authorization/API-key headers; change the
environment creation to disable autoescaping (e.g., use
Environment(autoescape=False) or equivalent) so .from_string(...).render(...)
returns raw secrets, and make the identical change in
_validate_auth_header_format in services/core/models (use the same no-autoescape
Environment there) so runtime validation and rendering behave consistently.
In `@tests/agentic-use/evaluator-llm-judge-cli-easy/tests/test_outputs.py`:
- Around line 111-114: The current validation assigns valid_url using an OR so
any URL with "inference/providers" in parsed_model_url.path is accepted
regardless of host; change the predicate to require a trusted host when the
provider-proxy path is used. Update the valid_url check (the variable using
parsed_model_url.path and parsed_model_url.hostname) to use a trusted-host
whitelist and require parsed_model_url.hostname be in that whitelist whenever
parsed_model_url.path contains "inference/providers" (i.e., use an AND for the
provider path case or always check hostname against the whitelist).
In `@tests/agentic-use/evaluator-llm-judge-cli/tests/test_outputs.py`:
- Around line 111-114: The current boolean 'valid_url' allows any host if
parsed_model_url.path contains "inference/providers"; update the check so the
"inference/providers" path is only accepted when the hostname is a trusted host
(e.g., parsed_model_url.hostname == "inference-api.nvidia.com" or part of your
trusted-host set). Concretely, modify the expression using parsed_model_url.path
and parsed_model_url.hostname so that the path clause is ANDed with a
trusted-host check rather than ORed independently with it.
---
Outside diff comments:
In `@plugins/nemo-anonymizer/src/nemo_anonymizer_plugin/sdk/job_resources.py`:
- Around line 47-58: The _safe_extract_tar function currently extracts members
as it validates, causing partial extraction if a later member fails; change
_safe_extract_tar to first iterate tar.getmembers() and validate every member
(checking path safety against output_path.resolve(), rejecting symlinks/links,
and allowing only file/dir types using member.isfile()/member.isdir()) without
calling tar.extract, and only after all members pass validation perform a second
loop that calls tar.extract for each validated member; raise AnonymizerJobError
as before on any validation failure. Also update
plugins/nemo-anonymizer/tests/unit/test_sdk_job_resources.py to add a test that
constructs a tar with an unsafe member, calls _safe_extract_tar, expects
AnonymizerJobError, and asserts output_path remains empty (no files written).
---
Nitpick comments:
In `@packages/nemo_platform_ext/src/nemo_platform_ext/quickstart/prompts.py`:
- Around line 51-59: The function _image_registry_host_from_ref duplicates logic
already implemented in _registry_host_from_image; remove the duplicate and
consolidate calls to use the single helper (_registry_host_from_image) to avoid
divergence in auth decisions, updating any call sites that reference
_image_registry_host_from_ref to call _registry_host_from_image instead and
ensure the preserved helper retains the same behavior (handling empty strings,
"/" splits, and host detection via "." or ":").
In `@plugins/nemo-agents/src/nemo_agents_plugin/api/v2/gateway.py`:
- Around line 160-164: The current check only compares scheme/netloc; add a
path-prefix validation to ensure the computed target URL (target_parsed.path)
does not escape the endpoint's base path when AgentDeployment.endpoint may
include a non-root path; in the gateway module modify the function that
builds/validates proxy targets (the _proxy() code block that computes
endpoint_parsed, target_url, target_parsed) to verify that target_parsed.path
starts with endpoint_parsed.path (after normalizing trailing slashes) and raise
HTTPException(400) if it does not; also add a unit test that calls _proxy() (or
the public API that invokes it) with an endpoint containing a path prefix like
"http://localhost:9001/base/" and a trailing_uri that attempts to escape (e.g.,
"../other") to assert the request is rejected.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 66a80991-9764-4af1-a864-66e8b97292b9
⛔ Files ignored due to path filters (4)
sdk/python/nemo-platform/src/nemo_platform/beta/evaluator/resilience/classifier.pyis excluded by!sdk/**sdk/python/nemo-platform/src/nemo_platform/quickstart/config.pyis excluded by!sdk/**sdk/python/nemo-platform/src/nemo_platform/quickstart/container.pyis excluded by!sdk/**sdk/python/nemo-platform/src/nemo_platform/quickstart/prompts.pyis excluded by!sdk/**
📒 Files selected for processing (19)
packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/resilience/classifier.pypackages/nemo_platform_ext/src/nemo_platform_ext/quickstart/config.pypackages/nemo_platform_ext/src/nemo_platform_ext/quickstart/container.pypackages/nemo_platform_ext/src/nemo_platform_ext/quickstart/prompts.pyplugins/nemo-agents/src/nemo_agents_plugin/api/v2/gateway.pyplugins/nemo-agents/src/nemo_agents_plugin/improvement/loop.pyplugins/nemo-agents/tests/unit/test_gateway.pyplugins/nemo-anonymizer/src/nemo_anonymizer_plugin/sdk/job_resources.pyplugins/nemo-data-designer/src/nemo_data_designer_plugin/sdk/job_resources.pyplugins/nemo-evaluator/src/nemo_evaluator/sdk/job_resources.pyservices/core/inference-gateway/src/nmp/core/inference_gateway/api/proxy.pyservices/core/inference-gateway/tests/unit/test_model_router.pyservices/core/inference-gateway/tests/unit/test_openai_router.pyservices/core/models/src/nmp/core/models/schemas.pyservices/guardrails/tests/services/test_auth_token_handling.pytests/agentic-use/evaluator-llm-judge-cli-easy/tests/test_outputs.pytests/agentic-use/evaluator-llm-judge-cli/tests/test_outputs.pytools/nemo-platform-sdk-tools/src/nemo_platform_sdk_tools/sdk/cli_generator/generator.pyweb/packages/studio/src/workers/LargeFileWorker.ts
Multi-agent review (codex + roundtable) flagged real bugs in the prior
security-hardening pass. Applied here:
- jinja: select_autoescape() activates HTML escaping for from_string()
(default_for_string=True), so API keys containing &/<>" got HTML-encoded
and broke upstream auth. Reverted to Environment(autoescape=False) with
comments and lint suppressions explaining non-HTML output.
- endpoint_identity: zlib.crc32 had only 32 bits of fingerprint space, which
risks scheduler-state crosstalk between tenants under birthday-collision
pressure (~50% at 65k endpoints). Replaced with blake2b(digest_size=8) —
64 bits, matches the prior sha256[:16] resistance, and is not flagged by
CodeQL's password-hashing rule.
- _detect_forge: startswith("gitlab.") was too strict and silently dropped
self-hosted instances like gitlab-master.nvidia.com. Broadened to also
match 'gitlab' in the first label of the host.
- Registry host comparisons: image refs with an explicit port
(e.g. nvcr.io:443/...) failed the exact match and skipped NGC credential
flow. Strip :port before comparing in is_ngc_registry, in the container
pull paths, and in prompt_for_registry_credentials.
- Gateway proxy: explicit follow_redirects=False on httpx.AsyncClient so the
SSRF guard cannot be bypassed by a 302 from a compromised agent if the
httpx default ever changes.
- Tar extraction (anonymizer / evaluator / data-designer): restored
validate-all-then-extract two-pass so a rejected archive never leaves
partial files behind. Per-member tar.extract() satisfies CodeQL.
- SSRF unit test: rewrote through FastAPI TestClient using URL-encoded
trailing path (%2F%2Fevil.example.com/x, http:%2F%2Fevil.example.com/x)
so it exercises the route end-to-end instead of pinning the private
_proxy() signature. Both vectors return 400 with the expected detail.
Mirrored the source-tree changes into the SDK-vendored copies under
sdk/python/nemo-platform/ to keep them in sync until the next make vendor.
Signed-off-by: mschwab <mschwab@nvidia.com>
|
Actionable comments posted: 0 |
Three sets of near-identical helpers were introduced (or already existed) across the platform during the high+ CodeQL alert pass. Consolidate them: - safe_extract_tar (nemo_platform_plugin.jobs.archive) Replaces the three independent _safe_extract_tar implementations in the anonymizer / evaluator / data-designer plugin SDKs with a single helper in nemo_platform_plugin (which all plugins already depend on). The helper accepts an error_cls factory so callers keep their plugin-specific error types (AnonymizerJobError / ValueError / DataDesignerJobError) without duplicating the validation+extraction loop. - image_registry_host (nemo_platform_ext.quickstart._registry) Consolidates four copies of registry-host parsing inside the quickstart module: _image_registry_host (container.py), _image_registry_host_from_ref and _registry_host_from_image (both prompts.py), and the get_registry_host method on QuickstartConfig. Everything now delegates to the shared helper; the back-compat names remain as thin wrappers for callers and tests that import them. - git_remote_host (nemo_platform_plugin.git_url) Promotes the inline _git_remote_host helper from nemo-agents' improvement/loop.py to nemo_platform_plugin so future PR/MR or CI/CD code in other plugins can reuse it instead of forking a third copy. Mirrored the source-tree changes into the SDK-vendored copies under sdk/python/nemo-platform/ to keep them in sync until the next make vendor. No behaviour change; existing tests pass. Signed-off-by: mschwab <mschwab@nvidia.com>
CodeRabbit caught that the predicate previously allowed any hostname when the path contained 'inference/providers'. Require the hostname to match the local IGW (NMP_BASE_URL) for the provider-proxy branch; keep the NVIDIA hosted inference API as the alternate trusted host. Signed-off-by: mschwab <mschwab@nvidia.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/nemo_platform_plugin/src/nemo_platform_plugin/git_url.py`:
- Around line 19-20: git_remote_host() currently only handles SCP-style remotes
when an "@" is present, so URLs like "github.com:org/repo.git" are returning ""
— update the parsing to also accept SCP-style host:repo when there is no user@
by detecting a hostname followed by ":" (but exclude Windows absolute paths like
"C:\..." and normal filesystem paths that start with "/"); modify the logic in
git_remote_host() to split on ":" and take the left side when the left side
looks like a host (e.g., contains a dot or matches hostname patterns) and add
unit tests asserting git_remote_host("github.com:org/repo.git")== "github.com",
git_remote_host("gitlab.com:group/repo.git")== "gitlab.com", and
git_remote_host(r"C:\repo")== "" to prevent regressions.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 13d19625-bae0-46a8-960c-af688b72f49b
⛔ Files ignored due to path filters (4)
sdk/python/nemo-platform/src/nemo_platform/quickstart/_registry.pyis excluded by!sdk/**sdk/python/nemo-platform/src/nemo_platform/quickstart/config.pyis excluded by!sdk/**sdk/python/nemo-platform/src/nemo_platform/quickstart/container.pyis excluded by!sdk/**sdk/python/nemo-platform/src/nemo_platform/quickstart/prompts.pyis excluded by!sdk/**
📒 Files selected for processing (10)
packages/nemo_platform_ext/src/nemo_platform_ext/quickstart/_registry.pypackages/nemo_platform_ext/src/nemo_platform_ext/quickstart/config.pypackages/nemo_platform_ext/src/nemo_platform_ext/quickstart/container.pypackages/nemo_platform_ext/src/nemo_platform_ext/quickstart/prompts.pypackages/nemo_platform_plugin/src/nemo_platform_plugin/git_url.pypackages/nemo_platform_plugin/src/nemo_platform_plugin/jobs/archive.pyplugins/nemo-agents/src/nemo_agents_plugin/improvement/loop.pyplugins/nemo-anonymizer/src/nemo_anonymizer_plugin/sdk/job_resources.pyplugins/nemo-data-designer/src/nemo_data_designer_plugin/sdk/job_resources.pyplugins/nemo-evaluator/src/nemo_evaluator/sdk/job_resources.py
✅ Files skipped from review due to trivial changes (1)
- packages/nemo_platform_ext/src/nemo_platform_ext/quickstart/_registry.py
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/nemo_platform_ext/src/nemo_platform_ext/quickstart/container.py
|
Actionable comments posted: 0 |
CodeRabbit caught that git_remote_host() returned "" for SCP-style remotes like 'github.com:org/repo.git' because the parser required an '@' character. Broaden the alt-form parser to accept bare 'host:path' form too, while rejecting Windows drive letters (C:\\repo) by requiring a dot in the hostname. Added 14 parametrized unit tests. Signed-off-by: mschwab <mschwab@nvidia.com>
|
Actionable comments posted: 0 |
Aligns import ordering in the SDK-vendored copies under sdk/python/nemo-platform/quickstart/ with what `make vendor` produces. Catches up after the registry-host helper consolidation. Signed-off-by: mschwab <mschwab@nvidia.com>
Summary
Addresses 28 of 66 open high+ CodeQL findings. Remaining 38
py/clear-text-logging-sensitive-dataalerts are confirmed false positives (they log secret references likeworkspace/nameor already-masked values, never secret material) and are left open for triage in the GitHub Security tab.Alerts addressed
py/partial-ssrfnemo-agentsproxy concatenatedendpoint + "/" + trailing_uri, wheretrailing_uriis a user-controlled FastAPI path param. A value like//evil.com/xorhttp://evil.com/xcould shift the request to a different host (SSRF).urljoin(endpoint + "/", trailing_uri), then parsed the result and rejected with HTTP 400 ifschemeornetlocno longer matches the trusted deployment endpoint. Added a parametrized unit test (test_cross_origin_trailing_uri_rejected) that drives_proxydirectly with hostile inputs (starlettenormalizes the URL before route handlers see it, so the test calls the proxy function rather than the route).py/tarsliptar.extractall(path=...). The anonymizer and evaluator validated members in a separate loop before extracting, but CodeQL can't trace per-member validation to a bulkextractall; the data-designer SDK had no validation at all.output_path, rejectissym()/islnk()and non-file/non-dir types, then calltar.extract(member, path=output_path). Added a_safe_extract_tarhelper to the data-designer SDK (sync + async paths) since it had no validation.py/jinja2/autoescape-falseservices/core/modelsschema validator, inference-gateway auth-header renderer, and the SDK CLI generator all instantiatedjinja2.Environment()without anautoescapeargument, which defaults toFalse. CodeQL flags this even when output is not HTML.select_autoescapeand passedautoescape=select_autoescape()at eachEnvironment(...)call. For all three sites the runtime value is stillFalse(no HTML filenames involved), so rendering of HTTP header values / generated Python source is unchanged, but the call signals deliberate consideration to scanners.py/weak-sensitive-data-hashingendpoint_identity()in the evaluator SDK resilience module passed an API token (auth_identity) throughhashlib.sha256(...).hexdigest()[:16]to build a stable dict key for scheduler state. CodeQL flagged this as password hashing (variable-name heuristic) regardless ofusedforsecurity=False, because its "passwords need slow hashing" sub-rule does not respect that flag.zlib.crc32(...)formatted as 8 hex chars. CRC32 is not cryptographic, so the password-hashing rule cannot reach it; identity behaviour (stable, unique per token) is preserved. Mirrored the change to the SDK-vendored copy.py/incomplete-url-substring-sanitization"nvcr.io" in image_lower/"github.com" in urlchecks could be spoofed by paths or repo names containing the literal host (e.g.https://github.com.evil.example.com, an image tag withnvcr.ioin its repo path). 9 production sites and 9 test-assertion sites."nvcr.io" in registrywithregistry == "nvcr.io"once the registry host was already extracted; added an_image_registry_host_from_ref()helper to the quickstart prompts so we compare an extracted host equality; replaced the agents' forge detection ("github.com" in url) with a new_git_remote_host()helper that handles both HTTPS (urlparse) and SSH (user@host:path) git URL forms. Test assertions: switched tourlparse(call_args.kwargs["url"]).hostname == "expected.host". Mirrored every quickstart change in the SDK-vendored copy.py/clear-text-logging-sensitive-data*api_key*,*secret*,*auth*appear in the format string.workspace/name, provider name, env-var name) or a pre-masked value — none log secret material. Recommend bulk-dismissing in the Security tab with reason "false positive: only secret reference logged, not value".Test plan
uv run pytest plugins/nemo-agents/tests/unit/test_gateway.py— 20 pass (incl. new parametrized SSRF guard)uv run pytest plugins/nemo-anonymizer/tests/unit/test_sdk_job_resources.py -k 'extract or tar'— 3 passuv run pytest plugins/nemo-evaluator/tests/test_sdk_job_resources.py -k 'extract or tar or safe'— 6 passuv run pytest services/core/inference-gateway/tests/unit/test_proxy.py— 82 passuv run pytest services/core/inference-gateway/tests/unit/test_model_router.py services/core/inference-gateway/tests/unit/test_openai_router.py— 72 passuv run pytest packages/nemo_platform_ext/tests/quickstart/test_prompts.py test_container.py test_quickstart_config.py— 118 passuv run pytest plugins/nemo-agents/tests/unit/test_improvement_preflight.py test_improvement_jobs.py— 17 passpnpm --filter nemo-studio-ui typecheck— cleanpy/clear-text-logging-sensitive-dataalerts as "false positive" in the Security tabSummary by CodeRabbit
New Features
Bug Fixes
Security Enhancements
Tests