Skip to content

feat(core): connect hosted A2A agents - #5020

Merged
steve8708 merged 13 commits into
mainfrom
steve8708/changes-8092
Sep 15, 2026
Merged

steve8708 merged 13 commits into
mainfrom
steve8708/changes-8092

Conversation

@steve8708

@steve8708 steve8708 commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

What changed

Connected hosted A2A agents through the existing remote-agents/<id>.json registry. The connection form accepts a custom agent-card URL and vault-backed bearer or OAuth client-credentials references. Server-side calls resolve credentials in the caller’s workspace scope, support A2A 0.3 and 1.0 JSON-RPC cards, send the v1 wire methods and A2A-Version, fall back to message/send when streaming is unavailable, and surface typed protocol and credential failures.

The settings probe passes hosted card/auth configuration through live capability loading and reports reachable, auth-rejected, and no-JSON-RPC states. Public agent discovery strips credential wiring. The form uses the existing credential picker with optimistic add/save/delete rollback. Documentation and localized catalogs cover Foundry Entra client credentials, including the https://ai.azure.com/.default scope, and Gemini Enterprise generic bearer A2A with a custom card URL.

Phase 1 status

  • Done: manifest/auth contract, vault-scoped bearer and client credentials, custom cards, A2A 0.3/1.0 JSON-RPC, non-streaming fallback, typed failures, probe statuses, picker UI, docs/locales/skill, and fixture coverage.
  • Partial: no live Foundry or Gemini project was available. The local fixture covers a bearer-protected custom-card A2A 1.0 JSON-RPC server. Gemini Enterprise managed assistants expose a generic bearer A2A endpoint with a custom card URL; custom agent registration is Pre-GA and was not live-verified. Browser add/check was verified in local Dispatch against the loopback fixture after configuring its workspace origin; Add succeeded and Check reported the fixture live. Chat delegation was not exercised in this isolated session because no LLM provider was connected. The fixture path is covered end to end.
  • Not started (explicitly out of PR 1): provider-side discovery, a workspace connection kind/cross-app grants, Anthropic/OpenAI native adapters, durable a2a run projection, and structured remote-tool approval UI.

Trust model: any member who can save a remote-agents manifest can bind an organization vault reference to an HTTPS URL and call-agent it; this matches existing credentialed connections and is unchanged in this PR.

Validation

  • Broad core suite: 577 tests passed across A2A, resources, and call-agent suites.
  • Focused hosted-agent/probe/settings suite: 224 tests passed.
  • oxfmt, git diff --check, and pnpm guard:i18n-changed-copy pass.
  • pnpm guards passes every check except the pre-existing repository-wide guard:i18n-catalogs failure, which reports 436 existing template import/stale-baseline issues; no changed core catalog issue was reported.
  • The CI Typecheck lane passed on this head. The equivalent local pnpm --filter @agent-native/core typecheck command remains blocked by unrelated current-main toolkit export/module errors in filter-trigger.tsx, ExtensionsSidebarSection.tsx, and AppSidebar.tsx; no hosted-agent type errors were reported.
  • node packages/core/bin/agent-native.js doctor --only no-env-credentials reports the repository’s existing findings; no new changed-line process.env credential reads were introduced.
  • The current assets PR preview build check is blocked by the repository’s existing function-size baseline (server 50.9 MB → 56.0 MB); the matching baseline update is in open PR feat: support administered enterprise workspaces #5028, so this PR does not ratchet scripts/serverless-function-baseline.json.

References: Foundry A2A endpoint, Gemini Enterprise agent invocation.

builder-io-integration[bot]

This comment was marked as outdated.

@builder-io-integration builder-io-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Builder reviewed your changes and found 5 potential issues 🔴

Review Details

Code Review Summary

This incremental review evaluated the latest fixes to hosted A2A credential handling, HTTPS validation, probe authorization, authenticated card caching, redirect behavior, explicit endpoint discovery, and A2A 1.0 message normalization. The previously open eight findings were rechecked and resolved because the corresponding code paths are now addressed. The remaining implementation is directionally strong and has substantially improved security boundaries and v1 fixture coverage, but the new endpoint metadata and credential-cache edge cases still affect correctness. Risk remains high because this feature performs credentialed outbound calls and implements a public protocol contract.

