( app: App, packagePath: string, )
| 91 | } |
| 92 | |
| 93 | export async function readQuickAddPackage( |
| 94 | app: App, |
| 95 | packagePath: string, |
| 96 | ): Promise<LoadedQuickAddPackage> { |
| 97 | // Containment guard at the single read entry point, before any filesystem |
| 98 | // touch. `packagePath` is untrusted (the CLI `path=` flag, the GUI file |
| 99 | // picker): `normalizePath` collapses slashes but does NOT resolve "..", so a |
| 100 | // path like "../../../etc/passwd" (or an absolute/drive path) would otherwise |
| 101 | // reach `adapter.read` and disclose a file OUTSIDE the vault. The sibling |
| 102 | // analysePackage/analysePackagePreview probes already guard with this; the read |
| 103 | // path must too, so every current and future caller inherits the protection. |
| 104 | if (escapesVaultBoundary(packagePath)) { |
| 105 | throw new Error( |
| 106 | `Refusing to read a package outside the vault: "${packagePath}".`, |
| 107 | ); |
| 108 | } |
| 109 | |
| 110 | const normalized = normalizePath(packagePath.trim()); |
| 111 | if (!normalized) throw new Error("Package path cannot be empty."); |
| 112 | |
| 113 | const exists = await app.vault.adapter.exists(normalized); |
| 114 | if (!exists) throw new Error(`Package file not found: ${normalized}`); |
| 115 | |
| 116 | const raw = await app.vault.adapter.read(normalized); |
| 117 | const parsed = parseQuickAddPackage(raw); |
| 118 | |
| 119 | return { |
| 120 | pkg: parsed, |
| 121 | path: normalized, |
| 122 | }; |
| 123 | } |
| 124 | |
| 125 | export function parseQuickAddPackage(raw: string): QuickAddPackage { |
| 126 | let parsed: unknown; |
no test coverage detected