fix(webconfig): stop the CLI reading secrets, and enforce the setup password
Two findings from review, both real, both mine. The CLI could read secrets the portal has never exposed. CommonCLI splits its surface by CALLER, not by command: a serial caller (sender_timestamp 0, physical access) reads secrets in plaintext, a remote one gets "******** (serial only)". Its own comments say so — "Serial only (WiFi creds grant LAN access); remote sees set/unset". execCommand passes 0, which is what makes `erase`, `stats-*` and `set freq` reachable at all, and with it the terminal inherited the serial console's plaintext answers for an HTTP request: `get prv.key` returned this node's identity, `get wifi.pwd` the operator's network. Worse in setup mode, which authenticates by proximity to an open AP — and `start webconfig ap` can be run on an already-configured node, so the secrets are real by then, not blank. I had reasoned that the AP was the trust boundary either way because the wizard can already rewrite these. That conflated two capabilities: replacing a WiFi password does not reveal the current one, and replacing an identity does not reveal the existing private key. /api/config has always masked these on read (wcIsSecretKey); the CLI simply broke that rule. Now only the READ is masked — the command surface stays whole — in CommonCLI's own words, keeping the set/unset signal that is the useful part. Onboarding could also skip the mandatory password. handleConfigPost refuses to arm a reboot during initial setup without one; the CLI only warned in the browser, which a pasted script or a direct POST ignores, so a node could reboot onto the LAN still holding the factory credential. Same rule now applies at POST. It is satisfied by a `password` command anywhere in the session rather than only in the same request, so the natural two-step console flow still works — the form batch always sends both together and never needed that memory. wcIsSecretReadCommand lives in WebConfigKeys.h beside the rest of the secret classification, pinned by three host tests: what must be masked, what must not, and that only reads are touched. 17 keys + 24 batch tests pass; the audit checks a masked read round-trips as masked.
This commit is contained in:
1 parent
536794e937
commit
a56376a6da
7 files changed
+150
-5
No files matched your search
@@ -119,6 +119,45 @@ TEST(WebConfigKeys, EverySecretKeyIsAlsoAllowed) {
|
||||
}
|
||||
}
|
||||
|
||||
// ---- CLI secret reads ----------------------------------------------------
|
||||
// The web CLI runs commands with sender_timestamp 0, which is how CommonCLI
|
||||
// recognises the serial console and answers secret getters in plaintext. These
|
||||
// are the reads that must be masked back down for an HTTP caller.
|
||||
|
||||
TEST(WebConfigKeys, MasksEverySecretReadTheCliCanReach) {
|
||||
const char* masked[] = {
|
||||
"get prv.key", // this node's identity — the worst one to leak
|
||||
"get wifi.pwd", // grants the operator's LAN, not just the node
|
||||
"get guest.password",
|
||||
"get alert.psk",
|
||||
"get bridge.secret",
|
||||
"get mqtt1.password", "get mqtt1.token",
|
||||
"get mqtt6.password", "get mqtt6.token",
|
||||
"get wifi.pwd", // extra space after the verb
|
||||
};
|
||||
for (const char* c : masked) EXPECT_TRUE(wcIsSecretReadCommand(c)) << c;
|
||||
}
|
||||
|
||||
TEST(WebConfigKeys, DoesNotMaskReadsThatCarryNoSecret) {
|
||||
const char* plain[] = {
|
||||
"get wifi.ssid", "get mqtt1.username", "get mqtt1.server", "get tx",
|
||||
"get public.key", // public half, safe to read
|
||||
"get mqtt.owner", // an owner's public key, not a credential
|
||||
};
|
||||
for (const char* c : plain) EXPECT_FALSE(wcIsSecretReadCommand(c)) << c;
|
||||
}
|
||||
|
||||
TEST(WebConfigKeys, OnlyMasksReads) {
|
||||
// Writing a secret has always been the portal's job and reveals nothing;
|
||||
// only the read is restricted. Nor may a prefix be mistaken for a `get`.
|
||||
EXPECT_FALSE(wcIsSecretReadCommand("set wifi.pwd hunter2"));
|
||||
EXPECT_FALSE(wcIsSecretReadCommand("set prv.key aabb"));
|
||||
EXPECT_FALSE(wcIsSecretReadCommand("password hunter2"));
|
||||
EXPECT_FALSE(wcIsSecretReadCommand("getwifi.pwd"));
|
||||
EXPECT_FALSE(wcIsSecretReadCommand("get"));
|
||||
EXPECT_FALSE(wcIsSecretReadCommand(""));
|
||||
}
|
||||
|
||||
// ---- request correlation -------------------------------------------------
|
||||
|
||||
TEST(WebConfigKeys, AcceptsExactHexRequestIds) {
|
||||
|
||||
Reference in new issue
Block a user