The CLV1 protocol is now publicRead the spec

Docs · Reference

Protection Kit

A sample app with ClavisAUH's license protection built in, made for your app with the measures you pick, ready to build and to copy into your software

What you get

Notebook, a small console app: tier 1 is always in it, the tier 2 measures you pick are added as modules. You build it, run it against your app, then copy its files into your own software.

Pick and download it in the dashboard, under App security › Protection Kit. Each kit is made for one app: its App ID, API address and public key are filled in, and kit.lock lists every file with its SHA-256.

  • GUIDE.md, HUONG-DAN.mdbuild, run, put it into your software, release
  • HOW-IT-WORKS.md, GIAI-THICH.mdwhat each measure does and does not do, as on this page; GIAI-THICH.md is one line each in Vietnamese
  • TESTING.md, KIEM-TRA.mdhow to check each measure yourself

C# (.NET 8), C++ (C++17 with CMake), Python (3.9 or later) and Node.js (20 or later)

Five gates

A measure reaches the picker in a language only after five gates: its card, reviewed by someone told to refute it, then in that language the code, a check that a valid app works normally, a check that the attack is caught, and a check that the valid setups the card lists (virtual machines, slow networks, other time zones, system updates) are not blocked

Last full run of the kit lab, every check and every choice built: 2026-11-05

  • C#: 25 tests on Windows and Linux
  • C++: 27 tests on Windows and Linux
  • Python: 24 tests on Windows and Linux
  • Node.js: 24 tests on Windows and Linux

Presets

Core
The required measures only
T1, T2, T3, T4, T5, T6
Recommended
Core plus what most apps need; what is not ready yet is left out and named
N1, N3, N4, N5
Full
Every measure that passed its gates in that language

Core: in every kit

Part of the sample app; you do not choose them

Response Signing and Replay Protection

In every kitReady in C#, C++, Python, Node.js

The software trusts only answers signed by your app's key for the request it just sent; a fake server is refused

Server side
Nothing to turn on
Check it yourself

In a test copy, put another app's public key in the settings file's list of public keys (or, in the repository's sample whose settings are empty, set CLAVIS_APP_KEY), build and run activate: it stops with FAIL unknown_key.

Checks in C# · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpassed

Checks in C++ · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpassed

Checks in Python · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpassed

Checks in Node.js · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpassed

Threat

Someone runs the software against a server of their own (an "emulator" that answers "license active" to everything), or sits between the software and ClavisAUH and rewrites answers, or replays an answer recorded earlier.

How it works

  • Every request is signed on the machine with the device's Ed25519 key, over the app id, method, path, SHA-256 of the body, a timestamp, a nonce and the session id. The server refuses a nonce it has seen (kept 150 seconds) and a timestamp more than 60 seconds from its clock.
  • Every answer the server gives once it has identified the app, error answers included, is signed with the app's Ed25519 key over its key id (kid), the HTTP status, the method, the path, **the nonce of the request it answers**, a timestamp and SHA-256 of the body (CLV1.1). The SDK refuses an answer that is unsigned, signed by a key outside the build's list, signed over anything else, or more than 60 seconds off its corrected clock. No endpoint is exempt; the one answer not held to the 60-second window is the signed clock_skew refusal, which exists because the machine's clock is wrong (it is still bound to the request's nonce).
  • Some answers cannot be signed because the server does not know the app yet: missing headers, a bad nonce or encoding, an unknown app, unreadable JSON, an unknown path, a server error while it loads the app, the server's flood guard (429), and any page a proxy or CDN in front of the API sends. The SDK refuses them exactly like a forgery. (A request whose body the route refuses, such as a key too short to be one, was answered unsigned too until the fix that came with this card's second review; it now gets a signed validation_failed.)
  • After a refused answer, the SDK remembers it in memory and reports it (response_verification) with the next answer it can verify, which Protect counts as T1.

What it does not do

  • It does not stop someone from patching the executable so that it skips the check or ignores the answer. That is what T6, N2, N3, N4 and N9 address, each only in part.
  • It does not hide what travels: TLS does . Anyone who controls the machine can read the requests.
  • It does not help if the app's private key leaks (a compromised server). Rotating the key (Signing Key Rotation) protects only the builds shipped after the rotation without the leaked key; builds already installed keep trusting it, and nothing revokes a key they carry.
  • A fake server used all the time is never reported: the report goes to the same address as everything else and is only sent after a verified answer, and the count lives in memory. The SDK still refuses every fake answer; T1 in Protect only sees tampering that comes and goes.
  • It does not stop a customer from sharing a valid key: that is T4 and Protect T5.

False alarms

  • A machine clock more than 60 seconds off: the server answers clock_skew (signed), the SDK corrects its clock and retries once. The correction lives in memory, so a clock more than 5 minutes off raises Protect T3 (clock drift, weight 6) at every launch of the software.
  • A clock almost exactly 60 seconds off sits between the two limits: the server still accepts the request, but the answer's time is just outside the SDK's own 60-second window, so every answer is refused as stale_response with no clock_skew to learn from. The customer has to set the clock right; the SDK's sentence for stale_response says to check the clock.
  • Unsigned infrastructure answers are failed tries, not forgeries. A 502 from the reverse proxy while the API restarts, a CDN error or challenge page, the flood guard's 429, a company proxy's block page or 407: the SDK refuses each one and tries again, and nothing is reported. Otherwise one outage would raise T1 on every license at once. The price: a fake server that only ever answers with unsigned errors stays invisible, but it also changes nothing. An unsigned 200, or an answer with a wrong signature, is still reported.
  • A wrong API address or public key in the build gives unsigned_response or unknown_key for every call: a configuration error that shows on the first test run, not later.
  • A transparent TLS proxy (one that re-encrypts without changing the bytes) is invisible to it: the signature is end to end. A proxy that blocks, rewrites or asks for credentials is not, as above.

No Embedded Secrets

In every kitReady in C#, C++, Python, Node.js

The build carries only the App ID, the API address and the public key; a scan stops the secret shapes it knows and the values you list

Server side
Nothing to turn on
Check it yourself

build.ps1 (or build.sh) ends with OK secrets; put a string like clv_live_ plus 32 letters or digits into a message the app prints and the build stops.

Checks in C# · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpassed

Checks in C++ · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpassed

Checks in Python · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpassed

Checks in Node.js · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpassed

Threat

Anything inside the executable can be read by whoever has the file: strings, resources, configuration shipped beside it. A secret placed there (a key that signs, a Seller API key, a password to another service) is a secret given to every customer and every cracker.

