diff --git a/.changeset/shiny-carpets-lick.md b/.changeset/shiny-carpets-lick.md new file mode 100644 index 000000000..44ba36f71 --- /dev/null +++ b/.changeset/shiny-carpets-lick.md @@ -0,0 +1,5 @@ +--- +"@rnx-kit/align-deps": patch +--- + +Sanitize input capabilities diff --git a/docsite/generate.js b/docsite/generate.js index 730b56996..a1eb45fa1 100644 --- a/docsite/generate.js +++ b/docsite/generate.js @@ -3,8 +3,8 @@ const fs = require("node:fs"); const path = require("node:path"); const badges = [ - "https://github.com/microsoft/rnx-kit/actions/workflows", - "https://img.shields.io", + "https://github\\.com/microsoft/rnx-kit/actions/workflows", + "https://img\\.shields\\.io", ].map((s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")); const badgesRE = new RegExp( diff --git a/packages/align-deps/src/capabilities.ts b/packages/align-deps/src/capabilities.ts index 229d7ab2c..346549068 100644 --- a/packages/align-deps/src/capabilities.ts +++ b/packages/align-deps/src/capabilities.ts @@ -9,7 +9,7 @@ type ResolvedDependencies = { unresolvedCapabilities: Record; }; -const ProvidesMeta = Symbol("provides"); +const PROVIDES_SYMKEY = "provides"; /** * Returns the list of capabilities used in the specified package manifest. @@ -49,7 +49,7 @@ export function capabilitiesFor( export function capabilityProvidedBy( pkg: MetaPackage | Package ): string | undefined { - return pkg[ProvidesMeta]; + return pkg[Symbol.for(PROVIDES_SYMKEY)]; } export function isMetaPackage(pkg: MetaPackage | Package): pkg is MetaPackage { @@ -82,7 +82,7 @@ function resolveCapability( return; } - pkg[ProvidesMeta] = capability; + pkg[Symbol.for(PROVIDES_SYMKEY)] = capability; pkg.capabilities?.forEach((capability) => resolveCapability( diff --git a/packages/align-deps/src/config.ts b/packages/align-deps/src/config.ts index cc0a5f5a3..353778203 100644 --- a/packages/align-deps/src/config.ts +++ b/packages/align-deps/src/config.ts @@ -1,4 +1,4 @@ -import type { KitConfig } from "@rnx-kit/config"; +import type { Capability, KitConfig } from "@rnx-kit/config"; import { getKitCapabilities, getKitConfig } from "@rnx-kit/config"; import { error, warn } from "@rnx-kit/console"; import type { PackageManifest } from "@rnx-kit/tools-node/package"; @@ -14,6 +14,8 @@ import type { type ConfigResult = AlignDepsConfig | LegacyCheckConfig | ErrorCode; +const ILLEGAL_CAPABILITIES = ["__proto__", "constructor", "prototype"]; + export const defaultConfig: AlignDepsConfig["alignDeps"] = { presets: ["microsoft/react-native"], requirements: [], @@ -63,6 +65,12 @@ export function isPackageManifest( ); } +export function sanitizeCapabilities( + capabilities?: Capability[] +): Capability[] { + return capabilities?.filter((c) => !ILLEGAL_CAPABILITIES.includes(c)) ?? []; +} + /** * Loads configuration from the specified package manifest. * @param manifestPath The path to the package manifest to load configuration from @@ -121,6 +129,7 @@ export function loadConfig( alignDeps: { ...defaultConfig, ...alignDeps, + capabilities: sanitizeCapabilities(alignDeps.capabilities), }, ...config, manifest, @@ -139,7 +148,7 @@ export function loadConfig( kitType, reactNativeVersion, ...(config.reactNativeDevVersion ? { reactNativeDevVersion } : undefined), - capabilities, + capabilities: sanitizeCapabilities(capabilities), customProfiles, manifest, }; diff --git a/packages/align-deps/test/config.test.ts b/packages/align-deps/test/config.test.ts index f9c09f523..5be985523 100644 --- a/packages/align-deps/test/config.test.ts +++ b/packages/align-deps/test/config.test.ts @@ -1,8 +1,10 @@ +import type { Capability } from "@rnx-kit/config"; import type { PackageManifest } from "@rnx-kit/tools-node/package"; import { containsValidPresets, findEmptyRequirements, isPackageManifest, + sanitizeCapabilities, } from "../src/config"; jest.mock("@rnx-kit/config"); @@ -91,7 +93,7 @@ describe("findEmptyRequirements()", () => { }); describe("isPackageManifest()", () => { - test("isPackageManifest() returns true when the object is a PackageManifest", () => { + test("returns true when the object is a PackageManifest", () => { const manifest: PackageManifest = { name: "package name", version: "1.0.0", @@ -99,7 +101,7 @@ describe("isPackageManifest()", () => { expect(isPackageManifest(manifest)).toBe(true); }); - test("isPackageManifest() returns false when the object is not a PackageManifest", () => { + test("returns false when the object is not a PackageManifest", () => { expect(isPackageManifest(undefined)).toBe(false); expect(isPackageManifest({})).toBe(false); expect(isPackageManifest("hello")).toBe(false); @@ -107,3 +109,21 @@ describe("isPackageManifest()", () => { expect(isPackageManifest({ version: "version but no name" })).toBe(false); }); }); + +describe("sanitizeCapabilities()", () => { + test("removes illegal names", () => { + const capabilities = [ + "__proto__", + "constructor", + "prototype", + "core", + ] as Capability[]; + + expect(sanitizeCapabilities(capabilities)).toEqual(["core"]); + }); + + test("handles empty array", () => { + expect(sanitizeCapabilities(undefined)).toEqual([]); + expect(sanitizeCapabilities([])).toEqual([]); + }); +}); diff --git a/scripts/src/commands/format.js b/scripts/src/commands/format.js index 236956b51..6f91e8ba3 100644 --- a/scripts/src/commands/format.js +++ b/scripts/src/commands/format.js @@ -9,7 +9,7 @@ export async function format() { "--write", "--log-level", "error", - "**/*.{js,json,jsx,md,ts,tsx,yml}", + "**/*.{js,json,jsx,md,mjs,ts,tsx,yml}", "!{CODE_OF_CONDUCT,SECURITY}.md", "!**/{__fixtures__,lib}/**", "!**/CHANGELOG.*"