Skip to content

security: do not expose legacy adapter exception text in protocol errors #1126

Description

@bokelley

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

  1. A synthetic exception containing a sentinel hostname, query fragment, and
    secret does not place the sentinel in any MCP or A2A wire field.
  2. The original exception and traceback remain available to operator logging.
  3. Public, explicitly typed buyer-correctable adapter errors retain actionable
    messages.
  4. Error codes, recovery metadata, and request-context echo remain unchanged.
  5. Tests exercise both the request-adapter and response-normalizer failure
    paths through the real transport-facing dispatcher.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions