●OPUS5 — Claude Opus 5 has arrived: a faster, more cost-efficient model for coding and knowledge work, now the default on Max and the top choice on Pro●SUNSET — Claude Opus 4.1, deprecated back on June 5, reaches its retirement date today, August 5. Worth checking anywhere you pin an API version●FOCUS — VSCode gains a Focus view that tucks tool activity behind a per-turn summary, so Ctrl+Alt+F leaves you with just the thread of the conversation●MASK — Sandbox credential masking landed for Linux and WSL. Commands inside read a sentinel copy while the proxy substitutes the real value on egress●LEAK — Several long-session memory leaks are fixed, including MCP stdio stderr piling up and LSP documents that never closed●MCP — The 2026-07-28 MCP spec brings a stateless core plus stronger OAuth and OIDC authorization. Monthly SDK downloads have passed 400 million●OPUS5 — Claude Opus 5 has arrived: a faster, more cost-efficient model for coding and knowledge work, now the default on Max and the top choice on Pro●SUNSET — Claude Opus 4.1, deprecated back on June 5, reaches its retirement date today, August 5. Worth checking anywhere you pin an API version●FOCUS — VSCode gains a Focus view that tucks tool activity behind a per-turn summary, so Ctrl+Alt+F leaves you with just the thread of the conversation●MASK — Sandbox credential masking landed for Linux and WSL. Commands inside read a sentinel copy while the proxy substitutes the real value on egress●LEAK — Several long-session memory leaks are fixed, including MCP stdio stderr piling up and LSP documents that never closed●MCP — The 2026-07-28 MCP spec brings a stateless core plus stronger OAuth and OIDC authorization. Monthly SDK downloads have passed 400 million
Passing the Request, Not the Secret — Where Sandbox Credential Masking Works and Where Substitution Breaks
Claude Code's sandbox credential masking lets processes read sentinel values while a proxy swaps in the real secret at send time. I rebuilt the mechanism as a minimal proxy and measured exactly which auth schemes survive the swap — and which break.
Denying reads is a strong control. But any job that legitimately calls an external API still needs the real token somewhere. Deny can hide the secrets a job does not need — it cannot protect the one secret the job actually uses.
Then the August 5, 2026 changelog landed with mode: "mask" for sandbox credentials on Linux and WSL, and one sentence stopped me: commands inside the sandbox read a sentinel copy of the value, and the sandbox proxy swaps in the real value at send time.
Not hiding the secret. Handing the process a decoy, then swapping in the original at the exit.
How far that design actually protects you is not something the docs alone can answer. So I rebuilt the core of the mechanism — the byte-level swap — as a minimal proxy and pushed on its boundaries myself.
From "Can't Read It" to "Reads a Decoy"
Deny and mask protect different things.
Deny controls exposure. It hides ~/.aws/credentials and unrelated environment variables from the sandbox — the same instinct as tightening filesystem isolation from the write surface: shrink what can be touched at all.
Mask protects the next layer. Even a secret the job legitimately uses is never visible to the process. What the process reads is a harmless string like SBX_SENTINEL_...; the real value only exists in the request for the instant it passes through the egress proxy.
The implication is significant. If prompt injection or malicious code runs inside the sandbox, all it can exfiltrate is the sentinel — a string with no authority anywhere in the outside world.
And yet: a mechanism defined as "replace bytes at send time" must have cases it structurally cannot cover. To find them, I wrote the smallest proxy that could.
Rebuilding the Mechanism in Miniature
My test environment is Python 3.10.12 in a Linux sandbox. Real implementations involve TLS handling and more, but here I isolate the one core operation — replacing sentinel bytes on the wire with the real value — over a plaintext path.
The naive version replaces within each received chunk and forwards it:
# Naive: substitute the sentinel per received chunkdef handle(client_sock): up = socket.create_connection((UPSTREAM_HOST, UPSTREAM_PORT)) client_sock.settimeout(0.5) try: while True: chunk = client_sock.recv(8192) if not chunk: break # Only sentinels fully contained in one chunk get replaced up.sendall(chunk.replace(SENTINEL, REAL_VALUE)) except socket.timeout: pass
It appears to work. But it has a classic stream-processing hole: the moment a sentinel straddles two recv chunks, neither chunk contains the complete pattern, and the substitution silently misses.
The fix is a rolling buffer that always retains a tail of length len(sentinel) - 1:
# Rolling buffer: catches sentinels split across chunk boundariesS = SENTINEL # byteskeep = len(S) - 1 # longest prefix that could straddle a boundarydef handle(client_sock): up = socket.create_connection((UPSTREAM_HOST, UPSTREAM_PORT)) client_sock.settimeout(0.5) buf = b"" try: while True: chunk = client_sock.recv(8192) if not chunk: break buf += chunk buf = buf.replace(S, REAL_VALUE) if len(buf) > keep: # Hold back the last `keep` bytes until the next chunk arrives up.sendall(buf[:-keep]) buf = buf[-keep:] except socket.timeout: pass if buf: # Final flush: replace anything left in the tail before sending up.sendall(buf.replace(S, REAL_VALUE))
Error handling is trimmed for clarity, but the two essentials are the carried-over tail and the final flush. Drop either one and you get a bug that only fires when a sentinel lands at the end of a request — the kind that is miserable to reproduce.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦Learn to classify auth schemes into ones sentinel substitution can protect and ones it structurally cannot, backed by base64 and HMAC measurements
✦See why per-chunk replacement silently leaks sentinels that straddle recv boundaries, with reusable naive vs. rolling-buffer proxy code
✦Take away measured substitution overhead (+0.665ms median) and a four-step checklist for sorting your own jobs before enabling mask mode
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Measurement 1: Straddled Sentinels Slip Past Naive Replacement
I forced recv into 8-byte chunks so the 19-byte sentinel always straddles a boundary, put the sentinel in a Bearer header, and ran it through both proxies.
Implementation
Real value reached upstream
Sentinel leaked verbatim
Naive (per-chunk replace)
No
Yes
Rolling buffer
Yes
No
With the naive version, the upstream server's receive log contained the sentinel byte-for-byte. If you are ever tempted to hand-roll this kind of proxy, this single result is a good reason to reconsider. Stream substitution is a notch harder than it looks, and production implementations carry rolling-buffer logic for a reason.
Measurement 2: Transformed Secrets Cannot Be Swapped
This is where the results diverged from my expectations.
Substitution presupposes that the sentinel appears verbatim in the bytes on the wire. So what happens with auth schemes that transform the secret before sending?
First, Basic auth — user:password base64-encoded into a header. I put the sentinel in the password slot and sent it through the rolling-buffer proxy:
Authorization header received upstream, after base64 decode: user:SBX_SENTINEL_a7f3e9Swap performed: none (the sentinel survived inside the base64 envelope)
The proxy scans wire bytes, but what travels is the base64-transformed string. The sentinel's original form never appears, so there is nothing to replace. Upstream receives credentials that sincerely believe the sentinel is a password — and rejects them with a 401.
Next, HMAC signing (think AWS SigV4). I compared a signature keyed on the sentinel against one keyed on the real value:
First 16 hex chars of HMAC-SHA256("payload"): sentinel key: c22efffa06d956f2 real key: 37c6b2d919ceadfd
Different, of course. The signature is computed inside the sandbox, so a sentinel-keyed signature is what leaves. The proxy only sees the finished artifact passing by — it can swap the secret's raw form, never the computation the secret fed into. Verification fails every time.
So the comfortable reading — "enable masking and all secrets are protected" — turned out to be imprecise. Masking protects exactly those schemes where the secret appears verbatim on the wire. Schemes with encoding or signing in between are structurally out of reach. I doubt I would have extracted that from one line of documentation without running it.
Which Auth Schemes You Can Hand Over
Here is how the measurements sort things:
Auth scheme
Secret's form on the wire
Fit with mask mode
Bearer token in the Authorization header
Verbatim
Good
Custom headers such as X-API-Key
Verbatim
Good
Basic auth
Base64-encoded
Does not work
HMAC-signed requests (SigV4 and similar)
Transformed into a signature
Does not work
JWTs signed with a private key
Transformed into a signature
Does not work
In the scheduled pipelines I run as an indie developer, the secrets that matter are a GitHub PAT for pushes and an Anthropic API key — both verbatim header schemes, so both are a good fit for masking. If I ever wire up a signature-based API, the signing step will have to live outside the sandbox, likely combined with a design that leans on short-lived credentials.
Measured Overhead, and Four Steps Before Enabling
I also measured the scanning cost: 200 requests with 1KB bodies, direct versus through the proxy (loopback, plaintext — treat these as reference values).
Path
Median
p95
Direct
0.612 ms
0.848 ms
Via proxy (with substitution)
1.277 ms
1.547 ms
+0.665ms at the median — roughly 2.1x the direct path. Against real network round-trips of tens to hundreds of milliseconds, that disappears into noise for ordinary API calls. Only tight inner loops firing many small requests need to think about the accumulation.
Before enabling mask mode, I would work through this sequence:
List every external API your jobs call and its auth scheme. Split them into verbatim-header schemes versus encode/sign schemes
Put only the verbatim-scheme secrets under mask. Bearer-style tokens belong here
Keep transformed-scheme secrets on the deny/unset side. If signing is required, do it outside the sandbox
Watch upstream 401 rates after the switch. A misclassified secret surfaces immediately as auth failures
Step 4 is the mirror image of Measurement 2. A masking misclassification does not fail silently — it announces itself as an upstream auth error, which, as failure modes go, is a considerate one.
Measure the Boundary, Then Delegate
Deny narrows the exposure surface, filesystem isolation narrows the write surface, and now mask keeps even the in-use secret away from the process. The sandbox's defenses are visibly becoming layered.
But the work of measuring each new layer's boundary cannot be skipped. Had I applied mask mode to a signature-based API without knowing that substitution only touches verbatim bytes, I would have lost hours to auth errors with no obvious cause.
Start by making the table for your own jobs: each secret, each auth scheme, transformed or not. Once the table is filled in, the range you can safely hand to masking becomes self-evident.
The verification code above is yours to reuse. If it shortens someone's debugging session even a little, writing this up was worth it.
Share
Thank You for Reading
Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.