@intelligo-dev/core 1.0.0-beta.5 → 1.0.0-beta.6

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/README.md CHANGED
@@ -14,20 +14,21 @@ pnpm add @intelligo-dev/core
14
14
 
15
15
  ## Exports
16
16
 
17
- | Subpath | What |
18
- | ------------------ | -------------------------------------------------------------------------- |
19
- | `/db` | Drizzle client and the workspace-scoped query helpers |
20
- | `/db/schema` | Every framework table |
21
- | `/conversations` | Conversation and message persistence (ADR-0009) |
22
- | `/documents` | Document persistence and the title classifier |
23
- | `/identity` | The identity graph: facts, memories, profile snapshots |
24
- | `/email` | Transactional email and its templates |
25
- | `/notifications` | In-app notification records and triggers |
26
- | `/logger` | Structured logging with credential redaction |
27
- | `/registry` | Registries that survive a bundler duplicating a module |
28
- | `/money` | Amount-plus-currency value object in micros; imports nothing |
29
- | `/request-context` | Where the framework reads the request's headers from; bound by the adapter |
30
- | `/env` | Environment validation |
17
+ | Subpath | What |
18
+ | ------------------ | -------------------------------------------------------------------------------------- |
19
+ | `/db` | Drizzle client and the workspace-scoped query helpers |
20
+ | `/db/schema` | Every framework table |
21
+ | `/conversations` | Conversation and message persistence (ADR-0009) |
22
+ | `/documents` | Document persistence and the title classifier |
23
+ | `/identity` | The identity graph: facts, memories, profile snapshots |
24
+ | `/email` | Transactional email and its templates |
25
+ | `/notifications` | In-app notification records and triggers |
26
+ | `/logger` | Structured logging with credential redaction |
27
+ | `/registry` | Registries that survive a bundler duplicating a module |
28
+ | `/money` | Amount-plus-currency value object in micros; imports nothing |
29
+ | `/request-context` | Where the framework reads the request's headers from; bound by the adapter |
30
+ | `/prompt` | Prompt-injection sanitisation for user text bound for a system prompt; imports nothing |
31
+ | `/env` | Environment validation |
31
32
 
32
33
  ## Migrations
