From 37c4f42c93466fb8f28e192155069186c1ab89e5 Mon Sep 17 00:00:00 2001 From: Kenny Date: Sun, 19 Apr 2026 15:04:11 +0800 Subject: [PATCH] fix(notify): classify npm alias notify specs as unsafe --- src/shared/bundled-notify-ownership.test.ts | 29 +++++++++++++++++++++ src/shared/bundled-notify-ownership.ts | 19 +++++++++++--- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/src/shared/bundled-notify-ownership.test.ts b/src/shared/bundled-notify-ownership.test.ts index deb08912c..0690a86a1 100644 --- a/src/shared/bundled-notify-ownership.test.ts +++ b/src/shared/bundled-notify-ownership.test.ts @@ -137,6 +137,35 @@ describe("ensureBundledNotifyOwnership", () => { expect(readConfig(userConfigPath).plugin).toEqual([["npm:@custom/opencode-notify@1.2.3", {}], "oh-my-openagent"]) }) + test("fails loudly for npm alias string spec targeting custom opencode-notify package", () => { + // given + const userConfigPath = join(userConfigDir, "opencode.json") + writeFileSync(userConfigPath, JSON.stringify({ plugin: ["alias@npm:@custom/opencode-notify@1.2.3", "oh-my-openagent"] }, null, 2) + "\n") + + // when + const run = () => ensureBundledNotifyOwnership({ projectDirectory: projectDir, packageRoot }) + + // then + expect(run).toThrow("Unsafe external notify plugin ownership detected") + expect(readConfig(userConfigPath).plugin).toEqual(["alias@npm:@custom/opencode-notify@1.2.3", "oh-my-openagent"]) + }) + + test("fails loudly for npm alias tuple spec targeting custom opencode-notify package", () => { + // given + const userConfigPath = join(userConfigDir, "opencode.json") + writeFileSync( + userConfigPath, + JSON.stringify({ plugin: [["alias@npm:@custom/opencode-notify@1.2.3", {}], "oh-my-openagent"] }, null, 2) + "\n", + ) + + // when + const run = () => ensureBundledNotifyOwnership({ projectDirectory: projectDir, packageRoot }) + + // then + expect(run).toThrow("Unsafe external notify plugin ownership detected") + expect(readConfig(userConfigPath).plugin).toEqual([["alias@npm:@custom/opencode-notify@1.2.3", {}], "oh-my-openagent"]) + }) + test("migrates stale bundled dist/opencode-notify file URL to canonical bundled entry", () => { // given const userConfigPath = join(userConfigDir, "opencode.json") diff --git a/src/shared/bundled-notify-ownership.ts b/src/shared/bundled-notify-ownership.ts index d9dd9202c..8b3f6732b 100644 --- a/src/shared/bundled-notify-ownership.ts +++ b/src/shared/bundled-notify-ownership.ts @@ -83,12 +83,23 @@ function stripVersionSuffix(entry: string): string { return trimmed.slice(0, versionSeparatorIndex) } -function isCustomPackageNotifyCandidate(entry: string): boolean { +function extractPackageIdentifierFromSpec(entry: string): string | null { const normalized = stripNpmPrefix(entry.trim().toLowerCase()) - if (normalized.length === 0) return false - if (normalized.includes("://")) return false + if (normalized.length === 0) return null + if (normalized.includes("://")) return null - const packageIdentifier = stripVersionSuffix(normalized) + const aliasSeparatorIndex = normalized.indexOf("@npm:") + const packageSpec = aliasSeparatorIndex >= 0 + ? normalized.slice(aliasSeparatorIndex + "@npm:".length) + : normalized + + if (packageSpec.length === 0) return null + return stripVersionSuffix(packageSpec) +} + +function isCustomPackageNotifyCandidate(entry: string): boolean { + const packageIdentifier = extractPackageIdentifierFromSpec(entry) + if (!packageIdentifier) return false if (packageIdentifier === "opencode-notify") return true if (/^@[a-z0-9._-]+\/opencode-notify$/i.test(packageIdentifier)) return true if (/^[a-z0-9._-]+\/opencode-notify$/i.test(packageIdentifier)) return true