paperclipai/paperclip · error · TeamsServiceUrlValidationError
Teams destination contains an untrusted service URL
Error message
Teams destination contains an untrusted service URL
What it means
After shape checks, normalizedTeamsServiceUrl applies a trust policy: the normalized URL is accepted only if its host is an official Microsoft connector host (without percent-encoded path bytes, enforcing a canonical one-segment path) or if it equals the configured API URL. Anything else throws TeamsServiceUrlValidationError with 'Teams destination contains an untrusted service URL' to prevent SSRF via attacker-supplied serviceUrl values.
Source
Thrown at server/src/services/chat-sdk-runtime.ts:991
function trustedTeamsServiceUrl(
value: unknown,
configuredApiUrl: string | null,
): string {
const rawValue = typeof value === "string" ? value : "";
const normalized = normalizedTeamsServiceUrl(value);
const parsed = new URL(normalized);
const rawParsed = new URL(rawValue);
const officialHost =
rawValue === rawValue.trim() &&
parsed.port === "" &&
isOfficialTeamsConnectorHost(parsed.hostname) &&
// Encoded path bytes can be normalized away by URL parsing. Microsoft
// connector base URLs do not require them, so reject them before applying
// the canonical one-segment path policy.
!/%[0-9a-f]{2}/i.test(rawValue) &&
isCanonicalTeamsConnectorPath(rawParsed.pathname);
if (officialHost || normalized === configuredApiUrl) return normalized;
throw new TeamsServiceUrlValidationError(
"Teams destination contains an untrusted service URL",
);
}
/**
* Microsoft binds serviceUrl into the authenticated Bot Connector JWT and
* requires replies to target that matching URL. The URL is mutable routing
* state, not conversation identity, so current thread ids omit it. Persist the
* latest verified route under the stable conversation id and scope each
* outbound call to a fresh API client rooted at that route. Legacy thread ids
* that embedded a URL remain readable, but a newer persisted route wins. A
* context-local getter keeps simultaneous conversations isolated without
* forcing unrelated Teams threads through a single network queue.
*/
export function scopeMicrosoftTeamsEgress(
adapter: Adapter,
configuredApiUrl?: string,
enableFileConsent = false,View on GitHub (pinned to 01ad858492)
Solutions
- Use only Microsoft Bot Connector serviceUrl hosts (e.g. smba.trafficmanager.net, webchat.botframework.com) exactly as issued in the authenticated activity.
- Remove percent-encoding from the path; Microsoft connector base URLs do not need encoded bytes.
- If you intentionally target a private/gateway endpoint, configure it as the trusted configured API URL that normalization compares against.
- Treat repeated occurrences as a security signal: validate the activity's serviceUrl against the Bot Connector JWT-issued value before ingestion.
Example fix
// before serviceUrl: "https://evil.example.com/v3" // after serviceUrl: "https://smba.trafficmanager.net/americas"
Defensive patterns
Strategy: try-catch
Validate before calling
const allowedHosts = [/\.trafficmanager\.net$/, /botframework\.com$/];
const u = new URL(dest.serviceUrl);
if (!allowedHosts.some(r => r.test(u.hostname)) && dest.serviceUrl !== configuredApiUrl) {
throw new Error("serviceUrl is not a trusted Teams connector URL");
} Type guard
function isTrustedTeamsServiceUrl(value: string, configuredApiUrl: string): boolean {
return value === configuredApiUrl || /\.trafficmanager\.net$|\.botframework\.com$/.test(new URL(value).hostname);
} Try / catch
try {
const url = normalizedTeamsServiceUrl(dest.serviceUrl);
} catch (e) {
if (e instanceof TeamsServiceUrlValidationError && e.message.includes("untrusted")) {
// treat as potential spoofed activity: drop it and alert, never call the URL
}
} Prevention
- Never trust serviceUrl from unauthenticated payloads; verify the Bot Connector JWT first.
- Only accept Microsoft connector hosts or your configured API URL.
- Treat untrusted-serviceUrl errors as security events worth logging/alerting.
- Avoid percent-encoded path bytes in connector URLs; validate paths before use.
When it happens
Trigger: A Teams activity/destination carries a serviceUrl pointing at a non-Microsoft host (e.g. attacker-controlled server), contains percent-encoded path bytes like %2f, or has a non-canonical path segment pattern that fails isCanonicalTeamsConnectorPath, and it does not match the configured API URL.
Common situations: Security probing where a crafted Bot Framework activity injects a malicious serviceUrl; proxy or gateway URLs substituted for the real connector host; encoded characters sneaked into the connector path.
Understand the failure class
Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.
Related errors
- devUiUrl must use http or https protocol
- Invalid published report URL
- github_attachment_source_mismatch
- Teams destination is missing its verified service URL
- Teams destination contains an invalid service URL
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/2ffc697c4b6d221f.
Report an issue: GitHub.