mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 12:10:49 +00:00
Compare commits
8 commits
main
...
0.0.8-new-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c37b0c8265 | ||
|
|
83b62f9c7a | ||
|
|
67a8f2db52 | ||
|
|
0411a579ae | ||
|
|
6006760b67 | ||
|
|
22580bcf5f | ||
|
|
f6abfb8a4e | ||
|
|
9d1f7ac6fd |
3 changed files with 290 additions and 119 deletions
|
|
@ -317,6 +317,35 @@ main() {
|
|||
|
||||
info "Binary installed to: $FINAL_FILE"
|
||||
|
||||
# Download safechain-proxy
|
||||
if [ "$OS" = "macos" ] || [ "$OS" = "linux" ] || [ "$OS" = "linuxstatic" ]; then
|
||||
info "Downloading safechain-proxy..."
|
||||
|
||||
if [ "$OS" = "macos" ]; then
|
||||
if [ "$ARCH" = "arm64" ]; then
|
||||
PROXY_URL="https://github.com/AikidoSec/safechain-internals/releases/download/v0.0.7-linux-proxy-bins/safechain-proxy-darwin-arm64"
|
||||
else
|
||||
PROXY_URL="https://github.com/AikidoSec/safechain-internals/releases/download/v0.0.7-linux-proxy-bins/safechain-proxy-darwin-amd64"
|
||||
fi
|
||||
else
|
||||
# Linux (both linux and linuxstatic)
|
||||
if [ "$ARCH" = "x64" ]; then
|
||||
PROXY_URL="https://github.com/AikidoSec/safechain-internals/releases/download/v0.0.7-linux-proxy-bins/safechain-proxy-linux-amd64"
|
||||
else
|
||||
# Skip for non-x64 Linux architectures
|
||||
info "Skipping safechain-proxy download (not available for linux-${ARCH})"
|
||||
PROXY_URL=""
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$PROXY_URL" ]; then
|
||||
PROXY_FILE="${INSTALL_DIR}/safechain-proxy"
|
||||
download "$PROXY_URL" "$PROXY_FILE"
|
||||
chmod +x "$PROXY_FILE" || error "Failed to make proxy executable"
|
||||
info "Proxy installed to: $PROXY_FILE"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Build setup command based on arguments
|
||||
SETUP_CMD="setup"
|
||||
SETUP_ARGS=""
|
||||
|
|
|
|||
115
packages/safe-chain/src/registryProxy/createRamaProxy.js
Normal file
115
packages/safe-chain/src/registryProxy/createRamaProxy.js
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
import { ChildProcess, spawn } from "node:child_process";
|
||||
import { existsSync } from "node:fs";
|
||||
import { mkdtempSync, readFile, writeFile } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { dirname, join } from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
import { ui } from "../environment/userInteraction.js";
|
||||
import { getLoggingLevel, LOGGING_VERBOSE } from "../config/settings.js";
|
||||
|
||||
const readFilePromise = promisify(readFile);
|
||||
const writeFilePromise = promisify(writeFile);
|
||||
|
||||
/**
|
||||
* @typedef {Object} RamaProxyInstance
|
||||
* @property {ChildProcess} process
|
||||
* @property {string} proxyAddress
|
||||
* @property {string} metaAddress
|
||||
* @property {string} certPath
|
||||
*/
|
||||
|
||||
/**
|
||||
* @returns {String | null}
|
||||
*/
|
||||
export function getRamaPath() {
|
||||
const executableDir = dirname(process.execPath);
|
||||
const ramaPath = join(executableDir, "safechain-proxy");
|
||||
|
||||
if (existsSync(ramaPath)) {
|
||||
return ramaPath;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} ramaPath
|
||||
*
|
||||
* @returns {import("./registryProxy.js").SafeChainProxy} */
|
||||
export function createRamaProxy(ramaPath) {
|
||||
const tempDir = mkdtempSync(join(tmpdir(), "safe-chain-proxy-"));
|
||||
/** @type {RamaProxyInstance | null} */
|
||||
let ramaInstance = null;
|
||||
|
||||
return {
|
||||
startServer: async () => {
|
||||
ramaInstance = await startRama(ramaPath, tempDir);
|
||||
ui.writeVerbose(
|
||||
`Proxy started at address "${ramaInstance.proxyAddress}"`
|
||||
);
|
||||
},
|
||||
stopServer: async () => {
|
||||
if (ramaInstance) {
|
||||
ramaInstance.process.kill();
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
verifyNoMaliciousPackages: () => true,
|
||||
hasSuppressedVersions: () => false,
|
||||
getServerPort: () => {
|
||||
if (!ramaInstance) return null;
|
||||
const url = new URL(`http://${ramaInstance.proxyAddress}`);
|
||||
return url.port ? parseInt(url.port, 10) : null;
|
||||
},
|
||||
getCombinedCaBundlePath: () => ramaInstance?.certPath ?? "",
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} ramaPath
|
||||
* @param {string} dataFolder
|
||||
* @returns {Promise<RamaProxyInstance>}
|
||||
*/
|
||||
async function startRama(ramaPath, dataFolder) {
|
||||
const startTime = Date.now();
|
||||
const args = ["--secrets", "memory", "--data", dataFolder];
|
||||
const process =
|
||||
getLoggingLevel() === LOGGING_VERBOSE
|
||||
? spawn(ramaPath, args, {
|
||||
stdio: "inherit",
|
||||
})
|
||||
: spawn(ramaPath, args);
|
||||
|
||||
// wait for the proxy process to start (poll for proxy.addr.txt file)
|
||||
const proxyAddrPath = join(dataFolder, "proxy.addr.txt");
|
||||
const maxWaitTime = 60000; // 60 seconds
|
||||
const pollInterval = 500; // 500 ms
|
||||
|
||||
while (!existsSync(proxyAddrPath)) {
|
||||
if (Date.now() - startTime > maxWaitTime) {
|
||||
throw new Error("Timeout waiting for proxy to start");
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
||||
}
|
||||
|
||||
const elapsedTime = Date.now() - startTime;
|
||||
ui.writeVerbose(`Proxy started in ${elapsedTime}ms`);
|
||||
|
||||
const proxyAddress = await readFilePromise(proxyAddrPath, "utf-8");
|
||||
const metaAddress = await readFilePromise(
|
||||
join(dataFolder, "meta.addr.txt"),
|
||||
"utf-8"
|
||||
);
|
||||
|
||||
const certResponse = await fetch(`http://${metaAddress}/ca`);
|
||||
const cert = await certResponse.text();
|
||||
const certPath = join(dataFolder, "cert.ca");
|
||||
await writeFilePromise(certPath, cert);
|
||||
|
||||
return {
|
||||
process,
|
||||
proxyAddress,
|
||||
metaAddress,
|
||||
certPath,
|
||||
};
|
||||
}
|
||||
|
|
@ -7,37 +7,47 @@ import { ui } from "../environment/userInteraction.js";
|
|||
import chalk from "chalk";
|
||||
import { createInterceptorForUrl } from "./interceptors/createInterceptorForEcoSystem.js";
|
||||
import { getHasSuppressedVersions } from "./interceptors/npm/modifyNpmInfo.js";
|
||||
import { createRamaProxy, getRamaPath } from "./createRamaProxy.js";
|
||||
|
||||
const SERVER_STOP_TIMEOUT_MS = 1000;
|
||||
/**
|
||||
* @type {{port: number | null, blockedRequests: {packageName: string, version: string, url: string}[]}}
|
||||
* @typedef {Object} SafeChainProxy
|
||||
* @prop {() => Promise<void>} startServer
|
||||
* @prop {() => Promise<void>} stopServer
|
||||
* @prop {() => boolean} verifyNoMaliciousPackages
|
||||
* @prop {() => boolean} hasSuppressedVersions
|
||||
* @prop {() => Number | null} getServerPort
|
||||
* @prop {() => string} getCombinedCaBundlePath
|
||||
*/
|
||||
const state = {
|
||||
port: null,
|
||||
blockedRequests: [],
|
||||
};
|
||||
|
||||
/** @type {SafeChainProxy} */
|
||||
let server;
|
||||
|
||||
export function createSafeChainProxy() {
|
||||
const server = createProxyServer();
|
||||
if (server) {
|
||||
return server;
|
||||
}
|
||||
|
||||
return {
|
||||
startServer: () => startServer(server),
|
||||
stopServer: () => stopServer(server),
|
||||
verifyNoMaliciousPackages,
|
||||
hasSuppressedVersions: getHasSuppressedVersions,
|
||||
};
|
||||
let ramaPath = getRamaPath();
|
||||
if (ramaPath) {
|
||||
ui.writeInformation("Starting safe-chain rama proxy");
|
||||
server = createRamaProxy(ramaPath);
|
||||
} else {
|
||||
server = createBuiltInProxyServer();
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Record<string, string>}
|
||||
*/
|
||||
function getSafeChainProxyEnvironmentVariables() {
|
||||
if (!state.port) {
|
||||
if (!server || !server.getServerPort()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const proxyUrl = `http://localhost:${state.port}`;
|
||||
const caCertPath = getCombinedCaBundlePath();
|
||||
const proxyUrl = `http://localhost:${server.getServerPort()}`;
|
||||
const caCertPath = server.getCombinedCaBundlePath();
|
||||
|
||||
return {
|
||||
HTTPS_PROXY: proxyUrl,
|
||||
|
|
@ -68,7 +78,17 @@ export function mergeSafeChainProxyEnvironmentVariables(env) {
|
|||
return proxyEnv;
|
||||
}
|
||||
|
||||
function createProxyServer() {
|
||||
/** @returns {SafeChainProxy} */
|
||||
function createBuiltInProxyServer() {
|
||||
const SERVER_STOP_TIMEOUT_MS = 1000;
|
||||
/**
|
||||
* @type {{port: number | null, blockedRequests: {packageName: string, version: string, url: string}[]}}
|
||||
*/
|
||||
const state = {
|
||||
port: null,
|
||||
blockedRequests: [],
|
||||
};
|
||||
|
||||
const server = http.createServer(
|
||||
// This handles direct HTTP requests (non-CONNECT requests)
|
||||
// This is normally http-only traffic, but we also handle
|
||||
|
|
@ -79,8 +99,14 @@ function createProxyServer() {
|
|||
// This handles HTTPS requests via the CONNECT method
|
||||
server.on("connect", handleConnect);
|
||||
|
||||
return server;
|
||||
}
|
||||
return {
|
||||
startServer: () => startServer(server),
|
||||
stopServer: () => stopServer(server),
|
||||
verifyNoMaliciousPackages,
|
||||
hasSuppressedVersions: getHasSuppressedVersions,
|
||||
getServerPort: () => state.port,
|
||||
getCombinedCaBundlePath,
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import("http").Server} server
|
||||
|
|
@ -190,3 +216,4 @@ function verifyNoMaliciousPackages() {
|
|||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue