Files
myprimal/api-docs/security-critique.md
T
arnaudne 941abd98d5 feat: initial myprimal MQTT bridge service
Dynamic MySolem/MyIndygo IoT bridge with MQTT, Home Assistant
autodiscovery, and Homebridge EasyMQTT compatibility.

- Device discovery via MySolem API on startup (all modules/inputs/outputs)
- Configurable poll engine (fast/normal/slow buckets, per-input overrides)
- MQTT retained state publishing + HA autodiscovery payloads
- Unified manual command routing (linesControl vs sendManualModuleCommand)
- Runtime config via MQTT $config/set, persisted to config.json
- Expression override system (API-provided > hardcoded > passthrough)
- Minimal REST API: /health, /state, /control, /rediscover
- Docker deployment (Dockerfile + docker-compose.yml)
- 38 unit tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 01:05:24 +02:00

9.5 KiB
Raw Blame History

MySolem API — Security Critique

Assessment based on black-box reverse engineering of https://qualif.mysolem.com.


Critical

1. /modules/search leaks the entire platform's device database

POST /modules/search with an empty query ("query": {}) returned 4,607 modules belonging to all users on the platform. Confirmed exposed fields: name, serialNumber, macAddress, softwareVersion, seenAt (last-seen timestamp), batteryAlarm, uuid, type, and createdAt.

Any authenticated user can enumerate every device on the platform. This is a broken object-level authorization (BOLA / IDOR) vulnerability — the endpoint enforces authentication but not ownership scoping.

Impact: full device inventory of all platform customers — device names, serial numbers, MAC addresses, firmware versions, and last-seen timestamps across all tenants.


2. POST /users/search exposes full user records including password hashes and account takeover tokens

