Screenshots: encrypted, off by default
Bastrel can ask a player’s game for a screenshot, for a staff member to review. This is off by default, and turning it on is your decision as the server owner: a screenshot is personal data (see Who is responsible). Read this page before you do.
What you need
Section titled “What you need”-
The resource
screenshot-basic, started (ensure screenshot-basic). Bastrel does not ship it and does not list it as a dependency: when it is not started, Bastrel only refuses to ask for a screenshot and tells you. -
An encryption key that you generate: 64 hexadecimal characters (32 bytes). Generate one with Node.js, with the tool of your choice, or with any password manager that can produce random hexadecimal:
Terminal window node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" -
The key in
server.cfg, withsetand neversetr:set bastrel_screenshot_key <64 hexadecimal characters, the output of the command above>Write your own value in place of the placeholder; there is no example key on purpose. Bastrel refuses a key that is not 64 hexadecimal characters, and a key that repeats a short pattern (a single digit repeated,
abab…, or any pattern of 16 bytes or less): that is a copied example, not a key.setrsends the key to every player who connects. Useset. A key written withsetris readable by anybody, and every screenshot encrypted with it must be considered readable. Bastrel cannot detect the difference from the server side; this page is the protection. -
screenshots.enabled = trueinconfig/modules.lua.
Without the key, nothing is captured and nothing is stored in clear. There is no fallback. If the key is missing, invalid (not 64 hexadecimal characters, or a short repeated pattern), or if the encryption self-test fails, Bastrel writes one screenshots.disabled journal line (JSON, its message field tells you what to do; never a [bastrel] console line, which would appear in the middle of the game) and raises one BAS-CV-109 finding (reason: key_missing, key_invalid, selftest_failed, resource_missing or crypto_unavailable). The same finding is raised when screenshot-basic is not started.
The key is checked before every request (the encryption script runs a self-test, then asks the player’s game for the image): if you remove or break the key while the server runs, the next request is refused and nothing is asked of the player. It is read by Bastrel’s encryption script at every use, so you can also set it while the server runs (set bastrel_screenshot_key … in the console). It never goes through Bastrel’s Lua code, is never written to a log, and is never sent to a player or to Discord.
Keep a copy of the key somewhere safe. Lose it and the screenshots cannot be read again; nobody, Bastrel included, can recover them.
Who asks, and when
Section titled “Who asks, and when”| Trigger | When |
|---|---|
| Staff | /bas screenshot <id> (commands), permission bastrel.staff |
| Score | A player’s score crosses the screenshot threshold (60 by default) |
| Detection | A detection whose actions contain screenshot and neither kick nor ban |
| Aim | A finding of the aim module, when aim.screenshot is true (the default); it never kicks or bans, and asks nothing while BAS-AIM-001 is unverified |
A detection that kicks or bans does not ask for a screenshot: the player is gone by the time the request would leave. A detection still marked unverified never asks for one either.
In observe mode, only the staff command takes a screenshot. In observe (the global mode, or the mode of a rule) a detection applies no action, so neither the score threshold nor a detection asks for one: they ask only in enforce. /bas screenshot works in every mode. The staff alert lists screenshot among its actions only when a screenshot was really requested (not when the cooldown or a missing key refused it).
The server asks; the player’s client is never given an address to upload to, and Bastrel’s own client files never mention screenshots. The image travels through screenshot-basic’s own channel to your server. Bastrel cannot limit how large that upload is before it arrives: it checks the size afterwards and throws away an image above screenshots.maxBytes.
What is stored
Section titled “What is stored”For each screenshot, one row of bastrel_screenshots: an id (the reference shotRef), the date (UTC), the player’s license, the detection id if any, the reason (staff, score or detection), who asked, the size of the image, the encrypted data, the format version (v1) and a fingerprint of the key that was used.
- The image is encrypted with AES-256-GCM, the standard authenticated encryption of Node.js, before it reaches your database. A row that is altered cannot be decrypted.
- It is never in a detection, a log line, a Discord message or a copy of evidence: a log line only carries the reference.
- If the database is unavailable or too busy, the screenshot is dropped; it is never kept in memory.
- Format
v1isv1:followed by the base64 ofiv(12 bytes),tag(16 bytes) and the encrypted image, with the 15 ASCII bytesbastrel-shot-v1as additional authenticated data. - The fingerprint (
key_fp) is 16 hexadecimal characters derived from the key. It does not allow anyone to find the key, but it tells you which key a row was encrypted with after you changed it.
How long it is kept
Section titled “How long it is kept”30 days by default, then deleted by Bastrel’s purge, which runs at startup and once a day even when screenshots are turned off (what was captured before expires too), in small batches (logger.screenshotsPurgeBatch, 20 rows) because a screenshot row is large. A wrong value for these two settings costs only that setting (a warning, the default is used), never the rest of the logger block. Change it in config/modules.lua:
logger = { retention = { screenshotsDays = 30 } } -- 1 to 365You can also delete rows yourself, at any time, from the table.
Reading a screenshot
Section titled “Reading a screenshot”There is no command to display an image in game. The file tools/decrypt-shot.js (a single file, Node.js, nothing to install; tested with Node.js 22) decrypts one cell of the table:
# export the `data` cell of one row to a file (any SQL tool), then:BASTREL_SHOT_KEY=<your key> node decrypt-shot.js --in cell.txt --out shot.jpg --key-fp <the key_fp cell of the row># or, with the key in a file:node decrypt-shot.js --in cell.txt --out shot.jpg --key-file key.txt --key-fp <key_fp>The tool reads the key from the BASTREL_SHOT_KEY variable or from --key-file, never from an argument (an argument stays in your shell history and in the list of processes). It refuses a key whose fingerprint is not the key_fp of the row, and a row that does not authenticate (wrong key, or altered data). The panel of a later version will display them.
Limits and costs
Section titled “Limits and costs”Per player (followed by his Rockstar license, so reconnecting does not reset it), screenshots.cooldownMs (60 s) between two requests, whoever asks; never two at once. For the whole server, at most screenshots.maxPending (4) requests waiting, and screenshots.dailyMax (200) per UTC day. A request nobody answers is given up after screenshots.replyTimeoutMs (15 s): a player who never answers is not a detection, he may simply have no screenshot-basic client. Quality is screenshots.quality (0.6) for jpg and webp.
Encryption runs in a small script (server/crypto/shot_crypto.js) that needs the Node.js 22 runtime of FXServer: Bastrel’s manifest asks for it with node_version '22'. That file is shipped in clear: Asset Escrow does not encrypt .js files. It contains no detection logic and no secret, only the standard encryption; you can read it, and if you change it you only break your own screenshots.
Known limits. (1) Encryption runs synchronously on the server thread. What was measured on a dev server is the round trip of the test script (encrypt, decrypt, small answer): about 5 ms for 200 KB, 54 ms for 1 MB and 260 ms for 5 MB. From these, estimates: about 10 to 20 ms for the default maxBytes (400 000) and about 110 ms at the upper bound of 2 MB (maxBytes is refused above 2 000 000). Keep maxBytes low. (2) screenshot-basic receives the upload and writes the image in clear to a temporary file of the operating system, then reads and deletes it before handing it to Bastrel. The file is not always deleted: an upload with an unknown or already used token, or a failed read, leaves it behind. “Nothing is written in clear on disk” is true of bastrel only, not of the whole chain: protect the server’s temporary folder like the database.
Setting (config/modules.lua, screenshots) |
Default | Meaning |
|---|---|---|
enabled |
false |
Turn the function on |
encoding |
jpg |
jpg, png or webp |
quality |
0.6 |
0.1 to 1.0 |
maxBytes |
400000 |
Largest image kept (decoded, 1000 to 2000000: the encryption blocks the server thread, about 54 ms per MB); a larger one is dropped, never re-encoded. On a wide screen an image can come close: a 3440x1440 capture at the default quality was about 299 KB on a dev server (21/10/2026), and one was over 400 KB. Raise it if your players use large screens; the screenshot.rejected line (too_large) gives estimatedBytes |
cooldownMs |
60000 |
Minimum time between two requests for one player |
maxPending |
4 |
Requests waiting for an answer, whole server |
replyTimeoutMs |
15000 |
Give up a request after this |
dailyMax |
200 |
Requests per UTC day |
checkDelayMs |
15000 |
First check, after start, that screenshot-basic and the key work; and the time a failed check is kept before the next request tries again |
logWindowMs |
60000 |
Shortest time between two identical log lines for one player |
An invalid value takes its default, with a warning at startup.
Who is responsible for these data (GDPR)
Section titled “Who is responsible for these data (GDPR)”This section describes how Bastrel is built. It is not legal advice: have your own situation checked.
- Local mode (this version). Everything stays on your server. You, the server owner, are the controller of the personal data in the screenshots: you decide to collect them, you store them, you must inform your players (privacy notice, rules of the server, legal basis, retention) and you answer their requests. Bastrel is software running on your server; it does not receive these images.
- Cloud (a later version). When screenshots go through the Bastrel cloud, Bastrel acts as your processor and a data processing agreement (DPA) between you and Bastrel is needed before any real data is sent. The images are already encrypted by your server with your key before they leave it; a later version of the panel decrypts them in your browser, with a key you paste locally. That reduces what Bastrel can read, but it is not a promise that Bastrel could never read an image: if you need that guarantee, decrypt offline with
tools/decrypt-shot.js. - What you can configure: whether the function is on, how long the data is kept (
logger.retention.screenshotsDays), who hasbastrel.staff. - What we recommend: tell your players, in your rules, that staff may request a screenshot of the game; keep the retention short; give the staff permission to the few people who need it; keep the key out of any repository and out of any file a client can download.
Because the key is the only thing that protects the images at rest, treat server.cfg like a secret file.