mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 12:10:49 +00:00
Some cleanup
This commit is contained in:
parent
97bbc77162
commit
c71320386e
13 changed files with 1601 additions and 240 deletions
|
|
@ -9,3 +9,26 @@ The installer bundles the Safe Chain Node.js application into a standalone binar
|
|||
1. Install the `safe-chain` binary to the system PATH
|
||||
2. Generate and install the CA certificate in the OS trust store
|
||||
3. Configure the system for automatic MITM proxy interception
|
||||
|
||||
## Building the Installer
|
||||
|
||||
To build the installer for the current platform, run the following command from the root of the workspace:
|
||||
|
||||
```bash
|
||||
npm run build:installer
|
||||
```
|
||||
|
||||
To build for a specific platform, you can pass arguments to the script:
|
||||
|
||||
```bash
|
||||
# macOS
|
||||
npm run build:installer -- --platform=macos
|
||||
|
||||
# Linux
|
||||
npm run build:installer -- --platform=linux
|
||||
|
||||
# Windows
|
||||
npm run build:installer -- --platform=windows
|
||||
```
|
||||
|
||||
The build artifacts (binaries and installer packages) will be created in the `installer/dist` directory.
|
||||
|
|
|
|||
|
|
@ -1,46 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Wrapper script for certificate generation during installer build
|
||||
* This re-exports the certificate generation functionality from the main package
|
||||
*/
|
||||
|
||||
import { generateCACertificate } from '../packages/safe-chain/src/registryProxy/certUtils.js';
|
||||
import { writeFileSync, mkdirSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
|
||||
/**
|
||||
* Generate certificate files (simple version for installer build)
|
||||
* For the full CLI version with nice output, use: safe-chain _generate-cert
|
||||
*
|
||||
* @param {string} outputDir - Directory to save certificate files
|
||||
* @returns {Promise<{certPath: string, keyPath: string}>}
|
||||
*/
|
||||
export async function generateCertificates(outputDir) {
|
||||
console.log('Generating Safe Chain CA certificate...');
|
||||
|
||||
mkdirSync(outputDir, { recursive: true });
|
||||
|
||||
const { cert, key } = generateCACertificate();
|
||||
|
||||
const certPath = join(outputDir, 'ca-cert.pem');
|
||||
const keyPath = join(outputDir, 'ca-key.pem');
|
||||
|
||||
writeFileSync(certPath, cert);
|
||||
writeFileSync(keyPath, key);
|
||||
|
||||
console.log('✓ Certificate generated:');
|
||||
console.log(` Certificate: ${certPath}`);
|
||||
console.log(` Private Key: ${keyPath}`);
|
||||
|
||||
return { certPath, keyPath };
|
||||
}
|
||||
|
||||
// CLI usage - when run directly
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
const outputDir = process.argv[2] || './certs';
|
||||
generateCertificates(outputDir).catch(error => {
|
||||
console.error('Error generating certificates:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
1545
installer/package-lock.json
generated
Normal file
1545
installer/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,21 +0,0 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Get the directory where this script is located
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
INSTALLER_ROOT="${SCRIPT_DIR}/.."
|
||||
|
||||
echo "=== Building Safe Chain Installer for macOS ==="
|
||||
|
||||
# Ensure we are in the installer directory
|
||||
cd "${INSTALLER_ROOT}"
|
||||
|
||||
# Install dependencies
|
||||
echo "Installing build dependencies..."
|
||||
npm install
|
||||
|
||||
# Build the binary and installer using the Node.js build script
|
||||
echo "Building binary and installer..."
|
||||
node build.js --platform=macos
|
||||
|
||||
echo "Done."
|
||||
|
|
@ -3,6 +3,12 @@ set -e
|
|||
|
||||
echo "Installing Safe Chain..."
|
||||
|
||||
# Get the actual user (console user)
|
||||
ACTUAL_USER=$(stat -f '%Su' /dev/console)
|
||||
if [ -z "$ACTUAL_USER" ] || [ "$ACTUAL_USER" = "root" ]; then
|
||||
echo "Warning: Could not detect console user, defaulting to root ownership might cause issues."
|
||||
fi
|
||||
|
||||
# The binary is installed to the location specified by --install-location
|
||||
# which is passed as $3 (installation volume/mountpoint)
|
||||
INSTALL_LOCATION="${3}/tmp/safe-chain-install"
|
||||
|
|
@ -53,8 +59,6 @@ fi
|
|||
echo "Starting Safe Chain proxy service..."
|
||||
|
||||
# Get the actual user to install the LaunchAgent in their home
|
||||
# (We still need this for the LaunchAgent, but not for file permissions)
|
||||
ACTUAL_USER=$(stat -f '%Su' /dev/console)
|
||||
USER_HOME=$(eval echo "~${ACTUAL_USER}")
|
||||
|
||||
# Create LaunchAgent for auto-start on login
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
#!/bin/bash
|
||||
echo "=== Building Safe Chain Installer for Linux ==="
|
||||
echo "TODO: Implement Linux installer build"
|
||||
echo "This is a placeholder script."
|
||||
exit 0
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
@echo off
|
||||
echo === Building Safe Chain Installer for Windows ===
|
||||
echo TODO: Implement Windows installer build
|
||||
echo This is a placeholder script.
|
||||
exit /b 0
|
||||
11
package-lock.json
generated
11
package-lock.json
generated
|
|
@ -15,6 +15,17 @@
|
|||
"oxlint": "^1.22.0"
|
||||
}
|
||||
},
|
||||
"installer": {
|
||||
"name": "@aikidosec/safe-chain-installer",
|
||||
"version": "1.0.0",
|
||||
"extraneous": true,
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@yao-pkg/pkg": "^5.15.0",
|
||||
"esbuild": "^0.24.0"
|
||||
},
|
||||
"devDependencies": {}
|
||||
},
|
||||
"node_modules/@aikidosec/safe-chain": {
|
||||
"resolved": "packages/safe-chain",
|
||||
"link": true
|
||||
|
|
|
|||
|
|
@ -4,15 +4,14 @@
|
|||
"type": "module",
|
||||
"workspaces": [
|
||||
"packages/*",
|
||||
"test/e2e",
|
||||
"installer"
|
||||
"test/e2e"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "npm run test --workspace=packages/safe-chain --workspace=packages/safe-chain-bun",
|
||||
"test:e2e": "npm run test --workspace=test/e2e",
|
||||
"lint": "npm run lint --workspace=packages/safe-chain",
|
||||
"typecheck": "npm run typecheck --workspace=packages/safe-chain",
|
||||
"build:installer": "npm run build --workspace=installer"
|
||||
"build:installer": "cd installer && npm install && npm run build"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
|||
|
|
@ -1,8 +1,3 @@
|
|||
/**
|
||||
* Generate certificate command for Safe Chain
|
||||
* Creates CA certificate and key for MITM proxy
|
||||
*/
|
||||
|
||||
import { generateCACertificate } from "../registryProxy/certUtils.js";
|
||||
import { writeFileSync, mkdirSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
|
|
@ -12,6 +7,7 @@ import chalk from "chalk";
|
|||
|
||||
/**
|
||||
* Generate certificate command
|
||||
* Allows us to call this independently, for instance from the installer.
|
||||
* @param {string[]} args - Command line arguments
|
||||
*/
|
||||
export async function generateCertCommand(args) {
|
||||
|
|
@ -26,9 +22,6 @@ export async function generateCertCommand(args) {
|
|||
}
|
||||
|
||||
try {
|
||||
ui.writeInformation(chalk.bold("Generating Safe Chain CA certificate..."));
|
||||
ui.emptyLine();
|
||||
|
||||
// Create output directory
|
||||
mkdirSync(outputDir, { recursive: true });
|
||||
|
||||
|
|
@ -41,20 +34,7 @@ export async function generateCertCommand(args) {
|
|||
|
||||
writeFileSync(certPath, cert);
|
||||
writeFileSync(keyPath, key);
|
||||
|
||||
ui.writeInformation(chalk.green("✓") + " Certificate generated successfully!");
|
||||
ui.emptyLine();
|
||||
ui.writeInformation(chalk.bold("Files created:"));
|
||||
ui.writeInformation(` Certificate: ${chalk.cyan(certPath)}`);
|
||||
ui.writeInformation(` Private Key: ${chalk.cyan(keyPath)}`);
|
||||
ui.emptyLine();
|
||||
ui.writeInformation(chalk.dim("To install this certificate in your system trust store:"));
|
||||
ui.writeInformation(chalk.dim(" macOS: sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain " + certPath));
|
||||
ui.writeInformation(chalk.dim(" Linux: sudo cp " + certPath + " /usr/local/share/ca-certificates/ && sudo update-ca-certificates"));
|
||||
ui.writeInformation(chalk.dim(" Windows: certutil -addstore -f ROOT " + certPath));
|
||||
ui.emptyLine();
|
||||
} catch (/** @type {any} */ error) {
|
||||
ui.writeError(`Failed to generate certificate: ${error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,82 +0,0 @@
|
|||
import fs from "fs";
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
|
||||
/**
|
||||
* Get the path to the proxy state file
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getProxyStateFilePath() {
|
||||
const homeDir = os.homedir();
|
||||
const safeChainDir = path.join(homeDir, ".safe-chain");
|
||||
|
||||
// Ensure directory exists
|
||||
if (!fs.existsSync(safeChainDir)) {
|
||||
fs.mkdirSync(safeChainDir, { recursive: true });
|
||||
}
|
||||
|
||||
return path.join(safeChainDir, "proxy-state.json");
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the proxy state to a file that shell scripts can read
|
||||
* @param {{port: number, url: string, pid: number, ecosystem: string, certPath: string}} state
|
||||
*/
|
||||
export function writeProxyState(state) {
|
||||
const statePath = getProxyStateFilePath();
|
||||
fs.writeFileSync(statePath, JSON.stringify(state, null, 2), "utf-8");
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the current proxy state
|
||||
* @returns {{port: number, url: string, pid: number, ecosystem: string, certPath: string} | null}
|
||||
*/
|
||||
export function readProxyState() {
|
||||
const statePath = getProxyStateFilePath();
|
||||
|
||||
if (!fs.existsSync(statePath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const content = fs.readFileSync(statePath, "utf-8");
|
||||
const state = JSON.parse(content);
|
||||
|
||||
// Verify the process is still running
|
||||
if (state.pid) {
|
||||
try {
|
||||
// Sending signal 0 checks if process exists without actually sending a signal
|
||||
process.kill(state.pid, 0);
|
||||
return state;
|
||||
} catch {
|
||||
// Process doesn't exist, clean up state file
|
||||
clearProxyState();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the proxy state file
|
||||
*/
|
||||
export function clearProxyState() {
|
||||
const statePath = getProxyStateFilePath();
|
||||
|
||||
if (fs.existsSync(statePath)) {
|
||||
fs.unlinkSync(statePath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a proxy is currently running
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isProxyRunning() {
|
||||
const state = readProxyState();
|
||||
return state !== null;
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@ import { StandaloneProxyService } from "./standaloneProxy.js";
|
|||
import { ui } from "../environment/userInteraction.js";
|
||||
import chalk from "chalk";
|
||||
import { initializeCliArguments } from "../config/cliArguments.js";
|
||||
import { writeProxyState, clearProxyState } from "./proxyState.js";
|
||||
import { getCaCertPath } from "../registryProxy/certUtils.js";
|
||||
|
||||
/**
|
||||
|
|
@ -34,15 +33,6 @@ export async function runCommand(args) {
|
|||
|
||||
// Setup event listeners
|
||||
service.on("started", ({ port, url }) => {
|
||||
// Write proxy state to file so shell integration can detect it
|
||||
writeProxyState({
|
||||
port,
|
||||
url,
|
||||
pid: process.pid,
|
||||
ecosystem: 'all',
|
||||
certPath: getCaCertPath(),
|
||||
});
|
||||
|
||||
ui.emptyLine();
|
||||
ui.writeInformation(chalk.green("✔") + " Safe Chain proxy started successfully!");
|
||||
ui.emptyLine();
|
||||
|
|
@ -68,9 +58,6 @@ export async function runCommand(args) {
|
|||
});
|
||||
|
||||
service.on("stopped", ({ blockedPackages }) => {
|
||||
// Clear proxy state file
|
||||
clearProxyState();
|
||||
|
||||
ui.emptyLine();
|
||||
ui.writeInformation(chalk.yellow("Proxy stopped."));
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import { initializeCliArguments } from "./config/cliArguments.js";
|
|||
import { createSafeChainProxy } from "./registryProxy/registryProxy.js";
|
||||
import chalk from "chalk";
|
||||
import { getAuditStats } from "./scanning/audit/index.js";
|
||||
import { readProxyState } from "./agent/proxyState.js";
|
||||
|
||||
/**
|
||||
* @param {string[]} args
|
||||
|
|
@ -18,32 +17,15 @@ export async function main(args) {
|
|||
process.on("SIGTERM", handleProcessTermination);
|
||||
|
||||
// Check if a proxy is already running from 'safe-chain run'
|
||||
const existingProxy = readProxyState();
|
||||
const usingExistingProxy = existingProxy !== null;
|
||||
// In the new agent architecture, we rely on system-wide environment variables
|
||||
// so we don't need to detect or connect to an existing proxy here.
|
||||
// The 'main' function is now only used when running 'aikido-npm' etc. directly
|
||||
// (legacy wrapper mode) or when running 'safe-chain run' (which doesn't call main() directly)
|
||||
|
||||
let proxy;
|
||||
if (usingExistingProxy) {
|
||||
// Use the existing proxy - don't start a new one
|
||||
ui.writeInformation(`Safe-chain: Using existing proxy at ${existingProxy.url}`);
|
||||
// Create a proxy object that uses the existing proxy
|
||||
// We need to set the environment variables to point to the existing proxy
|
||||
const url = new URL(existingProxy.url);
|
||||
const port = parseInt(url.port);
|
||||
|
||||
// Import and set the proxy state so getSafeChainProxyEnvironmentVariables works
|
||||
const { setProxyState } = await import("./registryProxy/registryProxy.js");
|
||||
setProxyState(port, existingProxy.certPath);
|
||||
|
||||
proxy = {
|
||||
verifyNoMaliciousPackages: () => true, // Existing proxy handles this
|
||||
getBlockedRequests: () => [], // Can't access blocked requests from existing proxy
|
||||
stopServer: async () => {}, // Don't stop the existing proxy
|
||||
};
|
||||
} else {
|
||||
// No existing proxy, start one inline
|
||||
proxy = createSafeChainProxy();
|
||||
await proxy.startServer();
|
||||
}
|
||||
// No existing proxy logic needed anymore as we don't wrap commands when using the agent
|
||||
proxy = createSafeChainProxy();
|
||||
await proxy.startServer();
|
||||
|
||||
// Global error handlers to log unhandled errors
|
||||
process.on("uncaughtException", (error) => {
|
||||
|
|
@ -91,19 +73,11 @@ export async function main(args) {
|
|||
const auditStats = getAuditStats();
|
||||
if (auditStats.totalPackages > 0) {
|
||||
ui.emptyLine();
|
||||
if (usingExistingProxy) {
|
||||
ui.writeInformation(
|
||||
`${chalk.green("✔")} Safe-chain: Scanned ${
|
||||
auditStats.totalPackages
|
||||
} packages via proxy, no malware found.`
|
||||
);
|
||||
} else {
|
||||
ui.writeInformation(
|
||||
`${chalk.green("✔")} Safe-chain: Scanned ${
|
||||
auditStats.totalPackages
|
||||
} packages, no malware found.`
|
||||
);
|
||||
}
|
||||
ui.writeInformation(
|
||||
`${chalk.green("✔")} Safe-chain: Scanned ${
|
||||
auditStats.totalPackages
|
||||
} packages, no malware found.`
|
||||
);
|
||||
}
|
||||
|
||||
// Returning the exit code back to the caller allows the promise
|
||||
|
|
@ -116,10 +90,7 @@ export async function main(args) {
|
|||
// to be awaited in the bin files and return the correct exit code
|
||||
return 1;
|
||||
} finally {
|
||||
// Only stop the proxy if we started it (not using existing proxy)
|
||||
if (!usingExistingProxy) {
|
||||
await proxy.stopServer();
|
||||
}
|
||||
await proxy.stopServer();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue