@@ -14,13 +14,16 @@ import { resetCommandCatalogCache } from "../load-commands.ts";
1414import { clearCommandsCache } from "./cache.ts" ;
1515import { discoverPlugins , readPackageJson } from "./discover.ts" ;
1616import {
17+ buildNpmEnv ,
1718 diffAddedDepNames ,
19+ parsePackageNameFromSpec ,
1820 pickInstalledPackageName ,
19- readSandboxPjsonDepNames ,
21+ readSandboxDeps ,
2022 resolveSandboxPackageRoot ,
2123 topLevelDepNamesFromNpmLs ,
2224 type NpmLsNode ,
2325} from "./npm-sandbox.ts" ;
26+ import { assertPluginAllowed } from "./policy.ts" ;
2427import type { UserPluginsManifest } from "./types.ts" ;
2528
2629const INIT_MANIFEST : UserPluginsManifest = {
@@ -44,7 +47,11 @@ async function readManifest(): Promise<UserPluginsManifest> {
4447 } ,
4548 } ;
4649 } catch {
47- return structuredClone ( INIT_MANIFEST ) ;
50+ throw new BailianError (
51+ `Plugin manifest at ${ path } is corrupted and could not be parsed.` ,
52+ ExitCode . GENERAL ,
53+ "Restore from backup or delete the file, then run bl plugin install again." ,
54+ ) ;
4855 }
4956}
5057
@@ -90,7 +97,7 @@ function runNpm(args: string[], cwd: string): void {
9097 const result = spawnSync ( "npm" , npmArgs , {
9198 cwd,
9299 stdio : "inherit" ,
93- env : process . env ,
100+ env : buildNpmEnv ( ) ,
94101 } ) ;
95102 if ( result . status !== 0 ) {
96103 throw new BailianError (
@@ -106,7 +113,7 @@ function runNpmJson(args: string[], cwd: string): NpmLsNode {
106113 const result = spawnSync ( "npm" , npmArgs , {
107114 cwd,
108115 encoding : "utf8" ,
109- env : process . env ,
116+ env : buildNpmEnv ( ) ,
110117 } ) ;
111118 const stdout = result . stdout ?. trim ( ) ;
112119 if ( ! stdout ) {
@@ -125,7 +132,7 @@ async function listTopLevelSandboxDeps(pluginsDir: string): Promise<string[]> {
125132 const tree = runNpmJson ( [ "ls" , "--json" , "--depth=0" ] , pluginsDir ) ;
126133 return topLevelDepNamesFromNpmLs ( tree ) ;
127134 } catch {
128- return readSandboxPjsonDepNames ( pluginsDir ) ;
135+ return readSandboxDeps ( pluginsDir ) ;
129136 }
130137}
131138
@@ -183,6 +190,7 @@ export async function listPlugins(): Promise<{
183190export async function linkPlugin ( pluginPath : string ) : Promise < void > {
184191 const root = resolve ( pluginPath ) ;
185192 const { name } = await validatePluginPackageAsync ( root ) ;
193+ assertPluginAllowed ( name ) ;
186194 const manifest = await readManifest ( ) ;
187195 const plugins = manifest . bailianCli ! . plugins ! . filter (
188196 ( p ) => ! ( p . type === "link" && p . name === name ) ,
@@ -194,6 +202,16 @@ export async function linkPlugin(pluginPath: string): Promise<void> {
194202
195203/** 安装 npm 插件到用户沙箱 */
196204export async function installPlugin ( packageSpec : string ) : Promise < string > {
205+ // 安装前先按 spec 解析包名做准入校验,避免对不被允许的包执行任何 npm 操作
206+ const specName = parsePackageNameFromSpec ( packageSpec ) ;
207+ if ( ! specName ) {
208+ throw new BailianError (
209+ `无法从 "${ packageSpec } " 识别 npm 包名(不支持 git / tarball / 本地路径安装)。目前仅支持安装官方白名单插件(@ali 作用域),暂不支持用户自定义插件。` ,
210+ ExitCode . USAGE ,
211+ ) ;
212+ }
213+ assertPluginAllowed ( specName ) ;
214+
197215 const pluginsDir = getPluginsDir ( ) ;
198216 await mkdir ( pluginsDir , { recursive : true , mode : 0o700 } ) ;
199217
@@ -202,8 +220,10 @@ export async function installPlugin(packageSpec: string): Promise<string> {
202220 }
203221
204222 const beforeDeps = await listTopLevelSandboxDeps ( pluginsDir ) ;
205-
206- runNpm ( [ "install" , packageSpec , "--save-exact" , "--no-fund" , "--no-audit" ] , pluginsDir ) ;
223+ runNpm (
224+ [ "install" , packageSpec , "--save-exact" , "--ignore-scripts" , "--no-fund" , "--no-audit" ] ,
225+ pluginsDir ,
226+ ) ;
207227
208228 const afterDeps = await listTopLevelSandboxDeps ( pluginsDir ) ;
209229 const added = diffAddedDepNames ( beforeDeps , afterDeps ) ;
@@ -223,6 +243,7 @@ export async function installPlugin(packageSpec: string): Promise<string> {
223243
224244 const root = resolveSandboxPackageRoot ( pluginsDir , packageName ) ;
225245 const { name } = await validatePluginPackageAsync ( root ) ;
246+ assertPluginAllowed ( name ) ;
226247
227248 const manifest = await readManifest ( ) ;
228249 const plugins = manifest . bailianCli ! . plugins ! . filter ( ( p ) => p . name !== name ) ;
@@ -232,7 +253,7 @@ export async function installPlugin(packageSpec: string): Promise<string> {
232253 return name ;
233254}
234255
235- /** 从用户沙箱移除插件 */
256+ /** remove plugin from user sandbox */
236257export async function removePlugin ( name : string ) : Promise < void > {
237258 const manifest = await readManifest ( ) ;
238259 const record = manifest . bailianCli ! . plugins ! . find ( ( p ) => p . name === name ) ;
@@ -243,9 +264,9 @@ export async function removePlugin(name: string): Promise<void> {
243264 if ( record . type === "user" ) {
244265 const pluginsDir = getPluginsDir ( ) ;
245266 try {
246- runNpm ( [ "uninstall" , name , "--no-fund" , "--no-audit" ] , pluginsDir ) ;
267+ runNpm ( [ "uninstall" , name , "--ignore-scripts" , "-- no-fund", "--no-audit" ] , pluginsDir ) ;
247268 } catch {
248- /* 包可能已被手动删除 */
269+ /* package may have been manually deleted */
249270 }
250271 }
251272
0 commit comments