Skip to content

fix(agent): pin custom MCP connections to the validated address - #184

Merged
Atom-oh merged 1 commit into
fix/datasource-connector-boundsfrom
fix/custom-mcp-pinned-transport
Oct 19, 2026
Merged

Atom-oh merged 1 commit into
fix/datasource-connector-boundsfrom
fix/custom-mcp-pinned-transport

Conversation

@Atom-oh

@Atom-oh Atom-oh commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Part 2 of 3, stacked on #183 (fix/datasource-connector-bounds). Merge after #183, then retarget #185 to dev. This PR is agent-core only; the reference doc and guides moved to #185 so they describe code that exists below them.

  • The custom MCP transport (agent/pinned_http.py, used by streamable_http_sigv4.py) connects to the address set validated for that invocation, preserving Host, TLS SNI and certificate verification, and rejects every 3xx before the MCP SDK can follow it. IPv6 pins are bracketed, so AAAA-only endpoints connect.
  • Connect and read budgets are separate: CONNECT_TIMEOUT = 8 per pinned address, READ_TIMEOUT = 60 for time-to-headers / between SSE events (sse_read_timeout), inside the 120s chat budget. The transport deadline follows the read budget, never the connect budget. Discarded per-address failures are logged so a certificate failure on one address is not hidden behind the last one.
  • agent.py SSRF checks return the validated addresses, use the endpoint's port, classify a malformed port as SsrfBlocked, and reject backslashes. api_key header names and values and stored bearer tokens are validated; a pasted trailing newline in a value is stripped, not rejected.
  • httpx 0.28.1 / httpcore 1.0.9 are pinned (requirements.txt, Dockerfile) — the versions the SNI extension contract was tested against.
  • datasource_http.py's docstring now describes both pinned transports (this PR lands the module the sentence refers to).
  • The test TLS server sets a TLS 1.2 minimum (CodeQL py/insecure-protocol).

Takes effect after the next Deploy AgentCore.

Review follow-ups (round 1)

IPv6 breakage → bracketed host; 300s→8s read collapse → separate READ_TIMEOUT; hardcoded 8 → module constants imported by agent.py; parsed.port ValueError → SsrfBlocked; "request-local" → "invocation-local"; discarded errors logged; 3xx message no longer says "redirect" for Location-less responses. The doc contradictions (apiPrefix, per-kind windows, Jaeger limit disclosure, 55s form, oauth_3lo) are addressed by stacking: those docs now live in #185 above the code they describe. The "custom MCP contradicts ADR-007" finding was rejected by the chair and is also settled by #177 (merged).

Tests

  • bash scripts/v2/merge-verify.sh on Python 3.12: web vitest PASS, Terraform fmt PASS, deployment Node tests PASS; only the codec-sandbox tests fail for the known environmental reason (same on dev).
  • cd agent && pytest test_pinned_http.py test_agent.py test_readiness.py tests/test_health_tools.py tests/test_anthropic_loop.py → 182 passed, with new IPv6, read-budget, factory-timeout, malformed-port and whitespace-strip cases. Needs Python ≥ 3.11 (asyncio.timeout).

🤖 Generated with Claude Code

@github-actions

Copy link
Copy Markdown

🤖 AI Code Review (two independent reviewers)

_Reviewer responses: codex/ALL claude/ALL _

Status: BLOCKED — CRITICAL/MAJOR issues must be resolved before merge

All disputed claims are now verified. The final synthesized review follows.


Chair review — PR #184: fix(agent): pin custom MCP connections to the validated address

IMAGE_COVERAGE: NOT_REQUIRED

1. Summary

