concept7 / laravel-forduty
Point browsers at your for.duty ingest endpoint with a Reporting-Endpoints header.
Fund package maintenance!
Requires
- php: ^8.3
- laravel/framework: ^13.0
Requires (Dev)
- larastan/larastan: ^3.9
- laravel/pao: ^1.0
- laravel/pint: ^1.29
- orchestra/testbench: ^11.0
- pestphp/pest: ^4.6
- pestphp/pest-plugin-laravel: ^4.1
- pestphp/pest-plugin-type-coverage: ^4.0
- phpstan/extension-installer: ^1.4
This package is auto-updated.
Last update: 2026-08-09 07:19:51 UTC
README
Laravel Forduty
for.duty collects your sites' browser reports — CSP violations, network errors, deprecations — groups them into distinct problems, and alerts your team only when something new appears.
Installation
You can install the package via Composer:
composer require concept7/laravel-forduty
You may publish all of the package's resources at once:
php artisan vendor:publish --tag="laravel-forduty"
Or, you may publish each resource individually:
Publishing the Configuration File
php artisan vendor:publish --tag="laravel-forduty-config"
Usage
Add your for.duty site token to your .env file:
FORDUTY_TOKEN=your-site-token
Then register the middleware yourself. The package ships it but does not attach it anywhere, so your application decides which responses carry the header.
Registering the Middleware
Append AddReportingEndpointsHeader to the global middleware stack in bootstrap/app.php:
use Concept7\LaravelForduty\Http\Middleware\AddReportingEndpointsHeader; ->withMiddleware(function (Middleware $middleware): void { $middleware->append(AddReportingEndpointsHeader::class); })
Every response your application returns then carries a Reporting-Endpoints header pointing browsers at your for.duty endpoint:
Reporting-Endpoints: default="https://in.forduty.app/your-site-token"
The global stack is the recommendation for a reason: a browser only delivers a report to an endpoint group it has been given on that same response, so anything the header misses reports nothing. Appending globally covers routes in the api group, routes that bring their own middleware list — a Filament panel, for one — and requests that match no route at all. Responses your application never sees, such as static files served by the web server, are beyond reach either way.
To limit the header to one middleware group instead:
->withMiddleware(function (Middleware $middleware): void { $middleware->web(append: [AddReportingEndpointsHeader::class]); })
Or to a single route:
Route::get('/checkout', CheckoutController::class) ->middleware(AddReportingEndpointsHeader::class);
Behavior
The middleware adds no header when the token is missing or blank, or when the base URL is blank, unparseable, not an absolute http or https URL, or carries credentials. That keeps a misconfiguration from breaking responses, and makes local and development environments quiet by default. The reporting URL defaults to https://in.forduty.app and can be overridden with FORDUTY_BASE_URL.
The middleware overwrites any existing Reporting-Endpoints header. To keep it off specific routes, register it on the web group rather than globally and exclude those routes with withoutMiddleware():
use Concept7\LaravelForduty\Http\Middleware\AddReportingEndpointsHeader; Route::get('/embed', EmbedController::class) ->withoutMiddleware(AddReportingEndpointsHeader::class);
withoutMiddleware() only reaches middleware the router gathers, so it has no effect on a middleware appended to the global stack. Route-level exclusions and a global registration are a choice between the two.
The package ships a second middleware, AddNetworkErrorLoggingHeader, which you register the same way once you opt in — see Network Error Logging.
Opting Report Types In
Reporting-Endpoints only names the endpoints a browser is allowed to deliver reports to. Which reports actually get sent depends on the report type:
-
Deprecations, interventions and crashes are delivered to the
defaultendpoint on their own. The header above is all they need. -
CSP violations are only reported once your
Content-Security-Policyheader points at the endpoint with areport-todirective:Content-Security-Policy: default-src 'self'; report-to default -
Network errors require an additional
NELheader, which this package can send for you — see Network Error Logging.
CSP reporting is the one this package cannot turn on for you, because the directive belongs to a header it does not own. If you build your policy with a package such as spatie/laravel-csp, add the report-to default directive to it.
Network Error Logging
Network Error Logging asks the browser to report requests that failed before your application ever saw them — DNS failures, TCP resets, TLS errors, aborted connections. It is off until you register AddNetworkErrorLoggingHeader alongside the other middleware:
use Concept7\LaravelForduty\Http\Middleware\AddNetworkErrorLoggingHeader; use Concept7\LaravelForduty\Http\Middleware\AddReportingEndpointsHeader; ->withMiddleware(function (Middleware $middleware): void { $middleware->append([ AddReportingEndpointsHeader::class, AddNetworkErrorLoggingHeader::class, ]); })
Every response that middleware sees then carries a policy alongside the endpoints header:
NEL: {"report_to":"default","max_age":2592000,"include_subdomains":false,"success_fraction":0,"failure_fraction":1}
Registering the middleware is the whole opt-in — there is no separate flag to set. To turn network error logging off again, take it back out of bootstrap/app.php. Browsers hold a policy for as long as its max_age says, though, so a site that has been sending one wants a deploy with FORDUTY_NEL_MAX_AGE=0 to clear it before the middleware goes.
Four optional variables tune it:
| Variable | Default | Meaning |
|---|---|---|
FORDUTY_NEL_MAX_AGE |
2592000 |
How long, in seconds, the browser keeps the policy. 0 clears a policy it already holds. |
FORDUTY_NEL_INCLUDE_SUBDOMAINS |
false |
Whether the policy also covers subdomains. |
FORDUTY_NEL_SUCCESS_FRACTION |
0.0 |
Fraction of successful requests to report. Raise this only deliberately — at 1.0 the browser reports every request your site makes. |
FORDUTY_NEL_FAILURE_FRACTION |
1.0 |
Fraction of failed requests to report. Lower this to sample on a high-traffic site. |
Two things to know:
- Browsers only honour
NELover HTTPS. Over plainhttpthe header is sent and ignored, so local development stays quiet on its own. - The policy needs
Reporting-Endpointson the same responses.report_tonames thedefaultgroup that the other middleware declares, so registering this one on its own — or excludingAddReportingEndpointsHeaderfrom a route that keeps this one — leaves the browser with a policy it cannot deliver to.
As with the endpoints header, a value a browser would reject — a negative or fractional max_age, a sampling fraction outside 0.0–1.0 — means no header at all rather than a silently corrected one. Keeping the policy off specific routes works the same way as for the endpoints header: register on a group, then exclude the route.
use Concept7\LaravelForduty\Http\Middleware\AddNetworkErrorLoggingHeader; Route::get('/embed', EmbedController::class) ->withoutMiddleware(AddNetworkErrorLoggingHeader::class);
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Thank you for considering contributing to Laravel Forduty! Please review our contributing guide to get started.
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
Credits
License
Laravel Forduty is open-sourced software licensed under the MIT license.