---
url: https://docs.sunlicense.com/guide/protection.md
description: >-
  Core unlock, ownership notice, tamper reports and protected files - what each
  does, how to set it up, how it behaves offline, and what no protection can
  stop.
---

# Protection

A license check alone is one `if` statement: anyone with the code, or an AI editor, can delete it. Protection makes
that pointless and makes leaks visible:

| Part | What it does | Plans |
|---|---|---|
| [Core unlock](#core-unlock) | A small part of your app only runs with a valid license. Remove the check and that part is missing. | All |
| [Ownership notice](#ownership-notice) | Every copy says whose product it is: a header comment and one start-up log line. | All |
| [Tamper reports](#tamper-reports) | A modified copy is reported to [Sentinel](/guide/sentinel) with its next license check. | All |
| [Protected files](/guide/protected-files) | Data files only licensed copies may have stay on SunLicense; the app downloads them after a valid check. | Standard |
| [Download watermark](/guide/downloads#how-the-license-gets-into-the-file) | Each buyer's copy carries their key and a mark that names them if it leaks. | Standard |
| [Obfuscated releases](/guide/obfuscation) | Release builds are obfuscated, so the code is hard to read and patch. | All (runs per month) |

## Set it up

Open **Products → your product → Setup**, leave the parts you want switched on and choose **Copy prompt**. Paste it
into your AI coding agent (Claude Code, Cursor, Codex, Copilot, Windsurf, Gemini CLI…): it works out what your project
is (a plugin, mod, bot, script, desktop or web app), adds each part, proves it works with a test license and reports
back. The Setup tab ticks each part off by itself once your app shows it working. Re-running the prompt is safe: it
skips what's done.

By hand: **Setup → Set up by hand** has the snippets per part, and the sections below show the calls.

## Core unlock

You pick something small your paid features can't work without (a core class or module, default rules, a lookup
table; at most 512 KB) and **seal** it. The sealed copy ships with your app; the plain one doesn't. At start-up, after a valid
license check, the SDK unseals it and your app loads it.

```bash
sunlicense seal build/core.jar --product my-plugin -o src/main/resources/core.sealed
```

```java
ValidationResult r = client.validate(licenseKey, machineId);   // a valid answer carries the unlock
byte[] core = client.unseal(SEALED_CORE);                       // throws Locked when there's no valid unlock
ClassLoader coreClasses = SunLicenseProtection.classLoader(core, getClass().getClassLoader());
```

The Java [templates](/guide/templates) do this in their release build (`./gradlew build -PsunlicenseProduct=<code>`);
the other SDKs have the same calls in their own idiom ([SDKs](/guide/sdks#protection-core-unlock-tamper-reports-notice-protected-files)).

* **The key never ships.** Sealing happens on SunLicense's servers (`POST /api/v1/products/{id}/seal`), with a key per
  product that never leaves them. A license check returns the unlock **only when the license is valid** and the
  product has sealed something. Suspended, expired and revoked licenses, and blocked customers, get no unlock.
* **Outages don't lock anyone out.** The SDK keeps the unlock from the last valid check for **7 days**, bound to that
  machine. If SunLicense can't be reached, the core still loads for up to 7 days; an
  [offline license file](/guide/offline-licenses) carries the unlock too, for customers who stay offline longer.
* **Turning it off is safe.** Switching Core unlock off in Setup only changes the prompt. Builds you already shipped
  keep unlocking: SunLicense keeps answering for every product that has sealed data.
* Members never see the key, and it's never logged.

### Unseal without an SDK

For a language without an official SDK: when a check is valid and the product has sealed data, the
[validation answer](/api/validation) carries `unlock`, the product's key in base64. A sealed string is
`SLS1.` followed by base64url of a 12-byte nonce, the AES-256-GCM ciphertext and its 16-byte tag. Decrypt it with the
unlock key and the additional data `SLS1`; the plaintext is JSON `{"v": 1, "digests": {"<file>": "<sha256 hex>"},
"data": "<base64>"}`, where `data` is what you sealed and `digests` the hashes for tamper reports. Keep the unlock in
memory, or encrypted on disk and bound to the machine id for the 7-day grace. Test
vectors: [`protection-test-vectors.json`](https://github.com/ka0un/SunLicenseCom/blob/master/cloud/docs/specs/protection-test-vectors.json).

## Ownership notice

Every copy carries a short header comment (your organization, the product and your terms link) and logs one line
at start-up with the product, the owner, the **masked** license key and your terms link (the line `client.notice()`
returns). Nothing personal is sent
to SunLicense for it. "Licensed to *buyer's name*" is written only into downloaded copies, through the
[download placeholders](/guide/downloads#how-the-license-gets-into-the-file) (Standard).

The agent records the notice as done with `sunlicense setup report`, since SunLicense can't see it from outside.

## Tamper reports

Your release build seals the expected hashes of the code you ship along with the core
(`sunlicense seal … --digest 'build/classes/java/main/**/*.class'`). After unsealing, the app compares them with what's
running (`client.integrity(getClass().getClassLoader())`, or a jar or folder) and sends `OK` or `MODIFIED`, with up to
200 characters naming the changed files, with its next license check. The app never stops itself because of it: you decide.

A license with modified copies becomes a Sentinel finding (**License AX8P-XXXX-XXXX-XXXX runs a modified copy**),
with **Suspend** as the fix:

* **Free and Lite:** a suggestion with one-click Suspend.
* **Standard:** in automatic mode Sentinel suspends the license by itself, and you can undo it.

Sentinel never revokes for this. A server host's own patches can look like tampering; if a license is legitimately
modified, ignore the finding for that license. The finding's text stays generic: it doesn't say how the check works.

## Protected files

On Standard, data only licensed copies may have (premium rule sets, presets, models, assets) stays on SunLicense, and
the app downloads it after a valid check: `sunlicense files push rules.dat --product my-plugin`, then
`client.protectedFile("rules.dat")` in the app. Suspended, expired and revoked licenses get nothing. Everything about
it: [Protected files](/guide/protected-files).

## What protection can't stop

Be clear-eyed about it: **nothing makes code uncrackable.** Someone who holds a valid license can still extract the
unlock and patch their own copy. What protection does:

* **Raises the cost.** Deleting the license check is no longer enough; a cracker needs a valid license and real work,
  per release, and [obfuscation](/guide/obfuscation) makes that work harder.
* **Makes leaks visible.** A patched copy fails its integrity check and is reported to Sentinel with the license
  that ran it; downloaded copies carry a [watermark](/guide/downloads#whose-copy-is-this) that names the buyer.
* **Lets you act.** Suspend the license and the next copy can't unlock anything new; once the 7-day grace runs out,
  the core stops loading.

It never stops paying customers: outages, a few days offline and a switched-off part don't lock them out.
