Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 16 additions & 7 deletions apps/oxlint/src-js/plugins/load.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import type { AfterHook, BeforeHook, Visitor, VisitorWithHooks } from "./types.t
import type { SetNullable } from "../utils/types.ts";

const ObjectKeys = Object.keys,
{ isArray } = Array;
{ isArray } = Array,
{ stringify: JSONStringify, parse: JSONParse } = JSON;

/**
* Linter plugin, comprising multiple rules
Expand Down Expand Up @@ -164,17 +165,25 @@ export function registerPlugin(plugin: Plugin, packageName: string | null): Plug

const inputDefaultOptions = ruleMeta.defaultOptions;
if (inputDefaultOptions != null) {
// TODO: Validate is JSON-serializable, and validate against provided options schema
// TODO: Validate against provided options schema
if (!isArray(inputDefaultOptions)) {
throw new TypeError("`rule.meta.defaultOptions` must be an array if provided");
}

if (inputDefaultOptions.length !== 0) {
// TODO: This isn't quite safe, as `defaultOptions` isn't from JSON, and `deepFreezeJsonArray`
// assumes it is. We should perform options merging on Rust side instead, and also validate
// `defaultOptions` against options schema.
deepFreezeJsonArray(inputDefaultOptions);
defaultOptions = inputDefaultOptions;
// Serialize to JSON and deserialize again.
// This is the simplest way to make sure that `defaultOptions` does not contain any `undefined` values,
// or circular references. It may also be the fastest, as `JSON.parse` and `JSON.serialize` are native code.
// If we move to doing options merging on Rust side, we'll need to convert to JSON anyway.
try {
defaultOptions = JSONParse(JSONStringify(inputDefaultOptions)) as Options;
} catch (err) {
throw new Error(
`\`rule.meta.defaultOptions\` must be JSON-serializable: ${getErrorMessage(err)}`,
);
}

deepFreezeJsonArray(defaultOptions as Writable<typeof defaultOptions>);
}
}

Expand Down
6 changes: 1 addition & 5 deletions apps/oxlint/src-js/plugins/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,11 +174,7 @@ function mergeValues(configValue: JsonValue, defaultValue: JsonValue): JsonValue
// Both are objects (not arrays)
const merged = { ...defaultValue, ...configValue };

// Symbol properties are not possible in JSON, so no need to handle them here.
//
// A malicious plugin could potentially get up to mischief here (prototype pollution?) if `defaultValue` is a `Proxy`.
// But plugins are executable code, so they have far easier ways to do that. No point in defending against it here.
//
// Symbol properties and circular references are not possible in JSON, so no need to handle them here.
// `configValue` is from JSON, so we can use a simple `for..in` loop over `configValue`.
for (const key in configValue) {
// `hasOwn` not `in`, in case `key` is `"__proto__"`
Expand Down
Loading