Skip to main content

ITAR / CMMC Mode

O.D.I.N. ships with a dedicated fail-closed air-gap mode for deployments that cannot leak data to public addresses — ITAR-regulated defense shops, CMMC L2 contractors, aerospace subs, and anyone else running on a restricted network.

Enable it by setting one environment variable:

services:
odin:
environment:
- ODIN_ITAR_MODE=1

That's it. The mode is either on or off — there is no "warn" state. A misconfigured ITAR container refuses to boot.


What It Enforces

Boot audit (fail at startup)

At container start, O.D.I.N. resolves every configured outbound destination and refuses to boot if any one of them resolves to a public address:

  • license_server_url, update_server_url (settings)
  • Every row in the webhooks table (decrypted)
  • smtp_config host (from system_config)
  • mqtt_republish_host (from system_config)
  • oidc_config.discovery_url (if OIDC is enabled)
  • spoolman_url (from settings)
  • Per-org webhook URLs in groups.settings_json

A public destination prints a clear operator message and the container exits with a non-zero status. Fix the config or unset ODIN_ITAR_MODE.

Runtime guards (fail at connect)

Boot-time DNS can drift after startup (split-horizon DNS, resolver rotation, poisoned cache). Every outbound HTTP/SMTP/MQTT call re-validates the destination before the socket connect:

  • HTTPsafe_post, license server, OIDC discovery + token + userinfo + JWKS, Spoolman, printer adapters (Moonraker, PrusaLink, smart plug) all route through pin_for_request(url). Resolver output is validated (all addresses private) and the connection is DNS-pinned to those IPs so the socket cannot be redirected. trust_env=False blocks HTTP(S)_PROXY env vars from routing around the pin.
  • SMTP — every smtplib.SMTP() call in alert_dispatcher, channels, org password-reset, user-invite, and scheduled-digest paths is host-validated before connect.
  • MQTT — the external-broker republisher validates the host on every (re)connect and on the admin "Test Connection" button.

Hard-disabled subsystems

Some transports are fundamentally public — no amount of DNS pinning makes them compliant. Under ITAR, these are refused outright:

  • APNs (Apple Push) — api.push.apple.com. The provider reports as "not configured" under ITAR regardless of credentials.
  • Web Pushpywebpush uses requests internally and cannot be DNS-pinned. The send is refused even for privately-resolved subscription endpoints (the library's transport bypasses our pin).

If you need mobile / browser push in an ITAR deployment, route through an internal push gateway and configure it as a standard user webhook — safe_post validates + pins those.

Context-safe under async load

The DNS pin uses contextvars.ContextVar so concurrent async requests on one event loop cannot overwrite each other's pin between validation and connect. Propagation is verified against asyncio.to_thread and anyio.to_thread.run_sync (the primitives httpx uses under the hood).


Agent-Native Under ITAR

ITAR mode and the agent-native surface are designed together. The typical ITAR agent stack:

  1. O.D.I.N. backendODIN_ITAR_MODE=1.
  2. Ollama running locally — Qwen2.5-32B-Instruct, Llama 3.3 70B, or similar. No API calls leave the box.
  3. odin-print-farm-mcp@2 — stdio MCP server, same host as the agent.
  4. Agent client (Claude Desktop / Claude Code with a local model backend, OpenClaw, etc.).

Every step stays inside the compliance boundary. The agent issues tool calls → MCP client talks to O.D.I.N. over http://localhost:8000 → O.D.I.N. talks to printers on the LAN. Zero outbound packets.


What Still Works Under ITAR

Everything in the core operator surface:

  • Printer control (Bambu, Moonraker, PrusaLink, smart plug) — all via LAN.
  • Job queue, scheduling, orders, approvals.
  • Filament inventory, spool tracking, maintenance logs.
  • Alerts, quiet-hours digests (to internal SMTP).
  • Vision AI, timelapses, print archive.
  • Multi-org, RBAC, MFA, OIDC (with an internal IdP like ADFS or Authentik).
  • Agent-native MCP surface with idempotent retries and dry-run previews.

ITAR only blocks calls that would leave your network.


What's Disabled Under ITAR

FeatureReason
Apple Push (APNs)api.push.apple.com is public.
Browser Web Pushpywebpush uses vendor push endpoints (FCM, Mozilla, etc.) and cannot be pinned.
Public OIDC providersRefuses boot if discovery_url resolves public. Use an internal IdP.
Public SMTP relay (Gmail, SendGrid, Mailgun, etc.)Refuses boot if smtp_config.host resolves public. Use internal SMTP.
Public webhooksRefuses boot if any webhook URL resolves public.
Public license serverRefuses boot if license_server_url resolves public. Use locally-signed licenses.

The agent-native push alternative: wire an internal push gateway (gotify, ntfy, your own) and configure it as a standard webhook.


Verifying a Clean Deployment

Log line on successful boot:

ODIN_ITAR_MODE=1 boot audit passed — all configured URLs are private.

Log line on a blocked runtime egress attempt:

smart_plug: ITAR blocked outbound to http://public.example.com/: ODIN_ITAR_MODE=1 refused outbound request — public.example.com resolves to a public address.

enforce_boot_config raises RuntimeError with a list of every violating URL on boot failure — there is no silent degradation.


See Also