Skip to content

feat(api): add an authorized artifact-content and storage-provenance contract #14031

Description

@jeffspahr

Feature description

PR #13986 replaces MLMD-backed artifact discovery with the native Artifact API, but artifact bytes are still read through a frontend-server route whose authority is assembled from caller-visible URI coordinates, an opaque providerInfo string, namespace-prefix heuristics, and optional per-namespace proxying.

That is not a complete replacement for MLMD's trusted ownership and storage context. The native API should expose an authenticated artifact-content contract keyed by artifact identity, with storage resolution and authorization performed from trusted server-side state.

Related:

Why this is a follow-up rather than a frontend patch in #13986

The current PR can and should close bounded frontend-server regressions: canonical namespace-prefix validation, view-role RBAC, query-safe links, request fan-out, and failure isolation. It cannot safely manufacture trusted storage provenance from browser-supplied values.

A durable fix requires a backend/API contract, authorization semantics, persisted or derivable trusted storage configuration, generated-client changes, and compatibility tests across S3, MinIO, GCS, custom roots, single-user, multi-user, and historical artifacts. Adding another query parameter or broadening the frontend server's credentials would enlarge the trust boundary and could turn compatibility failures into cross-tenant reads or credential use.

Verified gaps

1. Namespace-local Artifact rows are not sufficient ownership proof for custom roots

An authorized user can create/import an Artifact row in their namespace whose URI points at another location. An exact URI match in the caller's namespace therefore proves that the row is visible to the caller; it does not prove that centrally privileged object-store credentials may read the referenced bytes.

The private-artifacts/<namespace>/... convention provides independent ownership evidence for the standard root. Custom roots need equally trusted provenance or namespace-isolated credentials.

2. Opaque providerInfo crosses the browser/proxy trust boundary

Artifact URI query parameters are currently converted in the browser into provider JSON and forwarded through the central UI to the namespace artifact proxy. If accepted as authority, that JSON can select an endpoint and, in secret-backed mode, name credential Secret keys. Depending on the proxy service account's permissions, this permits unauthorized credential use/request signing and endpoint or SSRF control.

The browser may carry a hint, but it must not establish which endpoint or credentials the server is authorized to use.

3. Query-bearing URIs lose exact ownership identity

For a stored URI such as s3://bucket/key?endpoint=..., the browser separates the query from the object key before calling the frontend server. The ownership validator reconstructs s3://bucket/key, so an exact native Artifact lookup cannot match the stored row. Strict ownership mode and custom-root proxy reads therefore reject legitimate records.

Reversing an opaque provider JSON string into a URI is not a safe canonicalization contract.

4. Namespace-prefix fallback is not bound to a trusted root

The fallback recognizes private-artifacts/<namespace>/... below any scheme/authority. If shared credentials can read another bucket or endpoint containing the same path, matching the namespace segment alone is not sufficient ownership evidence.

The fallback should be bound to a trusted configured root and retired for migrated records once #14029 is complete.

5. Custom-root credentials have no safe default multi-user path

The central UI correctly refuses to read tenant Secrets. The current alternative is the optional per-namespace artifact proxy, which is disabled by default and already documented as carrying security, maintenance, and scalability costs.

The replacement architecture needs a supported namespace-local credential path without requiring the browser to transport credentials/configuration or the central UI service account to read tenant Secrets.

providers.<provider>.default supplies storage settings and credentials only after a URI is admitted by the configured pipeline root or an explicit override; it is not authorization for arbitrary outside-root paths.

6. Artifact content lacks bounded transfer metadata

The native Artifact model does not provide a content-read operation with authoritative size, media type, range/peek semantics, or a server-enforced limit. Selected HTML/Markdown and legacy viewer sources may therefore be buffered in full before rendering.

Proposed API shape

The exact transport is open for design, but it should provide an artifact-ID-based operation such as:

  • a streaming GetArtifactContent/HTTP content endpoint;
  • a short-lived presigned URL minted after authorization; or
  • a server-side proxy with equivalent bounded streaming behavior.

The operation should:

  1. Accept artifact identity, not caller-selected credentials.
  2. Load the Artifact record and authorize access to its run/namespace.
  3. Resolve storage configuration from trusted launcher/run/artifact provenance.
  4. Bind the resolved scheme, authority, bucket, key, query configuration, and credential scope to that artifact.
  5. Support custom roots without granting the central UI broad tenant Secret access.
  6. Return or enforce media type, content length when known, byte ranges/peek, maximum preview size, and download disposition.
  7. Preserve streaming and cancellation; do not buffer arbitrary objects in the API server.
  8. Emit auditable errors that distinguish authorization, missing record, invalid trusted configuration, missing object, and transient provider failure without exposing secrets.

Security requirements

  • Never treat a caller-created Artifact row alone as authorization to use centrally privileged credentials for an arbitrary URI.
  • Never accept browser-supplied providerInfo, endpoint, region, Secret name, or Secret key as trusted authority.
  • Resolve tenant credentials inside the tenant's authorization boundary, or mint a narrowly scoped read capability after server-side validation.
  • Bind any prefix fallback to the configured scheme/authority/bucket/root and claimed namespace.
  • Protect against SSRF, DNS rebinding, redirects to untrusted hosts, credential forwarding, path traversal, encoded separators, and signed-query leakage.
  • Keep authorization fail-closed for custom roots and unknown provenance.
  • Ensure cache keys include artifact identity, authorization identity/context, namespace, content range, and configuration revision; do not share authorization results across tenants.

Acceptance criteria

  • A documented native artifact-content API is keyed by artifact ID and authorizes against trusted run/namespace ownership.
  • Standard roots, custom roots, S3, MinIO, and GCS work in single-user and multi-user deployments.
  • Browser-supplied provider JSON cannot select credentials, Secret keys, or an untrusted endpoint.
  • Query-bearing stored URIs retain one canonical identity from persistence through authorization and content resolution.
  • Namespace-prefix compatibility fallback is bound to a trusted configured root and has a documented retirement plan after feat(backend): add MLMD-to-native data migration and startup gate #14029.
  • Tenant credentials are resolved without the central UI service account reading arbitrary tenant Secrets.
  • Preview reads have server-enforced bounds; downloads support streaming/range behavior without whole-object buffering.
  • Errors distinguish permanent authorization/configuration failures from transient provider failures and contain corrective action.
  • Tests cover forged Artifact rows, forged provider info, wrong roots/buckets, endpoint injection/SSRF, cross-namespace reads, stale configuration, redirects, malformed encoding, and large objects.
  • Historical migrated artifacts from feat(backend): add MLMD-to-native data migration and startup gate #14029 use the same content path without weakening ownership checks.

Test plan

  • Table-driven unit tests for canonical URI/root normalization across every supported provider.
  • API authorization tests for owner, namespace viewer, namespace editor, unauthorized identity, deleted run, and mismatched artifact/run namespace.
  • Adversarial tests for caller-selected endpoint/Secret/provider values and tenant-created rows pointing at another root.
  • Integration tests using default and custom roots in at least two namespaces.
  • Streaming/range/peek tests for empty, small, large, missing, and interrupted objects.
  • Upgrade tests that read historical non-prefix artifacts after feat(backend): add MLMD-to-native data migration and startup gate #14029 migration.

Love this idea? Give it a 👍.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions