feat(inventory): add security hardening for prototype pollution and DoS

Created comprehensive security layer to protect against malicious input
and resource exhaustion attacks.

New security.js module:
- sanitizeLocationName(): Blocks __proto__, constructor, toString, etc.
- sanitizeItemName(): Enforces max length (500 chars)
- validateStoredInventory(): Validates entire stored object structure
- MAX_ITEMS_PER_SECTION: Limit of 500 items per section

Protected attack vectors:
1. Prototype pollution via location names
   - Blocked: "__proto__", "constructor", "prototype", etc.
   - Alert shown to user if attempted

2. DoS via extremely long names
   - Location names: max 200 chars (truncated with warning)
   - Item names: max 500 chars (truncated with warning)

3. DoS via massive item lists
   - Max 500 items per section (truncated with warning)

Integration:
- itemParser.js: Uses sanitizeItemName() and enforces max items
- inventoryActions.js: Validates all user input before saving
  - Manual location creation: blocked dangerous names
  - Manual item addition: length limits enforced

Security best practices (2025):
- No regex DoS vulnerabilities (character-by-character parsing)
- Explicit hasOwnProperty checks to avoid inherited properties
- Console warnings for all security events (auditing)
- Graceful degradation (truncate, don't crash)
- Defense in depth (validation at multiple layers)

This protects against both malicious actors and accidental abuse.
This commit is contained in:
Lucas 'Paperboy' Rose-Winters
2025-10-20 07:16:54 +11:00
parent 3a84e24c0a
commit 681c2f0e47
3 changed files with 196 additions and 5 deletions
+19 -3
View File
@@ -3,6 +3,8 @@
* Utilities for parsing item strings into arrays and vice versa
*/
import { sanitizeItemName, MAX_ITEMS_PER_SECTION } from './security.js';
/**
* Parses item strings from AI responses into clean arrays.
* Handles numerous AI formatting quirks and edge cases.
@@ -124,7 +126,7 @@ export function parseItems(itemString) {
processed = processed.replace(/\s+/g, ' ');
// STEP 6: Smart comma splitting (only split on commas OUTSIDE parentheses)
// Also handles list markers and quotes per-item
// Also handles list markers, quotes, and security validation per-item
const items = [];
let currentItem = '';
parenDepth = 0;
@@ -147,7 +149,17 @@ export function parseItems(itemString) {
// Comma outside parentheses - this is a separator
const cleaned = cleanSingleItem(currentItem);
if (cleaned) {
items.push(cleaned);
// Security check: validate and sanitize item name
const sanitized = sanitizeItemName(cleaned);
if (sanitized) {
items.push(sanitized);
}
// DoS protection: enforce max items limit
if (items.length >= MAX_ITEMS_PER_SECTION) {
console.warn(`[RPG Companion] Reached max items limit (${MAX_ITEMS_PER_SECTION}), truncating list`);
return items;
}
}
currentItem = ''; // Start new item
} else {
@@ -158,7 +170,11 @@ export function parseItems(itemString) {
// Don't forget the last item
const cleaned = cleanSingleItem(currentItem);
if (cleaned) {
items.push(cleaned);
// Security check: validate and sanitize item name
const sanitized = sanitizeItemName(cleaned);
if (sanitized) {
items.push(sanitized);
}
}
// Warn if parentheses were unmatched