How to run the benchmark
Twenty minutes end to end. Most of that is you watching your SOC to see whether they notice.
Run this on a disposable machine only
This client deliberately mimics malware, known signatures and attacker behaviour in order to provoke your security tooling into reacting. It will be flagged. Files will be quarantined. Processes will be killed. The machine may be isolated from the network by your own SOC. All of that is a successful test, not a malfunction.
Use a dedicated laptop you can wipe, or a virtual machine you can roll back. Never run it on:
- an employee's production machine, or your own daily driver;
- a server, a domain controller, or anything else shared;
- any machine whose loss, lockdown or forensic hold would hurt.
Only run it against machines and networks you own or have written authorisation to test. The network tests reach out to known-bad destinations and scan neighbouring hosts: on a corporate network that is indistinguishable from an intrusion until you explain otherwise.
Tell someone first — then don't tell them everything
The point of the exercise is to see what your SOC detects unprompted. So: get written authorisation from whoever owns the risk in advance, but keep the exact timing away from the analysts on shift if you want an honest measurement.
Have a named contact who can call the test off, and be ready to identify yourself the moment anyone asks. An unexplained "intrusion" that runs into a major-incident process is expensive for everybody.
Afterwards, always disclose. Compare their timeline against the one in your report.
Step by step
- Prepare the machine A clean install, or a VM snapshot you can restore. Leave the security tooling exactly as your fleet has it configured — do not add exclusions, and do not disable anything. Measuring your real configuration is the entire point.
- Download the client Grab the binary for the platform under test and verify its SHA-256 against the published checksum. One file, no installer, no runtime.
-
Read what it is about to do
--explainprints every command the run would execute, every outbound destination it would contact and every change it would make — and runs nothing.--list-targetsprints the destinations on their own. These two are what a security team needs in order to approve a run. - Run it with the consent flag The client refuses to do anything destructive-looking until you have explicitly consented. See below.
- Watch it run — and watch your SOC Every check and every trigger prints its result as it completes. The main process supervises each test in its own worker, so when your EDR kills a worker the run continues and records the kill as a detection. Anything that changes machine state writes down how to undo itself before making the change, so a worker that is killed mid-change still gets reverted.
-
Collect the report
The client writes
securitycheck-report-<timestamp>.jsonnext to itself and also prints it to stdout.--formats html,markdown,csv,navigatorwrites the same run as a self-contained HTML report, a summary you can paste into a ticket, a CSV of the results, and an ATT&CK Navigator layer for your own coverage map. -
Upload it, or don't
Uploading gets you the comparison against everyone else — either by hand with
curl, or with--uploadat the end of the run. Reading the JSON locally and never sending it is a perfectly valid way to use this tool. - Destroy the machine Wipe it or roll the snapshot back. The client cleans up after itself, but a machine that has just been used to simulate an intrusion is not a machine to hand back to a user.
The three gates
Nothing that triggers a detection runs without --i-understand. Without it
the client will collect posture information and then stop, which is a useful dry run in itself.
Two further gates sit on top of it, because they are genuinely different decisions:
| Gate | Unlocks | Why it is separate |
|---|---|---|
--i-understand |
Every trigger test whose effect can be undone. | Consent to be attacked on purpose. |
--allow-irreversible |
The two tests that cannot be undone: deleting volume shadow copies, and clearing an event log. | “This machine is disposable” and “destroy the evidence of this run” are not the same statement. Clearing the log also erases what you came to measure. |
--live-samples
--live-samples-key
|
Fetching real malware samples from MalwareBazaar with your own abuse.ch key. | Samples are downloaded and unpacked only, to see whether that alone is noticed. There is no code path that executes a sample and there never will be. |
./securitycheck --safe
./securitycheck --i-understand
On Windows, run the same binary from an elevated PowerShell prompt so the Defender, Group Policy and local-administrator collectors can see what they need:
.\securitycheck-windows-amd64.exe --i-understand
The consent_given field is recorded in the report, so anyone reading it
can tell whether the destructive tests were part of the run or were skipped. So are the run
mode, the profile, whether the run was elevated, whether it was interrupted, and whether live
samples were fetched — a run with fewer privileges legitimately produces fewer verdicts,
and a reader needs to know which it is looking at.
Only --safe is genuinely inert. Every other mode — including
--non-destructive, which merely leaves machine state alone —
writes AV test files, connects to known-bad networks and Tor, beacons to a controlled endpoint,
uploads synthetic canary documents to consumer cloud storage, tries to reach scan databases and
exploit archives, and scans your local subnet. Add --no-network to
suppress all egress and scanning, or --profile quick, which enforces
that itself.
What a full run measures
Three separate things, kept separate in the report because they answer different questions.
- Posture
-
What is configured: protection products and their health, firewall, disk encryption, patch
currency, boot and firmware state, logging and audit configuration, certificate trust, proxy
inspection, backup, cloud management, browsers, remote-access paths and installed software.
Read-only, and collected even in
--safe. - Hardening control checks
- A curated set of CIS-aligned controls, each ending in pass, fail, not-applicable or unknown — and unknown is never scored as a failure. They are also mapped onto CIS Controls v8, ISO/IEC 27001:2022, NIS2 Article 21 and SOC 2 identifiers, which is a themed mapping to start a conversation with, not an audit position.
- Trigger tests
- Simulated attacker behaviour across antivirus signatures, ransomware-like activity, persistence, credential access, process injection, living-off-the-land binaries, defence evasion, discovery, lateral movement, command-and-control, exfiltration, cloud abuse, device control and access to known hacking services. Every test declares the ATT&CK techniques it exercises, and the registry refuses to load one that declares none.
Where the client can read the host's own logs, it also correlates each trigger against them and records how long the first matching entry took to appear. A match is evidence rather than proof — it is matched by time window and indicator — and a test with no matching entry is left out of the timing figures rather than counted as slow.
--list prints every test, --only and
--skip select by key or category, and
--list-profiles shows the named scenarios. A long run can be paced
with --pace, and one interrupted mid-way can be picked up again with
--resume.
Uploading a report
Uploads are server-to-server with a bearer token, so nothing depends on a browser session. Create a token under Account → API tokens — it is shown once, at creation.
curl -sS -X POST https://securitycheck.thugs.red/api/v1/reports \
-H "Authorization: Bearer $SECURITYCHECK_TOKEN" \
-H "Content-Type: application/json" \
--data-binary @securitycheck-report-20260813T101500Z.json
The client can also do it for you at the end of a run. It still writes the report to disk first, and it never uploads unless you ask:
SECURITYCHECK_TOKEN=… ./securitycheck --i-understand --upload
Without --upload the client writes the report and stops, so you can
read it, keep it, or decide not to share it at all — nothing leaves the test machine
unless you send it. The token is read from the environment so it never appears in the process
list.
- Endpoint
POST https://securitycheck.thugs.red/api/v1/reports- Authentication
Authorization: Bearer <token>- Body
- The report JSON exactly as the client wrote it, unmodified.
- Maximum size
- 2 MB per report
- Rate limit
- 20 uploads per hour per account
- Duplicates
- Re-uploading the same run is idempotent — it will not create a second report.
Reports arrive private. Nothing you upload appears in the public statistics until you change its visibility yourself.
What gets shared
| Visibility | Who can read it | In public statistics? |
|---|---|---|
| private | Only you | No |
| organisation | Members of your organisation | No |
| public | Anyone with the link | Yes — this is what makes the comparison possible |
The client hashes anything identifying before it ever leaves the machine: hostname, serial numbers and USB serials are SHA-256 truncated, and your IP is stored only as a keyed hash. Aggregates are additionally suppressed until enough separate reports back them, so a public statistic can never describe a single contributor.
The half the client cannot measure
The report tells you what the machine did. It cannot tell you what your organisation did. Keep your own notes against the run timeline:
- Did an alert fire at all — and how long after the trigger?
- Did it reach a human, or die in a queue?
- Was the correct severity assigned? Did major incident management activate?
- Were the documented contact and escalation paths actually followed?
- Did anyone isolate the machine, and how long did that take?
- Was the "user" contacted? Did anyone ask you what you were doing?
Every test in the report carries a timestamp, so you can lay your SOC's timeline directly against the machine's.
Those answers can also travel with the report instead of living in a separate
document. --soc-template writes a blank answers file,
--soc-questions prints the questions, and
--soc-answers attaches a filled-in file to the report on the next
run or upload:
./securitycheck --soc-template soc.json # write the blank file
# … fill it in once the team has had a chance to react …
./securitycheck --safe --soc-answers soc.json
A question you skip is recorded as unanswered, never as “no”. The difference between “we asked and nobody was contacted” and “we did not ask” is the whole value of the section, and these answers are self-reported — which is exactly why they are kept apart from everything the machine measured.
Get the client
- Linux x86-32 securitycheck-linux-386 5.2 MB
- Linux x86-64 securitycheck-linux-amd64 5.4 MB
- Windows x86-32 securitycheck-windows-386.exe 5.4 MB
- Windows x86-64 securitycheck-windows-amd64.exe 5.6 MB
- macOS Apple silicon / ARM64 securitycheck-darwin-arm64 5.6 MB
- macOS x86-64 securitycheck-darwin-amd64 5.8 MB
root@thugsred:~$ ./securitycheck --i-understand