@stll/anonymize-mcp 2.4.0 → 2.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -74,7 +74,7 @@ type AuditSafeResult = {
74
74
  piiCleanGuaranteed?: false;
75
75
  };
76
76
  type LocalAnonymizeServiceFaults = {
77
- beforeOutputPublish?: () => void;
77
+ beforeOutputPublish?: () => void | Promise<void>;
78
78
  };
79
79
  type ReadInputOptions = {
80
80
  path: string;
package/dist/local.mjs CHANGED
@@ -808,7 +808,7 @@ var LocalAnonymizeService = class {
808
808
  fillRgb: input.fillRgb
809
809
  });
810
810
  if (anonymized.certificate.structurePixelRewriteVerified !== true || anonymized.certificate.piiCleanGuaranteed !== false) throw new Error("PDF raster verification did not satisfy MCP policy");
811
- this.#faults.beforeOutputPublish?.();
811
+ await this.#faults.beforeOutputPublish?.();
812
812
  await destination.write(anonymized.document);
813
813
  return {
814
814
  operation: "anonymize",
@@ -835,7 +835,7 @@ var LocalAnonymizeService = class {
835
835
  try {
836
836
  const result = lease.entry.session.redact_text(text);
837
837
  await this.#persistSession(input.sessionId, lease.entry.session);
838
- this.#faults.beforeOutputPublish?.();
838
+ await this.#faults.beforeOutputPublish?.();
839
839
  await destination.write(result.redaction.redactedText);
840
840
  this.#commitSession(lease);
841
841
  return {
@@ -897,7 +897,7 @@ var LocalAnonymizeService = class {
897
897
  const redactedText = applyTextReplacements(text, block.replacements);
898
898
  plan.commit();
899
899
  await this.#persistSession(input.sessionId, lease.entry.session);
900
- this.#faults.beforeOutputPublish?.();
900
+ await this.#faults.beforeOutputPublish?.();
901
901
  await destination.write(redactedText);
902
902
  this.#commitSession(lease);
903
903
  return {
@@ -962,7 +962,7 @@ var LocalAnonymizeService = class {
962
962
  policy: { coverage: { mode: input.allowPartialCoverage ? DOCX_COVERAGE_MODES.allowPartial : DOCX_COVERAGE_MODES.requireFull } }
963
963
  });
964
964
  await this.#persistSession(input.sessionId, lease.entry.session);
965
- this.#faults.beforeOutputPublish?.();
965
+ await this.#faults.beforeOutputPublish?.();
966
966
  await destination.write(result.document);
967
967
  this.#commitSession(lease);
968
968
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"local.mjs","names":["SESSION_ID","READ_CHUNK_BYTES","sameFile","fsConstants","readHandleBounded","#directory","#faultInjector","#key","#lockHandle","#validateInventory","#syncDirectory","#assertOpen","#closePromise","#state","#mutationTail","#withMutation","#load","#path","#assertDirectory","#save","#inject","#delete","#roots","fsConstants","#scope","#sessionInitializations","#sessions","#durableSessions","#faults","#pdfProvider","#closePromise","#state","#activeOperations","#operationsDrained","#runOperation","#session","#rollbackSession","#persistSession","#readSession","#anonymizeText","#serializePdfOperation","#anonymizePdf","#pdfOperationTail","#commitSession","#restoreText","#anonymizeTextWithExternalDetections","#anonymizeDocx","#restoreDocx","#inspectDocx"],"sources":["../src/durable-sessions.ts","../src/local.ts"],"sourcesContent":["import { createHash, randomUUID } from \"node:crypto\";\nimport {\n constants as fsConstants,\n lstat,\n open,\n readdir,\n realpath,\n rename,\n stat,\n unlink,\n} from \"node:fs/promises\";\nimport type { FileHandle } from \"node:fs/promises\";\nimport { isAbsolute, join, resolve } from \"node:path\";\n\nexport const SESSION_ARCHIVE_KEY_BYTES = 32;\nexport const SESSION_ARCHIVE_MAX_BYTES = 16 * 1024 * 1024 + 57;\nexport const SESSION_ARCHIVE_MAX_COUNT = 256;\nexport const SESSION_ARCHIVE_TOTAL_MAX_BYTES = 256 * 1024 * 1024;\n\nconst ARCHIVE_NAME = /^[a-f0-9]{64}\\.stlasess$/u;\nconst SESSION_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/u;\nconst STAGING_NAME =\n /^[a-f0-9]{64}\\.stlasess\\.tmp\\.[0-9a-f]{8}-[0-9a-f-]{27}$/u;\nconst READ_CHUNK_BYTES = 64 * 1024;\nconst LOCK_FILE_NAME = \".stella-session.lock\";\n\nexport const DURABLE_SESSION_FAULT_POINTS = {\n beforeDirectoryFsync: \"before-directory-fsync\",\n beforeRename: \"before-rename\",\n beforeStagingFsync: \"before-staging-fsync\",\n beforeStagingWrite: \"before-staging-write\",\n} as const;\n\nexport type DurableSessionFaultPoint =\n (typeof DURABLE_SESSION_FAULT_POINTS)[keyof typeof DURABLE_SESSION_FAULT_POINTS];\n\ntype FileIdentity = {\n dev: number;\n ino: number;\n};\n\ntype DirectoryIdentity = FileIdentity & {\n path: string;\n};\n\ntype SessionInventory = {\n archiveCount: number;\n totalBytes: number;\n};\n\nconst sameFile = (left: FileIdentity, right: FileIdentity): boolean =>\n left.dev === right.dev && left.ino === right.ino;\n\nconst assertOwner = (uid: number, label: string): void => {\n if (typeof process.getuid === \"function\" && uid !== process.getuid()) {\n throw new Error(`${label} must be owned by the current user`);\n }\n};\n\nconst assertPrivateMode = (mode: number, label: string): void => {\n if ((mode & 0o077) !== 0) {\n throw new Error(`${label} must not grant group or other permissions`);\n }\n};\n\nconst assertPosixDurabilitySupport = (): void => {\n if (\n (process.platform !== \"darwin\" && process.platform !== \"linux\") ||\n typeof process.getuid !== \"function\" ||\n typeof fsConstants.O_NOFOLLOW !== \"number\" ||\n fsConstants.O_NOFOLLOW === 0 ||\n typeof fsConstants.O_DIRECTORY !== \"number\" ||\n fsConstants.O_DIRECTORY === 0\n ) {\n throw new Error(\n \"Encrypted durable MCP sessions require supported POSIX owner, nofollow, directory-fsync, and advisory-lock semantics on macOS or Linux\",\n );\n }\n};\n\nconst acquireDirectoryLock = async (\n directoryPath: string,\n): Promise<FileHandle> => {\n const path = join(directoryPath, LOCK_FILE_NAME);\n const handle = await open(\n path,\n fsConstants.O_RDWR |\n fsConstants.O_CREAT |\n fsConstants.O_NOFOLLOW |\n fsConstants.O_NONBLOCK,\n 0o600,\n );\n try {\n const metadata = await handle.stat();\n if (!metadata.isFile()) {\n throw new Error(\"MCP session lock must be a regular file\");\n }\n assertOwner(metadata.uid, \"MCP session lock\");\n assertPrivateMode(metadata.mode, \"MCP session lock\");\n // Loaded lazily because Bun is used as a repository test/build tool but\n // cannot safely load this Node native addon. The shipped MCP runtime is\n // Node; Node integration tests exercise this path.\n const { tryLock } = await import(\"fs-native-extensions\");\n if (!tryLock(handle.fd)) {\n throw new Error(\n \"MCP session directory is already locked by another server\",\n );\n }\n return handle;\n } catch (error) {\n await handle.close().catch(() => undefined);\n throw error;\n }\n};\n\nconst canonicalAbsolutePath = async (\n path: string,\n label: string,\n): Promise<string> => {\n if (!isAbsolute(path)) {\n throw new Error(`${label} must be an absolute path`);\n }\n const normalized = resolve(path);\n const canonical = await realpath(normalized);\n if (canonical !== normalized) {\n throw new Error(`${label} must not contain symbolic links`);\n }\n return canonical;\n};\n\nconst readHandleBounded = async (\n handle: Pick<Awaited<ReturnType<typeof open>>, \"read\">,\n maximumBytes: number,\n): Promise<Uint8Array> => {\n const chunks: Uint8Array[] = [];\n let total = 0;\n for (;;) {\n const remaining = maximumBytes - total;\n const chunk = Buffer.allocUnsafe(Math.min(READ_CHUNK_BYTES, remaining + 1));\n const { bytesRead } = await handle.read(chunk, 0, chunk.byteLength, null);\n if (bytesRead === 0) {\n return Buffer.concat(chunks, total);\n }\n total += bytesRead;\n if (total > maximumBytes) {\n throw new Error(\"Encrypted session archive exceeds the byte limit\");\n }\n chunks.push(chunk.subarray(0, bytesRead));\n }\n};\n\nconst readKey = async (path: string): Promise<Uint8Array> => {\n const canonical = await canonicalAbsolutePath(path, \"MCP session key file\");\n const linkedMetadata = await lstat(canonical);\n if (!linkedMetadata.isFile() || linkedMetadata.isSymbolicLink()) {\n throw new Error(\"MCP session key file must be a regular file\");\n }\n assertOwner(linkedMetadata.uid, \"MCP session key file\");\n assertPrivateMode(linkedMetadata.mode, \"MCP session key file\");\n if (linkedMetadata.size !== SESSION_ARCHIVE_KEY_BYTES) {\n throw new Error(\n `MCP session key file must contain exactly ${SESSION_ARCHIVE_KEY_BYTES} raw bytes`,\n );\n }\n const handle = await open(\n canonical,\n fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | fsConstants.O_NONBLOCK,\n );\n let keyBuffer: Buffer | undefined;\n try {\n const openedMetadata = await handle.stat();\n if (\n !openedMetadata.isFile() ||\n !sameFile(linkedMetadata, openedMetadata) ||\n openedMetadata.size !== SESSION_ARCHIVE_KEY_BYTES\n ) {\n throw new Error(\"MCP session key file changed while it was validated\");\n }\n keyBuffer = Buffer.alloc(SESSION_ARCHIVE_KEY_BYTES + 1);\n let keyLength = 0;\n for (;;) {\n const { bytesRead } = await handle.read(\n keyBuffer,\n keyLength,\n keyBuffer.byteLength - keyLength,\n null,\n );\n if (bytesRead === 0 || keyLength + bytesRead === keyBuffer.byteLength) {\n keyLength += bytesRead;\n break;\n }\n keyLength += bytesRead;\n }\n if (keyLength !== SESSION_ARCHIVE_KEY_BYTES) {\n keyBuffer.fill(0);\n throw new Error(\n `MCP session key file must contain exactly ${SESSION_ARCHIVE_KEY_BYTES} raw bytes`,\n );\n }\n const currentMetadata = await stat(canonical);\n if (!sameFile(openedMetadata, currentMetadata)) {\n keyBuffer.fill(0);\n throw new Error(\"MCP session key file changed while it was read\");\n }\n const key = new Uint8Array(SESSION_ARCHIVE_KEY_BYTES);\n key.set(keyBuffer.subarray(0, SESSION_ARCHIVE_KEY_BYTES));\n keyBuffer.fill(0);\n return key;\n } finally {\n keyBuffer?.fill(0);\n await handle.close();\n }\n};\n\nconst archiveName = (sessionId: string): string =>\n `${createHash(\"sha256\").update(sessionId, \"utf8\").digest(\"hex\")}.stlasess`;\n\nconst assertSessionId = (sessionId: string): void => {\n if (!SESSION_ID.test(sessionId)) {\n throw new Error(\"MCP session ID is invalid\");\n }\n};\n\nexport type DurableSessionStoreOptions = {\n faultInjector?: (point: DurableSessionFaultPoint) => Promise<void> | void;\n keyFile: string;\n sessionDirectory: string;\n};\n\nexport type StoredSessionArchive = {\n bytes: Uint8Array;\n};\n\nexport type EncryptableSession = {\n toEncryptedArchiveAt(\n key: Uint8Array,\n observedAtEpochSeconds: number,\n ): Uint8Array;\n};\n\nexport type EncryptedSessionRestorer<Session> = {\n restoreEncryptedRedactionSession(options: {\n archive: Uint8Array;\n expectedSessionId: string;\n key: Uint8Array;\n observedAtEpochSeconds: number;\n }): Session;\n};\n\nexport type RestoreStoredSessionOptions<Session> = {\n archive: Uint8Array;\n expectedSessionId: string;\n observedAtEpochSeconds: number;\n restorer: EncryptedSessionRestorer<Session>;\n};\n\nexport class DurableSessionStore {\n #closePromise: Promise<void> | undefined;\n readonly #directory: DirectoryIdentity;\n readonly #faultInjector:\n | ((point: DurableSessionFaultPoint) => Promise<void> | void)\n | undefined;\n readonly #key: Uint8Array;\n readonly #lockHandle: FileHandle;\n #mutationTail: Promise<void> = Promise.resolve();\n #state: \"closed\" | \"closing\" | \"open\" = \"open\";\n\n private constructor(\n directory: DirectoryIdentity,\n key: Uint8Array,\n lockHandle: FileHandle,\n faultInjector?: (point: DurableSessionFaultPoint) => Promise<void> | void,\n ) {\n this.#directory = directory;\n this.#key = key;\n this.#lockHandle = lockHandle;\n this.#faultInjector = faultInjector;\n }\n\n static async create({\n keyFile,\n sessionDirectory,\n faultInjector,\n }: DurableSessionStoreOptions): Promise<DurableSessionStore> {\n assertPosixDurabilitySupport();\n const directoryPath = await canonicalAbsolutePath(\n sessionDirectory,\n \"MCP session directory\",\n );\n const metadata = await lstat(directoryPath);\n if (!metadata.isDirectory() || metadata.isSymbolicLink()) {\n throw new Error(\"MCP session directory must be a directory\");\n }\n assertOwner(metadata.uid, \"MCP session directory\");\n assertPrivateMode(metadata.mode, \"MCP session directory\");\n const key = await readKey(keyFile);\n let lockHandle: FileHandle | undefined;\n try {\n lockHandle = await acquireDirectoryLock(directoryPath);\n const store = new DurableSessionStore(\n { dev: metadata.dev, ino: metadata.ino, path: directoryPath },\n key,\n lockHandle,\n faultInjector,\n );\n await store.#validateInventory({ removeStagingFiles: true });\n await store.#syncDirectory();\n return store;\n } catch (error) {\n key.fill(0);\n await lockHandle?.close().catch(() => undefined);\n throw error;\n }\n }\n\n seal(\n session: EncryptableSession,\n observedAtEpochSeconds: number,\n ): Uint8Array {\n this.#assertOpen();\n return session.toEncryptedArchiveAt(this.#key, observedAtEpochSeconds);\n }\n\n restore<Session>({\n archive,\n expectedSessionId,\n observedAtEpochSeconds,\n restorer,\n }: RestoreStoredSessionOptions<Session>): Session {\n this.#assertOpen();\n return restorer.restoreEncryptedRedactionSession({\n archive,\n expectedSessionId,\n key: this.#key,\n observedAtEpochSeconds,\n });\n }\n\n async close(): Promise<void> {\n if (this.#closePromise !== undefined) {\n return this.#closePromise;\n }\n this.#state = \"closing\";\n this.#closePromise = (async () => {\n await this.#mutationTail;\n this.#key.fill(0);\n await this.#lockHandle.close();\n this.#state = \"closed\";\n })();\n return this.#closePromise;\n }\n\n async load(sessionId: string): Promise<StoredSessionArchive | undefined> {\n this.#assertOpen();\n return this.#withMutation(() => this.#load(sessionId));\n }\n\n async #load(sessionId: string): Promise<StoredSessionArchive | undefined> {\n assertSessionId(sessionId);\n await this.#validateInventory({ removeStagingFiles: false });\n const path = this.#path(sessionId);\n let handle: Awaited<ReturnType<typeof open>>;\n try {\n handle = await open(\n path,\n fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | fsConstants.O_NONBLOCK,\n );\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return undefined;\n }\n throw new Error(\"Encrypted session archive could not be opened\", {\n cause: error,\n });\n }\n try {\n const metadata = await handle.stat();\n if (!metadata.isFile()) {\n throw new Error(\"Encrypted session archive must be a regular file\");\n }\n assertOwner(metadata.uid, \"Encrypted session archive\");\n assertPrivateMode(metadata.mode, \"Encrypted session archive\");\n if (metadata.size > SESSION_ARCHIVE_MAX_BYTES) {\n throw new Error(\"Encrypted session archive exceeds the byte limit\");\n }\n const bytes = await readHandleBounded(handle, SESSION_ARCHIVE_MAX_BYTES);\n await this.#assertDirectory();\n const currentMetadata = await lstat(path);\n if (!currentMetadata.isFile() || !sameFile(metadata, currentMetadata)) {\n throw new Error(\"Encrypted session archive changed while it was read\");\n }\n return { bytes };\n } finally {\n await handle.close();\n }\n }\n\n async save(sessionId: string, archive: Uint8Array): Promise<void> {\n this.#assertOpen();\n return this.#withMutation(() => this.#save(sessionId, archive));\n }\n\n async #save(sessionId: string, archive: Uint8Array): Promise<void> {\n assertSessionId(sessionId);\n if (archive.byteLength > SESSION_ARCHIVE_MAX_BYTES) {\n throw new Error(\"Encrypted session archive exceeds the byte limit\");\n }\n const inventory = await this.#validateInventory({\n removeStagingFiles: false,\n });\n await this.#assertDirectory();\n const destination = this.#path(sessionId);\n const temporary = `${destination}.tmp.${randomUUID()}`;\n const handle = await open(\n temporary,\n fsConstants.O_WRONLY |\n fsConstants.O_CREAT |\n fsConstants.O_EXCL |\n fsConstants.O_NOFOLLOW,\n 0o600,\n );\n try {\n const openedMetadata = await handle.stat();\n if (!openedMetadata.isFile()) {\n throw new Error(\"Encrypted session staging path is not a regular file\");\n }\n await this.#inject(DURABLE_SESSION_FAULT_POINTS.beforeStagingWrite);\n await handle.writeFile(archive);\n await this.#inject(DURABLE_SESSION_FAULT_POINTS.beforeStagingFsync);\n await handle.sync();\n const stagedMetadata = await lstat(temporary);\n if (\n !stagedMetadata.isFile() ||\n !sameFile(openedMetadata, stagedMetadata)\n ) {\n throw new Error(\n \"Encrypted session staging file changed before publication\",\n );\n }\n await this.#assertDirectory();\n const existing = await lstat(destination).catch((error: unknown) => {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return undefined;\n }\n throw error;\n });\n if (\n existing !== undefined &&\n (!existing.isFile() || existing.isSymbolicLink())\n ) {\n throw new Error(\"Encrypted session archive path is not a regular file\");\n }\n const nextCount =\n inventory.archiveCount + (existing === undefined ? 1 : 0);\n const nextTotalBytes =\n inventory.totalBytes - (existing?.size ?? 0) + archive.byteLength;\n if (nextCount > SESSION_ARCHIVE_MAX_COUNT) {\n throw new Error(\n `MCP session archives must not exceed ${SESSION_ARCHIVE_MAX_COUNT}`,\n );\n }\n if (nextTotalBytes > SESSION_ARCHIVE_TOTAL_MAX_BYTES) {\n throw new Error(\"MCP session archives exceed the aggregate byte limit\");\n }\n await this.#inject(DURABLE_SESSION_FAULT_POINTS.beforeRename);\n await rename(temporary, destination);\n await this.#assertDirectory();\n const published = await lstat(destination);\n if (!published.isFile() || !sameFile(openedMetadata, published)) {\n throw new Error(\"Encrypted session archive publication was not atomic\");\n }\n await this.#syncDirectory();\n } finally {\n await handle.close().catch(() => undefined);\n await unlink(temporary).catch(() => undefined);\n }\n }\n\n async delete(sessionId: string): Promise<void> {\n this.#assertOpen();\n return this.#withMutation(() => this.#delete(sessionId));\n }\n\n async #delete(sessionId: string): Promise<void> {\n assertSessionId(sessionId);\n await this.#assertDirectory();\n const path = this.#path(sessionId);\n const metadata = await lstat(path).catch((error: unknown) => {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return undefined;\n }\n throw error;\n });\n if (metadata === undefined) {\n return;\n }\n if (!metadata.isFile() || metadata.isSymbolicLink()) {\n throw new Error(\"Encrypted session archive path is not a regular file\");\n }\n await unlink(path);\n await this.#syncDirectory();\n await this.#assertDirectory();\n }\n\n #path(sessionId: string): string {\n return join(this.#directory.path, archiveName(sessionId));\n }\n\n #assertOpen(): void {\n if (this.#state !== \"open\") {\n throw new Error(\"MCP durable session store is closing or closed\");\n }\n }\n\n async #withMutation<Result>(\n operation: () => Promise<Result>,\n ): Promise<Result> {\n const previous = this.#mutationTail;\n let release = (): void => undefined;\n const current = new Promise<void>((resolvePromise) => {\n release = resolvePromise;\n });\n this.#mutationTail = previous.then(() => current);\n await previous;\n try {\n return await operation();\n } finally {\n release();\n }\n }\n\n async #assertDirectory(): Promise<void> {\n const canonical = await realpath(this.#directory.path);\n const metadata = await lstat(this.#directory.path);\n if (\n canonical !== this.#directory.path ||\n !metadata.isDirectory() ||\n metadata.isSymbolicLink() ||\n !sameFile(this.#directory, metadata)\n ) {\n throw new Error(\"MCP session directory changed while it was being used\");\n }\n assertOwner(metadata.uid, \"MCP session directory\");\n assertPrivateMode(metadata.mode, \"MCP session directory\");\n }\n\n async #syncDirectory(): Promise<void> {\n await this.#inject(DURABLE_SESSION_FAULT_POINTS.beforeDirectoryFsync);\n const handle = await open(\n this.#directory.path,\n fsConstants.O_RDONLY | fsConstants.O_DIRECTORY | fsConstants.O_NOFOLLOW,\n );\n try {\n const metadata = await handle.stat();\n if (!metadata.isDirectory() || !sameFile(this.#directory, metadata)) {\n throw new Error(\"MCP session directory changed before synchronization\");\n }\n await handle.sync();\n } finally {\n await handle.close();\n }\n }\n\n async #inject(point: DurableSessionFaultPoint): Promise<void> {\n await this.#faultInjector?.(point);\n }\n\n async #validateInventory({\n removeStagingFiles,\n }: {\n removeStagingFiles: boolean;\n }): Promise<SessionInventory> {\n await this.#assertDirectory();\n const entries = await readdir(this.#directory.path, {\n withFileTypes: true,\n });\n let archiveCount = 0;\n let totalBytes = 0;\n for (const entry of entries) {\n const path = join(this.#directory.path, entry.name);\n if (entry.name === LOCK_FILE_NAME) {\n const metadata = await lstat(path);\n if (!metadata.isFile() || metadata.isSymbolicLink()) {\n throw new Error(\"MCP session directory contains an unsafe lock path\");\n }\n assertOwner(metadata.uid, \"MCP session lock\");\n assertPrivateMode(metadata.mode, \"MCP session lock\");\n continue;\n }\n if (STAGING_NAME.test(entry.name)) {\n if (!removeStagingFiles) {\n throw new Error(\"MCP session directory contains a partial archive\");\n }\n const metadata = await lstat(path);\n if (!metadata.isFile() || metadata.isSymbolicLink()) {\n throw new Error(\n \"MCP session directory contains an unsafe staging path\",\n );\n }\n await unlink(path);\n await this.#syncDirectory();\n continue;\n }\n if (\n !ARCHIVE_NAME.test(entry.name) ||\n !entry.isFile() ||\n entry.isSymbolicLink()\n ) {\n throw new Error(\"MCP session directory contains an unsupported entry\");\n }\n const metadata = await lstat(path);\n if (!metadata.isFile() || metadata.isSymbolicLink()) {\n throw new Error(\n \"MCP session directory contains an unsafe archive path\",\n );\n }\n assertOwner(metadata.uid, \"Encrypted session archive\");\n assertPrivateMode(metadata.mode, \"Encrypted session archive\");\n if (metadata.size > SESSION_ARCHIVE_MAX_BYTES) {\n throw new Error(\"Encrypted session archive exceeds the byte limit\");\n }\n archiveCount += 1;\n totalBytes += metadata.size;\n if (archiveCount > SESSION_ARCHIVE_MAX_COUNT) {\n throw new Error(\n `MCP session archives must not exceed ${SESSION_ARCHIVE_MAX_COUNT}`,\n );\n }\n if (totalBytes > SESSION_ARCHIVE_TOTAL_MAX_BYTES) {\n throw new Error(\"MCP session archives exceed the aggregate byte limit\");\n }\n }\n await this.#assertDirectory();\n return { archiveCount, totalBytes };\n }\n}\n","import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport {\n CAPABILITY_MANIFEST,\n type NativeCallerDetection,\n type NativeTextReplacement,\n type PreparedNativePipeline,\n} from \"@stll/anonymize\";\nimport {\n DOCX_ARCHIVE_MAX_BYTES,\n DOCX_COVERAGE_MODES,\n anonymizeDocx,\n extractDocxText,\n restoreDocxText,\n type DocxAnonymizationSession,\n type DocxRestorationSession,\n} from \"@stll/anonymize-docx\";\nimport {\n PDF_DOCUMENT_MAX_BYTES,\n anonymizePdfRaster,\n renderPdfWithPopplerTesseract,\n} from \"@stll/anonymize-pdf\";\nimport * as nativeNode from \"@stll/anonymize/native-node\";\nimport {\n constants as fsConstants,\n link,\n lstat,\n open,\n realpath,\n stat,\n unlink,\n} from \"node:fs/promises\";\nimport { randomUUID } from \"node:crypto\";\nimport {\n dirname,\n extname,\n isAbsolute,\n relative,\n resolve,\n sep,\n} from \"node:path\";\nimport * as z from \"zod/v4\";\n\nimport { DurableSessionStore } from \"./durable-sessions\";\n\nconst TEXT_MAX_BYTES = 64 * 1024 * 1024;\nconst EXTERNAL_DETECTION_BATCH_MAX_BYTES = 16 * 1024 * 1024;\nconst EXTERNAL_DETECTION_BATCH_VERSION = 1 as const;\nconst PATH_MAX_CHARACTERS = 32_768;\nconst SESSION_MAX_COUNT = 256;\nconst SESSION_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/u;\nconst READ_CHUNK_BYTES = 64 * 1024;\n\nconst observedAtEpochSeconds = (): number => {\n const seconds = Math.floor(Date.now() / 1000);\n if (seconds < 0 || seconds > 0xff_ff_ff_ff) {\n throw new Error(\"The current time is outside the supported session range\");\n }\n return seconds;\n};\n\nexport const MCP_SESSION_MODES = {\n durableEncrypted: \"durable-encrypted\",\n memory: \"memory\",\n} as const;\n\nexport type McpSessionMode =\n (typeof MCP_SESSION_MODES)[keyof typeof MCP_SESSION_MODES];\n\nexport type AuditSafeResult = {\n operation: \"anonymize\" | \"inspect\" | \"restore\";\n format: \"docx\" | \"pdf\" | \"text\";\n outputCreated: boolean;\n sessionId?: string;\n entityCount?: number;\n blockCount?: number;\n rewrittenBlockCount?: number;\n restoredPlaceholderCount?: number;\n coverageStatus?: \"full\" | \"partial\";\n externalDetectionBatchStatus?: \"accepted\";\n externalDetectionCount?: number;\n retainedExternalDetectionCount?: number;\n pageCount?: number;\n mappedRegionCount?: number;\n structurePixelRewriteVerified?: true;\n piiCleanGuaranteed?: false;\n};\n\nconst EXTERNAL_DETECTION_FAILURES = {\n batchRejected: {\n code: \"EXTERNAL_DETECTION_BATCH_REJECTED\",\n message: \"The external detection batch was rejected.\",\n },\n documentRejected: {\n code: \"EXTERNAL_DETECTION_DOCUMENT_REJECTED\",\n message: \"The external detection document was rejected.\",\n },\n inputRejected: {\n code: \"EXTERNAL_DETECTION_INPUT_REJECTED\",\n message: \"The external detection request paths were rejected.\",\n },\n operationFailed: {\n code: \"EXTERNAL_DETECTION_OPERATION_FAILED\",\n message: \"The external detection operation failed safely.\",\n },\n sessionRejected: {\n code: \"EXTERNAL_DETECTION_SESSION_REJECTED\",\n message: \"The external detection session was rejected.\",\n },\n} as const;\n\ntype ExternalDetectionFailure =\n (typeof EXTERNAL_DETECTION_FAILURES)[keyof typeof EXTERNAL_DETECTION_FAILURES];\n\nclass ExternalDetectionAuditError extends Error {\n readonly code: ExternalDetectionFailure[\"code\"];\n\n constructor(failure: ExternalDetectionFailure) {\n super(failure.message);\n this.name = \"ExternalDetectionAuditError\";\n this.code = failure.code;\n }\n}\n\nconst externalDetectionFailure = (\n error: unknown,\n failure: ExternalDetectionFailure,\n): ExternalDetectionAuditError =>\n error instanceof ExternalDetectionAuditError\n ? error\n : new ExternalDetectionAuditError(failure);\n\nconst externalDetectionStep = async <Result>(\n failure: ExternalDetectionFailure,\n operation: () => Result | Promise<Result>,\n): Promise<Result> => {\n try {\n return await operation();\n } catch (error) {\n throw externalDetectionFailure(error, failure);\n }\n};\n\nexport type LocalAnonymizeServiceFaults = {\n beforeOutputPublish?: () => void;\n};\n\nconst inside = (root: string, target: string): boolean => {\n const path = relative(root, target);\n return (\n path === \"\" ||\n (path !== \"..\" && !path.startsWith(`..${sep}`) && !isAbsolute(path))\n );\n};\n\ntype ReadInputOptions = {\n path: string;\n extension: \".docx\" | \".json\" | \".pdf\" | \".txt\";\n maximumBytes: number;\n label: \"DOCX\" | \"External detection batch\" | \"PDF\" | \"Text\";\n};\n\ntype ScopedInput = {\n bytes: Uint8Array;\n path: string;\n};\n\ntype ReadableFileHandle = Pick<Awaited<ReturnType<typeof open>>, \"read\">;\n\nconst readHandleBounded = async (\n handle: ReadableFileHandle,\n maximumBytes: number,\n label: \"DOCX\" | \"External detection batch\" | \"PDF\" | \"Text\",\n): Promise<Uint8Array> => {\n const chunks: Uint8Array[] = [];\n let total = 0;\n for (;;) {\n const chunk = Buffer.allocUnsafe(\n Math.min(READ_CHUNK_BYTES, maximumBytes - total + 1),\n );\n const { bytesRead } = await handle.read(chunk, 0, chunk.byteLength, null);\n if (bytesRead === 0) {\n return Buffer.concat(chunks, total);\n }\n total += bytesRead;\n if (total > maximumBytes) {\n throw new Error(`${label} inputs must not exceed ${maximumBytes} bytes`);\n }\n chunks.push(chunk.subarray(0, bytesRead));\n }\n};\n\ntype DirectoryIdentity = {\n dev: number;\n ino: number;\n path: string;\n};\n\ntype FileIdentity = Pick<DirectoryIdentity, \"dev\" | \"ino\">;\n\nclass ScopedOutput {\n readonly parent: DirectoryIdentity;\n readonly path: string;\n\n constructor(path: string, parent: DirectoryIdentity) {\n this.path = path;\n this.parent = parent;\n }\n\n async write(bytes: Uint8Array | string): Promise<void> {\n await safeWrite(this, bytes);\n }\n}\n\nexport class PathScope {\n readonly #roots: readonly string[];\n\n private constructor(roots: readonly string[]) {\n this.#roots = roots;\n }\n\n static async create(roots: readonly string[]): Promise<PathScope> {\n if (roots.length === 0) {\n throw new Error(\"At least one --root directory is required\");\n }\n const canonical = await Promise.all(\n roots.map(async (root) => {\n if (!isAbsolute(root)) {\n throw new Error(\"MCP roots must be absolute paths\");\n }\n const path = await realpath(root);\n const metadata = await stat(path);\n if (!metadata.isDirectory()) {\n throw new Error(\"Every MCP root must be a directory\");\n }\n return path;\n }),\n );\n return new PathScope([...new Set(canonical)]);\n }\n\n async readInput({\n path,\n extension,\n maximumBytes,\n label,\n }: ReadInputOptions): Promise<ScopedInput> {\n if (!isAbsolute(path) || extname(path).toLowerCase() !== extension) {\n throw new Error(`Input must be an absolute ${extension} path`);\n }\n const initiallyCanonical = await realpath(path);\n if (!this.#roots.some((root) => inside(root, initiallyCanonical))) {\n throw new Error(\"Input is outside the configured roots\");\n }\n const initiallyRequestedMetadata = await lstat(path);\n if (!initiallyRequestedMetadata.isFile()) {\n throw new Error(\"Input must be a regular file\");\n }\n const handle = await open(\n path,\n fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | fsConstants.O_NONBLOCK,\n );\n try {\n const openedMetadata = await handle.stat();\n if (!openedMetadata.isFile()) {\n throw new Error(\"Input must be a regular file\");\n }\n const canonical = await realpath(path);\n if (!this.#roots.some((root) => inside(root, canonical))) {\n throw new Error(\"Input is outside the configured roots\");\n }\n const currentMetadata = await stat(canonical);\n if (\n currentMetadata.dev !== openedMetadata.dev ||\n currentMetadata.ino !== openedMetadata.ino\n ) {\n throw new Error(\"Input changed while it was being validated\");\n }\n if (openedMetadata.size > maximumBytes) {\n throw new Error(\n `${label} inputs must not exceed ${maximumBytes} bytes`,\n );\n }\n const bytes = await readHandleBounded(handle, maximumBytes, label);\n return { bytes, path: canonical };\n } finally {\n await handle.close();\n }\n }\n\n async output(\n path: string,\n extension: \".docx\" | \".pdf\" | \".txt\",\n ): Promise<ScopedOutput> {\n if (!isAbsolute(path) || extname(path).toLowerCase() !== extension) {\n throw new Error(`Output must be an absolute ${extension} path`);\n }\n const normalized = resolve(path);\n const parent = await realpath(dirname(normalized));\n if (!this.#roots.some((root) => inside(root, parent))) {\n throw new Error(\"Output is outside the configured roots\");\n }\n const parentMetadata = await stat(parent);\n try {\n await lstat(normalized);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return new ScopedOutput(normalized, {\n dev: parentMetadata.dev,\n ino: parentMetadata.ino,\n path: parent,\n });\n }\n throw new Error(\"Output availability could not be verified\", {\n cause: error,\n });\n }\n throw new Error(\"Output already exists; overwriting is not supported\");\n }\n}\n\ntype SessionEntry = {\n language: string | undefined;\n session: RedactionSession;\n restoreSession: (plaintextJson: string) => RedactionSession;\n status: \"busy\" | \"initializing\" | \"ready\";\n};\n\ntype SessionLease = {\n entry: SessionEntry;\n sessionId: string;\n rollback:\n | { type: \"delete\" }\n | { type: \"release\" }\n | { type: \"restore\"; checkpoint: string };\n};\n\ntype RedactionSession = DocxAnonymizationSession &\n DocxRestorationSession & {\n redact_text(text: string): {\n redaction: { redactedText: string; entityCount: number };\n };\n restoreText(text: string): string;\n toPlaintextJson(): string;\n toEncryptedArchiveAt(\n key: Uint8Array,\n observedAtEpochSeconds: number,\n ): Uint8Array;\n };\n\ntype NativeNodeSurface = {\n convert_external_detection_batch: (\n document: Uint8Array,\n batch: string,\n ) => NativeCallerDetection[];\n getDefaultNativePipeline: (options: { language?: string }) => {\n createRedactionSession: (sessionId: string) => RedactionSession;\n restoreRedactionSession: (plaintextJson: string) => RedactionSession;\n restoreEncryptedRedactionSession: (options: {\n archive: Uint8Array;\n expectedSessionId: string;\n key: Uint8Array;\n observedAtEpochSeconds: number;\n }) => RedactionSession;\n } & Pick<\n PreparedNativePipeline,\n \"redactText\" | \"redactTextWithCallerDetections\"\n >;\n native_package_version: () => string;\n};\n\nconst nativeNodeSurface = nativeNode as unknown as NativeNodeSurface; // SAFETY: These native-node runtime exports are public, but their generated declarations are minified during workspace builds.\n\nconst assertOutputParent = async ({\n parent,\n path,\n}: ScopedOutput): Promise<void> => {\n const canonical = await realpath(dirname(path));\n const metadata = await stat(canonical);\n if (\n canonical !== parent.path ||\n metadata.dev !== parent.dev ||\n metadata.ino !== parent.ino\n ) {\n throw new Error(\"Output directory changed while it was being used\");\n }\n};\n\nconst sameFile = (left: FileIdentity, right: FileIdentity): boolean =>\n left.dev === right.dev && left.ino === right.ino;\n\nconst safeWrite = async (\n output: ScopedOutput,\n bytes: Uint8Array | string,\n): Promise<void> => {\n await assertOutputParent(output);\n const temporary = `${output.path}.stella-${randomUUID()}.tmp`;\n let published = false;\n let committed = false;\n let temporaryMetadata: FileIdentity | undefined;\n const handle = await open(\n temporary,\n fsConstants.O_WRONLY |\n fsConstants.O_CREAT |\n fsConstants.O_EXCL |\n fsConstants.O_NOFOLLOW,\n 0o400,\n );\n try {\n temporaryMetadata = await handle.stat();\n const canonicalTemporary = await realpath(temporary);\n const currentTemporaryMetadata = await lstat(temporary);\n if (\n dirname(canonicalTemporary) !== output.parent.path ||\n !currentTemporaryMetadata.isFile() ||\n !sameFile(temporaryMetadata, currentTemporaryMetadata)\n ) {\n throw new Error(\"Output staging file changed while it was being used\");\n }\n await handle.writeFile(bytes);\n await handle.sync();\n await assertOutputParent(output);\n const stagedMetadata = await lstat(temporary);\n if (\n !stagedMetadata.isFile() ||\n !sameFile(temporaryMetadata, stagedMetadata)\n ) {\n throw new Error(\"Output staging file changed before publication\");\n }\n await link(temporary, output.path);\n published = true;\n const publishedMetadata = await lstat(output.path);\n if (\n !publishedMetadata.isFile() ||\n !sameFile(temporaryMetadata, publishedMetadata)\n ) {\n throw new Error(\"Published output does not match the staged file\");\n }\n await assertOutputParent(output);\n await handle.chmod(0o600);\n await handle.sync();\n committed = true;\n } finally {\n if (!committed) {\n await handle.truncate(0).catch(() => undefined);\n await handle.sync().catch(() => undefined);\n await handle.chmod(0o000).catch(() => undefined);\n }\n await handle.close().catch(() => undefined);\n await unlink(temporary).catch(() => undefined);\n if (published && !committed && temporaryMetadata !== undefined) {\n const publishedMetadata = await lstat(output.path).catch(() => undefined);\n if (\n publishedMetadata !== undefined &&\n sameFile(temporaryMetadata, publishedMetadata)\n ) {\n await unlink(output.path).catch(() => undefined);\n }\n }\n }\n};\n\nconst decodeText = (bytes: Uint8Array): string => {\n return decodeUtf8(bytes, \"Text inputs\");\n};\n\nconst decodeUtf8 = (bytes: Uint8Array, label: string): string => {\n try {\n return new TextDecoder(\"utf-8\", { fatal: true }).decode(bytes);\n } catch (error) {\n throw new Error(`${label} must contain valid UTF-8`, { cause: error });\n }\n};\n\nconst applyTextReplacements = (\n text: string,\n replacements: readonly NativeTextReplacement[],\n): string => {\n const parts: string[] = [];\n let cursor = 0;\n for (const replacement of replacements) {\n if (\n !Number.isSafeInteger(replacement.start) ||\n !Number.isSafeInteger(replacement.end) ||\n replacement.start < cursor ||\n replacement.end <= replacement.start ||\n replacement.end > text.length ||\n !isUtf16Boundary(text, replacement.start) ||\n !isUtf16Boundary(text, replacement.end)\n ) {\n throw new Error(\n \"Native caller-detection plan returned invalid replacements\",\n );\n }\n parts.push(text.slice(cursor, replacement.start), replacement.replacement);\n cursor = replacement.end;\n }\n parts.push(text.slice(cursor));\n return parts.join(\"\");\n};\n\nconst isUtf16Boundary = (text: string, offset: number): boolean => {\n if (offset <= 0 || offset >= text.length) {\n return true;\n }\n const before = text.charCodeAt(offset - 1);\n const after = text.charCodeAt(offset);\n return !(\n before >= 0xd800 &&\n before <= 0xdbff &&\n after >= 0xdc00 &&\n after <= 0xdfff\n );\n};\n\nconst assertDifferentPaths = (input: string, output: string): void => {\n if (input === output) {\n throw new Error(\"Input and output paths must differ\");\n }\n};\n\nconst textInput = z.object({\n inputPath: z.string().min(1).max(PATH_MAX_CHARACTERS),\n outputPath: z.string().min(1).max(PATH_MAX_CHARACTERS),\n sessionId: z.string().regex(SESSION_ID),\n language: z.string().min(2).max(35).optional(),\n});\n\nconst externalDetectionTextInput = textInput.extend({\n detectionBatchPath: z.string().min(1).max(PATH_MAX_CHARACTERS),\n});\n\nconst restoreInput = z.object({\n inputPath: z.string().min(1).max(PATH_MAX_CHARACTERS),\n outputPath: z.string().min(1).max(PATH_MAX_CHARACTERS),\n sessionId: z.string().regex(SESSION_ID),\n});\n\nconst docxRestoreInput = restoreInput.extend({\n allowPartialCoverage: z.boolean().optional().default(false),\n});\n\nconst docxInput = textInput.extend({\n allowPartialCoverage: z.boolean().optional().default(false),\n});\n\nconst pdfInput = z.object({\n inputPath: z.string().min(1).max(PATH_MAX_CHARACTERS),\n outputPath: z.string().min(1).max(PATH_MAX_CHARACTERS),\n ocrLanguage: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9_.-]*$/u),\n detectionLanguage: z.string().min(2).max(35).optional(),\n dpi: z.number().int().min(72).max(600).optional().default(300),\n timeoutMs: z.number().int().min(100).max(300_000).optional().default(120_000),\n fillRgb: z\n .tuple([\n z.number().int().min(0).max(255),\n z.number().int().min(0).max(255),\n z.number().int().min(0).max(255),\n ])\n .optional()\n .default([0, 0, 0]),\n});\n\nexport type LocalPdfProviderConfiguration = {\n pdftoppmPath?: string | undefined;\n tesseractPath?: string | undefined;\n};\n\nexport type LocalAnonymizeServiceOptions = {\n durableSessions?: DurableSessionStore | undefined;\n faults?: LocalAnonymizeServiceFaults | undefined;\n pdfProvider?: LocalPdfProviderConfiguration | undefined;\n};\n\nexport class LocalAnonymizeService {\n #activeOperations = 0;\n #closePromise: Promise<void> | undefined;\n #operationsDrained: (() => void) | undefined;\n readonly #scope: PathScope;\n #pdfOperationTail: Promise<void> = Promise.resolve();\n readonly #sessionInitializations = new Set<string>();\n readonly #sessions = new Map<string, SessionEntry>();\n #state: \"closed\" | \"closing\" | \"open\" = \"open\";\n readonly #durableSessions: DurableSessionStore | undefined;\n readonly #faults: LocalAnonymizeServiceFaults;\n readonly #pdfProvider: LocalPdfProviderConfiguration;\n\n constructor(scope: PathScope, options: LocalAnonymizeServiceOptions = {}) {\n if (options instanceof DurableSessionStore) {\n throw new TypeError(\n \"LocalAnonymizeService requires { durableSessions } as its second argument\",\n );\n }\n const { durableSessions, faults = {}, pdfProvider = {} } = options;\n this.#scope = scope;\n this.#durableSessions = durableSessions;\n this.#faults = faults;\n this.#pdfProvider = pdfProvider;\n }\n\n get sessionMode(): McpSessionMode {\n return this.#durableSessions === undefined\n ? MCP_SESSION_MODES.memory\n : MCP_SESSION_MODES.durableEncrypted;\n }\n\n async close(): Promise<void> {\n if (this.#closePromise !== undefined) {\n return this.#closePromise;\n }\n this.#state = \"closing\";\n this.#closePromise = (async () => {\n if (this.#activeOperations > 0) {\n await new Promise<void>((resolvePromise) => {\n this.#operationsDrained = resolvePromise;\n });\n }\n this.#sessions.clear();\n await this.#durableSessions?.close();\n this.#state = \"closed\";\n })();\n return this.#closePromise;\n }\n\n async #runOperation<Result>(\n operation: () => Promise<Result>,\n ): Promise<Result> {\n if (this.#state !== \"open\") {\n throw new Error(\"MCP anonymize service is closing or closed\");\n }\n this.#activeOperations += 1;\n try {\n return await operation();\n } finally {\n this.#activeOperations -= 1;\n if (this.#activeOperations === 0) {\n this.#operationsDrained?.();\n this.#operationsDrained = undefined;\n }\n }\n }\n\n async #session(sessionId: string, language?: string): Promise<SessionLease> {\n if (this.#durableSessions !== undefined && language !== undefined) {\n throw new Error(\n \"Durable sessions use the full all-language pipeline; omit language\",\n );\n }\n const existing = this.#sessions.get(sessionId);\n if (existing !== undefined) {\n if (language !== undefined && existing.language !== language) {\n throw new Error(\"A session cannot change language\");\n }\n if (existing.status === \"initializing\") {\n throw new Error(\"The requested session is still initializing\");\n }\n if (existing.status === \"busy\") {\n throw new Error(\"The requested session is handling another operation\");\n }\n const checkpoint = existing.session.toPlaintextJson();\n existing.status = \"busy\";\n return {\n entry: existing,\n sessionId,\n rollback: { type: \"restore\", checkpoint },\n };\n }\n if (this.#sessionInitializations.has(sessionId)) {\n throw new Error(\"The requested session is still initializing\");\n }\n this.#sessionInitializations.add(sessionId);\n try {\n if (this.#sessions.size >= SESSION_MAX_COUNT) {\n throw new Error(`MCP sessions must not exceed ${SESSION_MAX_COUNT}`);\n }\n const pipeline = nativeNodeSurface.getDefaultNativePipeline(\n language === undefined ? {} : { language },\n );\n const durableSessions = this.#durableSessions;\n const stored =\n durableSessions === undefined\n ? undefined\n : await durableSessions.load(sessionId);\n let session: RedactionSession;\n if (stored === undefined) {\n session = pipeline.createRedactionSession(sessionId);\n } else {\n if (durableSessions === undefined) {\n throw new Error(\"Durable session storage is unavailable\");\n }\n try {\n session = durableSessions.restore({\n archive: stored.bytes,\n expectedSessionId: sessionId,\n observedAtEpochSeconds: observedAtEpochSeconds(),\n restorer: pipeline,\n });\n } catch (error) {\n throw new Error(\"The requested durable session is unavailable\", {\n cause: error,\n });\n }\n }\n const entry: SessionEntry = {\n language,\n session,\n restoreSession: (plaintextJson) =>\n pipeline.restoreRedactionSession(plaintextJson),\n status: \"initializing\",\n };\n this.#sessions.set(sessionId, entry);\n return {\n entry,\n sessionId,\n rollback:\n stored === undefined\n ? { type: \"delete\" }\n : { type: \"restore\", checkpoint: session.toPlaintextJson() },\n };\n } finally {\n this.#sessionInitializations.delete(sessionId);\n }\n }\n\n #commitSession({ entry }: SessionLease): void {\n entry.status = \"ready\";\n }\n\n async #rollbackSession({\n entry,\n rollback,\n sessionId,\n }: SessionLease): Promise<void> {\n if (this.#sessions.get(sessionId) !== entry) {\n return;\n }\n if (rollback.type === \"delete\") {\n this.#sessions.delete(sessionId);\n await this.#durableSessions?.delete(sessionId).catch(() => undefined);\n return;\n }\n if (rollback.type === \"release\") {\n entry.status = \"ready\";\n return;\n }\n try {\n entry.session = entry.restoreSession(rollback.checkpoint);\n entry.status = \"ready\";\n await this.#persistSession(sessionId, entry.session);\n } catch {\n this.#sessions.delete(sessionId);\n }\n }\n\n async #readSession(sessionId: string): Promise<SessionLease> {\n let entry = this.#sessions.get(sessionId);\n if (entry === undefined && this.#durableSessions !== undefined) {\n if (this.#sessionInitializations.has(sessionId)) {\n throw new Error(\"The requested session is still initializing\");\n }\n this.#sessionInitializations.add(sessionId);\n try {\n const pipeline = nativeNodeSurface.getDefaultNativePipeline({});\n const stored = await this.#durableSessions.load(sessionId);\n if (stored !== undefined) {\n try {\n const session = this.#durableSessions.restore({\n archive: stored.bytes,\n expectedSessionId: sessionId,\n observedAtEpochSeconds: observedAtEpochSeconds(),\n restorer: pipeline,\n });\n entry = {\n language: undefined,\n session,\n restoreSession: (plaintextJson) =>\n pipeline.restoreRedactionSession(plaintextJson),\n status: \"ready\",\n };\n this.#sessions.set(sessionId, entry);\n } catch (error) {\n throw new Error(\"The requested durable session is unavailable\", {\n cause: error,\n });\n }\n }\n } finally {\n this.#sessionInitializations.delete(sessionId);\n }\n }\n if (entry === undefined) {\n throw new Error(\"The requested session is unavailable\");\n }\n if (entry.status !== \"ready\") {\n throw new Error(\"The requested in-memory session is unavailable\");\n }\n entry.status = \"busy\";\n return { entry, sessionId, rollback: { type: \"release\" } };\n }\n\n async #persistSession(\n sessionId: string,\n session: RedactionSession,\n ): Promise<void> {\n if (this.#durableSessions === undefined) {\n return;\n }\n const archive = this.#durableSessions.seal(\n session,\n observedAtEpochSeconds(),\n );\n await this.#durableSessions.save(sessionId, archive);\n }\n\n async anonymizeText(\n input: z.infer<typeof textInput>,\n ): Promise<AuditSafeResult> {\n return this.#runOperation(() => this.#anonymizeText(input));\n }\n\n async anonymizePdf(\n input: z.infer<typeof pdfInput>,\n ): Promise<AuditSafeResult> {\n return this.#runOperation(() =>\n this.#serializePdfOperation(() => this.#anonymizePdf(input)),\n );\n }\n\n async #serializePdfOperation<Result>(\n operation: () => Promise<Result>,\n ): Promise<Result> {\n const previous = this.#pdfOperationTail;\n let release = (): void => undefined;\n this.#pdfOperationTail = new Promise<void>((resolvePromise) => {\n release = resolvePromise;\n });\n await previous;\n try {\n return await operation();\n } finally {\n release();\n }\n }\n\n async #anonymizePdf(\n input: z.infer<typeof pdfInput>,\n ): Promise<AuditSafeResult> {\n const source = await this.#scope.readInput({\n path: input.inputPath,\n extension: \".pdf\",\n maximumBytes: PDF_DOCUMENT_MAX_BYTES,\n label: \"PDF\",\n });\n const destination = await this.#scope.output(input.outputPath, \".pdf\");\n assertDifferentPaths(source.path, destination.path);\n const pipeline = nativeNodeSurface.getDefaultNativePipeline(\n input.detectionLanguage === undefined\n ? {}\n : { language: input.detectionLanguage },\n );\n const observed = await renderPdfWithPopplerTesseract({\n document: source.bytes,\n ocrLanguage: input.ocrLanguage,\n dpi: input.dpi,\n timeoutMs: input.timeoutMs,\n ...this.#pdfProvider,\n });\n const anonymized = anonymizePdfRaster({\n document: source.bytes,\n pipeline,\n provider: observed.provider,\n pages: observed.pages,\n fillRgb: input.fillRgb,\n });\n if (\n anonymized.certificate.structurePixelRewriteVerified !== true ||\n anonymized.certificate.piiCleanGuaranteed !== false\n ) {\n throw new Error(\"PDF raster verification did not satisfy MCP policy\");\n }\n this.#faults.beforeOutputPublish?.();\n await destination.write(anonymized.document);\n return {\n operation: \"anonymize\",\n format: \"pdf\",\n outputCreated: true,\n pageCount: anonymized.certificate.pageCount,\n entityCount: anonymized.certificate.detectionCount,\n mappedRegionCount: anonymized.certificate.mappedRegionCount,\n structurePixelRewriteVerified: true,\n piiCleanGuaranteed: false,\n };\n }\n\n async #anonymizeText(\n input: z.infer<typeof textInput>,\n ): Promise<AuditSafeResult> {\n const source = await this.#scope.readInput({\n path: input.inputPath,\n extension: \".txt\",\n maximumBytes: TEXT_MAX_BYTES,\n label: \"Text\",\n });\n const destination = await this.#scope.output(input.outputPath, \".txt\");\n assertDifferentPaths(source.path, destination.path);\n const text = decodeText(source.bytes);\n const lease = await this.#session(input.sessionId, input.language);\n try {\n const result = lease.entry.session.redact_text(text);\n await this.#persistSession(input.sessionId, lease.entry.session);\n this.#faults.beforeOutputPublish?.();\n await destination.write(result.redaction.redactedText);\n this.#commitSession(lease);\n return {\n operation: \"anonymize\",\n format: \"text\",\n outputCreated: true,\n sessionId: input.sessionId,\n entityCount: result.redaction.entityCount,\n };\n } catch (error) {\n await this.#rollbackSession(lease);\n throw error;\n }\n }\n\n async restoreText(\n input: z.infer<typeof restoreInput>,\n ): Promise<AuditSafeResult> {\n return this.#runOperation(() => this.#restoreText(input));\n }\n\n async anonymizeTextWithExternalDetections(\n input: z.infer<typeof externalDetectionTextInput>,\n ): Promise<AuditSafeResult> {\n try {\n return await this.#runOperation(() =>\n this.#anonymizeTextWithExternalDetections(input),\n );\n } catch (error) {\n throw externalDetectionFailure(\n error,\n EXTERNAL_DETECTION_FAILURES.operationFailed,\n );\n }\n }\n\n async #anonymizeTextWithExternalDetections(\n input: z.infer<typeof externalDetectionTextInput>,\n ): Promise<AuditSafeResult> {\n const { batch, destination, source } = await externalDetectionStep(\n EXTERNAL_DETECTION_FAILURES.inputRejected,\n async () => {\n const scopedSource = await this.#scope.readInput({\n path: input.inputPath,\n extension: \".txt\",\n maximumBytes: TEXT_MAX_BYTES,\n label: \"Text\",\n });\n const scopedBatch = await this.#scope.readInput({\n path: input.detectionBatchPath,\n extension: \".json\",\n maximumBytes: EXTERNAL_DETECTION_BATCH_MAX_BYTES,\n label: \"External detection batch\",\n });\n const scopedDestination = await this.#scope.output(\n input.outputPath,\n \".txt\",\n );\n assertDifferentPaths(scopedSource.path, scopedDestination.path);\n assertDifferentPaths(scopedBatch.path, scopedDestination.path);\n assertDifferentPaths(scopedSource.path, scopedBatch.path);\n return {\n batch: scopedBatch,\n destination: scopedDestination,\n source: scopedSource,\n };\n },\n );\n const text = await externalDetectionStep(\n EXTERNAL_DETECTION_FAILURES.documentRejected,\n () => decodeText(source.bytes),\n );\n const detections = await externalDetectionStep(\n EXTERNAL_DETECTION_FAILURES.batchRejected,\n () =>\n nativeNodeSurface.convert_external_detection_batch(\n source.bytes,\n decodeUtf8(batch.bytes, \"External detection batches\"),\n ),\n );\n const lease = await externalDetectionStep(\n EXTERNAL_DETECTION_FAILURES.sessionRejected,\n () => this.#session(input.sessionId, input.language),\n );\n try {\n const plan = lease.entry.session.planTextBatchWithCallerDetections({\n inputs: [{ fullText: text, detections }],\n });\n const block = plan.blocks.at(0);\n if (plan.blocks.length !== 1 || block === undefined) {\n throw new Error(\n \"Native caller-detection plan did not match the text input\",\n );\n }\n const redactedText = applyTextReplacements(text, block.replacements);\n plan.commit();\n await this.#persistSession(input.sessionId, lease.entry.session);\n this.#faults.beforeOutputPublish?.();\n await destination.write(redactedText);\n this.#commitSession(lease);\n return {\n operation: \"anonymize\",\n format: \"text\",\n outputCreated: true,\n sessionId: input.sessionId,\n entityCount: block.entityCount,\n externalDetectionBatchStatus: \"accepted\",\n externalDetectionCount: detections.length,\n retainedExternalDetectionCount: block.callerEntityCount,\n };\n } catch (error) {\n await this.#rollbackSession(lease);\n throw externalDetectionFailure(\n error,\n EXTERNAL_DETECTION_FAILURES.operationFailed,\n );\n }\n }\n\n async #restoreText(\n input: z.infer<typeof restoreInput>,\n ): Promise<AuditSafeResult> {\n const source = await this.#scope.readInput({\n path: input.inputPath,\n extension: \".txt\",\n maximumBytes: TEXT_MAX_BYTES,\n label: \"Text\",\n });\n const destination = await this.#scope.output(input.outputPath, \".txt\");\n assertDifferentPaths(source.path, destination.path);\n const text = decodeText(source.bytes);\n const lease = await this.#readSession(input.sessionId);\n try {\n const restored = lease.entry.session.restoreText(text);\n await destination.write(restored);\n this.#commitSession(lease);\n return {\n operation: \"restore\",\n format: \"text\",\n outputCreated: true,\n sessionId: input.sessionId,\n };\n } catch (error) {\n await this.#rollbackSession(lease);\n throw error;\n }\n }\n\n async anonymizeDocx(\n input: z.infer<typeof docxInput>,\n ): Promise<AuditSafeResult> {\n return this.#runOperation(() => this.#anonymizeDocx(input));\n }\n\n async #anonymizeDocx(\n input: z.infer<typeof docxInput>,\n ): Promise<AuditSafeResult> {\n const source = await this.#scope.readInput({\n path: input.inputPath,\n extension: \".docx\",\n maximumBytes: DOCX_ARCHIVE_MAX_BYTES,\n label: \"DOCX\",\n });\n const destination = await this.#scope.output(input.outputPath, \".docx\");\n assertDifferentPaths(source.path, destination.path);\n const lease = await this.#session(input.sessionId, input.language);\n try {\n const result = anonymizeDocx({\n document: source.bytes,\n session: lease.entry.session,\n expectedSessionId: input.sessionId,\n policy: {\n coverage: {\n mode: input.allowPartialCoverage\n ? DOCX_COVERAGE_MODES.allowPartial\n : DOCX_COVERAGE_MODES.requireFull,\n },\n },\n });\n await this.#persistSession(input.sessionId, lease.entry.session);\n this.#faults.beforeOutputPublish?.();\n await destination.write(result.document);\n this.#commitSession(lease);\n return {\n operation: \"anonymize\",\n format: \"docx\",\n outputCreated: true,\n sessionId: input.sessionId,\n entityCount: result.summary.entityCount,\n blockCount: result.summary.blockCount,\n rewrittenBlockCount: result.summary.rewrittenBlockCount,\n coverageStatus: result.summary.coverage.status,\n };\n } catch (error) {\n await this.#rollbackSession(lease);\n throw error;\n }\n }\n\n async restoreDocx(\n input: z.infer<typeof docxRestoreInput>,\n ): Promise<AuditSafeResult> {\n return this.#runOperation(() => this.#restoreDocx(input));\n }\n\n async #restoreDocx(\n input: z.infer<typeof docxRestoreInput>,\n ): Promise<AuditSafeResult> {\n const source = await this.#scope.readInput({\n path: input.inputPath,\n extension: \".docx\",\n maximumBytes: DOCX_ARCHIVE_MAX_BYTES,\n label: \"DOCX\",\n });\n const destination = await this.#scope.output(input.outputPath, \".docx\");\n assertDifferentPaths(source.path, destination.path);\n const lease = await this.#readSession(input.sessionId);\n try {\n const result = restoreDocxText({\n document: source.bytes,\n session: lease.entry.session,\n expectedSessionId: input.sessionId,\n });\n if (result.coverage.status === \"partial\" && !input.allowPartialCoverage) {\n throw new Error(\n \"DOCX restoration has partial coverage; set allowPartialCoverage to publish it\",\n );\n }\n await destination.write(result.document);\n this.#commitSession(lease);\n return {\n operation: \"restore\",\n format: \"docx\",\n outputCreated: true,\n sessionId: input.sessionId,\n rewrittenBlockCount: result.restoredBlockCount,\n restoredPlaceholderCount: result.restoredPlaceholderCount,\n coverageStatus: result.coverage.status,\n };\n } catch (error) {\n await this.#rollbackSession(lease);\n throw error;\n }\n }\n\n async inspectDocx(inputPath: string): Promise<AuditSafeResult> {\n return this.#runOperation(() => this.#inspectDocx(inputPath));\n }\n\n async #inspectDocx(inputPath: string): Promise<AuditSafeResult> {\n const source = await this.#scope.readInput({\n path: inputPath,\n extension: \".docx\",\n maximumBytes: DOCX_ARCHIVE_MAX_BYTES,\n label: \"DOCX\",\n });\n const extraction = extractDocxText(source.bytes);\n const unsupported = extraction.coverage.parts.some(\n (part) => part.status === \"unsupported\",\n );\n const structuralGap =\n extraction.coverage.hyperlinkTextSegmentCount > 0 ||\n extraction.coverage.revisionTextSegmentCount > 0 ||\n extraction.coverage.unsupportedAlternateContentCount > 0 ||\n extraction.coverage.unsupportedFieldInstructionCount > 0 ||\n extraction.coverage.unsupportedSymbolCount > 0;\n return {\n operation: \"inspect\",\n format: \"docx\",\n outputCreated: false,\n blockCount: extraction.blocks.length,\n coverageStatus: unsupported || structuralGap ? \"partial\" : \"full\",\n };\n }\n}\n\nconst result = (value: AuditSafeResult) => ({\n content: [{ type: \"text\" as const, text: JSON.stringify(value) }],\n structuredContent: { ...value },\n});\n\nconst MCP_TOOL_NAMES = [\n \"anonymize_docx_file\",\n \"anonymize_pdf_file\",\n \"anonymize_text_file\",\n \"anonymize_text_file_with_external_detections\",\n \"capabilities\",\n \"inspect_docx_file\",\n \"restore_docx_file\",\n \"restore_text_file\",\n] as const;\n\nconst capabilitiesResult = (service: LocalAnonymizeService) => {\n const value = {\n capabilityManifest: CAPABILITY_MANIFEST,\n runtimeVersion: nativeNodeSurface.native_package_version(),\n mcp: {\n externalDetectionBatch: {\n ingestion: \"path-only\" as const,\n version: EXTERNAL_DETECTION_BATCH_VERSION,\n },\n formats: [\"docx\", \"pdf\", \"text\"] as const,\n sessionMode: service.sessionMode,\n tools: MCP_TOOL_NAMES,\n transport: \"stdio\" as const,\n },\n };\n return {\n content: [{ type: \"text\" as const, text: JSON.stringify(value) }],\n structuredContent: value,\n };\n};\n\nconst externalDetectionErrorResult = (error: unknown) => {\n const failure = externalDetectionFailure(\n error,\n EXTERNAL_DETECTION_FAILURES.operationFailed,\n );\n const value = { errorCode: failure.code, message: failure.message };\n return {\n isError: true,\n content: [{ type: \"text\" as const, text: JSON.stringify(value) }],\n structuredContent: value,\n };\n};\n\nexport const createAnonymizeMcpServer = (\n service: LocalAnonymizeService,\n): McpServer => {\n const server = new McpServer(\n {\n name: \"stella-anonymize-local\",\n version: nativeNodeSurface.native_package_version(),\n },\n {\n instructions:\n \"All tools accept local paths only. Never request or return document contents or session mappings. Outputs must be new explicit paths inside configured roots.\",\n },\n );\n server.registerTool(\n \"capabilities\",\n {\n description:\n \"Return the public runtime capability manifest and MCP surface metadata.\",\n inputSchema: z.object({}),\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n },\n },\n async () => capabilitiesResult(service),\n );\n server.registerTool(\n \"anonymize_text_file\",\n {\n description: \"Anonymize a local UTF-8 text file into a new local file.\",\n inputSchema: textInput,\n annotations: { destructiveHint: false, idempotentHint: false },\n },\n async (input) => result(await service.anonymizeText(input)),\n );\n server.registerTool(\n \"restore_text_file\",\n {\n description: \"Restore a text file using the configured session store.\",\n inputSchema: restoreInput,\n annotations: { destructiveHint: false, idempotentHint: false },\n },\n async (input) => result(await service.restoreText(input)),\n );\n server.registerTool(\n \"anonymize_text_file_with_external_detections\",\n {\n description:\n \"Anonymize a local UTF-8 text file with a provider-neutral ExternalDetectionBatch v1 JSON sidecar into a new local file.\",\n inputSchema: externalDetectionTextInput,\n annotations: { destructiveHint: false, idempotentHint: false },\n },\n async (input) => {\n try {\n return result(await service.anonymizeTextWithExternalDetections(input));\n } catch (error) {\n return externalDetectionErrorResult(error);\n }\n },\n );\n server.registerTool(\n \"anonymize_docx_file\",\n {\n description:\n \"Structure-preservingly anonymize a local DOCX into a new local DOCX.\",\n inputSchema: docxInput,\n annotations: { destructiveHint: false, idempotentHint: false },\n },\n async (input) => result(await service.anonymizeDocx(input)),\n );\n server.registerTool(\n \"anonymize_pdf_file\",\n {\n description:\n \"Destructively raster-anonymize a local PDF into a fresh image-only PDF. Returns aggregate verification only; it does not claim perfect OCR or detector recall.\",\n inputSchema: pdfInput,\n annotations: { destructiveHint: false, idempotentHint: false },\n },\n async (input) => result(await service.anonymizePdf(input)),\n );\n server.registerTool(\n \"restore_docx_file\",\n {\n description: \"Restore a DOCX using the configured session store.\",\n inputSchema: docxRestoreInput,\n annotations: { destructiveHint: false, idempotentHint: false },\n },\n async (input) => result(await service.restoreDocx(input)),\n );\n server.registerTool(\n \"inspect_docx_file\",\n {\n description:\n \"Return only aggregate DOCX coverage and block counts; never document text.\",\n inputSchema: z.object({\n inputPath: z.string().min(1).max(PATH_MAX_CHARACTERS),\n }),\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n },\n },\n async ({ inputPath }) => result(await service.inspectDocx(inputPath)),\n );\n return server;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"local.mjs","names":["SESSION_ID","READ_CHUNK_BYTES","sameFile","fsConstants","readHandleBounded","#directory","#faultInjector","#key","#lockHandle","#validateInventory","#syncDirectory","#assertOpen","#closePromise","#state","#mutationTail","#withMutation","#load","#path","#assertDirectory","#save","#inject","#delete","#roots","fsConstants","#scope","#sessionInitializations","#sessions","#durableSessions","#faults","#pdfProvider","#closePromise","#state","#activeOperations","#operationsDrained","#runOperation","#session","#rollbackSession","#persistSession","#readSession","#anonymizeText","#serializePdfOperation","#anonymizePdf","#pdfOperationTail","#commitSession","#restoreText","#anonymizeTextWithExternalDetections","#anonymizeDocx","#restoreDocx","#inspectDocx"],"sources":["../src/durable-sessions.ts","../src/local.ts"],"sourcesContent":["import { createHash, randomUUID } from \"node:crypto\";\nimport {\n constants as fsConstants,\n lstat,\n open,\n readdir,\n realpath,\n rename,\n stat,\n unlink,\n} from \"node:fs/promises\";\nimport type { FileHandle } from \"node:fs/promises\";\nimport { isAbsolute, join, resolve } from \"node:path\";\n\nexport const SESSION_ARCHIVE_KEY_BYTES = 32;\nexport const SESSION_ARCHIVE_MAX_BYTES = 16 * 1024 * 1024 + 57;\nexport const SESSION_ARCHIVE_MAX_COUNT = 256;\nexport const SESSION_ARCHIVE_TOTAL_MAX_BYTES = 256 * 1024 * 1024;\n\nconst ARCHIVE_NAME = /^[a-f0-9]{64}\\.stlasess$/u;\nconst SESSION_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/u;\nconst STAGING_NAME =\n /^[a-f0-9]{64}\\.stlasess\\.tmp\\.[0-9a-f]{8}-[0-9a-f-]{27}$/u;\nconst READ_CHUNK_BYTES = 64 * 1024;\nconst LOCK_FILE_NAME = \".stella-session.lock\";\n\nexport const DURABLE_SESSION_FAULT_POINTS = {\n beforeDirectoryFsync: \"before-directory-fsync\",\n beforeRename: \"before-rename\",\n beforeStagingFsync: \"before-staging-fsync\",\n beforeStagingWrite: \"before-staging-write\",\n} as const;\n\nexport type DurableSessionFaultPoint =\n (typeof DURABLE_SESSION_FAULT_POINTS)[keyof typeof DURABLE_SESSION_FAULT_POINTS];\n\ntype FileIdentity = {\n dev: number;\n ino: number;\n};\n\ntype DirectoryIdentity = FileIdentity & {\n path: string;\n};\n\ntype SessionInventory = {\n archiveCount: number;\n totalBytes: number;\n};\n\nconst sameFile = (left: FileIdentity, right: FileIdentity): boolean =>\n left.dev === right.dev && left.ino === right.ino;\n\nconst assertOwner = (uid: number, label: string): void => {\n if (typeof process.getuid === \"function\" && uid !== process.getuid()) {\n throw new Error(`${label} must be owned by the current user`);\n }\n};\n\nconst assertPrivateMode = (mode: number, label: string): void => {\n if ((mode & 0o077) !== 0) {\n throw new Error(`${label} must not grant group or other permissions`);\n }\n};\n\nconst assertPosixDurabilitySupport = (): void => {\n if (\n (process.platform !== \"darwin\" && process.platform !== \"linux\") ||\n typeof process.getuid !== \"function\" ||\n typeof fsConstants.O_NOFOLLOW !== \"number\" ||\n fsConstants.O_NOFOLLOW === 0 ||\n typeof fsConstants.O_DIRECTORY !== \"number\" ||\n fsConstants.O_DIRECTORY === 0\n ) {\n throw new Error(\n \"Encrypted durable MCP sessions require supported POSIX owner, nofollow, directory-fsync, and advisory-lock semantics on macOS or Linux\",\n );\n }\n};\n\nconst acquireDirectoryLock = async (\n directoryPath: string,\n): Promise<FileHandle> => {\n const path = join(directoryPath, LOCK_FILE_NAME);\n const handle = await open(\n path,\n fsConstants.O_RDWR |\n fsConstants.O_CREAT |\n fsConstants.O_NOFOLLOW |\n fsConstants.O_NONBLOCK,\n 0o600,\n );\n try {\n const metadata = await handle.stat();\n if (!metadata.isFile()) {\n throw new Error(\"MCP session lock must be a regular file\");\n }\n assertOwner(metadata.uid, \"MCP session lock\");\n assertPrivateMode(metadata.mode, \"MCP session lock\");\n // Loaded lazily because Bun is used as a repository test/build tool but\n // cannot safely load this Node native addon. The shipped MCP runtime is\n // Node; Node integration tests exercise this path.\n const { tryLock } = await import(\"fs-native-extensions\");\n if (!tryLock(handle.fd)) {\n throw new Error(\n \"MCP session directory is already locked by another server\",\n );\n }\n return handle;\n } catch (error) {\n await handle.close().catch(() => undefined);\n throw error;\n }\n};\n\nconst canonicalAbsolutePath = async (\n path: string,\n label: string,\n): Promise<string> => {\n if (!isAbsolute(path)) {\n throw new Error(`${label} must be an absolute path`);\n }\n const normalized = resolve(path);\n const canonical = await realpath(normalized);\n if (canonical !== normalized) {\n throw new Error(`${label} must not contain symbolic links`);\n }\n return canonical;\n};\n\nconst readHandleBounded = async (\n handle: Pick<Awaited<ReturnType<typeof open>>, \"read\">,\n maximumBytes: number,\n): Promise<Uint8Array> => {\n const chunks: Uint8Array[] = [];\n let total = 0;\n for (;;) {\n const remaining = maximumBytes - total;\n const chunk = Buffer.allocUnsafe(Math.min(READ_CHUNK_BYTES, remaining + 1));\n const { bytesRead } = await handle.read(chunk, 0, chunk.byteLength, null);\n if (bytesRead === 0) {\n return Buffer.concat(chunks, total);\n }\n total += bytesRead;\n if (total > maximumBytes) {\n throw new Error(\"Encrypted session archive exceeds the byte limit\");\n }\n chunks.push(chunk.subarray(0, bytesRead));\n }\n};\n\nconst readKey = async (path: string): Promise<Uint8Array> => {\n const canonical = await canonicalAbsolutePath(path, \"MCP session key file\");\n const linkedMetadata = await lstat(canonical);\n if (!linkedMetadata.isFile() || linkedMetadata.isSymbolicLink()) {\n throw new Error(\"MCP session key file must be a regular file\");\n }\n assertOwner(linkedMetadata.uid, \"MCP session key file\");\n assertPrivateMode(linkedMetadata.mode, \"MCP session key file\");\n if (linkedMetadata.size !== SESSION_ARCHIVE_KEY_BYTES) {\n throw new Error(\n `MCP session key file must contain exactly ${SESSION_ARCHIVE_KEY_BYTES} raw bytes`,\n );\n }\n const handle = await open(\n canonical,\n fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | fsConstants.O_NONBLOCK,\n );\n let keyBuffer: Buffer | undefined;\n try {\n const openedMetadata = await handle.stat();\n if (\n !openedMetadata.isFile() ||\n !sameFile(linkedMetadata, openedMetadata) ||\n openedMetadata.size !== SESSION_ARCHIVE_KEY_BYTES\n ) {\n throw new Error(\"MCP session key file changed while it was validated\");\n }\n keyBuffer = Buffer.alloc(SESSION_ARCHIVE_KEY_BYTES + 1);\n let keyLength = 0;\n for (;;) {\n const { bytesRead } = await handle.read(\n keyBuffer,\n keyLength,\n keyBuffer.byteLength - keyLength,\n null,\n );\n if (bytesRead === 0 || keyLength + bytesRead === keyBuffer.byteLength) {\n keyLength += bytesRead;\n break;\n }\n keyLength += bytesRead;\n }\n if (keyLength !== SESSION_ARCHIVE_KEY_BYTES) {\n keyBuffer.fill(0);\n throw new Error(\n `MCP session key file must contain exactly ${SESSION_ARCHIVE_KEY_BYTES} raw bytes`,\n );\n }\n const currentMetadata = await stat(canonical);\n if (!sameFile(openedMetadata, currentMetadata)) {\n keyBuffer.fill(0);\n throw new Error(\"MCP session key file changed while it was read\");\n }\n const key = new Uint8Array(SESSION_ARCHIVE_KEY_BYTES);\n key.set(keyBuffer.subarray(0, SESSION_ARCHIVE_KEY_BYTES));\n keyBuffer.fill(0);\n return key;\n } finally {\n keyBuffer?.fill(0);\n await handle.close();\n }\n};\n\nconst archiveName = (sessionId: string): string =>\n `${createHash(\"sha256\").update(sessionId, \"utf8\").digest(\"hex\")}.stlasess`;\n\nconst assertSessionId = (sessionId: string): void => {\n if (!SESSION_ID.test(sessionId)) {\n throw new Error(\"MCP session ID is invalid\");\n }\n};\n\nexport type DurableSessionStoreOptions = {\n faultInjector?: (point: DurableSessionFaultPoint) => Promise<void> | void;\n keyFile: string;\n sessionDirectory: string;\n};\n\nexport type StoredSessionArchive = {\n bytes: Uint8Array;\n};\n\nexport type EncryptableSession = {\n toEncryptedArchiveAt(\n key: Uint8Array,\n observedAtEpochSeconds: number,\n ): Uint8Array;\n};\n\nexport type EncryptedSessionRestorer<Session> = {\n restoreEncryptedRedactionSession(options: {\n archive: Uint8Array;\n expectedSessionId: string;\n key: Uint8Array;\n observedAtEpochSeconds: number;\n }): Session;\n};\n\nexport type RestoreStoredSessionOptions<Session> = {\n archive: Uint8Array;\n expectedSessionId: string;\n observedAtEpochSeconds: number;\n restorer: EncryptedSessionRestorer<Session>;\n};\n\nexport class DurableSessionStore {\n #closePromise: Promise<void> | undefined;\n readonly #directory: DirectoryIdentity;\n readonly #faultInjector:\n | ((point: DurableSessionFaultPoint) => Promise<void> | void)\n | undefined;\n readonly #key: Uint8Array;\n readonly #lockHandle: FileHandle;\n #mutationTail: Promise<void> = Promise.resolve();\n #state: \"closed\" | \"closing\" | \"open\" = \"open\";\n\n private constructor(\n directory: DirectoryIdentity,\n key: Uint8Array,\n lockHandle: FileHandle,\n faultInjector?: (point: DurableSessionFaultPoint) => Promise<void> | void,\n ) {\n this.#directory = directory;\n this.#key = key;\n this.#lockHandle = lockHandle;\n this.#faultInjector = faultInjector;\n }\n\n static async create({\n keyFile,\n sessionDirectory,\n faultInjector,\n }: DurableSessionStoreOptions): Promise<DurableSessionStore> {\n assertPosixDurabilitySupport();\n const directoryPath = await canonicalAbsolutePath(\n sessionDirectory,\n \"MCP session directory\",\n );\n const metadata = await lstat(directoryPath);\n if (!metadata.isDirectory() || metadata.isSymbolicLink()) {\n throw new Error(\"MCP session directory must be a directory\");\n }\n assertOwner(metadata.uid, \"MCP session directory\");\n assertPrivateMode(metadata.mode, \"MCP session directory\");\n const key = await readKey(keyFile);\n let lockHandle: FileHandle | undefined;\n try {\n lockHandle = await acquireDirectoryLock(directoryPath);\n const store = new DurableSessionStore(\n { dev: metadata.dev, ino: metadata.ino, path: directoryPath },\n key,\n lockHandle,\n faultInjector,\n );\n await store.#validateInventory({ removeStagingFiles: true });\n await store.#syncDirectory();\n return store;\n } catch (error) {\n key.fill(0);\n await lockHandle?.close().catch(() => undefined);\n throw error;\n }\n }\n\n seal(\n session: EncryptableSession,\n observedAtEpochSeconds: number,\n ): Uint8Array {\n this.#assertOpen();\n return session.toEncryptedArchiveAt(this.#key, observedAtEpochSeconds);\n }\n\n restore<Session>({\n archive,\n expectedSessionId,\n observedAtEpochSeconds,\n restorer,\n }: RestoreStoredSessionOptions<Session>): Session {\n this.#assertOpen();\n return restorer.restoreEncryptedRedactionSession({\n archive,\n expectedSessionId,\n key: this.#key,\n observedAtEpochSeconds,\n });\n }\n\n async close(): Promise<void> {\n if (this.#closePromise !== undefined) {\n return this.#closePromise;\n }\n this.#state = \"closing\";\n this.#closePromise = (async () => {\n await this.#mutationTail;\n this.#key.fill(0);\n await this.#lockHandle.close();\n this.#state = \"closed\";\n })();\n return this.#closePromise;\n }\n\n async load(sessionId: string): Promise<StoredSessionArchive | undefined> {\n this.#assertOpen();\n return this.#withMutation(() => this.#load(sessionId));\n }\n\n async #load(sessionId: string): Promise<StoredSessionArchive | undefined> {\n assertSessionId(sessionId);\n await this.#validateInventory({ removeStagingFiles: false });\n const path = this.#path(sessionId);\n let handle: Awaited<ReturnType<typeof open>>;\n try {\n handle = await open(\n path,\n fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | fsConstants.O_NONBLOCK,\n );\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return undefined;\n }\n throw new Error(\"Encrypted session archive could not be opened\", {\n cause: error,\n });\n }\n try {\n const metadata = await handle.stat();\n if (!metadata.isFile()) {\n throw new Error(\"Encrypted session archive must be a regular file\");\n }\n assertOwner(metadata.uid, \"Encrypted session archive\");\n assertPrivateMode(metadata.mode, \"Encrypted session archive\");\n if (metadata.size > SESSION_ARCHIVE_MAX_BYTES) {\n throw new Error(\"Encrypted session archive exceeds the byte limit\");\n }\n const bytes = await readHandleBounded(handle, SESSION_ARCHIVE_MAX_BYTES);\n await this.#assertDirectory();\n const currentMetadata = await lstat(path);\n if (!currentMetadata.isFile() || !sameFile(metadata, currentMetadata)) {\n throw new Error(\"Encrypted session archive changed while it was read\");\n }\n return { bytes };\n } finally {\n await handle.close();\n }\n }\n\n async save(sessionId: string, archive: Uint8Array): Promise<void> {\n this.#assertOpen();\n return this.#withMutation(() => this.#save(sessionId, archive));\n }\n\n async #save(sessionId: string, archive: Uint8Array): Promise<void> {\n assertSessionId(sessionId);\n if (archive.byteLength > SESSION_ARCHIVE_MAX_BYTES) {\n throw new Error(\"Encrypted session archive exceeds the byte limit\");\n }\n const inventory = await this.#validateInventory({\n removeStagingFiles: false,\n });\n await this.#assertDirectory();\n const destination = this.#path(sessionId);\n const temporary = `${destination}.tmp.${randomUUID()}`;\n const handle = await open(\n temporary,\n fsConstants.O_WRONLY |\n fsConstants.O_CREAT |\n fsConstants.O_EXCL |\n fsConstants.O_NOFOLLOW,\n 0o600,\n );\n try {\n const openedMetadata = await handle.stat();\n if (!openedMetadata.isFile()) {\n throw new Error(\"Encrypted session staging path is not a regular file\");\n }\n await this.#inject(DURABLE_SESSION_FAULT_POINTS.beforeStagingWrite);\n await handle.writeFile(archive);\n await this.#inject(DURABLE_SESSION_FAULT_POINTS.beforeStagingFsync);\n await handle.sync();\n const stagedMetadata = await lstat(temporary);\n if (\n !stagedMetadata.isFile() ||\n !sameFile(openedMetadata, stagedMetadata)\n ) {\n throw new Error(\n \"Encrypted session staging file changed before publication\",\n );\n }\n await this.#assertDirectory();\n const existing = await lstat(destination).catch((error: unknown) => {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return undefined;\n }\n throw error;\n });\n if (\n existing !== undefined &&\n (!existing.isFile() || existing.isSymbolicLink())\n ) {\n throw new Error(\"Encrypted session archive path is not a regular file\");\n }\n const nextCount =\n inventory.archiveCount + (existing === undefined ? 1 : 0);\n const nextTotalBytes =\n inventory.totalBytes - (existing?.size ?? 0) + archive.byteLength;\n if (nextCount > SESSION_ARCHIVE_MAX_COUNT) {\n throw new Error(\n `MCP session archives must not exceed ${SESSION_ARCHIVE_MAX_COUNT}`,\n );\n }\n if (nextTotalBytes > SESSION_ARCHIVE_TOTAL_MAX_BYTES) {\n throw new Error(\"MCP session archives exceed the aggregate byte limit\");\n }\n await this.#inject(DURABLE_SESSION_FAULT_POINTS.beforeRename);\n await rename(temporary, destination);\n await this.#assertDirectory();\n const published = await lstat(destination);\n if (!published.isFile() || !sameFile(openedMetadata, published)) {\n throw new Error(\"Encrypted session archive publication was not atomic\");\n }\n await this.#syncDirectory();\n } finally {\n await handle.close().catch(() => undefined);\n await unlink(temporary).catch(() => undefined);\n }\n }\n\n async delete(sessionId: string): Promise<void> {\n this.#assertOpen();\n return this.#withMutation(() => this.#delete(sessionId));\n }\n\n async #delete(sessionId: string): Promise<void> {\n assertSessionId(sessionId);\n await this.#assertDirectory();\n const path = this.#path(sessionId);\n const metadata = await lstat(path).catch((error: unknown) => {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return undefined;\n }\n throw error;\n });\n if (metadata === undefined) {\n return;\n }\n if (!metadata.isFile() || metadata.isSymbolicLink()) {\n throw new Error(\"Encrypted session archive path is not a regular file\");\n }\n await unlink(path);\n await this.#syncDirectory();\n await this.#assertDirectory();\n }\n\n #path(sessionId: string): string {\n return join(this.#directory.path, archiveName(sessionId));\n }\n\n #assertOpen(): void {\n if (this.#state !== \"open\") {\n throw new Error(\"MCP durable session store is closing or closed\");\n }\n }\n\n async #withMutation<Result>(\n operation: () => Promise<Result>,\n ): Promise<Result> {\n const previous = this.#mutationTail;\n let release = (): void => undefined;\n const current = new Promise<void>((resolvePromise) => {\n release = resolvePromise;\n });\n this.#mutationTail = previous.then(() => current);\n await previous;\n try {\n return await operation();\n } finally {\n release();\n }\n }\n\n async #assertDirectory(): Promise<void> {\n const canonical = await realpath(this.#directory.path);\n const metadata = await lstat(this.#directory.path);\n if (\n canonical !== this.#directory.path ||\n !metadata.isDirectory() ||\n metadata.isSymbolicLink() ||\n !sameFile(this.#directory, metadata)\n ) {\n throw new Error(\"MCP session directory changed while it was being used\");\n }\n assertOwner(metadata.uid, \"MCP session directory\");\n assertPrivateMode(metadata.mode, \"MCP session directory\");\n }\n\n async #syncDirectory(): Promise<void> {\n await this.#inject(DURABLE_SESSION_FAULT_POINTS.beforeDirectoryFsync);\n const handle = await open(\n this.#directory.path,\n fsConstants.O_RDONLY | fsConstants.O_DIRECTORY | fsConstants.O_NOFOLLOW,\n );\n try {\n const metadata = await handle.stat();\n if (!metadata.isDirectory() || !sameFile(this.#directory, metadata)) {\n throw new Error(\"MCP session directory changed before synchronization\");\n }\n await handle.sync();\n } finally {\n await handle.close();\n }\n }\n\n async #inject(point: DurableSessionFaultPoint): Promise<void> {\n await this.#faultInjector?.(point);\n }\n\n async #validateInventory({\n removeStagingFiles,\n }: {\n removeStagingFiles: boolean;\n }): Promise<SessionInventory> {\n await this.#assertDirectory();\n const entries = await readdir(this.#directory.path, {\n withFileTypes: true,\n });\n let archiveCount = 0;\n let totalBytes = 0;\n for (const entry of entries) {\n const path = join(this.#directory.path, entry.name);\n if (entry.name === LOCK_FILE_NAME) {\n const metadata = await lstat(path);\n if (!metadata.isFile() || metadata.isSymbolicLink()) {\n throw new Error(\"MCP session directory contains an unsafe lock path\");\n }\n assertOwner(metadata.uid, \"MCP session lock\");\n assertPrivateMode(metadata.mode, \"MCP session lock\");\n continue;\n }\n if (STAGING_NAME.test(entry.name)) {\n if (!removeStagingFiles) {\n throw new Error(\"MCP session directory contains a partial archive\");\n }\n const metadata = await lstat(path);\n if (!metadata.isFile() || metadata.isSymbolicLink()) {\n throw new Error(\n \"MCP session directory contains an unsafe staging path\",\n );\n }\n await unlink(path);\n await this.#syncDirectory();\n continue;\n }\n if (\n !ARCHIVE_NAME.test(entry.name) ||\n !entry.isFile() ||\n entry.isSymbolicLink()\n ) {\n throw new Error(\"MCP session directory contains an unsupported entry\");\n }\n const metadata = await lstat(path);\n if (!metadata.isFile() || metadata.isSymbolicLink()) {\n throw new Error(\n \"MCP session directory contains an unsafe archive path\",\n );\n }\n assertOwner(metadata.uid, \"Encrypted session archive\");\n assertPrivateMode(metadata.mode, \"Encrypted session archive\");\n if (metadata.size > SESSION_ARCHIVE_MAX_BYTES) {\n throw new Error(\"Encrypted session archive exceeds the byte limit\");\n }\n archiveCount += 1;\n totalBytes += metadata.size;\n if (archiveCount > SESSION_ARCHIVE_MAX_COUNT) {\n throw new Error(\n `MCP session archives must not exceed ${SESSION_ARCHIVE_MAX_COUNT}`,\n );\n }\n if (totalBytes > SESSION_ARCHIVE_TOTAL_MAX_BYTES) {\n throw new Error(\"MCP session archives exceed the aggregate byte limit\");\n }\n }\n await this.#assertDirectory();\n return { archiveCount, totalBytes };\n }\n}\n","import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport {\n CAPABILITY_MANIFEST,\n type NativeCallerDetection,\n type NativeTextReplacement,\n type PreparedNativePipeline,\n} from \"@stll/anonymize\";\nimport {\n DOCX_ARCHIVE_MAX_BYTES,\n DOCX_COVERAGE_MODES,\n anonymizeDocx,\n extractDocxText,\n restoreDocxText,\n type DocxAnonymizationSession,\n type DocxRestorationSession,\n} from \"@stll/anonymize-docx\";\nimport {\n PDF_DOCUMENT_MAX_BYTES,\n anonymizePdfRaster,\n renderPdfWithPopplerTesseract,\n} from \"@stll/anonymize-pdf\";\nimport * as nativeNode from \"@stll/anonymize/native-node\";\nimport {\n constants as fsConstants,\n link,\n lstat,\n open,\n realpath,\n stat,\n unlink,\n} from \"node:fs/promises\";\nimport { randomUUID } from \"node:crypto\";\nimport {\n dirname,\n extname,\n isAbsolute,\n relative,\n resolve,\n sep,\n} from \"node:path\";\nimport * as z from \"zod/v4\";\n\nimport { DurableSessionStore } from \"./durable-sessions\";\n\nconst TEXT_MAX_BYTES = 64 * 1024 * 1024;\nconst EXTERNAL_DETECTION_BATCH_MAX_BYTES = 16 * 1024 * 1024;\nconst EXTERNAL_DETECTION_BATCH_VERSION = 1 as const;\nconst PATH_MAX_CHARACTERS = 32_768;\nconst SESSION_MAX_COUNT = 256;\nconst SESSION_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/u;\nconst READ_CHUNK_BYTES = 64 * 1024;\n\nconst observedAtEpochSeconds = (): number => {\n const seconds = Math.floor(Date.now() / 1000);\n if (seconds < 0 || seconds > 0xff_ff_ff_ff) {\n throw new Error(\"The current time is outside the supported session range\");\n }\n return seconds;\n};\n\nexport const MCP_SESSION_MODES = {\n durableEncrypted: \"durable-encrypted\",\n memory: \"memory\",\n} as const;\n\nexport type McpSessionMode =\n (typeof MCP_SESSION_MODES)[keyof typeof MCP_SESSION_MODES];\n\nexport type AuditSafeResult = {\n operation: \"anonymize\" | \"inspect\" | \"restore\";\n format: \"docx\" | \"pdf\" | \"text\";\n outputCreated: boolean;\n sessionId?: string;\n entityCount?: number;\n blockCount?: number;\n rewrittenBlockCount?: number;\n restoredPlaceholderCount?: number;\n coverageStatus?: \"full\" | \"partial\";\n externalDetectionBatchStatus?: \"accepted\";\n externalDetectionCount?: number;\n retainedExternalDetectionCount?: number;\n pageCount?: number;\n mappedRegionCount?: number;\n structurePixelRewriteVerified?: true;\n piiCleanGuaranteed?: false;\n};\n\nconst EXTERNAL_DETECTION_FAILURES = {\n batchRejected: {\n code: \"EXTERNAL_DETECTION_BATCH_REJECTED\",\n message: \"The external detection batch was rejected.\",\n },\n documentRejected: {\n code: \"EXTERNAL_DETECTION_DOCUMENT_REJECTED\",\n message: \"The external detection document was rejected.\",\n },\n inputRejected: {\n code: \"EXTERNAL_DETECTION_INPUT_REJECTED\",\n message: \"The external detection request paths were rejected.\",\n },\n operationFailed: {\n code: \"EXTERNAL_DETECTION_OPERATION_FAILED\",\n message: \"The external detection operation failed safely.\",\n },\n sessionRejected: {\n code: \"EXTERNAL_DETECTION_SESSION_REJECTED\",\n message: \"The external detection session was rejected.\",\n },\n} as const;\n\ntype ExternalDetectionFailure =\n (typeof EXTERNAL_DETECTION_FAILURES)[keyof typeof EXTERNAL_DETECTION_FAILURES];\n\nclass ExternalDetectionAuditError extends Error {\n readonly code: ExternalDetectionFailure[\"code\"];\n\n constructor(failure: ExternalDetectionFailure) {\n super(failure.message);\n this.name = \"ExternalDetectionAuditError\";\n this.code = failure.code;\n }\n}\n\nconst externalDetectionFailure = (\n error: unknown,\n failure: ExternalDetectionFailure,\n): ExternalDetectionAuditError =>\n error instanceof ExternalDetectionAuditError\n ? error\n : new ExternalDetectionAuditError(failure);\n\nconst externalDetectionStep = async <Result>(\n failure: ExternalDetectionFailure,\n operation: () => Result | Promise<Result>,\n): Promise<Result> => {\n try {\n return await operation();\n } catch (error) {\n throw externalDetectionFailure(error, failure);\n }\n};\n\nexport type LocalAnonymizeServiceFaults = {\n beforeOutputPublish?: () => void | Promise<void>;\n};\n\nconst inside = (root: string, target: string): boolean => {\n const path = relative(root, target);\n return (\n path === \"\" ||\n (path !== \"..\" && !path.startsWith(`..${sep}`) && !isAbsolute(path))\n );\n};\n\ntype ReadInputOptions = {\n path: string;\n extension: \".docx\" | \".json\" | \".pdf\" | \".txt\";\n maximumBytes: number;\n label: \"DOCX\" | \"External detection batch\" | \"PDF\" | \"Text\";\n};\n\ntype ScopedInput = {\n bytes: Uint8Array;\n path: string;\n};\n\ntype ReadableFileHandle = Pick<Awaited<ReturnType<typeof open>>, \"read\">;\n\nconst readHandleBounded = async (\n handle: ReadableFileHandle,\n maximumBytes: number,\n label: \"DOCX\" | \"External detection batch\" | \"PDF\" | \"Text\",\n): Promise<Uint8Array> => {\n const chunks: Uint8Array[] = [];\n let total = 0;\n for (;;) {\n const chunk = Buffer.allocUnsafe(\n Math.min(READ_CHUNK_BYTES, maximumBytes - total + 1),\n );\n const { bytesRead } = await handle.read(chunk, 0, chunk.byteLength, null);\n if (bytesRead === 0) {\n return Buffer.concat(chunks, total);\n }\n total += bytesRead;\n if (total > maximumBytes) {\n throw new Error(`${label} inputs must not exceed ${maximumBytes} bytes`);\n }\n chunks.push(chunk.subarray(0, bytesRead));\n }\n};\n\ntype DirectoryIdentity = {\n dev: number;\n ino: number;\n path: string;\n};\n\ntype FileIdentity = Pick<DirectoryIdentity, \"dev\" | \"ino\">;\n\nclass ScopedOutput {\n readonly parent: DirectoryIdentity;\n readonly path: string;\n\n constructor(path: string, parent: DirectoryIdentity) {\n this.path = path;\n this.parent = parent;\n }\n\n async write(bytes: Uint8Array | string): Promise<void> {\n await safeWrite(this, bytes);\n }\n}\n\nexport class PathScope {\n readonly #roots: readonly string[];\n\n private constructor(roots: readonly string[]) {\n this.#roots = roots;\n }\n\n static async create(roots: readonly string[]): Promise<PathScope> {\n if (roots.length === 0) {\n throw new Error(\"At least one --root directory is required\");\n }\n const canonical = await Promise.all(\n roots.map(async (root) => {\n if (!isAbsolute(root)) {\n throw new Error(\"MCP roots must be absolute paths\");\n }\n const path = await realpath(root);\n const metadata = await stat(path);\n if (!metadata.isDirectory()) {\n throw new Error(\"Every MCP root must be a directory\");\n }\n return path;\n }),\n );\n return new PathScope([...new Set(canonical)]);\n }\n\n async readInput({\n path,\n extension,\n maximumBytes,\n label,\n }: ReadInputOptions): Promise<ScopedInput> {\n if (!isAbsolute(path) || extname(path).toLowerCase() !== extension) {\n throw new Error(`Input must be an absolute ${extension} path`);\n }\n const initiallyCanonical = await realpath(path);\n if (!this.#roots.some((root) => inside(root, initiallyCanonical))) {\n throw new Error(\"Input is outside the configured roots\");\n }\n const initiallyRequestedMetadata = await lstat(path);\n if (!initiallyRequestedMetadata.isFile()) {\n throw new Error(\"Input must be a regular file\");\n }\n const handle = await open(\n path,\n fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | fsConstants.O_NONBLOCK,\n );\n try {\n const openedMetadata = await handle.stat();\n if (!openedMetadata.isFile()) {\n throw new Error(\"Input must be a regular file\");\n }\n const canonical = await realpath(path);\n if (!this.#roots.some((root) => inside(root, canonical))) {\n throw new Error(\"Input is outside the configured roots\");\n }\n const currentMetadata = await stat(canonical);\n if (\n currentMetadata.dev !== openedMetadata.dev ||\n currentMetadata.ino !== openedMetadata.ino\n ) {\n throw new Error(\"Input changed while it was being validated\");\n }\n if (openedMetadata.size > maximumBytes) {\n throw new Error(\n `${label} inputs must not exceed ${maximumBytes} bytes`,\n );\n }\n const bytes = await readHandleBounded(handle, maximumBytes, label);\n return { bytes, path: canonical };\n } finally {\n await handle.close();\n }\n }\n\n async output(\n path: string,\n extension: \".docx\" | \".pdf\" | \".txt\",\n ): Promise<ScopedOutput> {\n if (!isAbsolute(path) || extname(path).toLowerCase() !== extension) {\n throw new Error(`Output must be an absolute ${extension} path`);\n }\n const normalized = resolve(path);\n const parent = await realpath(dirname(normalized));\n if (!this.#roots.some((root) => inside(root, parent))) {\n throw new Error(\"Output is outside the configured roots\");\n }\n const parentMetadata = await stat(parent);\n try {\n await lstat(normalized);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return new ScopedOutput(normalized, {\n dev: parentMetadata.dev,\n ino: parentMetadata.ino,\n path: parent,\n });\n }\n throw new Error(\"Output availability could not be verified\", {\n cause: error,\n });\n }\n throw new Error(\"Output already exists; overwriting is not supported\");\n }\n}\n\ntype SessionEntry = {\n language: string | undefined;\n session: RedactionSession;\n restoreSession: (plaintextJson: string) => RedactionSession;\n status: \"busy\" | \"initializing\" | \"ready\";\n};\n\ntype SessionLease = {\n entry: SessionEntry;\n sessionId: string;\n rollback:\n | { type: \"delete\" }\n | { type: \"release\" }\n | { type: \"restore\"; checkpoint: string };\n};\n\ntype RedactionSession = DocxAnonymizationSession &\n DocxRestorationSession & {\n redact_text(text: string): {\n redaction: { redactedText: string; entityCount: number };\n };\n restoreText(text: string): string;\n toPlaintextJson(): string;\n toEncryptedArchiveAt(\n key: Uint8Array,\n observedAtEpochSeconds: number,\n ): Uint8Array;\n };\n\ntype NativeNodeSurface = {\n convert_external_detection_batch: (\n document: Uint8Array,\n batch: string,\n ) => NativeCallerDetection[];\n getDefaultNativePipeline: (options: { language?: string }) => {\n createRedactionSession: (sessionId: string) => RedactionSession;\n restoreRedactionSession: (plaintextJson: string) => RedactionSession;\n restoreEncryptedRedactionSession: (options: {\n archive: Uint8Array;\n expectedSessionId: string;\n key: Uint8Array;\n observedAtEpochSeconds: number;\n }) => RedactionSession;\n } & Pick<\n PreparedNativePipeline,\n \"redactText\" | \"redactTextWithCallerDetections\"\n >;\n native_package_version: () => string;\n};\n\nconst nativeNodeSurface = nativeNode as unknown as NativeNodeSurface; // SAFETY: These native-node runtime exports are public, but their generated declarations are minified during workspace builds.\n\nconst assertOutputParent = async ({\n parent,\n path,\n}: ScopedOutput): Promise<void> => {\n const canonical = await realpath(dirname(path));\n const metadata = await stat(canonical);\n if (\n canonical !== parent.path ||\n metadata.dev !== parent.dev ||\n metadata.ino !== parent.ino\n ) {\n throw new Error(\"Output directory changed while it was being used\");\n }\n};\n\nconst sameFile = (left: FileIdentity, right: FileIdentity): boolean =>\n left.dev === right.dev && left.ino === right.ino;\n\nconst safeWrite = async (\n output: ScopedOutput,\n bytes: Uint8Array | string,\n): Promise<void> => {\n await assertOutputParent(output);\n const temporary = `${output.path}.stella-${randomUUID()}.tmp`;\n let published = false;\n let committed = false;\n let temporaryMetadata: FileIdentity | undefined;\n const handle = await open(\n temporary,\n fsConstants.O_WRONLY |\n fsConstants.O_CREAT |\n fsConstants.O_EXCL |\n fsConstants.O_NOFOLLOW,\n 0o400,\n );\n try {\n temporaryMetadata = await handle.stat();\n const canonicalTemporary = await realpath(temporary);\n const currentTemporaryMetadata = await lstat(temporary);\n if (\n dirname(canonicalTemporary) !== output.parent.path ||\n !currentTemporaryMetadata.isFile() ||\n !sameFile(temporaryMetadata, currentTemporaryMetadata)\n ) {\n throw new Error(\"Output staging file changed while it was being used\");\n }\n await handle.writeFile(bytes);\n await handle.sync();\n await assertOutputParent(output);\n const stagedMetadata = await lstat(temporary);\n if (\n !stagedMetadata.isFile() ||\n !sameFile(temporaryMetadata, stagedMetadata)\n ) {\n throw new Error(\"Output staging file changed before publication\");\n }\n await link(temporary, output.path);\n published = true;\n const publishedMetadata = await lstat(output.path);\n if (\n !publishedMetadata.isFile() ||\n !sameFile(temporaryMetadata, publishedMetadata)\n ) {\n throw new Error(\"Published output does not match the staged file\");\n }\n await assertOutputParent(output);\n await handle.chmod(0o600);\n await handle.sync();\n committed = true;\n } finally {\n if (!committed) {\n await handle.truncate(0).catch(() => undefined);\n await handle.sync().catch(() => undefined);\n await handle.chmod(0o000).catch(() => undefined);\n }\n await handle.close().catch(() => undefined);\n await unlink(temporary).catch(() => undefined);\n if (published && !committed && temporaryMetadata !== undefined) {\n const publishedMetadata = await lstat(output.path).catch(() => undefined);\n if (\n publishedMetadata !== undefined &&\n sameFile(temporaryMetadata, publishedMetadata)\n ) {\n await unlink(output.path).catch(() => undefined);\n }\n }\n }\n};\n\nconst decodeText = (bytes: Uint8Array): string => {\n return decodeUtf8(bytes, \"Text inputs\");\n};\n\nconst decodeUtf8 = (bytes: Uint8Array, label: string): string => {\n try {\n return new TextDecoder(\"utf-8\", { fatal: true }).decode(bytes);\n } catch (error) {\n throw new Error(`${label} must contain valid UTF-8`, { cause: error });\n }\n};\n\nconst applyTextReplacements = (\n text: string,\n replacements: readonly NativeTextReplacement[],\n): string => {\n const parts: string[] = [];\n let cursor = 0;\n for (const replacement of replacements) {\n if (\n !Number.isSafeInteger(replacement.start) ||\n !Number.isSafeInteger(replacement.end) ||\n replacement.start < cursor ||\n replacement.end <= replacement.start ||\n replacement.end > text.length ||\n !isUtf16Boundary(text, replacement.start) ||\n !isUtf16Boundary(text, replacement.end)\n ) {\n throw new Error(\n \"Native caller-detection plan returned invalid replacements\",\n );\n }\n parts.push(text.slice(cursor, replacement.start), replacement.replacement);\n cursor = replacement.end;\n }\n parts.push(text.slice(cursor));\n return parts.join(\"\");\n};\n\nconst isUtf16Boundary = (text: string, offset: number): boolean => {\n if (offset <= 0 || offset >= text.length) {\n return true;\n }\n const before = text.charCodeAt(offset - 1);\n const after = text.charCodeAt(offset);\n return !(\n before >= 0xd800 &&\n before <= 0xdbff &&\n after >= 0xdc00 &&\n after <= 0xdfff\n );\n};\n\nconst assertDifferentPaths = (input: string, output: string): void => {\n if (input === output) {\n throw new Error(\"Input and output paths must differ\");\n }\n};\n\nconst textInput = z.object({\n inputPath: z.string().min(1).max(PATH_MAX_CHARACTERS),\n outputPath: z.string().min(1).max(PATH_MAX_CHARACTERS),\n sessionId: z.string().regex(SESSION_ID),\n language: z.string().min(2).max(35).optional(),\n});\n\nconst externalDetectionTextInput = textInput.extend({\n detectionBatchPath: z.string().min(1).max(PATH_MAX_CHARACTERS),\n});\n\nconst restoreInput = z.object({\n inputPath: z.string().min(1).max(PATH_MAX_CHARACTERS),\n outputPath: z.string().min(1).max(PATH_MAX_CHARACTERS),\n sessionId: z.string().regex(SESSION_ID),\n});\n\nconst docxRestoreInput = restoreInput.extend({\n allowPartialCoverage: z.boolean().optional().default(false),\n});\n\nconst docxInput = textInput.extend({\n allowPartialCoverage: z.boolean().optional().default(false),\n});\n\nconst pdfInput = z.object({\n inputPath: z.string().min(1).max(PATH_MAX_CHARACTERS),\n outputPath: z.string().min(1).max(PATH_MAX_CHARACTERS),\n ocrLanguage: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9_.-]*$/u),\n detectionLanguage: z.string().min(2).max(35).optional(),\n dpi: z.number().int().min(72).max(600).optional().default(300),\n timeoutMs: z.number().int().min(100).max(300_000).optional().default(120_000),\n fillRgb: z\n .tuple([\n z.number().int().min(0).max(255),\n z.number().int().min(0).max(255),\n z.number().int().min(0).max(255),\n ])\n .optional()\n .default([0, 0, 0]),\n});\n\nexport type LocalPdfProviderConfiguration = {\n pdftoppmPath?: string | undefined;\n tesseractPath?: string | undefined;\n};\n\nexport type LocalAnonymizeServiceOptions = {\n durableSessions?: DurableSessionStore | undefined;\n faults?: LocalAnonymizeServiceFaults | undefined;\n pdfProvider?: LocalPdfProviderConfiguration | undefined;\n};\n\nexport class LocalAnonymizeService {\n #activeOperations = 0;\n #closePromise: Promise<void> | undefined;\n #operationsDrained: (() => void) | undefined;\n readonly #scope: PathScope;\n #pdfOperationTail: Promise<void> = Promise.resolve();\n readonly #sessionInitializations = new Set<string>();\n readonly #sessions = new Map<string, SessionEntry>();\n #state: \"closed\" | \"closing\" | \"open\" = \"open\";\n readonly #durableSessions: DurableSessionStore | undefined;\n readonly #faults: LocalAnonymizeServiceFaults;\n readonly #pdfProvider: LocalPdfProviderConfiguration;\n\n constructor(scope: PathScope, options: LocalAnonymizeServiceOptions = {}) {\n if (options instanceof DurableSessionStore) {\n throw new TypeError(\n \"LocalAnonymizeService requires { durableSessions } as its second argument\",\n );\n }\n const { durableSessions, faults = {}, pdfProvider = {} } = options;\n this.#scope = scope;\n this.#durableSessions = durableSessions;\n this.#faults = faults;\n this.#pdfProvider = pdfProvider;\n }\n\n get sessionMode(): McpSessionMode {\n return this.#durableSessions === undefined\n ? MCP_SESSION_MODES.memory\n : MCP_SESSION_MODES.durableEncrypted;\n }\n\n async close(): Promise<void> {\n if (this.#closePromise !== undefined) {\n return this.#closePromise;\n }\n this.#state = \"closing\";\n this.#closePromise = (async () => {\n if (this.#activeOperations > 0) {\n await new Promise<void>((resolvePromise) => {\n this.#operationsDrained = resolvePromise;\n });\n }\n this.#sessions.clear();\n await this.#durableSessions?.close();\n this.#state = \"closed\";\n })();\n return this.#closePromise;\n }\n\n async #runOperation<Result>(\n operation: () => Promise<Result>,\n ): Promise<Result> {\n if (this.#state !== \"open\") {\n throw new Error(\"MCP anonymize service is closing or closed\");\n }\n this.#activeOperations += 1;\n try {\n return await operation();\n } finally {\n this.#activeOperations -= 1;\n if (this.#activeOperations === 0) {\n this.#operationsDrained?.();\n this.#operationsDrained = undefined;\n }\n }\n }\n\n async #session(sessionId: string, language?: string): Promise<SessionLease> {\n if (this.#durableSessions !== undefined && language !== undefined) {\n throw new Error(\n \"Durable sessions use the full all-language pipeline; omit language\",\n );\n }\n const existing = this.#sessions.get(sessionId);\n if (existing !== undefined) {\n if (language !== undefined && existing.language !== language) {\n throw new Error(\"A session cannot change language\");\n }\n if (existing.status === \"initializing\") {\n throw new Error(\"The requested session is still initializing\");\n }\n if (existing.status === \"busy\") {\n throw new Error(\"The requested session is handling another operation\");\n }\n const checkpoint = existing.session.toPlaintextJson();\n existing.status = \"busy\";\n return {\n entry: existing,\n sessionId,\n rollback: { type: \"restore\", checkpoint },\n };\n }\n if (this.#sessionInitializations.has(sessionId)) {\n throw new Error(\"The requested session is still initializing\");\n }\n this.#sessionInitializations.add(sessionId);\n try {\n if (this.#sessions.size >= SESSION_MAX_COUNT) {\n throw new Error(`MCP sessions must not exceed ${SESSION_MAX_COUNT}`);\n }\n const pipeline = nativeNodeSurface.getDefaultNativePipeline(\n language === undefined ? {} : { language },\n );\n const durableSessions = this.#durableSessions;\n const stored =\n durableSessions === undefined\n ? undefined\n : await durableSessions.load(sessionId);\n let session: RedactionSession;\n if (stored === undefined) {\n session = pipeline.createRedactionSession(sessionId);\n } else {\n if (durableSessions === undefined) {\n throw new Error(\"Durable session storage is unavailable\");\n }\n try {\n session = durableSessions.restore({\n archive: stored.bytes,\n expectedSessionId: sessionId,\n observedAtEpochSeconds: observedAtEpochSeconds(),\n restorer: pipeline,\n });\n } catch (error) {\n throw new Error(\"The requested durable session is unavailable\", {\n cause: error,\n });\n }\n }\n const entry: SessionEntry = {\n language,\n session,\n restoreSession: (plaintextJson) =>\n pipeline.restoreRedactionSession(plaintextJson),\n status: \"initializing\",\n };\n this.#sessions.set(sessionId, entry);\n return {\n entry,\n sessionId,\n rollback:\n stored === undefined\n ? { type: \"delete\" }\n : { type: \"restore\", checkpoint: session.toPlaintextJson() },\n };\n } finally {\n this.#sessionInitializations.delete(sessionId);\n }\n }\n\n #commitSession({ entry }: SessionLease): void {\n entry.status = \"ready\";\n }\n\n async #rollbackSession({\n entry,\n rollback,\n sessionId,\n }: SessionLease): Promise<void> {\n if (this.#sessions.get(sessionId) !== entry) {\n return;\n }\n if (rollback.type === \"delete\") {\n this.#sessions.delete(sessionId);\n await this.#durableSessions?.delete(sessionId).catch(() => undefined);\n return;\n }\n if (rollback.type === \"release\") {\n entry.status = \"ready\";\n return;\n }\n try {\n entry.session = entry.restoreSession(rollback.checkpoint);\n entry.status = \"ready\";\n await this.#persistSession(sessionId, entry.session);\n } catch {\n this.#sessions.delete(sessionId);\n }\n }\n\n async #readSession(sessionId: string): Promise<SessionLease> {\n let entry = this.#sessions.get(sessionId);\n if (entry === undefined && this.#durableSessions !== undefined) {\n if (this.#sessionInitializations.has(sessionId)) {\n throw new Error(\"The requested session is still initializing\");\n }\n this.#sessionInitializations.add(sessionId);\n try {\n const pipeline = nativeNodeSurface.getDefaultNativePipeline({});\n const stored = await this.#durableSessions.load(sessionId);\n if (stored !== undefined) {\n try {\n const session = this.#durableSessions.restore({\n archive: stored.bytes,\n expectedSessionId: sessionId,\n observedAtEpochSeconds: observedAtEpochSeconds(),\n restorer: pipeline,\n });\n entry = {\n language: undefined,\n session,\n restoreSession: (plaintextJson) =>\n pipeline.restoreRedactionSession(plaintextJson),\n status: \"ready\",\n };\n this.#sessions.set(sessionId, entry);\n } catch (error) {\n throw new Error(\"The requested durable session is unavailable\", {\n cause: error,\n });\n }\n }\n } finally {\n this.#sessionInitializations.delete(sessionId);\n }\n }\n if (entry === undefined) {\n throw new Error(\"The requested session is unavailable\");\n }\n if (entry.status !== \"ready\") {\n throw new Error(\"The requested in-memory session is unavailable\");\n }\n entry.status = \"busy\";\n return { entry, sessionId, rollback: { type: \"release\" } };\n }\n\n async #persistSession(\n sessionId: string,\n session: RedactionSession,\n ): Promise<void> {\n if (this.#durableSessions === undefined) {\n return;\n }\n const archive = this.#durableSessions.seal(\n session,\n observedAtEpochSeconds(),\n );\n await this.#durableSessions.save(sessionId, archive);\n }\n\n async anonymizeText(\n input: z.infer<typeof textInput>,\n ): Promise<AuditSafeResult> {\n return this.#runOperation(() => this.#anonymizeText(input));\n }\n\n async anonymizePdf(\n input: z.infer<typeof pdfInput>,\n ): Promise<AuditSafeResult> {\n return this.#runOperation(() =>\n this.#serializePdfOperation(() => this.#anonymizePdf(input)),\n );\n }\n\n async #serializePdfOperation<Result>(\n operation: () => Promise<Result>,\n ): Promise<Result> {\n const previous = this.#pdfOperationTail;\n let release = (): void => undefined;\n this.#pdfOperationTail = new Promise<void>((resolvePromise) => {\n release = resolvePromise;\n });\n await previous;\n try {\n return await operation();\n } finally {\n release();\n }\n }\n\n async #anonymizePdf(\n input: z.infer<typeof pdfInput>,\n ): Promise<AuditSafeResult> {\n const source = await this.#scope.readInput({\n path: input.inputPath,\n extension: \".pdf\",\n maximumBytes: PDF_DOCUMENT_MAX_BYTES,\n label: \"PDF\",\n });\n const destination = await this.#scope.output(input.outputPath, \".pdf\");\n assertDifferentPaths(source.path, destination.path);\n const pipeline = nativeNodeSurface.getDefaultNativePipeline(\n input.detectionLanguage === undefined\n ? {}\n : { language: input.detectionLanguage },\n );\n const observed = await renderPdfWithPopplerTesseract({\n document: source.bytes,\n ocrLanguage: input.ocrLanguage,\n dpi: input.dpi,\n timeoutMs: input.timeoutMs,\n ...this.#pdfProvider,\n });\n const anonymized = anonymizePdfRaster({\n document: source.bytes,\n pipeline,\n provider: observed.provider,\n pages: observed.pages,\n fillRgb: input.fillRgb,\n });\n if (\n anonymized.certificate.structurePixelRewriteVerified !== true ||\n anonymized.certificate.piiCleanGuaranteed !== false\n ) {\n throw new Error(\"PDF raster verification did not satisfy MCP policy\");\n }\n await this.#faults.beforeOutputPublish?.();\n await destination.write(anonymized.document);\n return {\n operation: \"anonymize\",\n format: \"pdf\",\n outputCreated: true,\n pageCount: anonymized.certificate.pageCount,\n entityCount: anonymized.certificate.detectionCount,\n mappedRegionCount: anonymized.certificate.mappedRegionCount,\n structurePixelRewriteVerified: true,\n piiCleanGuaranteed: false,\n };\n }\n\n async #anonymizeText(\n input: z.infer<typeof textInput>,\n ): Promise<AuditSafeResult> {\n const source = await this.#scope.readInput({\n path: input.inputPath,\n extension: \".txt\",\n maximumBytes: TEXT_MAX_BYTES,\n label: \"Text\",\n });\n const destination = await this.#scope.output(input.outputPath, \".txt\");\n assertDifferentPaths(source.path, destination.path);\n const text = decodeText(source.bytes);\n const lease = await this.#session(input.sessionId, input.language);\n try {\n const result = lease.entry.session.redact_text(text);\n await this.#persistSession(input.sessionId, lease.entry.session);\n await this.#faults.beforeOutputPublish?.();\n await destination.write(result.redaction.redactedText);\n this.#commitSession(lease);\n return {\n operation: \"anonymize\",\n format: \"text\",\n outputCreated: true,\n sessionId: input.sessionId,\n entityCount: result.redaction.entityCount,\n };\n } catch (error) {\n await this.#rollbackSession(lease);\n throw error;\n }\n }\n\n async restoreText(\n input: z.infer<typeof restoreInput>,\n ): Promise<AuditSafeResult> {\n return this.#runOperation(() => this.#restoreText(input));\n }\n\n async anonymizeTextWithExternalDetections(\n input: z.infer<typeof externalDetectionTextInput>,\n ): Promise<AuditSafeResult> {\n try {\n return await this.#runOperation(() =>\n this.#anonymizeTextWithExternalDetections(input),\n );\n } catch (error) {\n throw externalDetectionFailure(\n error,\n EXTERNAL_DETECTION_FAILURES.operationFailed,\n );\n }\n }\n\n async #anonymizeTextWithExternalDetections(\n input: z.infer<typeof externalDetectionTextInput>,\n ): Promise<AuditSafeResult> {\n const { batch, destination, source } = await externalDetectionStep(\n EXTERNAL_DETECTION_FAILURES.inputRejected,\n async () => {\n const scopedSource = await this.#scope.readInput({\n path: input.inputPath,\n extension: \".txt\",\n maximumBytes: TEXT_MAX_BYTES,\n label: \"Text\",\n });\n const scopedBatch = await this.#scope.readInput({\n path: input.detectionBatchPath,\n extension: \".json\",\n maximumBytes: EXTERNAL_DETECTION_BATCH_MAX_BYTES,\n label: \"External detection batch\",\n });\n const scopedDestination = await this.#scope.output(\n input.outputPath,\n \".txt\",\n );\n assertDifferentPaths(scopedSource.path, scopedDestination.path);\n assertDifferentPaths(scopedBatch.path, scopedDestination.path);\n assertDifferentPaths(scopedSource.path, scopedBatch.path);\n return {\n batch: scopedBatch,\n destination: scopedDestination,\n source: scopedSource,\n };\n },\n );\n const text = await externalDetectionStep(\n EXTERNAL_DETECTION_FAILURES.documentRejected,\n () => decodeText(source.bytes),\n );\n const detections = await externalDetectionStep(\n EXTERNAL_DETECTION_FAILURES.batchRejected,\n () =>\n nativeNodeSurface.convert_external_detection_batch(\n source.bytes,\n decodeUtf8(batch.bytes, \"External detection batches\"),\n ),\n );\n const lease = await externalDetectionStep(\n EXTERNAL_DETECTION_FAILURES.sessionRejected,\n () => this.#session(input.sessionId, input.language),\n );\n try {\n const plan = lease.entry.session.planTextBatchWithCallerDetections({\n inputs: [{ fullText: text, detections }],\n });\n const block = plan.blocks.at(0);\n if (plan.blocks.length !== 1 || block === undefined) {\n throw new Error(\n \"Native caller-detection plan did not match the text input\",\n );\n }\n const redactedText = applyTextReplacements(text, block.replacements);\n plan.commit();\n await this.#persistSession(input.sessionId, lease.entry.session);\n await this.#faults.beforeOutputPublish?.();\n await destination.write(redactedText);\n this.#commitSession(lease);\n return {\n operation: \"anonymize\",\n format: \"text\",\n outputCreated: true,\n sessionId: input.sessionId,\n entityCount: block.entityCount,\n externalDetectionBatchStatus: \"accepted\",\n externalDetectionCount: detections.length,\n retainedExternalDetectionCount: block.callerEntityCount,\n };\n } catch (error) {\n await this.#rollbackSession(lease);\n throw externalDetectionFailure(\n error,\n EXTERNAL_DETECTION_FAILURES.operationFailed,\n );\n }\n }\n\n async #restoreText(\n input: z.infer<typeof restoreInput>,\n ): Promise<AuditSafeResult> {\n const source = await this.#scope.readInput({\n path: input.inputPath,\n extension: \".txt\",\n maximumBytes: TEXT_MAX_BYTES,\n label: \"Text\",\n });\n const destination = await this.#scope.output(input.outputPath, \".txt\");\n assertDifferentPaths(source.path, destination.path);\n const text = decodeText(source.bytes);\n const lease = await this.#readSession(input.sessionId);\n try {\n const restored = lease.entry.session.restoreText(text);\n await destination.write(restored);\n this.#commitSession(lease);\n return {\n operation: \"restore\",\n format: \"text\",\n outputCreated: true,\n sessionId: input.sessionId,\n };\n } catch (error) {\n await this.#rollbackSession(lease);\n throw error;\n }\n }\n\n async anonymizeDocx(\n input: z.infer<typeof docxInput>,\n ): Promise<AuditSafeResult> {\n return this.#runOperation(() => this.#anonymizeDocx(input));\n }\n\n async #anonymizeDocx(\n input: z.infer<typeof docxInput>,\n ): Promise<AuditSafeResult> {\n const source = await this.#scope.readInput({\n path: input.inputPath,\n extension: \".docx\",\n maximumBytes: DOCX_ARCHIVE_MAX_BYTES,\n label: \"DOCX\",\n });\n const destination = await this.#scope.output(input.outputPath, \".docx\");\n assertDifferentPaths(source.path, destination.path);\n const lease = await this.#session(input.sessionId, input.language);\n try {\n const result = anonymizeDocx({\n document: source.bytes,\n session: lease.entry.session,\n expectedSessionId: input.sessionId,\n policy: {\n coverage: {\n mode: input.allowPartialCoverage\n ? DOCX_COVERAGE_MODES.allowPartial\n : DOCX_COVERAGE_MODES.requireFull,\n },\n },\n });\n await this.#persistSession(input.sessionId, lease.entry.session);\n await this.#faults.beforeOutputPublish?.();\n await destination.write(result.document);\n this.#commitSession(lease);\n return {\n operation: \"anonymize\",\n format: \"docx\",\n outputCreated: true,\n sessionId: input.sessionId,\n entityCount: result.summary.entityCount,\n blockCount: result.summary.blockCount,\n rewrittenBlockCount: result.summary.rewrittenBlockCount,\n coverageStatus: result.summary.coverage.status,\n };\n } catch (error) {\n await this.#rollbackSession(lease);\n throw error;\n }\n }\n\n async restoreDocx(\n input: z.infer<typeof docxRestoreInput>,\n ): Promise<AuditSafeResult> {\n return this.#runOperation(() => this.#restoreDocx(input));\n }\n\n async #restoreDocx(\n input: z.infer<typeof docxRestoreInput>,\n ): Promise<AuditSafeResult> {\n const source = await this.#scope.readInput({\n path: input.inputPath,\n extension: \".docx\",\n maximumBytes: DOCX_ARCHIVE_MAX_BYTES,\n label: \"DOCX\",\n });\n const destination = await this.#scope.output(input.outputPath, \".docx\");\n assertDifferentPaths(source.path, destination.path);\n const lease = await this.#readSession(input.sessionId);\n try {\n const result = restoreDocxText({\n document: source.bytes,\n session: lease.entry.session,\n expectedSessionId: input.sessionId,\n });\n if (result.coverage.status === \"partial\" && !input.allowPartialCoverage) {\n throw new Error(\n \"DOCX restoration has partial coverage; set allowPartialCoverage to publish it\",\n );\n }\n await destination.write(result.document);\n this.#commitSession(lease);\n return {\n operation: \"restore\",\n format: \"docx\",\n outputCreated: true,\n sessionId: input.sessionId,\n rewrittenBlockCount: result.restoredBlockCount,\n restoredPlaceholderCount: result.restoredPlaceholderCount,\n coverageStatus: result.coverage.status,\n };\n } catch (error) {\n await this.#rollbackSession(lease);\n throw error;\n }\n }\n\n async inspectDocx(inputPath: string): Promise<AuditSafeResult> {\n return this.#runOperation(() => this.#inspectDocx(inputPath));\n }\n\n async #inspectDocx(inputPath: string): Promise<AuditSafeResult> {\n const source = await this.#scope.readInput({\n path: inputPath,\n extension: \".docx\",\n maximumBytes: DOCX_ARCHIVE_MAX_BYTES,\n label: \"DOCX\",\n });\n const extraction = extractDocxText(source.bytes);\n const unsupported = extraction.coverage.parts.some(\n (part) => part.status === \"unsupported\",\n );\n const structuralGap =\n extraction.coverage.hyperlinkTextSegmentCount > 0 ||\n extraction.coverage.revisionTextSegmentCount > 0 ||\n extraction.coverage.unsupportedAlternateContentCount > 0 ||\n extraction.coverage.unsupportedFieldInstructionCount > 0 ||\n extraction.coverage.unsupportedSymbolCount > 0;\n return {\n operation: \"inspect\",\n format: \"docx\",\n outputCreated: false,\n blockCount: extraction.blocks.length,\n coverageStatus: unsupported || structuralGap ? \"partial\" : \"full\",\n };\n }\n}\n\nconst result = (value: AuditSafeResult) => ({\n content: [{ type: \"text\" as const, text: JSON.stringify(value) }],\n structuredContent: { ...value },\n});\n\nconst MCP_TOOL_NAMES = [\n \"anonymize_docx_file\",\n \"anonymize_pdf_file\",\n \"anonymize_text_file\",\n \"anonymize_text_file_with_external_detections\",\n \"capabilities\",\n \"inspect_docx_file\",\n \"restore_docx_file\",\n \"restore_text_file\",\n] as const;\n\nconst capabilitiesResult = (service: LocalAnonymizeService) => {\n const value = {\n capabilityManifest: CAPABILITY_MANIFEST,\n runtimeVersion: nativeNodeSurface.native_package_version(),\n mcp: {\n externalDetectionBatch: {\n ingestion: \"path-only\" as const,\n version: EXTERNAL_DETECTION_BATCH_VERSION,\n },\n formats: [\"docx\", \"pdf\", \"text\"] as const,\n sessionMode: service.sessionMode,\n tools: MCP_TOOL_NAMES,\n transport: \"stdio\" as const,\n },\n };\n return {\n content: [{ type: \"text\" as const, text: JSON.stringify(value) }],\n structuredContent: value,\n };\n};\n\nconst externalDetectionErrorResult = (error: unknown) => {\n const failure = externalDetectionFailure(\n error,\n EXTERNAL_DETECTION_FAILURES.operationFailed,\n );\n const value = { errorCode: failure.code, message: failure.message };\n return {\n isError: true,\n content: [{ type: \"text\" as const, text: JSON.stringify(value) }],\n structuredContent: value,\n };\n};\n\nexport const createAnonymizeMcpServer = (\n service: LocalAnonymizeService,\n): McpServer => {\n const server = new McpServer(\n {\n name: \"stella-anonymize-local\",\n version: nativeNodeSurface.native_package_version(),\n },\n {\n instructions:\n \"All tools accept local paths only. Never request or return document contents or session mappings. Outputs must be new explicit paths inside configured roots.\",\n },\n );\n server.registerTool(\n \"capabilities\",\n {\n description:\n \"Return the public runtime capability manifest and MCP surface metadata.\",\n inputSchema: z.object({}),\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n },\n },\n async () => capabilitiesResult(service),\n );\n server.registerTool(\n \"anonymize_text_file\",\n {\n description: \"Anonymize a local UTF-8 text file into a new local file.\",\n inputSchema: textInput,\n annotations: { destructiveHint: false, idempotentHint: false },\n },\n async (input) => result(await service.anonymizeText(input)),\n );\n server.registerTool(\n \"restore_text_file\",\n {\n description: \"Restore a text file using the configured session store.\",\n inputSchema: restoreInput,\n annotations: { destructiveHint: false, idempotentHint: false },\n },\n async (input) => result(await service.restoreText(input)),\n );\n server.registerTool(\n \"anonymize_text_file_with_external_detections\",\n {\n description:\n \"Anonymize a local UTF-8 text file with a provider-neutral ExternalDetectionBatch v1 JSON sidecar into a new local file.\",\n inputSchema: externalDetectionTextInput,\n annotations: { destructiveHint: false, idempotentHint: false },\n },\n async (input) => {\n try {\n return result(await service.anonymizeTextWithExternalDetections(input));\n } catch (error) {\n return externalDetectionErrorResult(error);\n }\n },\n );\n server.registerTool(\n \"anonymize_docx_file\",\n {\n description:\n \"Structure-preservingly anonymize a local DOCX into a new local DOCX.\",\n inputSchema: docxInput,\n annotations: { destructiveHint: false, idempotentHint: false },\n },\n async (input) => result(await service.anonymizeDocx(input)),\n );\n server.registerTool(\n \"anonymize_pdf_file\",\n {\n description:\n \"Destructively raster-anonymize a local PDF into a fresh image-only PDF. Returns aggregate verification only; it does not claim perfect OCR or detector recall.\",\n inputSchema: pdfInput,\n annotations: { destructiveHint: false, idempotentHint: false },\n },\n async (input) => result(await service.anonymizePdf(input)),\n );\n server.registerTool(\n \"restore_docx_file\",\n {\n description: \"Restore a DOCX using the configured session store.\",\n inputSchema: docxRestoreInput,\n annotations: { destructiveHint: false, idempotentHint: false },\n },\n async (input) => result(await service.restoreDocx(input)),\n );\n server.registerTool(\n \"inspect_docx_file\",\n {\n description:\n \"Return only aggregate DOCX coverage and block counts; never document text.\",\n inputSchema: z.object({\n inputPath: z.string().min(1).max(PATH_MAX_CHARACTERS),\n }),\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n },\n },\n async ({ inputPath }) => result(await service.inspectDocx(inputPath)),\n );\n return server;\n};\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stll/anonymize-mcp",
3
- "version": "2.4.0",
3
+ "version": "2.4.2",
4
4
  "description": "Path-only local MCP server for stella PII anonymization",
5
5
  "type": "module",
6
6
  "bin": {
@@ -39,9 +39,9 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "@modelcontextprotocol/sdk": "^1.29.0",
42
- "@stll/anonymize": "2.4.0",
43
- "@stll/anonymize-docx": "^2.4.0",
44
- "@stll/anonymize-pdf": "2.4.0",
42
+ "@stll/anonymize": "2.4.2",
43
+ "@stll/anonymize-docx": "^2.4.2",
44
+ "@stll/anonymize-pdf": "2.4.2",
45
45
  "fs-native-extensions": "^1.5.0",
46
46
  "zod": "^4.4.3"
47
47
  },