The core change is a genuine security improvement: it replaces the documented resolve-and-recheck DNS-rebinding window on the custom MCP integration transport with real IP pinning (validated address set, preserved Host/SNI, certificate verification, in-transport redirect rejection), plus a header-injection gate on stored credentials — and it is well-tested, including against a real TLS server. However, the pinning transport breaks IPv6-only endpoints, the new timeout wiring silently collapses the integration read budget from 300s to 8s, and the new docs/reference/external-connections.md (plus the four-locale docs-site rewrite) asserts several behaviors (settings.apiPrefix, per-kind window forwarding, Jaeger limit disclosure, a 55s form display, registration-time oauth_3lo rejection) that do not exist in the resulting code — despite the doc declaring itself "source-supported."

2. Issues per lens

L2 — Code correctness

MAJOR (claude flagged; chair-confirmed) — Bare IPv6 pinned addresses break AAAA-only endpoints. agent/pinned_http.py:28 pins via request.url.copy_with(host=address) where address is the unbracketed string from getaddrinfo (e.g. 2606:4700::1111). In the pinned httpx 0.28.1, encode_host only takes the IPv6 branch for hosts matching ^\[.*\]$; a bare colon-bearing host falls through to the reg-name path (percent-encoding/invalid authority). For an endpoint resolving only to AAAA records, every pin attempt fails, raise last_error fires, and an integration that worked on BASE is dropped. All tests use IPv4 literals, so the path is unexercised. Fix: bracket :-containing addresses before copy_with and add an IPv6 test case.

MAJOR (claude flagged; chair-confirmed) — Integration read budget collapses from 300s to 8s. agent/agent.py now passes sse_read_timeout=INTEGRATION_CONNECT_TIMEOUT (= 8, agent/agent.py:641) at both call sites; the MCP SDK builds httpx.Timeout(timeout, read=sse_read_timeout), so 8s becomes the read timeout for every request on the session, not just SSE. On BASE, read was the SDK default 300s (bounded in practice by the 120s chat budget). Additionally pinned_http.py:20-21 wraps the whole time-to-response-headers in a deadline derived from the connect timeout. Any integration tool whose upstream takes more than 8s to start responding now fails with ReadTimeout — a silent behavior change not mentioned anywhere, including the new reference doc. Keep connect at 8s but give read/SSE a separate, deliberate budget.

MINOR (claude) — agent.py:566 parsed.port raises an uncaught ValueError for a malformed port (https://host:abc/); only socket.gaierror is converted to SsrfBlocked. Isolated by gather_integration_tools, but the error is less precise than BASE.
MINOR (claude) — pinned_http.py hardcodes 8 twice instead of referencing INTEGRATION_CONNECT_TIMEOUT; a future bump in agent.py silently won't propagate.
MINOR (claude) — a control character in the api_key value reports "header configuration is invalid" (misleading cause); a trailing newline in a pasted token now hard-fails the integration — consider .strip() before validating.
MINOR (claude) — the 3xx rejection covers 300–399 including 304, so the "redirect rejected" message can be wrong for Location-less responses. Harmless for MCP POSTs.
MINOR (codex) — agent/test_pinned_http.py is not covered by the repo's required agent gate (python3 -m pytest test_agent.py test_readiness.py -q per root CLAUDE.md), so these transport regressions aren't release-gated. Add it to the required invocation.

L3 — Security / AWS mutation safety

No CRITICAL/MAJOR. The patch adds no AWS-mutating call, IAM statement, or hardcoded secret; the only AWS API touched is the pre-existing read-only GetSecretValue. The pin is strictly stronger than BASE (trust_env=False, follow_redirects=False, in-transport 3xx rejection, verified-cert test), and the auth_headers gate closes a real header-injection path (stored secret setting Host/Content-Length or smuggling CRLF).

Rejected (codex flagged MAJOR) — "Custom external MCP registration contradicts ADR-007 / frozen BYO-MCP." This is a false positive. BYO-MCP is an ADR-004 capability tier explicitly listed in docs/reference/05-agentcore.md:240-242 ("built-in vs custom tiers; per-account Agent Spaces; BYO-MCP"), and the registration/connection machinery (INTEGRATION_TRANSPORTS in web/lib/integration-validation.ts:11, the integrations migration, _connect_integration in BASE agent.py) all predate this PR — this PR only hardens the existing transport. What is FROZEN per ADR-005 is AWS mutation/autonomy (remediation.tf, integrations_write_enabled), which the new doc correctly states is unchanged. No repo rule freezes read-only custom MCP registration.

MINOR (claude) — the comment at agent.py:718 ("the validated address set is request-local") overstates: the pin is resolved once per _connect_integration and serves the whole MCPClient session, which may outlive the DNS TTL. Correct design, wrong label — say "invocation-local."
MINOR (claude) — certificate-verification failures surface as httpx.ConnectError and are silently retried on the next pinned address, with only the last error kept; log discarded errors to preserve the TLS-misconfiguration diagnosis.
MINOR (claude) — the agent's forbidden-header set intentionally omits authorization (unlike datasource_http.py's); one comment would prevent that reading as an omission.
MINOR (claude) — oauth_3lo remains accepted at registration (validation + migration CHECK) and only fails closed at connect time; acceptable defense, but see the L5 doc claim.

