rulesync 43.6.1 → 43.6.2
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/dist/cli/index.cjs +7 -10
- package/dist/cli/index.js +7 -10
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-BzVkMPd5.cjs → import-8kSEsosv.cjs} +561 -311
- package/dist/{import-Safgeb1C.js → import-iLVVcze8.js} +557 -307
- package/dist/{import-Safgeb1C.js.map → import-iLVVcze8.js.map} +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -2291,6 +2291,190 @@ async function removeTempDirectory(tempDir) {
|
|
|
2291
2291
|
});
|
|
2292
2292
|
} catch {}
|
|
2293
2293
|
}
|
|
2294
|
+
// src/utils/prototype-pollution.ts
|
|
2295
|
+
/**
|
|
2296
|
+
* Keys that, if walked into when constructing or merging objects from
|
|
2297
|
+
* untrusted input, can mutate `Object.prototype` (or otherwise the prototype
|
|
2298
|
+
* chain) and propagate state to every other object in the runtime. Any code
|
|
2299
|
+
* that copies arbitrary user-supplied keys into a fresh object — frontmatter
|
|
2300
|
+
* parsing, MCP config conversion, settings round-trip — should skip these.
|
|
2301
|
+
*/
|
|
2302
|
+
const PROTOTYPE_POLLUTION_KEYS = /* @__PURE__ */ new Set([
|
|
2303
|
+
"__proto__",
|
|
2304
|
+
"constructor",
|
|
2305
|
+
"prototype"
|
|
2306
|
+
]);
|
|
2307
|
+
function isPrototypePollutionKey(key) {
|
|
2308
|
+
return PROTOTYPE_POLLUTION_KEYS.has(key);
|
|
2309
|
+
}
|
|
2310
|
+
/**
|
|
2311
|
+
* Returns a shallow copy of a record's own entries with every
|
|
2312
|
+
* prototype-pollution key (`__proto__`, `constructor`, `prototype`) dropped.
|
|
2313
|
+
*
|
|
2314
|
+
* Use when copying a nested, user-supplied string map — an MCP server's `env`
|
|
2315
|
+
* or `headers` table — into freshly generated config. Carrying such a map by
|
|
2316
|
+
* reference, or re-assigning its keys via bracket notation, would let a literal
|
|
2317
|
+
* `__proto__` key ride along (and re-assigning it would mutate the target's
|
|
2318
|
+
* prototype). Walking the entries through this helper severs that path while
|
|
2319
|
+
* preserving every legitimate key.
|
|
2320
|
+
*/
|
|
2321
|
+
function omitPrototypePollutionKeys(record) {
|
|
2322
|
+
const sanitized = {};
|
|
2323
|
+
for (const [key, value] of Object.entries(record)) {
|
|
2324
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
2325
|
+
sanitized[key] = value;
|
|
2326
|
+
}
|
|
2327
|
+
return sanitized;
|
|
2328
|
+
}
|
|
2329
|
+
/**
|
|
2330
|
+
* Like {@link omitPrototypePollutionKeys}, but recursive: every plain object
|
|
2331
|
+
* reachable through the value (directly, or through an array) is copied with
|
|
2332
|
+
* its prototype-pollution keys dropped. Scalars and arrays of scalars come back
|
|
2333
|
+
* as they are.
|
|
2334
|
+
*
|
|
2335
|
+
* Use when passing an unknown, user-supplied value through unchanged — an MCP
|
|
2336
|
+
* server field a tool documents that rulesync does not model — so a
|
|
2337
|
+
* `__proto__` key nested anywhere inside it cannot ride into the generated
|
|
2338
|
+
* config, where the consuming tool may merge the object without the same care.
|
|
2339
|
+
*/
|
|
2340
|
+
function omitPrototypePollutionKeysDeep(value) {
|
|
2341
|
+
if (Array.isArray(value)) return value.map((entry) => omitPrototypePollutionKeysDeep(entry));
|
|
2342
|
+
if (value === null || typeof value !== "object") return value;
|
|
2343
|
+
const sanitized = {};
|
|
2344
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
2345
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
2346
|
+
sanitized[key] = omitPrototypePollutionKeysDeep(entry);
|
|
2347
|
+
}
|
|
2348
|
+
return sanitized;
|
|
2349
|
+
}
|
|
2350
|
+
// src/utils/jsonc.ts
|
|
2351
|
+
/**
|
|
2352
|
+
* Rebuild the parsed value from its own enumerable entries, dropping
|
|
2353
|
+
* prototype-pollution keys (`__proto__`, `constructor`, `prototype`).
|
|
2354
|
+
* `jsonc-parser` assigns keys with plain `obj[key] = value` semantics, so a
|
|
2355
|
+
* literal `__proto__` key would replace the containing object's prototype
|
|
2356
|
+
* instead of becoming an own property; rebuilding gives every object a clean
|
|
2357
|
+
* `Object.prototype` again and severs that path.
|
|
2358
|
+
*/
|
|
2359
|
+
function deepSanitize(value) {
|
|
2360
|
+
if (Array.isArray(value)) return value.map((item) => deepSanitize(item));
|
|
2361
|
+
if (value !== null && typeof value === "object") {
|
|
2362
|
+
const sanitized = {};
|
|
2363
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
2364
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
2365
|
+
sanitized[key] = deepSanitize(entry);
|
|
2366
|
+
}
|
|
2367
|
+
return sanitized;
|
|
2368
|
+
}
|
|
2369
|
+
return value;
|
|
2370
|
+
}
|
|
2371
|
+
/**
|
|
2372
|
+
* Collect the paths of every prototype-pollution key in the document, walking
|
|
2373
|
+
* the syntax tree rather than the parsed value.
|
|
2374
|
+
*
|
|
2375
|
+
* The parsed value cannot answer this question for `__proto__`:
|
|
2376
|
+
* `obj["__proto__"] = "deny"` is discarded by the engine outright, and
|
|
2377
|
+
* `obj["__proto__"] = {...}` replaces the prototype instead of adding an own
|
|
2378
|
+
* property — either way the key is already gone by the time
|
|
2379
|
+
* {@link deepSanitize} could look for it, and only the source text still knows
|
|
2380
|
+
* the user wrote it. `constructor` and `prototype` do become own properties and
|
|
2381
|
+
* could be found in the parsed value, but they are collected here too so all
|
|
2382
|
+
* three keys are answered the same way.
|
|
2383
|
+
*
|
|
2384
|
+
* A pollution key's subtree is not descended into, matching
|
|
2385
|
+
* {@link deepSanitize}, which drops the whole value along with the key.
|
|
2386
|
+
*/
|
|
2387
|
+
function collectPollutionKeyPaths({ node, path, found }) {
|
|
2388
|
+
if (node.type === "array") {
|
|
2389
|
+
for (const [index, child] of (node.children ?? []).entries()) collectPollutionKeyPaths({
|
|
2390
|
+
node: child,
|
|
2391
|
+
path: `${path}[${index}]`,
|
|
2392
|
+
found
|
|
2393
|
+
});
|
|
2394
|
+
return;
|
|
2395
|
+
}
|
|
2396
|
+
if (node.type !== "object") return;
|
|
2397
|
+
for (const property of node.children ?? []) {
|
|
2398
|
+
const [keyNode, valueNode] = property.children ?? [];
|
|
2399
|
+
if (typeof keyNode?.value !== "string") continue;
|
|
2400
|
+
const keyPath = path === "" ? keyNode.value : `${path}.${keyNode.value}`;
|
|
2401
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(keyNode.value)) {
|
|
2402
|
+
found.push(keyPath);
|
|
2403
|
+
continue;
|
|
2404
|
+
}
|
|
2405
|
+
if (valueNode !== void 0) collectPollutionKeyPaths({
|
|
2406
|
+
node: valueNode,
|
|
2407
|
+
path: keyPath,
|
|
2408
|
+
found
|
|
2409
|
+
});
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
/**
|
|
2413
|
+
* Parse a JSONC (JSON with Comments) document strictly.
|
|
2414
|
+
*
|
|
2415
|
+
* Unlike `jsonc-parser`'s bare `parse` (which silently tolerates syntax
|
|
2416
|
+
* errors and returns a best-effort value), this throws on any parse error so
|
|
2417
|
+
* a malformed source file fails loudly instead of generating half-empty tool
|
|
2418
|
+
* configs. Plain JSON is valid JSONC, so this is a drop-in replacement for
|
|
2419
|
+
* `JSON.parse` on files that may contain comments or trailing commas.
|
|
2420
|
+
*/
|
|
2421
|
+
function parseJsonc$6(content) {
|
|
2422
|
+
return deepSanitize(parseStrict(content));
|
|
2423
|
+
}
|
|
2424
|
+
/**
|
|
2425
|
+
* The strict parse shared by both entry points. Kept separate from the syntax
|
|
2426
|
+
* tree walk so the many callers that never look at `droppedKeys` do not pay
|
|
2427
|
+
* for building a second representation of the document.
|
|
2428
|
+
*/
|
|
2429
|
+
function parseStrict(content) {
|
|
2430
|
+
const errors = [];
|
|
2431
|
+
const result = (0, jsonc_parser.parse)(content, errors, {
|
|
2432
|
+
allowTrailingComma: true,
|
|
2433
|
+
disallowComments: false
|
|
2434
|
+
});
|
|
2435
|
+
if (errors.length > 0) {
|
|
2436
|
+
const details = errors.map((e) => `${(0, jsonc_parser.printParseErrorCode)(e.error)} at offset ${e.offset}`).join(", ");
|
|
2437
|
+
throw new SyntaxError(`Failed to parse JSONC content: ${details}`);
|
|
2438
|
+
}
|
|
2439
|
+
return result;
|
|
2440
|
+
}
|
|
2441
|
+
/**
|
|
2442
|
+
* The error a source document should fail with when
|
|
2443
|
+
* {@link parseJsoncReportingDroppedKeys} reports keys the parser removed.
|
|
2444
|
+
*
|
|
2445
|
+
* Shared by every source that reports them, so the three files a user can
|
|
2446
|
+
* author explain the same removal the same way rather than each inventing its
|
|
2447
|
+
* own wording.
|
|
2448
|
+
*/
|
|
2449
|
+
function droppedPollutionKeysError({ sourcePath, droppedKeys }) {
|
|
2450
|
+
return /* @__PURE__ */ new Error(`${JSON.stringify(sourcePath)} uses ${droppedKeys.map((key) => JSON.stringify(key)).join(", ")} as ${droppedKeys.length === 1 ? "a key" : "keys"}. Rulesync removes __proto__, constructor and prototype from every source document it parses, because assigning them would reach the prototype chain instead of the object. They are therefore never written to any tool's config — rename them rather than leaving entries that silently do nothing.`);
|
|
2451
|
+
}
|
|
2452
|
+
/**
|
|
2453
|
+
* The same parse as {@link parseJsonc}, additionally reporting which
|
|
2454
|
+
* prototype-pollution keys were removed, as dotted paths
|
|
2455
|
+
* (`permission.bash.__proto__`).
|
|
2456
|
+
*
|
|
2457
|
+
* The parse itself is identical — the paths only let a caller tell the user
|
|
2458
|
+
* about entries that vanished, which they would otherwise have to notice by
|
|
2459
|
+
* comparing their source file against the generated output.
|
|
2460
|
+
*/
|
|
2461
|
+
function parseJsoncReportingDroppedKeys({ content }) {
|
|
2462
|
+
const result = parseStrict(content);
|
|
2463
|
+
const found = [];
|
|
2464
|
+
const tree = (0, jsonc_parser.parseTree)(content, [], {
|
|
2465
|
+
allowTrailingComma: true,
|
|
2466
|
+
disallowComments: false
|
|
2467
|
+
});
|
|
2468
|
+
if (tree !== void 0) collectPollutionKeyPaths({
|
|
2469
|
+
node: tree,
|
|
2470
|
+
path: "",
|
|
2471
|
+
found
|
|
2472
|
+
});
|
|
2473
|
+
return {
|
|
2474
|
+
value: deepSanitize(result),
|
|
2475
|
+
droppedKeys: [...new Set(found)]
|
|
2476
|
+
};
|
|
2477
|
+
}
|
|
2294
2478
|
// src/types/json-output.ts
|
|
2295
2479
|
/**
|
|
2296
2480
|
* Error codes for CLI errors
|
|
@@ -3580,10 +3764,37 @@ const getDefaults = () => ({
|
|
|
3580
3764
|
inputRoots: void 0,
|
|
3581
3765
|
sources: []
|
|
3582
3766
|
});
|
|
3767
|
+
/**
|
|
3768
|
+
* Parse the content of a config file (`rulesync.jsonc` / `rulesync.local.jsonc`)
|
|
3769
|
+
* through the same strict, sanitizing JSONC parser as every other rulesync
|
|
3770
|
+
* source.
|
|
3771
|
+
*
|
|
3772
|
+
* `jsonc-parser`'s bare `parse` recovers from syntax errors without reporting
|
|
3773
|
+
* them, which let a typo (a missing comma, unquoted keys, trailing garbage)
|
|
3774
|
+
* silently drop keys and run rulesync with a different config than the user
|
|
3775
|
+
* wrote. Prototype-pollution keys are rejected rather than silently removed,
|
|
3776
|
+
* matching the hooks, MCP and permissions sources.
|
|
3777
|
+
*/
|
|
3778
|
+
function parseConfigFileContent({ content, filePath }) {
|
|
3779
|
+
let result;
|
|
3780
|
+
try {
|
|
3781
|
+
result = parseJsoncReportingDroppedKeys({ content });
|
|
3782
|
+
} catch (error) {
|
|
3783
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
3784
|
+
throw new Error(`Failed to parse ${JSON.stringify(filePath)}: ${detail}`, { cause: error });
|
|
3785
|
+
}
|
|
3786
|
+
if (result.droppedKeys.length > 0) throw droppedPollutionKeysError({
|
|
3787
|
+
sourcePath: filePath,
|
|
3788
|
+
droppedKeys: result.droppedKeys
|
|
3789
|
+
});
|
|
3790
|
+
return result.value;
|
|
3791
|
+
}
|
|
3583
3792
|
const loadConfigFromFile = async (filePath) => {
|
|
3584
3793
|
if (!await fileExists(filePath)) return {};
|
|
3585
|
-
const
|
|
3586
|
-
|
|
3794
|
+
const jsonData = parseConfigFileContent({
|
|
3795
|
+
content: await readFileContent(filePath),
|
|
3796
|
+
filePath
|
|
3797
|
+
});
|
|
3587
3798
|
const { $schema: _schema, ...configParams } = ConfigFileSchema.parse(jsonData);
|
|
3588
3799
|
assertTargetsFeaturesExclusive({
|
|
3589
3800
|
targets: configParams.targets,
|
|
@@ -4196,62 +4407,6 @@ function createBoundedWalk({ subject, limits, root }) {
|
|
|
4196
4407
|
leave
|
|
4197
4408
|
};
|
|
4198
4409
|
}
|
|
4199
|
-
// src/utils/prototype-pollution.ts
|
|
4200
|
-
/**
|
|
4201
|
-
* Keys that, if walked into when constructing or merging objects from
|
|
4202
|
-
* untrusted input, can mutate `Object.prototype` (or otherwise the prototype
|
|
4203
|
-
* chain) and propagate state to every other object in the runtime. Any code
|
|
4204
|
-
* that copies arbitrary user-supplied keys into a fresh object — frontmatter
|
|
4205
|
-
* parsing, MCP config conversion, settings round-trip — should skip these.
|
|
4206
|
-
*/
|
|
4207
|
-
const PROTOTYPE_POLLUTION_KEYS = /* @__PURE__ */ new Set([
|
|
4208
|
-
"__proto__",
|
|
4209
|
-
"constructor",
|
|
4210
|
-
"prototype"
|
|
4211
|
-
]);
|
|
4212
|
-
function isPrototypePollutionKey(key) {
|
|
4213
|
-
return PROTOTYPE_POLLUTION_KEYS.has(key);
|
|
4214
|
-
}
|
|
4215
|
-
/**
|
|
4216
|
-
* Returns a shallow copy of a record's own entries with every
|
|
4217
|
-
* prototype-pollution key (`__proto__`, `constructor`, `prototype`) dropped.
|
|
4218
|
-
*
|
|
4219
|
-
* Use when copying a nested, user-supplied string map — an MCP server's `env`
|
|
4220
|
-
* or `headers` table — into freshly generated config. Carrying such a map by
|
|
4221
|
-
* reference, or re-assigning its keys via bracket notation, would let a literal
|
|
4222
|
-
* `__proto__` key ride along (and re-assigning it would mutate the target's
|
|
4223
|
-
* prototype). Walking the entries through this helper severs that path while
|
|
4224
|
-
* preserving every legitimate key.
|
|
4225
|
-
*/
|
|
4226
|
-
function omitPrototypePollutionKeys(record) {
|
|
4227
|
-
const sanitized = {};
|
|
4228
|
-
for (const [key, value] of Object.entries(record)) {
|
|
4229
|
-
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
4230
|
-
sanitized[key] = value;
|
|
4231
|
-
}
|
|
4232
|
-
return sanitized;
|
|
4233
|
-
}
|
|
4234
|
-
/**
|
|
4235
|
-
* Like {@link omitPrototypePollutionKeys}, but recursive: every plain object
|
|
4236
|
-
* reachable through the value (directly, or through an array) is copied with
|
|
4237
|
-
* its prototype-pollution keys dropped. Scalars and arrays of scalars come back
|
|
4238
|
-
* as they are.
|
|
4239
|
-
*
|
|
4240
|
-
* Use when passing an unknown, user-supplied value through unchanged — an MCP
|
|
4241
|
-
* server field a tool documents that rulesync does not model — so a
|
|
4242
|
-
* `__proto__` key nested anywhere inside it cannot ride into the generated
|
|
4243
|
-
* config, where the consuming tool may merge the object without the same care.
|
|
4244
|
-
*/
|
|
4245
|
-
function omitPrototypePollutionKeysDeep(value) {
|
|
4246
|
-
if (Array.isArray(value)) return value.map((entry) => omitPrototypePollutionKeysDeep(entry));
|
|
4247
|
-
if (value === null || typeof value !== "object") return value;
|
|
4248
|
-
const sanitized = {};
|
|
4249
|
-
for (const [key, entry] of Object.entries(value)) {
|
|
4250
|
-
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
4251
|
-
sanitized[key] = omitPrototypePollutionKeysDeep(entry);
|
|
4252
|
-
}
|
|
4253
|
-
return sanitized;
|
|
4254
|
-
}
|
|
4255
4410
|
// src/utils/type-guards.ts
|
|
4256
4411
|
/**
|
|
4257
4412
|
* Type guard to check if a value is a plain object (Record<string, unknown>).
|
|
@@ -4879,6 +5034,24 @@ function lookupOwn({ record, key }) {
|
|
|
4879
5034
|
return Object.hasOwn(record, key) ? record[key] : void 0;
|
|
4880
5035
|
}
|
|
4881
5036
|
/**
|
|
5037
|
+
* Return the value a map holds under its own `key`, first storing `create()`
|
|
5038
|
+
* there when it has none. A bracket read would find an inherited member for a
|
|
5039
|
+
* key such as `toString`, so an importer bucketing rules by a user-supplied
|
|
5040
|
+
* name would write into an `Object.prototype` function instead of a fresh
|
|
5041
|
+
* bucket.
|
|
5042
|
+
*/
|
|
5043
|
+
function getOrCreateOwn({ record, key, create }) {
|
|
5044
|
+
if (Object.hasOwn(record, key)) return record[key];
|
|
5045
|
+
const created = create();
|
|
5046
|
+
Object.defineProperty(record, key, {
|
|
5047
|
+
value: created,
|
|
5048
|
+
writable: true,
|
|
5049
|
+
enumerable: true,
|
|
5050
|
+
configurable: true
|
|
5051
|
+
});
|
|
5052
|
+
return created;
|
|
5053
|
+
}
|
|
5054
|
+
/**
|
|
4882
5055
|
* Swap the keys and values of a name map, e.g. a canonical-to-tool map into
|
|
4883
5056
|
* the tool-to-canonical map an importer reads. When two keys share a value,
|
|
4884
5057
|
* the later key wins.
|
|
@@ -6413,14 +6586,16 @@ const CANONICAL_TO_GOOSE_EVENT_NAMES = {
|
|
|
6413
6586
|
* Hook events supported by Goose.
|
|
6414
6587
|
*
|
|
6415
6588
|
* Goose adopts the Open Plugins hooks spec: each plugin's `hooks/hooks.json`
|
|
6416
|
-
* maps PascalCase event names to matcher/handler arrays.
|
|
6417
|
-
*
|
|
6418
|
-
*
|
|
6419
|
-
*
|
|
6420
|
-
*
|
|
6421
|
-
*
|
|
6589
|
+
* maps PascalCase event names to matcher/handler arrays.
|
|
6590
|
+
*
|
|
6591
|
+
* Goose's `HookEvent` enum defines these 11 events plus `PreToolUseResult`
|
|
6592
|
+
* (added in v1.48.0), which has no canonical equivalent: it is authored under
|
|
6593
|
+
* the `goose.hooks` override block by its native name, emitted verbatim, and
|
|
6594
|
+
* imported back into that block. Notably the enum has NO
|
|
6595
|
+
* `SubagentStart`/`SubagentStop` arms — emitting them would write keys Goose
|
|
6596
|
+
* silently ignores, so `subagentStart`/`subagentStop` are intentionally
|
|
6422
6597
|
* excluded here and from `CANONICAL_TO_GOOSE_EVENT_NAMES`.
|
|
6423
|
-
* @see https://github.com/aaif-goose/goose/blob/v1.
|
|
6598
|
+
* @see https://github.com/aaif-goose/goose/blob/v1.48.0/crates/goose/src/hooks/mod.rs
|
|
6424
6599
|
* @see https://goose-docs.ai/docs/guides/context-engineering/hooks/
|
|
6425
6600
|
*/
|
|
6426
6601
|
const GOOSE_HOOK_EVENTS = eventsOf(CANONICAL_TO_GOOSE_EVENT_NAMES);
|
|
@@ -6898,134 +7073,6 @@ const GROKCLI_HOOK_EVENTS = eventsOf(CANONICAL_TO_GROKCLI_EVENT_NAMES);
|
|
|
6898
7073
|
* Map Grok CLI PascalCase event names to canonical camelCase.
|
|
6899
7074
|
*/
|
|
6900
7075
|
const GROKCLI_TO_CANONICAL_EVENT_NAMES = invertRecord(CANONICAL_TO_GROKCLI_EVENT_NAMES);
|
|
6901
|
-
// src/utils/jsonc.ts
|
|
6902
|
-
/**
|
|
6903
|
-
* Rebuild the parsed value from its own enumerable entries, dropping
|
|
6904
|
-
* prototype-pollution keys (`__proto__`, `constructor`, `prototype`).
|
|
6905
|
-
* `jsonc-parser` assigns keys with plain `obj[key] = value` semantics, so a
|
|
6906
|
-
* literal `__proto__` key would replace the containing object's prototype
|
|
6907
|
-
* instead of becoming an own property; rebuilding gives every object a clean
|
|
6908
|
-
* `Object.prototype` again and severs that path.
|
|
6909
|
-
*/
|
|
6910
|
-
function deepSanitize(value) {
|
|
6911
|
-
if (Array.isArray(value)) return value.map((item) => deepSanitize(item));
|
|
6912
|
-
if (value !== null && typeof value === "object") {
|
|
6913
|
-
const sanitized = {};
|
|
6914
|
-
for (const [key, entry] of Object.entries(value)) {
|
|
6915
|
-
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
6916
|
-
sanitized[key] = deepSanitize(entry);
|
|
6917
|
-
}
|
|
6918
|
-
return sanitized;
|
|
6919
|
-
}
|
|
6920
|
-
return value;
|
|
6921
|
-
}
|
|
6922
|
-
/**
|
|
6923
|
-
* Collect the paths of every prototype-pollution key in the document, walking
|
|
6924
|
-
* the syntax tree rather than the parsed value.
|
|
6925
|
-
*
|
|
6926
|
-
* The parsed value cannot answer this question for `__proto__`:
|
|
6927
|
-
* `obj["__proto__"] = "deny"` is discarded by the engine outright, and
|
|
6928
|
-
* `obj["__proto__"] = {...}` replaces the prototype instead of adding an own
|
|
6929
|
-
* property — either way the key is already gone by the time
|
|
6930
|
-
* {@link deepSanitize} could look for it, and only the source text still knows
|
|
6931
|
-
* the user wrote it. `constructor` and `prototype` do become own properties and
|
|
6932
|
-
* could be found in the parsed value, but they are collected here too so all
|
|
6933
|
-
* three keys are answered the same way.
|
|
6934
|
-
*
|
|
6935
|
-
* A pollution key's subtree is not descended into, matching
|
|
6936
|
-
* {@link deepSanitize}, which drops the whole value along with the key.
|
|
6937
|
-
*/
|
|
6938
|
-
function collectPollutionKeyPaths({ node, path, found }) {
|
|
6939
|
-
if (node.type === "array") {
|
|
6940
|
-
for (const [index, child] of (node.children ?? []).entries()) collectPollutionKeyPaths({
|
|
6941
|
-
node: child,
|
|
6942
|
-
path: `${path}[${index}]`,
|
|
6943
|
-
found
|
|
6944
|
-
});
|
|
6945
|
-
return;
|
|
6946
|
-
}
|
|
6947
|
-
if (node.type !== "object") return;
|
|
6948
|
-
for (const property of node.children ?? []) {
|
|
6949
|
-
const [keyNode, valueNode] = property.children ?? [];
|
|
6950
|
-
if (typeof keyNode?.value !== "string") continue;
|
|
6951
|
-
const keyPath = path === "" ? keyNode.value : `${path}.${keyNode.value}`;
|
|
6952
|
-
if (PROTOTYPE_POLLUTION_KEYS.has(keyNode.value)) {
|
|
6953
|
-
found.push(keyPath);
|
|
6954
|
-
continue;
|
|
6955
|
-
}
|
|
6956
|
-
if (valueNode !== void 0) collectPollutionKeyPaths({
|
|
6957
|
-
node: valueNode,
|
|
6958
|
-
path: keyPath,
|
|
6959
|
-
found
|
|
6960
|
-
});
|
|
6961
|
-
}
|
|
6962
|
-
}
|
|
6963
|
-
/**
|
|
6964
|
-
* Parse a JSONC (JSON with Comments) document strictly.
|
|
6965
|
-
*
|
|
6966
|
-
* Unlike `jsonc-parser`'s bare `parse` (which silently tolerates syntax
|
|
6967
|
-
* errors and returns a best-effort value), this throws on any parse error so
|
|
6968
|
-
* a malformed source file fails loudly instead of generating half-empty tool
|
|
6969
|
-
* configs. Plain JSON is valid JSONC, so this is a drop-in replacement for
|
|
6970
|
-
* `JSON.parse` on files that may contain comments or trailing commas.
|
|
6971
|
-
*/
|
|
6972
|
-
function parseJsonc$6(content) {
|
|
6973
|
-
return deepSanitize(parseStrict(content));
|
|
6974
|
-
}
|
|
6975
|
-
/**
|
|
6976
|
-
* The strict parse shared by both entry points. Kept separate from the syntax
|
|
6977
|
-
* tree walk so the many callers that never look at `droppedKeys` do not pay
|
|
6978
|
-
* for building a second representation of the document.
|
|
6979
|
-
*/
|
|
6980
|
-
function parseStrict(content) {
|
|
6981
|
-
const errors = [];
|
|
6982
|
-
const result = (0, jsonc_parser.parse)(content, errors, {
|
|
6983
|
-
allowTrailingComma: true,
|
|
6984
|
-
disallowComments: false
|
|
6985
|
-
});
|
|
6986
|
-
if (errors.length > 0) {
|
|
6987
|
-
const details = errors.map((e) => `${(0, jsonc_parser.printParseErrorCode)(e.error)} at offset ${e.offset}`).join(", ");
|
|
6988
|
-
throw new SyntaxError(`Failed to parse JSONC content: ${details}`);
|
|
6989
|
-
}
|
|
6990
|
-
return result;
|
|
6991
|
-
}
|
|
6992
|
-
/**
|
|
6993
|
-
* The error a source document should fail with when
|
|
6994
|
-
* {@link parseJsoncReportingDroppedKeys} reports keys the parser removed.
|
|
6995
|
-
*
|
|
6996
|
-
* Shared by every source that reports them, so the three files a user can
|
|
6997
|
-
* author explain the same removal the same way rather than each inventing its
|
|
6998
|
-
* own wording.
|
|
6999
|
-
*/
|
|
7000
|
-
function droppedPollutionKeysError({ sourcePath, droppedKeys }) {
|
|
7001
|
-
return /* @__PURE__ */ new Error(`${JSON.stringify(sourcePath)} uses ${droppedKeys.map((key) => JSON.stringify(key)).join(", ")} as ${droppedKeys.length === 1 ? "a key" : "keys"}. Rulesync removes __proto__, constructor and prototype from every source document it parses, because assigning them would reach the prototype chain instead of the object. They are therefore never written to any tool's config — rename them rather than leaving entries that silently do nothing.`);
|
|
7002
|
-
}
|
|
7003
|
-
/**
|
|
7004
|
-
* The same parse as {@link parseJsonc}, additionally reporting which
|
|
7005
|
-
* prototype-pollution keys were removed, as dotted paths
|
|
7006
|
-
* (`permission.bash.__proto__`).
|
|
7007
|
-
*
|
|
7008
|
-
* The parse itself is identical — the paths only let a caller tell the user
|
|
7009
|
-
* about entries that vanished, which they would otherwise have to notice by
|
|
7010
|
-
* comparing their source file against the generated output.
|
|
7011
|
-
*/
|
|
7012
|
-
function parseJsoncReportingDroppedKeys({ content }) {
|
|
7013
|
-
const result = parseStrict(content);
|
|
7014
|
-
const found = [];
|
|
7015
|
-
const tree = (0, jsonc_parser.parseTree)(content, [], {
|
|
7016
|
-
allowTrailingComma: true,
|
|
7017
|
-
disallowComments: false
|
|
7018
|
-
});
|
|
7019
|
-
if (tree !== void 0) collectPollutionKeyPaths({
|
|
7020
|
-
node: tree,
|
|
7021
|
-
path: "",
|
|
7022
|
-
found
|
|
7023
|
-
});
|
|
7024
|
-
return {
|
|
7025
|
-
value: deepSanitize(result),
|
|
7026
|
-
droppedKeys: [...new Set(found)]
|
|
7027
|
-
};
|
|
7028
|
-
}
|
|
7029
7076
|
// src/utils/rulesync-source-path.ts
|
|
7030
7077
|
/**
|
|
7031
7078
|
* No `.rulesync/` source file exists for a feature at any of its candidate
|
|
@@ -19580,9 +19627,6 @@ var KiloCommand = class extends FrontmatterMarkdownToolCommand {
|
|
|
19580
19627
|
static getFrontmatterSchema() {
|
|
19581
19628
|
return KiloCommandFrontmatterSchema;
|
|
19582
19629
|
}
|
|
19583
|
-
getRulesyncOutputRoot() {
|
|
19584
|
-
return process.cwd();
|
|
19585
|
-
}
|
|
19586
19630
|
/**
|
|
19587
19631
|
* Refuse to write a command Kilo reserves (see
|
|
19588
19632
|
* {@link KILO_RESERVED_COMMAND_NAMES}). The name Kilo loads is the file
|
|
@@ -20871,9 +20915,6 @@ var OpenCodeCommand = class extends FrontmatterMarkdownToolCommand {
|
|
|
20871
20915
|
...Object.keys(restFields).length > 0 && { [toolTarget]: restFields }
|
|
20872
20916
|
};
|
|
20873
20917
|
}
|
|
20874
|
-
getRulesyncOutputRoot() {
|
|
20875
|
-
return process.cwd();
|
|
20876
|
-
}
|
|
20877
20918
|
/**
|
|
20878
20919
|
* Imports commands defined inline in `opencode.json` / `opencode.jsonc` (and,
|
|
20879
20920
|
* at project scope, `.opencode/opencode.json(c)`, which OpenCode merges over
|
|
@@ -58630,12 +58671,11 @@ var GooseHooks = class GooseHooks extends ToolHooks {
|
|
|
58630
58671
|
});
|
|
58631
58672
|
}
|
|
58632
58673
|
toRulesyncHooks({ logger } = {}) {
|
|
58633
|
-
const parsed = this.parseFileContentOrThrow({
|
|
58634
|
-
displayName: "Goose",
|
|
58635
|
-
parse: (fileContent) => JSON.parse(fileContent)
|
|
58636
|
-
});
|
|
58637
58674
|
const hooks = toolHooksToCanonical({
|
|
58638
|
-
hooks:
|
|
58675
|
+
hooks: this.parseFileContentOrThrow({
|
|
58676
|
+
displayName: "Goose",
|
|
58677
|
+
parse: (fileContent) => JSON.parse(fileContent)
|
|
58678
|
+
}).hooks,
|
|
58639
58679
|
converterConfig: GOOSE_CONVERTER_CONFIG,
|
|
58640
58680
|
logger
|
|
58641
58681
|
});
|
|
@@ -61902,12 +61942,14 @@ var ReasonixHooks = class ReasonixHooks extends ToolHooks {
|
|
|
61902
61942
|
const paths = ReasonixHooks.getSettablePaths({ global });
|
|
61903
61943
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
61904
61944
|
const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
|
|
61905
|
-
let
|
|
61945
|
+
let parsed;
|
|
61906
61946
|
try {
|
|
61907
|
-
|
|
61947
|
+
parsed = JSON.parse(existingContent);
|
|
61908
61948
|
} catch (error) {
|
|
61909
61949
|
throw new Error(`Failed to parse existing Reasonix settings at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
61910
61950
|
}
|
|
61951
|
+
if (!isPlainObject(parsed)) throw new Error(`Failed to parse existing Reasonix settings at ${filePath}: expected a mapping at the root`);
|
|
61952
|
+
const settings = parsed;
|
|
61911
61953
|
const config = rulesyncHooks.getJson();
|
|
61912
61954
|
const reasonixHooks = canonicalToReasonixHooks({
|
|
61913
61955
|
config,
|
|
@@ -62866,15 +62908,18 @@ function emittedEventsWithHook(params) {
|
|
|
62866
62908
|
}
|
|
62867
62909
|
/**
|
|
62868
62910
|
* The capabilities an adapter built on the shared hooks converter already
|
|
62869
|
-
* declares in its converter config: the events it emits
|
|
62870
|
-
* keeps
|
|
62871
|
-
*
|
|
62911
|
+
* declares in its converter config: the events it emits, the hook types it
|
|
62912
|
+
* keeps, and whether it writes the per-hook `enabled` flag. Reading them from
|
|
62913
|
+
* there keeps the processor's skip and dropped-field warnings in step with
|
|
62914
|
+
* what the converter actually writes.
|
|
62872
62915
|
*/
|
|
62873
62916
|
function converterCapabilities(config) {
|
|
62874
62917
|
if (!config.supportedHookTypes) throw new Error("Hooks converter config must declare its supportedHookTypes");
|
|
62918
|
+
const passesThroughEnabled = (config.booleanPassthroughFields ?? []).some((field) => field.canonical === "enabled");
|
|
62875
62919
|
return {
|
|
62876
62920
|
supportedEvents: config.supportedEvents,
|
|
62877
|
-
supportedHookTypes: [...config.supportedHookTypes]
|
|
62921
|
+
supportedHookTypes: [...config.supportedHookTypes],
|
|
62922
|
+
...passesThroughEnabled ? { supportsPerHookEnabled: true } : {}
|
|
62878
62923
|
};
|
|
62879
62924
|
}
|
|
62880
62925
|
const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
@@ -63496,8 +63541,7 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
63496
63541
|
supportsImport: true
|
|
63497
63542
|
},
|
|
63498
63543
|
...converterCapabilities(ZCODE_CONVERTER_CONFIG),
|
|
63499
|
-
supportsMatcher: true
|
|
63500
|
-
supportsPerHookEnabled: true
|
|
63544
|
+
supportsMatcher: true
|
|
63501
63545
|
}],
|
|
63502
63546
|
["zcode-plugin", {
|
|
63503
63547
|
class: ZcodePluginHooks,
|
|
@@ -63507,10 +63551,12 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
63507
63551
|
supportsImport: true
|
|
63508
63552
|
},
|
|
63509
63553
|
...converterCapabilities(ZCODE_PLUGIN_CONVERTER_CONFIG),
|
|
63510
|
-
supportsMatcher: true
|
|
63511
|
-
supportsPerHookEnabled: true
|
|
63554
|
+
supportsMatcher: true
|
|
63512
63555
|
}]
|
|
63513
63556
|
]);
|
|
63557
|
+
function targetsSupporting(capability) {
|
|
63558
|
+
return [...toolHooksFactories.entries()].filter(([, f]) => f[capability]).map(([t]) => t).join(" / ");
|
|
63559
|
+
}
|
|
63514
63560
|
const hooksProcessorToolTargets = [...toolHooksFactories.entries()].filter(([, f]) => f.meta.supportsProject).map(([t]) => t);
|
|
63515
63561
|
const hooksProcessorToolTargetsGlobal = [...toolHooksFactories.entries()].filter(([, f]) => f.meta.supportsGlobal).map(([t]) => t);
|
|
63516
63562
|
const hooksProcessorToolTargetsImportable = [...toolHooksFactories.entries()].filter(([, f]) => f.meta.supportsProject && f.meta.supportsImport).map(([t]) => t);
|
|
@@ -63671,7 +63717,7 @@ var HooksProcessor = class extends FeatureProcessor {
|
|
|
63671
63717
|
effectiveHooks,
|
|
63672
63718
|
predicate: (def) => def.enabled === false
|
|
63673
63719
|
});
|
|
63674
|
-
if (eventsWithDisabledHooks.length > 0) this.logger.warn(`Emitting "enabled: false" hook(s) as active for ${this.toolTarget} (only the
|
|
63720
|
+
if (eventsWithDisabledHooks.length > 0) this.logger.warn(`Emitting "enabled: false" hook(s) as active for ${this.toolTarget} (only the ${targetsSupporting("supportsPerHookEnabled")} hooks formats support the flag): ${eventsWithDisabledHooks.join(", ")}`);
|
|
63675
63721
|
}
|
|
63676
63722
|
if (!factory.supportsPerHookConfirm) {
|
|
63677
63723
|
const eventsWithConfirmHooks = emittedEventsWithHook({
|
|
@@ -63680,7 +63726,7 @@ var HooksProcessor = class extends FeatureProcessor {
|
|
|
63680
63726
|
effectiveHooks,
|
|
63681
63727
|
predicate: (def) => def.confirm !== void 0
|
|
63682
63728
|
});
|
|
63683
|
-
if (eventsWithConfirmHooks.length > 0) this.logger.warn(`Emitting hook(s) without their "confirm" prompt for ${this.toolTarget} (only the
|
|
63729
|
+
if (eventsWithConfirmHooks.length > 0) this.logger.warn(`Emitting hook(s) without their "confirm" prompt for ${this.toolTarget} (only the ${targetsSupporting("supportsPerHookConfirm")} hooks formats support it): ${eventsWithConfirmHooks.join(", ")}`);
|
|
63684
63730
|
}
|
|
63685
63731
|
}
|
|
63686
63732
|
async convertToolFilesToRulesyncFiles(toolFiles) {
|
|
@@ -64943,12 +64989,7 @@ var ReasonixIgnore = class ReasonixIgnore extends ToolIgnore {
|
|
|
64943
64989
|
}
|
|
64944
64990
|
toRulesyncIgnore() {
|
|
64945
64991
|
const rulesyncPatterns = (0, es_toolkit.uniq)(this.patterns.map((entry) => readDenyPatternOf(entry)).filter((pattern) => pattern !== null && pattern.length > 0));
|
|
64946
|
-
return
|
|
64947
|
-
outputRoot: this.outputRoot,
|
|
64948
|
-
relativeDirPath: RulesyncIgnore.getSettablePaths().recommended.relativeDirPath,
|
|
64949
|
-
relativeFilePath: RulesyncIgnore.getSettablePaths().recommended.relativeFilePath,
|
|
64950
|
-
fileContent: rulesyncPatterns.join("\n")
|
|
64951
|
-
});
|
|
64992
|
+
return this.toRulesyncIgnoreDefault({ fileContent: rulesyncPatterns.join("\n") });
|
|
64952
64993
|
}
|
|
64953
64994
|
static async fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore, global = false, logger }) {
|
|
64954
64995
|
await warnIfReasonixNestedProjectConfigShadowed({
|
|
@@ -78655,11 +78696,17 @@ function convertAmpToRulesync({ disable, permissions }) {
|
|
|
78655
78696
|
const assign = (tool, pattern, action) => {
|
|
78656
78697
|
if (isPrototypePollutionKey(tool)) return;
|
|
78657
78698
|
if (isPrototypePollutionKey(pattern)) return;
|
|
78658
|
-
const bucket =
|
|
78659
|
-
|
|
78699
|
+
const bucket = getOrCreateOwn({
|
|
78700
|
+
record: permission,
|
|
78701
|
+
key: tool,
|
|
78702
|
+
create: () => ({})
|
|
78703
|
+
});
|
|
78660
78704
|
if (isStricterAction({
|
|
78661
78705
|
action,
|
|
78662
|
-
existing
|
|
78706
|
+
existing: lookupOwn({
|
|
78707
|
+
record: bucket,
|
|
78708
|
+
key: pattern
|
|
78709
|
+
})
|
|
78663
78710
|
})) bucket[pattern] = action;
|
|
78664
78711
|
};
|
|
78665
78712
|
for (const tool of disable) assign(tool, "*", "deny");
|
|
@@ -80062,8 +80109,12 @@ function convertAntigravityToRulesyncPermissions({ allow, ask, deny, toCategory,
|
|
|
80062
80109
|
logger,
|
|
80063
80110
|
toolLabel
|
|
80064
80111
|
}) : target;
|
|
80065
|
-
if (
|
|
80066
|
-
|
|
80112
|
+
if (isPrototypePollutionKey(canonical) || isPrototypePollutionKey(pattern)) continue;
|
|
80113
|
+
getOrCreateOwn({
|
|
80114
|
+
record: permission,
|
|
80115
|
+
key: canonical,
|
|
80116
|
+
create: () => ({})
|
|
80117
|
+
})[pattern] = action;
|
|
80067
80118
|
}
|
|
80068
80119
|
};
|
|
80069
80120
|
processEntries(allow, "allow");
|
|
@@ -80131,10 +80182,16 @@ const ANTIGRAVITY_CLI_TO_CANONICAL_TOOL_NAMES = {
|
|
|
80131
80182
|
};
|
|
80132
80183
|
function toAntigravityCliToolName(canonical) {
|
|
80133
80184
|
if (canonical.startsWith("mcp__")) return "mcp";
|
|
80134
|
-
return
|
|
80185
|
+
return lookupOwn({
|
|
80186
|
+
record: CANONICAL_TO_ANTIGRAVITY_CLI_TOOL_NAMES,
|
|
80187
|
+
key: canonical
|
|
80188
|
+
}) ?? canonical;
|
|
80135
80189
|
}
|
|
80136
80190
|
function toCanonicalToolName$9(cliName) {
|
|
80137
|
-
return
|
|
80191
|
+
return lookupOwn({
|
|
80192
|
+
record: ANTIGRAVITY_CLI_TO_CANONICAL_TOOL_NAMES,
|
|
80193
|
+
key: cliName
|
|
80194
|
+
}) ?? cliName;
|
|
80138
80195
|
}
|
|
80139
80196
|
/**
|
|
80140
80197
|
* Permissions generator for the Google Antigravity CLI (`agy`, Antigravity 2.0).
|
|
@@ -80297,10 +80354,16 @@ const IDE_ACTION_TO_CANONICAL = {
|
|
|
80297
80354
|
};
|
|
80298
80355
|
function toIdeAction(canonical) {
|
|
80299
80356
|
if (canonical.startsWith("mcp__")) return "mcp";
|
|
80300
|
-
return
|
|
80357
|
+
return lookupOwn({
|
|
80358
|
+
record: CANONICAL_TO_IDE_ACTION,
|
|
80359
|
+
key: canonical
|
|
80360
|
+
}) ?? canonical;
|
|
80301
80361
|
}
|
|
80302
80362
|
function toCanonicalCategory$2(ideAction) {
|
|
80303
|
-
return
|
|
80363
|
+
return lookupOwn({
|
|
80364
|
+
record: IDE_ACTION_TO_CANONICAL,
|
|
80365
|
+
key: ideAction
|
|
80366
|
+
}) ?? ideAction;
|
|
80304
80367
|
}
|
|
80305
80368
|
/**
|
|
80306
80369
|
* Permissions generator for the Google Antigravity IDE (Antigravity 2.0).
|
|
@@ -81839,10 +81902,16 @@ const CANONICAL_TO_CLAUDE_TOOL_NAMES = {
|
|
|
81839
81902
|
*/
|
|
81840
81903
|
const CLAUDE_TO_CANONICAL_TOOL_NAMES = invertRecord(CANONICAL_TO_CLAUDE_TOOL_NAMES);
|
|
81841
81904
|
function toClaudeToolName(canonical) {
|
|
81842
|
-
return
|
|
81905
|
+
return lookupOwn({
|
|
81906
|
+
record: CANONICAL_TO_CLAUDE_TOOL_NAMES,
|
|
81907
|
+
key: canonical
|
|
81908
|
+
}) ?? canonical;
|
|
81843
81909
|
}
|
|
81844
81910
|
function toCanonicalToolName$7(claudeName) {
|
|
81845
|
-
return
|
|
81911
|
+
return lookupOwn({
|
|
81912
|
+
record: CLAUDE_TO_CANONICAL_TOOL_NAMES,
|
|
81913
|
+
key: claudeName
|
|
81914
|
+
}) ?? claudeName;
|
|
81846
81915
|
}
|
|
81847
81916
|
/**
|
|
81848
81917
|
* Merge `patch` into `base`, recursing into plain objects so a sibling key at
|
|
@@ -82814,7 +82883,10 @@ const CLAUDE_PATH_RULE_ALIASES = {
|
|
|
82814
82883
|
*/
|
|
82815
82884
|
function buildClaudePermissionEntry(toolName, pattern) {
|
|
82816
82885
|
if (pattern === "*") return toolName;
|
|
82817
|
-
return `${
|
|
82886
|
+
return `${lookupOwn({
|
|
82887
|
+
record: CLAUDE_PATH_RULE_ALIASES,
|
|
82888
|
+
key: toolName
|
|
82889
|
+
}) ?? toolName}(${pattern})`;
|
|
82818
82890
|
}
|
|
82819
82891
|
/**
|
|
82820
82892
|
* The Claude tool names the canonical config manages. Deliberately the tool
|
|
@@ -83052,8 +83124,12 @@ function convertClaudeToRulesyncPermissions(params) {
|
|
|
83052
83124
|
for (const entry of entries) {
|
|
83053
83125
|
const { toolName, pattern } = parseToolPatternEntry(entry);
|
|
83054
83126
|
const canonical = toCanonicalToolName$7(toolName);
|
|
83055
|
-
if (
|
|
83056
|
-
|
|
83127
|
+
if (isPrototypePollutionKey(canonical) || isPrototypePollutionKey(pattern)) continue;
|
|
83128
|
+
getOrCreateOwn({
|
|
83129
|
+
record: permission,
|
|
83130
|
+
key: canonical,
|
|
83131
|
+
create: () => ({})
|
|
83132
|
+
})[pattern] = action;
|
|
83057
83133
|
}
|
|
83058
83134
|
};
|
|
83059
83135
|
processEntries(params.allow, "allow");
|
|
@@ -83635,11 +83711,17 @@ var CodebuddyPermissions = class CodebuddyPermissions extends ToolPermissions {
|
|
|
83635
83711
|
for (const action of CODEBUDDY_RULE_LISTS) for (const rule of toStringArray$1(permissions[action])) {
|
|
83636
83712
|
const parsed = parseCodebuddyRule(rule);
|
|
83637
83713
|
if (parsed === null || isPrototypePollutionKey(parsed.category) || isPrototypePollutionKey(parsed.pattern)) continue;
|
|
83638
|
-
const rules =
|
|
83639
|
-
|
|
83714
|
+
const rules = getOrCreateOwn({
|
|
83715
|
+
record: permission,
|
|
83716
|
+
key: parsed.category,
|
|
83717
|
+
create: () => ({})
|
|
83718
|
+
});
|
|
83640
83719
|
if (isStricterAction({
|
|
83641
83720
|
action,
|
|
83642
|
-
existing
|
|
83721
|
+
existing: lookupOwn({
|
|
83722
|
+
record: rules,
|
|
83723
|
+
key: parsed.pattern
|
|
83724
|
+
})
|
|
83643
83725
|
})) rules[parsed.pattern] = action;
|
|
83644
83726
|
}
|
|
83645
83727
|
const codebuddy = liftCodebuddyOverride({
|
|
@@ -84149,7 +84231,10 @@ const CODEWHALE_WRITE_RESTRICTION_TOOLS = ["write_file", "apply_patch"];
|
|
|
84149
84231
|
/** The Codewhale tools a canonical path rule is written for. */
|
|
84150
84232
|
function pathCategoryTools(category, action) {
|
|
84151
84233
|
if (category === "write" && action !== "allow") return CODEWHALE_WRITE_RESTRICTION_TOOLS;
|
|
84152
|
-
return
|
|
84234
|
+
return lookupOwn({
|
|
84235
|
+
record: CODEWHALE_PATH_CATEGORY_TOOLS,
|
|
84236
|
+
key: category
|
|
84237
|
+
}) ?? [];
|
|
84153
84238
|
}
|
|
84154
84239
|
/** The canonical path categories a Codewhale file tool's rule can come from. */
|
|
84155
84240
|
const CODEWHALE_TOOL_TO_PATH_CATEGORIES = {
|
|
@@ -84468,7 +84553,10 @@ function canonicalToCodewhaleRules({ config, preservedRules, logger }) {
|
|
|
84468
84553
|
});
|
|
84469
84554
|
for (const [category, rules] of Object.entries(config.permission)) {
|
|
84470
84555
|
if (category === "bash" || category === "*") continue;
|
|
84471
|
-
if (
|
|
84556
|
+
if (lookupOwn({
|
|
84557
|
+
record: CODEWHALE_PATH_CATEGORY_TOOLS,
|
|
84558
|
+
key: category
|
|
84559
|
+
}) === void 0) {
|
|
84472
84560
|
if (Object.keys(rules).length > 0) logger?.warn(`Codewhale permissions: skipping unsupported category "${category}"; Codewhale evaluates permission rules only for shell commands and file tools.`);
|
|
84473
84561
|
continue;
|
|
84474
84562
|
}
|
|
@@ -84578,7 +84666,10 @@ function toImportCandidates(rule) {
|
|
|
84578
84666
|
}
|
|
84579
84667
|
const pattern = pathRuleToPattern(rule);
|
|
84580
84668
|
if (pattern === null) return [];
|
|
84581
|
-
return (
|
|
84669
|
+
return (lookupOwn({
|
|
84670
|
+
record: CODEWHALE_TOOL_TO_PATH_CATEGORIES,
|
|
84671
|
+
key: rule.tool
|
|
84672
|
+
}) ?? []).map((category) => ({
|
|
84582
84673
|
category,
|
|
84583
84674
|
pattern,
|
|
84584
84675
|
rule
|
|
@@ -86186,10 +86277,17 @@ function parseCommandcodeRuleLists(permissions) {
|
|
|
86186
86277
|
rule,
|
|
86187
86278
|
category
|
|
86188
86279
|
})) fallbackLogger.warn(`Command Code matches '${rule}' in "${action}" against MCP tool names case-insensitively, but the '${category}' rule generated from it matches the server and tool names exactly as written; spell the rule the way the tool is registered if they differ.`);
|
|
86189
|
-
const rules =
|
|
86280
|
+
const rules = getOrCreateOwn({
|
|
86281
|
+
record: permission,
|
|
86282
|
+
key: category,
|
|
86283
|
+
create: () => ({})
|
|
86284
|
+
});
|
|
86190
86285
|
if (isStricterAction({
|
|
86191
86286
|
action,
|
|
86192
|
-
existing:
|
|
86287
|
+
existing: lookupOwn({
|
|
86288
|
+
record: rules,
|
|
86289
|
+
key: pattern
|
|
86290
|
+
})
|
|
86193
86291
|
})) rules[pattern] = action;
|
|
86194
86292
|
}
|
|
86195
86293
|
}
|
|
@@ -86550,9 +86648,12 @@ function convertContinuePermissionsToRulesync(lists) {
|
|
|
86550
86648
|
const parsedEntry = parseContinuePermissionEntry(entry);
|
|
86551
86649
|
if (parsedEntry === void 0) continue;
|
|
86552
86650
|
if (isPrototypePollutionKey(parsedEntry.toolName) || isPrototypePollutionKey(parsedEntry.pattern)) continue;
|
|
86553
|
-
const
|
|
86554
|
-
|
|
86555
|
-
|
|
86651
|
+
const rules = getOrCreateOwn({
|
|
86652
|
+
record: permission,
|
|
86653
|
+
key: toCanonicalToolName$6(parsedEntry.toolName),
|
|
86654
|
+
create: () => ({})
|
|
86655
|
+
});
|
|
86656
|
+
if (!Object.hasOwn(rules, parsedEntry.pattern)) rules[parsedEntry.pattern] = action;
|
|
86556
86657
|
}
|
|
86557
86658
|
}
|
|
86558
86659
|
return { permission };
|
|
@@ -87181,7 +87282,9 @@ var CopilotcliPermissions = class CopilotcliPermissions extends ToolPermissions
|
|
|
87181
87282
|
const settings = this.parseSettings();
|
|
87182
87283
|
const rules = {};
|
|
87183
87284
|
for (const pattern of toUrlList(settings[DENIED_URLS_KEY])) rules[pattern] = "deny";
|
|
87184
|
-
if (this.global)
|
|
87285
|
+
if (this.global) {
|
|
87286
|
+
for (const pattern of toUrlList(settings[ALLOWED_URLS_KEY])) if (!Object.hasOwn(rules, pattern)) rules[pattern] = "allow";
|
|
87287
|
+
}
|
|
87185
87288
|
const permission = Object.keys(rules).length > 0 ? { [WEBFETCH_CATEGORY$1]: rules } : {};
|
|
87186
87289
|
const defaultPermissionMode = this.global ? settings[DEFAULT_PERMISSION_MODE_KEY] : void 0;
|
|
87187
87290
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({
|
|
@@ -87528,7 +87631,10 @@ function isMcpScopedCategory(canonical) {
|
|
|
87528
87631
|
}
|
|
87529
87632
|
function toCursorType(canonical) {
|
|
87530
87633
|
if (isMcpScopedCategory(canonical)) return "Mcp";
|
|
87531
|
-
return
|
|
87634
|
+
return lookupOwn({
|
|
87635
|
+
record: CANONICAL_TO_CURSOR_TYPE,
|
|
87636
|
+
key: canonical
|
|
87637
|
+
}) ?? canonical;
|
|
87532
87638
|
}
|
|
87533
87639
|
/**
|
|
87534
87640
|
* For per-tool MCP canonical categories like `mcp__puppeteer__navigate`, the
|
|
@@ -87560,9 +87666,15 @@ function toCanonicalCategory$1(cursorType, pattern) {
|
|
|
87560
87666
|
const tool = match[2] ?? "*";
|
|
87561
87667
|
return `${MCP_CATEGORY_PREFIX}${server}__${tool}`;
|
|
87562
87668
|
}
|
|
87563
|
-
return
|
|
87669
|
+
return lookupOwn({
|
|
87670
|
+
record: CURSOR_TYPE_TO_CANONICAL,
|
|
87671
|
+
key: cursorType
|
|
87672
|
+
}) ?? cursorType.toLowerCase();
|
|
87564
87673
|
}
|
|
87565
|
-
return
|
|
87674
|
+
return lookupOwn({
|
|
87675
|
+
record: CURSOR_TYPE_TO_CANONICAL,
|
|
87676
|
+
key: cursorType
|
|
87677
|
+
}) ?? cursorType.toLowerCase();
|
|
87566
87678
|
}
|
|
87567
87679
|
/**
|
|
87568
87680
|
* Narrow `JSON.parse` output to a plain object before treating it as a Cursor
|
|
@@ -87807,9 +87919,13 @@ function convertCursorToRulesyncPermissions(params) {
|
|
|
87807
87919
|
for (const entry of entries) {
|
|
87808
87920
|
const { toolName: type, pattern } = parseToolPatternEntry(entry);
|
|
87809
87921
|
const canonical = toCanonicalCategory$1(type, pattern);
|
|
87810
|
-
if (!permission[canonical]) permission[canonical] = {};
|
|
87811
87922
|
const canonicalPattern = type === "Mcp" && canonical.startsWith("mcp__") ? "*" : pattern;
|
|
87812
|
-
|
|
87923
|
+
if (isPrototypePollutionKey(canonical) || isPrototypePollutionKey(canonicalPattern)) continue;
|
|
87924
|
+
getOrCreateOwn({
|
|
87925
|
+
record: permission,
|
|
87926
|
+
key: canonical,
|
|
87927
|
+
create: () => ({})
|
|
87928
|
+
})[canonicalPattern] = action;
|
|
87813
87929
|
}
|
|
87814
87930
|
};
|
|
87815
87931
|
processEntries(params.allow, "allow");
|
|
@@ -88736,10 +88852,16 @@ const DEVIN_SCOPE_TO_CANONICAL = {
|
|
|
88736
88852
|
*/
|
|
88737
88853
|
const DEVIN_BARE_ONLY_SCOPES = /* @__PURE__ */ new Set(["web_search"]);
|
|
88738
88854
|
function toDevinScope(canonical) {
|
|
88739
|
-
return
|
|
88855
|
+
return lookupOwn({
|
|
88856
|
+
record: CANONICAL_TO_DEVIN_SCOPE,
|
|
88857
|
+
key: canonical
|
|
88858
|
+
}) ?? canonical;
|
|
88740
88859
|
}
|
|
88741
88860
|
function toCanonicalCategory(devinScope) {
|
|
88742
|
-
return
|
|
88861
|
+
return lookupOwn({
|
|
88862
|
+
record: DEVIN_SCOPE_TO_CANONICAL,
|
|
88863
|
+
key: devinScope
|
|
88864
|
+
}) ?? devinScope;
|
|
88743
88865
|
}
|
|
88744
88866
|
function asDevinRecord(value) {
|
|
88745
88867
|
return isRecord(value) ? { ...value } : {};
|
|
@@ -89091,7 +89213,11 @@ function convertDevinToRulesyncPermissions(params) {
|
|
|
89091
89213
|
const { toolName: scope, pattern } = parseToolPatternEntry(entry);
|
|
89092
89214
|
if (isPrototypePollutionKey(scope) || isPrototypePollutionKey(pattern)) continue;
|
|
89093
89215
|
const canonical = toCanonicalCategory(scope);
|
|
89094
|
-
(
|
|
89216
|
+
getOrCreateOwn({
|
|
89217
|
+
record: permission,
|
|
89218
|
+
key: canonical,
|
|
89219
|
+
create: () => ({})
|
|
89220
|
+
})[pattern] = action;
|
|
89095
89221
|
}
|
|
89096
89222
|
};
|
|
89097
89223
|
processEntries(params.allow, "allow");
|
|
@@ -89397,7 +89523,10 @@ function convertRulesyncToGoosePermissionConfig({ config, logger }) {
|
|
|
89397
89523
|
};
|
|
89398
89524
|
const assigned = /* @__PURE__ */ new Map();
|
|
89399
89525
|
for (const [category, rules] of Object.entries(honorAllToolsOnBash(config.permission))) {
|
|
89400
|
-
const toolName =
|
|
89526
|
+
const toolName = lookupOwn({
|
|
89527
|
+
record: RULESYNC_TO_GOOSE_TOOL_NAME,
|
|
89528
|
+
key: category
|
|
89529
|
+
}) ?? category;
|
|
89401
89530
|
for (const [pattern, action] of Object.entries(rules)) if (pattern !== "*") logger?.warn(`Goose permission.yaml lists whole tool names, so the per-pattern "${action}" rule for "${category}" (pattern "${pattern}") cannot be expressed and was skipped.`);
|
|
89402
89531
|
const { action, downgraded } = failSafeCatchAllAction({
|
|
89403
89532
|
rules,
|
|
@@ -89429,9 +89558,16 @@ function convertGoosePermissionConfigToRulesync(userPermission) {
|
|
|
89429
89558
|
for (const key of GOOSE_PERMISSION_LIST_KEYS) {
|
|
89430
89559
|
const toolNames = isStringArray(userPermission[key]) ? userPermission[key] : [];
|
|
89431
89560
|
const action = GOOSE_LIST_TO_ACTION[key];
|
|
89432
|
-
for (const toolName of toolNames) for (const category of
|
|
89433
|
-
|
|
89434
|
-
|
|
89561
|
+
for (const toolName of toolNames) for (const category of lookupOwn({
|
|
89562
|
+
record: GOOSE_TO_RULESYNC_TOOL_NAMES,
|
|
89563
|
+
key: toolName
|
|
89564
|
+
}) ?? [toolName]) {
|
|
89565
|
+
if (isPrototypePollutionKey(category)) continue;
|
|
89566
|
+
getOrCreateOwn({
|
|
89567
|
+
record: permission,
|
|
89568
|
+
key: category,
|
|
89569
|
+
create: () => ({})
|
|
89570
|
+
})["*"] = action;
|
|
89435
89571
|
}
|
|
89436
89572
|
}
|
|
89437
89573
|
return { permission };
|
|
@@ -89512,7 +89648,10 @@ function buildGrokEntry(category, pattern) {
|
|
|
89512
89648
|
return remainder.length > 0 ? `${GROK_MCP_TOOL}(${remainder})` : GROK_MCP_TOOL;
|
|
89513
89649
|
}
|
|
89514
89650
|
if (category === "mcp") return pattern === "*" || pattern === "" ? GROK_MCP_TOOL : `${GROK_MCP_TOOL}(${pattern})`;
|
|
89515
|
-
const tool =
|
|
89651
|
+
const tool = lookupOwn({
|
|
89652
|
+
record: CATEGORY_TO_GROK_TOOL,
|
|
89653
|
+
key: category
|
|
89654
|
+
});
|
|
89516
89655
|
if (tool === void 0) return null;
|
|
89517
89656
|
return pattern === "*" || pattern === "" ? tool : `${tool}(${toGrokPathPattern(category, pattern)})`;
|
|
89518
89657
|
}
|
|
@@ -89546,7 +89685,10 @@ function parseGrokEntry(entry) {
|
|
|
89546
89685
|
category: "mcp",
|
|
89547
89686
|
pattern: "*"
|
|
89548
89687
|
};
|
|
89549
|
-
const category =
|
|
89688
|
+
const category = lookupOwn({
|
|
89689
|
+
record: GROK_TOOL_TO_CATEGORY,
|
|
89690
|
+
key: tool
|
|
89691
|
+
});
|
|
89550
89692
|
if (category === void 0) return null;
|
|
89551
89693
|
return {
|
|
89552
89694
|
category,
|
|
@@ -89867,11 +90009,17 @@ function parseGrokPermissionArrays(permission) {
|
|
|
89867
90009
|
if ((allow?.length ?? 0) + (deny?.length ?? 0) + (ask?.length ?? 0) + (rules?.length ?? 0) === 0) return null;
|
|
89868
90010
|
const result = {};
|
|
89869
90011
|
const record = ({ category, pattern, action }) => {
|
|
89870
|
-
const bucket =
|
|
89871
|
-
|
|
90012
|
+
const bucket = getOrCreateOwn({
|
|
90013
|
+
record: result,
|
|
90014
|
+
key: category,
|
|
90015
|
+
create: () => ({})
|
|
90016
|
+
});
|
|
89872
90017
|
if (isStricterAction({
|
|
89873
90018
|
action,
|
|
89874
|
-
existing
|
|
90019
|
+
existing: lookupOwn({
|
|
90020
|
+
record: bucket,
|
|
90021
|
+
key: pattern
|
|
90022
|
+
})
|
|
89875
90023
|
})) bucket[pattern] = action;
|
|
89876
90024
|
};
|
|
89877
90025
|
const applyEntries = (entries, action) => {
|
|
@@ -89928,7 +90076,11 @@ function deleteRulesByAction(rules, action) {
|
|
|
89928
90076
|
for (const [pattern, currentAction] of Object.entries(rules)) if (currentAction === action) delete rules[pattern];
|
|
89929
90077
|
}
|
|
89930
90078
|
function ensureCategory(permission, category) {
|
|
89931
|
-
return
|
|
90079
|
+
return getOrCreateOwn({
|
|
90080
|
+
record: permission,
|
|
90081
|
+
key: category,
|
|
90082
|
+
create: () => ({})
|
|
90083
|
+
});
|
|
89932
90084
|
}
|
|
89933
90085
|
function removeEmptyCategories(permission) {
|
|
89934
90086
|
for (const [category, rules] of Object.entries(permission)) if (Object.keys(rules).length === 0) delete permission[category];
|
|
@@ -90571,7 +90723,10 @@ var JuniePermissions = class JuniePermissions extends ToolPermissions {
|
|
|
90571
90723
|
function convertRulesyncToJunieRules({ config, logger, existingRules, overrideSecretFile, overrideRuleDefaults }) {
|
|
90572
90724
|
const ruleLists = {};
|
|
90573
90725
|
for (const [category, patterns] of Object.entries(honorAllToolsOnBash(config.permission))) {
|
|
90574
|
-
const group =
|
|
90726
|
+
const group = lookupOwn({
|
|
90727
|
+
record: CANONICAL_TO_JUNIE_GROUP,
|
|
90728
|
+
key: category
|
|
90729
|
+
});
|
|
90575
90730
|
if (!group) {
|
|
90576
90731
|
if (Object.keys(patterns).length > 0) logger?.warn(`Junie allowlist only models executables/fileEditing/mcpTools/readOutsideProject (canonical bash/edit/write/read/mcp); '${category}' rules cannot be represented and were skipped.`);
|
|
90577
90732
|
continue;
|
|
@@ -90969,7 +91124,10 @@ const CATEGORY_TO_KIMI_CODE_TOOL = {
|
|
|
90969
91124
|
function buildKimiCodePattern(category, pattern) {
|
|
90970
91125
|
if (category.startsWith("mcp__")) return pattern === "*" || pattern === "" ? category : null;
|
|
90971
91126
|
if (category === "mcp") return pattern === "*" || pattern === "" ? "mcp__*" : `mcp__${pattern}`;
|
|
90972
|
-
const tool =
|
|
91127
|
+
const tool = lookupOwn({
|
|
91128
|
+
record: CATEGORY_TO_KIMI_CODE_TOOL,
|
|
91129
|
+
key: category
|
|
91130
|
+
});
|
|
90973
91131
|
if (!tool) return null;
|
|
90974
91132
|
return pattern === "*" || pattern === "" ? tool : `${tool}(${pattern})`;
|
|
90975
91133
|
}
|
|
@@ -91065,7 +91223,10 @@ function canonicalToKimiCodeRules({ config, logger }) {
|
|
|
91065
91223
|
const canonicalRules = [];
|
|
91066
91224
|
const overrideRules = config["kimi-code"]?.rules ?? [];
|
|
91067
91225
|
for (const [category, rules] of Object.entries(config.permission)) {
|
|
91068
|
-
if (!(category === "mcp" || category.startsWith("mcp__") ||
|
|
91226
|
+
if (!(category === "mcp" || category.startsWith("mcp__") || lookupOwn({
|
|
91227
|
+
record: CATEGORY_TO_KIMI_CODE_TOOL,
|
|
91228
|
+
key: category
|
|
91229
|
+
}) !== void 0)) {
|
|
91069
91230
|
if (Object.keys(rules).length > 0) logger?.warn(`Kimi Code permissions: skipping unsupported category "${category}".`);
|
|
91070
91231
|
continue;
|
|
91071
91232
|
}
|
|
@@ -91882,9 +92043,16 @@ var KiroCapabilityPermissions = class KiroCapabilityPermissions extends ToolPerm
|
|
|
91882
92043
|
if (!isImportableRule(rule) || !isRecord(rule) || !isPermissionAction(rule.effect)) continue;
|
|
91883
92044
|
const category = KIRO_CAPABILITY_TO_CANONICAL[rule.capability];
|
|
91884
92045
|
const patterns = rule.match === void 0 ? ["*"] : isStringArray(rule.match) ? rule.match : [];
|
|
91885
|
-
const categoryRules =
|
|
92046
|
+
const categoryRules = getOrCreateOwn({
|
|
92047
|
+
record: permission,
|
|
92048
|
+
key: category,
|
|
92049
|
+
create: () => ({})
|
|
92050
|
+
});
|
|
91886
92051
|
for (const pattern of patterns) {
|
|
91887
|
-
const previous =
|
|
92052
|
+
const previous = lookupOwn({
|
|
92053
|
+
record: categoryRules,
|
|
92054
|
+
key: pattern
|
|
92055
|
+
});
|
|
91888
92056
|
if (isStricterAction({
|
|
91889
92057
|
action: rule.effect,
|
|
91890
92058
|
existing: previous
|
|
@@ -91902,7 +92070,10 @@ var KiroCapabilityPermissions = class KiroCapabilityPermissions extends ToolPerm
|
|
|
91902
92070
|
function buildKiroCapabilityRules({ config, logger }) {
|
|
91903
92071
|
const buckets = /* @__PURE__ */ new Map();
|
|
91904
92072
|
for (const [category, rules] of Object.entries(honorAllToolsOnBash(config.permission))) {
|
|
91905
|
-
const capability =
|
|
92073
|
+
const capability = lookupOwn({
|
|
92074
|
+
record: CANONICAL_TO_KIRO_CAPABILITY,
|
|
92075
|
+
key: category
|
|
92076
|
+
});
|
|
91906
92077
|
if (capability === void 0) {
|
|
91907
92078
|
logger?.warn(`Kiro permissions.yaml has no capability for category: ${category}. Skipping.`);
|
|
91908
92079
|
continue;
|
|
@@ -92062,7 +92233,10 @@ function toLettacodeBashPayload({ pattern, action, logger }) {
|
|
|
92062
92233
|
return prefix === "" ? "" : `${prefix}${PREFIX_SUFFIX}`;
|
|
92063
92234
|
}
|
|
92064
92235
|
function toLettacodeRule({ category, pattern, action, logger }) {
|
|
92065
|
-
const tool =
|
|
92236
|
+
const tool = lookupOwn({
|
|
92237
|
+
record: CATEGORY_TO_LETTACODE_TOOL,
|
|
92238
|
+
key: category
|
|
92239
|
+
});
|
|
92066
92240
|
if (tool === void 0) return null;
|
|
92067
92241
|
const payload = category === "bash" ? toLettacodeBashPayload({
|
|
92068
92242
|
pattern,
|
|
@@ -92217,11 +92391,17 @@ var LettacodePermissions = class LettacodePermissions extends ToolPermissions {
|
|
|
92217
92391
|
for (const [key, action] of Object.entries(LIST_TO_ACTION)) for (const rule of toStringArray$1(permissions[key])) {
|
|
92218
92392
|
const parsed = fromLettacodeRule(rule);
|
|
92219
92393
|
if (parsed === null || isPrototypePollutionKey(parsed.pattern)) continue;
|
|
92220
|
-
const rules =
|
|
92221
|
-
|
|
92394
|
+
const rules = getOrCreateOwn({
|
|
92395
|
+
record: permission,
|
|
92396
|
+
key: parsed.category,
|
|
92397
|
+
create: () => ({})
|
|
92398
|
+
});
|
|
92222
92399
|
if (isStricterAction({
|
|
92223
92400
|
action,
|
|
92224
|
-
existing
|
|
92401
|
+
existing: lookupOwn({
|
|
92402
|
+
record: rules,
|
|
92403
|
+
key: parsed.pattern
|
|
92404
|
+
})
|
|
92225
92405
|
})) rules[parsed.pattern] = action;
|
|
92226
92406
|
}
|
|
92227
92407
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({ permission }, null, 2) });
|
|
@@ -92408,7 +92588,13 @@ function getOpencodeMcpServerNames(mcp) {
|
|
|
92408
92588
|
return isRecord(lowered) ? Object.keys(lowered) : [];
|
|
92409
92589
|
}
|
|
92410
92590
|
function toOpencodePermissionKey(canonical) {
|
|
92411
|
-
return
|
|
92591
|
+
return lookupOwn({
|
|
92592
|
+
record: CANONICAL_TO_OPENCODE_PERMISSION_KEYS,
|
|
92593
|
+
key: canonical
|
|
92594
|
+
}) ?? lookupOwn({
|
|
92595
|
+
record: CANONICAL_TO_OPENCODE_FOLDED_PERMISSION_KEYS,
|
|
92596
|
+
key: canonical
|
|
92597
|
+
}) ?? canonical;
|
|
92412
92598
|
}
|
|
92413
92599
|
/**
|
|
92414
92600
|
* Merge two canonical pattern maps that land on the same OpenCode key.
|
|
@@ -92432,7 +92618,10 @@ function mergePatternRules(base, incoming) {
|
|
|
92432
92618
|
return Object.fromEntries(Object.entries(merged).toSorted(([, a], [, b]) => PERMISSION_ACTION_PRIORITY[a] - PERMISSION_ACTION_PRIORITY[b]));
|
|
92433
92619
|
}
|
|
92434
92620
|
function toCanonicalPermissionKey(opencodeKey) {
|
|
92435
|
-
return
|
|
92621
|
+
return lookupOwn({
|
|
92622
|
+
record: OPENCODE_TO_CANONICAL_PERMISSION_KEYS,
|
|
92623
|
+
key: opencodeKey
|
|
92624
|
+
}) ?? opencodeKey;
|
|
92436
92625
|
}
|
|
92437
92626
|
function isSharedPermissionCategory(category) {
|
|
92438
92627
|
return category === "*" || CANONICAL_PERMISSION_CATEGORIES$1.has(category) || category.startsWith("mcp__");
|
|
@@ -93714,7 +93903,14 @@ function importPoolTool({ toolConfig, bucket }) {
|
|
|
93714
93903
|
const denied = isStringArray(toolConfig[POOL_DENY_KEY]) ? toolConfig[POOL_DENY_KEY] : [];
|
|
93715
93904
|
const allowed = isStringArray(toolConfig[POOL_ALLOW_KEY]) ? toolConfig[POOL_ALLOW_KEY] : [];
|
|
93716
93905
|
for (const pattern of denied.filter(isUsablePattern)) bucket[pattern] = "deny";
|
|
93717
|
-
for (const pattern of allowed.filter(isUsablePattern)) bucket[pattern]
|
|
93906
|
+
for (const pattern of allowed.filter(isUsablePattern)) if (!Object.hasOwn(bucket, pattern)) bucket[pattern] = "allow";
|
|
93907
|
+
}
|
|
93908
|
+
/**
|
|
93909
|
+
* Record `allow` for a path no deny has claimed. The check is on own
|
|
93910
|
+
* properties, so a path such as `toString` is not mistaken for a stored rule.
|
|
93911
|
+
*/
|
|
93912
|
+
function allowUnlessSet(bucket, path) {
|
|
93913
|
+
if (!Object.hasOwn(bucket, path)) bucket[path] = "allow";
|
|
93718
93914
|
}
|
|
93719
93915
|
/**
|
|
93720
93916
|
* Convert Pool's `tools` and `paths` blocks back into a rulesync config.
|
|
@@ -93749,8 +93945,8 @@ function convertPoolToRulesync({ tools, paths }) {
|
|
|
93749
93945
|
for (const entry of allowEntries) {
|
|
93750
93946
|
const path = pathOfEntry(entry);
|
|
93751
93947
|
if (path === void 0) continue;
|
|
93752
|
-
bucketFor(READ_CATEGORY)
|
|
93753
|
-
if (isRecord(entry) && entry[POOL_WRITE_KEY] === true) for (const category of WRITE_CATEGORIES) bucketFor(category)
|
|
93948
|
+
allowUnlessSet(bucketFor(READ_CATEGORY), path);
|
|
93949
|
+
if (isRecord(entry) && entry[POOL_WRITE_KEY] === true) for (const category of WRITE_CATEGORIES) allowUnlessSet(bucketFor(category), path);
|
|
93754
93950
|
}
|
|
93755
93951
|
return { permission };
|
|
93756
93952
|
}
|
|
@@ -93879,7 +94075,10 @@ function toQoderRule({ category, pattern, logger }) {
|
|
|
93879
94075
|
logger?.warn(`Qoder permissions: skipping the '${category}' rule '${pattern}' — Qoder matches MCP tools by name only.`);
|
|
93880
94076
|
return null;
|
|
93881
94077
|
}
|
|
93882
|
-
const tool =
|
|
94078
|
+
const tool = lookupOwn({
|
|
94079
|
+
record: CATEGORY_TO_QODER_TOOL,
|
|
94080
|
+
key: category
|
|
94081
|
+
});
|
|
93883
94082
|
if (tool === void 0) return null;
|
|
93884
94083
|
if (pattern === "*") return tool;
|
|
93885
94084
|
return `${QODER_PATH_RULE_ALIASES[tool] ?? tool}(${pattern})`;
|
|
@@ -94165,11 +94364,17 @@ var QoderPermissions = class QoderPermissions extends ToolPermissions {
|
|
|
94165
94364
|
for (const action of ACTIONS) for (const rule of toStringArray$1(permissions[action])) {
|
|
94166
94365
|
const parsed = fromQoderRule(rule);
|
|
94167
94366
|
if (parsed === null || isPrototypePollutionKey(parsed.category) || isPrototypePollutionKey(parsed.pattern)) continue;
|
|
94168
|
-
const rules =
|
|
94169
|
-
|
|
94367
|
+
const rules = getOrCreateOwn({
|
|
94368
|
+
record: permission,
|
|
94369
|
+
key: parsed.category,
|
|
94370
|
+
create: () => ({})
|
|
94371
|
+
});
|
|
94170
94372
|
if (isStricterAction({
|
|
94171
94373
|
action,
|
|
94172
|
-
existing
|
|
94374
|
+
existing: lookupOwn({
|
|
94375
|
+
record: rules,
|
|
94376
|
+
key: parsed.pattern
|
|
94377
|
+
})
|
|
94173
94378
|
})) rules[parsed.pattern] = action;
|
|
94174
94379
|
}
|
|
94175
94380
|
const outputStyle = settings[QODER_OUTPUT_STYLE_KEY];
|
|
@@ -94233,10 +94438,16 @@ const CANONICAL_TO_QWEN_TOOL_NAMES = {
|
|
|
94233
94438
|
};
|
|
94234
94439
|
const QWEN_TO_CANONICAL_TOOL_NAMES = invertRecord(CANONICAL_TO_QWEN_TOOL_NAMES);
|
|
94235
94440
|
function toQwenToolName(canonical) {
|
|
94236
|
-
return
|
|
94441
|
+
return lookupOwn({
|
|
94442
|
+
record: CANONICAL_TO_QWEN_TOOL_NAMES,
|
|
94443
|
+
key: canonical
|
|
94444
|
+
}) ?? canonical;
|
|
94237
94445
|
}
|
|
94238
94446
|
function toCanonicalToolName$3(qwenName) {
|
|
94239
|
-
return
|
|
94447
|
+
return lookupOwn({
|
|
94448
|
+
record: QWEN_TO_CANONICAL_TOOL_NAMES,
|
|
94449
|
+
key: qwenName
|
|
94450
|
+
}) ?? qwenName;
|
|
94240
94451
|
}
|
|
94241
94452
|
function parseQwenPermissionEntry(entry, options = {}) {
|
|
94242
94453
|
const parenIndex = entry.indexOf("(");
|
|
@@ -94852,15 +95063,23 @@ function convertQwenToRulesyncPermissions(params) {
|
|
|
94852
95063
|
if (!parsed.ok) {
|
|
94853
95064
|
if (action === "deny") {
|
|
94854
95065
|
const canonical = toCanonicalToolName$3(parsed.toolName);
|
|
94855
|
-
if (
|
|
94856
|
-
|
|
95066
|
+
if (isPrototypePollutionKey(canonical)) continue;
|
|
95067
|
+
getOrCreateOwn({
|
|
95068
|
+
record: permission,
|
|
95069
|
+
key: canonical,
|
|
95070
|
+
create: () => ({})
|
|
95071
|
+
})["*"] = action;
|
|
94857
95072
|
}
|
|
94858
95073
|
continue;
|
|
94859
95074
|
}
|
|
94860
95075
|
const { toolName, pattern } = parsed;
|
|
94861
95076
|
const canonical = toCanonicalToolName$3(toolName);
|
|
94862
|
-
if (
|
|
94863
|
-
|
|
95077
|
+
if (isPrototypePollutionKey(canonical) || isPrototypePollutionKey(pattern)) continue;
|
|
95078
|
+
getOrCreateOwn({
|
|
95079
|
+
record: permission,
|
|
95080
|
+
key: canonical,
|
|
95081
|
+
create: () => ({})
|
|
95082
|
+
})[pattern] = action;
|
|
94864
95083
|
}
|
|
94865
95084
|
};
|
|
94866
95085
|
processEntries(params.allow, "allow");
|
|
@@ -94924,15 +95143,24 @@ const REASONIX_TO_CANONICAL_TOOL_NAMES = {
|
|
|
94924
95143
|
};
|
|
94925
95144
|
/** Every Reasonix tool name a canonical category owns on generate. */
|
|
94926
95145
|
function reasonixToolNamesOf(canonical) {
|
|
94927
|
-
const legacy =
|
|
95146
|
+
const legacy = lookupOwn({
|
|
95147
|
+
record: CANONICAL_TO_LEGACY_REASONIX_TOOL_NAMES,
|
|
95148
|
+
key: canonical
|
|
95149
|
+
});
|
|
94928
95150
|
const current = toReasonixToolName(canonical);
|
|
94929
95151
|
return legacy === void 0 ? [current] : [current, legacy];
|
|
94930
95152
|
}
|
|
94931
95153
|
function toReasonixToolName(canonical) {
|
|
94932
|
-
return
|
|
95154
|
+
return lookupOwn({
|
|
95155
|
+
record: CANONICAL_TO_REASONIX_TOOL_NAMES,
|
|
95156
|
+
key: canonical
|
|
95157
|
+
}) ?? canonical;
|
|
94933
95158
|
}
|
|
94934
95159
|
function toCanonicalToolName$2(reasonixName) {
|
|
94935
|
-
return
|
|
95160
|
+
return lookupOwn({
|
|
95161
|
+
record: REASONIX_TO_CANONICAL_TOOL_NAMES,
|
|
95162
|
+
key: reasonixName
|
|
95163
|
+
}) ?? reasonixName;
|
|
94936
95164
|
}
|
|
94937
95165
|
/**
|
|
94938
95166
|
* Whether an entry uses the first-class `Tool=<literal>` exact-command form
|
|
@@ -95555,8 +95783,12 @@ function convertReasonixToRulesyncPermissions(params) {
|
|
|
95555
95783
|
for (const entry of entries) {
|
|
95556
95784
|
const { toolName, pattern } = parseToolPatternEntry(entry);
|
|
95557
95785
|
const canonical = toCanonicalToolName$2(toolName);
|
|
95558
|
-
if (
|
|
95559
|
-
|
|
95786
|
+
if (isPrototypePollutionKey(canonical) || isPrototypePollutionKey(pattern)) continue;
|
|
95787
|
+
getOrCreateOwn({
|
|
95788
|
+
record: permission,
|
|
95789
|
+
key: canonical,
|
|
95790
|
+
create: () => ({})
|
|
95791
|
+
})[pattern] = action;
|
|
95560
95792
|
}
|
|
95561
95793
|
};
|
|
95562
95794
|
processEntries(params.allow, "allow");
|
|
@@ -96108,7 +96340,10 @@ function convertRulesyncToRovodevToolPermissions({ config, logger }) {
|
|
|
96108
96340
|
if (bash) toolPermissions.bash = bash;
|
|
96109
96341
|
continue;
|
|
96110
96342
|
}
|
|
96111
|
-
const toolKeys =
|
|
96343
|
+
const toolKeys = lookupOwn({
|
|
96344
|
+
record: CATEGORY_TO_TOOL_KEYS,
|
|
96345
|
+
key: category
|
|
96346
|
+
});
|
|
96112
96347
|
if (!toolKeys) {
|
|
96113
96348
|
logger?.warn(`Rovo Dev permissions have no target for the "${category}" category. Skipping it.`);
|
|
96114
96349
|
markSharedToolKeyRestriction({
|
|
@@ -96256,7 +96491,7 @@ function convertRovodevToolPermissionsToRulesync(toolPermissions) {
|
|
|
96256
96491
|
}
|
|
96257
96492
|
if (isStringArray(toolPermissions.allowedExternalPaths)) for (const path of toolPermissions.allowedExternalPaths) {
|
|
96258
96493
|
permission.read ??= {};
|
|
96259
|
-
permission.read[path]
|
|
96494
|
+
if (!Object.hasOwn(permission.read, path)) permission.read[path] = "allow";
|
|
96260
96495
|
}
|
|
96261
96496
|
return { permission };
|
|
96262
96497
|
}
|
|
@@ -97993,11 +98228,17 @@ var TraePermissions = class TraePermissions extends ToolPermissions {
|
|
|
97993
98228
|
const permission = {};
|
|
97994
98229
|
const put = (category, pattern, action) => {
|
|
97995
98230
|
if (isPrototypePollutionKey(category) || isPrototypePollutionKey(pattern)) return;
|
|
97996
|
-
const rules =
|
|
97997
|
-
|
|
98231
|
+
const rules = getOrCreateOwn({
|
|
98232
|
+
record: permission,
|
|
98233
|
+
key: category,
|
|
98234
|
+
create: () => ({})
|
|
98235
|
+
});
|
|
97998
98236
|
if (isStricterAction({
|
|
97999
98237
|
action,
|
|
98000
|
-
existing
|
|
98238
|
+
existing: lookupOwn({
|
|
98239
|
+
record: rules,
|
|
98240
|
+
key: pattern
|
|
98241
|
+
})
|
|
98001
98242
|
})) rules[pattern] = action;
|
|
98002
98243
|
};
|
|
98003
98244
|
for (const [key, value] of Object.entries(readRecord(customProfiles, COMMAND_RULES_PATH))) {
|
|
@@ -100340,7 +100581,10 @@ function toZedToolName(canonical) {
|
|
|
100340
100581
|
const address = toolParts.length > 0 ? `${server}:${toolParts.join("__")}` : server ?? "";
|
|
100341
100582
|
return `${MCP_ZED_PREFIX}${address}`;
|
|
100342
100583
|
}
|
|
100343
|
-
return
|
|
100584
|
+
return lookupOwn({
|
|
100585
|
+
record: CANONICAL_TO_ZED_TOOL_NAMES,
|
|
100586
|
+
key: canonical
|
|
100587
|
+
}) ?? canonical;
|
|
100344
100588
|
}
|
|
100345
100589
|
function toCanonicalToolName(zedName) {
|
|
100346
100590
|
if (zedName.startsWith(MCP_ZED_PREFIX)) {
|
|
@@ -100348,7 +100592,10 @@ function toCanonicalToolName(zedName) {
|
|
|
100348
100592
|
const address = toolParts.length > 0 ? `${server}__${toolParts.join(":")}` : server ?? "";
|
|
100349
100593
|
return `${MCP_CATEGORY_PREFIX}${address}`;
|
|
100350
100594
|
}
|
|
100351
|
-
return
|
|
100595
|
+
return lookupOwn({
|
|
100596
|
+
record: ZED_TO_CANONICAL_TOOL_NAMES,
|
|
100597
|
+
key: zedName
|
|
100598
|
+
}) ?? zedName;
|
|
100352
100599
|
}
|
|
100353
100600
|
/**
|
|
100354
100601
|
* Zed matches `always_allow`/`always_deny`/`always_confirm` regexes against the
|
|
@@ -100660,7 +100907,10 @@ var ZedPermissions = class ZedPermissions extends ToolPermissions {
|
|
|
100660
100907
|
const globalDefault = parsed.success ? parsed.data.default : void 0;
|
|
100661
100908
|
const permission = {};
|
|
100662
100909
|
const ensure = (category) => {
|
|
100663
|
-
const existing =
|
|
100910
|
+
const existing = lookupOwn({
|
|
100911
|
+
record: permission,
|
|
100912
|
+
key: category
|
|
100913
|
+
});
|
|
100664
100914
|
if (existing) return existing;
|
|
100665
100915
|
const created = {};
|
|
100666
100916
|
permission[category] = created;
|
|
@@ -101287,7 +101537,7 @@ function resolveToolOutputRoot({ outputRoot, toolTarget, global }) {
|
|
|
101287
101537
|
async function convertFromTool(params) {
|
|
101288
101538
|
resetRunWarningState();
|
|
101289
101539
|
const packagingTarget = [params.fromTool, ...params.toTools].find(isPackagingToolTarget);
|
|
101290
|
-
if (packagingTarget) throw new Error(`Plugin packaging target '${packagingTarget}' is not supported by convert. Use import and generate with
|
|
101540
|
+
if (packagingTarget) throw new Error(`Plugin packaging target '${packagingTarget}' is not supported by convert. Use import with the plugin directory as outputRoot (--output-root) and generate with it in outputRoots (--output-roots).`);
|
|
101291
101541
|
const ctx = params;
|
|
101292
101542
|
const [rulesCount, ignoreCount, mcpCount, commandsCount, subagentsCount, skillsCount, hooksCount, permissionsCount, checksCount] = [
|
|
101293
101543
|
await runFeatureConvert(ctx, buildRulesStrategy(ctx)),
|
|
@@ -104535,12 +104785,6 @@ Object.defineProperty(exports, "isFileSystemError", {
|
|
|
104535
104785
|
return isFileSystemError;
|
|
104536
104786
|
}
|
|
104537
104787
|
});
|
|
104538
|
-
Object.defineProperty(exports, "isPackagingToolTarget", {
|
|
104539
|
-
enumerable: true,
|
|
104540
|
-
get: function() {
|
|
104541
|
-
return isPackagingToolTarget;
|
|
104542
|
-
}
|
|
104543
|
-
});
|
|
104544
104788
|
Object.defineProperty(exports, "isRecord", {
|
|
104545
104789
|
enumerable: true,
|
|
104546
104790
|
get: function() {
|
|
@@ -104607,6 +104851,12 @@ Object.defineProperty(exports, "parseCommaSeparatedList", {
|
|
|
104607
104851
|
return parseCommaSeparatedList;
|
|
104608
104852
|
}
|
|
104609
104853
|
});
|
|
104854
|
+
Object.defineProperty(exports, "parseConfigFileContent", {
|
|
104855
|
+
enumerable: true,
|
|
104856
|
+
get: function() {
|
|
104857
|
+
return parseConfigFileContent;
|
|
104858
|
+
}
|
|
104859
|
+
});
|
|
104610
104860
|
Object.defineProperty(exports, "parseJsonc", {
|
|
104611
104861
|
enumerable: true,
|
|
104612
104862
|
get: function() {
|