Skip to content

Add new object utility helpers and harden defaults against prototype pollution#564

Merged
nev21 merged 3 commits into
mainfrom
nev21/objFuncs
May 19, 2026
Merged

Add new object utility helpers and harden defaults against prototype pollution#564
nev21 merged 3 commits into
mainfrom
nev21/objFuncs

Conversation

@nev21
Copy link
Copy Markdown
Contributor

@nev21 nev21 commented May 16, 2026

  • add new object helpers: objPick, objOmit, objPickBy, objOmitBy, objMapValues, objMergeIf, objDefaults, and objDiff
  • export the new helpers from the public index
  • add common test coverage for pick/omit, mapValues, defaults/mergeIf, and diff behavior
  • fix objPick/objOmit overload compatibility by widening implementation signatures to PropertyKey
  • preserve numeric key omission at runtime in objOmit when typed overloads pass numeric keys
  • document objDefaults as similar to Lodash defaults while clarifying it only uses own enumerable source properties
  • harden objMergeIf and objDefaults against prototype pollution by skipping unsafe keys and blocking unsafe built-in prototype targets

@nev21 nev21 added this to the 0.14.0 milestone May 16, 2026
@nev21 nev21 requested a review from a team as a code owner May 16, 2026 15:27
Copilot AI review requested due to automatic review settings May 16, 2026 15:27
@nev21 nev21 requested a review from a team as a code owner May 16, 2026 15:27
@codecov
Copy link
Copy Markdown

codecov Bot commented May 16, 2026

Codecov Report

❌ Patch coverage is 98.62385% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 99.39%. Comparing base (0a486d3) to head (349900e).

Files with missing lines Patch % Lines
lib/src/object/pick.ts 97.46% 2 Missing ⚠️
lib/src/object/forEachOwnKey.ts 96.15% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #564      +/-   ##
==========================================
- Coverage   99.43%   99.39%   -0.05%     
==========================================
  Files         150      154       +4     
  Lines        4460     4659     +199     
  Branches      951      998      +47     
==========================================
+ Hits         4435     4631     +196     
- Misses         25       28       +3     
Files with missing lines Coverage Δ
lib/src/internal/unwrapFunction.ts 100.00% <100.00%> (ø)
lib/src/object/copy.ts 100.00% <100.00%> (ø)
lib/src/object/defaults.ts 100.00% <100.00%> (ø)
lib/src/object/define.ts 100.00% <100.00%> (ø)
lib/src/object/diff.ts 100.00% <100.00%> (ø)
lib/src/object/for_each_key.ts 100.00% <100.00%> (ø)
lib/src/object/isUnsafeTarget.ts 100.00% <100.00%> (ø)
lib/src/object/map_values.ts 100.00% <100.00%> (ø)
lib/src/object/property_is_enumerable.ts 100.00% <100.00%> (ø)
lib/src/object/forEachOwnKey.ts 96.29% <96.15%> (ø)
... and 1 more
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds new object utility helpers to the TypeScript utility library, along with tests, documentation updates, exports, and adjusted bundle/dependency metadata.

Changes:

  • Adds object helpers for picking/omitting, predicate filtering, mapping values, defaults/conditional merge, and shallow diffs.
  • Adds common object tests and exports the new helpers from the main entrypoint.
  • Updates README/backlog docs, dependency minimums, size limits, and Copilot instructions.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
lib/src/object/pick.ts Adds pick/omit and predicate-based object selection helpers.
lib/src/object/map_values.ts Adds value-mapping helper for object properties.
lib/src/object/defaults.ts Adds conditional merge and defaults helpers.
lib/src/object/diff.ts Adds shallow object diff helper.
lib/src/index.ts Exports the new object helpers from the main entrypoint.
lib/test/src/common/object/pick.test.ts Adds tests for pick/omit helpers.
lib/test/src/common/object/map_values.test.ts Adds tests for object value mapping.
lib/test/src/common/object/defaults.test.ts Adds tests for merge/defaults helpers.
lib/test/src/common/object/diff.test.ts Adds tests for shallow diff behavior.
README.md Documents the new object utilities in feature summaries and API matrix.
docs/feature-backlog.md Removes implemented object utilities from the backlog.
package.json Raises selected development dependency minimums.
.size-limit.json Raises several bundle size thresholds.
.github/copilot-instructions.md Adds Azure-specific Copilot instruction rules.
Comments suppressed due to low confidence (2)

lib/src/object/pick.ts:77

  • The public overload above accepts any string keys, but the implementation signature only accepts ReadonlyArray<string> while the typed overload can pass keyof T keys, including symbol/number keys. This mismatch makes the overload set incompatible with the implementation and can break TypeScript compilation.
