Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Deny Routes By Env Bundle Laravel Package

grizzlylab/deny-routes-by-env-bundle

Symfony bundle that restricts routes to specific environments, letting you deny configured paths outside allowed envs. Simple configuration keeps dev-only or admin routes unreachable in production without touching controllers or firewall rules.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Symfony bundle, not a Laravel package — the first decision is whether the pattern or the code is what you actually want. If you're in a Laravel codebase, read this as a design reference and reimplement it as a 15-line middleware (see below). If you're in a Symfony app, it's installable today.

Minimal path:

  1. composer require grizzlylab/deny-routes-by-env-bundle (MIT, but 4 stars / 0 dependents — treat it as vendored code you own, not infrastructure you depend on).
  2. Register the bundle in config/bundles.php if Flex doesn't do it for you:
    GrizzlyLab\DenyRoutesByEnvBundle\DenyRoutesByEnvBundle::class => ['all' => true],
    
  3. Create the config file (config/packages/deny_routes_by_env.yaml — confirm the exact root key against the README). The shape is a mapping of environment → route names/patterns:
    deny_routes_by_env:
        rules:
            prod:
                - 'admin_'
                - 'debug_'
    
  4. bin/console cache:clear, then curl a matching route in that env and confirm you get a 404/403 instead of the controller.

Where to look first, in this order:

  • The README — it's the only documentation, and it's short.
  • DependencyInjection/Configuration.php — this is the ground truth for config keys, nesting, and whether wildcards/regexes are supported. Don't trust blog snippets over this file.
  • The event subscriber (likely EventSubscriber/…Subscriber.php) — you need to know two things: which exception it throws (404 vs 403) and at what priority it hooks kernel.request. Both have real security consequences.

First use case: you have admin dashboards, webhook test endpoints, /dev/mailer, profiler routes, or demo-only pages, and one environment (staging, a "demo" env, or a dev-mode prod box used for debugging) accidentally exposes them. You want them to not exist there, without deleting routes, duplicating route files, or fragmenting firewalls.

Implementation Patterns

Environment → route-name prefix mapping. The join point is the route name, not the path. Establish naming conventions (admin_*, debug_*, _preview_*) so the config stays a short list of prefixes rather than a brittle enumeration.

Decide deliberately between 404 and 403. 404 hides the route's existence from probing bots; 403 is honest and easier to debug. If the bundle gives you a choice, block unauthenticated probes with 404 and authenticated misuse with 403. If it doesn't, wrap or replace the subscriber.

Layer it with access_control, don't replace it. access_control is authorization (there is a user, there is a role). This bundle is route existence enforcement (there is no user, the route shouldn't resolve). Running the deny check early in kernel.request — before the firewall redirects to a login page — avoids leaking the route's existence via a 302 /login.

Use Symfony's env-scoped config rather than one big file. In Symfony 5.3+, when@prod: blocks keep the intent local:

when@prod:
    deny_routes_by_env:
        rules:
            prod: ['admin_', 'debug_']

Older versions: separate config/packages/prod/deny_routes_by_env.yaml.

Prove it in CI, per environment. A route that is "denied in prod" is a security control, so it needs a test, not a code review:

public function testAdminRoutesAreDeniedInProd(): void
{
    $client = static::createClient(['environment' => 'prod']);
    $client->request('GET', '/admin');
    self::assertResponseStatusCodeSame(404);
}

Run that across an env matrix in your pipeline, and add a post-deploy smoke check against staging before promotion.

The Laravel translation, if that's where you actually live — same intent, no dependency:

// AppServiceProvider::boot()
Route::middleware('deny.route.in.env:production')->group(function () {
    Route::get('/admin', AdminController::class)->name('admin.index');
});
// app/Http/Middleware/DenyRouteInEnv.php
public function handle(Request $request, Closure $next, string ...$envs)
{
    abort_if(app()->environment($envs) && $request->routeIs('admin.*'), 404);
    return $next($request);
}

Or, more simply, skip registration entirely in environments where the route shouldn't exist:

if (! app()->environment('production')) {
    Route::get('/dev/mailer', MailPreviewController::class)->name('dev.mailer');
}

The registration-time approach is strictly stronger — the route is absent from route:list, from URL generation, and from the router — which is worth noting as a design critique of the bundle: it denies at request time rather than at routing time.

Gotchas and Tips

  • Route names, not URIs. Unnamed routes, or names autogenerated from #[Route] attributes, won't match a prefix rule. Verify with bin/console debug:router --show-controllers before you assume a rule is live.
  • Sub-requests fire kernel.request too. ESI/fragments/render(controller(...)) will be evaluated against the same rules. If the subscriber doesn't guard on isMainRequest() (or isMasterRequest() on older Symfony), you can silently break fragment rendering in the target env.
  • Error-page leakage. A 404 is only hidden if the surrounding app doesn't advertise it — a custom error page that says "Admin route disabled in staging" defeats the point.
  • .env.local beats your config. Half the "it doesn't work" reports are APP_ENV not being what you think, especially in Docker/CI. Print it at boot, or check bin/console about.
  • Config is cached. In prod the mapping is compiled into the container; edit + cache:clear + (in some setups) cache:warmup. when@prod: blocks also require a recent Symfony — check the constraint.
  • Console and Messenger are unaffected. Workers, commands, and schedulers have no kernel.request; if a "denied" route's logic is also invoked from a command, this bundle does nothing to stop it. That's a real gap — deny at routing/config time, not just at request time.
  • Debug the dispatcher, not the code. bin/console debug:event-dispatcher kernel.request tells you whether the listener is registered and where it sits in the priority order relative to the firewall and router.
  • Maintenance risk is the headline. 4 stars, 0 dependents, a single-maintainer bundle, and a low overall score mean no ecosystem safety net: no StackOverflow answers, no Symfony-version upgrade guarantee, no CVE watch. Pin an exact version in composer.json, and be prepared to fork it — or better, inline the ~20-line EventSubscriberInterface implementation into your own src/EventSubscriber/ so upgrades to Symfony are your problem and not a dead package's.
  • Extension points you'll likely actually want: listener priority (a constructor arg or tag attribute you can override via a compiler pass) and an on_deny behavior toggle between 404 and 403. If neither is exposed, that's the fork trigger.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource