Using clients 

The public interfaces use the \Netresearch\HttpGuard namespace and are provided through TYPO3 services. Applications should inject bound PSR-18 clients or Public Fetch. The internal Guzzle client and its progress and cancellation driver belong together; they do not provide an additional public credential API.

Ordinary Core requests 

The registered Core path uses the public default policy. Existing supported Core middleware and context restrictions remain effective in addition. An ordinary call can look like this:

Public retrieval with TYPO3 RequestFactory
$response = $requestFactory->request(
    'https://www.example.org/document.json',
    'GET',
    ['timeout' => 10, 'http_errors' => false],
);
Copied!

$requestFactory is an injected \TYPO3\CMS\Core\Http\RequestFactory . For public content from untrusted URLs, Public Fetch provides a narrower API. Configuring an endpoint profile does not grant internal access to RequestFactory requests.

Bound integration clients 

class EndpointClientFactoryInterface
Fully qualified name
\Netresearch\HttpGuard\EndpointClientFactoryInterface
forEndpoint(string $configuredEndpointId)

Creates a client bound to a configured endpoint profile. Unknown, foreign or expired bindings are rejected.

The profile ID belongs in trusted service code or dependency injection configuration. It is not taken from the current user request. The following example uses the profile from Internal endpoint example:

Classes/Service/ErpClient.php in a sitepackage
<?php

declare (strict_types=1);
namespace Vendor\SitePackage\Service;

use GuzzleHttp\Psr7\Request;
use Netresearch\HttpGuard\EndpointClientFactoryInterface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\ResponseInterface;
final readonly class ErpClient
{
    private ClientInterface $client;
    public function __construct(EndpointClientFactoryInterface $factory)
    {
        $this->client = $factory->forEndpoint('erp-orders');
    }
    public function sendOrder(array $order): ResponseInterface
    {
        return $this->client->sendRequest(
            new Request(
                'POST',
                'https://erp.internal.example:8443/orders',
                ['Content-Type' => 'application/json'],
                json_encode($order, JSON_THROW_ON_ERROR)
            )
        );
    }
}
Copied!

The client checks the origin, method and all resolved addresses on every send. PSR-18 sendRequest() does not follow redirects and returns HTTP error responses such as 404 or 500 as response objects. Network errors and policy denials remain exceptions. Never bypass a policy error with an unprotected replacement client.

Public Fetch 

class PublicFetchClientInterface
Fully qualified name
\Netresearch\HttpGuard\PublicFetchClientInterface
fetch(Psr\Http\Message\UriInterface $uri, string $method = 'GET')

Accepts only GET or HEAD and creates its own public request without inherited credentials or a body.

Public Fetch does not inherit authentication, cookies, client certificates, SSL keys, custom headers, bodies or query defaults from another client. Its fixed header list consists of Accept, Accept-Encoding and User-Agent. The query of an accepted target URI remains part of that URI and is not logged. Permitted public redirects can reach another origin without forwarding the secrets of an original integration request.

Validate the raw URL before constructing a PSR-7 object
<?php

declare (strict_types=1);
namespace Vendor\SitePackage\Service;

use GuzzleHttp\Psr7\Uri;
use Netresearch\HttpGuard\PublicFetchClientInterface;
use Netresearch\HttpGuard\TargetNormalizer;
use Psr\Http\Message\ResponseInterface;
final readonly class PublicDocument
{
    public function __construct(private PublicFetchClientInterface $client)
    {
    }
    public function fetch(string $rawUrl): ResponseInterface
    {
        (new TargetNormalizer())->assertRawUri($rawUrl);
        return $this->client->fetch(new Uri($rawUrl));
    }
}
Copied!

No endpoint ID is accepted for a user-driven fetch. Private targets remain blocked even when the project has an internal endpoint profile with the same hostname.

Raw URLs and PSR-7 

TargetNormalizer::assertRawUri(string $uri) checks forbidden syntax while the original string is still available. Reject fragments, including an empty #, backslashes, control characters and invalid raw forms before constructing a URI object.

A PSR-7 URI object may already have discarded an empty fragment marker or encoded input. A later PSR-18 client cannot reconstruct that information. The TYPO3 RequestFactory adapter therefore checks raw strings before Guzzle. Custom PSR-18 applications validate the original URL themselves, as shown in the Public Fetch example. The same boundary applies to raw Vault resource URLs before constructing a RequestInterface object.

Handling policy errors 

PolicyException implements OutboundPolicyExceptionInterface . Its reasonCode() method returns a stable code from Understanding denials. Exception messages contain no URLs, credentials or request bodies. Applications can use the code for an understandable error message; do not include secrets or complete requests in supplementary error logs.

SDK options and streaming 

Ordinary request bodies, headers and supported authentication remain available in the controlled RequestFactory and SDK path. timeout and connect_timeout accept finite, non-negative numbers, including zero. verify accepts a boolean or a readable CA bundle, subject to the TLS policy. Readable client certificates and SSL keys enable mTLS. sink can write to a file path, resource or PSR-7 stream without creating an additional response copy inside the guard.

Supported callbacks such as on_headers, on_stats and progress remain available; exceptions terminate the associated transfer. HTTP 1.0, 1.1 and 2 are qualified; HTTP/3 is not. The IP-family option can select only v4 or v6 and does not expand address permissions.

Raw curl or cURL-multi options, stream_context, Unix sockets, proxy routes, custom handlers, transport sharing, delayed transport sends, debug dumps, unknown options and stream=true are rejected in the protected path. A response sink file does not provide an alternative stream HTTP handler. Guzzle-internal Digest and NTLM control options, inspected at source level for Guzzle 7, are distinguished from arbitrary raw cURL options.

Cancellation and incremental streaming require an adapter that uses the associated internal progress driver. The recorded nr-vault reference adapter does this for its existing APIs. Returning an arbitrary Guzzle client alone does not guarantee that lifecycle binding.

Optional nr-vault migration 

The extension does not read Vault secrets or automatically integrate Vault. The historical reference patch targets its recorded nr-vault source revision and requires deliberate integration in that project. Resource and OAuth token origins require their own bound clients and, where needed, their own profiles. A private token origin does not inherit a public resource permission, or vice versa.

Secret retrieval, auditing, masking, size limits and Vault's existing cancellation and streaming semantics remain the adapter's responsibility. The reference migration and recorded evidence remain attached to that historical revision. This extension ships no Vault overlay and does not deploy or freshly qualify nr-vault. Ordinary TYPO3 RequestFactory protection requires no Vault migration.