feat(coding-agent): add project trust gating
This commit is contained in:
@@ -14,6 +14,7 @@ import {
|
||||
} from "./config.ts";
|
||||
import { DefaultPackageManager } from "./core/package-manager.ts";
|
||||
import { SettingsManager } from "./core/settings-manager.ts";
|
||||
import { hasProjectTrustInputs, ProjectTrustStore } from "./core/trust-manager.ts";
|
||||
import { spawnProcess } from "./utils/child-process.ts";
|
||||
import { getLatestPiRelease, isNewerPackageVersion } from "./utils/version-check.ts";
|
||||
import {
|
||||
@@ -48,6 +49,7 @@ interface PackageCommandOptions {
|
||||
updateTarget?: UpdateTarget;
|
||||
local: boolean;
|
||||
force: boolean;
|
||||
projectTrustOverride?: boolean;
|
||||
help: boolean;
|
||||
invalidOption?: string;
|
||||
invalidArgument?: string;
|
||||
@@ -68,13 +70,13 @@ function reportSettingsErrors(settingsManager: SettingsManager, context: string)
|
||||
function getPackageCommandUsage(command: PackageCommand): string {
|
||||
switch (command) {
|
||||
case "install":
|
||||
return `${APP_NAME} install <source> [-l]`;
|
||||
return `${APP_NAME} install <source> [-l] [--approve|--no-approve]`;
|
||||
case "remove":
|
||||
return `${APP_NAME} remove <source> [-l]`;
|
||||
return `${APP_NAME} remove <source> [-l] [--approve|--no-approve]`;
|
||||
case "update":
|
||||
return `${APP_NAME} update [source|self|pi] [--self] [--extensions] [--extension <source>] [--force]`;
|
||||
return `${APP_NAME} update [source|self|pi] [--self] [--extensions] [--extension <source>] [--approve|--no-approve] [--force]`;
|
||||
case "list":
|
||||
return `${APP_NAME} list`;
|
||||
return `${APP_NAME} list [--approve|--no-approve]`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +89,9 @@ function printPackageCommandHelp(command: PackageCommand): void {
|
||||
Install a package and add it to settings.
|
||||
|
||||
Options:
|
||||
-l, --local Install project-locally (.pi/settings.json)
|
||||
-l, --local Install project-locally (.pi/settings.json)
|
||||
-a, --approve Trust project-local files for this command
|
||||
-na, --no-approve Ignore project-local files for this command
|
||||
|
||||
Examples:
|
||||
${APP_NAME} install npm:@foo/bar
|
||||
@@ -107,7 +111,9 @@ Remove a package and its source from settings.
|
||||
Alias: ${APP_NAME} uninstall <source> [-l]
|
||||
|
||||
Options:
|
||||
-l, --local Remove from project settings (.pi/settings.json)
|
||||
-l, --local Remove from project settings (.pi/settings.json)
|
||||
-a, --approve Trust project-local files for this command
|
||||
-na, --no-approve Ignore project-local files for this command
|
||||
|
||||
Examples:
|
||||
${APP_NAME} remove npm:@foo/bar
|
||||
@@ -125,6 +131,8 @@ Options:
|
||||
--self Update pi only
|
||||
--extensions Update installed packages only
|
||||
--extension <source> Update one package only
|
||||
-a, --approve Trust project-local files for this command
|
||||
-na, --no-approve Ignore project-local files for this command
|
||||
--force Reinstall pi even if the current version is latest
|
||||
|
||||
Short forms:
|
||||
@@ -139,6 +147,10 @@ Short forms:
|
||||
${getPackageCommandUsage("list")}
|
||||
|
||||
List installed packages from user and project settings.
|
||||
|
||||
Options:
|
||||
-a, --approve Trust project-local files for this command
|
||||
-na, --no-approve Ignore project-local files for this command
|
||||
`);
|
||||
return;
|
||||
}
|
||||
@@ -158,6 +170,7 @@ function parsePackageCommand(args: string[]): PackageCommandOptions | undefined
|
||||
|
||||
let local = false;
|
||||
let force = false;
|
||||
let projectTrustOverride: boolean | undefined;
|
||||
let help = false;
|
||||
let invalidOption: string | undefined;
|
||||
let invalidArgument: string | undefined;
|
||||
@@ -202,6 +215,16 @@ function parsePackageCommand(args: string[]): PackageCommandOptions | undefined
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === "--approve" || arg === "-a") {
|
||||
projectTrustOverride = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === "--no-approve" || arg === "-na") {
|
||||
projectTrustOverride = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === "--force") {
|
||||
if (command === "update") {
|
||||
force = true;
|
||||
@@ -280,6 +303,7 @@ function parsePackageCommand(args: string[]): PackageCommandOptions | undefined
|
||||
updateTarget,
|
||||
local,
|
||||
force,
|
||||
projectTrustOverride,
|
||||
help,
|
||||
invalidOption,
|
||||
invalidArgument,
|
||||
@@ -389,6 +413,25 @@ function prepareWindowsNpmSelfUpdate(): void {
|
||||
quarantineWindowsNativeDependencies(packageDir);
|
||||
}
|
||||
|
||||
function parseProjectTrustOverride(args: readonly string[]): boolean | undefined {
|
||||
let trustOverride: boolean | undefined;
|
||||
for (const arg of args) {
|
||||
if (arg === "--approve" || arg === "-a") {
|
||||
trustOverride = true;
|
||||
} else if (arg === "--no-approve" || arg === "-na") {
|
||||
trustOverride = false;
|
||||
}
|
||||
}
|
||||
return trustOverride;
|
||||
}
|
||||
|
||||
function resolveProjectTrusted(cwd: string, agentDir: string, trustOverride: boolean | undefined): boolean {
|
||||
if (trustOverride !== undefined) {
|
||||
return trustOverride;
|
||||
}
|
||||
return !hasProjectTrustInputs(cwd) || new ProjectTrustStore(agentDir).get(cwd) === true;
|
||||
}
|
||||
|
||||
export async function handleConfigCommand(args: string[]): Promise<boolean> {
|
||||
if (args[0] !== "config") {
|
||||
return false;
|
||||
@@ -396,7 +439,8 @@ export async function handleConfigCommand(args: string[]): Promise<boolean> {
|
||||
|
||||
const cwd = process.cwd();
|
||||
const agentDir = getAgentDir();
|
||||
const settingsManager = SettingsManager.create(cwd, agentDir);
|
||||
const projectTrusted = parseProjectTrustOverride(args) ?? true;
|
||||
const settingsManager = SettingsManager.create(cwd, agentDir, { projectTrusted });
|
||||
reportSettingsErrors(settingsManager, "config command");
|
||||
const packageManager = new DefaultPackageManager({ cwd, agentDir, settingsManager });
|
||||
const resolvedPaths = await packageManager.resolve();
|
||||
@@ -460,7 +504,14 @@ export async function handlePackageCommand(args: string[]): Promise<boolean> {
|
||||
|
||||
const cwd = process.cwd();
|
||||
const agentDir = getAgentDir();
|
||||
const settingsManager = SettingsManager.create(cwd, agentDir);
|
||||
const writesProjectPackageConfig = (options.command === "install" || options.command === "remove") && options.local;
|
||||
const projectTrusted = resolveProjectTrusted(cwd, agentDir, options.projectTrustOverride);
|
||||
if (!projectTrusted && writesProjectPackageConfig) {
|
||||
console.error(chalk.red("Project is not trusted. Use --approve to modify local package config."));
|
||||
process.exitCode = 1;
|
||||
return true;
|
||||
}
|
||||
const settingsManager = SettingsManager.create(cwd, agentDir, { projectTrusted });
|
||||
reportSettingsErrors(settingsManager, "package command");
|
||||
const selfUpdateNpmCommand = settingsManager.getGlobalSettings().npmCommand;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user