Security¶
Since 2026-07-30 every PASTA endpoint needs credentials1, so an EDI
API key is a real secret: it reads everything its owner can read, including
access-restricted packages. edi-mcp treats it accordingly. Version 1 is
read-only: no tool creates, updates or deletes anything in EDI, and the
only local writes will be the stdio-only downloads, confined to
downloads.directory.
Where the key comes from¶
- stdio: the server's environment only.
EDI_API_KEY(the name EDI's guides use2) orEDI_MCP_EDI__API_KEY, which overrides it. There is no command-line flag (command lines leak intopsand shell history),.envfiles are not loaded automatically, and look-alike names (EDI_KEY,EDI_TOKEN,EDI_AUTH_TOKEN) are not read: the loader warns when one is set withoutEDI_API_KEY. A YAMLedi.api_keyalso works but should not be committed. - HTTP: each caller's
X-EDI-Keyheader, honoured only whenserver.public_base_urlishttps://(or the development overrideallow_insecure_header_keyis on); otherwise the header is stripped before any handler runs. The operator's key is not lent to callers unlessshare_config_key_over_httpis enabled. The MCP-levelAuthorizationheader is never read for EDI and never forwarded upstream3. See Hosted HTTP deployment.
A bad key is an error (upstream_forbidden), never a silent downgrade to
DataONE.
What happens to the key¶
- The key is held in an
ApiKeyobject whosereprandstrnever show it, and every key and token the process sees is registered with the log redactor. - The key is exchanged for a short-lived
edi-token(8 hours) atauth.edirepository.org. Tokens live in memory only, one slot per key identity (a truncated SHA-256 of the key), so HTTP callers with different keys never share a token. A token is refreshed once fewer thanedi.token_refresh_margin_sseconds remain (default one hour); a 401 to a token triggers one re-exchange. - The token is sent to PASTA as a hand-set
Cookie: edi-token=…header. If the auth service is unreachable or failing, the key itself is sent as a?key=query parameter foredi.auth_fallback_sseconds (default 300). If the auth service rejects the key, the call fails. - Credentials are attached only to requests for the PASTA host. DataONE, the member nodes and the citation service are always called anonymously.
- The HTTP client's cookie jar accepts no cookies, so the
edi-tokencookie PASTA sets (even on anonymous 403 responses) is never stored or replayed to another caller. - Cached PASTA responses are keyed by the key's identity, so one caller's cached results are never served to another caller.
What never leaves the server¶
scrub_url()removes thekeyquery parameter and redacts every registered secret from each URL that is surfaced: resultsourcefields,_metaupstream labels, log lines, cache keys and error details. Endpoint labels (GET host/path) carry no query string at all.- HTTP transport errors are re-raised without the underlying exception, whose message could contain a keyed URL.
- Logs go to stderr (stdout is MCP framing on stdio). A structlog processor
masks values of credential-like keys (
token,authorization,api_key,x-edi-key,cookie, …) and replaces registered secrets anywhere in a logged string. - Upstream error text is cleaned before it reaches a result: registered
secrets are redacted, EDI principals (
EDI-<40 hex>) becomeEDI-…, and PASTA's 403 wordingUser '…' is not authorized, which names the caller, has the name removed. - MRTR continuations (
requestState) refuse credential-like keys and are capped at 16 KiB.
Outbound requests¶
- Upstream hosts come from the
edi.*URL settings: PASTA, the EDI auth service, the DataONE coordinating node, the two GMN member nodes and the citation service. Redirects are followed by hand, at most three, and only tohttps://hosts on that allow-list; anything else fails with an error. - Response bodies are read with a byte cap (
edi.max_response_bytes, default 20 MiB), and EML documents larger thanlimits.max_eml_bytesare refused. Tool results are trimmed tolimits.max_result_bytesand fail abovelimits.hard_max_result_bytes. - Concurrency is bounded (
edi.max_concurrency); 429 responses honourRetry-After, and 5xx and network errors are retried with backoff.
Untrusted XML¶
EML metadata and DataONE Solr responses are parsed with defusedxml4
only, which rejects entity expansion and external DTDs. Raw EML is not placed in
tool results.
Test fixtures¶
Hermetic tests replay recorded responses, so fixtures must never carry a
secret. scripts/record_fixtures.py record never records auth-service traffic,
keeps no response headers except Content-Type and Location, and scrubs every
body before writing it: the key, every edi-token seen, anything JWT-shaped,
and the identity claims decoded from the token. Recordings replace the committed
set only if check passes; CI runs check on every push (no JWT-shaped string,
no edi-token=, no set-cookie, no key= in a manifest URL). The test router
fails a test if a credential reaches any host other than PASTA. See
Testing.
Reporting a vulnerability¶
Report a suspected vulnerability privately to the idss-mesa maintainers rather than in a public issue, and never paste a key or token into an issue, log or fixture.
-
EDI news: authentication required for PASTA (2026-07-27). https://edirepository.org/news/news-20260727.00 ↩
-
EDI — Working with access keys. https://edirepository.org/resources/working-with-access-keys ↩
-
MCP 2026-07-28 — Security best practices. https://modelcontextprotocol.io/specification/2026-07-28/basic/security_best_practices ↩
-
defusedxml. https://github.com/tiran/defusedxml ↩