From 5b2466de184ed0b23a67f3ffe74e1260a659ede9 Mon Sep 17 00:00:00 2001
From: bakerboy448 <55419169+bakerboy448@users.noreply.github.com>
Date: Wed, 2 Sep 2026 12:21:20 -0500
Subject: [PATCH 1/5] feat(signalr): opt-in SignalR/WebSocket passthrough for
proxied arrs (#91)
Adds an nginx /signalr location that WebSocket-proxies //signalr/* to the
backend arr, using an internal auth_request to signalr/auth.php which resolves
the scoped key via getAppFromProxiedKey, gates on a new per-app 'signalr' opt-in
flag (migration 009), validates the backend URL, and swaps the scoped
access_token for the backend's real key. PHP cURL cannot proxy WebSockets, so
this stays at the nginx layer. Bazarr templates opt in; admin UI adds the toggle.
Closes #91
---
README.md | 4 ++-
root/app/www/public/ajax/starr.php | 33 +++++++++++------
root/app/www/public/ajax/templates.php | 7 ++--
root/app/www/public/api/index.php | 21 ++++++-----
root/app/www/public/js/starr.js | 1 +
root/app/www/public/js/templates.js | 6 ++++
.../app/www/public/migrations/009_signalr.php | 23 ++++++++++++
root/app/www/public/pages/home.php | 3 +-
root/app/www/public/pages/starr.php | 3 +-
root/app/www/public/signalr/auth.php | 35 +++++++++++++++++++
.../www/public/templates/radarr/bazarr.json | 1 +
.../www/public/templates/sonarr/bazarr.json | 1 +
root/etc/nginx/template.conf | 26 ++++++++++++++
13 files changed, 139 insertions(+), 25 deletions(-)
create mode 100644 root/app/www/public/migrations/009_signalr.php
create mode 100644 root/app/www/public/signalr/auth.php
diff --git a/README.md b/README.md
index 57ea70b..efd1b70 100644
--- a/README.md
+++ b/README.md
@@ -88,7 +88,9 @@ If you need to auto add starr apps and 3rd party apps you can do that via the ap
}
```
-The `template` variable is not required but if you do not use an existing template then the app will have no starr api access initially.
+The `template` variable is not required but if you do not use an existing template then the app will have no starr api access initially.
+
+SignalR/WebSocket access is separately disabled by default. Enable the SignalR option when configuring an app that needs an arr `/signalr` connection. The Radarr and Sonarr Bazarr templates enable this option automatically.
An example curl would be:
diff --git a/root/app/www/public/ajax/starr.php b/root/app/www/public/ajax/starr.php
index 2e46dc2..55ee0aa 100644
--- a/root/app/www/public/ajax/starr.php
+++ b/root/app/www/public/ajax/starr.php
@@ -154,12 +154,16 @@
-
= ucfirst($app) ?> endpoints
@@ -240,8 +244,9 @@
$fields['apikey'] = $_POST['apikey'];
$fields['starr_id'] = intval($_POST['starr_id']);
$fields['endpoints'] = json_encode($endpoints, JSON_UNESCAPED_SLASHES);
- $fields['template'] = $_POST['template'];
- $fields['redactions'] = $_POST['redactions'];
+ $fields['template'] = $_POST['template'];
+ $fields['redactions'] = $_POST['redactions'];
+ $fields['signalr'] = intval($_POST['signalr']);
if ($_POST['id'] != 99) {
$error = $proxyDb->updateApp($_POST['id'], $fields);
@@ -306,9 +311,11 @@
$templateFile = file_exists($app['template']) ? $app['template'] : str_replace('../', './', $app['template']);
$appTemplate = getFile($templateFile);
- if ($appTemplate) {
- $app['endpoints'] = json_encode($appTemplate);
- $error = $proxyDb->updateApp($_POST['appId'], $app);
+ if ($appTemplate) {
+ $app['signalr'] = intval(!empty($appTemplate['signalr']));
+ unset($appTemplate['signalr']);
+ $app['endpoints'] = json_encode($appTemplate);
+ $error = $proxyDb->updateApp($_POST['appId'], $app);
}
break;
@@ -331,8 +338,12 @@
}
$endpoints = [];
- foreach ($templateFile as $templateEndpoint => $methods) {
- foreach ($methods as $method) {
+ foreach ($templateFile as $templateEndpoint => $methods) {
+ if (!is_array($methods)) {
+ continue;
+ }
+
+ foreach ($methods as $method) {
$endpoints[$templateEndpoint][$method][] = 'template';
}
}
diff --git a/root/app/www/public/ajax/templates.php b/root/app/www/public/ajax/templates.php
index f9e60f6..55aaadf 100644
--- a/root/app/www/public/ajax/templates.php
+++ b/root/app/www/public/ajax/templates.php
@@ -42,8 +42,11 @@
}
if ($_POST['m'] == 'saveTemplateStarrAccess') {
- $existing = $proxyDb->getAppFromId($_POST['id'], $appsTable);
- $endpoints = $existing['endpoints'] ? json_decode($existing['endpoints'], true) : [];
+ $existing = $proxyDb->getAppFromId($_POST['id'], $appsTable);
+ $endpoints = $existing['endpoints'] ? json_decode($existing['endpoints'], true) : [];
+ if ($existing['signalr']) {
+ $endpoints['signalr'] = true;
+ }
$name = strtolower(preg_replace('/[^a-zA-Z0-9 _-]/', '', $_POST['name']));
file_put_contents(APP_USER_TEMPLATES_PATH . $app . '/' . $name . '.json', json_encode($endpoints, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
}
diff --git a/root/app/www/public/api/index.php b/root/app/www/public/api/index.php
index f1c08c4..68c0661 100644
--- a/root/app/www/public/api/index.php
+++ b/root/app/www/public/api/index.php
@@ -118,15 +118,18 @@
$starrApp = $starr->getAppFromStarrKey($request['apikey'], $starrsTable);
if (!$error) {
- $scopeKey = generateApikey();
- $scopeAccess = $request['template'] ? json_decode(file_get_contents('../templates/' . $request['starr'] . '/' . $request['template'] . '.json'), true) : [];
-
- $fields = [
- 'name' => $request['name'],
- 'apikey' => $scopeKey,
- 'starr_id' => intval($starrApp['id']),
- 'endpoints' => json_encode($scopeAccess, JSON_UNESCAPED_SLASHES)
- ];
+ $scopeKey = generateApikey();
+ $scopeAccess = $request['template'] ? json_decode(file_get_contents('../templates/' . $request['starr'] . '/' . $request['template'] . '.json'), true) : [];
+ $signalr = !empty($scopeAccess['signalr']);
+ unset($scopeAccess['signalr']);
+
+ $fields = [
+ 'name' => $request['name'],
+ 'apikey' => $scopeKey,
+ 'starr_id' => intval($starrApp['id']),
+ 'endpoints' => json_encode($scopeAccess, JSON_UNESCAPED_SLASHES),
+ 'signalr' => intval($signalr)
+ ];
$error = $proxyDb->addApp($fields);
if (!$error) {
diff --git a/root/app/www/public/js/starr.js b/root/app/www/public/js/starr.js
index fd27854..998cabb 100644
--- a/root/app/www/public/js/starr.js
+++ b/root/app/www/public/js/starr.js
@@ -125,6 +125,7 @@ function saveAppStarrAccess(app, id)
params += '&starr_id=' + $('#access-instance').val();
params += '&template=' + $('#access-template').val();
params += '&redactions=' + $('#access-redactions').val();
+ params += '&signalr=' + ($('#access-signalr').prop('checked') ? 1 : 0);
$.each($('[id^=endpoint-counter-]'), function() {
const counter = $(this).attr('id').replace('endpoint-counter-', '');
diff --git a/root/app/www/public/js/templates.js b/root/app/www/public/js/templates.js
index 39b5ec4..89e191e 100644
--- a/root/app/www/public/js/templates.js
+++ b/root/app/www/public/js/templates.js
@@ -38,12 +38,18 @@ function applyTemplateOptions()
const loopId = $(this).prop('id');
$.each(resultData, function(endpoint, methods) {
+ if (!Array.isArray(methods)) {
+ return;
+ }
+
if (loopEndpoint == endpoint && methods.includes(loopMethod)) {
$('#' + loopId).prop('checked', true);
}
});
});
+ $('#access-signalr').prop('checked', resultData.signalr === true);
+
toast('Templates', 'The selected template access has been applied', 'info');
}
});
diff --git a/root/app/www/public/migrations/009_signalr.php b/root/app/www/public/migrations/009_signalr.php
new file mode 100644
index 0000000..f8cee05
--- /dev/null
+++ b/root/app/www/public/migrations/009_signalr.php
@@ -0,0 +1,23 @@
+ '[Q] ' . preg_replace('!\s+!', ' ', $query)]);
+
+ $proxyDb->query($query);
+
+ if ($proxyDb->error() != 'not an error') {
+ logger(MIGRATION_LOG, ['text' => '[R] ' . $proxyDb->error()]);
+ } else {
+ logger(MIGRATION_LOG, ['text' => '[R] query applied!']);
+ }
+}
diff --git a/root/app/www/public/pages/home.php b/root/app/www/public/pages/home.php
index b615af4..609042b 100644
--- a/root/app/www/public/pages/home.php
+++ b/root/app/www/public/pages/home.php
@@ -184,7 +184,8 @@
$appAccess = json_decode($app['endpoints'], true);
if (file_exists($templateFile)) {
- $appTemplate = getFile($templateFile);
+ $appTemplate = getFile($templateFile);
+ unset($appTemplate['signalr']);
if (count($appAccess, COUNT_RECURSIVE) != count($appTemplate, COUNT_RECURSIVE)) {
foreach ($starrsTable as $starrApp) {
diff --git a/root/app/www/public/pages/starr.php b/root/app/www/public/pages/starr.php
index 17ee694..906a957 100644
--- a/root/app/www/public/pages/starr.php
+++ b/root/app/www/public/pages/starr.php
@@ -148,7 +148,8 @@
$templateFile = file_exists($accessApp['template']) ? $accessApp['template'] : str_replace('../', './', $accessApp['template']);
if (file_exists($templateFile)) {
- $templateEndpoints = getFile($templateFile);
+ $templateEndpoints = getFile($templateFile);
+ unset($templateEndpoints['signalr']);
$template = 'Template: ' . count($templateEndpoints, COUNT_RECURSIVE) . ' endpoint' . (count($templateEndpoints, COUNT_RECURSIVE) == 1 ? '' : 's') . '';
}
?>
diff --git a/root/app/www/public/signalr/auth.php b/root/app/www/public/signalr/auth.php
new file mode 100644
index 0000000..a1f9876
--- /dev/null
+++ b/root/app/www/public/signalr/auth.php
@@ -0,0 +1,35 @@
+getAppFromProxiedKey($apikey);
+
+if (!$apikey || !$proxiedApp['starrApp'] || !$proxiedApp['starrAppDetails'] || !$proxiedApp['proxiedAppDetails']) {
+ http_response_code(401);
+ exit();
+}
+
+if (!$proxiedApp['proxiedAppDetails']['signalr']) {
+ http_response_code(403);
+ exit();
+}
+
+$backendUrl = rtrim($proxiedApp['starrAppDetails']['url'], '/');
+$backendParts = parse_url($backendUrl);
+if (!filter_var($backendUrl, FILTER_VALIDATE_URL) || !in_array(strtolower($backendParts['scheme']), ['http', 'https'])) {
+ http_response_code(502);
+ exit();
+}
+
+header('X-Starr-Backend-Url: ' . $backendUrl);
+header('X-Starr-Api-Key: ' . $proxiedApp['starrAppDetails']['apikey']);
+$proxyArgs = $_GET;
+unset($proxyArgs['access_token'], $proxyArgs['apikey']);
+$proxyArgs = ['access_token' => $proxiedApp['starrAppDetails']['apikey']] + $proxyArgs;
+header('X-Starr-Proxy-Args: ' . http_build_query($proxyArgs));
+http_response_code(204);
diff --git a/root/app/www/public/templates/radarr/bazarr.json b/root/app/www/public/templates/radarr/bazarr.json
index 91c887b..0db59d9 100644
--- a/root/app/www/public/templates/radarr/bazarr.json
+++ b/root/app/www/public/templates/radarr/bazarr.json
@@ -1,4 +1,5 @@
{
+ "signalr": true,
"/api/v3/command": [
"post"
],
diff --git a/root/app/www/public/templates/sonarr/bazarr.json b/root/app/www/public/templates/sonarr/bazarr.json
index 35909e3..3909615 100644
--- a/root/app/www/public/templates/sonarr/bazarr.json
+++ b/root/app/www/public/templates/sonarr/bazarr.json
@@ -1,4 +1,5 @@
{
+ "signalr": true,
"/api/v3/command": [
"post"
],
diff --git a/root/etc/nginx/template.conf b/root/etc/nginx/template.conf
index f8f272b..d57f15c 100644
--- a/root/etc/nginx/template.conf
+++ b/root/etc/nginx/template.conf
@@ -133,6 +133,32 @@ http {
fastcgi_param QUERY_STRING endpoint=/api/$2&$args;
fastcgi_index index.php;
}
+
+ # SignalR routing must stay at the NGINX layer because PHP cURL cannot proxy WebSockets.
+ location = /signalr-auth {
+ internal;
+ alias $root/;
+ fastcgi_pass 127.0.0.1:9000;
+ include fastcgi_params;
+ fastcgi_param SCRIPT_FILENAME $root/signalr/auth.php;
+ fastcgi_param QUERY_STRING $args;
+ fastcgi_index index.php;
+ }
+
+ location ~ ^(.*)/signalr/(.*)$ {
+ alias $root/;
+ set $signalr_path $2;
+ auth_request /signalr-auth;
+ auth_request_set $signalr_backend_url $upstream_http_x_starr_backend_url;
+ auth_request_set $signalr_real_key $upstream_http_x_starr_api_key;
+ auth_request_set $signalr_proxy_args $upstream_http_x_starr_proxy_args;
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection $connection_upgrade;
+ proxy_set_header X-Api-Key $signalr_real_key;
+ proxy_pass $signalr_backend_url/signalr/$signalr_path?$signalr_proxy_args;
+ proxy_read_timeout 300s;
+ }
location ~ ^(.*)/feed/(.*)$ {
alias $root/;
fastcgi_pass 127.0.0.1:9000;
From a0837b7f98e664da8ce8aa1eede689759ae48537 Mon Sep 17 00:00:00 2001
From: bakerboy448 <55419169+bakerboy448@users.noreply.github.com>
Date: Sat, 17 Oct 2026 13:53:43 -0500
Subject: [PATCH 2/5] fix(signalr): read apikey and access_token from the
original query string
The auth_request subrequest has no query string, so keys passed as ?apikey= or ?access_token= (browser SignalR clients cannot set headers) were never seen by auth.php. Pass the original request URI to PHP and parse its query string.
---
root/app/www/public/signalr/auth.php | 3 +++
root/etc/nginx/template.conf | 1 +
2 files changed, 4 insertions(+)
diff --git a/root/app/www/public/signalr/auth.php b/root/app/www/public/signalr/auth.php
index a1f9876..dfe12c7 100644
--- a/root/app/www/public/signalr/auth.php
+++ b/root/app/www/public/signalr/auth.php
@@ -6,6 +6,9 @@
require ABSOLUTE_PATH . 'loader.php';
+// The auth_request subrequest carries no query string; nginx passes the original URI.
+parse_str((string) parse_url($_SERVER['ORIG_QUERY'] ?? '', PHP_URL_QUERY), $_GET);
+
$apikey = $_GET['access_token'] ?: $_GET['apikey'] ?: $_SERVER['HTTP_X_API_KEY'];
$proxiedApp = $starr->getAppFromProxiedKey($apikey);
diff --git a/root/etc/nginx/template.conf b/root/etc/nginx/template.conf
index d57f15c..fbe624f 100644
--- a/root/etc/nginx/template.conf
+++ b/root/etc/nginx/template.conf
@@ -142,6 +142,7 @@ http {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $root/signalr/auth.php;
fastcgi_param QUERY_STRING $args;
+ fastcgi_param ORIG_QUERY $request_uri;
fastcgi_index index.php;
}
From 6853f4121cefde0aa110a4d92c6476551da4618b Mon Sep 17 00:00:00 2001
From: bakerboy448 <55419169+bakerboy448@users.noreply.github.com>
Date: Wed, 21 Oct 2026 03:14:17 -0500
Subject: [PATCH 3/5] fix(signalr): run SignalR through the scoped-key endpoint
checks and keep the real key off the client
Replace the per-app signalr flag with allow-list entries (/signalr/messages get, /signalr/messages/negotiate post) checked by the same isAllowedEndpoint and method logic as REST, with the same logging and usage accounting. The auth script moves out of the web root so a client cannot call it, and the upstream path comes from the matched entry. The real key is injected by nginx as X-Api-Key and is no longer placed in the query string.
---
README.md | 6 +-
root/app/www/public/ajax/starr.php | 33 ++---
root/app/www/public/ajax/templates.php | 7 +-
root/app/www/public/api/index.php | 21 ++--
root/app/www/public/classes/Starr.php | 12 +-
root/app/www/public/js/starr.js | 1 -
root/app/www/public/js/templates.js | 6 -
.../app/www/public/migrations/009_signalr.php | 23 ----
root/app/www/public/pages/home.php | 3 +-
root/app/www/public/pages/starr.php | 3 +-
root/app/www/public/signalr/auth.php | 38 ------
.../www/public/templates/radarr/bazarr.json | 7 +-
.../www/public/templates/sonarr/bazarr.json | 7 +-
root/app/www/signalr-auth.php | 113 ++++++++++++++++++
root/etc/nginx/template.conf | 28 +++--
15 files changed, 179 insertions(+), 129 deletions(-)
delete mode 100644 root/app/www/public/migrations/009_signalr.php
delete mode 100644 root/app/www/public/signalr/auth.php
create mode 100644 root/app/www/signalr-auth.php
diff --git a/README.md b/README.md
index efd1b70..4988cd1 100644
--- a/README.md
+++ b/README.md
@@ -88,9 +88,9 @@ If you need to auto add starr apps and 3rd party apps you can do that via the ap
}
```
-The `template` variable is not required but if you do not use an existing template then the app will have no starr api access initially.
-
-SignalR/WebSocket access is separately disabled by default. Enable the SignalR option when configuring an app that needs an arr `/signalr` connection. The Radarr and Sonarr Bazarr templates enable this option automatically.
+The `template` variable is not required but if you do not use an existing template then the app will have no starr api access initially.
+
+SignalR (`/signalr/messages` and `/signalr/messages/negotiate`) is granted like any other endpoint. It is allowed only when the app's endpoint list or template includes those entries, and it goes through the same scoped key, endpoint, and method checks as the REST API. The Radarr and Sonarr Bazarr templates include them.
An example curl would be:
diff --git a/root/app/www/public/ajax/starr.php b/root/app/www/public/ajax/starr.php
index 55ee0aa..2e46dc2 100644
--- a/root/app/www/public/ajax/starr.php
+++ b/root/app/www/public/ajax/starr.php
@@ -154,16 +154,12 @@
|
-
= ucfirst($app) ?> endpoints
@@ -244,9 +240,8 @@
$fields['apikey'] = $_POST['apikey'];
$fields['starr_id'] = intval($_POST['starr_id']);
$fields['endpoints'] = json_encode($endpoints, JSON_UNESCAPED_SLASHES);
- $fields['template'] = $_POST['template'];
- $fields['redactions'] = $_POST['redactions'];
- $fields['signalr'] = intval($_POST['signalr']);
+ $fields['template'] = $_POST['template'];
+ $fields['redactions'] = $_POST['redactions'];
if ($_POST['id'] != 99) {
$error = $proxyDb->updateApp($_POST['id'], $fields);
@@ -311,11 +306,9 @@
$templateFile = file_exists($app['template']) ? $app['template'] : str_replace('../', './', $app['template']);
$appTemplate = getFile($templateFile);
- if ($appTemplate) {
- $app['signalr'] = intval(!empty($appTemplate['signalr']));
- unset($appTemplate['signalr']);
- $app['endpoints'] = json_encode($appTemplate);
- $error = $proxyDb->updateApp($_POST['appId'], $app);
+ if ($appTemplate) {
+ $app['endpoints'] = json_encode($appTemplate);
+ $error = $proxyDb->updateApp($_POST['appId'], $app);
}
break;
@@ -338,12 +331,8 @@
}
$endpoints = [];
- foreach ($templateFile as $templateEndpoint => $methods) {
- if (!is_array($methods)) {
- continue;
- }
-
- foreach ($methods as $method) {
+ foreach ($templateFile as $templateEndpoint => $methods) {
+ foreach ($methods as $method) {
$endpoints[$templateEndpoint][$method][] = 'template';
}
}
diff --git a/root/app/www/public/ajax/templates.php b/root/app/www/public/ajax/templates.php
index 55aaadf..f9e60f6 100644
--- a/root/app/www/public/ajax/templates.php
+++ b/root/app/www/public/ajax/templates.php
@@ -42,11 +42,8 @@
}
if ($_POST['m'] == 'saveTemplateStarrAccess') {
- $existing = $proxyDb->getAppFromId($_POST['id'], $appsTable);
- $endpoints = $existing['endpoints'] ? json_decode($existing['endpoints'], true) : [];
- if ($existing['signalr']) {
- $endpoints['signalr'] = true;
- }
+ $existing = $proxyDb->getAppFromId($_POST['id'], $appsTable);
+ $endpoints = $existing['endpoints'] ? json_decode($existing['endpoints'], true) : [];
$name = strtolower(preg_replace('/[^a-zA-Z0-9 _-]/', '', $_POST['name']));
file_put_contents(APP_USER_TEMPLATES_PATH . $app . '/' . $name . '.json', json_encode($endpoints, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
}
diff --git a/root/app/www/public/api/index.php b/root/app/www/public/api/index.php
index 68c0661..f1c08c4 100644
--- a/root/app/www/public/api/index.php
+++ b/root/app/www/public/api/index.php
@@ -118,18 +118,15 @@
$starrApp = $starr->getAppFromStarrKey($request['apikey'], $starrsTable);
if (!$error) {
- $scopeKey = generateApikey();
- $scopeAccess = $request['template'] ? json_decode(file_get_contents('../templates/' . $request['starr'] . '/' . $request['template'] . '.json'), true) : [];
- $signalr = !empty($scopeAccess['signalr']);
- unset($scopeAccess['signalr']);
-
- $fields = [
- 'name' => $request['name'],
- 'apikey' => $scopeKey,
- 'starr_id' => intval($starrApp['id']),
- 'endpoints' => json_encode($scopeAccess, JSON_UNESCAPED_SLASHES),
- 'signalr' => intval($signalr)
- ];
+ $scopeKey = generateApikey();
+ $scopeAccess = $request['template'] ? json_decode(file_get_contents('../templates/' . $request['starr'] . '/' . $request['template'] . '.json'), true) : [];
+
+ $fields = [
+ 'name' => $request['name'],
+ 'apikey' => $scopeKey,
+ 'starr_id' => intval($starrApp['id']),
+ 'endpoints' => json_encode($scopeAccess, JSON_UNESCAPED_SLASHES)
+ ];
$error = $proxyDb->addApp($fields);
if (!$error) {
diff --git a/root/app/www/public/classes/Starr.php b/root/app/www/public/classes/Starr.php
index 2174556..2b55ce7 100644
--- a/root/app/www/public/classes/Starr.php
+++ b/root/app/www/public/classes/Starr.php
@@ -97,7 +97,7 @@ public function getEndpoints($app)
if ($cache) {
$cached = json_decode($cache, true);
- return is_array($cached) ? $cached : $endpoints;
+ return $this->addSignalrEndpoints(is_array($cached) ? $cached : $endpoints);
}
switch ($app) {
@@ -169,6 +169,16 @@ public function getEndpoints($app)
$this->cache->set($cacheKey, json_encode($endpoints), STARR_ENDPOINT_LIST_TIME);
}
+ return $this->addSignalrEndpoints($endpoints);
+ }
+
+ //-- SIGNALR IS NOT IN THE OPENAPI SPEC, LIST IT SO IT IS GRANTED LIKE ANY OTHER ENDPOINT
+ private function addSignalrEndpoints($endpoints)
+ {
+ $endpoints = $endpoints ?: [];
+ $endpoints['/signalr/messages'] = ['label' => 'SignalR', 'methods' => ['get']];
+ $endpoints['/signalr/messages/negotiate'] = ['label' => 'SignalR', 'methods' => ['post']];
+
return $endpoints;
}
diff --git a/root/app/www/public/js/starr.js b/root/app/www/public/js/starr.js
index 998cabb..fd27854 100644
--- a/root/app/www/public/js/starr.js
+++ b/root/app/www/public/js/starr.js
@@ -125,7 +125,6 @@ function saveAppStarrAccess(app, id)
params += '&starr_id=' + $('#access-instance').val();
params += '&template=' + $('#access-template').val();
params += '&redactions=' + $('#access-redactions').val();
- params += '&signalr=' + ($('#access-signalr').prop('checked') ? 1 : 0);
$.each($('[id^=endpoint-counter-]'), function() {
const counter = $(this).attr('id').replace('endpoint-counter-', '');
diff --git a/root/app/www/public/js/templates.js b/root/app/www/public/js/templates.js
index 89e191e..39b5ec4 100644
--- a/root/app/www/public/js/templates.js
+++ b/root/app/www/public/js/templates.js
@@ -38,18 +38,12 @@ function applyTemplateOptions()
const loopId = $(this).prop('id');
$.each(resultData, function(endpoint, methods) {
- if (!Array.isArray(methods)) {
- return;
- }
-
if (loopEndpoint == endpoint && methods.includes(loopMethod)) {
$('#' + loopId).prop('checked', true);
}
});
});
- $('#access-signalr').prop('checked', resultData.signalr === true);
-
toast('Templates', 'The selected template access has been applied', 'info');
}
});
diff --git a/root/app/www/public/migrations/009_signalr.php b/root/app/www/public/migrations/009_signalr.php
deleted file mode 100644
index f8cee05..0000000
--- a/root/app/www/public/migrations/009_signalr.php
+++ /dev/null
@@ -1,23 +0,0 @@
- '[Q] ' . preg_replace('!\s+!', ' ', $query)]);
-
- $proxyDb->query($query);
-
- if ($proxyDb->error() != 'not an error') {
- logger(MIGRATION_LOG, ['text' => '[R] ' . $proxyDb->error()]);
- } else {
- logger(MIGRATION_LOG, ['text' => '[R] query applied!']);
- }
-}
diff --git a/root/app/www/public/pages/home.php b/root/app/www/public/pages/home.php
index 609042b..b615af4 100644
--- a/root/app/www/public/pages/home.php
+++ b/root/app/www/public/pages/home.php
@@ -184,8 +184,7 @@
$appAccess = json_decode($app['endpoints'], true);
if (file_exists($templateFile)) {
- $appTemplate = getFile($templateFile);
- unset($appTemplate['signalr']);
+ $appTemplate = getFile($templateFile);
if (count($appAccess, COUNT_RECURSIVE) != count($appTemplate, COUNT_RECURSIVE)) {
foreach ($starrsTable as $starrApp) {
diff --git a/root/app/www/public/pages/starr.php b/root/app/www/public/pages/starr.php
index 906a957..17ee694 100644
--- a/root/app/www/public/pages/starr.php
+++ b/root/app/www/public/pages/starr.php
@@ -148,8 +148,7 @@
$templateFile = file_exists($accessApp['template']) ? $accessApp['template'] : str_replace('../', './', $accessApp['template']);
if (file_exists($templateFile)) {
- $templateEndpoints = getFile($templateFile);
- unset($templateEndpoints['signalr']);
+ $templateEndpoints = getFile($templateFile);
$template = 'Template: ' . count($templateEndpoints, COUNT_RECURSIVE) . ' endpoint' . (count($templateEndpoints, COUNT_RECURSIVE) == 1 ? '' : 's') . '';
}
?>
diff --git a/root/app/www/public/signalr/auth.php b/root/app/www/public/signalr/auth.php
deleted file mode 100644
index dfe12c7..0000000
--- a/root/app/www/public/signalr/auth.php
+++ /dev/null
@@ -1,38 +0,0 @@
-getAppFromProxiedKey($apikey);
-
-if (!$apikey || !$proxiedApp['starrApp'] || !$proxiedApp['starrAppDetails'] || !$proxiedApp['proxiedAppDetails']) {
- http_response_code(401);
- exit();
-}
-
-if (!$proxiedApp['proxiedAppDetails']['signalr']) {
- http_response_code(403);
- exit();
-}
-
-$backendUrl = rtrim($proxiedApp['starrAppDetails']['url'], '/');
-$backendParts = parse_url($backendUrl);
-if (!filter_var($backendUrl, FILTER_VALIDATE_URL) || !in_array(strtolower($backendParts['scheme']), ['http', 'https'])) {
- http_response_code(502);
- exit();
-}
-
-header('X-Starr-Backend-Url: ' . $backendUrl);
-header('X-Starr-Api-Key: ' . $proxiedApp['starrAppDetails']['apikey']);
-$proxyArgs = $_GET;
-unset($proxyArgs['access_token'], $proxyArgs['apikey']);
-$proxyArgs = ['access_token' => $proxiedApp['starrAppDetails']['apikey']] + $proxyArgs;
-header('X-Starr-Proxy-Args: ' . http_build_query($proxyArgs));
-http_response_code(204);
diff --git a/root/app/www/public/templates/radarr/bazarr.json b/root/app/www/public/templates/radarr/bazarr.json
index 0db59d9..bb1b448 100644
--- a/root/app/www/public/templates/radarr/bazarr.json
+++ b/root/app/www/public/templates/radarr/bazarr.json
@@ -1,5 +1,4 @@
{
- "signalr": true,
"/api/v3/command": [
"post"
],
@@ -32,5 +31,11 @@
],
"/api/system/status": [
"get"
+ ],
+ "/signalr/messages": [
+ "get"
+ ],
+ "/signalr/messages/negotiate": [
+ "post"
]
}
diff --git a/root/app/www/public/templates/sonarr/bazarr.json b/root/app/www/public/templates/sonarr/bazarr.json
index 3909615..1fd504d 100644
--- a/root/app/www/public/templates/sonarr/bazarr.json
+++ b/root/app/www/public/templates/sonarr/bazarr.json
@@ -1,5 +1,4 @@
{
- "signalr": true,
"/api/v3/command": [
"post"
],
@@ -44,5 +43,11 @@
],
"/api/system/status": [
"get"
+ ],
+ "/signalr/messages": [
+ "get"
+ ],
+ "/signalr/messages/negotiate": [
+ "post"
]
}
diff --git a/root/app/www/signalr-auth.php b/root/app/www/signalr-auth.php
new file mode 100644
index 0000000..dd1e80c
--- /dev/null
+++ b/root/app/www/signalr-auth.php
@@ -0,0 +1,113 @@
+/signalr/*. Lives outside the web root so it can only be
+ * reached through the internal /signalr-auth location, never directly by a client.
+ *
+ * Applies the same scoped-key lookup, endpoint/method allow-list, logging, and usage accounting
+ * as api/index.php. The only response is status + headers consumed by nginx, which never forwards
+ * them to the client. The upstream path comes from the matched allow-list entry, not the request.
+ */
+
+if (!defined('ABSOLUTE_PATH')) {
+ define('ABSOLUTE_PATH', __DIR__ . '/public/');
+}
+
+require ABSOLUTE_PATH . 'loader.php';
+
+if (($_SERVER['SIGNALR_SUBREQUEST'] ?? '') !== '1') {
+ http_response_code(404);
+ exit();
+}
+
+$requestCounter = $cache->get(REQUEST_COUNTER_KEY) + 1;
+$cache->set(REQUEST_COUNTER_KEY, $requestCounter, REQUEST_COUNTER_TIME);
+
+$logfile = LOGS_PATH . 'access.log';
+$method = strtolower($_SERVER['ORIG_METHOD'] ?? '');
+$uri = (string) ($_SERVER['ORIG_URI'] ?? '');
+$path = (string) parse_url($uri, PHP_URL_PATH);
+$rawArgs = (string) parse_url($uri, PHP_URL_QUERY);
+
+//-- SPLIT THE QUERY, KEEPING IT RAW. THE SCOPED KEY NEVER GOES TO THE ARR
+$apikey = '';
+$keepArgs = [];
+foreach (array_filter(explode('&', $rawArgs), 'strlen') as $arg) {
+ $name = strtolower(urldecode(explode('=', $arg, 2)[0]));
+ if (in_array($name, ['access_token', 'apikey'])) {
+ $apikey = $apikey ?: urldecode(explode('=', $arg, 2)[1] ?? '');
+ continue;
+ }
+ $keepArgs[] = $arg;
+}
+$apikey = $apikey ?: ($_SERVER['HTTP_X_API_KEY'] ?? '');
+
+//-- //signalr/ -> /signalr/
+$endpoint = '';
+if (preg_match('#/signalr/(.*)$#i', $path, $match)) {
+ $endpoint = rtrim('/signalr/' . strtolower($match[1]), '/');
+}
+
+if (!$apikey) {
+ logger($logfile, ['req' => $requestCounter, 'proxyCode' => 401]);
+ http_response_code(401);
+ exit();
+}
+
+$proxiedApp = $starr->getAppFromProxiedKey($apikey);
+
+if (!$proxiedApp || !$proxiedApp['starrApp']) {
+ logger($logfile, ['req' => $requestCounter, 'apikey' => $apikey, 'endpoint' => $endpoint, 'proxyCode' => 401]);
+ http_response_code(401);
+ exit();
+}
+
+$proxiedAppLogfile = str_replace('access.log', 'access_' . $proxiedApp['proxiedAppDetails']['name'] . '.log', $logfile);
+$app = $proxiedApp['starrApp'];
+
+$block = function ($code, $extra = []) use ($logfile, $proxiedAppLogfile, $requestCounter, $apikey, $endpoint, $proxiedApp, $usageDb, $proxyDb, $notifications) {
+ logger($logfile, ['req' => $requestCounter, 'apikey' => $apikey, 'endpoint' => $endpoint, 'proxyCode' => $code]);
+ logger($proxiedAppLogfile, ['req' => $requestCounter, 'apikey' => $apikey, 'endpoint' => $endpoint, 'proxyCode' => $code]);
+ $usageDb->adjustAppUsage($proxiedApp['proxiedAppDetails']['id'], $code);
+
+ if ($proxyDb->isNotificationTriggerEnabled('blocked')) {
+ $notifications->notify(0, 'blocked', [
+ 'event' => 'blocked',
+ 'proxyApp' => $proxiedApp['proxiedAppDetails']['name'],
+ 'starrApp' => $proxiedApp['starrAppDetails']['name'],
+ 'endpoint' => $endpoint
+ ] + $extra);
+ }
+
+ //-- auth_request only understands 401/403
+ http_response_code($code == 405 ? 403 : 401);
+ exit();
+};
+
+$isAllowedEndpoint = $starr->isAllowedEndpoint($app, $proxiedApp['access'], $endpoint);
+if (!$isAllowedEndpoint['allowed']) {
+ $block(401);
+}
+
+$endpoint = $isAllowedEndpoint['starrEndpoint'];
+if (!$starr->isAllowedEndpointMethod($proxiedApp['access'], $endpoint, $method)) {
+ $block(405, ['method' => $method]);
+}
+
+$backendUrl = rtrim($proxiedApp['starrAppDetails']['url'], '/');
+$backendParts = parse_url($backendUrl);
+if (!filter_var($backendUrl, FILTER_VALIDATE_URL) || !in_array(strtolower($backendParts['scheme']), ['http', 'https'])) {
+ http_response_code(502);
+ exit();
+}
+
+logger($logfile, ['req' => $requestCounter, 'apikey' => $apikey, 'endpoint' => $endpoint, 'proxyCode' => 200]);
+logger($proxiedAppLogfile, ['req' => $requestCounter, 'apikey' => $apikey, 'endpoint' => $endpoint, 'proxyCode' => 200]);
+$usageDb->adjustAppUsage($proxiedApp['proxiedAppDetails']['id'], 200);
+
+//-- CONSUMED BY NGINX ONLY. THE REAL KEY IS INJECTED AS A REQUEST HEADER TO THE ARR
+header('X-Starr-Backend-Url: ' . $backendUrl);
+header('X-Starr-Endpoint: ' . $endpoint);
+header('X-Starr-Proxy-Args: ' . ($keepArgs ? '?' . implode('&', $keepArgs) : ''));
+header('X-Starr-Api-Key: ' . $proxiedApp['starrAppDetails']['apikey']);
+http_response_code(204);
diff --git a/root/etc/nginx/template.conf b/root/etc/nginx/template.conf
index fbe624f..152db48 100644
--- a/root/etc/nginx/template.conf
+++ b/root/etc/nginx/template.conf
@@ -134,30 +134,34 @@ http {
fastcgi_index index.php;
}
- # SignalR routing must stay at the NGINX layer because PHP cURL cannot proxy WebSockets.
+ # SignalR must stay at the NGINX layer because PHP cURL cannot proxy WebSockets.
+ # Auth, endpoint, and method checks run in a PHP script outside the web root, reachable only from here.
location = /signalr-auth {
internal;
- alias $root/;
fastcgi_pass 127.0.0.1:9000;
include fastcgi_params;
- fastcgi_param SCRIPT_FILENAME $root/signalr/auth.php;
- fastcgi_param QUERY_STRING $args;
- fastcgi_param ORIG_QUERY $request_uri;
- fastcgi_index index.php;
+ fastcgi_param SCRIPT_FILENAME /app/www/signalr-auth.php;
+ fastcgi_param QUERY_STRING "";
+ fastcgi_param SIGNALR_SUBREQUEST 1;
+ fastcgi_param ORIG_URI $request_uri;
+ fastcgi_param ORIG_METHOD $request_method;
+ fastcgi_pass_request_body off;
+ fastcgi_param CONTENT_LENGTH "";
}
location ~ ^(.*)/signalr/(.*)$ {
alias $root/;
- set $signalr_path $2;
auth_request /signalr-auth;
- auth_request_set $signalr_backend_url $upstream_http_x_starr_backend_url;
- auth_request_set $signalr_real_key $upstream_http_x_starr_api_key;
- auth_request_set $signalr_proxy_args $upstream_http_x_starr_proxy_args;
+ auth_request_set $signalr_backend $upstream_http_x_starr_backend_url;
+ auth_request_set $signalr_endpoint $upstream_http_x_starr_endpoint;
+ auth_request_set $signalr_args $upstream_http_x_starr_proxy_args;
+ auth_request_set $signalr_key $upstream_http_x_starr_api_key;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
- proxy_set_header X-Api-Key $signalr_real_key;
- proxy_pass $signalr_backend_url/signalr/$signalr_path?$signalr_proxy_args;
+ proxy_set_header X-Api-Key $signalr_key;
+ proxy_set_header Authorization "";
+ proxy_pass $signalr_backend$signalr_endpoint$signalr_args;
proxy_read_timeout 300s;
}
location ~ ^(.*)/feed/(.*)$ {
From 7d0d11a1e770c23ebaf33936aba7691da13cf323 Mon Sep 17 00:00:00 2001
From: bakerboy448 <55419169+bakerboy448@users.noreply.github.com>
Date: Wed, 21 Oct 2026 08:59:14 -0500
Subject: [PATCH 4/5] fix(signalr): refuse SignalR for redacted apps, cap
connections, restrict Upgrade and X-Accel-Redirect
Apps with redactions set get 401 on SignalR (the stream is unredacted). Only websocket Upgrade is forwarded. SignalR auth is logged as auth-granted instead of 200. SignalR location moved after /feed/ and /backup/. X-Accel-Redirect ignored on fastcgi and SignalR proxy. Concurrent SignalR connections capped at 10 per client address (429).
---
README.md | 2 ++
root/app/www/public/classes/Starr.php | 4 +--
root/app/www/signalr-auth.php | 10 ++++--
root/etc/nginx/template.conf | 49 +++++++++++++++++++--------
4 files changed, 46 insertions(+), 19 deletions(-)
diff --git a/README.md b/README.md
index 4988cd1..d878883 100644
--- a/README.md
+++ b/README.md
@@ -92,6 +92,8 @@ The `template` variable is not required but if you do not use an existing templa
SignalR (`/signalr/messages` and `/signalr/messages/negotiate`) is granted like any other endpoint. It is allowed only when the app's endpoint list or template includes those entries, and it goes through the same scoped key, endpoint, and method checks as the REST API. The Radarr and Sonarr Bazarr templates include them.
+The SignalR stream carries every broadcast event from the arr and cannot be field-redacted, so an app with any Redactions set is refused SignalR (401). Auth results for SignalR are logged as `proxyCode:auth-granted` (nginx contacts the arr after the auth check, so the real upstream status is not known to the log) and count as `allowed` in usage. SignalR connections are capped at 10 concurrent per client address (429 over the cap).
+
An example curl would be:
``` bash
diff --git a/root/app/www/public/classes/Starr.php b/root/app/www/public/classes/Starr.php
index 2b55ce7..a17dd8d 100644
--- a/root/app/www/public/classes/Starr.php
+++ b/root/app/www/public/classes/Starr.php
@@ -176,8 +176,8 @@ public function getEndpoints($app)
private function addSignalrEndpoints($endpoints)
{
$endpoints = $endpoints ?: [];
- $endpoints['/signalr/messages'] = ['label' => 'SignalR', 'methods' => ['get']];
- $endpoints['/signalr/messages/negotiate'] = ['label' => 'SignalR', 'methods' => ['post']];
+ $endpoints['/signalr/messages'] = ['label' => 'SignalR (all broadcast events, unredacted)', 'methods' => ['get']];
+ $endpoints['/signalr/messages/negotiate'] = ['label' => 'SignalR (negotiate)', 'methods' => ['post']];
return $endpoints;
}
diff --git a/root/app/www/signalr-auth.php b/root/app/www/signalr-auth.php
index dd1e80c..7da8bc1 100644
--- a/root/app/www/signalr-auth.php
+++ b/root/app/www/signalr-auth.php
@@ -84,6 +84,11 @@
exit();
};
+//-- THE SIGNALR STREAM CANNOT BE FIELD-REDACTED, SO AN APP WITH REDACTIONS NEVER GETS IT
+if (trim((string) $proxiedApp['proxiedAppDetails']['redactions'])) {
+ $block(401);
+}
+
$isAllowedEndpoint = $starr->isAllowedEndpoint($app, $proxiedApp['access'], $endpoint);
if (!$isAllowedEndpoint['allowed']) {
$block(401);
@@ -101,8 +106,9 @@
exit();
}
-logger($logfile, ['req' => $requestCounter, 'apikey' => $apikey, 'endpoint' => $endpoint, 'proxyCode' => 200]);
-logger($proxiedAppLogfile, ['req' => $requestCounter, 'apikey' => $apikey, 'endpoint' => $endpoint, 'proxyCode' => 200]);
+//-- AUTH ONLY: THE ARR HAS NOT BEEN CONTACTED YET, SO THIS IS NOT A PROXY RESULT AND IS NOT LOGGED AS 200
+logger($logfile, ['req' => $requestCounter, 'apikey' => $apikey, 'endpoint' => $endpoint, 'proxyCode' => 'auth-granted']);
+logger($proxiedAppLogfile, ['req' => $requestCounter, 'apikey' => $apikey, 'endpoint' => $endpoint, 'proxyCode' => 'auth-granted']);
$usageDb->adjustAppUsage($proxiedApp['proxiedAppDetails']['id'], 200);
//-- CONSUMED BY NGINX ONLY. THE REAL KEY IS INJECTED AS A REQUEST HEADER TO THE ARR
diff --git a/root/etc/nginx/template.conf b/root/etc/nginx/template.conf
index 152db48..d47736f 100644
--- a/root/etc/nginx/template.conf
+++ b/root/etc/nginx/template.conf
@@ -66,6 +66,19 @@ http {
'' close;
}
+ # Forward Upgrade to the arr only for real websocket upgrades (SignalR); anything else (h2c etc.) is dropped.
+ map $http_upgrade $signalr_upgrade {
+ default '';
+ ~*^websocket$ websocket;
+ }
+ map $signalr_upgrade $signalr_connection {
+ default upgrade;
+ '' close;
+ }
+
+ # Long-lived SignalR websockets: cap concurrent connections per client address.
+ limit_conn_zone $binary_remote_addr zone=signalr:10m;
+
# Enable http2 by default for all servers
# http2 on;
@@ -121,6 +134,7 @@ http {
fastcgi_pass 127.0.0.1:9000;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $root/$1;
+ fastcgi_ignore_headers X-Accel-Redirect;
fastcgi_index index.php;
}
@@ -130,6 +144,7 @@ http {
fastcgi_pass 127.0.0.1:9000;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $root/api/index.php;
+ fastcgi_ignore_headers X-Accel-Redirect;
fastcgi_param QUERY_STRING endpoint=/api/$2&$args;
fastcgi_index index.php;
}
@@ -149,21 +164,6 @@ http {
fastcgi_param CONTENT_LENGTH "";
}
- location ~ ^(.*)/signalr/(.*)$ {
- alias $root/;
- auth_request /signalr-auth;
- auth_request_set $signalr_backend $upstream_http_x_starr_backend_url;
- auth_request_set $signalr_endpoint $upstream_http_x_starr_endpoint;
- auth_request_set $signalr_args $upstream_http_x_starr_proxy_args;
- auth_request_set $signalr_key $upstream_http_x_starr_api_key;
- proxy_http_version 1.1;
- proxy_set_header Upgrade $http_upgrade;
- proxy_set_header Connection $connection_upgrade;
- proxy_set_header X-Api-Key $signalr_key;
- proxy_set_header Authorization "";
- proxy_pass $signalr_backend$signalr_endpoint$signalr_args;
- proxy_read_timeout 300s;
- }
location ~ ^(.*)/feed/(.*)$ {
alias $root/;
fastcgi_pass 127.0.0.1:9000;
@@ -198,6 +198,25 @@ http {
return 404 '{"error":"Starr Proxy: Invalid starr route"}';
}
+ location ~ ^(.*)/signalr/(.*)$ {
+ alias $root/;
+ auth_request /signalr-auth;
+ auth_request_set $signalr_backend $upstream_http_x_starr_backend_url;
+ auth_request_set $signalr_endpoint $upstream_http_x_starr_endpoint;
+ auth_request_set $signalr_args $upstream_http_x_starr_proxy_args;
+ auth_request_set $signalr_key $upstream_http_x_starr_api_key;
+ proxy_http_version 1.1;
+ limit_conn signalr 10;
+ limit_conn_status 429;
+ proxy_ignore_headers X-Accel-Redirect;
+ proxy_set_header Upgrade $signalr_upgrade;
+ proxy_set_header Connection $signalr_connection;
+ proxy_set_header X-Api-Key $signalr_key;
+ proxy_set_header Authorization "";
+ proxy_pass $signalr_backend$signalr_endpoint$signalr_args;
+ proxy_read_timeout 300s;
+ }
+
# Health check
location /health {
return 200 'healthy';
From 9b5cd89be313c9c230875272a5d7b538a2a1c89e Mon Sep 17 00:00:00 2001
From: bakerboy448 <55419169+bakerboy448@users.noreply.github.com>
Date: Wed, 21 Oct 2026 13:45:54 -0500
Subject: [PATCH 5/5] fix(signalr): ignore X-Accel-Redirect on every fastcgi
location, document SignalR limits
---
README.md | 5 +++++
root/etc/nginx/template.conf | 3 +++
2 files changed, 8 insertions(+)
diff --git a/README.md b/README.md
index d878883..930440d 100644
--- a/README.md
+++ b/README.md
@@ -94,6 +94,11 @@ SignalR (`/signalr/messages` and `/signalr/messages/negotiate`) is granted like
The SignalR stream carries every broadcast event from the arr and cannot be field-redacted, so an app with any Redactions set is refused SignalR (401). Auth results for SignalR are logged as `proxyCode:auth-granted` (nginx contacts the arr after the auth check, so the real upstream status is not known to the log) and count as `allowed` in usage. SignalR connections are capped at 10 concurrent per client address (429 over the cap).
+SignalR limitations:
+
+- nginx opens the SignalR upstream itself, resolving the arr hostname through Docker's embedded DNS (`127.0.0.11`). An arr URL that only resolves through `/etc/hosts`, `localhost`, `extra_hosts`, or host networking works for REST but fails for SignalR. Use an IP address or a hostname resolvable by Docker DNS in the starr app URL.
+- The 10 connection cap keys on the client address nginx sees. starrproxy has no trusted-proxy setting, so behind a reverse proxy every client shares one cap of 10.
+
An example curl would be:
``` bash
diff --git a/root/etc/nginx/template.conf b/root/etc/nginx/template.conf
index d47736f..664b86d 100644
--- a/root/etc/nginx/template.conf
+++ b/root/etc/nginx/template.conf
@@ -156,6 +156,7 @@ http {
fastcgi_pass 127.0.0.1:9000;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /app/www/signalr-auth.php;
+ fastcgi_ignore_headers X-Accel-Redirect;
fastcgi_param QUERY_STRING "";
fastcgi_param SIGNALR_SUBREQUEST 1;
fastcgi_param ORIG_URI $request_uri;
@@ -169,6 +170,7 @@ http {
fastcgi_pass 127.0.0.1:9000;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $root/api/index.php;
+ fastcgi_ignore_headers X-Accel-Redirect;
fastcgi_param QUERY_STRING feed=/feed/$2&$args;
fastcgi_index index.php;
}
@@ -177,6 +179,7 @@ http {
fastcgi_pass 127.0.0.1:9000;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $root/api/index.php;
+ fastcgi_ignore_headers X-Accel-Redirect;
fastcgi_param QUERY_STRING backup=/backup/$2&$args;
fastcgi_index index.php;
}
|