@kernel.chat/kbot 3.99.30 → 3.99.33
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 +1 -1
- package/dist/agents/security-agent.d.ts +31 -0
- package/dist/agents/security-agent.js +180 -0
- package/dist/agents/security-rules.d.ts +35 -0
- package/dist/agents/security-rules.js +206 -0
- package/dist/agents/specialists.d.ts +6 -0
- package/dist/agents/specialists.js +45 -0
- package/dist/architect.js +5 -0
- package/dist/auth.js +1 -1
- package/dist/channels/matrix.d.ts +4 -0
- package/dist/channels/matrix.js +28 -0
- package/dist/channels/office.d.ts +78 -0
- package/dist/channels/office.js +169 -0
- package/dist/channels/registry.d.ts +8 -0
- package/dist/channels/registry.js +38 -0
- package/dist/channels/signal.d.ts +4 -0
- package/dist/channels/signal.js +29 -0
- package/dist/channels/slack.d.ts +4 -0
- package/dist/channels/slack.js +97 -0
- package/dist/channels/teams.d.ts +4 -0
- package/dist/channels/teams.js +29 -0
- package/dist/channels/telegram.d.ts +4 -0
- package/dist/channels/telegram.js +28 -0
- package/dist/channels/types.d.ts +50 -0
- package/dist/channels/types.js +13 -0
- package/dist/channels/whatsapp.d.ts +4 -0
- package/dist/channels/whatsapp.js +28 -0
- package/dist/computer-use-coordinator.d.ts +44 -0
- package/dist/computer-use-coordinator.js +0 -0
- package/dist/doctor.js +6 -3
- package/dist/file-library.d.ts +76 -0
- package/dist/file-library.js +269 -0
- package/dist/managed-agents-anthropic.d.ts +90 -0
- package/dist/managed-agents-anthropic.js +123 -0
- package/dist/plugins-integrity.d.ts +72 -0
- package/dist/plugins-integrity.js +153 -0
- package/dist/plugins.d.ts +13 -2
- package/dist/plugins.js +87 -10
- package/dist/tools/anthropic-managed-agents-tools.d.ts +22 -0
- package/dist/tools/anthropic-managed-agents-tools.js +191 -0
- package/dist/tools/channel-tools.d.ts +4 -0
- package/dist/tools/channel-tools.js +80 -0
- package/dist/tools/computer-coordinator-tools.d.ts +13 -0
- package/dist/tools/computer-coordinator-tools.js +104 -0
- package/dist/tools/computer.js +463 -299
- package/dist/tools/file-library-tools.d.ts +12 -0
- package/dist/tools/file-library-tools.js +191 -0
- package/dist/tools/image-thoughtful.d.ts +31 -0
- package/dist/tools/image-thoughtful.js +233 -0
- package/dist/tools/index.js +1 -0
- package/dist/tools/security-agent-tools.d.ts +34 -0
- package/dist/tools/security-agent-tools.js +30 -0
- package/dist/tools/swarm-2026-04.d.ts +2 -0
- package/dist/tools/swarm-2026-04.js +91 -0
- package/dist/tools/workspace-agent-tools.d.ts +19 -0
- package/dist/tools/workspace-agent-tools.js +191 -0
- package/dist/workspace-agents.d.ts +132 -0
- package/dist/workspace-agents.js +379 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ Most terminal AI agents lock you into one provider, one model, one way of workin
|
|
|
30
30
|
- **20 providers, zero lock-in** — Claude, GPT, Gemini, Grok, DeepSeek, Groq, Mistral, SambaNova, Cerebras, OpenRouter, and more. Switch with one command.
|
|
31
31
|
- **Runs fully offline** — Embedded llama.cpp, Ollama, LM Studio, or Jan. $0, fully private.
|
|
32
32
|
- **Learns your patterns** — Bayesian skill ratings + pattern extraction. Gets faster over time.
|
|
33
|
-
- **35 specialist agents** — auto-routes your request to the right expert (coder, researcher, writer, guardian, quant, and 30 more).
|
|
33
|
+
- **35 specialist agents** — auto-routes your request to the right expert (coder, researcher, writer, guardian, quant, and 30 more). Run any agent manually: `kbot --agent <id> "<prompt>"`. List them: `kbot agents`.
|
|
34
34
|
- **787+ tools** — files, bash, git, GitHub, web search, deploy, database, game dev, VFX, research, science, finance, security, music production, iPhone control, and more.
|
|
35
35
|
- **Programmatic SDK** — use kbot as a library in your own apps.
|
|
36
36
|
- **MCP server built in** — plug kbot into Claude Code, Cursor, VS Code, Zed, or Neovim as a tool provider.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { RULES, RULES_BY_ID, type Severity } from './security-rules.js';
|
|
2
|
+
export type SecurityMode = 'scan' | 'scan-and-fix' | 'report-only';
|
|
3
|
+
export interface SecurityFinding {
|
|
4
|
+
id: string;
|
|
5
|
+
severity: Severity;
|
|
6
|
+
category: string;
|
|
7
|
+
file: string;
|
|
8
|
+
line?: number;
|
|
9
|
+
description: string;
|
|
10
|
+
recommendation: string;
|
|
11
|
+
fixed?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface SecurityReport {
|
|
14
|
+
scanned: number;
|
|
15
|
+
findings: SecurityFinding[];
|
|
16
|
+
fixesApplied: number;
|
|
17
|
+
summary: string;
|
|
18
|
+
}
|
|
19
|
+
export interface RunSecurityAgentInput {
|
|
20
|
+
target: string;
|
|
21
|
+
mode: SecurityMode;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Run the kbot security agent over a directory.
|
|
25
|
+
* - `scan`: read-only walk + report
|
|
26
|
+
* - `scan-and-fix`: applies the safest auto-fixes (currently md5 -> sha256)
|
|
27
|
+
* - `report-only`: identical to scan; never writes
|
|
28
|
+
*/
|
|
29
|
+
export declare function runSecurityAgent(input: RunSecurityAgentInput): Promise<SecurityReport>;
|
|
30
|
+
export { RULES, RULES_BY_ID };
|
|
31
|
+
//# sourceMappingURL=security-agent.d.ts.map
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
// kbot Security Agent — unified app-sec scanner over guardian/hacker tools.
|
|
2
|
+
// Mirrors the Codex Security agent surface (March 2026). Single named entry
|
|
3
|
+
// point: runSecurityAgent({ target, mode }).
|
|
4
|
+
import * as fs from 'node:fs';
|
|
5
|
+
import * as path from 'node:path';
|
|
6
|
+
import { RULES, RULES_BY_ID, SEVERITY_RANK, shouldScan, } from './security-rules.js';
|
|
7
|
+
const SKIP_DIRS = new Set(['node_modules', 'dist', '.git', '.next', 'build', '.turbo']);
|
|
8
|
+
function walk(dir, out) {
|
|
9
|
+
let entries;
|
|
10
|
+
try {
|
|
11
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
for (const e of entries) {
|
|
17
|
+
if (e.name.startsWith('.') && !e.name.startsWith('.env')) {
|
|
18
|
+
// skip hidden dirs except .env-style files
|
|
19
|
+
if (e.isDirectory())
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const full = path.join(dir, e.name);
|
|
23
|
+
if (e.isDirectory()) {
|
|
24
|
+
if (SKIP_DIRS.has(e.name))
|
|
25
|
+
continue;
|
|
26
|
+
walk(full, out);
|
|
27
|
+
}
|
|
28
|
+
else if (e.isFile()) {
|
|
29
|
+
if (shouldScan(e.name))
|
|
30
|
+
out.push(full);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function rankCompare(a, b) {
|
|
35
|
+
const r = SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity];
|
|
36
|
+
if (r !== 0)
|
|
37
|
+
return r;
|
|
38
|
+
if (a.file !== b.file)
|
|
39
|
+
return a.file < b.file ? -1 : 1;
|
|
40
|
+
return (a.line ?? 0) - (b.line ?? 0);
|
|
41
|
+
}
|
|
42
|
+
function scanFile(file, mode) {
|
|
43
|
+
let text;
|
|
44
|
+
try {
|
|
45
|
+
text = fs.readFileSync(file, 'utf8');
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return { findings: [], fixes: 0 };
|
|
49
|
+
}
|
|
50
|
+
const lines = text.split(/\r?\n/);
|
|
51
|
+
const findings = [];
|
|
52
|
+
let fixesApplied = 0;
|
|
53
|
+
let mutated = false;
|
|
54
|
+
const newLines = [...lines];
|
|
55
|
+
for (let i = 0; i < lines.length; i++) {
|
|
56
|
+
const line = lines[i];
|
|
57
|
+
const ctx = { file, line, lineNumber: i + 1, fullText: text };
|
|
58
|
+
for (const rule of RULES) {
|
|
59
|
+
if (rule.appliesTo && !rule.appliesTo(file))
|
|
60
|
+
continue;
|
|
61
|
+
const result = rule.test(ctx);
|
|
62
|
+
if (!result)
|
|
63
|
+
continue;
|
|
64
|
+
const hits = Array.isArray(result) ? result : [result];
|
|
65
|
+
for (const h of hits) {
|
|
66
|
+
let fixed;
|
|
67
|
+
if (mode === 'scan-and-fix' && h.fix) {
|
|
68
|
+
// Conservative: only apply when the find string appears verbatim.
|
|
69
|
+
if (newLines[i].includes(h.fix.find)) {
|
|
70
|
+
newLines[i] = newLines[i].replace(h.fix.find, h.fix.replace);
|
|
71
|
+
mutated = true;
|
|
72
|
+
fixesApplied++;
|
|
73
|
+
fixed = true;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else if (mode === 'scan-and-fix' && !h.fix) {
|
|
77
|
+
// Riskier patterns: log + skip with suggestion.
|
|
78
|
+
fixed = false;
|
|
79
|
+
}
|
|
80
|
+
findings.push({
|
|
81
|
+
id: h.id,
|
|
82
|
+
severity: h.severity,
|
|
83
|
+
category: h.category,
|
|
84
|
+
file,
|
|
85
|
+
line: i + 1,
|
|
86
|
+
description: h.description,
|
|
87
|
+
recommendation: h.recommendation,
|
|
88
|
+
fixed,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (mode === 'scan-and-fix' && mutated) {
|
|
94
|
+
fs.writeFileSync(file, newLines.join('\n'));
|
|
95
|
+
}
|
|
96
|
+
return { findings, fixes: fixesApplied };
|
|
97
|
+
}
|
|
98
|
+
function buildSummary(report) {
|
|
99
|
+
const counts = {
|
|
100
|
+
critical: 0, high: 0, medium: 0, low: 0, info: 0,
|
|
101
|
+
};
|
|
102
|
+
for (const f of report.findings)
|
|
103
|
+
counts[f.severity]++;
|
|
104
|
+
const lines = [];
|
|
105
|
+
lines.push('# kbot Security Agent Report');
|
|
106
|
+
lines.push('');
|
|
107
|
+
lines.push(`- Files scanned: **${report.scanned}**`);
|
|
108
|
+
lines.push(`- Findings: **${report.findings.length}**`);
|
|
109
|
+
lines.push(`- Fixes applied: **${report.fixesApplied}**`);
|
|
110
|
+
lines.push('');
|
|
111
|
+
lines.push('## Severity breakdown');
|
|
112
|
+
lines.push('');
|
|
113
|
+
lines.push(`- critical: ${counts.critical}`);
|
|
114
|
+
lines.push(`- high: ${counts.high}`);
|
|
115
|
+
lines.push(`- medium: ${counts.medium}`);
|
|
116
|
+
lines.push(`- low: ${counts.low}`);
|
|
117
|
+
lines.push(`- info: ${counts.info}`);
|
|
118
|
+
lines.push('');
|
|
119
|
+
if (report.findings.length > 0) {
|
|
120
|
+
lines.push('## Findings');
|
|
121
|
+
lines.push('');
|
|
122
|
+
for (const f of report.findings) {
|
|
123
|
+
const loc = f.line ? `${f.file}:${f.line}` : f.file;
|
|
124
|
+
const fixedTag = f.fixed === true ? ' [FIXED]'
|
|
125
|
+
: f.fixed === false ? ' [skipped]' : '';
|
|
126
|
+
lines.push(`- **[${f.severity.toUpperCase()}] ${f.id}** ${f.description}${fixedTag}`);
|
|
127
|
+
lines.push(` - location: \`${loc}\``);
|
|
128
|
+
lines.push(` - recommendation: ${f.recommendation}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
lines.push('No findings — clean.');
|
|
133
|
+
}
|
|
134
|
+
return lines.join('\n');
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Run the kbot security agent over a directory.
|
|
138
|
+
* - `scan`: read-only walk + report
|
|
139
|
+
* - `scan-and-fix`: applies the safest auto-fixes (currently md5 -> sha256)
|
|
140
|
+
* - `report-only`: identical to scan; never writes
|
|
141
|
+
*/
|
|
142
|
+
export async function runSecurityAgent(input) {
|
|
143
|
+
const { target } = input;
|
|
144
|
+
const mode = input.mode ?? 'scan';
|
|
145
|
+
const files = [];
|
|
146
|
+
let stat;
|
|
147
|
+
try {
|
|
148
|
+
stat = fs.statSync(target);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
const empty = {
|
|
152
|
+
scanned: 0, findings: [], fixesApplied: 0,
|
|
153
|
+
};
|
|
154
|
+
return { ...empty, summary: buildSummary(empty) };
|
|
155
|
+
}
|
|
156
|
+
if (stat.isDirectory()) {
|
|
157
|
+
walk(target, files);
|
|
158
|
+
}
|
|
159
|
+
else if (stat.isFile() && shouldScan(path.basename(target))) {
|
|
160
|
+
files.push(target);
|
|
161
|
+
}
|
|
162
|
+
const allFindings = [];
|
|
163
|
+
let fixesApplied = 0;
|
|
164
|
+
for (const file of files) {
|
|
165
|
+
const effectiveMode = mode === 'report-only' ? 'scan' : mode;
|
|
166
|
+
const { findings, fixes } = scanFile(file, effectiveMode);
|
|
167
|
+
allFindings.push(...findings);
|
|
168
|
+
fixesApplied += fixes;
|
|
169
|
+
}
|
|
170
|
+
allFindings.sort(rankCompare);
|
|
171
|
+
const base = {
|
|
172
|
+
scanned: files.length,
|
|
173
|
+
findings: allFindings,
|
|
174
|
+
fixesApplied,
|
|
175
|
+
};
|
|
176
|
+
return { ...base, summary: buildSummary(base) };
|
|
177
|
+
}
|
|
178
|
+
// Re-export the rule registry for callers that want to introspect / extend.
|
|
179
|
+
export { RULES, RULES_BY_ID };
|
|
180
|
+
//# sourceMappingURL=security-agent.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export type Severity = 'critical' | 'high' | 'medium' | 'low' | 'info';
|
|
2
|
+
export interface RuleContext {
|
|
3
|
+
file: string;
|
|
4
|
+
line: string;
|
|
5
|
+
lineNumber: number;
|
|
6
|
+
fullText: string;
|
|
7
|
+
}
|
|
8
|
+
export interface RuleHit {
|
|
9
|
+
id: string;
|
|
10
|
+
severity: Severity;
|
|
11
|
+
category: string;
|
|
12
|
+
description: string;
|
|
13
|
+
recommendation: string;
|
|
14
|
+
/** Auto-fix descriptor — only present when a rule supports a safe fix. */
|
|
15
|
+
fix?: {
|
|
16
|
+
find: string;
|
|
17
|
+
replace: string;
|
|
18
|
+
label: string;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export interface Rule {
|
|
22
|
+
id: string;
|
|
23
|
+
category: string;
|
|
24
|
+
severity: Severity;
|
|
25
|
+
description: string;
|
|
26
|
+
recommendation: string;
|
|
27
|
+
appliesTo?: (file: string) => boolean;
|
|
28
|
+
test: (ctx: RuleContext) => RuleHit | RuleHit[] | null;
|
|
29
|
+
}
|
|
30
|
+
export declare const RULES: Rule[];
|
|
31
|
+
export declare const RULES_BY_ID: Record<string, Rule>;
|
|
32
|
+
export declare const SEVERITY_RANK: Record<Severity, number>;
|
|
33
|
+
export declare const SCANNABLE_EXT: RegExp;
|
|
34
|
+
export declare function shouldScan(file: string): boolean;
|
|
35
|
+
//# sourceMappingURL=security-rules.d.ts.map
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
// kbot Security Agent — static rule catalog.
|
|
2
|
+
// Lightweight regex / string scans. No AST. Each rule returns zero or more
|
|
3
|
+
// hits given a single line of source text plus filename context.
|
|
4
|
+
const isJsLike = (f) => /\.(ts|tsx|js|jsx|mjs|cjs)$/.test(f);
|
|
5
|
+
const isEnvLike = (f) => /(^|\/)\.env(\..+)?$/.test(f);
|
|
6
|
+
const isJson = (f) => f.endsWith('.json');
|
|
7
|
+
// Compact factory — most rules share the shape "regex on line -> emit hit".
|
|
8
|
+
function regexRule(opts) {
|
|
9
|
+
return {
|
|
10
|
+
id: opts.id,
|
|
11
|
+
category: opts.category,
|
|
12
|
+
severity: opts.severity,
|
|
13
|
+
description: opts.description,
|
|
14
|
+
recommendation: opts.recommendation,
|
|
15
|
+
appliesTo: opts.appliesTo,
|
|
16
|
+
test: (ctx) => {
|
|
17
|
+
if (!opts.pattern.test(ctx.line))
|
|
18
|
+
return null;
|
|
19
|
+
return {
|
|
20
|
+
id: opts.id,
|
|
21
|
+
severity: opts.severity,
|
|
22
|
+
category: opts.category,
|
|
23
|
+
description: opts.description,
|
|
24
|
+
recommendation: opts.recommendation,
|
|
25
|
+
fix: opts.fix ? opts.fix(ctx.line) : undefined,
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export const RULES = [
|
|
31
|
+
regexRule({
|
|
32
|
+
id: 'SEC-001', category: 'secrets', severity: 'critical',
|
|
33
|
+
description: 'Hardcoded API key (sk-...) detected',
|
|
34
|
+
recommendation: 'Rotate the key and load from process.env instead.',
|
|
35
|
+
pattern: /['"`]sk-[A-Za-z0-9_-]{16,}['"`]/,
|
|
36
|
+
}),
|
|
37
|
+
regexRule({
|
|
38
|
+
id: 'SEC-002', category: 'secrets', severity: 'critical',
|
|
39
|
+
description: 'Hardcoded AWS access key id',
|
|
40
|
+
recommendation: 'Rotate via IAM and load from environment / role.',
|
|
41
|
+
pattern: /AKIA[0-9A-Z]{16}/,
|
|
42
|
+
}),
|
|
43
|
+
regexRule({
|
|
44
|
+
id: 'SEC-003', category: 'secrets', severity: 'critical',
|
|
45
|
+
description: 'Embedded BEGIN PRIVATE KEY block',
|
|
46
|
+
recommendation: 'Move to a secrets vault and rotate.',
|
|
47
|
+
pattern: /-----BEGIN (RSA |EC |OPENSSH |DSA |)PRIVATE KEY-----/,
|
|
48
|
+
}),
|
|
49
|
+
regexRule({
|
|
50
|
+
id: 'SEC-004', category: 'secrets', severity: 'high',
|
|
51
|
+
description: 'Inline password= "..." literal',
|
|
52
|
+
recommendation: 'Read from process.env or a secrets manager.',
|
|
53
|
+
pattern: /(?:^|[^A-Za-z_])password\s*[:=]\s*['"][^'"\s]{4,}['"]/i,
|
|
54
|
+
}),
|
|
55
|
+
regexRule({
|
|
56
|
+
id: 'SEC-005', category: 'secrets', severity: 'high',
|
|
57
|
+
description: 'AWS secret literal',
|
|
58
|
+
recommendation: 'Rotate and load from environment.',
|
|
59
|
+
pattern: /AWS_SECRET[_A-Z]*\s*[:=]\s*['"][^'"\s]{8,}['"]/,
|
|
60
|
+
}),
|
|
61
|
+
regexRule({
|
|
62
|
+
id: 'SEC-006', category: 'secrets', severity: 'critical',
|
|
63
|
+
description: 'GitHub personal-access token literal',
|
|
64
|
+
recommendation: 'Revoke immediately and use env var.',
|
|
65
|
+
pattern: /gh[pousr]_[A-Za-z0-9]{20,}/,
|
|
66
|
+
}),
|
|
67
|
+
regexRule({
|
|
68
|
+
id: 'SEC-007', category: 'code-injection', severity: 'high',
|
|
69
|
+
description: 'eval() call',
|
|
70
|
+
recommendation: 'Replace with a safe parser (JSON.parse, etc.).',
|
|
71
|
+
pattern: /(?:^|[^A-Za-z_$.])eval\s*\(/,
|
|
72
|
+
appliesTo: isJsLike,
|
|
73
|
+
}),
|
|
74
|
+
regexRule({
|
|
75
|
+
id: 'SEC-008', category: 'code-injection', severity: 'high',
|
|
76
|
+
description: 'new Function() constructor',
|
|
77
|
+
recommendation: 'Replace with predefined functions or sandbox.',
|
|
78
|
+
pattern: /new\s+Function\s*\(/,
|
|
79
|
+
appliesTo: isJsLike,
|
|
80
|
+
}),
|
|
81
|
+
regexRule({
|
|
82
|
+
id: 'SEC-009', category: 'command-injection', severity: 'high',
|
|
83
|
+
description: 'exec(`... ${...}`) template literal',
|
|
84
|
+
recommendation: 'Switch to execFile(cmd, [args]) — never interpolate.',
|
|
85
|
+
pattern: /\bexec\s*\(\s*`[^`]*\$\{/,
|
|
86
|
+
appliesTo: isJsLike,
|
|
87
|
+
}),
|
|
88
|
+
regexRule({
|
|
89
|
+
id: 'SEC-010', category: 'sql-injection', severity: 'high',
|
|
90
|
+
description: 'SQL string concatenation',
|
|
91
|
+
recommendation: 'Use parameter placeholders ($1, ?) with the driver.',
|
|
92
|
+
pattern: /['"`]\s*(SELECT|INSERT|UPDATE)\b[^'"`]*['"`]\s*\+/i,
|
|
93
|
+
appliesTo: isJsLike,
|
|
94
|
+
}),
|
|
95
|
+
regexRule({
|
|
96
|
+
id: 'SEC-011', category: 'xss', severity: 'high',
|
|
97
|
+
description: 'Dynamic innerHTML assignment',
|
|
98
|
+
recommendation: 'Use textContent or sanitise via DOMPurify.',
|
|
99
|
+
pattern: /\.innerHTML\s*=\s*[^'"`;]+(?:\+|\$\{|\()/,
|
|
100
|
+
appliesTo: isJsLike,
|
|
101
|
+
}),
|
|
102
|
+
regexRule({
|
|
103
|
+
id: 'SEC-012', category: 'xss', severity: 'high',
|
|
104
|
+
description: 'dangerouslySetInnerHTML bound to a variable',
|
|
105
|
+
recommendation: 'Sanitise the HTML string with DOMPurify first.',
|
|
106
|
+
pattern: /dangerouslySetInnerHTML\s*=\s*\{\{\s*__html\s*:\s*[A-Za-z_$][\w$.]*\s*\}\}/,
|
|
107
|
+
appliesTo: isJsLike,
|
|
108
|
+
}),
|
|
109
|
+
regexRule({
|
|
110
|
+
id: 'SEC-013', category: 'auth', severity: 'critical',
|
|
111
|
+
description: 'JWT alg "none" allowed',
|
|
112
|
+
recommendation: 'Pin algorithms (HS256/RS256); never accept "none".',
|
|
113
|
+
pattern: /(['"]alg['"]\s*:\s*['"]none['"]|algorithms\s*:\s*\[\s*['"]none['"])/i,
|
|
114
|
+
appliesTo: isJsLike,
|
|
115
|
+
}),
|
|
116
|
+
// SEC-014: contextual — Math.random() in crypto-adjacent code
|
|
117
|
+
{
|
|
118
|
+
id: 'SEC-014', category: 'crypto', severity: 'medium',
|
|
119
|
+
description: 'Math.random() in crypto-adjacent code',
|
|
120
|
+
recommendation: 'Replace with crypto.randomBytes(); leave a TODO marker.',
|
|
121
|
+
appliesTo: isJsLike,
|
|
122
|
+
test: (ctx) => {
|
|
123
|
+
if (!/Math\.random\s*\(\s*\)/.test(ctx.line))
|
|
124
|
+
return null;
|
|
125
|
+
const lower = ctx.line.toLowerCase();
|
|
126
|
+
if (!/(token|secret|nonce|salt|password|otp|sessionid|api_?key)/.test(lower))
|
|
127
|
+
return null;
|
|
128
|
+
return {
|
|
129
|
+
id: 'SEC-014', severity: 'medium', category: 'crypto',
|
|
130
|
+
description: 'Math.random() in crypto-adjacent code',
|
|
131
|
+
recommendation: 'Use crypto.randomBytes() instead.',
|
|
132
|
+
};
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
// SEC-015: weak hash — supports auto-fix for md5 only
|
|
136
|
+
{
|
|
137
|
+
id: 'SEC-015', category: 'crypto', severity: 'high',
|
|
138
|
+
description: "createHash('md5'/'sha1') is weak",
|
|
139
|
+
recommendation: "Replace with createHash('sha256') or use bcrypt/argon2 for passwords.",
|
|
140
|
+
appliesTo: isJsLike,
|
|
141
|
+
test: (ctx) => {
|
|
142
|
+
const m = /createHash\(\s*['"](md5|sha1)['"]\s*\)/.exec(ctx.line);
|
|
143
|
+
if (!m)
|
|
144
|
+
return null;
|
|
145
|
+
const algo = m[1];
|
|
146
|
+
return {
|
|
147
|
+
id: 'SEC-015', severity: 'high', category: 'crypto',
|
|
148
|
+
description: `createHash('${algo}') is weak`,
|
|
149
|
+
recommendation: `Replace with createHash('sha256') or bcrypt for passwords.`,
|
|
150
|
+
fix: algo === 'md5'
|
|
151
|
+
? { find: `createHash('md5')`, replace: `createHash('sha256')`, label: 'md5 -> sha256' }
|
|
152
|
+
: undefined,
|
|
153
|
+
};
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
// SEC-016: open CORS * with credentials — needs whole-file context
|
|
157
|
+
{
|
|
158
|
+
id: 'SEC-016', category: 'cors', severity: 'high',
|
|
159
|
+
description: 'Wildcard CORS origin with credentials enabled',
|
|
160
|
+
recommendation: 'Echo a vetted Origin header instead of *.',
|
|
161
|
+
appliesTo: isJsLike,
|
|
162
|
+
test: (ctx) => {
|
|
163
|
+
if (!/Access-Control-Allow-Origin['"\s:,]+\*/i.test(ctx.line))
|
|
164
|
+
return null;
|
|
165
|
+
if (!/Access-Control-Allow-Credentials['"\s:,]+true/i.test(ctx.fullText))
|
|
166
|
+
return null;
|
|
167
|
+
return {
|
|
168
|
+
id: 'SEC-016', severity: 'high', category: 'cors',
|
|
169
|
+
description: 'Wildcard CORS origin with credentials enabled',
|
|
170
|
+
recommendation: 'Echo a vetted Origin header instead of *.',
|
|
171
|
+
};
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
// SEC-017: <form method="post"> with no csrf token anywhere in the file
|
|
175
|
+
{
|
|
176
|
+
id: 'SEC-017', category: 'csrf', severity: 'medium',
|
|
177
|
+
description: 'POST form without CSRF token',
|
|
178
|
+
recommendation: 'Add a hidden CSRF token input or use SameSite=strict cookies.',
|
|
179
|
+
test: (ctx) => {
|
|
180
|
+
if (!/<form[^>]*method\s*=\s*["']post["']/i.test(ctx.line))
|
|
181
|
+
return null;
|
|
182
|
+
if (/csrf|_token|csrfToken/i.test(ctx.fullText))
|
|
183
|
+
return null;
|
|
184
|
+
return {
|
|
185
|
+
id: 'SEC-017', severity: 'medium', category: 'csrf',
|
|
186
|
+
description: 'POST form without CSRF token',
|
|
187
|
+
recommendation: 'Add a hidden CSRF token input.',
|
|
188
|
+
};
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
];
|
|
192
|
+
export const RULES_BY_ID = Object.fromEntries(RULES.map((r) => [r.id, r]));
|
|
193
|
+
export const SEVERITY_RANK = {
|
|
194
|
+
critical: 0, high: 1, medium: 2, low: 3, info: 4,
|
|
195
|
+
};
|
|
196
|
+
export const SCANNABLE_EXT = /\.(ts|tsx|js|jsx|mjs|cjs|json)$/;
|
|
197
|
+
export function shouldScan(file) {
|
|
198
|
+
if (isEnvLike(file))
|
|
199
|
+
return true;
|
|
200
|
+
if (SCANNABLE_EXT.test(file))
|
|
201
|
+
return true;
|
|
202
|
+
if (isJson(file))
|
|
203
|
+
return true;
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
//# sourceMappingURL=security-rules.js.map
|
|
@@ -3,6 +3,12 @@ export interface SpecialistDef {
|
|
|
3
3
|
icon: string;
|
|
4
4
|
color: string;
|
|
5
5
|
prompt: string;
|
|
6
|
+
/**
|
|
7
|
+
* Optional whitelist of tool names this specialist is allowed to call.
|
|
8
|
+
* When omitted (or empty), the specialist inherits the global tool set.
|
|
9
|
+
* Used by the workspace agent runtime to gate tool execution per-specialist.
|
|
10
|
+
*/
|
|
11
|
+
allowedTools?: string[];
|
|
6
12
|
}
|
|
7
13
|
export declare const SPECIALISTS: Record<string, SpecialistDef>;
|
|
8
14
|
//# sourceMappingURL=specialists.d.ts.map
|
|
@@ -126,6 +126,7 @@ You know Ableton Live 12 at an expert level — 20 instruments, 58 audio effects
|
|
|
126
126
|
prompt: `You are a Security specialist — a defensive security engineer who protects systems and users.
|
|
127
127
|
|
|
128
128
|
When reviewing for security:
|
|
129
|
+
- Step 0 (always first): run \`security_agent_scan\` over the target directory in \`report-only\` mode. Surface critical+high findings before running heavier tools. Use \`security_agent_report\` to format the consolidated output.
|
|
129
130
|
- Check OWASP Top 10: injection, broken auth, sensitive data exposure, XXE, broken access control, misconfiguration, XSS, insecure deserialization, vulnerable dependencies, insufficient logging
|
|
130
131
|
- Review auth flows: JWT handling, session management, RBAC enforcement
|
|
131
132
|
- Check for secrets in code: API keys, tokens, passwords, connection strings
|
|
@@ -135,6 +136,50 @@ When reviewing for security:
|
|
|
135
136
|
- Rate findings: Critical (exploit now) → High → Medium → Low (hardening)
|
|
136
137
|
|
|
137
138
|
You think like an attacker to defend like a guardian. Every input is hostile until proven otherwise.`,
|
|
139
|
+
allowedTools: [
|
|
140
|
+
// Unified security agent (always first — Step 0)
|
|
141
|
+
'security_agent_scan',
|
|
142
|
+
'security_agent_report',
|
|
143
|
+
// Code/file inspection
|
|
144
|
+
'read_file',
|
|
145
|
+
'grep',
|
|
146
|
+
'glob',
|
|
147
|
+
'list_directory',
|
|
148
|
+
// Dependency + secret scanning
|
|
149
|
+
'dep_audit',
|
|
150
|
+
'deps_audit',
|
|
151
|
+
'secret_scan',
|
|
152
|
+
'license_check',
|
|
153
|
+
// Vulnerability research / CVE
|
|
154
|
+
'cve_lookup',
|
|
155
|
+
'exploit_search',
|
|
156
|
+
'attack_lookup',
|
|
157
|
+
'threat_feed',
|
|
158
|
+
// Hardening + reporting
|
|
159
|
+
'security_brain',
|
|
160
|
+
'security_headers_generate',
|
|
161
|
+
'security_hunt',
|
|
162
|
+
'blueteam_checklist',
|
|
163
|
+
'blueteam_harden',
|
|
164
|
+
'redteam_report',
|
|
165
|
+
'threat_model',
|
|
166
|
+
'killchain_analyze',
|
|
167
|
+
'incident_response',
|
|
168
|
+
'forensics_analyze',
|
|
169
|
+
// Web/HTTP surface checks
|
|
170
|
+
'headers_check',
|
|
171
|
+
'cors_check',
|
|
172
|
+
'ssl_check',
|
|
173
|
+
'ssl_analyze',
|
|
174
|
+
'owasp_check',
|
|
175
|
+
'jwt_analyze',
|
|
176
|
+
// General workflow
|
|
177
|
+
'web_search',
|
|
178
|
+
'url_fetch',
|
|
179
|
+
'agent_memory_read',
|
|
180
|
+
'agent_memory_write',
|
|
181
|
+
'team_handoff',
|
|
182
|
+
],
|
|
138
183
|
},
|
|
139
184
|
curator: {
|
|
140
185
|
name: 'Curator',
|
package/dist/architect.js
CHANGED
|
@@ -320,6 +320,11 @@ function displayReport(report) {
|
|
|
320
320
|
* @returns Full report of the architect session
|
|
321
321
|
*/
|
|
322
322
|
export async function runArchitectMode(task, options = {}) {
|
|
323
|
+
// Default architect role to Claude Opus 4.7 (Apr 2026) — best long-horizon
|
|
324
|
+
// SWE + computer-use model. User --model flag still wins.
|
|
325
|
+
if (!options.model || options.model === 'auto') {
|
|
326
|
+
options = { ...options, model: 'claude-opus-4-7' };
|
|
327
|
+
}
|
|
323
328
|
printInfo('Entering architect mode...');
|
|
324
329
|
console.log();
|
|
325
330
|
// Phase 1: Architect creates plan
|
package/dist/auth.js
CHANGED
|
@@ -28,7 +28,7 @@ export const PROVIDERS = {
|
|
|
28
28
|
inputCost: 3.0,
|
|
29
29
|
outputCost: 15.0,
|
|
30
30
|
authHeader: 'x-api-key',
|
|
31
|
-
models: ['claude-mythos-1', 'claude-opus-4-6', 'claude-sonnet-4-6', 'claude-haiku-4-5-20251001'],
|
|
31
|
+
models: ['claude-mythos-1', 'claude-opus-4-7', 'claude-opus-4-6', 'claude-sonnet-4-6', 'claude-haiku-4-5-20251001'],
|
|
32
32
|
},
|
|
33
33
|
openai: {
|
|
34
34
|
name: 'OpenAI',
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Matrix channel adapter — STUB.
|
|
2
|
+
//
|
|
3
|
+
// TODO: Implement against the Matrix Client-Server API.
|
|
4
|
+
// - MATRIX_HOMESERVER (e.g. https://matrix.org)
|
|
5
|
+
// - MATRIX_TOKEN (access token from /login or a registered application service)
|
|
6
|
+
// - MATRIX_USER_ID (optional — only required for some endpoints)
|
|
7
|
+
// - send → PUT {homeserver}/_matrix/client/v3/rooms/{roomId}/send/m.room.message/{txnId}
|
|
8
|
+
// - receive → /sync endpoint with `since` token (kbot persists the token
|
|
9
|
+
// between calls) → translate timeline events into ChannelMessage.
|
|
10
|
+
// - listChannels → /joined_rooms then resolve names via /state.
|
|
11
|
+
import { ChannelNotImplementedError } from './types.js';
|
|
12
|
+
export const matrixAdapter = {
|
|
13
|
+
name: 'matrix',
|
|
14
|
+
isConfigured() {
|
|
15
|
+
return Boolean(process.env.MATRIX_HOMESERVER && process.env.MATRIX_TOKEN);
|
|
16
|
+
},
|
|
17
|
+
async send() {
|
|
18
|
+
throw new ChannelNotImplementedError('matrix');
|
|
19
|
+
},
|
|
20
|
+
async receive() {
|
|
21
|
+
throw new ChannelNotImplementedError('matrix');
|
|
22
|
+
},
|
|
23
|
+
async listChannels() {
|
|
24
|
+
throw new ChannelNotImplementedError('matrix');
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
export default matrixAdapter;
|
|
28
|
+
//# sourceMappingURL=matrix.js.map
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { ChannelAdapter, ChannelEnvelope, ChannelInfo, ChannelMessage, ChannelReceiveOptions } from './types.js';
|
|
2
|
+
interface GraphDriveItem {
|
|
3
|
+
id: string;
|
|
4
|
+
name?: string;
|
|
5
|
+
webUrl?: string;
|
|
6
|
+
folder?: {
|
|
7
|
+
childCount?: number;
|
|
8
|
+
};
|
|
9
|
+
file?: {
|
|
10
|
+
mimeType?: string;
|
|
11
|
+
};
|
|
12
|
+
size?: number;
|
|
13
|
+
}
|
|
14
|
+
export interface ListFilesOptions {
|
|
15
|
+
driveId?: string;
|
|
16
|
+
folderId?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface ReadDocumentOptions {
|
|
19
|
+
fileId: string;
|
|
20
|
+
/** "raw" returns the file bytes as text; "worksheets" lists Excel sheets. */
|
|
21
|
+
format?: 'raw' | 'worksheets';
|
|
22
|
+
}
|
|
23
|
+
export interface WriteDocumentSectionOptions {
|
|
24
|
+
fileId: string;
|
|
25
|
+
/** Path within the document. Currently informational — see TODO below. */
|
|
26
|
+
sectionPath: string;
|
|
27
|
+
content: string | Uint8Array;
|
|
28
|
+
}
|
|
29
|
+
export interface AppendToWorkbookOptions {
|
|
30
|
+
fileId: string;
|
|
31
|
+
sheet: string;
|
|
32
|
+
rows: Array<Array<string | number | boolean | null>>;
|
|
33
|
+
/** Table name in the worksheet. Defaults to "Table1". */
|
|
34
|
+
table?: string;
|
|
35
|
+
}
|
|
36
|
+
export interface AddSlideOptions {
|
|
37
|
+
fileId: string;
|
|
38
|
+
layout?: string;
|
|
39
|
+
content?: Record<string, unknown>;
|
|
40
|
+
}
|
|
41
|
+
export declare class OfficeChannel implements ChannelAdapter {
|
|
42
|
+
readonly name = "office";
|
|
43
|
+
isConfigured(): boolean;
|
|
44
|
+
/** Post a comment to a drive item (file). `envelope.channel` is the fileId. */
|
|
45
|
+
send(envelope: ChannelEnvelope): Promise<{
|
|
46
|
+
id: string;
|
|
47
|
+
ts: number;
|
|
48
|
+
}>;
|
|
49
|
+
/** List comments on a drive item. `opts.channel` is the fileId. */
|
|
50
|
+
receive(opts: ChannelReceiveOptions): Promise<ChannelMessage[]>;
|
|
51
|
+
/** List drive root children. */
|
|
52
|
+
listChannels(): Promise<ChannelInfo[]>;
|
|
53
|
+
listFiles(options?: ListFilesOptions): Promise<GraphDriveItem[]>;
|
|
54
|
+
readDocument(options: ReadDocumentOptions): Promise<string | unknown>;
|
|
55
|
+
/**
|
|
56
|
+
* Replace an entire Word document with `content`.
|
|
57
|
+
*
|
|
58
|
+
* TODO(office-add-in): For surgical, paragraph- or range-level edits, route
|
|
59
|
+
* through an Office Add-in's `Word.Range` API (Word JavaScript API). The
|
|
60
|
+
* Graph REST surface does not expose Word ranges directly; the standard
|
|
61
|
+
* workaround is the Office Add-in JS bridge.
|
|
62
|
+
*/
|
|
63
|
+
writeDocumentSection(options: WriteDocumentSectionOptions): Promise<{
|
|
64
|
+
id: string;
|
|
65
|
+
size?: number;
|
|
66
|
+
}>;
|
|
67
|
+
appendToWorkbook(options: AppendToWorkbookOptions): Promise<unknown>;
|
|
68
|
+
/**
|
|
69
|
+
* TODO(pptx): Microsoft Graph does not expose slide-level mutation. The
|
|
70
|
+
* workaround is to regenerate the .pptx (e.g. via `pptxgenjs`) and PUT the
|
|
71
|
+
* bytes back via `/me/drive/items/{fileId}/content`. Until that dependency
|
|
72
|
+
* is wired, this method throws.
|
|
73
|
+
*/
|
|
74
|
+
addSlide(_options: AddSlideOptions): Promise<never>;
|
|
75
|
+
}
|
|
76
|
+
export declare const officeAdapter: ChannelAdapter;
|
|
77
|
+
export default officeAdapter;
|
|
78
|
+
//# sourceMappingURL=office.d.ts.map
|