UnifyWeaver includes a comprehensive security system called the “firewall” that controls what operations generated code can perform. This chapter explores how the firewall integrates with the Prolog target to enforce security policies.
Code generation systems can be security risks:
The firewall provides:
Firewall policies are defined in .firewall files:
% .firewall
% Security policy for this project
% Deny network access
:- disallow(network_access).
% Deny native compilation
:- disallow(compile(native)).
% Allow only SWI-Prolog
:- allow(prolog_target(swi)).
:- disallow(prolog_target(gnu)).
% Require all data sources to be local files
:- require(data_source(file(_))).
allow/1: Explicitly permit an operation
:- allow(prolog_target(swi)).
disallow/1: Explicitly deny an operation
:- disallow(prolog_target(gnu)).
require/1: Make an operation mandatory
:- require(dialect(swi)).
implies/2: Create conditional policies
:- implies(prolog_target(gnu), compile(false)).
% "If using GNU Prolog, compilation must be disabled"
The Prolog target integrates with the firewall at several points:
Check Point: Before generating code for a dialect
generate_prolog_script(Predicates, Options, Code) :-
option(dialect(Dialect), Options, swi),
% Firewall check
( firewall_allows(prolog_target(Dialect))
-> true
; throw(error(firewall_denied(prolog_target(Dialect)),
'Dialect blocked by security policy'))
),
% ... continue generation
Policy Examples:
% Only allow SWI-Prolog
:- allow(prolog_target(swi)).
:- disallow(prolog_target(gnu)).
% Allow both but log usage
:- allow(prolog_target(swi)).
:- allow_with_audit(prolog_target(gnu)).
Check Point: Before invoking compiler
compile_script_safe(Dialect, ScriptPath, Options) :-
% Check if compilation is allowed
( firewall_allows(compile(native))
-> compile_script(Dialect, ScriptPath)
; format('[Firewall] DENIED: Native compilation blocked~n'),
throw(error(firewall_denied(compile(native)),
'Compilation blocked by security policy'))
).
Policy Examples:
% Disable all compilation
:- disallow(compile(native)).
% Allow compilation only for specific dialects
:- implies(compile(native), prolog_target(gnu)).
% Require compilation for production
:- implies(environment(production), compile(native)).
Check Point: Before adding dependencies
add_dependency(Module, AllowedDeps, FinalDeps) :-
( firewall_allows(import_module(Module))
-> FinalDeps = [Module|AllowedDeps]
; format('[Firewall] DENIED: Module ~w blocked~n', [Module]),
FinalDeps = AllowedDeps
).
Policy Examples:
% Block network modules
:- disallow(import_module(library(http/_))).
% Block database access
:- disallow(import_module(library(odbc))).
% Allow only approved modules
:- allow(import_module(library(lists))).
:- allow(import_module(library(filesex))).
:- disallow(import_module(_)). % Deny all others
Check Point: Before generating main/0
generate_main_with_restrictions(Options, MainCode) :-
% Check what capabilities are allowed
( firewall_allows(file_access(write))
-> IncludeFileWrite = true
; IncludeFileWrite = false
),
( firewall_allows(network_access)
-> IncludeNetwork = true
; IncludeNetwork = false
),
% Generate restricted main/0
generate_main_predicate([
file_write(IncludeFileWrite),
network(IncludeNetwork)
| Options
], MainCode).
Behavior: Warn but allow
:- firewall_mode(guidance).
:- guidance(prolog_target(gnu),
'GNU Prolog has limited library support').
Output:
[Firewall] GUIDANCE: prolog_target(gnu)
Reason: GNU Prolog has limited library support
Action: Proceeding with caution
Behavior: Block violations
:- firewall_mode(enforce).
:- disallow(prolog_target(gnu)).
Output:
[Firewall] DENIED: prolog_target(gnu)
Reason: Blocked by security policy
ERROR: Operation cannot proceed
Behavior: Allow but log everything
:- firewall_mode(audit).
:- audit_all.
Output:
[Firewall] AUDIT: prolog_target(gnu) - ALLOWED
[Firewall] AUDIT: compile(native) - ALLOWED
[Firewall] AUDIT: import_module(library(lists)) - ALLOWED
Policies can be combined using logical operators:
:- allow(prolog_target(gnu)) :-
environment(production),
code_reviewed(true).
% Only allow GNU Prolog in production if code was reviewed
:- allow(compile(native)) :-
environment(production).
:- allow(compile(native)) :-
explicit_permission(user).
% Allow compilation in production OR with explicit permission
:- disallow(network_access) :-
\+ environment(development).
% Disallow network except in development
:- implies(prolog_target(gnu), validation(strict)).
% If using GNU Prolog, strict validation is required
:- implies(compile(native), code_review(required)).
% If compiling, code review is mandatory
Requirement: Only allow SWI-Prolog
% .firewall
:- allow(prolog_target(swi)).
:- disallow(prolog_target(gnu)).
:- disallow(prolog_target(_)). % Deny any other dialects
Requirement: Different rules for different environments
% .firewall
% Development: permissive
:- allow(prolog_target(_)) :- environment(development).
:- allow(compile(_)) :- environment(development).
% Production: strict
:- allow(prolog_target(gnu)) :- environment(production).
:- require(compile(native)) :- environment(production).
:- require(code_review(passed)) :- environment(production).
Requirement: Only local files, no network
% .firewall
:- allow(data_source(csv(file(_)))).
:- allow(data_source(json(file(_)))).
:- allow(data_source(sqlite(file(_)))).
:- disallow(data_source(http(_))).
:- disallow(network_access).
Requirement: Require validation before compilation
% .firewall
:- implies(compile(native), validation(passed)).
:- implies(compile(native), tests(passed)).
:- disallow(compile(native)) :- validation(failed).
Requirement: Only allow specific modules
% .firewall
% Whitelist
:- allow(import_module(library(lists))).
:- allow(import_module(library(filesex))).
:- allow(import_module(unifyweaver(core/partitioner))).
% Deny all others
:- disallow(import_module(_)).
Complete example showing firewall integration:
% my_project/.firewall
:- firewall_mode(enforce).
% Only allow SWI-Prolog by default
:- allow(prolog_target(swi)).
% Allow GNU Prolog only if explicitly requested and validated
:- allow(prolog_target(gnu)) :-
option(explicit_gnu_request(true)),
validate_for_dialect(gnu, Predicates, []).
% Disable network access
:- disallow(network_access).
:- disallow(import_module(library(http/_))).
% Require validation
:- require(validation(strict)).
% Generation code
generate_secure(Predicates, Code) :-
% Load firewall
load_firewall_policy('.firewall'),
% Attempt generation (firewall checks automatically)
catch(
generate_prolog_script(Predicates,
[dialect(gnu), compile(true)],
Code),
error(firewall_denied(Operation), Reason),
( format('[Security] Operation ~w denied: ~w~n', [Operation, Reason]),
fail
)
).
You can create custom policy predicates:
% custom_policy.pl
%% safe_for_production(+Dialect, +Options)
% Check if configuration is safe for production
safe_for_production(Dialect, Options) :-
% Must be GNU Prolog
Dialect = gnu,
% Must be compiled
option(compile(true), Options),
% Must have been validated
option(validation(passed), Options),
% Must have test coverage > 80%
option(test_coverage(Coverage), Options),
Coverage >= 80,
% Must not use network
\+ option(network_access(true), Options).
% Use in firewall
:- allow(deployment(production)) :-
safe_for_production(Dialect, Options).
:- set_firewall_flag(trace, true).
% Shows decision process
[Firewall] Checking: prolog_target(gnu)
[Firewall] Rule 1: allow(prolog_target(swi)) - No match
[Firewall] Rule 2: disallow(prolog_target(gnu)) - MATCH
[Firewall] RESULT: DENIED
% Check what would be allowed
?- firewall_allows(prolog_target(gnu)).
false.
?- firewall_allows(prolog_target(swi)).
true.
% Check why something is denied
?- firewall_explain_denial(prolog_target(gnu), Reason).
Reason = 'Explicitly disallowed by policy rule 2'.
Chapter 8 explores fallback mechanisms, showing how the system gracefully handles firewall denials and other failures by trying alternative approaches.
Key Takeaways:
| ← Previous: Chapter 3: Understanding Prolog Dialects | 📖 Book 11: Prolog Target | Next: Chapter 8: Dialect Fallback Mechanisms → |