@riotprompt/riotprompt 0.0.21 → 1.0.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 (69) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/MIGRATION.md +235 -0
  3. package/README.md +2 -0
  4. package/SECURITY.md +132 -0
  5. package/dist/builder.js +6 -0
  6. package/dist/builder.js.map +1 -1
  7. package/dist/cli.js +481 -22
  8. package/dist/context-manager.js +1 -1
  9. package/dist/conversation-logger.d.ts +17 -1
  10. package/dist/conversation-logger.js +21 -17
  11. package/dist/conversation-logger.js.map +1 -1
  12. package/dist/conversation.js +1 -1
  13. package/dist/error-handling.d.ts +52 -0
  14. package/dist/error-handling.js +132 -0
  15. package/dist/error-handling.js.map +1 -0
  16. package/dist/formatter.js +1 -1
  17. package/dist/iteration-strategy.js +1 -1
  18. package/dist/loader.js +60 -12
  19. package/dist/loader.js.map +1 -1
  20. package/dist/logger.d.ts +52 -0
  21. package/dist/logger.js +114 -14
  22. package/dist/logger.js.map +1 -1
  23. package/dist/logging-config.d.ts +84 -0
  24. package/dist/logging-config.js +116 -0
  25. package/dist/logging-config.js.map +1 -0
  26. package/dist/message-builder.js +1 -1
  27. package/dist/model-config.js +1 -1
  28. package/dist/override.js +10 -4
  29. package/dist/override.js.map +1 -1
  30. package/dist/recipes.js +6 -0
  31. package/dist/recipes.js.map +1 -1
  32. package/dist/reflection.js +1 -1
  33. package/dist/riotprompt.d.ts +9 -0
  34. package/dist/riotprompt.js +8 -0
  35. package/dist/riotprompt.js.map +1 -1
  36. package/dist/security/audit-logger.d.ts +61 -0
  37. package/dist/security/audit-logger.js +281 -0
  38. package/dist/security/audit-logger.js.map +1 -0
  39. package/dist/security/cli-security.d.ts +143 -0
  40. package/dist/security/cli-security.js +302 -0
  41. package/dist/security/cli-security.js.map +1 -0
  42. package/dist/security/defaults.d.ts +31 -0
  43. package/dist/security/defaults.js +72 -0
  44. package/dist/security/defaults.js.map +1 -0
  45. package/dist/security/events.d.ts +8 -0
  46. package/dist/security/index.d.ts +27 -0
  47. package/dist/security/index.js +22 -0
  48. package/dist/security/index.js.map +1 -0
  49. package/dist/security/path-guard.d.ts +161 -0
  50. package/dist/security/path-guard.js +327 -0
  51. package/dist/security/path-guard.js.map +1 -0
  52. package/dist/security/rate-limiter.d.ts +117 -0
  53. package/dist/security/rate-limiter.js +165 -0
  54. package/dist/security/rate-limiter.js.map +1 -0
  55. package/dist/security/serialization-schemas.d.ts +183 -0
  56. package/dist/security/serialization-schemas.js +174 -0
  57. package/dist/security/serialization-schemas.js.map +1 -0
  58. package/dist/security/timeout-guard.d.ts +123 -0
  59. package/dist/security/timeout-guard.js +223 -0
  60. package/dist/security/timeout-guard.js.map +1 -0
  61. package/dist/security/types.d.ts +86 -0
  62. package/dist/security/types.js +80 -0
  63. package/dist/security/types.js.map +1 -0
  64. package/dist/token-budget.js +1 -1
  65. package/dist/tools.js +1 -1
  66. package/guide/index.md +2 -0
  67. package/guide/integration.md +1109 -0
  68. package/guide/security.md +237 -0
  69. package/package.json +17 -11
package/dist/cli.js CHANGED
@@ -3,14 +3,17 @@ import "dotenv/config";
3
3
  import { Command } from "commander";
4
4
  import { create as create$7 } from "@theunwalked/cardigantime";
5
5
  import { z } from "zod";
6
+ import Logging from "@fjell/logging";
6
7
  import * as path from "path";
7
8
  import path__default from "path";
9
+ import { SafeRegex } from "@theunwalked/pressurelid";
8
10
  import "zod-to-json-schema";
9
11
  import "tiktoken";
10
12
  import { XMLParser } from "fast-xml-parser";
11
13
  import OpenAI from "openai";
