Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions src/Context/GivenStepDefinitions.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,20 +53,28 @@ public function given_a_specific_directory( $empty_or_nonexistent, $dir ): void
// Mac OS X can prefix the `/var` folder to turn it into `/private/var`.
$dir = preg_replace( '|^/private/var/|', '/var/', $dir );

$temp_dir = realpath( sys_get_temp_dir() );
$temp_dir = $temp_dir ? Path::normalize( $temp_dir ) : Path::normalize( sys_get_temp_dir() );
$dir = Path::normalize( $dir );
$temp_dir_original = Path::normalize( sys_get_temp_dir() );
$temp_dir_real = realpath( sys_get_temp_dir() );
$temp_dir_real = $temp_dir_real ? Path::normalize( $temp_dir_real ) : $temp_dir_original;
$dir = Path::normalize( $dir );

// Also normalize temp dir for Mac OS X.
$temp_dir = preg_replace( '|^/private/var/|', '/var/', $temp_dir );
$temp_dir_original = preg_replace( '|^/private/var/|', '/var/', $temp_dir_original );
$temp_dir_real = preg_replace( '|^/private/var/|', '/var/', $temp_dir_real );

// Also check for temp dir prefixed with `/private` for Mac OS X.
if ( 0 !== strpos( $dir, $temp_dir ) && 0 !== strpos( $dir, "/private{$temp_dir}" ) ) {
$is_windows = Utils\is_windows();

$in_temp = 0 === ( $is_windows ? stripos( $dir, $temp_dir_original ) : strpos( $dir, $temp_dir_original ) )
|| 0 === ( $is_windows ? stripos( $dir, $temp_dir_real ) : strpos( $dir, $temp_dir_real ) )
|| 0 === ( $is_windows ? stripos( $dir, "/private{$temp_dir_original}" ) : strpos( $dir, "/private{$temp_dir_original}" ) )
|| 0 === ( $is_windows ? stripos( $dir, "/private{$temp_dir_real}" ) : strpos( $dir, "/private{$temp_dir_real}" ) );
Comment on lines +67 to +70
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic for checking if the directory is within the temporary directory is quite repetitive and difficult to read. Since you are checking multiple candidates against the same directory using a conditional comparison function (stripos vs strpos), you can simplify this by using a loop. This improves maintainability and reduces the risk of errors when adding new candidates.

		$compare = $is_windows ? 'stripos' : 'strpos';
		$in_temp = false;
		foreach ( [ $temp_dir_original, $temp_dir_real ] as $temp_path ) {
			if ( 0 === $compare( $dir, $temp_path ) || 0 === $compare( $dir, "/private{$temp_path}" ) ) {
				$in_temp = true;
				break;
			}
		}

Comment on lines +67 to +70
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The temp-dir guard uses a raw prefix check (strpos/stripos === 0). This can be bypassed by paths that merely share the same prefix (e.g. temp dir /tmp vs directory /tmpfoo), which could allow deleting directories outside the temp tree. Consider enforcing a path-boundary check by normalizing/rtrimming both sides and comparing with an added trailing separator (or otherwise ensuring the next character is a separator/end-of-string) before calling remove_dir().

Copilot uses AI. Check for mistakes.

if ( ! $in_temp ) {
throw new RuntimeException(
sprintf(
"Attempted to delete directory '%s' that is not in the temp directory '%s'. " . __FILE__ . ':' . __LINE__,
$dir,
$temp_dir
$temp_dir_real
)
);
}
Expand Down