Skip to content

fix(copilot): validate credential-link URL scheme before rendering#5416

Merged
waleedlatif1 merged 2 commits into
stagingfrom
fix-credential-link-scheme
Jul 4, 2026
Merged

fix(copilot): validate credential-link URL scheme before rendering#5416
waleedlatif1 merged 2 commits into
stagingfrom
fix-credential-link-scheme

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • Only render the credential connect link as a clickable anchor when its value resolves to an http(s) URL, reusing the isSafeHttpUrl helper already used for chat file links.

Type of Change

  • Bug fix

Testing

  • Added special-tags.test.tsx covering non-http(s) scheme values (rendered as no anchor) and a normal https connect URL (renders correctly).
  • bun run check:api-validation passes.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

Only render the credential connect link as a clickable anchor when its
value resolves to an http(s) URL, reusing the isSafeHttpUrl helper
already used for chat file links.
@vercel

vercel Bot commented Jul 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jul 4, 2026 10:04pm

Request Review

@cursor

cursor Bot commented Jul 4, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches XSS-sensitive rendering of untrusted model-provided URLs in anchors, but the change is a narrow allowlist gate with existing chat usage and new component tests.

Overview
Credential connect links from streamed copilot output are no longer rendered as <a href="…"> unless data.value passes shared isSafeHttpUrl (http/https only). Unsafe schemes such as javascript: and data: produce no anchor, alongside the existing canEdit / provider checks.

isSafeHttpUrl is moved from chat file-download into @/lib/core/utils/urls so chat file fallbacks and workspace credential tags share one helper. Unit coverage lives in urls.test.ts; special-tags.test.tsx asserts blocked schemes, valid OAuth URLs, and read-only users.

Reviewed by Cursor Bugbot for commit fb774d8. Configure here.

@greptile-apps

greptile-apps Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR closes an XSS vector in the copilot credential-connect flow: the <a> element rendered by CredentialDisplay now only materialises when data.value resolves to a real http(s) URL, blocking injected javascript:, data:, or other dangerous schemes from streamed model output.

  • isSafeHttpUrl is extracted from the chat-feature-scoped file-download.tsx into the shared utility module lib/core/utils/urls.ts, alongside the getBrowserOrigin helper it depends on, so both consumers (file-download.tsx and special-tags.tsx) import from a single canonical location.
  • A new special-tags.test.tsx verifies the unsafe-scheme guard and the happy-path https case; the old per-file test in file-download.test.tsx is removed in favour of the new suite in urls.test.ts.

Confidence Score: 5/5

Safe to merge — the change is a targeted security guard with no impact on existing functionality beyond blocking malicious URL schemes in a single render path.

The PR adds a well-scoped URL-scheme check before rendering a credential connect link from streamed model output. The isSafeHttpUrl helper is correctly placed in the shared utility module, its behaviour is unchanged from the previous location, both consumers import it from one canonical source, and the test suite covers the dangerous-scheme and happy-path cases across both features.

No files require special attention.

Important Files Changed

Filename Overview
apps/sim/lib/core/utils/urls.ts Added isSafeHttpUrl as a shared export — correctly delegates to getBrowserOrigin and checks protocol; server-side relative URL calls conservatively return false (safe for current client-only callers).
apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/special-tags/special-tags.tsx Guard added before rendering the credential <a> tag: rejects missing or non-http(s) data.value from streamed model output, correctly placed after the existing canEdit check.
apps/sim/app/(interfaces)/chat/components/message/components/file-download.tsx Removed the now-shared isSafeHttpUrl definition; import updated to point to @/lib/core/utils/urls. No functional change to the download component itself.
apps/sim/lib/core/utils/urls.test.ts Tests for isSafeHttpUrl migrated here from the deleted file-download.test.tsx; run under jsdom so getBrowserOrigin() resolves a real origin for relative-URL cases.
apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/special-tags/special-tags.test.tsx New test suite covering the credential link guard: verifies javascript: and data: schemes produce no anchor, a real https URL renders correctly, and the canEdit: false gate still fires.
apps/sim/app/(interfaces)/chat/components/message/components/file-download.test.tsx Deleted; its isSafeHttpUrl test suite has been consolidated into urls.test.ts alongside the canonical implementation.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Model as Streamed Model Output
    participant ST as SpecialTags (client)
    participant CD as CredentialDisplay
    participant Safe as isSafeHttpUrl (urls.ts)
    participant Browser as Browser

    Model->>ST: "segment { type: 'credential', data.value }"
    ST->>CD: render CredentialDisplay
    CD->>CD: "check canEdit & data.provider"
    CD->>Safe: isSafeHttpUrl(data.value)
    alt URL is http(s)
        Safe-->>CD: true
        CD->>Browser: "render <a href={data.value}>"
    else URL is javascript: / data: / other
        Safe-->>CD: false
        CD-->>ST: return null (no anchor rendered)
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Model as Streamed Model Output
    participant ST as SpecialTags (client)
    participant CD as CredentialDisplay
    participant Safe as isSafeHttpUrl (urls.ts)
    participant Browser as Browser

    Model->>ST: "segment { type: 'credential', data.value }"
    ST->>CD: render CredentialDisplay
    CD->>CD: "check canEdit & data.provider"
    CD->>Safe: isSafeHttpUrl(data.value)
    alt URL is http(s)
        Safe-->>CD: true
        CD->>Browser: "render <a href={data.value}>"
    else URL is javascript: / data: / other
        Safe-->>CD: false
        CD-->>ST: return null (no anchor rendered)
    end
Loading

Reviews (2): Last reviewed commit: "refactor(copilot): move isSafeHttpUrl to..." | Re-trigger Greptile

Per Greptile's convention feedback: isSafeHttpUrl was consumed by both
chat and workspace/home but defined inside a feature-specific 'use client'
component. Move it alongside getBrowserOrigin (which it already depends
on) in lib/core/utils/urls.ts, matching this repo's shared-utility rule.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile review

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit fb774d8. Configure here.

@waleedlatif1 waleedlatif1 merged commit 04432b6 into staging Jul 4, 2026
18 checks passed
@waleedlatif1 waleedlatif1 deleted the fix-credential-link-scheme branch July 4, 2026 22:19
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