Sandboxing SWI-Prolog

Greetings. I am working on a new project utilizing ClearScript which facilitates interoperation between .NET and the V8 JavaScript engine. The project also utilizes and updates Uwe Lesta’s SwiPLcs project which facilitates interoperation between .NET and SWI-Prolog.

I am exploring JavaScript API’s for JavaScript-Prolog interoperation: asserting, retracting and querying Prolog from JavaScript, adding foreign predicates from JavaScript, and so forth. So far so good.

I would like to sandbox or secure the SWI-Prolog runtime environment. I would like to not load or to remove the operating system shell functions, file system functions, and so forth. How might I go about this?

A feature request might be a SWI-Prolog initialization flag to request initialization of a secure, sandboxed Prolog runtime environment.

It sounds like you want Pengines. http://www.swi-prolog.org/pldoc/doc_for?object=section('packages/pengines.html')

Thanks for the hyperlink to the interesting SWI-Prolog package.I like the <script type="text/x-prolog"> markup syntax as well as the interoperation between JavaScript and Prolog.

You might also be interested in Tau Prolog, a Prolog interpreter written entirely in JavaScript. It utilizes a <script id="likes.pl" type="text/prolog"> markup syntax.

The specific project I’m working on focuses on scenarios where the JavaScript and Prolog are to be run on the same machines, which could be clients or servers. The project is going to be a component for a few other projects including one involving a new markup language for automated planning and scheduling (resembling an XML-based PDDL).

I’m hoping to sandbox or secure a SWI-Prolog environment which I suppose is done server-side in Pengines scenarios.

Pengines do that. This is based on two libraries: library(sandbox) and in_temporary_module/3 from library(modules). You can look at the pengines code for example usage.

A downside of the Pengines approach is that the Prolog is run in one module. The code sent to a pengine is executed in the context of the module pengine_sandbox and the safety of goals is validated using safe_goal/1 prior to execution. Any pengine has access to the safe predicates defined in library(sandbox).

The JavaScript API I’m hoping to provide for a number of projects utilizes modules.

Here is some source code from the project. By utilizing ClearScript, I initialize a V8 JavaScript engine. Then, I initialize a custom Prolog engine which uses SWI-Prolog. Then, I add some console functionality and the Prolog engine to the JavaScript environment.