L4 — Observability / data-integration correctness

No Steampipe/CIS/diagnosis code is modified; the findings are contradictions between this PR's new documentation and the data-integration code it describes.

MAJOR (claude flagged; chair-confirmed) — the "search tools receive start/end" / "selected bounds are forwarded" contract is false for four of eight kinds. web/app/api/datasources/query/route.ts:102 applies the validated window only if (spec.range) — the comment literally says "otherwise the validated window is ignored (instant)" — and only Prometheus/Mimir/Loki have range tools. Tempo/Jaeger/Dynatrace/Datadog silently fall back to their 1h connector defaults, and maxWindow is a flat 30 days for all non-Loki kinds (route.ts:89), so the doc's per-kind 7-day ceilings are unenforced. Also "Unsupported SQL API ranges are rejected" is wrong: an object range is validated then ignored for ClickHouse, not rejected.

MAJOR (claude flagged; chair-confirmed) — Jaeger limit disclosure does not exist. The doc claims "Jaeger discloses its requested limit and retained count; reaching the requested limit is potentially partial." agent/lambda/jaeger_mcp.py:116 returns only truncated: len(traces) > MAX_TRACES (50); with the default limit=20, a result of exactly 20 traces reports truncated: false with no limit/count fields — i.e. it renders as verified-complete, the exact failure mode the doc says is prevented.

MINOR (claude) — the doc's 4 MiB success / 64 KiB error body caps have no implementation; datasource_http.py reads unbounded with only a 4000-char non-JSON display fallback. (The ok/empty/partial/unknown/error status union and HTTP-206 handling are real.)

L5 — Docs/ADR consistency

MAJOR (claude flagged; chair-confirmed) — settings.apiPrefix does not exist anywhere. Zero matches repo-wide; sanitizeDsSettings (web/lib/datasources.ts:32-45) retains only timeoutS and database, and the Mimir Lambda hardcodes /prometheus/api/v1. The claim ("defaults to /prometheus… empty value selects the root… 128 chars… not appended twice") appears in the new reference doc and all four docs-site locales — an operator following it will silently get the hardcoded prefix.

MAJOR (claude flagged; chair-confirmed) — the 55-second "form shows the effective maximum" claim is false, and the 55s clamp is ClickHouse-only. DatasourceForm.tsx:181-182 shows "Timeout (초, 1–60 · 선택)" / placeholder "기본 10" with no 55s anywhere, and timeoutS rides the connection config only for ClickHouse (datasources.ts:191).

MAJOR (claude flagged; chair-confirmed) — "oauth_3lo … is rejected at registration/enablement" is unsupported. It is an accepted member of INTEGRATION_TRANSPORTS and of the migration's transport CHECK; the only enforcement is the connect-time ValueError in agent.auth_headers. Either add the write-path rejection or restate as "not executable; fails closed at connect."

