llm-canary 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.
- package/README.md +97 -0
- package/dist/__tests__/canary.test.d.ts +2 -0
- package/dist/__tests__/canary.test.d.ts.map +1 -0
- package/dist/__tests__/canary.test.js +149 -0
- package/dist/__tests__/canary.test.js.map +1 -0
- package/dist/__tests__/zero-width.test.d.ts +2 -0
- package/dist/__tests__/zero-width.test.d.ts.map +1 -0
- package/dist/__tests__/zero-width.test.js +52 -0
- package/dist/__tests__/zero-width.test.js.map +1 -0
- package/dist/canary.d.ts +6 -0
- package/dist/canary.d.ts.map +1 -0
- package/dist/canary.js +24 -0
- package/dist/canary.js.map +1 -0
- package/dist/codec.d.ts +6 -0
- package/dist/codec.d.ts.map +1 -0
- package/dist/codec.js +41 -0
- package/dist/codec.js.map +1 -0
- package/dist/detect.d.ts +3 -0
- package/dist/detect.d.ts.map +1 -0
- package/dist/detect.js +72 -0
- package/dist/detect.js.map +1 -0
- package/dist/embed.d.ts +3 -0
- package/dist/embed.d.ts.map +1 -0
- package/dist/embed.js +69 -0
- package/dist/embed.js.map +1 -0
- package/dist/encoders/homoglyph.d.ts +3 -0
- package/dist/encoders/homoglyph.d.ts.map +1 -0
- package/dist/encoders/homoglyph.js +72 -0
- package/dist/encoders/homoglyph.js.map +1 -0
- package/dist/encoders/zero-width.d.ts +3 -0
- package/dist/encoders/zero-width.d.ts.map +1 -0
- package/dist/encoders/zero-width.js +48 -0
- package/dist/encoders/zero-width.js.map +1 -0
- package/dist/generate.d.ts +3 -0
- package/dist/generate.d.ts.map +1 -0
- package/dist/generate.js +12 -0
- package/dist/generate.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +45 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# llm-canary
|
|
2
|
+
|
|
3
|
+
Invisible canary tokens for prompt leakage detection. Embed hidden markers in prompts using zero-width Unicode characters or homoglyph substitution, then detect whether the prompt was leaked or reproduced by an untrusted party.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install llm-canary
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { generate, embed, detect, verify, createCanary } from 'llm-canary'
|
|
15
|
+
|
|
16
|
+
// Generate a canary token
|
|
17
|
+
const token = generate()
|
|
18
|
+
console.log(token.payload) // UUID like "3f2a1b4c-..."
|
|
19
|
+
|
|
20
|
+
// Embed into a prompt (zero-width chars appended by default)
|
|
21
|
+
const prompt = 'You are a helpful assistant. Answer the question below.'
|
|
22
|
+
const protected = embed(prompt, token)
|
|
23
|
+
|
|
24
|
+
// Later: detect if the prompt was leaked
|
|
25
|
+
const result = detect(receivedText)
|
|
26
|
+
if (result.found) {
|
|
27
|
+
console.log('Canary detected:', result.tokens[0].payload)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Or verify a specific token
|
|
31
|
+
const leaked = verify(receivedText, token)
|
|
32
|
+
console.log('Token present:', leaked)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## createCanary() convenience API
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
const canary = createCanary({ payload: 'my-secret-id' })
|
|
39
|
+
const protected = canary.embed('System prompt here.')
|
|
40
|
+
console.log(canary.verify(protected)) // true
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Encoding types
|
|
44
|
+
|
|
45
|
+
### zero-width (default)
|
|
46
|
+
|
|
47
|
+
Encodes the payload as a binary packet using zero-width Unicode characters appended (or prepended) to the text:
|
|
48
|
+
|
|
49
|
+
- `U+200B` = bit 0
|
|
50
|
+
- `U+200C` = bit 1
|
|
51
|
+
- `U+200D` = byte separator
|
|
52
|
+
|
|
53
|
+
The binary packet format is `[0xCA, 0x1A, length, ...payload bytes, xor_checksum]`.
|
|
54
|
+
|
|
55
|
+
Invisible to the human eye and to most rendered views; detected with high confidence when the XOR checksum is valid.
|
|
56
|
+
|
|
57
|
+
### homoglyph
|
|
58
|
+
|
|
59
|
+
Encodes bits by substituting Latin characters with Cyrillic lookalikes (e.g. `a` → `а`, `o` → `о`). The substitution is invisible in most fonts.
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
const token = generate({ type: 'homoglyph', payload: 'my-id' })
|
|
63
|
+
const protected = embed(longPrompt, token) // needs enough substitutable chars
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The text needs sufficient substitutable characters (Latin letters in the PAIRS list). An error is thrown if capacity is insufficient.
|
|
67
|
+
|
|
68
|
+
## embed() options
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
embed(prompt, token, { position: 'after-first-sentence' })
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Positions: `'start'` | `'end'` (default) | `'after-first-sentence'` | `'before-last-sentence'` | `'random'`
|
|
75
|
+
|
|
76
|
+
Only applies to `zero-width` and `whitespace` types; `homoglyph` always modifies the text in-place.
|
|
77
|
+
|
|
78
|
+
## detect() options
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
const result = detect(text, {
|
|
82
|
+
types: ['zero-width'], // limit which encodings to check
|
|
83
|
+
minConfidence: 'high', // 'high' | 'medium' | 'low'
|
|
84
|
+
})
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## verify()
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { verify } from 'llm-canary'
|
|
91
|
+
const present = verify(text, token) // default medium confidence
|
|
92
|
+
const strict = verify(text, token, { minConfidence: 'high' })
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## License
|
|
96
|
+
|
|
97
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canary.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/canary.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const generate_1 = require("../generate");
|
|
5
|
+
const embed_1 = require("../embed");
|
|
6
|
+
const detect_1 = require("../detect");
|
|
7
|
+
const canary_1 = require("../canary");
|
|
8
|
+
const ZW_SPACE = '\u200B';
|
|
9
|
+
const ZW_NON_JOINER = '\u200C';
|
|
10
|
+
const ZW_JOINER = '\u200D';
|
|
11
|
+
function hasZeroWidthChars(text) {
|
|
12
|
+
return text.includes(ZW_SPACE) || text.includes(ZW_NON_JOINER) || text.includes(ZW_JOINER);
|
|
13
|
+
}
|
|
14
|
+
(0, vitest_1.describe)('generate()', () => {
|
|
15
|
+
(0, vitest_1.it)('returns a token with a UUID payload by default', () => {
|
|
16
|
+
const token = (0, generate_1.generate)();
|
|
17
|
+
(0, vitest_1.expect)(token.payload).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);
|
|
18
|
+
});
|
|
19
|
+
(0, vitest_1.it)('returns zero-width type by default', () => {
|
|
20
|
+
const token = (0, generate_1.generate)();
|
|
21
|
+
(0, vitest_1.expect)(token.type).toBe('zero-width');
|
|
22
|
+
});
|
|
23
|
+
(0, vitest_1.it)('uses custom payload when provided', () => {
|
|
24
|
+
const token = (0, generate_1.generate)({ payload: 'secret-id-123' });
|
|
25
|
+
(0, vitest_1.expect)(token.payload).toBe('secret-id-123');
|
|
26
|
+
});
|
|
27
|
+
(0, vitest_1.it)('uses custom type when provided', () => {
|
|
28
|
+
const token = (0, generate_1.generate)({ type: 'homoglyph' });
|
|
29
|
+
(0, vitest_1.expect)(token.type).toBe('homoglyph');
|
|
30
|
+
});
|
|
31
|
+
(0, vitest_1.it)('includes createdAt ISO timestamp', () => {
|
|
32
|
+
const token = (0, generate_1.generate)();
|
|
33
|
+
(0, vitest_1.expect)(() => new Date(token.createdAt)).not.toThrow();
|
|
34
|
+
(0, vitest_1.expect)(new Date(token.createdAt).toISOString()).toBe(token.createdAt);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
(0, vitest_1.describe)('embed() zero-width', () => {
|
|
38
|
+
(0, vitest_1.it)('produces output containing zero-width chars', () => {
|
|
39
|
+
const token = (0, generate_1.generate)({ payload: 'test-payload' });
|
|
40
|
+
const prompt = 'Hello, world!';
|
|
41
|
+
const embedded = (0, embed_1.embed)(prompt, token);
|
|
42
|
+
(0, vitest_1.expect)(hasZeroWidthChars(embedded)).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
(0, vitest_1.it)('appends to end by default', () => {
|
|
45
|
+
const token = (0, generate_1.generate)({ payload: 'end-test' });
|
|
46
|
+
const prompt = 'My prompt';
|
|
47
|
+
const embedded = (0, embed_1.embed)(prompt, token);
|
|
48
|
+
(0, vitest_1.expect)(embedded.startsWith(prompt)).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
(0, vitest_1.it)('prepends when position is start', () => {
|
|
51
|
+
const token = (0, generate_1.generate)({ payload: 'start-test' });
|
|
52
|
+
const prompt = 'My prompt';
|
|
53
|
+
const embedded = (0, embed_1.embed)(prompt, token, { position: 'start' });
|
|
54
|
+
(0, vitest_1.expect)(hasZeroWidthChars(embedded.slice(0, 50))).toBe(true);
|
|
55
|
+
(0, vitest_1.expect)(embedded).toContain(prompt);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
(0, vitest_1.describe)('detect()', () => {
|
|
59
|
+
(0, vitest_1.it)('finds a zero-width embedded token', () => {
|
|
60
|
+
const token = (0, generate_1.generate)({ payload: 'detect-me' });
|
|
61
|
+
const prompt = 'Some text here.';
|
|
62
|
+
const embedded = (0, embed_1.embed)(prompt, token);
|
|
63
|
+
const result = (0, detect_1.detect)(embedded);
|
|
64
|
+
(0, vitest_1.expect)(result.found).toBe(true);
|
|
65
|
+
(0, vitest_1.expect)(result.tokens).toHaveLength(1);
|
|
66
|
+
(0, vitest_1.expect)(result.tokens[0].type).toBe('zero-width');
|
|
67
|
+
(0, vitest_1.expect)(result.tokens[0].payload).toBe('detect-me');
|
|
68
|
+
(0, vitest_1.expect)(result.tokens[0].checksumValid).toBe(true);
|
|
69
|
+
(0, vitest_1.expect)(result.tokens[0].confidence).toBe('high');
|
|
70
|
+
});
|
|
71
|
+
(0, vitest_1.it)('returns found=false for plain text', () => {
|
|
72
|
+
const result = (0, detect_1.detect)('No hidden data here.');
|
|
73
|
+
(0, vitest_1.expect)(result.found).toBe(false);
|
|
74
|
+
(0, vitest_1.expect)(result.tokens).toHaveLength(0);
|
|
75
|
+
});
|
|
76
|
+
(0, vitest_1.it)('includes durationMs', () => {
|
|
77
|
+
const result = (0, detect_1.detect)('some text');
|
|
78
|
+
(0, vitest_1.expect)(typeof result.durationMs).toBe('number');
|
|
79
|
+
(0, vitest_1.expect)(result.durationMs).toBeGreaterThanOrEqual(0);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
(0, vitest_1.describe)('verify() roundtrip', () => {
|
|
83
|
+
(0, vitest_1.it)('returns true for correctly embedded token', () => {
|
|
84
|
+
const token = (0, generate_1.generate)({ payload: 'verify-roundtrip' });
|
|
85
|
+
const embedded = (0, embed_1.embed)('The prompt text.', token);
|
|
86
|
+
(0, vitest_1.expect)((0, canary_1.verify)(embedded, token)).toBe(true);
|
|
87
|
+
});
|
|
88
|
+
(0, vitest_1.it)('returns false for plain text', () => {
|
|
89
|
+
const token = (0, generate_1.generate)({ payload: 'some-id' });
|
|
90
|
+
(0, vitest_1.expect)((0, canary_1.verify)('plain text with no hidden data', token)).toBe(false);
|
|
91
|
+
});
|
|
92
|
+
(0, vitest_1.it)('returns false when payload does not match', () => {
|
|
93
|
+
const token1 = (0, generate_1.generate)({ payload: 'token-alpha' });
|
|
94
|
+
const token2 = (0, generate_1.generate)({ payload: 'token-beta' });
|
|
95
|
+
const embedded = (0, embed_1.embed)('Hello world.', token1);
|
|
96
|
+
// token2 payload is different, should not be found
|
|
97
|
+
(0, vitest_1.expect)((0, canary_1.verify)(embedded, token2)).toBe(false);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
(0, vitest_1.describe)('homoglyph roundtrip', () => {
|
|
101
|
+
(0, vitest_1.it)('embed then detect finds correct payload', () => {
|
|
102
|
+
// Packet = [0xCA, 0x1A, len, ...payload, xor] = 4+2 = 6 bytes = 48 bits needed
|
|
103
|
+
// Use a short payload "ab" so packet is 6 bytes = 48 bits needed
|
|
104
|
+
// Provide a prompt with well over 48 substitutable chars
|
|
105
|
+
const prompt = 'The quick brown fox jumps over the lazy dog. ' +
|
|
106
|
+
'Pack my box with five dozen liquor jugs. ' +
|
|
107
|
+
'How vexingly quick daft zebras jump! ' +
|
|
108
|
+
'A complex mixture of every character type possible. ' +
|
|
109
|
+
'Explore creative approaches to problems and overcome obstacles effectively.';
|
|
110
|
+
const token = (0, generate_1.generate)({ type: 'homoglyph', payload: 'ab' });
|
|
111
|
+
const embedded = (0, embed_1.embed)(prompt, token);
|
|
112
|
+
const result = (0, detect_1.detect)(embedded, { types: ['homoglyph'] });
|
|
113
|
+
(0, vitest_1.expect)(result.found).toBe(true);
|
|
114
|
+
(0, vitest_1.expect)(result.tokens[0].type).toBe('homoglyph');
|
|
115
|
+
(0, vitest_1.expect)(result.tokens[0].payload).toBe('ab');
|
|
116
|
+
(0, vitest_1.expect)(result.tokens[0].checksumValid).toBe(true);
|
|
117
|
+
});
|
|
118
|
+
(0, vitest_1.it)('throws when text has insufficient substitutable characters', () => {
|
|
119
|
+
const token = (0, generate_1.generate)({ type: 'homoglyph', payload: 'long-payload-that-needs-many-bits' });
|
|
120
|
+
(0, vitest_1.expect)(() => (0, embed_1.embed)('Hi.', token)).toThrow('Insufficient capacity');
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
(0, vitest_1.describe)('createCanary()', () => {
|
|
124
|
+
(0, vitest_1.it)('returns a canary object with token, embed, detect, verify', () => {
|
|
125
|
+
const canary = (0, canary_1.createCanary)();
|
|
126
|
+
(0, vitest_1.expect)(canary.token).toBeDefined();
|
|
127
|
+
(0, vitest_1.expect)(typeof canary.embed).toBe('function');
|
|
128
|
+
(0, vitest_1.expect)(typeof canary.detect).toBe('function');
|
|
129
|
+
(0, vitest_1.expect)(typeof canary.verify).toBe('function');
|
|
130
|
+
});
|
|
131
|
+
(0, vitest_1.it)('verify() returns true after embed()', () => {
|
|
132
|
+
const canary = (0, canary_1.createCanary)({ payload: 'canary-test' });
|
|
133
|
+
const embedded = canary.embed('This is the prompt text to protect.');
|
|
134
|
+
(0, vitest_1.expect)(canary.verify(embedded)).toBe(true);
|
|
135
|
+
});
|
|
136
|
+
(0, vitest_1.it)('respects config type homoglyph', () => {
|
|
137
|
+
// payload "xy" -> packet = 6 bytes = 48 bits needed
|
|
138
|
+
const canary = (0, canary_1.createCanary)({ type: 'homoglyph', payload: 'xy' });
|
|
139
|
+
(0, vitest_1.expect)(canary.token.type).toBe('homoglyph');
|
|
140
|
+
// Provide a long prompt with many substitutable chars
|
|
141
|
+
const prompt = 'Type checking is very important in every project. ' +
|
|
142
|
+
'Homoglyph encoding uses lookalike characters between Latin and Cyrillic alphabets. ' +
|
|
143
|
+
'Accuracy matters when processing text at a byte level. ' +
|
|
144
|
+
'Every character type must be handled properly to ensure correctness.';
|
|
145
|
+
const embedded = canary.embed(prompt);
|
|
146
|
+
(0, vitest_1.expect)(canary.verify(embedded)).toBe(true);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
//# sourceMappingURL=canary.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canary.test.js","sourceRoot":"","sources":["../../src/__tests__/canary.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zero-width.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/zero-width.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const zero_width_1 = require("../encoders/zero-width");
|
|
5
|
+
const ZW_SPACE = '\u200B';
|
|
6
|
+
const ZW_NON_JOINER = '\u200C';
|
|
7
|
+
const ZW_JOINER = '\u200D';
|
|
8
|
+
(0, vitest_1.describe)('encodeZeroWidth', () => {
|
|
9
|
+
(0, vitest_1.it)('encodes bytes using only zero-width chars', () => {
|
|
10
|
+
const bytes = new Uint8Array([0xAB, 0xCD]);
|
|
11
|
+
const encoded = (0, zero_width_1.encodeZeroWidth)(bytes);
|
|
12
|
+
for (const ch of encoded) {
|
|
13
|
+
(0, vitest_1.expect)([ZW_SPACE, ZW_NON_JOINER, ZW_JOINER]).toContain(ch);
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
(0, vitest_1.it)('returns empty string for empty bytes', () => {
|
|
17
|
+
(0, vitest_1.expect)((0, zero_width_1.encodeZeroWidth)(new Uint8Array([]))).toBe('');
|
|
18
|
+
});
|
|
19
|
+
(0, vitest_1.it)('encodes a single byte as 8 zero-width bits', () => {
|
|
20
|
+
const bytes = new Uint8Array([0b10101010]);
|
|
21
|
+
const encoded = (0, zero_width_1.encodeZeroWidth)(bytes);
|
|
22
|
+
// No separator for single byte, 8 bit chars
|
|
23
|
+
const bits = [...encoded].filter(c => c !== ZW_JOINER);
|
|
24
|
+
(0, vitest_1.expect)(bits).toHaveLength(8);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
(0, vitest_1.describe)('decodeZeroWidth roundtrip', () => {
|
|
28
|
+
(0, vitest_1.it)('roundtrips single byte', () => {
|
|
29
|
+
const bytes = new Uint8Array([42]);
|
|
30
|
+
const encoded = (0, zero_width_1.encodeZeroWidth)(bytes);
|
|
31
|
+
const decoded = (0, zero_width_1.decodeZeroWidth)(encoded);
|
|
32
|
+
(0, vitest_1.expect)(decoded).not.toBeNull();
|
|
33
|
+
(0, vitest_1.expect)(decoded).toEqual(bytes);
|
|
34
|
+
});
|
|
35
|
+
(0, vitest_1.it)('roundtrips multiple bytes', () => {
|
|
36
|
+
const bytes = new Uint8Array([0, 127, 255, 0xCA, 0x1A]);
|
|
37
|
+
const encoded = (0, zero_width_1.encodeZeroWidth)(bytes);
|
|
38
|
+
const decoded = (0, zero_width_1.decodeZeroWidth)(encoded);
|
|
39
|
+
(0, vitest_1.expect)(decoded).not.toBeNull();
|
|
40
|
+
(0, vitest_1.expect)(decoded).toEqual(bytes);
|
|
41
|
+
});
|
|
42
|
+
(0, vitest_1.it)('returns null when no zero-width chars present', () => {
|
|
43
|
+
(0, vitest_1.expect)((0, zero_width_1.decodeZeroWidth)('hello world')).toBeNull();
|
|
44
|
+
});
|
|
45
|
+
(0, vitest_1.it)('ignores surrounding normal text during roundtrip', () => {
|
|
46
|
+
const bytes = new Uint8Array([99, 88]);
|
|
47
|
+
const encoded = 'prefix' + (0, zero_width_1.encodeZeroWidth)(bytes) + 'suffix';
|
|
48
|
+
const decoded = (0, zero_width_1.decodeZeroWidth)(encoded);
|
|
49
|
+
(0, vitest_1.expect)(decoded).toEqual(bytes);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
//# sourceMappingURL=zero-width.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zero-width.test.js","sourceRoot":"","sources":["../../src/__tests__/zero-width.test.ts"],"names":[],"mappings":"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"}
|
package/dist/canary.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Canary, CanaryConfig, CanaryToken, Confidence } from './types';
|
|
2
|
+
export declare function verify(text: string, token: CanaryToken, options?: {
|
|
3
|
+
minConfidence?: Confidence;
|
|
4
|
+
}): boolean;
|
|
5
|
+
export declare function createCanary(config?: CanaryConfig): Canary;
|
|
6
|
+
//# sourceMappingURL=canary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canary.d.ts","sourceRoot":"","sources":["../src/canary.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,WAAW,EAAgD,UAAU,EAAE,MAAM,SAAS,CAAA;AAE1H,wBAAgB,MAAM,CACpB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,WAAW,EAClB,OAAO,CAAC,EAAE;IAAE,aAAa,CAAC,EAAE,UAAU,CAAA;CAAE,GACvC,OAAO,CAMT;AAED,wBAAgB,YAAY,CAAC,MAAM,CAAC,EAAE,YAAY,GAAG,MAAM,CAS1D"}
|
package/dist/canary.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.verify = verify;
|
|
4
|
+
exports.createCanary = createCanary;
|
|
5
|
+
const generate_1 = require("./generate");
|
|
6
|
+
const embed_1 = require("./embed");
|
|
7
|
+
const detect_1 = require("./detect");
|
|
8
|
+
function verify(text, token, options) {
|
|
9
|
+
const result = (0, detect_1.detect)(text, {
|
|
10
|
+
types: [token.type],
|
|
11
|
+
minConfidence: options?.minConfidence ?? 'medium',
|
|
12
|
+
});
|
|
13
|
+
return result.tokens.some(t => t.payload === token.payload);
|
|
14
|
+
}
|
|
15
|
+
function createCanary(config) {
|
|
16
|
+
const token = (0, generate_1.generate)({ type: config?.type, payload: config?.payload });
|
|
17
|
+
return {
|
|
18
|
+
token,
|
|
19
|
+
embed: (prompt, options) => (0, embed_1.embed)(prompt, token, { position: config?.position, ...options }),
|
|
20
|
+
detect: (text, options) => (0, detect_1.detect)(text, options),
|
|
21
|
+
verify: (text) => verify(text, token),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=canary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canary.js","sourceRoot":"","sources":["../src/canary.ts"],"names":[],"mappings":";;AAKA,wBAUC;AAED,oCASC;AA1BD,yCAAqC;AACrC,mCAA+B;AAC/B,qCAAiC;AAGjC,SAAgB,MAAM,CACpB,IAAY,EACZ,KAAkB,EAClB,OAAwC;IAExC,MAAM,MAAM,GAAoB,IAAA,eAAM,EAAC,IAAI,EAAE;QAC3C,KAAK,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC;QACnB,aAAa,EAAE,OAAO,EAAE,aAAa,IAAI,QAAQ;KAClD,CAAC,CAAA;IACF,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,KAAK,CAAC,OAAO,CAAC,CAAA;AAC7D,CAAC;AAED,SAAgB,YAAY,CAAC,MAAqB;IAChD,MAAM,KAAK,GAAG,IAAA,mBAAQ,EAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAA;IACxE,OAAO;QACL,KAAK;QACL,KAAK,EAAE,CAAC,MAAc,EAAE,OAAsB,EAAU,EAAE,CACxD,IAAA,aAAK,EAAC,MAAM,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,EAAE,CAAC;QAClE,MAAM,EAAE,CAAC,IAAY,EAAE,OAAuB,EAAmB,EAAE,CAAC,IAAA,eAAM,EAAC,IAAI,EAAE,OAAO,CAAC;QACzF,MAAM,EAAE,CAAC,IAAY,EAAW,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC;KACvD,CAAA;AACH,CAAC"}
|
package/dist/codec.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codec.d.ts","sourceRoot":"","sources":["../src/codec.ts"],"names":[],"mappings":"AAGA,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,CAcxD;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAelG"}
|
package/dist/codec.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.encodePacket = encodePacket;
|
|
4
|
+
exports.decodePacket = decodePacket;
|
|
5
|
+
// Packet format: [0xCA, 0x1A, length, ...payload bytes, xor_checksum]
|
|
6
|
+
const MAGIC = [0xca, 0x1a];
|
|
7
|
+
function encodePacket(payload) {
|
|
8
|
+
const encoder = new TextEncoder();
|
|
9
|
+
const payloadBytes = encoder.encode(payload);
|
|
10
|
+
let checksum = 0;
|
|
11
|
+
for (const b of payloadBytes) {
|
|
12
|
+
checksum ^= b;
|
|
13
|
+
}
|
|
14
|
+
const packet = new Uint8Array(MAGIC.length + 1 + payloadBytes.length + 1);
|
|
15
|
+
packet[0] = MAGIC[0];
|
|
16
|
+
packet[1] = MAGIC[1];
|
|
17
|
+
packet[2] = payloadBytes.length;
|
|
18
|
+
packet.set(payloadBytes, 3);
|
|
19
|
+
packet[packet.length - 1] = checksum;
|
|
20
|
+
return packet;
|
|
21
|
+
}
|
|
22
|
+
function decodePacket(bytes) {
|
|
23
|
+
if (bytes.length < 4)
|
|
24
|
+
return null;
|
|
25
|
+
if (bytes[0] !== MAGIC[0] || bytes[1] !== MAGIC[1])
|
|
26
|
+
return null;
|
|
27
|
+
const length = bytes[2];
|
|
28
|
+
if (bytes.length < 3 + length + 1)
|
|
29
|
+
return null;
|
|
30
|
+
const payloadBytes = bytes.slice(3, 3 + length);
|
|
31
|
+
const storedChecksum = bytes[3 + length];
|
|
32
|
+
let computed = 0;
|
|
33
|
+
for (const b of payloadBytes) {
|
|
34
|
+
computed ^= b;
|
|
35
|
+
}
|
|
36
|
+
const checksumValid = computed === storedChecksum;
|
|
37
|
+
const decoder = new TextDecoder();
|
|
38
|
+
const payload = decoder.decode(payloadBytes);
|
|
39
|
+
return { payload, checksumValid };
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=codec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codec.js","sourceRoot":"","sources":["../src/codec.ts"],"names":[],"mappings":";;AAGA,oCAcC;AAED,oCAeC;AAlCD,sEAAsE;AACtE,MAAM,KAAK,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;AAE1B,SAAgB,YAAY,CAAC,OAAe;IAC1C,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAA;IACjC,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IAC5C,IAAI,QAAQ,GAAG,CAAC,CAAA;IAChB,KAAK,MAAM,CAAC,IAAI,YAAY,EAAE,CAAC;QAC7B,QAAQ,IAAI,CAAC,CAAA;IACf,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,GAAG,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IACzE,MAAM,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;IACpB,MAAM,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;IACpB,MAAM,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,MAAM,CAAA;IAC/B,MAAM,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC,CAAC,CAAA;IAC3B,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAA;IACpC,OAAO,MAAM,CAAA;AACf,CAAC;AAED,SAAgB,YAAY,CAAC,KAAiB;IAC5C,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,IAAI,CAAA;IACjC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAA;IAC/D,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;IACvB,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,GAAG,MAAM,GAAG,CAAC;QAAE,OAAO,IAAI,CAAA;IAC9C,MAAM,YAAY,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,CAAA;IAC/C,MAAM,cAAc,GAAG,KAAK,CAAC,CAAC,GAAG,MAAM,CAAC,CAAA;IACxC,IAAI,QAAQ,GAAG,CAAC,CAAA;IAChB,KAAK,MAAM,CAAC,IAAI,YAAY,EAAE,CAAC;QAC7B,QAAQ,IAAI,CAAC,CAAA;IACf,CAAC;IACD,MAAM,aAAa,GAAG,QAAQ,KAAK,cAAc,CAAA;IACjD,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAA;IACjC,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,CAAA;IAC5C,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,CAAA;AACnC,CAAC"}
|
package/dist/detect.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../src/detect.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAiB,eAAe,EAAE,aAAa,EAA0B,MAAM,SAAS,CAAA;AAQpG,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,eAAe,CA8D7E"}
|
package/dist/detect.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.detect = detect;
|
|
4
|
+
const zero_width_1 = require("./encoders/zero-width");
|
|
5
|
+
const homoglyph_1 = require("./encoders/homoglyph");
|
|
6
|
+
const codec_1 = require("./codec");
|
|
7
|
+
const CONFIDENCE_ORDER = { high: 3, medium: 2, low: 1 };
|
|
8
|
+
function meetsMinConfidence(conf, min) {
|
|
9
|
+
return CONFIDENCE_ORDER[conf] >= CONFIDENCE_ORDER[min];
|
|
10
|
+
}
|
|
11
|
+
function detect(text, options) {
|
|
12
|
+
const start = Date.now();
|
|
13
|
+
const enabledTypes = options?.types ?? ['zero-width', 'homoglyph', 'whitespace'];
|
|
14
|
+
const minConfidence = options?.minConfidence ?? 'low';
|
|
15
|
+
const tokens = [];
|
|
16
|
+
if (enabledTypes.includes('zero-width')) {
|
|
17
|
+
const bytes = (0, zero_width_1.decodeZeroWidth)(text);
|
|
18
|
+
if (bytes) {
|
|
19
|
+
const decoded = (0, codec_1.decodePacket)(bytes);
|
|
20
|
+
if (decoded) {
|
|
21
|
+
const confidence = decoded.checksumValid ? 'high' : 'medium';
|
|
22
|
+
if (meetsMinConfidence(confidence, minConfidence)) {
|
|
23
|
+
// Find position of zero-width chars in text
|
|
24
|
+
const startIdx = text.search(/[\u200B\u200C\u200D]/);
|
|
25
|
+
let endIdx = startIdx;
|
|
26
|
+
if (startIdx !== -1) {
|
|
27
|
+
for (let i = startIdx; i < text.length; i++) {
|
|
28
|
+
const c = text[i];
|
|
29
|
+
if (c === '\u200B' || c === '\u200C' || c === '\u200D') {
|
|
30
|
+
endIdx = i + 1;
|
|
31
|
+
}
|
|
32
|
+
else if (endIdx > startIdx) {
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
tokens.push({
|
|
38
|
+
type: 'zero-width',
|
|
39
|
+
payload: decoded.payload,
|
|
40
|
+
confidence,
|
|
41
|
+
checksumValid: decoded.checksumValid,
|
|
42
|
+
position: { start: startIdx === -1 ? 0 : startIdx, end: endIdx },
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (enabledTypes.includes('homoglyph')) {
|
|
49
|
+
const bytes = (0, homoglyph_1.decodeHomoglyph)(text);
|
|
50
|
+
if (bytes) {
|
|
51
|
+
const decoded = (0, codec_1.decodePacket)(bytes);
|
|
52
|
+
if (decoded) {
|
|
53
|
+
const confidence = decoded.checksumValid ? 'high' : 'medium';
|
|
54
|
+
if (meetsMinConfidence(confidence, minConfidence)) {
|
|
55
|
+
tokens.push({
|
|
56
|
+
type: 'homoglyph',
|
|
57
|
+
payload: decoded.payload,
|
|
58
|
+
confidence,
|
|
59
|
+
checksumValid: decoded.checksumValid,
|
|
60
|
+
position: { start: 0, end: text.length },
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
found: tokens.length > 0,
|
|
68
|
+
tokens,
|
|
69
|
+
durationMs: Date.now() - start,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=detect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detect.js","sourceRoot":"","sources":["../src/detect.ts"],"names":[],"mappings":"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"}
|
package/dist/embed.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embed.d.ts","sourceRoot":"","sources":["../src/embed.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAmCxD,wBAAgB,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,MAAM,CA+BxF"}
|
package/dist/embed.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.embed = embed;
|
|
4
|
+
const zero_width_1 = require("./encoders/zero-width");
|
|
5
|
+
const homoglyph_1 = require("./encoders/homoglyph");
|
|
6
|
+
const codec_1 = require("./codec");
|
|
7
|
+
function insertAt(prompt, marker, position) {
|
|
8
|
+
const pos = position ?? 'end';
|
|
9
|
+
if (pos === 'start') {
|
|
10
|
+
return marker + prompt;
|
|
11
|
+
}
|
|
12
|
+
if (pos === 'end') {
|
|
13
|
+
return prompt + marker;
|
|
14
|
+
}
|
|
15
|
+
if (pos === 'after-first-sentence') {
|
|
16
|
+
const match = prompt.match(/[.!?]\s/);
|
|
17
|
+
if (match && match.index !== undefined) {
|
|
18
|
+
const idx = match.index + 1;
|
|
19
|
+
return prompt.slice(0, idx) + marker + prompt.slice(idx);
|
|
20
|
+
}
|
|
21
|
+
return prompt + marker;
|
|
22
|
+
}
|
|
23
|
+
if (pos === 'before-last-sentence') {
|
|
24
|
+
// Find start of last sentence
|
|
25
|
+
const trimmed = prompt.trimEnd();
|
|
26
|
+
const match = [...trimmed.matchAll(/[.!?]\s+/g)].at(-1);
|
|
27
|
+
if (match && match.index !== undefined) {
|
|
28
|
+
const idx = match.index + match[0].length;
|
|
29
|
+
return prompt.slice(0, idx) + marker + prompt.slice(idx);
|
|
30
|
+
}
|
|
31
|
+
return marker + prompt;
|
|
32
|
+
}
|
|
33
|
+
if (pos === 'random') {
|
|
34
|
+
const idx = Math.floor(Math.random() * (prompt.length + 1));
|
|
35
|
+
return prompt.slice(0, idx) + marker + prompt.slice(idx);
|
|
36
|
+
}
|
|
37
|
+
return prompt + marker;
|
|
38
|
+
}
|
|
39
|
+
function embed(prompt, token, options) {
|
|
40
|
+
const bytes = (0, codec_1.encodePacket)(token.payload);
|
|
41
|
+
if (token.type === 'homoglyph') {
|
|
42
|
+
// encodeHomoglyph replaces chars in-place, returns full modified text
|
|
43
|
+
return (0, homoglyph_1.encodeHomoglyph)(prompt, bytes);
|
|
44
|
+
}
|
|
45
|
+
if (token.type === 'whitespace') {
|
|
46
|
+
// Encode as trailing spaces per line: 1 space = bit 0, 2 spaces = bit 1
|
|
47
|
+
// Each line carries one bit; we need bytes.length * 8 lines
|
|
48
|
+
const bits = [];
|
|
49
|
+
for (const byte of bytes) {
|
|
50
|
+
for (let bit = 7; bit >= 0; bit--) {
|
|
51
|
+
bits.push((byte >> bit) & 1);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const lines = prompt.split('\n');
|
|
55
|
+
let bitIndex = 0;
|
|
56
|
+
const encodedLines = lines.map(line => {
|
|
57
|
+
if (bitIndex >= bits.length)
|
|
58
|
+
return line;
|
|
59
|
+
const trailing = bits[bitIndex] === 1 ? ' ' : ' ';
|
|
60
|
+
bitIndex++;
|
|
61
|
+
return line + trailing;
|
|
62
|
+
});
|
|
63
|
+
return encodedLines.join('\n');
|
|
64
|
+
}
|
|
65
|
+
// zero-width (default)
|
|
66
|
+
const marker = (0, zero_width_1.encodeZeroWidth)(bytes);
|
|
67
|
+
return insertAt(prompt, marker, options?.position);
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=embed.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embed.js","sourceRoot":"","sources":["../src/embed.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"homoglyph.d.ts","sourceRoot":"","sources":["../../src/encoders/homoglyph.ts"],"names":[],"mappings":"AAgBA,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,MAAM,CA8BvE;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAwB/D"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Latin <-> Cyrillic lookalike substitution
|
|
3
|
+
// bit 0 = keep Latin, bit 1 = substitute with Cyrillic
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.encodeHomoglyph = encodeHomoglyph;
|
|
6
|
+
exports.decodeHomoglyph = decodeHomoglyph;
|
|
7
|
+
const PAIRS = [
|
|
8
|
+
['a', 'а'], ['c', 'с'], ['e', 'е'], ['o', 'о'],
|
|
9
|
+
['p', 'р'], ['x', 'х'], ['y', 'у'],
|
|
10
|
+
['A', 'А'], ['B', 'В'], ['C', 'С'], ['E', 'Е'],
|
|
11
|
+
['H', 'Н'], ['K', 'К'], ['M', 'М'], ['O', 'О'],
|
|
12
|
+
['P', 'Р'], ['T', 'Т'], ['X', 'Х'], ['Y', 'У'],
|
|
13
|
+
];
|
|
14
|
+
// Build lookup maps
|
|
15
|
+
const latinToCyrillic = new Map(PAIRS.map(([l, c]) => [l, c]));
|
|
16
|
+
const cyrillicToLatin = new Map(PAIRS.map(([l, c]) => [c, l]));
|
|
17
|
+
const substitutable = new Set(PAIRS.map(([l]) => l));
|
|
18
|
+
function encodeHomoglyph(text, bytes) {
|
|
19
|
+
// Find all substitutable positions in text
|
|
20
|
+
const positions = [];
|
|
21
|
+
for (let i = 0; i < text.length; i++) {
|
|
22
|
+
if (substitutable.has(text[i])) {
|
|
23
|
+
positions.push(i);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
// We need one substitutable position per bit
|
|
27
|
+
const bitsNeeded = bytes.length * 8;
|
|
28
|
+
if (positions.length < bitsNeeded) {
|
|
29
|
+
throw new Error(`Insufficient capacity: need ${bitsNeeded} substitutable chars, found ${positions.length}`);
|
|
30
|
+
}
|
|
31
|
+
const chars = text.split('');
|
|
32
|
+
let bitIndex = 0;
|
|
33
|
+
for (let byteIndex = 0; byteIndex < bytes.length; byteIndex++) {
|
|
34
|
+
for (let bit = 7; bit >= 0; bit--) {
|
|
35
|
+
const bitVal = (bytes[byteIndex] >> bit) & 1;
|
|
36
|
+
const pos = positions[bitIndex];
|
|
37
|
+
if (bitVal === 1) {
|
|
38
|
+
chars[pos] = latinToCyrillic.get(chars[pos]) ?? chars[pos];
|
|
39
|
+
}
|
|
40
|
+
bitIndex++;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return chars.join('');
|
|
44
|
+
}
|
|
45
|
+
function decodeHomoglyph(text) {
|
|
46
|
+
// Collect positions that are either Cyrillic lookalikes (=1) or Latin substitutables (=0)
|
|
47
|
+
const bits = [];
|
|
48
|
+
for (const ch of text) {
|
|
49
|
+
if (cyrillicToLatin.has(ch)) {
|
|
50
|
+
bits.push(1);
|
|
51
|
+
}
|
|
52
|
+
else if (substitutable.has(ch)) {
|
|
53
|
+
bits.push(0);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (bits.length === 0)
|
|
57
|
+
return null;
|
|
58
|
+
// Reconstruct bytes from bits (groups of 8)
|
|
59
|
+
const byteCount = Math.floor(bits.length / 8);
|
|
60
|
+
if (byteCount === 0)
|
|
61
|
+
return null;
|
|
62
|
+
const bytes = new Uint8Array(byteCount);
|
|
63
|
+
for (let i = 0; i < byteCount; i++) {
|
|
64
|
+
let byte = 0;
|
|
65
|
+
for (let bit = 0; bit < 8; bit++) {
|
|
66
|
+
byte = (byte << 1) | bits[i * 8 + bit];
|
|
67
|
+
}
|
|
68
|
+
bytes[i] = byte;
|
|
69
|
+
}
|
|
70
|
+
return bytes;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=homoglyph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"homoglyph.js","sourceRoot":"","sources":["../../src/encoders/homoglyph.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zero-width.d.ts","sourceRoot":"","sources":["../../src/encoders/zero-width.ts"],"names":[],"mappings":"AASA,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAWzD;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAmB/D"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Encode binary data as zero-width Unicode characters:
|
|
3
|
+
// bit 0 = U+200B (zero-width space)
|
|
4
|
+
// bit 1 = U+200C (zero-width non-joiner)
|
|
5
|
+
// byte separator = U+200D (zero-width joiner)
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.encodeZeroWidth = encodeZeroWidth;
|
|
8
|
+
exports.decodeZeroWidth = decodeZeroWidth;
|
|
9
|
+
const BIT0 = '\u200B';
|
|
10
|
+
const BIT1 = '\u200C';
|
|
11
|
+
const SEP = '\u200D';
|
|
12
|
+
function encodeZeroWidth(bytes) {
|
|
13
|
+
if (bytes.length === 0)
|
|
14
|
+
return '';
|
|
15
|
+
const parts = [];
|
|
16
|
+
for (const byte of bytes) {
|
|
17
|
+
let byteStr = '';
|
|
18
|
+
for (let bit = 7; bit >= 0; bit--) {
|
|
19
|
+
byteStr += (byte >> bit) & 1 ? BIT1 : BIT0;
|
|
20
|
+
}
|
|
21
|
+
parts.push(byteStr);
|
|
22
|
+
}
|
|
23
|
+
return parts.join(SEP);
|
|
24
|
+
}
|
|
25
|
+
function decodeZeroWidth(text) {
|
|
26
|
+
// Extract all zero-width chars
|
|
27
|
+
const zwChars = [...text].filter(c => c === BIT0 || c === BIT1 || c === SEP);
|
|
28
|
+
if (zwChars.length === 0)
|
|
29
|
+
return null;
|
|
30
|
+
// Split on SEP to get per-byte bit strings
|
|
31
|
+
const segments = zwChars.join('').split(SEP);
|
|
32
|
+
const bytes = [];
|
|
33
|
+
for (const seg of segments) {
|
|
34
|
+
if (seg.length === 0)
|
|
35
|
+
continue;
|
|
36
|
+
if (seg.length !== 8)
|
|
37
|
+
continue;
|
|
38
|
+
let byte = 0;
|
|
39
|
+
for (let i = 0; i < 8; i++) {
|
|
40
|
+
byte = (byte << 1) | (seg[i] === BIT1 ? 1 : 0);
|
|
41
|
+
}
|
|
42
|
+
bytes.push(byte);
|
|
43
|
+
}
|
|
44
|
+
if (bytes.length === 0)
|
|
45
|
+
return null;
|
|
46
|
+
return new Uint8Array(bytes);
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=zero-width.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zero-width.js","sourceRoot":"","sources":["../../src/encoders/zero-width.ts"],"names":[],"mappings":";AAAA,uDAAuD;AACvD,oCAAoC;AACpC,yCAAyC;AACzC,8CAA8C;;AAM9C,0CAWC;AAED,0CAmBC;AApCD,MAAM,IAAI,GAAG,QAAQ,CAAA;AACrB,MAAM,IAAI,GAAG,QAAQ,CAAA;AACrB,MAAM,GAAG,GAAG,QAAQ,CAAA;AAEpB,SAAgB,eAAe,CAAC,KAAiB;IAC/C,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAA;IACjC,MAAM,KAAK,GAAa,EAAE,CAAA;IAC1B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,OAAO,GAAG,EAAE,CAAA;QAChB,KAAK,IAAI,GAAG,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE,GAAG,EAAE,EAAE,CAAC;YAClC,OAAO,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAA;QAC5C,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IACrB,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AACxB,CAAC;AAED,SAAgB,eAAe,CAAC,IAAY;IAC1C,+BAA+B;IAC/B,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,GAAG,CAAC,CAAA;IAC5E,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IAErC,2CAA2C;IAC3C,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAC5C,MAAM,KAAK,GAAa,EAAE,CAAA;IAC1B,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC3B,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;YAAE,SAAQ;QAC9B,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;YAAE,SAAQ;QAC9B,IAAI,IAAI,GAAG,CAAC,CAAA;QACZ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC3B,IAAI,GAAG,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;QAChD,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAClB,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IACnC,OAAO,IAAI,UAAU,CAAC,KAAK,CAAC,CAAA;AAC9B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAE3D,wBAAgB,QAAQ,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,WAAW,CAM/D"}
|
package/dist/generate.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generate = generate;
|
|
4
|
+
const crypto_1 = require("crypto");
|
|
5
|
+
function generate(options) {
|
|
6
|
+
return {
|
|
7
|
+
type: options?.type ?? 'zero-width',
|
|
8
|
+
payload: options?.payload ?? (0, crypto_1.randomUUID)(),
|
|
9
|
+
createdAt: new Date().toISOString(),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=generate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate.js","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":";;AAGA,4BAMC;AATD,mCAAmC;AAGnC,SAAgB,QAAQ,CAAC,OAAyB;IAChD,OAAO;QACL,IAAI,EAAE,OAAO,EAAE,IAAI,IAAI,YAAY;QACnC,OAAO,EAAE,OAAO,EAAE,OAAO,IAAI,IAAA,mBAAU,GAAE;QACzC,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC,CAAA;AACH,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { generate } from './generate';
|
|
2
|
+
export { embed } from './embed';
|
|
3
|
+
export { detect } from './detect';
|
|
4
|
+
export { verify, createCanary } from './canary';
|
|
5
|
+
export type { CanaryType, Confidence, CanaryToken, GenerateOptions, EmbedOptions, DetectedToken, DetectionResult, DetectOptions, CanaryConfig, Canary, } from './types';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AACrC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAC/B,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AACjC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAE/C,YAAY,EACV,UAAU,EACV,UAAU,EACV,WAAW,EACX,eAAe,EACf,YAAY,EACZ,aAAa,EACb,eAAe,EACf,aAAa,EACb,YAAY,EACZ,MAAM,GACP,MAAM,SAAS,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createCanary = exports.verify = exports.detect = exports.embed = exports.generate = void 0;
|
|
4
|
+
// llm-canary - Invisible canary tokens for prompt leakage detection
|
|
5
|
+
var generate_1 = require("./generate");
|
|
6
|
+
Object.defineProperty(exports, "generate", { enumerable: true, get: function () { return generate_1.generate; } });
|
|
7
|
+
var embed_1 = require("./embed");
|
|
8
|
+
Object.defineProperty(exports, "embed", { enumerable: true, get: function () { return embed_1.embed; } });
|
|
9
|
+
var detect_1 = require("./detect");
|
|
10
|
+
Object.defineProperty(exports, "detect", { enumerable: true, get: function () { return detect_1.detect; } });
|
|
11
|
+
var canary_1 = require("./canary");
|
|
12
|
+
Object.defineProperty(exports, "verify", { enumerable: true, get: function () { return canary_1.verify; } });
|
|
13
|
+
Object.defineProperty(exports, "createCanary", { enumerable: true, get: function () { return canary_1.createCanary; } });
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,oEAAoE;AACpE,uCAAqC;AAA5B,oGAAA,QAAQ,OAAA;AACjB,iCAA+B;AAAtB,8FAAA,KAAK,OAAA;AACd,mCAAiC;AAAxB,gGAAA,MAAM,OAAA;AACf,mCAA+C;AAAtC,gGAAA,MAAM,OAAA;AAAE,sGAAA,YAAY,OAAA"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export type CanaryType = 'zero-width' | 'homoglyph' | 'whitespace' | 'custom';
|
|
2
|
+
export type Confidence = 'high' | 'medium' | 'low';
|
|
3
|
+
export interface CanaryToken {
|
|
4
|
+
type: CanaryType;
|
|
5
|
+
payload: string;
|
|
6
|
+
createdAt: string;
|
|
7
|
+
}
|
|
8
|
+
export interface GenerateOptions {
|
|
9
|
+
payload?: string;
|
|
10
|
+
type?: CanaryType;
|
|
11
|
+
}
|
|
12
|
+
export interface EmbedOptions {
|
|
13
|
+
position?: 'start' | 'end' | 'after-first-sentence' | 'before-last-sentence' | 'random';
|
|
14
|
+
}
|
|
15
|
+
export interface DetectedToken {
|
|
16
|
+
type: CanaryType;
|
|
17
|
+
payload: string;
|
|
18
|
+
confidence: Confidence;
|
|
19
|
+
checksumValid: boolean;
|
|
20
|
+
position: {
|
|
21
|
+
start: number;
|
|
22
|
+
end: number;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export interface DetectionResult {
|
|
26
|
+
found: boolean;
|
|
27
|
+
tokens: DetectedToken[];
|
|
28
|
+
durationMs: number;
|
|
29
|
+
}
|
|
30
|
+
export interface DetectOptions {
|
|
31
|
+
types?: CanaryType[];
|
|
32
|
+
minConfidence?: Confidence;
|
|
33
|
+
}
|
|
34
|
+
export interface CanaryConfig {
|
|
35
|
+
type?: CanaryType;
|
|
36
|
+
payload?: string;
|
|
37
|
+
position?: EmbedOptions['position'];
|
|
38
|
+
}
|
|
39
|
+
export interface Canary {
|
|
40
|
+
token: CanaryToken;
|
|
41
|
+
embed(prompt: string, options?: EmbedOptions): string;
|
|
42
|
+
detect(text: string, options?: DetectOptions): DetectionResult;
|
|
43
|
+
verify(text: string): boolean;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,YAAY,GAAG,WAAW,GAAG,YAAY,GAAG,QAAQ,CAAA;AAC7E,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAA;AAElD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,UAAU,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,UAAU,CAAA;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG,sBAAsB,GAAG,sBAAsB,GAAG,QAAQ,CAAA;CACxF;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,UAAU,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,UAAU,CAAA;IACtB,aAAa,EAAE,OAAO,CAAA;IACtB,QAAQ,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;CACzC;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,OAAO,CAAA;IACd,MAAM,EAAE,aAAa,EAAE,CAAA;IACvB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,UAAU,EAAE,CAAA;IACpB,aAAa,CAAC,EAAE,UAAU,CAAA;CAC3B;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,YAAY,CAAC,UAAU,CAAC,CAAA;CACpC;AAED,MAAM,WAAW,MAAM;IACrB,KAAK,EAAE,WAAW,CAAA;IAClB,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,MAAM,CAAA;IACrD,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,eAAe,CAAA;IAC9D,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;CAC9B"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "llm-canary",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Invisible canary tokens for prompt leakage detection",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"test": "vitest run",
|
|
13
|
+
"lint": "eslint src/",
|
|
14
|
+
"prepublishOnly": "npm run build"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [],
|
|
17
|
+
"author": "",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=18"
|
|
21
|
+
},
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/node": "^25.5.0",
|
|
27
|
+
"@typescript-eslint/eslint-plugin": "^8.57.1",
|
|
28
|
+
"@typescript-eslint/parser": "^8.57.1",
|
|
29
|
+
"eslint": "^8.57.1",
|
|
30
|
+
"typescript": "^5.9.3",
|
|
31
|
+
"vitest": "^4.1.0"
|
|
32
|
+
}
|
|
33
|
+
}
|