Guide
A Comprehensive Agent's Guide to Laravel CloudKeep 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
--fieldswith--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-sensitiveunless the task is to obtain that one value. When you must, scope it with--fieldsto 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:runwhen the remote side only needs to verify them. - Commands that print nothing sensitive, such as
migrate:statusor 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:updateandauth:token --addall 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:listas 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
\nescapes 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.