security: disable workspace-local plugin autoload by default (P0 RCE)
ober
37ce5bb987013ec1d90c94bd363d0ef19f55367a
--- a/src/jcode/core/plugin.ss +++ b/src/jcode/core/plugin.ss @@ -15,15 +15,30 @@ (def *loaded-plugins* '()) +(def (workspace-plugins-allowed?) + "Workspace-local plugins (<cwd>/.jcode/plugins) are untrusted: a cloned + repository could ship one and obtain silent code execution at startup. + They are disabled unless the user explicitly opts in via the + JCODE_ALLOW_WORKSPACE_PLUGINS env var or the pluginAllowWorkspace config." + (or (let ((v (getenv "JCODE_ALLOW_WORKSPACE_PLUGINS"))) + (and v (not (string=? v "")) (not (string=? v "0")))) + (config-ref "pluginAllowWorkspace"))) + (def (plugin-dirs) "Return list of directories to scan for plugins." (let ((home-dir (path-join (jcode-home) "plugins")) (local-dir (path-join (current-directory) ".jcode" "plugins")) (cfg-dirs (config-ref "pluginDirs"))) + (unless (workspace-plugins-allowed?) + (when (file-directory? local-dir) + (log-warn logger "workspace-plugins-skipped" + `((dir . ,local-dir) + (hint . "set JCODE_ALLOW_WORKSPACE_PLUGINS=1 to load workspace-local plugins"))))) (filter file-directory? (append (if (and cfg-dirs (list? cfg-dirs)) cfg-dirs '()) - (list home-dir local-dir))))) + (list home-dir) + (if (workspace-plugins-allowed?) (list local-dir) '()))))) (def (find-plugins) "Find all .ss files in plugin directories." --- a/test/security-regression.ss +++ b/test/security-regression.ss @@ -5,6 +5,7 @@ (jcode core agent-defs) (jcode core debug-repl) (jcode core remote-auth) + (jcode core plugin) (std misc ports) (std misc string) (std net tcp)) @@ -135,6 +136,39 @@ (refused? (lambda () (start-jcode-repl! 0 "0.0.0.0")))) (putenv "HOME" saved-home) +;; ── (a) workspace plugin auto-load RCE ──────────────────────────────── +;; cwd is the repo root here; a malicious <cwd>/.jcode/plugins/*.ss must NOT +;; be evaluated at init unless the user explicitly opts in. +(define plugin-dir (string-append (current-directory) "/.jcode/plugins")) +(define plugin-evil (string-append plugin-dir "/evil.ss")) +(define plugin-marker "/tmp/jcode-sec-plugin-marker") +(define plugin-home "/tmp/jcode-sec-plugin-home") +(define plugin-saved-home (getenv "HOME")) +(define plugin-saved-opt (getenv "JCODE_ALLOW_WORKSPACE_PLUGINS")) +(define plugin-had-dir (file-directory? plugin-dir)) +(system (string-append "mkdir -p '" plugin-home "'")) +(unless plugin-had-dir (mkdir plugin-dir)) +(call-with-output-file plugin-evil + (lambda (p) + (display (string-append "(call-with-output-file \"" plugin-marker + "\" (lambda (pp) (display \"pwned\" pp)))") p))) +(putenv "HOME" plugin-home) +(putenv "JCODE_ALLOW_WORKSPACE_PLUGINS" "0") +(when (file-exists? plugin-marker) (delete-file plugin-marker)) +(init-plugins) +(check "workspace plugin NOT auto-loaded without opt-in (no RCE)" + (not (file-exists? plugin-marker))) +(putenv "JCODE_ALLOW_WORKSPACE_PLUGINS" "1") +(init-plugins) +(check "workspace plugin loads with explicit opt-in" + (file-exists? plugin-marker)) +(putenv "HOME" plugin-saved-home) +(putenv "JCODE_ALLOW_WORKSPACE_PLUGINS" (or plugin-saved-opt "0")) +(when (file-exists? plugin-evil) (delete-file plugin-evil)) +(unless plugin-had-dir (system (string-append "rmdir '" plugin-dir "'"))) +(when (file-exists? plugin-marker) (delete-file plugin-marker)) +(system (string-append "rm -rf '" plugin-home "'")) + (when (> failures 0) (error 'security-regression (format "~a security regression test(s) failed" failures))) (printf "Security regressions passed~n")