Skip to content
Merged
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
29 changes: 29 additions & 0 deletions docs/dist/idocs.editor.umd.js
Original file line number Diff line number Diff line change
Expand Up @@ -1187,6 +1187,27 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
atRules: {},
hasFlags: false
};
const completeBlockAtRules = [
// Keyframes and variants
"keyframes",
"-webkit-keyframes",
"-moz-keyframes",
"-o-keyframes",
// Font-related at-rules
"font-face",
"font-feature-values",
"font-palette-values",
// Page and counter styling
"page",
"counter-style",
// CSS Houdini and newer features
"property",
"layer",
"container",
"scope",
"starting-style",
"position-try"
];
function checkSecurityIssues(node) {
if (node.type === "Function" && node.name === "expression") {
return { flag: "scriptExec", reason: "CSS expression() function detected" };
Expand Down Expand Up @@ -1259,6 +1280,14 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
result.hasFlags = true;
return;
}
if (completeBlockAtRules.includes(node.name)) {
const ruleContent = csstree.generate(node);
result.atRules[atRuleSignature] = {
signature: atRuleSignature,
css: ruleContent
};
return;
}
if (node.block) {
if (!result.atRules[atRuleSignature]) {
result.atRules[atRuleSignature] = {
Expand Down
104 changes: 72 additions & 32 deletions docs/dist/idocs.host.umd.js
Original file line number Diff line number Diff line change
Expand Up @@ -1187,6 +1187,27 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
atRules: {},
hasFlags: false
};
const completeBlockAtRules = [
// Keyframes and variants
"keyframes",
"-webkit-keyframes",
"-moz-keyframes",
"-o-keyframes",
// Font-related at-rules
"font-face",
"font-feature-values",
"font-palette-values",
// Page and counter styling
"page",
"counter-style",
// CSS Houdini and newer features
"property",
"layer",
"container",
"scope",
"starting-style",
"position-try"
];
function checkSecurityIssues(node) {
if (node.type === "Function" && node.name === "expression") {
return { flag: "scriptExec", reason: "CSS expression() function detected" };
Expand Down Expand Up @@ -1259,6 +1280,14 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
result.hasFlags = true;
return;
}
if (completeBlockAtRules.includes(node.name)) {
const ruleContent = csstree.generate(node);
result.atRules[atRuleSignature] = {
signature: atRuleSignature,
css: ruleContent
};
return;
}
if (node.block) {
if (!result.atRules[atRuleSignature]) {
result.atRules[atRuleSignature] = {
Expand Down Expand Up @@ -2775,54 +2804,65 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
function checkUrlForFile(host) {
const urlParams = new URLSearchParams(window.location.search);
const loadUrl = urlParams.get(host.options.urlParamName);
if (!loadUrl) {
return false;
}
if (!isValidLoadUrl(loadUrl)) {
host.errorHandler(
new Error("Invalid URL format"),
"The URL provided has an invalid format or contains suspicious characters."
);
return false;
}
const isValidUrl = (url) => isSameOrigin(url) || isHttps(url);
if (loadUrl && !isValidUrl(loadUrl)) {
if (!isValidUrl(loadUrl)) {
host.errorHandler(
new Error(`Invalid URL provided`),
"The URL provided is not valid. Please ensure it is on the same origin or uses HTTPS."
);
return false;
}
if (loadUrl) {
if (!isValidLoadUrl(loadUrl)) {
host.errorHandler(
new Error("Invalid URL format"),
"The URL provided has an invalid format or contains suspicious characters."
);
return false;
}
try {
fetch(loadUrl).then((response) => {
if (!response.ok) {
throw new Error(`Failed to load content from URL`);
}
return response.text();
}).then((content) => {
determineContent(content, host);
}).catch((error) => {
host.errorHandler(error, `Error loading file from the provided URL`);
});
} catch (error) {
try {
fetch(loadUrl).then((response) => {
if (!response.ok) {
throw new Error(`Failed to load content from URL`);
}
return response.text();
}).then((content) => {
determineContent(content, host);
}).catch((error) => {
host.errorHandler(error, `Error loading file from the provided URL`);
}
return true;
} else {
return false;
});
} catch (error) {
host.errorHandler(error, `Error loading file from the provided URL`);
}
return true;
}
function isSameOrigin(url) {
const link = document.createElement("a");
link.href = url;
return link.origin === window.location.origin;
try {
if (!url.includes("://")) {
return true;
}
const parsedUrl = new URL(url);
return parsedUrl.origin === window.location.origin;
} catch {
return false;
}
}
function isHttps(url) {
const link = document.createElement("a");
link.href = url;
return link.protocol === "https:";
try {
if (!url.includes("://")) {
return window.location.protocol === "https:";
}
const parsedUrl = new URL(url);
return parsedUrl.protocol === "https:";
} catch {
return false;
}
}
function isValidLoadUrl(url) {
try {
const parsedUrl = new URL(url);
const parsedUrl = new URL(url, window.location.href);
if (!["http:", "https:"].includes(parsedUrl.protocol)) {
return false;
}
Expand Down
29 changes: 29 additions & 0 deletions docs/dist/idocs.sandbox.umd.js
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,27 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
atRules: {},
hasFlags: false
};
const completeBlockAtRules = [
// Keyframes and variants
"keyframes",
"-webkit-keyframes",
"-moz-keyframes",
"-o-keyframes",
// Font-related at-rules
"font-face",
"font-feature-values",
"font-palette-values",
// Page and counter styling
"page",
"counter-style",
// CSS Houdini and newer features
"property",
"layer",
"container",
"scope",
"starting-style",
"position-try"
];
function checkSecurityIssues(node) {
if (node.type === "Function" && node.name === "expression") {
return { flag: "scriptExec", reason: "CSS expression() function detected" };
Expand Down Expand Up @@ -859,6 +880,14 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
result.hasFlags = true;
return;
}
if (completeBlockAtRules.includes(node.name)) {
const ruleContent = csstree.generate(node);
result.atRules[atRuleSignature] = {
signature: atRuleSignature,
css: ruleContent
};
return;
}
if (node.block) {
if (!result.atRules[atRuleSignature]) {
result.atRules[atRuleSignature] = {
Expand Down
110 changes: 64 additions & 46 deletions packages/host/src/url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,84 +5,102 @@ export function checkUrlForFile(host: Listener) {
const urlParams = new URLSearchParams(window.location.search);
const loadUrl = urlParams.get(host.options.urlParamName);

//ensure loadUrl is on the same origin, or must be https
const isValidUrl = (url: string) => isSameOrigin(url) || isHttps(url);
if (!loadUrl) {
return false; // No load parameter found
}

// First, validate the URL format and security
if (!isValidLoadUrl(loadUrl)) {
host.errorHandler(
new Error('Invalid URL format'),
'The URL provided has an invalid format or contains suspicious characters.'
);
return false;
}

if (loadUrl && !isValidUrl(loadUrl)) {
// Then check origin/protocol requirements
const isValidUrl = (url: string) => isSameOrigin(url) || isHttps(url);
if (!isValidUrl(loadUrl)) {
host.errorHandler(
new Error(`Invalid URL provided`),
'The URL provided is not valid. Please ensure it is on the same origin or uses HTTPS.'
);
return false;
}

if (loadUrl) {
// Additional URL validation
if (!isValidLoadUrl(loadUrl)) {
host.errorHandler(
new Error('Invalid URL format'),
'The URL provided has an invalid format or contains suspicious characters.'
);
return false;
}

try {

fetch(loadUrl)
.then(response => {
if (!response.ok) {
throw new Error(`Failed to load content from URL`);
}
return response.text();
})
.then(content => {
determineContent(content, host);
})
.catch(error => {
host.errorHandler(error as Error, `Error loading file from the provided URL`);
});
try {
fetch(loadUrl)
.then(response => {
if (!response.ok) {
throw new Error(`Failed to load content from URL`);
}
return response.text();
})
.then(content => {
determineContent(content, host);
})
.catch(error => {
host.errorHandler(error as Error, `Error loading file from the provided URL`);
});

} catch (error) {
host.errorHandler(error as Error, `Error loading file from the provided URL`);
}
return true; // We found a load parameter
} else {
return false; // No load parameter found
} catch (error) {
host.errorHandler(error as Error, `Error loading file from the provided URL`);
}
return true; // We found a load parameter
}

function isSameOrigin(url: string) {
const link = document.createElement("a");
link.href = url;
return link.origin === window.location.origin;
try {
// First check if it's a relative URL (no protocol)
if (!url.includes('://')) {
// Relative URLs are inherently same-origin
return true;
}

// For absolute URLs, check if origin matches
const parsedUrl = new URL(url);
return parsedUrl.origin === window.location.origin;
} catch {
return false;
}
}

function isHttps(url: string) {
const link = document.createElement("a");
link.href = url;
return link.protocol === "https:";
try {
// Relative URLs inherit the current page's protocol
if (!url.includes('://')) {
return window.location.protocol === "https:";
}

// For absolute URLs, check the protocol directly
const parsedUrl = new URL(url);
return parsedUrl.protocol === "https:";
} catch {
return false;
}
}

function isValidLoadUrl(url: string): boolean {
try {
const parsedUrl = new URL(url);

// Resolve relative URLs against current location
const parsedUrl = new URL(url, window.location.href);

// Only allow http and https protocols
if (!['http:', 'https:'].includes(parsedUrl.protocol)) {
return false;
}
// Prevent javascript:, vbscript:, and data: URLs

// Prevent javascript:, vbscript:, and data: URLs (redundant check but good for clarity)
if (parsedUrl.protocol === 'javascript:' || parsedUrl.protocol === 'vbscript:' || parsedUrl.protocol === 'data:') {
return false;
}

// Basic hostname validation (prevent obvious malicious patterns)
const hostname = parsedUrl.hostname.toLowerCase();
if (hostname.includes('<') || hostname.includes('>') || hostname.includes('"') || hostname.includes("'")) {
return false;
}

return true;
} catch {
return false;
Expand Down
Loading