@hasna/skills 0.10.23 → 0.10.24

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/README.md CHANGED
@@ -431,6 +431,12 @@ directory within your home, add `--allow-root-aliases` to hook installation and
431
431
  native migration. The plan records and rechecks the exact link and target;
432
432
  links inside skill contents or configuration files remain refused.
433
433
 
434
+ On macOS, the OS-owned `/var`, `/tmp` and `/etc` aliases into `/private` are
435
+ accepted only while their exact targets and protected parent directories verify.
436
+ The protection check refuses extended ACLs and unavailable native ACL inspection.
437
+ Inventory reports canonical paths and deduplicates their alias spellings. This
438
+ does not permit other root-owned links or user-controlled ancestor links.
439
+
434
440
  Cooperating installers can use the root package's
435
441
  `planClaudeStopHookUpdate({ expectedSettingsSha256, replacement })`, then
436
442
  `applyAgentIntegration(plan)`, to write an explicitly authorized Claude settings
@@ -519,8 +525,12 @@ Only manifest version metadata may vary. Reviewed app declarations use the impli
519
525
  `id`, optional `category`, and optional boolean `required`. The entire canonical app
520
526
  file and its presence stay bound; it preserves existing connector availability and
521
527
  native capability hints. Changed or unknown app fields, custom references, native
522
- hooks, commands and MCP controls refuse. Name controls suppress Skills, not every
523
- plugin capability. No prompt hook starts an app server to obtain this receipt:
528
+ hooks, commands and unsupported MCP controls refuse. Referenced own-root
529
+ `./.mcp.json` declarations may preserve local stdio servers with Codex's typed
530
+ tool, approval, timeout and environment metadata. Their complete fingerprints
531
+ and presence remain bound; review never executes commands or resolves environment
532
+ values. Name controls suppress Skills, not every plugin capability.
533
+ No prompt hook starts an app server to obtain this receipt:
524
534
  native startup can maintain caches. The explicit capture starts and closes only its
525
535
  own bounded native client; it does not reload an existing agent session.
526
536
  Neither a receipt nor a config write proves an existing session adopted new rules;
