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,8 @@
|
|
|
1
|
+
import { codeRules } from './code.js';
|
|
2
|
+
import { configRules } from './config.js';
|
|
3
|
+
import { secretRules } from './secrets.js';
|
|
4
|
+
import { textRules } from './text.js';
|
|
5
|
+
export const allRules = [...textRules, ...codeRules, ...secretRules, ...configRules];
|
|
6
|
+
export function getRule(id) {
|
|
7
|
+
return allRules.find((rule) => rule.id === id);
|
|
8
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hardcoded-credential rules. TM-SEC-001 reports known credential formats
|
|
3
|
+
* (via the shared secret patterns); TM-SEC-002 catches generic high-entropy
|
|
4
|
+
* values assigned to secret-looking names. Both skip obvious placeholders and
|
|
5
|
+
* documented examples so they stay quiet on templates and READMEs.
|
|
6
|
+
*
|
|
7
|
+
* Messages never contain the secret itself; the engine redacts snippets, and
|
|
8
|
+
* these rules name only the credential type.
|
|
9
|
+
*/
|
|
10
|
+
import { findSecrets } from '../patterns/secrets.js';
|
|
11
|
+
import { findAll } from '../util/text.js';
|
|
12
|
+
/** Secret rules apply to every kind except live tool definitions (the proxy guards those). */
|
|
13
|
+
const SECRET_KINDS = [
|
|
14
|
+
'skill',
|
|
15
|
+
'instructions',
|
|
16
|
+
'docs',
|
|
17
|
+
'script',
|
|
18
|
+
'mcp-config',
|
|
19
|
+
'agent-settings',
|
|
20
|
+
'plugin-manifest',
|
|
21
|
+
'package-manifest',
|
|
22
|
+
];
|
|
23
|
+
/** Signals that a literal is a placeholder, example or environment reference rather than a real secret. */
|
|
24
|
+
const PLACEHOLDER = /example|your[_-]?|changeme|change[_-]?this|placeholder|dummy|sample|redacted|insert[_-]?|replace[_-]?|token[_-]?here|key[_-]?here|xxxxx|\.\.\.|\$\{|\$\(|<[^>]{1,40}>|%[a-z_]+%|os\.environ|process\.env|getenv/i;
|
|
25
|
+
/** True when a secret literal looks like a template value, not a live credential. */
|
|
26
|
+
function isPlaceholder(value) {
|
|
27
|
+
if (PLACEHOLDER.test(value))
|
|
28
|
+
return true;
|
|
29
|
+
if (/^[x0*.\-_]+$/i.test(value))
|
|
30
|
+
return true;
|
|
31
|
+
const alnum = value.replace(/[^A-Za-z0-9]/g, '');
|
|
32
|
+
if (alnum.length > 0 && new Set(alnum.toLowerCase()).size <= 3)
|
|
33
|
+
return true;
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* True when a BEGIN PRIVATE KEY header is followed by actual key material.
|
|
38
|
+
* A bare header (a test fixture, a detection pattern, docs) is not a leak.
|
|
39
|
+
*/
|
|
40
|
+
function hasKeyBody(content, from) {
|
|
41
|
+
const after = content.slice(from, from + 400).replace(/\\n|\\r|\s/g, '');
|
|
42
|
+
return /[A-Za-z0-9+/=]{60}/.test(after);
|
|
43
|
+
}
|
|
44
|
+
/** Shannon entropy in bits per character. */
|
|
45
|
+
function shannonEntropy(s) {
|
|
46
|
+
if (s.length === 0)
|
|
47
|
+
return 0;
|
|
48
|
+
const freq = new Map();
|
|
49
|
+
for (const ch of s)
|
|
50
|
+
freq.set(ch, (freq.get(ch) ?? 0) + 1);
|
|
51
|
+
let bits = 0;
|
|
52
|
+
for (const count of freq.values()) {
|
|
53
|
+
const p = count / s.length;
|
|
54
|
+
bits -= p * Math.log2(p);
|
|
55
|
+
}
|
|
56
|
+
return bits;
|
|
57
|
+
}
|
|
58
|
+
const secHardcoded = {
|
|
59
|
+
id: 'TM-SEC-001',
|
|
60
|
+
title: 'Hardcoded credential',
|
|
61
|
+
description: 'A live-looking credential (API key, token or private key block) is embedded in the file. Committed secrets leak to anyone who reads the skill or its repository.',
|
|
62
|
+
category: 'secrets',
|
|
63
|
+
severity: 'high',
|
|
64
|
+
kinds: SECRET_KINDS,
|
|
65
|
+
remediation: 'Move the secret to an environment variable or secret manager and rotate the exposed value.',
|
|
66
|
+
check(file) {
|
|
67
|
+
const matches = [];
|
|
68
|
+
for (const hit of findSecrets(file.content, 50)) {
|
|
69
|
+
const value = file.content.slice(hit.index, hit.index + hit.length);
|
|
70
|
+
if (hit.patternId === 'private-key' && !hasKeyBody(file.content, hit.index + hit.length))
|
|
71
|
+
continue;
|
|
72
|
+
if (hit.patternId !== 'private-key' && isPlaceholder(value))
|
|
73
|
+
continue;
|
|
74
|
+
matches.push({
|
|
75
|
+
index: hit.index,
|
|
76
|
+
severity: hit.patternId === 'private-key' ? 'critical' : 'high',
|
|
77
|
+
message: `Hardcoded credential: ${hit.name}`,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return matches;
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
const ASSIGN = /\b(api[_-]?key|apikey|secret|client[_-]?secret|access[_-]?token|auth[_-]?token|refresh[_-]?token|bearer[_-]?token|token|password|passwd|pwd|passphrase|private[_-]?key)\b\s*[:=]{1,2}\s*["'`]([A-Za-z0-9_\-+./=~]{16,200})["'`]/i;
|
|
84
|
+
const secAssignment = {
|
|
85
|
+
id: 'TM-SEC-002',
|
|
86
|
+
title: 'Secret-like assignment',
|
|
87
|
+
description: 'A secret-looking name (password, api_key, token) is assigned a long, high-entropy literal. Even without a known vendor prefix this is very likely a hardcoded credential.',
|
|
88
|
+
category: 'secrets',
|
|
89
|
+
severity: 'medium',
|
|
90
|
+
kinds: SECRET_KINDS,
|
|
91
|
+
remediation: 'Load the value from the environment or a secret manager instead of hardcoding it.',
|
|
92
|
+
check(file) {
|
|
93
|
+
const matches = [];
|
|
94
|
+
for (const h of findAll(ASSIGN, file.content, 50)) {
|
|
95
|
+
const value = h.groups[1];
|
|
96
|
+
if (!value)
|
|
97
|
+
continue;
|
|
98
|
+
if (isPlaceholder(value))
|
|
99
|
+
continue;
|
|
100
|
+
if (shannonEntropy(value) <= 3.5)
|
|
101
|
+
continue;
|
|
102
|
+
const name = (h.groups[0] ?? 'secret').toLowerCase();
|
|
103
|
+
matches.push({ index: h.index, message: `High-entropy value assigned to "${name}" (likely a hardcoded secret)` });
|
|
104
|
+
}
|
|
105
|
+
return matches;
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
export const secretRules = [secHardcoded, secAssignment];
|