Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion app/src/components/authentication/LoginHandler/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ const LoginHandler: React.FC = () => {
const navigateParams = urlObj.pathname + urlObj.search;
navigate(navigateParams);
} else {
window.open(url, "_self");
// Never redirect to a different origin (open-redirect / CWE-601).
redirectToHome(appMode, navigate);
}
} catch (error) {
Logger.log("[LoginHandler-redirect] catch", { error });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,27 @@ import { Rule } from "@requestly/shared/types/entities/rules";
const { RULE_EDITOR_CONFIG } = APP_CONSTANTS;
const REQUESTLY_POST_MESSAGE_AUTHOR = "requestly";

// Browser-extension schemes for the only legitimate external caller of this
// channel: the Requestly extension's DevTools "create rule from traffic" flow.
// (chrome-extension for Chrome/Edge, moz-extension for Firefox, safari-web-extension for Safari.)
const TRUSTED_EXTENSION_ORIGIN_PROTOCOLS = ["chrome-extension:", "moz-extension:", "safari-web-extension:"];

/**
* Guards the rule-editor postMessage listener against cross-origin injection
* (CWE-79 / RQ-2309). Only the app's own origin and the Requestly extension are
* trusted senders; messages from arbitrary web pages are rejected.
*/
const isTrustedRuleEditorMessageOrigin = (origin: string): boolean => {
if (origin === window.location.origin) {
return true;
}
try {
return TRUSTED_EXTENSION_ORIGIN_PROTOCOLS.includes(new URL(origin).protocol);
} catch {
return false;
}
};

interface PostMessageData {
author: string;
action: string;
Expand All @@ -26,7 +47,10 @@ const useExternalRuleCreation = (mode: string): void => {

useEffect(() => {
const onMessageReceived = (event: MessageEvent<PostMessageData>) => {
const { author, action, payload } = event.data;
if (!isTrustedRuleEditorMessageOrigin(event.origin)) {
return;
}
const { author, action, payload } = event.data ?? {};
if (author === REQUESTLY_POST_MESSAGE_AUTHOR && action === "ruleEditor:loadData") {
const { ruleData, inputSelectorToFocus } = payload;
setCurrentlySelectedRule(dispatch, ruleData);
Expand Down Expand Up @@ -57,6 +81,12 @@ const useExternalRuleCreation = (mode: string): void => {
ruleData: currentlySelectedRuleData,
},
},
// Accepted risk (RQ-2309): wildcard target origin. Safe here because this only
// fires in CREATE mode where ruleData is a blank default template (no user data),
// and the injection vector is already closed by the inbound origin check above.
// A concrete target origin can't be used without a coordinated extension change,
// since the legitimate opener is the extension (chrome/moz/safari-web-extension://<id>,
// which varies by browser and build). Do not broadcast sensitive data through here.
"*"
);
hasSentReadyEventRef.current = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import React from "react";
import { RQButton } from "lib/design-system/components";
import ReactMarkdown from "react-markdown";
import { Banner, BANNER_TYPE } from "../banner.types";
import rehypeRaw from "rehype-raw";
import { capitalize } from "lodash";
import { trackAppBannerCtaClicked } from "../analytics";
import { RequestBillingTeamAccessModal } from "features/settings";
Expand Down Expand Up @@ -49,7 +48,7 @@ export const BaseBanner: React.FC<Props> = ({
)}

<div className="app-banner-text">
<ReactMarkdown rehypePlugins={[rehypeRaw]}>{text}</ReactMarkdown>
<ReactMarkdown>{text}</ReactMarkdown>
</div>

{actionsConfig && actionsConfig.length > 0 && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import * as Sentry from "@sentry/react";
import { InlineInput } from "componentsV2/InlineInput/InlineInput";
import { Input, notification, Tabs } from "antd";
import ReactMarkdown from "react-markdown";
import rehypeRaw from "rehype-raw";
import remarkGfm from "remark-gfm";
import { useOutsideClick } from "hooks";
import { useRBAC } from "features/rbac";
Expand Down Expand Up @@ -107,7 +106,6 @@ export const CollectionOverview: React.FC<CollectionOverviewProps> = ({ collecti
return (
<ReactMarkdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeRaw]}
components={{
a(props) {
return (
Expand Down
Loading