Skip to content

Require the API schema parameter to be a URL - #1445

Merged
andredlng merged 1 commit into
mainfrom
fix-api-schema-url
Jul 28, 2026
Merged

Require the API schema parameter to be a URL#1445
andredlng merged 1 commit into
mainfrom
fix-api-schema-url

Conversation

@andredlng

Copy link
Copy Markdown
Collaborator

Summary

The /lint endpoint of the datacontract api server documents its schema query parameter as "This must be a URL", but nothing enforced it. fetch_schema treats anything that is not http(s):// as a filesystem path, so an unauthenticated caller could make the server read its own files.

Verified against a running server before the fix:

request before
?schema=/tmp/local-schema.json server read the local file and validated against it — "result":"passed"
?schema=/etc/passwd server opened it, failing at JSON parse (Expecting value: line 1) — proof the read happened
?schema=/nonexistent/path distinct The file ... does not exist — a file-existence oracle

Only http:// and https:// are accepted now, and every rejection returns the same 422, so nothing can be learned about the filesystem from the response.

After the fix, same three requests:

/tmp/local-schema.json   -> {"detail":"The schema parameter must be an http:// or https:// URL."}
/etc/passwd              -> {"detail":"The schema parameter must be an http:// or https:// URL."}
/nonexistent/path        -> {"detail":"The schema parameter must be an http:// or https:// URL."}

A real URL and the no-schema case both still work.

How it was found

Two of the three open CodeQL alerts — py/path-injection at lint/schema.py:50 and :60. They read as false positives at first, since the same function is reached from the CLI's --json-schema, where the operator supplies the path deliberately. Following the call graph to api.py showed a second caller where the value is remote and unauthenticated, which is what makes them real.

The third alert is a false positive

py/unsafe-deserialization at lint/resolve.py:405 flags yaml.load(data_contract_str, Loader=_SafeLoaderNoTimestamp). That loader is a yaml.SafeLoader subclass which only drops the timestamp implicit resolver so dates stay strings. CodeQL does not track the subclass. No code change; it should be dismissed as a false positive rather than worked around.

Still open, deliberately not in this PR

The URL branch calls requests.get on a caller-supplied URL with no restriction, so the server can be made to fetch arbitrary hosts, including link-local metadata endpoints. Closing that needs a policy decision — an allowlist, or blocking private ranges — rather than a one-line validation, so it is worth deciding separately.

Testing

Four tests: a local path rejected, a missing path rejected identically to an existing one, a relative path rejected, and an http URL still accepted.

The lint endpoint documents schema as a URL, but nothing enforced it and
fetch_schema falls through to the filesystem for anything that is not http(s).
An unauthenticated caller could therefore have the server open its own files,
and distinguish an existing path from a missing one by the error returned.

Only http and https are accepted now, and every rejection looks the same.
@andredlng andredlng self-assigned this Jul 28, 2026
@github-actions

Copy link
Copy Markdown

📖 Docs preview

Preview site: https://brave-water-0cee36e03-1445.westeurope.7.azurestaticapps.net

Changed pages (most-changed first):

@andredlng
andredlng merged commit f27a459 into main Jul 28, 2026
16 checks passed
@andredlng
andredlng deleted the fix-api-schema-url branch July 28, 2026 13:57
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.

1 participant