mirror of
https://github.com/apache/skywalking-booster-ui.git
synced 2025-08-02 16:11:54 +00:00
fix: restrict and validate url for widgets (#480)
This commit is contained in:
parent
5d311a41a2
commit
1421f95ad3
@ -150,6 +150,7 @@ $theme-background: var(--theme-background);
|
||||
$active-background: var(--el-color-primary);
|
||||
$font-size-smaller: 12px;
|
||||
$font-size-normal: 14px;
|
||||
$error-color: #e66;
|
||||
|
||||
.opt:hover {
|
||||
background-color: var(--sw-list-hover) !important;
|
||||
@ -208,9 +209,9 @@ div.vis-tooltip {
|
||||
}
|
||||
|
||||
.vis-item.Error {
|
||||
background-color: #e66;
|
||||
background-color: $error-color;
|
||||
opacity: 0.8;
|
||||
border-color: #e66;
|
||||
border-color: $error-color;
|
||||
color: var(--sw-event-vis-selected) !important;
|
||||
}
|
||||
|
||||
|
@ -13,13 +13,14 @@ limitations under the License. -->
|
||||
<template>
|
||||
<div class="item">
|
||||
<span class="label">{{ t("iframeSrc") }}</span>
|
||||
<el-input class="input" v-model="url" size="small" @change="changeConfig({ url: encodeURIComponent(url) })" />
|
||||
<el-input class="input" v-model="url" size="small" @change="handleUrlChange" :class="{ error: urlError }" />
|
||||
<div v-if="urlError" class="error-message">{{ urlError }}</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<el-button size="small" @click="cancelConfig">
|
||||
{{ t("cancel") }}
|
||||
</el-button>
|
||||
<el-button size="small" type="primary" @click="applyConfig">
|
||||
<el-button size="small" type="primary" @click="applyConfig" :disabled="!!urlError">
|
||||
{{ t("apply") }}
|
||||
</el-button>
|
||||
</div>
|
||||
@ -28,24 +29,89 @@ limitations under the License. -->
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { ref } from "vue";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
|
||||
const { t } = useI18n();
|
||||
const dashboardStore = useDashboardStore();
|
||||
const originConfig = dashboardStore.selectedGrid;
|
||||
const widget = originConfig.widget || {};
|
||||
const url = ref(widget.url || "");
|
||||
const urlError = ref("");
|
||||
|
||||
// URL validation function to prevent XSS
|
||||
function validateAndSanitizeUrl(inputUrl: string): { isValid: boolean; sanitizedUrl: string; error: string } {
|
||||
if (!inputUrl.trim()) {
|
||||
return { isValid: true, sanitizedUrl: "", error: "" };
|
||||
}
|
||||
|
||||
try {
|
||||
// Create URL object to validate the URL format
|
||||
const urlObj = new URL(inputUrl);
|
||||
|
||||
// Only allow HTTP and HTTPS protocols to prevent XSS
|
||||
if (!["http:", "https:"].includes(urlObj.protocol)) {
|
||||
return {
|
||||
isValid: false,
|
||||
sanitizedUrl: "",
|
||||
error: "Only HTTP and HTTPS URLs are allowed",
|
||||
};
|
||||
}
|
||||
|
||||
// Additional security checks
|
||||
const dangerousProtocols = ["javascript:", "data:", "vbscript:", "le:"];
|
||||
const lowerUrl = inputUrl.toLowerCase();
|
||||
|
||||
for (const protocol of dangerousProtocols) {
|
||||
if (lowerUrl.includes(protocol)) {
|
||||
return {
|
||||
isValid: false,
|
||||
sanitizedUrl: "",
|
||||
error: "Dangerous protocols are not allowed",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Return the sanitized URL (using the URL object to normalize it)
|
||||
return {
|
||||
isValid: true,
|
||||
sanitizedUrl: urlObj.href,
|
||||
error: "",
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
isValid: false,
|
||||
sanitizedUrl: "",
|
||||
error: "Please enter a valid URL",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function handleUrlChange() {
|
||||
const validation = validateAndSanitizeUrl(url.value);
|
||||
urlError.value = validation.error;
|
||||
|
||||
if (validation.isValid) {
|
||||
changeConfig({ url: validation.sanitizedUrl });
|
||||
}
|
||||
}
|
||||
|
||||
function changeConfig(param: { [key: string]: string }) {
|
||||
const key = Object.keys(param)[0];
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { selectedGrid } = dashboardStore;
|
||||
const widget = {
|
||||
...dashboardStore.selectedGrid.widget,
|
||||
[key]: decodeURIComponent(param[key]),
|
||||
[key]: param[key], // Use the sanitized URL directly, no need for decodeURIComponent
|
||||
};
|
||||
dashboardStore.selectWidget({ ...selectedGrid, widget });
|
||||
}
|
||||
|
||||
function applyConfig() {
|
||||
if (urlError.value) {
|
||||
return; // Don't apply if there's a validation error
|
||||
}
|
||||
dashboardStore.setConfigPanel(false);
|
||||
dashboardStore.setConfigs(dashboardStore.selectedGrid);
|
||||
}
|
||||
@ -76,6 +142,18 @@ limitations under the License. -->
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.url-input.error {
|
||||
:deep(.el-input__inner) {
|
||||
border-color: $error-color;
|
||||
}
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: $error-color;
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
|
@ -37,6 +37,8 @@ limitations under the License. -->
|
||||
height="100%"
|
||||
scrolling="no"
|
||||
style="border: none"
|
||||
sandbox="allow-scripts allow-same-origin allow-forms allow-popups"
|
||||
referrerpolicy="no-referrer"
|
||||
></iframe>
|
||||
<div v-else class="tips">{{ t("iframeWidgetTip") }}</div>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user