source-cooperative / source-cooperative/data.source.coop

Code comments claim the data-connection fetch authorizes the caller; it does not

Open
#217 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

documentation
Dominant language
Rust
Stars
24
Forks
6
Avg merge
1h 32m
Merged PRs (30d)
1

Description

Summary

Two code comments assert that the subject-scoped data-connection fetch authorizes the caller for that connection. It does not — the endpoint authorizes every caller, including anonymous ones. The actual gate is the product fetch.

No known vulnerability follows from this (see below), but the comments describe a security guarantee the system does not provide, and they are load-bearing for anyone reasoning about the federation ordering.

Evidence

GET /api/v1/data-connections/{id} gates on Actions.GetDataConnection, whose implementation is:

// source.coop:src/lib/api/authz.ts
function getDataConnection(
  principal: UserSession | null,
  _dataConnection: DataConnection
): boolean {
  if (principal?.account?.disabled) {
    return false;
  }
  return true;
}

For an anonymous caller principal is null, so principal?.account?.disabled is undefined → falsy → returns true. It has returned true unconditionally since it was introduced.

This is deliberate on the API side — source.coop:src/lib/api/sanitize-data-connection.ts says secret-less authentication config "is returned to any caller (the proxy reads it impersonating the end user)".

The inaccurate comments

  • src/source_api/cache.rs (get_or_fetch_data_connection): "lets the subject-scoped Source API authorize this exact connection: a caller not entitled to it gets 404/403"
  • src/source_api/registry.rs (resolve_product, step 3 and the confused-deputy note): "the subject-scoped API authorizes this exact resource" / "the subject-scoped Source API fetches above … only return the product/connection this caller is authorized for"

Why the security conclusion still holds

The product fetch is genuinely gated (Actions.GetRepository), and a connection is only ever reached by following a mirror reference from an already-authorized product. So the deny-before-federate ordering is intact; it just rests entirely on the product gate, not on both fetches.

Suggested action

  1. Correct the two comments so the guarantee is attributed to the product fetch alone.
  2. Decide whether getDataConnection should gate. Returning connection metadata to any caller who can guess an id is a deliberate choice, but it is worth an explicit decision rather than an implicit one — connection ids are not secrets and the response includes provider, bucket, region, prefix, and the auth type.

Notes

Found while auditing the ADRs in #115 against the implementation. The ADR text making the same claim is being corrected separately; this issue covers the code comments and the API-side question.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Read src/source_api/cache.rs and src/source_api/registry.rs alongside src/lib/api/authz.ts and src/lib/api/sanitize-data-connection.ts to verify which fetch performs authorization. Update the two comments to attribute the guarantee to the product fetch, and record the explicit decision about whether getDataConnection should gate callers; done means the comments no longer claim a guarantee the endpoint does not provide.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust, typescript
Domain
backend-api-design, security
Issue type
Documentation
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
58/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.