mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 12:10:49 +00:00
Merge pull request #142 from AikidoSec/feature/pypi-ci
[PYPI] Add CI Shims
This commit is contained in:
commit
76acf43128
21 changed files with 325 additions and 94 deletions
2
package-lock.json
generated
2
package-lock.json
generated
|
|
@ -2096,6 +2096,8 @@
|
|||
"aikido-npx": "bin/aikido-npx.js",
|
||||
"aikido-pip": "bin/aikido-pip.js",
|
||||
"aikido-pip3": "bin/aikido-pip3.js",
|
||||
"aikido-python": "bin/aikido-python.js",
|
||||
"aikido-python3": "bin/aikido-python3.js",
|
||||
"aikido-pnpm": "bin/aikido-pnpm.js",
|
||||
"aikido-pnpx": "bin/aikido-pnpx.js",
|
||||
"aikido-yarn": "bin/aikido-yarn.js",
|
||||
|
|
|
|||
|
|
@ -3,17 +3,16 @@
|
|||
import { main } from "../src/main.js";
|
||||
import { initializePackageManager } from "../src/packagemanager/currentPackageManager.js";
|
||||
import { setEcoSystem, ECOSYSTEM_PY } from "../src/config/settings.js";
|
||||
|
||||
// Defaults
|
||||
let packageManagerName = "pip";
|
||||
// Pass through user args as-is
|
||||
const argv = process.argv.slice(2);
|
||||
import { setCurrentPipInvocation, PIP_INVOCATIONS, PIP_PACKAGE_MANAGER } from "../src/packagemanager/pip/pipSettings.js";
|
||||
|
||||
// Set eco system
|
||||
// This can be used in other parts of the code to determine which eco system we are working with
|
||||
setEcoSystem(ECOSYSTEM_PY);
|
||||
|
||||
initializePackageManager(packageManagerName);
|
||||
var exitCode = await main(argv);
|
||||
// Set current invocation
|
||||
setCurrentPipInvocation(PIP_INVOCATIONS.PIP);
|
||||
|
||||
initializePackageManager(PIP_PACKAGE_MANAGER);
|
||||
|
||||
// Pass through only user-supplied pip args
|
||||
var exitCode = await main(process.argv.slice(2));
|
||||
process.exit(exitCode);
|
||||
|
|
|
|||
|
|
@ -3,17 +3,17 @@
|
|||
import { main } from "../src/main.js";
|
||||
import { initializePackageManager } from "../src/packagemanager/currentPackageManager.js";
|
||||
import { setEcoSystem, ECOSYSTEM_PY } from "../src/config/settings.js";
|
||||
import { setCurrentPipInvocation, PIP_INVOCATIONS, PIP_PACKAGE_MANAGER } from "../src/packagemanager/pip/pipSettings.js";
|
||||
|
||||
// Explicit pip3 entrypoint
|
||||
const packageManagerName = "pip3";
|
||||
|
||||
// Copy argv as-is
|
||||
const argv = process.argv.slice(2);
|
||||
|
||||
// Set ecosystem to Python
|
||||
// Set eco system
|
||||
setEcoSystem(ECOSYSTEM_PY);
|
||||
|
||||
initializePackageManager(packageManagerName);
|
||||
var exitCode = await main(argv);
|
||||
// Set current invocation
|
||||
setCurrentPipInvocation(PIP_INVOCATIONS.PIP3);
|
||||
|
||||
// Create package manager
|
||||
initializePackageManager(PIP_PACKAGE_MANAGER);
|
||||
|
||||
// Pass through only user-supplied pip args
|
||||
var exitCode = await main(process.argv.slice(2));
|
||||
process.exit(exitCode);
|
||||
|
|
|
|||
28
packages/safe-chain/bin/aikido-python.js
Executable file
28
packages/safe-chain/bin/aikido-python.js
Executable file
|
|
@ -0,0 +1,28 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import { initializePackageManager } from "../src/packagemanager/currentPackageManager.js";
|
||||
import { setCurrentPipInvocation, PIP_INVOCATIONS, PIP_PACKAGE_MANAGER } from "../src/packagemanager/pip/pipSettings.js";
|
||||
import { setEcoSystem, ECOSYSTEM_PY } from "../src/config/settings.js";
|
||||
import { main } from "../src/main.js";
|
||||
|
||||
// Set eco system
|
||||
setEcoSystem(ECOSYSTEM_PY);
|
||||
|
||||
// Strip nodejs and wrapper script from args
|
||||
let argv = process.argv.slice(2);
|
||||
|
||||
if (argv[0] === '-m' && (argv[1] === 'pip' || argv[1] === 'pip3')) {
|
||||
setEcoSystem(ECOSYSTEM_PY);
|
||||
setCurrentPipInvocation(argv[1] === 'pip3' ? PIP_INVOCATIONS.PY_PIP3 : PIP_INVOCATIONS.PY_PIP);
|
||||
initializePackageManager(PIP_PACKAGE_MANAGER);
|
||||
|
||||
// Strip off the '-m pip' or '-m pip3' from the args
|
||||
argv = argv.slice(2);
|
||||
|
||||
var exitCode = await main(argv);
|
||||
process.exit(exitCode);
|
||||
} else {
|
||||
// Forward to real python binary for non-pip flows
|
||||
const { spawn } = await import('child_process');
|
||||
spawn('python', argv, { stdio: 'inherit' });
|
||||
}
|
||||
28
packages/safe-chain/bin/aikido-python3.js
Executable file
28
packages/safe-chain/bin/aikido-python3.js
Executable file
|
|
@ -0,0 +1,28 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import { initializePackageManager } from "../src/packagemanager/currentPackageManager.js";
|
||||
import { setCurrentPipInvocation, PIP_INVOCATIONS, PIP_PACKAGE_MANAGER } from "../src/packagemanager/pip/pipSettings.js";
|
||||
import { setEcoSystem, ECOSYSTEM_PY } from "../src/config/settings.js";
|
||||
import { main } from "../src/main.js";
|
||||
|
||||
// Set eco system
|
||||
setEcoSystem(ECOSYSTEM_PY);
|
||||
|
||||
// Strip nodejs and wrapper script from args
|
||||
let argv = process.argv.slice(2);
|
||||
|
||||
if (argv[0] === '-m' && (argv[1] === 'pip' || argv[1] === 'pip3')) {
|
||||
setEcoSystem(ECOSYSTEM_PY);
|
||||
setCurrentPipInvocation(argv[1] === 'pip3' ? PIP_INVOCATIONS.PY3_PIP3 : PIP_INVOCATIONS.PY3_PIP);
|
||||
initializePackageManager(PIP_PACKAGE_MANAGER);
|
||||
|
||||
// Strip off the '-m pip' or '-m pip3' from the args
|
||||
argv = argv.slice(2);
|
||||
|
||||
var exitCode = await main(argv);
|
||||
process.exit(exitCode);
|
||||
} else {
|
||||
// Forward to real python3 binary for non-pip flows
|
||||
const { spawn } = await import('child_process');
|
||||
spawn('python3', argv, { stdio: 'inherit' });
|
||||
}
|
||||
|
|
@ -17,6 +17,8 @@
|
|||
"aikido-bunx": "bin/aikido-bunx.js",
|
||||
"aikido-pip": "bin/aikido-pip.js",
|
||||
"aikido-pip3": "bin/aikido-pip3.js",
|
||||
"aikido-python": "bin/aikido-python.js",
|
||||
"aikido-python3": "bin/aikido-python3.js",
|
||||
"safe-chain": "bin/safe-chain.js"
|
||||
},
|
||||
"type": "module",
|
||||
|
|
|
|||
|
|
@ -52,8 +52,8 @@ export function initializePackageManager(packageManagerName) {
|
|||
state.packageManagerName = createBunPackageManager();
|
||||
} else if (packageManagerName === "bunx") {
|
||||
state.packageManagerName = createBunxPackageManager();
|
||||
} else if (packageManagerName === "pip" || packageManagerName === "pip3") {
|
||||
state.packageManagerName = createPipPackageManager(packageManagerName);
|
||||
} else if (packageManagerName === "pip") {
|
||||
state.packageManagerName = createPipPackageManager();
|
||||
} else {
|
||||
throw new Error("Unsupported package manager: " + packageManagerName);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,18 @@
|
|||
import { runPip } from "./runPipCommand.js";
|
||||
|
||||
import { getCurrentPipInvocation } from "./pipSettings.js";
|
||||
/**
|
||||
* @param {string} [command]
|
||||
* @returns {import("../currentPackageManager.js").PackageManager}
|
||||
*/
|
||||
export function createPipPackageManager(command = "pip") {
|
||||
export function createPipPackageManager() {
|
||||
return {
|
||||
runCommand: /** @param {string[]} args */ (args) => runPip(command, args),
|
||||
/**
|
||||
* @param {string[]} args
|
||||
*/
|
||||
runCommand: (args) => {
|
||||
const invocation = getCurrentPipInvocation();
|
||||
const fullArgs = [...invocation.args, ...args];
|
||||
return runPip(invocation.command, fullArgs);
|
||||
},
|
||||
// For pip, rely solely on MITM proxy to detect/deny downloads from known registries.
|
||||
isSupportedCommand: () => false,
|
||||
getDependencyUpdatesForCommand: () => [],
|
||||
|
|
|
|||
30
packages/safe-chain/src/packagemanager/pip/pipSettings.js
Normal file
30
packages/safe-chain/src/packagemanager/pip/pipSettings.js
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
export const PIP_PACKAGE_MANAGER = "pip";
|
||||
|
||||
// All supported python/pip invocations for Safe Chain interception
|
||||
export const PIP_INVOCATIONS = {
|
||||
PIP: { command: "pip", args: [] },
|
||||
PIP3: { command: "pip3", args: [] },
|
||||
PY_PIP: { command: "python", args: ["-m", "pip"] },
|
||||
PY3_PIP: { command: "python3", args: ["-m", "pip"] },
|
||||
PY_PIP3: { command: "python", args: ["-m", "pip3"] },
|
||||
PY3_PIP3: { command: "python3", args: ["-m", "pip3"] }
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {{ command: string, args: string[] }}
|
||||
*/
|
||||
let currentInvocation = PIP_INVOCATIONS.PY3_PIP; // Default to python3 -m pip
|
||||
|
||||
/**
|
||||
* @param {{ command: string, args: string[] }} invocation
|
||||
*/
|
||||
export function setCurrentPipInvocation(invocation) {
|
||||
currentInvocation = invocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {{ command: string, args: string[] }}
|
||||
*/
|
||||
export function getCurrentPipInvocation() {
|
||||
return currentInvocation;
|
||||
}
|
||||
|
|
@ -29,7 +29,8 @@ export async function runPip(command, args) {
|
|||
if (error.status) {
|
||||
return { status: error.status };
|
||||
} else {
|
||||
ui.writeError("Error executing command:", error.message);
|
||||
ui.writeError(`Error executing command: ${error.message}`);
|
||||
ui.writeError(`Is '${command}' installed and available on your system?`);
|
||||
return { status: 1 };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,8 +57,6 @@ export async function auditChanges(changes) {
|
|||
);
|
||||
|
||||
for (const change of changes) {
|
||||
//Uncomment next line during manual testing
|
||||
//console.log(" Safe-chain: auditing package:", change);
|
||||
const malwarePackage = malwarePackages.find(
|
||||
(pkg) => pkg.name === change.name && pkg.version === change.version
|
||||
);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ export const knownAikidoTools = [
|
|||
{ tool: "bunx", aikidoCommand: "aikido-bunx" },
|
||||
{ tool: "pip", aikidoCommand: "aikido-pip" },
|
||||
{ tool: "pip3", aikidoCommand: "aikido-pip3" },
|
||||
{ tool: "python", aikidoCommand: "aikido-python" },
|
||||
{ tool: "python3", aikidoCommand: "aikido-python3" },
|
||||
// When adding a new tool here, also update the documentation for the new tool in the README.md
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -19,4 +19,4 @@ else
|
|||
echo "Error: Could not find original {{PACKAGE_MANAGER}}" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -51,13 +51,9 @@ function createUnixShims(shimsDir) {
|
|||
|
||||
const template = fs.readFileSync(templatePath, "utf-8");
|
||||
|
||||
// Create a shim for each tool except pip (CI support not yet implemented)
|
||||
// Create a shim for each tool
|
||||
let created = 0;
|
||||
for (const toolInfo of knownAikidoTools) {
|
||||
if (toolInfo.tool === "pip") {
|
||||
continue; // Skip pip shims in CI for now
|
||||
}
|
||||
|
||||
const shimContent = template
|
||||
.replaceAll("{{PACKAGE_MANAGER}}", toolInfo.tool)
|
||||
.replaceAll("{{AIKIDO_COMMAND}}", toolInfo.aikidoCommand);
|
||||
|
|
@ -98,18 +94,14 @@ function createWindowsShims(shimsDir) {
|
|||
|
||||
const template = fs.readFileSync(templatePath, "utf-8");
|
||||
|
||||
// Create a shim for each tool except pip (CI support not yet implemented)
|
||||
// Create a shim for each tool
|
||||
let created = 0;
|
||||
for (const toolInfo of knownAikidoTools) {
|
||||
if (toolInfo.tool === "pip") {
|
||||
continue; // Skip pip shims in CI for now
|
||||
}
|
||||
|
||||
const shimContent = template
|
||||
.replaceAll("{{PACKAGE_MANAGER}}", toolInfo.tool)
|
||||
.replaceAll("{{AIKIDO_COMMAND}}", toolInfo.aikidoCommand);
|
||||
|
||||
const shimPath = path.join(shimsDir, `${toolInfo.tool}.cmd`);
|
||||
const shimPath = `${shimsDir}/${toolInfo.tool}.cmd`;
|
||||
fs.writeFileSync(shimPath, shimContent, "utf-8");
|
||||
created++;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -147,4 +147,4 @@ describe("Setup CI shell integration", () => {
|
|||
assert.ok(!fs.existsSync(unixNpmShim), "Unix npm shim should not exist on Windows");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -79,26 +79,10 @@ end
|
|||
|
||||
# `python -m pip`, `python -m pip3`.
|
||||
function python
|
||||
if test (count $argv) -ge 2; and test $argv[1] = "-m"; and string match -qr '^pip(3)?$' -- $argv[2]
|
||||
set mod $argv[2]
|
||||
set args $argv[3..-1]
|
||||
if test $mod = "pip3"
|
||||
wrapSafeChainCommand "pip3" "aikido-pip3" $args
|
||||
else
|
||||
wrapSafeChainCommand "pip" "aikido-pip" $args
|
||||
end
|
||||
else
|
||||
command python $argv
|
||||
end
|
||||
wrapSafeChainCommand "python" "aikido-python" $argv
|
||||
end
|
||||
|
||||
# `python3 -m pip`, `python3 -m pip3'.
|
||||
function python3
|
||||
if test (count $argv) -ge 2; and test $argv[1] = "-m"; and string match -qr '^pip(3)?$' -- $argv[2]
|
||||
set args $argv[3..-1]
|
||||
# python3 always uses pip3, regardless of whether user types `pip` or `pip3`
|
||||
wrapSafeChainCommand "pip3" "aikido-pip3" $args
|
||||
else
|
||||
command python3 $argv
|
||||
end
|
||||
wrapSafeChainCommand "python3" "aikido-python3" $argv
|
||||
end
|
||||
|
|
|
|||
|
|
@ -71,26 +71,10 @@ function pip3() {
|
|||
|
||||
# `python -m pip`, `python -m pip3`.
|
||||
function python() {
|
||||
if [[ "$1" == "-m" && "$2" == pip* ]]; then
|
||||
local mod="$2"
|
||||
shift 2
|
||||
if [[ "$mod" == "pip3" ]]; then
|
||||
wrapSafeChainCommand "pip3" "aikido-pip3" "$@"
|
||||
else
|
||||
wrapSafeChainCommand "pip" "aikido-pip" "$@"
|
||||
fi
|
||||
else
|
||||
command python "$@"
|
||||
fi
|
||||
wrapSafeChainCommand "python" "aikido-python" "$@"
|
||||
}
|
||||
|
||||
# `python3 -m pip`, `python3 -m pip3'.
|
||||
function python3() {
|
||||
if [[ "$1" == "-m" && "$2" == pip* ]]; then
|
||||
shift 2
|
||||
# python3 always uses pip3, regardless of whether user types `pip` or `pip3`
|
||||
wrapSafeChainCommand "pip3" "aikido-pip3" "$@"
|
||||
else
|
||||
command python3 "$@"
|
||||
fi
|
||||
wrapSafeChainCommand "python3" "aikido-python3" "$@"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,27 +97,11 @@ function pip3 {
|
|||
|
||||
# `python -m pip`, `python -m pip3`.
|
||||
function python {
|
||||
param([Parameter(ValueFromRemainingArguments=$true)]$Args)
|
||||
if ($Args.Length -ge 2 -and $Args[0] -eq '-m' -and $Args[1] -match '^pip(3)?$') {
|
||||
$pipArgs = if ($Args.Length -gt 2) { $Args | Select-Object -Skip 2 } else { @() }
|
||||
if ($Args[1] -eq 'pip3') { Invoke-WrappedCommand 'pip3' 'aikido-pip3' $pipArgs }
|
||||
else { Invoke-WrappedCommand 'pip' 'aikido-pip' $pipArgs }
|
||||
}
|
||||
else {
|
||||
Invoke-RealCommand 'python' $Args
|
||||
}
|
||||
Invoke-WrappedCommand 'python' 'aikido-python' $args
|
||||
}
|
||||
|
||||
# `python3 -m pip`, `python3 -m pip3'.
|
||||
function python3 {
|
||||
param([Parameter(ValueFromRemainingArguments=$true)]$Args)
|
||||
if ($Args.Length -ge 2 -and $Args[0] -eq '-m' -and $Args[1] -match '^pip(3)?$') {
|
||||
# python3 always uses pip3, regardless of whether user types `pip` or `pip3`
|
||||
$pipArgs = if ($Args.Length -gt 2) { $Args | Select-Object -Skip 2 } else { @() }
|
||||
Invoke-WrappedCommand 'pip3' 'aikido-pip3' $pipArgs
|
||||
}
|
||||
else {
|
||||
Invoke-RealCommand 'python3' $Args
|
||||
}
|
||||
Invoke-WrappedCommand 'python3' 'aikido-python3' $args
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -53,7 +53,19 @@ RUN curl -fsSL https://bun.sh/install | bash
|
|||
# Install Python and pip (pip3)
|
||||
RUN apt-get update && apt-get install -y python${PYTHON_VERSION} python3-pip && \
|
||||
ln -sf /usr/bin/python${PYTHON_VERSION} /usr/local/bin/python3 && \
|
||||
ln -sf /usr/bin/pip3 /usr/local/bin/pip3
|
||||
ln -sf /usr/bin/python${PYTHON_VERSION} /usr/local/bin/python && \
|
||||
ln -sf /usr/bin/pip3 /usr/local/bin/pip3 && \
|
||||
cat <<'EOF' > /usr/lib/python3/dist-packages/pip3.py
|
||||
"""
|
||||
Shim module so 'python[3] -m pip3 …' resolves to pip's CLI entry point.
|
||||
"""
|
||||
try:
|
||||
import pip._internal
|
||||
pip._internal.main()
|
||||
except Exception as exc:
|
||||
print("pip3 module shim failed:", exc)
|
||||
raise
|
||||
EOF
|
||||
|
||||
# Copy and install Safe chain
|
||||
COPY --from=builder /app/*.tgz /pkgs/
|
||||
|
|
|
|||
171
test/e2e/pip-ci.e2e.spec.js
Normal file
171
test/e2e/pip-ci.e2e.spec.js
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
import { describe, it, before, beforeEach, afterEach } from "node:test";
|
||||
import { DockerTestContainer } from "./DockerTestContainer.js";
|
||||
import assert from "node:assert";
|
||||
|
||||
describe("E2E: safe-chain setup-ci command for pip/pip3", () => {
|
||||
let container;
|
||||
|
||||
before(async () => {
|
||||
DockerTestContainer.buildImage();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
container = new DockerTestContainer();
|
||||
await container.start();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (container) {
|
||||
await container.stop();
|
||||
container = null;
|
||||
}
|
||||
});
|
||||
|
||||
describe("E2E: pip CI support", () => {
|
||||
it("does not intercept python3 --version", async () => {
|
||||
const shell = await container.openShell("zsh");
|
||||
const result = await shell.runCommand("python3 --version");
|
||||
assert.ok(result.output.match(/Python \d+\.\d+\.\d+/), `Output was: ${result.output}`);
|
||||
assert.ok(!result.output.includes("Safe-chain"), "Safe Chain should not intercept generic python3 command");
|
||||
});
|
||||
|
||||
it("does not intercept python3 -c 'print(\"hello\")'", async () => {
|
||||
const shell = await container.openShell("zsh");
|
||||
const result = await shell.runCommand("python3 -c 'print(\"hello\")'");
|
||||
assert.ok(result.output.includes("hello"), `Output was: ${result.output}`);
|
||||
assert.ok(!result.output.includes("Safe-chain"), "Safe Chain should not intercept generic python3 -c command");
|
||||
});
|
||||
|
||||
it("does not intercept python3 test.py", async () => {
|
||||
const shell = await container.openShell("zsh");
|
||||
await shell.runCommand("echo 'print(\"Hello from test.py!\")' > test.py");
|
||||
const result = await shell.runCommand("python3 test.py");
|
||||
assert.ok(result.output.includes("Hello from test.py!"), `Output was: ${result.output}`);
|
||||
assert.ok(!result.output.includes("Safe-chain"), "Safe Chain should not intercept generic python3 script execution");
|
||||
});
|
||||
|
||||
it("does not intercept python test.py", async () => {
|
||||
const shell = await container.openShell("zsh");
|
||||
await shell.runCommand("echo 'print(\"Hello from test.py!\")' > test.py");
|
||||
const result = await shell.runCommand("python test.py");
|
||||
assert.ok(result.output.includes("Hello from test.py!"), `Output was: ${result.output}`);
|
||||
assert.ok(!result.output.includes("Safe-chain"), "Safe Chain should not intercept generic python script execution");
|
||||
});
|
||||
});
|
||||
|
||||
for (let shell of ["bash", "zsh"]) {
|
||||
it(`safe-chain setup-ci wraps pip3 command with PATH shim after installation for ${shell}`, async () => {
|
||||
// Setup safe-chain CI shims
|
||||
const installationShell = await container.openShell(shell);
|
||||
await installationShell.runCommand("safe-chain setup-ci");
|
||||
|
||||
// Add $HOME/.safe-chain/shims to PATH for subsequent shells
|
||||
await installationShell.runCommand(
|
||||
"echo 'export PATH=\"$HOME/.safe-chain/shims:$PATH\"' >> ~/.zshrc"
|
||||
);
|
||||
await installationShell.runCommand(
|
||||
"echo 'export PATH=\"$HOME/.safe-chain/shims:$PATH\"' >> ~/.bashrc"
|
||||
);
|
||||
|
||||
const projectShell = await container.openShell(shell);
|
||||
// Use --break-system-packages to avoid Debian/Ubuntu external management restrictions
|
||||
const result = await projectShell.runCommand(
|
||||
"pip3 install --break-system-packages certifi"
|
||||
);
|
||||
|
||||
const hasExpectedOutput = result.output.includes(
|
||||
"no malware found."
|
||||
);
|
||||
assert.ok(
|
||||
hasExpectedOutput,
|
||||
hasExpectedOutput
|
||||
? "Expected pip3 command to be wrapped by safe-chain"
|
||||
: `Output did not contain \"no malware found.\": \n${result.output}`
|
||||
);
|
||||
});
|
||||
|
||||
it(`setup-ci routes python -m pip through safe-chain for ${shell}`, async () => {
|
||||
const installationShell = await container.openShell(shell);
|
||||
await installationShell.runCommand("safe-chain setup-ci");
|
||||
await installationShell.runCommand(
|
||||
"echo 'export PATH=\"$HOME/.safe-chain/shims:$PATH\"' >> ~/.zshrc"
|
||||
);
|
||||
await installationShell.runCommand(
|
||||
"echo 'export PATH=\"$HOME/.safe-chain/shims:$PATH\"' >> ~/.bashrc"
|
||||
);
|
||||
|
||||
const projectShell = await container.openShell(shell);
|
||||
const result = await projectShell.runCommand(
|
||||
"python -m pip install --break-system-packages certifi"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
result.output.includes("no malware found."),
|
||||
`Output did not contain scan message. Output was:\n${result.output}`
|
||||
);
|
||||
});
|
||||
|
||||
it(`setup-ci routes python3 -m pip through safe-chain for ${shell}`, async () => {
|
||||
const installationShell = await container.openShell(shell);
|
||||
await installationShell.runCommand("safe-chain setup-ci");
|
||||
await installationShell.runCommand(
|
||||
"echo 'export PATH=\"$HOME/.safe-chain/shims:$PATH\"' >> ~/.zshrc"
|
||||
);
|
||||
await installationShell.runCommand(
|
||||
"echo 'export PATH=\"$HOME/.safe-chain/shims:$PATH\"' >> ~/.bashrc"
|
||||
);
|
||||
|
||||
const projectShell = await container.openShell(shell);
|
||||
const result = await projectShell.runCommand(
|
||||
"python3 -m pip install --break-system-packages certifi"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
result.output.includes("no malware found."),
|
||||
`Output did not contain scan message. Output was:\n${result.output}`
|
||||
);
|
||||
});
|
||||
|
||||
it(`setup-ci routes pip through safe-chain for ${shell}`, async () => {
|
||||
const installationShell = await container.openShell(shell);
|
||||
await installationShell.runCommand("safe-chain setup-ci");
|
||||
await installationShell.runCommand(
|
||||
"echo 'export PATH=\"$HOME/.safe-chain/shims:$PATH\"' >> ~/.zshrc"
|
||||
);
|
||||
await installationShell.runCommand(
|
||||
"echo 'export PATH=\"$HOME/.safe-chain/shims:$PATH\"' >> ~/.bashrc"
|
||||
);
|
||||
|
||||
const projectShell = await container.openShell(shell);
|
||||
const result = await projectShell.runCommand(
|
||||
"pip install --break-system-packages certifi"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
result.output.includes("no malware found."),
|
||||
`Output did not contain scan message. Output was:\n${result.output}`
|
||||
);
|
||||
});
|
||||
|
||||
it(`setup-ci routes pip3 through safe-chain for ${shell}`, async () => {
|
||||
const installationShell = await container.openShell(shell);
|
||||
await installationShell.runCommand("safe-chain setup-ci");
|
||||
await installationShell.runCommand(
|
||||
"echo 'export PATH=\"$HOME/.safe-chain/shims:$PATH\"' >> ~/.zshrc"
|
||||
);
|
||||
await installationShell.runCommand(
|
||||
"echo 'export PATH=\"$HOME/.safe-chain/shims:$PATH\"' >> ~/.bashrc"
|
||||
);
|
||||
|
||||
const projectShell = await container.openShell(shell);
|
||||
const result = await projectShell.runCommand(
|
||||
"pip3 install --break-system-packages certifi"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
result.output.includes("no malware found."),
|
||||
`Output did not contain scan message. Output was:\n${result.output}`
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -323,4 +323,12 @@ describe("E2E: pip coverage", () => {
|
|||
`Should not have SSL/certificate errors for tunneled hosts. Output was:\n${result.output}`
|
||||
);
|
||||
});
|
||||
|
||||
it(`pip3 install requests with --safe-chain-logging=verbose`, async () => {
|
||||
const shell = await container.openShell("zsh");
|
||||
const result = await shell.runCommand(
|
||||
"pip3 install --break-system-packages requests --safe-chain-logging=verbose"
|
||||
);
|
||||
assert.ok(result.output.includes("no malware found."), `Output did not include expected text. Output was:\n${result.output}`);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue