From 0de85f31c4ad0459e1aefdb91c1efeeddf669cf7 Mon Sep 17 00:00:00 2001 From: Forbiddem Date: Fri, 29 May 2026 01:05:47 +0000 Subject: [PATCH] Contain template path within the workspace (path traversal fix) resolveTemplatePath() passed the `@@x-template` field of an .arb file (and `arb-dir` / `template-arb-file` from l10n.yaml) straight to fs.readFileSync without any containment check. Both values come from workspace content, so a crafted repository could set e.g. "@@x-template": "../../../../etc/passwd" (or an absolute path) and make the extension read any file the editing user can open, outside the project root. JSON-with-string-values targets additionally have their top-level keys echoed into the Problems panel via the "Missing messages from template" diagnostic. Resolve the candidate path and reject anything that escapes the workspace folder (falling back to the document's own directory when there is no workspace folder). Legitimate in-project templates are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/messageParser.ts | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/messageParser.ts b/src/messageParser.ts index a655707..78416b2 100644 --- a/src/messageParser.ts +++ b/src/messageParser.ts @@ -204,18 +204,39 @@ export class Parser { document: vscode.TextDocument; messageList: MessageList; } & L10nYamlPathAndOptions): string | undefined { + let candidate: string | undefined; if (messageList.templatePath) { - return path.isAbsolute(messageList.templatePath) + candidate = path.isAbsolute(messageList.templatePath) ? messageList.templatePath : path.join(path.dirname(document.uri.fsPath), messageList.templatePath); } else if (l10nOptions !== undefined) { const templateRootFromOptions = l10nOptions?.['arb-dir'] ?? 'lib/l10n'; const templatePathFromOptions = l10nOptions?.['template-arb-file'] ?? 'app_en.arb'; - return path.isAbsolute(templatePathFromOptions) + candidate = path.isAbsolute(templatePathFromOptions) ? templatePathFromOptions : path.join(path.dirname(l10nYamlPath), templateRootFromOptions, templatePathFromOptions); } + + if (candidate === undefined) { + return undefined; + } + + // Security: the template path is derived from workspace content that an + // untrusted party may control — the `@@x-template` field of an .arb file, + // or `arb-dir` / `template-arb-file` in l10n.yaml — and is passed straight + // to fs.readFileSync below. Without containment, a crafted value such as + // `@@x-template: "../../../../etc/passwd"` (or an absolute path) lets a + // repository read any file the editing user can open, outside the project. + // Refuse any resolved path that escapes the workspace folder. + const resolved = path.resolve(candidate); + const workspaceRoot = path.resolve( + vscode.workspace.getWorkspaceFolder(document.uri)?.uri.fsPath + ?? path.dirname(document.uri.fsPath)); + if (resolved !== workspaceRoot && !resolved.startsWith(workspaceRoot + path.sep)) { + return undefined; + } + return resolved; } parseAndDecorate({