diff --git a/VERSION b/VERSION
index 31ac37b0..75f45468 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-20250327.1
+20250403
diff --git a/src/Storage/Files/FilesStorage.php b/src/Storage/Files/FilesStorage.php
index 0ac3842e..01d2c147 100644
--- a/src/Storage/Files/FilesStorage.php
+++ b/src/Storage/Files/FilesStorage.php
@@ -7,8 +7,8 @@ use Misuzu\Storage\HashHelpers;
 
 class FilesStorage {
     public function __construct(
-        private string $localPath,
-        private string $remotePath,
+        public private(set) string $localPath,
+        public private(set) string $remotePath,
     ) {}
 
     public static function stringifyHash(FileInfo|string $infoOrHash): string {
diff --git a/tools/cron b/tools/cron
index cc47383b..8d6743e0 100755
--- a/tools/cron
+++ b/tools/cron
@@ -250,123 +250,127 @@ try {
         }
     });
 
-    msz_sched_task_func('Removing stale entries from storage pools...', false, function() use ($msz) {
-        $pools = [];
-        $getPool = fn($ruleRaw) => array_key_exists($ruleRaw->poolId, $pools) ? $pools[$ruleRaw->poolId] : (
-            $pools[$ruleRaw->poolId] = $msz->storageCtx->poolsCtx->pools->getPool($ruleRaw->poolId)
-        );
+    if(!empty($msz->storageCtx->filesCtx->storage->localPath)
+        && is_dir($msz->storageCtx->filesCtx->storage->localPath)
+        && fileowner($msz->storageCtx->filesCtx->storage->localPath) === posix_getuid()) {
+        msz_sched_task_func('Removing stale entries from storage pools...', false, function() use ($msz) {
+            $pools = [];
+            $getPool = fn($ruleRaw) => array_key_exists($ruleRaw->poolId, $pools) ? $pools[$ruleRaw->poolId] : (
+                $pools[$ruleRaw->poolId] = $msz->storageCtx->poolsCtx->pools->getPool($ruleRaw->poolId)
+            );
 
-        // Run cleanup adjacent tasks first
-        $rules = $msz->storageCtx->poolsCtx->pools->getPoolRules(types: ['remove_stale', 'scan_forum']);
-        foreach($rules as $ruleRaw) {
-            $poolInfo = $getPool($ruleRaw);
-            $rule = $msz->storageCtx->poolsCtx->rules->create($ruleRaw);
+            // Run cleanup adjacent tasks first
+            $rules = $msz->storageCtx->poolsCtx->pools->getPoolRules(types: ['remove_stale', 'scan_forum']);
+            foreach($rules as $ruleRaw) {
+                $poolInfo = $getPool($ruleRaw);
+                $rule = $msz->storageCtx->poolsCtx->rules->create($ruleRaw);
 
-            if($rule instanceof RemoveStaleRule) {
-                printf('Removing stale entries for pool #%d...%s', $poolInfo->id, PHP_EOL);
-                $msz->storageCtx->uploadsCtx->uploads->deleteStaleUploads(
-                    $poolInfo,
-                    $rule->inactiveForSeconds,
-                    $rule->ignoreUploadIds
-                );
-            } elseif($rule instanceof ScanForumRule) {
-                //var_dump('scan_forum', $rule);
-                // necessary stuff for this doesn't exist yet so this can remain a no-op
+                if($rule instanceof RemoveStaleRule) {
+                    printf('Removing stale entries for pool #%d...%s', $poolInfo->id, PHP_EOL);
+                    $msz->storageCtx->uploadsCtx->uploads->deleteStaleUploads(
+                        $poolInfo,
+                        $rule->inactiveForSeconds,
+                        $rule->ignoreUploadIds
+                    );
+                } elseif($rule instanceof ScanForumRule) {
+                    //var_dump('scan_forum', $rule);
+                    // necessary stuff for this doesn't exist yet so this can remain a no-op
+                }
             }
-        }
-    });
+        });
 
-    msz_sched_task_func('Purging denylisted files...', true, function() use ($msz) {
-        $fileInfos = $msz->storageCtx->filesCtx->data->getFiles(denied: true);
-        foreach($fileInfos as $fileInfo) {
-            printf('Deleting file #%d...%s', $fileInfo->id, PHP_EOL);
-            $msz->storageCtx->filesCtx->deleteFile($fileInfo);
-        }
-    });
+        msz_sched_task_func('Purging denylisted files...', true, function() use ($msz) {
+            $fileInfos = $msz->storageCtx->filesCtx->data->getFiles(denied: true);
+            foreach($fileInfos as $fileInfo) {
+                printf('Deleting file #%d...%s', $fileInfo->id, PHP_EOL);
+                $msz->storageCtx->filesCtx->deleteFile($fileInfo);
+            }
+        });
 
-    msz_sched_task_func('Purging orphaned files...', true, function() use ($msz) {
-        $fileInfos = $msz->storageCtx->filesCtx->data->getFiles(orphaned: true);
-        foreach($fileInfos as $fileInfo) {
-            printf('Deleting file #%d...%s', $fileInfo->id, PHP_EOL);
-            $msz->storageCtx->filesCtx->deleteFile($fileInfo);
-        }
-    });
+        msz_sched_task_func('Purging orphaned files...', true, function() use ($msz) {
+            $fileInfos = $msz->storageCtx->filesCtx->data->getFiles(orphaned: true);
+            foreach($fileInfos as $fileInfo) {
+                printf('Deleting file #%d...%s', $fileInfo->id, PHP_EOL);
+                $msz->storageCtx->filesCtx->deleteFile($fileInfo);
+            }
+        });
 
-    msz_sched_task_func('Ensuring alternate variants exist...', true, function() use ($msz) {
-        $pools = [];
-        $getPool = fn($ruleRaw) => array_key_exists($ruleRaw->poolId, $pools) ? $pools[$ruleRaw->poolId] : (
-            $pools[$ruleRaw->poolId] = $msz->storageCtx->poolsCtx->pools->getPool($ruleRaw->poolId)
-        );
+        msz_sched_task_func('Ensuring alternate variants exist...', true, function() use ($msz) {
+            $pools = [];
+            $getPool = fn($ruleRaw) => array_key_exists($ruleRaw->poolId, $pools) ? $pools[$ruleRaw->poolId] : (
+                $pools[$ruleRaw->poolId] = $msz->storageCtx->poolsCtx->pools->getPool($ruleRaw->poolId)
+            );
 
-        $rules = $msz->storageCtx->poolsCtx->pools->getPoolRules(types: ['ensure_variant']);
-        foreach($rules as $ruleRaw) {
-            $poolInfo = $getPool($ruleRaw);
-            $rule = $msz->storageCtx->poolsCtx->rules->create($ruleRaw);
+            $rules = $msz->storageCtx->poolsCtx->pools->getPoolRules(types: ['ensure_variant']);
+            foreach($rules as $ruleRaw) {
+                $poolInfo = $getPool($ruleRaw);
+                $rule = $msz->storageCtx->poolsCtx->rules->create($ruleRaw);
 
-            if($rule instanceof EnsureVariantRule) {
-                if(!$rule->onCron)
-                    continue;
+                if($rule instanceof EnsureVariantRule) {
+                    if(!$rule->onCron)
+                        continue;
 
-                $createVariant = null; // ensure this wasn't previously assigned
-                if($rule->params instanceof EnsureVariantRuleThumb) {
-                    $createVariant = function(
-                        FilesContext $filesCtx,
-                        UploadsContext $uploadsCtx,
-                        PoolInfo $poolInfo,
-                        EnsureVariantRule $rule,
-                        EnsureVariantRuleThumb $ruleInfo,
-                        FileInfo $originalInfo,
-                        UploadInfo $uploadInfo
-                    ) {
-                        $uploadsCtx->uploads->createUploadVariant(
-                            $uploadInfo,
-                            $rule->variant,
-                            $filesCtx->createThumbnailFromRule(
-                                $originalInfo,
-                                $ruleInfo
-                            )
-                        );
-                    };
-                }
-
-                if(!isset($createVariant) || !is_callable($createVariant)) {
-                    printf('!!! Could not create a constructor for "%s" variants for pool #%d !!! Skipping for now...%s', $rule->variant, $poolInfo->id, PHP_EOL);
-                    continue;
-                }
-
-                printf('Ensuring existence of "%s" variants for pool #%d...%s', $rule->variant, $poolInfo->id, PHP_EOL);
-
-                $uploads = $msz->storageCtx->uploadsCtx->uploads->getUploads(
-                    poolInfo: $poolInfo,
-                    variant: $rule->variant,
-                    variantExists: false
-                );
-
-                $processed = 0;
-                foreach($uploads as $uploadInfo)
-                    try {
-                        $createVariant(
-                            $msz->storageCtx->filesCtx,
-                            $msz->storageCtx->uploadsCtx,
-                            $poolInfo,
-                            $rule,
-                            $rule->params,
-                            $msz->storageCtx->filesCtx->data->getFile(
-                                $msz->storageCtx->uploadsCtx->uploads->getUploadVariant($uploadInfo, '')->fileId
-                            ),
-                            $uploadInfo
-                        );
-                    } catch(Exception $ex) {
-                        printf('Exception thrown while processing "%s" variant for upload #%d in pool #%d:%s', $rule->variant, $uploadInfo->id, $poolInfo->id, PHP_EOL);
-                        printf('%s%s', $ex->getMessage(), PHP_EOL);
-                    } finally {
-                        ++$processed;
+                    $createVariant = null; // ensure this wasn't previously assigned
+                    if($rule->params instanceof EnsureVariantRuleThumb) {
+                        $createVariant = function(
+                            FilesContext $filesCtx,
+                            UploadsContext $uploadsCtx,
+                            PoolInfo $poolInfo,
+                            EnsureVariantRule $rule,
+                            EnsureVariantRuleThumb $ruleInfo,
+                            FileInfo $originalInfo,
+                            UploadInfo $uploadInfo
+                        ) {
+                            $uploadsCtx->uploads->createUploadVariant(
+                                $uploadInfo,
+                                $rule->variant,
+                                $filesCtx->createThumbnailFromRule(
+                                    $originalInfo,
+                                    $ruleInfo
+                                )
+                            );
+                        };
                     }
 
-                printf('Processed %d records!%s%s', $processed, PHP_EOL);
+                    if(!isset($createVariant) || !is_callable($createVariant)) {
+                        printf('!!! Could not create a constructor for "%s" variants for pool #%d !!! Skipping for now...%s', $rule->variant, $poolInfo->id, PHP_EOL);
+                        continue;
+                    }
+
+                    printf('Ensuring existence of "%s" variants for pool #%d...%s', $rule->variant, $poolInfo->id, PHP_EOL);
+
+                    $uploads = $msz->storageCtx->uploadsCtx->uploads->getUploads(
+                        poolInfo: $poolInfo,
+                        variant: $rule->variant,
+                        variantExists: false
+                    );
+
+                    $processed = 0;
+                    foreach($uploads as $uploadInfo)
+                        try {
+                            $createVariant(
+                                $msz->storageCtx->filesCtx,
+                                $msz->storageCtx->uploadsCtx,
+                                $poolInfo,
+                                $rule,
+                                $rule->params,
+                                $msz->storageCtx->filesCtx->data->getFile(
+                                    $msz->storageCtx->uploadsCtx->uploads->getUploadVariant($uploadInfo, '')->fileId
+                                ),
+                                $uploadInfo
+                            );
+                        } catch(Exception $ex) {
+                            printf('Exception thrown while processing "%s" variant for upload #%d in pool #%d:%s', $rule->variant, $uploadInfo->id, $poolInfo->id, PHP_EOL);
+                            printf('%s%s', $ex->getMessage(), PHP_EOL);
+                        } finally {
+                            ++$processed;
+                        }
+
+                    printf('Processed %d records!%s%s', $processed, PHP_EOL);
+                }
             }
-        }
-    });
+        });
+    }
 
     echo 'Running ' . count($schedTasks) . ' tasks...' . PHP_EOL;
 
diff --git a/tools/delete-upload b/tools/delete-upload
index 0967e3ba..ef2fc63c 100755
--- a/tools/delete-upload
+++ b/tools/delete-upload
@@ -4,6 +4,13 @@ use Index\XNumber;
 
 require_once __DIR__ . '/../misuzu.php';
 
+if(empty($msz->storageCtx->filesCtx->storage->localPath))
+    die(sprintf('No storage path is specified.%s', PHP_EOL));
+if(!is_dir($msz->storageCtx->filesCtx->storage->localPath))
+    die(sprintf('Storage directory does not exist.%s', PHP_EOL));
+if(fileowner($msz->storageCtx->filesCtx->storage->localPath) !== posix_getuid())
+    die(sprintf('Script must be run as the owner of the storage directory.%s', PHP_EOL));
+
 $options = getopt('s', [
     'base62',
 ], $restIndex);
diff --git a/tools/deny-file b/tools/deny-file
index 8c36863d..cc6bbcd0 100755
--- a/tools/deny-file
+++ b/tools/deny-file
@@ -6,6 +6,13 @@ use Misuzu\Storage\Files\FileInfoGetFileField;
 
 require_once __DIR__ . '/../misuzu.php';
 
+if(empty($msz->storageCtx->filesCtx->storage->localPath))
+    die(sprintf('No storage path is specified.%s', PHP_EOL));
+if(!is_dir($msz->storageCtx->filesCtx->storage->localPath))
+    die(sprintf('Storage directory does not exist.%s', PHP_EOL));
+if(fileowner($msz->storageCtx->filesCtx->storage->localPath) !== posix_getuid())
+    die(sprintf('Script must be run as the owner of the storage directory.%s', PHP_EOL));
+
 $options = getopt('hsur:', [
     'hash',
     'base62',
diff --git a/tools/nuke-file b/tools/nuke-file
index bfac710b..b236c7e6 100755
--- a/tools/nuke-file
+++ b/tools/nuke-file
@@ -5,6 +5,13 @@ use Misuzu\Storage\Files\FileInfoGetFileField;
 
 require_once __DIR__ . '/../misuzu.php';
 
+if(empty($msz->storageCtx->filesCtx->storage->localPath))
+    die(sprintf('No storage path is specified.%s', PHP_EOL));
+if(!is_dir($msz->storageCtx->filesCtx->storage->localPath))
+    die(sprintf('Storage directory does not exist.%s', PHP_EOL));
+if(fileowner($msz->storageCtx->filesCtx->storage->localPath) !== posix_getuid())
+    die(sprintf('Script must be run as the owner of the storage directory.%s', PHP_EOL));
+
 $options = getopt('hsuv:', [
     'hash',
     'base62',
diff --git a/tools/nuke-tpl-cache b/tools/nuke-tpl-cache
index 3dbd8847..92ed2846 100755
--- a/tools/nuke-tpl-cache
+++ b/tools/nuke-tpl-cache
@@ -1,17 +1,12 @@
 #!/usr/bin/env php
 <?php
-namespace Misuzu;
-
 require_once __DIR__ . '/../misuzu.php';
 
-$path = $msz->tplCtx->cachePath;
-if(empty($path))
+if(empty($msz->tplCtx->cachePath))
     die(sprintf('No cache path is specified.%s', PHP_EOL));
-
-if(!is_dir($path))
+if(!is_dir($msz->tplCtx->cachePath))
     die(sprintf('Cache directory does not exist.%s', PHP_EOL));
-
-if(fileowner($path) !== posix_getuid())
+if(fileowner($msz->tplCtx->cachePath) !== posix_getuid())
     die(sprintf('Script must be run as the owner of the cache directory.%s', PHP_EOL));
 
-echo `rm -rv {$path}`;
+echo `rm -rv {$msz->tplCtx->cachePath}`;
diff --git a/tools/purge-user-uploads b/tools/purge-user-uploads
index fbea69eb..e66d1102 100755
--- a/tools/purge-user-uploads
+++ b/tools/purge-user-uploads
@@ -2,6 +2,13 @@
 <?php
 require_once __DIR__ . '/../misuzu.php';
 
+if(empty($msz->storageCtx->filesCtx->storage->localPath))
+    die(sprintf('No storage path is specified.%s', PHP_EOL));
+if(!is_dir($msz->storageCtx->filesCtx->storage->localPath))
+    die(sprintf('Storage directory does not exist.%s', PHP_EOL));
+if(fileowner($msz->storageCtx->filesCtx->storage->localPath) !== posix_getuid())
+    die(sprintf('Script must be run as the owner of the storage directory.%s', PHP_EOL));
+
 if($argc <= 1)
     die(sprintf('No uploader ID specified.%s', PHP_EOL));