How it works

  • The software carries only the app id, the API address and the app's public key. The public key verifies answers; it cannot sign anything.
  • The device makes its own key pair on first run (T3); the server learns only its public half.
  • The app's private key is generated and sealed on the server and is never shown or downloadable.
  • The kit adds a build scan: a test that reads every file the build ships and fails when it finds something shaped like a secret: a Seller API key (clv_live_ or clv_test_ followed by 32 letters and digits), a webhook secret (whsec_), a PEM private key block, what a test run leaves in a state folder (device.key, license.key, offline.token, a clv1. token), or a value the seller lists in a secrets.deny file in the folder the scan runs from (the kit's root for build.ps1 and build.sh), one per line (their own third-party keys).
  • It searches each file as bytes (UTF-8 reads the same for these shapes), as UTF-16, because C# string literals and C++ L"" strings on Windows are stored that way, and as UTF-32, the width of C++ L"" strings on Linux and macOS.
  • It opens what is compressed before searching: .jar, .whl, .zip and .nupkg files.
  • It runs on the publish folder, before any step that hides or packs content: obfuscators that encode strings (N9), and formats that pack everything into one file (PyInstaller, .NET single-file publish, installers).

What it does not do

  • It does not keep the seller's own secrets out of the build if the seller writes them in: the scan catches the shapes it knows and the values the seller lists, not every secret.
  • It does not protect the code itself: logic in the executable can still be read (N9 makes it harder, nothing makes it impossible).
  • A third-party API key the software truly needs at run time cannot be hidden on the client; it belongs on a server (a relay, not built yet).

False alarms

  • A test string that only looks like a key (for example clv_live_ in documentation shipped with the build).
  • PEM test keys inside third-party packages the build ships (node_modules in an Electron app, Python dependencies that bundle their own test data).

The seller allows each one by file and value in secrets.allow; the kit never ignores a finding silently.

Device Key Storage

In every kitChecks pending

The machine's signing key stays in the user's state folder: sealed with DPAPI on Windows in C# and C++, readable only by the user on Linux and macOS, and on Windows in Python and Node.js as private as its folder

Server side
Nothing to turn on
Check it yourself

After activate, the state folder (%APPDATA%\Notebook, or CLAVIS_STATE_DIR) holds device.key; on Windows it is sealed with DPAPI, so another Windows account cannot open that file.

Checks in C# · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpending

Checks in C++ · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpending

Checks in Python · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpending

Checks in Node.js · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpending

Threat

The device key signs every request. A session belongs to the device whose key opened it, and an offline token is valid only where its key hash (dev) matches. Copied to another machine, the key lets that machine use the first one's sessions and offline tokens.

How it works

  • Every request is signed with an Ed25519 device key. The SDKs keep it between runs only through their key helper (loadOrCreateDeviceKey and its equivalents) or when the software passes a key in; a client created without one makes a new key in memory each time. The kit's sample app always uses the helper. JavaScript in a browser has no helper.
  • On Windows, the C# and C++ helpers seal the key with DPAPI for the current user. The C library and the CLI use the C++ SDK, so they do the same.
  • On Linux and macOS the helpers keep it in a file readable only by the current user (mode 0600). The C++ helper, used by the C library and the CLI, did not restrict the mode until the fix that came with this card; it now creates the file with 0600 like the others.
  • On Windows, the helpers of the JavaScript, Python, Go, Rust and Java SDKs write the seed as is, and the Unix mode means nothing there: the file has the access rights of its folder. A per-user folder (%APPDATA%, the default the kit uses) is private to the user; a folder chosen elsewhere may not be.
  • The kits use each SDK's own helper: DPAPI on Windows in C# and C++, a file readable only by the user on Linux and macOS; on Windows, Python and Node.js keep it as text, as private as its folder. A key store in the OS store where the language reaches it (DPAPI from Python and Node.js, Keychain on macOS, libsecret on Linux) is not in the kits yet.
  • The server checks every session request against the device's current public key: a session id used with another key is refused with session_device_mismatch. Protect records it as T5 (key sharing) only on redeem and transfer; a heartbeat, variables(), the update check or a sign-out refused this way raises no signal.

What it does not do

  • It does not hold the seat. The server matches a machine by its hashed identifiers (fingerprint, or enough shared components), never by the key; a known machine signing in with a new key simply has its stored key replaced.
  • It does not stop malware running as the same user: it can use the key in place, as the software does.
  • The licensed user can move it. DPAPI protects the key from other accounts, not from its own user, who can unseal it and seal it again on another machine. That machine then passes the first one's offline tokens. Online, it sends its own identifiers and counts as another device.
  • A user-only file can be copied by that user just as well.
  • It is not hardware-bound: a key in a TPM or Secure Enclave (N10) would be, and is not built.

False alarms

  • Two Windows accounts on one machine (or two state folders) have one fingerprint and two keys. Each sign-in replaces the device's stored key, so the other account's next heartbeat gets session_device_mismatch. While both run, they keep replacing each other's key and signing in again does not settle it; the kit's guide says to run the software under one account at a time on a one-seat license.
  • Reinstalling the system or a new user profile loses the key. The machine signs in again with a new key; whether it keeps its seat depends on its identifiers, and the SDKs' default description changes on a reinstall (PLAN6 F.9).
  • A roaming profile carries a DPAPI key to another machine of the same user: that machine is still a new device for the seat count, but it can use the first machine's offline token.

Session and Heartbeat

In every kitReady in C#, C++, Python, Node.js

The software asks the server again on a rhythm, so a suspension or a revocation reaches running copies at the next heartbeat

Server side
Nothing to turn on
Check it yourself

Start run with CLAVIS_HEARTBEAT_SEC=10 (it can only shorten the usual 30 minutes), suspend the key on the dashboard: within 10 seconds the app shows the customer's sentence and stops.

Checks in C# · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpassed

Checks in C++ · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpassed

Checks in Python · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpassed

Checks in Node.js · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpassed

Threat

A license checked once at start-up keeps working after it is revoked, suspended or expired, for as long as the software stays open; and a key shared between machines keeps several of them running at once.

How it works

  • Activation opens a session bound to the device key. The software sends a heartbeat on a timer, about every 30 minutes by default (the SDKs do not send it by themselves); a session without one for 60 minutes ends, or after three heartbeat intervals when the app turns on short sessions (Short-Lived Session Lease).
  • Each heartbeat re-checks the license: suspended, revoked or expired answers end the run with the matching code; suspending, revoking or resetting devices on the dashboard ends the sessions at once, so the next heartbeat gets session_expired and the next activation gets the precise reason.
  • Dead machines give their seat back, two ways: a background job releases a machine silent for the license plan's deadAfterHours (empty means 72, 0 means never), and it may come back within 7 days on the same seat if one is free; and when the plan sets its own heartbeat, a machine silent for two of its intervals is removed at the next sign-in on that key, after which its own next heartbeat gets device_revoked.
  • An app can cap concurrent sessions per license (Concurrent Session Limit).
  • The kit's sample app runs the heartbeat on a timer at its "heartbeat loop" hook point and handles each outcome: continue, retry later (network), or stop and show the user's sentence.

What it does not do

  • Between two heartbeats the software keeps running: a revoked license stops at the next heartbeat, not instantly.
  • With offline tokens, a copy without network keeps running until the token expires (N5 shortens this).
  • A patched executable can skip the heartbeat altogether; T6, N3 and N4 make that less useful.
  • A copy that cannot reach the server (the API's address blocked by a firewall) gets network errors, which the kit retries rather than stopping on; it keeps running until its session would have ended (60 minutes, or three Short-Lived Session Lease intervals) or until its offline token runs out, whichever is later. Only a server answer can stop it sooner.

False alarms

  • A network drop makes a heartbeat fail with a retryable code, and an answer that cannot be proven to come from the server (a proxy's page, the API restarting) is treated the same way: the kit keeps the software running within the session's time and any offline allowance, rather than stopping at the first failure.
  • A laptop waking from a long sleep finds its session ended (session_expired), or the machine removed (device_revoked) if the plan's heartbeat reaped it. The SDK does not sign in again by itself; the kit's sample activates again with the key it was given, which takes a seat only if one is free.
  • A plan heartbeat shorter than the software's own interval: the plan's heartbeat setting is not sent to the SDK (only Short-Lived Session Lease's pulse is). If it is shorter than half the interval the software uses (30 minutes by default), another machine's sign-in on the same key removes machines that are running normally, and they get device_revoked. The kit's guide says to keep the plan heartbeat at least twice the software's interval, or to use Short-Lived Session Lease, whose pulse the software does receive.
  • With a session cap (Concurrent Session Limit), a session left open by a crash or a close without logout() counts for up to 60 minutes: a customer who moves to another machine meanwhile gets session_limit and Protect records T7. The kit calls logout() on every normal exit and says so in its guide.

Error Handling

In every kitReady in C#, C++, Python, Node.js

Every error goes through one place and shows the customer a sentence; a machine that holds no seat is not told why a key failed

Server side
Nothing to turn on
Check it yourself

Activate a key in the right format that does not exist (change a few characters of your test key): the app shows "The key or the sign-in details are not valid" and FAIL invalid_credentials.

Checks in C# · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpassed

Checks in C++ · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpassed

Checks in Python · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpassed

Checks in Node.js · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpassed

Threat

Two harms come from poor error handling. Detailed refusals tell an attacker which keys exist (key enumeration), and errors swallowed or shown as raw codes leave honest users stuck and tempt sellers to "fix" them by ignoring errors, which turns into a bypass.

How it works

  • Every failure has one code from a single table shared by the server, every SDK, the C library and the CLI, with a sentence for the user in English and Vietnamese and whether trying again later can help.
  • For a CLV1.1 client (one that sends x-clavis-proto: 1.1, as every official SDK does) on a machine that holds no seat on a key, an unknown, suspended, revoked or expired key answers the same invalid_credentials, after a minimum time of 300 ms . The machine holding the seat gets the precise reason.
  • Failed keys and passwords are limited per app and IP: after 20 failures in 15 minutes the IP gets too_many_attempts, and Protect T4 (key guessing) is raised.
  • The kit's sample app has one place that turns an error into what the user sees: the sentence, and for retryable errors a later retry. It never treats an unknown error as success.

What it does not do

  • The uniform answer is opt-in by header. A caller that leaves out x-clavis-proto: 1.1 gets invalid_key for an unknown key and the precise reason (license_expired, license_suspended, license_revoked) for a real one, so it can still tell which keys exist. Applying The server to every protocol version is a question for the owner (PLAN6 F.10).
  • Even for 1.1 callers, some answers show that a key exists: an unused key activates and takes a seat, device_limit says the key is full, license_already_registered says an account owns it. The server hides why a key fails, not whether a usable or full key exists.
  • The 300 ms is a floor, not equal timing: a slower refusal can still differ from a fast one.
  • It does not stop someone who already has a valid key from sharing it (T4, Protect T5), and it does not stop a patched executable from ignoring errors (T6, N3, N4).

False alarms

  • A customer who mistypes a key sees the invalid_credentials sentence, which says to check the key; a key too short or too long to be one, an empty key, or a username or password the form refuses gets validation_failed ("check what you entered"). Until the fix that came with this card's second review, those got an unsigned answer that the SDK took for a forged server (T1).
  • A customer whose key is expired or suspended, on a new or reinstalled machine (no seat there) is told to check the key rather than the real reason; the seller's support should look the key up before answering.
  • Everyone behind one shared address (an office, a school, a mobile carrier's NAT) shares the 20-failure limit: enough mistyped keys or passwords there lock all of them out for up to 15 minutes, and Protect raises T4 for that address. With Graduated Enforcement on Act on its own, T4 blocks that address for 24 hours, and every call from it is refused, heartbeats of valid licenses included, until the block ends or the seller undoes it.

Server-Side Enforcement

In every kitReady in C#, C++, Python, Node.js

The paid feature needs the plan's entitlement and data that only a valid session receives

Server side
  • Variable report_template and entitlement export_report
Check it yourself

With a key whose plan lacks export_report, export answers "Your license does not include this feature".

Checks in C# · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpassed

Checks in C++ · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpassed

Checks in Python · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpassed

Checks in Node.js · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpassed

Threat

A check that lives only in the executable can be patched out: change one jump, and the paid feature runs without a license. Everything the feature needs is already on the machine.

How it works

  • Values the paid feature needs (a template, a rate, a configuration, a download address) are kept as app variables on the server, and the server gives them only to a valid session: every sign-in answer carries them and variables() fetches them again; a machine without a session gets nothing.
  • Variables are scoped to the whole app, to one license, or to one end user. An app-wide variable reaches every session, trials and free accounts included, so the kit's sample also checks the paid entitlement in the signed answer before using one, and its guide says which scope fits paid data.
  • Feature flags come from the license plan (entitlements) in the signed answer, so they cannot be edited on the way.
  • The kit's sample app ("Notebook") keeps its paid feature's report template as a variable: without a valid session the feature has nothing to work with, patched or not.

What it does not do

  • It only protects what the seller actually moves to the server. A feature whose logic and data are all in the executable stays patchable.
  • Once a valid session has fetched a variable, its value is on that machine: a customer with a valid license can copy it out, and so can anyone who starts a trial, when the app offers one: trials are given per machine as the machine describes itself, so a cracker can start new ones at will. App-wide variables are therefore not secret from non-paying users of an app with trials or free accounts.
  • A seller who caches the value for offline use (the guide describes how, the sample does not) keeps it on disk, where a patched build can read it whatever the offline allowance says. Variables suit data that changes, is large, or is worth little alone; secrets that must never reach a machine belong behind a server call (a relay, not built yet).
  • It is not encryption of the executable: N9 is about reading the code.

False alarms

Offline use: a feature that needs a variable does not work offline. The sample app says the feature needs a connection; caching the last value is the seller's choice, with the risk above.

Optional: you pick

Each one is a module; leave one out and the app still builds

Certificate Pinning

Ready in C#, C++, Python, Node.js

The software talks only to a server whose TLS key is one of its pins; an intercepting proxy is refused

Server side
  • TLS pins on the server
Check it yourself

Run the app through an intercepting proxy (mitmproxy, Fiddler) with its root trusted: it cannot connect and logs pin_mismatch.

Checks in C# · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpassed

Checks in C++ · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpassed

Checks in Python · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpassed

Checks in Node.js · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpassed

Threat

Someone on the customer's machine installs their own root certificate and runs an intercepting proxy (Fiddler, mitmproxy, Charles) to read the traffic between the software and ClavisAUH: the license key in activate, a password in login, the variables a valid session receives. The protocol itself is public; what the proxy reveals is these values.

How it works

  • The kit replaces the SDK's transport with one that, after the normal TLS checks, compares the SHA-256 of the server certificate's public key (SPKI) with a short list built into the software: the current key and at least one backup key that is not in use yet. No match: the connection is closed before anything is sent.
  • Every official SDK takes a custom transport, the place its documentation names for pinning: the Transport option in C#, Python, Go, Rust, Java and C++, and the fetch option in JavaScript. Per language: C# ServerCertificateCustomValidationCallback, C++ libcurl CURLOPT_PINNEDPUBLICKEY, Python the peer certificate of the TLS socket, Node a fetch built on an agent whose checkServerIdentity checks the pin (the JavaScript guide says the SDK cannot pin: true of browsers and of the default fetch; it gets a line about this route when the module ships). The C library and the CLI use the C++ SDK's default transport and cannot take one, so they are out of scope.
  • The kit's transports also ignore TLS key-log settings they control: Python's context is built without SSLKEYLOGFILE, and the C++ module removes SSLKEYLOGFILE from its own process environment before libcurl starts (libcurl with OpenSSL, on Linux and macOS, would otherwise write the session keys there).
  • A refused connection surfaces as the SDK's network error, so the software's existing "cannot reach the server" handling applies; the kit adds a distinct log line (pin_mismatch) for support.

What it does not do

  • It does not stop someone who changes the executable: removing the pin check, or hooking the TLS library inside the process, defeats it. Pinning raises the cost of reading the traffic; it is not a barrier against patching.
  • Reading the traffic without changing the file still works with the right tools: an instrumentation tool such as Frida attached to the process, NODE_OPTIONS=--tls-keylog for Node, SSLKEYLOGFILE for Electron (Chromium).
  • Update downloads: the JavaScript SDK downloads through the same fetch option, so they are pinned too. The other SDKs' download helpers use a client of their own; the kit downloads the file itself through its pinned transport, from the address in the verified manifest, then checks the SHA-256 the manifest gives. Either way the files are checked against the signed manifest, so they cannot be altered.
  • It adds nothing to the integrity of answers: T1 already refuses altered or forged answers, pinned or not.
  • It cannot be done from JavaScript in a browser (no access to the certificate). Electron and Node can.
  • It does not protect the server's own TLS : the API must be on HTTPS with its own domain first. Until ClavisAUH is deployed, there is no production key to pin.
  • The deployment must keep a controlled key. The reverse proxy in the deployment guide gets its certificates from Let's Encrypt on its own and makes a new key at each renewal by default; behind the CDN option, clients see the CDN's key, which ClavisAUH does not control. Pinning needs a certificate key the operator keeps and renews with (or a pinned intermediate, with its own risks), decided before any build ships pins.
  • The server does not send pins today: securityHints.pinning is always "off" and spkiPins empty, so the pins live in the software build only.

False alarms

  • TLS inspection breaks pinned connections: a company proxy that re-signs all HTTPS, and on home machines the HTTPS scanning of antivirus suites and parental filters. The seller decides whether such customers matter; the kit documents the symptom and the setting to exclude the API's address.
  • Key rotation without a backup pin locks out every installed copy at once. The kit refuses to build with fewer than two pins, and its guide explains the order: add the next key as a pin, ship, then rotate the server key.
  • Certificates renewed with the same key do not change the pin; renewals that make a new key (the default of common ACME clients, including the deployment's) do, so key reuse must be switched on deliberately.

Build Integrity Verification

Checks pending

Each sign-in sends the build's hash; a changed build shows as an unknown build in Protection

Server side
  • Build integrity check (A10)
  • A published release, with your builds uploaded
Check it yourself

Upload the build's file to a draft release and run node tools/check-release.mjs; a build changed by one byte raises Unknown build in Protection.

Checks in C# · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpending

Checks in C++ · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpending

Checks in Python · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpending

Checks in Node.js · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpending

Threat

A cracked copy circulates: the seller's executable with its license check patched, sometimes repackaged under the seller's name. It still talks to ClavisAUH (it needs a session for the data behind T6), and nothing tells the seller that this binary is not one they shipped.

How it works

  • At start, the kit computes the SHA-256 of one file, the one that holds the program (see the limits for which), and passes it to the SDK as buildHash, with appVersion. Every sign-in sends both to the server.
  • The server knows a hash when it matches any file uploaded under the app's releases, drafts included. A release holds one file per platform, and its published file is what the update check serves, usually the installer; so the kit's release step puts the hashed file in a separate draft release that is never published, under its own version or channel (an app has one release per channel and version). Drafts can be deleted later, published releases cannot, and both count against the workspace's release storage.
  • The server compares the hash with the files the seller uploaded to Licensing & releases › Releases. A hash that matches no file, for an app with at least one published release, is an unknown build: Protection raises **Unknown build** (T8). The check runs before the license is looked up, so T8 lands on the machine only when that device key is already registered; a patched copy on a new machine is recorded against its IP address. T8 is raised whether Build Integrity Verification is on or off, and Graduated Enforcement acts on it either way. Build Integrity Verification decides only the answer: off or Warn only lets the build sign in, Block refuses it with unknown_build. A hash that matches a release the seller blocked gets build_blocked.
  • With Build Integrity Verification on, init also tells the SDK reportTamperSignals: true, which N6 uses.

What it does not do

  • The hash is reported by the software itself. A cracker who reads the kit's code can make the patched binary send the original file's hash; the server cannot tell. The code says it plainly: a signal, never proof. N2 catches copies made without that extra work, and redistributions that were not built for ClavisAUH at all.
  • It does not stop the patched copy from running offline or from running at all when the patch also removes the sign-in. T6 and N3 are what make a copy without sign-in useless.
  • It hashes one file only (plugins, other DLLs, scripts loaded later are not covered). In C#, a framework-dependent build keeps its code in YourApp.dll beside a small launcher .exe: the kit hashes that main assembly, or the single file when the app is published as one. For Python and Node.js the executable is the interpreter: the kit hashes the main script or bundle, which a cracker can edit just as easily, and other modules are not covered. With N9, Python is compiled by Nuitka and has no script left: the kit then hashes the compiled executable.
  • It does not sign the executable: that is N8.

False alarms

  • A build shipped without uploading its files: every customer on it raises T8, and with Block cannot sign in. Files are uploaded on the Releases page (the Seller API can list, publish and block releases, not upload them), so the kit ships a check script instead: with a releases:read Seller API key it confirms that the build's hash is listed before the build goes out.
  • Files changed after release by an installer or a code-signing step: the uploaded hash must be the hash of the file as installed. The kit hashes the file after signing (N8) and uploads that.
  • Antivirus "repairs" or a half-done self-update: the hash may be unknown for a while. Warn only keeps those customers signing in, but only while Graduated Enforcement is on Alerts only: with Act on its own, every unknown-build sign-in adds 15 points, so a registered machine is blocked after a few such sign-ins (T8 repeated) and the license is signed out at 60. The guide says to use Warn only with Alerts only.

Server-Side Code Execution

Not in kits yet

Not in kits yet

Server side
  • An offline window on the license plan
  • A Seller API key
Check it yourself

Not in kits yet.

Threat

T6 keeps the paid feature's data on the server, but once a valid session has fetched a variable, a cracker with one valid license can copy the value into a patched build and share that build. Whatever the executable can compute on its own, a patched executable can compute too.

How it works

The feature's essential step runs on a server the seller controls, which serves only calls backed by a license that is valid right now and entitled to the feature:

  1. The software, signed in, holds a signed offline token from ClavisAUH (clv1.<payload>.<signature>, payload with app, lic, dev, iat, nbf, exp, lexp) and its device key.
  2. It calls the seller's endpoint with the token, the device public key, and a signature by the device key over the request body, a timestamp and a nonce.
  3. The seller's server checks with the kit's reference verifier: the token's signature against the app public key (and kid), app equal to the seller's app, iat/nbf not in the future, exp not passed, sha256(device public key) equal to dev, the device signature, the timestamp within 60 seconds, the nonce unused.
  4. Then, always, it asks the Seller API for the license (GET /seller/v1/licenses/{lic}, scope licenses:read) and requires its status to be usable and its plan's entitlements to include the paid feature. Offline tokens carry neither the plan nor the entitlements, and trials and free accounts receive tokens too, so without this lookup every trial would get the paid work. The answer may be cached for a short time (the kit uses 60 seconds).
  5. Only then does it do the paid work and return the result.

What it does not do

  • The device key, the token and a session can be copied by their owner. A licensed customer can export the key (DPAPI protects it from other accounts, not from its own user; the SDKs expose the seed) and build it, the token and the session id into copies for others; heartbeats refresh the token. Every such call then looks like the one licensed machine. The verifier rate-limits per license and device and counts distinct addresses per lic/dev; ClavisAUH's own Protect does not see it (heartbeats raise no signals, impossible travel needs different devices).
  • Changes to devices are invisible to the seller's server until the token expires: a device reset, a transfer (old tokens stay valid), a blocked device. The Seller API's device records carry no key hash, so the verifier cannot match dev to a device. Only the license's own status is checked live.
  • Grace days break it: when a license is past its end date but inside the plan's grace days, ClavisAUH still lets it sign in, but issues tokens that are already expired (exp is capped at the license's lexp). The verifier refuses them, and a heartbeat cannot fix it. The Seller API cannot help: it reports such a license as expired and does not show the plan's grace days. So N3 refuses grace-day licenses until F.11 is settled.
  • It needs offline tokens: a plan with an offline window (at least 1 hour), offline licenses on the workspace (Premium by default) and Online-Only Mode off.
  • The Seller API allows 120 requests per minute per key (600 on Premium) and can be switched off for a workspace; the verifier must decide what to do when it answers 429 or not at all. The kit fails closed (no paid work) and says so to the user.
  • The seller runs, secures and pays for that server; the kit gives a reference verifier, not hosting.
  • It does not hide the result once delivered: a report produced for a valid license can be copied like any file.

False alarms

  • Grace days, as above: a customer inside them can sign in to the software but not use the paid work.
  • A machine clock far off: the device signature's timestamp is refused. The verifier answers with its own time and the kit retries once with the offset, as the SDK does for clock_skew.
  • A token near exp while the network is up: the kit sends a heartbeat (which brings a fresh token) before calling.
  • The Seller API being slow or rate-limited: paid actions fail while it lasts (fail closed).

Redundant License Checks

Ready in C#, C++, Python, Node.js

Several checks read the license state their own way and report what does not add up

Server side
  • Risk-based automated response (A13)
Check it yourself

Remove the app's own entitlement check from a test copy and export with a plan that lacks it, on a license that still receives report_template (app-wide for this test): N4 refuses and Protection shows a tamper report.

Checks in C# · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpassed

Checks in C++ · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpassed

Checks in Python · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpassed

Checks in Node.js · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpassed

Threat

The license check sits in one place, usually right after start. A cracker finds it, flips one jump, and the whole program is unlocked. Worse, the program itself decides what happens when something looks wrong, so the same patch also removes the reaction.

How it works

  • The kit's sample app checks the license state at several hook points, not one: at start, before each paid action, in the heartbeat loop and once at a random moment 2 to 10 minutes after start (at the first hook after it, with no timer or thread of its own). They read the state two ways: the last signed grant's status and state (at start, after each heartbeat and at the random moment), and the entitlement the paid action needs, read from the signed answer rather than through the SDK's has(), so patching one does not silence the other. Their reports, though, all leave through the SDK's one request() call to /v1/client/report.
  • A check that finds an inconsistency (a paid action reached with no valid grant, a grant that does not say active while online) does not crash or delete anything. It sends a report (kind: "patched", with which check and why) through the SDK's request() at once while the session is live (at most once per kind per session; a check that fires offline sends nothing), and carries on as the license state allows.
  • The server turns reports into the risk score of the license and device. The tamper kinds (patched, and N6's debugger, vm, hook) count as T9 (weight 4); one signal type adds at most 5 times its weight (20 for T9) to a subject's score; at most 5 reports per session per hour are counted, all kinds together. Beside them, Protect sees everything else (key sharing, impossible travel, unknown builds). Graduated Enforcement decides: alerts only, or, with **Act on its own**, sign out the license's sessions, suspend it, block for 24 hours an address that keeps guessing keys (T4), block a device that keeps raising T5 or T8.

What it does not do

  • Every check runs on the customer's machine and can be found and removed. Scattering raises the work per crack; it does not make a crack impossible.
  • The report path is a single point of failure: patching the one call that sends reports silences N4 and N6 together, while every check still runs.
  • A cracked copy that never signs in again sends no report: N4 sees only copies that still talk to the server, which is why it pairs with T6 and N3 (copies that skip the server lose the paid data).
  • N4 alone never reaches Graduated Enforcement's thresholds: T9 is capped at 20 points, below the lowest default (40). A workspace alert rule that matches T9 or low-severity signals still alerts on the first report. Reports past the 5-per-hour limit are dropped while the software is still told ok, and they share that allowance with N5's rewind reports (offline_rewind, counted as T11, weight 8, which can reach 40 alone) and the SDK's own verification reports (response_verification, counted as T1, weight 18, which can reach the suspend threshold alone). Graduated Enforcement acts on the combined score, driven mostly by T1, T5 and T8.
  • The C library and the CLI have no call for sending reports, so N4 is for the SDKs only.
  • Act on its own is a Premium feature; on Basic, Graduated Enforcement only alerts.

False alarms

  • The checks never crash or stop the software. One of them refuses: the paid-action check, when the latest signed answer lacks the entitlement (the kit takes it from each heartbeat too, so a plan changed while the software runs is not refused). A wrong report still adds up to 20 points, and on top of key sharing (T5, up to 75) or unknown builds (T8, up to 75) that can cross the suspend threshold (80) under **Act on its own**.
  • A long sleep or hibernation makes the last verified answer old and usually ends the session (60 minutes, or three Short-Lived Session Lease intervals): the heartbeat after waking gets session_expired. The kit signs in again before its next check, so waking up raises no report.
  • Automatic suspension on a wrong score is the real risk: the kit's guide says to start with Alerts only, review incidents, then switch. Ended sessions cannot be undone (the customer signs in again). Undoing a suspension leaves the score where it was, so the next signal suspends again unless the seller also uses Dismiss on the license, which sets its risk score back to 0 (closing the incident does not); the guide gives both steps.

Clock Tampering Detection

Ready in C#, C++, Python, Node.js

Keeps the latest time seen; a clock more than 10 minutes behind it turns offline use off until the next connection, which reports it

Server side
  • An offline window on the license plan
Check it yourself

With a plan that has an offline window: go offline and run list (it works). Turn the clock back a day and run list again: offline use is refused; online again, Protection shows an offline clock rollback.

Checks in C# · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpassed

Checks in C++ · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpassed

Checks in Python · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpassed

Checks in Node.js · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpassed

Threat

A customer whose license was revoked, suspended or refunded cuts the software off the network and keeps using the offline token it already has. To stretch that token past its expiry, they turn the machine's clock back.

How it works

  • Short window. The offline window is the license plan's offlineTtlSeconds (1 hour to 90 days). Every heartbeat brings a fresh token, so the window counts from the last time the machine reached the server. The kit's guide picks the shortest window the seller's customers can live with, and the kit's build also sets its own maxOfflineSec (a cap on a token's age that no server answer can loosen). With Online-Only Mode on, the server's hint is 0, so no token is trusted in any process of any build that reaches init.
  • What the SDK already refuses. A token for another app or another machine (dev is the hash of this device's key), a token past exp or past the license's lexp, a token whose iat or nbf is more than 60 seconds in the future (the clock is behind the time it was issued), and, within one process, a token once the clock runs more than 60 seconds behind the last verified server time.
  • What the kit adds. The SDK forgets that server time when the process ends. The kit keeps a high-water mark: the latest time ever seen (server time from verified answers, and the machine's own time at each start and exit and while running), stored beside the device key: sealed with DPAPI in C# and C++ on Windows, in Python and Node.js a file readable only by the user on Linux and macOS, as private as its folder on Windows. Either way the same user can rewrite it (DPAPI protects it from other accounts, not from its own user). A verified server time also lowers the mark when the machine's clock had run fast, so correcting a fast clock does not lock offline use for good. At start, and at each heartbeat that gets no answer, a clock more than the tolerance behind that mark means a rewind: offline use is refused and the software asks to go online. Back online and signed in, it reports kind: "offline_rewind", which Protect counts as T11.

What it does not do

  • A revoked license still runs **offline until its token's exp**: the window is the price of working offline. Only Online-Only Mode (online only) removes it.
  • Deleting or restoring the high-water mark file (with an old backup of the state folder) defeats the kit's extra check; the SDK's own iat/nbf check still holds. A patched executable can skip both.
  • A machine that never comes back online never reports the rewind.
  • The window and the hint only reach builds that call init; a token already on a machine that never manages to call init runs until its own exp.

False alarms

  • Normal clock drift and daylight saving are within the tolerance (the kit uses 10 minutes for its own mark; the SDK's checks use 60 seconds against signed times). Daylight saving changes the displayed time, not UTC.
  • A clock fixed backwards by the user or by time sync after it ran fast: refused offline until the machine is online once; online use is never blocked by N5. The guide gives the message to show.
  • Restoring a whole-disk backup or a VM snapshot sets the clock and the mark back together: no alarm.
  • Timezone changes do not move UTC time: no alarm.
  • Dual-boot machines where one system keeps the hardware clock in UTC and the other in local time, and **dead CMOS batteries** that reset the clock at boot: the clock lands behind the mark, offline use is refused until the machine is online once, then the server time corrects it. That start reports a rewind (Protect T11, weight 8): a machine that does this at every boot adds up on its own, so check its incidents before acting on them.

Anti-Debugging and VM Detection

Checks pendingNeeds N4

A debugger or a virtual machine is reported to the server, never blocked on the machine

Server side
  • Build integrity check (A10)
  • Risk-based automated response (A13)
Check it yourself

With A10 on, run the app under a debugger: Protection shows a tamper report.

Checks in C# · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpending
  • No false alarmpending

Checks in C++ · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpending
  • No false alarmpending

Threat

A cracker runs the software under a debugger to find the license check, or in a throwaway virtual machine to try keys and patches without risking their own system. Nothing tells the seller this is happening.

How it works

  • The kit looks for a few well-known signs at start and again later in the run:
  • Debugger: on Windows, IsDebuggerPresent and CheckRemoteDebuggerPresent (and Debugger.IsAttached for managed debuggers in C#); on Linux, a non-zero TracerPid in /proc/self/status.
  • Virtual machine: the system manufacturer and model names that common hypervisors report (VMware, VirtualBox, QEMU/KVM, Parallels; for Hyper-V the model "Virtual Machine", since Microsoft's own Surface devices share its manufacturer name), and in C++ the CPU's hypervisor bit. That bit is also set on physical Windows machines that run Hyper-V, Virtualization-based Security or Memory Integrity, WSL2 or Windows Sandbox, all common on Windows 11; the kit counts it only when Hyper-V does not report the root partition (CPUID leaf 0x40000003, the privilege to create partitions), which is what the physical machine sees.
  • What it finds is reported, never acted on locally: kind: "debugger" or kind: "vm" with a short detail, at most once per kind per session, through the SDK's request() on a live session, only when the server's signed init answer says reportTamperSignals: true (Build Integrity Verification on). No crash, no deleted file, no refusal on the machine.
  • Protect counts each report as T9 in the risk score (weight 4, at most 5 reports per session per hour); Graduated Enforcement (N4) decides what the combined score leads to.

What it does not do

  • These checks are easy to defeat. Anti-anti-debug plugins hide debuggers from exactly these calls, and a VM can be configured to report a real manufacturer. N6 catches casual analysis, not a careful cracker.
  • It does not detect hooks or injected code in this first round: reliable hook detection depends on the exact functions and builds and is left for later, with its own card.
  • It does not decide anything: without Graduated Enforcement the reports only add to a score and an incident.
  • It sends nothing when Build Integrity Verification is off (reportTamperSignals false), so the seller switches Build Integrity Verification on, Warn only at first, to receive these signals. Hints only tighten: a running copy that once received true keeps reporting until it restarts after Build Integrity Verification is turned off.
  • Its reports share the report path and the 5-per-hour allowance with N4 (see there), so patching that one call silences both.

False alarms

  • Many customers legitimately run in VMs (virtual desktops, Parallels on Mac, cloud workstations). A VM report is information, weighted low, never a block on its own: one signal type adds at most 5 times its weight, 20 for T9, below the lowest default Graduated Enforcement threshold (40). It does add to the combined score: non-persistent virtual desktops look like new machines and raise key sharing (T5, up to 75), cloud workstations can raise impossible travel (T6, up to 50); with the VM's 20 on top, such a license can cross the sign-out (60) or suspend (80) threshold under **Act on its own**. The guide tells sellers with VM customers to keep Graduated Enforcement on Alerts only or to leave VM reports off.
  • Developers of the software run it under a debugger all day: the kit's debug builds do not report.

Forensic Watermarking

Ready in C#, C++, Python, Node.js

The buyer's mark goes on the About text and every report; the mark leads back to the license and the machine

Server side
  • License watermarking (A8)
Check it yourself

Run about, paste the Licensed: mark into the search box of Licenses: it finds the license.

Checks in C# · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpassed

Checks in C++ · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpassed

Checks in Python · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpassed

Checks in Node.js · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpassed

Threat

A cracked copy, an exported file or a screenshot of a licensed feature turns up on a forum. The seller cannot tell which customer it came from, so they cannot act on that license or ask that customer what happened.

How it works

  • With Forensic Watermarking on, every successful sign-in returns a buyer mark in license.mark, shaped <10 hex>.<6 hex>: the first half is fixed per license (made on its first sign-in), the second half is derived from it and this machine's device key. Offline tokens carry the same mark in their signed wm field. (Until the fix that came with this card's review, a license's first sign-in could produce two different marks; the answer, the token and the stored value now agree.)
  • The kit puts the mark where a leak carries it: the sample app's About text and a footer line on every file its paid feature exports (a seller adds it to their own log headers and file formats the same way).
  • To trace a leaked file, the seller pastes the mark into the search box of Licenses, or sends it to the Seller API's POST /seller/v1/licenses/lookup as {"mark": …}, which answers the license and, when the second half matches a machine's current device key, that machine (markDevice).

What it does not do

  • A mark can be removed. Anyone who finds it in a file or in the executable's output can strip or replace it; it helps against careless sharing, not against a cracker who knows it is there.
  • It does not stop the leak; it tells the seller where to look afterwards.
  • A cracked executable carries no mark: the mark arrives at run time, after sign-in. N7 traces what a licensed copy produced (files, screenshots, logs), not a redistributed program.
  • A mark found in a file is not authenticated: the signature protects the server's answer, not the file. Anyone can copy a real customer's mark into a leaked file to frame them.
  • The second half follows the device key: a machine that signs in again with a new key, or a second user account on the same PC, gets another second half. The lookup still finds the license from the first half, but only the machine's current key names the machine; a seller who needs older marks keeps their own record, built from the signed offline token (lic, dev, wm) rather than from what the software sends, which can be forged.
  • The mark identifies a license and a machine, not a person: a key shared by its buyer points to the buyer, who may not be the one who leaked.

False alarms

None on the customer's side: the mark changes nothing in how the software runs. The risk is a wrong accusation (a copied or planted mark): the guide tells sellers to treat a mark as a lead to check with the customer, not as proof, and to say in their terms of sale that licensed copies carry a buyer mark (the kit does not write those terms).

Code Signing

Ready in C#, C++

Signs your release files with your certificate and a timestamp, so customers know the file is yours

Server side
Nothing to turn on
Check it yourself

After release/N8/sign.ps1, the file's Properties show a Digital Signatures tab with your name and a timestamp.

Checks in C# · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpassed

Checks in C++ · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpassed

Checks in Python · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypending
  • Catches the attackpending
  • No false alarmpending

Node.js: coming

Threat

Someone repackages the seller's software, cracked or with malware added, and passes it around under the seller's name. Customers cannot tell the real file from the fake one, and Windows warns about every unsigned download, which teaches customers to click through warnings.

How it works

  • The kit adds a signing step to the sample app's release script, run with the seller's own code-signing certificate:
  • Windows: signtool sign with SHA-256 and an RFC 3161 timestamp, then signtool verify /pa.
  • macOS: codesign with the hardened runtime, then notarization with notarytool and stapling (once the kit has a macOS sample).
  • The signed files are what gets uploaded to Releases, so N2 compares hashes of signed files.
  • ClavisAUH's own signed release manifests (CLV1-MANIFEST, used by the SDKs' update check) already let the software verify an update before installing it; N8 is about the first download and the file on disk.

What it does not do

  • It does not stop cracking: a cracked copy is simply unsigned or signed by someone else, and runs anyway if the customer accepts the warning.
  • It does not prove the software is safe, only who published it.
  • A stolen signing key signs malware validly under the seller's name. Revoking the certificate helps only where the operating system checks revocation; a hardware token or a cloud signing service makes theft harder, not impossible.
  • It does not remove SmartScreen warnings at once: Windows builds reputation per certificate (and per file) over time.
  • ClavisAUH does not sign for the seller and does not hold their certificate; the seller buys one (an organisation validated certificate, or an Apple Developer ID) and keeps its key, ideally on a hardware token or a cloud signing service, as certificate authorities now require.

False alarms

  • Antivirus can still flag a signed file, especially a packed one (N9); signing usually lowers that, it does not end it.
  • A file changed after signing (an installer that patches it, a resource editor) loses its signature: the release script signs last and verifies.
  • A missing timestamp makes every installed copy look unsigned the day the certificate expires: the script refuses to sign without one.

Code Obfuscation

Checks pending

Renames and hides the app's own code so reading it takes longer; the mapping stays with you for crash logs

Server side
Nothing to turn on
Check it yourself

Open publish/Notebook.dll in a .NET decompiler: the license code's names and sentences are no longer readable.

Checks in C# · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpending

Checks in C++ · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpending

Checks in Python · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpending

Checks in Node.js · run 2026-11-05 on Windows and Linux

  • Codepassed
  • Works normallypassed
  • Catches the attackpassed
  • No false alarmpending

Threat

The shipped program is easy to read: a .NET assembly decompiles back to near-original C#, Python and JavaScript ship as source or close to it, a native binary with symbols names its own functions. Finding the license check takes minutes.

How it works

The kit's release script runs one open-source tool for the language, configured not to break the SDK:

LanguageTool (license)What it does
C#Obfuscar (MIT)renames non-public types and members, hides strings
C++the compiler itselfrelease build, symbols stripped, hidden visibility, link-time optimisation
PythonNuitka (Apache 2.0)compiles the program to native code (standalone folder, not onefile), no .py files
Node.jsjavascript-obfuscator (BSD-2-Clause)renames, flattens control flow, encodes strings

The SDK's public names stay as they are (the tools are told to exclude them) so the program still links and its calls still match the protocol. The chosen tool and version are written into kit.lock.

What it does not do

  • It delays, it does not prevent. Every one of these outputs can be analysed with enough time; deobfuscators exist for common .NET and JavaScript obfuscators.
  • It does not protect secrets in the program: there should be none (T2), and obfuscated strings are recovered at run time anyway.
  • C++ gets no real obfuscation from open-source tools in this round: the open LLVM obfuscators are unmaintained or, like O-MVLL, aimed at ARM64 mobile targets, not desktop x86-64. Stripping and optimising remove names; **string literals stay readable** in the binary, and so do the names of classes with virtual functions (RTTI). Commercial packers (VMProtect, Themida and the like) go further; the guide describes how to wrap the code around the SDK calls and their drawbacks , without recommending one.
  • Nuitka hides the Python source, not names or strings: function, class, module and attribute names stay readable because Python needs them at run time, and its free edition leaves constants readable (string encryption is in the commercial edition).
  • javascript-obfuscator renames locals, not properties: method names and the names the files import from each other stay readable, because renaming them would break the calls between files and into the SDK.
  • It does not hide network traffic (N1) or stop patching once the check is found (N3, N4).

False alarms

  • Antivirus: packed and heavily obfuscated executables are flagged more often. The kit does not pack: Nuitka runs in standalone mode (a folder), not onefile mode, which unpacks itself at start like a packer and is flagged more. Signing (N8) and submitting false positives to vendors is the guide's advice.
  • Broken behaviour: renaming breaks code that uses reflection, serialisation by member name or dynamic imports. The kit's configuration excludes the SDK and the sample's serialised types; the seller must add their own, and the positive gate runs the obfuscated build, not the plain one.
  • Crash reports and logs carry renamed symbols; the tools' mapping files must be kept per release to read them.

Hardware-Bound Device Key

Not in kits yet

Not possible yet: needs P-256 device keys in the protocol and hardware attestation

Server side
Nothing to turn on
Check it yourself

Not in kits yet.

Threat

The device key (T3) is a file. Someone who copies the state folder and the hardware identifiers of a licensed machine can make a second machine look like the first, and a license meant for one machine runs on many.

How it works

  • The device key is created inside the machine's TPM (Windows) or Secure Enclave (macOS) and can sign but never be exported. Copying the state folder copies a reference to a key that only exists in that chip.
  • The SDK says so with device.hardwareKey: true, and a server policy Require a hardware key refuses devices without one (with a fallback the seller chooses for machines that have no chip).

What it does not do

  • It cannot be built on today's protocol. TPMs in the field and the Secure Enclave sign with ECDSA P-256 (TPMs also RSA), not Ed25519, while CLV1.1 device signatures are Ed25519 only. N10 needs a protocol version that accepts P-256 device keys, on the server and in the native SDKs.
  • A self-reported flag proves nothing. Without attestation (a TPM key attestation, Apple's App Attest), a patched client can claim hardwareKey: true for a file key. A real check needs the server to verify an attestation, which is a separate project.
  • It does not stop a cracked executable that skips the license check, nor a customer running the software for others (N3, N4, Protect).
  • Many machines have no usable chip (older PCs, most virtual machines, Linux without a TPM 2.0 stack), so a strict policy locks real customers out unless a fallback exists.

False alarms

  • Machines without a TPM, with the TPM disabled in firmware, or in VMs without a virtual TPM: they need the fallback.
  • A TPM cleared during a firmware update or a Windows reset loses the key: the machine has to be reset by the seller or the customer, like a new machine.

No measure stops everything: each one's limits are in its section above and in HOW-IT-WORKS.md in your kit. What the measures report shows on Protection; the server side settings are explained in the two protection modes

How can we help?

Frequently asked

What is ClavisAUH?

A licensing and authentication service for desktop software: license keys, device binding, end-user sign-in and signed responses. All run from one dashboard

Why can't a cracker fake your server?

The SDK only trusts answers signed with your app's private key. That key never leaves our servers, so a fake server can't sign and gets rejected

What ends up in my binary?

Your app id and public key. Nothing secret, because each device makes its own key pair on first run

Does it work offline?

Yes. Every plan gets a signed offline token for 7 days, and your app checks it locally. Premium lets you choose how long. A revoked license can keep working offline for that time, unless Online-Only Mode is on

Which languages are supported?

JavaScript and TypeScript today. C++, C# and Python are next, on the same open protocol

Is Basic really free?

Yes. No time limit, no card. Move to Premium when you need more

Get in touch