Skip to content

Security

The current system authenticates boot executables, immutable content and release metadata through three separate trust domains. Development images use explicit test keys. Hardware-rooted boot and encrypted DATA are not claimed.

Native init authenticates the selected deployment and the kernel requires signed verity hashes for both root and support. It verifies blocks as they are read; latent unread corruption is detected on access. Matching modules are mounted read-only before services. The root and /var parent skeleton stay read-only; only explicitly allowed leaves use DATA or bounded volatile storage.

UEFI Secure Boot or required FIT signatures protect the kernel and early policy when the corresponding enforcing firmware and anchor are trusted. This is separate from authenticating every first mutable hardware boot stage.

status: shipped — evidence: mica-deploy:src/bin/mica-init.rs, docs/design/ro-root.md, docs/design/release-signing.md

The updater checks signed deployment/catalog metadata, board identity and exact component bytes before staging. It preserves current and retained fallback objects, publishes a candidate only after durable writes, and confirms through the health gate. Loader firmware is a separate signed maintenance artifact. Catalog expiry gates acquisition; it does not expire installed offline boot.

Metadata anchors are embedded in authenticated kernel policy. Editable update source/channel settings cannot replace them. Content-anchor overlap/removal is a kernel-package change; boot-key rotation changes the boot trust/firmware domain. Each rotation must leave a usable retained association. There is no historical format reader or old-layout migration.

GET /api/v1/system/info reports the baked development marker and named domains. It is provenance information, not proof of active firmware enforcement. The update status reports the boot/content verification receipt separately. Key creation never happens implicitly during a build; supply inputs explicitly.

status: shipped — evidence: mica-deploy:src/acquisition.rs, mica-deploy:src/deployments.rs, docs/design/release-signing.md

  • Per-device credentials, minted on the device. Nothing secret is baked into an image — an image is byte-identical fleet-wide, so a baked credential would be a fleet-wide secret. The build fails if the factory shadow file carries a usable password hash, on both profiles.
  • The management API is the gate. HTTPS only; password login with signed sessions and CSRF protection for browsers, bearer tokens for automation; persistent login-backoff counters and a bounded, fsynced audit trail of logins, setup, power actions and transient-password events.
  • SSH is off by default on both profiles. Persistent access is by public key only, and every authorized key is a root key — stated in the UI in as many words. The transient root password (set by an authenticated administrator, cleared automatically at the next boot) covers the operator-at-the-bench case without creating a long-lived password.
  • Lockout is real. Losing the administrator credential and all keys leaves no software path in (recovery.md) — a deliberate trade, since credentials surviving updates means updates are not a back door.

The audit trail does not yet record session lifecycle events and nothing uploads it; there is no hard lockout threshold (deliberately, until a physical-presence release path exists).

status: shipped — evidence: docs/design/access.md, docs/design/provisioning.md

Containers run rootful (rootless mode is not built) and container image signatures are not verified by the shipped policy — registry TLS and digest pinning are the protections, and the trusted-integrator threat model is the context. The management/application boundary is structural: the MQTT bridge can only reach exactly-enrolled application services and can never address the management daemon. See applications.md.

status: shipped — evidence: docs/design/containers.md, docs/design/bus.md

The inbound surface of a stock device is apid on 443 (and the redirect on 80) — nothing else listens for management, nothing dials out, and no fleet or cloud channel exists. Static UI assets are public; every appliance datum and operation sits behind the API credential boundary.

status: shipped — evidence: docs/design/remote-management.md

5.1 The firewall tools, and the firewall there is not

Section titled “5.1 The firewall tools, and the firewall there is not”

Every image ships both nft and iptables. Both are dependencies of the base package, so a build that declines containers has both too. The image ships the tools and no policy: no default rule set, no allow or deny list, and nothing that manages rules for you. There is no API and no console surface for them; ssh and these two commands are the whole of it.

Reach for nft. It is the complete view of what the device is actually doing, and it is the vocabulary any policy this product eventually ships will be written in. iptables is here as the compatibility path — for third-party tooling and existing scripts that cannot speak nft — and not as the equal of the other.

Six things about them, because each is a surprise otherwise.

The iptables extension set is bounded, and it is the same on every board. A rule that names a match or target the kernel was not built with is refused, by name:

# iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-ports 8080
Warning: Extension REDIRECT revision 0 not supported, missing kernel module?

