@riotprompt/riotprompt 0.0.21 → 1.0.1-dev.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.
- package/CHANGELOG.md +74 -0
- package/MIGRATION.md +235 -0
- package/README.md +2 -0
- package/SECURITY.md +132 -0
- package/dist/builder.js +6 -0
- package/dist/builder.js.map +1 -1
- package/dist/cli.js +481 -22
- package/dist/context-manager.js +1 -1
- package/dist/conversation-logger.d.ts +17 -1
- package/dist/conversation-logger.js +21 -17
- package/dist/conversation-logger.js.map +1 -1
- package/dist/conversation.js +1 -1
- package/dist/error-handling.d.ts +52 -0
- package/dist/error-handling.js +132 -0
- package/dist/error-handling.js.map +1 -0
- package/dist/formatter.js +1 -1
- package/dist/iteration-strategy.js +1 -1
- package/dist/loader.js +60 -12
- package/dist/loader.js.map +1 -1
- package/dist/logger.d.ts +52 -0
- package/dist/logger.js +114 -14
- package/dist/logger.js.map +1 -1
- package/dist/logging-config.d.ts +84 -0
- package/dist/logging-config.js +116 -0
- package/dist/logging-config.js.map +1 -0
- package/dist/message-builder.js +1 -1
- package/dist/model-config.js +1 -1
- package/dist/override.js +10 -4
- package/dist/override.js.map +1 -1
- package/dist/recipes.js +6 -0
- package/dist/recipes.js.map +1 -1
- package/dist/reflection.js +1 -1
- package/dist/riotprompt.d.ts +9 -0
- package/dist/riotprompt.js +8 -0
- package/dist/riotprompt.js.map +1 -1
- package/dist/security/audit-logger.d.ts +61 -0
- package/dist/security/audit-logger.js +281 -0
- package/dist/security/audit-logger.js.map +1 -0
- package/dist/security/cli-security.d.ts +143 -0
- package/dist/security/cli-security.js +302 -0
- package/dist/security/cli-security.js.map +1 -0
- package/dist/security/defaults.d.ts +31 -0
- package/dist/security/defaults.js +72 -0
- package/dist/security/defaults.js.map +1 -0
- package/dist/security/events.d.ts +8 -0
- package/dist/security/index.d.ts +27 -0
- package/dist/security/index.js +22 -0
- package/dist/security/index.js.map +1 -0
- package/dist/security/path-guard.d.ts +161 -0
- package/dist/security/path-guard.js +327 -0
- package/dist/security/path-guard.js.map +1 -0
- package/dist/security/rate-limiter.d.ts +117 -0
- package/dist/security/rate-limiter.js +165 -0
- package/dist/security/rate-limiter.js.map +1 -0
- package/dist/security/serialization-schemas.d.ts +183 -0
- package/dist/security/serialization-schemas.js +174 -0
- package/dist/security/serialization-schemas.js.map +1 -0
- package/dist/security/timeout-guard.d.ts +123 -0
- package/dist/security/timeout-guard.js +223 -0
- package/dist/security/timeout-guard.js.map +1 -0
- package/dist/security/types.d.ts +86 -0
- package/dist/security/types.js +80 -0
- package/dist/security/types.js.map +1 -0
- package/dist/token-budget.js +1 -1
- package/dist/tools.js +1 -1
- package/guide/index.md +2 -0
- package/guide/integration.md +1109 -0
- package/guide/security.md +237 -0
- package/package.json +18 -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
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
package/dist/context-manager.js
CHANGED
|
@@ -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
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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:
|
|
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
|
|
499
|
+
export { ConversationLogger, ConversationReplayer };
|
|
496
500
|
//# sourceMappingURL=conversation-logger.js.map
|