mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 12:10:49 +00:00
Introduce silent mode to disable logging
This commit is contained in:
parent
2e1ee0dfa4
commit
9a78cafbfd
7 changed files with 142 additions and 4 deletions
12
README.md
12
README.md
|
|
@ -88,6 +88,18 @@ Example usage:
|
||||||
npm install suspicious-package --safe-chain-malware-action=prompt
|
npm install suspicious-package --safe-chain-malware-action=prompt
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Logging
|
||||||
|
|
||||||
|
You can control the output from Aikido Safe Chain using the `--safe-chain-logging` flag:
|
||||||
|
|
||||||
|
- `--safe-chain-logging=silent` - Suppresses all Aikido Safe Chain output except when malware is blocked. The package manager output is written to stdout as normal, and Safe Chain only writes a short message if it has blocked malware and causes the process to exit.
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npm install express --safe-chain-logging=silent
|
||||||
|
```
|
||||||
|
|
||||||
# Usage in CI/CD
|
# Usage in CI/CD
|
||||||
|
|
||||||
You can protect your CI/CD pipelines from malicious packages by integrating Aikido Safe Chain into your build process. This ensures that any packages installed during your automated builds are checked for malware before installation.
|
You can protect your CI/CD pipelines from malicious packages by integrating Aikido Safe Chain into your build process. This ensures that any packages installed during your automated builds are checked for malware before installation.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
const state = {
|
const state = {
|
||||||
malwareAction: undefined,
|
malwareAction: undefined,
|
||||||
|
loggingLevel: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
const SAFE_CHAIN_ARG_PREFIX = "--safe-chain-";
|
const SAFE_CHAIN_ARG_PREFIX = "--safe-chain-";
|
||||||
|
|
@ -7,6 +8,7 @@ const SAFE_CHAIN_ARG_PREFIX = "--safe-chain-";
|
||||||
export function initializeCliArguments(args) {
|
export function initializeCliArguments(args) {
|
||||||
// Reset state on each call
|
// Reset state on each call
|
||||||
state.malwareAction = undefined;
|
state.malwareAction = undefined;
|
||||||
|
state.loggingLevel = undefined;
|
||||||
|
|
||||||
const safeChainArgs = [];
|
const safeChainArgs = [];
|
||||||
const remainingArgs = [];
|
const remainingArgs = [];
|
||||||
|
|
@ -20,6 +22,7 @@ export function initializeCliArguments(args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
setMalwareAction(safeChainArgs);
|
setMalwareAction(safeChainArgs);
|
||||||
|
setLoggingLevel(safeChainArgs);
|
||||||
|
|
||||||
return remainingArgs;
|
return remainingArgs;
|
||||||
}
|
}
|
||||||
|
|
@ -48,3 +51,17 @@ function getLastArgEqualsValue(args, prefix) {
|
||||||
export function getMalwareAction() {
|
export function getMalwareAction() {
|
||||||
return state.malwareAction;
|
return state.malwareAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setLoggingLevel(args) {
|
||||||
|
const safeChainLoggingArg = SAFE_CHAIN_ARG_PREFIX + "logging=";
|
||||||
|
|
||||||
|
const level = getLastArgEqualsValue(args, safeChainLoggingArg);
|
||||||
|
if (!level) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state.loggingLevel = level.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getLoggingLevel() {
|
||||||
|
return state.loggingLevel;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
import { describe, it } from "node:test";
|
import { describe, it } from "node:test";
|
||||||
import assert from "node:assert";
|
import assert from "node:assert";
|
||||||
import { initializeCliArguments, getMalwareAction } from "./cliArguments.js";
|
import {
|
||||||
|
initializeCliArguments,
|
||||||
|
getMalwareAction,
|
||||||
|
getLoggingLevel,
|
||||||
|
} from "./cliArguments.js";
|
||||||
|
|
||||||
describe("initializeCliArguments", () => {
|
describe("initializeCliArguments", () => {
|
||||||
it("should return all args when no safe-chain args are present", () => {
|
it("should return all args when no safe-chain args are present", () => {
|
||||||
|
|
@ -105,4 +109,67 @@ describe("initializeCliArguments", () => {
|
||||||
assert.deepEqual(result, ["install"]);
|
assert.deepEqual(result, ["install"]);
|
||||||
assert.strictEqual(getMalwareAction(), "block");
|
assert.strictEqual(getMalwareAction(), "block");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should not set loggingLevel when no logging argument is passed", () => {
|
||||||
|
const args = ["install", "express", "--save"];
|
||||||
|
initializeCliArguments(args);
|
||||||
|
|
||||||
|
assert.strictEqual(getLoggingLevel(), undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should parse logging=silent and set state", () => {
|
||||||
|
const args = ["--safe-chain-logging=silent", "install", "package"];
|
||||||
|
const result = initializeCliArguments(args);
|
||||||
|
|
||||||
|
assert.deepEqual(result, ["install", "package"]);
|
||||||
|
assert.strictEqual(getLoggingLevel(), "silent");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should parse logging=normal and set state", () => {
|
||||||
|
const args = ["--safe-chain-logging=normal", "install", "package"];
|
||||||
|
const result = initializeCliArguments(args);
|
||||||
|
|
||||||
|
assert.deepEqual(result, ["install", "package"]);
|
||||||
|
assert.strictEqual(getLoggingLevel(), "normal");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle multiple logging args, using the last one", () => {
|
||||||
|
const args = [
|
||||||
|
"--safe-chain-logging=normal",
|
||||||
|
"--safe-chain-logging=silent",
|
||||||
|
"install",
|
||||||
|
];
|
||||||
|
const result = initializeCliArguments(args);
|
||||||
|
|
||||||
|
assert.deepEqual(result, ["install"]);
|
||||||
|
assert.strictEqual(getLoggingLevel(), "silent");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle logging level case-insensitively", () => {
|
||||||
|
const args = ["--safe-chain-logging=SILENT", "install"];
|
||||||
|
initializeCliArguments(args);
|
||||||
|
|
||||||
|
assert.strictEqual(getLoggingLevel(), "silent");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should capture invalid logging level as-is (lowercased)", () => {
|
||||||
|
const args = ["--safe-chain-logging=invalid", "install"];
|
||||||
|
initializeCliArguments(args);
|
||||||
|
|
||||||
|
assert.strictEqual(getLoggingLevel(), "invalid");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle logging with other safe-chain args", () => {
|
||||||
|
const args = [
|
||||||
|
"--safe-chain-debug",
|
||||||
|
"--safe-chain-logging=silent",
|
||||||
|
"--safe-chain-malware-action=block",
|
||||||
|
"install",
|
||||||
|
];
|
||||||
|
const result = initializeCliArguments(args);
|
||||||
|
|
||||||
|
assert.deepEqual(result, ["install"]);
|
||||||
|
assert.strictEqual(getLoggingLevel(), "silent");
|
||||||
|
assert.strictEqual(getMalwareAction(), "block");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -10,5 +10,18 @@ export function getMalwareAction() {
|
||||||
return MALWARE_ACTION_BLOCK;
|
return MALWARE_ACTION_BLOCK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getLoggingLevel() {
|
||||||
|
const level = cliArguments.getLoggingLevel();
|
||||||
|
|
||||||
|
if (level === LOGGING_SILENT) {
|
||||||
|
return LOGGING_SILENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
return LOGGING_NORMAL;
|
||||||
|
}
|
||||||
|
|
||||||
export const MALWARE_ACTION_BLOCK = "block";
|
export const MALWARE_ACTION_BLOCK = "block";
|
||||||
export const MALWARE_ACTION_PROMPT = "prompt";
|
export const MALWARE_ACTION_PROMPT = "prompt";
|
||||||
|
|
||||||
|
export const LOGGING_SILENT = "silent";
|
||||||
|
export const LOGGING_NORMAL = "normal";
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,27 @@ import chalk from "chalk";
|
||||||
import ora from "ora";
|
import ora from "ora";
|
||||||
import { createInterface } from "readline";
|
import { createInterface } from "readline";
|
||||||
import { isCi } from "./environment.js";
|
import { isCi } from "./environment.js";
|
||||||
|
import { getLoggingLevel, LOGGING_SILENT } from "../config/settings.js";
|
||||||
|
|
||||||
|
function isSilentMode() {
|
||||||
|
return getLoggingLevel() === LOGGING_SILENT;
|
||||||
|
}
|
||||||
|
|
||||||
function emptyLine() {
|
function emptyLine() {
|
||||||
|
if (isSilentMode()) return;
|
||||||
|
|
||||||
writeInformation("");
|
writeInformation("");
|
||||||
}
|
}
|
||||||
|
|
||||||
function writeInformation(message, ...optionalParams) {
|
function writeInformation(message, ...optionalParams) {
|
||||||
|
if (isSilentMode()) return;
|
||||||
|
|
||||||
console.log(message, ...optionalParams);
|
console.log(message, ...optionalParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
function writeWarning(message, ...optionalParams) {
|
function writeWarning(message, ...optionalParams) {
|
||||||
|
if (isSilentMode()) return;
|
||||||
|
|
||||||
if (!isCi()) {
|
if (!isCi()) {
|
||||||
message = chalk.yellow(message);
|
message = chalk.yellow(message);
|
||||||
}
|
}
|
||||||
|
|
@ -26,7 +37,24 @@ function writeError(message, ...optionalParams) {
|
||||||
console.error(message, ...optionalParams);
|
console.error(message, ...optionalParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function writeExitWithoutInstallingMaliciousPackages() {
|
||||||
|
let message = "Safe-chain: Exiting without installing malicious packages.";
|
||||||
|
if (!isCi()) {
|
||||||
|
message = chalk.red(message);
|
||||||
|
}
|
||||||
|
console.error(message);
|
||||||
|
}
|
||||||
|
|
||||||
function startProcess(message) {
|
function startProcess(message) {
|
||||||
|
if (isSilentMode()) {
|
||||||
|
return {
|
||||||
|
succeed: () => {},
|
||||||
|
fail: () => {},
|
||||||
|
stop: () => {},
|
||||||
|
setText: () => {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (isCi()) {
|
if (isCi()) {
|
||||||
return {
|
return {
|
||||||
succeed: (message) => {
|
succeed: (message) => {
|
||||||
|
|
@ -60,7 +88,7 @@ function startProcess(message) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function confirm(config) {
|
async function confirm(config) {
|
||||||
if (isCi()) {
|
if (isCi() || isSilentMode()) {
|
||||||
return Promise.resolve(config.default);
|
return Promise.resolve(config.default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -91,6 +119,7 @@ export const ui = {
|
||||||
writeInformation,
|
writeInformation,
|
||||||
writeWarning,
|
writeWarning,
|
||||||
writeError,
|
writeError,
|
||||||
|
writeExitWithoutInstallingMaliciousPackages,
|
||||||
emptyLine,
|
emptyLine,
|
||||||
startProcess,
|
startProcess,
|
||||||
confirm,
|
confirm,
|
||||||
|
|
|
||||||
|
|
@ -153,7 +153,7 @@ function verifyNoMaliciousPackages() {
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.emptyLine();
|
ui.emptyLine();
|
||||||
ui.writeError("Exiting without installing malicious packages.");
|
ui.writeExitWithoutInstallingMaliciousPackages();
|
||||||
ui.emptyLine();
|
ui.emptyLine();
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ async function onMalwareFound() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.writeError("Exiting without installing malicious packages.");
|
ui.writeExitWithoutInstallingMaliciousPackages();
|
||||||
ui.emptyLine();
|
ui.emptyLine();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue