Rename safe-chain ultimate to Aikido Endpoint

This commit is contained in:
Sander Declerck 2026-03-30 12:03:36 +02:00
parent 2c8a1b4972
commit 99e822d509
No known key found for this signature in database
9 changed files with 25 additions and 599 deletions

14
install-scripts/install-endpoint-mac.sh Normal file → Executable file
View file

@ -1,14 +1,14 @@
#!/bin/sh
# Downloads and installs SafeChain Ultimate endpoint on macOS
# Downloads and installs Aikido Endpoint Protection on macOS
#
# Usage: curl -fsSL <url> | sudo sh -s -- --token <TOKEN>
set -e # Exit on error
# Configuration
INSTALL_URL="https://github.com/AikidoSec/safechain-internals/releases/download/v1.2.5/SafeChainUltimate.pkg"
DOWNLOAD_SHA256="abc2b0e6c6a4ca33cd893eeb16744f9f2da90013fb1abac301f5c00c2ad8bc30"
INSTALL_URL="https://github.com/AikidoSec/safechain-internals/releases/download/v1.2.7/EndpointProtection.pkg"
DOWNLOAD_SHA256="2c180c575b6fbeb1e33b69cf8357a2a7dbf6868b5f98cfb82b83243daccc0cf9"
TOKEN_FILE="/tmp/aikido_endpoint_token.txt"
# Colors for output
@ -111,10 +111,10 @@ main() {
esac
# 2. Download and verify checksum
PKG_FILE=$(mktemp /tmp/SafeChainUltimate.XXXXXX.pkg)
PKG_FILE=$(mktemp /tmp/AikidoEndpoint.XXXXXX.pkg)
trap cleanup EXIT
info "Downloading SafeChain Ultimate..."
info "Downloading Aikido Endpoint Protection..."
download "$INSTALL_URL" "$PKG_FILE"
info "Verifying checksum..."
@ -124,10 +124,10 @@ main() {
printf "%s" "$TOKEN" > "$TOKEN_FILE"
# 4. Install the package
info "Installing SafeChain Ultimate..."
info "Installing Aikido Endpoint Protection..."
installer -pkg "$PKG_FILE" -target /
info "SafeChain Ultimate installed successfully!"
info "Aikido Endpoint Protection installed successfully!"
}
main "$@"

View file

@ -1,4 +1,4 @@
# Downloads and installs SafeChain Ultimate endpoint on Windows
# Downloads and installs Aikido Endpoint Protection on Windows
#
# Usage: iex "& { $(iwr '<url>' -UseBasicParsing) } -token <TOKEN>"
@ -7,8 +7,8 @@ param(
)
# Configuration
$InstallUrl = "https://github.com/AikidoSec/safechain-internals/releases/download/v1.2.5/SafeChainUltimate.msi"
$DownloadSha256 = "c4d1be7bb2128473b8e955244dc186b5d3f091f668b43cdd3d810cff9d38193c"
$InstallUrl = "https://github.com/AikidoSec/safechain-internals/releases/download/v1.2.7/EndpointProtection.msi"
$DownloadSha256 = "7bad18d7df9e0654d2edd16a52aea34b0455c3c6d8fb407362d0a86a77cb7d4f"
# Ensure TLS 1.2 is enabled for downloads
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
@ -53,9 +53,9 @@ function Install-Endpoint {
}
# 2. Download the .msi
$msiFile = Join-Path $env:TEMP "SafeChainUltimate-$([System.Guid]::NewGuid().ToString('N')).msi"
$msiFile = Join-Path $env:TEMP "AikidoEndpoint-$([System.Guid]::NewGuid().ToString('N')).msi"
Write-Info "Downloading SafeChain Ultimate..."
Write-Info "Downloading Aikido Endpoint Protection..."
try {
$ProgressPreference = 'SilentlyContinue'
Invoke-WebRequest -Uri $InstallUrl -OutFile $msiFile -UseBasicParsing
@ -75,13 +75,13 @@ function Install-Endpoint {
Write-Info "Checksum verified successfully."
# 3. Install the package with token passed as MSI property
Write-Info "Installing SafeChain Ultimate..."
Write-Info "Installing Aikido Endpoint Protection..."
$process = Start-Process -FilePath "msiexec" -ArgumentList "/i", "`"$msiFile`"", "/qn", "/norestart", "AIKIDO_TOKEN=$token" -Wait -PassThru
if ($process.ExitCode -ne 0) {
Write-Error-Custom "MSI installer failed (exit code: $($process.ExitCode))."
}
Write-Info "SafeChain Ultimate installed successfully!"
Write-Info "Aikido Endpoint Protection installed successfully!"
}
finally {
# Cleanup

10
install-scripts/uninstall-endpoint-mac.sh Normal file → Executable file
View file

@ -1,13 +1,13 @@
#!/bin/sh
# Uninstalls SafeChain Ultimate endpoint on macOS
# Uninstalls Aikido Endpoint Protection on macOS
#
# Usage: curl -fsSL <url> | sudo sh
set -e # Exit on error
# Configuration
UNINSTALL_SCRIPT="/Library/Application Support/AikidoSecurity/SafeChainUltimate/scripts/uninstall"
UNINSTALL_SCRIPT="/Library/Application Support/AikidoSecurity/EndpointProtection/scripts/uninstall"
# Colors for output
RED='\033[0;31m'
@ -38,13 +38,13 @@ main() {
# Check if the uninstall script exists
if [ ! -f "$UNINSTALL_SCRIPT" ]; then
error "SafeChain Ultimate does not appear to be installed (uninstall script not found)."
error "Aikido Endpoint Protection does not appear to be installed (uninstall script not found)."
fi
info "Uninstalling SafeChain Ultimate..."
info "Uninstalling Aikido Endpoint Protection..."
"$UNINSTALL_SCRIPT"
info "SafeChain Ultimate uninstalled successfully!"
info "Aikido Endpoint Protection uninstalled successfully!"
}
main "$@"

View file

@ -1,9 +1,9 @@
# Uninstalls SafeChain Ultimate endpoint on Windows
# Uninstalls Aikido Endpoint Protection endpoint on Windows
#
# Usage: iex (iwr '<url>' -UseBasicParsing)
# Configuration
$AppName = "SafeChain Ultimate"
$AppName = "Aikido Endpoint Protection"
# Helper functions
function Write-Info {
@ -32,22 +32,22 @@ function Uninstall-Endpoint {
}
# Find the installed product
Write-Info "Looking for SafeChain Ultimate installation..."
Write-Info "Looking for Aikido Endpoint Protection installation..."
$app = Get-WmiObject -Class Win32_Product -Filter "Name='$AppName'"
if (-not $app) {
Write-Error-Custom "SafeChain Ultimate does not appear to be installed."
Write-Error-Custom "Aikido Endpoint Protection does not appear to be installed."
}
$productCode = $app.IdentifyingNumber
Write-Info "Uninstalling SafeChain Ultimate..."
Write-Info "Uninstalling Aikido Endpoint Protection..."
$process = Start-Process -FilePath "msiexec" -ArgumentList "/x", $productCode, "/qn", "/norestart" -Wait -PassThru
if ($process.ExitCode -ne 0) {
Write-Error-Custom "Uninstall failed (exit code: $($process.ExitCode))."
}
Write-Info "SafeChain Ultimate uninstalled successfully!"
Write-Info "Aikido Endpoint Protection uninstalled successfully!"
}
# Run uninstallation

View file

@ -1,125 +0,0 @@
import { createWriteStream, createReadStream } from "fs";
import { createHash } from "crypto";
import { pipeline } from "stream/promises";
import fetch from "make-fetch-happen";
const ULTIMATE_VERSION = "v1.0.0";
export const DOWNLOAD_URLS = {
win32: {
x64: {
url: `https://github.com/AikidoSec/safechain-internals/releases/download/${ULTIMATE_VERSION}/SafeChainUltimate-windows-amd64.msi`,
checksum:
"sha256:c6a36f9b8e55ab6b7e8742cbabc4469d85809237c0f5e6c21af20b36c416ee1d",
},
arm64: {
url: `https://github.com/AikidoSec/safechain-internals/releases/download/${ULTIMATE_VERSION}/SafeChainUltimate-windows-arm64.msi`,
checksum:
"sha256:46acd1af6a9938ea194c8ee8b34ca9b47c8de22e088a0791f3c0751dd6239c90",
},
},
darwin: {
x64: {
url: `https://github.com/AikidoSec/safechain-internals/releases/download/${ULTIMATE_VERSION}/SafeChainUltimate-darwin-amd64.pkg`,
checksum:
"sha256:bb1829e8ca422e885baf37bef08dcbe7df7a30f248e2e89c4071564f7d4f3396",
},
arm64: {
url: `https://github.com/AikidoSec/safechain-internals/releases/download/${ULTIMATE_VERSION}/SafeChainUltimate-darwin-arm64.pkg`,
checksum:
"sha256:7fe4a785709911cc366d8224b4c290677573b8c4833bd9054768299e55c5f0ed",
},
},
};
/**
* Builds the download URL for the SafeChain Agent installer.
* @param {string} fileName
*/
export function getAgentDownloadUrl(fileName) {
return `https://github.com/AikidoSec/safechain-internals/releases/download/${ULTIMATE_VERSION}/${fileName}`;
}
/**
* Downloads a file from a URL to a local path.
* @param {string} url
* @param {string} destPath
*/
export async function downloadFile(url, destPath) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Download failed: ${response.statusText}`);
}
await pipeline(response.body, createWriteStream(destPath));
}
/**
* Returns the current agent version.
*/
export function getAgentVersion() {
return ULTIMATE_VERSION;
}
/**
* Returns download info (url, checksum) for the current OS and architecture.
* @returns {{ url: string, checksum: string } | null}
*/
export function getDownloadInfoForCurrentPlatform() {
const platform = process.platform;
const arch = process.arch;
if (!Object.hasOwn(DOWNLOAD_URLS, platform)) {
return null;
}
const platformUrls =
DOWNLOAD_URLS[/** @type {keyof typeof DOWNLOAD_URLS} */ (platform)];
if (!Object.hasOwn(platformUrls, arch)) {
return null;
}
return platformUrls[/** @type {keyof typeof platformUrls} */ (arch)];
}
/**
* Verifies the checksum of a file.
* @param {string} filePath
* @param {string} expectedChecksum - Format: "algorithm:hash" (e.g., "sha256:abc123...")
* @returns {Promise<boolean>}
*/
export async function verifyChecksum(filePath, expectedChecksum) {
const [algorithm, expected] = expectedChecksum.split(":");
const hash = createHash(algorithm);
if (filePath.includes("..")) throw new Error("Invalid file path");
const stream = createReadStream(filePath);
for await (const chunk of stream) {
hash.update(chunk);
}
const actual = hash.digest("hex");
return actual === expected;
}
/**
* Downloads the SafeChain agent for the current OS/arch and verifies its checksum.
* @param {string} fileName - Destination file path
* @returns {Promise<string | null>} The file path if successful, null if no download URL for current platform
*/
export async function downloadAgentToFile(fileName) {
const info = getDownloadInfoForCurrentPlatform();
if (!info) {
return null;
}
await downloadFile(info.url, fileName);
const isValid = await verifyChecksum(fileName, info.checksum);
if (!isValid) {
throw new Error("Checksum verification failed");
}
return fileName;
}

View file

@ -1,56 +0,0 @@
import { describe, it, after } from "node:test";
import assert from "node:assert";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { unlinkSync, writeFileSync } from "node:fs";
import { createHash } from "node:crypto";
import {
DOWNLOAD_URLS,
verifyChecksum,
} from "./downloadAgent.js";
describe("downloadAgent", () => {
const tempFiles = [];
after(() => {
for (const file of tempFiles) {
try {
unlinkSync(file);
} catch {
// ignore cleanup errors
}
}
});
for (const [platform, architectures] of Object.entries(DOWNLOAD_URLS)) {
for (const [arch, { url, checksum }] of Object.entries(architectures)) {
it(`${platform}/${arch} has a valid download definition`, () => {
assert.match(
url,
/^https:\/\/github\.com\/AikidoSec\/safechain-internals\/releases\/download\/v\d+\.\d+\.\d+\/.+/,
);
assert.match(checksum, /^sha256:[a-f0-9]{64}$/);
});
}
}
it("verifies checksum for a local file", async () => {
const destPath = join(tmpdir(), `safe-chain-test-${Date.now()}`);
tempFiles.push(destPath);
writeFileSync(destPath, "safe-chain-test");
const expectedHash = createHash("sha256")
.update("safe-chain-test")
.digest("hex");
assert.equal(
await verifyChecksum(destPath, `sha256:${expectedHash}`),
true,
);
assert.equal(
await verifyChecksum(destPath, `sha256:${"0".repeat(64)}`),
false,
);
});
});

View file

@ -1,155 +0,0 @@
import { tmpdir } from "os";
import { unlinkSync } from "fs";
import { join } from "path";
import { execSync, spawnSync } from "child_process";
import { ui } from "../environment/userInteraction.js";
import { printVerboseAndSafeSpawn } from "../utils/safeSpawn.js";
import { downloadAgentToFile, getAgentVersion } from "./downloadAgent.js";
import chalk from "chalk";
const MACOS_PKG_IDENTIFIER = "com.aikidosecurity.safechainultimate";
/**
* Checks if root privileges are available and displays error message if not.
* @param {string} command - The sudo command to show in the error message
* @returns {boolean} True if running as root, false otherwise.
*/
function requireRootPrivileges(command) {
if (isRunningAsRoot()) {
return true;
}
ui.writeError("Root privileges required.");
ui.writeInformation("Please run this command with sudo:");
ui.writeInformation(` ${command}`);
return false;
}
function isRunningAsRoot() {
const rootUserUid = 0;
return process.getuid?.() === rootUserUid;
}
export async function installOnMacOS() {
if (!requireRootPrivileges("sudo safe-chain ultimate")) {
return;
}
const pkgPath = join(tmpdir(), `SafeChainUltimate-${Date.now()}.pkg`);
ui.emptyLine();
ui.writeInformation(`📥 Downloading SafeChain Ultimate ${getAgentVersion()}`);
ui.writeVerbose(`Destination: ${pkgPath}`);
const result = await downloadAgentToFile(pkgPath);
if (!result) {
ui.writeError("No download available for this platform/architecture.");
return;
}
try {
ui.writeInformation("⚙️ Installing SafeChain Ultimate...");
await runPkgInstaller(pkgPath);
ui.emptyLine();
ui.writeInformation(
"✅ SafeChain Ultimate installed and started successfully!",
);
ui.emptyLine();
ui.writeInformation(
chalk.cyan("🔐 ") +
chalk.bold("ACTION REQUIRED: ") +
"macOS will show a popup to install our certificate.",
);
ui.writeInformation(
" " +
chalk.bold("Please accept the certificate") +
" to complete the installation.",
);
ui.emptyLine();
} finally {
ui.writeVerbose(`Cleaning up temporary file: ${pkgPath}`);
cleanup(pkgPath);
}
}
const MACOS_UNINSTALL_SCRIPT =
"/Library/Application\\ Support/AikidoSecurity/SafeChainUltimate/scripts/uninstall";
export async function uninstallOnMacOS() {
if (!requireRootPrivileges("sudo safe-chain ultimate uninstall")) {
return;
}
ui.emptyLine();
if (!isPackageInstalled()) {
ui.writeInformation("SafeChain Ultimate is not installed.");
return;
}
ui.writeInformation("🗑️ Uninstalling SafeChain Ultimate...");
ui.writeVerbose(`Running: ${MACOS_UNINSTALL_SCRIPT}`);
const result = spawnSync(MACOS_UNINSTALL_SCRIPT, {
stdio: "inherit",
shell: true,
});
if (result.status !== 0) {
ui.writeError(
`Uninstall script failed (exit code: ${result.status}). Please try again or remove manually.`,
);
return;
}
ui.emptyLine();
ui.writeInformation("✅ SafeChain Ultimate has been uninstalled.");
ui.emptyLine();
}
function isPackageInstalled() {
try {
const output = execSync(`pkgutil --pkg-info ${MACOS_PKG_IDENTIFIER}`, {
encoding: "utf8",
stdio: "pipe",
});
return output.includes(MACOS_PKG_IDENTIFIER);
} catch {
return false;
}
}
/**
* @param {string} pkgPath
*/
async function runPkgInstaller(pkgPath) {
// Uses installer to install the package (https://ss64.com/mac/installer.html)
// Options:
// -pkg (required): The package to be installed.
// -target (required): The target volume is specified with the -target parameter.
// --> "-target /" installs to the current boot volume.
const result = await printVerboseAndSafeSpawn(
"installer",
["-pkg", pkgPath, "-target", "/"],
{
stdio: "inherit",
},
);
if (result.status !== 0) {
throw new Error(`PKG installer failed (exit code: ${result.status})`);
}
}
/**
* @param {string} pkgPath
*/
function cleanup(pkgPath) {
try {
unlinkSync(pkgPath);
} catch {
ui.writeVerbose("Failed to clean up temporary installer file.");
}
}

View file

@ -1,203 +0,0 @@
import { tmpdir } from "os";
import { unlinkSync } from "fs";
import { join } from "path";
import { execSync } from "child_process";
import { ui } from "../environment/userInteraction.js";
import { printVerboseAndSafeSpawn, safeSpawn } from "../utils/safeSpawn.js";
import { downloadAgentToFile, getAgentVersion } from "./downloadAgent.js";
const WINDOWS_SERVICE_NAME = "SafeChainUltimate";
const WINDOWS_APP_NAME = "SafeChain Ultimate";
export async function uninstallOnWindows() {
if (!(await requireAdminPrivileges())) {
return;
}
ui.emptyLine();
const productCode = getInstalledProductCode();
if (!productCode) {
ui.writeInformation("SafeChain Ultimate is not installed.");
return;
}
await stopServiceIfRunning();
ui.writeInformation("🗑️ Uninstalling SafeChain Ultimate...");
await uninstallByProductCode(productCode);
ui.emptyLine();
ui.writeInformation("✅ SafeChain Ultimate has been uninstalled.");
ui.emptyLine();
}
export async function installOnWindows() {
if (!(await requireAdminPrivileges())) {
return;
}
const msiPath = join(tmpdir(), `SafeChainUltimate-${Date.now()}.msi`);
ui.emptyLine();
ui.writeInformation(`📥 Downloading SafeChain Ultimate ${getAgentVersion()}`);
ui.writeVerbose(`Destination: ${msiPath}`);
const result = await downloadAgentToFile(msiPath);
if (!result) {
ui.writeError("No download available for this platform/architecture.");
return;
}
try {
ui.emptyLine();
await stopServiceIfRunning();
await uninstallIfInstalled();
ui.writeInformation("⚙️ Installing SafeChain Ultimate...");
await runMsiInstaller(msiPath);
ui.emptyLine();
ui.writeInformation(
"✅ SafeChain Ultimate installed and started successfully!",
);
ui.emptyLine();
} finally {
ui.writeVerbose(`Cleaning up temporary file: ${msiPath}`);
cleanup(msiPath);
}
}
/**
* Checks if admin privileges are available and displays error message if not.
* @returns {Promise<boolean>} True if running as admin, false otherwise.
*/
async function requireAdminPrivileges() {
if (await isRunningAsAdmin()) {
return true;
}
ui.writeError("Administrator privileges required.");
ui.writeInformation(
"Please run this command in an elevated terminal (Run as Administrator).",
);
return false;
}
async function isRunningAsAdmin() {
// Uses Windows Security API to check if current process has admin privileges.
// Returns "True" or "False" as a string.
const result = await safeSpawn(
"powershell",
[
"-Command",
"([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)",
],
{ stdio: "pipe" },
);
return result.status === 0 && result.stdout.trim() === "True";
}
/**
* Returns the MSI product code for SafeChain Ultimate, or null if not installed.
* @returns {string | null}
*/
function getInstalledProductCode() {
// Query Win32_Product via WMI to find the installed SafeChain Agent.
// If found, outputs the product GUID (e.g., "{12345678-1234-...}") needed for msiexec uninstall.
ui.writeVerbose(`Finding product code with PowerShell`);
let productCode;
try {
productCode = execSync(
`powershell -Command "$app = Get-WmiObject -Class Win32_Product -Filter \\"Name='${WINDOWS_APP_NAME}'\\"; if ($app) { Write-Output $app.IdentifyingNumber }"`,
{ encoding: "utf8" },
).trim();
} catch {
return null;
}
return productCode || null;
}
/**
* @param {string} productCode
*/
async function uninstallByProductCode(productCode) {
ui.writeVerbose(`Found product code: ${productCode}`);
// Use msiexec to run the msi installer quitely (https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/msiexec)
// Options:
// - /x: Uninstalls the package.
// - /qn: Specifies there's no UI during the installation process.
// - /norestart: Stops the device from restarting after the installation completes.
const uninstallResult = await printVerboseAndSafeSpawn(
"msiexec",
["/x", productCode, "/qn", "/norestart"],
{ stdio: "inherit" },
);
if (uninstallResult.status !== 0) {
throw new Error(`Uninstall failed (exit code: ${uninstallResult.status})`);
}
}
async function uninstallIfInstalled() {
const productCode = getInstalledProductCode();
if (!productCode) {
ui.writeVerbose("No existing installation found (fresh install).");
return;
}
ui.writeInformation("🗑️ Removing previous installation...");
await uninstallByProductCode(productCode);
}
/**
* @param {string} msiPath
*/
async function runMsiInstaller(msiPath) {
// Use msiexec to run the msi installer quitely (https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/msiexec)
// Options:
// - /i: Specifies normal installation
// - /qn: Specifies there's no UI during the installation process.
const result = await printVerboseAndSafeSpawn(
"msiexec",
["/i", msiPath, "/qn"],
{
stdio: "inherit",
},
);
if (result.status !== 0) {
throw new Error(`MSI installer failed (exit code: ${result.status})`);
}
}
async function stopServiceIfRunning() {
ui.writeInformation("⏹️ Stopping running service...");
const result = await printVerboseAndSafeSpawn(
"net",
["stop", WINDOWS_SERVICE_NAME],
{
stdio: "pipe",
},
);
if (result.status !== 0) {
ui.writeVerbose("Service not running (will start after installation).");
}
}
/**
* @param {string} msiPath
*/
function cleanup(msiPath) {
try {
unlinkSync(msiPath);
} catch {
ui.writeVerbose("Failed to clean up temporary installer file.");
}
}

View file

@ -1,35 +0,0 @@
import { platform } from "os";
import { ui } from "../environment/userInteraction.js";
import { initializeCliArguments } from "../config/cliArguments.js";
import { installOnWindows, uninstallOnWindows } from "./installOnWindows.js";
import { installOnMacOS, uninstallOnMacOS } from "./installOnMacOS.js";
export async function uninstallUltimate() {
initializeCliArguments(process.argv);
const operatingSystem = platform();
if (operatingSystem === "win32") {
await uninstallOnWindows();
} else if (operatingSystem === "darwin") {
await uninstallOnMacOS();
} else {
ui.writeInformation(
`Uninstall is not yet supported on ${operatingSystem}.`,
);
}
}
export async function installUltimate() {
const operatingSystem = platform();
if (operatingSystem === "win32") {
await installOnWindows();
} else if (operatingSystem === "darwin") {
await installOnMacOS();
} else {
ui.writeInformation(
`${operatingSystem} is not supported yet by SafeChain's ultimate version.`,
);
}
}