Gemini CLI environment redaction for QA deserves a real test plan before teams enable third-party hooks. A hook runs as a local process and can inherit environment variables from the Gemini CLI process. If those variables include API keys, tokens, or internal configuration, a harmless logging mistake can turn into a secret leak.
This tutorial builds a disposable hook lab with synthetic canaries. It tests redaction off and on, exact allowlists, custom blocked names, trusted and untrusted folders, hook failures, restarts, and every output path that could preserve a canary. The objective is evidence: prove what the hook can see without ever using a production credential.
What Google officially documents
The current Gemini CLI configuration reference documents security.environmentVariableRedaction.enabled, allowed, and blocked. The documented default for redaction is false, and those settings require a restart. That makes the effective startup configuration part of the QA evidence.
The official hook best-practices guide says hooks inherit the Gemini CLI process environment, warns that hooks run as the current user, and recommends enabling redaction for third-party hooks or sensitive environments. It also says an exact variable needed by a hook can be explicitly allowlisted.
The hook reference defines a strict interface: JSON input arrives on stdin, final JSON goes to stdout, and diagnostics go to stderr. Exit code 0 means success, code 2 is a system block, and other nonzero codes are warnings. Redaction testing must preserve that contract; stray debug output can break the hook even when secrets are hidden.
Build a synthetic secret-canary lab
Create a private repository containing one tiny application, one deterministic test, and one BeforeTool probe hook. Use a dedicated OS account or disposable VM when possible. Disable production network access and do not copy real .env files into the lab.
Seed four invented variables in the process that launches Gemini CLI:
| Name | Purpose | Expected when redaction is enabled |
|---|---|---|
QA_HOOK_KEY |
Sensitive-name canary | Absent |
QA_ACCESS_TOKEN |
Second sensitive pattern | Absent |
QA_INTERNAL_LABEL |
Custom blocked name | Absent when explicitly blocked |
QA_BUILD_ID |
Required nonsensitive metadata | Present only when intended |
Give every variable a unique, obviously fake value such as QA_CANARY_20260907_A. Keep the expected values in the external test harness, not in the hook source, screenshots, or article repository. A canary is test data, not permission to use actual credentials.
Step 1: freeze the complete run identity
Before each trial, record the Gemini CLI version, installation source, OS, shell or process launcher, working directory, Git commit, trust state, settings scopes and hashes, hook name and command, script hash, event and matcher, policy and sandbox state, telemetry state, environment fixture ID, and evidence-directory permissions.
Also record whether Gemini CLI was restarted after changing redaction. Because the documented redaction settings require restart, an unchanged process can produce a false conclusion. Never infer the effective value from a file edit alone.
Step 2: configure three controlled modes
Create three settings fixtures and run them in separate sessions:
{
"security": {
"environmentVariableRedaction": {
"enabled": true,
"allowed": ["QA_BUILD_ID"],
"blocked": ["QA_INTERNAL_LABEL"]
}
}
}
- Baseline: redaction explicitly false.
- Protected: redaction true, no allowlist, custom blocked label.
- Narrow exception: redaction true, only
QA_BUILD_IDallowed.
Do not allowlist a broad prefix, wildcard, or generic name. Test exact names, similar prefixes, suffixes, mixed case, empty values, whitespace, Unicode, and multiline values. A variable that looks similar to an allowed name must not inherit the exception.
Step 3: make the hook observe presence, not values
The hook should report only whether a named variable exists and place its diagnostics on stderr. It must never print the value. This Node.js probe keeps the output small and deterministic:
const names = [
'QA_HOOK_KEY',
'QA_ACCESS_TOKEN',
'QA_INTERNAL_LABEL',
'QA_BUILD_ID'
];
const present = Object.fromEntries(
names.map(name => [name, Object.hasOwn(process.env, name)])
);
process.stderr.write(`qa-redaction-probe:${JSON.stringify(present)}\n`);
process.stdout.write(JSON.stringify({ decision: 'allow' }));
Run the script directly with a sanitized harness before connecting it to Gemini CLI. Confirm that it emits one JSON object on stdout and one structured diagnostic on stderr. Scan both streams for every synthetic canary value and fail if any value appears.
Step 4: validate the baseline and protected modes
In the explicit-off baseline, the sensitive-name variables may be visible to the hook. That trial proves the probe can detect exposure; it is not an approved production configuration. In the protected session, the KEY and TOKEN canaries should be absent, the custom blocked label should be absent, and the build ID should follow the chosen allowlist policy.
Use two assertions for every variable: expected presence and forbidden value leakage. A missing variable is not enough if its value was copied into a startup log, command line, transcript, telemetry record, or error before the hook ran.
Step 5: test allowlists as a high-risk exception
Add exactly one required synthetic name to allowed. Restart, invoke the same hook, and prove that only that name becomes visible. Then remove it, restart again, and prove the variable disappears.
Exercise a typo, different case, trailing space, prefix collision, and duplicate entry. Test both a harmless build identifier and a sensitive-pattern name in an isolated negative trial. If an operational hook truly needs a credential, prefer a narrower credential-delivery mechanism and a dedicated process identity. An allowlist changes exposure; it does not make the hook trustworthy.
Step 6: cover settings precedence and folder trust
Place conflicting redaction fixtures at the supported configuration scopes one at a time. Capture the effective startup state and never guess which file won. Hash settings files rather than attaching secret-bearing copies to test reports.
The official trusted-folders guide says an untrusted workspace ignores project settings and project .env files, does not connect project MCP servers, and does not load custom commands. Build a project-level hook and .env canary, mark the folder untrusted, and verify that neither influences the session. Then trust the disposable folder deliberately and rerun. Folder trust is another layer, not evidence that a hook is safe.
Step 7: exercise hook protocol failures
Redaction must remain consistent when the hook misbehaves. Create separate fixtures for:
- valid JSON and exit code 0;
- block result with exit code 2 and a sanitized reason on stderr;
- a warning exit code;
- plain debug text before the stdout JSON;
- invalid or oversized JSON;
- timeout, crash, and missing executable;
- two hooks running sequentially and in parallel; and
- a hook command changed after it was previously trusted.
The best-practices guide says project hook identity is derived from its name and command; a changed command should be treated as a new untrusted identity. Verify the warning and refuse to auto-approve the new command in the lab. A protocol error must not dump the full process environment into an error report.
Step 8: inspect every downstream surface
After each trial, search the raw evidence—not only the hook diagnostic—for every unique canary. Cover stdout, stderr, hook-created files, CLI logs, transcripts, terminal capture, test reports, crash files, caches, child-process environments, telemetry exports, and any local collector.
Gemini CLI’s telemetry guide says telemetry is disabled by default. When enabled, it can contain configuration, prompt, tool, hook, and other execution evidence. Run telemetry-off and local-telemetry trials. Confirm that disabling prompt logging does not become a substitute for environment redaction, and remember that hook metadata can be suppressed. Missing telemetry is not proof that a hook never executed.
Step 9: test lifecycle and automation paths
Repeat the protected matrix after a full CLI restart, terminal restart, settings edit, hook enable and disable, repository update, and session resume. Run one interactive session and one headless session with the same synthetic environment. Compare the hook inventory, effective settings evidence, presence matrix, exit result, and canary scan.
In CI, avoid printing the environment for debugging. Configure the job with only the synthetic variables needed for the test and ensure masked CI values are not mistaken for Gemini CLI redaction. Validate each layer independently: CI secret masking, process inheritance, Gemini CLI filtering, hook output, and artifact retention.
Step 10: define a release gate
Fail the gate if redaction is unexpectedly disabled, a blocked or sensitive-name canary reaches the hook, an allowlist exposes an unlisted variable, a canary appears in any artifact, a settings change is tested without restart, a modified hook runs without review, invalid stdout is accepted as valid protocol, or a hook failure dumps environment data.
Warn and require human review when a CLI update changes the effective presence matrix, hook identity, matching behavior, default configuration, telemetry schema, or trust flow. Do not automatically rewrite expected results to match a new build.
QA evidence checklist
- Exact Gemini CLI build, OS, launcher, and clean workspace identity
- Settings scope, hash, effective redaction state, allowed and blocked names
- Proof of restart after every restart-required change
- Hook source, name, command, script hash, event, matcher, and trust state
- Synthetic fixture ID with no production values
- Baseline, protected, and narrow-exception presence matrices
- Exact-name, case, prefix, empty, multiline, and custom-block tests
- Trusted and untrusted folder results
- Success, block, warning, invalid JSON, timeout, crash, and modified-command results
- Interactive, headless, restart, and concurrent-hook evidence
- Canary scans across streams, files, transcripts, telemetry, crashes, and child processes
- Human security approval for any exception or third-party hook
Common mistakes
Using real secrets as test data: a redaction test should be safe even when it fails.
Printing the environment: this creates the exposure the test is supposed to prevent.
Editing settings without restart: the official configuration says redaction changes require restart.
Testing only variable absence: the same value may leak into another output before or after the hook.
Overusing allowlists: every exception widens what an executable hook can receive.
Calling redaction a sandbox: hooks run as the user and may access other data available to that user.
Conclusion
Gemini CLI environment redaction for QA is best validated with synthetic canaries, explicit configurations, complete restarts, narrow allowlists, and independent scans of every output surface. Pair redaction with folder trust, least privilege, reviewed hook code, network controls, safe credential delivery, and human approval. That produces defensible evidence without turning a useful safeguard into a false promise of secret isolation.
Official references
- Gemini CLI configuration reference
- Hooks best practices
- Hooks reference
- Trusted folders
- OpenTelemetry guide
