From aae21cc23d797b0336373f7fb4771823bd844441 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 7 Dec 2025 16:53:54 +0000 Subject: [PATCH] Fix CodeQL security issues - Fix polynomial regex vulnerability in src/privacy.js:213 - Add input length limiting to prevent ReDoS attacks - Replace vulnerable email regex with bounded quantifiers - Add length limit to URL regex pattern - Fix insecure randomness in scripts/benchmark.js:29-30 - Replace Math.random() with crypto.randomBytes() - Add secureRandomFloat() function for cryptographically secure random floats - Add explicit permissions to CI workflow - Set minimum required permissions (contents: read) - Follows GitHub security best practices --- .github/workflows/ci.yml | 4 ++++ scripts/benchmark.js | 17 ++++++++++++++--- src/privacy.js | 17 +++++++++++------ 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5e7ecf6..267019a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,10 @@ on: pull_request: branches: [main, master] +# Restrict permissions to minimum required (security best practice) +permissions: + contents: read + jobs: test: runs-on: ubuntu-latest diff --git a/scripts/benchmark.js b/scripts/benchmark.js index 798d52a..1a674a9 100755 --- a/scripts/benchmark.js +++ b/scripts/benchmark.js @@ -7,10 +7,21 @@ import { UrbanKnowledgeMapper } from '../src/mapper.js'; import { PerformanceMonitor, benchmark } from '../src/performance.js'; -import { randomUUID } from 'crypto'; +import { randomUUID, randomBytes } from 'crypto'; const DATASET_SIZES = [10, 50, 100, 500, 1000]; +/** + * Generate a cryptographically secure random float between 0 and 1 + * @returns {number} Random float in range [0, 1) + */ +function secureRandomFloat() { + // Use 4 bytes to create a 32-bit unsigned integer, then normalize to [0, 1) + const bytes = randomBytes(4); + const uint32 = bytes.readUInt32BE(0); + return uint32 / 0x100000000; +} + async function generateTestData(count) { const mapper = new UrbanKnowledgeMapper('/tmp/benchmark-ubicity'); await mapper.initialize(); @@ -26,8 +37,8 @@ async function generateTestData(count) { location: { name: locations[i % locations.length], coordinates: { - latitude: 37.7749 + (Math.random() - 0.5) * 0.1, - longitude: -122.4194 + (Math.random() - 0.5) * 0.1, + latitude: 37.7749 + (secureRandomFloat() - 0.5) * 0.1, + longitude: -122.4194 + (secureRandomFloat() - 0.5) * 0.1, }, }, }, diff --git a/src/privacy.js b/src/privacy.js index dd4bc35..6ade38d 100644 --- a/src/privacy.js +++ b/src/privacy.js @@ -207,11 +207,16 @@ function hashString(str) { * @returns {string} Sanitized text */ function sanitizeText(text) { - let sanitized = text; - - // Email addresses + // Limit input length to prevent ReDoS attacks + const MAX_TEXT_LENGTH = 10000; + let sanitized = text.length > MAX_TEXT_LENGTH + ? text.slice(0, MAX_TEXT_LENGTH) + : text; + + // Email addresses - use possessive-like matching to prevent backtracking + // Pattern: local part (alphanumeric, limited special chars) @ domain sanitized = sanitized.replace( - /[\w.-]+@[\w.-]+\.\w+/g, + /[a-zA-Z0-9](?:[a-zA-Z0-9._-]{0,62}[a-zA-Z0-9])?@[a-zA-Z0-9](?:[a-zA-Z0-9.-]{0,252}[a-zA-Z0-9])?\.[a-zA-Z]{2,63}/g, '[email]' ); @@ -221,9 +226,9 @@ function sanitizeText(text) { '[phone]' ); - // URLs + // URLs - use non-greedy matching with explicit character class sanitized = sanitized.replace( - /https?:\/\/[^\s]+/g, + /https?:\/\/[^\s]{1,2000}/g, '[url]' );