Files
WRNexusJS/packages/core/src/security.ts
T
2026-07-12 15:55:18 +05:30

54 lines
1.6 KiB
TypeScript

/**
* Small, dependency-free security helpers shared across packages.
*/
const HTML_ESCAPES: Record<string, string> = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#39;",
};
/**
* Escape a string for safe interpolation into HTML text or attributes.
* Used for page metadata (title/description) so untrusted values can't
* break out of an attribute or inject markup.
*/
export function escapeHtml(value: string): string {
return value.replace(/[&<>"']/g, (ch) => HTML_ESCAPES[ch]!);
}
/**
* Client island names come from `data-client="..."` attributes and from
* filenames in `app/client`. We only ever allow a conservative charset so a
* name can never be used to traverse the filesystem or inject code.
*/
const SAFE_NAME = /^[A-Za-z0-9_-]+$/;
export function isSafeIslandName(name: string): boolean {
return SAFE_NAME.test(name);
}
/**
* Reject obvious path-traversal in a request path before it is ever used to
* resolve a file. The router never builds file paths from request input
* (routes are resolved against a pre-scanned table), but this is a cheap
* defense-in-depth guard.
*/
export function isSafeRequestPath(pathname: string): boolean {
if (pathname.includes("\0")) return false;
// Reject `..` segments and backslashes that could escape a directory.
const decoded = safeDecode(pathname);
if (decoded === null) return false;
return !/(^|\/)\.\.(\/|$)/.test(decoded) && !decoded.includes("\\");
}
function safeDecode(value: string): string | null {
try {
return decodeURIComponent(value);
} catch {
return null;
}
}