diff --git a/README.md b/README.md
index fe001ed..79feb5b 100644
--- a/README.md
+++ b/README.md
@@ -21,6 +21,8 @@ The default driver aligns with [OWASP](https://cheatsheetseries.owasp.org/cheats
 
 - PHP >= 8.2
 - ext-json
+- Node >= 18
+- NPM
 
 ## Installation
 
@@ -56,14 +58,17 @@ return [
 
     'drivers' => [
         'dompurify-cli' => new DompurifyCliConfig(
-            env('NODE_PATH', 'node'),
-            env('NPM_PATH', 'npm'),
+            node: env('NODE_PATH'),
+            npm: env('NPM_PATH'),
+            binary: null,
+            tempFolder: null,
         ),
         'dompurify-service' => new DompurifyServiceConfig(
-            env('NODE_PATH', 'node'),
-            env('NPM_PATH', 'npm'),
-            '127.0.0.1',
-            63000,
+            node: env('NODE_PATH'),
+            npm: env('NPM_PATH'),
+            host: '127.0.0.1',
+            port: 63000,
+            binary: null,
         ),
     ],
 ];
diff --git a/src/Dompurify/DompurifyCli.php b/src/Dompurify/DompurifyCli.php
index d23b3d2..d1f06e2 100644
--- a/src/Dompurify/DompurifyCli.php
+++ b/src/Dompurify/DompurifyCli.php
@@ -38,6 +38,10 @@ public function exec(string $html): string
         $output = $process->getOutput();
         $cleanHtmlPath = trim($output);
 
+        if (! file_exists($cleanHtmlPath)) {
+            throw new XsslessException("Could not locate the file '{$cleanHtmlPath}'");
+        }
+
         $clean = file_get_contents($cleanHtmlPath);
 
         if ($clean === false) {
@@ -53,8 +57,7 @@ public function exec(string $html): string
 
     private function binPath(): string
     {
-        // TODO: allow config to override
-        $binPath = __DIR__.DIRECTORY_SEPARATOR.'cli.js';
+        $binPath = $this->config->binary ?? __DIR__.DIRECTORY_SEPARATOR.'cli.js';
 
         $binAbsPath = realpath($binPath);
 
@@ -69,6 +72,7 @@ private function saveHtml(string $value): string
     {
         $dir = $this->tempDir();
 
+        // ? use tempnam
         $fileName = mt_rand().'-'.str_replace([' ', '.'], '', microtime()).'.xss';
 
         $path = $dir.DIRECTORY_SEPARATOR.$fileName;
@@ -82,16 +86,20 @@ private function saveHtml(string $value): string
 
     private function tempDir(): string
     {
-        // TODO: take path from config
-        $tempDir = rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR);
-        $dir = $tempDir.DIRECTORY_SEPARATOR.'xssless';
+        if (is_null($this->config->tempFolder)) {
+            $dir = sys_get_temp_dir().DIRECTORY_SEPARATOR.'xssless';
 
-        if (! file_exists($dir)) {
-            if (mkdir($dir, 0777, true) === false) {
+            if (! file_exists($dir) && mkdir($dir, 0777, true) === false) {
                 throw new XsslessException("Could not create temporary directory '{$dir}'");
             }
+
+            return $dir;
+        }
+
+        if (! file_exists($this->config->tempFolder)) {
+            throw new XsslessException("Could not locate temporary directory '{$this->config->tempFolder}'");
         }
 
-        return $dir;
+        return $this->config->tempFolder;
     }
 }
diff --git a/src/Dompurify/DompurifyCliConfig.php b/src/Dompurify/DompurifyCliConfig.php
index 31feca6..e1390fa 100644
--- a/src/Dompurify/DompurifyCliConfig.php
+++ b/src/Dompurify/DompurifyCliConfig.php
@@ -9,8 +9,10 @@ class DompurifyCliConfig implements ConfigInterface
     private readonly string $class;
 
     public function __construct(
-        public string $node,
-        public string $npm,
+        public string $node = 'node',
+        public string $npm = 'npm',
+        public ?string $binary = null,
+        public ?string $tempFolder = null,
     ) {
         $this->class = DompurifyCli::class;
     }
diff --git a/src/Dompurify/DompurifyService.php b/src/Dompurify/DompurifyService.php
index f7115dd..eef8783 100644
--- a/src/Dompurify/DompurifyService.php
+++ b/src/Dompurify/DompurifyService.php
@@ -53,7 +53,7 @@ public function start(): static
     {
         $this->serviceProcess = new Process([
             $this->config->node,
-            __DIR__.DIRECTORY_SEPARATOR.'http.js',
+            $this->config->binary ?? __DIR__.DIRECTORY_SEPARATOR.'http.js',
             $this->config->host,
             $this->config->port,
         ]);
@@ -145,4 +145,6 @@ private function isSigTerm(): bool
     // {
     //     return $this->serviceProcess->getTermSignal() === 1;
     // }
+
+    // 92..97, 104, 113..136
 }
diff --git a/src/Dompurify/DompurifyServiceConfig.php b/src/Dompurify/DompurifyServiceConfig.php
index 68e97ee..e3bf332 100644
--- a/src/Dompurify/DompurifyServiceConfig.php
+++ b/src/Dompurify/DompurifyServiceConfig.php
@@ -9,10 +9,11 @@ class DompurifyServiceConfig implements ConfigInterface
     public readonly string $class;
 
     public function __construct(
-        public string $node,
-        public string $npm,
-        public string $host,
-        public int $port,
+        public string $node = 'node',
+        public string $npm = 'npm',
+        public string $host = '127.0.0.1',
+        public int $port = 6300,
+        public ?string $binary = null,
     ) {
         $this->class = DompurifyService::class;
     }
diff --git a/src/laravel/config/xssless.php b/src/laravel/config/xssless.php
index cc085a3..907ac64 100644
--- a/src/laravel/config/xssless.php
+++ b/src/laravel/config/xssless.php
@@ -8,14 +8,17 @@
 
     'drivers' => [
         'dompurify-cli' => new DompurifyCliConfig(
-            env('NODE_PATH', 'node'), // @phpstan-ignore argument.type
-            env('NPM_PATH', 'npm'), // @phpstan-ignore argument.type
+            node: env('NODE_PATH'), // @phpstan-ignore argument.type
+            npm: env('NPM_PATH'), // @phpstan-ignore argument.type
+            binary: null,
+            tempFolder: null,
         ),
         'dompurify-service' => new DompurifyServiceConfig(
-            env('NODE_PATH', 'node'), // @phpstan-ignore argument.type
-            env('NPM_PATH', 'npm'), // @phpstan-ignore argument.type
-            '127.0.0.1',
-            63000,
+            node: env('NODE_PATH'), // @phpstan-ignore argument.type
+            npm: env('NPM_PATH'), // @phpstan-ignore argument.type
+            host: '127.0.0.1',
+            port: 63000,
+            binary: null,
         ),
     ],
 ];
diff --git a/tests/Dompurify/DompurifyCliTest.php b/tests/Dompurify/DompurifyCliTest.php
index d4cff17..46cedd1 100644
--- a/tests/Dompurify/DompurifyCliTest.php
+++ b/tests/Dompurify/DompurifyCliTest.php
@@ -3,10 +3,20 @@
 use Medilies\Xssless\Dompurify\DompurifyCli;
 use Medilies\Xssless\Dompurify\DompurifyCliConfig;
 use Medilies\Xssless\Xssless;
+use Medilies\Xssless\XsslessException;
 use Symfony\Component\Process\Exception\ProcessFailedException;
 
-test('setup()', function () {
+it('throws on bad node path', function () {
     $cleaner = (new DompurifyCli)->configure(new DompurifyCliConfig(
+        'nodeZz',
+        'npm',
+    ));
+
+    expect(fn () => $cleaner->exec('foo'))->toThrow(ProcessFailedException::class);
+});
+
+test('setup()', function () {
+    $cleaner = (new Xssless)->using(new DompurifyCliConfig(
         'node',
         'npm',
     ));
@@ -27,8 +37,9 @@
 
 test('clean()', function () {
     $cleaner = (new Xssless)->using(new DompurifyCliConfig(
-        'node',
-        'npm',
+        node: 'node',
+        npm: 'npm',
+        tempFolder: __DIR__,
     ));
 
     $clean = $cleaner->clean('<IMG """><SCRIPT>alert("XSS")</SCRIPT>">');
@@ -36,11 +47,32 @@
     expect($clean)->toBe('<img>"&gt;');
 })->depends('setup()');
 
-it('throws on bad node path', function () {
+it('throws when cannot read cleaned file', function () {
     $cleaner = (new DompurifyCli)->configure(new DompurifyCliConfig(
-        'nodeZz',
-        'npm',
+        node: 'node',
+        npm: 'npm',
+        binary: __DIR__.'/js-mocks/cli-returns-bad-path.js',
     ));
 
-    expect(fn () => $cleaner->exec('foo'))->toThrow(ProcessFailedException::class);
-});
+    expect(fn () => $cleaner->exec('foo'))->toThrow(XsslessException::class);
+})->depends('setup()');
+
+it('throws when cannot find binary file', function () {
+    $cleaner = (new DompurifyCli)->configure(new DompurifyCliConfig(
+        node: 'node',
+        npm: 'npm',
+        binary: __DIR__.'/js-mocks/x.js',
+    ));
+
+    expect(fn () => $cleaner->exec('foo'))->toThrow(XsslessException::class);
+})->depends('setup()');
+
+it('throws when cannot locate temp folder', function () {
+    $cleaner = (new DompurifyCli)->configure(new DompurifyCliConfig(
+        node: 'node',
+        npm: 'npm',
+        tempFolder: __DIR__.'/x',
+    ));
+
+    expect(fn () => $cleaner->exec('foo'))->toThrow(XsslessException::class);
+})->depends('setup()');
diff --git a/tests/Dompurify/DompurifyServiceTest.php b/tests/Dompurify/DompurifyServiceTest.php
index bd14587..1bc2751 100644
--- a/tests/Dompurify/DompurifyServiceTest.php
+++ b/tests/Dompurify/DompurifyServiceTest.php
@@ -46,9 +46,7 @@
 
     $cleaner = (new Xssless)->using($config);
 
-    $service = (new DompurifyService)->configure($config);
-
-    $service->start();
+    $service = $cleaner->start();
 
     $dirty = '<IMG """><SCRIPT>alert("XSS")</SCRIPT>">';
 
diff --git a/tests/Dompurify/js-mocks/cli-returns-bad-path.js b/tests/Dompurify/js-mocks/cli-returns-bad-path.js
new file mode 100644
index 0000000..cc22d81
--- /dev/null
+++ b/tests/Dompurify/js-mocks/cli-returns-bad-path.js
@@ -0,0 +1,4 @@
+const htmlFile = process.argv[2];
+
+console.log(htmlFile + ".clean13465789");
+process.exit(0);
diff --git a/tests/XsslessTest.php b/tests/XsslessTest.php
new file mode 100644
index 0000000..932d2cb
--- /dev/null
+++ b/tests/XsslessTest.php
@@ -0,0 +1,61 @@
+<?php
+
+use Medilies\Xssless\CliInterface;
+use Medilies\Xssless\ConfigInterface;
+use Medilies\Xssless\Dompurify\DompurifyCliConfig;
+use Medilies\Xssless\Xssless;
+use Medilies\Xssless\XsslessException;
+
+it('throws when makeCleaner() with no config', function () {
+    $cleaner = new Xssless;
+
+    $cleaner->using(new class implements ConfigInterface
+    {
+        public function getClass(): string
+        {
+            return Xssless::class;
+        }
+    });
+
+    expect(fn () => $cleaner->clean('foo'))->toThrow(XsslessException::class);
+});
+
+it('throws when makeCleaner() with no interface', function () {
+    $cleaner = new Xssless;
+
+    expect(fn () => $cleaner->clean('foo'))->toThrow(XsslessException::class);
+});
+
+it('throws when start() with CliInterface', function () {
+    $cleaner = new Xssless;
+    $cleaner->using(new DompurifyCliConfig);
+
+    expect(fn () => $cleaner->start())->toThrow(XsslessException::class);
+});
+
+it('throws when setup() without HasSetupInterface', function () {
+    $cleaner = new Xssless;
+
+    $cleaner->using(new class implements ConfigInterface
+    {
+        public function getClass(): string
+        {
+            return NoSetupDriver::class;
+        }
+    });
+
+    expect(fn () => $cleaner->setup())->toThrow(XsslessException::class);
+});
+
+class NoSetupDriver implements CliInterface
+{
+    public function configure(ConfigInterface $config): static
+    {
+        return $this;
+    }
+
+    public function exec(string $html): string
+    {
+        return '';
+    }
+}