These doc findings gate because docs/CLAUDE.md requires reference docs to "verify … descriptions against current source" and to "distinguish source-supported capability from actual execution evidence," and the new doc opens by declaring itself the "source-supported contract." Fix is either code or doc text, but the contradiction cannot merge as-is.

MINOR (claude) — agent/lambda/datasource_http.py:27-29 still says the agent's _assert_host_allowed gap is "untouched here … needs its own fix"; this PR is that fix — update the stale cross-reference along with the removed KNOWN LIMITATION block.
MINOR (claude) — the removed comment's reference to web/lib/ssrf-guard.ts as the matching resolve-based tier is gone; one clause clarifying that registration remains resolve-based while connection is now pinned would keep the ADR-011 layering legible.
MINOR (claude) — the retired "deferred to P3 hardening" posture is worth a line in the published decisions page (docs-site/docs/faq/decisions.md); there are no in-tree ADR files to update (private upstream, consistent with repo convention).
Agreement notes: codex found the docs otherwise consistent and found no L2 code issues; its sole MAJOR is rejected above. The two panels did not independently converge on any gating finding — every adopted MAJOR is single-model but was chair-verified against the diff, BASE source, and the pinned httpx version before adoption.

3. Suggestions

  1. Bracket IPv6 pinned addresses (f"[{a}]" if ":" in a else a) before copy_with, and add an AAAA test case to test_pinned_http.py.
  2. Keep sse_read_timeout on its own constant (or the SDK default bounded by the 120s chat budget) and give pinned_http.py an explicit connect budget sourced from INTEGRATION_CONNECT_TIMEOUT rather than a hardcoded 8 and the request's connect extension.
  3. Reconcile docs/reference/external-connections.md and the four docs-site locales with the code: drop or implement apiPrefix, the per-kind window forwarding/ceilings, the Jaeger limit disclosure, the body caps, the 55s form claim, and restate the oauth_3lo posture as connect-time fail-closed.
  4. Catch ValueError around parsed.port and raise SsrfBlocked; log discarded per-address errors in the fallback loop; update the stale cross-reference comment in datasource_http.py; add test_pinned_http.py to the required agent test command.

4. Verdict

The security direction is right and the transport tests are strong, but two chair-confirmed functional regressions (IPv6-only endpoint breakage, 8s read-budget collapse) and multiple chair-confirmed contradictions between the PR's own new "source-supported" documentation and the shipped code require a revision cycle.


Triggered by commit 55ad447b60bf4a889628b3b6dca6b09c35e9a3a1 · workflow: .github/workflows/pr-review.yml

@Atom-oh
Atom-oh changed the base branch from dev to fix/datasource-connector-bounds October 18, 2026 14:28
@Atom-oh
Atom-oh force-pushed the fix/custom-mcp-pinned-transport branch from 55ad447 to ca77596 Compare October 18, 2026 14:49
@Atom-oh
Atom-oh force-pushed the fix/datasource-connector-bounds branch from 55b8c7d to 06759fd Compare October 18, 2026 14:50
@Atom-oh
Atom-oh force-pushed the fix/custom-mcp-pinned-transport branch from ca77596 to 8b66c8d Compare October 18, 2026 15:08
Custom MCP transports connect to the address set validated at request
time while preserving Host, TLS SNI and certificate verification, and
reject every 3xx. IPv6 addresses are bracketed so AAAA-only endpoints
connect. The transport deadline and the MCP session read timeout follow
a 60s read budget separate from the 8s connect budget. A malformed port
is an SSRF-classified rejection, discarded per-address failures are
logged, and pasted trailing whitespace in an api_key value is stripped.
The pin composes with the default-off custom MCP governance: spec
validation and the attested secret version run first, then the
validated addresses are pinned for that session.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@Atom-oh

Atom-oh commented Oct 19, 2026

Copy link
Copy Markdown
Contributor Author

Status note for reviewers:

Agent suites on this head: 189 passed (test_pinned_http.py test_agent.py test_readiness.py test_custom_mcp_policy.py tests/).

