| Did you know ... | Search Documentation: |
| Pack hornguard -- prolog/hornguard_worker.pl |
The pack in its own process. A host of any language spawns
swipl prolog/hornguard_worker_main.pl (or: make worker)
and exchanges one JSON object per line over stdin/stdout. The worker reads
author text itself, under the backend's reader flags and with the standard
operator table, so the untrusted engine never parses author text: an
admitted term comes back in canonical form, and only that form should cross
to the engine. Reader-level hazards (syntax errors, quasi-quotations,
oversized input, more than one term where one is expected) are refused
under the evasion class with a reader(Reason) rule.
Handshake: on start the worker writes
{"hello":"hornguard","protocol":1,"engine":"swi","version":"9.2.9",
"profiles":[...],"profiles_engine":"9.2.9","engine_matches_profiles":true}
profiles_engine is the engine version the generated swi profile records;
an attestation of purity is per engine version, so a host running on
another one is told at the handshake.
Requests carry an id (echoed), an op, and op-specific fields:
{"id":1,"op":"judge_goal","text":"findall(X, member(X,[a]), L)",
"backend":"swi","profiles":["iso","prologue"],
"options":{"strict_negation":true,"defer_unknown":false,
"allow":["foo/2"],"trust":[["bar/3","none"]],
"dynamic_dispatch":"judged","defining":["my_battery"]}}
{"id":2,"op":"judge_clause","text":"p(X) :- q(X)."}
{"id":3,"op":"judge_program","text":"p(1).\np(X) :- q(X)."}
{"id":4,"op":"load_policy","path":"/etc/host/policy.pl"}
{"id":5,"op":"load_profiles","dirs":["/a/profiles","/b/profiles"]}
{"id":6,"op":"profiles"}
{"id":7,"op":"ping"}
backend, profiles and options are optional; absent, the loaded policy
applies. Responses:
{"id":1,"verdict":"admit","canonical":"findall(A,member(A,[a]),B)"}
{"id":1,"verdict":"admit_with","canonical":"hornguard_call(G)"}
{"id":1,"verdict":"admit_needs","needs":[{"profile":"swi"}],"canonical":"..."}
{"id":1,"verdict":"refused","class":"escape_attempt","rule":"pinned(process)",
"depth":1,"reason":"permission_error(execute,goal,shell/1)"}
{"id":4,"ok":true}
{"id":9,"error":"unknown_op","detail":"..."}
The worker is sequential; hosts wanting parallelism run several.
::) reads. The
canonical form is operator-free, so the engine never needs them; op/3
stays pinned for authors. Called at start and after load_profiles.backend(B), profiles(Ps) and the judge options. Response is a dict
with the verdict fields described in the module header.ok or
refused(reader(Why)). VarNames has one Name=Var list per term, so
the canonical form can keep the author's variable names and a host can
map bindings back.Name=Var pairs from the
reader) the author's variable names are kept, so a host can map the
engine's bindings back to them; every other variable is anonymous and
prints as _. Without names, variables are lettered in order of
appearance and singletons print as _. Only this form should cross to
the engine.The following predicates are exported, but not or incorrectly documented.