Skip to content

fix(jmespath): stop sort() and reverse() from mutating the input array - #5823

Closed
unknownhackerworld wants to merge 1 commit into
aws-powertools:mainfrom
unknownhackerworld:fix/jmespath-sort-reverse-mutation
Closed

unknownhackerworld wants to merge 1 commit into
aws-powertools:mainfrom
unknownhackerworld:fix/jmespath-sort-reverse-mutation

Conversation

@unknownhackerworld

Copy link
Copy Markdown
Contributor

Summary

Changes

sort() and reverse() sorted and reversed the caller's array in place, so evaluating an expression changed the data it read. Both functions now work on a copy and leave the input untouched.

Root cause

In packages/jmespath/src/Functions.ts, funcSort returned arg.sort(...) and funcReverse returned arg.reverse() for arrays. Array.prototype.sort and Array.prototype.reverse both mutate the array they are called on, and arg is the array from the caller's object, not a copy (the tree interpreter passes field values by reference). So search('sort(numbers)', data) returned the sorted array and also left data.numbers sorted.

This reaches the utilities that call search() on the Lambda event:

  • Idempotency evaluates eventKeyJmesPath on the event before it runs the handler (IdempotencyHandler and BasePersistenceLayer.getHashedIdempotencyKey). makeIdempotent passes the handler's own argument (makeIdempotent.ts, functionPayloadToBeHashed = args[0]), so with eventKeyJmesPath: 'sort(items)' the handler received event.items already reordered. I reproduced this with makeIdempotent and the PersistenceLayerTestClass helper: the handler saw ['a', 'b', 'c'] for an event sent with ['b', 'c', 'a']. With this change it sees ['b', 'c', 'a'].
  • The same applies to validationKeyJmesPath, to Validation envelopes (validate.ts) and to any direct search() call.

The Python reference implementation (jmespath.py, used by Powertools for AWS Lambda (Python)) returns new lists from both functions (sorted(arg) and list(reversed(arg))), so this was a TypeScript-only divergence.

Fix

Copy the array before the in-place call: [...arg].sort(...) and [...arg].reverse(). The comparator, the string branch of reverse() (which already builds a new string) and the return types are unchanged. I checked the other functions in the file: sort_by() already sorts a new array produced by map(), and max_by(), min_by(), map() and merge() do not mutate their input.

Tests

In packages/jmespath/tests/unit/compliance/functions.test.ts, a new it.each evaluates sort(numbers), sort(strings) and reverse(numbers) and checks both the result and that the input object is unchanged. All three cases fail on main and pass with this change. npm run test:unit:coverage -w packages/jmespath passes with 100% coverage (891 tests), and biome ci and npm run build:tests are clean.

Tradeoffs and alternatives

  • The copy costs one shallow array allocation per call. The arrays are small event fields and sort() is already O(n log n), so I did not think it was worth avoiding.
  • Array.prototype.toSorted() and toReversed() would also work, since the project targets ES2024 and Node.js 22. I used the spread copy because it is the smallest change to the existing calls and keeps the comparator untouched. Happy to switch to toSorted() / toReversed() if you prefer them.
  • Deep-cloning the event before search() in Idempotency and Validation would also hide the problem, but it costs more on every invocation and leaves direct search() callers exposed. Fixing the two functions keeps evaluation side-effect free for every caller.

Issue number: closes #5822


By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

Disclaimer: We value your time and bandwidth. As such, any pull requests created on non-triaged issues might not be successful.

Copilot AI balanced review requested due to automatic review settings October 20, 2026 05:38
@powertools-for-aws-oss-automation powertools-for-aws-oss-automation Bot added the size/M PR between 30-99 LOC label Oct 20, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Comment thread packages/jmespath/src/Functions.ts Outdated
public funcReverse(arg: string | Array<unknown>): string | Array<unknown> {
return Array.isArray(arg)
? arg.reverse()
? [...arg].reverse()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can we use toReversed instead?

Comment thread packages/jmespath/src/Functions.ts Outdated
})
public funcSort(arg: Array<string> | Array<number>): Array<unknown> {
return arg.sort((a: string | number, b: string | number): number => {
return [...arg].sort((a: string | number, b: string | number): number => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can we use toSorted in stead here?

@dreamorosi

Copy link
Copy Markdown
Contributor

Hi @unknownhackerworld - are you still interested in completing this PR? If yes, please try to address the comments by the end of the week, if not and priorities have changed we’ll close it on Monday - that’s not an issue, and you’re welcome to contribute another PR in the future.

Signed-off-by: Allen Joseph G <63872540+unknownhackerworld@users.noreply.github.com>
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.

Bug: JMESPath sort() and reverse() mutate the input array, so search() reorders the event

4 participants