@7one/vite-agent-poisoner 0.0.0-stage → 0.1.1
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 +66 -2
- package/dist/index.d.ts +14 -0
- package/dist/index.js +131 -0
- package/package.json +41 -3
package/README.md
CHANGED
|
@@ -1,3 +1,67 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @7one/vite-agent-poisoner
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Vite plugin for LLM agent evasion. It obfuscates emitted chunks with an
|
|
4
|
+
adversarial vocabulary, uglifies expressions, and injects sentences as function
|
|
5
|
+
names and code comments.
|
|
6
|
+
|
|
7
|
+
The plugin is a thin adapter. All obfuscation runs in
|
|
8
|
+
[`@7one/agent-poisoner-core`](../core/README.md).
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
pnpm add -D @7one/vite-agent-poisoner
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Use
|
|
17
|
+
|
|
18
|
+
Zero-config with default adversarial vocabulary:
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
// vite.config.ts
|
|
22
|
+
import { agentPoisoner } from "@7one/vite-agent-poisoner";
|
|
23
|
+
import { defineConfig } from "vite";
|
|
24
|
+
|
|
25
|
+
export default defineConfig({
|
|
26
|
+
plugins: [agentPoisoner()],
|
|
27
|
+
});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Or pass options to customize:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
// vite.config.ts
|
|
34
|
+
import { agentPoisoner, vocabularyPath } from "@7one/vite-agent-poisoner";
|
|
35
|
+
import { defineConfig } from "vite";
|
|
36
|
+
|
|
37
|
+
export default defineConfig({
|
|
38
|
+
plugins: [
|
|
39
|
+
agentPoisoner({
|
|
40
|
+
// vocabulary defaults to vocabularyPath from @7one/agent-poisoner-vocabulary
|
|
41
|
+
vocabulary: vocabularyPath,
|
|
42
|
+
obfuscatorOptions: { renameGlobals: true },
|
|
43
|
+
}),
|
|
44
|
+
],
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The plugin runs only for `vite build`. It turns off `build.minify`, because a
|
|
49
|
+
minifier that runs after the obfuscator renames every identifier again.
|
|
50
|
+
|
|
51
|
+
## Options
|
|
52
|
+
|
|
53
|
+
Every core option passes through. The plugin adds `enabled`, `include`,
|
|
54
|
+
`exclude`, `concurrency`, and `sourcemap`. Read the
|
|
55
|
+
[core documentation](../core/README.md#options) for the full table.
|
|
56
|
+
|
|
57
|
+
| Option | Default | Meaning |
|
|
58
|
+
| :--- | :--- | :--- |
|
|
59
|
+
| `enabled` | `true` | Turn the plugin off without removing it |
|
|
60
|
+
| `include` / `exclude` | all `.js` and `.mjs` | Chunk selectors by name |
|
|
61
|
+
| `concurrency` | available parallelism, max 4 | Parallel obfuscation jobs |
|
|
62
|
+
| `sourcemap` | `"drop"` | `"drop"` warns and omits maps; `"fail"` stops the build |
|
|
63
|
+
|
|
64
|
+
## Note
|
|
65
|
+
|
|
66
|
+
Obfuscation grows the bundle. Numbers become arithmetic expressions and strings
|
|
67
|
+
are split. Measure the gzip size before you release.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Plugin } from "vite";
|
|
2
|
+
import { type ObfuscateOptions } from "@7one/agent-poisoner-core";
|
|
3
|
+
export type { CasingStyle, ChunkFilter, ChunkObfuscator, CompoundSpec, CoreOptions, ObfuscateOptions, ResolvedObfuscateOptions, VocabularyFile, VocabularySource, } from "@7one/agent-poisoner-core";
|
|
4
|
+
export { buildDictionary, createObfuscator, createRandom, DEFAULT_OBFUSCATOR_OPTIONS, estimateIdentifierBudget, hashString, injectRandomComments, loadObfuscator, loadVocabulary, normalizeWord, obfuscateCode, resolveOptions, sentenceToCamelCase, toCamelCase, toSnakeCase, } from "@7one/agent-poisoner-core";
|
|
5
|
+
export { vocabularyPath } from "@7one/agent-poisoner-vocabulary";
|
|
6
|
+
/** Decide whether one emitted chunk gets obfuscated. */
|
|
7
|
+
export declare function shouldObfuscate(fileName: string, options: Pick<ObfuscateOptions, "include" | "exclude">): boolean;
|
|
8
|
+
/** The plugin. Pass options to set the vocabulary and evasion settings. */
|
|
9
|
+
export declare function agentPoisoner(options?: ObfuscateOptions): Plugin;
|
|
10
|
+
/** Backward-compatible alias for `agentPoisoner`. */
|
|
11
|
+
export declare const agentEvasion: typeof agentPoisoner;
|
|
12
|
+
/** Backward-compatible alias for `agentPoisoner`. */
|
|
13
|
+
export declare const obfuscate: typeof agentPoisoner;
|
|
14
|
+
export default agentPoisoner;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite plugin for LLM agent evasion: obfuscates emitted chunks with a custom
|
|
3
|
+
* snake_case and camelCase vocabulary, uglifies expressions, and injects
|
|
4
|
+
* sentences as function names and code comments.
|
|
5
|
+
*
|
|
6
|
+
* The plugin is a thin adapter: the obfuscation lives in
|
|
7
|
+
* `@7one/agent-poisoner-core`.
|
|
8
|
+
*/
|
|
9
|
+
import { vocabularyPath } from "@7one/agent-poisoner-vocabulary";
|
|
10
|
+
import { createObfuscator, resolveOptions, } from "@7one/agent-poisoner-core";
|
|
11
|
+
export { buildDictionary, createObfuscator, createRandom, DEFAULT_OBFUSCATOR_OPTIONS, estimateIdentifierBudget, hashString, injectRandomComments, loadObfuscator, loadVocabulary, normalizeWord, obfuscateCode, resolveOptions, sentenceToCamelCase, toCamelCase, toSnakeCase, } from "@7one/agent-poisoner-core";
|
|
12
|
+
export { vocabularyPath } from "@7one/agent-poisoner-vocabulary";
|
|
13
|
+
const PLUGIN_NAME = "@7one/vite-agent-poisoner";
|
|
14
|
+
const LOG_PREFIX = `[${PLUGIN_NAME}]`;
|
|
15
|
+
/** Matches emitted JavaScript, and nothing else. */
|
|
16
|
+
const JS_CHUNK = /\.(?:mjs|js)$/;
|
|
17
|
+
function matchesFilter(filter, fileName) {
|
|
18
|
+
if (!filter) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
if (typeof filter === "function") {
|
|
22
|
+
return filter(fileName);
|
|
23
|
+
}
|
|
24
|
+
return (Array.isArray(filter) ? filter : [filter]).some((pattern) => pattern.test(fileName));
|
|
25
|
+
}
|
|
26
|
+
/** Decide whether one emitted chunk gets obfuscated. */
|
|
27
|
+
export function shouldObfuscate(fileName, options) {
|
|
28
|
+
if (matchesFilter(options.exclude, fileName)) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
if (!JS_CHUNK.test(fileName)) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
return matchesFilter(options.include, fileName) ?? true;
|
|
35
|
+
}
|
|
36
|
+
/** Bounded parallelism: obfuscation is CPU heavy and chunks arrive together. */
|
|
37
|
+
function createLimiter(max) {
|
|
38
|
+
let active = 0;
|
|
39
|
+
const waiting = [];
|
|
40
|
+
return async (task) => {
|
|
41
|
+
if (active >= max) {
|
|
42
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
43
|
+
waiting.push(resolve);
|
|
44
|
+
await promise;
|
|
45
|
+
}
|
|
46
|
+
active += 1;
|
|
47
|
+
try {
|
|
48
|
+
return await task();
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
active -= 1;
|
|
52
|
+
waiting.shift()?.();
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/** The plugin. Pass options to set the vocabulary and evasion settings. */
|
|
57
|
+
export function agentPoisoner(options = {}) {
|
|
58
|
+
const settings = resolveOptions({
|
|
59
|
+
...options,
|
|
60
|
+
vocabulary: options.vocabulary ?? vocabularyPath,
|
|
61
|
+
});
|
|
62
|
+
const limit = createLimiter(settings.concurrency);
|
|
63
|
+
let config;
|
|
64
|
+
let minifyDisabled = false;
|
|
65
|
+
let core;
|
|
66
|
+
let warnSink = () => { };
|
|
67
|
+
return {
|
|
68
|
+
name: PLUGIN_NAME,
|
|
69
|
+
apply: "build",
|
|
70
|
+
enforce: "post",
|
|
71
|
+
config(userConfig) {
|
|
72
|
+
if (!settings.enabled || userConfig.build?.minify === false) {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
minifyDisabled = true;
|
|
76
|
+
return {
|
|
77
|
+
build: {
|
|
78
|
+
minify: false,
|
|
79
|
+
cssMinify: userConfig.build?.cssMinify ?? true,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
configResolved(resolved) {
|
|
84
|
+
config = resolved;
|
|
85
|
+
if (minifyDisabled) {
|
|
86
|
+
resolved.logger.info(`${LOG_PREFIX} build.minify is off: the obfuscator compacts the output, and a later ` +
|
|
87
|
+
"minifier would rename every obfuscated identifier.");
|
|
88
|
+
}
|
|
89
|
+
if (settings.obfuscatorOptions.identifierNamesGenerator !== "dictionary") {
|
|
90
|
+
resolved.logger.warn(`${LOG_PREFIX} identifierNamesGenerator is "${String(settings.obfuscatorOptions.identifierNamesGenerator)}", ` +
|
|
91
|
+
"so the custom vocabulary is not used.");
|
|
92
|
+
}
|
|
93
|
+
if (resolved.build.sourcemap) {
|
|
94
|
+
if (settings.sourcemap === "fail") {
|
|
95
|
+
throw new Error(`${LOG_PREFIX} build.sourcemap is on, and source maps cannot survive obfuscation. ` +
|
|
96
|
+
'Turn source maps off or set sourcemap: "drop".');
|
|
97
|
+
}
|
|
98
|
+
resolved.logger.warn(`${LOG_PREFIX} source maps are dropped for obfuscated chunks.`);
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
async renderChunk(code, chunk) {
|
|
102
|
+
if (!settings.enabled || !shouldObfuscate(chunk.fileName, options)) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
warnSink = (message) => {
|
|
106
|
+
this.warn(message);
|
|
107
|
+
};
|
|
108
|
+
core ??= createObfuscator({
|
|
109
|
+
...options,
|
|
110
|
+
...(settings.vocabulary ? { vocabulary: settings.vocabulary } : {}),
|
|
111
|
+
root: config?.root ?? process.cwd(),
|
|
112
|
+
onWarn: (message) => warnSink(message),
|
|
113
|
+
});
|
|
114
|
+
const run = core;
|
|
115
|
+
try {
|
|
116
|
+
const obfuscated = await limit(() => run(code, chunk.fileName));
|
|
117
|
+
this.info(`${LOG_PREFIX} ${chunk.fileName}: ${code.length} -> ${obfuscated.length} bytes`);
|
|
118
|
+
return { code: obfuscated, map: null };
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
122
|
+
this.error(`${LOG_PREFIX} ${chunk.fileName}: ${reason}`);
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/** Backward-compatible alias for `agentPoisoner`. */
|
|
128
|
+
export const agentEvasion = agentPoisoner;
|
|
129
|
+
/** Backward-compatible alias for `agentPoisoner`. */
|
|
130
|
+
export const obfuscate = agentPoisoner;
|
|
131
|
+
export default agentPoisoner;
|
package/package.json
CHANGED
|
@@ -1,6 +1,44 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@7one/vite-agent-poisoner",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Vite plugin for LLM agent evasion: adversarial vocabulary, uglification, and comment injection.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=24"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"main": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"sideEffects": false,
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@7one/agent-poisoner-vocabulary": "0.1.1",
|
|
28
|
+
"@7one/agent-poisoner-core": "0.1.1"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"vite": ">=6"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "^24.19.2",
|
|
35
|
+
"typescript": "^7.0.2",
|
|
36
|
+
"vite": "^8.3.5"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsc -p tsconfig.build.json",
|
|
40
|
+
"typecheck": "tsc --noEmit",
|
|
41
|
+
"test": "node --test \"test/*.test.ts\"",
|
|
42
|
+
"clean": "rm -rf dist"
|
|
43
|
+
}
|
|
6
44
|
}
|