{"record":{"id":"ae65f74fa88be13d","repo":"HelloZeroNet/ZeroNet","slug":"only-select-query-supported","errorCode":null,"errorMessage":"Only SELECT query supported","messagePattern":"Only SELECT query supported","errorType":"validation","errorClass":"Exception","httpStatus":null,"severity":"warning","filePath":"plugins/Chart/ChartPlugin.py","lineNumber":39,"sourceCode":"    def load(self, *args, **kwargs):\n        back = super(SiteManagerPlugin, self).load(*args, **kwargs)\n        collector.setInitialLastValues(self.sites.values())\n        return back\n\n    def delete(self, address, *args, **kwargs):\n        db.deleteSite(address)\n        return super(SiteManagerPlugin, self).delete(address, *args, **kwargs)\n\n@PluginManager.registerTo(\"UiWebsocket\")\nclass UiWebsocketPlugin(object):\n    @flag.admin\n    def actionChartDbQuery(self, to, query, params=None):\n        if config.debug or config.verbose:\n            s = time.time()\n        rows = []\n        try:\n            if not query.strip().upper().startswith(\"SELECT\"):\n                raise Exception(\"Only SELECT query supported\")\n            res = db.execute(query, params)\n        except Exception as err:  # Response the error to client\n            self.log.error(\"ChartDbQuery error: %s\" % err)\n            return {\"error\": str(err)}\n        # Convert result to dict\n        for row in res:\n            rows.append(dict(row))\n        if config.verbose and time.time() - s > 0.1:  # Log slow query\n            self.log.debug(\"Slow query: %s (%.3fs)\" % (query, time.time() - s))\n        return rows\n\n    @flag.admin\n    def actionChartGetPeerLocations(self, to):\n        peers = {}\n        for site in self.server.sites.values():\n            peers.update(site.peers)\n        peer_locations = self.getPeerLocations(peers)\n        return peer_locations","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/HelloZeroNet/ZeroNet/blob/454c0b2e7e000fda7000cba49027541fbf327b96/plugins/Chart/ChartPlugin.py#L21-L57","documentation":"actionChartDbQuery is the ZeroNet chart API exposed to (untrusted) chart providers. To prevent SQL injection and data modification, it only permits read-only SELECT statements and rejects anything else before executing. The rejection is also returned to the client as {'error': ...} rather than raised over RPC.","triggerScenarios":"Calling the ChartDbQuery API (user_action chartDbQuery) with a query that does not start with SELECT — e.g. INSERT/UPDATE/DELETE/DROP, a leading comment or whitespace like '-- dump\\nSELECT ...', or queries beginning with WITH/EXPLAIN/PRAGMA.","commonSituations":"Chart provider code accidentally sending write queries; queries built with leading SQL comments; attempting schema setup (CREATE TABLE) through the chart API instead of the site's own trusted context; case/format issues defeated by .strip().upper().","solutions":["Rewrite the query to start with the SELECT keyword (no leading comments/CTEs before it)","Perform writes/schema changes in trusted site code (dbschema.json / site owner context), never via the chart API","If you need only a count or specific fields, keep it a plain 'SELECT ...' query","Note the error is returned as {'error': ...} in the response — check the response's error key in your chart provider code"],"exampleFix":"// before\nquery = \"-- daily stats\\nSELECT * FROM message\"  // leading comment fails\n// after\nquery = \"SELECT * FROM message\"","handlingStrategy":"validation","validationCode":"query = query.strip()\nif not query.upper().startswith('SELECT'):\n    raise ValueError('ChartDbQuery only accepts SELECT queries')","typeGuard":null,"tryCatchPattern":"try:\n    res = await cmd('chartDbQuery', query)\nexcept Exception as err:\n    if 'Only SELECT query supported' in (res.get('error') or str(err)):\n        query = rewrite_as_select(query)\n        retry_with_select_only()","preventionTips":["Treat the chart API as read-only: plan writes/schema changes in dbschema.json or trusted code","Strip leading SQL comments/whitespace so the query truly starts with SELECT","Validate queries at build time (lint for INSERT/UPDATE/DELETE in chart provider code)","Always inspect the {'error': ...} key in the chartDbQuery response"],"tags":["sql","security","readonly","chart"],"backgroundTag":"non-select-query-rejected","analyzedSha":"454c0b2e7e000fda7000cba49027541fbf327b96","analyzedAt":"2026-09-02T19:46:57.278Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}