skip to content

If Laravel Telescope runs outside local, how do the viewTelescope gate, Telescope::filter() and hideRequestParameters() protect the dashboard and the data it records?

level: seniorimportance: must knowfreq 32%

answer

  1. local open, elsewhere gate decides
  2. published gate lists no emails
  3. filter keeps exceptions, failed jobs, monitored tags
  4. password fields hidden by default
  5. SQL bindings and sessions still stored

basics

~20 s

Outside local, the viewTelescope gate decides who may open /telescope; the published version allows nobody until you list users. Telescope::filter() limits recording to exceptions, failed requests and jobs, scheduled tasks and monitored tags, and hideRequestParameters() masks named fields.

solid answer

~40 s

`Telescope::auth()` lets the `local` environment in freely; everywhere else it calls `Gate::check('viewTelescope', [$request->user()])`. The published `App\Providers\TelescopeServiceProvider` defines that gate with an empty email list, so nobody gets in until you add users, and guests are always refused. That makes `APP_ENV` critical: a server left on `local` exposes the dashboard. What is recorded is decided by `Telescope::filter()` in the same provider: everything locally; elsewhere only reportable exceptions, failed requests (5xx), failed jobs, scheduled tasks and monitored tags. Masking is separate: `password` and `password_confirmation` are always replaced by `********`, and the stub adds `Telescope::hideRequestParameters(['_token'])` plus `hideRequestHeaders()` for `cookie` and CSRF headers outside local. Queries still store bindings, and requests store session data, so filtering what you record matters as much as masking.

code

php · 31 lines
php
<?php

namespace App\Providers;

use App\Models\User;
use Illuminate\Support\Facades\Gate;
use Laravel\Telescope\IncomingEntry;
use Laravel\Telescope\Telescope;
use Laravel\Telescope\TelescopeApplicationServiceProvider;

class TelescopeServiceProvider extends TelescopeApplicationServiceProvider
{
    public function register(): void
    {
        Telescope::hideRequestParameters(['_token', 'card.number', 'api_key']);
        Telescope::hideRequestHeaders(['cookie', 'x-csrf-token', 'x-xsrf-token']);

        $isLocal = $this->app->environment('local');

        Telescope::filter(fn (IncomingEntry $entry) => $isLocal
            || $entry->isReportableException()
            || $entry->isFailedJob()
            || $entry->isSlowQuery()
            || $entry->hasMonitoredTag());
    }

    protected function gate(): void
    {
        Gate::define('viewTelescope', fn (User $user) => $user->is_admin);
    }
}

go deeper

for a junior

Know that /telescope is open locally but protected by the viewTelescope gate elsewhere, and that production must not run with APP_ENV=local.

for a middle

Explain Telescope::auth, the empty default gate, the published filter's conditions and the default hidden parameters and headers.

for a senior

Assess the residual exposure in queries, sessions and mail entries, choose filters and monitored tags, and decide whether production should run Telescope at all.

for a principal

Own the decision on recording tools in production, balancing incident debugging value against performance, retention and personal-data obligations.

