abhigyanpatwari/GitNexus · error
Write queries are not allowed via the HTTP API
Error message
Write queries are not allowed via the HTTP API
What it means
HTTP 403 from POST /api/query when the Cypher statement attempts a write and the read-only LadybugDB connection rejects it — the catch inspects the error with isReadOnlyDbError. The HTTP API deliberately opens withLbugDb in readOnly: true mode, so the endpoint is a query surface only; any CREATE/MERGE/DELETE/SET/REMOVE/DETACH DELETE/DROP-style clause surfaces as this 403.
Source
Thrown at gitnexus/src/server/api.ts:674
res.status(400).json({
error: '"params" must be a plain object with scalar values (string/number/boolean/null)',
});
return;
}
const entry = await resolveRepo(requestedRepo(req));
if (!entry) {
res.status(404).json({ error: 'Repository not found' });
return;
}
const lbugPath = path.join(entry.storagePath, 'lbug');
const result = await withLbugDb(lbugPath, () => executePrepared(cypher, queryParams ?? {}), {
readOnly: true,
});
res.json({ result });
} catch (err: any) {
if (isReadOnlyDbError(err)) {
res.status(403).json({ error: 'Write queries are not allowed via the HTTP API' });
return;
}
res.status(500).json({ error: err.message || 'Query failed' });
}
};
/**
* Validate the optional `token` field of POST /api/analyze. Returns an
* { status, error } to send, or null when the token is absent or valid.
*
* The token is a GitHub PAT: charset-restricted (blocks CRLF header
* smuggling), length-bounded (1–256), and bound to github.com using the SAME
* GITHUB_TOKEN_HOSTS allowlist + hostname parse as resolveGitCredential, so a
* token the API accepts is exactly the one buildGitEnv will inject — and one
* it rejects is never sent off github.com.
*
* Exported for unit tests (the route validation is otherwise only reachable
* by booting the server).View on GitHub (pinned to aac7515d2a)
Solutions
- Rewrite the statement as read-only: MATCH/WITH/UNWIND/WHERE/RETURN only
- Perform any write/maintenance through the gitnexus CLI (which owns index mutation) rather than the HTTP API
- If a mutation genuinely belongs in the product, request/expect a dedicated authenticated admin endpoint — do not try to bypass the read-only flag
- Check nested CALL { ... } blocks for hidden writes — the read-only rejection applies to the whole transaction
Example fix
// before
body: JSON.stringify({ cypher: 'MATCH (n:Symbol) SET n.seen = true RETURN n' }) // 403
// after
body: JSON.stringify({ cypher: 'MATCH (n:Symbol) RETURN n.name LIMIT 25' }) // read-only is allowed Defensive patterns
Strategy: validation
Validate before calling
// Reject write-shaped Cypher before it reaches the API.
const WRITE_CLAUSE = /\b(CREATE|MERGE|DELETE|DETACH|SET|REMOVE|DROP|LOAD\\s+CSV|FOREACH)\b/i;
function assertReadOnlyCypher(cypher: string): void {
// strip string literals so keywords inside quotes don't false-positive
const stripped = cypher.replace(/'[^']*'|\"[^\"]*\"/g, '');
if (WRITE_CLAUSE.test(stripped)) throw new Error('HTTP query API is read-only — remove write clauses');
} Try / catch
const res = await postQuery(cypher);
if (res.status === 403 && /not allowed/.test((await res.json()).error)) {
throw new Error('this query writes — use the gitnexus CLI for index mutations');
} Prevention
- Treat the HTTP query API as strictly read-only by design
- Keep a separate writable workflow (CLI) for any graph maintenance
- Audit nested CALL { ... } blocks for hidden writes
When it happens
Trigger: MATCH (n) MERGE (n:X) RETURN n; MATCH (n) DELETE n; SET/REMOVE property writes; CALL of a subquery containing writes; maintaining Cypher that worked against a writable Neo4j and reusing it verbatim against the HTTP API.
Common situations: Trying to clean up or annotate graph nodes through the serve API instead of the CLI; porting admin Cypher scripts to the web client; experiments meant for a local writable console pasted into /api/query; misunderstandings where users expect the API to mutate the index.
Related errors
- Bridge query prepare failed: ${errMsg}
- Path traversal denied
- Missing "cypher" in request body
- "params" must be a plain object with scalar values (string/n
- Query failed
AI-assisted analysis of abhigyanpatwari/GitNexus@aac7515d2a (2026-08-20).
Data as JSON: /api/errors/acdc04586f66f609.
Report an issue: GitHub.