using (var engine = new V8ScriptEngine())
{
    using (var prolog = new Prolog())
    {
        engine.AddHostType("Console", typeof(Console));
        engine.AddHostObject("prolog", prolog);

Using the Prolog engine’s API, here is some JavaScript which works:

var module1 = prolog.createModule('module1');
var module2 = prolog.createModule('module2');

module1.assert('p(1, 1)');
module1.assert('p(2, 1)');
module1.assert('p(3, 1)');

module2.assert('p(a, 1)');
module2.assert('p(b, 1)');
module2.assert('p(c, 1)');

for (var r of module1.query('p(X, Y)'))
{
    Console.WriteLine('p({0}, {1})', r['X'], r['Y']);
}

Console.WriteLine();

for (var r of module2.query('p(X, Y)'))
{
    Console.WriteLine('p({0}, {1})', r.X, r.Y);
}

Console.WriteLine();

module1.assertRule('p2(X)', 'p(X, X)');

for (var r of module1.query('p2(X)'))
{
    Console.WriteLine('p2({0})', r.X);
}

module1.addPredicate('p3', 2, function(x, y) { return x.toInteger() > y.toInteger(); });

if (module1.contains('p3(2, 1)')) { Console.WriteLine('2 > 1'); }
if (!module1.contains('p3(1, 2)')) { Console.WriteLine('1 <= 2'); }

The API provides developers with multiple modules, which differs from the Pengines approach. Also, one can observe the smooth registration of foreign predicates from JavaScript with addPredicate.

In summary, the API desires to provide developers with multiple modules and pengines executes Prolog in the context of one module, pengine_sandbox, and validates the safety of goals using safe_goal/1.

What I’m hoping for is one or more flags to initialize SWI-Prolog with so that it only loads a very minimal set of Prolog functionality. In this way, we could secure or sandbox SWI-Prolog while providing use of multiple modules to developers.

1 Like

Interesting! I guess the sandbox module is still a good starting point. The main change would be that the current implementation considers a call to a module through the public API is considered safe. That should change. How is not really clear to me.

Thank you. I will take a closer look at the sandbox module and source code (e.g. http://www.swi-prolog.org/pldoc/doc/SWI/library/sandbox.pl).

I hope for initialization flags to someday be able to initialize SWI Prolog to load a minimal set of functionality and builtins, a secure or sandboxed set, as such approaches are more efficient.

Also, good news. I implemented non-deterministic foreign predicates atop Uwe Lesta’s SwiPlCs and added this functionality to the JavaScript API. When a JavaScript function is added as a foreign predicate, it is called while it returns true, until it returns false, and is called each time with a preserved context parameter (which is expando). An example of adding a non-deterministic foreign predicate can be observed in the following JavaScript in addPredicateNondeterministic.

var module1 = prolog.createModule('module1');
var module2 = prolog.createModule('module2');

module1.assert('p(1, 1)');
module1.assert('p(2, 1)');
module1.assert('p(3, 1)');

module2.assert('p(a, 1)');
module2.assert('p(b, 1)');
module2.assert('p(c, 1)');

for (var r of module1.query('p(X, Y)'))
{
    Console.WriteLine('p({0}, {1})', r['X'], r['Y']);
}

Console.WriteLine();

for (var r of module2.query('p(X, Y)'))
{
    Console.WriteLine('p({0}, {1})', r.X, r.Y);
}

Console.WriteLine();

module1.assertRule('p2(X)', 'p(X, X)');

for (var r of module1.query('p2(X)'))
{
    Console.WriteLine('p2({0})', r.X);
}

Console.WriteLine();

for(var a of module1.getAssertions())
{
    Console.WriteLine(a);
}

Console.WriteLine();

for(var r of module1.getRules())
{
    Console.WriteLine(r);
}

Console.WriteLine();

module1.addPredicate('p3', 2, function(x, y)
{
    return x.toInteger() > y.toInteger();
});

if (module1.contains('p3(2, 1)')) { Console.WriteLine('2 > 1'); }
if (!module1.contains('p3(1, 2)')) { Console.WriteLine('1 <= 2'); }

Console.WriteLine();

var data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

module1.addPredicateNondeterministic('p4', 1, function(x, context)
{
    if(context.counter == undefined)
    {
        context.counter = 0;
    }
    else
    {
        if(++context.counter > 9) return false;
    }

    x.unify(data[context.counter]);
    return true;
});

for(var r of module1.query('p4(X)'))
{
    Console.WriteLine('X = {0}', r['X']);
}

There are definitely other ways than the sandbox library to implement sandboxing. For example you can create a module that imports all safe predicates. Now make sure code is loaded such that it inherits from this module rather than user and the user cannot import or call arbitrary code. Possibly some additional kernel support could create what you want. It isn’t clear to me what isolation and sandboxing you are after though.

Great! is that available?

Yes, the project’s source code is available. I can email it to you.

I just installed the Visual Studio GitHub extension and, at some point in the next month, I will add a new repository to my GitHub area (https://github.com/AdamSobieski?tab=repositories) for the project.

Topics which I intend to explore utilizing the project include: computer-generated programs, self-modifying programs, planning domains which include actions which can invoke JavaScript and Prolog, as well as the obtaining and utilization of resources from URL’s, as per:

<script type="text/prolog" src="http://www.otherdomain.com/resource.pl" />

and, as the project adds a Prolog API to its global JavaScript environment:

<script type="text/javascript" src="http://www.otherdomain.com/resource.js" />

where such markup could exist in either HTML files or XML-based planning domains. One of the goals of the project is to facilitate the rapid prototyping of new programming languages and markup languages which utilize JavaScript and Prolog, e.g. XML-based planning domain markup languages with <script> elements.

As for securing, sandboxing, or isolating instances of SWI Prolog, I would like to provide developers with high performance and as much cool functionality as possible (e.g. multi-module programming, meta-programming, and so forth) while simultaneously ensuring the security of processing environments.

I’m hoping for, in upcoming years, some initialization flags, for command line and PL_initialise() use, with which to indicate for SWI Prolog to load a very minimal and secure set of Prolog functionality.

1 Like

Just a little note: are you aware of the WebAssembly version? See http://www.swi-prolog.org/build/WebAssembly.txt

1 Like

That is interesting. While the V8 JavaScript engine supports WebAssembly, I’m not sure when ClearScript shall. ClearScript provides the .NET interfaces to the V8 JavaScript engine. I could ask the ClearScript developer team about that.

Also, the source code for the project I was mentioning is now available online: https://github.com/AdamSobieski/Logic .