Repository navigation
Relations: a reader or writer learns nothing of a version it may not see (0.1.4) - #26
Merged
Merged
Conversation
…see (0.1.4) A public Card citing a private Eval gave the Eval away three ways. GET and POST .../query returned the Card body verbatim, so relations[].to carried the private ns/name@seq. A relations.to condition in a query matched every stored edge, so a caller could confirm a private name by whether a public Card was hit. And writes resolved targets regardless of visibility, so the response and the refs_resolved badge told the writer whether a private version existed. Reads now remove the relations[] elements whose target the reader may not see and list them under withheld.relations as the commitment (type, version_id, content_hash). Query conditions match only visible targets. Ingest, POST .../relations and the deferred resolver treat an invisible target exactly as a nonexistent one: stored unresolved, same response, same badge; it resolves once the target becomes visible. The crate docs (schema eval, store records/relations, server API) state the rule. OpenAPI snapshot, web/ client and .sqlx are regenerated. Verified: cargo fmt --check; cargo clippy --workspace --all-targets -D warnings; cargo test -p evalhub-schema / -store / -server (Docker); just sqlx-check; pnpm check; just e2e. Refs #25 Signed-off-by: Yutaka Nishimura <ytk.nishimura@gmail.com>
CI for ba247f0 failed in evalhub-store tests/objects.rs before any test code ran: pulling quay.io/minio/minio:RELEASE.2025-02-28T09-55-16Z answered 401 unauthorized. The same image passed CI on 0c96b4a the day before; quay.io now refuses anonymous manifest reads for minio/minio and minio/mc, and Docker Hub's minio/minio was already unpullable. cgr.dev/chainguard/minio is pullable anonymously and ships mc and a shell, which the bucket setup needs. It publishes only `latest`, so it is pinned by digest. /data is not a volume in that image: on the overlay root MinIO logs a rename error whose first line is "API: ...", which the old wait matched before the server was up. The tests mount a tmpfs on /data and wait for the "API: http" banner instead. compose.yml uses the same image for minio and, for its bundled mc, for minio-init. Verified: cargo fmt --check; cargo clippy --workspace --all-targets -D warnings; cargo test -p evalhub-store and -p evalhub-server (Docker). Signed-off-by: Yutaka Nishimura <ytk.nishimura@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden U
6860
nicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
For #25. Releases 0.1.4.
evalhub_schema::evalpromises that when a public Card points at a private Eval, a reader without access learns that the Eval exists, itsversion_idand itscontent_hash, and nothing else. The hub kept that promise only inGET …/relations. Three other paths gave the Eval away:GET /cards/…andPOST /cards/queryreturnedrelations[].to, which is the privatens/name@seqas text.relations.tocondition (exact or prefix) was evaluated against targets the caller could not see, so a caller could confirm a private name by whether a public Card was hit.POST …/relationsthat named someone else's private version resolved it, so the response and therefs_resolvedbadge told the writer that the version existed.Export was not a path:
hf-model-indexis rendered fromresults[]and carries no relation.What changes
Reads withhold what the reader may not see. Every read that returns a body removes the
relations[]elements whose target the reader may not see, and lists them in the envelope as the commitment:A body with elements withheld is not the stored body, so its
content_hashdoes not match it. Verify the hash only on responses that have nowithheld. A member of the Eval's namespace gets the body unchanged and nowithheld.Query conditions see only visible targets. A
relations.tocondition matches only edges whose target the caller may see. Negated, the hidden edge is just as absent.A write learns no more than a read. At ingest, in
POST …/relationsand in the deferred resolver, a target the writer may not see is handled exactly like one that does not exist: it is stored unresolved, the response is the same and norefs_resolvedbadge is given. Once the target is visible to a reader, the edge resolves for them. The writer's own namespace and their organisations' namespaces are visible, so legitimate private references resolve as before.One visibility predicate (
evalhub_store::relations::visible_to) serves the relation DTO, body redaction and resolution.Contract
withheldis new and optional on the version envelope and on query hits. The OpenAPI snapshot and theweb/client are regenerated.just semver-checkreports no semver update required forevalhub-schema,-coreand-query. The schema change is limited 8000 to the crate doc (eval§ "Privacy across the relation")..sqlxis regenerated for the changed queries.Verification
crates/evalhub-server/tests/privacy.rs: five HTTP tests covering the body onGET, the body on query, therelations.tocondition, the badge onPOST, andPOST …/relations. All five were first run againstmainand failed on the defect itself:alice/secret@1appeared in the body, bob's condition hit, the badge differed from that of a nonexistent target, and the relation response revealed the target.relations_resolve_only_to_versions_the_writer_may_see,resolution_never_reveals_a_private_versionandrelation_filters_see_only_targets_the_caller_may_seewere checked against this commit with the three visibility checks disabled, and each failed on the leak.cargo fmt --check;cargo clippy --workspace --all-targets -- -D warnings;cargo test -p evalhub-schema / -core / -query / -store / -server(Docker);just sqlx-check;pnpm check;just e2e(18 passed);just package+package-gate;just semver-check.Test image: MinIO from Chainguard
The first CI run of this PR failed in
evalhub-storetests/objects.rsbefore any test code ran: quay.io now answers401to anonymous pulls ofquay.io/minio/minio(andminio/mc), which passed onmainthe day before. The store and server test harnesses andcompose.ymlnow usecgr.dev/chainguard/minio, pinned by digest (it publisheslatestonly). It shipsmcand a shell, so the bucket setup is unchanged. The tests mount a tmpfs on/dataand wait for theAPI: httpbanner, because on the overlay root MinIO first logs a rename error that also starts withAPI:.