fix(core): avoid mutating caller's config object in axios(url, config) by koreahghg · Pull Request #11154 · axios/axios · GitHub
Skip to content

fix(core): avoid mutating caller's config object in axios(url, config) - #11154

Open
koreahghg wants to merge 7 commits into
axios:v1.xfrom
koreahghg:fix/axios-request-config-mutation
Open

fix(core): avoid mutating caller's config object in axios(url, config)#11154
koreahghg wants to merge 7 commits into
axios:v1.xfrom
koreahghg:fix/axios-request-config-mutation

Conversation

@koreahghg

@koreahghg koreahghg commented Aug 19, 2026

Copy link
Copy Markdown

Summary

  • Axios#_request() wrote url directly onto the config object passed by the caller when using the axios(url, config) shorthand (e.g. axios('example/url', config)), silently mutating an object the library does not own.
  • This is easy to hit by accident: const shared = {...}; axios('/first', shared); axios('/second', shared); leaves shared.url set to '/second' afterwards, even though the caller never asked for that field to be added.
  • Fixed by building a shallow copy (config = { ...config, url: configOrUrl }) instead of mutating in place, consistent with the project's existing "do not mutate config objects" convention used elsewhere in mergeConfig/utils.merge.

Test plan

  • Added a regression test in tests/unit/axios.test.js asserting the original config object is unchanged (no url key added) after axios.request(url, config).
  • npm run test:vitest:unit — full unit suite passes (one pre-existing, unrelated failure in tests/unit/adapters/fetch.test.js reproduces identically on v1.x without this change).
  • npx eslint lib/core/Axios.js tests/unit/axios.test.js — clean.
  • Added a PRE_RELEASE_CHANGELOG.md entry under Bug Fixes.

🏄


Summary by cubic

Prevents axios(url, config) from mutating the caller’s config and from invoking unsafe getters. Previously _request wrote url onto the provided object; now it builds a fresh null-prototype copy that copies only enumerable own keys (including symbols) and skips __proto__/constructor/prototype.

Description

  • Summary: Copy only enumerable own keys (incl. symbols), skip unsafe names before reading, and avoid non-enumerables; use a null-prototype target to prevent inherited Object.prototype setters from intercepting assignments.
  • Reasoning: Preserve caller ownership, mirror native object spread semantics, and align with mergeConfig behavior.
  • Additional context: Handles the no-config shorthand call and guards proxies that report symbol keys without descriptors.

Docs

  • Update /docs/ (request configuration and axios(url, config) shorthand) to state the input config is not mutated, only enumerable own keys are read, and __proto__/constructor/prototype are ignored.

Testing

  • Added regression tests: input config remains unchanged; unsafe key getters aren’t invoked; non-enumerable getters aren’t read; shorthand works without a config; proxy phantom symbol doesn’t throw; inherited Object.prototype setter isn’t invoked.
  • Full unit suite passes; one unrelated fetch adapter test still fails on v1.x.

Semantic version impact

Patch (no API changes).

Written for commit e130935. Summary will update on new commits.

Review in cubic

_request() wrote `url` directly onto the config object passed by the
caller when using the axios(url, config) shorthand, silently mutating
it. Build a shallow copy instead so a config object reused across
multiple calls is left untouched.

:surfer:

@cubic-dev-ai cubic-dev-ai Bot 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.

No issues found across 3 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Re-trigger cubic

@greptile-apps

greptile-apps Bot commented Aug 19, 2026

Copy link
Copy Markdown

Comment thread lib/core/Axios.js Outdated
The shorthand's shallow copy read every enumerable own property,
including __proto__/constructor/prototype, before mergeConfig's
filter runs. Route it through the same safe-materialization helper
already used at the dispatchRequest boundary so unsafe keys are
skipped before their values are ever read, and add a regression test
covering getter-backed unsafe keys.

:surfer:
@koreahghg

Copy link
Copy Markdown
Author

Thanks for the review — the P1 was real. Fixed in 323b623 by routing the shorthand's config copy through the same utils.toSafeFlatObject helper already used at the dispatchRequest boundary (lib/core/dispatchRequest.js:39), instead of a raw object spread.

For anyone following along, toSafeFlatObject checks each own property name against isUnsafeObjectKey (__proto__/constructor/prototype) before reading its value (Object.getOwnPropertyNames + a continue on unsafe keys, never thing[prop] for those), so it never invokes a getter for those keys. A raw {...config} spread reads every enumerable own property unconditionally, which is exactly the gap flagged here.

One nuance worth recording for reviewers of the original commit (417939e): a repro showed the plain-spread version didn't actually change the net exposure versus the pre-PR code, because mergeConfig's own key-collection step (ownEnumerableKeys({ ...config1, ...config2 }), lib/core/mergeConfig.js:152) already spreads both configs before its own filter check, so the same getters fire there regardless of this PR. But that's beside the point for this PR specifically — with the fix, the sanitized object is what flows into mergeConfig and dispatchRequest, so those getters are never invoked at all for the url-string shorthand path, which is strictly better and matches this repo's "every new materialization boundary filters unsafe keys" rule (AGENTS.md).

Added a regression test (tests/unit/axios.test.js) with getter-backed __proto__/constructor/prototype properties on the config object, asserting zero reads — confirmed it fails against 417939e and passes with the fix. Full unit suite still green (npm run test:vitest:unit, 1063/1064 passing; the one failure is a pre-existing, unrelated flake in tests/unit/adapters/fetch.test.js that reproduces identically on v1.x without any of this PR's changes).

🏄

Comment thread lib/core/Axios.js Outdated

@cubic-dev-ai cubic-dev-ai Bot 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.

All reported issues were addressed across 3 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread lib/core/Axios.js Outdated
Comment thread lib/core/Axios.js Outdated
toSafeFlatObject reads every own property name via
Object.getOwnPropertyNames, including non-enumerable ones, so a
config object with a throwing non-enumerable getter unrelated to
__proto__/constructor/prototype caused axios(url, config) to reject
before dispatch - behavior a native object spread would never have
triggered, since spread only touches enumerable own properties. It
also doesn't short-circuit on undefined/null, though the call site
already guarded that.

Replace it with a small local copy that mirrors mergeConfig's own
"enumerable own keys (incl. symbols), skip the three reserved names
before reading their value" semantics, without touching non-enumerable
properties. Added regression tests for the no-config shorthand call
and for a non-enumerable throwing getter.

:surfer:
@koreahghg

Copy link
Copy Markdown
Author

@cubic-dev-ai cubic-dev-ai Bot 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.

All reported issues were addressed across 3 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread lib/core/Axios.js Outdated
Comment thread lib/core/Axios.js
Guard the symbol filter against a config Proxy whose ownKeys trap
reports a symbol with no matching property descriptor (getOwnPropertyDescriptor
returning undefined), and build the copy on a null-prototype object so an
Object.prototype setter placed under an ordinary key name can't intercept
the copy's plain assignments the way a native spread's CreateDataProperty
semantics never would. Added regression tests for both.

:surfer:
@jasonsaayman jasonsaayman added commit::fix The PR is related to a bugfix v1x Version 1 labels Sep 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

commit::fix The PR is related to a bugfix v1x Version 1

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants