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) |
|
Policy (.das_project) |
What is allowed among existing files |
|
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 |
|---|---|
|
Register a virtual file from a string |
|
Read a disk file into cache |
|
Cache all |
|
Cache all native plugin modules |
|
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:
Compiles the
.das_projectfile as a normal daslang programSimulates it in a fresh context
Looks up
[export]functions by name (module_get,module_allowed, etc.)Runs
[init]functionsSets the
DAS_PAK_ROOTvariable 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 |
|---|---|---|
|
Yes |
Resolve |
|
No |
Whitelist which modules can be loaded |
|
No |
Control whether |
|
No |
Whitelist which |
|
No |
Whitelist which annotations are accepted |
|
No |
Fine-grained control over |
|
No |
Control |
|
No |
Resolve |
|
No |
Custom file path comparison |
|
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 bypassed — module_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_valueandgreet)VALID_SCRIPT — requires allowed modules, calls helper functions
VIOLATES_OPTION — uses
options persistent_heap(banned)VIOLATES_UNSAFE — uses an
unsafeblock (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