Add uvx support

Add uvx as a supported package manager so that `uvx` commands are
routed through safe-chain's MITM proxy for malware detection, just
like `uv`. Previously, `uvx` bypassed all safe-chain protections.

The uvx package manager reuses the existing uv command runner since
uvx is functionally equivalent to `uv tool run`.

Fixes #268

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Stephen Benjamin 2026-03-17 17:12:42 -04:00
parent 83f9f378f6
commit 14c8abffea
12 changed files with 82 additions and 9 deletions

View file

@ -13,6 +13,7 @@ import { createPipPackageManager } from "./pip/createPackageManager.js";
import { createUvPackageManager } from "./uv/createUvPackageManager.js";
import { createPoetryPackageManager } from "./poetry/createPoetryPackageManager.js";
import { createPipXPackageManager } from "./pipx/createPipXPackageManager.js";
import { createUvxPackageManager } from "./uvx/createUvxPackageManager.js";
/**
* @type {{packageManagerName: PackageManager | null}}
@ -60,6 +61,8 @@ export function initializePackageManager(packageManagerName, context) {
state.packageManagerName = createPipPackageManager(context);
} else if (packageManagerName === "uv") {
state.packageManagerName = createUvPackageManager();
} else if (packageManagerName === "uvx") {
state.packageManagerName = createUvxPackageManager();
} else if (packageManagerName === "poetry") {
state.packageManagerName = createPoetryPackageManager();
} else if (packageManagerName === "pipx") {

View file

@ -0,0 +1,18 @@
import { runUv } from "../uv/runUvCommand.js";
/**
* @returns {import("../currentPackageManager.js").PackageManager}
*/
export function createUvxPackageManager() {
return {
/**
* @param {string[]} args
*/
runCommand: (args) => {
return runUv("uvx", args);
},
// For uvx, rely solely on MITM
isSupportedCommand: () => false,
getDependencyUpdatesForCommand: () => [],
};
}

View file

@ -0,0 +1,14 @@
import { test } from "node:test";
import assert from "node:assert";
import { createUvxPackageManager } from "./createUvxPackageManager.js";
test("createUvxPackageManager returns valid package manager interface", () => {
const pm = createUvxPackageManager();
assert.ok(pm);
assert.strictEqual(typeof pm.runCommand, "function");
assert.strictEqual(typeof pm.isSupportedCommand, "function");
assert.strictEqual(typeof pm.getDependencyUpdatesForCommand, "function");
assert.strictEqual(pm.isSupportedCommand(), false);
assert.deepStrictEqual(pm.getDependencyUpdatesForCommand(), []);
});

View file

@ -66,6 +66,12 @@ export const knownAikidoTools = [
ecoSystem: ECOSYSTEM_PY,
internalPackageManagerName: "uv",
},
{
tool: "uvx",
aikidoCommand: "aikido-uvx",
ecoSystem: ECOSYSTEM_PY,
internalPackageManagerName: "uvx",
},
{
tool: "pip",
aikidoCommand: "aikido-pip",

View file

@ -51,6 +51,10 @@ function uv
wrapSafeChainCommand "uv" $argv
end
function uvx
wrapSafeChainCommand "uvx" $argv
end
function poetry
wrapSafeChainCommand "poetry" $argv
end

View file

@ -47,6 +47,10 @@ function uv() {
wrapSafeChainCommand "uv" "$@"
}
function uvx() {
wrapSafeChainCommand "uvx" "$@"
}
function poetry() {
wrapSafeChainCommand "poetry" "$@"
}

View file

@ -52,6 +52,10 @@ function uv {
Invoke-WrappedCommand "uv" $args $MyInvocation.Line $MyInvocation.OffsetInLine
}
function uvx {
Invoke-WrappedCommand "uvx" $args $MyInvocation.Line $MyInvocation.OffsetInLine
}
function poetry {
Invoke-WrappedCommand "poetry" $args $MyInvocation.Line $MyInvocation.OffsetInLine
}