@hasna/skills 0.10.34 → 0.10.36
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 +6 -0
- package/bin/index.js +558 -338
- package/bin/mcp.js +124 -123
- package/bin/migrate.js +1 -1
- package/bin/server.js +1 -1
- package/bin/worker.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +413 -219
- package/dist/lib/agent-integration.d.ts +27 -0
- package/dist/lib/codex-plugin-skill-controls.d.ts +1 -1
- package/dist/sdk/index.js +204 -11
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9655,8 +9655,8 @@ function hashDiscoveryPathFile(path, budget, changes) {
|
|
|
9655
9655
|
}
|
|
9656
9656
|
|
|
9657
9657
|
// src/lib/plugin-discovery.ts
|
|
9658
|
-
import { closeSync as
|
|
9659
|
-
import { basename as basename7, dirname as
|
|
9658
|
+
import { closeSync as closeSync11, constants as constants10, fstatSync as fstatSync11, lstatSync as lstatSync20, openSync as openSync11, opendirSync as opendirSync5, readSync as readSync9 } from "fs";
|
|
9659
|
+
import { basename as basename7, dirname as dirname15, isAbsolute as isAbsolute14, join as join27, resolve as resolve14 } from "path";
|
|
9660
9660
|
|
|
9661
9661
|
// src/lib/plugin-admission.ts
|
|
9662
9662
|
import { homedir as homedir5 } from "os";
|
|
@@ -10975,7 +10975,7 @@ function replaceSkillSession(expected, replacement, plan, options = {}, beforeCo
|
|
|
10975
10975
|
// package.json
|
|
10976
10976
|
var package_default = {
|
|
10977
10977
|
name: "@hasna/skills",
|
|
10978
|
-
version: "0.10.
|
|
10978
|
+
version: "0.10.36",
|
|
10979
10979
|
description: "Skills library for AI coding agents",
|
|
10980
10980
|
type: "module",
|
|
10981
10981
|
bin: {
|
|
@@ -12797,7 +12797,7 @@ import { homedir as homedir4 } from "os";
|
|
|
12797
12797
|
import {
|
|
12798
12798
|
cpSync as cpSync2,
|
|
12799
12799
|
existsSync as existsSync10,
|
|
12800
|
-
lstatSync as
|
|
12800
|
+
lstatSync as lstatSync18,
|
|
12801
12801
|
mkdirSync as mkdirSync6,
|
|
12802
12802
|
mkdtempSync,
|
|
12803
12803
|
readFileSync as readFileSync13,
|
|
@@ -12805,10 +12805,10 @@ import {
|
|
|
12805
12805
|
renameSync as renameSync3,
|
|
12806
12806
|
rmSync,
|
|
12807
12807
|
statSync as statSync9,
|
|
12808
|
-
writeFileSync as
|
|
12808
|
+
writeFileSync as writeFileSync6
|
|
12809
12809
|
} from "fs";
|
|
12810
12810
|
import { homedir as homedir3 } from "os";
|
|
12811
|
-
import { basename as basename6, dirname as
|
|
12811
|
+
import { basename as basename6, dirname as dirname13, join as join19 } from "path";
|
|
12812
12812
|
|
|
12813
12813
|
// src/lib/codex-local-mcp-controls.ts
|
|
12814
12814
|
function isCodexLocalStdioMcpServer(name, server) {
|
|
@@ -13717,7 +13717,7 @@ function reviewedCodexPluginCapabilitiesUnchanged(cache, controls, read2) {
|
|
|
13717
13717
|
return false;
|
|
13718
13718
|
}
|
|
13719
13719
|
}
|
|
13720
|
-
function reviewCodexPluginControls(catalog, documents, cache, cwd, read2, rules, pluginSettings) {
|
|
13720
|
+
function reviewCodexPluginControls(catalog, documents, cache, cwd, read2, rules, pluginSettings, plannedRules) {
|
|
13721
13721
|
if (!supportsCodexNativeCapability(catalog?.version, "qualified-skill-catalog") || catalog.cwd !== cwd)
|
|
13722
13722
|
refuse2();
|
|
13723
13723
|
const skills = projectCodexNativeSkillCatalog({ data: [{ cwd, errors: [], skills: catalog.skills }] }, cwd);
|
|
@@ -13788,7 +13788,9 @@ function reviewCodexPluginControls(catalog, documents, cache, cwd, read2, rules,
|
|
|
13788
13788
|
remotePluginId = plugin.remotePluginId;
|
|
13789
13789
|
}
|
|
13790
13790
|
const exactPathDeny = Array.isArray(rules) && rules.some((rule) => rule?.path === document2 && rule?.enabled === false);
|
|
13791
|
-
|
|
13791
|
+
const nativeSkill = { name: parsed.name, path: document2, pluginId: expectedPluginId, enabled: true };
|
|
13792
|
+
const plannedPathDeny = catalog.version === "codex-cli 0.160.0" && remotePluginId !== undefined && Array.isArray(rules) && rules.some((rule) => typeof rule?.name === "string" && rule.name.trim() === parsed.name && rule.enabled === false) && !rules.some((rule) => (rule?.path === document2 || typeof rule?.name === "string" && rule.name.trim() === parsed.name) && rule.enabled !== false) && Array.isArray(plannedRules) && plannedRules.some((rule) => rule?.path === document2 && rule.enabled === false) && isCodexNativeSkillDisabled(nativeSkill, plannedRules);
|
|
13793
|
+
if (!exactPathDeny && !plannedPathDeny || !isCodexNativeSkillDisabled(nativeSkill, rules))
|
|
13792
13794
|
refuse2();
|
|
13793
13795
|
}
|
|
13794
13796
|
result.push({ ...parsed, pluginId: expectedPluginId, ...remotePluginId ? { remotePluginId } : {} });
|
|
@@ -14208,10 +14210,10 @@ function upgradeCodexSettingsWitnessV3(previous, reviewedSettingsPath) {
|
|
|
14208
14210
|
}
|
|
14209
14211
|
|
|
14210
14212
|
// src/lib/agent-integration.ts
|
|
14211
|
-
import { createHash as
|
|
14213
|
+
import { createHash as createHash13, randomUUID as randomUUID2 } from "crypto";
|
|
14212
14214
|
import { isDeepStrictEqual as isDeepStrictEqual4 } from "util";
|
|
14213
|
-
import { existsSync as existsSync9, lstatSync as
|
|
14214
|
-
import { dirname as
|
|
14215
|
+
import { existsSync as existsSync9, lstatSync as lstatSync17, statSync as statSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync12, readdirSync as readdirSync8, opendirSync as opendirSync3, readlinkSync as readlinkSync4, realpathSync as realpathSync7, renameSync as renameSync2, rmdirSync as rmdirSync2, writeFileSync as writeFileSync5, unlinkSync as unlinkSync3, chmodSync, openSync as openSync9, closeSync as closeSync9, fsyncSync as fsyncSync3, fstatSync as fstatSync9, readSync as readSync7, constants as constants8, linkSync as linkSync2 } from "fs";
|
|
14216
|
+
import { dirname as dirname12, join as join18, resolve as resolve11, relative as relative5, isAbsolute as isAbsolute11, sep as sep5 } from "path";
|
|
14215
14217
|
import { homedir as homedir2 } from "os";
|
|
14216
14218
|
|
|
14217
14219
|
// src/lib/managed-policy.ts
|
|
@@ -14713,15 +14715,66 @@ var SUMI_SKILL_PERMISSIONS = [
|
|
|
14713
14715
|
{ action: "skill", resource: "skills-cli", effect: "allow" }
|
|
14714
14716
|
];
|
|
14715
14717
|
|
|
14718
|
+
// src/lib/codex-hook-trust-files.ts
|
|
14719
|
+
import { closeSync as closeSync8, constants as constants7, fstatSync as fstatSync8, fsyncSync as fsyncSync2, lstatSync as lstatSync16, openSync as openSync8, readSync as readSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
14720
|
+
import { createHash as createHash12 } from "crypto";
|
|
14721
|
+
import { dirname as dirname11 } from "path";
|
|
14722
|
+
function need5(value, code) {
|
|
14723
|
+
if (!value)
|
|
14724
|
+
throw new Error(`CODEX_HOOK_TRUST_${code}`);
|
|
14725
|
+
}
|
|
14726
|
+
var sameStat = (a, b) => ["dev", "ino", "uid", "nlink", "mode", "size", "mtimeNs", "ctimeNs"].every((key) => a[key] === b[key]);
|
|
14727
|
+
function safeParents(file) {
|
|
14728
|
+
for (let p = dirname11(file);; p = dirname11(p)) {
|
|
14729
|
+
const s = lstatSync16(p);
|
|
14730
|
+
need5(s.isDirectory() && !s.isSymbolicLink(), "UNSAFE_PARENT");
|
|
14731
|
+
need5((s.mode & 18) === 0 || s.uid === 0 && (s.mode & 512) !== 0, "UNSAFE_PARENT");
|
|
14732
|
+
if (dirname11(p) === p)
|
|
14733
|
+
break;
|
|
14734
|
+
}
|
|
14735
|
+
}
|
|
14736
|
+
function snapshot(file, privateFile = false, options = {}) {
|
|
14737
|
+
const readOnlyPackage = options.readOnlyPackage === true && !privateFile;
|
|
14738
|
+
safeParents(file);
|
|
14739
|
+
const initial = lstatSync16(file, { bigint: true });
|
|
14740
|
+
need5(initial.isFile() && initial.uid === BigInt(process.getuid()) && (initial.nlink === 1n || readOnlyPackage && initial.nlink > 1n) && (initial.mode & 0o022n) === 0n && (!privateFile || (initial.mode & 0o777n) === 0o600n), "UNSAFE_FILE");
|
|
14741
|
+
need5(initial.size <= 8n * 1024n * 1024n, "FILE_BOUND");
|
|
14742
|
+
const fd = openSync8(file, constants7.O_RDONLY | constants7.O_NOFOLLOW | constants7.O_NONBLOCK);
|
|
14743
|
+
try {
|
|
14744
|
+
const before = fstatSync8(fd, { bigint: true });
|
|
14745
|
+
need5(sameStat(initial, before), "INPUT_CHANGED");
|
|
14746
|
+
const bytes = Buffer.alloc(Number(before.size) + 1);
|
|
14747
|
+
let length = 0;
|
|
14748
|
+
while (length < bytes.length) {
|
|
14749
|
+
const n2 = readSync6(fd, bytes, length, bytes.length - length, null);
|
|
14750
|
+
if (!n2)
|
|
14751
|
+
break;
|
|
14752
|
+
length += n2;
|
|
14753
|
+
}
|
|
14754
|
+
need5(length === Number(before.size) && sameStat(before, fstatSync8(fd, { bigint: true })) && sameStat(before, lstatSync16(file, { bigint: true })), "INPUT_CHANGED");
|
|
14755
|
+
const content = bytes.subarray(0, length), text2 = new TextDecoder("utf-8", { fatal: true }).decode(content);
|
|
14756
|
+
return { file, privateFile, readOnlyPackage, text: text2, bytes: content, stat: before, sha256: createHash12("sha256").update(content).digest("hex") };
|
|
14757
|
+
} finally {
|
|
14758
|
+
closeSync8(fd);
|
|
14759
|
+
}
|
|
14760
|
+
}
|
|
14761
|
+
function unchanged2(before) {
|
|
14762
|
+
const after = snapshot(before.file, before.privateFile, before.readOnlyPackage ? { readOnlyPackage: true } : {});
|
|
14763
|
+
need5(sameStat(before.stat, after.stat) && after.sha256 === before.sha256, "INPUT_CHANGED");
|
|
14764
|
+
}
|
|
14765
|
+
|
|
14716
14766
|
// src/lib/agent-integration.ts
|
|
14717
|
-
var sha3 = (data) =>
|
|
14767
|
+
var sha3 = (data) => createHash13("sha256").update(data).digest("hex");
|
|
14718
14768
|
var ROOTS = NATIVE_SKILL_ROOTS;
|
|
14719
|
-
function
|
|
14769
|
+
function hasTrustedDarwinAcl(fd, allowDenyOnly = false) {
|
|
14720
14770
|
try {
|
|
14721
|
-
const { dlopen, read: read2 } = __require("bun:ffi");
|
|
14771
|
+
const { dlopen, read: read2, ptr } = __require("bun:ffi");
|
|
14722
14772
|
const library = dlopen("/usr/lib/libSystem.B.dylib", {
|
|
14723
14773
|
acl_get_fd: { args: ["i32"], returns: "ptr" },
|
|
14724
14774
|
acl_free: { args: ["ptr"], returns: "i32" },
|
|
14775
|
+
acl_valid: { args: ["ptr"], returns: "i32" },
|
|
14776
|
+
acl_get_entry: { args: ["ptr", "i32", "ptr"], returns: "i32" },
|
|
14777
|
+
acl_get_tag_type: { args: ["ptr", "ptr"], returns: "i32" },
|
|
14725
14778
|
__error: { args: [], returns: "ptr" }
|
|
14726
14779
|
});
|
|
14727
14780
|
try {
|
|
@@ -14730,8 +14783,23 @@ function hasNoDarwinAcl(fd) {
|
|
|
14730
14783
|
return false;
|
|
14731
14784
|
const acl = library.symbols.acl_get_fd(fd);
|
|
14732
14785
|
if (acl) {
|
|
14733
|
-
|
|
14734
|
-
|
|
14786
|
+
try {
|
|
14787
|
+
if (!allowDenyOnly || library.symbols.acl_valid(acl) !== 0)
|
|
14788
|
+
return false;
|
|
14789
|
+
const entry = new BigUint64Array(1), tag = new Int32Array(1);
|
|
14790
|
+
for (let count = 0;count <= 128; count++) {
|
|
14791
|
+
const status = library.symbols.acl_get_entry(acl, count === 0 ? 0 : -1, ptr(entry));
|
|
14792
|
+
if (status !== 0) {
|
|
14793
|
+
return status === -1 && read2.i32(errno) === 22 && library.symbols.acl_valid(acl) === 0;
|
|
14794
|
+
}
|
|
14795
|
+
const value = read2.ptr(ptr(entry));
|
|
14796
|
+
if (count === 128 || !value || library.symbols.acl_get_tag_type(value, ptr(tag)) !== 0 || tag[0] !== 2)
|
|
14797
|
+
return false;
|
|
14798
|
+
}
|
|
14799
|
+
return false;
|
|
14800
|
+
} finally {
|
|
14801
|
+
library.symbols.acl_free(acl);
|
|
14802
|
+
}
|
|
14735
14803
|
}
|
|
14736
14804
|
return (acl === null || acl === 0) && read2.i32(errno) === 2;
|
|
14737
14805
|
} finally {
|
|
@@ -14747,11 +14815,11 @@ function isSystemRootAlias(path) {
|
|
|
14747
14815
|
const target = `/private${path}`, link = `private${path}`;
|
|
14748
14816
|
const identity4 = (stat) => `${stat.dev}:${stat.ino}:${stat.ctimeNs}:${stat.uid}:${stat.mode}`;
|
|
14749
14817
|
try {
|
|
14750
|
-
const before =
|
|
14818
|
+
const before = lstatSync17(path, { bigint: true });
|
|
14751
14819
|
if (!before.isSymbolicLink() || before.uid !== 0n || readlinkSync4(path) !== link)
|
|
14752
14820
|
return false;
|
|
14753
14821
|
const directories = ["/", "/private", target].map((directory) => {
|
|
14754
|
-
const stat =
|
|
14822
|
+
const stat = lstatSync17(directory, { bigint: true });
|
|
14755
14823
|
if (!stat.isDirectory() || stat.uid !== 0n)
|
|
14756
14824
|
throw new Error("Untrusted system alias directory");
|
|
14757
14825
|
if (directory !== "/private/tmp") {
|
|
@@ -14759,23 +14827,23 @@ function isSystemRootAlias(path) {
|
|
|
14759
14827
|
throw new Error("Writable system alias directory");
|
|
14760
14828
|
} else if ((stat.mode & 0o1000n) === 0n)
|
|
14761
14829
|
throw new Error("Unprotected system temporary directory");
|
|
14762
|
-
const fd =
|
|
14830
|
+
const fd = openSync9(directory, constants8.O_RDONLY | constants8.O_DIRECTORY | constants8.O_NOFOLLOW);
|
|
14763
14831
|
try {
|
|
14764
|
-
if (identity4(
|
|
14832
|
+
if (identity4(fstatSync9(fd, { bigint: true })) !== identity4(stat) || !hasTrustedDarwinAcl(fd) || identity4(fstatSync9(fd, { bigint: true })) !== identity4(stat))
|
|
14765
14833
|
throw new Error("Unverified system alias directory ACL");
|
|
14766
14834
|
} finally {
|
|
14767
|
-
|
|
14835
|
+
closeSync9(fd);
|
|
14768
14836
|
}
|
|
14769
14837
|
return { directory, identity: identity4(stat) };
|
|
14770
14838
|
});
|
|
14771
|
-
return realpathSync7(path) === target && readlinkSync4(path) === link && identity4(
|
|
14839
|
+
return realpathSync7(path) === target && readlinkSync4(path) === link && identity4(lstatSync17(path, { bigint: true })) === identity4(before) && directories.every((item) => identity4(lstatSync17(item.directory, { bigint: true })) === item.identity);
|
|
14772
14840
|
} catch {
|
|
14773
14841
|
return false;
|
|
14774
14842
|
}
|
|
14775
14843
|
}
|
|
14776
14844
|
function canonicalSystemPath(path) {
|
|
14777
14845
|
const absolute = resolve11(path), root = `/${absolute.split(sep5)[1]}`;
|
|
14778
|
-
if (process.platform !== "darwin" || !["/var", "/tmp", "/etc"].includes(root) || !
|
|
14846
|
+
if (process.platform !== "darwin" || !["/var", "/tmp", "/etc"].includes(root) || !lstatSync17(root, { throwIfNoEntry: false })?.isSymbolicLink())
|
|
14779
14847
|
return absolute;
|
|
14780
14848
|
if (!isSystemRootAlias(root))
|
|
14781
14849
|
throw new Error(`Refusing symlink path: ${root}`);
|
|
@@ -14784,9 +14852,9 @@ function canonicalSystemPath(path) {
|
|
|
14784
14852
|
function assertSafePath(path) {
|
|
14785
14853
|
let cursor = resolve11(path);
|
|
14786
14854
|
while (true) {
|
|
14787
|
-
if (
|
|
14855
|
+
if (lstatSync17(cursor, { throwIfNoEntry: false })?.isSymbolicLink() && !isSystemRootAlias(cursor))
|
|
14788
14856
|
throw new Error(`Refusing symlink path: ${cursor}`);
|
|
14789
|
-
const parent =
|
|
14857
|
+
const parent = dirname12(cursor);
|
|
14790
14858
|
if (parent === cursor)
|
|
14791
14859
|
return;
|
|
14792
14860
|
cursor = parent;
|
|
@@ -14795,12 +14863,12 @@ function assertSafePath(path) {
|
|
|
14795
14863
|
function readRootAlias(home, agent) {
|
|
14796
14864
|
assertSafePath(home);
|
|
14797
14865
|
const realHome = realpathSync7(home), alias = join18(realHome, `.${agent}`);
|
|
14798
|
-
const stat =
|
|
14866
|
+
const stat = lstatSync17(alias, { throwIfNoEntry: false, bigint: true });
|
|
14799
14867
|
if (!stat?.isSymbolicLink())
|
|
14800
14868
|
throw new Error(`Agent root alias changed after planning: ${alias}`);
|
|
14801
|
-
const link = readlinkSync4(alias), targetPath = resolve11(
|
|
14869
|
+
const link = readlinkSync4(alias), targetPath = resolve11(dirname12(alias), link);
|
|
14802
14870
|
assertSafePath(targetPath);
|
|
14803
|
-
const targetStat =
|
|
14871
|
+
const targetStat = lstatSync17(targetPath, { throwIfNoEntry: false });
|
|
14804
14872
|
if (!targetStat?.isDirectory())
|
|
14805
14873
|
throw new Error(`Agent root alias must target an existing directory: ${alias}`);
|
|
14806
14874
|
const target = realpathSync7(targetPath), inside = relative5(realHome, target);
|
|
@@ -14813,7 +14881,7 @@ function rootAliases(home, allowed = false) {
|
|
|
14813
14881
|
return [];
|
|
14814
14882
|
const result = [];
|
|
14815
14883
|
for (const agent of ["claude", "codex"])
|
|
14816
|
-
if (
|
|
14884
|
+
if (lstatSync17(join18(home, `.${agent}`), { throwIfNoEntry: false })?.isSymbolicLink())
|
|
14817
14885
|
result.push(readRootAlias(home, agent));
|
|
14818
14886
|
if (result.some((item, index) => result.slice(index + 1).some((other) => item.target === other.target || item.target.startsWith(other.target + sep5) || other.target.startsWith(item.target + sep5))))
|
|
14819
14887
|
throw new Error("Agent root alias targets must not overlap");
|
|
@@ -14848,7 +14916,7 @@ function disabledCodexSkillPaths(config, aliases) {
|
|
|
14848
14916
|
continue;
|
|
14849
14917
|
settings.set(document2, [...settings.get(document2) ?? [], entry.enabled === false]);
|
|
14850
14918
|
}
|
|
14851
|
-
return [...settings].filter(([, values]) => values.length === 1 && values[0] === true).map(([document2]) =>
|
|
14919
|
+
return [...settings].filter(([, values]) => values.length === 1 && values[0] === true).map(([document2]) => dirname12(document2));
|
|
14852
14920
|
}
|
|
14853
14921
|
function recheckRootAliases(aliases) {
|
|
14854
14922
|
for (const binding of aliases) {
|
|
@@ -14861,35 +14929,35 @@ function recheckRootAliases(aliases) {
|
|
|
14861
14929
|
}
|
|
14862
14930
|
function readNativeBytes(path, maximum = 64 * 1024 * 1024) {
|
|
14863
14931
|
assertSafePath(path);
|
|
14864
|
-
const before =
|
|
14932
|
+
const before = lstatSync17(path);
|
|
14865
14933
|
if (!before.isFile() || before.size > maximum)
|
|
14866
14934
|
throw new Error("Unsupported or oversized native skill file");
|
|
14867
|
-
const descriptor =
|
|
14935
|
+
const descriptor = openSync9(path, constants8.O_RDONLY | constants8.O_NOFOLLOW | constants8.O_NONBLOCK);
|
|
14868
14936
|
try {
|
|
14869
|
-
const opened =
|
|
14937
|
+
const opened = fstatSync9(descriptor);
|
|
14870
14938
|
if (!opened.isFile() || opened.dev !== before.dev || opened.ino !== before.ino || opened.size !== before.size)
|
|
14871
14939
|
throw new Error("Native skill file changed while reading");
|
|
14872
14940
|
const bytes = Buffer.allocUnsafe(before.size + 1);
|
|
14873
14941
|
let length = 0;
|
|
14874
14942
|
while (length < bytes.length) {
|
|
14875
|
-
const count =
|
|
14943
|
+
const count = readSync7(descriptor, bytes, length, bytes.length - length, null);
|
|
14876
14944
|
if (!count)
|
|
14877
14945
|
break;
|
|
14878
14946
|
length += count;
|
|
14879
14947
|
}
|
|
14880
|
-
const after =
|
|
14948
|
+
const after = fstatSync9(descriptor), current = lstatSync17(path);
|
|
14881
14949
|
if (current.dev !== opened.dev || current.ino !== opened.ino || current.size !== after.size || current.mtimeMs !== after.mtimeMs || current.ctimeMs !== after.ctimeMs || length !== before.size || after.size !== before.size || after.mtimeMs !== opened.mtimeMs || after.ctimeMs !== opened.ctimeMs)
|
|
14882
14950
|
throw new Error("Native skill file changed while reading");
|
|
14883
14951
|
return bytes.subarray(0, length);
|
|
14884
14952
|
} finally {
|
|
14885
|
-
|
|
14953
|
+
closeSync9(descriptor);
|
|
14886
14954
|
}
|
|
14887
14955
|
}
|
|
14888
14956
|
function treeHash(root) {
|
|
14889
|
-
const hash2 =
|
|
14957
|
+
const hash2 = createHash13("sha256");
|
|
14890
14958
|
let bytes = 0, files = 0;
|
|
14891
14959
|
function visit2(path) {
|
|
14892
|
-
const stat =
|
|
14960
|
+
const stat = lstatSync17(path);
|
|
14893
14961
|
if (stat.isSymbolicLink())
|
|
14894
14962
|
throw new Error(`Refusing symlink in skill: ${path}`);
|
|
14895
14963
|
const rel = relative5(root, path);
|
|
@@ -14921,7 +14989,7 @@ function selectNativeMigrationTargets(inventory, manifest) {
|
|
|
14921
14989
|
if (!isAbsolute11(target.projectRoot) || resolve11(target.projectRoot) !== target.projectRoot)
|
|
14922
14990
|
throw new Error(`Native migration target project root must be an existing absolute directory: ${target.projectRoot}`);
|
|
14923
14991
|
const projectRoot = canonicalSystemPath(target.projectRoot), normalized = relative5(projectRoot, resolve11(projectRoot, target.path));
|
|
14924
|
-
if (!existsSync9(projectRoot) || !
|
|
14992
|
+
if (!existsSync9(projectRoot) || !lstatSync17(projectRoot).isDirectory())
|
|
14925
14993
|
throw new Error(`Native migration target project root must be an existing absolute directory: ${target.projectRoot}`);
|
|
14926
14994
|
assertSafePath(projectRoot);
|
|
14927
14995
|
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))
|
|
@@ -14950,7 +15018,7 @@ function projectAncestorDirectories(projects) {
|
|
|
14950
15018
|
if (depth >= 100)
|
|
14951
15019
|
throw new Error("NATIVE_SKILL_DRIFT: project ancestor discovery limit exceeded");
|
|
14952
15020
|
directories.add(path);
|
|
14953
|
-
const parent =
|
|
15021
|
+
const parent = dirname12(path);
|
|
14954
15022
|
if (parent === path)
|
|
14955
15023
|
break;
|
|
14956
15024
|
path = parent;
|
|
@@ -14996,12 +15064,12 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
14996
15064
|
let reviewedCacheAliasFound = false;
|
|
14997
15065
|
function verifiedSiblingCacheAlias(agent, path, parent) {
|
|
14998
15066
|
const link = readlinkSync4(path), lexicalTarget = resolve11(parent, link);
|
|
14999
|
-
if (isAbsolute11(link) ? link !== lexicalTarget :
|
|
15067
|
+
if (isAbsolute11(link) ? link !== lexicalTarget : dirname12(link) !== ".")
|
|
15000
15068
|
return false;
|
|
15001
15069
|
const target = canonicalSystemPath(lexicalTarget);
|
|
15002
|
-
if (
|
|
15070
|
+
if (dirname12(target) !== parent)
|
|
15003
15071
|
return false;
|
|
15004
|
-
if (!
|
|
15072
|
+
if (!lstatSync17(target, { throwIfNoEntry: false })?.isDirectory())
|
|
15005
15073
|
return false;
|
|
15006
15074
|
const scan = cacheScans.get(target);
|
|
15007
15075
|
if (!scan?.complete || scan.entries > maxAliasProofEntries)
|
|
@@ -15026,7 +15094,7 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
15026
15094
|
return scan;
|
|
15027
15095
|
if (!existsSync9(path))
|
|
15028
15096
|
return scan;
|
|
15029
|
-
const stat =
|
|
15097
|
+
const stat = lstatSync17(path);
|
|
15030
15098
|
if (stat.isFile()) {
|
|
15031
15099
|
if (agent === "sumi" && depth === 1 && path.endsWith(".md"))
|
|
15032
15100
|
throw new Error("NATIVE_SKILL_DRIFT: flat Sumi skill documents require a dedicated migration adapter");
|
|
@@ -15075,7 +15143,7 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
15075
15143
|
if (isVendor && !options.includeVendor)
|
|
15076
15144
|
continue;
|
|
15077
15145
|
const child = join18(path, name);
|
|
15078
|
-
if (isVendor &&
|
|
15146
|
+
if (isVendor && lstatSync17(child, { throwIfNoEntry: false })?.isSymbolicLink()) {
|
|
15079
15147
|
const target = statSync8(child, { throwIfNoEntry: false });
|
|
15080
15148
|
if (target?.isFile() || pluginCache && target?.isDirectory() && verifiedSiblingCacheAlias(agent, child, path)) {
|
|
15081
15149
|
scan.entries++;
|
|
@@ -15290,15 +15358,21 @@ function planAgentIntegration(options) {
|
|
|
15290
15358
|
}
|
|
15291
15359
|
return resolveAgentDiscovery({ home, agent, reviewed: options.discoveryInputs, retainedReview, canonical: (path) => canonicalAgentPath(path, aliases) });
|
|
15292
15360
|
});
|
|
15293
|
-
const
|
|
15361
|
+
const codexConfigBefore = options.agents.includes("codex") ? readOptional(canonicalAgentPath(join18(home, ".codex", "config.toml"), aliases)) : null;
|
|
15362
|
+
const codexConfig = Bun.TOML.parse(codexConfigBefore ?? "");
|
|
15294
15363
|
const nativeSkills = inventoryNativeSkills(home, { includeVendor: true, guardHermes: options.agents.includes("hermes"), agents: options.agents, projectDir: options.projectDir, agentRoots: discoveries.flatMap((binding) => binding.roots.map((path) => ({ agent: binding.agent, path }))), allowRootAliases: options.allowRootAliases, reviewedCacheAlias: options.reviewedCacheAlias, disabledVendorPaths: disabledCodexSkillPaths(codexConfig, aliases) });
|
|
15364
|
+
const codexPathConfig = options.agents.includes("codex") ? disableCodexSkills(codexConfigBefore ?? "", nativeSkills, aliases, canonicalAgentPath(join18(home, ".codex/skills", CLI_BRIDGE_NAME), aliases)) : undefined;
|
|
15365
|
+
const plannedCodexRules = codexPathConfig === undefined ? undefined : Bun.TOML.parse(codexPathConfig).skills?.config ?? [];
|
|
15295
15366
|
const observedNativeSources = [];
|
|
15296
15367
|
const codexPluginControls = options.codexNativeCatalog ? reviewCodexPluginControls(options.codexNativeCatalog, nativeSkills.filter((entry) => entry.agent === "codex" && entry.vendor).map((entry) => join18(entry.path, "SKILL.md")), canonicalAgentPath(join18(home, ".codex/plugins/cache"), aliases), resolve11(options.projectDir ?? home), (path) => {
|
|
15297
15368
|
const bytes = readNativeBytes(path, 1024 * 1024);
|
|
15298
15369
|
observedNativeSources.push({ path, sha256: sha3(bytes) });
|
|
15299
15370
|
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
15300
|
-
}, codexConfig.skills?.config ?? [], codexConfig.plugins) : { skills: policy.bridge?.codexPluginSkills ?? [], inactivePlugins: policy.bridge?.codexInactivePlugins ?? [], sourceInputs: policy.bridge?.discovery?.codex?.codexInstallationInputs?.plugins ?? [] };
|
|
15371
|
+
}, codexConfig.skills?.config ?? [], codexConfig.plugins, plannedCodexRules) : { skills: policy.bridge?.codexPluginSkills ?? [], inactivePlugins: policy.bridge?.codexInactivePlugins ?? [], sourceInputs: policy.bridge?.discovery?.codex?.codexInstallationInputs?.plugins ?? [] };
|
|
15301
15372
|
const codexPluginSkills = codexPluginControls.skills;
|
|
15373
|
+
if (options.agents.includes("codex") && !options.codexNativeCatalog && codexPluginSkills.length && !reviewedCodexPluginCapabilitiesUnchanged(canonicalAgentPath(join18(home, ".codex/plugins/cache"), aliases), codexPluginSkills, (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024)))) {
|
|
15374
|
+
throw new Error("NATIVE_SKILL_DRIFT: reviewed native plugin capability controls changed; provide a fresh native catalog and discovery review");
|
|
15375
|
+
}
|
|
15302
15376
|
const codexInactivePlugins = codexPluginControls.inactivePlugins;
|
|
15303
15377
|
const codexDiscovery = discoveries.find((binding) => binding.agent === "codex");
|
|
15304
15378
|
const codexPluginSourceInputs = codexPluginControls.sourceInputs.filter((input) => codexDiscovery?.method === "reviewed" && codexDiscovery.roots.some((root) => root === input.sourceRoot || root.startsWith(input.sourceRoot + sep5) || input.sourceRoot.startsWith(root + sep5)) && codexDiscovery.sources.some((source) => source.path === join18(input.sourceRoot, ".codex-plugin/plugin.json") && source.sha256 !== null && source.format === undefined && source.fields === undefined));
|
|
@@ -15373,7 +15447,9 @@ function planAgentIntegration(options) {
|
|
|
15373
15447
|
changes.push({ path, before, after });
|
|
15374
15448
|
if (agent === "codex") {
|
|
15375
15449
|
const configPath = canonicalAgentPath(join18(home, ".codex", "config.toml"), aliases), previous = readOptional(configPath);
|
|
15376
|
-
|
|
15450
|
+
if (previous !== codexConfigBefore)
|
|
15451
|
+
throw new Error(`Configuration changed during planning: ${configPath}`);
|
|
15452
|
+
const next = disableReviewedCodexPluginNames(codexPathConfig, codexPluginSkills);
|
|
15377
15453
|
if (next !== (previous ?? ""))
|
|
15378
15454
|
changes.push({ path: configPath, before: previous, after: next });
|
|
15379
15455
|
}
|
|
@@ -15409,6 +15485,171 @@ function planAgentIntegration(options) {
|
|
|
15409
15485
|
recheckRootAliases(aliases);
|
|
15410
15486
|
return { dataDir: dataDir2, profileId, changes, nativeSkills, ...observedNativeSources.length ? { observedNativeSources } : {}, ...nextPolicy.bridge.codexPluginSkillReview ? { codexPluginSkillReview: nextPolicy.bridge.codexPluginSkillReview } : {}, observedPolicy: { path: policyPath, before: previousPolicy }, discoveryBefore: discoveries, discoveryAfter, ...aliases.length ? { rootAliases: aliases } : {}, ...retainedAgents.length ? { retainedReviewChecks: { home, projectDir: options.projectDir ?? home, agents: retainedAgents } } : {} };
|
|
15411
15487
|
}
|
|
15488
|
+
function hookProjectionRefusal(reason) {
|
|
15489
|
+
throw new Error(`CLAUDE_MANAGED_HOOK_${reason}`);
|
|
15490
|
+
}
|
|
15491
|
+
function projectedHookAcl(path, observed) {
|
|
15492
|
+
if (process.platform !== "darwin")
|
|
15493
|
+
return;
|
|
15494
|
+
const identity4 = (stat) => `${stat.dev}:${stat.ino}:${stat.ctimeNs}:${stat.uid}:${stat.mode}:${stat.nlink}:${stat.size}:${stat.mtimeNs}`;
|
|
15495
|
+
const fd = openSync9(path, constants8.O_RDONLY | constants8.O_NOFOLLOW | constants8.O_NONBLOCK | (observed.isDirectory() ? constants8.O_DIRECTORY : 0));
|
|
15496
|
+
try {
|
|
15497
|
+
if (identity4(fstatSync9(fd, { bigint: true })) !== identity4(observed) || !hasTrustedDarwinAcl(fd, true) || identity4(fstatSync9(fd, { bigint: true })) !== identity4(observed) || identity4(lstatSync17(path, { bigint: true })) !== identity4(observed))
|
|
15498
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
15499
|
+
} finally {
|
|
15500
|
+
closeSync9(fd);
|
|
15501
|
+
}
|
|
15502
|
+
}
|
|
15503
|
+
function projectedHookExecutable(command, expectedResolved) {
|
|
15504
|
+
try {
|
|
15505
|
+
if (!isAbsolute11(command) || /[\0\r\n]/.test(command) || resolve11(command) !== command)
|
|
15506
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
15507
|
+
command = canonicalSystemPath(command);
|
|
15508
|
+
const parents2 = (file = command) => {
|
|
15509
|
+
const result = [];
|
|
15510
|
+
for (let path = dirname12(file);; path = dirname12(path)) {
|
|
15511
|
+
const stat = lstatSync17(path, { bigint: true });
|
|
15512
|
+
if (!stat.isDirectory() || (stat.mode & 0o022n) !== 0n && !(stat.uid === 0n && (stat.mode & 0o1000n) !== 0n))
|
|
15513
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
15514
|
+
projectedHookAcl(path, stat);
|
|
15515
|
+
result.push(`${path}:${stat.dev}:${stat.ino}:${stat.uid}:${stat.mode}`);
|
|
15516
|
+
if (dirname12(path) === path)
|
|
15517
|
+
break;
|
|
15518
|
+
}
|
|
15519
|
+
return result;
|
|
15520
|
+
};
|
|
15521
|
+
const parentIdentity = parents2(), link = lstatSync17(command, { bigint: true });
|
|
15522
|
+
if (link.uid !== BigInt(process.getuid()) || !link.isFile() && !link.isSymbolicLink())
|
|
15523
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
15524
|
+
const linkIdentity = (stat) => `${stat.dev}:${stat.ino}:${stat.mode}:${stat.uid}:${stat.ctimeNs}`;
|
|
15525
|
+
const resolved = realpathSync7(command);
|
|
15526
|
+
if (expectedResolved !== undefined && resolved !== expectedResolved)
|
|
15527
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
15528
|
+
const resolvedParentIdentity = parents2(resolved);
|
|
15529
|
+
const manifest = snapshot(join18(dirname12(dirname12(resolved)), "package.json"), false, { readOnlyPackage: true });
|
|
15530
|
+
projectedHookAcl(manifest.file, manifest.stat);
|
|
15531
|
+
const pkg = JSON.parse(manifest.text);
|
|
15532
|
+
if (pkg.name !== "@hasna/skills" || !/^\d+\.\d+\.\d+$/.test(pkg.version) || pkg.bin?.skills !== "bin/index.js" || resolved !== join18(dirname12(manifest.file), "bin/index.js"))
|
|
15533
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
15534
|
+
const cli = snapshot(resolved, false, { readOnlyPackage: true });
|
|
15535
|
+
projectedHookAcl(cli.file, cli.stat);
|
|
15536
|
+
if ((cli.stat.mode & 0o100n) === 0n)
|
|
15537
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
15538
|
+
const recheck = () => {
|
|
15539
|
+
try {
|
|
15540
|
+
if (realpathSync7(command) !== resolved || linkIdentity(lstatSync17(command, { bigint: true })) !== linkIdentity(link) || !isDeepStrictEqual4(parents2(), parentIdentity) || !isDeepStrictEqual4(parents2(resolved), resolvedParentIdentity))
|
|
15541
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
15542
|
+
projectedHookAcl(cli.file, cli.stat);
|
|
15543
|
+
projectedHookAcl(manifest.file, manifest.stat);
|
|
15544
|
+
unchanged2(cli);
|
|
15545
|
+
unchanged2(manifest);
|
|
15546
|
+
} catch {
|
|
15547
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
15548
|
+
}
|
|
15549
|
+
};
|
|
15550
|
+
recheck();
|
|
15551
|
+
return { resolved, sha256: cli.sha256, manifestSha256: manifest.sha256, recheck };
|
|
15552
|
+
} catch {
|
|
15553
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
15554
|
+
}
|
|
15555
|
+
}
|
|
15556
|
+
function planClaudeManagedHookProjection(options) {
|
|
15557
|
+
if (typeof options.targetSettings !== "string" || Buffer.byteLength(options.targetSettings) > 1024 * 1024)
|
|
15558
|
+
hookProjectionRefusal("TARGET_INVALID");
|
|
15559
|
+
const targetSha256 = sha3(options.targetSettings);
|
|
15560
|
+
if (!/^[a-f0-9]{64}$/.test(options.expectedTargetSha256) || options.expectedTargetSha256 !== targetSha256)
|
|
15561
|
+
hookProjectionRefusal("TARGET_CHANGED");
|
|
15562
|
+
let target;
|
|
15563
|
+
try {
|
|
15564
|
+
target = jsonObject(options.targetSettings, "target");
|
|
15565
|
+
} catch {
|
|
15566
|
+
hookProjectionRefusal("TARGET_INVALID");
|
|
15567
|
+
}
|
|
15568
|
+
const home = resolve11(options.home ?? homedir2()), dataDir2 = options.dataDir ?? getDataDirReadOnly();
|
|
15569
|
+
const policyPath = join18(dataDir2, "agent-policy.json");
|
|
15570
|
+
let policyBytes, settingsBytes, settingsPath, command, profile;
|
|
15571
|
+
try {
|
|
15572
|
+
policyBytes = readNativeBytes(policyPath, 1024 * 1024);
|
|
15573
|
+
const snapshot2 = readManagedSkillPolicySnapshot(dataDir2);
|
|
15574
|
+
if (!snapshot2 || snapshot2.text !== policyBytes.toString("utf8"))
|
|
15575
|
+
hookProjectionRefusal("SOURCE_UNVERIFIED");
|
|
15576
|
+
const binding = snapshot2.value.bridge;
|
|
15577
|
+
settingsPath = canonicalAgentPath(join18(home, AGENT_ADAPTERS.claude.config), binding?.rootAliases ?? []);
|
|
15578
|
+
settingsBytes = readNativeBytes(settingsPath, 1024 * 1024);
|
|
15579
|
+
command = binding?.commands?.claude;
|
|
15580
|
+
profile = binding?.profiles?.claude;
|
|
15581
|
+
if (typeof command !== "string" || typeof profile !== "string")
|
|
15582
|
+
hookProjectionRefusal("SOURCE_UNVERIFIED");
|
|
15583
|
+
assertManagedAgentBridge("claude", { home, dataDir: dataDir2, projectDir: options.projectDir, profileId: profile });
|
|
15584
|
+
} catch {
|
|
15585
|
+
hookProjectionRefusal("SOURCE_UNVERIFIED");
|
|
15586
|
+
}
|
|
15587
|
+
const executable = projectedHookExecutable(command);
|
|
15588
|
+
const sourceSha256 = sha3(JSON.stringify({
|
|
15589
|
+
home,
|
|
15590
|
+
policyPath,
|
|
15591
|
+
policy: sha3(policyBytes),
|
|
15592
|
+
settingsPath,
|
|
15593
|
+
settings: sha3(settingsBytes),
|
|
15594
|
+
executable: { path: executable.resolved, sha256: executable.sha256, manifestSha256: executable.manifestSha256 }
|
|
15595
|
+
}));
|
|
15596
|
+
if (options.expectedSourceSha256 !== undefined && options.expectedSourceSha256 !== sourceSha256)
|
|
15597
|
+
hookProjectionRefusal("SOURCE_CHANGED");
|
|
15598
|
+
const replacements = [];
|
|
15599
|
+
const observed = [executable];
|
|
15600
|
+
if (target.hooks !== undefined) {
|
|
15601
|
+
if (!target.hooks || typeof target.hooks !== "object" || Array.isArray(target.hooks))
|
|
15602
|
+
hookProjectionRefusal("TARGET_INVALID");
|
|
15603
|
+
for (const event of [...AGENT_ADAPTERS.claude.events, "PreToolUse"]) {
|
|
15604
|
+
const groups = target.hooks[event];
|
|
15605
|
+
if (groups === undefined)
|
|
15606
|
+
continue;
|
|
15607
|
+
if (!Array.isArray(groups))
|
|
15608
|
+
hookProjectionRefusal("TARGET_INVALID");
|
|
15609
|
+
let recognized = 0;
|
|
15610
|
+
for (const [groupIndex, group] of groups.entries()) {
|
|
15611
|
+
if (!group || !Array.isArray(group.hooks))
|
|
15612
|
+
hookProjectionRefusal("TARGET_INVALID");
|
|
15613
|
+
for (const [hookIndex, hook] of group.hooks.entries()) {
|
|
15614
|
+
if (hook?.type !== "command" || typeof hook.command !== "string")
|
|
15615
|
+
continue;
|
|
15616
|
+
if (!hook.command.includes("hook user-prompt --agent claude"))
|
|
15617
|
+
continue;
|
|
15618
|
+
const match = /^('(?:[^']|'\\'')*') hook user-prompt --agent claude --selection-profile ([A-Za-z0-9][A-Za-z0-9._-]{0,127}) --event ([A-Za-z]+)$/.exec(hook.command);
|
|
15619
|
+
if (!match || match[3] !== event)
|
|
15620
|
+
hookProjectionRefusal("UNRECOGNIZED");
|
|
15621
|
+
const oldCommand = match[1].slice(1, -1).replaceAll("'\\''", "'");
|
|
15622
|
+
let rendered;
|
|
15623
|
+
try {
|
|
15624
|
+
rendered = renderAgentHookCommand(oldCommand, "claude", match[2], event);
|
|
15625
|
+
} catch {
|
|
15626
|
+
hookProjectionRefusal("UNRECOGNIZED");
|
|
15627
|
+
}
|
|
15628
|
+
if (rendered !== hook.command)
|
|
15629
|
+
hookProjectionRefusal("UNRECOGNIZED");
|
|
15630
|
+
const legacy = projectedHookExecutable(oldCommand, executable.resolved);
|
|
15631
|
+
if (legacy.resolved !== executable.resolved || legacy.sha256 !== executable.sha256 || legacy.manifestSha256 !== executable.manifestSha256)
|
|
15632
|
+
hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
|
|
15633
|
+
observed.push(legacy);
|
|
15634
|
+
if (++recognized > 1)
|
|
15635
|
+
hookProjectionRefusal("AMBIGUOUS");
|
|
15636
|
+
const replacement = renderAgentHookCommand(command, "claude", profile, event);
|
|
15637
|
+
if (replacement !== hook.command)
|
|
15638
|
+
replacements.push({ event, groupIndex, hookIndex, beforeSha256: sha3(hook.command), command: replacement });
|
|
15639
|
+
}
|
|
15640
|
+
}
|
|
15641
|
+
}
|
|
15642
|
+
}
|
|
15643
|
+
try {
|
|
15644
|
+
if (!readNativeBytes(policyPath, 1024 * 1024).equals(policyBytes) || !readNativeBytes(settingsPath, 1024 * 1024).equals(settingsBytes))
|
|
15645
|
+
hookProjectionRefusal("SOURCE_CHANGED");
|
|
15646
|
+
} catch {
|
|
15647
|
+
hookProjectionRefusal("SOURCE_CHANGED");
|
|
15648
|
+
}
|
|
15649
|
+
for (const item of observed)
|
|
15650
|
+
item.recheck();
|
|
15651
|
+
return { schema: "hasna.claude-managed-hook-projection.v1", targetSha256, sourceSha256, replacements };
|
|
15652
|
+
}
|
|
15412
15653
|
function planClaudeStopHookUpdate(options) {
|
|
15413
15654
|
return planClaudeHookEventsUpdate({ ...options, events: ["Stop"] });
|
|
15414
15655
|
}
|
|
@@ -15441,10 +15682,10 @@ function planClaudeHookEventsUpdate(options) {
|
|
|
15441
15682
|
const home = resolve11(options.home ?? homedir2()), dataDir2 = options.dataDir ?? getDataDirReadOnly();
|
|
15442
15683
|
const policyPath = join18(dataDir2, "agent-policy.json");
|
|
15443
15684
|
assertSafePath(policyPath);
|
|
15444
|
-
const
|
|
15445
|
-
if (!
|
|
15685
|
+
const snapshot2 = readManagedSkillPolicySnapshot(dataDir2);
|
|
15686
|
+
if (!snapshot2)
|
|
15446
15687
|
return null;
|
|
15447
|
-
const policy =
|
|
15688
|
+
const policy = snapshot2.value, bridge = policy.bridge;
|
|
15448
15689
|
if (!bridge || bridge.home !== home || bridge.version !== CLI_BRIDGE_VERSION || bridge.digest !== CLI_BRIDGE_DIGEST || !Array.isArray(bridge.agents) || !bridge.agents.length || bridge.agents.some((agent) => !INTEGRATION_AGENTS.includes(agent)) || new Set(bridge.agents).size !== bridge.agents.length || !bridge.discovery || typeof bridge.discovery !== "object" || Array.isArray(bridge.discovery)) {
|
|
15449
15690
|
throw new Error("Invalid managed bridge for Claude settings update");
|
|
15450
15691
|
}
|
|
@@ -15452,7 +15693,7 @@ function planClaudeHookEventsUpdate(options) {
|
|
|
15452
15693
|
recheckRootAliases(aliases);
|
|
15453
15694
|
const path = canonicalAgentPath(join18(home, ".claude", "settings.json"), aliases);
|
|
15454
15695
|
assertSafePath(path);
|
|
15455
|
-
const before =
|
|
15696
|
+
const before = lstatSync17(path, { throwIfNoEntry: false }) ? new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(readNativeBytes(path, 1024 * 1024)) : null;
|
|
15456
15697
|
if ((before === null ? "absent" : sha3(before)) !== options.expectedSettingsSha256)
|
|
15457
15698
|
throw new Error("Claude settings preimage changed");
|
|
15458
15699
|
assertClaudeHookEventsReplacement(before, options.replacement, options.events);
|
|
@@ -15482,7 +15723,7 @@ function planClaudeHookEventsUpdate(options) {
|
|
|
15482
15723
|
const nextPolicy = { ...policy, bridge: { ...bridge, discovery: Object.fromEntries(discoveryAfter.map((binding) => [binding.agent, binding])) } };
|
|
15483
15724
|
const changes = before === options.replacement ? [] : [{ path, before, after: options.replacement }];
|
|
15484
15725
|
if (JSON.stringify(nextPolicy) !== JSON.stringify(policy))
|
|
15485
|
-
changes.push({ path: policyPath, before:
|
|
15726
|
+
changes.push({ path: policyPath, before: snapshot2.text, after: serializeManagedSkillPolicy(nextPolicy) });
|
|
15486
15727
|
recheckRootAliases(aliases);
|
|
15487
15728
|
const nativeSkills = inventoryNativeSkills(home, {
|
|
15488
15729
|
agents: consumers,
|
|
@@ -15497,7 +15738,7 @@ function planClaudeHookEventsUpdate(options) {
|
|
|
15497
15738
|
profileId: policy.profileId ?? "default",
|
|
15498
15739
|
changes,
|
|
15499
15740
|
nativeSkills,
|
|
15500
|
-
observedPolicy: { path: policyPath, before:
|
|
15741
|
+
observedPolicy: { path: policyPath, before: snapshot2.text },
|
|
15501
15742
|
discoveryBefore: discoveries,
|
|
15502
15743
|
discoveryAfter,
|
|
15503
15744
|
rootAliases: aliases,
|
|
@@ -15506,10 +15747,10 @@ function planClaudeHookEventsUpdate(options) {
|
|
|
15506
15747
|
}
|
|
15507
15748
|
function atomicWrite2(path, content) {
|
|
15508
15749
|
assertSafePath(path);
|
|
15509
|
-
mkdirSync5(
|
|
15750
|
+
mkdirSync5(dirname12(path), { recursive: true, mode: 448 });
|
|
15510
15751
|
const temporary = `${path}.skills-${randomUUID2()}`;
|
|
15511
15752
|
try {
|
|
15512
|
-
|
|
15753
|
+
writeFileSync5(temporary, content, { mode: 384, flag: "wx" });
|
|
15513
15754
|
renameSync2(temporary, path);
|
|
15514
15755
|
} finally {
|
|
15515
15756
|
if (existsSync9(temporary))
|
|
@@ -15520,10 +15761,10 @@ function planAgentSettingsWitnessUpgrade(options) {
|
|
|
15520
15761
|
if (options.targetCodexVersion !== undefined && (options.agent !== "codex" || ![2, 3].includes(options.targetCodexVersion)))
|
|
15521
15762
|
throw new Error("Invalid Codex witness target version");
|
|
15522
15763
|
const home = resolve11(options.home ?? homedir2()), dataDir2 = options.dataDir ?? getDataDirReadOnly();
|
|
15523
|
-
const
|
|
15524
|
-
if (!
|
|
15764
|
+
const snapshot2 = readManagedSkillPolicySnapshot(dataDir2);
|
|
15765
|
+
if (!snapshot2 || sha3(snapshot2.text) !== options.expectedPolicySha256)
|
|
15525
15766
|
throw new Error("Managed policy preimage changed");
|
|
15526
|
-
const policy =
|
|
15767
|
+
const policy = snapshot2.value, aliases = policy.bridge?.rootAliases ?? [];
|
|
15527
15768
|
recheckRootAliases(aliases);
|
|
15528
15769
|
const binding = policy.bridge?.discovery?.[options.agent];
|
|
15529
15770
|
if (!binding || binding.agent !== options.agent || binding.method !== "reviewed")
|
|
@@ -15567,7 +15808,7 @@ function planAgentSettingsWitnessUpgrade(options) {
|
|
|
15567
15808
|
const replacement = { ...binding, sources: rebound };
|
|
15568
15809
|
verifyAgentDiscovery(replacement);
|
|
15569
15810
|
const after = serializeManagedSkillPolicy({ ...policy, bridge: { ...policy.bridge, discovery: { ...policy.bridge.discovery, [options.agent]: replacement } } });
|
|
15570
|
-
return { dataDir: dataDir2, profileId: policy.profileId, changes: [{ path: join18(dataDir2, "agent-policy.json"), before:
|
|
15811
|
+
return { dataDir: dataDir2, profileId: policy.profileId, changes: [{ path: join18(dataDir2, "agent-policy.json"), before: snapshot2.text, after }], nativeSkills: [], observedPolicy: { path: join18(dataDir2, "agent-policy.json"), before: snapshot2.text }, observedSettings: { path: configPath, before: settings }, discoveryBefore: [replacement], discoveryAfter: [replacement], rootAliases: aliases, managedAgentChecks: { home, agents: [options.agent] }, settingsWitnessUpgrade: { agent: options.agent, path: configPath, fromHashMode: previous.hashMode ?? "bytes", fromSha256: previous.sha256, toHashMode: next.hashMode, toSha256: next.sha256, reviewedPreimage: options.reviewedPreimage, currentSettingsSha256: options.expectedSettingsSha256, replacedWitnesses: replaced.map((source) => ({ hashMode: source.hashMode ?? "bytes", sha256: source.sha256 })) } };
|
|
15571
15812
|
}
|
|
15572
15813
|
function applyAgentIntegration(plan) {
|
|
15573
15814
|
for (const source of plan.observedNativeSources ?? [])
|
|
@@ -15620,7 +15861,7 @@ function applyAgentIntegration(plan) {
|
|
|
15620
15861
|
for (const [index, change] of plan.changes.entries())
|
|
15621
15862
|
if (change.before !== null) {
|
|
15622
15863
|
const backup = join18(backupRoot, `${index}-${sha3(change.path).slice(0, 12)}.backup`);
|
|
15623
|
-
|
|
15864
|
+
writeFileSync5(backup, change.before, { mode: 384, flag: "wx" });
|
|
15624
15865
|
if (readFileSync12(backup, "utf8") !== change.before)
|
|
15625
15866
|
throw new Error("Native configuration preservation readback failed");
|
|
15626
15867
|
backups.push(backup);
|
|
@@ -15630,7 +15871,7 @@ function applyAgentIntegration(plan) {
|
|
|
15630
15871
|
recheckRootAliases(aliases);
|
|
15631
15872
|
if (currentText(change.path) !== change.before)
|
|
15632
15873
|
throw new Error(`Configuration changed after planning: ${change.path}`);
|
|
15633
|
-
for (let directory =
|
|
15874
|
+
for (let directory = dirname12(change.path);!existsSync9(directory); directory = dirname12(directory))
|
|
15634
15875
|
createdDirectories.add(directory);
|
|
15635
15876
|
const after = change.after;
|
|
15636
15877
|
atomicWrite2(change.path, after);
|
|
@@ -15681,11 +15922,11 @@ function applyAgentIntegration(plan) {
|
|
|
15681
15922
|
}
|
|
15682
15923
|
function syncArchiveDirectory(path) {
|
|
15683
15924
|
assertSafePath(path);
|
|
15684
|
-
const descriptor =
|
|
15925
|
+
const descriptor = openSync9(path, constants8.O_RDONLY | constants8.O_DIRECTORY | constants8.O_NOFOLLOW | constants8.O_NONBLOCK);
|
|
15685
15926
|
try {
|
|
15686
|
-
|
|
15927
|
+
fsyncSync3(descriptor);
|
|
15687
15928
|
} finally {
|
|
15688
|
-
|
|
15929
|
+
closeSync9(descriptor);
|
|
15689
15930
|
}
|
|
15690
15931
|
}
|
|
15691
15932
|
function writeArchiveJournal(path, value) {
|
|
@@ -15693,18 +15934,18 @@ function writeArchiveJournal(path, value) {
|
|
|
15693
15934
|
const temporary = `${path}.skills-${randomUUID2()}`;
|
|
15694
15935
|
let descriptor;
|
|
15695
15936
|
try {
|
|
15696
|
-
descriptor =
|
|
15697
|
-
|
|
15937
|
+
descriptor = openSync9(temporary, constants8.O_WRONLY | constants8.O_CREAT | constants8.O_EXCL | constants8.O_NOFOLLOW, 384);
|
|
15938
|
+
writeFileSync5(descriptor, `${JSON.stringify(value)}
|
|
15698
15939
|
`);
|
|
15699
|
-
|
|
15700
|
-
|
|
15940
|
+
fsyncSync3(descriptor);
|
|
15941
|
+
closeSync9(descriptor);
|
|
15701
15942
|
descriptor = undefined;
|
|
15702
15943
|
renameSync2(temporary, path);
|
|
15703
|
-
syncArchiveDirectory(
|
|
15944
|
+
syncArchiveDirectory(dirname12(path));
|
|
15704
15945
|
} finally {
|
|
15705
15946
|
if (descriptor !== undefined)
|
|
15706
|
-
|
|
15707
|
-
if (
|
|
15947
|
+
closeSync9(descriptor);
|
|
15948
|
+
if (lstatSync17(temporary, { throwIfNoEntry: false }))
|
|
15708
15949
|
unlinkSync3(temporary);
|
|
15709
15950
|
}
|
|
15710
15951
|
}
|
|
@@ -15738,7 +15979,7 @@ function archiveNativeSkills(inventory, options) {
|
|
|
15738
15979
|
const moved = [];
|
|
15739
15980
|
const verifyArchive = (entry) => {
|
|
15740
15981
|
assertSafePath(entry.archive);
|
|
15741
|
-
const stat =
|
|
15982
|
+
const stat = lstatSync17(entry.archive);
|
|
15742
15983
|
if (entry.discoveryOnly ? !stat.isFile() : !stat.isDirectory())
|
|
15743
15984
|
throw new Error("Archive type changed");
|
|
15744
15985
|
if ((entry.discoveryOnly ? sha3(readNativeBytes(entry.archive)) : treeHash(entry.archive)) !== entry.hash)
|
|
@@ -15746,14 +15987,14 @@ function archiveNativeSkills(inventory, options) {
|
|
|
15746
15987
|
};
|
|
15747
15988
|
assertSafePath(archiveRoot);
|
|
15748
15989
|
const created = [];
|
|
15749
|
-
for (let path = archiveRoot;!existsSync9(path); path =
|
|
15990
|
+
for (let path = archiveRoot;!existsSync9(path); path = dirname12(path))
|
|
15750
15991
|
created.push(path);
|
|
15751
15992
|
mkdirSync5(archiveRoot, { recursive: true, mode: 448 });
|
|
15752
15993
|
try {
|
|
15753
15994
|
for (const directory of created)
|
|
15754
15995
|
syncArchiveDirectory(directory);
|
|
15755
15996
|
if (created.length)
|
|
15756
|
-
syncArchiveDirectory(
|
|
15997
|
+
syncArchiveDirectory(dirname12(created.at(-1)));
|
|
15757
15998
|
writeArchiveJournal(receiptPath, journal);
|
|
15758
15999
|
for (const [index, entry] of selected.entries()) {
|
|
15759
16000
|
recheckRootAliases(aliases);
|
|
@@ -15765,7 +16006,7 @@ function archiveNativeSkills(inventory, options) {
|
|
|
15765
16006
|
writeArchiveJournal(receiptPath, journal);
|
|
15766
16007
|
renameSync2(move.source, move.archive);
|
|
15767
16008
|
moved.push(move);
|
|
15768
|
-
syncArchiveDirectory(
|
|
16009
|
+
syncArchiveDirectory(dirname12(move.source));
|
|
15769
16010
|
syncArchiveDirectory(archiveRoot);
|
|
15770
16011
|
verifyArchive(move);
|
|
15771
16012
|
move.status = "archived";
|
|
@@ -15786,7 +16027,7 @@ function archiveNativeSkills(inventory, options) {
|
|
|
15786
16027
|
recheckRootAliases(aliases);
|
|
15787
16028
|
assertSafePath(entry.source);
|
|
15788
16029
|
assertSafePath(entry.archive);
|
|
15789
|
-
if (
|
|
16030
|
+
if (lstatSync17(entry.source, { throwIfNoEntry: false })) {
|
|
15790
16031
|
entry.status = "recovery-required";
|
|
15791
16032
|
entry.conflict = "source-occupied";
|
|
15792
16033
|
continue;
|
|
@@ -15800,10 +16041,10 @@ function archiveNativeSkills(inventory, options) {
|
|
|
15800
16041
|
}
|
|
15801
16042
|
if (entry.discoveryOnly) {
|
|
15802
16043
|
linkSync2(entry.archive, entry.source);
|
|
15803
|
-
syncArchiveDirectory(
|
|
16044
|
+
syncArchiveDirectory(dirname12(entry.source));
|
|
15804
16045
|
unlinkSync3(entry.archive);
|
|
15805
16046
|
} else {
|
|
15806
|
-
if (
|
|
16047
|
+
if (lstatSync17(entry.source, { throwIfNoEntry: false })) {
|
|
15807
16048
|
entry.status = "recovery-required";
|
|
15808
16049
|
entry.conflict = "source-occupied";
|
|
15809
16050
|
continue;
|
|
@@ -15811,7 +16052,7 @@ function archiveNativeSkills(inventory, options) {
|
|
|
15811
16052
|
renameSync2(entry.archive, entry.source);
|
|
15812
16053
|
}
|
|
15813
16054
|
entry.status = "restored";
|
|
15814
|
-
syncArchiveDirectory(
|
|
16055
|
+
syncArchiveDirectory(dirname12(entry.source));
|
|
15815
16056
|
syncArchiveDirectory(archiveRoot);
|
|
15816
16057
|
} catch {
|
|
15817
16058
|
entry.status = "recovery-required";
|
|
@@ -15836,10 +16077,10 @@ function assertManagedAgentBridge(agent, options = {}) {
|
|
|
15836
16077
|
function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscovery) {
|
|
15837
16078
|
const home = resolve11(options.home ?? homedir2()), dataDir2 = options.dataDir ?? getDataDirReadOnly();
|
|
15838
16079
|
assertSafePath(join18(dataDir2, "agent-policy.json"));
|
|
15839
|
-
const
|
|
15840
|
-
if (!
|
|
16080
|
+
const snapshot2 = readManagedSkillPolicySnapshot(dataDir2);
|
|
16081
|
+
if (!snapshot2)
|
|
15841
16082
|
throw new Error("NATIVE_SKILL_DRIFT: install the Skills bridge with skills hook install");
|
|
15842
|
-
const binding =
|
|
16083
|
+
const binding = snapshot2.value.bridge;
|
|
15843
16084
|
if (!binding || binding.version !== CLI_BRIDGE_VERSION || binding.digest !== CLI_BRIDGE_DIGEST || binding.home !== home || !Array.isArray(binding.agents) || !binding.agents.includes(agent))
|
|
15844
16085
|
throw new Error("NATIVE_SKILL_DRIFT: the managed Skills bridge binding is missing or incompatible; run skills hook install");
|
|
15845
16086
|
const aliases = binding.rootAliases ?? [];
|
|
@@ -15980,7 +16221,7 @@ function isSkillsOwnershipMarker(marker) {
|
|
|
15980
16221
|
function hasSkillsOwnershipMarker(dir) {
|
|
15981
16222
|
const path = join19(dir, SYNC_MARKER_FILE);
|
|
15982
16223
|
try {
|
|
15983
|
-
if (!
|
|
16224
|
+
if (!lstatSync18(path).isFile())
|
|
15984
16225
|
return false;
|
|
15985
16226
|
const marker = JSON.parse(readFileSync13(path, "utf8"));
|
|
15986
16227
|
return isSkillsOwnershipMarker(marker);
|
|
@@ -16206,7 +16447,7 @@ function writeManagedSkillDir(dir, skillMd, options) {
|
|
|
16206
16447
|
const action = dirExists ? "update" : "create";
|
|
16207
16448
|
if (options.dryRun)
|
|
16208
16449
|
return { action, path: skillMdPath };
|
|
16209
|
-
const parentDir =
|
|
16450
|
+
const parentDir = dirname13(dir);
|
|
16210
16451
|
mkdirSync6(parentDir, { recursive: true });
|
|
16211
16452
|
const transactionDir = mkdtempSync(join19(parentDir, `.hasna-skills-write-${basename6(dir)}-`));
|
|
16212
16453
|
const candidateDir = join19(transactionDir, "candidate");
|
|
@@ -16228,10 +16469,10 @@ function writeManagedSkillDir(dir, skillMd, options) {
|
|
|
16228
16469
|
} else {
|
|
16229
16470
|
mkdirSync6(candidateDir, { recursive: true });
|
|
16230
16471
|
}
|
|
16231
|
-
|
|
16472
|
+
writeFileSync6(candidateSkillMdPath, skillMd.endsWith(`
|
|
16232
16473
|
`) ? skillMd : `${skillMd}
|
|
16233
16474
|
`);
|
|
16234
|
-
|
|
16475
|
+
writeFileSync6(candidateMarkerPath, `${JSON.stringify(marker, null, 2)}
|
|
16235
16476
|
`);
|
|
16236
16477
|
if (dirExists) {
|
|
16237
16478
|
originalMoved = true;
|
|
@@ -16311,7 +16552,7 @@ function resolveSkillAlias(name) {
|
|
|
16311
16552
|
}
|
|
16312
16553
|
|
|
16313
16554
|
// src/lib/project-state.ts
|
|
16314
|
-
import { existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync14, writeFileSync as
|
|
16555
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "fs";
|
|
16315
16556
|
import { join as join20 } from "path";
|
|
16316
16557
|
var VALID_PIN_SOURCES = [
|
|
16317
16558
|
"official",
|
|
@@ -16360,7 +16601,7 @@ function saveProjectConfig(config, targetDir = process.cwd()) {
|
|
|
16360
16601
|
const dir = getProjectStateDir(targetDir);
|
|
16361
16602
|
mkdirSync7(dir, { recursive: true });
|
|
16362
16603
|
const normalized = normalizeProjectConfig({ ...config, updatedAt: new Date().toISOString() });
|
|
16363
|
-
|
|
16604
|
+
writeFileSync7(getProjectConfigPath(targetDir), JSON.stringify(normalized, null, 2) + `
|
|
16364
16605
|
`);
|
|
16365
16606
|
}
|
|
16366
16607
|
function pinProjectSkill(name, details = {}, targetDir = process.cwd()) {
|
|
@@ -17179,7 +17420,7 @@ function exactProfileSelection(spec, profile) {
|
|
|
17179
17420
|
}
|
|
17180
17421
|
|
|
17181
17422
|
// src/lib/plugin-projection.ts
|
|
17182
|
-
import { createHash as
|
|
17423
|
+
import { createHash as createHash14 } from "crypto";
|
|
17183
17424
|
import { posix } from "path";
|
|
17184
17425
|
var PLUGIN_PROJECTION_MANIFEST = "plugin-projection.json";
|
|
17185
17426
|
var PLUGIN_PROJECTION_LIMITS = Object.freeze({ files: 1024, bytes: 64 * 1024 * 1024, fileBytes: 16 * 1024 * 1024, metadataBytes: 512 * 1024, registrations: 64, history: 128 });
|
|
@@ -17190,7 +17431,7 @@ function pluginNeed(value, message) {
|
|
|
17190
17431
|
if (!value)
|
|
17191
17432
|
pluginRefusal(message);
|
|
17192
17433
|
}
|
|
17193
|
-
var pluginHash = (bytes) =>
|
|
17434
|
+
var pluginHash = (bytes) => createHash14("sha256").update(bytes).digest("hex");
|
|
17194
17435
|
function pluginObject(value) {
|
|
17195
17436
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
17196
17437
|
}
|
|
@@ -17378,16 +17619,16 @@ function buildPluginProjection(entries) {
|
|
|
17378
17619
|
}
|
|
17379
17620
|
|
|
17380
17621
|
// src/lib/plugin-projection-store.ts
|
|
17381
|
-
import { createHash as
|
|
17382
|
-
import { constants as
|
|
17383
|
-
import { dirname as
|
|
17384
|
-
var READ_FLAGS =
|
|
17385
|
-
var WRITE_FLAGS =
|
|
17622
|
+
import { createHash as createHash15, randomUUID as randomUUID3 } from "crypto";
|
|
17623
|
+
import { constants as constants9, closeSync as closeSync10, fchmodSync, fstatSync as fstatSync10, fsyncSync as fsyncSync4, linkSync as linkSync3, lstatSync as lstatSync19, mkdirSync as mkdirSync8, openSync as openSync10, opendirSync as opendirSync4, readSync as readSync8, renameSync as renameSync4, rmdirSync as rmdirSync3, rmSync as rmSync3, unlinkSync as unlinkSync4, writeSync as writeSync2 } from "fs";
|
|
17624
|
+
import { dirname as dirname14, isAbsolute as isAbsolute12, join as join25, parse as parse5, resolve as resolve12 } from "path";
|
|
17625
|
+
var READ_FLAGS = constants9.O_RDONLY | constants9.O_NOFOLLOW | constants9.O_NONBLOCK;
|
|
17626
|
+
var WRITE_FLAGS = constants9.O_WRONLY | constants9.O_CREAT | constants9.O_EXCL | constants9.O_NOFOLLOW | constants9.O_NONBLOCK;
|
|
17386
17627
|
var uid = () => typeof process.getuid === "function" ? BigInt(process.getuid()) : undefined;
|
|
17387
17628
|
var missing = (error) => Boolean(error && typeof error === "object" && "code" in error && error.code === "ENOENT");
|
|
17388
17629
|
function stat(path) {
|
|
17389
17630
|
try {
|
|
17390
|
-
return
|
|
17631
|
+
return lstatSync19(path, { bigint: true });
|
|
17391
17632
|
} catch (error) {
|
|
17392
17633
|
if (missing(error))
|
|
17393
17634
|
return null;
|
|
@@ -17406,7 +17647,7 @@ function same3(left, right, identityOnly = false) {
|
|
|
17406
17647
|
}
|
|
17407
17648
|
function ancestors(path, create = false) {
|
|
17408
17649
|
canonical5(path);
|
|
17409
|
-
const result = [], parent =
|
|
17650
|
+
const result = [], parent = dirname14(path), filesystemRoot = parse5(parent).root;
|
|
17410
17651
|
let current = filesystemRoot;
|
|
17411
17652
|
for (const segment of ["", ...parent.slice(filesystemRoot.length).split("/").filter(Boolean)]) {
|
|
17412
17653
|
if (segment)
|
|
@@ -17434,7 +17675,7 @@ function verifyAncestors(chain, identityOnly = true) {
|
|
|
17434
17675
|
}
|
|
17435
17676
|
function privateParent(path) {
|
|
17436
17677
|
const chain = ancestors(path, true), parent = chain.at(-1);
|
|
17437
|
-
pluginNeed(parent && parent.path ===
|
|
17678
|
+
pluginNeed(parent && parent.path === dirname14(path), "Plugin storage parent is unavailable");
|
|
17438
17679
|
owner(parent.stat);
|
|
17439
17680
|
pluginNeed((parent.stat.mode & 0o7777n) === 0o700n, "Plugin storage parent must have mode 0700");
|
|
17440
17681
|
return chain;
|
|
@@ -17463,19 +17704,19 @@ function readBytes(path, maximum, kind) {
|
|
|
17463
17704
|
fileShape(before, maximum, kind);
|
|
17464
17705
|
let fd;
|
|
17465
17706
|
try {
|
|
17466
|
-
fd =
|
|
17467
|
-
const opened =
|
|
17707
|
+
fd = openSync10(path, READ_FLAGS);
|
|
17708
|
+
const opened = fstatSync10(fd, { bigint: true });
|
|
17468
17709
|
same3(before, opened);
|
|
17469
17710
|
fileShape(opened, maximum, kind);
|
|
17470
17711
|
const bytes = new Uint8Array(Number(opened.size));
|
|
17471
17712
|
let offset = 0;
|
|
17472
17713
|
while (offset < bytes.byteLength) {
|
|
17473
|
-
const count =
|
|
17714
|
+
const count = readSync8(fd, bytes, offset, bytes.byteLength - offset, null);
|
|
17474
17715
|
pluginNeed(count > 0, "Plugin file changed while being read");
|
|
17475
17716
|
offset += count;
|
|
17476
17717
|
}
|
|
17477
|
-
pluginNeed(
|
|
17478
|
-
same3(opened,
|
|
17718
|
+
pluginNeed(readSync8(fd, new Uint8Array(1), 0, 1, null) === 0, "Plugin file grew while being read");
|
|
17719
|
+
same3(opened, fstatSync10(fd, { bigint: true }));
|
|
17479
17720
|
same3(opened, stat(path));
|
|
17480
17721
|
verifyAncestors(chain);
|
|
17481
17722
|
return bytes;
|
|
@@ -17485,7 +17726,7 @@ function readBytes(path, maximum, kind) {
|
|
|
17485
17726
|
return pluginRefusal("Cannot safely read plugin file");
|
|
17486
17727
|
} finally {
|
|
17487
17728
|
if (fd !== undefined)
|
|
17488
|
-
|
|
17729
|
+
closeSync10(fd);
|
|
17489
17730
|
}
|
|
17490
17731
|
}
|
|
17491
17732
|
function exactPaths(entries) {
|
|
@@ -17595,16 +17836,16 @@ function verifyPluginTree(root, witnesses) {
|
|
|
17595
17836
|
function writeExclusive(path, bytes, mode) {
|
|
17596
17837
|
let fd, created;
|
|
17597
17838
|
try {
|
|
17598
|
-
fd =
|
|
17839
|
+
fd = openSync10(path, WRITE_FLAGS, mode);
|
|
17599
17840
|
fchmodSync(fd, mode);
|
|
17600
|
-
created =
|
|
17841
|
+
created = fstatSync10(fd, { bigint: true });
|
|
17601
17842
|
let offset = 0;
|
|
17602
17843
|
while (offset < bytes.byteLength) {
|
|
17603
17844
|
const count = writeSync2(fd, bytes, offset, bytes.byteLength - offset);
|
|
17604
17845
|
pluginNeed(count > 0, "Plugin file write did not progress");
|
|
17605
17846
|
offset += count;
|
|
17606
17847
|
}
|
|
17607
|
-
|
|
17848
|
+
fsyncSync4(fd);
|
|
17608
17849
|
} catch (error) {
|
|
17609
17850
|
if (created) {
|
|
17610
17851
|
same3(created, stat(path), true);
|
|
@@ -17613,7 +17854,7 @@ function writeExclusive(path, bytes, mode) {
|
|
|
17613
17854
|
throw error;
|
|
17614
17855
|
} finally {
|
|
17615
17856
|
if (fd !== undefined)
|
|
17616
|
-
|
|
17857
|
+
closeSync10(fd);
|
|
17617
17858
|
}
|
|
17618
17859
|
}
|
|
17619
17860
|
function materializePluginTree(root, entries) {
|
|
@@ -17716,20 +17957,20 @@ function pluginExecutableDigest(path) {
|
|
|
17716
17957
|
fileShape(before, maximum, "executable");
|
|
17717
17958
|
let fd;
|
|
17718
17959
|
try {
|
|
17719
|
-
fd =
|
|
17720
|
-
const opened =
|
|
17960
|
+
fd = openSync10(path, READ_FLAGS);
|
|
17961
|
+
const opened = fstatSync10(fd, { bigint: true });
|
|
17721
17962
|
same3(before, opened);
|
|
17722
17963
|
fileShape(opened, maximum, "executable");
|
|
17723
|
-
const hash2 =
|
|
17964
|
+
const hash2 = createHash15("sha256"), buffer = new Uint8Array(64 * 1024);
|
|
17724
17965
|
let remaining = Number(opened.size);
|
|
17725
17966
|
while (remaining > 0) {
|
|
17726
|
-
const count =
|
|
17967
|
+
const count = readSync8(fd, buffer, 0, Math.min(buffer.byteLength, remaining), null);
|
|
17727
17968
|
pluginNeed(count > 0, "Plugin executable changed during hashing");
|
|
17728
17969
|
hash2.update(buffer.subarray(0, count));
|
|
17729
17970
|
remaining -= count;
|
|
17730
17971
|
}
|
|
17731
|
-
pluginNeed(
|
|
17732
|
-
same3(opened,
|
|
17972
|
+
pluginNeed(readSync8(fd, buffer, 0, 1, null) === 0, "Plugin executable grew during hashing");
|
|
17973
|
+
same3(opened, fstatSync10(fd, { bigint: true }));
|
|
17733
17974
|
same3(opened, stat(path));
|
|
17734
17975
|
verifyAncestors(chain);
|
|
17735
17976
|
return `sha256:${hash2.digest("hex")}`;
|
|
@@ -17739,7 +17980,7 @@ function pluginExecutableDigest(path) {
|
|
|
17739
17980
|
return pluginRefusal("Cannot safely hash plugin executable");
|
|
17740
17981
|
} finally {
|
|
17741
17982
|
if (fd !== undefined)
|
|
17742
|
-
|
|
17983
|
+
closeSync10(fd);
|
|
17743
17984
|
}
|
|
17744
17985
|
}
|
|
17745
17986
|
|
|
@@ -18149,29 +18390,29 @@ function validateManagedPluginWitnesses(value) {
|
|
|
18149
18390
|
function safe3(path) {
|
|
18150
18391
|
pluginText(path);
|
|
18151
18392
|
pluginNeed(isAbsolute14(path) && resolve14(path) === path, "Invalid plugin registry path");
|
|
18152
|
-
for (let at = path;; at =
|
|
18153
|
-
pluginNeed(!
|
|
18154
|
-
if (at ===
|
|
18393
|
+
for (let at = path;; at = dirname15(at)) {
|
|
18394
|
+
pluginNeed(!lstatSync20(at, { throwIfNoEntry: false })?.isSymbolicLink(), "Plugin registry path traverses a symlink");
|
|
18395
|
+
if (at === dirname15(at))
|
|
18155
18396
|
break;
|
|
18156
18397
|
}
|
|
18157
18398
|
}
|
|
18158
18399
|
function readRegistry(path) {
|
|
18159
18400
|
safe3(path);
|
|
18160
|
-
const initial =
|
|
18401
|
+
const initial = lstatSync20(path);
|
|
18161
18402
|
pluginNeed(initial.isFile() && initial.size <= 1024 * 1024, "Plugin registry is not a bounded regular file");
|
|
18162
|
-
const fd =
|
|
18403
|
+
const fd = openSync11(path, constants10.O_RDONLY | constants10.O_NOFOLLOW | constants10.O_NONBLOCK);
|
|
18163
18404
|
try {
|
|
18164
|
-
const opened =
|
|
18405
|
+
const opened = fstatSync11(fd);
|
|
18165
18406
|
pluginNeed(opened.isFile() && opened.ino === initial.ino && opened.dev === initial.dev, "Plugin registry changed during open");
|
|
18166
18407
|
const bytes = Buffer.alloc(1024 * 1024 + 1);
|
|
18167
18408
|
let length = 0;
|
|
18168
18409
|
while (length < bytes.length) {
|
|
18169
|
-
const count =
|
|
18410
|
+
const count = readSync9(fd, bytes, length, bytes.length - length, null);
|
|
18170
18411
|
if (!count)
|
|
18171
18412
|
break;
|
|
18172
18413
|
length += count;
|
|
18173
18414
|
}
|
|
18174
|
-
const after =
|
|
18415
|
+
const after = fstatSync11(fd), current = lstatSync20(path);
|
|
18175
18416
|
safe3(path);
|
|
18176
18417
|
pluginNeed(length <= 1024 * 1024 && length === opened.size && after.size === opened.size && after.mtimeMs === opened.mtimeMs && after.ctimeMs === opened.ctimeMs && current.dev === opened.dev && current.ino === opened.ino && current.mtimeMs === opened.mtimeMs && current.ctimeMs === opened.ctimeMs, "Plugin registry changed during read");
|
|
18177
18418
|
let value;
|
|
@@ -18183,7 +18424,7 @@ function readRegistry(path) {
|
|
|
18183
18424
|
pluginNeed(pluginObject(value) && value.version === 2 && pluginObject(value.plugins), "Unsupported native plugin registry schema");
|
|
18184
18425
|
return { value, rawDigest: pluginHash(bytes.subarray(0, length)) };
|
|
18185
18426
|
} finally {
|
|
18186
|
-
|
|
18427
|
+
closeSync11(fd);
|
|
18187
18428
|
}
|
|
18188
18429
|
}
|
|
18189
18430
|
function nativeVersionMatches(version, receipt) {
|
|
@@ -18192,7 +18433,7 @@ function nativeVersionMatches(version, receipt) {
|
|
|
18192
18433
|
}
|
|
18193
18434
|
function hashManagedPluginRegistry(path, managed) {
|
|
18194
18435
|
validateManagedPluginWitnesses(managed);
|
|
18195
|
-
const
|
|
18436
|
+
const snapshot2 = readRegistry(path), plugins = snapshot2.value.plugins;
|
|
18196
18437
|
const seen = new Set, verified = new Set;
|
|
18197
18438
|
let verifiedBytes = 0;
|
|
18198
18439
|
for (const witness of managed) {
|
|
@@ -18201,7 +18442,7 @@ function hashManagedPluginRegistry(path, managed) {
|
|
|
18201
18442
|
seen.add(target.pluginId);
|
|
18202
18443
|
const rows = plugins[target.pluginId];
|
|
18203
18444
|
pluginNeed(Array.isArray(rows) && rows.length === target.registrations.length, "Managed plugin registration membership changed");
|
|
18204
|
-
const [name, market] = target.pluginId.split("@"), cacheRoot = join27(
|
|
18445
|
+
const [name, market] = target.pluginId.split("@"), cacheRoot = join27(dirname15(path), "cache", market, name);
|
|
18205
18446
|
const scopes = new Set, approvals = new Map, currentFiles = new Map;
|
|
18206
18447
|
plugins[target.pluginId] = rows.map((row) => {
|
|
18207
18448
|
pluginNeed(pluginObject(row), "Invalid managed plugin registration");
|
|
@@ -18244,7 +18485,7 @@ function hashManagedPluginRegistry(path, managed) {
|
|
|
18244
18485
|
return Object.fromEntries(Object.entries(row).filter(([key]) => !["version", "installPath", "sourceProducerPath", "previousProducerPaths", "lastUpdated"].includes(key)));
|
|
18245
18486
|
});
|
|
18246
18487
|
safe3(cacheRoot);
|
|
18247
|
-
const before =
|
|
18488
|
+
const before = lstatSync20(cacheRoot);
|
|
18248
18489
|
pluginNeed(before.isDirectory(), "Invalid managed plugin cache root");
|
|
18249
18490
|
const directory = opendirSync5(cacheRoot), names = [];
|
|
18250
18491
|
try {
|
|
@@ -18267,64 +18508,64 @@ function hashManagedPluginRegistry(path, managed) {
|
|
|
18267
18508
|
}
|
|
18268
18509
|
for (const row of rows)
|
|
18269
18510
|
pluginNeed(names.includes(row.version), "Managed native plugin installation is missing");
|
|
18270
|
-
const after =
|
|
18511
|
+
const after = lstatSync20(cacheRoot);
|
|
18271
18512
|
safe3(cacheRoot);
|
|
18272
18513
|
pluginNeed(before.dev === after.dev && before.ino === after.ino && before.mtimeMs === after.mtimeMs && before.ctimeMs === after.ctimeMs, "Managed cache version membership changed during verification");
|
|
18273
18514
|
}
|
|
18274
|
-
pluginNeed(readRegistry(path).rawDigest ===
|
|
18275
|
-
return pluginHash(JSON.stringify(
|
|
18515
|
+
pluginNeed(readRegistry(path).rawDigest === snapshot2.rawDigest, "Native plugin registry changed during content verification");
|
|
18516
|
+
return pluginHash(JSON.stringify(snapshot2.value));
|
|
18276
18517
|
}
|
|
18277
18518
|
function captureManagedPluginRegistry(path, managedPlugins) {
|
|
18278
18519
|
return { path, hashMode: "claude-plugin-registry", managedPlugins, sha256: hashManagedPluginRegistry(path, managedPlugins) };
|
|
18279
18520
|
}
|
|
18280
18521
|
|
|
18281
18522
|
// src/lib/claude-marketplace-registry.ts
|
|
18282
|
-
import { createHash as
|
|
18283
|
-
import { closeSync as
|
|
18284
|
-
import { basename as basename8, dirname as
|
|
18523
|
+
import { createHash as createHash16 } from "crypto";
|
|
18524
|
+
import { closeSync as closeSync12, constants as constants11, fstatSync as fstatSync12, lstatSync as lstatSync21, openSync as openSync12, readSync as readSync10 } from "fs";
|
|
18525
|
+
import { basename as basename8, dirname as dirname16, isAbsolute as isAbsolute15, resolve as resolve15 } from "path";
|
|
18285
18526
|
var CLAUDE_MARKETPLACE_REGISTRY_LIMITS = Object.freeze({ bytes: 1024 * 1024, rows: 1024, depth: 32, nodes: 65536, stringCharacters: 16384, pathCharacters: 4096 });
|
|
18286
|
-
function
|
|
18527
|
+
function need6(value, reason) {
|
|
18287
18528
|
if (!value)
|
|
18288
18529
|
throw new Error(`Claude marketplace registry ${reason}`);
|
|
18289
18530
|
}
|
|
18290
18531
|
function absolutePath3(value) {
|
|
18291
|
-
|
|
18532
|
+
need6(typeof value === "string" && value.length > 0 && value.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.pathCharacters && !/[\x00-\x1f\x7f]/.test(value) && isAbsolute15(value) && resolve15(value) === value, "requires a normalized absolute path");
|
|
18292
18533
|
}
|
|
18293
18534
|
var sameFile2 = (a, b) => a.dev === b.dev && a.ino === b.ino && a.mode === b.mode && a.uid === b.uid && a.gid === b.gid && a.nlink === b.nlink && a.size === b.size && a.mtimeNs === b.mtimeNs && a.ctimeNs === b.ctimeNs;
|
|
18294
18535
|
function parents2(path) {
|
|
18295
18536
|
const result = [];
|
|
18296
|
-
for (let at =
|
|
18297
|
-
const stat2 =
|
|
18298
|
-
|
|
18537
|
+
for (let at = dirname16(path);; at = dirname16(at)) {
|
|
18538
|
+
const stat2 = lstatSync21(at, { bigint: true });
|
|
18539
|
+
need6(stat2.isDirectory() && !stat2.isSymbolicLink(), "path traverses a symlink or non-directory");
|
|
18299
18540
|
result.push([at, stat2]);
|
|
18300
|
-
if (at ===
|
|
18541
|
+
if (at === dirname16(at))
|
|
18301
18542
|
return result;
|
|
18302
18543
|
}
|
|
18303
18544
|
}
|
|
18304
18545
|
function readRegistry2(path, budget) {
|
|
18305
18546
|
absolutePath3(path);
|
|
18306
|
-
|
|
18307
|
-
|
|
18308
|
-
const ancestors2 = parents2(path), initial =
|
|
18309
|
-
|
|
18310
|
-
|
|
18311
|
-
const fd =
|
|
18547
|
+
need6(basename8(path) === "known_marketplaces.json", "requires known_marketplaces.json");
|
|
18548
|
+
need6(Number.isSafeInteger(budget.remaining) && budget.remaining >= 0, "has an invalid byte budget");
|
|
18549
|
+
const ancestors2 = parents2(path), initial = lstatSync21(path, { bigint: true });
|
|
18550
|
+
need6(initial.isFile() && !initial.isSymbolicLink() && initial.size <= BigInt(CLAUDE_MARKETPLACE_REGISTRY_LIMITS.bytes), "is not a bounded regular file");
|
|
18551
|
+
need6(initial.size <= BigInt(budget.remaining), "exceeds its aggregate byte limit");
|
|
18552
|
+
const fd = openSync12(path, constants11.O_RDONLY | constants11.O_NOFOLLOW | constants11.O_NONBLOCK);
|
|
18312
18553
|
try {
|
|
18313
|
-
const opened =
|
|
18314
|
-
|
|
18554
|
+
const opened = fstatSync12(fd, { bigint: true });
|
|
18555
|
+
need6(opened.isFile() && sameFile2(initial, opened), "changed during open");
|
|
18315
18556
|
const bytes = Buffer.alloc(Number(opened.size) + 1);
|
|
18316
18557
|
let length = 0;
|
|
18317
18558
|
while (length < bytes.length) {
|
|
18318
|
-
const count =
|
|
18559
|
+
const count = readSync10(fd, bytes, length, bytes.length - length, null);
|
|
18319
18560
|
if (!count)
|
|
18320
18561
|
break;
|
|
18321
18562
|
length += count;
|
|
18322
18563
|
}
|
|
18323
|
-
const after =
|
|
18324
|
-
|
|
18564
|
+
const after = fstatSync12(fd, { bigint: true }), current = lstatSync21(path, { bigint: true });
|
|
18565
|
+
need6(current.isFile() && sameFile2(opened, after) && sameFile2(after, current) && BigInt(length) === opened.size, "changed during read");
|
|
18325
18566
|
for (const [ancestor, before] of ancestors2) {
|
|
18326
|
-
const now =
|
|
18327
|
-
|
|
18567
|
+
const now = lstatSync21(ancestor, { bigint: true });
|
|
18568
|
+
need6(now.isDirectory() && before.dev === now.dev && before.ino === now.ino && before.mode === now.mode, "parent changed during read");
|
|
18328
18569
|
}
|
|
18329
18570
|
budget.remaining -= length;
|
|
18330
18571
|
try {
|
|
@@ -18333,7 +18574,7 @@ function readRegistry2(path, budget) {
|
|
|
18333
18574
|
throw new Error("Claude marketplace registry is not strict UTF-8");
|
|
18334
18575
|
}
|
|
18335
18576
|
} finally {
|
|
18336
|
-
|
|
18577
|
+
closeSync12(fd);
|
|
18337
18578
|
}
|
|
18338
18579
|
}
|
|
18339
18580
|
function parse6(text2) {
|
|
@@ -18344,7 +18585,7 @@ function parse6(text2) {
|
|
|
18344
18585
|
at++;
|
|
18345
18586
|
};
|
|
18346
18587
|
function string() {
|
|
18347
|
-
|
|
18588
|
+
need6(text2[at] === '"', "contains invalid JSON");
|
|
18348
18589
|
const start = at++;
|
|
18349
18590
|
while (at < text2.length) {
|
|
18350
18591
|
const character = text2[at++];
|
|
@@ -18355,7 +18596,7 @@ function parse6(text2) {
|
|
|
18355
18596
|
} catch {
|
|
18356
18597
|
throw new Error("Claude marketplace registry contains invalid JSON string");
|
|
18357
18598
|
}
|
|
18358
|
-
|
|
18599
|
+
need6(value2.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.stringCharacters, "exceeds its string limit");
|
|
18359
18600
|
return value2;
|
|
18360
18601
|
}
|
|
18361
18602
|
if (character === "\\")
|
|
@@ -18364,7 +18605,7 @@ function parse6(text2) {
|
|
|
18364
18605
|
throw new Error("Claude marketplace registry contains an unterminated JSON string");
|
|
18365
18606
|
}
|
|
18366
18607
|
function value(depth) {
|
|
18367
|
-
|
|
18608
|
+
need6(depth <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.depth && ++nodes <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.nodes, "exceeds its nesting or node limit");
|
|
18368
18609
|
space();
|
|
18369
18610
|
const character = text2[at];
|
|
18370
18611
|
if (character === '"')
|
|
@@ -18378,17 +18619,17 @@ function parse6(text2) {
|
|
|
18378
18619
|
if (object5) {
|
|
18379
18620
|
space();
|
|
18380
18621
|
const key = string();
|
|
18381
|
-
|
|
18622
|
+
need6(!keys.has(key), "contains a duplicate JSON key");
|
|
18382
18623
|
keys.add(key);
|
|
18383
18624
|
space();
|
|
18384
|
-
|
|
18625
|
+
need6(text2[at++] === ":", "contains invalid JSON");
|
|
18385
18626
|
entries.push([key, value(depth + 1)]);
|
|
18386
18627
|
} else
|
|
18387
18628
|
items.push(value(depth + 1));
|
|
18388
18629
|
space();
|
|
18389
18630
|
if (text2[at] === end)
|
|
18390
18631
|
break;
|
|
18391
|
-
|
|
18632
|
+
need6(text2[at++] === ",", "contains invalid JSON");
|
|
18392
18633
|
}
|
|
18393
18634
|
at++;
|
|
18394
18635
|
return object5 ? { kind: "object", entries } : { kind: "array", items };
|
|
@@ -18400,14 +18641,14 @@ function parse6(text2) {
|
|
|
18400
18641
|
}
|
|
18401
18642
|
numberPattern.lastIndex = at;
|
|
18402
18643
|
const number = numberPattern.exec(text2);
|
|
18403
|
-
|
|
18644
|
+
need6(number, "contains invalid JSON");
|
|
18404
18645
|
at += number[0].length;
|
|
18405
18646
|
return { kind: "number", value: number[0] };
|
|
18406
18647
|
}
|
|
18407
18648
|
const root2 = value(0);
|
|
18408
18649
|
space();
|
|
18409
|
-
|
|
18410
|
-
|
|
18650
|
+
need6(at === text2.length && root2.kind === "object", "requires a JSON object without trailing content");
|
|
18651
|
+
need6(root2.entries.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.rows, "exceeds its marketplace row limit");
|
|
18411
18652
|
return root2;
|
|
18412
18653
|
}
|
|
18413
18654
|
function exactKeys(value, keys) {
|
|
@@ -18415,7 +18656,7 @@ function exactKeys(value, keys) {
|
|
|
18415
18656
|
}
|
|
18416
18657
|
var field = (object5, key) => object5.entries.find(([name]) => name === key)?.[1];
|
|
18417
18658
|
function textField(value) {
|
|
18418
|
-
|
|
18659
|
+
need6(value?.kind === "string" && value.value.length > 0 && !/[\x00-\x1f\x7f]/.test(value.value), "contains an invalid recognized field");
|
|
18419
18660
|
return value.value;
|
|
18420
18661
|
}
|
|
18421
18662
|
function normalizeRow(row, version = 1) {
|
|
@@ -18423,7 +18664,7 @@ function normalizeRow(row, version = 1) {
|
|
|
18423
18664
|
if (version !== 2 || !exactKeys(row, ["source", "installLocation", "lastUpdated", "autoUpdate"]))
|
|
18424
18665
|
return;
|
|
18425
18666
|
const autoUpdate = field(row, "autoUpdate");
|
|
18426
|
-
|
|
18667
|
+
need6(autoUpdate?.kind === "literal" && (autoUpdate.value === "true" || autoUpdate.value === "false"), "contains an invalid automatic-update preference");
|
|
18427
18668
|
}
|
|
18428
18669
|
const source = field(row, "source");
|
|
18429
18670
|
if (source?.kind !== "object")
|
|
@@ -18439,77 +18680,29 @@ function normalizeRow(row, version = 1) {
|
|
|
18439
18680
|
absolutePath3(sourceLocation);
|
|
18440
18681
|
absolutePath3(textField(field(row, "installLocation")));
|
|
18441
18682
|
const updated = textField(field(row, "lastUpdated"));
|
|
18442
|
-
|
|
18683
|
+
need6(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(updated), "contains an invalid recognized update timestamp");
|
|
18443
18684
|
const millis = Date.parse(updated);
|
|
18444
|
-
|
|
18685
|
+
need6(Number.isFinite(millis) && new Date(millis).toISOString() === updated, "contains an invalid recognized update timestamp");
|
|
18445
18686
|
row.entries = row.entries.map(([key, value]) => [key, key === "lastUpdated" ? { kind: "validated-update-time" } : value]);
|
|
18446
18687
|
}
|
|
18447
18688
|
function captureClaudeMarketplaceRegistry(path, budget = { remaining: 256 * 1024 * 1024 }) {
|
|
18448
18689
|
const value = parse6(readRegistry2(path, budget));
|
|
18449
18690
|
for (const [, row] of value.entries)
|
|
18450
18691
|
normalizeRow(row);
|
|
18451
|
-
const sha2562 =
|
|
18692
|
+
const sha2562 = createHash16("sha256").update("hasna.skills.claude-marketplace-registry.v1\x00").update(JSON.stringify(value)).digest("hex");
|
|
18452
18693
|
return { path, hashMode: "claude-marketplace-registry", sha256: sha2562 };
|
|
18453
18694
|
}
|
|
18454
18695
|
function captureClaudeMarketplaceRegistryV2(path, budget = { remaining: 256 * 1024 * 1024 }) {
|
|
18455
18696
|
const value = parse6(readRegistry2(path, budget));
|
|
18456
18697
|
for (const [, row] of value.entries)
|
|
18457
18698
|
normalizeRow(row, 2);
|
|
18458
|
-
const sha2562 =
|
|
18699
|
+
const sha2562 = createHash16("sha256").update("hasna.skills.claude-marketplace-registry.v2\x00").update(JSON.stringify(value)).digest("hex");
|
|
18459
18700
|
return { path, hashMode: "claude-marketplace-registry-v2", sha256: sha2562 };
|
|
18460
18701
|
}
|
|
18461
18702
|
|
|
18462
18703
|
// src/lib/codex-hook-discovery-recovery.ts
|
|
18463
18704
|
import { createHash as createHash17 } from "crypto";
|
|
18464
18705
|
|
|
18465
|
-
// src/lib/codex-hook-trust-files.ts
|
|
18466
|
-
import { closeSync as closeSync12, constants as constants11, fstatSync as fstatSync12, fsyncSync as fsyncSync4, lstatSync as lstatSync21, openSync as openSync12, readSync as readSync10, writeFileSync as writeFileSync7 } from "fs";
|
|
18467
|
-
import { createHash as createHash16 } from "crypto";
|
|
18468
|
-
import { dirname as dirname16 } from "path";
|
|
18469
|
-
function need6(value, code) {
|
|
18470
|
-
if (!value)
|
|
18471
|
-
throw new Error(`CODEX_HOOK_TRUST_${code}`);
|
|
18472
|
-
}
|
|
18473
|
-
var sameStat = (a, b) => ["dev", "ino", "uid", "nlink", "mode", "size", "mtimeNs", "ctimeNs"].every((key) => a[key] === b[key]);
|
|
18474
|
-
function safeParents(file) {
|
|
18475
|
-
for (let p = dirname16(file);; p = dirname16(p)) {
|
|
18476
|
-
const s = lstatSync21(p);
|
|
18477
|
-
need6(s.isDirectory() && !s.isSymbolicLink(), "UNSAFE_PARENT");
|
|
18478
|
-
need6((s.mode & 18) === 0 || s.uid === 0 && (s.mode & 512) !== 0, "UNSAFE_PARENT");
|
|
18479
|
-
if (dirname16(p) === p)
|
|
18480
|
-
break;
|
|
18481
|
-
}
|
|
18482
|
-
}
|
|
18483
|
-
function snapshot(file, privateFile = false, options = {}) {
|
|
18484
|
-
const readOnlyPackage = options.readOnlyPackage === true && !privateFile;
|
|
18485
|
-
safeParents(file);
|
|
18486
|
-
const initial = lstatSync21(file, { bigint: true });
|
|
18487
|
-
need6(initial.isFile() && initial.uid === BigInt(process.getuid()) && (initial.nlink === 1n || readOnlyPackage && initial.nlink > 1n) && (initial.mode & 0o022n) === 0n && (!privateFile || (initial.mode & 0o777n) === 0o600n), "UNSAFE_FILE");
|
|
18488
|
-
need6(initial.size <= 8n * 1024n * 1024n, "FILE_BOUND");
|
|
18489
|
-
const fd = openSync12(file, constants11.O_RDONLY | constants11.O_NOFOLLOW | constants11.O_NONBLOCK);
|
|
18490
|
-
try {
|
|
18491
|
-
const before = fstatSync12(fd, { bigint: true });
|
|
18492
|
-
need6(sameStat(initial, before), "INPUT_CHANGED");
|
|
18493
|
-
const bytes = Buffer.alloc(Number(before.size) + 1);
|
|
18494
|
-
let length = 0;
|
|
18495
|
-
while (length < bytes.length) {
|
|
18496
|
-
const n2 = readSync10(fd, bytes, length, bytes.length - length, null);
|
|
18497
|
-
if (!n2)
|
|
18498
|
-
break;
|
|
18499
|
-
length += n2;
|
|
18500
|
-
}
|
|
18501
|
-
need6(length === Number(before.size) && sameStat(before, fstatSync12(fd, { bigint: true })) && sameStat(before, lstatSync21(file, { bigint: true })), "INPUT_CHANGED");
|
|
18502
|
-
const content = bytes.subarray(0, length), text2 = new TextDecoder("utf-8", { fatal: true }).decode(content);
|
|
18503
|
-
return { file, privateFile, readOnlyPackage, text: text2, bytes: content, stat: before, sha256: createHash16("sha256").update(content).digest("hex") };
|
|
18504
|
-
} finally {
|
|
18505
|
-
closeSync12(fd);
|
|
18506
|
-
}
|
|
18507
|
-
}
|
|
18508
|
-
function unchanged2(before) {
|
|
18509
|
-
const after = snapshot(before.file, before.privateFile, before.readOnlyPackage ? { readOnlyPackage: true } : {});
|
|
18510
|
-
need6(sameStat(before.stat, after.stat) && after.sha256 === before.sha256, "INPUT_CHANGED");
|
|
18511
|
-
}
|
|
18512
|
-
|
|
18513
18706
|
// src/lib/codex-hook-additions-review.ts
|
|
18514
18707
|
var events = { UserPromptSubmit: ["userPromptSubmit", "user_prompt_submit"], SessionStart: ["sessionStart", "session_start"], SubagentStart: ["subagentStart", "subagent_start"] };
|
|
18515
18708
|
var matchedEvents = { ...events, PreToolUse: ["preToolUse", "pre_tool_use"] };
|
|
@@ -18521,7 +18714,7 @@ var fields = [...CODEX_DISCOVERY_PROJECTION_FIELDS];
|
|
|
18521
18714
|
var proofs2 = new WeakMap;
|
|
18522
18715
|
function assertCodexHookDiscoveryRecovery(binding, witness) {
|
|
18523
18716
|
const proof = proofs2.get(witness);
|
|
18524
|
-
|
|
18717
|
+
need5(!!proof && binding.agent === "codex" && witness.discoverySha256 === sha4(JSON.stringify(binding)), "RECONCILE_DISCOVERY_RECOVERY_CHANGED");
|
|
18525
18718
|
unchanged2(proof.before);
|
|
18526
18719
|
unchanged2(proof.current);
|
|
18527
18720
|
proof.recheckAdditions();
|
|
@@ -18530,7 +18723,7 @@ function verifiesCodexHookDiscoverySource(binding, source, currentDigest, witnes
|
|
|
18530
18723
|
if (!witness || source.path !== witness.configPath || source.hashMode !== "bytes")
|
|
18531
18724
|
return false;
|
|
18532
18725
|
assertCodexHookDiscoveryRecovery(binding, witness);
|
|
18533
|
-
|
|
18726
|
+
need5(source.sha256 === witness.beforeSha256 && currentDigest === witness.currentSha256, "RECONCILE_DISCOVERY_RECOVERY_CHANGED");
|
|
18534
18727
|
return true;
|
|
18535
18728
|
}
|
|
18536
18729
|
|
|
@@ -45935,6 +46128,7 @@ export {
|
|
|
45935
46128
|
planPluginAdmission,
|
|
45936
46129
|
planClaudeStopHookUpdate,
|
|
45937
46130
|
planClaudePreToolUseHookUpdate,
|
|
46131
|
+
planClaudeManagedHookProjection,
|
|
45938
46132
|
planClaudeHookEventsUpdate,
|
|
45939
46133
|
planAgentSettingsWitnessUpgrade,
|
|
45940
46134
|
planAgentIntegration,
|