New findings

  • 🔴 HIGH: Endpoint candidate deduplication can erase v1 protocol/tenant metadata, causing the exact explicit-endpoint case to regress.
  • 🔴 HIGH: An explicitly supplied protocolVersion is dropped for direct endpoints and ignored when a card interface omits its version.
  • 🟡 MEDIUM: OAuth tokens remain cached after vault secret rotation, and RPC-only typed credential rejections are still shown as reachable by the probe.

🧪 Browser testing: Will run after this review (PR touches UI code)

Comment thread packages/core/src/a2a/client.ts Outdated
Comment on lines +1826 to +1829
const existing = byUrl.get(candidate.url);
byUrl.set(
candidate.url,
existing ? { ...existing, ...candidate } : candidate,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Endpoint deduplication erases v1 interface metadata

When the card advertises the same URL as an explicit /a2a candidate, this merge lets the later candidate's undefined protocol version, tenant, and streaming fields overwrite the card-derived metadata. The client then sends v0.3 method names without A2A-Version/tenant to the v1 peer. Merge only defined fields or retain the first card-derived candidate.

Additional Info
Found by 1 of 4 reviewers.

Fix in Builder

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3861bbd: endpoint deduplication now retains the first defined protocolVersion, tenant, and streaming metadata instead of overwriting it with undefined values. The v1 duplicate-endpoint test covers this.

Comment thread packages/core/src/a2a/client.ts Outdated
if (explicitEndpoint) {
this.endpointCandidates = [explicitEndpoint.endpointUrl];
this.endpointResolved = true;
this.endpointCandidates = [{ url: explicitEndpoint.endpointUrl }];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Explicit protocolVersion is dropped for direct endpoints

For a direct explicit A2A endpoint with no cardUrl, the candidate is initialized without the configured protocolVersion, and endpoint resolution is already complete. The request path therefore uses v0.3 method names and omits A2A-Version even when the caller passes protocolVersion: "1.0". Initialize the candidate with the configured version or fall back to the client-level value.

Additional Info
Found by 1 of 4 reviewers.

Fix in Builder

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3861bbd: direct explicit endpoint candidates carry the configured protocolVersion, so a direct v1 call sends PascalCase methods and A2A-Version. Added a direct v1 test.

Comment thread packages/core/src/a2a/client.ts Outdated
Comment on lines +1276 to +1281
const protocolVersion =
typeof entry.protocolVersion === "string"
? entry.protocolVersion
: typeof entry.protocol_version === "string"
? entry.protocol_version
: card.protocolVersion;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Configured protocol version is ignored when the card interface omits it

selectJsonRpcInterface falls back only to card.protocolVersion for an interface with no per-interface version. A valid v1 card can omit both values while the caller supplies protocolVersion: "1.0"; that interface is skipped and discovery raises A2ANoJsonRpcInterfaceError. Pass the configured client version into the selector as the final fallback.

Additional Info
Found by 1 of 4 reviewers.

Fix in Builder

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3861bbd: interface selection falls back to the configured client protocol version when both the card and interface omit it. Added the configured-v1 test.

"Hosted agent OAuth auth is missing its client ID or client-secret reference.",
});
}
const cacheKey = [

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 OAuth token cache ignores client-secret rotation

The client-credentials cache key uses the vault reference, user/org, and endpoint metadata, but not a credential version or rotation signal. Replacing the vault value under the same reference continues using an access token minted with the old secret until its provider expiry, potentially for hours; the exported cache-clear helper is not wired to runtime rotation. Include credential-version invalidation or clear the relevant cache entry when credentials change or are rejected.

Additional Info
Found by 2 of 4 reviewers.

Fix in Builder

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3861bbd: the client secret is resolved before cache lookup and a SHA-256 fingerprint of the resolved value is part of the cache key. Secret material is never retained; the rotation test exercises old and new tokens.

Comment on lines +141 to +143
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
if (/A2A request failed \(401\)/.test(message)) {
if (/A2A request failed \((401|403)\)/.test(message)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 RPC credential rejections are not reflected in probe status

A2AClient.getTask() now throws RemoteAgentCredentialRejectedError for RPC 401/403, but this catch still recognizes only the legacy error-message regex. If the card is public but its RPC endpoint rejects the credential, the probe falls through to the generic branch and leaves the agent marked reachable instead of auth-rejected. Handle the typed rejection and set authorized: false, authError, and cardStatus: "auth-rejected".

Additional Info
Found by 2 of 4 reviewers.

Fix in Builder

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3861bbd: RPC-level RemoteAgentCredentialRejectedError now marks the probe unauthorized with cardStatus auth-rejected and the HTTP status. Added the typed 403 probe test.

@github-actions

github-actions Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Here's a visual recap of what changed:

Visual recap

Open the full interactive recap

# Conflicts:
#	templates/content/docs/solutions/2026-09-14-content-desktop-clipboard-repair-shape.md
builder-io-integration[bot]

This comment was marked as outdated.

@builder-io-integration builder-io-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Builder reviewed your changes and found 8 potential issues 🔴

Review Details

Code Review Summary

This incremental review covered the latest hosted-A2A changes without reposting the seven unresolved findings from the prior cycle. The implementation has added more v1 normalization and fixture coverage, but the new conversion paths still contain protocol and credential-transport gaps. The risk remains high because these paths send workspace-scoped bearer/OAuth credentials to remote endpoints and define the A2A 1.0 interoperability contract.

New findings

  • 🔴 HIGH: v1 text requests omit the required kind discriminator; v1 streaming status/artifact events are not recognized.
  • 🔴 HIGH: Headless agentNative.invoke drops saved hosted-agent auth, and v1 async submission uses the wrong configuration field.
  • 🔴 HIGH: RPC calls can leak hosted tokens through redirects or card-advertised HTTP interfaces.
  • 🟡 MEDIUM: Streaming transport errors can duplicate accepted messages, and ordinary JWT peer failures are mislabeled as hosted-credential failures.

The existing unresolved findings were skipped as required. 🧪 Browser testing: Will run after this review (PR touches UI code)

Comment on lines +1408 to +1409
const { type, file, ...rest } = value;
if (type === "text") return rest;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 V1 text parts are sent without the required discriminator

toV1Part removes the internal type field and returns only { text }. A2A 1.0 text parts use the discriminated { kind: "text", text: ... } shape, so normal v1 requests are malformed and compliant peers reject them. Preserve the v1 discriminator and add a request assertion to the fixture.

Additional Info
Found by 1 of 4 reviewers.

Fix in Builder

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am pushing back on this finding. The current A2A 1.0 specification defines Part as exactly one of text, raw, url, or data; it does not require a kind discriminator. toV1Part intentionally removes the internal type field. The v1 fixture asserts the emitted text part. Reference: https://a2a-protocol.org/dev/specification/

status: update.status,
});
}
if (isRecord(value) && isRecord(value.artifactUpdate)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 V1 streaming events are not recognized in their wire shape

A2A 1.0 stream results use a top-level kind discriminator such as status-update or artifact-update, with taskId, status, or artifact. Normalization only handles the wrapper keys statusUpdate and artifactUpdate, so compliant v1 progress/artifact events throw instead of yielding tasks. Handle the direct wire shape while retaining legacy wrappers.

Additional Info
Found by 1 of 4 reviewers.

Fix in Builder

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am pushing back on this finding. The current A2A 1.0 StreamResponse uses statusUpdate and artifactUpdate wrapper members; direct kind status-update and artifact-update events are legacy compatibility shapes. The implementation supports both, and the v1 fixture uses the wrapper wire shape. Reference: https://a2a-protocol.org/dev/specification/

Comment on lines 209 to +212
pollIntervalMs: options.pollIntervalMs,
correlation: options.correlation,
idempotencyKey: options.idempotencyKey,
...((options.cardUrl ?? target.cardUrl)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Headless invocation drops saved hosted-agent credentials

resolveAgentInvocationTarget finds a registered agent but returns only cardUrl, discarding its hosted auth descriptor. agentNative.invoke and the direct-action path therefore never resolve the saved vault-backed bearer/OAuth credential and cannot call registered hosted peers. Resolve auth in the caller's user/org scope and pass the resulting token and card URL without exposing the descriptor in the public target.

Additional Info
Found by 1 of 4 reviewers.

Fix in Builder

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3861bbd: discovered hosted auth is held in a private WeakMap keyed by the resolved target, resolved in the request user and org scope, and passed only as the runtime token. The public target and invocation result contain no auth descriptor.

Comment on lines +1372 to +1376
if (method === "message/send" && params.async === true) {
const configuration = isRecord(wireParams.configuration)
? wireParams.configuration
: {};
wireParams.configuration = { ...configuration, returnImmediately: true };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Use the A2A 1.0 async configuration field

The v1 async conversion emits configuration.returnImmediately, but A2A 1.0 uses configuration.blocking: false. Strict peers may reject the unknown field; permissive peers may block the initial request and defeat the async/poll path. Emit the v1 blocking field and add a fixture assertion for async submission.

Additional Info
Found by 1 of 4 reviewers.

Fix in Builder

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am pushing back on this finding. The current A2A 1.0 SendMessageConfiguration field is returnImmediately, not blocking. The implementation and async fixture use configuration.returnImmediately: true. Reference: https://a2a-protocol.org/dev/specification/

Comment on lines +1104 to +1106
if (interfaceHint) {
this.protocolVersion ??= interfaceHint.protocolVersion;
candidates.unshift({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Hosted RPC calls can leak bearer tokens through redirects

The selected v1 interface is later called by postJson, which uses default redirect behavior while reusing Authorization. A provider endpoint can redirect cross-origin and receive the hosted bearer/OAuth token. Disable redirects for credentialed RPC requests or restrict them to same-origin redirects while rebuilding authorization headers.

Additional Info
Found by 1 of 4 reviewers.

Fix in Builder

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3861bbd: credentialed card and RPC requests disable redirects and reject cleartext non-loopback URLs before sending Authorization. Added redirect and insecure-endpoint coverage.

Comment on lines +1104 to +1107
if (interfaceHint) {
this.protocolVersion ??= interfaceHint.protocolVersion;
candidates.unshift({
url: interfaceHint.url,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Card-advertised HTTP interfaces bypass HTTPS credential protection

Manifest validation requires HTTPS for configured hosted URLs, but a valid HTTPS card can advertise an http: JSON-RPC interface and selectJsonRpcInterface accepts it. The resolved hosted token is then sent in cleartext to that card-selected URL. Require HTTPS (or an explicit loopback-only development exception) for credentialed interface candidates.

Additional Info
Found by 1 of 4 reviewers.

Fix in Builder

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3861bbd: card-selected JSON-RPC interfaces go through the same HTTPS or loopback assertion before a credentialed request. The insecure advertised-interface test confirms no RPC request is sent.

Comment thread packages/core/src/a2a/client.ts Outdated
Comment on lines +928 to +930
} catch (error) {
lastError = error instanceof Error ? error : new Error(String(error));
continue;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Streaming retries can duplicate an accepted message

The new catch falls through to the next endpoint for every postJson exception, including a timeout after the remote agent has accepted and started processing SendStreamingMessage. The fallback endpoint can receive the same user message and create duplicate runs or side effects. Restrict fallback to failures known to occur before request acceptance, or require idempotency before retrying.

Additional Info
Found by 1 of 4 reviewers.

Fix in Builder

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3861bbd: a streaming transport failure now fails immediately instead of trying another endpoint or token after the message may have been accepted. Added a no-retry transport test.

agentName: string,
value: unknown,
): { message: string; errorCode: string } | null {
if (value instanceof RemoteAgentCredentialRejectedError) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Do not label ordinary peer auth failures as hosted credential failures

A2AClient now throws RemoteAgentCredentialRejectedError for any 401/403, including ordinary peers using signed Agent-Native JWTs. This helper maps that error to hosted-agent credential remediation without checking agent.auth, so existing non-hosted peer failures are misreported. Gate this mapping on an explicitly configured hosted auth descriptor or use a hosted-token-specific error type.

Additional Info
Found by 1 of 4 reviewers.

Fix in Builder

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3861bbd: RemoteAgentCredentialRejectedError is mapped to hosted remediation only when the manifest has hosted auth; ordinary peers receive the generic A2A auth rejection message. The call-agent test uses the typed rejection.

builder-io-integration[bot]

This comment was marked as outdated.

@builder-io-integration builder-io-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Builder reviewed your changes and found 3 potential issues 🟡

Review Details

Code Review Summary

This incremental review evaluated the latest hosted-A2A changes and skipped all 13 still-open comments as required; none were reposted. The remaining implementation continues to have broad protocol and credential-boundary concerns already visible on the PR. The new delta introduces additional hosted-card discovery and settings-validation inconsistencies. Risk remains high because this code resolves workspace credentials and performs outbound A2A protocol calls.

New findings

  • 🟡 MEDIUM: Default hosted card discovery constructs the well-known card path beneath an invocation endpoint such as /a2a, instead of the host root.
  • 🟡 MEDIUM: A2AClient(endpoint, token) does not authenticate default card discovery unless cardUrl is explicitly supplied.
  • 🟡 MEDIUM: The settings UI accepts credentialed HTTP URLs that manifest parsing later rejects, leaving an optimistic but undiscoverable saved row.

One reviewer found no additional reportable issue; focused hosted-A2A tests passed in that review. Browser testing will be attempted after this review.

🧪 Browser testing: Will run after this review (PR touches UI code)

Comment thread packages/core/src/scripts/call-agent.ts Outdated
Comment on lines +347 to +348
return agent.auth
? `${agent.url.replace(/\/$/, "")}/.well-known/agent-card.json`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Default hosted card URL is built under the invocation endpoint

When a hosted manifest omits cardUrl, this derives discovery as ${agent.url}/.well-known/agent-card.json. If url is an invocation endpoint such as https://host/_agent-native/a2a, discovery requests https://host/_agent-native/a2a/.well-known/agent-card.json instead of the host-rooted well-known card URL, so valid hosted agents cannot be discovered. Strip the explicit A2A endpoint before appending the card path, or let A2AClient derive it.

Additional Info
Found by 1 of 3 reviewers.

Fix in Builder

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3861bbd: call-agent forwards only an explicit cardUrl. A2AClient now derives the host-root well-known card URL after splitting explicit /a2a endpoints, so it cannot append discovery beneath the invocation path.

Comment thread packages/core/src/a2a/client.ts Outdated
Comment on lines +1075 to +1077
const card = await this.getAgentCard({
timeoutMs,
...(this.cardUrl && this.apiKey ? { token: this.apiKey } : {}),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Authenticated default card discovery drops the bearer token

The discovery call passes token only when this.cardUrl is configured. A caller using new A2AClient(endpoint, token) with the documented default /.well-known/agent-card.json therefore fetches the card anonymously, so protected hosted cards fail discovery or expose anonymous capabilities. Pass the configured token for default card discovery too, while retaining the scoped cache key.

Additional Info
Found by 1 of 3 reviewers.

Fix in Builder

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3861bbd: endpoint discovery passes the client bearer token to getAgentCard whether cardUrl is explicit or the default host-root URL. Authenticated cache keys remain scoped by user, org, and token discriminator.

Comment on lines +1071 to +1075
const normalizedAuth = normalizeHostedAuth(auth);
if (auth && !normalizedAuth) {
throw new Error(t("agents.authIncomplete"));
}
const normalizedCardUrl = cardUrl.trim() || undefined;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Credentialed HTTP agents are accepted but discarded by discovery

handleAdd accepts and persists any URL/card URL when auth is configured, but manifest parsing rejects credential-bearing non-loopback HTTP URLs. The row can therefore appear optimistically in Settings while discoverAgents omits it, making later probes and calls fail. Apply the same HTTPS-or-loopback validation before saving and show a visible validation error.

Additional Info
Found by 1 of 3 reviewers.

Fix in Builder

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3861bbd: credentialed agent and card URLs are validated with the same HTTPS-or-loopback rule used by manifest parsing, and invalid rows are rejected before save.

@steve8708
steve8708 merged commit 629b2cb into main Sep 15, 2026
76 of 77 checks passed
@steve8708
steve8708 deleted the steve8708/changes-8092 branch September 15, 2026 12:56
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