Skip to content

2. Reading persisted data from a terminal

The local REST API is a Unix socket, deliberately never a TCP listener (internal/localapi, TestNothingInTheAgentCanOpenATCPListener, TestATCPShapedSocketPathDoesNotBecomeAPort) — there is nothing to curl from off the device, by design.

$ ssh edge-gateway ls -la /run/bootstrap-agent/
srw-rw---- 1 root tss 0 Sep 15 11:42 api.sock

The loopback trust rule — read this before you rely on anything else about the socket

internal/localapi/peercred_linux.go reads SO_PEERCRED on every mutating call, and its own comment says why: "LOCAL DOES NOT MEAN TRUSTED." But that read is for the audit log only — it records which local UID/PID called, it does not gate anything. There is no password, no token, no second factor on this socket. The only real access control is the socket's own file permissions: group tss, mode 0660. Anything in that group, or root, reaches every endpoint, full stop. Don't mistake the peer-credential logging for an authentication check — that mistake is what "faked a password check" this week.

2.1 Status

$ ssh edge-gateway curl -s --unix-socket /run/bootstrap-agent/api.sock http://localhost/v1/status
{"interface_version":1,"agent_version":"8df05ff-20260914T115157Z","state":"ready",
 "attestation_mode":"tpm","profile_version":163971955832716,
 "last_checkin":"2026-09-15T09:43:21Z"}

No credential fields ever appear here — safe to run and paste anywhere.

2.2 Packages

$ ssh edge-gateway curl -s --unix-socket /run/bootstrap-agent/api.sock http://localhost/v1/profile/packages
{"profile_version":163971955832716,"packages":[
  {"id":"heartbeat_seconds","version":163971955832716,"sha256":"izQEK3...","acked":true},
  {"id":"target-connection","version":163971955832716,"sha256":"HinfC...","acked":true},
  {"id":"target-credential","version":163971955832716,"sha256":"oxQpp...","acked":true},
  ...]}

Also always safe: id/version/sha256/acked only (internal/localapi/server.go:packageEntry) — no package content is ever included here.

2.3 The full profile, safely — named-field extraction

GET /v1/profile returns the profile exactly as the platform rendered it (prof.Raw, byte for byte) — and a package's content is arbitrary JSON (docs/protocol/profile-format-v1.md): whoever wrote the template decides its shape, and nothing stops a template from embedding tls_cert/tls_key/ca_cert directly in a package's content instead of a vault reference.

Why this is a named-field extraction, not a raw dump

This device's current profile happens to reference its credential via credential_vault_path rather than embedding it — so you will not see those three keys in the output below today. That is a fact about this one template, not a guarantee about every template. Always redact; never assume a given profile happens to be safe to print raw. Four credential leaks this project hit this week came from exactly this shape: someone printing a "probably safe" raw response instead of extracting the fields that are actually needed.

ssh edge-gateway 'curl -s --unix-socket /run/bootstrap-agent/api.sock http://localhost/v1/profile \
  | jq "walk(if type == \"object\" then del(.tls_cert, .tls_key, .ca_cert) else . end)"'

walk/1 recurses the whole document bottom-up and deletes those three keys wherever they appear, at any depth, in any package — it does not need to know the package structure in advance, which is the point: content is unconstrained JSON and a fixed-depth jq path would miss a differently nested template.

Verified live (redacted output, placeholders in place of this device's real vault path):

redacted GET /v1/profile
{
  "packages": [
    {"content": {"broker": "mqtt.ztd.dev.pxdc.io:8883",
      "credential_vault_path": "k8s://wlpc-credentials/wlpc-cred-<redacted>",
      "target_platform": "generic", "topic_prefix": "wlpc/acceptance-run/"},
     "id": "target-connection", "...": "..."},
    {"content": {"mqtt_server": "mqtt.ztd.dev.pxdc.io:8883",
      "not_after": "2027-10-20T07:14:37Z"},
     "id": "target-credential", "...": "..."}
  ],
  "profile_version": 163971955832716
}

Note also: never print credential_vault_path's target, never kubectl get secret wlpc-cred-... -o yaml "just to check" — same class of leak, on a different object.

What to do when it fails

GET /v1/profile on a device with no persisted profile answers a no_config shape, not an error — check /v1/status's state field first; no_config means there is genuinely nothing to read yet, not that the socket or the redaction command is broken.