Re-signing AWS Requests After Credential Masking
A sandbox-runtime signing replay checks why placeholder credentials require a new HMAC, how forwarded body bytes enter the signature, and where the re-signing hook declines or denies a request.
Replacing a placeholder access key ID with its host-side value did not produce the expected AWS signature. Re-signing did. In a synthetic POST request, the sandbox runtime's planner generated an Authorization header matching an independent HMAC calculation over the replacement body and an injected session token.
That distinction matters for agent sandboxes. A process can use placeholder credentials while a trusted proxy holds the corresponding secrets. Plain bearer-token substitution is a string operation. AWS Signature Version 4 also binds a derived signing key to request details, so the proxy has to repair the cryptographic authentication after those details change.
I examined revision 98a4016d27609ed34dd1fb43d23ed6efb6eeeb77 of Anthropic Sandbox Runtime, whose package declares version 0.0.81. I ran the unchanged signer, credential-pair registration, and planner modules with Node 24.19.0 on Linux. The tests used a public AWS test vector and synthetic credentials. No request reached AWS, and no TLS proxy or OS sandbox was started.
A Credential Pair Is More Than Two Replacements
The runtime contains OS sandbox wrappers, network proxies, request filtering, credential masking, and integration tests. Its package configuration exposes the srt CLI and declares Node >=22.12.0. This replay uses the protocol modules directly, without installing or validating the complete distribution.
The sentinel registry associates placeholder values with host-side credentials for substitution. SigV4 needs an additional credential-pair registry: access key ID, secret access key, and optional session token must remain linked.
The pair registry indexes the placeholder access key ID exactly. That ID appears in the client's Authorization credential scope, making it a usable trigger for selecting the corresponding signing secret. Replacing only the ID cannot change the HMAC that the client computed with its placeholder secret.
The probe exercised registration using synthetic versions of AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN. I supplied an explicit fixture environment map, so registration read no account credentials from the execution environment. The pair's destination list contained one exact fixture host.
Detect Before Replacement, Sign After Replacement
The planner receives a request method, target, headers, and destination. For a registered key and matching destination, a supported header signature produces a resign plan. The plan's apply() function mutates the outgoing headers.
Two ordering constraints follow from that interface:
| Stage | Required view of the request |
|---|---|
| Select the pair | Headers still contain the registered placeholder key ID |
| Compute the outgoing signature | Headers and payload hash describe what will go upstream |
For a literal body hash, plan.payloadHash is undefined: the caller must supply a digest of the forwarded bytes. In my fixture, the body changed from {"value":"placeholder"} to {"value":"replacement"}. The planner's application updated x-amz-content-sha256, inserted the host-side session token, and added that token to the signed-header set.
The resulting signed headers were:
host;x-amz-content-sha256;x-amz-date;x-amz-security-token
I compared the output with a small independent oracle for this fixed ASCII request. It built the canonical POST request and derived the signing key using Node's crypto primitives, without calling the production canonicalization or key-derivation helpers. The output matched the replacement-body calculation and differed from the original-body calculation.
This verifies one concrete signing path. It does not establish correctness for every URL encoding, repeated header, service-specific canonicalization rule, or AWS endpoint.
The Executed Replay
The probe verifies source and fixture digests before importing the pinned modules. A local loader hook resolves their emitted .js import specifiers to the corresponding original .ts files; Node strips the types. The module bodies are unchanged.
Obtain the six files listed in the probe from the pinned revision, preserving their paths. From the portfolio checkout, I ran:
node public/blog/sigv4-after-credential-masking/signing-probe.mjs /tmp/srt-note27
The command exited zero. Selected stdout:
public get-vanilla vector: canonical request and Authorization matched
key-id substitution only: does not match real-secret HMAC
registered pair: re-signed forwarded-body hash; session token included; independent HMAC matched
outside injectHosts: no re-sign plan
unregistered key id: no re-sign plan
missing signed date: deny
streaming signature with registered key: deny by default
presigned URL: deny by default; explicit passthrough gives no re-sign plan
cleared registry: no re-sign plan
The golden case uses the vendored public get-vanilla vector. Both its canonical request and complete Authorization header matched. I did not execute the entire vector suite or the upstream Bun tests.
No Plan and Deny Have Different Meanings
The hook does not claim every AWS-looking request. In the replay, an unregistered key ID or a destination outside the pair's injectHosts returned undefined. That means this hook declined to re-sign. It is not a network denial; other proxy policy and forwarding logic remain responsible for the request.
For the registered fixture pair at its allowed destination, a missing signed date produced a denial. Streaming SigV4 payloads and presigned URLs were denied under the default policy. Those formats need more than replacing the header signature: streaming payloads chain signatures through chunks, while presigned requests carry authentication in the URL. An explicit presigned passthrough policy returned no plan, so the hook performed no repair.
These checks used an exact-host matcher supplied by the probe. They do not test the runtime's wildcard matching, DNS resolution, TLS verification, or complete credential-substitution policy. Destination eligibility for re-signing also does not decide whether an application-level action is appropriate. The signer preserves the client's requested region, service, timestamp, and target; authorization policy still needs its own enforcement.
The Transport Still Owns the Bytes
Source inspection of the TLS-terminating handler shows how it wires these pieces together. Planning happens after outgoing header filtering and before credential replacement. For a literal payload hash, body substitution happens before buffering and hashing. The handler then applies the signing plan to the outgoing headers.
That path has a default 64 MiB buffering limit. A body exceeding the limit is denied, and a signing error is handled before forwarding a partially rewritten request. These are inspected branches, not exercised transport results. The upstream integration tests cover the surrounding TLS proxy, but I did not run them here.
The practical boundary is clear from the executed modules: masking a signing secret changes the authentication problem. A trusted bridge must identify the placeholder before replacement, bind re-signing to the intended credential pair and destination, then authenticate the final request representation. The replay validates that protocol path for its fixtures; deployment isolation and upstream acceptance require separate tests.