33
34
 
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Prompt-injection sanitisation for user-authored text that reaches a
3
+ * system prompt.
4
+ *
5
+ * A stored conversation summary, a profile field a student typed, a
6
+ * tool result quoting a web page — each is text the model will read as
7
+ * instructions if it is concatenated into the system prompt as-is.
8
+ * This strips role markers, override phrases and obfuscated separators
9
+ * before that concatenation, and reports which patterns it saw so a
10
+ * transport can log the attempt.
11
+ *
12
+ * Dependency-free on purpose: it is reached from the chat transport's
13
+ * `prepareMessages` seam, from tools, and from any package that
14
+ * assembles a prompt, none of which should pull anything else in for
15
+ * it. ADR-0008 routed it here from the product's runtime package.
16
+ *
17
+ * Returns an empty string when more than half of the original was
18
+ * injection patterns: that is a hostile payload, and feeding the
19
+ * remaining fragments to the model is worse than dropping it.
20
+ */
21
+ export type InjectionPattern = {
22
+ name: string;
23
+ pattern: RegExp;
24
+ };
25
+ /**
26
+ * What is stripped. Exported so a product can extend the list for its
27
+ * own model or language without re-implementing the sweep.
28
+ */
29
+ export declare const INJECTION_PATTERNS: readonly InjectionPattern[];
30
+ export type InjectionReport = {
31
+ isInjection: boolean;
32
+ /** Names of the patterns that matched, for a log line. */
33
+ patterns: string[];
34
+ };
35
+ /**
36
+ * Which patterns the text carries, without changing it. Zero-width
37
+ * characters are both removed and replaced by spaces before matching,
38
+ * so neither obfuscation hides a marker.
39
+ */
40
+ export declare function detectPromptInjection(text: string, patterns?: readonly InjectionPattern[]): InjectionReport;
41
+ /**
42
+ * The text with every pattern replaced by a space and whitespace
43
+ * collapsed — or an empty string when the text was mostly patterns.
44
+ */
45
+ export declare function sanitizeForSystemPrompt(text: string, patterns?: readonly InjectionPattern[]): string;
46
+ //# sourceMappingURL=prompt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../src/prompt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAYH,MAAM,MAAM,gBAAgB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjE;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,SAAS,gBAAgB,EAuCzD,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,WAAW,EAAE,OAAO,CAAC;IACrB,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,QAAQ,GAAE,SAAS,gBAAgB,EAAuB,GACzD,eAAe,CAajB;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,QAAQ,GAAE,SAAS,gBAAgB,EAAuB,GACzD,MAAM,CAsBR"}
package/dist/prompt.js ADDED
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Prompt-injection sanitisation for user-authored text that reaches a
3
+ * system prompt.
4
+ *
5
+ * A stored conversation summary, a profile field a student typed, a
6
+ * tool result quoting a web page — each is text the model will read as
7
+ * instructions if it is concatenated into the system prompt as-is.
8
+ * This strips role markers, override phrases and obfuscated separators
9
+ * before that concatenation, and reports which patterns it saw so a
10
+ * transport can log the attempt.
11
+ *
12
+ * Dependency-free on purpose: it is reached from the chat transport's
13
+ * `prepareMessages` seam, from tools, and from any package that
14
+ * assembles a prompt, none of which should pull anything else in for
15
+ * it. ADR-0008 routed it here from the product's runtime package.
16
+ *
17
+ * Returns an empty string when more than half of the original was
18
+ * injection patterns: that is a hostile payload, and feeding the
19
+ * remaining fragments to the model is worse than dropping it.
20
+ */
21
+ const ZERO_WIDTH_CHARS = /[\u200B\u200C\u200D\u200E\u200F\uFEFF\u00AD\u2060\u2061\u2062\u2063\u2064]/g;
22
+ function normalizeText(text) {
23
+ return {
24
+ withSpaces: text.replace(ZERO_WIDTH_CHARS, " ").normalize("NFKC"),
25
+ stripped: text.replace(ZERO_WIDTH_CHARS, "").normalize("NFKC"),
26
+ };
27
+ }
28
+ /**
29
+ * What is stripped. Exported so a product can extend the list for its
30
+ * own model or language without re-implementing the sweep.
31
+ */
32
+ export const INJECTION_PATTERNS = [
33
+ { name: "[SYSTEM]", pattern: /\[SYSTEM\]/gi },
34
+ { name: "<<SYS>>", pattern: /<<SYS>>/gi },
35
+ { name: "<|system|>", pattern: /<\|system\|>/gi },
36
+ { name: "<|im_start|>", pattern: /<\|im_start\|>/gi },
37
+ { name: "<|im_end|>", pattern: /<\|im_end\|>/gi },
38
+ { name: "system: prefix", pattern: /^(SYSTEM|System|system)\s*:\s*/gm },
39
+ {
40
+ name: "ignore previous instructions",
41
+ pattern: /ignore\s+(?:all\s+)?previous\s+instructions?/gi,
42
+ },
43
+ { name: "disregard above", pattern: /disregard\s+(?:everything\s+)?above/gi },
44
+ { name: "forget everything", pattern: /forget\s+(?:all|everything)/gi },
45
+ {
46
+ name: "override instructions",
47
+ pattern: /override\s+(?:your\s+)?instructions?/gi,
48
+ },
49
+ {
50
+ name: "new instructions",
51
+ pattern: /(?:your\s+)?new\s+instructions?\s+(?:are|follow)/gi,
52
+ },
53
+ { name: "you are now", pattern: /you\s+are\s+now\s+/gi },
54
+ { name: "act as", pattern: /\bact\s+as\s+(?:a\s+|an\s+)?/gi },
55
+ { name: "pretend to be", pattern: /pretend\s+to\s+be\s+/gi },
56
+ { name: "roleplay as", pattern: /roleplay\s+as\s+/gi },
57
+ { name: "play the role", pattern: /play\s+(?:the\s+)?role\s+of\s+/gi },
58
+ { name: "separator ---", pattern: /\n{0,2}-{3,}\n{0,2}/g },
59
+ { name: "separator ===", pattern: /\n{0,2}={3,}\n{0,2}/g },
60
+ { name: "separator ###", pattern: /\n{0,2}#{3,}\n{0,2}/g },
61
+ { name: "script tag", pattern: /<script[\s>]/gi },
62
+ { name: "base64 data", pattern: /data:[^;]+;base64,/gi },
63
+ {
64
+ name: "do not follow",
65
+ pattern: /do\s+not\s+follow\s+(?:your\s+)?(?:previous\s+)?instructions?/gi,
66
+ },
67
+ {
68
+ name: "new system prompt",
69
+ pattern: /(?:new|updated?)\s+system\s+prompt/gi,
70
+ },
71
+ ];
72
+ /**
73
+ * Which patterns the text carries, without changing it. Zero-width
74
+ * characters are both removed and replaced by spaces before matching,
75
+ * so neither obfuscation hides a marker.
76
+ */
77
+ export function detectPromptInjection(text, patterns = INJECTION_PATTERNS) {
78
+ if (!text || text.trim().length === 0) {
79
+ return { isInjection: false, patterns: [] };
80
+ }
81
+ const { withSpaces, stripped } = normalizeText(text);
82
+ const matched = [];
83
+ for (const { name, pattern } of patterns) {
84
+ // Fresh instances: a global RegExp carries lastIndex between tests.
85
+ const a = new RegExp(pattern.source, pattern.flags);
86
+ const b = new RegExp(pattern.source, pattern.flags);
87
+ if (a.test(withSpaces) || b.test(stripped))
88
+ matched.push(name);
89
+ }
90
+ return { isInjection: matched.length > 0, patterns: matched };
91
+ }
92
+ /**
93
+ * The text with every pattern replaced by a space and whitespace
94
+ * collapsed — or an empty string when the text was mostly patterns.
95
+ */
96
+ export function sanitizeForSystemPrompt(text, patterns = INJECTION_PATTERNS) {
97
+ if (!text || text.trim().length === 0)
98
+ return text;
99
+ const originalLength = text.length;
100
+ let sanitized = normalizeText(text).withSpaces;
101
+ for (const { pattern } of patterns) {
102
+ sanitized = sanitized.replace(new RegExp(pattern.source, pattern.flags), " ");
103
+ }
104
+ sanitized = sanitized
105
+ // A pattern replaced between two newlines leaves a whitespace-only
106
+ // line behind; drop those before collapsing blank lines.
107
+ .replace(/\n[ \t]+\n/g, "\n\n")
108
+ .replace(/\n{3,}/g, "\n\n")
109
+ .replace(/ {2,}/g, " ")
110
+ .trim();
111
+ const removed = originalLength - sanitized.length;
112
+ if (removed / originalLength > 0.5)
113
+ return "";
114
+ return sanitized;
115
+ }
116
+ //# sourceMappingURL=prompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt.js","sourceRoot":"","sources":["../src/prompt.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intelligo-dev/core",
3
- "version": "1.0.0-beta.5",
3
+ "version": "1.0.0-beta.6",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -61,6 +61,10 @@
61
61
  "types": "./dist/request-context.d.ts",
62
62
  "default": "./dist/request-context.js"
63
63
  },
64
+ "./prompt": {
65
+ "types": "./dist/prompt.d.ts",
66
+ "default": "./dist/prompt.js"
67
+ },
64
68
  "./logger": {
65
69
  "types": "./dist/logger.d.ts",
66
70
  "import": "./dist/logger.js",