mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 12:10:49 +00:00
Merge pull request #285 from AikidoSec/logging-as-env-variable
Allow to configure loglevel through an env variable
This commit is contained in:
commit
31b5f73197
5 changed files with 172 additions and 34 deletions
25
README.md
25
README.md
|
|
@ -152,24 +152,37 @@ iex (iwr "https://github.com/AikidoSec/safe-chain/releases/latest/download/unins
|
|||
|
||||
## Logging
|
||||
|
||||
You can control the output from Aikido Safe Chain using the `--safe-chain-logging` flag:
|
||||
You can control the output from Aikido Safe Chain using the `--safe-chain-logging` flag or the `SAFE_CHAIN_LOGGING` environment variable.
|
||||
|
||||
- `--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.
|
||||
### Configuration Options
|
||||
|
||||
Example usage:
|
||||
You can set the logging level through multiple sources (in order of priority):
|
||||
|
||||
1. **CLI Argument** (highest priority):
|
||||
|
||||
- `--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.
|
||||
|
||||
```shell
|
||||
npm install express --safe-chain-logging=silent
|
||||
```
|
||||
|
||||
- `--safe-chain-logging=verbose` - Enables detailed diagnostic output from Aikido Safe Chain. Useful for troubleshooting issues or understanding what Safe Chain is doing behind the scenes.
|
||||
|
||||
Example usage:
|
||||
- `--safe-chain-logging=verbose` - Enables detailed diagnostic output from Aikido Safe Chain. Useful for troubleshooting issues or understanding what Safe Chain is doing behind the scenes.
|
||||
|
||||
```shell
|
||||
npm install express --safe-chain-logging=verbose
|
||||
```
|
||||
|
||||
2. **Environment Variable**:
|
||||
|
||||
```shell
|
||||
export SAFE_CHAIN_LOGGING=verbose
|
||||
npm install express
|
||||
```
|
||||
|
||||
Valid values: `silent`, `normal`, `verbose`
|
||||
|
||||
This is useful for setting a default logging level for all package manager commands in your terminal session or CI/CD environment.
|
||||
|
||||
## Minimum Package Age
|
||||
|
||||
You can configure how long packages must exist before Safe Chain allows their installation. By default, packages must be at least 24 hours old before they can be installed through npm-based package managers.
|
||||
|
|
|
|||
|
|
@ -48,12 +48,16 @@ These test packages are flagged as malware and should be blocked by Safe Chain.
|
|||
|
||||
### Logging Options
|
||||
|
||||
Use logging flags to get more information:
|
||||
Use logging flags or environment variables to get more information:
|
||||
|
||||
```bash
|
||||
# Verbose mode - detailed diagnostic output for troubleshooting
|
||||
npm install express --safe-chain-logging=verbose
|
||||
|
||||
# Or set it globally for all commands in your session
|
||||
export SAFE_CHAIN_LOGGING=verbose
|
||||
npm install express
|
||||
|
||||
# Silent mode - suppress all output except malware blocking
|
||||
npm install express --safe-chain-logging=silent
|
||||
```
|
||||
|
|
@ -277,11 +281,16 @@ rm -rf ~/.safe-chain
|
|||
|
||||
### Enable Verbose Logging
|
||||
|
||||
Get detailed diagnostic output:
|
||||
Get detailed diagnostic output using a CLI flag or environment variable:
|
||||
|
||||
```bash
|
||||
# Using CLI flag
|
||||
npm install express --safe-chain-logging=verbose
|
||||
pip install requests --safe-chain-logging=verbose
|
||||
|
||||
# Using environment variable (applies to all commands)
|
||||
export SAFE_CHAIN_LOGGING=verbose
|
||||
npm install express
|
||||
```
|
||||
|
||||
### Report Issues
|
||||
|
|
|
|||
|
|
@ -25,3 +25,12 @@ export function getNpmCustomRegistries() {
|
|||
export function getPipCustomRegistries() {
|
||||
return process.env.SAFE_CHAIN_PIP_CUSTOM_REGISTRIES;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the logging level from environment variable
|
||||
* Valid values: "silent", "normal", "verbose"
|
||||
* @returns {string | undefined}
|
||||
*/
|
||||
export function getLoggingLevel() {
|
||||
return process.env.SAFE_CHAIN_LOGGING;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,14 +7,20 @@ export const LOGGING_NORMAL = "normal";
|
|||
export const LOGGING_VERBOSE = "verbose";
|
||||
|
||||
export function getLoggingLevel() {
|
||||
const level = cliArguments.getLoggingLevel();
|
||||
|
||||
if (level === LOGGING_SILENT) {
|
||||
return LOGGING_SILENT;
|
||||
// Priority 1: CLI argument
|
||||
const cliLevel = cliArguments.getLoggingLevel();
|
||||
if (cliLevel === LOGGING_SILENT || cliLevel === LOGGING_VERBOSE) {
|
||||
return cliLevel;
|
||||
}
|
||||
if (cliLevel) {
|
||||
// CLI arg was set but invalid, default to normal for backwards compatibility.
|
||||
return LOGGING_NORMAL;
|
||||
}
|
||||
|
||||
if (level === LOGGING_VERBOSE) {
|
||||
return LOGGING_VERBOSE;
|
||||
// Priority 2: Environment variable
|
||||
const envLevel = environmentVariables.getLoggingLevel()?.toLowerCase();
|
||||
if (envLevel === LOGGING_SILENT || envLevel === LOGGING_VERBOSE) {
|
||||
return envLevel;
|
||||
}
|
||||
|
||||
return LOGGING_NORMAL;
|
||||
|
|
|
|||
|
|
@ -11,9 +11,15 @@ mock.module("fs", {
|
|||
},
|
||||
});
|
||||
|
||||
const { getNpmCustomRegistries, getPipCustomRegistries } = await import(
|
||||
"./settings.js"
|
||||
);
|
||||
const {
|
||||
getNpmCustomRegistries,
|
||||
getPipCustomRegistries,
|
||||
getLoggingLevel,
|
||||
LOGGING_SILENT,
|
||||
LOGGING_NORMAL,
|
||||
LOGGING_VERBOSE,
|
||||
} = await import("./settings.js");
|
||||
const { initializeCliArguments } = await import("./cliArguments.js");
|
||||
|
||||
for (const { packageManager, getCustomRegistries, envVarName } of [
|
||||
{
|
||||
|
|
@ -26,8 +32,7 @@ for (const { packageManager, getCustomRegistries, envVarName } of [
|
|||
getCustomRegistries: getPipCustomRegistries,
|
||||
envVarName: "SAFE_CHAIN_PIP_CUSTOM_REGISTRIES",
|
||||
},
|
||||
])
|
||||
{
|
||||
]) {
|
||||
describe(getCustomRegistries.name, async () => {
|
||||
let originalEnv;
|
||||
|
||||
|
|
@ -55,7 +60,10 @@ for (const { packageManager, getCustomRegistries, envVarName } of [
|
|||
it("should return registries without protocol", () => {
|
||||
configFileContent = JSON.stringify({
|
||||
[packageManager]: {
|
||||
customRegistries: [`${packageManager}.company.com`, "registry.internal.net"],
|
||||
customRegistries: [
|
||||
`${packageManager}.company.com`,
|
||||
"registry.internal.net",
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -143,8 +151,7 @@ for (const { packageManager, getCustomRegistries, envVarName } of [
|
|||
|
||||
it("should parse comma-separated registries from environment variable", () => {
|
||||
delete process.env[envVarName];
|
||||
process.env[envVarName] =
|
||||
"env1.registry.com,env2.registry.net";
|
||||
process.env[envVarName] = "env1.registry.com,env2.registry.net";
|
||||
configFileContent = undefined;
|
||||
|
||||
const registries = getCustomRegistries();
|
||||
|
|
@ -157,8 +164,7 @@ for (const { packageManager, getCustomRegistries, envVarName } of [
|
|||
|
||||
it("should trim whitespace from environment variable registries", () => {
|
||||
delete process.env[envVarName];
|
||||
process.env[envVarName] =
|
||||
" env1.registry.com , env2.registry.net ";
|
||||
process.env[envVarName] = " env1.registry.com , env2.registry.net ";
|
||||
configFileContent = undefined;
|
||||
|
||||
const registries = getCustomRegistries();
|
||||
|
|
@ -188,11 +194,15 @@ for (const { packageManager, getCustomRegistries, envVarName } of [
|
|||
|
||||
it("should remove duplicate registries when merging env and config", () => {
|
||||
delete process.env[envVarName];
|
||||
process.env[envVarName] =
|
||||
`${packageManager}.company.com,env.registry.com`;
|
||||
process.env[
|
||||
envVarName
|
||||
] = `${packageManager}.company.com,env.registry.com`;
|
||||
configFileContent = JSON.stringify({
|
||||
[packageManager]: {
|
||||
customRegistries: [`${packageManager}.company.com`, "config.registry.net"],
|
||||
customRegistries: [
|
||||
`${packageManager}.company.com`,
|
||||
"config.registry.net",
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -221,8 +231,7 @@ for (const { packageManager, getCustomRegistries, envVarName } of [
|
|||
|
||||
it("should handle empty strings in comma-separated list", () => {
|
||||
delete process.env[envVarName];
|
||||
process.env[envVarName] =
|
||||
"env1.registry.com,,env2.registry.net,";
|
||||
process.env[envVarName] = "env1.registry.com,,env2.registry.net,";
|
||||
configFileContent = undefined;
|
||||
|
||||
const registries = getCustomRegistries();
|
||||
|
|
@ -264,3 +273,95 @@ for (const { packageManager, getCustomRegistries, envVarName } of [
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe("getLoggingLevel", () => {
|
||||
let originalEnv;
|
||||
|
||||
beforeEach(() => {
|
||||
originalEnv = process.env.SAFE_CHAIN_LOGGING;
|
||||
delete process.env.SAFE_CHAIN_LOGGING;
|
||||
// Reset CLI arguments state
|
||||
initializeCliArguments([]);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (originalEnv !== undefined) {
|
||||
process.env.SAFE_CHAIN_LOGGING = originalEnv;
|
||||
} else {
|
||||
delete process.env.SAFE_CHAIN_LOGGING;
|
||||
}
|
||||
});
|
||||
|
||||
it("should return normal by default when nothing is configured", () => {
|
||||
const level = getLoggingLevel();
|
||||
|
||||
assert.strictEqual(level, LOGGING_NORMAL);
|
||||
});
|
||||
|
||||
it("should return silent from environment variable", () => {
|
||||
process.env.SAFE_CHAIN_LOGGING = "silent";
|
||||
|
||||
const level = getLoggingLevel();
|
||||
|
||||
assert.strictEqual(level, LOGGING_SILENT);
|
||||
});
|
||||
|
||||
it("should return verbose from environment variable", () => {
|
||||
process.env.SAFE_CHAIN_LOGGING = "verbose";
|
||||
|
||||
const level = getLoggingLevel();
|
||||
|
||||
assert.strictEqual(level, LOGGING_VERBOSE);
|
||||
});
|
||||
|
||||
it("should handle uppercase environment variable values", () => {
|
||||
process.env.SAFE_CHAIN_LOGGING = "VERBOSE";
|
||||
|
||||
const level = getLoggingLevel();
|
||||
|
||||
assert.strictEqual(level, LOGGING_VERBOSE);
|
||||
});
|
||||
|
||||
it("should handle mixed case environment variable values", () => {
|
||||
process.env.SAFE_CHAIN_LOGGING = "Silent";
|
||||
|
||||
const level = getLoggingLevel();
|
||||
|
||||
assert.strictEqual(level, LOGGING_SILENT);
|
||||
});
|
||||
|
||||
it("should return normal for invalid environment variable values", () => {
|
||||
process.env.SAFE_CHAIN_LOGGING = "invalid";
|
||||
|
||||
const level = getLoggingLevel();
|
||||
|
||||
assert.strictEqual(level, LOGGING_NORMAL);
|
||||
});
|
||||
|
||||
it("should prioritize CLI argument over environment variable", () => {
|
||||
process.env.SAFE_CHAIN_LOGGING = "verbose";
|
||||
initializeCliArguments(["--safe-chain-logging=silent"]);
|
||||
|
||||
const level = getLoggingLevel();
|
||||
|
||||
assert.strictEqual(level, LOGGING_SILENT);
|
||||
});
|
||||
|
||||
it("should use environment variable when CLI argument is not set", () => {
|
||||
process.env.SAFE_CHAIN_LOGGING = "silent";
|
||||
initializeCliArguments(["install", "express"]);
|
||||
|
||||
const level = getLoggingLevel();
|
||||
|
||||
assert.strictEqual(level, LOGGING_SILENT);
|
||||
});
|
||||
|
||||
it("should return normal when CLI argument is invalid (even if env var is valid)", () => {
|
||||
process.env.SAFE_CHAIN_LOGGING = "verbose";
|
||||
initializeCliArguments(["--safe-chain-logging=invalid"]);
|
||||
|
||||
const level = getLoggingLevel();
|
||||
|
||||
assert.strictEqual(level, LOGGING_NORMAL);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue