Reject unlisted filesystem aliases before vault mutations

(src/serviceModules/FileSystemAdapters/pathAliasGuard.ts)
- Require every existing, unindexed path component to use a name listed by its parent directory.
- Accept case-only and Unicode-normalisation differences while caching positive directory listings briefly.

(src/serviceModules/FileSystemAdapters/ObsidianStorageAdapter.ts, src/serviceModules/FileSystemAdapters/ObsidianVaultAdapter.ts)
- Apply the alias boundary before text, binary, append, directory, removal, create, modify, rename, delete, and trash operations.
- Check both the source and destination of vault renames before calling the host API.

(src/serviceModules/FileSystemAdapters/pathAliasGuard.unit.spec.ts)
- Cover directory and file aliases across every mutating adapter route with synthetic host behaviour.
- Exercise real host filesystem aliases where the platform exposes them, including new files and directories below an aliased folder.
- Preserve listed names, case-only differences, indexed paths, and new-file behaviour.

(src/serviceModules/FileSystemAdapters/ObsidianStorageAdapter.unit.spec.ts, src/serviceModules/FileSystemAdapters/ObsidianVaultAdapter.unit.spec.ts)
- Extend existing adapter fixtures with the vault-index contract used by the guard.
This commit is contained in:
Kim Jansheden committed 2026-10-14 17:15:20 +02:00
1 parent 0956663f7b
commit b71cc14022
6 files changed
+367 -2

No files matched your search

@@ -3,13 +3,17 @@ import type { IStorageAdapter } from "@vrtmrz/livesync-commonlib/compat/serviceM
import { toArrayBuffer } from "@vrtmrz/livesync-commonlib/compat/serviceModules/FileAccessBase";
import type { Stat, App } from "obsidian";
import { toIntegerTimestamps } from "./sanitizeWriteOptions";
import { createPathAliasGuard, type PathAliasGuard } from "./pathAliasGuard";
/**
* Storage adapter implementation for Obsidian
*/
export class ObsidianStorageAdapter implements IStorageAdapter<Stat> {
constructor(private app: App) {}
constructor(
private app: App,
private readonly aliasGuard: PathAliasGuard = createPathAliasGuard(app)
) {}
async exists(path: string): Promise<boolean> {
return await this.app.vault.adapter.exists(path);
@@ -25,10 +29,12 @@ export class ObsidianStorageAdapter implements IStorageAdapter<Stat> {
}
async mkdir(path: string): Promise<void> {
await this.aliasGuard.assertNoAlias(path);
await this.app.vault.adapter.mkdir(path);
}
async remove(path: string): Promise<void> {
await this.aliasGuard.assertNoAlias(path);
await this.app.vault.adapter.remove(path);
}
@@ -41,14 +47,17 @@ export class ObsidianStorageAdapter implements IStorageAdapter<Stat> {
}
async write(path: string, data: string, options?: UXDataWriteOptions): Promise<void> {
await this.aliasGuard.assertNoAlias(path);
return await this.app.vault.adapter.write(path, data, toIntegerTimestamps(options));
}
async writeBinary(path: string, data: ArrayBuffer, options?: UXDataWriteOptions): Promise<void> {
await this.aliasGuard.assertNoAlias(path);
return await this.app.vault.adapter.writeBinary(path, toArrayBuffer(data), toIntegerTimestamps(options));
}
async append(path: string, data: string, options?: UXDataWriteOptions): Promise<void> {
await this.aliasGuard.assertNoAlias(path);
return await this.app.vault.adapter.append(path, data, toIntegerTimestamps(options));
}