POST /users/search accepts an arbitrary MongoDB-style query with no ownership enforcement. Querying by any known user ID returns the complete user document, including:

  • Email address and full name
  • Hashed password (password field — 64-character hex string, consistent with unsalted SHA-256; notably not bcrypt which is Sails.js's default — warrants further analysis)
  • Active password reset token (resetPasswordToken) — can be used directly to take over the account without knowing the current password
  • APNs and FCM push notification tokens — allows sending arbitrary push notifications to the user's devices
  • Address fields, notification preferences, and app version history

User IDs are trivially obtained from finding #1: every module document in the /modules/search dump contains the owning user's ID in its relational fields.

Proof of concept: querying {"query": {"id": "641c3250efed32199bed0166"}} returned the full record for jdheilly@solem.fr including password hash, reset token, and 11 push tokens across iOS and Android.

Impact: complete account takeover of any platform user via the exposed reset token, plus credential exposure (unsalted SHA-256 hashes are trivially crackable). Push token exposure enables notification spam or phishing. This is the most severe finding in this assessment.


3. POST /outputs/search and POST /programs/search expose full platform watering configuration

Both search endpoints accept an empty MongoDB query with no ownership enforcement:

  • POST /outputs/search with {"query": {}} returned 12,034 outputs (valve/station definitions) across all tenants, including names, flow rates, and module associations.
  • POST /programs/search with {"query": {}} returned 6,073 irrigation programs across all tenants, including full schedules, station durations, start times, water-budget percentages, and weekday patterns.

Impact: complete read access to every customer's watering configuration. Combined with finding #4 (device control), this gives an attacker enough information to silently modify or disrupt any installation on the platform.


4. GET /remote/module/configuration exposes device internals for any module

The endpoint accepts any moduleId without ownership verification. Probing a foreign active module (67697cc9673ef4bfbb4fdbbc) returned full device configuration including:

  • WiFi SSID and all MAC addresses (WiFi, Bluetooth, Ethernet)
  • Firmware, hardware, and BLE versions
  • Connected network domain and IoT subdomain
  • Uptime counter and reset count
  • Active link states (WiFi, Bluetooth, modem)

Impact: network topology and credential-adjacent data (WiFi SSID + MAC) for any online device on the platform, without owning it.


5. No authorization check on /remote/input/getComputedSensorData

The sensor telemetry endpoint accepts any inputId. If an attacker enumerates input IDs (trivially derived from the module search above — they are sequential MongoDB ObjectIds), they can pull historical sensor data for any device on the platform without owning it.

Impact: silent exfiltration of long-term environmental data (soil moisture trends, rain events, watering schedules) for any customer site.


7. POST /module/sendManualModuleCommand — no apparent ownership check

The control endpoint takes a moduleSerialNumber, not a session-scoped module ID. Serial numbers are visible in the /modules/search dump. It is likely (not confirmed) that a valid session can send commands to modules owned by other users.

Impact: if confirmed, any authenticated user could start or stop irrigation on any device on the platform — including commercial agricultural installations.


High

8. Google Maps API key hard-coded in HTML

The key AIzaSyDh8fCC9vTEuN9eKewxgqWXGP9ENb5an1c is embedded in every page load with no referrer restriction visible. It can be extracted and abused for Maps API billing fraud against the platform owner.


Sails.js default session TTL. There is no SameSite attribute confirmed in the Set-Cookie header, which means the cookie is sent on cross-site navigated requests — making CSRF possible for state-changing endpoints despite the lack of a CSRF token check (both conditions compound each other).

A SameSite=Lax or SameSite=Strict attribute would mitigate this without requiring a token.


10. No rate limiting observed on /login

The login endpoint accepted repeated requests without delay, lockout, or CAPTCHA. Credential stuffing and brute-force attacks are uninhibited.


11. MongoDB ObjectIds as primary keys are sequentially guessable

MongoDB ObjectIds encode a timestamp + machine ID + process ID + counter. They are not random. An attacker who knows one valid ID can narrow the search space for adjacent resources created around the same time. Combined with findings #12, this accelerates enumeration significantly.


Medium

12. CORS is misconfigured (blank Access-Control-Allow-Origin)

The server returns Access-Control-Allow-Origin: (empty string) rather than omitting the header or setting a specific origin. This is a misconfiguration — browsers treat it as invalid, but it signals the header is being generated dynamically and may be inconsistently applied across routes.


13. No Content-Security-Policy header

The React frontend loads without a CSP. If an XSS vulnerability exists anywhere in the app, the attacker has full access to the DOM and cookie-less session (cookies are HttpOnly, but XSS can still make authenticated API calls).


14. Verbose server fingerprinting

X-Powered-By: Sails <sailsjs.org> is returned on every response. Attackers can immediately target known Sails.js CVEs without fingerprinting effort. This header should be removed.


15. Module serial numbers and MAC addresses exposed in bulk

The /modules/search dump includes full serialNumber, uuid, macAddress, and manufacturerType for every device. These are used as authentication tokens in some IoT protocols (LoRa device EUIs, BLE MAC-based pairing). Bulk exposure reduces the bar for physical-layer attacks.


Low / Informational

16. Qualification environment uses real production data

The qualif.mysolem.com environment appears to share the same database as production (or a live mirror), given that real device GPS coordinates, real customer addresses, and live telemetry timestamps are visible. Qualification/staging environments should use anonymized or synthetic data.


17. Input IDs discoverable via HTML scraping

Input IDs are embedded as data-input-id attributes in the module detail page. This is not a vulnerability on its own, but combined with #1 (module enumeration) and #2 (unrestricted telemetry), it completes the chain without requiring any guessing.


Summary table

# Finding Confirmed Severity OWASP / CWE
1 /modules/search — 4,607 devices platform-wide Yes Critical API1:2023 BOLA
2 /users/search — password hashes, reset tokens, push tokens Yes Critical API1:2023 BOLA / CWE-312
3 /outputs/search + /programs/search — 12K outputs, 6K programs Yes Critical API1:2023 BOLA
4 /remote/module/configuration — WiFi/MAC/firmware for any device Yes Critical API1:2023 BOLA
5 Sensor telemetry readable for any input ID Likely Critical API1:2023 BOLA
6 Sensor history readable for any module ID Likely High API1:2023 BOLA
7 Device control likely has no ownership check Unconfirmed Critical API1:2023 BOLA
8 Google Maps API key in HTML, no restriction Yes High CWE-312
9 Session cookie missing SameSite attribute Yes High CWE-352 CSRF
10 No login rate limiting Yes High CWE-307
11 Sequential ObjectIds accelerate enumeration Yes High CWE-330
12 Blank CORS header (misconfigured) Yes Medium CWE-942
13 No Content-Security-Policy Yes Medium CWE-1021
14 X-Powered-By header leaks framework Yes Low CWE-200
15 MAC/serial bulk exposure Yes Medium CWE-200
16 Staging uses real data Yes Medium
17 Input IDs in HTML source Yes Info

Responsible disclosure note

These findings were discovered through authorized testing of an account owned by the researcher. The critical BOLA issues (#17) should be reported to the MySolem security team before any public disclosure, as they affect all platform customers.