@@ -504,6 +504,113 @@ Worker::~Worker() {
504504 Debug (this , " Worker %llu destroyed" , thread_id_.id );
505505}
506506
507+
508+ // When the parent has --permission enabled, explicit Worker execArgv (including
509+ // []) must not yield a wider grant set than the parent. Enforcement is on the
510+ // C++ side after options are parsed so NODE_OPTIONS and repeated --allow-* are
511+ // already reflected in EnvironmentOptions (no JS process.execArgv copying).
512+ //
513+ // If the worker did not configure Permission Model flags at all, treat requested
514+ // grants as unrestricted under the model so the intersection equals the parent
515+ // grant set. If the worker did configure permission flags, intersect with parent.
516+
517+ static bool WorkerConfiguredPermission (const EnvironmentOptions* w) {
518+ if (w->permission || w->permission_audit ) return true ;
519+ if (!w->allow_fs_read .empty () || !w->allow_fs_write .empty ()) return true ;
520+ if (w->allow_addons || w->allow_inspector || w->allow_child_process ||
521+ w->allow_net || w->allow_wasi || w->allow_ffi || w->allow_openssl_store ||
522+ w->allow_worker_threads ) {
523+ return true ;
524+ }
525+ return false ;
526+ }
527+
528+ static bool PathGrantedByParentList (const std::vector<std::string>& parent_paths,
529+ const std::string& requested) {
530+ if (parent_paths.empty ()) return false ;
531+ for (const std::string& p : parent_paths) {
532+ if (p == " *" || p == requested) return true ;
533+ if (p.empty () || requested.size () < p.size ()) continue ;
534+ if (requested.compare (0 , p.size (), p) != 0 ) continue ;
535+ if (requested.size () == p.size ()) return true ;
536+ if (p.back () == ' /' || requested[p.size ()] == ' /' ) return true ;
537+ }
538+ return false ;
539+ }
540+
541+ static void IntersectPathList (std::vector<std::string>* worker,
542+ const std::vector<std::string>& parent) {
543+ if (worker == nullptr || worker->empty ()) return ;
544+ std::vector<std::string> out;
545+ out.reserve (worker->size ());
546+ for (const std::string& wpath : *worker) {
547+ if (wpath == " *" ) {
548+ for (const std::string& p : parent) {
549+ if (p == " *" ) {
550+ out.push_back (wpath);
551+ break ;
552+ }
553+ }
554+ continue ;
555+ }
556+ if (PathGrantedByParentList (parent, wpath)) out.push_back (wpath);
557+ }
558+ *worker = std::move (out);
559+ }
560+
561+ static void CopyParentPermissionGrants (EnvironmentOptions* w,
562+ const EnvironmentOptions* parent) {
563+ w->permission = parent->permission ;
564+ w->permission_audit = parent->permission_audit ;
565+ w->allow_addons = parent->allow_addons ;
566+ w->allow_inspector = parent->allow_inspector ;
567+ w->allow_child_process = parent->allow_child_process ;
568+ w->allow_net = parent->allow_net ;
569+ w->allow_wasi = parent->allow_wasi ;
570+ w->allow_ffi = parent->allow_ffi ;
571+ w->allow_openssl_store = parent->allow_openssl_store ;
572+ w->allow_worker_threads = parent->allow_worker_threads ;
573+ w->allow_fs_read = parent->allow_fs_read ;
574+ w->allow_fs_write = parent->allow_fs_write ;
575+ }
576+
577+ static void IntersectPermissionGrants (EnvironmentOptions* w,
578+ const EnvironmentOptions* parent) {
579+ w->permission = true ;
580+ w->permission_audit = w->permission_audit || parent->permission_audit ;
581+
582+ w->allow_addons = w->allow_addons && parent->allow_addons ;
583+ w->allow_inspector = w->allow_inspector && parent->allow_inspector ;
584+ w->allow_child_process = w->allow_child_process && parent->allow_child_process ;
585+ w->allow_net = w->allow_net && parent->allow_net ;
586+ w->allow_wasi = w->allow_wasi && parent->allow_wasi ;
587+ w->allow_ffi = w->allow_ffi && parent->allow_ffi ;
588+ w->allow_openssl_store = w->allow_openssl_store && parent->allow_openssl_store ;
589+ w->allow_worker_threads =
590+ w->allow_worker_threads && parent->allow_worker_threads ;
591+
592+ IntersectPathList (&w->allow_fs_read , parent->allow_fs_read );
593+ IntersectPathList (&w->allow_fs_write , parent->allow_fs_write );
594+ }
595+
596+ static void ClampWorkerPermissionToParent (Environment* env,
597+ PerIsolateOptions* worker_opts) {
598+ if (worker_opts == nullptr || !env->permission ()->enabled ()) return ;
599+
600+ EnvironmentOptions* parent =
601+ env->isolate_data ()->options ()->get_per_env_options ();
602+ EnvironmentOptions* w = worker_opts->get_per_env_options ();
603+ if (parent == nullptr || w == nullptr ) return ;
604+
605+ if (!WorkerConfiguredPermission (w)) {
606+ CopyParentPermissionGrants (w, parent);
607+ w->permission = true ;
608+ return ;
609+ }
610+
611+ IntersectPermissionGrants (w, parent);
612+ }
613+
507614void Worker::New (const FunctionCallbackInfo<Value>& args) {
508615 Environment* env = Environment::GetCurrent (args);
509616 THROW_IF_INSUFFICIENT_PERMISSIONS (
@@ -688,6 +795,10 @@ void Worker::New(const FunctionCallbackInfo<Value>& args) {
688795 // essential to load user codes and must not be blocked by the inspector
689796 // for internal scripts.
690797 // Still, `--inspect-node` can break on the first line of internal scripts.
798+ if (env->permission ()->enabled () && per_isolate_opts) {
799+ ClampWorkerPermissionToParent (env, per_isolate_opts.get ());
800+ }
801+
691802 if (is_internal) {
692803 per_isolate_opts->per_env ->get_debug_options ()
693804 ->DisableWaitOrBreakFirstLine ();
0 commit comments