3939#include "py_nif.h"
4040#include "py_asgi.h"
4141#include "py_wsgi.h"
42+ #include "py_sandbox.h"
4243
4344/* ============================================================================
4445 * Global state definitions
@@ -138,6 +139,7 @@ static ERL_NIF_TERM build_suspended_result(ErlNifEnv *env, suspended_state_t *su
138139#include "py_event_loop.c"
139140#include "py_asgi.c"
140141#include "py_wsgi.c"
142+ #include "py_sandbox.c"
141143
142144/* ============================================================================
143145 * Resource callbacks
@@ -155,6 +157,12 @@ static void worker_destructor(ErlNifEnv *env, void *obj) {
155157 close (worker -> callback_pipe [1 ]);
156158 }
157159
160+ /* Clean up sandbox policy */
161+ if (worker -> sandbox != NULL ) {
162+ sandbox_policy_destroy (worker -> sandbox );
163+ worker -> sandbox = NULL ;
164+ }
165+
158166 /* Only clean up Python state if Python is still initialized */
159167 if (worker -> thread_state != NULL && g_python_initialized ) {
160168 PyEval_RestoreThread (worker -> thread_state );
@@ -415,6 +423,13 @@ static ERL_NIF_TERM nif_py_init(ErlNifEnv *env, int argc, const ERL_NIF_TERM arg
415423 /* Detect execution mode based on Python version and build */
416424 detect_execution_mode ();
417425
426+ /* Initialize sandbox system (audit hooks) */
427+ if (init_sandbox_system () < 0 ) {
428+ Py_Finalize ();
429+ g_python_initialized = false;
430+ return make_error (env , "sandbox_init_failed" );
431+ }
432+
418433 /* Save main thread state and release GIL for other threads */
419434 g_main_thread_state = PyEval_SaveThread ();
420435
@@ -535,9 +550,6 @@ static ERL_NIF_TERM nif_finalize(ErlNifEnv *env, int argc, const ERL_NIF_TERM ar
535550 * ============================================================================ */
536551
537552static ERL_NIF_TERM nif_worker_new (ErlNifEnv * env , int argc , const ERL_NIF_TERM argv []) {
538- (void )argc ;
539- (void )argv ;
540-
541553 if (!g_python_initialized ) {
542554 return make_error (env , "python_not_initialized" );
543555 }
@@ -547,6 +559,28 @@ static ERL_NIF_TERM nif_worker_new(ErlNifEnv *env, int argc, const ERL_NIF_TERM
547559 return make_error (env , "alloc_failed" );
548560 }
549561
562+ /* Initialize sandbox to NULL */
563+ worker -> sandbox = NULL ;
564+
565+ /* Parse options if provided */
566+ if (argc > 0 && enif_is_map (env , argv [0 ])) {
567+ ERL_NIF_TERM sandbox_key = enif_make_atom (env , "sandbox" );
568+ ERL_NIF_TERM sandbox_opts ;
569+ if (enif_get_map_value (env , argv [0 ], sandbox_key , & sandbox_opts )) {
570+ /* Create and configure sandbox policy */
571+ worker -> sandbox = sandbox_policy_new ();
572+ if (worker -> sandbox == NULL ) {
573+ enif_release_resource (worker );
574+ return make_error (env , "sandbox_alloc_failed" );
575+ }
576+ if (parse_sandbox_options (env , sandbox_opts , worker -> sandbox ) < 0 ) {
577+ sandbox_policy_destroy (worker -> sandbox );
578+ enif_release_resource (worker );
579+ return make_error (env , "invalid_sandbox_options" );
580+ }
581+ }
582+ }
583+
550584 /* Acquire GIL to create thread state */
551585 PyGILState_STATE gstate = PyGILState_Ensure ();
552586
@@ -993,6 +1027,77 @@ static ERL_NIF_TERM nif_send_callback_response(ErlNifEnv *env, int argc, const E
9931027 return ATOM_OK ;
9941028}
9951029
1030+ /* ============================================================================
1031+ * Sandbox control NIFs
1032+ * ============================================================================ */
1033+
1034+ static ERL_NIF_TERM nif_sandbox_set_policy (ErlNifEnv * env , int argc , const ERL_NIF_TERM argv []) {
1035+ (void )argc ;
1036+ py_worker_t * worker ;
1037+
1038+ if (!enif_get_resource (env , argv [0 ], WORKER_RESOURCE_TYPE , (void * * )& worker )) {
1039+ return make_error (env , "invalid_worker" );
1040+ }
1041+
1042+ /* Create policy if it doesn't exist */
1043+ if (worker -> sandbox == NULL ) {
1044+ worker -> sandbox = sandbox_policy_new ();
1045+ if (worker -> sandbox == NULL ) {
1046+ return make_error (env , "sandbox_alloc_failed" );
1047+ }
1048+ }
1049+
1050+ /* Update policy with new options */
1051+ if (sandbox_policy_update (env , worker -> sandbox , argv [1 ]) < 0 ) {
1052+ return make_error (env , "invalid_policy" );
1053+ }
1054+
1055+ return ATOM_OK ;
1056+ }
1057+
1058+ static ERL_NIF_TERM nif_sandbox_enable (ErlNifEnv * env , int argc , const ERL_NIF_TERM argv []) {
1059+ (void )argc ;
1060+ py_worker_t * worker ;
1061+
1062+ if (!enif_get_resource (env , argv [0 ], WORKER_RESOURCE_TYPE , (void * * )& worker )) {
1063+ return make_error (env , "invalid_worker" );
1064+ }
1065+
1066+ char enabled [16 ];
1067+ if (!enif_get_atom (env , argv [1 ], enabled , sizeof (enabled ), ERL_NIF_LATIN1 )) {
1068+ return make_error (env , "invalid_enabled" );
1069+ }
1070+
1071+ bool enable = (strcmp (enabled , "true" ) == 0 );
1072+
1073+ if (worker -> sandbox == NULL ) {
1074+ if (!enable ) {
1075+ /* Already disabled - no sandbox exists */
1076+ return ATOM_OK ;
1077+ }
1078+ /* Need to enable but no sandbox - create one with empty policy */
1079+ worker -> sandbox = sandbox_policy_new ();
1080+ if (worker -> sandbox == NULL ) {
1081+ return make_error (env , "sandbox_alloc_failed" );
1082+ }
1083+ }
1084+
1085+ sandbox_policy_set_enabled (worker -> sandbox , enable );
1086+ return ATOM_OK ;
1087+ }
1088+
1089+ static ERL_NIF_TERM nif_sandbox_get_policy (ErlNifEnv * env , int argc , const ERL_NIF_TERM argv []) {
1090+ (void )argc ;
1091+ py_worker_t * worker ;
1092+
1093+ if (!enif_get_resource (env , argv [0 ], WORKER_RESOURCE_TYPE , (void * * )& worker )) {
1094+ return make_error (env , "invalid_worker" );
1095+ }
1096+
1097+ ERL_NIF_TERM policy_map = sandbox_policy_to_term (env , worker -> sandbox );
1098+ return enif_make_tuple2 (env , ATOM_OK , policy_map );
1099+ }
1100+
9961101/* ============================================================================
9971102 * Async worker NIFs
9981103 * ============================================================================ */
@@ -1827,6 +1932,11 @@ static ErlNifFunc nif_funcs[] = {
18271932 {"send_callback_response" , 2 , nif_send_callback_response , 0 },
18281933 {"resume_callback" , 2 , nif_resume_callback , 0 },
18291934
1935+ /* Sandbox support */
1936+ {"sandbox_set_policy" , 2 , nif_sandbox_set_policy , 0 },
1937+ {"sandbox_enable" , 2 , nif_sandbox_enable , 0 },
1938+ {"sandbox_get_policy" , 1 , nif_sandbox_get_policy , 0 },
1939+
18301940 /* Async worker management */
18311941 {"async_worker_new" , 0 , nif_async_worker_new , 0 },
18321942 {"async_worker_destroy" , 1 , nif_async_worker_destroy , 0 },
0 commit comments