Skip to content

fix: redact API key in headers passed to request/response hooks - #383

Open
msuitcase wants to merge 2 commits into
masterfrom
sec-786-redact-hook-api-key
Open

msuitcase wants to merge 2 commits into
masterfrom
sec-786-redact-hook-api-key

Conversation

@msuitcase

Copy link
Copy Markdown

Description

Fixes SEC-786 (pentest finding "API Key Forwarded to Request-Hook Handlers", Medium). Requestor.httpRequest built the hook headers with generateHeaders(client.getApiKey()) and passed them to both RequestHookResponses and ResponseHookResponses, so every hook received Authorization: Bearer <full api key>. A hook that logs or exports its event (a common use for debugging, tracing, or APM) would write the full key to logs or a third-party platform.

Changes:

  • Hooks get a redacted copy of the headers. The new generateHookHeaders builds the hook headers map and replaces the Authorization value with Bearer ****WXYZ (last four characters only). This keeps enough auth context to tell keys apart in logs without exposing the credential.
    • The mask has a fixed length, so it doesn't reveal the key length either.
    • Keys of 8 characters or fewer are fully masked (Bearer ****), since showing four characters would reveal half or more of the key. Real EasyPost keys are much longer, so this only affects unusual or test keys.
  • The outbound request is unchanged. createEasyPostConnection and the App Engine path still build their own headers with the full key. The hook map was already a separate instance from the one used on the connection; it's now also redacted.
  • Updated the headers Javadoc on both hook classes. The ResponseHookResponses doc said "headers of the response", but these are the request headers.
  • CHANGELOG entry under Next Release.

Authorization is the only sensitive header the SDK sets, so no other headers needed redacting.

Testing

New tests in HookTest send a request through a mocked HttpsURLConnection (via EasyPost._vcrUrlFunction, the same approach as #382) and capture what both hooks receive:

  • testHooksReceiveRedactedApiKey: both hooks see Bearer ****WXYZ, no header value contains the full key, and Mockito.verify confirms the real connection was sent Bearer <full key>.
  • testHooksFullyRedactShortApiKey: an 8-character key is fully masked in both hooks and still sent in full on the wire.

The existing VCR hook tests are unchanged. An @AfterEach resets _vcrUrlFunction so the mock doesn't leak into other tests.

⚠️ I couldn't build this locally (my sandbox couldn't clone the repo or reach Maven), so CI is the first compile, checkstyle and test run.

Notes for merging:

  • fix: cap HTTP response body reads to prevent heap exhaustion #382 also adds a line under Next Release in CHANGELOG.md, so whichever lands second will have a one-line conflict to resolve. The code changes touch different parts of Requestor and don't overlap.
  • Same release caveat as fix: cap HTTP response body reads to prevent heap exhaustion #382: the ticket asks for a patch release, but Next Release already contains a breaking change (addCreditCardToUser removal). A true 8.8.1 would need a backport branch from v8.8.0; this change applies there cleanly in concept (only httpRequest and two new private helpers).
  • The ticket also asks to check the other SDKs (node, python, ruby, php, go, csharp) for the same hook behavior. That's not covered here.

Pull Request Type

Please select the option(s) that are relevant to this PR.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Improvement (fixing a typo, updating readme, renaming a variable name, etc)

🤖 Generated with Claude Code

msuitcase and others added 2 commits October 2, 2026 20:49
Hooks received the same headers map used to build the request, including
the full `Authorization: Bearer <api key>` value. A hook that logs or
exports its event would leak the key.

Hooks now get their own copy of the headers with the API key redacted to
its last four characters (e.g. `Bearer ****WXYZ`). Keys of 8 characters
or fewer are fully masked. The outbound request still sends the full key.

Fixes SEC-786.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@msuitcase
msuitcase requested review from a team as code owners October 3, 2026 03:51

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant