T09 · Insecure Skill Coding Practices
- Location
reference/maton-controller.ts:272- Finding
Unvalidated Remote OAuth URLs Are Opened in the Browser
- Content
View full analysis
Vulnerability Details
File Location:
reference/maton-backend.ts:317-325,reference/maton-controller.ts:272-280, andreference/maton-views.ts:137-160
Vulnerability Type: Unvalidated redirect target and unsafe external-window handling
Risk Level: MediumVulnerable Code
reference/maton-backend.ts:317-325:typescript respond(true, { success: true, connectionId: result.data?.connection_Id, oauthUrl: result.data?.url, message: result.data?.url ? "Complete the OAuth flow at the provided URL" : "Connection created", });reference/maton-controller.ts:272-280:typescript if (result.oauthUrl) { window.open(result.oauthUrl, "_blank"); setState((prev) => ({ ...prev, connectingApp: null, showAppPicker: false, success: "OAuth window opened. Complete the authorization flow.", })); }reference/maton-views.ts:137-160:typescript <a href="${url}" target="_blank" rel="noopener noreferrer" @click=${() => props.onOpenOAuthUrl(url)} > Click here to authorize </a> ... <button class="btn small primary" @click=${() => window.open(url, "_blank")} > Authorize </button>Technical Analysis
The gateway accepts the OAuth URL returned by the remote Maton API and forwards it to the browser without validating its scheme, hostname, port, embedded credentials, or intended OAuth destination. The UI then assigns that value to an anchor and passes it directly to
window.open().Although the anchor includes
rel="noopener noreferrer", both the anchor click handler and the separate authorization button open the URL programmatically. Thesewindow.open()calls do not explicitly requestnoopenerandnoreferrer. The anchor also has a duplicate click action: its default navigation opens one page whileprops.onOpenOAuthUrl(url)may open another.Exploitation requires an attacker to ...[truncated 1773 chars]
- Remediation
View remediation
Remediation Suggestions
-
Validate every OAuth URL on the backend before returning it to the browser:
- Parse it with
new URL(). - Require the
https:scheme. - Reject URLs containing embedded usernames or passwords.
- Reject unexpected ports.
- Enforce an explicit allowlist of approved Maton and OAuth-provider hostnames.
- Compare normalized hostnames rather than using suffix or substring checks.
- Parse it with
-
Return an error instead of forwarding a URL that fails validation. Apply the same validation to URLs received from both connection-creation and connection-list endpoints.
-
Add defense-in-depth validation in the UI before opening any external URL.
-
Open approved URLs with explicit isolation:
typescript const parsed = new URL(result.oauthUrl); const popup = window.open( parsed.toString(), "_blank", "noopener,noreferrer", ); if (popup) { popup.opener = null; }-
Remove the anchor's duplicate programmatic click handler. Use either a properly isolated anchor or a validated
window.open()call, but not both. -
Maintain the approved OAuth-host list centrally, document why each host is required, and add tests covering malformed URLs, non-HTTPS schemes, embedded credentials, deceptive subdomains, unexpected ports, and unapproved hosts.
-
