Security & threat model
What the design defends against, what it deliberately does not, and where the real boundaries are. Written to be shown to a customer's security team.
What leaves the camera#
| Data | Leaves? |
|---|---|
| Frames, crops, thumbnails, any imagery | Never |
| Detections, boxes, tracks | Never |
| Event contents | Never |
| Licence activation: device fingerprint, batch token, SDK version | Once, at first boot, if online |
| Heartbeat: counts of inferences, frames, stream-seconds, events, per module | Periodically, if online |
That is the complete list. Inference runs on the unit; there is no upload path for image data because there is no code that could use one.
With cfg.offline = 1 the SDK never opens a socket at all, and building with -DXCAICX_WITH_TLS=OFF removes the HTTP client from the binary entirely. For a site that wants to verify rather than trust that, the second option is the one to offer: a binary with no network stack linked in cannot exfiltrate anything, and that is checkable with ldd and strings.
The licensing threat model#
What it stops#
Verified by the 45 checks in core/tests/test_license.cpp:
| Attack | Result |
|---|---|
| Flip a byte in the payload | BadSignature |
| Re-encode payload with a module enabled, keep the signature | BadSignature |
| Copy a licence to another camera | WrongDevice |
| Wind the clock back to dodge expiry | ClockRollback |
| Use a token past its term and grace | Expired |
| Use a revoked licence | Revoked |
| Forge a batch key with your own Ed25519 key | 403 at activation |
| Present a validly-signed key never issued by this authority | 403 — no seat behind it |
| Replay a usage record to avoid billing | Deduped server-side |
| Reboot to reset the metered counter | Counters resume from disk |
You can exercise every row yourself in the token lab, which runs real Ed25519 against a throwaway keypair generated in your own browser.
What it does not stop#
Do not
Offline licensing is deterrence, not DRM. An attacker with root on the camera and a disassembler can patch out the check. That is true of every software licensing scheme that runs on hardware the attacker controls, and any vendor who tells you otherwise is selling you something.
What the design actually buys:
- Casual copying is dead. Device binding plus signatures stop the realistic threat: an integrator cloning one paid image onto fifty cameras.
- Entitlements cannot be widened. Enabling a module you did not buy requires forging Ed25519, not editing a config file.
- Seat accounting is server-side. Unit counts cannot be inflated on-device.
- Tamper leaves evidence. Weak fingerprint sources are recorded per activation and surface in
xcaicxctl fleet.
Escalating#
If the threat model demands more, the escalation is hardware, and the SDK already supports it as a deployment decision rather than a code change:
- Bind
XCAICX_DEVICE_IDto a secure element, TPM or fused SoC serial. - Have the bootloader verify SDK integrity as part of a signed boot chain.
- Keep the rootfs read-only with
state_diron the only writable partition.
Cryptography#
| Property | Choice |
|---|---|
| Signature | Ed25519 |
| Token format | XCAICX1.<base64url(payload)>.<base64url(sig)> |
| Signed bytes | The ASCII of XCAICX1.<base64url(payload)> — the encoded payload |
| Verification | OpenSSL libcrypto on the device; public key compiled in |
| Key custody | Private key server-side or in an HSM, never on a camera |
Signing the encoded payload rather than re-serialised JSON means verification never depends on signer and verifier agreeing on key order or whitespace. The C++ verifier and the Python signer are independent implementations tested against each other's output, precisely because that drift is what breaks licensing systems in practice.
Server surface#
- Interactive API docs are disabled in production. They grant no access on their own, but publishing the whole admin surface tells an attacker exactly what to aim at.
- Admin key comparison is constant-time. A naive
==leaks the prefix to a patient attacker. - The service refuses to boot without an admin key, rather than defaulting to an open admin surface.
/v1/usagetrusts the token, not the body, for whose usage a record belongs to. Trustingbody.license_idwould let any device attribute usage to another account./v1/activateis rate-limited at the edge, since it is the one unauthenticated endpoint that mints credentials.- The service index carries no repository link and no admin endpoint listing.
Data at rest#
On the camera#
state_dir, mode 700, holds the unit token, usage counters, the spool, the cached revocation list and the clock high-water mark. The token is a credential: anyone who copies it gets a licence bound to that fingerprint, which is why binding matters more than file permissions.
Writes are tmp-file + fsync + rename, so power loss cannot corrupt state.
On the server#
The database holds customers, batches, licences (including live tokens), activations and usage. It is both a billing record and a customer list. Encrypt the volume, restrict access to the one process, and keep backups out of version control.
Reporting a vulnerability#
Email [email protected] with enough detail to reproduce. We will confirm receipt, tell you what we found, and tell you when a fix ships. If a fix requires firmware — as a signing-key compromise would — we will say so plainly rather than describing it as a configuration change.
Please do not test against production licensing infrastructure or against other people's cameras. If you need an environment to test against, ask and we will provide one.
For a customer's security review#
The questions industrial security teams actually ask, with the short answers:
| Question | Answer |
|---|---|
| Does video leave the site? | No. Inference is on-device; only counts are reported, and only if online. |
| Can it run with no network at all? | Yes — offline = 1, and TLS can be compiled out entirely. |
| What does the heartbeat contain? | Counts of inferences, frames, stream-seconds and events. No imagery. |
| What if your licensing server is unreachable? | Cameras keep working. Usage spools locally and ships when the link returns. |
| What if your company disappears? | Perpetual tokens keep verifying — the public key is compiled into the firmware and needs no server. Term licences stop at expiry plus grace. |
| Can you remotely disable our cameras? | Revocation stops analytics on an online camera at its next start. It never stops the video path. An offline camera is unaffected. |
| What is stored on the camera? | A licence token, usage counters and a revocation cache, in state_dir. |
The fifth and sixth rows are the ones that decide industrial deals, and the honest answer is better than the comfortable one: revocation is real, it is scoped to analytics, and it cannot reach a camera that is not online.
Next#
- Licensing model — the enforcement in detail
- Running the authority — key custody and backups
- Air-gapped deployment — the no-network path