That is not a translation failure and retrying it will not help — the extension is simply not in this kernel, and adding one is a kernel change rather than a package install.

Guaranteed on every board, because the shared kernel floor pins it: the targets MASQUERADE, REDIRECT, SNAT, DNAT, MARK, CHECKSUM and CT --notrack; the matches addrtype, conntrack, state and mark; the plain verdicts (ACCEPT, DROP, RETURN, jumps) and the built-in matches (-p, --dport, -i, -o, --tcp-flags); and the four tables filter, nat, mangle and raw in both address families. Before 2026-09-04 REDIRECT, CHECKSUM and CT worked on the arm64 board and were refused on x64 — that asymmetry is what the floor replaced.

Outside that set, ask before you rely on it, and do not assume the two boards answer alike. Measured on 2026-09-04, four extensions still differ, and not all in the same direction:

x64 arm64 (cx3576)
-m multiport, -m comment, -j CT --zone refused works
-j LOG works refused

-m limit and -m iprange are refused on both. -j REJECT and -j TCPMSS work on both today but are not pinned by the floor, so treat them as convention rather than contract. If your scripts need any of these, say so — which of them the product guarantees is an open decision, not an oversight.

nft list ruleset is the complete view. iptables -S is not. Both tools program one kernel subsystem, nf_tables. nft list ruleset prints all of it: rules you added through either tool, and the tables the container network driver writes for itself. iptables -S prints only what came through the iptables front-end, in the tables that front-end owns. On a device running containers, reading iptables -S as “the firewall on this box” is wrong — and a rule you cannot find with it is not evidence that the rule is absent.

iptables here is iptables-nft. On Debian trixie the iptables command is a translation layer over that same nf_tables subsystem, not the legacy xtables path and not a second firewall; iptables --version says so itself, printing (nf_tables). Rules it creates are real nf_tables rules in tables of its own. The legacy binaries (iptables-legacy and its save/restore pair, over xtables-legacy-multi) are in the image, because the same Debian package ships them, and nothing in the image selects them: the alternatives group is left in auto mode, where the nft front-end outranks the legacy one, and no unit, script or postinst here runs update-alternatives. Selecting legacy by hand would put your rules in a second, older kernel rule store that nothing else on the device reads — not nft, not the container driver.

The container driver’s tables are the container driver’s. They are visible through nft and not through iptables, and the driver reconciles them: a rule you edit inside them by hand is a rule you are contesting with a reconciler, and it will be rewritten. Add your own rules in your own chains.

Nothing persists, through either tool. A rule added at runtime lives in the kernel and is gone at the next reboot. There is no netfilter-persistent, no iptables-save unit, and nothing in the image loads a ruleset at boot. One file looks like it might: the nftables package ships /etc/nftables.conf and nftables.service, and this image keeps that unit disabled on purpose, with a preset it owns rather than by leaving a symlink out. That matters in both directions — the unit’s ExecStart is nft -f /etc/nftables.conf, and that config begins with flush ruleset, so an enabled unit would clear the container network’s rules at every boot. The file is on the read-only root, so it is not somewhere you can put your own rules either.

If you want a rule to survive a power cycle, the route today is your own unit: a service that reapplies the rules, installed into the writable unit directory /usr/local/lib/systemd/system like any other native application (applications.md). That is a statement of what the product does now, not a recommendation of how to run a firewall.

status: shipped — evidence: mica-system:system/control/mica-system.control, mica-boards:common/kernel/mica-required.fragment, mica-build:verify/src/checks-firewall.ts

The lifecycle is now written down: every credential in the product with its owning role and rotation procedure, the release channel and signing procedures, severity classes with triage and patch targets, advisory publication, incident response, and support windows and end of life. Each section states its own maturity rather than implying it is enforced. Boards carry a boot-assurance claim in a committed evidence file, and the release gate reads that file rather than letting a release name its own level.

status: shipped — evidence: docs/design/security-lifecycle.md, mica-boards:boards/cx3576/evidence.json

None of the response channels exists yet, and an auditor should be told so. There is no published security contact and no disclosure policy at the repository root, no advisory feed, no end-of-life announcement mechanism, and no tooling that enforces a support window or a patch target — reports today reach the maintainers privately and are handled case by case. Factory identity injection, factory records and debug/fuse policy are likewise designed and unbuilt (manufacturing.md). The advisories brief for the official site is ../website/security.md; this page is the honest inventory.

status: unsupported