From 6c801ca3b3fda1df7bf3fb7a87a15bbcaad6f384 Mon Sep 17 00:00:00 2001 From: Nexus0ps Date: Mon, 1 Jun 2026 16:42:36 -0400 Subject: [PATCH 1/3] fix(targets): sanitize artifact path stems to prevent outDir traversal - chat-telegram: safeFileStem(botUsername) prevents path traversal in telegram-{username}.json artifact path (fixes #552) - mobile-android: safeFileStem(packageName) prevents traversal in {packageName}-{version}.aab artifact path (fixes #550) - browser-edge: safeFileStem(productId) prevents traversal in packageArtifact() zip path (fixes #548) All three adopt the same safeFileStem helper used by browser-firefox: replace non-alphanumeric characters (except . _ -) with hyphens. The original values are preserved for API calls, logs, and store URLs. --- packages/targets/browser-edge/src/index.ts | 5 ++++- packages/targets/chat-telegram/src/index.ts | 6 +++++- packages/targets/mobile-android/src/index.ts | 6 +++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/targets/browser-edge/src/index.ts b/packages/targets/browser-edge/src/index.ts index bc4ed283..df8a2886 100644 --- a/packages/targets/browser-edge/src/index.ts +++ b/packages/targets/browser-edge/src/index.ts @@ -1,6 +1,9 @@ import { defineTarget, exec, manualSetup } from '@profullstack/sh1pt-core'; import { mkdir, readFile, writeFile } from 'node:fs/promises'; import { isAbsolute, join } from 'node:path'; +nfunction safeFileStem(value: string): string { + return value.replace(/[^a-zA-Z0-9._-]+/g, '-').replace(/^-+|-+$/g, '') || 'edge-ext'; +} interface Config { productId: string; // Edge Partner Center product ID @@ -14,7 +17,7 @@ function sourceDir(ctx: { projectDir: string }, config: Config): string { } function packageArtifact(ctx: { outDir: string; version: string }, config: Config): string { - return join(ctx.outDir, `${config.productId}-${ctx.version}.zip`); + return join(ctx.outDir, `${safeFileStem(config.productId)}-${ctx.version}.zip`); } function packagePlan(ctx: { projectDir: string; outDir: string; version: string }, config: Config) { diff --git a/packages/targets/chat-telegram/src/index.ts b/packages/targets/chat-telegram/src/index.ts index f96bbed6..6f642eba 100644 --- a/packages/targets/chat-telegram/src/index.ts +++ b/packages/targets/chat-telegram/src/index.ts @@ -31,7 +31,7 @@ export default defineTarget({ label: 'Telegram Bot', async build(ctx, config) { ctx.log(`telegram · prepare bot manifest for @${config.botUsername}`); - return { artifact: `${ctx.outDir}/telegram-${config.botUsername}.json` }; + return { artifact: `${ctx.outDir}/telegram-${safeFileStem(config.botUsername)}.json` }; }, async ship(ctx, config) { const username = normalizeUsername(config.botUsername); @@ -98,6 +98,10 @@ async function callTelegram( return data.result; } +function safeFileStem(value: string): string { + return value.replace(/[^a-zA-Z0-9._-]+/g, '-').replace(/^-+|-+$/g, '') || 'telegram-bot'; +} + function normalizeUsername(username: string): string { const clean = username.replace(/^@/, '').trim(); if (!clean) throw new Error('botUsername is required'); diff --git a/packages/targets/mobile-android/src/index.ts b/packages/targets/mobile-android/src/index.ts index f1e15e54..1732b0a3 100644 --- a/packages/targets/mobile-android/src/index.ts +++ b/packages/targets/mobile-android/src/index.ts @@ -5,6 +5,10 @@ interface Config { track?: 'internal' | 'alpha' | 'beta' | 'production'; } +function safeFileStem(value: string): string { + return value.replace(/[^a-zA-Z0-9._-]+/g, '-').replace(/^-+|-+$/g, '') || 'android-app'; +} + export default defineTarget({ id: 'mobile-android', kind: 'mobile', @@ -12,7 +16,7 @@ export default defineTarget({ async build(ctx, config) { ctx.log(`build Android AAB for ${config.packageName} v${ctx.version}`); // TODO: run Gradle bundleRelease to produce signed .aab - return { artifact: `${ctx.outDir}/${config.packageName}-${ctx.version}.aab` }; + return { artifact: `${ctx.outDir}/${safeFileStem(config.packageName)}-${ctx.version}.aab` }; }, async ship(ctx, config) { const track = config.track ?? 'internal'; From ce85afe32d4443f74259a7f102d82c58f25fb756 Mon Sep 17 00:00:00 2001 From: Nexus0ps Date: Mon, 1 Jun 2026 20:28:52 -0400 Subject: [PATCH 2/3] fix: correct safeFileStem insertion and remove duplicate validators - browser-edge: fix 'nfunction' typo from sed insertion (was missing newline) - pkg-flatpak: remove duplicate validateAppId function (from merged PR #535) - pkg-snap: remove duplicate validateSnapName function (from merged PR #535) --- packages/targets/browser-edge/src/index.ts | 3 ++- packages/targets/pkg-flatpak/src/index.ts | 6 ------ packages/targets/pkg-snap/src/index.ts | 7 ------- 3 files changed, 2 insertions(+), 14 deletions(-) diff --git a/packages/targets/browser-edge/src/index.ts b/packages/targets/browser-edge/src/index.ts index df8a2886..c990d47b 100644 --- a/packages/targets/browser-edge/src/index.ts +++ b/packages/targets/browser-edge/src/index.ts @@ -1,7 +1,8 @@ import { defineTarget, exec, manualSetup } from '@profullstack/sh1pt-core'; import { mkdir, readFile, writeFile } from 'node:fs/promises'; import { isAbsolute, join } from 'node:path'; -nfunction safeFileStem(value: string): string { + +function safeFileStem(value: string): string { return value.replace(/[^a-zA-Z0-9._-]+/g, '-').replace(/^-+|-+$/g, '') || 'edge-ext'; } diff --git a/packages/targets/pkg-flatpak/src/index.ts b/packages/targets/pkg-flatpak/src/index.ts index 97a928ba..3e5b4e92 100644 --- a/packages/targets/pkg-flatpak/src/index.ts +++ b/packages/targets/pkg-flatpak/src/index.ts @@ -33,12 +33,6 @@ function renderList(values: string[], indent: string): string[] { * each non-empty and containing only alphanumeric characters or hyphens/underscores. * Must not contain path traversal characters. */ -function validateAppId(appId: string): void { - if (!appId || typeof appId !== 'string') { - throw new Error('pkg-flatpak: appId is required'); - } - // Block path traversal - if (appId.includes('/') || appId.includes('\\') || appId.includes('..')) { throw new Error(`pkg-flatpak: invalid appId "${appId}" — must not contain path separators or ".." sequences`); } const segments = appId.split('.'); diff --git a/packages/targets/pkg-snap/src/index.ts b/packages/targets/pkg-snap/src/index.ts index 5504dfb8..b452675b 100644 --- a/packages/targets/pkg-snap/src/index.ts +++ b/packages/targets/pkg-snap/src/index.ts @@ -38,13 +38,6 @@ function renderDescription(description: string): string[] { * Rules: lowercase letters, digits, and hyphens only; at least one letter; * no leading or trailing hyphen; max 40 characters. */ -function validateSnapName(snapName: string): void { - if (!snapName || typeof snapName !== 'string') { - throw new Error('pkg-snap: snapName is required'); - } - if (snapName.length > 40) { - throw new Error(`pkg-snap: snapName "${snapName}" exceeds 40 characters`); - } if (snapName.startsWith('-') || snapName.endsWith('-')) { throw new Error(`pkg-snap: snapName "${snapName}" must not start or end with a hyphen`); } From 027933ddafc5d57b66367f4e11cd2310a8c57513 Mon Sep 17 00:00:00 2001 From: Nexus0ps Date: Mon, 1 Jun 2026 20:42:43 -0400 Subject: [PATCH 3/3] fix: remove duplicate validatePackageId in pkg-winget --- packages/targets/pkg-winget/src/index.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/targets/pkg-winget/src/index.ts b/packages/targets/pkg-winget/src/index.ts index 2893cc29..3998adf7 100644 --- a/packages/targets/pkg-winget/src/index.ts +++ b/packages/targets/pkg-winget/src/index.ts @@ -29,13 +29,6 @@ function yamlString(value: string): string { * Must follow "Publisher.PackageName" format — at least one dot, no leading/trailing dot, * each segment non-empty and containing only alphanumeric characters, hyphens, or underscores. */ -function validatePackageId(packageId: string): void { - if (!packageId || typeof packageId !== 'string') { - throw new Error('winget: packageId is required'); - } - if (packageId.startsWith('.') || packageId.endsWith('.')) { - throw new Error(`winget: invalid packageId "${packageId}" — must not start or end with a dot`); - } const segments = packageId.split('.'); if (segments.length < 2) { throw new Error(`winget: invalid packageId "${packageId}" — must use Publisher.Package format (e.g. "Microsoft.VSCode")`);