Skip to content

CLDSRV-863: Checksums for PutObject and UploadPart#6105

Draft
leif-scality wants to merge 7 commits intodevelopment/9.4from
improvement/CLDSRV-863-checksums-put-object-part
Draft

CLDSRV-863: Checksums for PutObject and UploadPart#6105
leif-scality wants to merge 7 commits intodevelopment/9.4from
improvement/CLDSRV-863-checksums-put-object-part

Conversation

@leif-scality
Copy link
Contributor

No description provided.

@bert-e
Copy link
Contributor

bert-e commented Mar 12, 2026

Hello leif-scality,

My role is to assist you with the merge of this
pull request. Please type @bert-e help to get information
on this process, or consult the user documentation.

Available options
name description privileged authored
/after_pull_request Wait for the given pull request id to be merged before continuing with the current one.
/bypass_author_approval Bypass the pull request author's approval
/bypass_build_status Bypass the build and test status
/bypass_commit_size Bypass the check on the size of the changeset TBA
/bypass_incompatible_branch Bypass the check on the source branch prefix
/bypass_jira_check Bypass the Jira issue check
/bypass_peer_approval Bypass the pull request peers' approval
/bypass_leader_approval Bypass the pull request leaders' approval
/approve Instruct Bert-E that the author has approved the pull request. ✍️
/create_pull_requests Allow the creation of integration pull requests.
/create_integration_branches Allow the creation of integration branches.
/no_octopus Prevent Wall-E from doing any octopus merge and use multiple consecutive merge instead
/unanimity Change review acceptance criteria from one reviewer at least to all reviewers
/wait Instruct Bert-E not to run until further notice.
Available commands
name description privileged
/help Print Bert-E's manual in the pull request.
/status Print Bert-E's current status in the pull request TBA
/clear Remove all comments from Bert-E from the history TBA
/retry Re-start a fresh build TBA
/build Re-start a fresh build TBA
/force_reset Delete integration branches & pull requests, and restart merge process from the beginning.
/reset Try to remove integration branches unless there are commits on them which do not appear on the source branch.

Status report is not available.

@bert-e
Copy link
Contributor

bert-e commented Mar 12, 2026

Incorrect fix version

The Fix Version/s in issue CLDSRV-863 contains:

  • None

Considering where you are trying to merge, I ignored possible hotfix versions and I expected to find:

  • 9.4.0

Please check the Fix Version/s of CLDSRV-863, or the target
branch of this pull request.


const { unsupportedSignatureChecksums, supportedSignatureChecksums } = require('../../../../constants');

// FIXME: merge this
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The FIXME: merge this comment and // Why do we need this check... are dev notes that should be resolved or removed before merging.

— Claude Code

const vault = require('../vault');
const constructChunkStringToSign = require('./constructChunkStringToSign');

// Do we use this one or vaults?
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dev note // Do we use this one or vaults? should be resolved and removed before merging.

— Claude Code

}
const value = Buffer.alloc(parsedContentLength);
const cbOnce = jsutil.once(callback);
// TODO
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The // TODO comment here indicates veeam/utils.js still uses the old prepareStream which is no longer wired to checksum validation. This should be addressed in this PR or tracked explicitly.

— Claude Code

@claude
Copy link

claude bot commented Mar 12, 2026

  • Race condition in ChecksumTransform: validateChecksum() is called synchronously in storeObject.js after data.put, but _flush (which computes the digest) is async. If _flush hasn't resolved before the callback fires, this.digest will be undefined and validation will silently pass or give wrong results.
    • Move validation into _flush and emit an error event, or wait for the stream's finish event before calling validateChecksum().
  • Error type mismatch in storeObject.js: prepareStream2 returns errors.InvalidArgument (Arsenal error) in the V4 streaming case, but arsenalErrorFromChecksumError() expects a ChecksumError string enum. This will fall through to the default case returning BadDigest instead of InvalidArgument.
    • Return a ChecksumError enum value from prepareStream2, or handle Arsenal errors separately in the caller.
  • Leftover debug artifacts: Multiple commented-out console.log statements across createAndStoreObject.js, prepareStream.js, and commented-out imports in storeObject.js.
    • Remove all before merging.
  • Unresolved dev notes: // FIXME: merge this in validateChecksumHeaders.js, // Do we use this one or vaults? in V4Transform.js, // TODO in veeam/utils.js, // TODO: test AWS in trailingChecksumTransform.js.
    • Resolve or remove before merging.
  • ChecksumTransform._transform callback: passes 3 args callback(null, input, encoding) but Node.js Transform callback only accepts (error, data).
    • Remove the extra encoding argument.
  • TrailingChecksumTransform trailer parsing: bytes after the trailer \r\n in the same chunk are silently dropped due to the break statement.
    • Verify this matches AWS behavior or handle leftover bytes.

