From 5d68cb89a4c2471fb12f94c9b6714345b490691c Mon Sep 17 00:00:00 2001 From: JohnDoe6345789 Date: Fri, 26 Dec 2025 01:15:35 +0000 Subject: [PATCH] docs: tsx,studio,nextjs (3 files) --- dbal/docs/CVE_ANALYSIS_2025_12.md | 191 +++++++++++++++--- docs/codegen-studio.md | 1 + .../src/app/codegen/CodegenStudioClient.tsx | 31 ++- 3 files changed, 197 insertions(+), 26 deletions(-) diff --git a/dbal/docs/CVE_ANALYSIS_2025_12.md b/dbal/docs/CVE_ANALYSIS_2025_12.md index f81decff6..a84e85502 100644 --- a/dbal/docs/CVE_ANALYSIS_2025_12.md +++ b/dbal/docs/CVE_ANALYSIS_2025_12.md @@ -328,17 +328,47 @@ const id = `req_${++this.requestIdCounter}` - Could aid in request correlation attacks - Timing analysis of request frequency -**Recommendation**: +**🏰 Fort Knox Remediation**: ```typescript -import { randomBytes } from 'crypto' -const id = `req_${randomBytes(16).toString('hex')}` +import { randomBytes, createHash } from 'crypto' + +class CryptographicRequestIdGenerator { + private instanceId: string + private counter: bigint = 0n + + constructor() { + // Unique per instance, prevents cross-instance correlation + this.instanceId = randomBytes(8).toString('hex') + } + + generate(): string { + // Combine: random + counter + timestamp + instance + const random = randomBytes(16) + const timestamp = BigInt(Date.now()) + const counter = this.counter++ + + // Hash to prevent information leakage + const hash = createHash('sha256') + .update(random) + .update(Buffer.from(timestamp.toString())) + .update(Buffer.from(counter.toString())) + .update(this.instanceId) + .digest('hex') + .substring(0, 24) + + return `req_${hash}` + } +} + +// Also add request ID correlation tracking for debugging +// but only in non-production or with explicit opt-in ``` --- ### 1.2 Blob Storage Layer -#### DBAL-2025-004: Path Traversal Incomplete Mitigation (MEDIUM) +#### DBAL-2025-004: Path Traversal Incomplete Mitigation (MEDIUM → HIGH with Fort Knox) **Location**: [filesystem-storage.ts](../ts/src/blob/filesystem-storage.ts#L27-L30) ```typescript @@ -356,33 +386,144 @@ private getFullPath(key: string): string { - Unicode normalization bypasses - Similar to CVE-2024-4068 (path traversal patterns) -**Recommendation**: +**🏰 Fort Knox Remediation**: ```typescript -private getFullPath(key: string): string { - // Decode any URL encoding - let decoded = decodeURIComponent(key) +import path from 'path' +import { createHash } from 'crypto' + +/** + * Fort Knox Path Sanitizer + * Multiple layers of path traversal protection + */ +class SecurePathResolver { + private basePath: string + private resolvedBasePath: string - // Normalize Unicode - decoded = decoded.normalize('NFKC') + // Known dangerous patterns across platforms + private static readonly TRAVERSAL_PATTERNS = [ + /\.\./g, // Basic traversal + /\.\.%2f/gi, // URL encoded + /\.\.%5c/gi, // URL encoded backslash + /%2e%2e/gi, // Double URL encoded dots + /%252e%252e/gi, // Triple URL encoded + /\.\./g, // Unicode fullwidth + /\x00/g, // Null bytes + /[\x01-\x1f\x7f]/g, // Control characters + ] - // Remove all path traversal patterns - const sanitized = decoded - .replace(/\\/g, '/') // Normalize slashes - .replace(/\/+/g, '/') // Collapse multiple slashes - .split('/') - .filter(segment => segment !== '..' && segment !== '.') - .join('/') - .replace(/^\/+/, '') // Remove leading slashes + // Allowed characters in path (whitelist approach) + private static readonly ALLOWED_PATH_CHARS = /^[a-zA-Z0-9._\-\/]+$/ - const fullPath = path.join(this.basePath, sanitized) - - // Final validation: ensure result is under basePath - const resolved = path.resolve(fullPath) - if (!resolved.startsWith(path.resolve(this.basePath) + path.sep)) { - throw DBALError.forbidden('Path traversal detected') + constructor(basePath: string) { + this.basePath = basePath + this.resolvedBasePath = path.resolve(basePath) + + // Ensure base path exists and is a directory + if (!this.resolvedBasePath.endsWith(path.sep)) { + this.resolvedBasePath += path.sep + } } - return resolved + resolve(userKey: string): string { + // LAYER 1: Input validation + if (!userKey || typeof userKey !== 'string') { + throw DBALError.validationError('Invalid path key') + } + + if (userKey.length > 1024) { + throw DBALError.validationError('Path too long (max 1024 chars)') + } + + // LAYER 2: URL decode all possible encodings (multiple passes) + let decoded = userKey + for (let i = 0; i < 3; i++) { + try { + const newDecoded = decodeURIComponent(decoded) + if (newDecoded === decoded) break + decoded = newDecoded + } catch { + break // Invalid encoding + } + } + + // LAYER 3: Unicode normalization (NFKC is most aggressive) + decoded = decoded.normalize('NFKC') + + // LAYER 4: Check against dangerous patterns (blocklist) + for (const pattern of SecurePathResolver.TRAVERSAL_PATTERNS) { + if (pattern.test(decoded)) { + this.logSecurityViolation('PATH_TRAVERSAL_ATTEMPT', userKey) + throw DBALError.forbidden('Path traversal detected') + } + } + + // LAYER 5: Whitelist character validation + if (!SecurePathResolver.ALLOWED_PATH_CHARS.test(decoded)) { + this.logSecurityViolation('INVALID_PATH_CHARS', userKey) + throw DBALError.validationError('Path contains invalid characters') + } + + // LAYER 6: Normalize path separators + const normalized = decoded + .replace(/\\/g, '/') // Windows to Unix + .replace(/\/+/g, '/') // Collapse multiple slashes + .replace(/^\/+/, '') // Remove leading slashes + .split('/') + .filter(segment => segment !== '.' && segment !== '..' && segment !== '') + .join(path.sep) + + // LAYER 7: Resolve and validate final path + const fullPath = path.resolve(this.basePath, normalized) + + // CRITICAL: Final containment check + if (!fullPath.startsWith(this.resolvedBasePath)) { + this.logSecurityViolation('PATH_ESCAPE_ATTEMPT', { + userKey, + resolved: fullPath, + base: this.resolvedBasePath + }) + throw DBALError.forbidden('Path traversal detected') + } + + // LAYER 8: Check for symlink escape + return this.resolveSymlinks(fullPath) + } + + private async resolveSymlinks(filePath: string): Promise { + try { + const realPath = await fs.promises.realpath(filePath) + if (!realPath.startsWith(this.resolvedBasePath)) { + this.logSecurityViolation('SYMLINK_ESCAPE', { filePath, realPath }) + throw DBALError.forbidden('Symlink escape detected') + } + return realPath + } catch (error: any) { + if (error.code === 'ENOENT') { + // File doesn't exist yet, that's okay for new files + // But verify parent directory is safe + const parentDir = path.dirname(filePath) + if (parentDir !== this.basePath) { + const realParent = await fs.promises.realpath(parentDir).catch(() => null) + if (realParent && !realParent.startsWith(this.resolvedBasePath)) { + throw DBALError.forbidden('Parent directory escape detected') + } + } + return filePath + } + throw error + } + } + + private logSecurityViolation(event: string, details: unknown): void { + console.error(JSON.stringify({ + timestamp: new Date().toISOString(), + severity: 'CRITICAL', + category: 'PATH_SECURITY', + event, + details, + stack: new Error().stack + })) + } } ``` diff --git a/docs/codegen-studio.md b/docs/codegen-studio.md index ef5b5eafd..c0224c973 100644 --- a/docs/codegen-studio.md +++ b/docs/codegen-studio.md @@ -34,6 +34,7 @@ The runtime-specific paragraph is drawn from the `runtime` value, and the CLI st - **Path**: `/codegen` - **Experience**: A Material UI-powered scaffold designer that bundles the same payload used by the API and streams the ZIP for download. - **Feedback**: Inline alerts surface success or failure so teams can iteratively tune briefs before sharing the starter kit with collaborators. +- **Preview**: A manifest panel summarizes the generated values (runtime, tone, package, timestamp) after each export. ## Development tooling diff --git a/frontends/nextjs/src/app/codegen/CodegenStudioClient.tsx b/frontends/nextjs/src/app/codegen/CodegenStudioClient.tsx index 9d5ea2849..54826476b 100644 --- a/frontends/nextjs/src/app/codegen/CodegenStudioClient.tsx +++ b/frontends/nextjs/src/app/codegen/CodegenStudioClient.tsx @@ -102,11 +102,13 @@ export default function CodegenStudioClient() { setError(null) setMessage(null) try { - const filename = await fetchZip(form) + const { filename, manifest } = await fetchZip(form) setMessage(`Zip ${filename} created successfully.`) + setManifest(manifest) setStatus('success') } catch (err) { setError(err instanceof Error ? err.message : 'Unable to generate the zip') + setManifest(null) setStatus('idle') } } @@ -180,6 +182,33 @@ export default function CodegenStudioClient() { {message && {message}} {error && {error}} + {manifest && ( + + + Manifest preview + + + + Project: {manifest.projectName} + + + Package: {manifest.packageId} + + + Runtime: {manifest.runtime} + + + Tone: {manifest.tone ?? 'adaptive'} + + + Generated at: {new Date(manifest.generatedAt).toLocaleString()} + + + + )}