What broke?
At current main (5d960f55a4494a39034db8c4256d10556ef4fb5a), a catalog imported from a local path can later expose that path as the source field in an ARD search result.
ard add catalog SOURCE passes the raw CLI argument to UpsertCatalogWithStatuses; recordFromEntryWithStatus stores it in CatalogEntryRecord.Source; and SearchPage copies that value into ard.SearchResult.Source. For a command such as:
ard add catalog fixtures/catalog.json
the resulting public search item can therefore contain:
{
"identifier": "urn:air:example.com:tool:reader",
"source": "fixtures/catalog.json"
}
ARD v0.91 defines source, when present, as the URI endpoint of the registry where the entry was indexed. The current normative text also permits every search-result field except identifier to be omitted, so a registry need not invent a value when it has no public base URI configured.
Relevant current paths:
internal/cli/add.go: passes the local SOURCE argument into the store
internal/store/postgres.go: stores it as CatalogEntryRecord.Source
internal/store/postgres.go: emits record.Source in search results
Reproduction
A minimal in-package reproduction is:
entry := ard.CatalogEntry{
Identifier: "urn:air:example.com:tool:reader",
DisplayName: "Reader",
Type: "application/mcp-server-card+json",
URL: "https://example.com/reader.json",
}
record, err := recordFromEntry(entry, "fixtures/catalog.json")
if err != nil {
t.Fatal(err)
}
result := ard.SearchResult{CatalogEntry: entry, Score: 100, Source: record.Source}
// result.Source == "fixtures/catalog.json", which is not an absolute URI.
This follows the same assignments as the CLI import and search paths. I verified the behavior against the current source at the commit above. I could not execute the Go test on the qualification host because that host does not have the Go toolchain installed.
Expected behavior
Public search results should either:
- emit the configured absolute URI of the registry that indexed the entry; or
- omit
source when no such public URI is configured.
Local import provenance can remain an internal field, but should not be reused as the public registry-source URI. Federated results should continue to preserve a valid upstream registry URI.
Impact
Schema-valid consumers that enforce format: uri can reject otherwise valid OpenARD search output. The current field also conflates private import provenance with public registry provenance and can disclose a local path. This report is limited to wire-format interoperability and provenance semantics; it does not assert an execution or authorization vulnerability.
Additional context
The relevant ARD v0.91 schema describes source as a URI and the registry endpoint where the entry was indexed:
https://github.com/ards-project/ard-spec/blob/aa3e598bb7752a9175897823234311216acfa864/spec/schemas/ard.openapi.yaml#L283-L305
The normative search text states that only identifier is mandatory in a returned item:
https://github.com/ards-project/ard-spec/blob/aa3e598bb7752a9175897823234311216acfa864/spec/ard.md#L283-L290
What broke?
At current
main(5d960f55a4494a39034db8c4256d10556ef4fb5a), a catalog imported from a local path can later expose that path as thesourcefield in an ARD search result.ard add catalog SOURCEpasses the raw CLI argument toUpsertCatalogWithStatuses;recordFromEntryWithStatusstores it inCatalogEntryRecord.Source; andSearchPagecopies that value intoard.SearchResult.Source. For a command such as:ard add catalog fixtures/catalog.jsonthe resulting public search item can therefore contain:
{ "identifier": "urn:air:example.com:tool:reader", "source": "fixtures/catalog.json" }ARD v0.91 defines
source, when present, as the URI endpoint of the registry where the entry was indexed. The current normative text also permits every search-result field exceptidentifierto be omitted, so a registry need not invent a value when it has no public base URI configured.Relevant current paths:
internal/cli/add.go: passes the localSOURCEargument into the storeinternal/store/postgres.go: stores it asCatalogEntryRecord.Sourceinternal/store/postgres.go: emitsrecord.Sourcein search resultsReproduction
A minimal in-package reproduction is:
This follows the same assignments as the CLI import and search paths. I verified the behavior against the current source at the commit above. I could not execute the Go test on the qualification host because that host does not have the Go toolchain installed.
Expected behavior
Public search results should either:
sourcewhen no such public URI is configured.Local import provenance can remain an internal field, but should not be reused as the public registry-source URI. Federated results should continue to preserve a valid upstream registry URI.
Impact
Schema-valid consumers that enforce
format: urican reject otherwise valid OpenARD search output. The current field also conflates private import provenance with public registry provenance and can disclose a local path. This report is limited to wire-format interoperability and provenance semantics; it does not assert an execution or authorization vulnerability.Additional context
The relevant ARD v0.91 schema describes
sourceas a URI and the registry endpoint where the entry was indexed:https://github.com/ards-project/ard-spec/blob/aa3e598bb7752a9175897823234311216acfa864/spec/schemas/ard.openapi.yaml#L283-L305
The normative search text states that only
identifieris mandatory in a returned item:https://github.com/ards-project/ard-spec/blob/aa3e598bb7752a9175897823234311216acfa864/spec/ard.md#L283-L290