All posts

Node.js guide

How to protect a Discord bot with System Locker

Use Quicksilver to authenticate your bot once, then keep its authorization session alive with rotating heartbeat tokens.

By System Locker 8 min read

Before your bot can even think about serving commands, it must confirm it's authorized to run. This guide covers a small, practical setup for adding that check to a discord.js deployment.

Why use Quicksilver for a bot?

Quicksilver is the production session-auth API. Your bot makes one key-only initialization request when it starts, then sends a lightweight heartbeat at the accepted interval. Every successful heartbeat replaces the previous token, so your process must retain the new value.

Create a system, generate a license key for it, and use that key only in the environment where the bot runs. Keep the Discord token and license key separate so each can be rotated without changing the other.

1. Configure the bot host

Use Node.js 18 or later so that fetch is available. Keep the Discord token and System Locker license key in environment variables. Do not place the system ID in an easy-to-access location.

DISCORD_TOKEN=your-discord-bot-token
SYSTEM_LOCKER_KEY=the-license-key-for-this-bot

Install node-machine-id with npm install node-machine-id. Because this bot is sold to customers and runs on their hosts, derive the HWID from the host's stable OS machine identifier instead of asking each customer to invent a random value. Hash that identifier before sending it; it is an identifier for detecting changes and sharing, not a password or an unforgeable secret.

2. Add a small Quicksilver client

This client uses the key-only endpoint, verifies the signed initialization response, checks that the returned identity is the key it sent, and rotates the heartbeat token. Fail closed: if initialization or a heartbeat fails, do not log the bot in.

import crypto from 'node:crypto';
import { machineIdSync } from 'node-machine-id';

const baseUrl = 'https://systemlocker.net/quicksilver';

function verifyInitResponse(response, expectedKey) {
  const colon = response.lastIndexOf(':');
  if (colon <= 0) return null;

  const signed = response.slice(0, colon);
  const receivedHash = response.slice(colon + 1);
  const expectedHash = crypto.createHash('sha1').update(signed).digest('hex');
  if (receivedHash.length !== expectedHash.length ||
      !crypto.timingSafeEqual(Buffer.from(receivedHash), Buffer.from(expectedHash))) return null;

  const [token, identifier, timestamp] = signed.split('|');
  if (!token?.startsWith('TT') || identifier !== expectedKey) return null;
  if (Math.abs(Number(timestamp) - Math.floor(Date.now() / 1000 / 29)) > 1) return null;
  return token;
}

async function post(path, fields) {
  const response = await fetch(`${baseUrl}/${path}`, {
    method: 'POST',
    headers: { 'content-type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams(fields),
  });
  return (await response.text()).trim();
}

export async function startQuicksilver() {
  const key = process.env.SYSTEM_LOCKER_KEY;
  const system = process.env.SYSTEM_LOCKER_SYSTEM;
  if (!key || !system) throw new Error('System Locker configuration is incomplete');

  const hwid = crypto.createHash('sha256')
    .update(`system-locker:${system}:${machineIdSync(true)}`)
    .digest('hex');
  const response = await post('init-mikros', {
    key, system, hwid,
    version: 'bypass', beatrate: '30',
  });
  const token = verifyInitResponse(response, key);
  if (!token) throw new Error(`System Locker initialization failed: ${response}`);
  return { system, token };
}

export async function heartbeat(session) {
  const nextToken = await post('beat', { system: session.system, token: session.token });
  if (!nextToken.startsWith('TTr')) throw new Error(`System Locker heartbeat failed: ${nextToken}`);
  session.token = nextToken;
}

3. Authenticate before connecting to Discord

Only create or log in the discord.js client after Quicksilver succeeds. Schedule heartbeats at the exact accepted interval—30 seconds in this example—and stop the process if one fails. Your process manager can restart the bot after you fix the license or network problem.

import { Client, GatewayIntentBits } from 'discord.js';
import { startQuicksilver, heartbeat } from './system-locker.js';

const session = await startQuicksilver();
setInterval(() => {
  heartbeat(session).catch((error) => {
    console.error(error.message);
    process.exit(1);
  });
}, 30_000);

const client = new Client({ intents: [GatewayIntentBits.Guilds] });
await client.login(process.env.DISCORD_TOKEN);

The bot's license key is not an API key: it grants this deployment access to your system. Keep the management API separate and server-side if you use it later for fulfillment or support tooling.

4. Review copied installations with Aegis

A stable HWID gives you a useful signal when a customer copies the bot to another host and reuses the same license key. If Aegis is available for your system, regularly review the authentication logs for one key appearing with different locations or IP addresses. Investigate before enforcing a response: a legitimate host migration, a cloned image, or a container restart can also change or duplicate the identifier.

Do not enable Aegis VPN detection if your bot is expected to run on a VPS or in a cloud environment, as it could trigger false positives and block legitimate users.

When your bot grows

The first deployment only needs the session flow shown above. If your bot grows, you can evaluate higher system and user limits, expanded logging, more variables, reseller tooling, and higher-tier Aegis IP Intelligence when you need more operational visibility.

Ready to try Quicksilver on your bot?

Create a system and protect one real discord.js deployment before you build out your production workflow.