HelloZeroNet/ZeroNet · warning · Exception
Only SELECT query supported
Error message
Only SELECT query supported
What it means
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.
Source
Thrown at plugins/Chart/ChartPlugin.py:39
def load(self, *args, **kwargs):
back = super(SiteManagerPlugin, self).load(*args, **kwargs)
collector.setInitialLastValues(self.sites.values())
return back
def delete(self, address, *args, **kwargs):
db.deleteSite(address)
return super(SiteManagerPlugin, self).delete(address, *args, **kwargs)
@PluginManager.registerTo("UiWebsocket")
class UiWebsocketPlugin(object):
@flag.admin
def actionChartDbQuery(self, to, query, params=None):
if config.debug or config.verbose:
s = time.time()
rows = []
try:
if not query.strip().upper().startswith("SELECT"):
raise Exception("Only SELECT query supported")
res = db.execute(query, params)
except Exception as err: # Response the error to client
self.log.error("ChartDbQuery error: %s" % err)
return {"error": str(err)}
# Convert result to dict
for row in res:
rows.append(dict(row))
if config.verbose and time.time() - s > 0.1: # Log slow query
self.log.debug("Slow query: %s (%.3fs)" % (query, time.time() - s))
return rows
@flag.admin
def actionChartGetPeerLocations(self, to):
peers = {}
for site in self.server.sites.values():
peers.update(site.peers)
peer_locations = self.getPeerLocations(peers)
return peer_locationsView on GitHub (pinned to 454c0b2e7e)
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
Example fix
// before query = "-- daily stats\nSELECT * FROM message" // leading comment fails // after query = "SELECT * FROM message"
Defensive patterns
Strategy: validation
Validate before calling
query = query.strip()
if not query.upper().startswith('SELECT'):
raise ValueError('ChartDbQuery only accepts SELECT queries') Try / catch
try:
res = await cmd('chartDbQuery', query)
except Exception as err:
if 'Only SELECT query supported' in (res.get('error') or str(err)):
query = rewrite_as_select(query)
retry_with_select_only() Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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().
Related errors
AI-assisted analysis of HelloZeroNet/ZeroNet@454c0b2e7e (2026-09-02).
Data as JSON: /api/errors/ae65f74fa88be13d.
Report an issue: GitHub.