package/bin/index.js CHANGED
@@ -36868,7 +36868,7 @@ var package_default;
36868
36868
  var init_package = __esm(() => {
36869
36869
  package_default = {
36870
36870
  name: "@hasna/skills",
36871
- version: "0.10.23",
36871
+ version: "0.10.24",
36872
36872
  description: "Skills library for AI coding agents",
36873
36873
  type: "module",
36874
36874
  bin: {
@@ -51240,27 +51240,62 @@ function mcpControls(path, read2) {
51240
51240
  const object5 = (v) => Boolean(v && typeof v === "object" && !Array.isArray(v));
51241
51241
  const textField = (v, empty = false) => typeof v === "string" && (empty || Boolean(v.trim())) && Buffer.byteLength(v) <= 16384 && !/[\x00-\x1f\x7f]/.test(v);
51242
51242
  const envName = (v) => typeof v === "string" && /^[A-Za-z_][A-Za-z0-9_]{0,255}$/.test(v);
51243
+ const optional = (v, valid) => v === undefined || v === null || valid(v);
51244
+ const stringList = (v) => Array.isArray(v) && v.length <= 256 && v.every((item) => textField(item));
51245
+ const approval = (v) => ["auto", "prompt", "writes", "approve"].includes(v);
51246
+ const duration = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0;
51247
+ const positiveInteger = (v) => Number.isSafeInteger(v) && v > 0;
51248
+ const fields = [
51249
+ "command",
51250
+ "args",
51251
+ "cwd",
51252
+ "env",
51253
+ "env_vars",
51254
+ "enabled",
51255
+ "required",
51256
+ "environment_id",
51257
+ "startup_readiness",
51258
+ "supports_parallel_tool_calls",
51259
+ "tool_input_schema_max_bytes",
51260
+ "startup_timeout_sec",
51261
+ "startup_timeout_ms",
51262
+ "tool_timeout_sec",
51263
+ "default_tools_approval_mode",
51264
+ "enabled_tools",
51265
+ "disabled_tools",
51266
+ "omit_tools_from",
51267
+ "tools",
51268
+ "name"
51269
+ ];
51243
51270
  if (!object5(value) || Object.keys(value).length !== 1 || !object5(value.mcpServers))
51244
51271
  refuse2();
51245
51272
  const servers = Object.entries(value.mcpServers);
51246
51273
  if (!servers.length || servers.length > 256)
51247
51274
  refuse2();
51248
51275
  for (const [name, server] of servers) {
51249
- if (!identifier2(name) || !object5(server) || !Object.keys(server).every((key) => ["command", "args", "cwd", "env_vars"].includes(key)) || !textField(server.command) || server.args !== undefined && (!Array.isArray(server.args) || server.args.length > 256 || !server.args.every((arg) => textField(arg, true))) || server.cwd !== undefined && !textField(server.cwd))
51276
+ if (!identifier2(name) || !object5(server) || !Object.keys(server).every((key) => fields.includes(key)) || !textField(server.command) || !optional(server.args, (v) => Array.isArray(v) && v.length <= 256 && v.every((arg) => textField(arg, true))) || !optional(server.cwd, textField) || !optional(server.env, (v) => object5(v) && Object.keys(v).length <= 256 && Object.entries(v).every(([key, value2]) => envName(key) && textField(value2, true))) || !["enabled", "required", "supports_parallel_tool_calls"].every((key) => optional(server[key], (v) => typeof v === "boolean")) || !optional(server.environment_id, (v) => v === "local") || !optional(server.startup_readiness, (v) => ["connection", "catalog"].includes(v)) || !optional(server.tool_input_schema_max_bytes, positiveInteger) || !["startup_timeout_sec", "tool_timeout_sec"].every((key) => optional(server[key], duration)) || !optional(server.startup_timeout_ms, (v) => Number.isSafeInteger(v) && v >= 0) || !optional(server.default_tools_approval_mode, approval) || !["enabled_tools", "disabled_tools"].every((key) => optional(server[key], stringList)) || !optional(server.omit_tools_from, (v) => Array.isArray(v) && v.length <= 256 && v.every((item) => ["code_mode", "deferred", "direct"].includes(item))) || !optional(server.name, textField))
51250
51277
  refuse2();
51251
- if (server.env_vars !== undefined) {
51278
+ if (server.env_vars !== undefined && server.env_vars !== null) {
51252
51279
  if (!Array.isArray(server.env_vars) || server.env_vars.length > 256)
51253
51280
  refuse2();
51254
51281
  const names = server.env_vars.map((entry) => {
51255
51282
  if (envName(entry))
51256
51283
  return entry;
51257
- if (!object5(entry) || !Object.keys(entry).every((key) => ["name", "source"].includes(key)) || !envName(entry.name) || entry.source !== undefined && entry.source !== "local")
51284
+ if (!object5(entry) || !Object.keys(entry).every((key) => ["name", "source"].includes(key)) || !envName(entry.name) || !optional(entry.source, (v) => v === "local"))
51258
51285
  refuse2();
51259
51286
  return entry.name;
51260
51287
  });
51261
51288
  if (new Set(names).size !== names.length)
51262
51289
  refuse2();
51263
51290
  }
51291
+ if (server.tools !== undefined && server.tools !== null) {
51292
+ if (!object5(server.tools) || Object.keys(server.tools).length > 256)
51293
+ refuse2();
51294
+ for (const [tool, config] of Object.entries(server.tools)) {
51295
+ if (!textField(tool) || !object5(config) || !Object.keys(config).every((key) => ["approval_mode", "output_token_limit"].includes(key)) || !optional(config.approval_mode, approval) || !optional(config.output_token_limit, positiveInteger))
51296
+ refuse2();
51297
+ }
51298
+ }
51264
51299
  }
51265
51300
  return sha2562;
51266
51301
  }
@@ -52121,10 +52156,75 @@ import { isDeepStrictEqual as isDeepStrictEqual4 } from "util";
52121
52156
  import { existsSync as existsSync12, lstatSync as lstatSync15, statSync as statSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync13, readdirSync as readdirSync8, opendirSync as opendirSync3, readlinkSync as readlinkSync4, realpathSync as realpathSync6, renameSync as renameSync2, rmdirSync as rmdirSync2, writeFileSync as writeFileSync4, unlinkSync as unlinkSync3, chmodSync, openSync as openSync8, closeSync as closeSync8, fsyncSync as fsyncSync2, fstatSync as fstatSync8, readSync as readSync6, constants as constants8, linkSync } from "fs";
52122
52157
  import { dirname as dirname11, join as join21, resolve as resolve11, relative as relative5, isAbsolute as isAbsolute11, sep as sep5 } from "path";
52123
52158
  import { homedir as homedir3 } from "os";
52159
+ function hasNoDarwinAcl(fd) {
52160
+ try {
52161
+ const { dlopen, read: read2 } = __require("bun:ffi");
52162
+ const library = dlopen("/usr/lib/libSystem.B.dylib", {
52163
+ acl_get_fd: { args: ["i32"], returns: "ptr" },
52164
+ acl_free: { args: ["ptr"], returns: "i32" },
52165
+ __error: { args: [], returns: "ptr" }
52166
+ });
52167
+ try {
52168
+ const errno = library.symbols.__error();
52169
+ if (!errno)
52170
+ return false;
52171
+ const acl = library.symbols.acl_get_fd(fd);
52172
+ if (acl) {
52173
+ library.symbols.acl_free(acl);
52174
+ return false;
52175
+ }
52176
+ return (acl === null || acl === 0) && read2.i32(errno) === 2;
52177
+ } finally {
52178
+ library.close();
52179
+ }
52180
+ } catch {
52181
+ return false;
52182
+ }
52183
+ }
52184
+ function isSystemRootAlias(path) {
52185
+ if (process.platform !== "darwin" || !["/var", "/tmp", "/etc"].includes(path))
52186
+ return false;
52187
+ const target = `/private${path}`, link2 = `private${path}`;
52188
+ const identity5 = (stat) => `${stat.dev}:${stat.ino}:${stat.ctimeNs}:${stat.uid}:${stat.mode}`;
52189
+ try {
52190
+ const before2 = lstatSync15(path, { bigint: true });
52191
+ if (!before2.isSymbolicLink() || before2.uid !== 0n || readlinkSync4(path) !== link2)
52192
+ return false;
52193
+ const directories = ["/", "/private", target].map((directory) => {
52194
+ const stat = lstatSync15(directory, { bigint: true });
52195
+ if (!stat.isDirectory() || stat.uid !== 0n)
52196
+ throw new Error("Untrusted system alias directory");
52197
+ if (directory !== "/private/tmp") {
52198
+ if ((stat.mode & 0o022n) !== 0n)
52199
+ throw new Error("Writable system alias directory");
52200
+ } else if ((stat.mode & 0o1000n) === 0n)
52201
+ throw new Error("Unprotected system temporary directory");
52202
+ const fd = openSync8(directory, constants8.O_RDONLY | constants8.O_DIRECTORY | constants8.O_NOFOLLOW);
52203
+ try {
52204
+ if (identity5(fstatSync8(fd, { bigint: true })) !== identity5(stat) || !hasNoDarwinAcl(fd) || identity5(fstatSync8(fd, { bigint: true })) !== identity5(stat))
52205
+ throw new Error("Unverified system alias directory ACL");
52206
+ } finally {
52207
+ closeSync8(fd);
52208
+ }
52209
+ return { directory, identity: identity5(stat) };
52210
+ });
52211
+ return realpathSync6(path) === target && readlinkSync4(path) === link2 && identity5(lstatSync15(path, { bigint: true })) === identity5(before2) && directories.every((item) => identity5(lstatSync15(item.directory, { bigint: true })) === item.identity);
52212
+ } catch {
52213
+ return false;
52214
+ }
52215
+ }
52216
+ function canonicalSystemPath(path) {
52217
+ const absolute = resolve11(path), root = `/${absolute.split(sep5)[1]}`;
52218
+ if (process.platform !== "darwin" || !["/var", "/tmp", "/etc"].includes(root) || !lstatSync15(root, { throwIfNoEntry: false })?.isSymbolicLink())
52219
+ return absolute;
52220
+ if (!isSystemRootAlias(root))
52221
+ throw new Error(`Refusing symlink path: ${root}`);
52222
+ return `/private${absolute}`;
52223
+ }
52124
52224
  function assertSafePath(path) {
52125
52225
  let cursor = resolve11(path);
52126
52226
  while (true) {
52127
- if (lstatSync15(cursor, { throwIfNoEntry: false })?.isSymbolicLink())
52227
+ if (lstatSync15(cursor, { throwIfNoEntry: false })?.isSymbolicLink() && !isSystemRootAlias(cursor))
52128
52228
  throw new Error(`Refusing symlink path: ${cursor}`);
52129
52229
  const parent = dirname11(cursor);
52130
52230
  if (parent === cursor)
@@ -52160,7 +52260,7 @@ function rootAliases(home, allowed = false) {
52160
52260
  return result2;
52161
52261
  }
52162
52262
  function canonicalAgentPath(path, aliases) {
52163
- const absolute = resolve11(path), binding = aliases.find((item) => absolute === item.alias || absolute.startsWith(item.alias + sep5));
52263
+ const absolute = canonicalSystemPath(path), binding = aliases.find((item) => absolute === item.alias || absolute.startsWith(item.alias + sep5));
52164
52264
  return binding ? join21(binding.target, relative5(binding.alias, absolute)) : absolute;
52165
52265
  }
52166
52266
  function disabledCodexSkillPaths(config, aliases) {
@@ -52277,8 +52377,10 @@ function parseNativeMigrationTargetManifest(input) {
52277
52377
  throw new Error("Invalid native migration target fields");
52278
52378
  if (!ROOTS.some(([agent]) => agent === item.agent))
52279
52379
  throw new Error(`Unsupported native migration target agent: ${item.agent}`);
52280
- const projectRoot = resolve11(item.projectRoot);
52281
- if (!isAbsolute11(item.projectRoot) || projectRoot !== item.projectRoot || !existsSync12(projectRoot) || !lstatSync15(projectRoot).isDirectory())
52380
+ if (!isAbsolute11(item.projectRoot) || resolve11(item.projectRoot) !== item.projectRoot)
52381
+ throw new Error(`Native migration target project root must be an existing absolute directory: ${item.projectRoot}`);
52382
+ const projectRoot = canonicalSystemPath(item.projectRoot);
52383
+ if (!existsSync12(projectRoot) || !lstatSync15(projectRoot).isDirectory())
52282
52384
  throw new Error(`Native migration target project root must be an existing absolute directory: ${item.projectRoot}`);
52283
52385
  assertSafePath(projectRoot);
52284
52386
  if (isAbsolute11(item.path) || item.path.includes("\\") || item.path.length === 0 || item.path.includes("\x00"))
@@ -52288,7 +52390,7 @@ function parseNativeMigrationTargetManifest(input) {
52288
52390
  throw new Error(`Native migration target path escapes or is not normalized: ${item.path}`);
52289
52391
  if (!/^[0-9a-f]{64}$/.test(item.treeSha256))
52290
52392
  throw new Error(`Invalid native migration tree digest for ${item.path}`);
52291
- const identity5 = `${item.agent}\x00${projectRoot}\x00${normalized}`;
52393
+ const identity5 = `${item.agent}\x00${canonicalSystemPath(projectRoot)}\x00${normalized}`;
52292
52394
  if (identities.has(identity5))
52293
52395
  throw new Error(`Duplicate native migration target: ${item.path}`);
52294
52396
  identities.add(identity5);
@@ -52305,17 +52407,19 @@ function selectNativeMigrationTargets(inventory, manifest) {
52305
52407
  const identities = new Set;
52306
52408
  const selected = [];
52307
52409
  for (const target of manifest.targets) {
52308
- const projectRoot = resolve11(target.projectRoot), normalized = relative5(projectRoot, resolve11(projectRoot, target.path));
52309
- if (!isAbsolute11(target.projectRoot) || projectRoot !== target.projectRoot || !existsSync12(projectRoot) || !lstatSync15(projectRoot).isDirectory())
52410
+ if (!isAbsolute11(target.projectRoot) || resolve11(target.projectRoot) !== target.projectRoot)
52411
+ throw new Error(`Native migration target project root must be an existing absolute directory: ${target.projectRoot}`);
52412
+ const projectRoot = canonicalSystemPath(target.projectRoot), normalized = relative5(projectRoot, resolve11(projectRoot, target.path));
52413
+ if (!existsSync12(projectRoot) || !lstatSync15(projectRoot).isDirectory())
52310
52414
  throw new Error(`Native migration target project root must be an existing absolute directory: ${target.projectRoot}`);
52311
52415
  assertSafePath(projectRoot);
52312
52416
  if (isAbsolute11(target.path) || target.path.includes("\\") || target.path.includes("\x00") || !normalized || normalized === ".." || normalized.startsWith(`..${sep5}`) || normalized !== target.path || !/^[0-9a-f]{64}$/.test(target.treeSha256))
52313
52417
  throw new Error(`Invalid native migration target: ${target.path}`);
52314
- const identity5 = `${target.agent}\x00${projectRoot}\x00${normalized}`;
52418
+ const identity5 = `${target.agent}\x00${canonicalSystemPath(projectRoot)}\x00${normalized}`;
52315
52419
  if (identities.has(identity5))
52316
52420
  throw new Error(`Duplicate native migration target: ${target.path}`);
52317
52421
  identities.add(identity5);
52318
- const expected = resolve11(target.projectRoot, target.path);
52422
+ const expected = canonicalSystemPath(resolve11(target.projectRoot, target.path));
52319
52423
  const matches2 = inventory.filter((entry2) => entry2.agent === target.agent && resolve11(entry2.path) === expected);
52320
52424
  if (matches2.length !== 1)
52321
52425
  throw new Error(`Native migration target was not found exactly once: ${target.agent} ${target.projectRoot}/${target.path}`);
@@ -52331,7 +52435,7 @@ function selectNativeMigrationTargets(inventory, manifest) {
52331
52435
  function projectAncestorDirectories(projects) {
52332
52436
  const directories = new Set;
52333
52437
  for (const project of projects) {
52334
- for (let path = resolve11(project), depth = 0;; depth++) {
52438
+ for (let path = canonicalSystemPath(project), depth = 0;; depth++) {
52335
52439
  if (depth >= 100)
52336
52440
  throw new Error("NATIVE_SKILL_DRIFT: project ancestor discovery limit exceeded");
52337
52441
  directories.add(path);
@@ -52344,11 +52448,13 @@ function projectAncestorDirectories(projects) {
52344
52448
  return [...directories];
52345
52449
  }
52346
52450
  function inventoryNativeSkills(home = homedir3(), options = {}) {
52451
+ home = canonicalSystemPath(home);
52347
52452
  if (options.reviewedCacheAlias !== undefined && (!options.includeVendor || !isAbsolute11(options.reviewedCacheAlias) || resolve11(options.reviewedCacheAlias) !== options.reviewedCacheAlias))
52348
52453
  throw new Error("A reviewed cache alias requires vendor inventory and an exact absolute path");
52349
52454
  if (options.disabledVendorPaths?.some((path) => !isAbsolute11(path) || resolve11(path) !== path))
52350
52455
  throw new Error("Disabled vendor paths must be exact absolute paths");
52351
- const disabledVendorPaths = new Set(options.disabledVendorPaths ?? []);
52456
+ const reviewedCacheAlias = options.reviewedCacheAlias === undefined ? undefined : canonicalSystemPath(options.reviewedCacheAlias);
52457
+ const disabledVendorPaths = new Set((options.disabledVendorPaths ?? []).map(canonicalSystemPath));
52352
52458
  const aliases = rootAliases(home, options.allowRootAliases);
52353
52459
  const selectedAgents = options.agents ? new Set(options.agents) : undefined;
52354
52460
  const includesAgent = (agent) => !selectedAgents || selectedAgents.has(agent);
@@ -52371,8 +52477,11 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
52371
52477
  }
52372
52478
  let reviewedCacheAliasFound = false;
52373
52479
  function verifiedSiblingCacheAlias(agent, path, parent) {
52374
- const link2 = readlinkSync4(path), target = resolve11(parent, link2);
52375
- if (dirname11(target) !== parent || (isAbsolute11(link2) ? link2 !== target : dirname11(link2) !== "."))
52480
+ const link2 = readlinkSync4(path), lexicalTarget = resolve11(parent, link2);
52481
+ if (isAbsolute11(link2) ? link2 !== lexicalTarget : dirname11(link2) !== ".")
52482
+ return false;
52483
+ const target = canonicalSystemPath(lexicalTarget);
52484
+ if (dirname11(target) !== parent)
52376
52485
  return false;
52377
52486
  if (!lstatSync15(target, { throwIfNoEntry: false })?.isDirectory())
52378
52487
  return false;
@@ -52381,11 +52490,11 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
52381
52490
  return false;
52382
52491
  if (scan.hasSkills) {
52383
52492
  const targetSkills = entries.filter((entry) => entry.agent === agent && entry.vendor && (entry.path === target || entry.path.startsWith(target + sep5)));
52384
- if (path === options.reviewedCacheAlias && targetSkills.length > 0)
52493
+ if (path === reviewedCacheAlias && targetSkills.length > 0)
52385
52494
  reviewedCacheAliasFound = true;
52386
52495
  if (agent === "codex" && targetSkills.length > 0 && targetSkills.every((entry) => disabledVendorPaths.has(entry.path) || options.disabledVendorSkill?.(entry) === true))
52387
52496
  return true;
52388
- if (path !== options.reviewedCacheAlias)
52497
+ if (path !== reviewedCacheAlias)
52389
52498
  return false;
52390
52499
  }
52391
52500
  return true;
package/bin/mcp.js CHANGED
@@ -20019,7 +20019,7 @@ var package_default;
20019
20019
  var init_package = __esm(() => {
20020
20020
  package_default = {
20021
20021
  name: "@hasna/skills",
20022
- version: "0.10.23",
20022
+ version: "0.10.24",
20023
20023
  description: "Skills library for AI coding agents",
20024
20024
  type: "module",
20025
20025
  bin: {
package/bin/migrate.js CHANGED
@@ -7,7 +7,7 @@ import { join as join5 } from "path";
7
7
  // package.json
8
8
  var package_default = {
9
9
  name: "@hasna/skills",
10
- version: "0.10.23",
10
+ version: "0.10.24",
11
11
  description: "Skills library for AI coding agents",
12
12
  type: "module",
13
13
  bin: {
package/bin/server.js CHANGED
@@ -24122,7 +24122,7 @@ var init_dist_es9 = __esm(() => {
24122
24122
  // package.json
24123
24123
  var package_default = {
24124
24124
  name: "@hasna/skills",
24125
- version: "0.10.23",
24125
+ version: "0.10.24",
24126
24126
  description: "Skills library for AI coding agents",
24127
24127
  type: "module",
24128
24128
  bin: {
package/bin/worker.js CHANGED
@@ -24125,7 +24125,7 @@ import { randomUUID as randomUUID6 } from "crypto";
24125
24125
  // package.json
24126
24126
  var package_default = {
24127
24127
  name: "@hasna/skills",
24128
- version: "0.10.23",
24128
+ version: "0.10.24",
24129
24129
  description: "Skills library for AI coding agents",
24130
24130
  type: "module",
24131
24131
  bin: {
package/dist/index.js CHANGED
@@ -10881,7 +10881,7 @@ function replaceSkillSession(expected, replacement, plan, options = {}, beforeCo
10881
10881
  // package.json
10882
10882
  var package_default = {
10883
10883
  name: "@hasna/skills",
10884
- version: "0.10.23",
10884
+ version: "0.10.24",
10885
10885
  description: "Skills library for AI coding agents",
10886
10886
  type: "module",
10887
10887
  bin: {
@@ -13381,27 +13381,62 @@ function mcpControls(path, read2) {
13381
13381
  const object5 = (v) => Boolean(v && typeof v === "object" && !Array.isArray(v));
13382
13382
  const textField = (v, empty = false) => typeof v === "string" && (empty || Boolean(v.trim())) && Buffer.byteLength(v) <= 16384 && !/[\x00-\x1f\x7f]/.test(v);
13383
13383
  const envName = (v) => typeof v === "string" && /^[A-Za-z_][A-Za-z0-9_]{0,255}$/.test(v);
13384
+ const optional = (v, valid) => v === undefined || v === null || valid(v);
13385
+ const stringList = (v) => Array.isArray(v) && v.length <= 256 && v.every((item) => textField(item));
13386
+ const approval = (v) => ["auto", "prompt", "writes", "approve"].includes(v);
13387
+ const duration = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0;
13388
+ const positiveInteger = (v) => Number.isSafeInteger(v) && v > 0;
13389
+ const fields = [
13390
+ "command",
13391
+ "args",
13392
+ "cwd",
13393
+ "env",
13394
+ "env_vars",
13395
+ "enabled",
13396
+ "required",
13397
+ "environment_id",
13398
+ "startup_readiness",
13399
+ "supports_parallel_tool_calls",
13400
+ "tool_input_schema_max_bytes",
13401
+ "startup_timeout_sec",
13402
+ "startup_timeout_ms",
13403
+ "tool_timeout_sec",
13404
+ "default_tools_approval_mode",
13405
+ "enabled_tools",
13406
+ "disabled_tools",
13407
+ "omit_tools_from",
13408
+ "tools",
13409
+ "name"
13410
+ ];
13384
13411
  if (!object5(value) || Object.keys(value).length !== 1 || !object5(value.mcpServers))
13385
13412
  refuse2();
13386
13413
  const servers = Object.entries(value.mcpServers);
13387
13414
  if (!servers.length || servers.length > 256)
13388
13415
  refuse2();
13389
13416
  for (const [name, server] of servers) {
13390
- if (!identifier2(name) || !object5(server) || !Object.keys(server).every((key) => ["command", "args", "cwd", "env_vars"].includes(key)) || !textField(server.command) || server.args !== undefined && (!Array.isArray(server.args) || server.args.length > 256 || !server.args.every((arg) => textField(arg, true))) || server.cwd !== undefined && !textField(server.cwd))
13417
+ if (!identifier2(name) || !object5(server) || !Object.keys(server).every((key) => fields.includes(key)) || !textField(server.command) || !optional(server.args, (v) => Array.isArray(v) && v.length <= 256 && v.every((arg) => textField(arg, true))) || !optional(server.cwd, textField) || !optional(server.env, (v) => object5(v) && Object.keys(v).length <= 256 && Object.entries(v).every(([key, value2]) => envName(key) && textField(value2, true))) || !["enabled", "required", "supports_parallel_tool_calls"].every((key) => optional(server[key], (v) => typeof v === "boolean")) || !optional(server.environment_id, (v) => v === "local") || !optional(server.startup_readiness, (v) => ["connection", "catalog"].includes(v)) || !optional(server.tool_input_schema_max_bytes, positiveInteger) || !["startup_timeout_sec", "tool_timeout_sec"].every((key) => optional(server[key], duration)) || !optional(server.startup_timeout_ms, (v) => Number.isSafeInteger(v) && v >= 0) || !optional(server.default_tools_approval_mode, approval) || !["enabled_tools", "disabled_tools"].every((key) => optional(server[key], stringList)) || !optional(server.omit_tools_from, (v) => Array.isArray(v) && v.length <= 256 && v.every((item) => ["code_mode", "deferred", "direct"].includes(item))) || !optional(server.name, textField))
13391
13418
  refuse2();
13392
- if (server.env_vars !== undefined) {
13419
+ if (server.env_vars !== undefined && server.env_vars !== null) {
13393
13420
  if (!Array.isArray(server.env_vars) || server.env_vars.length > 256)
13394
13421
  refuse2();
13395
13422
  const names = server.env_vars.map((entry) => {
13396
13423
  if (envName(entry))
13397
13424
  return entry;
13398
- if (!object5(entry) || !Object.keys(entry).every((key) => ["name", "source"].includes(key)) || !envName(entry.name) || entry.source !== undefined && entry.source !== "local")
13425
+ if (!object5(entry) || !Object.keys(entry).every((key) => ["name", "source"].includes(key)) || !envName(entry.name) || !optional(entry.source, (v) => v === "local"))
13399
13426
  refuse2();
13400
13427
  return entry.name;
13401
13428
  });
13402
13429
  if (new Set(names).size !== names.length)
13403
13430
  refuse2();
13404
13431
  }
13432
+ if (server.tools !== undefined && server.tools !== null) {
13433
+ if (!object5(server.tools) || Object.keys(server.tools).length > 256)
13434
+ refuse2();
13435
+ for (const [tool, config] of Object.entries(server.tools)) {
13436
+ if (!textField(tool) || !object5(config) || !Object.keys(config).every((key) => ["approval_mode", "output_token_limit"].includes(key)) || !optional(config.approval_mode, approval) || !optional(config.output_token_limit, positiveInteger))
13437
+ refuse2();
13438
+ }
13439
+ }
13405
13440
  }
13406
13441
  return sha2562;
13407
13442
  }
@@ -14245,10 +14280,75 @@ function disableCodexBundledSkills(text2) {
14245
14280
  // src/lib/agent-integration.ts
14246
14281
  var sha3 = (data) => createHash12("sha256").update(data).digest("hex");
14247
14282
  var ROOTS = NATIVE_SKILL_ROOTS;
14283
+ function hasNoDarwinAcl(fd) {
14284
+ try {
14285
+ const { dlopen, read: read2 } = __require("bun:ffi");
14286
+ const library = dlopen("/usr/lib/libSystem.B.dylib", {
14287
+ acl_get_fd: { args: ["i32"], returns: "ptr" },
14288
+ acl_free: { args: ["ptr"], returns: "i32" },
14289
+ __error: { args: [], returns: "ptr" }
14290
+ });
14291
+ try {
14292
+ const errno = library.symbols.__error();
14293
+ if (!errno)
14294
+ return false;
14295
+ const acl = library.symbols.acl_get_fd(fd);
14296
+ if (acl) {
14297
+ library.symbols.acl_free(acl);
14298
+ return false;
14299
+ }
14300
+ return (acl === null || acl === 0) && read2.i32(errno) === 2;
14301
+ } finally {
14302
+ library.close();
14303
+ }
14304
+ } catch {
14305
+ return false;
14306
+ }
14307
+ }
14308
+ function isSystemRootAlias(path) {
14309
+ if (process.platform !== "darwin" || !["/var", "/tmp", "/etc"].includes(path))
14310
+ return false;
14311
+ const target = `/private${path}`, link = `private${path}`;
14312
+ const identity4 = (stat) => `${stat.dev}:${stat.ino}:${stat.ctimeNs}:${stat.uid}:${stat.mode}`;
14313
+ try {
14314
+ const before = lstatSync15(path, { bigint: true });
14315
+ if (!before.isSymbolicLink() || before.uid !== 0n || readlinkSync4(path) !== link)
14316
+ return false;
14317
+ const directories = ["/", "/private", target].map((directory) => {
14318
+ const stat = lstatSync15(directory, { bigint: true });
14319
+ if (!stat.isDirectory() || stat.uid !== 0n)
14320
+ throw new Error("Untrusted system alias directory");
14321
+ if (directory !== "/private/tmp") {
14322
+ if ((stat.mode & 0o022n) !== 0n)
14323
+ throw new Error("Writable system alias directory");
14324
+ } else if ((stat.mode & 0o1000n) === 0n)
14325
+ throw new Error("Unprotected system temporary directory");
14326
+ const fd = openSync8(directory, constants7.O_RDONLY | constants7.O_DIRECTORY | constants7.O_NOFOLLOW);
14327
+ try {
14328
+ if (identity4(fstatSync8(fd, { bigint: true })) !== identity4(stat) || !hasNoDarwinAcl(fd) || identity4(fstatSync8(fd, { bigint: true })) !== identity4(stat))
14329
+ throw new Error("Unverified system alias directory ACL");
14330
+ } finally {
14331
+ closeSync8(fd);
14332
+ }
14333
+ return { directory, identity: identity4(stat) };
14334
+ });
14335
+ return realpathSync6(path) === target && readlinkSync4(path) === link && identity4(lstatSync15(path, { bigint: true })) === identity4(before) && directories.every((item) => identity4(lstatSync15(item.directory, { bigint: true })) === item.identity);
14336
+ } catch {
14337
+ return false;
14338
+ }
14339
+ }
14340
+ function canonicalSystemPath(path) {
14341
+ const absolute = resolve10(path), root = `/${absolute.split(sep5)[1]}`;
14342
+ if (process.platform !== "darwin" || !["/var", "/tmp", "/etc"].includes(root) || !lstatSync15(root, { throwIfNoEntry: false })?.isSymbolicLink())
14343
+ return absolute;
14344
+ if (!isSystemRootAlias(root))
14345
+ throw new Error(`Refusing symlink path: ${root}`);
14346
+ return `/private${absolute}`;
14347
+ }
14248
14348
  function assertSafePath(path) {
14249
14349
  let cursor = resolve10(path);
14250
14350
  while (true) {
14251
- if (lstatSync15(cursor, { throwIfNoEntry: false })?.isSymbolicLink())
14351
+ if (lstatSync15(cursor, { throwIfNoEntry: false })?.isSymbolicLink() && !isSystemRootAlias(cursor))
14252
14352
  throw new Error(`Refusing symlink path: ${cursor}`);
14253
14353
  const parent = dirname11(cursor);
14254
14354
  if (parent === cursor)
@@ -14284,7 +14384,7 @@ function rootAliases(home, allowed = false) {
14284
14384
  return result;
14285
14385
  }
14286
14386
  function canonicalAgentPath(path, aliases) {
14287
- const absolute = resolve10(path), binding = aliases.find((item) => absolute === item.alias || absolute.startsWith(item.alias + sep5));
14387
+ const absolute = canonicalSystemPath(path), binding = aliases.find((item) => absolute === item.alias || absolute.startsWith(item.alias + sep5));
14288
14388
  return binding ? join17(binding.target, relative5(binding.alias, absolute)) : absolute;
14289
14389
  }
14290
14390
  function disabledCodexSkillPaths(config, aliases) {
@@ -14382,17 +14482,19 @@ function selectNativeMigrationTargets(inventory, manifest) {
14382
14482
  const identities = new Set;
14383
14483
  const selected = [];
14384
14484
  for (const target of manifest.targets) {
14385
- const projectRoot = resolve10(target.projectRoot), normalized = relative5(projectRoot, resolve10(projectRoot, target.path));
14386
- if (!isAbsolute11(target.projectRoot) || projectRoot !== target.projectRoot || !existsSync8(projectRoot) || !lstatSync15(projectRoot).isDirectory())
14485
+ if (!isAbsolute11(target.projectRoot) || resolve10(target.projectRoot) !== target.projectRoot)
14486
+ throw new Error(`Native migration target project root must be an existing absolute directory: ${target.projectRoot}`);
14487
+ const projectRoot = canonicalSystemPath(target.projectRoot), normalized = relative5(projectRoot, resolve10(projectRoot, target.path));
14488
+ if (!existsSync8(projectRoot) || !lstatSync15(projectRoot).isDirectory())
14387
14489
  throw new Error(`Native migration target project root must be an existing absolute directory: ${target.projectRoot}`);
14388
14490
  assertSafePath(projectRoot);
14389
14491
  if (isAbsolute11(target.path) || target.path.includes("\\") || target.path.includes("\x00") || !normalized || normalized === ".." || normalized.startsWith(`..${sep5}`) || normalized !== target.path || !/^[0-9a-f]{64}$/.test(target.treeSha256))
14390
14492
  throw new Error(`Invalid native migration target: ${target.path}`);
14391
- const identity4 = `${target.agent}\x00${projectRoot}\x00${normalized}`;
14493
+ const identity4 = `${target.agent}\x00${canonicalSystemPath(projectRoot)}\x00${normalized}`;
14392
14494
  if (identities.has(identity4))
14393
14495
  throw new Error(`Duplicate native migration target: ${target.path}`);
14394
14496
  identities.add(identity4);
14395
- const expected = resolve10(target.projectRoot, target.path);
14497
+ const expected = canonicalSystemPath(resolve10(target.projectRoot, target.path));
14396
14498
  const matches = inventory.filter((entry2) => entry2.agent === target.agent && resolve10(entry2.path) === expected);
14397
14499
  if (matches.length !== 1)
14398
14500
  throw new Error(`Native migration target was not found exactly once: ${target.agent} ${target.projectRoot}/${target.path}`);
@@ -14408,7 +14510,7 @@ function selectNativeMigrationTargets(inventory, manifest) {
14408
14510
  function projectAncestorDirectories(projects) {
14409
14511
  const directories = new Set;
14410
14512
  for (const project of projects) {
14411
- for (let path = resolve10(project), depth = 0;; depth++) {
14513
+ for (let path = canonicalSystemPath(project), depth = 0;; depth++) {
14412
14514
  if (depth >= 100)
14413
14515
  throw new Error("NATIVE_SKILL_DRIFT: project ancestor discovery limit exceeded");
14414
14516
  directories.add(path);
@@ -14421,11 +14523,13 @@ function projectAncestorDirectories(projects) {
14421
14523
  return [...directories];
14422
14524
  }
14423
14525
  function inventoryNativeSkills(home = homedir2(), options = {}) {
14526
+ home = canonicalSystemPath(home);
14424
14527
  if (options.reviewedCacheAlias !== undefined && (!options.includeVendor || !isAbsolute11(options.reviewedCacheAlias) || resolve10(options.reviewedCacheAlias) !== options.reviewedCacheAlias))
14425
14528
  throw new Error("A reviewed cache alias requires vendor inventory and an exact absolute path");
14426
14529
  if (options.disabledVendorPaths?.some((path) => !isAbsolute11(path) || resolve10(path) !== path))
14427
14530
  throw new Error("Disabled vendor paths must be exact absolute paths");
14428
- const disabledVendorPaths = new Set(options.disabledVendorPaths ?? []);
14531
+ const reviewedCacheAlias = options.reviewedCacheAlias === undefined ? undefined : canonicalSystemPath(options.reviewedCacheAlias);
14532
+ const disabledVendorPaths = new Set((options.disabledVendorPaths ?? []).map(canonicalSystemPath));
14429
14533
  const aliases = rootAliases(home, options.allowRootAliases);
14430
14534
  const selectedAgents = options.agents ? new Set(options.agents) : undefined;
14431
14535
  const includesAgent = (agent) => !selectedAgents || selectedAgents.has(agent);
@@ -14448,8 +14552,11 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
14448
14552
  }
14449
14553
  let reviewedCacheAliasFound = false;
14450
14554
  function verifiedSiblingCacheAlias(agent, path, parent) {
14451
- const link = readlinkSync4(path), target = resolve10(parent, link);
14452
- if (dirname11(target) !== parent || (isAbsolute11(link) ? link !== target : dirname11(link) !== "."))
14555
+ const link = readlinkSync4(path), lexicalTarget = resolve10(parent, link);
14556
+ if (isAbsolute11(link) ? link !== lexicalTarget : dirname11(link) !== ".")
14557
+ return false;
14558
+ const target = canonicalSystemPath(lexicalTarget);
14559
+ if (dirname11(target) !== parent)
14453
14560
  return false;
14454
14561
  if (!lstatSync15(target, { throwIfNoEntry: false })?.isDirectory())
14455
14562
  return false;
@@ -14458,11 +14565,11 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
14458
14565
  return false;
14459
14566
  if (scan.hasSkills) {
14460
14567
  const targetSkills = entries.filter((entry) => entry.agent === agent && entry.vendor && (entry.path === target || entry.path.startsWith(target + sep5)));
14461
- if (path === options.reviewedCacheAlias && targetSkills.length > 0)
14568
+ if (path === reviewedCacheAlias && targetSkills.length > 0)
14462
14569
  reviewedCacheAliasFound = true;
14463
14570
  if (agent === "codex" && targetSkills.length > 0 && targetSkills.every((entry) => disabledVendorPaths.has(entry.path) || options.disabledVendorSkill?.(entry) === true))
14464
14571
  return true;
14465
- if (path !== options.reviewedCacheAlias)
14572
+ if (path !== reviewedCacheAlias)
14466
14573
  return false;
14467
14574
  }
14468
14575
  return true;
package/dist/sdk/index.js CHANGED
@@ -32373,7 +32373,7 @@ var init_event_sink = __esm(() => {
32373
32373
  // package.json
32374
32374
  var package_default = {
32375
32375
  name: "@hasna/skills",
32376
- version: "0.10.23",
32376
+ version: "0.10.24",
32377
32377
  description: "Skills library for AI coding agents",
32378
32378
  type: "module",
32379
32379
  bin: {
@@ -38359,27 +38359,62 @@ function mcpControls(path, read) {
38359
38359
  const object3 = (v) => Boolean(v && typeof v === "object" && !Array.isArray(v));
38360
38360
  const textField = (v, empty = false) => typeof v === "string" && (empty || Boolean(v.trim())) && Buffer.byteLength(v) <= 16384 && !/[\x00-\x1f\x7f]/.test(v);
38361
38361
  const envName = (v) => typeof v === "string" && /^[A-Za-z_][A-Za-z0-9_]{0,255}$/.test(v);
38362
+ const optional = (v, valid) => v === undefined || v === null || valid(v);
38363
+ const stringList = (v) => Array.isArray(v) && v.length <= 256 && v.every((item) => textField(item));
38364
+ const approval = (v) => ["auto", "prompt", "writes", "approve"].includes(v);
38365
+ const duration = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0;
38366
+ const positiveInteger = (v) => Number.isSafeInteger(v) && v > 0;
38367
+ const fields = [
38368
+ "command",
38369
+ "args",
38370
+ "cwd",
38371
+ "env",
38372
+ "env_vars",
38373
+ "enabled",
38374
+ "required",
38375
+ "environment_id",
38376
+ "startup_readiness",
38377
+ "supports_parallel_tool_calls",
38378
+ "tool_input_schema_max_bytes",
38379
+ "startup_timeout_sec",
38380
+ "startup_timeout_ms",
38381
+ "tool_timeout_sec",
38382
+ "default_tools_approval_mode",
38383
+ "enabled_tools",
38384
+ "disabled_tools",
38385
+ "omit_tools_from",
38386
+ "tools",
38387
+ "name"
38388
+ ];
38362
38389
  if (!object3(value) || Object.keys(value).length !== 1 || !object3(value.mcpServers))
38363
38390
  refuse3();
38364
38391
  const servers = Object.entries(value.mcpServers);
38365
38392
  if (!servers.length || servers.length > 256)
38366
38393
  refuse3();
38367
38394
  for (const [name, server] of servers) {
38368
- if (!identifier3(name) || !object3(server) || !Object.keys(server).every((key) => ["command", "args", "cwd", "env_vars"].includes(key)) || !textField(server.command) || server.args !== undefined && (!Array.isArray(server.args) || server.args.length > 256 || !server.args.every((arg) => textField(arg, true))) || server.cwd !== undefined && !textField(server.cwd))
38395
+ if (!identifier3(name) || !object3(server) || !Object.keys(server).every((key) => fields.includes(key)) || !textField(server.command) || !optional(server.args, (v) => Array.isArray(v) && v.length <= 256 && v.every((arg) => textField(arg, true))) || !optional(server.cwd, textField) || !optional(server.env, (v) => object3(v) && Object.keys(v).length <= 256 && Object.entries(v).every(([key, value2]) => envName(key) && textField(value2, true))) || !["enabled", "required", "supports_parallel_tool_calls"].every((key) => optional(server[key], (v) => typeof v === "boolean")) || !optional(server.environment_id, (v) => v === "local") || !optional(server.startup_readiness, (v) => ["connection", "catalog"].includes(v)) || !optional(server.tool_input_schema_max_bytes, positiveInteger) || !["startup_timeout_sec", "tool_timeout_sec"].every((key) => optional(server[key], duration)) || !optional(server.startup_timeout_ms, (v) => Number.isSafeInteger(v) && v >= 0) || !optional(server.default_tools_approval_mode, approval) || !["enabled_tools", "disabled_tools"].every((key) => optional(server[key], stringList)) || !optional(server.omit_tools_from, (v) => Array.isArray(v) && v.length <= 256 && v.every((item) => ["code_mode", "deferred", "direct"].includes(item))) || !optional(server.name, textField))
38369
38396
  refuse3();
38370
- if (server.env_vars !== undefined) {
38397
+ if (server.env_vars !== undefined && server.env_vars !== null) {
38371
38398
  if (!Array.isArray(server.env_vars) || server.env_vars.length > 256)
38372
38399
  refuse3();
38373
38400
  const names = server.env_vars.map((entry) => {
38374
38401
  if (envName(entry))
38375
38402
  return entry;
38376
- if (!object3(entry) || !Object.keys(entry).every((key) => ["name", "source"].includes(key)) || !envName(entry.name) || entry.source !== undefined && entry.source !== "local")
38403
+ if (!object3(entry) || !Object.keys(entry).every((key) => ["name", "source"].includes(key)) || !envName(entry.name) || !optional(entry.source, (v) => v === "local"))
38377
38404
  refuse3();
38378
38405
  return entry.name;
38379
38406
  });
38380
38407
  if (new Set(names).size !== names.length)
38381
38408
  refuse3();
38382
38409
  }
38410
+ if (server.tools !== undefined && server.tools !== null) {
38411
+ if (!object3(server.tools) || Object.keys(server.tools).length > 256)
38412
+ refuse3();
38413
+ for (const [tool, config] of Object.entries(server.tools)) {
38414
+ if (!textField(tool) || !object3(config) || !Object.keys(config).every((key) => ["approval_mode", "output_token_limit"].includes(key)) || !optional(config.approval_mode, approval) || !optional(config.output_token_limit, positiveInteger))
38415
+ refuse3();
38416
+ }
38417
+ }
38383
38418
  }
38384
38419
  return sha2562;
38385
38420
  }
@@ -42140,10 +42175,75 @@ function disableCodexBundledSkills(text2) {
42140
42175
  // src/lib/agent-integration.ts
42141
42176
  var sha4 = (data) => createHash18("sha256").update(data).digest("hex");
42142
42177
  var ROOTS = NATIVE_SKILL_ROOTS;
42178
+ function hasNoDarwinAcl(fd) {
42179
+ try {
42180
+ const { dlopen, read: read3 } = __require("bun:ffi");
42181
+ const library = dlopen("/usr/lib/libSystem.B.dylib", {
42182
+ acl_get_fd: { args: ["i32"], returns: "ptr" },
42183
+ acl_free: { args: ["ptr"], returns: "i32" },
42184
+ __error: { args: [], returns: "ptr" }
42185
+ });
42186
+ try {
42187
+ const errno = library.symbols.__error();
42188
+ if (!errno)
42189
+ return false;
42190
+ const acl = library.symbols.acl_get_fd(fd);
42191
+ if (acl) {
42192
+ library.symbols.acl_free(acl);
42193
+ return false;
42194
+ }
42195
+ return (acl === null || acl === 0) && read3.i32(errno) === 2;
42196
+ } finally {
42197
+ library.close();
42198
+ }
42199
+ } catch {
42200
+ return false;
42201
+ }
42202
+ }
42203
+ function isSystemRootAlias(path) {
42204
+ if (process.platform !== "darwin" || !["/var", "/tmp", "/etc"].includes(path))
42205
+ return false;
42206
+ const target = `/private${path}`, link = `private${path}`;
42207
+ const identity4 = (stat2) => `${stat2.dev}:${stat2.ino}:${stat2.ctimeNs}:${stat2.uid}:${stat2.mode}`;
42208
+ try {
42209
+ const before = lstatSync19(path, { bigint: true });
42210
+ if (!before.isSymbolicLink() || before.uid !== 0n || readlinkSync4(path) !== link)
42211
+ return false;
42212
+ const directories = ["/", "/private", target].map((directory) => {
42213
+ const stat2 = lstatSync19(directory, { bigint: true });
42214
+ if (!stat2.isDirectory() || stat2.uid !== 0n)
42215
+ throw new Error("Untrusted system alias directory");
42216
+ if (directory !== "/private/tmp") {
42217
+ if ((stat2.mode & 0o022n) !== 0n)
42218
+ throw new Error("Writable system alias directory");
42219
+ } else if ((stat2.mode & 0o1000n) === 0n)
42220
+ throw new Error("Unprotected system temporary directory");
42221
+ const fd = openSync16(directory, constants12.O_RDONLY | constants12.O_DIRECTORY | constants12.O_NOFOLLOW);
42222
+ try {
42223
+ if (identity4(fstatSync16(fd, { bigint: true })) !== identity4(stat2) || !hasNoDarwinAcl(fd) || identity4(fstatSync16(fd, { bigint: true })) !== identity4(stat2))
42224
+ throw new Error("Unverified system alias directory ACL");
42225
+ } finally {
42226
+ closeSync16(fd);
42227
+ }
42228
+ return { directory, identity: identity4(stat2) };
42229
+ });
42230
+ return realpathSync7(path) === target && readlinkSync4(path) === link && identity4(lstatSync19(path, { bigint: true })) === identity4(before) && directories.every((item) => identity4(lstatSync19(item.directory, { bigint: true })) === item.identity);
42231
+ } catch {
42232
+ return false;
42233
+ }
42234
+ }
42235
+ function canonicalSystemPath(path) {
42236
+ const absolute = resolve15(path), root2 = `/${absolute.split(sep5)[1]}`;
42237
+ if (process.platform !== "darwin" || !["/var", "/tmp", "/etc"].includes(root2) || !lstatSync19(root2, { throwIfNoEntry: false })?.isSymbolicLink())
42238
+ return absolute;
42239
+ if (!isSystemRootAlias(root2))
42240
+ throw new Error(`Refusing symlink path: ${root2}`);
42241
+ return `/private${absolute}`;
42242
+ }
42143
42243
  function assertSafePath(path) {
42144
42244
  let cursor = resolve15(path);
42145
42245
  while (true) {
42146
- if (lstatSync19(cursor, { throwIfNoEntry: false })?.isSymbolicLink())
42246
+ if (lstatSync19(cursor, { throwIfNoEntry: false })?.isSymbolicLink() && !isSystemRootAlias(cursor))
42147
42247
  throw new Error(`Refusing symlink path: ${cursor}`);
42148
42248
  const parent = dirname15(cursor);
42149
42249
  if (parent === cursor)
@@ -42179,7 +42279,7 @@ function rootAliases(home, allowed = false) {
42179
42279
  return result;
42180
42280
  }
42181
42281
  function canonicalAgentPath(path, aliases) {
42182
- const absolute = resolve15(path), binding = aliases.find((item) => absolute === item.alias || absolute.startsWith(item.alias + sep5));
42282
+ const absolute = canonicalSystemPath(path), binding = aliases.find((item) => absolute === item.alias || absolute.startsWith(item.alias + sep5));
42183
42283
  return binding ? join22(binding.target, relative4(binding.alias, absolute)) : absolute;
42184
42284
  }
42185
42285
  function disabledCodexSkillPaths(config, aliases) {
@@ -42277,17 +42377,19 @@ function selectNativeMigrationTargets(inventory, manifest) {
42277
42377
  const identities = new Set;
42278
42378
  const selected = [];
42279
42379
  for (const target of manifest.targets) {
42280
- const projectRoot = resolve15(target.projectRoot), normalized = relative4(projectRoot, resolve15(projectRoot, target.path));
42281
- if (!isAbsolute17(target.projectRoot) || projectRoot !== target.projectRoot || !existsSync6(projectRoot) || !lstatSync19(projectRoot).isDirectory())
42380
+ if (!isAbsolute17(target.projectRoot) || resolve15(target.projectRoot) !== target.projectRoot)
42381
+ throw new Error(`Native migration target project root must be an existing absolute directory: ${target.projectRoot}`);
42382
+ const projectRoot = canonicalSystemPath(target.projectRoot), normalized = relative4(projectRoot, resolve15(projectRoot, target.path));
42383
+ if (!existsSync6(projectRoot) || !lstatSync19(projectRoot).isDirectory())
42282
42384
  throw new Error(`Native migration target project root must be an existing absolute directory: ${target.projectRoot}`);
42283
42385
  assertSafePath(projectRoot);
42284
42386
  if (isAbsolute17(target.path) || target.path.includes("\\") || target.path.includes("\x00") || !normalized || normalized === ".." || normalized.startsWith(`..${sep5}`) || normalized !== target.path || !/^[0-9a-f]{64}$/.test(target.treeSha256))
42285
42387
  throw new Error(`Invalid native migration target: ${target.path}`);
42286
- const identity4 = `${target.agent}\x00${projectRoot}\x00${normalized}`;
42388
+ const identity4 = `${target.agent}\x00${canonicalSystemPath(projectRoot)}\x00${normalized}`;
42287
42389
  if (identities.has(identity4))
42288
42390
  throw new Error(`Duplicate native migration target: ${target.path}`);
42289
42391
  identities.add(identity4);
42290
- const expected = resolve15(target.projectRoot, target.path);
42392
+ const expected = canonicalSystemPath(resolve15(target.projectRoot, target.path));
42291
42393
  const matches = inventory.filter((entry2) => entry2.agent === target.agent && resolve15(entry2.path) === expected);
42292
42394
  if (matches.length !== 1)
42293
42395
  throw new Error(`Native migration target was not found exactly once: ${target.agent} ${target.projectRoot}/${target.path}`);
@@ -42303,7 +42405,7 @@ function selectNativeMigrationTargets(inventory, manifest) {
42303
42405
  function projectAncestorDirectories(projects) {
42304
42406
  const directories = new Set;
42305
42407
  for (const project of projects) {
42306
- for (let path = resolve15(project), depth = 0;; depth++) {
42408
+ for (let path = canonicalSystemPath(project), depth = 0;; depth++) {
42307
42409
  if (depth >= 100)
42308
42410
  throw new Error("NATIVE_SKILL_DRIFT: project ancestor discovery limit exceeded");
42309
42411
  directories.add(path);
@@ -42316,11 +42418,13 @@ function projectAncestorDirectories(projects) {
42316
42418
  return [...directories];
42317
42419
  }
42318
42420
  function inventoryNativeSkills(home = homedir3(), options = {}) {
42421
+ home = canonicalSystemPath(home);
42319
42422
  if (options.reviewedCacheAlias !== undefined && (!options.includeVendor || !isAbsolute17(options.reviewedCacheAlias) || resolve15(options.reviewedCacheAlias) !== options.reviewedCacheAlias))
42320
42423
  throw new Error("A reviewed cache alias requires vendor inventory and an exact absolute path");
42321
42424
  if (options.disabledVendorPaths?.some((path) => !isAbsolute17(path) || resolve15(path) !== path))
42322
42425
  throw new Error("Disabled vendor paths must be exact absolute paths");
42323
- const disabledVendorPaths = new Set(options.disabledVendorPaths ?? []);
42426
+ const reviewedCacheAlias = options.reviewedCacheAlias === undefined ? undefined : canonicalSystemPath(options.reviewedCacheAlias);
42427
+ const disabledVendorPaths = new Set((options.disabledVendorPaths ?? []).map(canonicalSystemPath));
42324
42428
  const aliases = rootAliases(home, options.allowRootAliases);
42325
42429
  const selectedAgents = options.agents ? new Set(options.agents) : undefined;
42326
42430
  const includesAgent = (agent) => !selectedAgents || selectedAgents.has(agent);
@@ -42343,8 +42447,11 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
42343
42447
  }
42344
42448
  let reviewedCacheAliasFound = false;
42345
42449
  function verifiedSiblingCacheAlias(agent, path, parent) {
42346
- const link = readlinkSync4(path), target = resolve15(parent, link);
42347
- if (dirname15(target) !== parent || (isAbsolute17(link) ? link !== target : dirname15(link) !== "."))
42450
+ const link = readlinkSync4(path), lexicalTarget = resolve15(parent, link);
42451
+ if (isAbsolute17(link) ? link !== lexicalTarget : dirname15(link) !== ".")
42452
+ return false;
42453
+ const target = canonicalSystemPath(lexicalTarget);
42454
+ if (dirname15(target) !== parent)
42348
42455
  return false;
42349
42456
  if (!lstatSync19(target, { throwIfNoEntry: false })?.isDirectory())
42350
42457
  return false;
@@ -42353,11 +42460,11 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
42353
42460
  return false;
42354
42461
  if (scan.hasSkills) {
42355
42462
  const targetSkills = entries.filter((entry) => entry.agent === agent && entry.vendor && (entry.path === target || entry.path.startsWith(target + sep5)));
42356
- if (path === options.reviewedCacheAlias && targetSkills.length > 0)
42463
+ if (path === reviewedCacheAlias && targetSkills.length > 0)
42357
42464
  reviewedCacheAliasFound = true;
42358
42465
  if (agent === "codex" && targetSkills.length > 0 && targetSkills.every((entry) => disabledVendorPaths.has(entry.path) || options.disabledVendorSkill?.(entry) === true))
42359
42466
  return true;
42360
- if (path !== options.reviewedCacheAlias)
42467
+ if (path !== reviewedCacheAlias)
42361
42468
  return false;
42362
42469
  }
42363
42470
  return true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/skills",
3
- "version": "0.10.23",
3
+ "version": "0.10.24",
4
4
  "description": "Skills library for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {