API & Security

5. /api/cmd (HTTP Shell)

All Shell commands are available over HTTP. Requires auth. Run the test script with --web-pass to enable automated checks, or test manually with curl:

# version command
curl -s -u admin:changeme \
  -X POST http://[ip]/api/cmd \
  -H 'Content-Type: application/json' \
  -d '{"cmd":"version"}'
# -> {"ok":true,"output":["thesada-fw v1.x ..."]}

# wrong password -> 401
curl -s -u admin:wrong \
  -X POST http://[ip]/api/cmd \
  -H 'Content-Type: application/json' \
  -d '{"cmd":"version"}'
# -> {"ok":false,"error":"Unauthorized"}
Check Expected
POST /api/cmd {"cmd":"version"} with correct password {"ok":true,"output":["thesada-fw v1.x ..."]}
POST /api/cmd {"cmd":"heap"} {"ok":true,"output":["Free: XXXXXX B ..."]}
POST /api/cmd {"cmd":"xyzzy"} {"ok":true,"output":["Unknown command: xyzzy"]}
POST /api/cmd with wrong password 401 Unauthorized
POST /api/cmd with malformed JSON body 400 / {"ok":false,"error":"..."}

Test script:

python tests/test_firmware.py --web-pass changeme

6. Security

Rate limiting - 5 failed logins lock out the source IP for 30 s:

for i in $(seq 1 6); do
  curl -s -u admin:wrong http://[ip]/api/auth/check
  echo
done
# Attempts 1-5 -> {"ok":false,"error":"Unauthorized"}
# Attempt 6   -> {"ok":false,"error":"Too many attempts - wait 30s"}

WebSocket auth - unauthenticated direct connections are rejected:

curl -i http://[ip]/ws/serial \
  -H "Upgrade: websocket" \
  -H "Connection: Upgrade" \
  -H "Sec-WebSocket-Key: dGhlc2FtcGxlbm9uY2U=" \
  -H "Sec-WebSocket-Version: 13"
# -> 101 (handshake completes), then immediately receives WS close frame
# Serial log shows: [WRN][WebServer] WS: rejected - not pre-authorized

WS token endpoint - requires auth:

curl http://[ip]/api/ws/token
# -> {"ok":false,"error":"Unauthorized"}

curl -u admin:changeme http://[ip]/api/ws/token
# -> {"ok":true}

Bearer token auth (v1.2.4+):

# Login - exchange Basic Auth for Bearer token
curl -s -u admin:changeme -X POST http://[ip]/api/login
# -> {"ok":true,"token":"<32-hex>","expires_in":3600}

# Use token for admin endpoints
TOKEN="<token from above>"
curl -s -H "Authorization: Bearer $TOKEN" -X POST http://[ip]/api/cmd \
  -H "Content-Type: application/json" -d '{"cmd":"version"}'
# -> {"ok":true,"output":["thesada-fw v1.x..."]}

# Invalid token
curl -s -H "Authorization: Bearer invalidtoken" -X POST http://[ip]/api/cmd \
  -H "Content-Type: application/json" -d '{"cmd":"version"}'
# -> {"ok":false,"error":"Unauthorized"}

# Wrong credentials
curl -s -u admin:wrong -X POST http://[ip]/api/login
# -> {"ok":false,"error":"Unauthorized"}

# Rate limiting (after 5 failures)
# -> {"ok":false,"error":"Too many attempts - wait 30s"}

# Basic Auth still works (backwards compatible)
curl -s -u admin:changeme -X POST http://[ip]/api/cmd \
  -H "Content-Type: application/json" -d '{"cmd":"version"}'
# -> {"ok":true,...}

7. Config Editor

Check Expected
Admin - Config tab config.json loads in editor
Edit device.friendly_name, save Device restarts; new name in footer
POST /api/config with wrong password 401, file unchanged

Curl test (should return 401):

curl -X POST http://[ip]/api/config \
  -u admin:wrongpassword \
  -H 'Content-Type: application/json' \
  -d '{"device":{"name":"hacked"}}'

8. Lua Scripting

Check Expected
lua.exec return 42 42
lua.exec Log.info("test") OK (plus [INF][Lua] test in log)
lua.exec bad syntax!!! Error: ...
lua.reload Lua scripts reloaded + scripts re-execute
Modify /scripts/rules.lua, run lua.reload New rule behavior active immediately
MQTT message to <prefix>/cmd/lua/reload Same as lua.reload

Thesada - CERN-OHL-P-2.0 / GPL-3.0-only / CC BY 4.0

This site uses Just the Docs, a documentation theme for Jekyll.