1515call_user_func (function () {
1616 $ cwd = null ;
1717 $ cwdFromArg = false ;
18+ $ forceTrust = false ;
19+ $ forceUntrust = false ;
1820
1921 // Find the cwd arg (if present)
2022 $ argv = isset ($ _SERVER ['argv ' ]) ? $ _SERVER ['argv ' ] : array ();
@@ -34,6 +36,14 @@ call_user_func(function () {
3436 $ cwdFromArg = true ;
3537 continue ;
3638 }
39+
40+ if ($ arg === '--trust-project ' ) {
41+ $ forceTrust = true ;
42+ $ forceUntrust = false ;
43+ } elseif ($ arg === '--no-trust-project ' ) {
44+ $ forceUntrust = true ;
45+ $ forceTrust = false ;
46+ }
3747 }
3848
3949 if ($ cwdFromArg ) {
@@ -72,6 +82,131 @@ call_user_func(function () {
7282 $ argv = $ filtered ;
7383 }
7484
85+ if (isset ($ _SERVER ['PSYSH_TRUST_PROJECT ' ]) && $ _SERVER ['PSYSH_TRUST_PROJECT ' ] !== '' ) {
86+ $ mode = strtolower (trim ($ _SERVER ['PSYSH_TRUST_PROJECT ' ]));
87+ if (in_array ($ mode , array ('true ' , '1 ' ))) {
88+ $ forceTrust = true ;
89+ $ forceUntrust = false ;
90+ } elseif (in_array ($ mode , array ('false ' , '0 ' ))) {
91+ $ forceUntrust = true ;
92+ $ forceTrust = false ;
93+ } else {
94+ fwrite (STDERR , 'Invalid PSYSH_TRUST_PROJECT value: ' . $ _SERVER ['PSYSH_TRUST_PROJECT ' ] . '. Expected: true, 1, false, or 0. ' . PHP_EOL );
95+ exit (1 );
96+ }
97+ }
98+
99+ // Pass trust decision via env var and strip CLI flags. This allows a local
100+ // psysh version to read the trust state while avoiding errors on older
101+ // versions that don't understand --trust-project flags.
102+ if ($ forceTrust ) {
103+ $ _SERVER ['PSYSH_TRUST_PROJECT ' ] = 'true ' ;
104+ $ _ENV ['PSYSH_TRUST_PROJECT ' ] = 'true ' ;
105+ putenv ('PSYSH_TRUST_PROJECT=true ' );
106+ } elseif ($ forceUntrust ) {
107+ $ _SERVER ['PSYSH_TRUST_PROJECT ' ] = 'false ' ;
108+ $ _ENV ['PSYSH_TRUST_PROJECT ' ] = 'false ' ;
109+ putenv ('PSYSH_TRUST_PROJECT=false ' );
110+ }
111+
112+ if ($ forceTrust || $ forceUntrust ) {
113+ $ filtered = array ();
114+ foreach ($ argv as $ arg ) {
115+ if ($ arg === '--trust-project ' || $ arg === '--no-trust-project ' ) {
116+ continue ;
117+ }
118+ $ filtered [] = $ arg ;
119+ }
120+ $ _SERVER ['argv ' ] = $ filtered ;
121+ $ _SERVER ['argc ' ] = count ($ filtered );
122+ $ argv = $ filtered ;
123+ }
124+
125+ $ trustedRoots = array ();
126+ if (!$ forceTrust ) {
127+ // Find the current config directory (matching ConfigPaths logic)
128+ $ currentConfigDir = null ;
129+ $ fallbackConfigDir = null ;
130+
131+ // Windows: %APPDATA%/PsySH takes priority
132+ if ($ currentConfigDir === null && defined ('PHP_WINDOWS_VERSION_MAJOR ' )) {
133+ if (isset ($ _SERVER ['APPDATA ' ]) && $ _SERVER ['APPDATA ' ]) {
134+ $ dir = str_replace ('\\' , '/ ' , $ _SERVER ['APPDATA ' ]).'/PsySH ' ;
135+ $ fallbackConfigDir = $ fallbackConfigDir !== null ? $ fallbackConfigDir : $ dir ;
136+ if (@is_dir ($ dir )) {
137+ $ currentConfigDir = $ dir ;
138+ }
139+ }
140+ }
141+
142+ // XDG_CONFIG_HOME/psysh
143+ if ($ currentConfigDir === null && isset ($ _SERVER ['XDG_CONFIG_HOME ' ]) && $ _SERVER ['XDG_CONFIG_HOME ' ]) {
144+ $ dir = rtrim (str_replace ('\\' , '/ ' , $ _SERVER ['XDG_CONFIG_HOME ' ]), '/ ' ).'/psysh ' ;
145+ $ fallbackConfigDir = $ fallbackConfigDir !== null ? $ fallbackConfigDir : $ dir ;
146+ if (@is_dir ($ dir )) {
147+ $ currentConfigDir = $ dir ;
148+ }
149+ }
150+
151+ // HOME/.config/psysh (default XDG location)
152+ if ($ currentConfigDir === null && isset ($ _SERVER ['HOME ' ]) && $ _SERVER ['HOME ' ]) {
153+ $ home = rtrim (str_replace ('\\' , '/ ' , $ _SERVER ['HOME ' ]), '/ ' );
154+
155+ $ dir = $ home .'/.config/psysh ' ;
156+ $ fallbackConfigDir = $ fallbackConfigDir !== null ? $ fallbackConfigDir : $ dir ;
157+ if (@is_dir ($ dir )) {
158+ $ currentConfigDir = $ dir ;
159+ }
160+
161+ // legacy
162+ if ($ currentConfigDir === null ) {
163+ $ dir = $ home .'/.psysh ' ;
164+ if (@is_dir ($ dir )) {
165+ $ currentConfigDir = $ dir ;
166+ }
167+ }
168+ }
169+
170+ // Windows: HOMEDRIVE/HOMEPATH fallback
171+ if ($ currentConfigDir === null && defined ('PHP_WINDOWS_VERSION_MAJOR ' )) {
172+ if (isset ($ _SERVER ['HOMEDRIVE ' ]) && isset ($ _SERVER ['HOMEPATH ' ]) && $ _SERVER ['HOMEDRIVE ' ] && $ _SERVER ['HOMEPATH ' ]) {
173+ $ dir = rtrim (str_replace ('\\' , '/ ' , $ _SERVER ['HOMEDRIVE ' ].'/ ' .$ _SERVER ['HOMEPATH ' ]), '/ ' ).'/.psysh ' ;
174+ if (@is_dir ($ dir )) {
175+ $ currentConfigDir = $ dir ;
176+ }
177+ }
178+ }
179+
180+ // Fall back to the first candidate directory if none exist yet
181+ if ($ currentConfigDir === null ) {
182+ $ currentConfigDir = $ fallbackConfigDir ;
183+ }
184+
185+ if ($ currentConfigDir !== null ) {
186+ $ trustFile = $ currentConfigDir .'/trusted_projects.json ' ;
187+ if (is_file ($ trustFile )) {
188+ $ contents = file_get_contents ($ trustFile );
189+ if ($ contents !== false && $ contents !== '' ) {
190+ $ data = json_decode ($ contents , true );
191+ if (is_array ($ data )) {
192+ foreach ($ data as $ dir ) {
193+ if (!is_string ($ dir ) || $ dir === '' ) {
194+ continue ;
195+ }
196+
197+ $ real = realpath ($ dir );
198+ if ($ real !== false ) {
199+ $ dir = $ real ;
200+ }
201+
202+ $ trustedRoots [] = str_replace ('\\' , '/ ' , $ dir );
203+ }
204+ }
205+ }
206+ }
207+ }
208+ }
209+
75210 $ chunks = explode ('/ ' , $ cwd );
76211 while (!empty ($ chunks )) {
77212 $ path = implode ('/ ' , $ chunks );
@@ -86,6 +221,17 @@ call_user_func(function () {
86221 if (isset ($ cfg ['name ' ]) && $ cfg ['name ' ] === 'psy/psysh ' ) {
87222 // We're inside the psysh project. Let's use the local Composer autoload.
88223 if (is_file ($ path . '/vendor/autoload.php ' )) {
224+ $ realPath = realpath ($ path );
225+ $ realPath = $ realPath ? str_replace ('\\' , '/ ' , $ realPath ) : $ path ;
226+
227+ if (!$ forceTrust && ($ forceUntrust || !in_array ($ realPath , $ trustedRoots , true ))) {
228+ fwrite (STDERR , 'Skipping local PsySH at ' . $ prettyPath . ' (project is untrusted). Re-run with --trust-project to allow. ' . PHP_EOL );
229+ $ _SERVER ['PSYSH_UNTRUSTED_PROJECT ' ] = $ realPath ;
230+ $ _ENV ['PSYSH_UNTRUSTED_PROJECT ' ] = $ realPath ;
231+ putenv ('PSYSH_UNTRUSTED_PROJECT= ' .$ realPath );
232+ return ;
233+ }
234+
89235 if (realpath ($ path ) !== realpath (__DIR__ . '/.. ' )) {
90236 fwrite (STDERR , 'Using local PsySH version at ' . $ prettyPath . PHP_EOL );
91237 }
@@ -101,10 +247,22 @@ call_user_func(function () {
101247 // Or a composer.lock
102248 if (is_file ($ path . '/composer.lock ' )) {
103249 if ($ cfg = json_decode (file_get_contents ($ path . '/composer.lock ' ), true )) {
104- foreach (array_merge ($ cfg ['packages ' ], $ cfg ['packages-dev ' ]) as $ pkg ) {
250+ $ packages = array_merge (isset ($ cfg ['packages ' ]) ? $ cfg ['packages ' ] : array (), isset ($ cfg ['packages-dev ' ]) ? $ cfg ['packages-dev ' ] : array ());
251+ foreach ($ packages as $ pkg ) {
105252 if (isset ($ pkg ['name ' ]) && $ pkg ['name ' ] === 'psy/psysh ' ) {
106253 // We're inside a project which requires psysh. We'll use the local Composer autoload.
107254 if (is_file ($ path . '/vendor/autoload.php ' )) {
255+ $ realPath = realpath ($ path );
256+ $ realPath = $ realPath ? str_replace ('\\' , '/ ' , $ realPath ) : $ path ;
257+
258+ if (!$ forceTrust && ($ forceUntrust || !in_array ($ realPath , $ trustedRoots , true ))) {
259+ fwrite (STDERR , 'Skipping local PsySH at ' . $ prettyPath . ' (project is untrusted). Re-run with --trust-project to allow. ' . PHP_EOL );
260+ $ _SERVER ['PSYSH_UNTRUSTED_PROJECT ' ] = $ realPath ;
261+ $ _ENV ['PSYSH_UNTRUSTED_PROJECT ' ] = $ realPath ;
262+ putenv ('PSYSH_UNTRUSTED_PROJECT= ' .$ realPath );
263+ return ;
264+ }
265+
108266 if (realpath ($ path . '/vendor ' ) !== realpath (__DIR__ . '/../../.. ' )) {
109267 fwrite (STDERR , 'Using local PsySH version at ' . $ prettyPath . PHP_EOL );
110268 }
0 commit comments