Security training splits along a seam that does not exist in the work. There are games where you attack and platforms where you triage, sold to different people at different prices. Someone who spends a weekend rooting boxes never sees the row their scan wrote into somebody's console. Someone grinding alert queues never learns which of the fifty lines in front of them was a person and which was a backup script running at three every morning. Both halves are taught well. The join is taught nowhere, and the join is the job.
Signal / Noise closes that join at one specific point. When you finish a contract, the game seats you in the SOC the client outsources to, puts the last hour of alerts in front of you, and asks which row is you.
The first contract, SN-0028, is a haulage company that inherited a server block and lost the inventory list. Scope is 10.13.37.0/24. Nothing is exploited. You read the contract file on your own machine, sweep the block, enumerate versions, find that one of three hosts is not on the list, and pull an asset tag out of an HTML comment on the status page it serves on 8080. Twelve minutes, by the estimate in the mission file.
Then the shift starts. Same evening, night desk, five rows on the console:
13:04:11 5501 3 10.0.0.4 PAM: session opened (cron) 13:11:39 31101 5 10.0.0.211 Web server 400 error code 13:20:00 100010 5 10.13.37.5 Multiple connection attempts to closed ports 13:20:05 100012 6 10.13.37.5 Service banner probing pattern 13:52:02 5402 3 10.0.0.4 Successful sudo to ROOT executed
Three rows are the office's ordinary night. The cron session and the sudo at 13:52 are the same backup account doing what it does nightly, each carrying a written explanation of why it is not an incident. The 400 is one staff browser, and a single 400 is not a pattern.
Two rows are yours. 10.13.37.5 is the address the mission file gave your own machine four paragraphs earlier, and 100010 fired because a firewall noticed one source touching a spread of closed ports. The shift asks a single question: which source was mapping the block. The answer is your own IP, and you look at it for a second before you recognise it.
The lesson underneath is why this is worth building. The request that worked is not in the queue. The curl to the unlisted host returned a normal 200, the ruleset grouped it under 31100, and at level 0 it raised nothing. Nothing on that console records the moment you got what you came for. You were caught counting doors.
The same books are kept on the red side. A sweep costs 5 trace, version detection 6, this contract's threshold is 30, so the recon that produced both blue rows spent eleven of your thirty. Crossing fails nothing. It takes a quarter off the fee, and the game says so at the moment you cross.
A quieter piece of this project took longer than the loop did.
The game used to check objectives the way most teaching games do: a regular expression over the command you typed. Type something shaped like cat rol-tanimlari and "read the role definitions" completes. On your own machine, where no such file exists, against nothing. It paid the same for reading the wrong file and for reading none, and several patterns rejected head and tail, so the game was quietly grading your choice of reader.
The engine already had the right primitive and exactly one mission was using it. A discovery declared as on_read fires from cat, head, tail and grep alike, carries the absolute path, and requires the host to match. The conversion ran act by act: twenty-one objectives in Act IV, twenty-seven in Act III, thirteen in Act II, nine in Act I. Act III was announced as thirty and came in at twenty-seven, because the first pass matched cat anywhere in a pattern and (>|rm|truncate) contains one. Those three destroy a file rather than read it.
Across the four act folders today, 95 objectives are graded on a finding rather than a string and 66 discoveries fire on a file being opened. Eight fire on what came back instead, which is a deliberate distinction: one Act II objective says "search the June record for the attacker's address", and grading that as a read would pay for cat of the whole log, which puts the line on screen without finding anything. A grep that matches closes it. cat does not.
119 command patterns remain, and they should. (Counting rule, since it changes the number: two objectives accept either a reading or a command, and those branches are counted. Top-level checks alone give 118 patterns and 93 findings. Both are true; neither is, without the rule.) They grade an action: nmap against a named block, ssh-keygen, tar -c, aircrack-ng, an rm over a cron entry. No file's opening proves you generated a key. Exactly three survivors grade a reading command, and all three are one objective in three variants of a template, counting failed logins with grep -c instead of scrolling by eye. There, the choice of -c is the lesson.
What this bought is small and real. head -40 now finishes an objective whose old pattern accepted only cat, a command that errored is not paid for, and the right shape typed on the wrong machine is worth nothing. What it cost: 95 absolute paths became load-bearing, and one wrong character makes an objective permanently unreachable while the file still parses. So it shipped with a lint that checks every on_read names a file the host actually has, and that every finding a loot objective waits for is granted by something.
Through Act I the blue phase is a lesson. You attack, you read your own trace, you move on, and nothing you wrote survives. Act II changes that. On the second night shift the centre asks you for a rule of your own: catch sessions opened on the warehouse server between midnight and six, and leave the backup alone, because it runs in those hours too. Two lines must be caught, four ignored, and the four include a cron session at 03:00:04 and a legitimate daytime login. The hour by itself is not the answer.
Grading is not a set partition over the lines on screen. Every rule is re-run against the same events on another night with the incidental columns changed: process ids, source ports, day of month, minutes, seconds. The hour is left alone on purpose, because several contracts are about somebody working at three in the morning and that is signal. The test exists because four testers independently found that eleven of the thirteen rule tasks fell to one meaningless column: a bare pid, an exact timestamp, Jun 14.
Then the rule is saved, and from there it runs against the lines your own work produces, on every later contract. It costs trace equal to its level times one and a half, and you set the level yourself. It fires at most once per mission, because a brute force writes fifty matching lines and charging for all fifty would end the run on the first hydra. Matching is the only thing that costs.
The next contract is the counterweight. Same server, same hour, and the rebuild has made ssh key-only, so the door your rule watches is the one that is shut. The inventory dump walks out over http, from a backup directory robots.txt politely names, while your correct, tested, well-tuned detection sees nothing. That is not a trick played on the player. It is the most ordinary thing that happens to real detection: it was written for the technique somebody used last time. The blue half asks you to close the gap you just walked through.
100 of the 103 contracts run entirely inside a simulated shell in a browser tab. No packet leaves the tab. The OpenSSH 7.2p2 Ubuntu 4ubuntu2.8 on that first web server is a string in a YAML file, not a banner anything returned, and the firewall that wrote you down is a mapping table in the mission: scan.sweep becomes rule 100010 at level 5. The commands are real in that the syntax is real and every accepted flag does something. They are not real in the sense of touching a network.
That buys reach: no install, no account, no VM, and content a script can replay end to end. It costs the thing only a real box teaches, which is that tools hang, fail, return nothing, and half the work is deciding whether the silence is the target or your own syntax. Three contracts, m14 through m16, drive real Docker containers and real log lines through a shared decoder file, and they need Docker running locally. Three out of a hundred and three. This is an on-ramp, not a range, and a simulator that claims to replace a lab is selling something.
The simulation is a chosen subset too, and the game says so where it counts. No shipped decoder exists for a port scan, a SQL injection or a log truncation. Those lines appear and no rule names them. That absence is Act II's subject: the ruleset you were handed does not cover what you do.
103 contracts across four acts, Turkish and English, both written rather than translated. 100% coverage of the curriculum model the project grades itself against, per npm run coverage. 620 assertions in the thirteen test suites that do not need Docker. Runs in a browser and as a desktop build.
The pitch is one sentence and it is not a genre. Your own attack becomes the alert queue you triage, and the row you left sits next to the backup script.
https://samerule.com