@jini-ai/core 0.6.21 → 0.6.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
+ ## 0.6.23
2
+
3
+ - Security hardening.
4
+
1
5
  # @jini-ai/core
2
6
 
7
+ ## 0.6.22
8
+
9
+ - `UserFacingError` and `formatUserFacingError`; `decodeToolInputObject` and `unwrapToolInputEnvelope` for delegated tool input.
10
+ - Redaction: whole PEM blocks, merged spans, desktop id and path masking; an unquoted URL secret parameter keeps its enclosing quote.
11
+ - `PlanStaleError` carries `code: "PLAN_STALE"`.
12
+
3
13
  ## 0.6.21
4
14
 
5
15
  - `createPreparedBatchHandler`: prepares a complete direct batch before any effect, reusing the approval handler's input snapshot, cap, duplicate detection and cancellation lifecycle, with no review card.
@@ -35,6 +35,17 @@ export interface RememberedApprovalPort {
35
35
  confirmer: HumanConfirmer;
36
36
  }): Promise<void>;
37
37
  }
38
+ /** Host-owned view/version snapshot captured before asking, then carried into the approved
39
+ * effect. The host validates it at the effect boundary; this port grants no authorization. */
40
+ export interface ApprovalContextPort {
41
+ capture(required: {
42
+ ctx: ToolExecutionContext;
43
+ }): Promise<unknown>;
44
+ prepare(required: {
45
+ ctx: ToolExecutionContext;
46
+ snapshot: unknown;
47
+ }): ToolExecutionContext;
48
+ }
38
49
  export interface ApprovalSteps<TPrepared, TDescription> {
39
50
  prepare(required: {
40
51
  ctx: ToolExecutionContext;
@@ -153,7 +164,10 @@ export declare function createApprovalProposalStore<TDescription>({ responseTool
153
164
  };
154
165
  /** Apply a host classification through the same handler owner used by domain plans.
155
166
  * A host description's string `rememberKey` opts into reusable grants; descriptions remain opaque.
167
+ * Optional context is captured only for asking actions and prepared only after approval.
156
168
  * @returns The registration with one approval handler; authorization policy is preserved.
169
+ * @complexity O(input bytes) snapshot plus bounded injected ports; no retained cross-call state.
170
+ * @overallScore 100
157
171
  */
158
172
  export declare function applyApprovalPolicy<TDescription>({ registration, classify, asks, describe, askHuman, notConfirmed }: {
159
173
  registration: ToolRegistration;
@@ -171,5 +185,6 @@ export declare function applyApprovalPolicy<TDescription>({ registration, classi
171
185
  notConfirmed: ApprovalSteps<string, TDescription>['notConfirmed'];
172
186
  }, optional?: {
173
187
  remembered?: RememberedApprovalPort;
188
+ context?: ApprovalContextPort;
174
189
  }): ToolRegistration;
175
190
  //# sourceMappingURL=approval.d.ts.map
package/dist/approval.js CHANGED
@@ -249,20 +249,34 @@ export function createApprovalProposalStore({ responseTool, messages }, { create
249
249
  }
250
250
  /** Apply a host classification through the same handler owner used by domain plans.
251
251
  * A host description's string `rememberKey` opts into reusable grants; descriptions remain opaque.
252
+ * Optional context is captured only for asking actions and prepared only after approval.
252
253
  * @returns The registration with one approval handler; authorization policy is preserved.
254
+ * @complexity O(input bytes) snapshot plus bounded injected ports; no retained cross-call state.
255
+ * @overallScore 100
253
256
  */
254
257
  export function applyApprovalPolicy({ registration, classify, asks, describe, askHuman, notConfirmed }, optional = {}) {
255
258
  return { ...registration, handler: createApprovalHandler({
256
- prepare: async ({ ctx }) => classify({ input: ctx.input }),
259
+ prepare: async ({ ctx }) => {
260
+ const actionClass = classify({ input: ctx.input });
261
+ const ask = asks({ class: actionClass });
262
+ if (!ask || !optional.context)
263
+ return { actionClass, ask };
264
+ return { actionClass, ask, snapshot: await optional.context.capture({ ctx }) };
265
+ },
257
266
  describe: async ({ ctx, prepared }) => {
258
- const ask = asks({ class: prepared });
259
- const description = await describe({ ctx, class: prepared });
267
+ const description = await describe({ ctx, class: prepared.actionClass });
260
268
  const candidate = { value: description };
261
- return { ask, description, ...(isNonArrayRecord(candidate) && typeof candidate.value.rememberKey === 'string'
269
+ return { ask: prepared.ask, description, ...(isNonArrayRecord(candidate) && typeof candidate.value.rememberKey === 'string'
262
270
  ? { rememberKey: candidate.value.rememberKey } : {}) };
263
271
  },
264
- askHuman, notConfirmed,
265
- run: ({ ctx }, options) => registration.handler(ctx, options),
272
+ askHuman,
273
+ notConfirmed: ({ reason, prepared }) => notConfirmed({ reason, prepared: prepared?.actionClass }),
274
+ run: ({ ctx, prepared }, options) => {
275
+ if (!prepared.ask || !optional.context)
276
+ return registration.handler(ctx, options);
277
+ const executionContext = optional.context.prepare({ ctx, snapshot: prepared.snapshot });
278
+ return registration.handler(executionContext, options);
279
+ },
266
280
  }, optional) };
267
281
  }
268
282
  //# sourceMappingURL=approval.js.map
@@ -62,6 +62,7 @@ export declare class ForbiddenError extends Error {
62
62
  }, optional?: ErrorOptions);
63
63
  }
64
64
  export declare class PlanStaleError extends Error {
65
+ readonly code = "PLAN_STALE";
65
66
  constructor(required: {
66
67
  message: string;
67
68
  }, optional?: ErrorOptions);
@@ -14,6 +14,7 @@ export class ForbiddenError extends Error {
14
14
  }
15
15
  }
16
16
  export class PlanStaleError extends Error {
17
+ code = "PLAN_STALE";
17
18
  constructor(required, optional = {}) {
18
19
  super(required.message, optional);
19
20
  }
package/dist/index.d.ts CHANGED
@@ -7,6 +7,8 @@ export { createDaemon } from './daemon.js';
7
7
  export type { PackDisposalFailure } from './pack-lifecycle.js';
8
8
  export { disposePacks, registerPackTools } from './pack-lifecycle.js';
9
9
  export * from './redact.js';
10
+ export * from './user-facing-error.js';
11
+ export { onTeardown, runTeardownSteps, type TeardownHost, type TeardownStep } from './teardown.js';
10
12
  export * from './api-token-auth.js';
11
13
  export * from './origin-validation.js';
12
14
  export * from './principal.js';
@@ -30,6 +32,7 @@ export * from "./native-regexp.js";
30
32
  export { ForbiddenError as ToolPermissionDeniedError, requireToolPermission, adaptLegacyAuthorize, type LegacyToolAuthorizeFn, type AuthorizationPort as ToolPermissionAuthorizationPort, type RequireToolPermissionRequired, type RequireToolPermissionOptional, } from "./tool-permissions.js";
31
33
  export { evaluateFeatureFlag, type FeatureFlagDecision } from './feature-flags.js';
32
34
  export { normalizeToolInput, withNormalizationNotes, ToolInputNormalizationError, type NormalizedToolInput, type ToolInputNormalizer } from './tool-input-normalization.js';
35
+ export { decodeToolInputObject, unwrapToolInputEnvelope } from './tool-input-envelope.js';
33
36
  export { applyBatchInputs, BATCH_GUIDANCE_MARKER, withoutBatchGuidance, batchOutcomeFor, batchDescriptorFor, batchItemRequiredFields, batchEligibilityFor, defaultBatchableTools, withBatchInput, DEFAULT_MAX_BATCH_ITEMS, type BatchableTool, type BatchEligibility, type BatchOptOut, type BatchResult, type BatchItemResult } from './batch-tool-inputs.js';
34
37
  export type { MediaProviderCatalogPolicy, MediaProviderOption, ProviderPreset, ProviderPresetKind } from './provider-catalog.js';
35
38
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -5,6 +5,8 @@ export * from './bindings.js';
5
5
  export { createDaemon } from './daemon.js';
6
6
  export { disposePacks, registerPackTools } from './pack-lifecycle.js';
7
7
  export * from './redact.js';
8
+ export * from './user-facing-error.js';
9
+ export { onTeardown, runTeardownSteps } from './teardown.js';
8
10
  export * from './api-token-auth.js';
9
11
  export * from './origin-validation.js';
10
12
  export * from './principal.js';
@@ -25,5 +27,6 @@ export * from "./native-regexp.js";
25
27
  export { ForbiddenError as ToolPermissionDeniedError, requireToolPermission, adaptLegacyAuthorize, } from "./tool-permissions.js";
26
28
  export { evaluateFeatureFlag } from './feature-flags.js';
27
29
  export { normalizeToolInput, withNormalizationNotes, ToolInputNormalizationError } from './tool-input-normalization.js';
30
+ export { decodeToolInputObject, unwrapToolInputEnvelope } from './tool-input-envelope.js';
28
31
  export { applyBatchInputs, BATCH_GUIDANCE_MARKER, withoutBatchGuidance, batchOutcomeFor, batchDescriptorFor, batchItemRequiredFields, batchEligibilityFor, defaultBatchableTools, withBatchInput, DEFAULT_MAX_BATCH_ITEMS } from './batch-tool-inputs.js';
29
32
  //# sourceMappingURL=index.js.map
package/dist/redact.d.ts CHANGED
@@ -38,6 +38,21 @@ export interface SecretShapePattern {
38
38
  }
39
39
  export declare const PEM_PATTERN_NAME = "PEM private key block";
40
40
  export declare const SECRET_SHAPE_PATTERNS: readonly SecretShapePattern[];
41
+ /**
42
+ * Suppress PEM bodies across complete, ordered text lines before other redactors run.
43
+ * @param _required No ambient state; create one instance per framed output stream.
44
+ * @param _options Reserved optional policy inputs.
45
+ * @returns A line redactor and an EOF reset. Call reset after flushing the final line so a
46
+ * replacement child cannot inherit an unterminated block. Does not frame raw chunks or do I/O.
47
+ * @complexity O(n) per line; O(h) retained state for the open header's key type of length h.
48
+ * @example const keys = createPrivateKeyLineRedactor({}, {}); keys.redact({ text: line }, {});
49
+ */
50
+ export declare function createPrivateKeyLineRedactor(_required: Record<string, never>, _options?: {}): {
51
+ redact(required: {
52
+ text: string;
53
+ }, optional?: Record<string, never>): string;
54
+ reset(required: Record<string, never>, optional?: Record<string, never>): void;
55
+ };
41
56
  export declare const AGENT_FAILURE_SECRET_SHAPE_RE: RegExp;
42
57
  /**
43
58
  * Exact path SEGMENT names refused at any depth, matched case-insensitively against each segment
@@ -69,6 +84,8 @@ export interface SecretRedactionPattern {
69
84
  name: string;
70
85
  regex: RegExp;
71
86
  }
87
+ /** Every marker redact can generate from its built-in categories; consumers normalize only these. */
88
+ export declare const GENERATED_MARKER_RE: RegExp;
72
89
  /** Optional caller-owned secrets and named lexical rules, without ambient credential lookup. */
73
90
  export interface SecretRedactionOptions {
74
91
  /** Diagnostic text can retain addresses/numbers while using the same credential rules. Default true. */
@@ -88,9 +105,11 @@ export declare const NON_SECRET_TOKEN_RE: RegExp;
88
105
  /**
89
106
  * Returns `input` with every recognised secret / PII pattern replaced by a
90
107
  * `[REDACTED:<kind>]` marker. Under the default policies, idempotent — re-running on already redacted
91
- * text only matches new tokens. Defaults to conservative; `{ policy: 'aggressive' }`
108
+ * text only matches new tokens. Exact occurrences wholly within credential labels or generated markers preserve that syntax;
109
+ * crossing occurrences still mask their complete range; all value rules still see the original context. Defaults to conservative; `{ policy: 'aggressive' }`
92
110
  * additionally masks bare 20+ character opaque runs. Exact secrets and extra patterns
93
- * are honored under either heuristic policy. Exact-only replaces only supplied secrets in one pass,
111
+ * are honored under either heuristic policy.
112
+ * Exact-only replaces only supplied secrets in one pass,
94
113
  * preserves unmatched characters, and inserts the optional literal marker without reprocessing it.
95
114
  *
96
115
  * Empty / non-string input passes through unchanged so the caller can use
@@ -117,9 +136,17 @@ export declare function formatErrorForLog(required: {
117
136
  * caller can attach an audit summary to the trace metadata ('we stripped 2
118
137
  * keys + 1 email before send').
119
138
  *
120
- * Exact secrets run first, longest first: lexical rules or a shorter supplied secret must not
121
- * split a known credential and leave a suffix behind. Opt-in aggressive opaque rules precede
122
- * PII rules for the same reason (a numeric token can also contain phone/card candidates).
139
+ * Every rule matches the ORIGINAL input and contributes value spans; overlapping or adjacent
140
+ * spans merge, and one left-to-right pass writes one marker per merged span. Sequential
141
+ * substitution passes leaked in every order tried: each pass saw text an earlier pass had
142
+ * changed. Exact secrets run first split labels (`a` inside `password=hunter2` left `hunter2`) and
143
+ * lost context (`note password=` left `hunter2`); run last, a lexical rule that stopped partway
144
+ * (the label rule at whitespace, vendor/query rules at `.`, `;`, `&`, `!` or quotes) left the rest
145
+ * of a known secret visible; protecting spans exempted secrets overlapping a marker; and
146
+ * overlapping secrets left a tail behind. Opt-in aggressive opaque
147
+ * rules are collected before PII rules so a numeric token keeps the opaque category when tied.
148
+ * @complexity O(n·(r + e) + s + k) for n input chars, r rules, e exact-secret needles, s needle
149
+ * chars and k spans.
123
150
  */
124
151
  export declare function redactSecretsWithCounts({ input }: {
125
152
  input: string;