Skip to content
Scalpels home

Scalpels is officially launching on October 31, 2026. Special early adopter pricing is available now.

← All guides

Guide

A Comprehensive Agent's Guide to Laravel Cloud

Keep credentials out of your context

· Ed Grosvenor

Anything a command prints lands in your context, your transcript, and often a log the human cannot easily scrub. Several ordinary Cloud reads print production credentials. The defence is not redaction after the fact. It is asking only for the fields you need.

The habits

  • Always pass --fields with --json. Name the fields you need. This allowlist is the only filter that does not depend on the CLI guessing which values are sensitive.
  • Never pass --show-sensitive unless the task is to obtain that one value. When you must, scope it with --fields to that value alone, use it immediately, and do not repeat it in your reply.
  • Never read ~/.config/cloud/config.json. It holds every stored API token in plain text.
  • Report names, not values. When you inventory environment variables, list the keys. To show a token exists, report its length.
  • Never print a secret from a command that runs on Cloud. Cloud keeps the output. See below.

Surfaces that print secrets

Surface What leaks Notes
application:list, environment:list, environment:get, deployment:list, deployment:get without --fields Build commands, in clear From v0.6.0, environment variable values are masked, but build commands are not. We found a GitHub token and a package registry licence key inside build commands this way.
command:run --json, command:get --json The whole environment record, including build commands Older CLIs also printed every environment variable in clear here.
environment:list --json on CLIs before v0.6.0 Every environment variable, in clear Includes private keys and API tokens.
cache:list --json on CLIs before v0.6.0 Live cache passwords in the connection block Masked by key name from v0.6.0. Still never paste a raw listing anywhere.
database-cluster:list --show-sensitive Database credentials
bucket-key:create or bucket-key:get with --show-sensitive secretAccessKey bucket-key:list --show-sensitive stays masked. On v0.5.0 the secret was never returned at all.
websocket-application:get --show-sensitive The Reverb app secret
GET /api/environments/{id} Every environment variable value under environment_variables The raw API has no masking at all.
GET /api/buckets/{id}/keys Key secrets, in clear
Any 422 response from the API The value you just tried to write Cloud's validation messages echo the rejected value, such as Invalid value <your secret> for <KEY>. Log the keys of errors, never message.
HTTP client exceptions The request URL, including any secret in it Log the exception class, never its message.

From v0.6.0 the CLI masks values by default in JSON output, but the masking is a pattern on key names: anything whose key contains pass, secret, token, credential, private, dsn, url or uri. A secret stored under any other name, or embedded inside a longer string such as a build command, is printed as it is. That is why --fields comes first.

Cloud keeps what remote commands print

cloud command:run stores the command line and its full output with the command record. cloud command:get <id> shows it again later, and command:run --history and --copy-output reach it too. Anyone with access to the organization can read it.

So a remote command that mints a token and prints it writes that token into Cloud's history permanently. We did this more than once before we noticed. The patterns that replaced it:

  • Mint credentials over the application's own HTTPS API, where the secret appears once, in a response body only you see.
  • If a remote mint is unavoidable, mint one bootstrap credential, then immediately rotate it over HTTPS, so the value in history is dead.
  • Generate secrets locally and send only a hash through command:run when the remote side only needs to verify them.
  • Commands that print nothing sensitive, such as migrate:status or a health check, are fine to run remotely.

Scripts you copy to an instance are a related trap. command:run lands on one instance of a multi-instance environment, and a file written to /tmp there can outlive your session on that instance. Never stage a secret on an instance's disk.

Some local commands run on production

Some packages proxy artisan commands to Cloud by calling cloud command:run on your behalf. In those applications, a command that looks local, such as php artisan token:create, can execute against the production environment unless you pass a flag such as --local. Two of our agents minted real production tokens this way. Before running any state-changing artisan command in an application wired to Cloud, check whether it proxies, and pass whatever keeps it local unless you were told to act remotely.

Write secrets you cannot read back

Environment variables can be read back, so anything set with environment:variables can later be printed by the surfaces above. Cloud's Secrets are different. They are encrypted on the client with the organization's public key, injected as environment variables at deploy, and never returned by the API.

printf '%s' "$STRIPE_SECRET" | cloud secret:create --name=STRIPE_SECRET --json -n --fields=id,name
cloud environment-secret:attach <environment-id> <secret-id> -n
  • Pipe the value on standard input. --value= puts the secret into your shell history and the process list. secret:create, secret:update and auth:token --add all read standard input.
  • environment:variables --value= has no standard-input form, so a value set that way passes through the command line. Prefer a Secret for anything sensitive.
  • Secrets are a second place configuration lives. When you inventory what an environment receives, check environment-secret:list as well as its variables.
  • Secrets and variables still become environment variables, so the same quoting rules apply. Multi-line values such as PEM keys or service-account JSON should be base64-encoded and decoded by the application. Cloud's environment file quoting turns \n escapes inside a JSON value into real newlines, and the JSON then fails to decode no matter how you escape it.

Before you publish anything

Logs, reports, issues and screenshots you produce travel further than your transcript. Before sharing one, grep it for identifier and hostname shapes:

grep -nE 'app-[0-9a-f]|env-[0-9a-f]|org-[0-9a-f]|inst-|depl-|laravel\.cloud' report.md

Crop screenshots of the Cloud dashboard, where invitation and environment cards show live hostnames and some pages show configuration values.

Want some help?

After you buy, we will pay for an hour with one of these experts to help you get everything up and running.

  • Artisan Build

    USA

    We run Scalpels, as well as a suite of other tools for developers and development teams.

    Agency Ask for help
    Ask Artisan Build for help