mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 12:10:49 +00:00
Merge branch 'main' into rama-integration-beta
This commit is contained in:
commit
9f0e1aeab0
9 changed files with 232 additions and 10 deletions
37
.github/workflows/build-and-release.yml
vendored
37
.github/workflows/build-and-release.yml
vendored
|
|
@ -60,12 +60,43 @@ jobs:
|
|||
mv binaries/safe-chain-win-x64/safe-chain.exe release-artifacts/safe-chain-win-x64.exe
|
||||
mv binaries/safe-chain-win-arm64/safe-chain.exe release-artifacts/safe-chain-win-arm64.exe
|
||||
|
||||
- name: Move install scripts and hard-code version
|
||||
- name: Move install scripts and hard-code version and checksums
|
||||
env:
|
||||
VERSION: ${{ needs.set-version.outputs.version }}
|
||||
run: |
|
||||
sed "s/\$(fetch_latest_version)/${VERSION}/" install-scripts/install-safe-chain.sh > release-artifacts/install-safe-chain.sh
|
||||
sed "s/\$Version = Get-LatestVersion/\$Version = \"${VERSION}\"/" install-scripts/install-safe-chain.ps1 > release-artifacts/install-safe-chain.ps1
|
||||
SHA_MACOS_X64=$(sha256sum release-artifacts/safe-chain-macos-x64 | awk '{print $1}')
|
||||
SHA_MACOS_ARM64=$(sha256sum release-artifacts/safe-chain-macos-arm64 | awk '{print $1}')
|
||||
SHA_LINUX_X64=$(sha256sum release-artifacts/safe-chain-linux-x64 | awk '{print $1}')
|
||||
SHA_LINUX_ARM64=$(sha256sum release-artifacts/safe-chain-linux-arm64 | awk '{print $1}')
|
||||
SHA_LINUXSTATIC_X64=$(sha256sum release-artifacts/safe-chain-linuxstatic-x64 | awk '{print $1}')
|
||||
SHA_LINUXSTATIC_ARM64=$(sha256sum release-artifacts/safe-chain-linuxstatic-arm64 | awk '{print $1}')
|
||||
SHA_WIN_X64=$(sha256sum release-artifacts/safe-chain-win-x64.exe | awk '{print $1}')
|
||||
SHA_WIN_ARM64=$(sha256sum release-artifacts/safe-chain-win-arm64.exe | awk '{print $1}')
|
||||
|
||||
sed \
|
||||
-e "s/\$(fetch_latest_version)/${VERSION}/" \
|
||||
-e "s|^SHA256_MACOS_X64=\"\"|SHA256_MACOS_X64=\"${SHA_MACOS_X64}\"|" \
|
||||
-e "s|^SHA256_MACOS_ARM64=\"\"|SHA256_MACOS_ARM64=\"${SHA_MACOS_ARM64}\"|" \
|
||||
-e "s|^SHA256_LINUX_X64=\"\"|SHA256_LINUX_X64=\"${SHA_LINUX_X64}\"|" \
|
||||
-e "s|^SHA256_LINUX_ARM64=\"\"|SHA256_LINUX_ARM64=\"${SHA_LINUX_ARM64}\"|" \
|
||||
-e "s|^SHA256_LINUXSTATIC_X64=\"\"|SHA256_LINUXSTATIC_X64=\"${SHA_LINUXSTATIC_X64}\"|" \
|
||||
-e "s|^SHA256_LINUXSTATIC_ARM64=\"\"|SHA256_LINUXSTATIC_ARM64=\"${SHA_LINUXSTATIC_ARM64}\"|" \
|
||||
-e "s|^SHA256_WIN_X64=\"\"|SHA256_WIN_X64=\"${SHA_WIN_X64}\"|" \
|
||||
-e "s|^SHA256_WIN_ARM64=\"\"|SHA256_WIN_ARM64=\"${SHA_WIN_ARM64}\"|" \
|
||||
install-scripts/install-safe-chain.sh > release-artifacts/install-safe-chain.sh
|
||||
|
||||
sed \
|
||||
-e "s/\$Version = Get-LatestVersion/\$Version = \"${VERSION}\"/" \
|
||||
-e "s|^\$SHA256_MACOS_X64 = \"\"|\$SHA256_MACOS_X64 = \"${SHA_MACOS_X64}\"|" \
|
||||
-e "s|^\$SHA256_MACOS_ARM64 = \"\"|\$SHA256_MACOS_ARM64 = \"${SHA_MACOS_ARM64}\"|" \
|
||||
-e "s|^\$SHA256_LINUX_X64 = \"\"|\$SHA256_LINUX_X64 = \"${SHA_LINUX_X64}\"|" \
|
||||
-e "s|^\$SHA256_LINUX_ARM64 = \"\"|\$SHA256_LINUX_ARM64 = \"${SHA_LINUX_ARM64}\"|" \
|
||||
-e "s|^\$SHA256_LINUXSTATIC_X64 = \"\"|\$SHA256_LINUXSTATIC_X64 = \"${SHA_LINUXSTATIC_X64}\"|" \
|
||||
-e "s|^\$SHA256_LINUXSTATIC_ARM64 = \"\"|\$SHA256_LINUXSTATIC_ARM64 = \"${SHA_LINUXSTATIC_ARM64}\"|" \
|
||||
-e "s|^\$SHA256_WIN_X64 = \"\"|\$SHA256_WIN_X64 = \"${SHA_WIN_X64}\"|" \
|
||||
-e "s|^\$SHA256_WIN_ARM64 = \"\"|\$SHA256_WIN_ARM64 = \"${SHA_WIN_ARM64}\"|" \
|
||||
install-scripts/install-safe-chain.ps1 > release-artifacts/install-safe-chain.ps1
|
||||
|
||||
cp install-scripts/uninstall-safe-chain.sh release-artifacts/uninstall-safe-chain.sh
|
||||
cp install-scripts/uninstall-safe-chain.ps1 release-artifacts/uninstall-safe-chain.ps1
|
||||
cp install-scripts/install-endpoint-mac.sh release-artifacts/install-endpoint-mac.sh
|
||||
|
|
|
|||
|
|
@ -290,6 +290,12 @@ You can set custom registries through environment variable or config file. Both
|
|||
}
|
||||
```
|
||||
|
||||
## PYPI Configuration File
|
||||
|
||||
If you rely on a `pip.conf` file for pip configuration you must point pip at it explicitly via the `PIP_CONFIG_FILE` environment variable so Safe Chain can merge it.
|
||||
|
||||
Safe Chain runs pip behind its MITM proxy and writes a temporary pip configuration file to inject its certificate and proxy settings. When `PIP_CONFIG_FILE` is set, Safe Chain merges its settings into a copy of your file (your original file is never modified) so your `index-url`, credentials, and other options are preserved. When `PIP_CONFIG_FILE` is not set, pip's user-level config (e.g. `~/.config/pip/pip.conf`) might be overridden by Safe Chain's temporary file and your settings will not be picked up.
|
||||
|
||||
## Malware List Base URL
|
||||
|
||||
Configure Safe Chain to fetch malware databases and new packages lists from a custom mirror URL. This allows you to host your own copy of the Aikido malware database.
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@
|
|||
set -e # Exit on error
|
||||
|
||||
# Configuration
|
||||
INSTALL_URL="https://github.com/AikidoSec/safechain-internals/releases/download/v1.2.23/EndpointProtection.pkg"
|
||||
DOWNLOAD_SHA256="9af1e0f72e53516c888ade1753ed03f087c1def89244eb0afb60e1f11e8e87e2"
|
||||
INSTALL_URL="https://github.com/AikidoSec/safechain-internals/releases/download/v1.3.4/EndpointProtection.pkg"
|
||||
DOWNLOAD_SHA256="f2ea55588d42e4aa17545ad787f46dd36001009e2ddb9655c497b1a36edf3581"
|
||||
TOKEN_FILE="/tmp/aikido_endpoint_token.txt"
|
||||
|
||||
# Colors for output
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ param(
|
|||
)
|
||||
|
||||
# Configuration
|
||||
$InstallUrl = "https://github.com/AikidoSec/safechain-internals/releases/download/v1.2.23/EndpointProtection.msi"
|
||||
$DownloadSha256 = "3327d35db6654d12dbd7c5ccec0645edb0277f71dcd993ba9733e266bbd235f8"
|
||||
$InstallUrl = "https://github.com/AikidoSec/safechain-internals/releases/download/v1.3.4/EndpointProtection.msi"
|
||||
$DownloadSha256 = "0699379716a9a8b1531befa538befb237252af9f7fd780b33f4dce73588c6f83"
|
||||
|
||||
# Ensure TLS 1.2 is enabled for downloads
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
|
|
|
|||
|
|
@ -52,6 +52,20 @@ $SafeChainBase = $installDirValidation.Normalized
|
|||
$InstallDir = Join-Path $SafeChainBase "bin"
|
||||
$RepoUrl = "https://github.com/AikidoSec/safe-chain"
|
||||
|
||||
# SHA256 checksums for release binaries.
|
||||
# Empty in source; populated by the release pipeline.
|
||||
# When empty (running from main), checksum verification is skipped.
|
||||
# Non-Windows hashes are unused today (PS script is Windows-only) but baked in
|
||||
# for future cross-platform support.
|
||||
$SHA256_MACOS_X64 = ""
|
||||
$SHA256_MACOS_ARM64 = ""
|
||||
$SHA256_LINUX_X64 = ""
|
||||
$SHA256_LINUX_ARM64 = ""
|
||||
$SHA256_LINUXSTATIC_X64 = ""
|
||||
$SHA256_LINUXSTATIC_ARM64 = ""
|
||||
$SHA256_WIN_X64 = ""
|
||||
$SHA256_WIN_ARM64 = ""
|
||||
|
||||
# Ensure TLS 1.2 is enabled for downloads
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
|
||||
|
|
@ -166,6 +180,38 @@ function Get-BinaryName {
|
|||
return "safe-chain-win-$Architecture.exe"
|
||||
}
|
||||
|
||||
# Returns the expected SHA256 for the given OS+arch, or empty if not baked in.
|
||||
function Get-ExpectedSha256 {
|
||||
param([string]$Os, [string]$Architecture)
|
||||
switch ("$Os-$Architecture") {
|
||||
"macos-x64" { return $SHA256_MACOS_X64 }
|
||||
"macos-arm64" { return $SHA256_MACOS_ARM64 }
|
||||
"linux-x64" { return $SHA256_LINUX_X64 }
|
||||
"linux-arm64" { return $SHA256_LINUX_ARM64 }
|
||||
"linuxstatic-x64" { return $SHA256_LINUXSTATIC_X64 }
|
||||
"linuxstatic-arm64" { return $SHA256_LINUXSTATIC_ARM64 }
|
||||
"win-x64" { return $SHA256_WIN_X64 }
|
||||
"win-arm64" { return $SHA256_WIN_ARM64 }
|
||||
default { return "" }
|
||||
}
|
||||
}
|
||||
|
||||
function Test-Checksum {
|
||||
param([string]$File, [string]$Expected)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($Expected)) { return }
|
||||
|
||||
$actual = (Get-FileHash -Path $File -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||
$expectedLower = $Expected.ToLowerInvariant()
|
||||
|
||||
if ($actual -ne $expectedLower) {
|
||||
Remove-Item -Path $File -Force -ErrorAction SilentlyContinue
|
||||
Write-Error-Custom "Checksum verification failed. Expected: $expectedLower, Got: $actual"
|
||||
}
|
||||
|
||||
Write-Info "Checksum verified."
|
||||
}
|
||||
|
||||
# Runs safe-chain setup or setup-ci after the binary is installed.
|
||||
# Temporarily appends the install directory to PATH and downgrades setup failures to warnings.
|
||||
function Invoke-SafeChainSetup {
|
||||
|
|
@ -305,6 +351,9 @@ function Install-SafeChain {
|
|||
Write-Error-Custom "Failed to download from $downloadUrl : $_"
|
||||
}
|
||||
|
||||
$expectedSha = Get-ExpectedSha256 -Os "win" -Architecture $arch
|
||||
Test-Checksum -File $tempFile -Expected $expectedSha
|
||||
|
||||
# Rename to final location
|
||||
$finalFile = Join-Path $InstallDir "safe-chain.exe"
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -55,6 +55,18 @@ SAFE_CHAIN_BASE="${HOME}/.safe-chain"
|
|||
INSTALL_DIR="${SAFE_CHAIN_BASE}/bin"
|
||||
REPO_URL="https://github.com/AikidoSec/safe-chain"
|
||||
|
||||
# SHA256 checksums for release binaries.
|
||||
# Empty in source; populated by the release pipeline via sed.
|
||||
# When empty (running from main), checksum verification is skipped.
|
||||
SHA256_MACOS_X64=""
|
||||
SHA256_MACOS_ARM64=""
|
||||
SHA256_LINUX_X64=""
|
||||
SHA256_LINUX_ARM64=""
|
||||
SHA256_LINUXSTATIC_X64=""
|
||||
SHA256_LINUXSTATIC_ARM64=""
|
||||
SHA256_WIN_X64=""
|
||||
SHA256_WIN_ARM64=""
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
|
|
@ -156,6 +168,57 @@ fetch_latest_version() {
|
|||
echo "$latest_version"
|
||||
}
|
||||
|
||||
# Returns the expected SHA256 for the detected platform, or empty if the
|
||||
# release pipeline has not baked one in (i.e. running the source from main).
|
||||
get_expected_sha256() {
|
||||
os="$1"; arch="$2"
|
||||
case "${os}-${arch}" in
|
||||
macos-x64) echo "$SHA256_MACOS_X64" ;;
|
||||
macos-arm64) echo "$SHA256_MACOS_ARM64" ;;
|
||||
linux-x64) echo "$SHA256_LINUX_X64" ;;
|
||||
linux-arm64) echo "$SHA256_LINUX_ARM64" ;;
|
||||
linuxstatic-x64) echo "$SHA256_LINUXSTATIC_X64" ;;
|
||||
linuxstatic-arm64) echo "$SHA256_LINUXSTATIC_ARM64" ;;
|
||||
win-x64) echo "$SHA256_WIN_X64" ;;
|
||||
win-arm64) echo "$SHA256_WIN_ARM64" ;;
|
||||
*) echo "" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
compute_sha256() {
|
||||
file="$1"
|
||||
if command_exists sha256sum; then
|
||||
sha256sum "$file" | awk '{print $1}'
|
||||
elif command_exists shasum; then
|
||||
shasum -a 256 "$file" | awk '{print $1}'
|
||||
else
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
# Verifies the downloaded binary against the expected hash baked in by the release pipeline.
|
||||
# No-op when no expected hash is set (running the script from main).
|
||||
verify_checksum() {
|
||||
file="$1"; expected="$2"
|
||||
|
||||
if [ -z "$expected" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
actual=$(compute_sha256 "$file")
|
||||
if [ -z "$actual" ]; then
|
||||
rm -f "$file"
|
||||
error "Cannot verify checksum: neither sha256sum nor shasum is available. Install one and re-run."
|
||||
fi
|
||||
|
||||
if [ "$actual" != "$expected" ]; then
|
||||
rm -f "$file"
|
||||
error "Checksum verification failed. Expected: $expected, Got: $actual"
|
||||
fi
|
||||
|
||||
info "Checksum verified."
|
||||
}
|
||||
|
||||
# Download file
|
||||
download() {
|
||||
url="$1"
|
||||
|
|
@ -432,6 +495,9 @@ main() {
|
|||
info "Downloading from: $DOWNLOAD_URL"
|
||||
download "$DOWNLOAD_URL" "$TEMP_FILE"
|
||||
|
||||
EXPECTED_SHA256=$(get_expected_sha256 "$OS" "$ARCH")
|
||||
verify_checksum "$TEMP_FILE" "$EXPECTED_SHA256"
|
||||
|
||||
# Rename and make executable
|
||||
FINAL_FILE=$(get_final_binary_path "$OS")
|
||||
mv "$TEMP_FILE" "$FINAL_FILE" || error "Failed to move binary to $FINAL_FILE"
|
||||
|
|
|
|||
|
|
@ -48,8 +48,11 @@ export function createBuiltInProxyServer() {
|
|||
*/
|
||||
function startServer(server) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Passing port 0 makes the OS assign an available port
|
||||
server.listen(0, () => {
|
||||
// Bind to loopback only. Without an explicit host, Node listens on every
|
||||
// interface, turning the proxy into an unauthenticated forward proxy that
|
||||
// anyone reachable on the network can use to hit the victim's localhost,
|
||||
// intranet, or cloud metadata endpoints. Port 0 lets the OS pick a port.
|
||||
server.listen(0, "127.0.0.1", () => {
|
||||
const address = server.address();
|
||||
if (address && typeof address === "object") {
|
||||
state.port = address.port;
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ export function getProxySettings() {
|
|||
};
|
||||
}
|
||||
|
||||
const proxyUrl = `http://localhost:${server.getServerPort()}`;
|
||||
const proxyUrl = `http://127.0.0.1:${server.getServerPort()}`;
|
||||
const caCert = server.getCaCert();
|
||||
const caCertBundlePath = getCombinedCaBundlePath(caCert);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
import { before, after, describe, it } from "node:test";
|
||||
import assert from "node:assert";
|
||||
import net from "node:net";
|
||||
import os from "node:os";
|
||||
import {
|
||||
createSafeChainProxy,
|
||||
mergeSafeChainProxyEnvironmentVariables,
|
||||
} from "./registryProxy.js";
|
||||
|
||||
describe("registryProxy loopback binding", () => {
|
||||
let proxy, proxyPort;
|
||||
|
||||
before(async () => {
|
||||
proxy = createSafeChainProxy();
|
||||
await proxy.startServer();
|
||||
const envVars = mergeSafeChainProxyEnvironmentVariables([]);
|
||||
proxyPort = parseInt(new URL(envVars.HTTPS_PROXY).port, 10);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await proxy.stopServer();
|
||||
});
|
||||
|
||||
it("advertises a loopback HTTPS_PROXY URL", () => {
|
||||
const envVars = mergeSafeChainProxyEnvironmentVariables([]);
|
||||
const hostname = new URL(envVars.HTTPS_PROXY).hostname;
|
||||
assert.ok(
|
||||
hostname === "127.0.0.1" || hostname === "::1" || hostname === "localhost",
|
||||
`expected loopback hostname, got ${hostname}`
|
||||
);
|
||||
});
|
||||
|
||||
it("refuses connections on non-loopback interfaces", async () => {
|
||||
const externalAddrs = Object.values(os.networkInterfaces())
|
||||
.flat()
|
||||
.filter((iface) => iface && iface.family === "IPv4" && !iface.internal)
|
||||
.map((iface) => iface.address);
|
||||
|
||||
if (externalAddrs.length === 0) {
|
||||
// No non-loopback interface available (e.g. locked-down CI) - skip.
|
||||
return;
|
||||
}
|
||||
|
||||
for (const addr of externalAddrs) {
|
||||
await new Promise((resolve, reject) => {
|
||||
const sock = net.createConnection({ host: addr, port: proxyPort });
|
||||
const timer = setTimeout(() => {
|
||||
sock.destroy();
|
||||
resolve(); // Filtered / dropped is also fine - we just don't want success.
|
||||
}, 500);
|
||||
sock.once("connect", () => {
|
||||
clearTimeout(timer);
|
||||
sock.destroy();
|
||||
reject(
|
||||
new Error(
|
||||
`proxy accepted a connection on non-loopback ${addr}:${proxyPort}`
|
||||
)
|
||||
);
|
||||
});
|
||||
sock.once("error", () => {
|
||||
clearTimeout(timer);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue