CSP related

In the latest PHPMaker 2026 release, there is a CSP-related issue when using ParagonIE CSPBuilder with WebSocket sources.

When adding:

ws://localhost:2000
wss://localhost:2000

into CSP.connect-src.allow, the final generated CSP header is incorrect:

connect-src 'self' https://*.googleapis.com https://*.gstatic.com https://*.google.com *.google.com ws://:2000 wss://:2000

Your approach is correct, but how did you add them into CSP.connect-src.allow?

If you add them correctly, they will work, for example, you can follow the examples of Global Code:

$config = Config();
$config->append('CSP.connect-src.allow', 'ws://localhost:2000');
$config->append('CSP.connect-src.allow', 'wss://localhost:2000');

In Global code ('userfn.php') we add the following:

$host = $_SERVER['HTTP_HOST'] ?? null;

if ($host) {
    $config = Config();
    $config->append('CSP.connect-src.allow', "ws://{$host}:2000");
    $config->append('CSP.connect-src.allow', "wss://{$host}:2000");
}

Elsewhere, for example in a custom file:

dump(Config("CSP.connect-src.allow"));
exit();

Output:

array:5 [▼
  0 => "https://*.googleapis.com"
  1 => "https://*.gstatic.com"
  2 => "*.google.com"
  3 => "ws://127.0.0.1:2000"
  4 => "wss://127.0.0.1:2000"
]

In CspListener.php:

public function onKernelResponse(ResponseEvent $event): void
{
    //...
    // Retrieve the headers
    $cspHeaders = $this->builder->getHeaderArray(false);

    dump($this->builder);
    exit();
    //...
}

Output:

CspListener.php on line 55:
ParagonIE\CSPBuilder\CSPBuilder {#930 ▼
  -policies: array:12 [▼
    "report-only" => false
    "font-src" => array:3 [▶]
    "form-action" => array:2 [▶]
    "object-src" => array:1 [▶]
    "frame-ancestors" => array:1 [▶]
    "frame-src" => array:3 [▶]
    "script-src" => array:7 [▶]
    "connect-src" => array:4 [▼
      "self" => true
      "blob" => true
      "data" => true
      "allow" => array:5 [▼
        0 => "https://*.googleapis.com"
        1 => "https://*.gstatic.com"
        2 => "*.google.com"
        3 => "ws://:2000"
        4 => "wss://:2000"
      ]
...
    ]
  ]
}

Issue Summary:
The HTTP_HOST value (127.0.0.1) is correctly stored in the config, but when CSPBuilder processes it, the host part is lost — only ws://:2000 and wss://:2000 remain (the host is missing between :// and :2000).

If you do it this way: (in Global Code)

    $host = Request()?->getHost() ?: ($_SERVER['HTTP_HOST'] ?? '');
    //...
    $config = Config();
    $config->append('CSP.connect-src.allow', "ws://{$host}:2000");
    $config->append('CSP.connect-src.allow', "wss://{$host}:2000");

It will remove the famous $host line. (It worked without any issues until version 2025.10)

Only Container(CSPBuilder::class)?->addSource directly, which is an unofficial method, works for this version (2026.10):

// Page Unloaded event
function Page_Unloaded(): void
{
//...

	$host = $_SERVER['HTTP_HOST'] ?? null;
	if ($host) {
		$id = CSPBuilder::class;
		$csp = Container($id);
		$csp->addSource('connect', "ws://{$host}:2000");
		$csp->addSource('connect', "wss://{$host}:2000");
	}

//...
}

Real cause is that there is no $_SERVER['HTTP_HOST'] when Symfony builds the cache via CLI. Your approach of getting it during runtime is viable, addSource() is also a public method and the correct method to use.

The issue is that the current implementation is fundamentally failing to handle dynamic hosts in a way that respects CSP best practices.

Instead of expecting the dynamic host, why don't you use logical if-else that will handle both host for localhost and host for production server?