Review by Claude Code

@codecov
Copy link

codecov bot commented Mar 12, 2026

Codecov Report

❌ Patch coverage is 74.19355% with 56 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.14%. Comparing base (8c7babd) to head (8b667bc).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
lib/api/apiUtils/integrity/validateChecksums.js 64.17% 24 Missing ⚠️
lib/auth/streamingV4/trailingChecksumTransform.js 72.50% 11 Missing ⚠️
lib/api/apiUtils/object/storeObject.js 70.00% 9 Missing ⚠️
lib/api/apiUtils/object/prepareStream.js 85.00% 6 Missing ⚠️
lib/auth/streamingV4/ChecksumTransform.js 83.78% 6 Missing ⚠️

❌ Your patch check has failed because the patch coverage (74.19%) is below the target coverage (80.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files

Impacted file tree graph

Files with missing lines Coverage Δ
lib/api/apiUtils/object/validateChecksumHeaders.js 100.00% <100.00%> (ø)
lib/auth/streamingV4/V4Transform.js 84.00% <ø> (ø)
lib/routes/veeam/utils.js 90.00% <ø> (ø)
lib/services.js 86.96% <100.00%> (+0.06%) ⬆️
lib/api/apiUtils/object/prepareStream.js 74.13% <85.00%> (-25.87%) ⬇️
lib/auth/streamingV4/ChecksumTransform.js 83.78% <83.78%> (ø)
lib/api/apiUtils/object/storeObject.js 78.00% <70.00%> (-11.66%) ⬇️
lib/auth/streamingV4/trailingChecksumTransform.js 83.33% <72.50%> (-9.12%) ⬇️
lib/api/apiUtils/integrity/validateChecksums.js 83.75% <64.17%> (-14.21%) ⬇️
@@                 Coverage Diff                 @@
##           development/9.4    #6105      +/-   ##
===================================================
- Coverage            84.38%   84.14%   -0.25%     
===================================================
  Files                  206      207       +1     
  Lines                13329    13528     +199     
===================================================
+ Hits                 11248    11383     +135     
- Misses                2081     2145      +64     
Flag Coverage Δ
file-ft-tests 68.11% <67.74%> (-0.15%) ⬇️
kmip-ft-tests 28.00% <32.71%> (+0.06%) ⬆️
mongo-v0-ft-tests 69.38% <67.74%> (-0.07%) ⬇️
mongo-v1-ft-tests 69.40% <67.74%> (-0.17%) ⬇️
multiple-backend 35.07% <35.94%> (-0.05%) ⬇️
sur-tests 35.49% <28.57%> (-1.01%) ⬇️
sur-tests-inflights 37.31% <28.57%> (-0.24%) ⬇️
unit 69.60% <47.00%> (-0.42%) ⬇️
utapi-v2-tests 34.45% <35.02%> (+0.04%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.


const { unsupportedSignatureChecksums, supportedSignatureChecksums } = require('../../../../constants');

// FIXME: merge this
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug comments left in the code: // FIXME: merge this and // Why do we need this check if we have unsupportedSignatureChecksums? appear to be development notes that should be resolved or removed before merging.

— Claude Code

const vault = require('../vault');
const constructChunkStringToSign = require('./constructChunkStringToSign');

// Do we use this one or vaults?
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment // Do we use this one or vaults? looks like a development note that should be resolved and removed before merging.

— Claude Code

}
const value = Buffer.alloc(parsedContentLength);
const cbOnce = jsutil.once(callback);
// TODO
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bare // TODO comment with no description. Either add context about what needs to be done (presumably migrating to prepareStream2) or remove if not actionable.

— Claude Code

@claude
Copy link

claude bot commented Mar 12, 2026

  • Race condition in checksum validation (storeObject.js:81): validateChecksum() is called synchronously but ChecksumTransform._flush() is async for crc64nvme. The digest may not be computed yet when validation runs.
    - Await a finish event on the checksumed stream, or make validateChecksum() async and await the digest.
    - Missing error handler on ChecksumTransform (prepareStream.js:88): In the STREAMING-AWS4-HMAC-SHA256-PAYLOAD case, ChecksumTransform has no error listener - a _flush rejection will be unhandled.
    - Add checksumedStream.on('error', errCb).
    - Orphaned data on checksum failure (storeObject.js:84): When checksum validation fails after data.put succeeds, the stored data is never cleaned up.
    - Delete stored data via data.batchDelete before returning the error, as checkHashMatchMD5 does for MD5 mismatches.
    - Development notes left in code: FIXME/TODO/question comments in validateChecksumHeaders.js, V4Transform.js, and veeam/utils.js should be resolved or removed.
    - Duplicate switch cases (prepareStream.js:108-125): UNSIGNED-PAYLOAD and default are identical - combine into a single default case.

    Review by Claude Code

const vault = require('../vault');
const constructChunkStringToSign = require('./constructChunkStringToSign');

// Do we use this one or vaults?
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leftover development comment: // Do we use this one or vaults? — resolve or remove before merging.

— Claude Code

}
const value = Buffer.alloc(parsedContentLength);
const cbOnce = jsutil.once(callback);
// TODO
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bare // TODO with no description. This call still uses the old prepareStream which doesn't do checksum validation. Either migrate this to prepareStream2 or add a clear explanation of why it's deferred.

