This guide walks through creating a sandboxed Python application step by step.
Basic Workflow
Every nono application follows the same pattern:
Check support - Verify sandboxing is available
Build capabilities - Define what the process can access
Apply sandbox - Lock down the process (irreversible)
Run application - Execute your code within the sandbox
Always verify sandboxing is available before relying on it:
from nono_py import is_supported, support_info
if not is_supported():
info = support_info()
print ( f "Sandboxing not available: { info.details } " )
exit ( 1 )
Step 2: Build Capabilities
Create a CapabilitySet and grant the permissions your application needs:
from nono_py import CapabilitySet, AccessMode
caps = CapabilitySet()
# Grant directory access (recursive)
caps.allow_path( "/tmp" , AccessMode. READ_WRITE )
caps.allow_path( "/home/user/data" , AccessMode. READ )
# Grant single file access
caps.allow_file( "/etc/hosts" , AccessMode. READ )
# Block network access
caps.block_network()
Access Modes
Mode Description AccessMode.READRead-only access AccessMode.WRITEWrite-only access AccessMode.READ_WRITEBoth read and write
Only grant the minimum permissions your application needs. Excessive permissions defeat the purpose of sandboxing.
Step 3: Apply the Sandbox
Once you’ve defined capabilities, apply them:
from nono_py import apply
apply(caps)
print ( "Sandbox applied successfully!" )
This is irreversible. After apply(), there is no way to expand permissions. The sandbox persists for the lifetime of the process and all child processes.
Step 4: Run Your Application
After applying the sandbox, your code runs with restricted permissions:
# This works - /tmp is allowed
with open ( "/tmp/output.txt" , "w" ) as f:
f.write( "Hello from sandbox!" )
# This fails - /etc/passwd is not allowed
try :
with open ( "/etc/passwd" , "r" ) as f:
print (f.read())
except PermissionError :
print ( "Access denied (expected)" )
Complete Example
Here’s a complete sandboxed application:
#!/usr/bin/env python3
"""Example sandboxed application."""
from nono_py import (
CapabilitySet,
AccessMode,
apply,
is_supported,
support_info,
)
def main ():
# Check platform support
if not is_supported():
info = support_info()
print ( f "Error: { info.details } " )
return 1
# Build capabilities
caps = CapabilitySet()
caps.allow_path( "/tmp" , AccessMode. READ_WRITE )
caps.block_network()
# Show what we're granting
print ( "Capabilities:" )
print (caps.summary())
# Apply sandbox
apply(caps)
print ( " \n Sandbox applied! \n " )
# Run sandboxed code
test_file = "/tmp/nono_test.txt"
# Write to allowed path
with open (test_file, "w" ) as f:
f.write( "Hello from sandbox!" )
print ( f "Wrote to { test_file } " )
# Read from allowed path
with open (test_file, "r" ) as f:
content = f.read()
print ( f "Read: { content } " )
# Try to access disallowed path
try :
with open ( "/etc/passwd" , "r" ) as f:
f.read()
except PermissionError :
print ( "Blocked access to /etc/passwd (expected)" )
return 0
if __name__ == "__main__" :
exit (main())
Query Without Applying
Use QueryContext to check permissions without applying the sandbox:
from nono_py import CapabilitySet, AccessMode, QueryContext
caps = CapabilitySet()
caps.allow_path( "/tmp" , AccessMode. READ )
# Create query context
ctx = QueryContext(caps)
# Check if operations would be allowed
result = ctx.query_path( "/tmp/file.txt" , AccessMode. READ )
print (result) # {'status': 'allowed', 'reason': 'granted_path', ...}
result = ctx.query_path( "/etc/passwd" , AccessMode. READ )
print (result) # {'status': 'denied', 'reason': 'path_not_granted'}
result = ctx.query_network()
print (result) # {'status': 'allowed', 'reason': 'network_allowed'}
This is useful for:
Validating configurations before deployment
Building permission-checking UIs
Testing capability sets
Next Steps
API Reference Full documentation for all classes and functions
Examples Real-world usage patterns and recipes