Skip to content

security: fix path traversal and query injection in resource names - #96

Merged
tamirse merged 7 commits into
masterfrom
fix/path-traversal-in-resource-names
Aug 10, 2026
Merged

security: fix path traversal and query injection in resource names#96
tamirse merged 7 commits into
masterfrom
fix/path-traversal-in-resource-names

Conversation

@tamirse

@tamirse tamirse commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Resource names and IDs are no longer interpolated into request paths. containers.delete_deployment('../../v1/instances') issued DELETE /v1/instances under the SDK's own credentials; a ? in a name could inject query parameters, e.g. overriding the force flag of delete_secret.

HTTPClient.get/post/put/patch/delete now take path_params mapping, and every service module passes names and IDs that way. Values are validated as a single path segment.

Breaking changes

  • A path value must match [A-Za-z0-9._~-]; /, \, %, spaces, ? and # now raise ValueError.
  • ., .., empty, None, and any type other than str/int/UUID raise.
  • InferenceClient requires endpoint_base_url to include the deployment path. Without one, base_domain became https:/ and async status/result requests went to a host named status, still carrying the inference key..

tamirse added 3 commits August 7, 2026 09:58
Caller-supplied resource names and IDs are no longer interpolated directly into request paths. This prevents malicious input containing `../` from retargeting API calls, and `?` or `#` from injecting query parameters or fragments.

Values are now percent-encoded as single path segments, and `.` `..` or empty values are rejected. The HTTP client also includes a backstop to refuse requests that attempt to escape the API base path.
Expands the path traversal protection to cover additional service methods,
including `instances.is_available()` and `clusters.is_available()`, and
various container and job deployment operations.

Enhances path parameter validation to reject unused keys, preventing silent
errors from misspelled or stale parameters. Non-string path values are now
gracefully coerced to strings for backward compatibility.

Reorders token refresh to occur after path validation, preventing unnecessary
network calls when path parameters are invalid.
Strengthens path traversal protection by explicitly rejecting resource names
or IDs that contain relative path segments (`.` or `..`), are empty, or `None`.
Previously, such values were merely percent-encoded, which is insufficient as
`requests` decodes unreserved characters or intermediaries unescape encoded
slashes, re-introducing vulnerabilities.

This also expands protection to `InferenceClient` paths, ensuring they cannot
escape their deployment's base URL. All HTTP verb methods now delegate to a
central `_request` method, standardizing path validation and ensuring
comprehensive coverage.

This introduces two breaking changes:
- Resource names or IDs containing a relative path segment, an empty value,
  or `None` now raise `ValueError`.
- Resource names and IDs are always percent-encoded by the client; pre-encoded
  values will be double-encoded. Callers should pass raw names.
@tamirse
tamirse requested a review from shamrin August 7, 2026 08:01
tamirse and others added 4 commits August 7, 2026 11:25
Moves path traversal detection to `verda.helpers.has_relative_path_segment` to unify checks across `HTTPClient` and `InferenceClient`, ensuring consistent protection against `.` and `..` segments, even when percent-encoded.

Expands `_encode_path_segment` to explicitly reject path parameters that are not `str`, `int`, or `UUID`, preventing ambiguous coercion (e.g., `bytes` to `"b'abc'"`) that would result in confusing 404s.

Hardens `_add_base_url` to reject paths containing query strings or fragments, preventing URL component injection. Adds a dynamic test to ensure all service methods accepting path parameters are covered by traversal tests.

**Breaking changes:**
- A path value that is not `str`, `int`, or `UUID` now raises `ValueError`.
- A `/` within a resource name is now consistently encoded as `%2F`, ensuring it remains a single path segment. Servers that reject or refuse to decode encoded slashes may break.
A resource name or id in a request path must now match `[A-Za-z0-9._~-]` (RFC
3986 unreserved). A value containing `/`, `\`, `%`, a space, `?` or `#` raises
`ValueError` rather than being percent-encoded and sent. Every name the API
takes in a path position is a slug, an id or a machine type (`my-deployment`,
`1A100.22V`, a UUID), so ordinary calls are unaffected; pass a raw name rather
than a pre-encoded one.

`InferenceClient` now requires `endpoint_base_url` to include the deployment
path. Without one, `rindex('/')` found the `//` of the scheme, making
`base_domain` `https:/` and sending async status and result requests to a host
named `status` while still carrying the inference key.

Refusing `%` removes the ambiguity the previous approach had to resolve by
predicting how `requests` and intermediaries decode a path. The http client no
longer models either, so its raw-value and finished-path checks can no longer
disagree: `..\..\x` previously passed the first and was caught only by the
backstop, which is documented as redundant. `has_relative_path_segment` is now
used by `InferenceClient` alone.

**Breaking changes:**
- A path value outside the unreserved set now raises `ValueError` instead of
  being percent-encoded. This includes `/`, which is no longer sent as `%2F`.
- `InferenceClient` rejects an `endpoint_base_url` with no deployment path.
A checked path value cannot contain a separator, so substitution can no longer
change the shape of the route. The guard was reachable only by monkeypatching
the encoder, which is all its test did.

@shamrin shamrin 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.

The current design for the fix seems to be: "accept almost any character, encode it, then model what downstream systems will decode." I think the PR is trying to do too much. In fact I've pushed the change to fix it. Please review.

@tamirse
tamirse requested a review from shamrin August 10, 2026 09:00
@tamirse
tamirse merged commit cf751a4 into master Aug 10, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants