5.2.6. C Integration: Sandbox

This tutorial demonstrates how to run daslang code in a sandboxed environment from a C host. The sandbox has two independent layers:

Layer

Controls

API

Filesystem lock

Which files exist (physical)

das_fileaccess_introduce_*, das_fileaccess_lock

Policy (.das_project)

What is allowed among existing files

das_fileaccess_make_project, callback functions

5.2.6.1. Filesystem locking

The filesystem lock prevents the compiler from accessing any file that was not pre-introduced into the file access cache:

das_file_access * fa = das_fileaccess_make_project(project_path);

// Pre-cache all standard library files
das_fileaccess_introduce_daslib(fa);

// Introduce user scripts as virtual files (inline C strings)
das_fileaccess_introduce_file(fa, "helpers.das", HELPER_SOURCE, 0);
das_fileaccess_introduce_file(fa, "script.das", SCRIPT_SOURCE, 0);

// Lock — getNewFileInfo() now returns null for uncached files
das_fileaccess_lock(fa);

The 0 in das_fileaccess_introduce_file means the file access borrows the string pointer (no copy) — suitable for static C string constants. Pass 1 for dynamically allocated strings that should be owned and freed by the file access.

Available introduce functions:

Function

Purpose

das_fileaccess_introduce_file

Register a virtual file from a string

das_fileaccess_introduce_file_from_disk

Read a disk file into cache

das_fileaccess_introduce_daslib

Cache all daslib/*.das files

das_fileaccess_introduce_native_modules

Cache all native plugin modules

das_fileaccess_introduce_native_module

Cache a single native module

5.2.6.2. The .das_project file

A .das_project is a regular daslang file that the compiler loads before compiling user scripts. It exports [export] callback functions that the compiler invokes during compilation.

The project file is loaded via das_fileaccess_make_project:

das_file_access * fa = das_fileaccess_make_project(
    "path/to/project.das_project");

Internally, the constructor:

  1. Compiles the .das_project file as a normal daslang program

  2. Simulates it in a fresh context

  3. Looks up [export] functions by name (module_get, module_allowed, etc.)

  4. Runs [init] functions

  5. Sets the DAS_PAK_ROOT variable to the project file’s directory

The project file itself is not subject to sandbox restrictions — it compiles with full filesystem access. Restrictions only apply to subsequent user script compilations.

5.2.6.3. Callback reference

The compiler recognizes these exported callback function names:

Function

Required

Purpose

module_get(req, from : string) : module_info

Yes

Resolve require paths to (moduleName, fileName, importName)

module_allowed(mod, filename : string) : bool

No

Whitelist which modules can be loaded

module_allowed_unsafe(mod, filename : string) : bool

No

Control whether unsafe blocks are permitted

option_allowed(opt, from : string) : bool

No

Whitelist which options directives are accepted

annotation_allowed(ann, from : string) : bool

No

Whitelist which annotations are accepted

can_module_be_required(mod, fname : string; isPublic : bool) : bool

No

Fine-grained control over require statements

is_pod_in_scope_allowed(moduleName, fileName : string) : bool

No

Control var inscope for POD types

include_get(inc, from : string) : string

No

Resolve include directives

is_same_file_name(a, b : string) : bool

No

Custom file path comparison

dyn_modules_folder() : string

No

Set dynamic module search folder

module_info is defined as tuple<string; string; string> const — the three fields are (moduleName, fileName, importName).

Callbacks that are not exported fall back to permissive defaults (everything allowed). If module_get is missing, the project is treated as failed and all callbacks revert to defaults.

This tutorial demonstrates the five most impactful callbacks.

5.2.6.3.1. module_get — path resolution

When a .das_project is active, the built-in daslib/ resolution logic is completely bypassedmodule_get is the sole path resolver. It must handle daslib/X paths as well as relative module paths:

[export]
def module_get(req, from : string) : module_info {
    let rs <- split_by_chars(req, "./")
    let mod_name = rs[length(rs) - 1]
    if (length(rs) == 2 && rs[0] == "daslib") {
        return (mod_name, "{get_das_root()}/daslib/{mod_name}.das", "")
    }
    // Resolve relative to the requiring file
    var fr <- split_by_chars(from, "/")
    if (length(fr) > 0) { pop(fr) }
    for (se in rs) { push(fr, se) }
    return (mod_name, join(fr, "/") + ".das", "")
}

5.2.6.3.2. module_allowed — module whitelist

Called for every module (native C++ and compiled .das) that is loaded during compilation. The mod parameter is the short module name — for example "strings", "fio", "sandbox_helpers" — not the require path.

The built-in core module "$" must always be whitelisted:

[export]
def module_allowed(mod, filename : string) : bool {
    if (mod == "$") { return true }
    if (mod == "math" || mod == "strings") { return true }
    if (mod == "strings_boost" || mod == "sandbox_helpers") { return true }
    return false
}

Returning false produces a module not allowed compilation error.

5.2.6.3.3. module_allowed_unsafe — forbid unsafe

Called once per compiled module. Returning false forbids all unsafe blocks in that module:

[export]
def module_allowed_unsafe(mod, filename : string) : bool {
    return false   // no module may use unsafe
}

5.2.6.3.4. option_allowed — restrict options

Called during parsing for each options declaration. Returning false rejects the option with an invalid option error:

[export]
def option_allowed(opt, from : string) : bool {
    if (opt == "gen2" || opt == "indenting") { return true }
    return false
}

5.2.6.3.5. annotation_allowed — restrict annotations

Called for each annotation used in the script:

[export]
def annotation_allowed(ann, from : string) : bool {
    if (ann == "export" || ann == "private") { return true }
    return false
}

5.2.6.4. The C host code

The tutorial embeds four inline daslang scripts as C string constants:

  • HELPER_MODULE — a utility module (sandbox_helpers) with two simple functions (clamp_value and greet)

  • VALID_SCRIPT — requires allowed modules, calls helper functions

  • VIOLATES_OPTION — uses options persistent_heap (banned)

  • VIOLATES_UNSAFE — uses an unsafe block (forbidden)

  • VIOLATES_MODULE — requires fio (not whitelisted)

5.2.6.4.1. Setting up the sandbox

// 1. Load the .das_project
das_file_access * fa = das_fileaccess_make_project(project_path);

// 2. Pre-cache daslib (needed for module path resolution)
das_fileaccess_introduce_daslib(fa);

// 3. Introduce user files
das_fileaccess_introduce_file(fa, "sandbox_helpers.das", HELPER_MODULE, 0);
das_fileaccess_introduce_file(fa, "user_script.das", VALID_SCRIPT, 0);

// 4. Lock filesystem
das_fileaccess_lock(fa);

// 5. Compile — only cached files accessible, policy enforced
das_program * program = das_program_compile("user_script.das",
                                             fa, tout, module_group);

5.2.6.4.2. Reporting violations

When policy violations occur, the compiler reports them as errors. Iterate das_program_get_error and format with das_error_report:

int err_count = das_program_err_count(program);
for (int i = 0; i < err_count; i++) {
    das_error * error = das_program_get_error(program, i);
    char buf[1024];
    das_error_report(error, buf, sizeof(buf));
    printf("  error %d: %s\n", i, buf);
}

5.2.6.5. Building and running

Build with CMake:

cmake --build build --config Release --target integration_c_06

Run:

bin/Release/integration_c_06

Expected output (Part 1 — valid script):

=== Part 1: Valid script in sandbox ===

clamped: 100
Hello, sandbox!
split: [[ a; b; c]]

Expected output (Part 2 — banned option):

=== Part 2: Banned option ===

Compilation produced 1 error(s):
  error 0: option persistent_heap is not allowed here ...

Expected output (Part 3 — unsafe forbidden):

=== Part 3: Unsafe block forbidden ===

Compilation produced 1 error(s):
  error 0: unsafe function test ...

Expected output (Part 4 — banned module):

=== Part 4: Banned module ===

Compilation produced 1 error(s):
  error 0: module not allowed 'fio' ...

Expected output (Part 5 — CodeOfPolicies via C API):

=== Part 5: CodeOfPolicies via C API ===

Compilation with DAS_POLICY_NO_UNSAFE produced 1 error(s):
  error 0: unsafe function test ...

Part 5 uses das_policies_make and das_policies_set_bool to set DAS_POLICY_NO_UNSAFE directly from C, without a .das_project file. The error is identical to the one produced by the project-based sandbox in Part 3.

See also

Full source: 06_sandbox.c, 06_sandbox.das_project

Previous tutorial: tutorial_integration_c_unaligned_advanced

Next tutorial: tutorial_integration_c_context_variables

type_mangling — complete type mangling reference

daScriptC.h API header: include/daScript/daScriptC.h