elepha 0.1.0

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 (290) hide show
  1. package/LICENSE.md +375 -0
  2. package/README.md +135 -0
  3. package/bin/elepha.js +2 -0
  4. package/dist/adapters/base.js +600 -0
  5. package/dist/adapters/base.js.map +1 -0
  6. package/dist/adapters/claude-code.js +260 -0
  7. package/dist/adapters/claude-code.js.map +1 -0
  8. package/dist/adapters/codex.js +499 -0
  9. package/dist/adapters/codex.js.map +1 -0
  10. package/dist/adapters/discriminators.js +39 -0
  11. package/dist/adapters/discriminators.js.map +1 -0
  12. package/dist/adapters/index.js +6 -0
  13. package/dist/adapters/index.js.map +1 -0
  14. package/dist/adapters/internal-command.js +3 -0
  15. package/dist/adapters/internal-command.js.map +1 -0
  16. package/dist/cli/backup-wizard.js +97 -0
  17. package/dist/cli/backup-wizard.js.map +1 -0
  18. package/dist/cli/capture-service.js +80 -0
  19. package/dist/cli/capture-service.js.map +1 -0
  20. package/dist/cli/commands/backfills.js +336 -0
  21. package/dist/cli/commands/backfills.js.map +1 -0
  22. package/dist/cli/commands/backup.js +234 -0
  23. package/dist/cli/commands/backup.js.map +1 -0
  24. package/dist/cli/commands/config.js +66 -0
  25. package/dist/cli/commands/config.js.map +1 -0
  26. package/dist/cli/commands/consent.js +237 -0
  27. package/dist/cli/commands/consent.js.map +1 -0
  28. package/dist/cli/commands/daemon-control.js +76 -0
  29. package/dist/cli/commands/daemon-control.js.map +1 -0
  30. package/dist/cli/commands/doctor.js +31 -0
  31. package/dist/cli/commands/doctor.js.map +1 -0
  32. package/dist/cli/commands/hook.js +24 -0
  33. package/dist/cli/commands/hook.js.map +1 -0
  34. package/dist/cli/commands/import.js +611 -0
  35. package/dist/cli/commands/import.js.map +1 -0
  36. package/dist/cli/commands/init.js +19 -0
  37. package/dist/cli/commands/init.js.map +1 -0
  38. package/dist/cli/commands/inspect.js +54 -0
  39. package/dist/cli/commands/inspect.js.map +1 -0
  40. package/dist/cli/commands/install.js +49 -0
  41. package/dist/cli/commands/install.js.map +1 -0
  42. package/dist/cli/commands/internal.js +16 -0
  43. package/dist/cli/commands/internal.js.map +1 -0
  44. package/dist/cli/commands/mcp.js +12 -0
  45. package/dist/cli/commands/mcp.js.map +1 -0
  46. package/dist/cli/commands/projects.js +26 -0
  47. package/dist/cli/commands/projects.js.map +1 -0
  48. package/dist/cli/commands/purge.js +216 -0
  49. package/dist/cli/commands/purge.js.map +1 -0
  50. package/dist/cli/commands/reingest.js +80 -0
  51. package/dist/cli/commands/reingest.js.map +1 -0
  52. package/dist/cli/commands/rekey.js +58 -0
  53. package/dist/cli/commands/rekey.js.map +1 -0
  54. package/dist/cli/commands/restore.js +376 -0
  55. package/dist/cli/commands/restore.js.map +1 -0
  56. package/dist/cli/commands/rollup.js +130 -0
  57. package/dist/cli/commands/rollup.js.map +1 -0
  58. package/dist/cli/commands/sanitize.js +72 -0
  59. package/dist/cli/commands/sanitize.js.map +1 -0
  60. package/dist/cli/commands/segment.js +153 -0
  61. package/dist/cli/commands/segment.js.map +1 -0
  62. package/dist/cli/commands/self-update.js +26 -0
  63. package/dist/cli/commands/self-update.js.map +1 -0
  64. package/dist/cli/commands/start.js +53 -0
  65. package/dist/cli/commands/start.js.map +1 -0
  66. package/dist/cli/commands/stats.js +42 -0
  67. package/dist/cli/commands/stats.js.map +1 -0
  68. package/dist/cli/commands/status.js +73 -0
  69. package/dist/cli/commands/status.js.map +1 -0
  70. package/dist/cli/commands/uninstall.js +23 -0
  71. package/dist/cli/commands/uninstall.js.map +1 -0
  72. package/dist/cli/config-wizard.js +104 -0
  73. package/dist/cli/config-wizard.js.map +1 -0
  74. package/dist/cli/destructive-op.js +32 -0
  75. package/dist/cli/destructive-op.js.map +1 -0
  76. package/dist/cli/import-wizard.js +89 -0
  77. package/dist/cli/import-wizard.js.map +1 -0
  78. package/dist/cli/index.js +74 -0
  79. package/dist/cli/index.js.map +1 -0
  80. package/dist/cli/init-wizard.js +80 -0
  81. package/dist/cli/init-wizard.js.map +1 -0
  82. package/dist/cli/init.js +159 -0
  83. package/dist/cli/init.js.map +1 -0
  84. package/dist/cli/launcher-probe.js +71 -0
  85. package/dist/cli/launcher-probe.js.map +1 -0
  86. package/dist/cli/project-path.js +16 -0
  87. package/dist/cli/project-path.js.map +1 -0
  88. package/dist/cli/purge-wizard.js +200 -0
  89. package/dist/cli/purge-wizard.js.map +1 -0
  90. package/dist/cli/restore-wizard.js +101 -0
  91. package/dist/cli/restore-wizard.js.map +1 -0
  92. package/dist/cli/shared.js +128 -0
  93. package/dist/cli/shared.js.map +1 -0
  94. package/dist/cli/status.js +22 -0
  95. package/dist/cli/status.js.map +1 -0
  96. package/dist/cli/wordmark.js +15 -0
  97. package/dist/cli/wordmark.js.map +1 -0
  98. package/dist/config/constants.js +115 -0
  99. package/dist/config/constants.js.map +1 -0
  100. package/dist/config/env.js +168 -0
  101. package/dist/config/env.js.map +1 -0
  102. package/dist/config/memory-config.js +63 -0
  103. package/dist/config/memory-config.js.map +1 -0
  104. package/dist/config/paths.js +343 -0
  105. package/dist/config/paths.js.map +1 -0
  106. package/dist/config/settings.js +132 -0
  107. package/dist/config/settings.js.map +1 -0
  108. package/dist/config/version.js +12 -0
  109. package/dist/config/version.js.map +1 -0
  110. package/dist/daemon/failure-window.js +38 -0
  111. package/dist/daemon/failure-window.js.map +1 -0
  112. package/dist/daemon/heartbeat.js +40 -0
  113. package/dist/daemon/heartbeat.js.map +1 -0
  114. package/dist/daemon/index.js +873 -0
  115. package/dist/daemon/index.js.map +1 -0
  116. package/dist/daemon/log-rotation.js +27 -0
  117. package/dist/daemon/log-rotation.js.map +1 -0
  118. package/dist/daemon/readability-guard.js +89 -0
  119. package/dist/daemon/readability-guard.js.map +1 -0
  120. package/dist/daemon/rollup-service.js +239 -0
  121. package/dist/daemon/rollup-service.js.map +1 -0
  122. package/dist/daemon/update-check.js +87 -0
  123. package/dist/daemon/update-check.js.map +1 -0
  124. package/dist/daemon/work-queue.js +28 -0
  125. package/dist/daemon/work-queue.js.map +1 -0
  126. package/dist/discovery/session-projects.js +242 -0
  127. package/dist/discovery/session-projects.js.map +1 -0
  128. package/dist/hooks/codex-trust.js +80 -0
  129. package/dist/hooks/codex-trust.js.map +1 -0
  130. package/dist/hooks/common.js +81 -0
  131. package/dist/hooks/common.js.map +1 -0
  132. package/dist/hooks/hook-log.js +38 -0
  133. package/dist/hooks/hook-log.js.map +1 -0
  134. package/dist/hooks/installer.js +200 -0
  135. package/dist/hooks/installer.js.map +1 -0
  136. package/dist/hooks/session-start.js +321 -0
  137. package/dist/hooks/session-start.js.map +1 -0
  138. package/dist/hooks/user-prompt-submit.js +248 -0
  139. package/dist/hooks/user-prompt-submit.js.map +1 -0
  140. package/dist/install/binary.js +72 -0
  141. package/dist/install/binary.js.map +1 -0
  142. package/dist/install/config-file.js +119 -0
  143. package/dist/install/config-file.js.map +1 -0
  144. package/dist/install/daemon-health.js +48 -0
  145. package/dist/install/daemon-health.js.map +1 -0
  146. package/dist/install/daemon-service.js +309 -0
  147. package/dist/install/daemon-service.js.map +1 -0
  148. package/dist/install/doctor.js +180 -0
  149. package/dist/install/doctor.js.map +1 -0
  150. package/dist/install/health-checks.js +90 -0
  151. package/dist/install/health-checks.js.map +1 -0
  152. package/dist/install/installer.js +411 -0
  153. package/dist/install/installer.js.map +1 -0
  154. package/dist/install/launchd-backend.js +261 -0
  155. package/dist/install/launchd-backend.js.map +1 -0
  156. package/dist/install/launcher.js +224 -0
  157. package/dist/install/launcher.js.map +1 -0
  158. package/dist/install/markers.js +14 -0
  159. package/dist/install/markers.js.map +1 -0
  160. package/dist/install/platform.js +29 -0
  161. package/dist/install/platform.js.map +1 -0
  162. package/dist/install/present-tools.js +16 -0
  163. package/dist/install/present-tools.js.map +1 -0
  164. package/dist/install/self-update.js +120 -0
  165. package/dist/install/self-update.js.map +1 -0
  166. package/dist/install/service-backend.js +36 -0
  167. package/dist/install/service-backend.js.map +1 -0
  168. package/dist/install/service-manifest.js +36 -0
  169. package/dist/install/service-manifest.js.map +1 -0
  170. package/dist/install/status.js +58 -0
  171. package/dist/install/status.js.map +1 -0
  172. package/dist/install/systemd-backend.js +225 -0
  173. package/dist/install/systemd-backend.js.map +1 -0
  174. package/dist/markers.js +5 -0
  175. package/dist/markers.js.map +1 -0
  176. package/dist/mcp/installer.js +131 -0
  177. package/dist/mcp/installer.js.map +1 -0
  178. package/dist/mcp/server.js +81 -0
  179. package/dist/mcp/server.js.map +1 -0
  180. package/dist/mcp/tools/get-context.js +3 -0
  181. package/dist/mcp/tools/get-context.js.map +1 -0
  182. package/dist/mcp/tools/list-projects.js +3 -0
  183. package/dist/mcp/tools/list-projects.js.map +1 -0
  184. package/dist/mcp/tools/save-memory.js +3 -0
  185. package/dist/mcp/tools/save-memory.js.map +1 -0
  186. package/dist/mcp/tools.js +288 -0
  187. package/dist/mcp/tools.js.map +1 -0
  188. package/dist/rendering/raw-turn-renderer.js +85 -0
  189. package/dist/rendering/raw-turn-renderer.js.map +1 -0
  190. package/dist/security/file-permissions.js +15 -0
  191. package/dist/security/file-permissions.js.map +1 -0
  192. package/dist/security/provider-transcript.js +74 -0
  193. package/dist/security/provider-transcript.js.map +1 -0
  194. package/dist/security/sanitize.js +210 -0
  195. package/dist/security/sanitize.js.map +1 -0
  196. package/dist/security/self-ingestion.js +47 -0
  197. package/dist/security/self-ingestion.js.map +1 -0
  198. package/dist/security/sentinel.js +19 -0
  199. package/dist/security/sentinel.js.map +1 -0
  200. package/dist/security/subprocess-allowlist.js +421 -0
  201. package/dist/security/subprocess-allowlist.js.map +1 -0
  202. package/dist/serving/instructions.js +33 -0
  203. package/dist/serving/instructions.js.map +1 -0
  204. package/dist/serving/lexical-recall.js +249 -0
  205. package/dist/serving/lexical-recall.js.map +1 -0
  206. package/dist/serving/session-reader.js +302 -0
  207. package/dist/serving/session-reader.js.map +1 -0
  208. package/dist/storage/backfill-runner.js +34 -0
  209. package/dist/storage/backfill-runner.js.map +1 -0
  210. package/dist/storage/backup.js +46 -0
  211. package/dist/storage/backup.js.map +1 -0
  212. package/dist/storage/candidate-validator.js +77 -0
  213. package/dist/storage/candidate-validator.js.map +1 -0
  214. package/dist/storage/consent-store.js +198 -0
  215. package/dist/storage/consent-store.js.map +1 -0
  216. package/dist/storage/custom-title-backfill.js +68 -0
  217. package/dist/storage/custom-title-backfill.js.map +1 -0
  218. package/dist/storage/db.js +312 -0
  219. package/dist/storage/db.js.map +1 -0
  220. package/dist/storage/external-agent-import-purge.js +182 -0
  221. package/dist/storage/external-agent-import-purge.js.map +1 -0
  222. package/dist/storage/first-prompt-search-backfill.js +76 -0
  223. package/dist/storage/first-prompt-search-backfill.js.map +1 -0
  224. package/dist/storage/first-prompt-search.js +7 -0
  225. package/dist/storage/first-prompt-search.js.map +1 -0
  226. package/dist/storage/injection-store.js +37 -0
  227. package/dist/storage/injection-store.js.map +1 -0
  228. package/dist/storage/memory-store.js +428 -0
  229. package/dist/storage/memory-store.js.map +1 -0
  230. package/dist/storage/project-resolver.js +294 -0
  231. package/dist/storage/project-resolver.js.map +1 -0
  232. package/dist/storage/project-store.js +104 -0
  233. package/dist/storage/project-store.js.map +1 -0
  234. package/dist/storage/rendered-chars-backfill.js +85 -0
  235. package/dist/storage/rendered-chars-backfill.js.map +1 -0
  236. package/dist/storage/resegmentation.js +582 -0
  237. package/dist/storage/resegmentation.js.map +1 -0
  238. package/dist/storage/rollup-store.js +246 -0
  239. package/dist/storage/rollup-store.js.map +1 -0
  240. package/dist/storage/root-commit-backfill.js +32 -0
  241. package/dist/storage/root-commit-backfill.js.map +1 -0
  242. package/dist/storage/sanitize-backfill.js +184 -0
  243. package/dist/storage/sanitize-backfill.js.map +1 -0
  244. package/dist/storage/segmentation.js +87 -0
  245. package/dist/storage/segmentation.js.map +1 -0
  246. package/dist/storage/session-fields-backfill.js +196 -0
  247. package/dist/storage/session-fields-backfill.js.map +1 -0
  248. package/dist/storage/session-read-model.js +130 -0
  249. package/dist/storage/session-read-model.js.map +1 -0
  250. package/dist/storage/session-store.js +200 -0
  251. package/dist/storage/session-store.js.map +1 -0
  252. package/dist/storage/session-title-backfill.js +75 -0
  253. package/dist/storage/session-title-backfill.js.map +1 -0
  254. package/dist/storage/session-title.js +71 -0
  255. package/dist/storage/session-title.js.map +1 -0
  256. package/dist/storage/shown-session-list-store.js +33 -0
  257. package/dist/storage/shown-session-list-store.js.map +1 -0
  258. package/dist/storage/stats.js +30 -0
  259. package/dist/storage/stats.js.map +1 -0
  260. package/dist/storage/turn-store.js +202 -0
  261. package/dist/storage/turn-store.js.map +1 -0
  262. package/dist/storage/ulid.js +17 -0
  263. package/dist/storage/ulid.js.map +1 -0
  264. package/dist/summarizer/anthropic-call.js +76 -0
  265. package/dist/summarizer/anthropic-call.js.map +1 -0
  266. package/dist/summarizer/call-log.js +120 -0
  267. package/dist/summarizer/call-log.js.map +1 -0
  268. package/dist/summarizer/haiku-provider.js +136 -0
  269. package/dist/summarizer/haiku-provider.js.map +1 -0
  270. package/dist/summarizer/pricing.js +8 -0
  271. package/dist/summarizer/pricing.js.map +1 -0
  272. package/dist/summarizer/prompt-cache.js +6 -0
  273. package/dist/summarizer/prompt-cache.js.map +1 -0
  274. package/dist/summarizer/prompt.js +47 -0
  275. package/dist/summarizer/prompt.js.map +1 -0
  276. package/dist/summarizer/provider-config.js +25 -0
  277. package/dist/summarizer/provider-config.js.map +1 -0
  278. package/dist/summarizer/rollup-prompt.js +225 -0
  279. package/dist/summarizer/rollup-prompt.js.map +1 -0
  280. package/dist/summarizer/rollup-provider.js +224 -0
  281. package/dist/summarizer/rollup-provider.js.map +1 -0
  282. package/dist/types/index.js +10 -0
  283. package/dist/types/index.js.map +1 -0
  284. package/dist/util/error.js +5 -0
  285. package/dist/util/error.js.map +1 -0
  286. package/dist/util/fs.js +97 -0
  287. package/dist/util/fs.js.map +1 -0
  288. package/dist/util/relative-time.js +12 -0
  289. package/dist/util/relative-time.js.map +1 -0
  290. package/package.json +81 -0
