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.
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):
{
"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.