Assessor_2.0/tests/local-only-check.mjs

304 lines
25 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 nvdCveClient = read(join('server', 'cve', 'nvd-cve-client.ts'));
assert.match(nvdCveClient, /const NVD_CVE_API = 'https:\/\/services\.nvd\.nist\.gov\/rest\/json\/cves\/2\.0'/,
'the approved NVD client must retain its fixed backend origin');
assert.match(nvdCveClient, /MAXIMUM_RESPONSE_BYTES/, 'NVD responses must remain size-bounded');
assert.match(nvdCveClient, /AbortSignal\.timeout/, 'NVD requests must retain a timeout');
assert.doesNotMatch(nvdCveClient, /new URL\([^N]/, 'the NVD client must not construct request targets from user input');
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, restoreArtifactId\)/, '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.match(gpoMetadataParser, /includeTemplateNames = false/, 'Template labels must require explicit baseline-analyzer opt-in');
assert.doesNotMatch(gpoMetadataParser.match(/const gpoId = [\s\S]*?const domainKey =/)?.[0] ?? '', /templatename|displayname/i,
'GPO and backup identities must not depend on display names');
assert.doesNotMatch(gpoMetadataParser, /JSDOM/, 'GPO metadata parsing must not allocate a report DOM');
assert.match(gpoMetadataParser, /new SaxesParser/, 'GPO metadata must use the bounded streaming XML parser');
assert.match(gpoMetadataParser, /stack\.length >= 128/, 'GPO metadata must bound XML nesting');
assert.match(gpoMetadataParser, /slice\(0, 2049\)/, 'GPO metadata must bound retained identity text');
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, /deleteGpoArtifact/, '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\.delete/, '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, /<!DOCTYPE/, `${adapterPath} must reject XML document types independently`);
}
for (const relativePath of sourceFiles(join('src', 'importers'))) {
assert.doesNotMatch(read(relativePath), /\b(?:mkdtemp|tmpdir|createWriteStream|writeFile)\b/, `${relativePath} must not create unmanaged temporary files`);
}
const formatAdapterPaths = [
join('src', 'importers', 'csv-importer.ts'),
join('src', 'importers', 'excel-importer.ts'),
join('src', 'importers', 'html-importer.ts'),
join('src', 'importers', 'json-importer.ts'),
join('src', 'importers', 'xml-importer.ts')
];
for (const relativePath of formatAdapterPaths) {
assert.doesNotMatch(
read(relativePath),
/purple[ -]?knight|pingcastle|cis[ -]?cat|maester/i,
`${relativePath} must not contain source-tool knowledge`
);
}
const directParserImport = /from\s+['"][^'"]*\/importers\/(?:csv-importer|excel-importer|html-importer|json-importer|xml-importer|intermediate|source-extractors|candidate-mapping|canonical-candidate-projection)['"]/;
for (const directory of ['src/app', 'src/components', 'src/features']) {
for (const relativePath of sourceFiles(directory).filter(file => file.endsWith('.tsx'))) {
assert.doesNotMatch(read(relativePath), directParserImport, `${relativePath} must not contain parser or mapping logic`);
}
}console.log('Local-only security checks passed.');