mirror of
https://github.com/wekan/wekan.git
synced 2026-11-07 11:02:33 -05:00
secureTransfer recorded every throw as a high "blocked" event naming the user, and that disables the account. sanitize-html 2.18.0 threw RangeError on nested <iframe>, so a card planted with the payload locked out the next member who exported the board; any library fault would do the same. The transfer boundary now marks its own refusals (TransferBoundaryError: a cycle, a non-plain object, a size limit), and models/lib/transferFailure.cjs records only those as blocked - high, and medium for a size limit as before. Anything else is ImportExportFailure: detected, still naming whose import or export it was, and never blocking an account. tests/transferFailure.test.cjs drives real boundary refusals and a sanitizer that throws through blockOnSecurityEvent's own decision; with the old secureTransfer it fails. It also fails if the boundary throws a plain Error or anything but secureTransfer calls it. Thanks to xet7 !
45 lines
1.8 KiB
JavaScript
45 lines
1.8 KiB
JavaScript
import { sanitizeTransferValue } from '/models/lib/importExportBoundary';
|
|
import { sanitizeInput } from '/server/lib/inputSanitizer';
|
|
|
|
// Server wrapper around the pure structural boundary. This is deliberately the
|
|
// only place that couples transfer validation to server HTML sanitization and Admin Problems,
|
|
// so every adapter records blocked and sanitized attempts consistently.
|
|
export function secureTransfer(value, context = {}) {
|
|
try {
|
|
const result = sanitizeTransferValue(value, {
|
|
direction: context.direction || 'import',
|
|
sanitizeHtml: sanitizeInput,
|
|
maxDepth: context.maxDepth,
|
|
maxNodes: context.maxNodes,
|
|
maxArray: context.maxArray,
|
|
maxString: context.maxString,
|
|
maxBinaryString: context.maxBinaryString,
|
|
});
|
|
if (result.warnings.length) {
|
|
require('/server/lib/securityLog').record({
|
|
category: 'Import/export validation',
|
|
bleed: 'ImportExportSanitization',
|
|
severity: 'warning',
|
|
action: 'sanitized',
|
|
source: context.source || `${context.direction || 'import'}:unknown`,
|
|
userId: context.userId,
|
|
username: context.username,
|
|
ip: context.ip,
|
|
detail: `${result.warnings.length} unsafe value(s): ${result.warnings.slice(0, 5).map(w => `${w.path} ${w.reason}`).join('; ')}`,
|
|
});
|
|
}
|
|
return result.value;
|
|
} catch (error) {
|
|
// Only the boundary's own refusals are the user's attempt; a fault of the
|
|
// code (a sanitizer that throws) is recorded without blocking anybody.
|
|
// See models/lib/transferFailure.cjs.
|
|
try {
|
|
const { transferFailureEvent } = require('/models/lib/transferFailure.cjs');
|
|
require('/server/lib/securityLog').record(transferFailureEvent(error, context));
|
|
} catch (e) { /* logging must never break the guard */ }
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
export default secureTransfer;
|