feat(coding-agent): add project trust gating

This commit is contained in:
Mario Zechner
2026-06-05 10:51:20 +02:00
parent db594d3a59
commit 89a92207f1
28 changed files with 1029 additions and 112 deletions

View File

@@ -963,6 +963,7 @@ export class DefaultPackageManager implements PackageManager {
async install(source: string, options?: { local?: boolean }): Promise<void> {
const parsed = this.parseSource(source);
const scope: SourceScope = options?.local ? "project" : "user";
this.assertProjectTrustedForScope(scope);
await this.withProgress("install", source, `Installing ${source}...`, async () => {
if (parsed.type === "npm") {
await this.installNpm(parsed, scope, false);
@@ -991,6 +992,7 @@ export class DefaultPackageManager implements PackageManager {
async remove(source: string, options?: { local?: boolean }): Promise<void> {
const parsed = this.parseSource(source);
const scope: SourceScope = options?.local ? "project" : "user";
this.assertProjectTrustedForScope(scope);
await this.withProgress("remove", source, `Removing ${source}...`, async () => {
if (parsed.type === "npm") {
await this.uninstallNpm(parsed, scope);
@@ -1673,6 +1675,12 @@ export class DefaultPackageManager implements PackageManager {
return { name, version };
}
private assertProjectTrustedForScope(scope: SourceScope): void {
if (scope === "project" && !this.settingsManager.isProjectTrusted()) {
throw new Error("Project is not trusted; refusing to access project package storage");
}
}
private getNpmCommand(): { command: string; args: string[] } {
const configuredCommand = this.settingsManager.getNpmCommand();
if (!configuredCommand || configuredCommand.length === 0) {
@@ -1893,6 +1901,7 @@ export class DefaultPackageManager implements PackageManager {
return this.getTemporaryDir("npm");
}
if (scope === "project") {
this.assertProjectTrustedForScope(scope);
return join(this.cwd, CONFIG_DIR_NAME, "npm");
}
return join(this.agentDir, "npm");
@@ -1933,6 +1942,7 @@ export class DefaultPackageManager implements PackageManager {
return join(this.getTemporaryDir("npm"), "node_modules", source.name);
}
if (scope === "project") {
this.assertProjectTrustedForScope(scope);
return join(this.cwd, CONFIG_DIR_NAME, "npm", "node_modules", source.name);
}
return join(this.agentDir, "npm", "node_modules", source.name);
@@ -1971,6 +1981,7 @@ export class DefaultPackageManager implements PackageManager {
return undefined;
}
if (scope === "project") {
this.assertProjectTrustedForScope(scope);
return join(this.cwd, CONFIG_DIR_NAME, "git");
}
return join(this.agentDir, "git");
@@ -1996,6 +2007,7 @@ export class DefaultPackageManager implements PackageManager {
private getBaseDirForScope(scope: SourceScope): string {
if (scope === "project") {
this.assertProjectTrustedForScope(scope);
return join(this.cwd, CONFIG_DIR_NAME);
}
if (scope === "user") {
@@ -2257,9 +2269,10 @@ export class DefaultPackageManager implements PackageManager {
themes: join(projectBaseDir, "themes"),
};
const userAgentsSkillsDir = join(getHomeDir(), ".agents", "skills");
const projectAgentsSkillDirs = collectAncestorAgentsSkillDirs(this.cwd).filter(
(dir) => resolve(dir) !== resolve(userAgentsSkillsDir),
);
const projectTrusted = this.settingsManager.isProjectTrusted();
const projectAgentsSkillDirs = projectTrusted
? collectAncestorAgentsSkillDirs(this.cwd).filter((dir) => resolve(dir) !== resolve(userAgentsSkillsDir))
: [];
const addResources = (
resourceType: ResourceType,
@@ -2275,23 +2288,25 @@ export class DefaultPackageManager implements PackageManager {
}
};
// Project extensions from .pi/
addResources(
"extensions",
collectAutoExtensionEntries(projectDirs.extensions),
projectMetadata,
projectOverrides.extensions,
projectBaseDir,
);
if (projectTrusted) {
// Project extensions from .pi/
addResources(
"extensions",
collectAutoExtensionEntries(projectDirs.extensions),
projectMetadata,
projectOverrides.extensions,
projectBaseDir,
);
// Project skills from .pi/
addResources(
"skills",
collectAutoSkillEntries(projectDirs.skills, "pi"),
projectMetadata,
projectOverrides.skills,
projectBaseDir,
);
// Project skills from .pi/
addResources(
"skills",
collectAutoSkillEntries(projectDirs.skills, "pi"),
projectMetadata,
projectOverrides.skills,
projectBaseDir,
);
}
// Project skills from .agents/ (each with its own baseDir)
for (const agentsSkillsDir of projectAgentsSkillDirs) {
@@ -2309,20 +2324,22 @@ export class DefaultPackageManager implements PackageManager {
);
}
addResources(
"prompts",
collectAutoPromptEntries(projectDirs.prompts),
projectMetadata,
projectOverrides.prompts,
projectBaseDir,
);
addResources(
"themes",
collectAutoThemeEntries(projectDirs.themes),
projectMetadata,
projectOverrides.themes,
projectBaseDir,
);
if (projectTrusted) {
addResources(
"prompts",
collectAutoPromptEntries(projectDirs.prompts),
projectMetadata,
projectOverrides.prompts,
projectBaseDir,
);
addResources(
"themes",
collectAutoThemeEntries(projectDirs.themes),
projectMetadata,
projectOverrides.themes,
projectBaseDir,
);
}
// User extensions from ~/.pi/agent/
addResources(