@groeponline/pi-wishcraft 0.23.2 → 0.23.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.23.3] - 2026-08-20
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- Nested settings writes reject `__proto__` / `constructor` / `prototype` keys in the assignment loop.
|
|
9
|
+
- Test workflow pins `GITHUB_TOKEN` to `contents: read`.
|
|
10
|
+
|
|
5
11
|
## [0.23.2] - 2026-08-20
|
|
6
12
|
|
|
7
13
|
### Fixed
|
package/package.json
CHANGED
|
@@ -55,7 +55,44 @@ export function readConfigPath(settings: Record<string, unknown>, path: string):
|
|
|
55
55
|
return null;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
/**
|
|
58
|
+
/** True for path segments that would mutate Object.prototype. */
|
|
59
|
+
export function isUnsafeConfigKey(key: string): boolean {
|
|
60
|
+
return key === "__proto__" || key === "constructor" || key === "prototype";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Set `parts` (after the root key) on `root`. Returns false and leaves
|
|
65
|
+
* `root` unchanged when a segment is `__proto__`, `constructor`, or `prototype`.
|
|
66
|
+
*/
|
|
67
|
+
export function assignNestedConfigValue(
|
|
68
|
+
root: Record<string, unknown>,
|
|
69
|
+
parts: string[],
|
|
70
|
+
value: ConfigValue,
|
|
71
|
+
): boolean {
|
|
72
|
+
for (const part of parts) {
|
|
73
|
+
if (part === "__proto__" || part === "constructor" || part === "prototype") {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
let node = root;
|
|
78
|
+
for (let i = 0; i < parts.length; i++) {
|
|
79
|
+
const key = parts[i]!;
|
|
80
|
+
// Guard in this loop so CodeQL sees the key check next to the assignment.
|
|
81
|
+
if (key === "__proto__" || key === "constructor" || key === "prototype") {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
if (i === parts.length - 1) {
|
|
85
|
+
if (value === null) delete node[key];
|
|
86
|
+
else node[key] = value;
|
|
87
|
+
} else {
|
|
88
|
+
if (!isRecord(node[key])) node[key] = {};
|
|
89
|
+
node = node[key] as Record<string, unknown>;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Nested write (new object per level) + persist to settings.json. */
|
|
59
96
|
export function writeConfigPath(
|
|
60
97
|
cwd: string,
|
|
61
98
|
path: string,
|
|
@@ -63,29 +100,20 @@ export function writeConfigPath(
|
|
|
63
100
|
): boolean {
|
|
64
101
|
const parts = path.split(".");
|
|
65
102
|
const rootKey = parts[0]!;
|
|
66
|
-
|
|
67
|
-
if (parts.some((p) => p === "__proto__" || p === "constructor" || p === "prototype")) {
|
|
103
|
+
if (!rootKey || parts.some(isUnsafeConfigKey)) {
|
|
68
104
|
return false;
|
|
69
105
|
}
|
|
70
106
|
return writeSettingKey(cwd, rootKey, (existing) => {
|
|
71
|
-
// Shorthand string
|
|
72
|
-
|
|
107
|
+
// Shorthand string under powerline (e.g. "chef") is a preset name: keep it.
|
|
108
|
+
const node: Record<string, unknown> = isRecord(existing)
|
|
73
109
|
? existing
|
|
74
110
|
: rootKey === "powerline" && typeof existing === "string"
|
|
75
111
|
? { preset: existing }
|
|
76
112
|
: {};
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const key = parts[i]!;
|
|
80
|
-
if (i === parts.length - 1) {
|
|
81
|
-
if (value === null) delete node[key];
|
|
82
|
-
else node[key] = value;
|
|
83
|
-
} else {
|
|
84
|
-
if (!isRecord(node[key])) node[key] = {};
|
|
85
|
-
node = node[key] as Record<string, unknown>;
|
|
86
|
-
}
|
|
113
|
+
if (!assignNestedConfigValue(node, parts.slice(1), value)) {
|
|
114
|
+
return existing;
|
|
87
115
|
}
|
|
88
|
-
return
|
|
116
|
+
return node;
|
|
89
117
|
});
|
|
90
118
|
}
|
|
91
119
|
|