Secrets and the files an agent must never read
A deny rule on .env stops the careless read, not the three other routes to the same bytes. What to configure, in what order, and why a read you allowed once is already a disclosure.
The obvious first rule
Usually the first entry in a config file for permissions is the .env file because it's the most typical use case when it comes to secrets — someone exploring the codebase tries to connect to a database and needs to find the URL to do so, opens the .env file, and can't read anything. There are a few more sensible options — ~/.aws/credentials and ~/.ssh — that I'd personally include as well.
But that's not the whole story, and the gap isn't subtle. This is because a permission config is not about preventing someone from reading a secret, but about preventing them from doing so accidentally. And this is what the .env file is about. It's the route the agent takes when it's being cooperative. There are multiple ways of accessing the same piece of information, and this is one of at least four of them. This lesson is about the other three.
Today I'll cover the remaining paths an agent can take to read a secret (there's actually more, but they're a bit more abstract and I won't go into details about them today). I'll also show you the out-of-the-box defaults in these tools, which don't secure any of these paths. And at the end we'll have a little chat about the stakes, because once a secret is read, it's not just about permissions anymore — it's about rotation.
"It must never read this" is two requests
If you were to say "It must never read this", you actually mean two different things, and need two different mechanisms for them. The first one is "It mustn't find its way there". Your agent isn't hostile, they're just grepping for the connection string, and have the credentials file in their path. So you can use path rules or ignore files — they're cheap, and cover all of the ways the harness is aware of.
This is actually outlined in the documentation of both tools:
Claude Code says it tries to apply
Readpermissions to its own file-based tools (like Grep and Glob) whenever possibleCursor, on the other hand, directly says that it ignores files, but can't guarantee absolute security given the LLM's randomness; and that Run Modes are best-effort guardrails, not a hard boundary
But the second part of "It must never read this" is more about "It mustn't be there". You didn't forbid your agent to go to the secret, you just told it they shouldn't be there in the first place — which means the bytes themselves aren't available. This can be achieved by keeping them on no disk the agent can access, or not allowing the open system call on the kernel level. Or, what's the same on another channel, stripping the environment before a command runs.
Both of these are valid, but you need to be conscious about paying for the first one and assuming you get the second.
Four routes to the same bytes
So, let's have a look at the ways an agent can access the very same bytes:
using one of the tools of their file system (
Read,Grep,Glob,@filereferences, or the open editor tab) — this is governed by path rules, which are good for the use case they were built for; though Claude Code still only says it tries to applyReadpermissions to tools like Grep and Globrunning
cat .envin a shell — it's just a command, so its shape is checked by command matching (unless it's one of the commands the harness doesn't recognise, but Claude Code has a few examples:cat,head,tail,sed); also, their docs say that deny rules don't work for arbitrary subprocesses that open files themselves (like a Python or Node script), and they recommend using the sandbox to address thisaccessing environment variables — the
.envfile is just a place to put env vars, and there are many secrets in them, but path rules have nothing to say about env vars; so you can doprintenvwithout any file-related rulesgetting it indirectly — from an MCP server (which has its own tool for reading files, and Kimi Code's documentation says it matches by name rather than path, so you can only deny the tool itself); or from a subagent (which inherits the parent's config but generates its own turns and transcript); or from a hook you've written to log agent activity (which goes wherever you point it to); or from a test suite (which prints the resolved config, including the secret)
The environment is the route nobody configures
There are a few things that stand out here:
the most crucial one is that the environment channel hasn't been configured at all
I decided to include it because the outcome doesn't align with what you might expect and is easily verifiable in a terminal with no model call
But let's have a look. Codex has its own helper that can run a command in its sandbox with no model involved, so we have a clean bench. Let's export a token and ask the sandbox to echo it back:
export FAKE_TOKEN=canary-tok-9911
codex sandbox -- /usr/bin/printenv FAKE_TOKENAnd here's what it prints — just the single variable we've exported:
canary-tok-9911As you can see, the variable named TOKEN made it to the subprocess in the sandbox. If we were to run the same command with the automatic secret-name exclusions enabled, it wouldn't print anything.
codex sandbox -c shell_environment_policy.ignore_default_excludes=false -- /usr/bin/printenv FAKE_TOKEN(no output)This is because of how the flag works — ignore_default_excludes is true by default, which means (as per OpenAI's configuration docs) that Codex doesn't automatically strip variables whose names contain KEY, SECRET or TOKEN. Setting it to false is what enables this protection.
Similarly, Claude Code's sandboxing page says plainly that sandboxed Bash commands inherit the parent process environment (with credentials in it), and this is exactly what we can see when we run it: with the default settings, asking Claude Code to printenv a canary variable echoes the value straight back.
So both tools have the environment channel open by default, and in both of them it can be closed with a setting that's not connected to file permissions.
A read is a disclosure
Now, if you think about it, once a secret is read, it has been disclosed. So we need to trace its journey. After the earlier run, the canary value appeared in the session store, inside the per-directory transcript:
~/.claude/projects/-private-tmp-secrets-check-proj/158dcbf7-….jsonlThis means that within a single turn the secret is in context, gets transmitted to the model provider and written to a resumable transcript on your local disk.
Both tools have more of such things:
Codex has a
history.persistencesetting that controls whether it saves session transcripts tohistory.jsonlKimi Code writes
wire.jsonlper session, which is (as the docs say) the main agent's entire communication record
Whenever a tool supports resuming, it must save turns somewhere — so don't assume there's no file, look for it.
Let's have a look at the implications of this:
if you were to delete the transcript, the request wouldn't be revoked. The secret your agent read is a secret that left your machine
if you were to resume the session, it would replay it, including the transcript, so the value would come back with the session the next day
the tool output is content too — it can end up in a commit message, PR description, or comment; or even in a file the agent writes
So what we have here isn't a wall, but rather a clock stop. It doesn't prevent an incident, it delays it until you've had a chance to act, which is why it's worth paying attention to.
Nobody ships a secrets denylist
But no vendor ever ships a secrets denylist. Actually, if anything, the defaults are usually set in a way that makes it easier to read things. So check the documentation of your tool, instead of assuming it did the sensible thing.
Claude Code, for example:
their sandbox allows reading the entire machine except for some denied directories, with a little note saying that this includes credential files like
~/.aws/credentialsand~/.sshtheir dedicated credentials block is also empty — they don't ship a default denylist of credentials
Cursor's approach is more radical:
the sandbox reference says SSL certificate paths and
~/.sshare always readableand their schema doesn't have the read-deny key at all; just read and write paths to add
Antigravity CLI takes the opposite approach — it limits reads by default, but within the workspace, which basically means they're limited to the project directory, where your .env file is.
The only mode in Codex that permits no writes is read-only. Let's pin it on the same bench and see what happens:
codex sandbox -c sandbox_mode='"read-only"' -- /bin/cat .env # SECRET=hunter2
codex sandbox -c sandbox_mode='"read-only"' -- /bin/ls ~/.ssh # the directory listing
codex sandbox -c sandbox_mode='"read-only"' -- /usr/bin/touch ./x # Operation not permittedtwo reads succeed (the
.envcontents and the listing of~/.ssh)but a write attempt gets rejected as not allowed
This isn't a bug — read-only doesn't limit what can be observed, but what can be modified; these sandboxes are there to contain writes and egress to the network, and you're responsible for securing the reads.
The order to fix it in
But if you do, here's the sequence of things that make the most sense:
get the secret off any disk your agent can reach. A short-lived token a secret manager gives you during the use is the best rule you can write, as the most strict deny rule is no file; this is also the only thing that doesn't require configuring the harness
clear the environment for spawned commands. Starting from scratch or with a reduced set, and adding what's needed for the build, is a two-lines change that closes the path without any per-path maintenance
block both paths for whatever must stay. For each protected path, create the path rule and the command-shape rule; either of these leaves half of the door open
enable the boundary if you want enforcement instead of a judgement call. Containment on the OS level is stronger than anything that builds on a string, like everything above
make rotation cheap. If you were to rotate a credential in 30 seconds, the entire subject would become a non-issue; but if it's a key shared between all your developers that needs a week to be changed, it's why teams end up acting as though the read never happened
audit both sides of the channel. A secret can enter the repo via the agent's own output, not only leave through a read; this is a commit hook, which I cover in another lesson in this chapter
In Claude Code
File: deny rules and their 2 ways of being wrong
If you want to use gitignore-style patterns to define the files and directories the code is not allowed to read or edit, then you need to follow these in settings.json for both Read() and Edit(), but what can be tricky is the anchoring part:
{
"permissions": {
"deny": [
"Read(*.env)",
"Read(//Users/alice/.aws/**)",
"Read(~/.ssh/**)",
"Read(**/.env)",
"Bash(cat *.env*)",
"Bash(printenv *)",
"Bash(env)"
]
}
}The anchoring is the first way to get this wrong. A single leading slash anchors at the settings source, not at the filesystem root — the docs give five different anchors depending on which settings file the rule sits in — so Read(/Users/alice/.aws/credentials) resolves under one of those instead of at /, and never matches the file you meant. Absolute needs //. A bare path or ./path is relative to the current directory, which is why Read(**/.env) sits next to Read(*.env) above rather than instead of it.
The last three entries are the shell and env routes (we can't do cat on these files, run printenv, and run env), and I did those in a specific way to make sure that they don't conflict with any reasonable usage of these commands. So I added a space after printenv so we cover both bare printenv and printenv FOO (it is defined like this in the docs: "prefix followed by space or end of string") and I left env as an exact match because I think it's reasonable to have 55 lines of output if you run env (which it does on my machine) and it's ok if you want to do env FOO=1 command, for example.
Also, what's important is that starting from v2.1.208 a Read deny also forbids Edit on this path (including creating a new file), so since Write and NotebookEdit aren't covered make sure to add an explicit Edit deny for paths you don't want any tool to edit.
Also, it's important to know that paths are only considered for Edit() and Read(). If you define a rule for Write or Glob or NotebookEdit it will be accepted but will produce a warning during the start up of Claude Code and will never be used. Except for Glob — if you provide it using the --allowedTools parameter it will be used, but in order for this to work you need v2.1.210 or newer.
Credentials: where we lock access to the environment
Starting from v2.1.187 there's a new section called sandbox.credentials that is intended for this purpose and it comes with an empty config (so by default it doesn't block access to any credentials, only entries you add will be locked):
{
"sandbox": {
"enabled": true,
"credentials": {
"files": [
{ "path": "~/.aws/credentials", "mode": "deny" },
{ "path": "~/.ssh", "mode": "deny" }
],
"envVars": [
{ "name": "GITHUB_TOKEN", "mode": "deny" },
{ "name": "NPM_TOKEN", "mode": "deny" }
]
}
}
}This means we can't read the ~/.aws/credentials file or the ~/.ssh directory and the two variables will not be available before running any command. Re-running the canary from the shared section with exactly these settings: no output and exit status 1 — so the variable was definitely missing before the execution, not just refused after it.
Starting from v2.1.199 you can set the mode to mask for variables if removing them breaks sth. This way your command will be run with a special value set for this variable in the environment. What's more, thanks to it Claude Code's proxy will modify the requests you make to the hosts listed under injectHosts by replacing that special value with the real one so the authentication should work as usual, but nothing the tool writes to the logs (for example) will contain the token. It's important though to set network.tlsTerminate in such scenario because the proxy needs to see the requests' content in order to modify them.
Another option is setting CLAUDE_CODE_SUBPROCESS_ENV_SCRUB which is a general switch independent of the sandbox that removes Anthropic and cloud provider credentials from every subprocess. The downside is it's not as granular.
Two gotchas
The file half of credentials lives in the filesystem layer, so filesystem.disabled: true switches it off — while the environment half keeps working, because env scrubbing is independent of that layer. So if you turn filesystem isolation off to keep some stubborn tool happy, your credential files become readable again while the variables stay protected, which is not an obvious pairing. Two things narrow it: the key needs v2.1.216 or newer, and it's only honoured from user settings, managed settings or --settings, so a repository you checked out can't switch filesystem isolation off on you.
The second one is that the built-in file tools don't go through the sandbox at all — Read, Edit and Write use the permission system directly. So the path rules and the sandbox rules are protecting different callers, and a path that actually matters needs an entry on both sides.
In Codex CLI
The environment, first, because it's the cheapest win here.
This is the lowest effort and biggest effect you can get in Codex CLI. The configuration is defined using shell_environment_policy in the ~/.codex/config.toml file, here's a configuration for example:
[shell_environment_policy]
inherit = "core"
ignore_default_excludes = false
[shell_environment_policy.filters]
"AWS_*" = "exclude"
"AZURE_*" = "exclude"This is how it works:
inheritcan be set toall,coreornonesetting
ignore_default_excludestofalseactually enables default exclusions of names containing KEY, SECRET or TOKEN — by default they're disabledfiltersis a list of globs — they work case-insensitive and you can use*and?wildcards in thereif you add a single
includeentry, the entire policy works as an allowlist instead of a denylist (but you cannot include what you've already excluded)the configuration is processed in the following order: default exclusions, user exclusions,
setvalues, include allowlist
That means that due to the fact that set is being applied after the exclusions, you might be setting some variable that was actually excluded.
The shell route, verifiable without a model call.
The second layer of security is the shell itself and can be configured using Codex's execpolicy feature which doesn't require any model call to explore. The files with rules are located under rules/ in the ~/.codex directory, like ~/.codex/rules/secrets.rules for example:
prefix_rule(
pattern = ["cat"],
decision = "forbidden",
justification = "Reading files through the shell bypasses path rules.",
)the
catprefix is marked as forbidden, and the justification says why: it's a way to read a file through the shell despite the path rulesif you want to explore a file with execpolicy rules, you can use the
codex execpolicy checksubcommand — it's good to get used to running it
codex execpolicy check --pretty --rules ~/.codex/rules/secrets.rules -- cat .env{
"matchedRules": [
{
"prefixRuleMatch": {
"matchedPrefix": ["cat"],
"decision": "forbidden",
"justification": "Reading files through the shell bypasses path rules."
}
}
],
"decision": "forbidden"
}it will print all the rules that are being applied for a given command and an outcome (
forbiddenorprompt)forbidden— as the name suggests — forbids running the command,promptasks for confirmationif there are both an
allowand aforbiddenrule with the same prefix, both of them will appear inmatchedRulesand the outcome isforbiddenanyway, regardless of the order in which they're defined
What the sandbox does and doesn't cover.
The last thing is the sandbox, and it isn't the tool for this job — it's there to stop a command writing somewhere it shouldn't, not to stop a secret being read. It's not about what you can see, but what you can do: as long as it's set to read-only, you can read the .env file and list ~/.ssh contents, but you can't touch anything — you'll get Operation not permitted.
codex sandbox -c sandbox_mode='"read-only"' -- /bin/cat .env # SECRET=hunter2
codex sandbox -c sandbox_mode='"read-only"' -- /bin/ls ~/.ssh # the directory listing
codex sandbox -c sandbox_mode='"read-only"' -- /usr/bin/touch ./x # Operation not permittedNote the shape of that invocation: if you try using -s (or --sandbox) instead of -c here, it will throw an error, as the codex sandbox subcommand takes config overrides with -c and has no -s of its own.
The transcript.
You can also configure whether the sessions are being recorded by changing history.persistence in the ~/.codex/config.toml file. It accepts save-all or none values and decides if the session transcripts are being persisted to history.jsonl or not. You can also set history.max_bytes to limit the size of the file, removing oldest entries automatically once it reaches this size.
But setting persistence to none will only change where a potentially leaked secret ends up — it won't change the fact that it's already been sent during the request.
In Cursor
.cursorignore is the mechanism, and Cursor documents its own two holes.
We could use .cursorignore for this, it's a kind of single source of truth when it comes to what is being ignored by Cursor. It goes in the project root and uses gitignore syntax, so you can list files there like .env or its variants, a secrets folder, .pem files, or credentials.json no matter what level it sits on:
.env
.env.*
secrets/
*.pem
**/credentials.json.gitignore is covered too (there's also a built-in default list which Cursor ignores automatically), and everything listed won't be accessible from the code via Agent, Tab, Inline Edit or via @-mentions.
But there are two gaps, and both of them are in the official docs. First, the terminal and MCP server tools that the Agent uses cannot block access to code governed by .cursorignore — that's the shell route from the shared section, named by the people who built it. Second, the docs say that while Cursor blocks ignored files, complete protection isn't guaranteed due to LLM unpredictability. So it's not a perfect solution.
The second thing you can do is creating a .cursorindexingignore file which will remove all the files from the indexing process but won't block them from being used by the AI features, they'll just not be visible in codebase search; it's not about not having access to them.
The shell route goes through permissions.json.
The terminal path is defined in the permissions.json file which is placed in two places:
user-level
~/.cursor/permissions.jsonproject-level
<project>/.cursor/permissions.json
(they are being merged) and contains natural language instructions that you can put there to not allow Cursor to run specific things. Here's an example:
{
"autoRun": {
"allow_instructions": [],
"block_instructions": [
"Any command that reads a .env file or a credentials file should go through approval first.",
"Any command that prints the environment, such as env or printenv, should go through approval first."
]
}
}As you can see, you can define there for example that in order to run commands that use the .env file or read the credentials file (or even the one that dumps the environment — env, printenv) you need to ask me first. Under the hood there's a classifier reading these instructions so it can understand more than a glob pattern, but it's less predictable than a glob, and it understands "commands like this" rather than "this specific path and nothing else".
The sandbox will not do this job.
What's more, we cannot achieve that with the sandbox as it only allows to add permissions (the type, additionalReadwritePaths, additionalReadonlyPaths and network policy), there's no such thing as a read-deny key in it. The reference states plainly that SSL certificate paths and ~/.ssh are always readable, and the default sandbox behaviour for terminal commands is read and write access inside the workspace, with .cursorignore named as the thing that can hide files from the agent.
So ultimately for Cursor the only reasonable thing you can do in such scenarios is to just go with the first step I've mentioned in the shared section and remove the secret from the workspace and the environment.
The gotcha. Run Modes — the Auto-review classifier included — are described in the docs as best-effort guardrails rather than a hard security boundary, and the same page says outright that the classifier can make mistakes. Cloud Agents are a separate story again: the docs say they don't use Run Modes at all, because they run on their own dedicated machine and never stop to ask you to approve anything.
In Antigravity CLI
As it uses its own permission for reading files, it's actually more convenient to define the permissions for it than for most of the other tools.
The config file can be found at ~/.gemini/antigravity-cli/settings.json and is the following:
{
"permissions": {
"deny": [
"read_file(.env)",
"read_file(/home/user/.ssh)",
"read_file(/home/user/.aws)",
"command(cat .*env.*)",
"command(printenv)",
"command(env)"
],
"ask": ["command(*)"]
}
}Here I've listed a few sensible defaults like denying access to .env, ~/.ssh and ~/.aws as well as to the printenv and env commands that can be used to output the environment variables. You should also have an ask entry here that covers every single command you use.
The way it works is that:
read_file— this permission takes paths as targets, and:either absolute or relative to the workspace's root
recursive — if you put a directory here, everything inside of it will be denied as well
wildcard — if you enter
*here, all files on the machine will be denied
the precedence is fixed by the type of the list:
denycomes beforeaskaskcomes beforeallowallowcannot overridedeny, not even with a narrower entry
if you deny access to
read_filefor some path, you'll automatically deny access to it usingwrite_fileas well
One thing that may be surprising here is that in the workspace everything is allowed by default (unless you say otherwise). That means that all read and write operations within the active project will work without a prompt. That in particular means that if you create a .env file inside of your working project, it'll be accessible unless you define it in either deny or ask.
Also, the secure fallback the docs describe is only used when there's no entry for some action in any of these lists.
The opposite is true outside of the workspace:
by default the
allowNonWorkspaceAccessproperty in the same settings file is set tofalse, which means that file tools generally can't access anything outside of the workspaceif you don't define a path in any of these lists, it'll fall back to "ask"
That's why as I said earlier it's crucial to keep your secrets outside of your projects.
Command patterns — here we have another layer for the command permission, where we can specify separate commands and match them per token. This means that every single whitespace-separated word of a command is evaluated as an anchored regex. So for example if you set the following pattern: command(npm run (build|lint|test)) it will catch only these 3 alternatives.
The thing is, that as with everything connected with string matching this only works if someone uses the commands that are similar to the ones you've listed. If they create a script that reads the file themselves, the file won't be safe.
The terminal sandbox doesn't close that either, and it's set to false by default in the same settings file — enableTerminalSandbox, which underneath is:
nsjailon Linuxsandbox-execon macOSAppContainer on Windows
The official docs say that it's used to prevent destructive things being done with the shell and unauthorised network requests. There's also no word there about scoping reads, so I'd rather enable this for the reasons mentioned in the three-gates lesson than for hiding credentials.
Last but not least, whenever you're prompted by Antigravity CLI for any file, URL or MCP permission, the target input is editable before you approve it. If you alter it, the access will be granted for all its occurrences during the current turn, so as I mentioned earlier when it comes to files — if you change it from let's say /my-file.txt to my-dir, all actions that operate on this file or any file from this directory won't be prompted.
In Kimi Code CLI
If you want to set up permissions in Kimi Code CLI, the most crucial thing is the order of rules defined in config.toml (in ~/.kimi-code/) under [[permission.rules]]. These are being evaluated in order from top to bottom and the first matching rule is applied. The other solutions like Claude Code or Antigravity have a hardcoded order in which they're checked, for example denials are checked first, then asks and finally allowances — that way if you were to add eg a general ask at the very beginning of your config and then add a specific deny further down it will not work as it'll be shadowed by the ask and will not be applied without any notification. The config below denies the env files, the Bash route to the same place, and config.toml with the plaintext provider keys in it, using a general ask for Bash at the very end:
[[permission.rules]]
decision = "deny"
pattern = "Read(**/.env)"
reason = "Secrets never reach the model"
[[permission.rules]]
decision = "deny"
pattern = "Bash(cat *.env*)"
reason = "Same file, other route"
[[permission.rules]]
decision = "deny"
pattern = "Read(**/.kimi-code/config.toml)"
reason = "This file holds provider API keys in plain text"
[[permission.rules]]
decision = "ask"
pattern = "Bash"In config.toml's [[permission.rules]] you can define the rules in the following format:
decision: (allow|deny|ask)pattern:ToolName(orToolName(arg-pattern)) — the argument part is optional and describes what the argument is. It's the responsibility of every tool to decide what it matches against, for example in the case ofBashit's commands and forReadit's pathsreason: (optional, but recommended) — especially if you're setting up a denylist, and after some time, when you'll be tired of no. 100 denials in a row you'll just remove it from the config without a reason
The doctor subcommand runs some checks before actually using the config so it can tell you if everything's alright with the config file:
kimi doctorIt prints an OK line for each config file it read, a SKIP line for each one that doesn't exist, and finishes with All checked config files are valid. The sample config above is being validated correctly, ofc, but it validates the syntax not the intent — it's happy with an inverse order of these rules if you were to swap the positions.
Protecting the config file itself. That's the third rule in the block above, and it's there because the config file is the only place where provider keys are stored — they're not being read from any env variables — and according to the docs the api_key for example is stored there as plain text. So it's obvious that we want it on a never-read list. The OAuth credentials are being handled better though: there's the credentials directory under ~/.kimi-code/ with 0700 permissions and inside of it files with 0600 permissions.
Two gaps. There are also 2 things that aren't supported by this system:
The MCP tools, custom tools and
AgentSwarmare only matchable based on tool name, so if you were to use a tool that takes paths for example, you won't be able to secure some of them without affecting the entire tool (allow or deny the tool).There's no sandbox in this system — it's the empty cell in the Academy table. It means that there's no operating system level separation and therefore it's up to the user to make sure their project is being isolated by using a container or a VM for example.
The trap. The last thing (and most important tbh) is that every turn of a session is being recorded in a wire.jsonl file located in ~/.kimi-code/sessions/<workDirKey>/<sessionId>/agents/main/ and is described as a full communication log of the main agent so it can be used for resume or replay of sessions. That means any secret that reached one turn is sitting in that file, and every sub-agent maintains their own such file next to it.
What none of this buys you
There are a few things that aren't covered by this:
exfiltration control. If your agent can read a secret and reach the network, they can send it; and the network layer is leakier than you think — Claude Code's docs say that permitting a broad domain like
github.comcan open up exfiltration paths because the proxy believes the client-supplied hostname and doesn't inspect TLSinjection-proofness. All of these gates check from the outside — a path, a command shape or, in Cursor's case, a classifier reading a sentence you've written — so they limit access, not intent; the content an agent reads from a file it was allowed to open can still tell them to do sth you don't want
a rule that didn't load is no rule. Claude Code's own
--helpeven says that for print mode, settings files that are invalid get swallowed in silence; Kimi Code's docs say it reverts to defaults and shows a notice, but this isn't always the case, so better check (there's another lesson in this course about it)
And then there's the sanity check. It's worth spending a minute on — put a canary where you'd put the actual secret, ask your agent to print it, and see what they return; if the value comes back, what you've written is just a wish.