12
14
  import Anthropic from "@anthropic-ai/sdk";
13
15
  import { GoogleGenerativeAI } from "@google/generative-ai";
16
+ import { createSafeError as createSafeError$1 } from "@theunwalked/spotclean";
14
17
  import { glob } from "glob";
15
18
  import * as fs from "fs";
16
19
  import crypto from "crypto";
@@ -225,19 +228,60 @@ const DEFAULT_FORMAT_OPTIONS = {
225
228
  sectionTitleProperty: DEFAULT_SECTION_TITLE_PROPERTY,
226
229
  sectionDepth: 0
227
230
  };
228
- const DEFAULT_LOGGER = {
229
- name: "default",
230
- debug: (message, ...args) => console.debug(message, ...args),
231
- info: (message, ...args) => console.info(message, ...args),
232
- warn: (message, ...args) => console.warn(message, ...args),
233
- error: (message, ...args) => console.error(message, ...args),
234
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
235
- verbose: (message, ...args) => {
236
- },
237
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
238
- silly: (message, ...args) => {
231
+ const LibLogger = Logging.getLogger("@theunwalked/riotprompt");
232
+ function createSilentLogger(name) {
233
+ return {
234
+ name,
235
+ debug: () => {
236
+ },
237
+ info: () => {
238
+ },
239
+ warn: () => {
240
+ },
241
+ error: () => {
242
+ },
243
+ verbose: () => {
244
+ },
245
+ silly: () => {
246
+ },
247
+ get: (...components) => createSilentLogger(`${name}:${components.join(":")}`)
248
+ };
249
+ }
250
+ const SILENT_LOGGER = createSilentLogger("silent");
251
+ const isLoggingEnabled = () => {
252
+ return process.env.RIOTPROMPT_LOGGING === "true" || process.env.DEBUG?.includes("riotprompt") || process.env.NODE_ENV === "development";
253
+ };
254
+ function createLoggerFromFjell(fjellLogger, name) {
255
+ return {
256
+ name,
257
+ debug: (message, ...args) => fjellLogger.debug(message, ...args),
258
+ info: (message, ...args) => fjellLogger.info(message, ...args),
259
+ warn: (message, ...args) => fjellLogger.warning(message, ...args),
260
+ error: (message, ...args) => fjellLogger.error(message, ...args),
261
+ verbose: (message, ...args) => fjellLogger.debug(message, ...args),
262
+ // Map to debug
263
+ silly: (message, ...args) => fjellLogger.debug(message, ...args),
264
+ // Map to debug
265
+ get: (...components) => {
266
+ const childLogger = fjellLogger.get(...components);
267
+ return createLoggerFromFjell(childLogger, `${name}:${components.join(":")}`);
268
+ }
269
+ };
270
+ }
271
+ const FJELL_LOGGER = {
272
+ name: "fjell",
273
+ debug: (message, ...args) => LibLogger.debug(message, ...args),
274
+ info: (message, ...args) => LibLogger.info(message, ...args),
275
+ warn: (message, ...args) => LibLogger.warning(message, ...args),
276
+ error: (message, ...args) => LibLogger.error(message, ...args),
277
+ verbose: (message, ...args) => LibLogger.debug(message, ...args),
278
+ silly: (message, ...args) => LibLogger.debug(message, ...args),
279
+ get: (...components) => {
280
+ const childLogger = LibLogger.get(...components);
281
+ return createLoggerFromFjell(childLogger, components.join(":"));
239
282
  }
240
283
  };
284
+ const DEFAULT_LOGGER = isLoggingEnabled() ? FJELL_LOGGER : SILENT_LOGGER;
241
285
  const wrapLogger = (toWrap, name) => {
242
286
  const requiredMethods = ["debug", "info", "warn", "error", "verbose", "silly"];
243
287
  const missingMethods = requiredMethods.filter((method) => typeof toWrap[method] !== "function");
@@ -254,13 +298,14 @@ const wrapLogger = (toWrap, name) => {
254
298
  else if (level === "silly") toWrap.silly(message, ...args);
255
299
  };
256
300
  return {
257
- name: "wrapped",
301
+ name: name || "wrapped",
258
302
  debug: (message, ...args) => log("debug", message, ...args),
259
303
  info: (message, ...args) => log("info", message, ...args),
260
304
  warn: (message, ...args) => log("warn", message, ...args),
261
305
  error: (message, ...args) => log("error", message, ...args),
262
306
  verbose: (message, ...args) => log("verbose", message, ...args),
263
- silly: (message, ...args) => log("silly", message, ...args)
307
+ silly: (message, ...args) => log("silly", message, ...args),
308
+ get: (...components) => wrapLogger(toWrap, name ? `${name}:${components.join(":")}` : components.join(":"))
264
309
  };
265
310
  };
266
311
  class ModelRegistry {
@@ -716,11 +761,268 @@ const create$1 = (params) => {
716
761
  listFiles
717
762
  };
718
763
  };
764
+ const DEFAULT_CONFIG = {
765
+ enabled: true,
766
+ logLevel: "warning",
767
+ includeContext: true,
768
+ maxContextSize: 1e3
769
+ };
770
+ class SecurityAuditLogger {
771
+ config;
772
+ logger;
773
+ eventCount = /* @__PURE__ */ new Map();
774
+ constructor(config = {}, logger) {
775
+ this.config = { ...DEFAULT_CONFIG, ...config };
776
+ this.logger = wrapLogger(logger || DEFAULT_LOGGER, "SecurityAudit");
777
+ }
778
+ /**
779
+ * Log a security event
780
+ */
781
+ log(event) {
782
+ if (!this.config.enabled) return;
783
+ const fullEvent = {
784
+ ...event,
785
+ timestamp: /* @__PURE__ */ new Date(),
786
+ context: this.sanitizeContext(event.context)
787
+ };
788
+ const count = this.eventCount.get(event.type) || 0;
789
+ this.eventCount.set(event.type, count + 1);
790
+ this.config.onEvent?.(fullEvent);
791
+ if (!this.shouldLog(event.severity)) return;
792
+ const logMessage = this.formatEvent(fullEvent);
793
+ switch (event.severity) {
794
+ case "critical":
795
+ this.logger.error(`[CRITICAL] ${logMessage}`);
796
+ break;
797
+ case "error":
798
+ this.logger.error(logMessage);
799
+ break;
800
+ case "warning":
801
+ this.logger.warn(logMessage);
802
+ break;
803
+ case "info":
804
+ this.logger.info(logMessage);
805
+ break;
806
+ }
807
+ }
808
+ /**
809
+ * Convenience methods for common events
810
+ */
811
+ pathTraversalBlocked(path2, reason) {
812
+ this.log({
813
+ type: "path_traversal_blocked",
814
+ severity: "warning",
815
+ message: `Path traversal attempt blocked: ${reason}`,
816
+ context: { attemptedPath: this.sanitizePath(path2) }
817
+ });
818
+ }
819
+ pathValidationFailed(path2, reason) {
820
+ this.log({
821
+ type: "path_validation_failed",
822
+ severity: "warning",
823
+ message: `Path validation failed: ${reason}`,
824
+ context: { attemptedPath: this.sanitizePath(path2) }
825
+ });
826
+ }
827
+ toolValidationFailed(toolName, reason) {
828
+ this.log({
829
+ type: "tool_validation_failed",
830
+ severity: "warning",
831
+ message: `Tool parameter validation failed for "${toolName}": ${reason}`,
832
+ context: { toolName }
833
+ });
834
+ }
835
+ toolExecutionBlocked(toolName, reason) {
836
+ this.log({
837
+ type: "tool_execution_blocked",
838
+ severity: "error",
839
+ message: `Tool execution blocked for "${toolName}": ${reason}`,
840
+ context: { toolName }
841
+ });
842
+ }
843
+ toolTimeout(toolName, timeoutMs) {
844
+ this.log({
845
+ type: "tool_timeout",
846
+ severity: "warning",
847
+ message: `Tool "${toolName}" timed out after ${timeoutMs}ms`,
848
+ context: { toolName, timeoutMs }
849
+ });
850
+ }
851
+ secretRedacted(source) {
852
+ this.log({
853
+ type: "secret_redacted",
854
+ severity: "info",
855
+ message: `Sensitive data redacted from ${source}`,
856
+ context: { source }
857
+ });
858
+ }
859
+ apiKeyUsed(provider) {
860
+ this.log({
861
+ type: "api_key_used",
862
+ severity: "info",
863
+ message: `API key accessed for provider: ${provider}`,
864
+ context: { provider }
865
+ });
866
+ }
867
+ deserializationFailed(source, reason) {
868
+ this.log({
869
+ type: "deserialization_failed",
870
+ severity: "warning",
871
+ message: `Deserialization failed from ${source}: ${reason}`,
872
+ context: { source }
873
+ });
874
+ }
875
+ regexTimeout(pattern, timeoutMs) {
876
+ this.log({
877
+ type: "regex_timeout",
878
+ severity: "warning",
879
+ message: `Regex operation timed out after ${timeoutMs}ms`,
880
+ context: { patternLength: pattern.length, timeoutMs }
881
+ });
882
+ }
883
+ requestTimeout(operation, timeoutMs) {
884
+ this.log({
885
+ type: "request_timeout",
886
+ severity: "warning",
887
+ message: `Operation "${operation}" timed out after ${timeoutMs}ms`,
888
+ context: { operation, timeoutMs }
889
+ });
890
+ }
891
+ rateLimitExceeded(resource, limit) {
892
+ this.log({
893
+ type: "rate_limit_exceeded",
894
+ severity: "warning",
895
+ message: `Rate limit exceeded for ${resource}: limit is ${limit}`,
896
+ context: { resource, limit }
897
+ });
898
+ }
899
+ /**
900
+ * Get event statistics
901
+ */
902
+ getStats() {
903
+ return new Map(this.eventCount);
904
+ }
905
+ /**
906
+ * Get total event count
907
+ */
908
+ getTotalEventCount() {
909
+ let total = 0;
910
+ for (const count of this.eventCount.values()) {
911
+ total += count;
912
+ }
913
+ return total;
914
+ }
915
+ /**
916
+ * Reset statistics
917
+ */
918
+ resetStats() {
919
+ this.eventCount.clear();
920
+ }
921
+ /**
922
+ * Check if any events of a specific type have been logged
923
+ */
924
+ hasEventsOfType(type) {
925
+ return (this.eventCount.get(type) || 0) > 0;
926
+ }
927
+ /**
928
+ * Get count for a specific event type
929
+ */
930
+ getEventCount(type) {
931
+ return this.eventCount.get(type) || 0;
932
+ }
933
+ shouldLog(severity) {
934
+ const levels = ["info", "warning", "error", "critical"];
935
+ const configLevel = levels.indexOf(this.config.logLevel === "all" ? "info" : this.config.logLevel);
936
+ const eventLevel = levels.indexOf(severity);
937
+ return eventLevel >= configLevel;
938
+ }
939
+ formatEvent(event) {
940
+ let message = `[${event.type}] ${event.message}`;
941
+ if (this.config.includeContext && event.context) {
942
+ message += ` | Context: ${JSON.stringify(event.context)}`;
943
+ }
944
+ return message;
945
+ }
946
+ sanitizeContext(context) {
947
+ if (!context || !this.config.includeContext) return void 0;
948
+ const sanitized = {};
949
+ let size = 0;
950
+ for (const [key, value] of Object.entries(context)) {
951
+ const stringValue = String(value);
952
+ if (size + stringValue.length > this.config.maxContextSize) break;
953
+ if (this.looksLikeSensitiveKey(key)) {
954
+ sanitized[key] = "[REDACTED]";
955
+ } else {
956
+ sanitized[key] = value;
957
+ }
958
+ size += stringValue.length;
959
+ }
960
+ return sanitized;
961
+ }
962
+ sanitizePath(path2) {
963
+ const parts = path2.split(/[/\\]/);
964
+ const lastPart = parts[parts.length - 1];
965
+ return `.../${lastPart} (${path2.length} chars)`;
966
+ }
967
+ looksLikeSensitiveKey(key) {
968
+ const sensitivePatterns = [
969
+ /key/i,
970
+ /secret/i,
971
+ /password/i,
972
+ /token/i,
973
+ /auth/i,
974
+ /credential/i
975
+ ];
976
+ return sensitivePatterns.some((p) => p.test(key));
977
+ }
978
+ }
979
+ let globalAuditLogger = null;
980
+ function getAuditLogger() {
981
+ if (!globalAuditLogger) {
982
+ globalAuditLogger = new SecurityAuditLogger();
983
+ }
984
+ return globalAuditLogger;
985
+ }
986
+ function createSafeError(error, context) {
987
+ const err = error instanceof Error ? error : new Error(String(error));
988
+ return createSafeError$1(err, context);
989
+ }
719
990
  const OptionsSchema = z.object({
720
991
  logger: z.any().optional().default(DEFAULT_LOGGER),
721
992
  ignorePatterns: z.array(z.string()).optional().default(DEFAULT_IGNORE_PATTERNS),
722
993
  parameters: ParametersSchema.optional().default({})
723
994
  });
995
+ function createSafeIgnorePatterns(patterns, logger) {
996
+ const auditLogger = getAuditLogger();
997
+ const safeRegex = new SafeRegex({
998
+ maxLength: 500,
999
+ timeoutMs: 1e3,
1000
+ onBlock: (message, pattern) => {
1001
+ logger.warn(`Blocked unsafe ignore pattern: ${message}`, { patternLength: pattern.length });
1002
+ auditLogger.log({
1003
+ type: "regex_blocked",
1004
+ severity: "warning",
1005
+ message: `Blocked unsafe regex pattern: ${message}`,
1006
+ context: { patternLength: pattern.length }
1007
+ });
1008
+ },
1009
+ onWarning: (message, _pattern) => {
1010
+ logger.debug(`Regex warning: ${message}`);
1011
+ }
1012
+ });
1013
+ return patterns.map((pattern) => {
1014
+ const result = safeRegex.create(pattern, "i");
1015
+ if (result.safe && result.regex) {
1016
+ return result.regex;
1017
+ }
1018
+ const globResult = safeRegex.globToRegex(pattern);
1019
+ if (globResult.safe && globResult.regex) {
1020
+ return globResult.regex;
1021
+ }
1022
+ logger.warn(`Invalid or unsafe ignore pattern "${pattern}": ${result.error || globResult.error}`);
1023
+ return null;
1024
+ }).filter((regex) => regex !== null);
1025
+ }
724
1026
  function extractFirstHeader(markdownText) {
725
1027
  const headerRegex = /^(#{1,6})\s+(.+?)(?:\n|$)/m;
726
1028
  const match = markdownText.match(headerRegex);
@@ -782,14 +1084,7 @@ const create = (loaderOptions) => {
782
1084
  mainContextSection = create$4({ ...sectionOptions, title: dirName });
783
1085
  }
784
1086
  const files = await storage.listFiles(contextDir);
785
- const ignorePatternsRegex = ignorePatterns.map((pattern) => {
786
- try {
787
- return new RegExp(pattern, "i");
788
- } catch (error) {
789
- logger.error(`Invalid ignore pattern: ${pattern}`, { error });
790
- return /(?!)/;
791
- }
792
- });
1087
+ const ignorePatternsRegex = createSafeIgnorePatterns(ignorePatterns, logger);
793
1088
  const filteredFiles = files.filter((file) => {
794
1089
  const fullPath = path__default.join(contextDir, file);
795
1090
  return !ignorePatternsRegex.some((regex) => regex.test(file) || regex.test(fullPath));
@@ -816,7 +1111,8 @@ const create = (loaderOptions) => {
816
1111
  }
817
1112
  contextSections.push(mainContextSection);
818
1113
  } catch (error) {
819
- logger.error(`Error processing context directory ${contextDir}: ${error}`);
1114
+ const safeError = createSafeError(error, { operation: "load", directory: path__default.basename(contextDir) });
1115
+ logger.error(`Error processing context directory: ${safeError.message}`);
820
1116
  }
821
1117
  }
822
1118
  return contextSections;
@@ -838,6 +1134,7 @@ z.object({
838
1134
  overrides: z.boolean().optional().default(false),
839
1135
  parameters: ParametersSchema.optional().default({})
840
1136
  });
1137
+ Logging.getLogger("@theunwalked/riotprompt");
841
1138
  z.object({
842
1139
  model: z.string(),
843
1140
  formatter: z.any().optional(),
@@ -1344,6 +1641,168 @@ const execute = async (request, options = {}) => {
1344
1641
  const manager = new ExecutionManager();
1345
1642
  return manager.execute(request, options);
1346
1643
  };
1644
+ const PathSecurityConfigSchema = z.object({
1645
+ enabled: z.boolean().optional().default(true),
1646
+ basePaths: z.array(z.string()).optional().default([]),
1647
+ allowAbsolute: z.boolean().optional().default(false),
1648
+ allowSymlinks: z.boolean().optional().default(false),
1649
+ denyPatterns: z.array(z.string()).optional().default([
1650
+ "\\.\\.",
1651
+ // Parent directory
1652
+ "~",
1653
+ // Home directory expansion
1654
+ "\\$\\{"
1655
+ // Variable expansion
1656
+ ])
1657
+ });
1658
+ const ToolSecurityConfigSchema = z.object({
1659
+ enabled: z.boolean().optional().default(true),
1660
+ validateParams: z.boolean().optional().default(true),
1661
+ sandboxExecution: z.boolean().optional().default(false),
1662
+ allowedTools: z.array(z.string()).optional(),
1663
+ deniedTools: z.array(z.string()).optional().default([]),
1664
+ maxExecutionTime: z.number().optional().default(3e4),
1665
+ // 30 seconds
1666
+ maxConcurrentCalls: z.number().optional().default(10)
1667
+ });
1668
+ const SecretSecurityConfigSchema = z.object({
1669
+ enabled: z.boolean().optional().default(true),
1670
+ redactInLogs: z.boolean().optional().default(true),
1671
+ redactInErrors: z.boolean().optional().default(true),
1672
+ patterns: z.array(z.instanceof(RegExp)).optional().default([
1673
+ /api[_-]?key[\s:="']+[\w-]+/gi,
1674
+ /password[\s:="']+[\w-]+/gi,
1675
+ /Bearer\s+[\w-]+/gi,
1676
+ /sk-[a-zA-Z0-9]{48,}/g,
1677
+ /AKIA[0-9A-Z]{16}/g
1678
+ // AWS Access Key
1679
+ ]),
1680
+ customPatterns: z.array(z.instanceof(RegExp)).optional().default([])
1681
+ });
1682
+ const LogSecurityConfigSchema = z.object({
1683
+ enabled: z.boolean().optional().default(true),
1684
+ auditSecurityEvents: z.boolean().optional().default(true),
1685
+ sanitizeStackTraces: z.boolean().optional().default(true),
1686
+ maxContentLength: z.number().optional().default(1e4)
1687
+ });
1688
+ const TimeoutConfigSchema = z.object({
1689
+ enabled: z.boolean().optional().default(true),
1690
+ defaultTimeout: z.number().optional().default(3e4),
1691
+ llmTimeout: z.number().optional().default(12e4),
1692
+ // 2 minutes for LLM calls
1693
+ toolTimeout: z.number().optional().default(3e4),
1694
+ fileTimeout: z.number().optional().default(5e3)
1695
+ });
1696
+ function createDefaultPathSecurityConfig() {
1697
+ return PathSecurityConfigSchema.parse({});
1698
+ }
1699
+ function createDefaultToolSecurityConfig() {
1700
+ return ToolSecurityConfigSchema.parse({});
1701
+ }
1702
+ function createDefaultSecretSecurityConfig() {
1703
+ return SecretSecurityConfigSchema.parse({});
1704
+ }
1705
+ function createDefaultLogSecurityConfig() {
1706
+ return LogSecurityConfigSchema.parse({});
1707
+ }
1708
+ function createDefaultTimeoutConfig() {
1709
+ return TimeoutConfigSchema.parse({});
1710
+ }
1711
+ const SecurityConfigSchema = z.object({
1712
+ paths: PathSecurityConfigSchema.optional().default(createDefaultPathSecurityConfig),
1713
+ tools: ToolSecurityConfigSchema.optional().default(createDefaultToolSecurityConfig),
1714
+ secrets: SecretSecurityConfigSchema.optional().default(createDefaultSecretSecurityConfig),
1715
+ logging: LogSecurityConfigSchema.optional().default(createDefaultLogSecurityConfig),
1716
+ timeouts: TimeoutConfigSchema.optional().default(createDefaultTimeoutConfig)
1717
+ });
1718
+ SecurityConfigSchema.parse({});
1719
+ const SERIALIZATION_LIMITS = {
1720
+ maxContentLength: 1e6,
1721
+ // 1MB per message
1722
+ maxArgumentsLength: 1e5,
1723
+ // 100KB for tool arguments
1724
+ maxMessages: 1e4,
1725
+ // 10k messages per conversation
1726
+ maxContextItems: 1e3,
1727
+ // 1k context items
1728
+ maxStringLength: 100,
1729
+ // 100 chars for names/ids
1730
+ maxToolCalls: 100
1731
+ // 100 tool calls per message
1732
+ };
1733
+ const ToolCallSchema = z.object({
1734
+ id: z.string().max(SERIALIZATION_LIMITS.maxStringLength),
1735
+ type: z.literal("function"),
1736
+ function: z.object({
1737
+ name: z.string().max(SERIALIZATION_LIMITS.maxStringLength),
1738
+ arguments: z.string().max(SERIALIZATION_LIMITS.maxArgumentsLength)
1739
+ })
1740
+ });
1741
+ const ConversationMessageSchema = z.object({
1742
+ role: z.enum(["system", "user", "assistant", "tool"]),
1743
+ content: z.string().nullable().refine(
1744
+ (val) => val === null || val.length <= SERIALIZATION_LIMITS.maxContentLength,
1745
+ { message: `Content exceeds maximum length of ${SERIALIZATION_LIMITS.maxContentLength}` }
1746
+ ),
1747
+ name: z.string().max(SERIALIZATION_LIMITS.maxStringLength).optional(),
1748
+ tool_calls: z.array(ToolCallSchema).max(SERIALIZATION_LIMITS.maxToolCalls).optional(),
1749
+ tool_call_id: z.string().max(SERIALIZATION_LIMITS.maxStringLength).optional()
1750
+ });
1751
+ const ConversationMetadataSchema = z.object({
1752
+ model: z.string().max(SERIALIZATION_LIMITS.maxStringLength),
1753
+ created: z.string().datetime(),
1754
+ lastModified: z.string().datetime(),
1755
+ messageCount: z.number().int().nonnegative(),
1756
+ toolCallCount: z.number().int().nonnegative()
1757
+ });
1758
+ z.object({
1759
+ // Optional version for forward compatibility
1760
+ version: z.string().optional(),
1761
+ messages: z.array(ConversationMessageSchema).max(SERIALIZATION_LIMITS.maxMessages),
1762
+ metadata: ConversationMetadataSchema,
1763
+ contextProvided: z.array(z.string().max(1e3)).max(SERIALIZATION_LIMITS.maxContextItems).optional()
1764
+ });
1765
+ z.object({
1766
+ version: z.string().optional(),
1767
+ persona: z.any().optional(),
1768
+ instructions: z.any().optional(),
1769
+ contexts: z.any().optional(),
1770
+ content: z.any().optional()
1771
+ });
1772
+ z.object({
1773
+ id: z.string().max(200),
1774
+ metadata: z.object({
1775
+ startTime: z.union([z.string().datetime(), z.date()]),
1776
+ endTime: z.union([z.string().datetime(), z.date()]).optional(),
1777
+ duration: z.number().nonnegative().optional(),
1778
+ model: z.string().max(SERIALIZATION_LIMITS.maxStringLength),
1779
+ template: z.string().max(SERIALIZATION_LIMITS.maxStringLength).optional(),
1780
+ userContext: z.record(z.string(), z.any()).optional()
1781
+ }),
1782
+ prompt: z.object({
1783
+ persona: z.string().optional(),
1784
+ instructions: z.string().optional(),
1785
+ content: z.array(z.string()).optional(),
1786
+ context: z.array(z.string()).optional()
1787
+ }).optional(),
1788
+ messages: z.array(z.object({
1789
+ index: z.number().int().nonnegative(),
1790
+ timestamp: z.string(),
1791
+ role: z.string(),
1792
+ content: z.string().nullable(),
1793
+ tool_calls: z.array(ToolCallSchema).optional(),
1794
+ tool_call_id: z.string().optional(),
1795
+ metadata: z.record(z.string(), z.any()).optional()
1796
+ })).max(SERIALIZATION_LIMITS.maxMessages),
1797
+ summary: z.object({
1798
+ totalMessages: z.number().int().nonnegative(),
1799
+ totalTokens: z.number().int().nonnegative().optional(),
1800
+ toolCallsExecuted: z.number().int().nonnegative(),
1801
+ iterations: z.number().int().nonnegative(),
1802
+ finalOutput: z.string().optional(),
1803
+ success: z.boolean()
1804
+ })
1805
+ });
1347
1806
  const program = new Command();
1348
1807
  const configManager = create$7({
1349
1808
  configShape: ConfigSchema.shape,
@@ -239,5 +239,5 @@ function _define_property(obj, key, value) {
239
239
  }
240
240
  }
241
241
 
242
- export { ContextManager, ContextManager as default };
242
+ export { ContextManager };
243
243
  //# sourceMappingURL=context-manager.js.map
@@ -1,3 +1,4 @@
1
+ import { MaskingConfig } from './logging-config';
1
2
  import { ConversationMessage, ToolCall } from './conversation';
2
3
  /**
3
4
  * Log format
@@ -13,8 +14,14 @@ export interface LogConfig {
13
14
  filenameTemplate?: string;
14
15
  includeMetadata?: boolean;
15
16
  includePrompt?: boolean;
17
+ /** Enable sensitive data redaction (defaults to true) */
16
18
  redactSensitive?: boolean;
19
+ /**
20
+ * @deprecated Use maskingConfig instead. Custom regex patterns for redaction.
21
+ */
17
22
  redactPatterns?: RegExp[];
23
+ /** Masking configuration for @fjell/logging integration */
24
+ maskingConfig?: MaskingConfig;
18
25
  onSaved?: (path: string) => void;
19
26
  onError?: (error: Error) => void;
20
27
  }
@@ -178,7 +185,16 @@ export declare class ConversationLogger {
178
185
  */
179
186
  private appendToJSONL;
180
187
  /**
181
- * Redact sensitive content
188
+ * Redact sensitive content using @fjell/logging masking
189
+ *
190
+ * Automatically masks:
191
+ * - API keys (OpenAI, Anthropic, AWS, GitHub, etc.)
192
+ * - Passwords and secrets
193
+ * - Email addresses
194
+ * - SSNs
195
+ * - Private keys
196
+ * - JWTs
197
+ * - Large base64 blobs
182
198
  */
183
199
  private redactContent;
184
200
  }
@@ -1,6 +1,7 @@
1
1
  import fs__default from 'fs/promises';
2
2
  import path__default from 'path';
3
3
  import { wrapLogger, DEFAULT_LOGGER } from './logger.js';
4
+ import { maskSensitive } from './logging-config.js';
4
5
 
5
6
  function _define_property(obj, key, value) {
6
7
  if (key in obj) {
@@ -252,22 +253,24 @@ function _define_property(obj, key, value) {
252
253
  await fs__default.appendFile(outputPath, line, 'utf-8');
253
254
  }
254
255
  /**
255
- * Redact sensitive content
256
+ * Redact sensitive content using @fjell/logging masking
257
+ *
258
+ * Automatically masks:
259
+ * - API keys (OpenAI, Anthropic, AWS, GitHub, etc.)
260
+ * - Passwords and secrets
261
+ * - Email addresses
262
+ * - SSNs
263
+ * - Private keys
264
+ * - JWTs
265
+ * - Large base64 blobs
256
266
  */ redactContent(content) {
257
- let redacted = content;
258
- // Apply custom patterns
259
- for (const pattern of this.config.redactPatterns){
260
- redacted = redacted.replace(pattern, '[REDACTED]');
261
- }
262
- // Default patterns
263
- const defaultPatterns = [
264
- /api[_-]?key[\s:="']+[\w-]+/gi,
265
- /password[\s:="']+[\w-]+/gi,
266
- /Bearer\s+[\w-]+/gi,
267
- /sk-[a-zA-Z0-9]{48}/g
268
- ];
269
- for (const pattern of defaultPatterns){
270
- redacted = redacted.replace(pattern, '[REDACTED]');
267
+ // Use Fjell's comprehensive masking
268
+ let redacted = maskSensitive(content, this.config.maskingConfig);
269
+ // Also apply any legacy custom patterns for backwards compatibility
270
+ if (this.config.redactPatterns && this.config.redactPatterns.length > 0) {
271
+ for (const pattern of this.config.redactPatterns){
272
+ redacted = redacted.replace(pattern, '[REDACTED]');
273
+ }
271
274
  }
272
275
  return redacted;
273
276
  }
@@ -288,8 +291,9 @@ function _define_property(obj, key, value) {
288
291
  filenameTemplate: 'conversation-{timestamp}',
289
292
  includeMetadata: true,
290
293
  includePrompt: false,
291
- redactSensitive: false,
294
+ redactSensitive: true,
292
295
  redactPatterns: [],
296
+ maskingConfig: undefined,
293
297
  onSaved: ()=>{},
294
298
  onError: ()=>{},
295
299
  ...config
@@ -492,5 +496,5 @@ function _define_property(obj, key, value) {
492
496
  }
493
497
  }
494
498
 
495
- export { ConversationLogger, ConversationReplayer, ConversationLogger as default };
499
+ export { ConversationLogger, ConversationReplayer };
496
500
  //# sourceMappingURL=conversation-logger.js.map