Skip to content

Security: Prototype pollution via untrusted YAML user-config keys#1494

Merged
CodFrm merged 2 commits into
scriptscat:mainfrom
qdzsh:fix/security/prototype-pollution-via-untrusted-yaml-u
Jun 10, 2026
Merged

Security: Prototype pollution via untrusted YAML user-config keys#1494
CodFrm merged 2 commits into
scriptscat:mainfrom
qdzsh:fix/security/prototype-pollution-via-untrusted-yaml-u

Conversation

@qdzsh

@qdzsh qdzsh commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Security: Prototype pollution via untrusted YAML user-config keys

Problem

Severity: Medium | File: src/pkg/utils/yaml.ts:L12

parseUserConfig() copies YAML group names directly into a plain object (ret[groupKey] = groupValue). A malicious userscript can use special keys such as __proto__, constructor, or prototype to mutate object prototypes or interfere with later merges/lookups. Because this parser handles untrusted script metadata, this can become a cross-script integrity issue.

Solution

Create the result object with Object.create(null) and reject dangerous keys (__proto__, constructor, prototype) before assignment. Prefer a schema-validated structure instead of copying arbitrary keys into a plain object.

Changes

  • src/pkg/utils/yaml.ts (modified)

Testing

  • Existing tests pass
  • Manual review completed
  • No new warnings/errors introduced

parseUserConfig() copies YAML group names directly into a plain object (`ret[groupKey] = groupValue`). A malicious userscript can use special keys such as `__proto__`, `constructor`, or `prototype` to mutate object prototypes or interfere with later merges/lookups. Because this parser handles untrusted script metadata, this can become a cross-script integrity issue.

Affected files: yaml.ts

Signed-off-by: Quang Do <github@qdzsh.dev>
@cyfung1031

cyfung1031 commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

Then why not change it to Map?

Actually the ScriptCat coding was really poorly designed.
Using object without considering the special keys and key order... etc
Also the author did not set the rule for the key ... for example whether #xxxx is allowed.


But yeah, the proposed change is good to have.

Comment thread src/pkg/utils/yaml.ts Outdated
}
// 验证是否符合分组规范:group -> config -> properties
for (const [groupKey, groupValue] of Object.entries(obj)) {
if (["__proto__", "constructor", "prototype"].includes(groupKey)) {

@cyfung1031 cyfung1031 Jun 9, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
if (["__proto__", "constructor", "prototype"].includes(groupKey)) {
if (Reflect.has(Object.prototype, groupKey)) {

This should be better. This covers valueOf and toString, etc.
prototype is the inherited property for Function not Object, so it is okay. Since it is not a class function, adding the key prototype does not have any negative impact (pollution).

Comment thread src/pkg/utils/yaml.ts Outdated
// 验证是否符合分组规范:group -> config -> properties
for (const [groupKey, groupValue] of Object.entries(obj)) {
if (["__proto__", "constructor", "prototype"].includes(groupKey)) {
throw new Error(`UserConfig group "${groupKey}" is not a valid object.`);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
throw new Error(`UserConfig group "${groupKey}" is not a valid object.`);
throw new Error(`UserConfig key "${groupKey}" is not valid.`);

The Error message here was wrong.

- Reject any key inherited from Object.prototype (covers valueOf, toString,
  etc.) instead of a hardcoded __proto__/constructor/prototype list.
- Correct the error message to reference the invalid key, not 'object'.
@qdzsh

qdzsh commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

Good points, thanks for the careful review.

  • Switched to Reflect.has(Object.prototype, groupKey) as you suggested — it covers valueOf, toString, etc. too, which the hardcoded list missed. And you're right that prototype is harmless here since this isn't a function.
  • Fixed the error message — it was wrongly reusing the "is not a valid object" text.

On Map: I considered it, but UserConfig is consumed as a plain object in a few places downstream, so swapping the type would turn a small hardening fix into a wider refactor. I kept it as Object.create(null) to stay minimal and avoid touching unrelated code, but happy to follow up with a Map-based version in a separate PR if you'd prefer that direction.

@cyfung1031 cyfung1031 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@CodFrm please final check and merge.

@CodFrm

CodFrm commented Jun 10, 2026

Copy link
Copy Markdown
Member

Thanks!

@CodFrm CodFrm merged commit 04fe882 into scriptscat:main Jun 10, 2026
4 checks passed
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.

3 participants