@hasna/skills 0.10.43 → 0.10.44

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.
@@ -1,5 +1,6 @@
1
1
  import { type CodexCorpusWriteOptions } from "./codex-corpus-write.js";
2
2
  import { type ReviewedCodexSkillDenial } from "./codex-plugin-skill-controls.js";
3
+ import { type CodexNativeHookEnvelope, type ProcessInspector, type NativePolicyHelperRunner } from "./codex-native-skill-policy.js";
3
4
  import { type CodexNativeSkillCatalog } from "./codex-native-skill-catalog.js";
4
5
  import { type AgentDiscoveryBinding, type ReviewedDiscoveryInputs } from "./agent-discovery.js";
5
6
  import { type IntegrationAgent } from "./agent-adapters.js";
@@ -222,16 +223,31 @@ export declare function archiveNativeSkills(inventory: NativeSkillEntry[], optio
222
223
  targetCount: number;
223
224
  };
224
225
  };
225
- /** Run before any prompt context load. Missing ownership or reappearing native
226
- * discovery files require repair; verified cache availability is not an override. */
227
- export declare function assertManagedAgentBridge(agent: IntegrationAgent, options?: {
226
+ /** Native hook input for the Codex policy adapter. Only the Codex hook paths that
227
+ * receive native SessionStart/UserPromptSubmit input supply it; install-time and
228
+ * other guard calls never do. `inspector` is a test seam for process lineage. */
229
+ export interface CodexNativePolicyGuardInput extends CodexNativeHookEnvelope {
230
+ inspector?: ProcessInspector;
231
+ helperRunner?: NativePolicyHelperRunner; /** Absolute epoch ms by which the hook must answer; bounds the hash and helper. */
232
+ deadlineMs?: number;
233
+ }
234
+ interface ManagedBridgeOptions {
228
235
  home?: string;
229
236
  dataDir?: string;
230
237
  projectDir?: string;
231
238
  projectDirs?: string[];
232
239
  profileId?: string;
233
240
  codexDiscoveryRecovery?: CodexHookDiscoveryRecovery;
234
- }): void;
241
+ codexNativePolicy?: CodexNativePolicyGuardInput;
242
+ }
243
+ /** Package-classified installed-plugin content below the Codex plugin cache that
244
+ * a verified restricted policy cannot load. Classification is the existing plugin
245
+ * manifest identity and real-path binding, never a path prefix; user, repo and
246
+ * project copies, unclassified cache files and any allowed host path refuse. */
247
+ export declare function isInertCodexPluginCacheCopy(entry: NativeSkillEntry, cache: string, allowedHostPaths: readonly string[], read: (path: string) => string): boolean;
248
+ /** Run before any prompt context load. Missing ownership or reappearing native
249
+ * discovery files require repair; verified cache availability is not an override. */
250
+ export declare function assertManagedAgentBridge(agent: IntegrationAgent, options?: ManagedBridgeOptions): void;
235
251
  export declare function normalizeAgentHookPrompt(agent: IntegrationAgent, event: string, prompt: string): string;
236
252
  export declare function hookContextOutput(event: string, result: {
237
253
  context: string;
@@ -0,0 +1,310 @@
1
+ import { type BigIntStats } from "node:fs";
2
+ export declare const CODEX_NATIVE_POLICY_CAPABILITY = "host-path-allowlist-v1";
3
+ export declare const CODEX_NATIVE_POLICY_RECEIPT_SCHEMA = "skills.codex-native-policy-acceptance/v1";
4
+ /** Loop safety for the parent walk. It is not a policy bound on launch depth:
5
+ * Unix hooks run through the configured shell (`-lc`, new session), which may
6
+ * exec into the command or keep wrappers, so the real chain length varies. */
7
+ export declare const CODEX_NATIVE_POLICY_ANCESTRY_SAFETY_HOPS = 64;
8
+ export type CodexNativeHookEvent = "SessionStart" | "UserPromptSubmit";
9
+ /** Exactly the fields the native hook emits (hook_runtime.rs, native_skill_policy_for_hook). */
10
+ export interface CodexNativeSkillPolicy {
11
+ capability: typeof CODEX_NATIVE_POLICY_CAPABILITY;
12
+ mode: "restricted";
13
+ allowedHostPaths: [string];
14
+ nonHostSources: "disabled";
15
+ processId: number;
16
+ effectiveConfigDigest: string;
17
+ }
18
+ export interface CodexNativeHookEnvelope {
19
+ event: CodexNativeHookEvent;
20
+ policy: unknown;
21
+ sessionId: unknown;
22
+ /** Present on UserPromptSubmit only; SessionStart input has no turn id. */
23
+ turnId?: unknown;
24
+ /** SHA-256 of the exact hook stdin bytes, for the authenticated channel binding. */
25
+ hookInputSha256?: unknown;
26
+ /** Inherited read end of the per-hook socketpair Codex passes to the hook
27
+ * child, for the authenticated channel binding. Not yet named by the native
28
+ * contract, so the hook does not supply it today. */
29
+ inheritedFd?: unknown;
30
+ }
31
+ /** Native Codex sets this only on the current hook child; it names the
32
+ * inherited read end of the per-hook socketpair (draft binding contract). */
33
+ export declare const CODEX_NATIVE_POLICY_FD_ENV = "CODEX_NATIVE_SKILL_POLICY_FD";
34
+ /** Map one native hook input object to the adapter envelope, exactly as the
35
+ * Codex `hook user-prompt` path does. Undefined means the input carries no
36
+ * native policy or is not a SessionStart/UserPromptSubmit input, so the guard
37
+ * keeps today's behaviour. `inputBytes` are the exact raw stdin bytes, hashed
38
+ * before any decoding; `input` must have been parsed from those same bytes. */
39
+ export declare function codexNativeHookEnvelopeFromInput(input: Record<string, unknown>, event: string, inputBytes: Buffer, inheritedFd?: string): CodexNativeHookEnvelope | undefined;
40
+ /** Injectable so tests can supply fake lineages; production uses the OS. */
41
+ export interface ProcessInspector {
42
+ platform: string;
43
+ arch: string;
44
+ /** The hook process itself. */
45
+ pid: number;
46
+ parentOf(pid: number): number | null;
47
+ /** Opaque start identity; two equal reads mean the same process incarnation. */
48
+ startTime(pid: number): string | null;
49
+ executablePath(pid: number): string | null;
50
+ }
51
+ /** Reviewed operator trust from the managed policy (`bridge.codexNativePolicy`). */
52
+ export interface CodexNativePolicyTrust {
53
+ executableDigests: Readonly<Record<string, readonly string[]>>;
54
+ }
55
+ /** What the native helper attests over the inherited channel (draft contract). */
56
+ export interface CodexNativePolicyAttestation {
57
+ schema: typeof CODEX_NATIVE_POLICY_PEER_SCHEMA;
58
+ peerProcessId: number;
59
+ }
60
+ export interface CodexNativePolicyVerification {
61
+ event: CodexNativeHookEvent;
62
+ sessionId: string;
63
+ turnId: string | null;
64
+ policy: CodexNativeSkillPolicy;
65
+ platform: string;
66
+ ancestryHops: number;
67
+ processStartTime: string;
68
+ executablePath: string;
69
+ executableSha256: string;
70
+ /** No-follow identity of the qualified executable taken before hashing; re-taken after the helper. */
71
+ executableWitness: ExecutableWitness;
72
+ bridge: {
73
+ path: string;
74
+ sha256: string;
75
+ };
76
+ attestation?: CodexNativePolicyAttestation;
77
+ }
78
+ export interface CodexNativePolicyAcceptedSkill {
79
+ path: string;
80
+ treeSha256: string;
81
+ }
82
+ export interface CodexNativePolicyAcceptance {
83
+ schema: typeof CODEX_NATIVE_POLICY_RECEIPT_SCHEMA;
84
+ acceptedAt: string;
85
+ event: CodexNativeHookEvent;
86
+ sessionId: string;
87
+ turnId: string | null;
88
+ process: {
89
+ id: number;
90
+ startTime: string;
91
+ executablePath: string;
92
+ executableSha256: string;
93
+ platform: string;
94
+ };
95
+ effectiveConfigDigest: string;
96
+ bridge: {
97
+ path: string;
98
+ sha256: string;
99
+ };
100
+ acceptedCache: CodexNativePolicyAcceptedSkill[];
101
+ attestation?: CodexNativePolicyAttestation;
102
+ }
103
+ /** Draft native helper transport: argv, bounded run, JSON attestation. */
104
+ export declare const CODEX_NATIVE_POLICY_PEER_SCHEMA = "native-hook-policy-peer-v1";
105
+ export declare const CODEX_NATIVE_POLICY_HELPER_TIMEOUT_MS = 5000;
106
+ export declare const CODEX_NATIVE_POLICY_HELPER_MAX_OUTPUT_BYTES: number;
107
+ /** Hook-deadline bounds: the helper gets min(5 s, remaining minus the margin);
108
+ * a first-run executable hash needs this much budget; below the minimum the
109
+ * adapter refuses fast instead of spawning anything. */
110
+ export declare const CODEX_NATIVE_POLICY_DEADLINE_MARGIN_MS = 1000;
111
+ export declare const CODEX_NATIVE_POLICY_HELPER_MIN_TIMEOUT_MS = 500;
112
+ export declare const CODEX_NATIVE_POLICY_FIRST_HASH_BUDGET_MS = 3000;
113
+ export interface NativePolicyHelperRequest {
114
+ executablePath: string;
115
+ fd: number;
116
+ expectedProcessId: number;
117
+ inputSha256: string;
118
+ timeoutMs: number;
119
+ maxOutputBytes: number;
120
+ }
121
+ export interface NativePolicyHelperResult {
122
+ exitCode: number | null;
123
+ stdout: Buffer;
124
+ timedOut: boolean;
125
+ oversized: boolean;
126
+ }
127
+ /** Injectable so tests can fake the helper; production spawns the verified binary. */
128
+ export type NativePolicyHelperRunner = (request: NativePolicyHelperRequest) => NativePolicyHelperResult;
129
+ export declare const CODEX_NATIVE_POLICY_EXECUTABLE_CACHE_SCHEMA = "skills.codex-native-policy-executable-cache/v1";
130
+ /** No-follow identity of the qualified executable file; the only thing cached. */
131
+ export interface ExecutableWitness {
132
+ dev: string;
133
+ ino: string;
134
+ size: string;
135
+ mtimeNs: string;
136
+ ctimeNs: string;
137
+ uid: string;
138
+ }
139
+ /** Absent trust is an empty set: every platform is unqualified and refuses.
140
+ * The pinned digests are reviewed operator configuration, never a package default. */
141
+ export declare function parseCodexNativePolicyTrust(value: unknown): CodexNativePolicyTrust;
142
+ /** Strict envelope parsing. Unknown keys, unknown capability, unrestricted mode
143
+ * and anything but the exact bridge document refuse. */
144
+ export declare function parseCodexNativeHookEnvelope(envelope: CodexNativeHookEnvelope, bridgeDocument: string): {
145
+ policy: CodexNativeSkillPolicy;
146
+ sessionId: string;
147
+ turnId: string | null;
148
+ hookInputSha256: string | null;
149
+ inheritedFd: number | null;
150
+ };
151
+ /** The owned bridge document: a regular file, no link in any component, its
152
+ * real path equal to its lexical path, and exactly the bytes the guard installs. */
153
+ export declare function verifyCodexNativeBridgeDocument(path: string, expectedContent: string, expectedSha256: string): {
154
+ path: string;
155
+ sha256: string;
156
+ };
157
+ /** Walk the real parent chain from the hook process to the root. The claimed
158
+ * consumer must appear as a strict ancestor. Only a cycle/loop guard bounds
159
+ * the walk; the expected launch-chain shape (which wrappers may sit between
160
+ * Codex and this hook) is pending from the native author and plugs in here as
161
+ * a predicate over the walked chain. */
162
+ export declare function verifyCodexNativeAncestry(inspector: ProcessInspector, processId: number): {
163
+ hops: number;
164
+ chain: number[];
165
+ startTime: string;
166
+ };
167
+ export declare function hashExecutableFile(path: string): string;
168
+ /** Accept only a regular file owned by root or the current user that neither
169
+ * group nor world can write. Pure over the no-follow stat so it is testable
170
+ * with synthetic owners. */
171
+ export declare function assertExecutableWitnessStat(stat: Pick<BigIntStats, "isFile" | "isSymbolicLink" | "uid" | "mode" | "dev" | "ino" | "size" | "mtimeNs" | "ctimeNs">, currentUid: number): ExecutableWitness;
172
+ /** No-follow witness of the path proc_pidpath reported: no component may be a
173
+ * link, and the file must satisfy assertExecutableWitnessStat. */
174
+ export declare function executableWitness(path: string): ExecutableWitness;
175
+ export declare function executableCachePath(dataDir: string): string;
176
+ /** Digest of the qualified executable through the artifact-identity cache.
177
+ * Only (path, dev, ino, size, mtimeNs, ctimeNs, uid) -> sha256 is cached, in
178
+ * process and optionally in a 0600 file under the Skills data dir. A witness
179
+ * that changes between before and after, or differs from the cached key, is
180
+ * re-hashed. Policy, attestation, session and turn results are never cached. */
181
+ export declare function qualifiedExecutableSha256(path: string, options?: {
182
+ dataDir?: string;
183
+ hasher?: (path: string) => string;
184
+ deadlineMs?: number; /** Test seam: the uid the cache file must be owned by (besides root). */
185
+ cacheOwnerUid?: number;
186
+ }): {
187
+ sha256: string;
188
+ witness: ExecutableWitness;
189
+ cached: boolean;
190
+ };
191
+ /** Bind the claimed process to its start identity and a pinned executable
192
+ * digest. The pin set is reviewed operator trust; an absent or empty set for
193
+ * this platform means the platform is unqualified and refuses. Linux stays
194
+ * unqualified until its artifact is reviewed. The helper cannot attest that it
195
+ * is itself reviewed, so this trust stays in the managed policy. */
196
+ export declare function verifyCodexNativeExecutable(inspector: ProcessInspector, processId: number, trust: CodexNativePolicyTrust, cache?: {
197
+ dataDir?: string;
198
+ hasher?: (path: string) => string;
199
+ deadlineMs?: number;
200
+ }, expectations?: {
201
+ ancestryStartTime?: string;
202
+ }): {
203
+ platform: string;
204
+ startTime: string;
205
+ executablePath: string;
206
+ executableSha256: string;
207
+ witness: ExecutableWitness;
208
+ };
209
+ /** The Skills data directory is the operator trust root: the managed policy
210
+ * that carries the reviewed executable digests, and the executable identity
211
+ * cache, must each be a regular file reached through no symlink, owned by the
212
+ * current user or root and writable by neither group nor world. Anything else
213
+ * fails the adapter closed. A writer with the same uid is outside this
214
+ * boundary. */
215
+ export declare function assertOperatorTrustRoot(dataDir: string): void;
216
+ /** Production helper runner: the verified binary, no shell, an allowlisted
217
+ * empty environment, cwd `/`, the inherited descriptor forwarded as fd 3, and
218
+ * bounded time and output. Bun.spawnSync accepts a fourth stdio entry, which
219
+ * dup2s that descriptor to 3 in the child (measured on bun 1.3.14). */
220
+ export declare function runCodexNativePolicyHelper(request: NativePolicyHelperRequest): NativePolicyHelperResult;
221
+ /** Authenticated channel binding, against the frozen native source
222
+ * (native-hook-policy-peer-v1; "line" numbers below index the native
223
+ * producer's hook-policy-peer patch as reviewed):
224
+ * - the hook runner creates a Unix socketpair per invocation (line 296),
225
+ * forwards the receiver to the hook child (line 171) and names it in
226
+ * CODEX_NATIVE_SKILL_POLICY_FD (lines 172-175, constant at line 264);
227
+ * - the verifier is `<codex> debug verify-hook-policy --fd <i32>
228
+ * --expected-process-id <u32> --input-sha256 <hex>` (lines 88-97, dispatch
229
+ * lines 102-103), dispatched in main before config, auth-home or session
230
+ * setup (lines 55-58); it requires fd > 2 (line 449), a positive pid
231
+ * (line 450) and a lowercase 64-hex digest (line 456);
232
+ * - success writes one compact JSON object to stdout followed by a newline and
233
+ * exits 0 (lines 56-58): {"schema":"native-hook-policy-peer-v1",
234
+ * "peerProcessId":<kernel peer pid>,"stdinSha256":<the digest>,
235
+ * "policy":<the native_skill_policy the producer emitted>} (lines 546-550,
236
+ * schema at line 263); any failure returns an error from main, so the
237
+ * process exits non-zero with the message on stderr and nothing on stdout;
238
+ * - the producer hashes the exact bytes it wrote to the hook's stdin,
239
+ * lowercase hex (lines 286-288), and the verifier compares it with
240
+ * --input-sha256 (line 536), so the hook must hash its raw stdin bytes;
241
+ * - the verifier checks the kernel peer before and after a fresh random
242
+ * challenge (lines 466-469, 490-491, 517-519, 526-528); Linux also
243
+ * authenticates every response chunk's writer through SCM_CREDENTIALS.
244
+ * A creator-pid-only binding was rejected (NO_GO: pre-exec forgery
245
+ * reproduced); the fresh challenge plus actual-writer verification is the
246
+ * correction.
247
+ * The hook forwards the descriptor to the QUALIFIED ancestor's own executable
248
+ * (the digest-verified path, never PATH) as fd 3, with no shell, an empty
249
+ * environment, cwd `/` and bounded time and output, and accepts the
250
+ * attestation only when every field matches. The proof is never cached:
251
+ * once per invocation, SessionStart included. The channel pre-check here is
252
+ * fstat only; the socket semantics are enforced by the verifier itself. */
253
+ export declare function verifyCodexNativeChannelBinding(options: {
254
+ executablePath: string;
255
+ inheritedFd: number | null;
256
+ expectedProcessId: number;
257
+ expectedStartTime: string;
258
+ expectedWitness?: ExecutableWitness;
259
+ inputSha256: string | null;
260
+ emittedPolicy: unknown;
261
+ inspector: ProcessInspector;
262
+ runner?: NativePolicyHelperRunner;
263
+ timeoutMs?: number;
264
+ maxOutputBytes?: number;
265
+ deadlineMs?: number;
266
+ }): CodexNativePolicyAttestation;
267
+ /** Required acceptance gate: the authenticated channel binding of the native
268
+ * contract, run with the qualified ancestor's verified executable and its
269
+ * witness, its start time, the raw stdin digest, the inherited descriptor, the
270
+ * emitted policy and the remaining hook budget. */
271
+ export declare function assertAuthenticatedChannelBinding(binding: {
272
+ processId: number;
273
+ hookInputSha256: string | null;
274
+ inheritedFd: number | null;
275
+ verification: CodexNativePolicyVerification;
276
+ emittedPolicy: unknown;
277
+ inspector: ProcessInspector;
278
+ runner?: NativePolicyHelperRunner;
279
+ deadlineMs?: number;
280
+ }): CodexNativePolicyAttestation;
281
+ /** Full adapter verification. Every step refuses with a reason. Order: trust
282
+ * configuration, envelope, bridge, ancestry, executable digest (reviewed trust),
283
+ * then the authenticated channel binding, once per invocation. */
284
+ export declare function verifyCodexNativeSkillPolicy(options: {
285
+ envelope: CodexNativeHookEnvelope;
286
+ bridgeDocument: string;
287
+ expectedBridgeContent: string;
288
+ expectedBridgeSha256: string;
289
+ trust?: unknown;
290
+ inspector?: ProcessInspector;
291
+ dataDir?: string;
292
+ executableHasher?: (path: string) => string;
293
+ helperRunner?: NativePolicyHelperRunner;
294
+ deadlineMs?: number;
295
+ }): CodexNativePolicyVerification;
296
+ /** Darwin: libproc through bun:ffi. proc_pidinfo(PROC_PIDTBSDINFO) yields the
297
+ * parent pid and start time; proc_pidpath yields the executable path. Layout
298
+ * from the SDK's sys/proc_info.h (struct proc_bsdinfo, 136 bytes): pbi_pid at
299
+ * 12, pbi_ppid at 16, pbi_start_tvsec at 120, pbi_start_tvusec at 128. */
300
+ export declare function darwinProcessInspector(): ProcessInspector;
301
+ /** Linux: procfs. The parent pid and start time (clock ticks since boot) come
302
+ * from /proc/<pid>/stat after the bracketed command name; the executable from
303
+ * /proc/<pid>/exe. Linux remains unqualified until its artifact is reviewed. */
304
+ export declare function linuxProcessInspector(): ProcessInspector;
305
+ export declare function defaultProcessInspector(): ProcessInspector;
306
+ export declare function codexNativePolicyReceiptPath(dataDir: string): string;
307
+ /** Write the provenance receipt for one accepted hook: mode 0600, exclusive
308
+ * no-follow temporary file, then rename. Any failure throws, and the caller
309
+ * treats that as a refusal. Reachable only after the channel binding passes. */
310
+ export declare function recordCodexNativePolicyAcceptance(dataDir: string, verification: CodexNativePolicyVerification, accepted: readonly CodexNativePolicyAcceptedSkill[]): string;
@@ -0,0 +1,49 @@
1
+ import { type AgentIntegrationPlan } from "./agent-integration.js";
2
+ export declare const CODEX_NATIVE_TRUST_PLAN_SCHEMA = "skills.codex-native-trust-plan/v1";
3
+ export declare const CODEX_NATIVE_TRUST_RECEIPT_SCHEMA = "skills.codex-native-trust-receipt/v1";
4
+ export interface CodexNativeTrustPlan {
5
+ schema: typeof CODEX_NATIVE_TRUST_PLAN_SCHEMA;
6
+ dataDir: string;
7
+ policyPath: string;
8
+ platform: string;
9
+ digestsBefore: string[];
10
+ digestsAfter: string[];
11
+ policySha256Before: string;
12
+ policySha256After: string;
13
+ /** The ordinary plan the existing apply path writes: at most one change, the managed policy. */
14
+ plan: AgentIntegrationPlan;
15
+ }
16
+ export interface CodexNativeTrustReceipt {
17
+ schema: typeof CODEX_NATIVE_TRUST_RECEIPT_SCHEMA;
18
+ applied: boolean;
19
+ platform: string;
20
+ digestsBefore: string[];
21
+ digestsAfter: string[];
22
+ policySha256Before: string;
23
+ policySha256After: string;
24
+ changes: string[];
25
+ backup?: {
26
+ path: string;
27
+ sha256: string;
28
+ verified: true;
29
+ };
30
+ readback?: {
31
+ policySha256: string;
32
+ digests: string[];
33
+ verified: true;
34
+ };
35
+ }
36
+ /** Validate the request, the current policy and its SHA, and build the plan. Read-only. */
37
+ export declare function planCodexNativeTrust(options: {
38
+ dataDir?: string;
39
+ platform: string;
40
+ digests: readonly string[];
41
+ expectedPolicySha256: string;
42
+ }): CodexNativeTrustPlan;
43
+ export declare function previewCodexNativeTrust(plan: CodexNativeTrustPlan): CodexNativeTrustReceipt;
44
+ /** Write the planned trust. The current bytes are re-checked against the
45
+ * expected SHA immediately before the existing apply path replaces them; that
46
+ * path compares the exact bytes again, backs the original up, replaces the
47
+ * file atomically at 0600 and restores the original on failure. The backup
48
+ * and the result are read back and verified before a receipt is returned. */
49
+ export declare function applyCodexNativeTrust(plan: CodexNativeTrustPlan, expectedPolicySha256: string): CodexNativeTrustReceipt;
@@ -32,6 +32,19 @@ export interface CodexPluginSourceInput {
32
32
  sourceSha256: string;
33
33
  }
34
34
  type Read = (path: string) => string;
35
+ export interface CodexPluginCacheDocumentIdentity {
36
+ name: string;
37
+ namespace: string;
38
+ pluginId: string;
39
+ pluginParent: string;
40
+ root: string;
41
+ manifestSha256: string;
42
+ }
43
+ /** Classify one cache document as installed-plugin content through the same
44
+ * manifest identity, capability review and real-path binding the reviewed
45
+ * predicates use. This is identity only: it consults no enable rule, reviewed
46
+ * control or native catalog, and it never admits anything by itself. */
47
+ export declare function classifyCodexPluginCacheDocument(document: string, cache: string, read: Read): CodexPluginCacheDocumentIdentity | null;
35
48
  /** Establish the reviewed denial/skills-only role for an exact remote parent.
36
49
  * Walk every ancestor: a dangling alias or non-directory is never absence. */
37
50
  export declare function reviewedDisabledCodexPluginParent(cache: string, parent: string, controls: CodexPluginSkillControl[], rules: unknown): boolean;