Harden auth/vault path sanitization and clean WeKnora docs

This commit is contained in:
Agent Board
2026-07-28 17:04:27 +08:00
parent 2deaf59f7b
commit 26ff1a9c9a
190 changed files with 13454 additions and 4987 deletions
@@ -11,20 +11,65 @@ function uploadedAssetTitle(asset) {
return String(asset && (asset.file_name || asset.title || asset.name) || '未命名附件').trim() || '未命名附件';
}
/** Reject path escape segments and null bytes after light decode. */
function hasPathEscape(value) {
var s = String(value || '').replace(/\\/g, '/');
try {
s = decodeURIComponent(s);
} catch (_) {
// keep raw
}
s = s.replace(/\\/g, '/');
if (s.indexOf('\0') >= 0) return true;
var parts = s.split('/');
for (var i = 0; i < parts.length; i += 1) {
if (parts[i] === '..') return true;
}
return false;
}
/** Allow only safe URL schemes for editor img/src or link href. */
function isSafeAssetUrl(url) {
var value = String(url || '').trim();
if (!value) return false;
if (value.charAt(0) === '#' || value.charAt(0) === '/' || value.indexOf('./') === 0) {
return !hasPathEscape(value);
}
// scheme-relative
if (value.indexOf('//') === 0) return false;
var colon = value.indexOf(':');
if (colon < 0) {
// relative path without scheme
return !hasPathEscape(value);
}
var scheme = value.slice(0, colon).toLowerCase();
if (scheme === 'http' || scheme === 'https' || scheme === 'blob' || scheme === 'data') {
// data: raster images only. Reject svg+xml (can embed script even when base64 hides "script").
if (scheme === 'data') {
if (!/^data:image\//i.test(value)) return false;
if (/^data:image\/svg\+xml/i.test(value)) return false;
// deny explicit script markers in non-svg image payloads
if (value.toLowerCase().indexOf('script') >= 0) return false;
return true;
}
return true;
}
return false;
}
function uploadedAssetUrl(asset) {
return String(asset && (asset.sourcePath || asset.file_url || asset.signedUrl || asset.signed_url || asset.thumbnail_url) || '').trim();
var raw = String(asset && (asset.sourcePath || asset.file_url || asset.signedUrl || asset.signed_url || asset.thumbnail_url) || '').trim();
return isSafeAssetUrl(raw) ? raw : '';
}
function uploadedAssetMarkdownHref(asset) {
var href = String(asset && (asset.markdownHref || asset.markdown_href) || '').trim();
if (href) {
var normalizedHref = href.replace(/\\/g, '/');
if (normalizedHref.indexOf('../') === 0 || normalizedHref.indexOf('/../') >= 0) return '';
if (hasPathEscape(href) || !isSafeAssetUrl(href)) return '';
return href;
}
var relativePath = String(asset && (asset.markdownRelativePath || asset.markdown_relative_path) || '').trim().replace(/\\/g, '/');
if (!relativePath) return '';
if (relativePath.indexOf('../') === 0 || relativePath.indexOf('/../') >= 0) return '';
if (!relativePath || hasPathEscape(relativePath)) return '';
if (relativePath.indexOf('./') === 0) return relativePath;
return './' + relativePath;
}
@@ -621,9 +666,9 @@ async function uploadFilesWithResolvedTarget(files, detail, options, deps) {
if (errors.length) {
var message = '部分文件上传失败:\n' + errors.slice(0, 6).join('\n') + (errors.length > 6 ? '\n...' : '');
if (typeof deps.alert === 'function') {
deps.alert(message);
await deps.alert(message);
} else {
window.alert(message);
await window.mnote.alert(message);
}
}
return uploaded;
@@ -633,7 +678,7 @@ function localAssetOpenUrl(asset, download, context) {
if (!isLocalUploadedAsset(asset)) return '';
var rootUri = String(context && context.rootUri || asset && (asset.rootUri || asset.root_uri) || '').trim() || currentRootUri();
var rootRelativePath = String(asset && (asset.rootRelativePath || asset.root_relative_path) || '').trim();
if (!rootUri || !rootRelativePath) return '';
if (!rootUri || !rootRelativePath || hasPathEscape(rootRelativePath)) return '';
var url = new URL('/api/local-folder/files/open', window.location.origin);
url.searchParams.set('rootUri', rootUri);
url.searchParams.set('path', rootRelativePath);