Skip to content

Conversation

@tsaarni
Copy link
Contributor

@tsaarni tsaarni commented Nov 18, 2025

Description

The JSON decoder returns io.EOF when the request body is empty, which was previously treated as an error. This prevented API endpoints with optional request bodies from succeeding when called without a body.

After this change is applied, io.EOF is treated as an empty object and nil return value to support endpoints like DELETE /sys/rekey/init that accept either a body with nonce parameter or no body at all (doc link).

Fixes #31649

PCI review checklist

  • I have documented a clear reason for, and description of, the change I am making.
  • If applicable, I've documented a plan to revert these changes if they require more than reverting the pull request.
  • If applicable, I've documented the impact of any changes to security controls.

Examples of changes to security controls include using new access control methods, adding or removing logging pipelines, etc.

@tsaarni tsaarni requested a review from a team as a code owner November 18, 2025 17:17
@vercel
Copy link

vercel bot commented Nov 18, 2025

@tsaarni is attempting to deploy a commit to the HashiCorp Team on Vercel.

A member of the Team first needs to authorize it.

@tsaarni tsaarni requested a deployment to community-pull-request November 18, 2025 17:18 — with GitHub Actions Waiting
@tsaarni
Copy link
Contributor Author

tsaarni commented Nov 18, 2025

I'm not 100% sure if this change is OK as it could affect other endpoints beyond the DELETE /sys/rekey/init (rekey cancel) where the issue was originally found. However, based on the existing code, it looks to me like the intent behind checking for io.EOF was to treat end-of-file as not an error:

vault/http/handler.go

Lines 898 to 905 in 226fb1a

err := jsonutil.DecodeJSONFromReader(reader, out)
if err != nil && err != io.EOF {
return nil, fmt.Errorf("failed to parse JSON input: %w", err)
}
if origBody != nil {
return io.NopCloser(origBody), err
}
return nil, err

The problem is that the err variable is never cleared. So error will be returned to callers, who will still interpret it as an error, unless they explicitly check for and ignore io.EOF (which does not seem to be the case).

The unit test TestCancelRekey_AfterDeadline didn't expose this issue because it calls Core.RekeyCancel() directly with an empty nonce:

err = c.RekeyCancel(tc.recovery, "", time.Microsecond)

This approach bypasses JSON request parsing entirely, so the io.EOF condition was never seen. To reproduce the problem, the test would need to call handleSysRekeyInitDelete instead, using an HTTP request with an empty body. That would trigger io.EOF at this point:

vault/http/sys_rekey.go

Lines 150 to 153 in 46b1821

if _, err := parseJSONRequest(core.PerfStandby(), r, w, &req); err != nil {
respondError(w, http.StatusBadRequest, err)
return
}

The JSON decoder returns io.EOF when the request body is empty, which
was previously treated as an error. This prevented API endpoints with
optional request bodies from succeeding when called without a body.

Treat io.EOF as an empty object to support endpoints like DELETE
/sys/rekey/init that accept either a body with parameters or no body
at all.

Signed-off-by: Tero Saarni <[email protected]>
@tsaarni tsaarni force-pushed the rekey-cancel-without-body branch from 319cf09 to 0e3e563 Compare November 18, 2025 17:28
@tsaarni
Copy link
Contributor Author

tsaarni commented Dec 9, 2025

Just following up on this PR to check if you had time to take a look. Thank you!

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rekey cancel returns 400 EOF error unless nonce is provided

2 participants