Summary
The generic failure paths for legacy request and response adapters include
type(exc).__name__ and str(exc) in buyer-facing AdCP error envelopes. An
unexpected adapter exception may contain internal model names, URLs, query
fragments, filesystem paths, or credential material.
This is the SDK-level form of the error-disclosure class identified while
Prebid Sales Agent audited its shared error normalizer:
prebid/salesagent#2193
Current behavior
On current main (28801dbc2e739541dd40ed2431dcf5aec74f408f):
- A generic exception from
legacy_adapter.adapt_request() becomes an
INVALID_REQUEST whose message includes the exception class and raw text:
|
try: |
|
params = legacy_adapter.adapt_request(params) |
|
except Exception as exc: |
|
raise ADCPTaskError( |
|
operation=method_name, |
|
errors=[ |
|
Error( |
|
code="INVALID_REQUEST", |
|
message=( |
|
f"Legacy adapter for {method_name!r} at " |
|
f"AdCP {wire_version} failed: " |
|
f"{type(exc).__name__}: {exc}" |
|
), |
|
) |
|
], |
|
) from exc |
- A generic exception from
legacy_adapter.normalize_response() becomes an
INTERNAL_ERROR with the same disclosure:
|
if legacy_adapter is not None and legacy_adapter.normalize_response is not None: |
|
if isinstance(result, dict) and "adcp_error" not in result: |
|
try: |
|
result = legacy_adapter.normalize_response(result) |
|
except Exception as exc: |
|
raise ADCPTaskError( |
|
operation=method_name, |
|
errors=[ |
|
Error( |
|
code="INTERNAL_ERROR", |
|
message=( |
|
f"Legacy response normalizer for " |
|
f"{method_name!r} at AdCP " |
|
f"{wire_version} failed: " |
|
f"{type(exc).__name__}: {exc}" |
|
), |
|
) |
|
], |
|
) from exc |
The A2A executor already applies the safer default for an unexpected handler
exception: it logs the traceback and sends a fixed Skill execution failed
message. Legacy-adapter failures should have the same trust boundary.
Proposed behavior
- Treat explicitly typed, buyer-correctable adapter errors as public only when
their contract says their message is safe for the wire.
- For every other request-adapter exception, preserve
INVALID_REQUEST if that
classification is intentional but send a fixed public message and log the
original exception with exc_info.
- For every unexpected response-normalizer exception, retain
INTERNAL_ERROR
and send a fixed public message; the original exception belongs only in
operator diagnostics.
- Centralize this policy so MCP and A2A cannot diverge.
Acceptance criteria
- A synthetic exception containing a sentinel hostname, query fragment, and
secret does not place the sentinel in any MCP or A2A wire field.
- The original exception and traceback remain available to operator logging.
- Public, explicitly typed buyer-correctable adapter errors retain actionable
messages.
- Error codes, recovery metadata, and request-context echo remain unchanged.
- Tests exercise both the request-adapter and response-normalizer failure
paths through the real transport-facing dispatcher.
Summary
The generic failure paths for legacy request and response adapters include
type(exc).__name__andstr(exc)in buyer-facing AdCP error envelopes. Anunexpected adapter exception may contain internal model names, URLs, query
fragments, filesystem paths, or credential material.
This is the SDK-level form of the error-disclosure class identified while
Prebid Sales Agent audited its shared error normalizer:
prebid/salesagent#2193
Current behavior
On current
main(28801dbc2e739541dd40ed2431dcf5aec74f408f):legacy_adapter.adapt_request()becomes anINVALID_REQUESTwhose message includes the exception class and raw text:adcp-client-python/src/adcp/server/mcp_tools.py
Lines 2861 to 2876 in 28801db
legacy_adapter.normalize_response()becomes anINTERNAL_ERRORwith the same disclosure:adcp-client-python/src/adcp/server/mcp_tools.py
Lines 3148 to 3166 in 28801db
The A2A executor already applies the safer default for an unexpected handler
exception: it logs the traceback and sends a fixed
Skill execution failedmessage. Legacy-adapter failures should have the same trust boundary.
Proposed behavior
their contract says their message is safe for the wire.
INVALID_REQUESTif thatclassification is intentional but send a fixed public message and log the
original exception with
exc_info.INTERNAL_ERRORand send a fixed public message; the original exception belongs only in
operator diagnostics.
Acceptance criteria
secret does not place the sentinel in any MCP or A2A wire field.
messages.
paths through the real transport-facing dispatcher.