Home›Java›Mods›SentinelAC
SentinelAC
ModsJava

SentinelAC

Java mod listed for Minecraft 26.x. Downloads from the MC Java Mods app on Android.

by iAyms · on Hangar

⬇ Download on Hangar
# Sentinel Anti-cheat, anti-bot and anti-xray for Paper — and a framework you can build on. Sentinel ships 18 detections, a bot guard with captcha, plugin-side ore obfuscation and mining analytics. Every part of it is reachable through a public API, so you can add your own checks, reroute punishments into your own moderation system, or use Sentinel purely as a detection engine behind your own plugin. - No external dependencies — Paper API only - No database, no web calls, no telemetry - Java 25, Paper 26.3 (`paper-plugin.yml`, Brigadier commands) --- ## Install 1. Drop `sentinel.jar` into `plugins/`. 2. Start the server. `config.yml`, `messages.yml` and `verified.yml` are created in `plugins/Sentinel/`. 3. Give your staff `sentinel.alerts` and `sentinel.admin`. ## Commands All of them require `sentinel.admin`. `/sn` is an alias of `/sentinel`. | Command | Effect | |---|---| | `/sentinel` | Version, check count, profile count, current TPS | | `/sentinel reload` | Reload `config.yml` and `messages.yml` | | `/sentinel menu` | Open the admin GUI | | `/sentinel checks` | List every registered check with its state | | `/sentinel check on\|off` | Toggle one check until the next reload | | `/sentinel alerts [on\|off]` | Toggle your own alert messages | | `/sentinel verbose [on\|off]` | Also show the detail line of every flag | | `/sentinel violations ` | Current violation level per check | | `/sentinel clear ` | Reset a player's violations | | `/sentinel xray top` | Mining-analytics suspect list, highest score first | | `/sentinel xray report ` | Full mining breakdown for one player | | `/sentinel xray reveal ` | Send the real blocks (debugging) | | `/sentinel xray hide ` | Re-hide everything around the player | | `/sentinel bot status` | Bot guard state, blocked connections, lockdown | | `/sentinel bot lockdown` | Toggle lockdown (refuse new connections) | | `/sentinel bot verify ` | Mark a player as verified for good | | `/sentinel bot captcha ` | Start a captcha for a player | ## Permissions | Node | Default | Meaning | |---|---|---| | `sentinel.admin` | op | Use `/sentinel` | | `sentinel.alerts` | op | Receive flag alerts | | `sentinel.bypass` | false | Skip every check | | `sentinel.bypass.xray` | false | See real blocks, no mining analytics | | `sentinel.bypass.captcha` | false | Never asked to verify | | `sentinel.bypass.bot` | false | Skip the behaviour profiler | --- ## How a detection becomes a punishment 1. A check sees something impossible and calls `flag`, which raises that player's **violation level** (vl) for that check. 2. `SentinelFlagEvent` fires. Cancel it and the violation is rolled back — nothing is logged, alerted or punished. 3. Staff with `sentinel.alerts` get a message; the flag is written to `violations.log`. 4. The **punishment ladder** runs. The highest rule whose `violations` threshold the vl has reached is executed once. 5. Violations decay by `decay-per-second` for every second without a new flag, so one bad reading never accumulates into a ban. Every check is exempted while the player is in a state the server cannot model cleanly: right after joining, teleporting, respawning, taking knockback, leaving a vehicle, changing world, while gliding, riptiding, in creative mode, and whenever the server is lagging or the player's ping is over the limit. --- ## Configuration ### `general` | Key | Default | Meaning | |---|---|---| | `prefix` | gradient | MiniMessage prefix in front of every plugin message | | `debug` | `false` | Reserved for verbose logging | ### `alerts` | Key | Default | Meaning | |---|---|---| | `enabled` | `true` | Master switch for staff alerts | | `console` | `true` | Also log flags to the console | | `cooldown-millis` | `1500` | Minimum gap between two alerts for the same player and check | | `enabled-by-default` | `true` | Staff get alerts without typing `/sentinel alerts on` | | `format` | — | Alert line; placeholders ` ` | | `verbose-format` | — | Second line in verbose mode; placeholder `` | The alert line carries a hover with the full detail text and a click that runs `/sentinel violations `. ### `logging` | Key | Default | Meaning | |---|---|---| | `file` | `true` | Append flags to a log file | | `file-name` | `violations.log` | File inside `plugins/Sentinel/` | | `keep-days` | `14` | Delete the log once it is older than this | ### `performance` | Key | Default | Meaning | |---|---|---| | `min-tps` | `17.0` | Below this the server counts as lagging and movement checks pause | | `max-ping` | `400` | Players above this ping are exempt | | `lag-grace-millis` | `2500` | How long the lag exemption lasts after a spike | | `chunk-scans-per-tick` | `2` | Ore index builds per tick (raise to warm up faster, costs CPU) | ### `exemptions` `creative`, `flying` and `operator` are switches. The `*-millis` keys set how long the matching exemption lasts after the event: `join`, `teleport`, `respawn`, `velocity`, `vehicle`, `world-change`, `damage`, `slime`. Set `operator: true` if your ops should never be flagged. ### `punishments` ```yaml punishments: reset-violations: true ladder: - violations: 15.0 action: alert - violations: 40.0 action: command value: "kick Sentinel: suspicious behaviour ()" - violations: 80.0 action: command value: "ban Sentinel: unfair advantage ()" ``` Built-in actions: `alert`, `command` (console command in `value`), `kick` (kick message in `value`), `none`. Placeholders in `value`: ``, ``, ``, ``, ``, ``. `reset-violations: true` zeroes the check's vl after a punishment other than `alert`, so the same ladder step does not fire twice in a row. Plugins can register their own action ids — see API. ### `checks` Every check has the same three keys plus its own `settings` block: | Key | Meaning | |---|---| | `enabled` | Run this check at all | | `decay-per-second` | How fast its violation level drains | | `max-violations` | Ceiling, so one check cannot dominate the ladder | Most `settings` blocks contain a `buffer`: how many consecutive bad readings are needed before the check flags. Raising a buffer or a tolerance is the right way to deal with a false positive; disabling the check is the last resort. | Check | Category | What it looks at | |---|---|---| | `speed` | movement | Horizontal distance per move against a model built from sprint state, walk speed, speed/slowness potions, ice, soul speed and sprint-jumps | | `flight` | movement | Airborne without descending, with no liquid, ladder, web, bounce block or ground below | | `nofall` | movement | Landing after a long fall while the client reports no fall distance | | `step` | movement | Ground-to-ground height gain above `max-step` (0.65 passes slabs and stairs) | | `jesus` | movement | Moving on a liquid surface with air at the feet and nothing solid below | | `timer` | movement | Movement packet rate against the 20 Hz tick rate, measured over `sample-size` moves | | `invalid-motion` | movement | Non-finite or absurd deltas and impossible pitch; cancels the move | | `reach` | combat | Distance from the eye to the victim's bounding box | | `killaura` | combat | Attack angle outside the view cone, several victims inside one window, attacking while blocking and sprinting | | `autoclicker` | combat | Clicks per second and the standard deviation of click intervals | | `aim` | combat | Pitch snaps during a fight and pitch deltas that never vary | | `fastbreak` | interaction | Break time against `Block#getBreakSpeed` for the tool actually held | | `nuker` | interaction | Blocks broken per second and how far apart they are | | `fastplace` | interaction | Blocks placed per second | | `scaffold` | interaction | Placing below yourself while moving and looking away from the block | | `inventory` | interaction | Inventory clicks while sprinting, airborne or moving | | `chat-spam` | chat | Message interval and repeated messages | | `command-spam` | chat | Command interval | ### `bot` **`connection`** — refuses connections before they reach the login stage. | Key | Default | Meaning | |---|---|---| | `max-per-address` | `3` | Connections per address inside the window | | `window-seconds` | `60` | Length of that window | | `max-joins-per-second` | `6` | Server-wide rate; exceeding it triggers lockdown | | `lockdown-seconds` | `30` | How long lockdown refuses every new connection | | `max-online-per-address` | `2` | Simultaneous players per address | **`names`** — `min-length`, `max-length`, `max-digit-ratio`, `min-entropy`/`max-entropy` (Shannon entropy over the name — random strings score high, words score low) and `blocked-patterns`, a list of Java regexes. Use single quotes in YAML so backslashes survive. **`activity`** — a client that has not moved or rotated within `require-seconds` is kicked. **`behaviour`** — `straight-line-threshold` and `straight-line-samples` catch paths with no lateral deviation at all; `zero-rotation-samples` catches moving without ever turning the head. Each signal is worth 30 points; `alert-score` notifies staff, `kick-score` kicks. **`captcha`** — `mode: chat` sends a code to type, `mode: gui` opens a menu and asks for one named item. `only-unverified: true` asks each account once and remembers it in `verified.yml`. `freeze` blocks movement and commands, `blindness` applies the effect for the duration. Wrong answers count against `max-attempts`, silence against `timeout-seconds`; both end in a kick. ### `xray` **`obfuscation`** — Sentinel replaces every block in `hidden-blocks` with stone, deepslate or netherrack in the packets the client receives, and sends the real block once the player is within `reveal-radius`. `hysteresis` keeps blocks visible a little past that radius so walking along the edge does not flicker. `update-interval-ticks` is how often the near window is recomputed, `max-blocks-per-update` caps one update, and the three `replacement-*` keys choose the filler per block family. > Paper has a server-side anti-xray (`anticheat.anti-xray` in > `paper-world-defaults.yml`) that is cheaper than any plugin can be, because it > runs inside the chunk serializer. If you enable Paper's engine-mode 2 or 3, > set `xray.obfuscation.enabled: false` and keep only the analytics below. Run > Sentinel's obfuscation when you cannot touch the server config, or when you > want per-permission control (`sentinel.bypass.xray`). **`analytics`** — scores mining behaviour instead of hiding anything, which catches the players who found their ores legitimately-looking but far too often. Four weighted signals over a rolling window: | Signal | Weight | Meaning | |---|---|---| | `ore-ratio` | 40 | Actual ore rate against `expected-ore-ratio` per material | | `tunnel` | 25 | Share of breaks that continue a straight 1×1 tunnel | | `beeline` | 20 | Share of ores reached right after a direction change | | `depth` | 15 | Share of breaks at or below `deep-y` | A player is listed as a suspect once the score reaches `suspect-score` and at least `min-samples` blocks were seen. `alert-cooldown-minutes` throttles the alert, not the scoring. Analytics never punishes on its own — it produces a list for staff and a `SentinelXrayEvent` for your own plugin. --- ## Files | File | Content | |---|---| | `config.yml` | Everything above | | `messages.yml` | Every player-facing string, MiniMessage | | `verified.yml` | UUIDs that passed the captcha | | `violations.log` | One line per flag | --- ## For developers Sentinel is built to be used as a detection engine by other plugins: register your own checks, read per-player state, replace the punishment layer, or listen for flags and feed them into your own moderation backend. # Sentinel API **API version 1.0.0** — everything under `org.sentinel.api` is the public surface and follows semantic versioning. Everything outside it is internal and may change without notice. Contents 1. [Setup](#setup) 2. [Entry point](#entry-point) 3. [Writing your own check](#writing-your-own-check) 4. [Reading player state](#reading-player-state) 5. [Violations](#violations) 6. [Exemptions](#exemptions) 7. [Punishment actions](#punishment-actions) 8. [Events](#events) 9. [Bot guard and captcha](#bot-guard-and-captcha) 10. [X-ray](#x-ray) 11. [Using Sentinel as a framework](#using-sentinel-as-a-framework) 12. [Version history](#version-history) --- ## Setup Sentinel has no repository publication, so depend on the jar directly. The build produces a sources jar next to it. ```kotlin dependencies { compileOnly(files("libs/sentinel.jar")) } ``` ```yaml # paper-plugin.yml dependencies: server: Sentinel: load: BEFORE required: true ``` Use `required: false` plus `SentinelProvider.available()` if Sentinel is optional for your plugin. --- ## Entry point ```java SentinelApi sentinel = SentinelProvider.get(); ``` `SentinelProvider.get()` throws `IllegalStateException` while Sentinel is not loaded; `find()` returns an `Optional` and `available()` a boolean. Sentinel is also registered with the Bukkit services manager: ```java RegisteredServiceProvider registration = Bukkit.getServicesManager().getRegistration(SentinelApi.class); ``` `SentinelApi` gives you every subsystem: | Method | Returns | |---|---| | `version()` | Plugin version | | `checks()` | `CheckRegistry` | | `violations()` | `ViolationService` | | `exemptions()` | `ExemptionService` | | `punishments()` | `PunishmentRegistry` | | `bots()` | `BotService` | | `xray()` | `XrayService` | | `profile(Player)` / `profile(UUID)` / `profiles()` | `PlayerProfile` | | `serverHealthy()` | `false` while the server is lagging | | `reload()` | Reload the configuration | --- ## Writing your own check Extend `Check`, override the hooks you need, call `flag` when something is wrong. You never touch violation levels, alerts, logging or punishments — that is handled for you. ```java public final class GlideCheck extends Check { public GlideCheck() { super("glide", CheckCategory.MOVEMENT, "Falls slower than gravity allows"); } @Override public void onMove(final MoveContext context) { if (context.onGround() || context.profile().airTicks() < 10) { return; } final double expected = -0.08D * context.profile().airTicks(); final double tolerance = settings().getDouble("tolerance", 0.05D); if (context.vertical() > expected + tolerance) { if (buffer(context.profile(), 1.0D, 5.0D) > 3.0D) { flag(context.profile(), "dy " + context.vertical() + " expected " + expected); resetBuffer(context.profile()); } } else { buffer(context.profile(), -1.0D, 5.0D); } } } ``` Register it with your own plugin as the owner, and clean up on disable: ```java @Override public void onEnable() { SentinelProvider.get().checks().register(this, new GlideCheck()); } @Override public void onDisable() { SentinelProvider.find().ifPresent(api -> api.checks().unregisterAll(this)); } ``` ### Hooks | Hook | Fired when | |---|---| | `onRegister()` / `onUnregister()` | The check is added or removed | | `onJoin(profile)` / `onQuit(profile)` | A player joins or leaves | | `onMove(MoveContext)` | The player's position changed | | `onRotate(profile, yawDelta, pitchDelta)` | The player's rotation changed | | `onAttack(AttackContext)` | The player damaged an entity | | `onSwing(profile)` | The player swung their arm | | `onBlockDamage` / `onBlockBreak` / `onBlockPlace` (`BlockContext`) | Block interaction | | `onInventoryClick(profile)` | A click in any inventory | | `onChat(profile, message)` | A chat message (on the main thread) | | `onCommand(profile, command)` | A command, including the slash | | `onTick(profile)` | Every 5 ticks, per online player | All hooks run on the main thread. `MoveContext`, `AttackContext` and `BlockContext` carry `cancel()` so a check can block the action outright; `MoveContext` exposes `from`, `to`, `deltaX/Y/Z`, `horizontal()`, `vertical()`, `onGround()` and `wasOnGround()`, `AttackContext` the `victim()`, `reach()` and `angle()`, `BlockContext` the `block()`, `action()` and `distance()`. ### Reporting | Method | Effect | |---|---| | `flag(profile, details)` | One violation | | `flag(profile, amount, details)` | Weighted violation | | `reward(profile, amount)` | Lower the violation level after a clean reading | ### Per-player state `buffer(profile)`, `buffer(profile, delta, max)` and `resetBuffer(profile)` give you a clamped counter per check and player. For anything richer use `state(profile, key, Supplier)`, which lazily creates and caches your own object: ```java private static final class Samples { private final double[] values = new double[32]; private int index; } final Samples samples = state(context.profile(), "samples", Samples::new); ``` `clearState(profile)` drops everything your check stored for that player. All of it lives in `profile.storage()` and disappears when the player leaves. ### Configuration `settings()` returns a `CheckSettings` backed by `checks.` in `config.yml`. A check that is not in the file still works — it gets `enabled: true`, `decay-per-second: 1.0` and `max-violations: 120.0`. Admins can add a block for it at any time: ```yaml checks: glide: enabled: true decay-per-second: 0.5 max-violations: 100.0 settings: tolerance: 0.05 ``` `CheckSettings` reads `getBoolean`, `getInt`, `getLong`, `getDouble`, `getString`, `getStringList` and `getDoubleMap`, each with a fallback, and `enabled(boolean)` toggles the check at runtime. --- ## Reading player state `PlayerProfile` is a live, read-mostly view of one online player. ```java PlayerProfile profile = SentinelProvider.get().profile(player); profile.ping(); profile.airTicks(); profile.lastHorizontal(); profile.clicksPerSecond(); profile.recentTargets(); profile.totalViolations(); profile.violationSnapshot(); // check name -> vl profile.mining().score(); // 0..100 profile.botScore(); // 0..100 profile.verified(); profile.exempt(ExemptType.TELEPORT); profile.storage(); // your own scratch space ``` `mining()` returns `MiningStats`: `totalBlocks()`, `totalOres()`, `ores()` per material, `oreRatio()`, `tunnelRatio()`, `beelineRatio()`, `deepRatio()`, `score()` and `reset()`. --- ## Violations ```java ViolationService violations = SentinelProvider.get().violations(); violations.violations(profile, "speed"); violations.total(profile); violations.snapshot(profile); violations.add(profile, "my-check", 2.0, "reason"); // runs alerts + ladder violations.subtract(profile, "speed", 5.0); violations.clear(profile); violations.recent(profile); // last 30 flags violations.recent(); // last 200 server-wide ``` `add` with an unregistered check name still works — it is logged under the `custom` category. That is the shortest path if your detection logic lives entirely in your own plugin and you only want Sentinel's violation bookkeeping, alerts and punishment ladder. A `Violation` is a record: `player`, `playerName`, `check`, `category`, `amount`, `total`, `details`, `timestamp`, `ping`, `tps`. --- ## Exemptions ```java ExemptionService exemptions = SentinelProvider.get().exemptions(); exemptions.exempt(profile, ExemptType.LAG); exemptions.exemptAny(profile, ExemptType.JOIN, ExemptType.TELEPORT); exemptions.apply(profile, ExemptType.MANUAL, 5000L); // exempt for 5 seconds exemptions.clear(profile, ExemptType.MANUAL); exemptions.active(profile); ``` Use `ExemptType.MANUAL` for your own effects — a custom dash ability, a lobby launch pad, a cutscene — and the built-in movement checks will ignore the player for that window. --- ## Punishment actions Register an id and use it in the ladder. This is how you route Sentinel into your own moderation system without touching its code. ```java SentinelProvider.get().punishments().register("mybans", context -> { MyBanService.ban(context.profile().uuid(), "Sentinel: " + context.violation().check(), context.violation().details()); }); ``` ```yaml punishments: ladder: - violations: 60.0 action: mybans value: "7d" ``` `PunishmentContext` carries the `profile()`, the `violation()`, the `threshold()` that was crossed and the `value()` string from the config. `ids()` lists all registered actions, `unregister(id)` removes one, and `execute(id, context)` runs one directly. Overwriting a built-in id (`alert`, `command`, `kick`, `none`) replaces it. --- ## Events All events are Bukkit events. Four of them are cancellable, and cancelling is the clean way to take control. | Event | Cancelling it | |---|---| | `SentinelFlagEvent` | Rolls the violation back; no alert, no log, no punishment | | `SentinelPunishEvent` | Stops that punishment action from running | | `SentinelBotEvent` | Lets the connection or player through | | `SentinelXrayEvent` | Suppresses the suspect alert (the suspect is still listed) | | `SentinelCaptchaEvent` | Not cancellable — reports `STARTED`, `PASSED`, `FAILED`, `TIMEOUT` | ```java @EventHandler public void onFlag(final SentinelFlagEvent event) { if (event.profile().player().getWorld().getName().equals("minigames")) { event.setCancelled(true); return; } MyWebhook.post(event.violation()); } ``` `SentinelBotEvent` fires asynchronously during pre-login for connection and name signals, and synchronously for behaviour kicks. It exposes the `playerName()`, the `address()`, the `signal()` and a mutable `kickReason()`. --- ## Bot guard and captcha ```java BotService bots = SentinelProvider.get().bots(); bots.lockdown(); // currently refusing connections? bots.lockdown(60_000L); // refuse for a minute bots.blockedConnections(); bots.pendingCaptchas(); bots.verified(uuid); bots.verify(uuid); // also completes a running captcha bots.unverify(uuid); bots.requestCaptcha(player); bots.verifying(player); bots.score(profile); bots.signals(profile); // Set ``` `BotSignal` values: `CONNECTION_RATE`, `ADDRESS_LIMIT`, `LOCKDOWN`, `NAME_PATTERN`, `NAME_ENTROPY`, `NAME_LENGTH`, `NAME_DIGITS`, `NO_ACTIVITY`, `STRAIGHT_LINE`, `ZERO_ROTATION`, `CAPTCHA_TIMEOUT`, `CAPTCHA_FAILED`. To replace the captcha with your own, listen for `SentinelCaptchaEvent.Result.STARTED`, run your flow, and call `bots.verify(uuid)` when the player passes. --- ## X-ray ```java XrayService xray = SentinelProvider.get().xray(); xray.obfuscationEnabled(); xray.hiddenBlocks(); xray.hideAll(player); xray.revealAll(player); xray.hiddenCount(player); xray.invalidate(chunk); // after changing blocks yourself xray.analyticsEnabled(); xray.score(profile); xray.suspects(); // List, best score first xray.clearSuspect(uuid); ``` Call `invalidate(chunk)` whenever your plugin changes blocks in a way Sentinel cannot see (world edits, schematic pastes, custom generation), otherwise the ore index for that chunk stays stale until it unloads. `XraySuspect` is a record: `player`, `playerName`, `score`, `blocks`, `ores`, `detectedAt`. --- ## Using Sentinel as a framework Three ways to build on it, from least to most invasive. **1. Detection engine, your reaction.** Leave the checks on, set the whole ladder to `action: none` or drop it, and listen to `SentinelFlagEvent`. Sentinel does the detecting, decaying and logging; your plugin decides what happens. ```yaml punishments: ladder: [] ``` **2. Your checks, Sentinel's plumbing.** Register your own `Check` subclasses. You get exemptions, violation decay, the buffer helpers, per-check config, alerts with hover and click, the log file, the admin GUI entry, the ladder and `/sentinel check on|off` — for free, for a class with one method. **3. Your moderation backend.** Register a `PunishmentAction` and point the ladder at it. Sentinel never calls `ban` or `kick` itself in that case; your code decides, and `SentinelPunishEvent` still lets a third plugin veto it. Migration notes if you are moving an existing anti-cheat into Sentinel: - One check class per detection, named in `kebab-case`; the name is the config key, the command argument and the violation key, so pick it once and keep it. - Replace your own tick counters with `profile.airTicks()`, `profile.groundTicks()`, `profile.lastHorizontal()` and `profile.clicksPerSecond()` instead of tracking movement twice. - Replace your own false-positive guards with the `ExemptType` checks before you port the detection logic itself — most of what looks like a detection bug is a missing exemption. - Anything you stored in a `Map` becomes `state(profile, key, Something::new)` and gets cleaned up on quit for you. - Keep your thresholds in `settings()` rather than as constants so servers can tune them without a rebuild. --- ## Version history ### 1.0.0 Initial API. - `SentinelApi`, `SentinelProvider` - `Check`, `CheckCategory`, `CheckRegistry`, `CheckSettings`, `MoveContext`, `AttackContext`, `BlockContext` - `PlayerProfile`, `MiningStats` - `Violation`, `ViolationService` - `ExemptType`, `ExemptionService` - `PunishmentAction`, `PunishmentContext`, `PunishmentRegistry` - `BotService`, `BotSignal` - `XrayService`, `XraySuspect` - `SentinelFlagEvent`, `SentinelPunishEvent`, `SentinelBotEvent`, `SentinelXrayEvent`, `SentinelCaptchaEvent`
Quick facts

Install steps are the general flow for this file type — How to install Minecraft Java mods & modpacks walks through it step by step.

SentinelAC is a free Minecraft Java mod. Compatible with Minecraft 26.3. Downloaded 26 times (via Hangar). Download it and open it directly in the game.

Explore more