@@ -0,0 +1,85 @@
1
+ // Raw-turn serving filter shared by ingestion (rendered_chars) and the
2
+ // serving surface. It deliberately accepts already-normalized ParsedTurn
3
+ // values: adapter JSONL parsing remains the only code that knows transcript
4
+ // formats, and this module never reads a transcript or starts a subprocess.
5
+ import { escapeShellSyntax } from '../security/sanitize.js';
6
+ export const RAW_TURN_SEPARATOR = '\n\n---\n\n';
7
+ export function omissionMarker(omitted, shown, total) {
8
+ return `+${omitted} older rendered turns in this episode omitted (${shown} shown of ${total}).`;
9
+ }
10
+ // Codex's internal memory markup is injected content, not part of the human
11
+ // conversation. Non-greedy matching keeps adjacent blocks independent.
12
+ const MEMORY_CITATION_BLOCK = /<oai-mem-citation>[\s\S]*?<\/oai-mem-citation>/g;
13
+ // This is intentionally categorical, never length-based. The pause is explicit
14
+ // in the user message and the turn did no tool work; the acknowledgement may
15
+ // include a sentence about deferred work, which must not defeat the filter.
16
+ const EXPLICIT_PAUSE_REQUEST = /\b(?:no\s+(?:hagas?|hacer|toques?)\s+nada(?:\s+de\s+momento)?|do(?:n't| not)\s+(?:do|change|touch)\s+anything|hold\s+off|pause\s+here|wait\s+for\s+now)\b/i;
17
+ function withoutMemoryCitations(text) {
18
+ return text.replace(MEMORY_CITATION_BLOCK, '');
19
+ }
20
+ function isExplicitPauseTurn(turn, userMessage) {
21
+ return turn.toolCalls.length === 0 && EXPLICIT_PAUSE_REQUEST.test(userMessage);
22
+ }
23
+ /** Reports whether a parsed turn survives the raw-serving filter without rendering its body. */
24
+ export function isRenderableRawTurn(turn) {
25
+ return !isExplicitPauseTurn(turn, withoutMemoryCitations(turn.userMessage).trim());
26
+ }
27
+ function renderToolCalls(turn) {
28
+ const pathBearing = turn.toolCalls.filter((call) => call.filePaths.length > 0);
29
+ const withoutPaths = turn.toolCalls.length - pathBearing.length;
30
+ if (pathBearing.length === 0 && withoutPaths === 0) {
31
+ return undefined;
32
+ }
33
+ const lines = pathBearing.flatMap((call) => [
34
+ `- \`${escapeShellSyntax(call.name)}\``,
35
+ ...call.filePaths.map((filePath) => ` - \`${escapeShellSyntax(filePath)}\``),
36
+ ]);
37
+ if (withoutPaths > 0) {
38
+ lines.push(`- ${withoutPaths} tool call${withoutPaths === 1 ? '' : 's'} without file paths omitted`);
39
+ }
40
+ return `**Tool calls**\n\n${lines.join('\n')}`;
41
+ }
42
+ /** Renders one kept turn, or null for an explicit no-tool-call pause. */
43
+ export function renderRawTurn(turn, renderedTurnNumber = turn.turnIndex) {
44
+ // Adapters preserve transcript whitespace. It is not conversation content
45
+ // at the outer edges, and retaining it would make the stored count differ
46
+ // from the reviewed golden artifacts.
47
+ const userMessage = withoutMemoryCitations(turn.userMessage).trim();
48
+ const assistantText = withoutMemoryCitations(turn.assistantText).trim();
49
+ if (isExplicitPauseTurn(turn, userMessage)) {
50
+ return null;
51
+ }
52
+ return [
53
+ `## Turn ${renderedTurnNumber}`,
54
+ `**User prompt**\n\n${escapeShellSyntax(userMessage)}`,
55
+ `**Assistant response**\n\n${escapeShellSyntax(assistantText)}`,
56
+ renderToolCalls(turn),
57
+ ]
58
+ .filter((section) => section !== undefined)
59
+ .join('\n\n');
60
+ }
61
+ /** Produces the rendered sequence once so every consumer shares its filter. */
62
+ export function renderableRawTurns(turns, renderedTurnOffset = 0) {
63
+ const rendered = [];
64
+ for (const turn of turns) {
65
+ const text = renderRawTurn(turn, renderedTurnOffset + rendered.length + 1);
66
+ if (text !== null) {
67
+ rendered.push(text);
68
+ }
69
+ }
70
+ return rendered;
71
+ }
72
+ /** Renders a segment's turns with the exact inter-turn bytes served to consumers. */
73
+ export function renderRawTurns(turns) {
74
+ const rendered = renderableRawTurns(turns);
75
+ return rendered.length === 0 ? '' : `${rendered.join(RAW_TURN_SEPARATOR)}\n`;
76
+ }
77
+ /** Stored turn-count input: the exact number of turns renderRawTurns keeps. */
78
+ export function renderedTurns(turns) {
79
+ return renderableRawTurns(turns).length;
80
+ }
81
+ /** Stored token-estimate input: the exact character count of renderRawTurns(). */
82
+ export function renderedChars(turns) {
83
+ return renderRawTurns(turns).length;
84
+ }
85
+ //# sourceMappingURL=raw-turn-renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"raw-turn-renderer.js","sourceRoot":"","sources":["../../src/rendering/raw-turn-renderer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ // ~/.elepha holds memory synthesized from session transcripts - DB, WAL,
2
+ // backups, logs. Created at the process umask (typically 0755/0644), it's
3
+ // world-readable by default on a shared machine. Pure fs, no subprocess -
4
+ // this module has nothing to do with Security Rule 2's allowlist.
5
+ import { chmodSync, existsSync } from 'node:fs';
6
+ import { PRIVATE_DIR_MODE, PRIVATE_FILE_MODE } from '../config/constants.js';
7
+ export function hardenDir(dir) {
8
+ chmodSync(dir, PRIVATE_DIR_MODE);
9
+ }
10
+ export function hardenFile(file) {
11
+ if (existsSync(file)) {
12
+ chmodSync(file, PRIVATE_FILE_MODE);
13
+ }
14
+ }
15
+ //# sourceMappingURL=file-permissions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-permissions.js","sourceRoot":"","sources":["../../src/security/file-permissions.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,0EAA0E;AAC1E,0EAA0E;AAC1E,kEAAkE;AAElE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAE7E,MAAM,UAAU,SAAS,CAAC,GAAW;IACjC,SAAS,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,IAAY;IACnC,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACnB,SAAS,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC;IACvC,CAAC;AACL,CAAC"}
@@ -0,0 +1,74 @@
1
+ import { O_NOFOLLOW, O_RDONLY } from 'node:constants';
2
+ import { open as fsOpen, realpath as fsRealpath, stat as fsStat } from 'node:fs/promises';
3
+ import { isWithinProviderStore } from '../config/paths.js';
4
+ function failureReason(error) {
5
+ return error.code === 'ENOENT' ? 'transcript_missing' : 'transcript_unreadable';
6
+ }
7
+ /**
8
+ * Opens a provider transcript and proves that the opened file is the same
9
+ * regular file reached by a fresh, contained physical path resolution.
10
+ */
11
+ export async function openProviderTranscript(tool, sourcePath, dependencies = {}) {
12
+ const open = dependencies.open ?? fsOpen;
13
+ const realpath = dependencies.realpath ?? fsRealpath;
14
+ const stat = dependencies.stat ?? fsStat;
15
+ let handle;
16
+ try {
17
+ handle = await open(sourcePath, O_RDONLY | O_NOFOLLOW);
18
+ }
19
+ catch (error) {
20
+ if (error.code !== 'ELOOP') {
21
+ return { reason: failureReason(error) };
22
+ }
23
+ // Deliberately relaxes CA-10's leaf-symlink strictness to preserve established same-store symlink ingestion.
24
+ const initialResolvedPath = await realpath(sourcePath).catch(() => undefined);
25
+ if (initialResolvedPath === undefined) {
26
+ return { reason: 'transcript_unreadable' };
27
+ }
28
+ if (!isWithinProviderStore(tool, initialResolvedPath)) {
29
+ return { reason: 'transcript_outside_store' };
30
+ }
31
+ try {
32
+ handle = await open(initialResolvedPath, O_RDONLY | O_NOFOLLOW);
33
+ }
34
+ catch (resolvedError) {
35
+ return { reason: failureReason(resolvedError) };
36
+ }
37
+ }
38
+ const fail = async (reason) => {
39
+ await handle.close().catch(() => undefined);
40
+ return { reason };
41
+ };
42
+ let openedStat;
43
+ try {
44
+ openedStat = await handle.stat();
45
+ }
46
+ catch (error) {
47
+ return fail(failureReason(error));
48
+ }
49
+ if (!openedStat.isFile()) {
50
+ return fail('transcript_unreadable');
51
+ }
52
+ let resolvedPath;
53
+ try {
54
+ resolvedPath = await realpath(sourcePath);
55
+ }
56
+ catch (error) {
57
+ return fail(failureReason(error));
58
+ }
59
+ if (!isWithinProviderStore(tool, resolvedPath)) {
60
+ return fail('transcript_outside_store');
61
+ }
62
+ let resolvedStat;
63
+ try {
64
+ resolvedStat = await stat(resolvedPath);
65
+ }
66
+ catch (error) {
67
+ return fail(failureReason(error));
68
+ }
69
+ if (resolvedStat.dev !== openedStat.dev || resolvedStat.ino !== openedStat.ino) {
70
+ return fail('transcript_unreadable');
71
+ }
72
+ return { handle, resolvedPath, stat: openedStat };
73
+ }
74
+ //# sourceMappingURL=provider-transcript.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-transcript.js","sourceRoot":"","sources":["../../src/security/provider-transcript.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,210 @@
1
+ // Security Rule 3 - neutralize shell-active syntax in summarizer output.
2
+ //
3
+ // Everything the store holds is eventually served to a coding AI that writes
4
+ // and executes shell commands, so every stored field is treated as if it will
5
+ // be pasted into a terminal. Sanitization happens ON WRITE, never on read: the
6
+ // store must never hold executable syntax in the first place.
7
+ //
8
+ // Two policies:
9
+ //
10
+ // strip - display strings (title, summary, pending_items). The
11
+ // metacharacter carries no information there, so removing it
12
+ // loses nothing.
13
+ // escape - decisions[].what / decisions[].why, pinned notes, digest output.
14
+ // A decision may legitimately need to reference the syntax it
15
+ // ruled out ("rejected `$(date)` in the template"); escaping
16
+ // preserves that, stripping destroys it.
17
+ //
18
+ // Plus, unconditionally in both: ANSI escape sequences and C0 control
19
+ // characters. A decision string ends up printed to a terminal, so
20
+ // escape-sequence injection is the same class of bug and costs one regex.
21
+ //
22
+ // IDEMPOTENCY IS A REQUIREMENT, not a nicety. The backfill re-runs over rows
23
+ // that may already be clean, the choke points re-run on every rebuild, and the
24
+ // post-backfill verification asserts detectShellSyntax() is false store-wide.
25
+ // Every transform below is written so f(f(x)) === f(x), and each has a test.
26
+ /** Two-character openers escaped by breaking the pair from the inside: `$(` -> `$\(`. */
27
+ const PAIRED_OPENERS = ['$(', '${', '>(', '<('];
28
+ /**
29
+ * Heredoc. Escaped the same way (`<<` -> `<\<`), which also defuses `<<<`:
30
+ * `<\<<` contains no unescaped `<<` pair.
31
+ */
32
+ const HEREDOC = '<<';
33
+ /**
34
+ * Command chaining is only shell-active at the start of a command, so the test
35
+ * is line-leading rather than "anywhere" - a `;` mid-sentence is punctuation
36
+ * and mangling it would corrupt ordinary prose. Escaped by PREFIXING a
37
+ * backslash to the whole run (`&&` -> `\&&`), never infixing: `&\&` would
38
+ * still start the line with `&` and the transform would not be idempotent.
39
+ */
40
+ const LINE_LEADING_CHAIN_RE = /^([ \t]*)(\|\||&&|[|;&])/gm;
41
+ // CSI (`\x1b[...`), OSC (`\x1b]...` terminated by BEL or ST), and any other
42
+ // two-character escape. Matched longest-first so a CSI is never chewed up by
43
+ // the catch-all.
44
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: neutralizing control characters is the entire point of this module
45
+ const ANSI_RE = /\x1b\[[0-?]*[ -/]*[@-~]|\x1b][^\x07\x1b]*(?:\x07|\x1b\\)?|\x1b[@-_]?/g;
46
+ // C0 controls and DEL, keeping \n and \t: those are legitimate in a summary,
47
+ // and \r is not (carriage-return line-overwrite hides text from a reader who
48
+ // only sees the rendered terminal output).
49
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: neutralizing control characters is the entire point of this module
50
+ const CONTROL_RE = /[\x00-\x08\x0b-\x1f\x7f]/g;
51
+ /** Number of backslashes immediately preceding `index`. Even means the character at `index` is unescaped, and therefore shell-active. */
52
+ function precedingBackslashes(s, index) {
53
+ let n = 0;
54
+ let i = index - 1;
55
+ while (i >= 0 && s[i] === '\\') {
56
+ n++;
57
+ i--;
58
+ }
59
+ return n;
60
+ }
61
+ function isActive(s, index) {
62
+ return precedingBackslashes(s, index) % 2 === 0;
63
+ }
64
+ function stripControls(s) {
65
+ return s.replace(ANSI_RE, '').replace(CONTROL_RE, '');
66
+ }
67
+ /**
68
+ * Finds the closer matching a paired opener at `openIndex`, honouring nesting,
69
+ * or -1 when the text has no closer at all (unbalanced input is common in
70
+ * prose and must not throw).
71
+ */
72
+ function findMatchingCloser(s, openIndex, open, close) {
73
+ let depth = 0;
74
+ for (let i = openIndex + 1; i < s.length; i++) {
75
+ if (s[i] === open) {
76
+ depth++;
77
+ }
78
+ else if (s[i] === close) {
79
+ if (depth === 0) {
80
+ return i;
81
+ }
82
+ depth--;
83
+ }
84
+ }
85
+ return -1;
86
+ }
87
+ /**
88
+ * Display-string policy: remove the metacharacter, keep the words.
89
+ * `` `foo` `` -> `foo`, `$(date)` -> `date`, `${VAR}` -> `VAR`.
90
+ *
91
+ * Runs to a fixed point (bounded) because removing one layer can expose
92
+ * another: `$($(date))` needs two passes to become `date`.
93
+ */
94
+ export function stripShellSyntax(input) {
95
+ let s = stripControls(input);
96
+ for (let pass = 0; pass < 8; pass++) {
97
+ const before = s;
98
+ // Paired openers, innermost-safe: remove the opener and its matching
99
+ // closer together, so the closer is not left orphaned.
100
+ let i = 0;
101
+ while (i < s.length - 1) {
102
+ const two = s.slice(i, i + 2);
103
+ const opener = PAIRED_OPENERS.find((o) => o === two);
104
+ if (!opener) {
105
+ i++;
106
+ continue;
107
+ }
108
+ const close = opener[1] === '(' ? ')' : '}';
109
+ const closeIndex = findMatchingCloser(s, i + 1, opener[1], close);
110
+ s = closeIndex === -1 ? s.slice(0, i) + s.slice(i + 2) : s.slice(0, i) + s.slice(i + 2, closeIndex) + s.slice(closeIndex + 1);
111
+ // Do not advance: the text that shifted into `i` may itself be an opener.
112
+ }
113
+ s = s.split('`').join('');
114
+ s = s.split(HEREDOC).join('');
115
+ s = s.replace(LINE_LEADING_CHAIN_RE, '$1');
116
+ if (s === before) {
117
+ break;
118
+ }
119
+ }
120
+ return s;
121
+ }
122
+ /**
123
+ * Information-preserving policy: break the token with a backslash. The result
124
+ * is readable, and inert in both unquoted and double-quoted shell contexts.
125
+ * `` ` `` -> `` \` ``, `$(` -> `$\(`, `${` -> `$\{`, `<<` -> `<\<`.
126
+ */
127
+ export function escapeShellSyntax(input) {
128
+ let s = stripControls(input);
129
+ // Paired openers and heredoc: infix a backslash. After this the literal
130
+ // two-character token no longer occurs, so a second run is a no-op.
131
+ let out = '';
132
+ for (let i = 0; i < s.length;) {
133
+ const two = s.slice(i, i + 2);
134
+ if (PAIRED_OPENERS.includes(two) || two === HEREDOC) {
135
+ out += `${two[0]}\\${two[1]}`;
136
+ i += 2;
137
+ continue;
138
+ }
139
+ out += s[i];
140
+ i++;
141
+ }
142
+ s = out;
143
+ // Backtick is a single character, so it survives escaping as a literal and
144
+ // needs the parity check to avoid `\\` growing on every pass.
145
+ out = '';
146
+ for (let i = 0; i < s.length; i++) {
147
+ if (s[i] === '`' && isActive(s, i)) {
148
+ out += '\\`';
149
+ }
150
+ else {
151
+ out += s[i];
152
+ }
153
+ }
154
+ s = out;
155
+ // Prefix, not infix - see LINE_LEADING_CHAIN_RE.
156
+ s = s.replace(LINE_LEADING_CHAIN_RE, '$1\\$2');
157
+ return s;
158
+ }
159
+ /**
160
+ * True when the text still carries shell-active syntax. Used by the backfill
161
+ * preview, by the post-backfill verification, and by the read-time assertion.
162
+ *
163
+ * Deliberately agrees with both transforms above: anything either of them
164
+ * would change (other than pure control-character noise) reports true here,
165
+ * and their output reports false. That equivalence is what makes "the store
166
+ * never holds executable syntax" a checkable invariant rather than a comment.
167
+ */
168
+ export function detectShellSyntax(input) {
169
+ if (ANSI_RE.test(input) || CONTROL_RE.test(input)) {
170
+ ANSI_RE.lastIndex = 0;
171
+ CONTROL_RE.lastIndex = 0;
172
+ return true;
173
+ }
174
+ ANSI_RE.lastIndex = 0;
175
+ CONTROL_RE.lastIndex = 0;
176
+ for (let i = 0; i < input.length; i++) {
177
+ if (input[i] === '`' && isActive(input, i)) {
178
+ return true;
179
+ }
180
+ const two = input.slice(i, i + 2);
181
+ if (PAIRED_OPENERS.includes(two) || two === HEREDOC) {
182
+ return true;
183
+ }
184
+ }
185
+ LINE_LEADING_CHAIN_RE.lastIndex = 0;
186
+ const hit = LINE_LEADING_CHAIN_RE.test(input);
187
+ LINE_LEADING_CHAIN_RE.lastIndex = 0;
188
+ return hit;
189
+ }
190
+ /**
191
+ * Read-time ASSERTION, not read-time sanitization.
192
+ *
193
+ * "Sanitization happens on write, never on read" is about where the store
194
+ * gets cleaned. It is not an argument for never checking: a store
195
+ * invariant that is never verified is documentation drift with extra steps,
196
+ * and this codebase has paid for that failure mode repeatedly. A hit here
197
+ * means a write path was missed - which is a bug report, not a routine event,
198
+ * so it logs loudly and then sanitizes defensively rather than serving
199
+ * executable syntax to a model that runs shell commands.
200
+ *
201
+ * Returns the text to actually serve, and whether it had to be repaired.
202
+ */
203
+ export function assertNoShellSyntax(text, context, log = console.error) {
204
+ if (!detectShellSyntax(text)) {
205
+ return { text, repaired: false };
206
+ }
207
+ log(`[elepha] WARNING: shell-active syntax reached the read path from ${context}; a write-time sanitizer was missed. Repairing.`);
208
+ return { text: escapeShellSyntax(text), repaired: true };
209
+ }
210
+ //# sourceMappingURL=sanitize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitize.js","sourceRoot":"","sources":["../../src/security/sanitize.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,EAAE;AACF,6EAA6E;AAC7E,8EAA8E;AAC9E,+EAA+E;AAC/E,8DAA8D;AAC9D,EAAE;AACF,gBAAgB;AAChB,EAAE;AACF,mEAAmE;AACnE,yEAAyE;AACzE,6BAA6B;AAC7B,+EAA+E;AAC/E,0EAA0E;AAC1E,yEAAyE;AACzE,qDAAqD;AACrD,EAAE;AACF,sEAAsE;AACtE,kEAAkE;AAClE,0EAA0E;AAC1E,EAAE;AACF,6EAA6E;AAC7E,+EAA+E;AAC/E,8EAA8E;AAC9E,6EAA6E;AAE7E,yFAAyF;AACzF,MAAM,cAAc,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAU,CAAC;AAEzD;;;GAGG;AACH,MAAM,OAAO,GAAG,IAAI,CAAC;AAErB;;;;;;GAMG;AACH,MAAM,qBAAqB,GAAG,4BAA4B,CAAC;AAE3D,4EAA4E;AAC5E,6EAA6E;AAC7E,iBAAiB;AACjB,8HAA8H;AAC9H,MAAM,OAAO,GAAG,uEAAuE,CAAC;AAExF,6EAA6E;AAC7E,6EAA6E;AAC7E,2CAA2C;AAC3C,8HAA8H;AAC9H,MAAM,UAAU,GAAG,2BAA2B,CAAC;AAE/C,yIAAyI;AACzI,SAAS,oBAAoB,CAAC,CAAS,EAAE,KAAa;IAClD,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,IAAI,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC;IAClB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC7B,CAAC,EAAE,CAAC;QACJ,CAAC,EAAE,CAAC;IACR,CAAC;IACD,OAAO,CAAC,CAAC;AACb,CAAC;AAED,SAAS,QAAQ,CAAC,CAAS,EAAE,KAAa;IACtC,OAAO,oBAAoB,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AACpD,CAAC;AAED,SAAS,aAAa,CAAC,CAAS;IAC5B,OAAO,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;AAC1D,CAAC;AAED;;;;GAIG;AACH,SAAS,kBAAkB,CAAC,CAAS,EAAE,SAAiB,EAAE,IAAY,EAAE,KAAa;IACjF,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,IAAI,CAAC,GAAG,SAAS,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5C,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YAChB,KAAK,EAAE,CAAC;QACZ,CAAC;aAAM,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,EAAE,CAAC;YACxB,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;gBACd,OAAO,CAAC,CAAC;YACb,CAAC;YACD,KAAK,EAAE,CAAC;QACZ,CAAC;IACL,CAAC;IACD,OAAO,CAAC,CAAC,CAAC;AACd,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC1C,IAAI,CAAC,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAE7B,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC;QAClC,MAAM,MAAM,GAAG,CAAC,CAAC;QAEjB,qEAAqE;QACrE,uDAAuD;QACvD,IAAI,CAAC,GAAG,CAAC,CAAC;QACV,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtB,MAAM,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;YAC9B,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC;YACrD,IAAI,CAAC,MAAM,EAAE,CAAC;gBACV,CAAC,EAAE,CAAC;gBACJ,SAAS;YACb,CAAC;YACD,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;YAC5C,MAAM,UAAU,GAAG,kBAAkB,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;YAClE,CAAC,GAAG,UAAU,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC;YAC9H,0EAA0E;QAC9E,CAAC;QAED,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC1B,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC9B,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,qBAAqB,EAAE,IAAI,CAAC,CAAC;QAE3C,IAAI,CAAC,KAAK,MAAM,EAAE,CAAC;YACf,MAAM;QACV,CAAC;IACL,CAAC;IACD,OAAO,CAAC,CAAC;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAa;IAC3C,IAAI,CAAC,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAE7B,wEAAwE;IACxE,oEAAoE;IACpE,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,GAAI,CAAC;QAC7B,MAAM,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QAC9B,IAAK,cAAoC,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,KAAK,OAAO,EAAE,CAAC;YACzE,GAAG,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9B,CAAC,IAAI,CAAC,CAAC;YACP,SAAS;QACb,CAAC;QACD,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QACZ,CAAC,EAAE,CAAC;IACR,CAAC;IACD,CAAC,GAAG,GAAG,CAAC;IAER,2EAA2E;IAC3E,8DAA8D;IAC9D,GAAG,GAAG,EAAE,CAAC;IACT,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAChC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;YACjC,GAAG,IAAI,KAAK,CAAC;QACjB,CAAC;aAAM,CAAC;YACJ,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QAChB,CAAC;IACL,CAAC;IACD,CAAC,GAAG,GAAG,CAAC;IAER,iDAAiD;IACjD,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,qBAAqB,EAAE,QAAQ,CAAC,CAAC;IAE/C,OAAO,CAAC,CAAC;AACb,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAa;IAC3C,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAChD,OAAO,CAAC,SAAS,GAAG,CAAC,CAAC;QACtB,UAAU,CAAC,SAAS,GAAG,CAAC,CAAC;QACzB,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,OAAO,CAAC,SAAS,GAAG,CAAC,CAAC;IACtB,UAAU,CAAC,SAAS,GAAG,CAAC,CAAC;IAEzB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACpC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC;YACzC,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QAClC,IAAK,cAAoC,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,KAAK,OAAO,EAAE,CAAC;YACzE,OAAO,IAAI,CAAC;QAChB,CAAC;IACL,CAAC;IAED,qBAAqB,CAAC,SAAS,GAAG,CAAC,CAAC;IACpC,MAAM,GAAG,GAAG,qBAAqB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC9C,qBAAqB,CAAC,SAAS,GAAG,CAAC,CAAC;IACpC,OAAO,GAAG,CAAC;AACf,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,mBAAmB,CAC/B,IAAY,EACZ,OAAe,EACf,GAAG,GAA0B,OAAO,CAAC,KAAK;IAK1C,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3B,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IACrC,CAAC;IACD,GAAG,CAAC,oEAAoE,OAAO,iDAAiD,CAAC,CAAC;IAClI,OAAO,EAAE,IAAI,EAAE,iBAAiB,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AAC7D,CAAC"}
@@ -0,0 +1,47 @@
1
+ /** Complete textual surface used by both Rule 4 guards. */
2
+ export function turnText(turn) {
3
+ return [turn.userMessage, turn.assistantText, ...turn.toolCalls.map((call) => JSON.stringify(call))].join('\n');
4
+ }
5
+ export function normalizeForNearVerbatim(text) {
6
+ return text
7
+ .toLowerCase()
8
+ .replace(/[^\p{L}\p{N}\s]+/gu, '')
9
+ .replace(/\s+/g, ' ')
10
+ .trim();
11
+ }
12
+ function shingles(text, size = 8) {
13
+ if (text.length < size) {
14
+ return new Set();
15
+ }
16
+ const out = new Set();
17
+ for (let index = 0; index <= text.length - size; index++) {
18
+ out.add(text.slice(index, index + size));
19
+ }
20
+ return out;
21
+ }
22
+ /**
23
+ * Rule 4 quote-back threshold: an exact long injected line, or at least 60%
24
+ * of the body's distinct normalized 8-grams. Short/empty bodies cannot match
25
+ * through shingles because their denominator would not be meaningful.
26
+ */
27
+ export function isNearVerbatim(turn, injection) {
28
+ const normalizedTurn = normalizeForNearVerbatim(turn);
29
+ const normalizedInjection = normalizeForNearVerbatim(injection);
30
+ if (normalizedTurn === '' || normalizedInjection === '') {
31
+ return false;
32
+ }
33
+ for (const line of injection.split(/\r?\n/)) {
34
+ const normalizedLine = normalizeForNearVerbatim(line);
35
+ if (normalizedLine.length >= 40 && normalizedTurn.includes(normalizedLine)) {
36
+ return true;
37
+ }
38
+ }
39
+ const injectionShingles = shingles(normalizedInjection);
40
+ if (injectionShingles.size === 0) {
41
+ return false;
42
+ }
43
+ const turnShingles = shingles(normalizedTurn);
44
+ const present = [...injectionShingles].filter((shingle) => turnShingles.has(shingle)).length;
45
+ return present / injectionShingles.size >= 0.6;
46
+ }
47
+ //# sourceMappingURL=self-ingestion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"self-ingestion.js","sourceRoot":"","sources":["../../src/security/self-ingestion.ts"],"names":[],"mappings":"AAEA,2DAA2D;AAC3D,MAAM,UAAU,QAAQ,CAAC,IAAqE;IAC1F,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,aAAa,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACpH,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,IAAY;IACjD,OAAO,IAAI;SACN,WAAW,EAAE;SACb,OAAO,CAAC,oBAAoB,EAAE,EAAE,CAAC;SACjC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;SACpB,IAAI,EAAE,CAAC;AAChB,CAAC;AAED,SAAS,QAAQ,CAAC,IAAY,EAAE,IAAI,GAAG,CAAC;IACpC,IAAI,IAAI,CAAC,MAAM,GAAG,IAAI,EAAE,CAAC;QACrB,OAAO,IAAI,GAAG,EAAE,CAAC;IACrB,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;IAC9B,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,IAAI,IAAI,CAAC,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACvD,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC;IAC7C,CAAC;IACD,OAAO,GAAG,CAAC;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,SAAiB;IAC1D,MAAM,cAAc,GAAG,wBAAwB,CAAC,IAAI,CAAC,CAAC;IACtD,MAAM,mBAAmB,GAAG,wBAAwB,CAAC,SAAS,CAAC,CAAC;IAChE,IAAI,cAAc,KAAK,EAAE,IAAI,mBAAmB,KAAK,EAAE,EAAE,CAAC;QACtD,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QAC1C,MAAM,cAAc,GAAG,wBAAwB,CAAC,IAAI,CAAC,CAAC;QACtD,IAAI,cAAc,CAAC,MAAM,IAAI,EAAE,IAAI,cAAc,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;YACzE,OAAO,IAAI,CAAC;QAChB,CAAC;IACL,CAAC;IAED,MAAM,iBAAiB,GAAG,QAAQ,CAAC,mBAAmB,CAAC,CAAC;IACxD,IAAI,iBAAiB,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QAC/B,OAAO,KAAK,CAAC;IACjB,CAAC;IACD,MAAM,YAAY,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAC;IAC9C,MAAM,OAAO,GAAG,CAAC,GAAG,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC;IAC7F,OAAO,OAAO,GAAG,iBAAiB,CAAC,IAAI,IAAI,GAAG,CAAC;AACnD,CAAC"}
@@ -0,0 +1,19 @@
1
+ import { newUlid } from '../storage/ulid.js';
2
+ export const OPEN = '[[elepha:';
3
+ export const CLOSE = '[[/elepha]]';
4
+ export function open(kind, ulid) {
5
+ return `${OPEN}${kind}:${ulid}]]`;
6
+ }
7
+ /** Wraps an injected body in line-isolated, transcript-stable markers. */
8
+ export function wrap(kind, ulid, body) {
9
+ return `${open(kind, ulid)}\n${body}\n${CLOSE}`;
10
+ }
11
+ /** The same canonical ULID format used by elepha's other durable records. */
12
+ export function buildInjectionId() {
13
+ return newUlid();
14
+ }
15
+ /** Deliberately a literal prefix match: malformed markers fail closed. */
16
+ export function containsSentinel(text) {
17
+ return text.includes(OPEN);
18
+ }
19
+ //# sourceMappingURL=sentinel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sentinel.js","sourceRoot":"","sources":["../../src/security/sentinel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAI7C,MAAM,CAAC,MAAM,IAAI,GAAG,WAAW,CAAC;AAChC,MAAM,CAAC,MAAM,KAAK,GAAG,aAAa,CAAC;AAEnC,MAAM,UAAU,IAAI,CAAC,IAAmB,EAAE,IAAY;IAClD,OAAO,GAAG,IAAI,GAAG,IAAI,IAAI,IAAI,IAAI,CAAC;AACtC,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,IAAI,CAAC,IAAmB,EAAE,IAAY,EAAE,IAAY;IAChE,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,IAAI,KAAK,KAAK,EAAE,CAAC;AACpD,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,gBAAgB;IAC5B,OAAO,OAAO,EAAE,CAAC;AACrB,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,gBAAgB,CAAC,IAAY;IACzC,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AAC/B,CAAC"}