@oka-core/reason 0.2.15 → 0.2.16

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.
Files changed (158) hide show
  1. package/dist/abort-controller.d.ts +19 -0
  2. package/dist/abort-controller.d.ts.map +1 -0
  3. package/dist/abort-controller.js +53 -0
  4. package/dist/activity-tracker.d.ts +48 -0
  5. package/dist/activity-tracker.d.ts.map +1 -0
  6. package/dist/activity-tracker.js +80 -0
  7. package/dist/analytics.d.ts +49 -0
  8. package/dist/analytics.d.ts.map +1 -0
  9. package/dist/analytics.js +88 -0
  10. package/dist/array.d.ts +12 -0
  11. package/dist/array.d.ts.map +1 -0
  12. package/dist/array.js +20 -0
  13. package/dist/async-context.d.ts +20 -0
  14. package/dist/async-context.d.ts.map +1 -0
  15. package/dist/async-context.js +25 -0
  16. package/dist/binary-check.d.ts +16 -0
  17. package/dist/binary-check.d.ts.map +1 -0
  18. package/dist/binary-check.js +43 -0
  19. package/dist/buffered-writer.d.ts +30 -0
  20. package/dist/buffered-writer.d.ts.map +1 -0
  21. package/dist/buffered-writer.js +87 -0
  22. package/dist/circular-buffer.d.ts +28 -0
  23. package/dist/circular-buffer.d.ts.map +1 -0
  24. package/dist/circular-buffer.js +61 -0
  25. package/dist/cleanup-registry.d.ts +23 -0
  26. package/dist/cleanup-registry.d.ts.map +1 -0
  27. package/dist/cleanup-registry.js +34 -0
  28. package/dist/client.d.ts +4 -0
  29. package/dist/client.d.ts.map +1 -1
  30. package/dist/client.js +32 -10
  31. package/dist/combined-abort-signal.d.ts +25 -0
  32. package/dist/combined-abort-signal.d.ts.map +1 -0
  33. package/dist/combined-abort-signal.js +47 -0
  34. package/dist/cron-lock.d.ts +29 -0
  35. package/dist/cron-lock.d.ts.map +1 -0
  36. package/dist/cron-lock.js +127 -0
  37. package/dist/cron-scheduler.d.ts +41 -0
  38. package/dist/cron-scheduler.d.ts.map +1 -0
  39. package/dist/cron-scheduler.js +189 -0
  40. package/dist/cron-tasks.d.ts +86 -0
  41. package/dist/cron-tasks.d.ts.map +1 -0
  42. package/dist/cron-tasks.js +205 -0
  43. package/dist/cron.d.ts +35 -0
  44. package/dist/cron.d.ts.map +1 -0
  45. package/dist/cron.js +215 -0
  46. package/dist/env.d.ts +26 -0
  47. package/dist/env.d.ts.map +1 -0
  48. package/dist/env.js +50 -0
  49. package/dist/errors.d.ts +99 -0
  50. package/dist/errors.d.ts.map +1 -0
  51. package/dist/errors.js +214 -0
  52. package/dist/format.d.ts +21 -0
  53. package/dist/format.d.ts.map +1 -0
  54. package/dist/format.js +48 -0
  55. package/dist/fps-tracker.d.ts +22 -0
  56. package/dist/fps-tracker.d.ts.map +1 -0
  57. package/dist/fps-tracker.js +44 -0
  58. package/dist/graceful-shutdown.d.ts +35 -0
  59. package/dist/graceful-shutdown.d.ts.map +1 -0
  60. package/dist/graceful-shutdown.js +89 -0
  61. package/dist/hash.d.ts +21 -0
  62. package/dist/hash.d.ts.map +1 -0
  63. package/dist/hash.js +31 -0
  64. package/dist/heap-diagnostics.d.ts +68 -0
  65. package/dist/heap-diagnostics.d.ts.map +1 -0
  66. package/dist/heap-diagnostics.js +110 -0
  67. package/dist/idle-timeout.d.ts +21 -0
  68. package/dist/idle-timeout.d.ts.map +1 -0
  69. package/dist/idle-timeout.js +42 -0
  70. package/dist/index.d.ts +2 -1
  71. package/dist/index.d.ts.map +1 -1
  72. package/dist/index.js +5 -0
  73. package/dist/intl.d.ts +18 -0
  74. package/dist/intl.d.ts.map +1 -0
  75. package/dist/intl.js +75 -0
  76. package/dist/jsonl.d.ts +16 -0
  77. package/dist/jsonl.d.ts.map +1 -0
  78. package/dist/jsonl.js +60 -0
  79. package/dist/lazy-schema.d.ts +6 -0
  80. package/dist/lazy-schema.d.ts.map +1 -0
  81. package/dist/lazy-schema.js +8 -0
  82. package/dist/memo.d.ts +64 -0
  83. package/dist/memo.d.ts.map +1 -0
  84. package/dist/memo.js +162 -0
  85. package/dist/pkce.d.ts +13 -0
  86. package/dist/pkce.d.ts.map +1 -0
  87. package/dist/pkce.js +28 -0
  88. package/dist/priority-queue.d.ts +36 -0
  89. package/dist/priority-queue.d.ts.map +1 -0
  90. package/dist/priority-queue.js +97 -0
  91. package/dist/process-utils.d.ts +20 -0
  92. package/dist/process-utils.d.ts.map +1 -0
  93. package/dist/process-utils.js +54 -0
  94. package/dist/query-guard.d.ts +34 -0
  95. package/dist/query-guard.d.ts.map +1 -0
  96. package/dist/query-guard.js +74 -0
  97. package/dist/retry.d.ts +60 -0
  98. package/dist/retry.d.ts.map +1 -0
  99. package/dist/retry.js +89 -0
  100. package/dist/schemas.d.ts +6 -6
  101. package/dist/secrets.d.ts +44 -0
  102. package/dist/secrets.d.ts.map +1 -0
  103. package/dist/secrets.js +115 -0
  104. package/dist/semantic-types.d.ts +39 -0
  105. package/dist/semantic-types.d.ts.map +1 -0
  106. package/dist/semantic-types.js +49 -0
  107. package/dist/sequential.d.ts +21 -0
  108. package/dist/sequential.d.ts.map +1 -0
  109. package/dist/sequential.js +49 -0
  110. package/dist/signal.d.ts +29 -0
  111. package/dist/signal.d.ts.map +1 -0
  112. package/dist/signal.js +39 -0
  113. package/dist/sleep.d.ts +21 -0
  114. package/dist/sleep.d.ts.map +1 -0
  115. package/dist/sleep.js +58 -0
  116. package/dist/slow-ops.d.ts +41 -0
  117. package/dist/slow-ops.d.ts.map +1 -0
  118. package/dist/slow-ops.js +133 -0
  119. package/dist/store.d.ts +20 -0
  120. package/dist/store.d.ts.map +1 -0
  121. package/dist/store.js +34 -0
  122. package/dist/stream.d.ts +29 -0
  123. package/dist/stream.d.ts.map +1 -0
  124. package/dist/stream.js +92 -0
  125. package/dist/string-utils.d.ts +46 -0
  126. package/dist/string-utils.d.ts.map +1 -0
  127. package/dist/string-utils.js +69 -0
  128. package/dist/strip-bom.d.ts +8 -0
  129. package/dist/strip-bom.d.ts.map +1 -0
  130. package/dist/strip-bom.js +10 -0
  131. package/dist/subprocess-env.d.ts +25 -0
  132. package/dist/subprocess-env.d.ts.map +1 -0
  133. package/dist/subprocess-env.js +55 -0
  134. package/dist/temp-file.d.ts +18 -0
  135. package/dist/temp-file.d.ts.map +1 -0
  136. package/dist/temp-file.js +26 -0
  137. package/dist/tool-contract.d.ts +85 -0
  138. package/dist/tool-contract.d.ts.map +1 -0
  139. package/dist/tool-contract.js +101 -0
  140. package/dist/tools/read.d.ts +2 -10
  141. package/dist/tools/read.d.ts.map +1 -1
  142. package/dist/tools/read.js +662 -537
  143. package/dist/tools/write.d.ts +3 -2
  144. package/dist/tools/write.d.ts.map +1 -1
  145. package/dist/tools/write.js +329 -177
  146. package/dist/uuid.d.ts +20 -0
  147. package/dist/uuid.d.ts.map +1 -0
  148. package/dist/uuid.js +28 -0
  149. package/dist/validation.d.ts +64 -0
  150. package/dist/validation.d.ts.map +1 -0
  151. package/dist/validation.js +236 -0
  152. package/dist/with-resolvers.d.ts +12 -0
  153. package/dist/with-resolvers.d.ts.map +1 -0
  154. package/dist/with-resolvers.js +14 -0
  155. package/dist/xml-escape.d.ts +12 -0
  156. package/dist/xml-escape.d.ts.map +1 -0
  157. package/dist/xml-escape.js +15 -0
  158. package/package.json +1 -1
