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.

terminal
# 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.

terminal
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.

prompt for your agent
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.

C / C++
__attribute__((annotate("veil:seal")))
uint32_t check_license(const char *key) {
    /* … */
}
Rust · Swift
// 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-max also 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-obfuscate in 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 --persistent on a function that is genuinely hot and secret, and know it widens the window.
game.c, seal gated behind progression
// 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-debug folds 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-bound if 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.

.github/workflows/release.yml
- 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.

request path
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:

verified matrix
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.