export function objOmit<T>(source: T, keys: ReadonlyArray<string>): Partial<T> {

lib/src/object/diff.ts:50

  • Added properties whose value is undefined are dropped because a missing base key is converted to undefined and then compared with !==. This contradicts the documented behavior that properties present in modified but not in base are included; {} vs { a: undefined } should retain the a key.
        const baseVal = objHasOwn(base, key) ? (base as any)[key] : undefined;
        if (baseVal !== value) {

Comment thread lib/src/object/pick.ts Outdated
Comment thread lib/src/object/diff.ts
Comment thread lib/src/object/defaults.ts Outdated
Comment thread .github/copilot-instructions.md Outdated
Comment thread lib/src/object/pick.ts Outdated
Comment thread lib/src/object/defaults.ts Outdated
@nev21 nev21 force-pushed the nev21/objFuncs branch from 7a2d5fd to 7618a28 Compare May 18, 2026 02:10
@nev21 nev21 requested a review from Copilot May 18, 2026 02:12
@nev21 nev21 changed the title Add object utility helpers with tests and docs Add new object utility helpers and harden defaults against prototype pollution May 18, 2026
@nev21 nev21 force-pushed the nev21/objFuncs branch from 7618a28 to 453ad24 Compare May 18, 2026 02:19
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 5 comments.

Comments suppressed due to low confidence (3)

lib/src/object/diff.ts:53

  • objDiff does not include newly added properties whose value is undefined: when base lacks the key, baseVal is set to undefined, so baseVal !== value is false. This contradicts the documented behavior that properties present in modified but not in base are included.
            const baseVal = objHasOwn(base, key) ? (base as any)[key] : undefined;
            if (baseVal !== value) {

lib/src/object/pick.ts:123

  • objPickBy cannot include enumerable symbol properties because objForEachKey only iterates string keys, but the function returns Partial<T> and documents filtering own enumerable properties. A source with symbol-keyed data will be silently missing those properties from the result.
    objForEachKey(source, (key, value) => {
        if (predicate(key, value)) {
            (result as any)[key] = value;

lib/src/object/pick.ts:153

  • objOmitBy drops enumerable symbol properties regardless of the predicate because objForEachKey only visits string keys. Since the result is typed as Partial<T> and the docs describe own enumerable properties, this can silently lose symbol-keyed data.
    objForEachKey(source, (key, value) => {
        if (!predicate(key, value)) {
            (result as any)[key] = value;

Comment thread lib/src/object/pick.ts Outdated
Comment thread lib/src/object/pick.ts Outdated
Comment thread lib/src/object/map_values.ts Outdated
Comment thread lib/src/object/diff.ts Outdated
Comment thread lib/src/object/defaults.ts
Copy link
Copy Markdown
Contributor

Copilot AI commented May 18, 2026

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • accounts.google.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=network --no-sandbox --disable-dev-shm-usage --use-angle=swiftshader-webgl --crashpad-handler-pid=6133 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/karma-45691960 --change-stack-guard-on-fork=enable --shared-files=network_parent_dirs_pipe:100,v8_context_snapshot_data:101 --field-trial-handle=3,i,1199227595089660070,15175812677166680058,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,10666523406645318218,11337487493276019718,4 --trace-process-track-uuid=3190708989122997041 (dns block)
    • Triggering command: /home/REDACTED/.cache/puppeteer/chrome/linux-148.0.7778.167/chrome-linux64/chrome /home/REDACTED/.cache/puppeteer/chrome/linux-148.0.7778.167/chrome-linux64/chrome --user-data-dir=/tmp/karma-45691960 --enable-automation --no-default-browser-check --no-first-run --disable-default-apps --disable-popup-blocking --disable-translate --disable-REDACTED-timer-throttling --disable-renderer-REDACTEDing --disable-device-discovery-notifications --no-sandbox --disable-gpu --disable-web-security --disable-dev-shm-usage http://localhost:9876/?id=45691960 --headless --disable-gpu --disable-dev-shm-usage --remote-debugging-port=9222 (dns block)
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=network --no-sandbox --disable-dev-shm-usage --use-angle=swiftshader-webgl --crashpad-handler-pid=6381 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/karma-15719036 --change-stack-guard-on-fork=enable --shared-files=network_parent_dirs_pipe:100,v8_context_snapshot_data:101 --field-trial-handle=3,i,16618068847485743766,3400912940417460984,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,11171104491658937797,16843082412332472818,4 --trace-process-track-uuid=3190708989122997041 (dns block)
  • clients2.google.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=network --no-sandbox --disable-dev-shm-usage --use-angle=swiftshader-webgl --crashpad-handler-pid=6133 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/karma-45691960 --change-stack-guard-on-fork=enable --shared-files=network_parent_dirs_pipe:100,v8_context_snapshot_data:101 --field-trial-handle=3,i,1199227595089660070,15175812677166680058,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,10666523406645318218,11337487493276019718,4 --trace-process-track-uuid=3190708989122997041 (dns block)
    • Triggering command: /home/REDACTED/.cache/puppeteer/chrome/linux-148.0.7778.167/chrome-linux64/chrome /home/REDACTED/.cache/puppeteer/chrome/linux-148.0.7778.167/chrome-linux64/chrome --user-data-dir=/tmp/karma-45691960 --enable-automation --no-default-browser-check --no-first-run --disable-default-apps --disable-popup-blocking --disable-translate --disable-REDACTED-timer-throttling --disable-renderer-REDACTEDing --disable-device-discovery-notifications --no-sandbox --disable-gpu --disable-web-security --disable-dev-shm-usage http://localhost:9876/?id=45691960 --headless --disable-gpu --disable-dev-shm-usage --remote-debugging-port=9222 (dns block)
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=network --no-sandbox --disable-dev-shm-usage --use-angle=swiftshader-webgl --crashpad-handler-pid=6381 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/karma-15719036 --change-stack-guard-on-fork=enable --shared-files=network_parent_dirs_pipe:100,v8_context_snapshot_data:101 --field-trial-handle=3,i,16618068847485743766,3400912940417460984,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,11171104491658937797,16843082412332472818,4 --trace-process-track-uuid=3190708989122997041 (dns block)
  • redirector.gvt1.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=network --no-sandbox --disable-dev-shm-usage --use-angle=swiftshader-webgl --crashpad-handler-pid=6133 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/karma-45691960 --change-stack-guard-on-fork=enable --shared-files=network_parent_dirs_pipe:100,v8_context_snapshot_data:101 --field-trial-handle=3,i,1199227595089660070,15175812677166680058,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,10666523406645318218,11337487493276019718,4 --trace-process-track-uuid=3190708989122997041 (dns block)
    • Triggering command: /home/REDACTED/.cache/puppeteer/chrome/linux-148.0.7778.167/chrome-linux64/chrome /home/REDACTED/.cache/puppeteer/chrome/linux-148.0.7778.167/chrome-linux64/chrome --user-data-dir=/tmp/karma-45691960 --enable-automation --no-default-browser-check --no-first-run --disable-default-apps --disable-popup-blocking --disable-translate --disable-REDACTED-timer-throttling --disable-renderer-REDACTEDing --disable-device-discovery-notifications --no-sandbox --disable-gpu --disable-web-security --disable-dev-shm-usage http://localhost:9876/?id=45691960 --headless --disable-gpu --disable-dev-shm-usage --remote-debugging-port=9222 (dns block)
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=network --no-sandbox --disable-dev-shm-usage --use-angle=swiftshader-webgl --crashpad-handler-pid=6381 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/karma-15719036 --change-stack-guard-on-fork=enable --shared-files=network_parent_dirs_pipe:100,v8_context_snapshot_data:101 --field-trial-handle=3,i,16618068847485743766,3400912940417460984,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,11171104491658937797,16843082412332472818,4 --trace-process-track-uuid=3190708989122997041 (dns block)
  • www.google.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=network --no-sandbox --disable-dev-shm-usage --use-angle=swiftshader-webgl --crashpad-handler-pid=6133 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/karma-45691960 --change-stack-guard-on-fork=enable --shared-files=network_parent_dirs_pipe:100,v8_context_snapshot_data:101 --field-trial-handle=3,i,1199227595089660070,15175812677166680058,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,10666523406645318218,11337487493276019718,4 --trace-process-track-uuid=3190708989122997041 (dns block)
    • Triggering command: /home/REDACTED/.cache/puppeteer/chrome/linux-148.0.7778.167/chrome-linux64/chrome /home/REDACTED/.cache/puppeteer/chrome/linux-148.0.7778.167/chrome-linux64/chrome --user-data-dir=/tmp/karma-45691960 --enable-automation --no-default-browser-check --no-first-run --disable-default-apps --disable-popup-blocking --disable-translate --disable-REDACTED-timer-throttling --disable-renderer-REDACTEDing --disable-device-discovery-notifications --no-sandbox --disable-gpu --disable-web-security --disable-dev-shm-usage http://localhost:9876/?id=45691960 --headless --disable-gpu --disable-dev-shm-usage --remote-debugging-port=9222 (dns block)
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=network --no-sandbox --disable-dev-shm-usage --use-angle=swiftshader-webgl --crashpad-handler-pid=6381 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/karma-15719036 --change-stack-guard-on-fork=enable --shared-files=network_parent_dirs_pipe:100,v8_context_snapshot_data:101 --field-trial-handle=3,i,16618068847485743766,3400912940417460984,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,11171104491658937797,16843082412332472818,4 --trace-process-track-uuid=3190708989122997041 (dns block)
  • www.googleapis.com
    • Triggering command: /usr/local/bin/node node install.js (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copy link
Copy Markdown
Contributor

Copilot AI commented May 18, 2026

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • accounts.google.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=network --no-sandbox --disable-dev-shm-usage --use-angle=swiftshader-webgl --crashpad-handler-pid=4853 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/karma-78305968 --change-stack-guard-on-fork=enable --shared-files=network_parent_dirs_pipe:100,v8_context_snapshot_data:101 --field-trial-handle=3,i,15288153321995751153,14195177576613823511,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,4667226726997537131,16531586613948724733,4 --trace-process-track-uuid=3190708989122997041 git (dns block)
    • Triggering command: /home/REDACTED/.cache/puppeteer/chrome/linux-148.0.7778.167/chrome-linux64/chrome /home/REDACTED/.cache/puppeteer/chrome/linux-148.0.7778.167/chrome-linux64/chrome --user-data-dir=/tmp/karma-78305968 --enable-automation --no-default-browser-check --no-first-run --disable-default-apps --disable-popup-blocking --disable-translate --disable-REDACTED-timer-throttling --disable-renderer-REDACTEDing --disable-device-discovery-notifications --no-sandbox --disable-gpu --disable-web-security --disable-dev-shm-usage http://localhost:9876/?id=78305968 --headless --disable-gpu --disable-dev-shm-usage --remote-debugging-port=9222 (dns block)
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=network --no-sandbox --disable-dev-shm-usage --use-angle=swiftshader-webgl --crashpad-handler-pid=5148 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/karma-42374183 --change-stack-guard-on-fork=enable --shared-files=network_parent_dirs_pipe:100,v8_context_snapshot_data:101 --field-trial-handle=3,i,8231824791362629505,7373620336497768115,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,15530739637824412198,7045516300377496495,4 --trace-process-track-uuid=3190708989122997041 clearInterval(i (dns block)
  • android.clients.google.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=network --no-sandbox --disable-dev-shm-usage --use-angle=swiftshader-webgl --crashpad-handler-pid=5433 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/karma-24428144 --change-stack-guard-on-fork=enable --shared-files=network_parent_dirs_pipe:100,v8_context_snapshot_data:101 --field-trial-handle=3,i,525031949261534026,11601532670734126931,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,10712885593962391962,66147913180941893,4 --trace-process-track-uuid=3190708989122997041 /opt/copilot-runlib/test/src/common/array/with.test.ts (dns block)
    • Triggering command: /home/REDACTED/.cache/puppeteer/chrome/linux-148.0.7778.167/chrome-linux64/chrome /home/REDACTED/.cache/puppeteer/chrome/linux-148.0.7778.167/chrome-linux64/chrome --user-data-dir=/tmp/karma-24428144 --enable-automation --no-default-browser-check --no-first-run --disable-default-apps --disable-popup-blocking --disable-translate --disable-REDACTED-timer-throttling --disable-renderer-REDACTEDing --disable-device-discovery-notifications --disable-web-security --disable-site-isolation-trials --no-sandbox http://localhost:9876/?id=24428144 --headless --disable-gpu --disable-dev-shm-usage --remote-debugging-port=9222 git (dns block)
  • clients2.google.com
    • Triggering command: /home/REDACTED/.cache/puppeteer/chrome/linux-148.0.7778.167/chrome-linux64/chrome /home/REDACTED/.cache/puppeteer/chrome/linux-148.0.7778.167/chrome-linux64/chrome --user-data-dir=/tmp/karma-78305968 --enable-automation --no-default-browser-check --no-first-run --disable-default-apps --disable-popup-blocking --disable-translate --disable-REDACTED-timer-throttling --disable-renderer-REDACTEDing --disable-device-discovery-notifications --no-sandbox --disable-gpu --disable-web-security --disable-dev-shm-usage http://localhost:9876/?id=78305968 --headless --disable-gpu --disable-dev-shm-usage --remote-debugging-port=9222 (dns block)
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=network --no-sandbox --disable-dev-shm-usage --use-angle=swiftshader-webgl --crashpad-handler-pid=5148 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/karma-42374183 --change-stack-guard-on-fork=enable --shared-files=network_parent_dirs_pipe:100,v8_context_snapshot_data:101 --field-trial-handle=3,i,8231824791362629505,7373620336497768115,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,15530739637824412198,7045516300377496495,4 --trace-process-track-uuid=3190708989122997041 clearInterval(i (dns block)
    • Triggering command: /home/REDACTED/.cache/puppeteer/chrome/linux-148.0.7778.167/chrome-linux64/chrome /home/REDACTED/.cache/puppeteer/chrome/linux-148.0.7778.167/chrome-linux64/chrome --user-data-dir=/tmp/karma-42374183 --enable-automation --no-default-browser-check --no-first-run --disable-default-apps --disable-popup-blocking --disable-translate --disable-REDACTED-timer-throttling --disable-renderer-REDACTEDing --disable-device-discovery-notifications --no-sandbox --disable-gpu --disable-web-security --disable-dev-shm-usage http://localhost:9876/?id=42374183 --headless --disable-gpu --disable-dev-shm-usage --remote-debugging-port=9222 (dns block)
  • redirector.gvt1.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=network --no-sandbox --disable-dev-shm-usage --use-angle=swiftshader-webgl --crashpad-handler-pid=4853 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/karma-78305968 --change-stack-guard-on-fork=enable --shared-files=network_parent_dirs_pipe:100,v8_context_snapshot_data:101 --field-trial-handle=3,i,15288153321995751153,14195177576613823511,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,4667226726997537131,16531586613948724733,4 --trace-process-track-uuid=3190708989122997041 git (dns block)
    • Triggering command: /home/REDACTED/.cache/puppeteer/chrome/linux-148.0.7778.167/chrome-linux64/chrome /home/REDACTED/.cache/puppeteer/chrome/linux-148.0.7778.167/chrome-linux64/chrome --user-data-dir=/tmp/karma-78305968 --enable-automation --no-default-browser-check --no-first-run --disable-default-apps --disable-popup-blocking --disable-translate --disable-REDACTED-timer-throttling --disable-renderer-REDACTEDing --disable-device-discovery-notifications --no-sandbox --disable-gpu --disable-web-security --disable-dev-shm-usage http://localhost:9876/?id=78305968 --headless --disable-gpu --disable-dev-shm-usage --remote-debugging-port=9222 (dns block)
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=network --no-sandbox --disable-dev-shm-usage --use-angle=swiftshader-webgl --crashpad-handler-pid=5148 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/karma-42374183 --change-stack-guard-on-fork=enable --shared-files=network_parent_dirs_pipe:100,v8_context_snapshot_data:101 --field-trial-handle=3,i,8231824791362629505,7373620336497768115,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,15530739637824412198,7045516300377496495,4 --trace-process-track-uuid=3190708989122997041 clearInterval(i (dns block)
  • www.google.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=network --no-sandbox --disable-dev-shm-usage --use-angle=swiftshader-webgl --crashpad-handler-pid=4853 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/karma-78305968 --change-stack-guard-on-fork=enable --shared-files=network_parent_dirs_pipe:100,v8_context_snapshot_data:101 --field-trial-handle=3,i,15288153321995751153,14195177576613823511,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,4667226726997537131,16531586613948724733,4 --trace-process-track-uuid=3190708989122997041 git (dns block)
    • Triggering command: /home/REDACTED/.cache/puppeteer/chrome/linux-148.0.7778.167/chrome-linux64/chrome /home/REDACTED/.cache/puppeteer/chrome/linux-148.0.7778.167/chrome-linux64/chrome --user-data-dir=/tmp/karma-78305968 --enable-automation --no-default-browser-check --no-first-run --disable-default-apps --disable-popup-blocking --disable-translate --disable-REDACTED-timer-throttling --disable-renderer-REDACTEDing --disable-device-discovery-notifications --no-sandbox --disable-gpu --disable-web-security --disable-dev-shm-usage http://localhost:9876/?id=78305968 --headless --disable-gpu --disable-dev-shm-usage --remote-debugging-port=9222 (dns block)
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=network --no-sandbox --disable-dev-shm-usage --use-angle=swiftshader-webgl --crashpad-handler-pid=5148 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/karma-42374183 --change-stack-guard-on-fork=enable --shared-files=network_parent_dirs_pipe:100,v8_context_snapshot_data:101 --field-trial-handle=3,i,8231824791362629505,7373620336497768115,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,15530739637824412198,7045516300377496495,4 --trace-process-track-uuid=3190708989122997041 clearInterval(i (dns block)
  • www.googleapis.com
    • Triggering command: /usr/local/bin/node node install.js (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

nevware21-bot
nevware21-bot previously approved these changes May 18, 2026
Copy link
Copy Markdown
Contributor

@nevware21-bot nevware21-bot left a comment

Choose a reason for hiding this comment

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

Approved by nevware21-bot

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 20 out of 20 changed files in this pull request and generated 9 comments.

Comments suppressed due to low confidence (7)

lib/test/src/common/object/defaults.test.ts:215

  • This test does not create an own enumerable __proto__ property because "__proto__" in an object literal is interpreted as prototype syntax. As written, it would pass even if objDefaults copied an own __proto__ key from untrusted input, so the key prototype-pollution path is not covered.
        it("should ignore unsafe source keys", () => {
            const target: any = {};
            objDefaults(target, { "__proto__": { polluted: true }, constructor: "bad", prototype: "bad", safe: 1 } as any);
            assert.equal(target.safe, 1);
            assert.isFalse(Object.prototype.hasOwnProperty.call(target, "__proto__"));

lib/src/object/defaults.ts:66

  • The docs describe copying own enumerable source properties but do not mention that unsafe keys are filtered and unsafe built-in prototype targets are left unchanged. Since this helper intentionally skips keys such as constructor and prototype, the generated API docs should make that behavior explicit.
 * This is similar to Lodash `_.defaults()`, but it only considers each source object's own
 * enumerable properties and does not copy inherited source properties.
 * @since 0.14.0
 * @group Object

lib/test/src/common/object/for_each_key.test.ts:223

  • The __proto__ entry in this fixture is not an own enumerable property, so this case only verifies constructor/prototype filtering. It does not cover the own-__proto__ path that objForEachKeySafe is supposed to block.
            const obj = Object.assign({}, {
                "__proto__": "attack",
                "constructor": "attack",
                "prototype": "attack",
                "name": "Dave"

lib/test/src/common/object/for_each_key.test.ts:317

  • This __proto__ entry is prototype-literal syntax rather than an own enumerable key, so the later assertion does not prove that objForEachKeySafe filters own __proto__ properties on complex inputs.
                func: function() {
                    return "test";
                },
                "__proto__": "attack"
            });

lib/src/object/forEachOwnKey.ts:61

  • This new exported helper has no @example block after replacing the old documented helper, so the generated API docs lose the usage example for safe iteration.
 * @typeParam T - The source type.
 * @param theObject - The object-like value to iterate.
 * @param callbackfn - Invoked for each safe own enumerable key.
 * @param thisArg - [Optional] The `this` context for the callback.
 */

lib/src/object/pick.ts:79

  • The exposed overloads still lack a ReadonlyArray<PropertyKey> fallback, so callers with a dynamic list of string/number/symbol keys cannot use the implementation's widened PropertyKey support without casts. The implementation signature itself is not part of overload resolution for consumers.
export function objOmit<T, const K extends keyof T>(source: T, keys: ReadonlyArray<K>): Omit<T, K>;
/*#__NO_SIDE_EFFECTS__*/
export function objOmit<T>(source: T, keys: ReadonlyArray<string>): Partial<T>;
export function objOmit<T>(source: T, keys: ReadonlyArray<PropertyKey>): Partial<T> {

lib/src/object/defaults.ts:90

  • The unsafe-target guard is incomplete for this hardening path: built-in prototypes such as ArrayBuffer.prototype and typed-array/DataView prototypes are not recognized by isUnsafeTarget, so objDefaults can still mutate them when used as target.
    if (target && !isUnsafeTarget(target)) {
        arrForEach(sources, (source) => {
            if (source) {
                forEachOwnKeySafe(source, (key, value) => {
                    if (!objHasOwn(target, key) || isStrictUndefined((target as any)[key])) {

Comment thread lib/src/object/diff.ts Outdated
Comment thread lib/src/object/defaults.ts
Comment thread lib/src/object/defaults.ts
Comment thread lib/test/src/common/object/defaults.test.ts Outdated
Comment thread lib/test/src/common/object/for_each_key.test.ts Outdated
Comment thread lib/src/object/for_each_key.ts
Comment thread lib/src/object/forEachOwnKey.ts
Comment thread lib/src/object/pick.ts Outdated
Comment thread lib/src/object/defaults.ts
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

…pollution

- add new object helpers: objPick, objOmit, objPickBy, objOmitBy, objMapValues, objMergeIf, objDefaults, and objDiff
- export the new helpers from the public index
- add common test coverage for pick/omit, mapValues, defaults/mergeIf, and diff behavior
- fix objPick/objOmit overload compatibility by widening implementation signatures to PropertyKey
- preserve numeric key omission at runtime in objOmit when typed overloads pass numeric keys
- document objDefaults as similar to Lodash defaults while clarifying it only uses own enumerable source properties
- harden objMergeIf and objDefaults against prototype pollution by skipping unsafe keys and blocking unsafe built-in prototype targets
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated 8 comments.

Comments suppressed due to low confidence (2)

lib/src/object/forEachOwnKey.ts:143

  • forEachOwnKeySafe now delegates to forEachOwnKey, which reads theObject[key] before this filter runs. That means an unsafe own enumerable key such as __proto__ with a getter is still invoked even though the key is skipped, whereas this safe helper should avoid touching unsafe-key values before filtering to prevent side effects from untrusted input.
    forEachOwnKey(theObject, (key, value) => {
        if (!isUnsafePropKey(key)) {
            return callbackfn[CALL](isStrictNullOrUndefined(thisArg) ? theObject : thisArg, key, value);

lib/src/object/for_each_key.ts:114

  • This safe wrapper receives value from objForEachKey after objForEachKey has already evaluated theObject[key], so an unsafe own key like __proto__ with a getter is invoked before isUnsafePropKey can skip it. Filter the key before reading the property value so the safe variant does not execute accessors on keys it promises to ignore.
    objForEachKey(theObject, (key: string, value: T[keyof T]) => {
        if (!isUnsafePropKey(key)) {
            return callbackfn[CALL](isStrictNullOrUndefined(thisArg) ? theObject : thisArg, key, value);

Comment thread lib/src/object/pick.ts Outdated
Comment thread lib/test/bundle-size-check.js Outdated
Comment thread lib/src/object/for_each_key.ts
Comment thread lib/src/object/forEachOwnKey.ts Outdated
Comment thread lib/test/src/common/object/property_is_enumerable.test.ts Outdated
Comment thread lib/test/src/common/object/defaults.test.ts Outdated
Comment thread lib/test/src/common/object/isUnsafeTarget.test.ts Outdated
Comment thread lib/src/object/defaults.ts
nevware21-bot
nevware21-bot previously approved these changes May 19, 2026
Copy link
Copy Markdown
Contributor

@nevware21-bot nevware21-bot left a comment

Choose a reason for hiding this comment

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

Approved by nevware21-bot

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated 5 comments.

Comments suppressed due to low confidence (1)

lib/test/src/common/object/for_each_key.test.ts:355

  • This test also uses Object.assign with a "proto" key. Because __proto__ can be treated specially during assignment, the resulting object may not actually contain an own enumerable "proto" property, so it doesn’t reliably validate that objForEachKeySafe filters it out. Prefer defining "proto" with Object.defineProperty so the test deterministically exercises the unsafe-key filtering logic.
        it("should work with complex objects and filter unsafe keys", () => {
            const complexObj = Object.assign({}, {
                num: 42,
                str: "hello",
                bool: true,
                arr: [1, 2, 3],
                nested: { a: 1, b: 2 },
                func: function() {
                    return "test";
                },
                "__proto__": "attack"
            });

Comment thread lib/src/object/property_is_enumerable.ts Outdated
Comment thread lib/src/object/property_is_enumerable.ts
Comment thread lib/test/src/common/object/property_is_enumerable.test.ts
Comment thread lib/src/object/for_each_key.ts Outdated
Comment thread lib/test/src/common/object/for_each_key.test.ts Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

lib/src/object/pick.ts:87

  • objOmit is typed to accept ArrayLike<...> keys, but the function currently no-ops unless isArray(keys) is true. That means non-Array array-likes (while still assignable to ArrayLike<PropertyKey>) won’t omit anything and will behave inconsistently with the declared overloads. Consider accepting array-like keys (and rejecting string inputs explicitly) or narrowing the types to real arrays to match the runtime check.
export function objOmit<T>(source: T, keys: ArrayLike<PropertyKey>): Partial<T> {
    const result: Partial<T> = objCreate(null);
    if (source && isArray(keys)) {
        forEachOwnKey(source, (key, value) => {
            let hasKey = arrIndexOf(keys, key) !== -1;

Comment thread lib/src/object/pick.ts
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

lib/test/src/common/object/for_each_key.test.ts:356

  • Object.assign({}, { "__proto__": ... }) is used here to inject an unsafe key, but __proto__ is special-cased and may not become an own enumerable property (and can alter the target’s prototype), so this is not a reliable test for filtering behavior. Define "proto" via Object.defineProperty before running the assertions.
        it("should work with complex objects and filter unsafe keys", () => {
            const complexObj = Object.assign({}, {
                num: 42,
                str: "hello",
                bool: true,
                arr: [1, 2, 3],
                nested: { a: 1, b: 2 },
                func: function() {
                    return "test";
                },
                "__proto__": "attack"
            });
            

Comment thread lib/src/object/property_is_enumerable.ts
Comment on lines +87 to +93
* ```ts
* // Safe iteration - unsafe keys are skipped
* const untrustedObj = Object.assign({}, { "__proto__": "attack", "name": "Alice", "constructor": {} });
* objForEachKeySafe(untrustedObj, (key, value) => {
* console.log(key, value); // Only prints: name Alice
* });
* ```
Comment thread lib/test/src/common/object/for_each_key.test.ts Outdated
@nev21 nev21 force-pushed the nev21/objFuncs branch from fdcb156 to 8a307a9 Compare May 19, 2026 05:23
…port in defaults

- Add `forEachOwnKey`: iterates own enumerable string + symbol (PropertyKey) keys without unsafe-key filtering
- Add `objForEachKeySafe`: safe wrapper around `objForEachKey` that filters unsafe keys (__proto__, constructor, prototype) before invoking the callback
- Refactor `forEachOwnKeySafe` (previously in forEachOwnKeySafe.ts) to use `forEachOwnKey` as its base, renamed module to forEachOwnKey.ts
- Fix `objDefaults` and `objMergeIf` to process enumerable symbol-keyed properties (previously silently skipped)
- Update callback types in `defaults.ts` to accept PropertyKey to properly support symbol keys
- Update `objForEachKey` TSDoc to document string-key-only behaviour and cross-reference all four iteration helpers
- Add tests: symbol enumerable/non-enumerable defaults, objForEachKeySafe unsafe-key filtering, forEachOwnKey symbol iteration
- Update README Available Utilities table: add forEachOwnKey, forEachOwnKeySafe, objForEachKeySafe entries
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated 2 comments.

Comment thread lib/src/object/property_is_enumerable.ts
Comment thread lib/src/object/for_each_key.ts Outdated
nevware21-bot
nevware21-bot previously approved these changes May 19, 2026
Copy link
Copy Markdown
Contributor

@nevware21-bot nevware21-bot left a comment

Choose a reason for hiding this comment

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

Approved by nevware21-bot

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@nev21 nev21 enabled auto-merge (squash) May 19, 2026 05:36
Copy link
Copy Markdown
Contributor

@nevware21-bot nevware21-bot left a comment

Choose a reason for hiding this comment

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

Approved by nevware21-bot

@nev21 nev21 merged commit 5e887f4 into main May 19, 2026
10 checks passed
@nev21 nev21 deleted the nev21/objFuncs branch May 19, 2026 05:45
nev21 added a commit that referenced this pull request May 19, 2026
nev21 added a commit that referenced this pull request May 19, 2026
## Release v0.14.0

This PR increases the version to `0.14.0` and updates the changelog with
all significant changes since v0.13.0.

### Summary of Changes

#### Features
- New array helpers and array-like detection (#525)
- `strReplace` and `strReplaceAll` string helpers (#527)
- `strCapitalizeWords` helper (#528)
- `strTruncate`, `strCount`, `strAt`, `strMatchAll` helpers (#529, #530)
- `arrFlatMap` with ES5 polyfill (#533)
- Typing utilities and expanded TSDoc examples (#535)
- `isAsyncIterable` and `isIntegerInRange` helpers (#536)
- `strStartsWithAny`, `strEndsWithAny`, `strWrap`, `strUnwrap`,
`strNormalizeNewlines` (#543)
- New object utility helpers and prototype pollution hardening (#564,
#565)

#### Bug Fixes
- Fix ES2015 built-in type errors in consumers by adding lib reference
directive to published declarations (#558)
- Fix `thisArg` binding in `polyArrFindIndex` / `polyArrFindLastIndex`
polyfills (#562)
- Fix falsy `thisArg` (0, `''`, `false`) being overridden in
`arrForEach`, `iterForOf`, `objForEachKey` (#566)

#### Repository Improvements
- Drop Node.js 16 from CI matrix and add Node.js 24 (#549)
- Upgrade Grunt devDependency to v1.6.2 (#552)
- Add funding metadata to published package manifests (#554)

### Files Updated
- `CHANGELOG.md` — Added v0.14.0 entry
- `package.json` — Version bumped to 0.14.0
- `lib/package.json` — Version bumped to 0.14.0
- `README.md` — Updated recommended version specification
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.

4 participants