@@ -0,0 +1,236 @@
1
+ /**
2
+ * Input validation and path security — pure domain layer.
3
+ *
4
+ * ZERO external dependencies. Inspired by Claude Code's hardened
5
+ * validation patterns for MCP tool inputs.
6
+ *
7
+ * @module validation
8
+ */
9
+ import { ValidationError, PathSecurityError, ContentTooLargeError, } from "./errors.js";
10
+ // ─── Constants ──────────────────────────────────────────────────────
11
+ /** Control characters to reject (excludes \t, \n, \r which are common whitespace). */
12
+ const CONTROL_CHAR_RE = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/;
13
+ /** URL-encoded traversal sequences (case-insensitive). */
14
+ const URL_ENCODED_TRAVERSAL_RE = /(%2e%2e|%2f|%5c)/i;
15
+ /** Dangerous device paths on Unix systems. */
16
+ const BLOCKED_DEVICE_PREFIXES = ["/dev/", "/proc/self/fd/", "/sys/"];
17
+ /** Exact blocked device paths. */
18
+ const BLOCKED_DEVICE_EXACT = new Set([
19
+ "/dev/zero",
20
+ "/dev/null",
21
+ "/dev/stdin",
22
+ "/dev/stdout",
23
+ "/dev/stderr",
24
+ "/dev/random",
25
+ "/dev/urandom",
26
+ "/dev/tty",
27
+ ]);
28
+ /** Filenames and directory names considered dangerous for write operations. */
29
+ const DANGEROUS_FILES = new Set([
30
+ ".gitconfig",
31
+ ".git-credentials",
32
+ ".bashrc",
33
+ ".bash_profile",
34
+ ".bash_login",
35
+ ".profile",
36
+ ".zshrc",
37
+ ".zprofile",
38
+ ".zshenv",
39
+ ".env",
40
+ ".env.local",
41
+ ".env.production",
42
+ ".env.development",
43
+ "credentials.json",
44
+ "service-account.json",
45
+ ".mcp.json",
46
+ ".npmrc",
47
+ ".yarnrc",
48
+ ".pypirc",
49
+ "id_rsa",
50
+ "id_ed25519",
51
+ "id_ecdsa",
52
+ "id_dsa",
53
+ "known_hosts",
54
+ "authorized_keys",
55
+ "config",
56
+ ".netrc",
57
+ ".pgpass",
58
+ ".my.cnf",
59
+ ".docker/config.json",
60
+ "kubeconfig",
61
+ ".kube/config",
62
+ ]);
63
+ /** Directory names that are dangerous to traverse into. */
64
+ const DANGEROUS_DIRS = new Set([
65
+ ".ssh",
66
+ ".gnupg",
67
+ ".aws",
68
+ ".azure",
69
+ ".gcloud",
70
+ ".config",
71
+ ".docker",
72
+ ".kube",
73
+ ]);
74
+ // ─── Input Validation ───────────────────────────────────────────────
75
+ /**
76
+ * Validate and sanitize a free-text input string.
77
+ *
78
+ * Rejects null bytes, control characters (except \t, \n, \r),
79
+ * and inputs exceeding `maxLength`. Returns the trimmed string.
80
+ *
81
+ * @param input - Raw input string
82
+ * @param maxLength - Maximum allowed length (after trim)
83
+ * @returns Trimmed, validated input
84
+ * @throws {ValidationError} On invalid input
85
+ */
86
+ export function validateInput(input, maxLength) {
87
+ if (input == null) {
88
+ throw new ValidationError("Input must not be null or undefined");
89
+ }
90
+ // Null byte check (explicit for clarity — also caught by control char regex)
91
+ if (input.includes("\0")) {
92
+ throw new ValidationError("Input contains null bytes");
93
+ }
94
+ // Control character check (allow \t \n \r)
95
+ if (CONTROL_CHAR_RE.test(input)) {
96
+ throw new ValidationError("Input contains disallowed control characters");
97
+ }
98
+ const trimmed = input.trim();
99
+ if (trimmed.length === 0) {
100
+ throw new ValidationError("Input must not be empty");
101
+ }
102
+ if (trimmed.length > maxLength) {
103
+ throw new ValidationError(`Input exceeds maximum length of ${maxLength} characters`);
104
+ }
105
+ return trimmed;
106
+ }
107
+ // ─── Path Security ──────────────────────────────────────────────────
108
+ /**
109
+ * Validate a file path against security constraints and allowed prefixes.
110
+ *
111
+ * Checks for:
112
+ * - Null bytes
113
+ * - URL-encoded traversal sequences (`%2e%2e`, `%2f`, `%5c`)
114
+ * - NFKC normalization attacks (path changes after normalization)
115
+ * - `..` path components (directory traversal)
116
+ * - Prefix boundary enforcement (`/allowed` must not match `/allowed-evil`)
117
+ *
118
+ * @param path - The file path to validate
119
+ * @param allowedPrefixes - Array of allowed path prefixes
120
+ * @returns The validated, normalized path
121
+ * @throws {PathSecurityError} On any security violation
122
+ */
123
+ export function validatePath(path, allowedPrefixes) {
124
+ if (!path || typeof path !== "string") {
125
+ throw new PathSecurityError("Path must be a non-empty string");
126
+ }
127
+ // Null byte injection
128
+ if (path.includes("\0")) {
129
+ throw new PathSecurityError("Path contains null bytes");
130
+ }
131
+ // URL-encoded traversal detection (before any decoding)
132
+ if (URL_ENCODED_TRAVERSAL_RE.test(path)) {
133
+ throw new PathSecurityError("Path contains URL-encoded traversal sequences");
134
+ }
135
+ // NFKC normalization attack: normalize and compare.
136
+ // Attackers use Unicode lookalikes (e.g. fullwidth dots) that normalize
137
+ // to traversal sequences.
138
+ const normalized = path.normalize("NFKC");
139
+ if (normalized !== path) {
140
+ throw new PathSecurityError("Path changes under NFKC normalization — possible Unicode attack");
141
+ }
142
+ // Split on both forward and back slashes for cross-platform safety
143
+ const segments = path.split(/[/\\]/);
144
+ for (const segment of segments) {
145
+ if (segment === "..") {
146
+ throw new PathSecurityError("Path contains directory traversal component (..)");
147
+ }
148
+ }
149
+ // Prefix boundary check: the path must start with an allowed prefix
150
+ // followed by either end-of-string or a path separator.
151
+ const matchesPrefix = allowedPrefixes.some((prefix) => {
152
+ if (path === prefix)
153
+ return true;
154
+ // Ensure boundary: prefix "/allowed" must not match "/allowed-evil"
155
+ const withSep = prefix.endsWith("/") ? prefix : `${prefix}/`;
156
+ return path.startsWith(withSep);
157
+ });
158
+ if (!matchesPrefix) {
159
+ throw new PathSecurityError("Path is outside allowed prefixes");
160
+ }
161
+ return path;
162
+ }
163
+ // ─── Device Path Blocking ───────────────────────────────────────────
164
+ /**
165
+ * Check whether a path refers to a blocked device or virtual filesystem.
166
+ *
167
+ * Blocks `/dev/*`, `/proc/self/fd/*`, `/sys/*` and specific device nodes.
168
+ *
169
+ * @param path - The path to check
170
+ * @returns `true` if the path is a blocked device path
171
+ */
172
+ export function isBlockedDevicePath(path) {
173
+ if (!path)
174
+ return false;
175
+ const lower = path.toLowerCase();
176
+ if (BLOCKED_DEVICE_EXACT.has(lower))
177
+ return true;
178
+ for (const prefix of BLOCKED_DEVICE_PREFIXES) {
179
+ if (lower.startsWith(prefix))
180
+ return true;
181
+ }
182
+ return false;
183
+ }
184
+ // ─── Dangerous File Detection ───────────────────────────────────────
185
+ /**
186
+ * Check whether a filename (or path) refers to a sensitive/dangerous file.
187
+ *
188
+ * Detects config files (`.gitconfig`, `.bashrc`, `.env`), credential files
189
+ * (`credentials.json`, SSH keys), and sensitive directories (`.ssh/`, `.aws/`).
190
+ *
191
+ * @param filename - Filename or path to check (basename is extracted)
192
+ * @returns `true` if the file is considered dangerous
193
+ */
194
+ export function isDangerousFile(filename) {
195
+ if (!filename)
196
+ return false;
197
+ // Extract basename — handle both / and \ separators
198
+ const segments = filename.split(/[/\\]/).filter(Boolean);
199
+ // Check if any directory component is a dangerous dir
200
+ for (const segment of segments) {
201
+ if (DANGEROUS_DIRS.has(segment))
202
+ return true;
203
+ }
204
+ // Check the final filename
205
+ const basename = segments[segments.length - 1];
206
+ if (!basename)
207
+ return false;
208
+ if (DANGEROUS_FILES.has(basename))
209
+ return true;
210
+ // Catch .env.* variants
211
+ if (basename.startsWith(".env."))
212
+ return true;
213
+ // Catch SSH private key patterns
214
+ if (basename.startsWith("id_") && !basename.includes(".pub"))
215
+ return true;
216
+ return false;
217
+ }
218
+ // ─── File Size Validation ───────────────────────────────────────────
219
+ /**
220
+ * Validate that a file size is within acceptable bounds.
221
+ *
222
+ * @param bytes - Actual size in bytes
223
+ * @param maxBytes - Maximum allowed size in bytes
224
+ * @throws {ContentTooLargeError} If size exceeds the limit
225
+ */
226
+ export function validateFileSize(bytes, maxBytes) {
227
+ if (bytes < 0) {
228
+ throw new ValidationError("File size must not be negative");
229
+ }
230
+ if (maxBytes < 0) {
231
+ throw new ValidationError("Max size must not be negative");
232
+ }
233
+ if (bytes > maxBytes) {
234
+ throw new ContentTooLargeError(bytes, maxBytes);
235
+ }
236
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Polyfill for Promise.withResolvers() (ES2024, Node 22+).
3
+ *
4
+ * Inspired by Claude Code's `src/utils/withResolvers.ts`.
5
+ */
6
+ export type Deferred<T> = {
7
+ promise: Promise<T>;
8
+ resolve: (value: T | PromiseLike<T>) => void;
9
+ reject: (reason?: unknown) => void;
10
+ };
11
+ export declare function withResolvers<T>(): Deferred<T>;
12
+ //# sourceMappingURL=with-resolvers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"with-resolvers.d.ts","sourceRoot":"","sources":["../src/with-resolvers.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI;IACxB,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IACpB,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;IAC7C,MAAM,EAAE,CAAC,MAAM,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;CACpC,CAAC;AAEF,wBAAgB,aAAa,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAQ9C"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Polyfill for Promise.withResolvers() (ES2024, Node 22+).
3
+ *
4
+ * Inspired by Claude Code's `src/utils/withResolvers.ts`.
5
+ */
6
+ export function withResolvers() {
7
+ let resolve;
8
+ let reject;
9
+ const promise = new Promise((res, rej) => {
10
+ resolve = res;
11
+ reject = rej;
12
+ });
13
+ return { promise, resolve, reject };
14
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Escape XML/HTML special characters for safe interpolation into element
3
+ * text content (between tags). Use when untrusted strings (process stdout,
4
+ * user input, external data) go inside `<tag>${here}</tag>`.
5
+ */
6
+ export declare function escapeXml(s: string): string;
7
+ /**
8
+ * Escape for interpolation into a double- or single-quoted attribute value:
9
+ * `<tag attr="${here}">`. Escapes quotes in addition to `& < >`.
10
+ */
11
+ export declare function escapeXmlAttr(s: string): string;
12
+ //# sourceMappingURL=xml-escape.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xml-escape.d.ts","sourceRoot":"","sources":["../src/xml-escape.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE/C"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Escape XML/HTML special characters for safe interpolation into element
3
+ * text content (between tags). Use when untrusted strings (process stdout,
4
+ * user input, external data) go inside `<tag>${here}</tag>`.
5
+ */
6
+ export function escapeXml(s) {
7
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
8
+ }
9
+ /**
10
+ * Escape for interpolation into a double- or single-quoted attribute value:
11
+ * `<tag attr="${here}">`. Escapes quotes in addition to `& < >`.
12
+ */
13
+ export function escapeXmlAttr(s) {
14
+ return escapeXml(s).replace(/"/g, "&quot;").replace(/'/g, "&apos;");
15
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oka-core/reason",
3
- "version": "0.2.15",
3
+ "version": "0.2.16",
4
4
  "description": "MCP server for institutional knowledge capture, semantic search, and consolidation",
5
5
  "private": false,
6
6
  "publishConfig": {