import assert from 'node:assert/strict'; import { readdirSync, readFileSync } from 'node:fs'; import { extname, join, resolve } from 'node:path'; const root = resolve(import.meta.dirname, '..'); function read(relativePath) { return readFileSync(join(root, relativePath), 'utf8'); } function sourceFiles(directory) { return readdirSync(join(root, directory), { withFileTypes: true }).flatMap(entry => { const relativePath = join(directory, entry.name); if (entry.isDirectory()) return sourceFiles(relativePath); return ['.ts', '.tsx'].includes(extname(entry.name)) ? [relativePath] : []; }); } const forbiddenRuntimePatterns = [ [/\bfetch\s*\(/, 'fetch'], [/\bXMLHttpRequest\b/, 'XMLHttpRequest'], [/\bWebSocket\b/, 'WebSocket'], [/\bEventSource\b/, 'EventSource'], [/\bsendBeacon\b/, 'sendBeacon'], [/\bBroadcastChannel\b/, 'BroadcastChannel'], [/serviceWorker\.register/, 'service worker registration'], [/@tauri-apps\/api\/http/, 'Tauri HTTP API'], [/\baxios\b/, 'axios'] ]; const sameOriginApiBoundary = new Set([join('src', 'api', 'assessor-api-client.ts')]); for (const relativePath of sourceFiles('src')) { const source = read(relativePath); for (const [pattern, label] of forbiddenRuntimePatterns) { if (label === 'fetch' && sameOriginApiBoundary.has(relativePath)) continue; assert.doesNotMatch(source, pattern, `${relativePath} introduces forbidden network capability: ${label}`); } } const indexHtml = read('index.html'); assert.doesNotMatch(indexHtml, /<(?:script|link)\b[^>]+(?:src|href)=["']https?:/i, 'index.html must not load remote assets'); const packageJson = JSON.parse(read('package.json')); assert.deepEqual( Object.keys(packageJson.dependencies).sort(), ['@fontsource-variable/inter', 'jsdom', 'pg', 'react', 'react-dom', 'saxes', 'xlsx'], 'production dependencies changed; review same-origin and local-processing guarantees before accepting new packages' ); const excelImporter = read(join('src', 'importers', 'excel-importer.ts')); assert.match(excelImporter, /cellFormula:\s*false/, 'Excel formulas must remain disabled'); assert.match(excelImporter, /bookVBA:\s*false/, 'Excel VBA extraction must remain disabled'); const platformNeutralDirectories = [ 'src/domain', 'src/analysis', 'src/importers', 'src/profiles', 'src/recognition', 'src/comparison', 'src/gpo', 'src/persistence', 'src/exports', 'src/shared' ]; const browserBoundaryExceptions = new Set([join('src', 'importers', 'importer.ts')]); const forbiddenCorePatterns = [ [/\bDOMParser\b/, 'DOMParser'], [/\bwindow\b/, 'window'], [/\blocalStorage\b/, 'localStorage'], [/\bFile\b/, 'browser File'], [/\bBlob\b/, 'browser Blob'], [/URL\.createObjectURL/, 'browser object URL'], [/from\s+['"](?:react|react-dom|@tauri)/, 'UI or Tauri runtime import'] ]; for (const directory of platformNeutralDirectories) { for (const relativePath of sourceFiles(directory)) { if (browserBoundaryExceptions.has(relativePath)) continue; const source = read(relativePath); for (const [pattern, label] of forbiddenCorePatterns) { assert.doesNotMatch(source, pattern, `${relativePath} crosses the platform-neutral core boundary: ${label}`); } } } const uiDirectories = ['src/app', 'src/components', 'src/features']; const forbiddenUiDatabasePatterns = [ [/(?:SELECT|INSERTs+INTO|UPDATEs+w+s+SET|DELETEs+FROM)/i, 'SQL statement'], [/froms+['"](?:pg|postgres|sqlite|better-sqlite3|typeorm|@prisma)/i, 'database client import'] ]; for (const directory of uiDirectories) { for (const relativePath of sourceFiles(directory)) { const source = read(relativePath); for (const [pattern, label] of forbiddenUiDatabasePatterns) { assert.doesNotMatch(source, pattern, relativePath + ' introduces forbidden UI database access: ' + label); } } } const apiClient = read(join('src', 'api', 'assessor-api-client.ts')); assert.match(apiClient, /ASSESSOR_API_PREFIX/, 'the browser API client must use the versioned same-origin API prefix'); assert.doesNotMatch(apiClient, /https?:\/\//, 'the browser API client must not embed a remote origin'); const serverEntry = read(join('server', 'index.ts')); assert.match(serverEntry, /'127\.0\.0\.1'/, 'the local server must bind to loopback by default'); const httpServer = read(join('server', 'assessor-http-server.ts')); assert.doesNotMatch(httpServer, /Access-Control-Allow-Origin/i, 'the local server must not enable cross-origin access'); assert.match(httpServer, /ASSESSOR_MUTATION_HEADER/, 'mutating API requests must retain the same-origin request guard'); assert.match(httpServer, /isValidImportBase64/, 'large Base64 import payloads must use iterative validation'); assert.doesNotMatch(httpServer, /\[A-Za-z0-9\+\/\]\{4\}.*\*/, 'Base64 validation must not use an unbounded repeated-group regex'); const postgresRepository = read(join('server', 'postgres', 'postgres-assessor-repository.ts')); assert.doesNotMatch(postgresRepository, /\\$\\{query\\./, 'PostgreSQL queries must not interpolate fields directly from request queries'); assert.match(postgresRepository, /FOR UPDATE/, 'PostgreSQL mutations must serialize repository revision updates'); const migrationRunner = read(join('server', 'postgres', 'migration-runner.ts')); assert.match(migrationRunner, /pg_advisory_lock/, 'database migrations must use a PostgreSQL advisory lock'); const resourcePolicy = read(join('src', 'importers', 'resource-policy.ts')); assert.match(resourcePolicy, /maximumZipCompressionRatio/, 'resource policy must enforce a ZIP compression-ratio limit'); assert.match(resourcePolicy, /xml-doctype-rejected/, 'resource policy must reject XML document types and entities'); assert.match(resourcePolicy, /maximumTotalCellCharacters/, 'resource policy must cap total decoded cell content'); const workerClient = read(join('server', 'import-worker-client.ts')); assert.match(workerClient, /resourceLimits/, 'server imports must run with worker heap limits'); assert.match(workerClient, /parser-time-limit/, 'server imports must retain an enforced parser timeout'); assert.match(workerClient, /maximumQueuedBytes/, 'server parser queue must be byte-bounded'); const gpoFolderTransport = read(join('src', 'gpo', 'gpo-folder-transport.ts')); assert.match(gpoFolderTransport, /normalizeArtifactRelativePath/, 'GPO folder transport must normalize every browser-relative path'); assert.match(gpoFolderTransport, /artifactPathCollisionKey/, 'GPO folder transport must reject Unicode and case path collisions'); assert.match(gpoFolderTransport, /maximumZipEntries/, 'GPO folder transport must enforce the shared entry limit'); assert.match(gpoFolderTransport, /compressionMethod|setUint16\(offset \+ 8, 0/, 'GPO folder transport must use a bounded non-compressing transport envelope'); const gpoFolderUi = read(join('src', 'features', 'project-import', 'GpoFolderImport.tsx')); assert.match(gpoFolderUi, /webkitdirectory/, 'the Datahub GPO import must select a complete browser folder'); assert.match(gpoFolderUi, /announceSubmission\(envelope,/, 'the GPO folder envelope must enter the shared submission boundary'); assert.match(gpoFolderUi, /submitGpoBackup\(projectId, file, assessmentId\)/, 'folder envelopes and uploaded ZIPs must cross the project-and-timeslot-bound API'); assert.match(gpoFolderUi, /accept="\.zip,application\/zip"/, 'the Datahub GPO import must also accept an existing ZIP package'); const gpoZipInventory = read(join('server', 'gpo', 'zip-inventory.ts')); assert.match(gpoZipInventory, /zip-entry-crc-mismatch/, 'GPO ZIP inventory must verify actual entry CRC values'); assert.match(gpoZipInventory, /zip-nested-archive-rejected/, 'GPO ZIP inventory must reject nested archives'); assert.match(gpoZipInventory, /zip-link-rejected/, 'GPO ZIP inventory must reject symbolic links'); assert.match(gpoZipInventory, /inflateRawSync/, 'GPO ZIP inventory must verify actual inflated sizes'); const gpoWorkerClient = read(join('server', 'gpo', 'gpo-inventory-worker-client.ts')); assert.match(gpoWorkerClient, /resourceLimits/, 'GPO inventory must run with worker heap limits'); assert.match(gpoWorkerClient, /gpo-inventory-time-limit/, 'GPO inventory must retain an enforced timeout'); assert.match(gpoWorkerClient, /maximumSourceBytes/, 'GPO inventory worker input must be byte-bounded'); assert.match(gpoWorkerClient, /transferList: \[transferableBytes\.buffer\]/, 'large GPO worker inputs must transfer a worker-owned buffer instead of structured-cloning it'); const gpoExecutor = read(join('server', 'gpo', 'gpo-inventory-executor.ts')); assert.match(gpoExecutor, /GpoBackupWorkerResult/, 'the GPO worker must use a persistence-focused result contract'); assert.doesNotMatch(gpoExecutor.match(/export interface GpoBackupWorkerResult[\\s\\S]*?\\n}/)?.[0] ?? '', /Documents|container/, 'the worker result must not clone container or parser intermediate documents back to the backend'); const artifactStore = read(join('server', 'artifacts', 'backend-artifact-store.ts')); assert.match(artifactStore, /randomUUID/, 'raw artifacts must use opaque backend-generated storage keys'); assert.match(artifactStore, /staging/, 'raw artifacts must be written to a private staging area first'); assert.match(artifactStore, /handle\.sync/, 'raw artifact staging writes must be flushed before publication'); assert.match(artifactStore, /await link/, 'raw artifacts must be published without overwriting an existing object'); assert.match(artifactStore, /artifact-store-integrity-mismatch/, 'raw artifact reads must verify stored size and SHA-256'); assert.match(artifactStore, /assertArtifactStoreOutsidePublicRoot/, 'raw artifact storage must reject a root inside the published webroot'); const gpoMetadataParser = read(join('server', 'gpo', 'gpo-metadata-parser.ts')); const securityTemplateParser = read(join('server', 'gpo', 'security-template-parser.ts')); assert.match(securityTemplateParser, /maximumFieldCharacters/, 'Security Template parsing must enforce the documented INF field boundary'); assert.match(securityTemplateParser, /gpo-security-template-section-unsupported/, 'unknown Security Template sections must remain explicitly unsupported'); assert.match(securityTemplateParser, /state: conflicted \? 'conflicted' : 'configured'/, 'Security Template reduction must retain conflicting duplicate instructions'); assert.match(securityTemplateParser, /effectiveState: 'unknown'/, 'Security Template observations must not claim effective application'); assert.doesNotMatch(securityTemplateParser, /FindingCandidate/, 'GPO policy parsing must remain outside the scanner candidate path'); const advancedAuditParser = read(join('server', 'gpo', 'advanced-audit-parser.ts')); assert.match(advancedAuditParser, /parseCsvLexically/, 'Advanced Audit must reuse only the generic lexical CSV boundary'); assert.match(advancedAuditParser, /subcategory guid/, 'Advanced Audit identity must require the technical subcategory GUID column'); assert.match(advancedAuditParser, /setting value/, 'Advanced Audit flags must come from the numeric setting column'); assert.match(advancedAuditParser, /gpo-advanced-audit-target-unsupported/, 'per-user audit semantics must remain explicitly unsupported'); assert.match(advancedAuditParser, /effectiveState: 'unknown'/, 'Advanced Audit observations must not claim effective application'); assert.doesNotMatch(advancedAuditParser, /FindingCandidate/, 'Advanced Audit parsing must remain outside the scanner candidate path'); const gppParser = read(join('server', 'gpo', 'group-policy-preference-parser.ts')); assert.match(gppParser, /gpo-preference-external-reference-rejected/, 'GPP XML must reject active and external XML constructs'); assert.match(gppParser, /operation: instruction\.operation/, 'GPP reduction must retain CSE-specific operation semantics'); assert.match(gppParser, /sequence: instruction\.sequence/, 'GPP reduction must retain item processing order'); assert.match(gppParser, /expressionSha256/, 'GPP targeting must remain attached as a non-evaluated expression fingerprint'); assert.match(gppParser, /gpo-secret-indicator-present/, 'GPP parsing must detect cpassword without exposing it'); assert.doesNotMatch(gppParser, /decrypt|decipher|createDecipher/i, 'GPP parsing must never decrypt retained secrets'); assert.match(gppParser, /effectiveState: 'unknown'/, 'GPP observations must not claim effective application'); assert.doesNotMatch(gppParser, /FindingCandidate/, 'GPP parsing must remain outside the scanner candidate path'); assert.match(gppParser, /dom\.window\.close\(\)/, 'each GPP DOM must release its JSDOM window after parsing'); const gppLocalUsersGroups = read(join('server', 'gpo', 'gpp-local-users-groups.ts')); assert.match(gppParser, /rootName === 'groups'/, 'Groups.xml must cross the shared safe GPP XML boundary'); assert.match(gppLocalUsersGroups, /selector: sid \?/, 'Local group membership must retain SID precedence over display names'); assert.match(gppLocalUsersGroups, /deleteAllUsers/, 'Local group reduction must retain delete-all-users semantics before member processing'); assert.match(gppLocalUsersGroups, /sequence: members\.length/, 'Local group membership operations must retain source order'); assert.match(gppLocalUsersGroups, /passwordAttributePresent:/, 'Local user preferences may retain only a password-presence indicator'); assert.doesNotMatch(gppLocalUsersGroups, /decrypt|decipher|createDecipher/i, 'Local Users and Groups parsing must never decrypt cpassword'); assert.match(gppLocalUsersGroups, /effectiveState: 'unknown'/, 'Local Users and Groups observations must not claim effective application'); assert.doesNotMatch(gppLocalUsersGroups, /FindingCandidate/, 'Local Users and Groups parsing must remain outside the scanner candidate path'); const gppServices = read(join('server', 'gpo', 'gpp-services.ts')); assert.match(gppParser, /rootName === 'ntservices'/, 'Services.xml must cross the shared safe GPP XML boundary'); assert.match(gppServices, /operation: 'configure'/, 'Services must not be forced into Registry C-D-R-U action semantics'); assert.match(gppServices, /serviceAction/, 'Services must retain runtime service actions separately from preference processing'); assert.match(gppServices, /startupType/, 'Services must retain startup configuration separately from runtime actions'); assert.match(gppServices, /passwordAttributePresent:/, 'Service preferences may retain only a password-presence indicator'); assert.doesNotMatch(gppServices, /decrypt|decipher|createDecipher/i, 'Services parsing must never decrypt cPassword'); assert.match(gppServices, /document.scope !== 'computer'/, 'Services observations must remain computer-scoped'); assert.match(gppServices, /effectiveState: 'unknown'/, 'Services observations must not claim effective application'); assert.doesNotMatch(gppServices, /FindingCandidate/, 'Services parsing must remain outside the scanner candidate path'); const gppDriveMaps = read(join('server', 'gpo', 'gpp-drive-maps.ts')); assert.match(gppParser, /rootName === 'drives'/, 'Drives.xml must cross the shared safe GPP XML boundary'); assert.match(gppDriveMaps, /delete-range/, 'Drive Maps must retain ranged delete semantics when useLetter is false'); assert.match(gppDriveMaps, /first-available/, 'Drive Maps must retain first-available create semantics when useLetter is false'); assert.match(gppDriveMaps, /first-mapped-in-range/, 'Drive Maps must retain range lookup for replace and update'); assert.match(gppDriveMaps, /thisDrive/, 'Drive Maps must retain per-drive visibility separately from global visibility'); assert.match(gppDriveMaps, /passwordAttributePresent:/, 'Drive Maps may retain only a credential-presence indicator'); assert.doesNotMatch(gppDriveMaps, /decrypt|decipher|createDecipher/i, 'Drive Maps parsing must never decrypt cpassword'); assert.match(gppDriveMaps, /document.scope !== 'user'/, 'Drive Maps observations must remain user-scoped'); assert.match(gppDriveMaps, /effectiveState: 'unknown'/, 'Drive Maps observations must not claim effective application'); assert.doesNotMatch(gppDriveMaps, /FindingCandidate/, 'Drive Maps parsing must remain outside the scanner candidate path'); assert.match(gpoMetadataParser, /maximumMarkupElements/, 'GPO metadata parsing must enforce an element limit'); assert.match(gpoMetadataParser, /gpo-metadata-external-reference-rejected/, 'GPO metadata parsing must reject external-reference mechanisms'); assert.doesNotMatch(gpoMetadataParser, /displayName/i, 'GPO display names must not be used as technical identity'); assert.match(gpoMetadataParser, /dom\.window\.close\(\)/, 'each GPO metadata DOM must release its JSDOM window after parsing'); const gpoBackupIngestion = read(join('server', 'gpo', 'gpo-backup-ingestion.ts')); assert.match(gpoBackupIngestion, /evidence: 'backend-receipt'/, 'GPO collection evidence must come from the backend receipt'); assert.match(gpoBackupIngestion, /scope: 'selected-gpos'/, 'A selected tenant GPO backup folder must remain a selected subset without completeness evidence'); assert.match(gpoBackupIngestion, /evidence: 'user-selection'/, 'All GPO roots from one selected folder must retain their common selection evidence'); const gpoSnapshotBuilder = read(join('src', 'gpo', 'snapshot-builder.ts')); assert.match(gpoSnapshotBuilder, /permitsSetAbsenceConclusions: status === 'consistent' && completeness === 'complete'/, 'Selected GPO snapshots must never permit set-absence conclusions'); const gpoSubmissionService = read(join('server', 'gpo', 'gpo-backup-submission-service.ts')); assert.match(gpoSubmissionService, /sensitivity: 'secret'/, 'GPO originals must default to secret sensitivity'); assert.match(gpoSubmissionService, /analyze-gpo-backup/, 'GPO submissions must enqueue the dedicated analysis job kind'); assert.match(gpoSubmissionService, /getProject\(request\.projectId\)/, 'GPO submissions must verify the project before retaining bytes'); assert.match(gpoSubmissionService, /saveImportArtifactAndEnqueueJob/, 'GPO artifact metadata and analysis job must cross one atomic repository operation'); assert.match(gpoSubmissionService, /artifactStore\.remove/, 'failed or concurrent GPO submissions must remove their newly created unreferenced object'); const gpoReconciler = read(join('server', 'gpo', 'gpo-artifact-reconciler.ts')); assert.match(gpoReconciler, /gracePeriodMs/, 'orphan reconciliation must use an explicit grace period'); assert.match(gpoReconciler, /saveImportArtifactAndEnqueueJob/, 'orphan reconciliation must repair referenced GPO artifacts without jobs atomically'); assert.match(gpoReconciler, /artifactStore\.reconcile/, 'orphan reconciliation must compare database references with private storage'); assert.match(gpoReconciler, /invalidStorageReferences === 0 && referencedStorageKeys\.length > 0/, 'orphan reconciliation must never remove regular objects while a persisted storage reference is invalid or the trusted reference set is empty'); assert.match(gpoReconciler, /listArtifactDeletionRequests/, 'artifact reconciliation must resume pending deletion outbox requests'); const gpoManagement = read(join('server', 'gpo', 'gpo-backup-management-service.ts')); assert.match(gpoManagement, /retryIngestionJob/, 'failed GPO analysis must be retryable without another upload'); assert.match(gpoManagement, /deleteFailedGpoArtifact/, 'failed GPO deletion must atomically create a durable deletion request'); assert.match(gpoManagement, /artifactStore\.remove/, 'failed GPO deletion must remove the backend-controlled copy'); const gpoJobProcessor = read(join('server', 'gpo', 'gpo-backup-job-processor.ts')); assert.match(gpoJobProcessor, /kinds: \['analyze-gpo-backup'\]/, 'the GPO worker must claim only its dedicated job kind'); assert.match(gpoJobProcessor, /artifactStore\.read/, 'the GPO worker must re-read the retained backend artifact through the integrity-checking store'); assert.match(gpoJobProcessor, /kind: 'artifact-parser'/, 'GPO processing history must identify the artifact parser boundary'); const gpoAnalysisSave = gpoJobProcessor.indexOf('saveGpoBackupAnalysis'); const gpoJobCompletion = gpoJobProcessor.indexOf('completeIngestionJob', gpoAnalysisSave); assert.ok(gpoAnalysisSave >= 0 && gpoJobCompletion > gpoAnalysisSave, 'GPO analysis must be persisted before its job is completed'); assert.match(serverEntry, /gpoJobRunner\.start\(\)/, 'the server must start the GPO job runner'); assert.match(serverEntry, /gpoJobRunner\.stop\(\)/, 'graceful shutdown must drain the GPO job runner'); assert.match(httpServer, /gpoBackupSubmissionService\.submit/, 'GPO uploads must cross the project-bound submission service'); assert.match(httpServer, /listProjectGpoBackupStatus/, 'GPO processing status must be reloaded through a project-bound read endpoint'); assert.match(httpServer, /confirmDelete !== true/, 'GPO artifact deletion must require exact confirmation'); assert.match(httpServer, /gpoBackupManagementService\.retry/, 'GPO retry must cross the project-bound management service'); assert.match(httpServer, /gpoBackupManagementService\.deleteFailed/, 'GPO deletion must cross the project-bound management service'); assert.match(serverEntry, /gpoArtifactReconciliationRunner\.start\(\)/, 'the server must start the artifact reconciliation runner'); assert.match(serverEntry, /gpoArtifactReconciliationRunner\.stop\(\)/, 'graceful shutdown must drain artifact reconciliation'); const gpoStatusUi = read(join('src', 'features', 'project-import', 'GpoFolderImport.tsx')); assert.match(gpoStatusUi, /listProjectGpoBackupStatus/, 'the tenant view must reload persistent GPO processing status'); assert.match(gpoStatusUi, /setInterval/, 'the visible tenant view must refresh GPO status while processing continues'); assert.match(gpoStatusUi, /Speichergrenze des isolierten Analyse-Workers überschritten/, 'the tenant view must explain the GPO memory error instead of showing only its technical code'); assert.match(serverEntry, /ASSESSOR_ARTIFACT_ROOT/, 'the server must expose an explicit artifact-root configuration'); assert.match(serverEntry, /assertArtifactStoreOutsidePublicRoot/, 'server startup must enforce artifact/webroot separation'); assert.doesNotMatch(httpServer, /storageKey.*sendJson/, 'GPO HTTP responses must not expose backend storage keys'); const serverBuild = read('vite.server.config.ts'); assert.match(serverBuild, /import-worker/, 'production server build must emit the isolated import worker'); assert.match(serverBuild, /gpo-inventory-worker/, 'production server build must emit the isolated GPO inventory worker'); const htmlExtraction = read(join('src', 'importers', 'html-extraction.ts')); assert.match(htmlExtraction, /sanitizeHtmlDocument/, 'shared HTML extraction must sanitize imported HTML'); assert.match(htmlExtraction, /bounded-lexical/, 'large HTML reports must retain a stack-safe extraction path'); assert.doesNotMatch(htmlExtraction, /\.textContent/, 'HTML extraction must not recursively request whole-subtree textContent'); for (const adapterPath of [join('server', 'node-import-runtime-adapters.ts'), join('src', 'platform', 'browser', 'import-runtime-adapters.ts')]) { const adapter = read(adapterPath); assert.match(adapter, /decodeHtmlLexically/, `${adapterPath} must retain the bounded HTML fallback`); assert.match(adapter, / file.endsWith('.tsx'))) { assert.doesNotMatch(read(relativePath), directParserImport, `${relativePath} must not contain parser or mapping logic`); } }console.log('Local-only security checks passed.');