@Atom-oh
Atom-oh force-pushed the fix/custom-mcp-pinned-transport branch from 8b66c8d to cf85a32 Compare October 19, 2026 02:45
@Atom-oh
Atom-oh merged commit 1e6e3a8 into fix/datasource-connector-bounds Oct 19, 2026
Atom-oh added a commit that referenced this pull request Oct 19, 2026
* fix(agent): pin custom MCP connections to the validated address (#184)

Custom MCP transports connect to the address set validated at request
time while preserving Host, TLS SNI and certificate verification, and
reject every 3xx. IPv6 addresses are bracketed so AAAA-only endpoints
connect. The transport deadline and the MCP session read timeout follow
a 60s read budget separate from the 8s connect budget. A malformed port
is an SSRF-classified rejection, discarded per-address failures are
logged, and pasted trailing whitespace in an api_key value is stripped.
The pin composes with the default-off custom MCP governance: spec
validation and the attested secret version run first, then the
validated addresses are pinned for that session.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

* fix: address review feedback (iteration 1/5)

* docs: add the pinned-transport suite to the required agent test commands

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Atom-oh added a commit that referenced this pull request Oct 24, 2026
…183)

* fix(datasources): bound connector reads and verify real query access

Datasource Lambdas cap success (4 MiB) and error (64 KiB) bodies before
parsing and close streams on every exit. Missing explicit instance IDs
fail closed. Connection tests run the real bounded query operation, and
Jaeger/Dynatrace/Datadog/Loki report partial or unverified results
instead of absence. Prometheus/Mimir enforce the saved execution
ceiling inside the connector. Optional Jaeger/Dynatrace VPC attachment
stays default-off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(datasources): share one request deadline and disclose limit saturation

Review follow-ups for the connector bounds: connect, headers and body
share one monotonic deadline so a slow upstream cannot double the
aligned HTTP timeout past the Lambda wall; oversized or slow error
bodies keep their HTTP status. Loki and Jaeger report limit saturation
as partial with requested/returned counts, separately from byte-bound
loss. Connection probes return latency_ms, accept empty or warned
results, and keep the SSRF classification. The default kind mirror is
stamped with its instance id, the id-keyed lookup accepts only that
default's mirror (legacy unstamped mirrors keep working with a warning
until the next save), and timeoutS rides the console conn config for
every kind. Restores the resolve-and-recheck disclosure that the
stacked agent change replaces.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test(web): expect the stamped instance id on default re-mirrors

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix: bind datasource credentials to their registered identity

* fix: address review feedback (iteration 2/5)

