CLI & API reference.
The veil CLI and the GitHub Action are open source and are thin clients of one Protection API. The protection itself runs server-side, so the algorithm never ships in your binary. Everything the CLI does, you can do over HTTP.
Install & auth
# install (macOS / Linux via Homebrew, or Go) brew install almata-ai/tap/veil # or: go install github.com/almata-ai/veil-cli@latest # sign in from the browser (people), stores a token locally veil login veil whoami # exposure scan — free, unmetered (what a decompiler + AI read first) veil scan app.o # or use an API key (CI) export VEILCC_KEY="veil_sk_live_…" # or pass --api-key
veil login opens the browser, you authorize the CLI, and a token is saved to ~/.config/veilcc. For CI, create a key in the console → API keys (shown once). Every command resolves auth in order: --api-key → VEILCC_KEY → the stored login.
veil protect
Protection is composable: obfuscate source or LLVM IR, seal a selected function, then optionally strip debug information and discardable local symbols. Sealing accepts a crown-only object or source file: keep other exported functions and global data in separate host objects. Stacking obfuscation and sealing needs source/IR. Link the returned object into your build.
veil protect [FLAGS] <input.o | module.ll> -o <output> # seal a crown-only C/C++ object veil protect --seal crown.o --proto "uint32_t check_license(const char*)" -o crown.sealed.o # seal by symbol + signature (Rust / any C-ABI object) veil protect --seal --symbols check_license \ --proto "uint32_t check_license(const unsigned char*, size_t)" \ liblicense.o -o license.sealed.o # obfuscate, seal, then harden source with one veil:seal annotation veil protect crown.c --obfuscate --seal --post-obfuscate -o crown.o # strip an existing object without sealing veil protect --strip app.o -o app.stripped.o # seal with a device-bound key, unwrapped at runtime, never in the file veil protect --seal --seal-key device-bound --key-provider veil_key \ attest.o --proto "uint32_t attest(const char*)" -o attest.sealed.o --receipt build-receipt.json
Flags
| Flag | Applies to | Description |
|---|---|---|
--obfuscate | obfuscate | Obfuscate the whole module on source / IR. Default set: mixed-boolean arithmetic + opaque-predicate decoys (lighter), reseeded every build. |
--obfuscate-max | obfuscate | Also apply control-flow flattening to every function. Strongest, but it bloats the binary and adds runtime cost. Implies --obfuscate. |
--seal | seal | Seal the annotated functions — ships as ciphertext, decrypts per call. Works on a compiled object. |
--post-obfuscate | all | Strip metadata and randomize retained private symbol names after compilation. Verify code/data contents, exports/imports and post-strip relocations; reject incompatible results. Does not rewrite instructions or pack executables. |
--scope sealed | seal | Restrict optional IR obfuscation to the exact C symbol selected for sealing. The web app uses this scope; CLI defaults to whole-module IR protection. |
--strip | all | Remove debug information and discardable local symbols after obfuscation/sealing, preserving exports and relocation-required symbols. Also works alone on compiled objects. Removes analysis metadata; does not encrypt code or measure resistance to reverse engineering. |
--seal-key | seal | embedded (default) · device-bound (Enterprise only; key from the enclave/TPM, never in the file). |
--level | legacy | Back-compat alias: obfuscate · seal · device-bound → maps to the flags above. |
--proto | seal | The sealed function's C signature, e.g. "uint32_t check_license(const char*)". Optional for source with exactly one veil:seal annotation; required for objects and IR. Use one function per request. |
--symbols | both | Comma-separated symbol names to protect when the source carries no annotation (Rust, IL2CPP, Swift). |
--target | both | Output triple. Default auto-detect from the object. See Targets. |
--persistent | seal | Decrypt once and cache, for very-hot sealed functions. Default is ephemeral: decrypt per call, then scrub and unmap, so plaintext never persists. |
--anti-debug | seal | Fold a debugger-present check into the key, so a traced process decrypts to garbage. A cost layer, patchable; off by default (can false-trigger under some sandboxes). |
--key-provider SYM | device-bound | Root the key outside the binary: the loader calls int SYM(unsigned char key[32]) at runtime. Back it with a secure-enclave/TPM unwrap. Returns a one-time provision key in the receipt. |
--seed 0x… | obfuscate/post-compile | Fix the seed for IR transformations and private-name randomization. Blank rotates it; sealing still uses fresh encryption randomness. |
-o, --out | all | Path for the protected artifact (a drop-in object). |
--receipt | all | Write the build receipt JSON here. |
--api-key | all | Overrides VEILCC_KEY. |
--api-url | all | Point at your own Protection API for on-prem / air-gapped builds. Default https://veilcc.com. |
Post-compile hardening runs before final linking/signing. Supported objects: 64-bit little-endian Mach-O and ELF on arm64/x86-64, and standard COFF x86-64. Linked binaries, archives, embedded LTO and extended COFF relocations are rejected. Public names, section names and arbitrary strings remain; builds that inspect private symbol names should leave this option off. Receipts report actual renamed-local counts, including zero when stripping removed every eligible local. Metadata hardening is an additional layer, not evidence of immunity to AI-assisted analysis.
Targets
| Triple | Platform |
|---|---|
auto-detect | Infer from the object (default) |
arm64-macos | macOS, Apple silicon |
x86_64-macos | macOS, Intel |
x86_64-linux | Linux, x86-64 |
aarch64-linux | Linux, arm64 |
x86_64-windows | Windows, x86-64 |
veil verify
The receipt records the protection stages, seed, and a shortened SHA-256 hash of the final artifact after stripping. Verify the artifact hash offline or in CI. The current verifier checks this hash; it does not authenticate a cryptographic signature or measure cost to break.
veil verify build-receipt.json --artifact app.sealed.o # → receipt hash matches · sealed: check_license · level seal · ok
Protection API
The CLI and Action are clients of this one endpoint. Authenticate with a Bearer key; upload the object and options as multipart form data; receive the protected artifact (base64) and a receipt. Quota is per workspace per month.
Headers: Authorization: Bearer veil_sk_live_…
| Form field | Value |
|---|---|
binary required | The object (.o) or source to seal, or source / IR (.c/.cpp/.ll) to obfuscate. Max 24 MB. |
obfuscate | 1 to obfuscate the whole module (source or IR). |
seal | 1 to seal the annotated functions. |
strip | 1 to remove debug information and discardable local symbols from the final object. Can be combined with obfuscation/sealing or used alone on an object. |
seal_key | embedded (default) · device-bound. |
level | Legacy alias for the above: obfuscate · seal · device-bound. |
proto | Seal: the C signature of the sealed function. |
symbols | Comma-separated symbol names (unannotated sources). |
target | One of the target triples. |
persistent | 1 to cache the decrypted function (seal). |
anti_debug | 1 to fold a debugger check into the key (seal). |
key_provider | Runtime key-unwrap symbol (device-bound). |
seed | 0x… fixed build seed (obfuscate). |
Request
curl https://veilcc.com/api/protect \ -H "Authorization: Bearer $VEILCC_KEY" \ -F "binary=@license.o" \ -F "seal=1" \ -F "proto=uint32_t check_license(const char*)" \ -F "anti_debug=1" \ -o response.json
Response
{
"ok": true,
"artifact_b64": "f0VMRgIBAQ…", // the protected object, base64
"receipt": {
"protect": "seal",
"obfuscate": "off",
"seal": "on",
"sealed_symbol": "check_license",
"target": "arm64-macos",
"plaintext_in_file": "none",
"hot_paths": "left native",
"key_cache": "ephemeral (scrubbed per call)",
"anti_debug": "on (traced process decrypts to garbage)",
"seed": "0x9f3ac1…",
"receipt_hash": "b7a4…e39c"
},
"quota": { "used": 42, "limit": 500 }
} Decode artifact_b64 to the output path and link it as normal. On-prem customers run the identical API inside their network and set --api-url / the Action's api-url accordingly; the object never leaves.
Errors
| Status | Meaning |
|---|---|
400 | Bad request, e.g. sealing without a proto, or an unknown target. |
401 | Missing or invalid API key (or a revoked key). |
403 | The selected feature needs another tier. Device-bound sealing requires Enterprise. The response includes code=plan_required, required_plan, upgrade_url, and action_label. |
413 | Object exceeds the 24 MB cap. |
422 | The toolchain rejected the input, e.g. the named symbol isn't in the object. The error carries the runner's message. |
429 | Monthly allowance is used or reserved by running jobs. Includes code=quota_exceeded, usage, resets_at, an upgrade/sales action, and a Retry-After header. Free: 1 build/month; Pro: 500; Enterprise: 1,000,000. |
502 | Protection runner unreachable. |
503 | Runner configuration or build accounting is unavailable. No protected artifact is delivered. |