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 (Homebrew / cargo / direct, once released) brew install veilcc/tap/veil # sign in from the browser (people), stores a token locally veil login veil whoami # 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
Protect one compiled object (sealing) or one LLVM IR module (obfuscation) and write the artifact. Your link step is unchanged.
veil protect [FLAGS] <input.o | module.ll> -o <output> # seal a C/C++ object (functions auto-discovered from veil:seal) veil protect --level seal app.o -o app.sealed.o # seal by symbol + signature (Rust / any C-ABI object) veil protect --level seal --symbols check_license \ --proto "uint32_t check_license(const unsigned char*, size_t)" \ liblicense.o -o license.sealed.o # obfuscate IR with a fixed, reproducible seed veil protect --level obfuscate --seed 0x9f3ac1… module.ll -o module.o # device-bound: the key is unwrapped at runtime, never in the file veil protect --level device-bound --key-provider veil_key \ attest.o -o attest.sealed.o --receipt build-receipt.json
Flags
| Flag | Applies to | Description |
|---|---|---|
--level | all | obfuscate · seal · device-bound. Default seal. |
--proto repeatable | seal | The sealed function's C signature, e.g. "uint32_t check_license(const char*)". Omit for a C source object where veil:seal functions are auto-discovered. |
--symbols | all | Comma-separated symbol names to protect when the source carries no annotation (Rust, IL2CPP, Swift). |
--target | all | 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 | Fix the moving-target seed for a reproducible build. Blank rotates it every build. |
-o, --out | all | Path for the protected artifact (a drop-in object). |
--receipt | all | Write the signed 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. |
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
Every protected build carries a signed receipt: what was sealed, the seed, and a cost-to-break estimate. Verify one offline, or in CI, against the artifact it describes.
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) to seal, or the IR (.ll) to obfuscate. Max 24 MB. |
level | 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 "level=seal" \ -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": {
"sealed_symbol": "check_license",
"level": "seal",
"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). |
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 quota reached for the workspace's plan. |
502 | Protection runner unreachable. |