Skip to content

0.8.1867

Choose a tag to compare

@TimoKramer TimoKramer released this 04 Sep 05:31
64c5982
Allow-list query functions on the server (#1058)

* Allow-list query functions on the server

Query functions, predicates, aggregates and attribute/entity predicates
resolved through clojure.core/resolve and reflection, so any client of
the HTTP API or the Kabel listener could run load-string in the server.

New leaf namespace datahike.query.resolve owns symbol resolution:
*symbol-resolver* stays permissive embedded, the server and
routes/handler set the safe resolver (curated clojure.core subset,
clojure.string, registry) process-wide and restore it on stop;
register-fn! / register-ns! expose application functions to clients.
Entity predicates go through the registry, unresolved aggregates are a
typed error, datahike.api/q is a nested-query built-in.

Also carries src-kabel on the :http-server alias, which has failed to
start the standalone server (and the native-image CBOR leg) since the
Kabel listener landed, and a kaocha hook so a handler a test never
releases does not decide the next namespace's resolution.

Claude-Session: https://claude.ai/code/session_01J9RUNkHFidHP8EvQVCSqf3

* Bind the safe resolver per request instead of mutating the root

The server no longer alter-var-roots the query function resolver.
routes/handler binds it around every request and the Kabel listener
around every client dispatch (register-global-handlers! :wrap-handler);
the local writer captures the caller's thread bindings at dispatch and
applies them on the writer thread, so entity and attribute predicates
see the same resolver. Plan and result caches are keyed by resolver.

datahike.api installs its read-only functions (q for subqueries, pull,
pull-many, entity, datoms, seek-datoms, index-range) as functions every
resolver knows. The kaocha hook is gone with the root mutation.

Claude-Session: https://claude.ai/code/session_01J9RUNkHFidHP8EvQVCSqf3

* Close the review findings on the safe resolver

The curated functions refuse a database, connection or reference as
argument: on the server `$` is the live DB record, so `(get $ :config)`
handed a read-only client the store configuration. The planner's
external-engine metadata hooks and the secondary-index solver resolve a
symbol only when the current resolver admits it. The permissive resolver
falls back to every bare clojure.core symbol except eval, as before, so
embedded queries using bit-and, ffirst and friends keep working. repeat
leaves the curated list; the writer's NPE payload drops the bindings.

Claude-Session: https://claude.ai/code/session_01J9RUNkHFidHP8EvQVCSqf3