Security & trusted repos
How a pod proves who it is, how CI proves which repo it is, how you decide what each may do, and how the pod protects itself, with no long-lived secret stored in your repositories. The narrative version is in the article No Secrets: How a Pod Behind NAT Runs in CI.
On your local network
The pod's direct TCP/JSON and SCPI API on port 8080 has no login, the same as most lab instruments. By default anyone who can reach the pod's IP can drive it, so keep pods on a trusted network segment and don't expose port 8080 to the internet. Remote access is meant to go through the cloud path below, not a forwarded port. Three controls narrow the LAN further:
- Network access without sign-in (the pod's Settings tab, or
benchpod lan-policy set). The API on Ethernet and Wi-Fi has no login, so this decides how much of the pod it opens. Signed-in use through embeddedci.com and the USB cable always work.- Full (
open, the default): anyone who reaches the pod can use all of it, settings and firmware included. For a desk or a private bench network. - Instruments only (
locked): anyone on the network can still run the instruments and flash your board, but settings such as Wi-Fi, the cloud link and safety limits, and firmware updates, need a sign-in or the USB cable. For a shared lab where tests run over the network. It protects the pod, not the board under test. - None (
off): the pod refuses API connections over Ethernet and Wi-Fi and stops announcing itself there. Everyone signs in (with roles and an audit log) or uses the cable. For an office or other network you do not trust. The pod stays online in the cloud.
- USB network (firmware after 3.9.0, on by default): whether the pod's USB-C port is also a network adapter with the full API, besides the console. It can only be changed on the device, over USB:
benchpod usb-network off(the pod re-enumerates as a console only and the computer sees no network adapter) andbenchpod usb-network onover the console. Ethernet, Wi-Fi and the cloud can only read it; the web app shows it read-only under Settings, Advanced. - HTTPS-only API (firmware 3.9.3+, off by default; the paths are drawn on Architecture; the pod's Settings tab or
benchpod lan-tls https-only on). The pod also serves its API with TLS on port 8443, with a certificate it makes itself (or yours,benchpod lan-tls set-cert). The CLI, SDK and MCP server use it when the pod has it and pin the certificate on first use. With HTTPS-only on, the pod refuses plaintext 8080 on Ethernet and Wi-Fi, so nothing on the network path (a Wi-Fi bridge, a shared switch) can read or change the traffic; the USB cable keeps 8080. TLS encrypts, it does not log anyone in: Network access without sign-in still decides what the API allows. - Cloud jobs come first. While a CI run, the web app or the SDK holds the pod over the cloud, LAN clients can only read its status; driving outputs, captures and settings wait until the job is done. The pod ends this on its own when the job's lease expires or the cloud link drops.
- The USB console is physical access and can always change these settings: it is the way back if a pod is locked down further than intended. It is also the only place to erase the pod's device key and make a new one (
benchpod identity wipe --connection usb); the pod then has to be registered again.
How a pod authenticates to the cloud
For remote/CI use, the pod opens an outbound TLS WebSocket to embeddedci.com and authenticates with a device key it holds locally. Because the connection is outbound, the pod works behind NAT or a firewall with no port-forwarding, and nothing is exposed to the public internet. The TLS connection verifies the server against embedded root certificates, so the pod only ever talks to the real server.
The pod proves possession of its key by signing a one-time challenge from the server, and the signature names the server it connected to. A challenge relayed through another server therefore does not log anyone in as the pod, and once a pod has used this form, the server refuses the older one for it (support can reset that if a pod has to go back to older firmware). Release firmware only connects over TLS.
Corporate networks: company CA and proxy
Networks that inspect HTTPS re-sign it with a company root certificate. Install that root on the pod with benchpod cloud ca set company-root.pem over USB (or from the cloud); the pod then trusts it in addition to its built-in roots, and still checks the server name. If the lab reaches the internet through an HTTP proxy, set it with benchpod cloud proxy set proxy.example:3128 (optionally with a user and password, which the pod never shows again). The pod sends the proxy a CONNECT for the server's name, so only the proxy needs to resolve outside names.
Neither can be installed, changed or cleared from the LAN, whatever the network access setting: someone on the LAN who could add their own CA and proxy could stand in for the server. The LAN can still read both.
Signed firmware
Every firmware release and the gateware and Wi-Fi images it installs are signed with an offline-held release key. The pod checks the signature before it accepts an update, from the cloud, the LAN or USB, and reports the result with every update. Its signature policy decides what happens on a failure: audit (the default: install and report), permissive (refuse a bad signature) or required (refuse anything not signed by a trusted key). Turn on Require signed firmware on the Organization page and the server moves each pod to required after its first signed update. From the cloud the policy can only get stricter; loosening it needs the pod's USB console. A pod on required also refuses firmware that could not enforce the policy itself.
Who may change what
Every member of an organization can use its pods: capture, drive outputs, flash the device under test. Changing what persists on a pod (Wi-Fi, cloud settings, safety limits, calibration, the LAN and signature policies) and updating its firmware needs an organization owner or admin. API keys need the benchpod:admin scope for those, on top of benchpod:control, and stop working for them when their owner is no longer an owner or admin. GitHub Actions jobs can use pods but never change what persists on them. Every such change, and every refused attempt, is recorded in the audit log.
Share a pod with your team
A pod belongs to one organization, and everyone in it can use it. A pod you register lands in the organization that is active when you register it, usually your personal one, which only you can see. To share it, create a team organization (top right menu, Organization), invite your teammates, and move the pod there. You do not need to deregister it.
Move it from either side: on the Organization page of the team, Move pods into this organization, or on the pod's Settings tab, Move to organization. The pod stays connected and keeps its captures, waveforms and wiring, and you stay its owner, so you can move it back the same way. Anyone using it at that moment loses their session. If the team already has a pod with the same name, you pick a new name in the same dialog.
To move a pod out of an organization you must own the pod or be an owner or admin of that organization. To move one in you must be an owner or admin of the organization it goes to. Moving a pod needs a signed-in session, not an API key, and is recorded in the audit log of the organization it goes to.
How CI authenticates (GitHub OIDC)
A GitHub Actions job doesn't store an API key. It mints a short-lived GitHub OIDC token(a signed assertion of "I am a job in owner/repo"), which the server verifies with GitHub and exchanges for a session scoped to the pods that repo may drive. This is the same model as PyPI Trusted Publishing. It needs one permission block:
permissions: id-token: write # lets the job mint a GitHub OIDC token for embeddedci contents: read
The token is minted per run and expires in minutes; there's no EMBEDDEDCI_API_KEY in your repo secrets to leak or rotate.
Outside GitHub Actions (a laptop, or another CI system), the SDK authenticates the same embeddedci:<name> destination with an API key instead (BENCHPOD_API_KEY / --benchpod-api-key). That key is a long-lived secret: give it only the benchpod:control scope (add benchpod:admin only to keys that update firmware or change settings), keep it out of logs, and revoke it when you no longer need it. The SDK exchanges it for a short-lived session token, so the key itself never goes into a WebSocket URL.
The trusted-repo allow-list
Identity isn't authorization. In the web app under BenchPod → GitHub Actions you declare which repositories are trusted and which pods each may drive:
- Add the repo as
OWNER/REPO(click Look up to resolve the numeric ids). - Choose Any pod, or pin it to a specific pod or set of pods.
A repo that isn't listed gets nothing; a listed repo can reach only the pods you named. So a shared fleet serves many teams without any one repo being able to grab a board it shouldn't.
What is and isn't stored
- Device key: lives on the pod, never leaves it.
- CI credential: a GitHub OIDC token minted per run, expires in minutes.
- Authorization: a server-side allow-list you control, not a secret in the repo.
- Firmware signing key: held by the release process, never on a pod or the server; pods carry only the public keys.
- Proxy password: on the pod only, if you set one; never shown back.
- Firmware update reports: when you update a pod with the
benchpodCLI while signed in (flash-self, an update over USB or the LAN,install-blobs), the CLI tells embeddedci.com the pod's identity key and name, the old and new firmware and gateware versions, how it was installed and the CLI version, so the pod's history shows updates that did not go through the web app. Turn it off withBENCHPOD_NO_FIRMWARE_REPORT=1or--no-firmware-report. A CLI that is not signed in sends nothing.
Reporting a vulnerability
Found a security issue in the BenchPod, its firmware, the SDK or embeddedci.com? Email hello@embeddedci.com with "Security" in the subject. Include what you found, how to reproduce it and what it affects. Please give us a chance to fix it before you make it public.
- We reply within two business days and keep you posted until it is fixed.
- We credit you in the fix, unless you would rather stay anonymous.
- Test only against your own BenchPod and your own account, never someone else's data.
Machine-readable: /.well-known/security.txt.