— Claude Code

@claude
Copy link

claude bot commented Mar 12, 2026

  • Leftover development comments that should be resolved or removed before merge:
    • validateChecksumHeaders.js:8: // Why do we need this check...
    • V4Transform.js:9: // Do we use this one or vaults?
    • veeam/utils.js:38: bare // TODO with no description — still uses old prepareStream without checksum validation
    • trailingChecksumTransform.js:99: // TODO: test AWS
  • trailingChecksumTransform.js:104: bytes after the trailer line are silently dropped — should validate or log leftover data
  • trailingChecksumTransform.js:89: suspicious newline stripping logic — the comment says "strip trailing \n before \r\n" but \r\n was already consumed by indexOf
  • ChecksumTransform.js:74: _flush is async for crc64nvme but validateChecksum() is called synchronously — verify data.put always waits for stream flush before callback
  • prepareStream.js:110: UNSIGNED-PAYLOAD and default switch cases are identical — merge them

Review by Claude Code

const vault = require('../vault');
const constructChunkStringToSign = require('./constructChunkStringToSign');

// Do we use this one or vaults?
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug comment left in production code. This looks like a personal note that should be removed before merging.

— Claude Code

// If the x-amz-trailer header is present the request is using one of the
// trailing checksum algorithms, which are not supported.
if (headers['x-amz-trailer'] !== undefined &&
if (headers['x-amz-trailer'] !== undefined && // Why do we need this check if we have unsupportedSignatureChecksums?
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug comment left in production code. This looks like a personal note that should be removed before merging.

— Claude Code

}
const value = Buffer.alloc(parsedContentLength);
const cbOnce = jsutil.once(callback);
// TODO
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bare TODO left in production code. This should either be addressed or removed before merging.

— Claude Code

checksumedStream.on('error', errCb);
stream.pipe(checksumedStream);
return { error: null, stream: checksumedStream };
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UNSIGNED-PAYLOAD and default cases have identical code. Consider combining them by removing the UNSIGNED-PAYLOAD case and letting it fall through to default.

— Claude Code

if (checksumedStream.stream.writableFinished) {
return doValidate();
}
checksumedStream.stream.once('finish', doValidate);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Race condition risk: if the data backend callback fires before ChecksumTransform emits finish, this attaches a finish listener. But if an error on the checksum stream prevents finish from ever firing (e.g. stream destroyed), doValidate never runs and cbOnce never fires, leaving the request hanging. Consider also listening for the error event as a safety net.

— Claude Code

if (xAmzChecksumCnt !== 0) {
return {
error: ChecksumError.TrailerAndChecksum,
details: { trailer: headers['x-amz-trailer'], checksum: checksumHeaders },
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in comment: "If AWS there is a mismatch" should be "If there is a mismatch".

— Claude Code

@claude
Copy link

claude bot commented Mar 13, 2026

  • Debug/personal comments left in production code (3 instances): V4Transform.js:9, validateChecksumHeaders.js:8, veeam/utils.js:38 - Remove before merging
  • storeObject.js:104: Potential hanging request if ChecksumTransform errors before emitting finish - Add error listener as safety net
  • prepareStream.js:110-129: UNSIGNED-PAYLOAD and default switch cases are identical - Combine them
  • validateChecksums.js:207: Typo in comment

Review by Claude Code

}
const value = Buffer.alloc(parsedContentLength);
const cbOnce = jsutil.once(callback);
// TODO
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfinished TODO left in production code. Should be addressed or tracked in a ticket before merging.

— Claude Code

checksumedStream.on('error', errCb);
stream.pipe(checksumedStream);
return { error: null, stream: checksumedStream };
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The UNSIGNED-PAYLOAD case and default case are identical. Consider combining them by removing the UNSIGNED-PAYLOAD case and letting it fall through to default.

— Claude Code

const vault = require('../vault');
const constructChunkStringToSign = require('./constructChunkStringToSign');

// Do we use this one or vaults?
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug question left in production code. Remove before merging.

— Claude Code

// If the x-amz-trailer header is present the request is using one of the
// trailing checksum algorithms, which are not supported.
if (headers['x-amz-trailer'] !== undefined &&
if (headers['x-amz-trailer'] !== undefined && // Why do we need this check if we have unsupportedSignatureChecksums?
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug question left in production code. Remove before merging.

— Claude Code

return callback();
})
.catch(callback);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Double-callback risk: if callback() throws synchronously inside .then(), .catch(callback) calls it again. Move callback() out of the .then() return chain to prevent this.

— Claude Code

@claude
Copy link

claude bot commented Mar 13, 2026

  • Debug/TODO comments left in production code (3 instances): V4Transform.js:9, validateChecksumHeaders.js:8, veeam/utils.js:38
    • Remove or resolve before merging
  • ChecksumTransform._flush (line 74-81) has a double-callback risk: return callback() inside .then() means if callback() throws, .catch(callback) calls it again
    • Separate the assignment from the callback invocation so exceptions don't flow into .catch
  • prepareStream.js UNSIGNED-PAYLOAD and default switch cases are identical
    • Remove the UNSIGNED-PAYLOAD case to let it fall through to default

Review by Claude Code

const vault = require('../vault');
const constructChunkStringToSign = require('./constructChunkStringToSign');

// Do we use this one or vaults?
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug comment left in code. This looks like a development note that should not be committed.

— Claude Code

}
const value = Buffer.alloc(parsedContentLength);
const cbOnce = jsutil.once(callback);
// TODO
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug TODO left in code. This should either be addressed or removed before merging.

— Claude Code

// If the x-amz-trailer header is present the request is using one of the
// trailing checksum algorithms, which are not supported.
if (headers['x-amz-trailer'] !== undefined &&
if (headers['x-amz-trailer'] !== undefined && // Why do we need this check if we have unsupportedSignatureChecksums?
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug comment left in code. This reads like an open question during development, not a permanent comment. Remove the comment or resolve the question.

— Claude Code

stream.pipe(checksumedStream);
return { error: null, stream: checksumedStream };
}
case 'UNSIGNED-PAYLOAD': {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The UNSIGNED-PAYLOAD and default cases are identical. Consider collapsing them into a single fallthrough to reduce duplication.

— Claude Code

stream.pipe(checksumedStream);
return { error: null, stream: checksumedStream };
}
default: {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER (signed payload with trailer) is not handled. If a client sends this content-sha256 value, it falls through to the default case, which does not set up V4Transform for signature verification. Should this be handled explicitly, or rejected with an error?

— Claude Code

_flush(callback) {
Promise.resolve(this.algo.digestFromHash(this.hash))
.then(digest => { this.digest = digest; })
.then(() => callback(), callback);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If digestFromHash rejects, callback is called with the raw error. The Transform stream emits this as an error event handled by errCb. In storeObject.js, after data.put completes, the onError handler calls cbOnce(err) with this raw error rather than a proper Arsenal error, which may leak internal details to the client. Consider wrapping to ensure a clean Arsenal error.

— Claude Code

@claude
Copy link

claude bot commented Mar 13, 2026

  • Debug comments and TODOs left in code: V4Transform.js line 9, validateChecksumHeaders.js line 8, veeam/utils.js line 38
  • STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER not handled in prepareStream2, falls through to default without V4 signature verification
  • Duplicated UNSIGNED-PAYLOAD and default switch cases in prepareStream2
  • ChecksumTransform._flush error path may leak raw errors to the client

Review by Claude Code

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.

2 participants