## Three separate controls Running Telescope anywhere but a laptop raises two questions: **who can see the dashboard**, and **what ends up in `telescope_entries`**. Telescope answers them with different tools: | Control | Where | Question it answers | |---|---|---| | `Telescope::auth()` and the `viewTelescope` gate | `TelescopeApplicationServiceProvider`, your `TelescopeServiceProvider::gate()` | who may open `/telescope` | | `Telescope::filter()` / `filterBatch()` | your `TelescopeServiceProvider::register()` | which entries are stored | | `hideRequestParameters()`, `hideRequestHeaders()`, `hideResponseParameters()` | the same `register()` | which fields inside stored entries are masked | ## Dashboard access `TelescopeApplicationServiceProvider::authorization()` registers: ```php Telescope::auth(function ($request) { return app()->environment('local') || Gate::check('viewTelescope', [$request->user()]); }); ``` So: - in **local**, anyone who can reach the URL is allowed; - elsewhere, the **`viewTelescope`** gate decides. The published provider defines it as `in_array($user->email, [])`: an empty list, so **no one** passes until you add addresses or replace the rule with a role check; - a guest has no user to pass to the gate closure, so the check fails. The docs warn that if production keeps `APP_ENV=local`, the dashboard is **publicly available**. The `middleware` option (`web` plus Telescope's `Authorize`) is where you would add more middleware, and `path`/`domain` move the dashboard. ## What gets recorded The published provider's filter: ```php Telescope::filter(function (IncomingEntry $entry) use ($isLocal) { return $isLocal || $entry->isReportableException() || $entry->isFailedRequest() || $entry->isFailedJob() || $entry->isScheduledTask() || $entry->hasMonitoredTag(); }); ``` - `isFailedRequest()` means a request entry with status **500 or above**. - `hasMonitoredTag()` is true when an entry carries a tag you added on the dashboard's **Monitoring** screen (stored in `telescope_monitoring`), which lets you record everything about, say, one user (`Auth:42`) temporarily. - `filterBatch()` decides for the whole request or command at once: if any entry qualifies, all its entries are kept, which preserves the queries and logs around a failure. Note that the docs' example filter includes `isSlowQuery()` and omits `isFailedRequest()`, while the stub that `telescope:install` publishes does the reverse; your app gets the stub's version. ## Masking inside entries - `Telescope::$hiddenRequestParameters` starts as `password` and `password_confirmation`; `hideRequestParameters([...])` adds more, including dot-notation keys like `card.number`. Matching values become `********`. - `$hiddenRequestHeaders` starts as `authorization` and `php-auth-pw`; the stub adds `cookie`, `x-csrf-token` and `x-xsrf-token` outside local. - `hideResponseParameters([...])` masks keys in JSON responses, for example an issued `token`. - The cache watcher's `hidden` option masks cache values for listed keys. What masking does **not** cover: 1. **Query entries** store SQL with bindings substituted, so a lookup by API token or email writes that value. 2. **Request entries** store session data and up to 64 KB of the response. 3. **Mail and notification entries** store rendered content. That is why the filter, not masking, is the main protection outside local. ## Checklist before recording on a shared server 1. `APP_ENV` is not `local` on that server, so the gate applies. 2. The `viewTelescope` gate names specific people or a role, and it has been tested with a non-admin account. 3. The filter records only what you will actually read, such as failures, slow queries and monitored tags. 4. Extra sensitive request fields, headers and response keys are hidden. 5. Pruning runs on a schedule with a short retention. 6. Telescope writes to a connection you are happy to fill, ideally not the main application database. ## Should it run in production at all? The docs frame Telescope as a local development companion, and every request pays for collecting entries and writing them to the database at termination. Teams that run it on production usually keep the strict filter, a real gate, scheduled pruning, and a separate database connection. For ongoing production performance monitoring, a purpose-built dashboard is usually the better fit.

  • A staging Telescope dashboard returns 403 for every admin, including you. Why?
    Outside `local`, access goes through the `viewTelescope` gate, and the published provider defines it with an empty email list, so it denies everyone. Add the admins' emails, or replace the rule with a role check such as `$user->is_admin`. You must also be logged in: guests never pass the gate.
  • Why can Telescope still store a customer's email address even after hideRequestParameters(['email'])?
    Masking applies only to request payloads (and headers or JSON responses through their own methods). If the controller runs `where('email', ...)`, the query watcher stores the SQL with that binding substituted, and mail or model entries may include it too. Limit recording with `Telescope::filter()`, not masking alone.

saying these in an interview costs you the question

  • Outside local the dashboard is open to any logged-in user by default.
  • The published viewTelescope gate allows everyone until you restrict it.
  • hideRequestParameters() also masks values in recorded SQL.
  • Telescope's default filter records everything in every environment.
  • Setting APP_ENV has no effect on who can open Telescope.