toolmoat 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +302 -2
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +80 -0
- package/dist/clients/configs.d.ts +124 -0
- package/dist/clients/configs.js +403 -0
- package/dist/commands/approve.d.ts +15 -0
- package/dist/commands/approve.js +128 -0
- package/dist/commands/diff.d.ts +6 -0
- package/dist/commands/diff.js +114 -0
- package/dist/commands/pins.d.ts +18 -0
- package/dist/commands/pins.js +88 -0
- package/dist/commands/proxy.d.ts +24 -0
- package/dist/commands/proxy.js +144 -0
- package/dist/commands/rules.d.ts +15 -0
- package/dist/commands/rules.js +173 -0
- package/dist/commands/scan.d.ts +57 -0
- package/dist/commands/scan.js +323 -0
- package/dist/commands/unwrap.d.ts +5 -0
- package/dist/commands/unwrap.js +69 -0
- package/dist/commands/wrap.d.ts +131 -0
- package/dist/commands/wrap.js +518 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +13 -0
- package/dist/patterns/indicators.d.ts +41 -0
- package/dist/patterns/indicators.js +85 -0
- package/dist/patterns/secrets.d.ts +20 -0
- package/dist/patterns/secrets.js +70 -0
- package/dist/proxy/audit.d.ts +53 -0
- package/dist/proxy/audit.js +79 -0
- package/dist/proxy/canonical.d.ts +13 -0
- package/dist/proxy/canonical.js +34 -0
- package/dist/proxy/diff.d.ts +22 -0
- package/dist/proxy/diff.js +124 -0
- package/dist/proxy/fingerprint.d.ts +34 -0
- package/dist/proxy/fingerprint.js +140 -0
- package/dist/proxy/firewall.d.ts +132 -0
- package/dist/proxy/firewall.js +665 -0
- package/dist/proxy/framing.d.ts +45 -0
- package/dist/proxy/framing.js +86 -0
- package/dist/proxy/lockfile.d.ts +58 -0
- package/dist/proxy/lockfile.js +258 -0
- package/dist/proxy/names.d.ts +27 -0
- package/dist/proxy/names.js +175 -0
- package/dist/proxy/policy.d.ts +72 -0
- package/dist/proxy/policy.js +86 -0
- package/dist/proxy/proxy.d.ts +18 -0
- package/dist/proxy/proxy.js +237 -0
- package/dist/proxy/spawn.d.ts +51 -0
- package/dist/proxy/spawn.js +110 -0
- package/dist/report/index.d.ts +35 -0
- package/dist/report/index.js +25 -0
- package/dist/report/json.d.ts +22 -0
- package/dist/report/json.js +40 -0
- package/dist/report/markdown.d.ts +12 -0
- package/dist/report/markdown.js +87 -0
- package/dist/report/sarif.d.ts +89 -0
- package/dist/report/sarif.js +142 -0
- package/dist/report/text.d.ts +16 -0
- package/dist/report/text.js +206 -0
- package/dist/rules/code.d.ts +15 -0
- package/dist/rules/code.js +729 -0
- package/dist/rules/config.d.ts +3 -0
- package/dist/rules/config.js +2164 -0
- package/dist/rules/index.d.ts +3 -0
- package/dist/rules/index.js +8 -0
- package/dist/rules/secrets.d.ts +2 -0
- package/dist/rules/secrets.js +108 -0
- package/dist/rules/text.d.ts +2 -0
- package/dist/rules/text.js +1698 -0
- package/dist/scan/discover.d.ts +37 -0
- package/dist/scan/discover.js +525 -0
- package/dist/scan/engine.d.ts +24 -0
- package/dist/scan/engine.js +111 -0
- package/dist/scan/ignore.d.ts +28 -0
- package/dist/scan/ignore.js +46 -0
- package/dist/types.d.ts +98 -0
- package/dist/types.js +25 -0
- package/dist/util/colors.d.ts +12 -0
- package/dist/util/colors.js +24 -0
- package/dist/util/frontmatter.d.ts +20 -0
- package/dist/util/frontmatter.js +136 -0
- package/dist/util/glob.d.ts +7 -0
- package/dist/util/glob.js +44 -0
- package/dist/util/hash.d.ts +3 -0
- package/dist/util/hash.js +24 -0
- package/dist/util/jsonc.d.ts +14 -0
- package/dist/util/jsonc.js +113 -0
- package/dist/util/paths.d.ts +9 -0
- package/dist/util/paths.js +37 -0
- package/dist/util/text.d.ts +29 -0
- package/dist/util/text.js +96 -0
- package/dist/util/toml-lite.d.ts +40 -0
- package/dist/util/toml-lite.js +507 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +1 -0
- package/package.json +57 -4
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* toml-lite: a small, defensive TOML reader for the TOML files toolmoat
|
|
3
|
+
* inspects, mainly Codex's ~/.codex/config.toml and pyproject.toml.
|
|
4
|
+
*
|
|
5
|
+
* Supported: [tables], [[arrays of tables]], dotted and quoted keys, basic and
|
|
6
|
+
* literal strings (single and multi-line, with escapes), integers (decimal,
|
|
7
|
+
* hex, octal, binary), floats, booleans, dates and times (kept as their
|
|
8
|
+
* source text), arrays (multi-line, nested, trailing commas) and inline
|
|
9
|
+
* tables. A statement it cannot read is skipped and listed in `errors`, so a
|
|
10
|
+
* malformed or hostile file never throws. It runs in linear time with bounded
|
|
11
|
+
* nesting because scanned files are attacker-controlled.
|
|
12
|
+
*
|
|
13
|
+
* Not a validator: duplicate keys overwrite, and integers beyond 2^53 lose
|
|
14
|
+
* precision.
|
|
15
|
+
*/
|
|
16
|
+
const MAX_DEPTH = 32;
|
|
17
|
+
const MAX_KEY_PARTS = 64;
|
|
18
|
+
const BARE_KEY = /^[A-Za-z0-9_-]+$/;
|
|
19
|
+
/**
|
|
20
|
+
* Joins a key path the way `TomlDocument.lines` is keyed: bare segments as
|
|
21
|
+
* they are, other segments JSON-quoted, array indexes as numbers.
|
|
22
|
+
* joinKeyPath(['mcp_servers', 'my.server', 'args', 0]) === 'mcp_servers."my.server".args.0'
|
|
23
|
+
*/
|
|
24
|
+
export function joinKeyPath(segments) {
|
|
25
|
+
return segments.map((s) => (typeof s === 'number' || BARE_KEY.test(s) ? String(s) : JSON.stringify(s))).join('.');
|
|
26
|
+
}
|
|
27
|
+
/** Parses TOML text. Never throws; unreadable statements are skipped and reported in `errors`. */
|
|
28
|
+
export function parseToml(text) {
|
|
29
|
+
return new TomlParser(text).parse();
|
|
30
|
+
}
|
|
31
|
+
class TomlSyntaxError extends Error {
|
|
32
|
+
}
|
|
33
|
+
function isTable(value) {
|
|
34
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
35
|
+
}
|
|
36
|
+
function getOwn(table, key) {
|
|
37
|
+
return Object.hasOwn(table, key) ? table[key] : undefined;
|
|
38
|
+
}
|
|
39
|
+
function setOwn(table, key, value) {
|
|
40
|
+
Object.defineProperty(table, key, { value, writable: true, enumerable: true, configurable: true });
|
|
41
|
+
}
|
|
42
|
+
function isBareKeyChar(code) {
|
|
43
|
+
return ((code >= 0x30 && code <= 0x39) || (code >= 0x41 && code <= 0x5a) || (code >= 0x61 && code <= 0x7a) || code === 0x5f || code === 0x2d);
|
|
44
|
+
}
|
|
45
|
+
/** Characters that can appear in an unquoted value: numbers, booleans, dates, inf and nan. */
|
|
46
|
+
function isScalarChar(code) {
|
|
47
|
+
return isBareKeyChar(code) || code === 0x2b || code === 0x2e || code === 0x3a;
|
|
48
|
+
}
|
|
49
|
+
function shorten(text) {
|
|
50
|
+
return text.length > 40 ? `${text.slice(0, 39)}…` : text;
|
|
51
|
+
}
|
|
52
|
+
class TomlParser {
|
|
53
|
+
text;
|
|
54
|
+
lineStarts = [0];
|
|
55
|
+
root = {};
|
|
56
|
+
lines = new Map();
|
|
57
|
+
errors = [];
|
|
58
|
+
pos = 0;
|
|
59
|
+
current;
|
|
60
|
+
currentPath = [];
|
|
61
|
+
/** True after a broken table header, so the keys under it are parsed but not recorded. */
|
|
62
|
+
detached = false;
|
|
63
|
+
constructor(text) {
|
|
64
|
+
this.text = text;
|
|
65
|
+
this.current = this.root;
|
|
66
|
+
for (let i = text.indexOf('\n'); i !== -1; i = text.indexOf('\n', i + 1))
|
|
67
|
+
this.lineStarts.push(i + 1);
|
|
68
|
+
}
|
|
69
|
+
parse() {
|
|
70
|
+
const { text } = this;
|
|
71
|
+
if (text.charCodeAt(0) === 0xfeff)
|
|
72
|
+
this.pos = 1;
|
|
73
|
+
for (;;) {
|
|
74
|
+
this.skipBlank();
|
|
75
|
+
if (this.pos >= text.length)
|
|
76
|
+
break;
|
|
77
|
+
const header = text[this.pos] === '[';
|
|
78
|
+
try {
|
|
79
|
+
if (header)
|
|
80
|
+
this.parseHeader();
|
|
81
|
+
else
|
|
82
|
+
this.parseKeyValue();
|
|
83
|
+
this.expectLineEnd();
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
this.errors.push({ line: this.lineOf(this.pos), message: error instanceof Error ? error.message : String(error) });
|
|
87
|
+
if (header) {
|
|
88
|
+
this.current = {};
|
|
89
|
+
this.currentPath = [];
|
|
90
|
+
this.detached = true;
|
|
91
|
+
}
|
|
92
|
+
this.skipLine();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return { data: this.root, lines: this.lines, errors: this.errors };
|
|
96
|
+
}
|
|
97
|
+
// --- statements ---------------------------------------------------------
|
|
98
|
+
parseHeader() {
|
|
99
|
+
const line = this.lineOf(this.pos);
|
|
100
|
+
const isArray = this.text.startsWith('[[', this.pos);
|
|
101
|
+
this.pos += isArray ? 2 : 1;
|
|
102
|
+
const keys = this.parseKey();
|
|
103
|
+
this.skipWs();
|
|
104
|
+
const close = isArray ? ']]' : ']';
|
|
105
|
+
if (!this.text.startsWith(close, this.pos))
|
|
106
|
+
throw new TomlSyntaxError(`expected ${close} after table name`);
|
|
107
|
+
this.pos += close.length;
|
|
108
|
+
if (isArray) {
|
|
109
|
+
const parent = this.descend(this.root, [], keys.slice(0, -1));
|
|
110
|
+
const last = keys[keys.length - 1];
|
|
111
|
+
let list = getOwn(parent.table, last);
|
|
112
|
+
if (list === undefined) {
|
|
113
|
+
list = [];
|
|
114
|
+
setOwn(parent.table, last, list);
|
|
115
|
+
}
|
|
116
|
+
if (!Array.isArray(list))
|
|
117
|
+
throw new TomlSyntaxError(`${shorten(last)} is not an array of tables`);
|
|
118
|
+
const table = {};
|
|
119
|
+
list.push(table);
|
|
120
|
+
this.detached = false;
|
|
121
|
+
this.record([...parent.path, last], line, false);
|
|
122
|
+
this.current = table;
|
|
123
|
+
this.currentPath = [...parent.path, last, list.length - 1];
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
const target = this.descend(this.root, [], keys);
|
|
127
|
+
this.detached = false;
|
|
128
|
+
this.current = target.table;
|
|
129
|
+
this.currentPath = target.path;
|
|
130
|
+
}
|
|
131
|
+
this.record(this.currentPath, line);
|
|
132
|
+
}
|
|
133
|
+
parseKeyValue() {
|
|
134
|
+
this.parsePair(this.current, this.currentPath, 0);
|
|
135
|
+
}
|
|
136
|
+
/** Parses `key = value` into `table`, whose own path is `basePath`. */
|
|
137
|
+
parsePair(table, basePath, depth) {
|
|
138
|
+
const line = this.lineOf(this.pos);
|
|
139
|
+
const keys = this.parseKey();
|
|
140
|
+
this.skipWs();
|
|
141
|
+
if (this.text[this.pos] !== '=')
|
|
142
|
+
throw new TomlSyntaxError('expected = after key');
|
|
143
|
+
this.pos++;
|
|
144
|
+
this.skipWs();
|
|
145
|
+
const parent = this.descend(table, basePath, keys.slice(0, -1));
|
|
146
|
+
for (let n = basePath.length + 1; n <= parent.path.length; n++)
|
|
147
|
+
this.record(parent.path.slice(0, n), line, false);
|
|
148
|
+
const key = keys[keys.length - 1];
|
|
149
|
+
const valuePath = [...parent.path, key];
|
|
150
|
+
const value = this.parseValue(valuePath, depth);
|
|
151
|
+
setOwn(parent.table, key, value);
|
|
152
|
+
this.record(valuePath, line);
|
|
153
|
+
}
|
|
154
|
+
/** Walks (and creates) nested tables; for an array of tables it enters the last element, as TOML does. */
|
|
155
|
+
descend(table, basePath, keys) {
|
|
156
|
+
let current = table;
|
|
157
|
+
const path = [...basePath];
|
|
158
|
+
for (const key of keys) {
|
|
159
|
+
let next = getOwn(current, key);
|
|
160
|
+
if (next === undefined) {
|
|
161
|
+
next = {};
|
|
162
|
+
setOwn(current, key, next);
|
|
163
|
+
}
|
|
164
|
+
if (Array.isArray(next)) {
|
|
165
|
+
const index = next.length - 1;
|
|
166
|
+
const last = next[index];
|
|
167
|
+
if (!isTable(last))
|
|
168
|
+
throw new TomlSyntaxError(`${shorten(key)} is an array, not a table`);
|
|
169
|
+
path.push(key, index);
|
|
170
|
+
current = last;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (!isTable(next))
|
|
174
|
+
throw new TomlSyntaxError(`${shorten(key)} is already a value, not a table`);
|
|
175
|
+
path.push(key);
|
|
176
|
+
current = next;
|
|
177
|
+
}
|
|
178
|
+
return { table: current, path };
|
|
179
|
+
}
|
|
180
|
+
parseKey() {
|
|
181
|
+
const keys = [];
|
|
182
|
+
for (;;) {
|
|
183
|
+
this.skipWs();
|
|
184
|
+
const ch = this.text[this.pos];
|
|
185
|
+
if (ch === '"')
|
|
186
|
+
keys.push(this.parseBasicString());
|
|
187
|
+
else if (ch === "'")
|
|
188
|
+
keys.push(this.parseLiteralString());
|
|
189
|
+
else {
|
|
190
|
+
const start = this.pos;
|
|
191
|
+
while (this.pos < this.text.length && isBareKeyChar(this.text.charCodeAt(this.pos)))
|
|
192
|
+
this.pos++;
|
|
193
|
+
if (this.pos === start)
|
|
194
|
+
throw new TomlSyntaxError('expected a key');
|
|
195
|
+
keys.push(this.text.slice(start, this.pos));
|
|
196
|
+
}
|
|
197
|
+
this.skipWs();
|
|
198
|
+
if (this.text[this.pos] !== '.')
|
|
199
|
+
return keys;
|
|
200
|
+
if (keys.length >= MAX_KEY_PARTS)
|
|
201
|
+
throw new TomlSyntaxError('key has too many parts');
|
|
202
|
+
this.pos++;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// --- values -------------------------------------------------------------
|
|
206
|
+
parseValue(path, depth) {
|
|
207
|
+
if (depth > MAX_DEPTH)
|
|
208
|
+
throw new TomlSyntaxError('value is nested too deeply');
|
|
209
|
+
const { text, pos } = this;
|
|
210
|
+
switch (text[pos]) {
|
|
211
|
+
case '"':
|
|
212
|
+
return text.startsWith('"""', pos) ? this.parseMultilineBasicString() : this.parseBasicString();
|
|
213
|
+
case "'":
|
|
214
|
+
return text.startsWith("'''", pos) ? this.parseMultilineLiteralString() : this.parseLiteralString();
|
|
215
|
+
case '[':
|
|
216
|
+
return this.parseArray(path, depth);
|
|
217
|
+
case '{':
|
|
218
|
+
return this.parseInlineTable(path, depth);
|
|
219
|
+
default:
|
|
220
|
+
return this.parseScalar();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
parseArray(path, depth) {
|
|
224
|
+
this.pos++;
|
|
225
|
+
const out = [];
|
|
226
|
+
for (;;) {
|
|
227
|
+
this.skipBlank();
|
|
228
|
+
if (this.pos >= this.text.length)
|
|
229
|
+
throw new TomlSyntaxError('unterminated array');
|
|
230
|
+
if (this.text[this.pos] === ']') {
|
|
231
|
+
this.pos++;
|
|
232
|
+
return out;
|
|
233
|
+
}
|
|
234
|
+
const elementPath = [...path, out.length];
|
|
235
|
+
this.record(elementPath, this.lineOf(this.pos));
|
|
236
|
+
out.push(this.parseValue(elementPath, depth + 1));
|
|
237
|
+
this.skipBlank();
|
|
238
|
+
const ch = this.text[this.pos];
|
|
239
|
+
if (ch === ',')
|
|
240
|
+
this.pos++;
|
|
241
|
+
else if (ch === ']') {
|
|
242
|
+
this.pos++;
|
|
243
|
+
return out;
|
|
244
|
+
}
|
|
245
|
+
else
|
|
246
|
+
throw new TomlSyntaxError('expected , or ] in array');
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/** Inline tables. Newlines and a trailing comma are accepted, as in TOML 1.1. */
|
|
250
|
+
parseInlineTable(path, depth) {
|
|
251
|
+
this.pos++;
|
|
252
|
+
const table = {};
|
|
253
|
+
for (;;) {
|
|
254
|
+
this.skipBlank();
|
|
255
|
+
if (this.pos >= this.text.length)
|
|
256
|
+
throw new TomlSyntaxError('unterminated inline table');
|
|
257
|
+
if (this.text[this.pos] === '}') {
|
|
258
|
+
this.pos++;
|
|
259
|
+
return table;
|
|
260
|
+
}
|
|
261
|
+
this.parsePair(table, path, depth + 1);
|
|
262
|
+
this.skipBlank();
|
|
263
|
+
const ch = this.text[this.pos];
|
|
264
|
+
if (ch === ',')
|
|
265
|
+
this.pos++;
|
|
266
|
+
else if (ch === '}') {
|
|
267
|
+
this.pos++;
|
|
268
|
+
return table;
|
|
269
|
+
}
|
|
270
|
+
else
|
|
271
|
+
throw new TomlSyntaxError('expected , or } in inline table');
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
parseBasicString() {
|
|
275
|
+
const { text } = this;
|
|
276
|
+
this.pos++;
|
|
277
|
+
let out = '';
|
|
278
|
+
let chunk = this.pos;
|
|
279
|
+
while (this.pos < text.length) {
|
|
280
|
+
const ch = text[this.pos];
|
|
281
|
+
if (ch === '"') {
|
|
282
|
+
out += text.slice(chunk, this.pos);
|
|
283
|
+
this.pos++;
|
|
284
|
+
return out;
|
|
285
|
+
}
|
|
286
|
+
if (ch === '\n')
|
|
287
|
+
break;
|
|
288
|
+
if (ch === '\\') {
|
|
289
|
+
out += text.slice(chunk, this.pos);
|
|
290
|
+
out += this.parseEscape();
|
|
291
|
+
chunk = this.pos;
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
this.pos++;
|
|
295
|
+
}
|
|
296
|
+
throw new TomlSyntaxError('unterminated string');
|
|
297
|
+
}
|
|
298
|
+
parseMultilineBasicString() {
|
|
299
|
+
const { text } = this;
|
|
300
|
+
this.pos += 3;
|
|
301
|
+
this.skipOneNewline();
|
|
302
|
+
let out = '';
|
|
303
|
+
let chunk = this.pos;
|
|
304
|
+
while (this.pos < text.length) {
|
|
305
|
+
const ch = text[this.pos];
|
|
306
|
+
if (ch === '"' && text.startsWith('"""', this.pos)) {
|
|
307
|
+
let quotes = 3;
|
|
308
|
+
while (quotes < 5 && text[this.pos + quotes] === '"')
|
|
309
|
+
quotes++;
|
|
310
|
+
out += text.slice(chunk, this.pos) + '"'.repeat(quotes - 3);
|
|
311
|
+
this.pos += quotes;
|
|
312
|
+
return out;
|
|
313
|
+
}
|
|
314
|
+
if (ch === '\\') {
|
|
315
|
+
out += text.slice(chunk, this.pos);
|
|
316
|
+
let j = this.pos + 1;
|
|
317
|
+
while (text[j] === ' ' || text[j] === '\t')
|
|
318
|
+
j++;
|
|
319
|
+
if (text[j] === '\n' || (text[j] === '\r' && text[j + 1] === '\n')) {
|
|
320
|
+
this.pos = j;
|
|
321
|
+
while (this.pos < text.length && ' \t\r\n'.includes(text[this.pos]))
|
|
322
|
+
this.pos++;
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
out += this.parseEscape();
|
|
326
|
+
}
|
|
327
|
+
chunk = this.pos;
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
this.pos++;
|
|
331
|
+
}
|
|
332
|
+
throw new TomlSyntaxError('unterminated multi-line string');
|
|
333
|
+
}
|
|
334
|
+
parseLiteralString() {
|
|
335
|
+
const { text } = this;
|
|
336
|
+
const start = ++this.pos;
|
|
337
|
+
while (this.pos < text.length) {
|
|
338
|
+
const ch = text[this.pos];
|
|
339
|
+
if (ch === "'")
|
|
340
|
+
return text.slice(start, this.pos++);
|
|
341
|
+
if (ch === '\n')
|
|
342
|
+
break;
|
|
343
|
+
this.pos++;
|
|
344
|
+
}
|
|
345
|
+
throw new TomlSyntaxError('unterminated literal string');
|
|
346
|
+
}
|
|
347
|
+
parseMultilineLiteralString() {
|
|
348
|
+
const { text } = this;
|
|
349
|
+
this.pos += 3;
|
|
350
|
+
this.skipOneNewline();
|
|
351
|
+
const end = text.indexOf("'''", this.pos);
|
|
352
|
+
if (end === -1) {
|
|
353
|
+
this.pos = text.length;
|
|
354
|
+
throw new TomlSyntaxError('unterminated multi-line literal string');
|
|
355
|
+
}
|
|
356
|
+
let quotes = 3;
|
|
357
|
+
while (quotes < 5 && text[end + quotes] === "'")
|
|
358
|
+
quotes++;
|
|
359
|
+
const out = text.slice(this.pos, end) + "'".repeat(quotes - 3);
|
|
360
|
+
this.pos = end + quotes;
|
|
361
|
+
return out;
|
|
362
|
+
}
|
|
363
|
+
/** Reads one escape sequence starting at the backslash. */
|
|
364
|
+
parseEscape() {
|
|
365
|
+
const { text } = this;
|
|
366
|
+
const code = text[this.pos + 1];
|
|
367
|
+
this.pos += 2;
|
|
368
|
+
switch (code) {
|
|
369
|
+
case 'b':
|
|
370
|
+
return '\b';
|
|
371
|
+
case 't':
|
|
372
|
+
return '\t';
|
|
373
|
+
case 'n':
|
|
374
|
+
return '\n';
|
|
375
|
+
case 'f':
|
|
376
|
+
return '\f';
|
|
377
|
+
case 'r':
|
|
378
|
+
return '\r';
|
|
379
|
+
case 'e':
|
|
380
|
+
return '\u001b';
|
|
381
|
+
case '"':
|
|
382
|
+
return '"';
|
|
383
|
+
case '\\':
|
|
384
|
+
return '\\';
|
|
385
|
+
case 'x':
|
|
386
|
+
return this.parseCodePoint(2);
|
|
387
|
+
case 'u':
|
|
388
|
+
return this.parseCodePoint(4);
|
|
389
|
+
case 'U':
|
|
390
|
+
return this.parseCodePoint(8);
|
|
391
|
+
default:
|
|
392
|
+
throw new TomlSyntaxError(`invalid escape \\${code ?? ''}`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
parseCodePoint(digits) {
|
|
396
|
+
const hex = this.text.slice(this.pos, this.pos + digits);
|
|
397
|
+
if (hex.length !== digits || !/^[0-9A-Fa-f]+$/.test(hex))
|
|
398
|
+
throw new TomlSyntaxError('invalid unicode escape');
|
|
399
|
+
const value = parseInt(hex, 16);
|
|
400
|
+
if (value > 0x10ffff || (value >= 0xd800 && value <= 0xdfff))
|
|
401
|
+
throw new TomlSyntaxError('invalid unicode scalar');
|
|
402
|
+
this.pos += digits;
|
|
403
|
+
return String.fromCodePoint(value);
|
|
404
|
+
}
|
|
405
|
+
parseScalar() {
|
|
406
|
+
const { text } = this;
|
|
407
|
+
const start = this.pos;
|
|
408
|
+
while (this.pos < text.length && isScalarChar(text.charCodeAt(this.pos)))
|
|
409
|
+
this.pos++;
|
|
410
|
+
let token = text.slice(start, this.pos);
|
|
411
|
+
if (!token)
|
|
412
|
+
throw new TomlSyntaxError(`unexpected character ${JSON.stringify(text[this.pos] ?? '')}`);
|
|
413
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(token) && text[this.pos] === ' ' && /^\d{2}:/.test(text.slice(this.pos + 1, this.pos + 4))) {
|
|
414
|
+
const timeStart = ++this.pos;
|
|
415
|
+
while (this.pos < text.length && isScalarChar(text.charCodeAt(this.pos)))
|
|
416
|
+
this.pos++;
|
|
417
|
+
token += ` ${text.slice(timeStart, this.pos)}`;
|
|
418
|
+
}
|
|
419
|
+
return scalarValue(token);
|
|
420
|
+
}
|
|
421
|
+
// --- whitespace and positions ------------------------------------------
|
|
422
|
+
/** Skips spaces, tabs, newlines and comments. */
|
|
423
|
+
skipBlank() {
|
|
424
|
+
const { text } = this;
|
|
425
|
+
while (this.pos < text.length) {
|
|
426
|
+
const ch = text[this.pos];
|
|
427
|
+
if (ch === ' ' || ch === '\t' || ch === '\r' || ch === '\n')
|
|
428
|
+
this.pos++;
|
|
429
|
+
else if (ch === '#')
|
|
430
|
+
this.skipComment();
|
|
431
|
+
else
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
skipWs() {
|
|
436
|
+
while (this.text[this.pos] === ' ' || this.text[this.pos] === '\t')
|
|
437
|
+
this.pos++;
|
|
438
|
+
}
|
|
439
|
+
skipComment() {
|
|
440
|
+
const nl = this.text.indexOf('\n', this.pos);
|
|
441
|
+
this.pos = nl === -1 ? this.text.length : nl;
|
|
442
|
+
}
|
|
443
|
+
skipOneNewline() {
|
|
444
|
+
if (this.text[this.pos] === '\n')
|
|
445
|
+
this.pos++;
|
|
446
|
+
else if (this.text[this.pos] === '\r' && this.text[this.pos + 1] === '\n')
|
|
447
|
+
this.pos += 2;
|
|
448
|
+
}
|
|
449
|
+
skipLine() {
|
|
450
|
+
const nl = this.text.indexOf('\n', this.pos);
|
|
451
|
+
this.pos = nl === -1 ? this.text.length : nl + 1;
|
|
452
|
+
}
|
|
453
|
+
expectLineEnd() {
|
|
454
|
+
this.skipWs();
|
|
455
|
+
if (this.text[this.pos] === '#')
|
|
456
|
+
this.skipComment();
|
|
457
|
+
if (this.pos >= this.text.length)
|
|
458
|
+
return;
|
|
459
|
+
if (this.text[this.pos] === '\n')
|
|
460
|
+
this.pos++;
|
|
461
|
+
else if (this.text[this.pos] === '\r' && this.text[this.pos + 1] === '\n')
|
|
462
|
+
this.pos += 2;
|
|
463
|
+
else
|
|
464
|
+
throw new TomlSyntaxError('expected end of line');
|
|
465
|
+
}
|
|
466
|
+
lineOf(index) {
|
|
467
|
+
let lo = 0;
|
|
468
|
+
let hi = this.lineStarts.length - 1;
|
|
469
|
+
while (lo < hi) {
|
|
470
|
+
const mid = (lo + hi + 1) >> 1;
|
|
471
|
+
if (this.lineStarts[mid] <= index)
|
|
472
|
+
lo = mid;
|
|
473
|
+
else
|
|
474
|
+
hi = mid - 1;
|
|
475
|
+
}
|
|
476
|
+
return lo + 1;
|
|
477
|
+
}
|
|
478
|
+
record(path, line, overwrite = true) {
|
|
479
|
+
if (this.detached || path.length === 0)
|
|
480
|
+
return;
|
|
481
|
+
const key = joinKeyPath(path);
|
|
482
|
+
if (overwrite || !this.lines.has(key))
|
|
483
|
+
this.lines.set(key, line);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
function scalarValue(token) {
|
|
487
|
+
if (token === 'true')
|
|
488
|
+
return true;
|
|
489
|
+
if (token === 'false')
|
|
490
|
+
return false;
|
|
491
|
+
if (/^[+-]?inf$/.test(token))
|
|
492
|
+
return token.startsWith('-') ? -Infinity : Infinity;
|
|
493
|
+
if (/^[+-]?nan$/.test(token))
|
|
494
|
+
return NaN;
|
|
495
|
+
if (/^\d{4}-\d{2}-\d{2}/.test(token) || /^\d{2}:\d{2}/.test(token))
|
|
496
|
+
return token;
|
|
497
|
+
const plain = token.replace(/_/g, '');
|
|
498
|
+
if (/^0x[0-9A-Fa-f]+$/.test(plain))
|
|
499
|
+
return parseInt(plain.slice(2), 16);
|
|
500
|
+
if (/^0o[0-7]+$/.test(plain))
|
|
501
|
+
return parseInt(plain.slice(2), 8);
|
|
502
|
+
if (/^0b[01]+$/.test(plain))
|
|
503
|
+
return parseInt(plain.slice(2), 2);
|
|
504
|
+
if (/^[+-]?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?$/.test(plain))
|
|
505
|
+
return Number(plain);
|
|
506
|
+
throw new TomlSyntaxError(`invalid value ${JSON.stringify(shorten(token))}`);
|
|
507
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const VERSION = "0.1.0";
|
package/dist/version.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const VERSION = '0.1.0';
|
package/package.json
CHANGED
|
@@ -1,6 +1,59 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolmoat",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Local-first security scanner and runtime firewall for AI agent skills, plugins, hooks and MCP servers. No account, no cloud, no telemetry.",
|
|
5
|
+
"author": "Whitul",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/Whitul/toolmoat.git"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/Whitul/toolmoat#readme",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/Whitul/toolmoat/issues"
|
|
13
|
+
},
|
|
14
|
+
"type": "module",
|
|
15
|
+
"bin": {
|
|
16
|
+
"toolmoat": "dist/cli.js"
|
|
17
|
+
},
|
|
18
|
+
"main": "dist/index.js",
|
|
19
|
+
"types": "dist/index.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"default": "./dist/index.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"README.md",
|
|
29
|
+
"LICENSE"
|
|
30
|
+
],
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=20"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsc -p tsconfig.build.json",
|
|
36
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
37
|
+
"test": "vitest run",
|
|
38
|
+
"prepublishOnly": "npm run typecheck && npm test && npm run build"
|
|
39
|
+
},
|
|
40
|
+
"keywords": [
|
|
41
|
+
"mcp",
|
|
42
|
+
"model-context-protocol",
|
|
43
|
+
"ai-agents",
|
|
44
|
+
"agent-skills",
|
|
45
|
+
"claude-code",
|
|
46
|
+
"security",
|
|
47
|
+
"scanner",
|
|
48
|
+
"prompt-injection",
|
|
49
|
+
"tool-poisoning",
|
|
50
|
+
"supply-chain",
|
|
51
|
+
"sarif"
|
|
52
|
+
],
|
|
53
|
+
"license": "MIT",
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@types/node": "^22.20.5",
|
|
56
|
+
"typescript": "^7.0.2",
|
|
57
|
+
"vitest": "^5.0.5"
|
|
58
|
+
}
|
|
59
|
+
}
|