* fix(agent): pin custom MCP connections to the validated address (#184)

Custom MCP transports connect to the address set validated at request
time while preserving Host, TLS SNI and certificate verification, and
reject every 3xx. IPv6 addresses are bracketed so AAAA-only endpoints
connect. The transport deadline and the MCP session read timeout follow
a 60s read budget separate from the 8s connect budget. A malformed port
is an SSRF-classified rejection, discarded per-address failures are
logged, and pasted trailing whitespace in an api_key value is stripped.
The pin composes with the default-off custom MCP governance: spec
validation and the attested secret version run first, then the
validated addresses are pinned for that session.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

* fix(web): show datasource completeness, limits and shared scope (#185)

* fix(web): show datasource completeness, limits and shared scope

The datasource API and UI report instance-specific configuration only,
show effective execution/response limits, pass the selected window to
trace and SaaS kinds, reject unsupported SQL range shapes, and label
shared default datasources.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(web): align datasource guides with the stacked connector contract

Review follow-ups: the datasource guides (ko/en/ja/zh) and API reference
describe the shared timeout contract, the real-query connection test and
the Mimir API prefix; the reference doc records Loki saturation
disclosure, the shared request deadline and the oauth_3lo posture. The
form no longer offers oauth_3lo. A Datadog instance's legacy key pair
counts as configured and survives a settings-only PATCH regardless of
the row's authType.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix: address review feedback (iteration 1/5)

* fix: address review feedback (iteration 2/5)

* fix(web): defer oauth_3lo enablement to the custom MCP governance gate

The enable-time transport check is superseded by the merged default-off
governance gate, which admits only api_key and oauth_client_credentials;
registration validation and the form still reject oauth_3lo.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* revert: move the web/datasource contract change out of this PR

Reverts the #185 squash (b5bfb94). It was merged into this branch
instead of dev, which pushed this PR's diff to 245 KB — past the AI
review input limit, so the reviewers were never called. The web and
documentation change returns as a dev-based PR after this one merges.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* chore: move the web credential-contract changes to the web PR

The instance-id stamp on the default kind mirror, the console-path
timeoutS forwarding and the default/delete mirror handling are web
changes that ship with the datasource web contract PR. Keeping them
here pushed this PR against the review input limit; the connector side
already fails closed without an id-bound credential, and the runbook's
backfill (re-save the default) writes the id key with today's web code.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix: address review feedback (iteration 2/5)

* fix: address review feedback (iteration 3/5)

* fix: address review feedback (iteration 4/5)

* chore: keep this PR within the review input limit

The optional Jaeger/Dynatrace VPC flags move to their own PR and the
datasource-development FAQ wording moves to the web contract PR; both
are independent of the connector changes reviewed here.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* chore: carry the connector CHANGELOG entry in the web contract PR

The feature-level bullet describes the net behavior of the connector and
web changes together, so it ships with the final PR of the pair; this
keeps the connector PR within the review input limit.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix: address review feedback (iteration 5/5)

* chore: move connector regression tests to a stacked follow-up PR

The AI review input limit is 128 KiB and this PR's diff was 131 KiB, so
the review never reached a model. The 544-line test_external_connections.py
moves unchanged to a follow-up PR stacked on this branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: address review feedback (iteration 6/10)

* fix: address review feedback (iteration 7/10)

* fix: address review feedback (iteration 8/10)

* fix: address review feedback (iteration 9/10)

* fix: address review feedback (iteration 10/10)

* chore: keep this PR within the review input limit

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: address review feedback (iteration 11/12)

* chore: move the Dynatrace connector regressions to the stacked test PR

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Atom-oh added a commit that referenced this pull request Oct 24, 2026
#191)

* fix(datasources): bound connector reads and verify real query access

Datasource Lambdas cap success (4 MiB) and error (64 KiB) bodies before
parsing and close streams on every exit. Missing explicit instance IDs
fail closed. Connection tests run the real bounded query operation, and
Jaeger/Dynatrace/Datadog/Loki report partial or unverified results
instead of absence. Prometheus/Mimir enforce the saved execution
ceiling inside the connector. Optional Jaeger/Dynatrace VPC attachment
stays default-off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(datasources): share one request deadline and disclose limit saturation

Review follow-ups for the connector bounds: connect, headers and body
share one monotonic deadline so a slow upstream cannot double the
aligned HTTP timeout past the Lambda wall; oversized or slow error
bodies keep their HTTP status. Loki and Jaeger report limit saturation
as partial with requested/returned counts, separately from byte-bound
loss. Connection probes return latency_ms, accept empty or warned
results, and keep the SSRF classification. The default kind mirror is
stamped with its instance id, the id-keyed lookup accepts only that
default's mirror (legacy unstamped mirrors keep working with a warning
until the next save), and timeoutS rides the console conn config for
every kind. Restores the resolve-and-recheck disclosure that the
stacked agent change replaces.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test(web): expect the stamped instance id on default re-mirrors

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix: bind datasource credentials to their registered identity

* fix: address review feedback (iteration 2/5)

* fix(agent): pin custom MCP connections to the validated address (#184)

Custom MCP transports connect to the address set validated at request
time while preserving Host, TLS SNI and certificate verification, and
reject every 3xx. IPv6 addresses are bracketed so AAAA-only endpoints
connect. The transport deadline and the MCP session read timeout follow
a 60s read budget separate from the 8s connect budget. A malformed port
is an SSRF-classified rejection, discarded per-address failures are
logged, and pasted trailing whitespace in an api_key value is stripped.
The pin composes with the default-off custom MCP governance: spec
validation and the attested secret version run first, then the
validated addresses are pinned for that session.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

* fix(web): show datasource completeness, limits and shared scope (#185)

* fix(web): show datasource completeness, limits and shared scope

The datasource API and UI report instance-specific configuration only,
show effective execution/response limits, pass the selected window to
trace and SaaS kinds, reject unsupported SQL range shapes, and label
shared default datasources.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(web): align datasource guides with the stacked connector contract

Review follow-ups: the datasource guides (ko/en/ja/zh) and API reference
describe the shared timeout contract, the real-query connection test and
the Mimir API prefix; the reference doc records Loki saturation
disclosure, the shared request deadline and the oauth_3lo posture. The
form no longer offers oauth_3lo. A Datadog instance's legacy key pair
counts as configured and survives a settings-only PATCH regardless of
the row's authType.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix: address review feedback (iteration 1/5)

* fix: address review feedback (iteration 2/5)

* fix(web): defer oauth_3lo enablement to the custom MCP governance gate

The enable-time transport check is superseded by the merged default-off
governance gate, which admits only api_key and oauth_client_credentials;
registration validation and the form still reject oauth_3lo.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* revert: move the web/datasource contract change out of this PR

Reverts the #185 squash (b5bfb94). It was merged into this branch
instead of dev, which pushed this PR's diff to 245 KB — past the AI
review input limit, so the reviewers were never called. The web and
documentation change returns as a dev-based PR after this one merges.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* chore: move the web credential-contract changes to the web PR

The instance-id stamp on the default kind mirror, the console-path
timeoutS forwarding and the default/delete mirror handling are web
changes that ship with the datasource web contract PR. Keeping them
here pushed this PR against the review input limit; the connector side
already fails closed without an id-bound credential, and the runbook's
backfill (re-save the default) writes the id key with today's web code.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix: address review feedback (iteration 2/5)

* fix: address review feedback (iteration 3/5)

* fix: address review feedback (iteration 4/5)

* chore: keep this PR within the review input limit

The optional Jaeger/Dynatrace VPC flags move to their own PR and the
datasource-development FAQ wording moves to the web contract PR; both
are independent of the connector changes reviewed here.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* chore: carry the connector CHANGELOG entry in the web contract PR

The feature-level bullet describes the net behavior of the connector and
web changes together, so it ships with the final PR of the pair; this
keeps the connector PR within the review input limit.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix: address review feedback (iteration 5/5)

* chore: move connector regression tests to a stacked follow-up PR

The AI review input limit is 128 KiB and this PR's diff was 131 KiB, so
the review never reached a model. The 544-line test_external_connections.py
moves unchanged to a follow-up PR stacked on this branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(datasources): add connection identity and bounded I/O regressions

Restores test_external_connections.py, split from the connector-bounds PR
to keep that PR under the AI review input limit. Covers connection
identity, bounded reads, query proof and result-completion behavior
(121 tests).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: address review feedback (iteration 6/10)

* test(datasources): count only emitted Jaeger traces in returnedCount

Matches the connector change in the base PR: when the byte bound drops
every trace, returnedCount is now the length of the emitted (empty)
traces array.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: address review feedback (iteration 7/10)

* fix: address review feedback (iteration 8/10)

* fix: address review feedback (iteration 9/10)

* fix: address review feedback (iteration 10/10)

* chore: keep this PR within the review input limit

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: address review feedback (iteration 11/12)

* chore: move the Dynatrace connector regressions to the stacked test PR

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(datasources): carry the Dynatrace connector regressions

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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