Integrate in a coffee break.
Mark the one function that matters, pick how hard it should be to recover, and build. For per-language walkthroughs see Languages; for every flag and endpoint, CLI & API.
Quickstart
Protect a binary from the CLI. The pass runs server-side against your API key, so your protection IP never ships inside the artifact.
# 1. build your object as usual clang -O2 -c app.c -o app.o # 2. seal the marked functions (key from your dashboard) veil protect --level seal app.o -o app.sealed.o # 3. link exactly as before, nothing else changes clang -O2 app.sealed.o -o app
The output is a drop-in replacement object. Your link step, your flags, your toolchain, untouched.
Hand it to an AI agent
Don't want to wire it yourself? Let your coding agent — Claude Code, Codex, Cursor — do it. It will find the functions worth protecting, mark them, and add the protect + verify steps to your build and CI, pausing for you to confirm which functions get sealed.
Install the skill (recommended)
We publish an open-source agent skill on skills.sh. Install it once and your agent knows how to use VeilCC in every session — no prompt to paste.
npx skills add almata-ai/veilcc-skill@veil-protect
Then just tell your agent "protect my license check with VeilCC". Source: github.com/almata-ai/veilcc-skill.
Or paste a one-off prompt
No skills runtime? Copy this self-contained prompt into any agent instead — it carries the same instructions.
Add VeilCC binary protection to this project. VeilCC seals or obfuscates the
compiled artifact server-side, so the functions I care about can't be read out of
the shipped binary. The protected object is a drop-in replacement — the link step
does not otherwise change. Docs: https://veilcc.com/docs
Do this, and pause for my confirmation where noted:
1. Install the CLI (pick one):
brew install almata-ai/tap/veil
go install github.com/almata-ai/veil-cli@latest
2. Identify the 1-3 "crown jewel" functions that must not be reverse-engineered
(license/entitlement checks, attestation, key derivation, a proprietary
algorithm). List them with file:line and PAUSE for me to confirm before
editing any code.
3. Mark the confirmed functions for sealing:
C/C++: add __attribute__((annotate("veil:seal"))) above each one
Rust: expose with the C ABI (#[no_mangle] pub extern "C" fn ...) and seal by symbol
Swift: @_cdecl("symbol_name")
If the source can't be annotated, plan to pass --symbols name1,name2 at build time
(the sealer works on the compiled object, so it is language-agnostic).
4. Insert a protect step in the build AFTER compiling to an object (.o) or LLVM IR
(.ll) and BEFORE linking:
veil protect --level seal <input.o> -o <input.sealed.o> --receipt build-receipt.json
then link the .sealed.o in place of the original. Keep every other flag identical.
Levels: obfuscate (IR, moving target) | seal (encrypt-at-rest, default) | device-bound.
5. Wire CI. In the release workflow add:
- uses: almata-ai/veilcc-protect@v1
with:
level: seal
api-key: ${{ secrets.VEILCC_KEY }}
Tell me to add the VEILCC_KEY repo secret (from veilcc.com -> console -> API keys).
Never commit the key.
6. Prove it in CI: run
veil verify build-receipt.json --artifact <input.sealed.o>
so every build carries a signed receipt of what was sealed.
Constraints: only seal functions I confirm; everything else stays native (no runtime
cost). Do not change my toolchain, flags, or link order beyond swapping in the sealed
object. Show me the diff before applying it. Either way the agent only changes code you approve; the sealed object is a drop-in, so nothing else in your build moves.
Mark the crown jewel
VeilCC protects the functions you point it at, a license check, an attestation routine, a proprietary kernel, and leaves everything else native. Mark them in source, or name them at build time.
__attribute__((annotate("veil:seal")))
uint32_t check_license(const char *key) {
/* … */
} // Rust, export with the C ABI
#[no_mangle]
pub extern "C" fn check_license(/* … */) -> u32 { … }
// Swift
@_cdecl("check_license")
func checkLicense(/* … */) -> UInt32 { … } No annotation in source? Pass a symbol list at build time, the sealer works on the compiled object, so it's language-agnostic.
Protection, and how it composes
Obfuscate and seal are independent and stackable — do either, or both. Device-bound is not a third level; it's a key-mode of sealing. Hot paths are refused for sealing automatically.
- obfuscate
- Across the whole module: mixed-boolean arithmetic + opaque-predicate / honeypot decoys that mislead AI decompilers, with a fresh moving-target seed every build (the default, lighter set).
--obfuscate-maxalso adds control-flow flattening — strongest, but heavier. The deterrence layer: it resists AI reading, it doesn't stop an attacker who runs the code (that's seal). A build-time transform — it needs your source / LLVM IR (the CLI and Action emit IR for you). - seal
- The annotated function ships as ciphertext (ChaCha20) and is decrypted into a W^X page only at call time, then scrubbed. A decompiler finds data, not logic. Works on a compiled object. Right for cold, high-value functions.
- post-compile hardening
- After compilation and optional sealing, strip debug metadata and randomize retained private names. Code/data, exports/imports and post-strip relocations are verified before delivery. This layer removes analysis hints; it does not rewrite instructions or measure resistance to AI analysis. Enable
--post-obfuscatein the CLI or the matching web option before final linking. - seal, device-bound key
- Sealing where the key is never in the binary — it's unwrapped at runtime by the device's secure enclave (or TPM), provisioned on first run. A copied binary cannot decrypt. The near-zero tier for extraction and off-device analysis. (The alternative key-mode, embedded, carries the key in the loader and scrubs it per call.)
The strong production combo is obfuscate + seal: harden the whole module and remove the crown jewel from the file. Obfuscation is a build-time IR transform, so feed it source (.c/.cpp, compiled on the runner) or LLVM IR (.ll/.bc) — via the CLI or the Action. The web console seals a selected function and can apply IR obfuscation to that exact function before sealing. A pre-compiled object has no IR to transform, so it can be sealed but not obfuscated.
Make the dump expensive
Sealing removes your logic from the file, so static analysis and an agent that just reads the binary come up empty. The one attack left is a live memory dump, catching the function decrypted in RAM during the microseconds it runs. You can't make that physically impossible in software, but you decide how expensive it is to reach, and you decide it in where and when you call the sealed function. Design for the dump and recovery stops being worth anyone's time.
Three dials, all yours in code:
- How late. Don't call the sealed function at startup, where a dump is a one-second job. Gate it behind deep runtime state. In a game, invoke it after ~15 hours of real play, or on the final boss, or deep in progression. To recover it an attacker now has to play the whole game AND catch the dump at that instant, and if it's a long game, they won't.
- How many. Stage several seals at different points in the timeline, an endgame routine, a late-tier unlock, a prestige path. One dump only catches the one function running right then, so each additional seal forces another full playthrough and another perfectly-timed dump.
- How rare. Each call decrypts, runs, and scrubs (the default, ephemeral), so the plaintext window is microseconds. Call it seldom and keep it ephemeral; only reach for
--persistenton a function that is genuinely hot and secret, and know it widens the window.
// BAD: runs at launch — an attacker dumps it in the first second.
// entitlement = check_license(key); // seal this and the window is wide open
// GOOD: the sealed call only fires deep in a long game.
__attribute__((annotate("veil:seal")))
uint64_t derive_endgame_cipher(const PlayerState *p); // ciphertext in the file
void on_final_boss_defeated(PlayerState *p) {
if (p->playtime_seconds < 15 * 3600) return; // ~15h of real play to get here
p->world_key = derive_endgame_cipher(p); // decrypt · run · scrub, once
} Same idea outside games: seal the entitlement re-check that runs days into a subscription, a rarely-hit premium codepath, or a server-authority routine, not the first-launch check everyone can trip on demand.
Two multipliers worth stacking on top:
--anti-debugfolds a debugger-present check into the key, so a traced process, the usual way people catch the dump, decrypts to garbage instead of your logic.device-boundif the real fear is redistribution: even a successful dump-and-patch produces a build that won't decrypt on anyone else's machine, so a crack doesn't travel.
None of this makes a dump impossible, and we won't pretend otherwise (see What it does & doesn't). It makes reaching the one window cost more than the thing you're protecting is worth, which is the number the build receipt puts in writing.
GitHub Action
Two lines in your release workflow. The Action uploads the object to the Protection API, runs the passes with a per-build seed, and returns the protected artifact with a signed receipt.
- uses: almata-ai/veilcc-protect@v1
with:
level: seal # obfuscate | seal | device-bound
symbols: check_license,attest_device
api-key: ${{ secrets.VEILCC_KEY }}
# → writes the protected object + build-receipt.json On-prem customers point api-url at their own Protection API; the object never leaves the network.
How protection runs
The passes and the sealer run server-side, so your protection IP never ships inside the artifact. The CLI, the GitHub Action and the console are thin clients of one Protection API.
client (CLI · Action · console)
└─ upload object + options ─────────────▶ API gateway (Cloudflare Worker)
· authenticate key, enforce quota
· validate, store, meter
└─ run ──▶ protection runner (container)
· LLVM toolchain + veil-seal
· seals / obfuscates, returns
◀─────────── protected object + signed receipt ◀──────────┘ A Worker can't run the native toolchain itself, so protection runs in a container the Worker calls (Cloudflare Containers, or an external runner). The sealing algorithm stays in that image, never on the client.
Regulated and air-gapped teams run the identical runner on-prem and point the Action's api-url at it, so objects never leave the network.
Supported targets
Anything that lowers through LLVM. Verified end-to-end in our test suite today:
language C C++ Rust Swift verified in CI os macOS Linux Windows verified in CI arch arm64 x86-64 verified in CI expected Zig · TinyGo · Unity IL2CPP same object path
We list a language as shipped only once the suite proves it end-to-end, not before.
What it does & doesn't
- Defeats static analysis, sandboxed and automated AI reversing, off-device key recovery, naive patching, and cross-release crack reuse.
- Raises a determined local attacker from minutes to measured hours or days, with a per-build cost-to-break number you can verify.
- Does not make locally-run code mathematically unrecoverable: someone who executes your binary and dumps RAM during a call can still recover a sealed function. No pure-software scheme escapes that. For near-zero against a copy, use device-bound keys.
Security & privacy
The passes run on compiled objects, never source, and on the hosted API they run in memory with zero retention, we keep the build receipt, not your code. Regulated, air-gapped and defense teams run the identical Protection API on-prem. Only scan or protect binaries you own or are authorized to test.