From 6fc2257716ef11f1ba603a65e93df77f3ba4e69c Mon Sep 17 00:00:00 2001 From: MocA-Love Date: Sat, 18 Apr 2026 17:26:10 +0900 Subject: [PATCH 1/3] =?UTF-8?q?fix(desktop):=20simple-git=203.36+=20?= =?UTF-8?q?=E3=81=AE=20PAGER=20env=20=E3=83=96=E3=83=AD=E3=83=83=E3=82=AF?= =?UTF-8?q?=E3=82=92=E5=9B=9E=E9=81=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ユーザーのシェル環境に PAGER や EDITOR 等が設定されていると、 simple-git 3.36+ の block-unsafe-operations プラグインが `Use of "PAGER" is not permitted without enabling allowUnsafePager` を投げて、ワークスペースの削除・worktree 操作等がすべて失敗する。 upstream v1.5.5 の同梱 simple-git が 3.36.0 に上がった結果、 `PAGER=less` をログインシェルで設定しているユーザーで発生していた。 simple-git が弾く env キー (PAGER/EDITOR/GIT_* 系) は このアプリの git 操作では不要なので、env に詰める前に除外する。 --- .../routers/workspaces/utils/git-client.ts | 13 ++++-- .../routers/workspaces/utils/shell-env.ts | 43 +++++++++++++++++++ 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/apps/desktop/src/lib/trpc/routers/workspaces/utils/git-client.ts b/apps/desktop/src/lib/trpc/routers/workspaces/utils/git-client.ts index b19235050e3..1abc9c31adf 100644 --- a/apps/desktop/src/lib/trpc/routers/workspaces/utils/git-client.ts +++ b/apps/desktop/src/lib/trpc/routers/workspaces/utils/git-client.ts @@ -4,13 +4,16 @@ import { execFile, } from "node:child_process"; import simpleGit, { type SimpleGit } from "simple-git"; -import { getProcessEnvWithShellPath } from "./shell-env"; +import { + getProcessEnvWithShellPath, + stripSimpleGitUnsafeEnv, +} from "./shell-env"; export async function getSimpleGitWithShellPath( repoPath?: string, ): Promise { const git = repoPath ? simpleGit(repoPath) : simpleGit(); - git.env(await getProcessEnvWithShellPath()); + git.env(stripSimpleGitUnsafeEnv(await getProcessEnvWithShellPath())); return git; } @@ -45,8 +48,10 @@ async function execGitWithShellPathWithEncoding< stdout: TEncoding extends "buffer" ? Buffer : string; stderr: TEncoding extends "buffer" ? Buffer : string; }> { - const env = await getProcessEnvWithShellPath( - options?.env ? { ...process.env, ...options.env } : process.env, + const env = stripSimpleGitUnsafeEnv( + await getProcessEnvWithShellPath( + options?.env ? { ...process.env, ...options.env } : process.env, + ), ); return new Promise((resolve, reject) => { diff --git a/apps/desktop/src/lib/trpc/routers/workspaces/utils/shell-env.ts b/apps/desktop/src/lib/trpc/routers/workspaces/utils/shell-env.ts index 5222791387d..36abf45c1d9 100644 --- a/apps/desktop/src/lib/trpc/routers/workspaces/utils/shell-env.ts +++ b/apps/desktop/src/lib/trpc/routers/workspaces/utils/shell-env.ts @@ -148,6 +148,49 @@ function copyStringEnv( return env; } +/** + * Env var names flagged as "unsafe" by simple-git's block-unsafe-operations + * plugin (v3.36+). If present in the env handed to simple-git, it throws + * `Use of "X" is not permitted without enabling allowUnsafeY` — e.g. a user + * shell with `PAGER=less` blocks every simple-git call. + * + * None of these are required for the git operations this app performs, so we + * strip them rather than toggling allowUnsafe flags. + */ +const SIMPLE_GIT_UNSAFE_ENV_KEYS = new Set([ + "EDITOR", + "PAGER", + "PREFIX", + "SSH_ASKPASS", + "GIT_ASKPASS", + "GIT_CONFIG", + "GIT_CONFIG_COUNT", + "GIT_CONFIG_GLOBAL", + "GIT_CONFIG_SYSTEM", + "GIT_EDITOR", + "GIT_EXEC_PATH", + "GIT_EXTERNAL_DIFF", + "GIT_PAGER", + "GIT_PROXY_COMMAND", + "GIT_SEQUENCE_EDITOR", + "GIT_SSH", + "GIT_SSH_COMMAND", + "GIT_TEMPLATE_DIR", +]); + +export function stripSimpleGitUnsafeEnv( + env: Record, +): Record { + const result: Record = {}; + for (const [key, value] of Object.entries(env)) { + const upper = key.toUpperCase(); + if (SIMPLE_GIT_UNSAFE_ENV_KEYS.has(upper)) continue; + if (/^GIT_CONFIG_(KEY|VALUE)_\d+$/.test(upper)) continue; + result[key] = value; + } + return result; +} + /** * Returns process env merged with missing variables from the user's shell. * Existing values always win so Electron/app-managed vars remain intact. From e56ed5fd6ae7d31a7b51310ca3e71d08fcd64e3f Mon Sep 17 00:00:00 2001 From: MocA-Love Date: Sat, 18 Apr 2026 18:12:59 +0900 Subject: [PATCH 2/3] fix(git): preserve env-backed simple-git behavior --- .../src/lib/trpc/routers/projects/projects.ts | 13 +- .../routers/workspaces/utils/git-client.ts | 66 +++++- .../routers/workspaces/utils/shell-env.ts | 43 ---- packages/host-service/src/runtime/git/git.ts | 16 +- .../src/runtime/git/simple-git.ts | 48 +++++ .../src/trpc/router/project/project.ts | 14 +- .../workspace-creation/workspace-creation.ts | 12 +- .../src/trpc/router/workspace/workspace.ts | 4 +- packages/shared/package.json | 4 + packages/shared/src/simple-git-unsafe.ts | 198 ++++++++++++++++++ 10 files changed, 342 insertions(+), 76 deletions(-) create mode 100644 packages/host-service/src/runtime/git/simple-git.ts create mode 100644 packages/shared/src/simple-git-unsafe.ts diff --git a/apps/desktop/src/lib/trpc/routers/projects/projects.ts b/apps/desktop/src/lib/trpc/routers/projects/projects.ts index 73d8c6598ab..935a29990f1 100644 --- a/apps/desktop/src/lib/trpc/routers/projects/projects.ts +++ b/apps/desktop/src/lib/trpc/routers/projects/projects.ts @@ -25,7 +25,7 @@ import { } from "main/lib/project-icons"; import { getWorkspaceRuntimeRegistry } from "main/lib/workspace-runtime"; import { PROJECT_COLOR_VALUES } from "shared/constants/project-colors"; -import simpleGit, { type SimpleGitProgressEvent } from "simple-git"; +import type { SimpleGitProgressEvent } from "simple-git"; import { z } from "zod"; import { publicProcedure, router } from "../.."; import { resolveDefaultEditor } from "../external"; @@ -45,11 +45,11 @@ import { refreshDefaultBranch, sanitizeAuthorPrefix, } from "../workspaces/utils/git"; -import { getSimpleGitWithShellPath } from "../workspaces/utils/git-client"; import { - execWithShellEnv, - getProcessEnvWithShellPath, -} from "../workspaces/utils/shell-env"; + createSimpleGitWithShellPath, + getSimpleGitWithShellPath, +} from "../workspaces/utils/git-client"; +import { execWithShellEnv } from "../workspaces/utils/shell-env"; import { getDefaultProjectColor } from "./utils/colors"; import { discoverAndSaveProjectIcon } from "./utils/favicon-discovery"; import { fetchGitHubOwner, getGitHubAvatarUrl } from "./utils/github"; @@ -1493,7 +1493,7 @@ export const createProjectsRouter = (getWindow: () => BrowserWindow | null) => { `Preparing clone into ${basename(clonePath)}`, ); try { - const gitWithProgress = simpleGit({ + const gitWithProgress = await createSimpleGitWithShellPath({ abort: abortController.signal, progress: (event: SimpleGitProgressEvent) => { emitCloneEvent({ @@ -1507,7 +1507,6 @@ export const createProjectsRouter = (getWindow: () => BrowserWindow | null) => { }); }, }); - gitWithProgress.env(await getProcessEnvWithShellPath()); emitCloneLog( cloneId, `Cloning ${redactGitCredentials(input.url)}`, diff --git a/apps/desktop/src/lib/trpc/routers/workspaces/utils/git-client.ts b/apps/desktop/src/lib/trpc/routers/workspaces/utils/git-client.ts index 1abc9c31adf..1a4a8b2fcd1 100644 --- a/apps/desktop/src/lib/trpc/routers/workspaces/utils/git-client.ts +++ b/apps/desktop/src/lib/trpc/routers/workspaces/utils/git-client.ts @@ -3,18 +3,64 @@ import { type ExecFileOptionsWithStringEncoding, execFile, } from "node:child_process"; -import simpleGit, { type SimpleGit } from "simple-git"; import { - getProcessEnvWithShellPath, - stripSimpleGitUnsafeEnv, -} from "./shell-env"; + buildSimpleGitUnsafeOptions, + type SimpleGitUnsafeOptions, +} from "@superset/shared/simple-git-unsafe"; +import simpleGit, { type SimpleGit, type SimpleGitProgressEvent } from "simple-git"; +import { getProcessEnvWithShellPath } from "./shell-env"; + +interface CreateSimpleGitWithShellPathOptions { + abort?: AbortSignal; + baseEnv?: NodeJS.ProcessEnv; + progress?: (event: SimpleGitProgressEvent) => void; + repoPath?: string; +} + +function createSimpleGitWithEnv( + env: Record, + options: Omit = {}, +): SimpleGit { + const unsafe = buildSimpleGitUnsafeOptions(env); + const gitOptions: { + abort?: AbortSignal; + baseDir?: string; + progress?: (event: SimpleGitProgressEvent) => void; + unsafe?: SimpleGitUnsafeOptions; + } = {}; + + if (options.abort) { + gitOptions.abort = options.abort; + } + if (options.progress) { + gitOptions.progress = options.progress; + } + if (options.repoPath) { + gitOptions.baseDir = options.repoPath; + } + if (unsafe) { + gitOptions.unsafe = unsafe; + } + + const git = + Object.keys(gitOptions).length > 0 + ? simpleGit(gitOptions as never) + : simpleGit(); + git.env(env); + return git; +} + +export async function createSimpleGitWithShellPath( + options: CreateSimpleGitWithShellPathOptions = {}, +): Promise { + const env = await getProcessEnvWithShellPath(options.baseEnv ?? process.env); + return createSimpleGitWithEnv(env, options); +} export async function getSimpleGitWithShellPath( repoPath?: string, ): Promise { - const git = repoPath ? simpleGit(repoPath) : simpleGit(); - git.env(stripSimpleGitUnsafeEnv(await getProcessEnvWithShellPath())); - return git; + return createSimpleGitWithShellPath({ repoPath }); } export async function execGitWithShellPath( @@ -48,10 +94,8 @@ async function execGitWithShellPathWithEncoding< stdout: TEncoding extends "buffer" ? Buffer : string; stderr: TEncoding extends "buffer" ? Buffer : string; }> { - const env = stripSimpleGitUnsafeEnv( - await getProcessEnvWithShellPath( - options?.env ? { ...process.env, ...options.env } : process.env, - ), + const env = await getProcessEnvWithShellPath( + options?.env ? { ...process.env, ...options.env } : process.env, ); return new Promise((resolve, reject) => { diff --git a/apps/desktop/src/lib/trpc/routers/workspaces/utils/shell-env.ts b/apps/desktop/src/lib/trpc/routers/workspaces/utils/shell-env.ts index 36abf45c1d9..5222791387d 100644 --- a/apps/desktop/src/lib/trpc/routers/workspaces/utils/shell-env.ts +++ b/apps/desktop/src/lib/trpc/routers/workspaces/utils/shell-env.ts @@ -148,49 +148,6 @@ function copyStringEnv( return env; } -/** - * Env var names flagged as "unsafe" by simple-git's block-unsafe-operations - * plugin (v3.36+). If present in the env handed to simple-git, it throws - * `Use of "X" is not permitted without enabling allowUnsafeY` — e.g. a user - * shell with `PAGER=less` blocks every simple-git call. - * - * None of these are required for the git operations this app performs, so we - * strip them rather than toggling allowUnsafe flags. - */ -const SIMPLE_GIT_UNSAFE_ENV_KEYS = new Set([ - "EDITOR", - "PAGER", - "PREFIX", - "SSH_ASKPASS", - "GIT_ASKPASS", - "GIT_CONFIG", - "GIT_CONFIG_COUNT", - "GIT_CONFIG_GLOBAL", - "GIT_CONFIG_SYSTEM", - "GIT_EDITOR", - "GIT_EXEC_PATH", - "GIT_EXTERNAL_DIFF", - "GIT_PAGER", - "GIT_PROXY_COMMAND", - "GIT_SEQUENCE_EDITOR", - "GIT_SSH", - "GIT_SSH_COMMAND", - "GIT_TEMPLATE_DIR", -]); - -export function stripSimpleGitUnsafeEnv( - env: Record, -): Record { - const result: Record = {}; - for (const [key, value] of Object.entries(env)) { - const upper = key.toUpperCase(); - if (SIMPLE_GIT_UNSAFE_ENV_KEYS.has(upper)) continue; - if (/^GIT_CONFIG_(KEY|VALUE)_\d+$/.test(upper)) continue; - result[key] = value; - } - return result; -} - /** * Returns process env merged with missing variables from the user's shell. * Existing values always win so Electron/app-managed vars remain intact. diff --git a/packages/host-service/src/runtime/git/git.ts b/packages/host-service/src/runtime/git/git.ts index 40009cab3cd..6e13281d269 100644 --- a/packages/host-service/src/runtime/git/git.ts +++ b/packages/host-service/src/runtime/git/git.ts @@ -1,19 +1,25 @@ -import simpleGit from "simple-git"; - import type { GitCredentialProvider, GitFactory } from "./types"; +import { createSimpleGitWithEnv } from "./simple-git"; import { getRemoteUrl } from "./utils"; export function createGitFactory(provider: GitCredentialProvider): GitFactory { return async (repoPath: string) => { const initialCredentials = await provider.getCredentials(null); - const git = simpleGit(repoPath).env(initialCredentials.env); + const git = createSimpleGitWithEnv({ + baseDir: repoPath, + env: initialCredentials.env, + }); const remoteUrl = await getRemoteUrl(git); const credentials = await provider.getCredentials(remoteUrl); - - return git.env({ + const env = { ...initialCredentials.env, ...credentials.env, GIT_OPTIONAL_LOCKS: "0", + }; + + return createSimpleGitWithEnv({ + baseDir: repoPath, + env, }); }; } diff --git a/packages/host-service/src/runtime/git/simple-git.ts b/packages/host-service/src/runtime/git/simple-git.ts new file mode 100644 index 00000000000..3a0c5796e5b --- /dev/null +++ b/packages/host-service/src/runtime/git/simple-git.ts @@ -0,0 +1,48 @@ +import { + buildSimpleGitUnsafeOptions, + type SimpleGitUnsafeOptions, +} from "@superset/shared/simple-git-unsafe"; +import simpleGit, { type SimpleGit } from "simple-git"; + +interface CreateSimpleGitWithEnvOptions { + baseDir?: string; + env?: NodeJS.ProcessEnv | Record; +} + +function copyStringEnv( + baseEnv: NodeJS.ProcessEnv | Record = process.env, +): Record { + const env: Record = {}; + + for (const [key, value] of Object.entries(baseEnv)) { + if (typeof value === "string") { + env[key] = value; + } + } + + return env; +} + +export function createSimpleGitWithEnv( + options: CreateSimpleGitWithEnvOptions = {}, +): SimpleGit { + const env = copyStringEnv(options.env ?? process.env); + const unsafe = buildSimpleGitUnsafeOptions(env); + const gitOptions: { + baseDir?: string; + unsafe?: SimpleGitUnsafeOptions; + } = {}; + + if (options.baseDir) { + gitOptions.baseDir = options.baseDir; + } + if (unsafe) { + gitOptions.unsafe = unsafe; + } + + const git = + Object.keys(gitOptions).length > 0 + ? simpleGit(gitOptions as never) + : simpleGit(); + return git.env(env); +} diff --git a/packages/host-service/src/trpc/router/project/project.ts b/packages/host-service/src/trpc/router/project/project.ts index 1a9b23b7d41..857e1df163f 100644 --- a/packages/host-service/src/trpc/router/project/project.ts +++ b/packages/host-service/src/trpc/router/project/project.ts @@ -2,8 +2,8 @@ import { existsSync, rmSync, statSync } from "node:fs"; import { basename, join, resolve } from "node:path"; import { TRPCError } from "@trpc/server"; import { eq } from "drizzle-orm"; -import simpleGit from "simple-git"; import { z } from "zod"; +import { createSimpleGitWithEnv } from "../../../runtime/git/simple-git"; import { projects, workspaces } from "../../../db/schema"; import { parseGitHubRemote } from "../../../runtime/pull-requests/utils/parse-github-remote"; import { protectedProcedure, router } from "../../index"; @@ -162,7 +162,7 @@ async function importExistingRepo( }); } - const git = simpleGit(localPath); + const git = createSimpleGitWithEnv({ baseDir: localPath }); let gitRoot: string; try { @@ -174,7 +174,9 @@ async function importExistingRepo( }); } - const remotes = await getGitHubRemotes(simpleGit(gitRoot)); + const remotes = await getGitHubRemotes( + createSimpleGitWithEnv({ baseDir: gitRoot }), + ); const matchingRemote = findMatchingRemote(remotes, expectedSlug); if (!matchingRemote) { @@ -230,7 +232,7 @@ async function cloneRepo( } try { - await simpleGit().clone(repoCloneUrl, targetPath); + await createSimpleGitWithEnv().clone(repoCloneUrl, targetPath); } catch (err) { if (existsSync(targetPath)) { rmSync(targetPath, { recursive: true, force: true }); @@ -241,7 +243,9 @@ async function cloneRepo( }); } - const remotes = await getGitHubRemotes(simpleGit(targetPath)); + const remotes = await getGitHubRemotes( + createSimpleGitWithEnv({ baseDir: targetPath }), + ); const matchingRemote = findMatchingRemote(remotes, expectedSlug); if (!matchingRemote) { diff --git a/packages/host-service/src/trpc/router/workspace-creation/workspace-creation.ts b/packages/host-service/src/trpc/router/workspace-creation/workspace-creation.ts index 0740a0210d6..e14bb30cc83 100644 --- a/packages/host-service/src/trpc/router/workspace-creation/workspace-creation.ts +++ b/packages/host-service/src/trpc/router/workspace-creation/workspace-creation.ts @@ -3,9 +3,9 @@ import { dirname, join, resolve, sep } from "node:path"; import { getDeviceName, getHashedDeviceId } from "@superset/shared/device-info"; import { TRPCError } from "@trpc/server"; import { and, eq } from "drizzle-orm"; -import simpleGit from "simple-git"; import { z } from "zod"; import { projects, workspaces } from "../../../db/schema"; +import { createSimpleGitWithEnv } from "../../../runtime/git/simple-git"; import { asLocalRef, asRemoteRef, @@ -732,7 +732,10 @@ export const workspaceCreationRouter = router({ if (!existsSync(repoPath)) { mkdirSync(dirname(repoPath), { recursive: true }); - await simpleGit().clone(cloudProject.repoCloneUrl, repoPath); + await createSimpleGitWithEnv().clone( + cloudProject.repoCloneUrl, + repoPath, + ); } localProject = ctx.db @@ -1073,7 +1076,10 @@ export const workspaceCreationRouter = router({ const repoPath = join(homeDir, ".superset", "repos", input.projectId); if (!existsSync(repoPath)) { mkdirSync(dirname(repoPath), { recursive: true }); - await simpleGit().clone(cloudProject.repoCloneUrl, repoPath); + await createSimpleGitWithEnv().clone( + cloudProject.repoCloneUrl, + repoPath, + ); } localProject = ctx.db .insert(projects) diff --git a/packages/host-service/src/trpc/router/workspace/workspace.ts b/packages/host-service/src/trpc/router/workspace/workspace.ts index 36424da116e..49adaf9beb9 100644 --- a/packages/host-service/src/trpc/router/workspace/workspace.ts +++ b/packages/host-service/src/trpc/router/workspace/workspace.ts @@ -3,9 +3,9 @@ import { dirname, join } from "node:path"; import { getDeviceName, getHashedDeviceId } from "@superset/shared/device-info"; import { TRPCError } from "@trpc/server"; import { eq } from "drizzle-orm"; -import simpleGit from "simple-git"; import { z } from "zod"; import { projects, workspaces } from "../../../db/schema"; +import { createSimpleGitWithEnv } from "../../../runtime/git/simple-git"; import { protectedProcedure, router } from "../../index"; export const workspaceRouter = router({ @@ -64,7 +64,7 @@ export const workspaceRouter = router({ if (!existsSync(repoPath)) { mkdirSync(dirname(repoPath), { recursive: true }); - await simpleGit().clone(cloudProject.repoCloneUrl, repoPath); + await createSimpleGitWithEnv().clone(cloudProject.repoCloneUrl, repoPath); } const inserted = ctx.db diff --git a/packages/shared/package.json b/packages/shared/package.json index ea93057b6a6..bb5c03629d2 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -67,6 +67,10 @@ "./shell-ready-scanner": { "types": "./src/shell-ready-scanner.ts", "default": "./src/shell-ready-scanner.ts" + }, + "./simple-git-unsafe": { + "types": "./src/simple-git-unsafe.ts", + "default": "./src/simple-git-unsafe.ts" } }, "scripts": { diff --git a/packages/shared/src/simple-git-unsafe.ts b/packages/shared/src/simple-git-unsafe.ts new file mode 100644 index 00000000000..4f4eeeef033 --- /dev/null +++ b/packages/shared/src/simple-git-unsafe.ts @@ -0,0 +1,198 @@ +export interface SimpleGitUnsafeOptions { + allowUnsafeAlias?: true; + allowUnsafeAskPass?: true; + allowUnsafeConfigEnvCount?: true; + allowUnsafeConfigPaths?: true; + allowUnsafeCredentialHelper?: true; + allowUnsafeDiffExternal?: true; + allowUnsafeDiffTextConv?: true; + allowUnsafeEditor?: true; + allowUnsafeFilter?: true; + allowUnsafeFsMonitor?: true; + allowUnsafeGitProxy?: true; + allowUnsafeGpgProgram?: true; + allowUnsafeHooksPath?: true; + allowUnsafeMergeDriver?: true; + allowUnsafePack?: true; + allowUnsafePager?: true; + allowUnsafeProtocolOverride?: true; + allowUnsafeSshCommand?: true; + allowUnsafeTemplateDir?: true; +} + +const SIMPLE_GIT_UNSAFE_ENV_TO_OPTION = { + EDITOR: "allowUnsafeEditor", + GIT_ASKPASS: "allowUnsafeAskPass", + GIT_CONFIG: "allowUnsafeConfigPaths", + GIT_CONFIG_COUNT: "allowUnsafeConfigEnvCount", + GIT_CONFIG_GLOBAL: "allowUnsafeConfigPaths", + GIT_CONFIG_SYSTEM: "allowUnsafeConfigPaths", + GIT_EDITOR: "allowUnsafeEditor", + GIT_EXEC_PATH: "allowUnsafeConfigPaths", + GIT_EXTERNAL_DIFF: "allowUnsafeDiffExternal", + GIT_PAGER: "allowUnsafePager", + GIT_PROXY_COMMAND: "allowUnsafeGitProxy", + GIT_SEQUENCE_EDITOR: "allowUnsafeEditor", + GIT_SSH: "allowUnsafeSshCommand", + GIT_SSH_COMMAND: "allowUnsafeSshCommand", + GIT_TEMPLATE_DIR: "allowUnsafeTemplateDir", + PAGER: "allowUnsafePager", + PREFIX: "allowUnsafeConfigPaths", + SSH_ASKPASS: "allowUnsafeAskPass", +} as const satisfies Record; + +function escapeRegex(value: string): string { + return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} + +function createUnsafeConfigPattern(value: string): RegExp { + return new RegExp(`^\\s*${escapeRegex(value.toLowerCase())}`); +} + +function createExpandedUnsafeConfigPattern(value: string): RegExp { + const escaped = escapeRegex(value.toLowerCase()).replace( + /\\\./g, + "(?:\\\\..+)?\\\\.", + ); + return new RegExp(`^\\s*${escaped}`); +} + +const SIMPLE_GIT_UNSAFE_CONFIG_PATTERNS = [ + { + pattern: createUnsafeConfigPattern("alias"), + option: "allowUnsafeAlias", + }, + { + pattern: createUnsafeConfigPattern("core.askPass"), + option: "allowUnsafeAskPass", + }, + { + pattern: createUnsafeConfigPattern("core.editor"), + option: "allowUnsafeEditor", + }, + { + pattern: createUnsafeConfigPattern("core.fsmonitor"), + option: "allowUnsafeFsMonitor", + }, + { + pattern: createUnsafeConfigPattern("core.gitProxy"), + option: "allowUnsafeGitProxy", + }, + { + pattern: createUnsafeConfigPattern("core.hooksPath"), + option: "allowUnsafeHooksPath", + }, + { + pattern: createUnsafeConfigPattern("core.pager"), + option: "allowUnsafePager", + }, + { + pattern: createUnsafeConfigPattern("core.sshCommand"), + option: "allowUnsafeSshCommand", + }, + { + pattern: createExpandedUnsafeConfigPattern("credential.helper"), + option: "allowUnsafeCredentialHelper", + }, + { + pattern: createExpandedUnsafeConfigPattern("diff.command"), + option: "allowUnsafeDiffExternal", + }, + { + pattern: createUnsafeConfigPattern("diff.external"), + option: "allowUnsafeDiffExternal", + }, + { + pattern: createExpandedUnsafeConfigPattern("diff.textconv"), + option: "allowUnsafeDiffTextConv", + }, + { + pattern: createExpandedUnsafeConfigPattern("filter.clean"), + option: "allowUnsafeFilter", + }, + { + pattern: createExpandedUnsafeConfigPattern("filter.smudge"), + option: "allowUnsafeFilter", + }, + { + pattern: createExpandedUnsafeConfigPattern("gpg.program"), + option: "allowUnsafeGpgProgram", + }, + { + pattern: createUnsafeConfigPattern("init.templateDir"), + option: "allowUnsafeTemplateDir", + }, + { + pattern: createExpandedUnsafeConfigPattern("merge.driver"), + option: "allowUnsafeMergeDriver", + }, + { + pattern: createExpandedUnsafeConfigPattern("mergetool.path"), + option: "allowUnsafeMergeDriver", + }, + { + pattern: createExpandedUnsafeConfigPattern("mergetool.cmd"), + option: "allowUnsafeMergeDriver", + }, + { + pattern: createExpandedUnsafeConfigPattern("protocol.allow"), + option: "allowUnsafeProtocolOverride", + }, + { + pattern: createExpandedUnsafeConfigPattern("remote.receivepack"), + option: "allowUnsafePack", + }, + { + pattern: createExpandedUnsafeConfigPattern("remote.uploadpack"), + option: "allowUnsafePack", + }, + { + pattern: createUnsafeConfigPattern("sequence.editor"), + option: "allowUnsafeEditor", + }, +] as const satisfies ReadonlyArray<{ + pattern: RegExp; + option: keyof SimpleGitUnsafeOptions; +}>; + +function markUnsafeOption( + options: SimpleGitUnsafeOptions, + option: keyof SimpleGitUnsafeOptions, +): void { + options[option] = true; +} + +export function buildSimpleGitUnsafeOptions( + env: Record, +): SimpleGitUnsafeOptions | undefined { + const unsafe: SimpleGitUnsafeOptions = {}; + const upperEnv = Object.fromEntries( + Object.entries(env).map(([key, value]) => [key.toUpperCase(), value]), + ); + + for (const key of Object.keys(upperEnv)) { + const option = SIMPLE_GIT_UNSAFE_ENV_TO_OPTION[key]; + if (option) { + markUnsafeOption(unsafe, option); + } + } + + const count = Number.parseInt(upperEnv.GIT_CONFIG_COUNT ?? "", 10); + if (Number.isFinite(count) && count > 0) { + for (let index = 0; index < count; index += 1) { + const configKey = upperEnv[`GIT_CONFIG_KEY_${index}`]; + if (!configKey) { + continue; + } + + const normalizedConfigKey = configKey.trim().toLowerCase(); + for (const { pattern, option } of SIMPLE_GIT_UNSAFE_CONFIG_PATTERNS) { + if (pattern.test(normalizedConfigKey)) { + markUnsafeOption(unsafe, option); + } + } + } + } + + return Object.keys(unsafe).length > 0 ? unsafe : undefined; +} From 01dc63b3f922822c6f2ea759693e4376b347bbb0 Mon Sep 17 00:00:00 2001 From: MocA-Love Date: Sat, 18 Apr 2026 18:39:01 +0900 Subject: [PATCH 3/3] fix(git): address PR CI failures --- .../src/lib/trpc/routers/workspaces/utils/git-client.ts | 5 ++++- packages/host-service/src/runtime/git/git.ts | 2 +- packages/host-service/src/trpc/router/project/project.ts | 2 +- .../src/trpc/router/workspace-creation/workspace-creation.ts | 2 +- packages/host-service/src/trpc/router/workspace/workspace.ts | 5 ++++- packages/shared/src/simple-git-unsafe.ts | 5 ++++- 6 files changed, 15 insertions(+), 6 deletions(-) diff --git a/apps/desktop/src/lib/trpc/routers/workspaces/utils/git-client.ts b/apps/desktop/src/lib/trpc/routers/workspaces/utils/git-client.ts index 1a4a8b2fcd1..246a2281bb8 100644 --- a/apps/desktop/src/lib/trpc/routers/workspaces/utils/git-client.ts +++ b/apps/desktop/src/lib/trpc/routers/workspaces/utils/git-client.ts @@ -7,7 +7,10 @@ import { buildSimpleGitUnsafeOptions, type SimpleGitUnsafeOptions, } from "@superset/shared/simple-git-unsafe"; -import simpleGit, { type SimpleGit, type SimpleGitProgressEvent } from "simple-git"; +import simpleGit, { + type SimpleGit, + type SimpleGitProgressEvent, +} from "simple-git"; import { getProcessEnvWithShellPath } from "./shell-env"; interface CreateSimpleGitWithShellPathOptions { diff --git a/packages/host-service/src/runtime/git/git.ts b/packages/host-service/src/runtime/git/git.ts index 6e13281d269..da0febfbd4e 100644 --- a/packages/host-service/src/runtime/git/git.ts +++ b/packages/host-service/src/runtime/git/git.ts @@ -1,5 +1,5 @@ -import type { GitCredentialProvider, GitFactory } from "./types"; import { createSimpleGitWithEnv } from "./simple-git"; +import type { GitCredentialProvider, GitFactory } from "./types"; import { getRemoteUrl } from "./utils"; export function createGitFactory(provider: GitCredentialProvider): GitFactory { diff --git a/packages/host-service/src/trpc/router/project/project.ts b/packages/host-service/src/trpc/router/project/project.ts index 857e1df163f..d02cdee0343 100644 --- a/packages/host-service/src/trpc/router/project/project.ts +++ b/packages/host-service/src/trpc/router/project/project.ts @@ -3,8 +3,8 @@ import { basename, join, resolve } from "node:path"; import { TRPCError } from "@trpc/server"; import { eq } from "drizzle-orm"; import { z } from "zod"; -import { createSimpleGitWithEnv } from "../../../runtime/git/simple-git"; import { projects, workspaces } from "../../../db/schema"; +import { createSimpleGitWithEnv } from "../../../runtime/git/simple-git"; import { parseGitHubRemote } from "../../../runtime/pull-requests/utils/parse-github-remote"; import { protectedProcedure, router } from "../../index"; import { diff --git a/packages/host-service/src/trpc/router/workspace-creation/workspace-creation.ts b/packages/host-service/src/trpc/router/workspace-creation/workspace-creation.ts index e14bb30cc83..1f00c99608f 100644 --- a/packages/host-service/src/trpc/router/workspace-creation/workspace-creation.ts +++ b/packages/host-service/src/trpc/router/workspace-creation/workspace-creation.ts @@ -5,7 +5,6 @@ import { TRPCError } from "@trpc/server"; import { and, eq } from "drizzle-orm"; import { z } from "zod"; import { projects, workspaces } from "../../../db/schema"; -import { createSimpleGitWithEnv } from "../../../runtime/git/simple-git"; import { asLocalRef, asRemoteRef, @@ -13,6 +12,7 @@ import { resolveDefaultBranchName, resolveRef, } from "../../../runtime/git/refs"; +import { createSimpleGitWithEnv } from "../../../runtime/git/simple-git"; import { createTerminalSessionInternal } from "../../../terminal/terminal"; import type { HostServiceContext } from "../../../types"; import { protectedProcedure, router } from "../../index"; diff --git a/packages/host-service/src/trpc/router/workspace/workspace.ts b/packages/host-service/src/trpc/router/workspace/workspace.ts index 49adaf9beb9..95be2e566c5 100644 --- a/packages/host-service/src/trpc/router/workspace/workspace.ts +++ b/packages/host-service/src/trpc/router/workspace/workspace.ts @@ -64,7 +64,10 @@ export const workspaceRouter = router({ if (!existsSync(repoPath)) { mkdirSync(dirname(repoPath), { recursive: true }); - await createSimpleGitWithEnv().clone(cloudProject.repoCloneUrl, repoPath); + await createSimpleGitWithEnv().clone( + cloudProject.repoCloneUrl, + repoPath, + ); } const inserted = ctx.db diff --git a/packages/shared/src/simple-git-unsafe.ts b/packages/shared/src/simple-git-unsafe.ts index 4f4eeeef033..8cf94e37196 100644 --- a/packages/shared/src/simple-git-unsafe.ts +++ b/packages/shared/src/simple-git-unsafe.ts @@ -171,7 +171,10 @@ export function buildSimpleGitUnsafeOptions( ); for (const key of Object.keys(upperEnv)) { - const option = SIMPLE_GIT_UNSAFE_ENV_TO_OPTION[key]; + const option = + SIMPLE_GIT_UNSAFE_ENV_TO_OPTION[ + key as keyof typeof SIMPLE_GIT_UNSAFE_ENV_TO_OPTION + ]; if (option) { markUnsafeOption(unsafe, option); }