@devran-ai/kit 4.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/.agent/CheatSheet.md +350 -0
- package/.agent/README.md +76 -0
- package/.agent/agents/README.md +155 -0
- package/.agent/agents/architect.md +185 -0
- package/.agent/agents/backend-specialist.md +276 -0
- package/.agent/agents/build-error-resolver.md +207 -0
- package/.agent/agents/code-reviewer.md +162 -0
- package/.agent/agents/database-architect.md +138 -0
- package/.agent/agents/devops-engineer.md +144 -0
- package/.agent/agents/doc-updater.md +229 -0
- package/.agent/agents/e2e-runner.md +145 -0
- package/.agent/agents/explorer-agent.md +143 -0
- package/.agent/agents/frontend-specialist.md +144 -0
- package/.agent/agents/go-reviewer.md +128 -0
- package/.agent/agents/knowledge-agent.md +197 -0
- package/.agent/agents/mobile-developer.md +150 -0
- package/.agent/agents/performance-optimizer.md +175 -0
- package/.agent/agents/planner.md +133 -0
- package/.agent/agents/pr-reviewer.md +148 -0
- package/.agent/agents/python-reviewer.md +123 -0
- package/.agent/agents/refactor-cleaner.md +201 -0
- package/.agent/agents/reliability-engineer.md +156 -0
- package/.agent/agents/security-reviewer.md +141 -0
- package/.agent/agents/sprint-orchestrator.md +124 -0
- package/.agent/agents/tdd-guide.md +179 -0
- package/.agent/agents/typescript-reviewer.md +110 -0
- package/.agent/checklists/README.md +102 -0
- package/.agent/checklists/pre-commit.md +93 -0
- package/.agent/checklists/session-end.md +99 -0
- package/.agent/checklists/session-start.md +102 -0
- package/.agent/checklists/task-complete.md +81 -0
- package/.agent/commands/README.md +130 -0
- package/.agent/commands/adr.md +29 -0
- package/.agent/commands/ask.md +28 -0
- package/.agent/commands/build.md +30 -0
- package/.agent/commands/changelog.md +40 -0
- package/.agent/commands/checkpoint.md +28 -0
- package/.agent/commands/code-review.md +65 -0
- package/.agent/commands/compact.md +28 -0
- package/.agent/commands/cook.md +30 -0
- package/.agent/commands/db.md +30 -0
- package/.agent/commands/debug.md +31 -0
- package/.agent/commands/deploy.md +37 -0
- package/.agent/commands/design.md +29 -0
- package/.agent/commands/doc.md +30 -0
- package/.agent/commands/eval.md +30 -0
- package/.agent/commands/fix.md +32 -0
- package/.agent/commands/git.md +32 -0
- package/.agent/commands/help.md +273 -0
- package/.agent/commands/implement.md +30 -0
- package/.agent/commands/integrate.md +32 -0
- package/.agent/commands/learn.md +29 -0
- package/.agent/commands/perf.md +31 -0
- package/.agent/commands/plan.md +56 -0
- package/.agent/commands/pr-describe.md +65 -0
- package/.agent/commands/pr-fix.md +45 -0
- package/.agent/commands/pr-merge.md +45 -0
- package/.agent/commands/pr-review.md +50 -0
- package/.agent/commands/pr-split.md +54 -0
- package/.agent/commands/pr-status.md +56 -0
- package/.agent/commands/pr.md +58 -0
- package/.agent/commands/refactor.md +32 -0
- package/.agent/commands/research.md +28 -0
- package/.agent/commands/scout.md +30 -0
- package/.agent/commands/security-scan.md +33 -0
- package/.agent/commands/setup.md +31 -0
- package/.agent/commands/status.md +59 -0
- package/.agent/commands/tdd.md +73 -0
- package/.agent/commands/verify.md +58 -0
- package/.agent/contexts/brainstorm.md +26 -0
- package/.agent/contexts/debug.md +28 -0
- package/.agent/contexts/implement.md +29 -0
- package/.agent/contexts/plan-quality-log.md +30 -0
- package/.agent/contexts/review.md +27 -0
- package/.agent/contexts/ship.md +28 -0
- package/.agent/decisions/001-trust-grade-governance.md +46 -0
- package/.agent/decisions/002-cross-ide-generation.md +15 -0
- package/.agent/engine/identity.json +4 -0
- package/.agent/engine/loading-rules.json +193 -0
- package/.agent/engine/marketplace-index.json +29 -0
- package/.agent/engine/mcp-servers/filesystem.json +9 -0
- package/.agent/engine/mcp-servers/github.json +11 -0
- package/.agent/engine/mcp-servers/postgres.json +11 -0
- package/.agent/engine/mcp-servers/supabase.json +11 -0
- package/.agent/engine/mcp-servers/vercel.json +11 -0
- package/.agent/engine/reliability-config.json +14 -0
- package/.agent/engine/sdlc-map.json +50 -0
- package/.agent/engine/workflow-state.json +167 -0
- package/.agent/hooks/README.md +101 -0
- package/.agent/hooks/hooks.json +104 -0
- package/.agent/hooks/templates/session-end.md +110 -0
- package/.agent/hooks/templates/session-start.md +95 -0
- package/.agent/manifest.json +466 -0
- package/.agent/rules/agent-upgrade-policy.md +56 -0
- package/.agent/rules/architecture.md +111 -0
- package/.agent/rules/coding-style.md +75 -0
- package/.agent/rules/documentation.md +74 -0
- package/.agent/rules/git-workflow.md +140 -0
- package/.agent/rules/quality-gate.md +117 -0
- package/.agent/rules/security.md +67 -0
- package/.agent/rules/sprint-tracking.md +103 -0
- package/.agent/rules/testing.md +80 -0
- package/.agent/rules/workflow-standards.md +30 -0
- package/.agent/rules.md +293 -0
- package/.agent/session-context.md +69 -0
- package/.agent/session-state.json +27 -0
- package/.agent/skills/README.md +135 -0
- package/.agent/skills/api-patterns/SKILL.md +117 -0
- package/.agent/skills/app-builder/SKILL.md +202 -0
- package/.agent/skills/architecture/SKILL.md +101 -0
- package/.agent/skills/behavioral-modes/SKILL.md +295 -0
- package/.agent/skills/brainstorming/SKILL.md +156 -0
- package/.agent/skills/clean-code/SKILL.md +142 -0
- package/.agent/skills/context-budget/SKILL.md +78 -0
- package/.agent/skills/continuous-learning/SKILL.md +145 -0
- package/.agent/skills/database-design/SKILL.md +303 -0
- package/.agent/skills/debugging-strategies/SKILL.md +158 -0
- package/.agent/skills/deployment-procedures/SKILL.md +191 -0
- package/.agent/skills/docker-patterns/SKILL.md +161 -0
- package/.agent/skills/eval-harness/SKILL.md +89 -0
- package/.agent/skills/frontend-patterns/SKILL.md +141 -0
- package/.agent/skills/git-workflow/SKILL.md +159 -0
- package/.agent/skills/i18n-localization/SKILL.md +191 -0
- package/.agent/skills/intelligent-routing/SKILL.md +180 -0
- package/.agent/skills/mcp-integration/SKILL.md +240 -0
- package/.agent/skills/mobile-design/SKILL.md +191 -0
- package/.agent/skills/nodejs-patterns/SKILL.md +164 -0
- package/.agent/skills/parallel-agents/SKILL.md +200 -0
- package/.agent/skills/performance-profiling/SKILL.md +134 -0
- package/.agent/skills/plan-validation/SKILL.md +192 -0
- package/.agent/skills/plan-writing/SKILL.md +183 -0
- package/.agent/skills/plan-writing/domain-enhancers.md +184 -0
- package/.agent/skills/plan-writing/plan-retrospective.md +116 -0
- package/.agent/skills/plan-writing/plan-schema.md +119 -0
- package/.agent/skills/pr-toolkit/SKILL.md +174 -0
- package/.agent/skills/production-readiness/SKILL.md +126 -0
- package/.agent/skills/security-practices/SKILL.md +109 -0
- package/.agent/skills/shell-conventions/SKILL.md +92 -0
- package/.agent/skills/strategic-compact/SKILL.md +62 -0
- package/.agent/skills/testing-patterns/SKILL.md +141 -0
- package/.agent/skills/typescript-expert/SKILL.md +160 -0
- package/.agent/skills/ui-ux-pro-max/SKILL.md +137 -0
- package/.agent/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/.agent/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/.agent/skills/ui-ux-pro-max/data/icons.csv +101 -0
- package/.agent/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/.agent/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/.agent/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.agent/skills/ui-ux-pro-max/data/styles.csv +68 -0
- package/.agent/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/.agent/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/.agent/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.agent/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/.agent/skills/ui-ux-pro-max/scripts/core.py +253 -0
- package/.agent/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/.agent/skills/ui-ux-pro-max/scripts/search.py +114 -0
- package/.agent/skills/verification-loop/SKILL.md +89 -0
- package/.agent/skills/webapp-testing/SKILL.md +175 -0
- package/.agent/templates/adr-template.md +32 -0
- package/.agent/templates/bug-report.md +37 -0
- package/.agent/templates/feature-request.md +32 -0
- package/.agent/workflows/README.md +101 -0
- package/.agent/workflows/brainstorm.md +86 -0
- package/.agent/workflows/create.md +85 -0
- package/.agent/workflows/debug.md +83 -0
- package/.agent/workflows/deploy.md +114 -0
- package/.agent/workflows/enhance.md +85 -0
- package/.agent/workflows/orchestrate.md +106 -0
- package/.agent/workflows/plan.md +105 -0
- package/.agent/workflows/pr-fix.md +163 -0
- package/.agent/workflows/pr-merge.md +117 -0
- package/.agent/workflows/pr-review.md +178 -0
- package/.agent/workflows/pr-split.md +118 -0
- package/.agent/workflows/pr.md +184 -0
- package/.agent/workflows/preflight.md +107 -0
- package/.agent/workflows/preview.md +95 -0
- package/.agent/workflows/quality-gate.md +103 -0
- package/.agent/workflows/retrospective.md +100 -0
- package/.agent/workflows/review.md +104 -0
- package/.agent/workflows/status.md +89 -0
- package/.agent/workflows/test.md +98 -0
- package/.agent/workflows/ui-ux-pro-max.md +93 -0
- package/.agent/workflows/upgrade.md +97 -0
- package/LICENSE +21 -0
- package/README.md +218 -0
- package/bin/kit.js +773 -0
- package/lib/agent-registry.js +228 -0
- package/lib/agent-reputation.js +343 -0
- package/lib/circuit-breaker.js +195 -0
- package/lib/cli-commands.js +322 -0
- package/lib/config-validator.js +274 -0
- package/lib/conflict-detector.js +252 -0
- package/lib/constants.js +47 -0
- package/lib/engineering-manager.js +336 -0
- package/lib/error-budget.js +370 -0
- package/lib/hook-system.js +256 -0
- package/lib/ide-generator.js +434 -0
- package/lib/identity.js +240 -0
- package/lib/io.js +146 -0
- package/lib/learning-engine.js +163 -0
- package/lib/loading-engine.js +421 -0
- package/lib/logger.js +118 -0
- package/lib/marketplace.js +321 -0
- package/lib/plugin-system.js +604 -0
- package/lib/plugin-verifier.js +197 -0
- package/lib/rate-limiter.js +113 -0
- package/lib/security-scanner.js +312 -0
- package/lib/self-healing.js +468 -0
- package/lib/session-manager.js +264 -0
- package/lib/skill-sandbox.js +244 -0
- package/lib/task-governance.js +522 -0
- package/lib/task-model.js +332 -0
- package/lib/updater.js +240 -0
- package/lib/verify.js +279 -0
- package/lib/workflow-engine.js +373 -0
- package/lib/workflow-events.js +166 -0
- package/lib/workflow-persistence.js +160 -0
- package/package.json +57 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Devran AI Kit — Plugin Signature Verification
|
|
3
|
+
*
|
|
4
|
+
* Generates and validates SHA-256 checksums for plugin integrity.
|
|
5
|
+
* Prevents supply chain attacks by verifying plugin contents
|
|
6
|
+
* have not been tampered with after installation.
|
|
7
|
+
*
|
|
8
|
+
* @module lib/plugin-verifier
|
|
9
|
+
* @author Emre Dursun
|
|
10
|
+
* @since v3.2.0
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
const crypto = require('crypto');
|
|
18
|
+
const { AGENT_DIR, ENGINE_DIR, PLUGINS_DIR } = require('./constants');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {object} PluginChecksum
|
|
22
|
+
* @property {string} pluginName - Plugin name
|
|
23
|
+
* @property {string} checksum - SHA-256 checksum of concatenated file contents
|
|
24
|
+
* @property {string[]} files - Files included in checksum
|
|
25
|
+
* @property {string} generatedAt - ISO timestamp
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Collects all files in a directory recursively.
|
|
30
|
+
*
|
|
31
|
+
* @param {string} dirPath - Directory to scan
|
|
32
|
+
* @returns {string[]} Sorted list of relative file paths
|
|
33
|
+
*/
|
|
34
|
+
function collectPluginFiles(dirPath) {
|
|
35
|
+
if (!fs.existsSync(dirPath)) {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const files = [];
|
|
40
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
41
|
+
|
|
42
|
+
for (const entry of entries) {
|
|
43
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
44
|
+
|
|
45
|
+
if (entry.isDirectory() && entry.name !== 'node_modules' && entry.name !== '.git') {
|
|
46
|
+
files.push(...collectPluginFiles(fullPath));
|
|
47
|
+
} else if (entry.isFile()) {
|
|
48
|
+
files.push(fullPath);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return files.sort();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Generates a SHA-256 checksum for a plugin directory.
|
|
57
|
+
*
|
|
58
|
+
* @param {string} pluginDir - Path to plugin directory
|
|
59
|
+
* @returns {PluginChecksum}
|
|
60
|
+
*/
|
|
61
|
+
function generateChecksum(pluginDir) {
|
|
62
|
+
const files = collectPluginFiles(pluginDir);
|
|
63
|
+
const hash = crypto.createHash('sha256');
|
|
64
|
+
const relativeFiles = [];
|
|
65
|
+
|
|
66
|
+
for (const file of files) {
|
|
67
|
+
const relativePath = path.relative(pluginDir, file).replace(/\\/g, '/');
|
|
68
|
+
relativeFiles.push(relativePath);
|
|
69
|
+
|
|
70
|
+
// Hash both the file path and contents for integrity
|
|
71
|
+
hash.update(relativePath);
|
|
72
|
+
hash.update(fs.readFileSync(file));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const manifestPath = path.join(pluginDir, 'plugin.json');
|
|
76
|
+
let pluginName = 'unknown';
|
|
77
|
+
if (fs.existsSync(manifestPath)) {
|
|
78
|
+
try {
|
|
79
|
+
pluginName = JSON.parse(fs.readFileSync(manifestPath, 'utf-8')).name || 'unknown';
|
|
80
|
+
} catch {
|
|
81
|
+
// Use default
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
pluginName,
|
|
87
|
+
checksum: hash.digest('hex'),
|
|
88
|
+
files: relativeFiles,
|
|
89
|
+
generatedAt: new Date().toISOString(),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Verifies a plugin's current state matches its stored checksum.
|
|
95
|
+
*
|
|
96
|
+
* @param {string} pluginDir - Path to plugin directory
|
|
97
|
+
* @param {string} expectedChecksum - Expected SHA-256 checksum
|
|
98
|
+
* @returns {{ valid: boolean, currentChecksum: string, expectedChecksum: string }}
|
|
99
|
+
*/
|
|
100
|
+
function verifyChecksum(pluginDir, expectedChecksum) {
|
|
101
|
+
const current = generateChecksum(pluginDir);
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
valid: current.checksum === expectedChecksum,
|
|
105
|
+
currentChecksum: current.checksum,
|
|
106
|
+
expectedChecksum,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Stores a checksum in the plugin's registry entry.
|
|
112
|
+
*
|
|
113
|
+
* @param {string} projectRoot - Root directory
|
|
114
|
+
* @param {string} pluginName - Plugin name
|
|
115
|
+
* @param {string} checksum - SHA-256 checksum to store
|
|
116
|
+
* @returns {void}
|
|
117
|
+
*/
|
|
118
|
+
function storeChecksum(projectRoot, pluginName, checksum) {
|
|
119
|
+
const checksumDir = path.join(projectRoot, AGENT_DIR, ENGINE_DIR, 'plugin-checksums');
|
|
120
|
+
|
|
121
|
+
if (!fs.existsSync(checksumDir)) {
|
|
122
|
+
fs.mkdirSync(checksumDir, { recursive: true });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const checksumPath = path.join(checksumDir, `${pluginName}.sha256`);
|
|
126
|
+
fs.writeFileSync(checksumPath, checksum, 'utf-8');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Retrieves a stored checksum for a plugin.
|
|
131
|
+
*
|
|
132
|
+
* @param {string} projectRoot - Root directory
|
|
133
|
+
* @param {string} pluginName - Plugin name
|
|
134
|
+
* @returns {string | null}
|
|
135
|
+
*/
|
|
136
|
+
function getStoredChecksum(projectRoot, pluginName) {
|
|
137
|
+
const checksumPath = path.join(projectRoot, AGENT_DIR, ENGINE_DIR, 'plugin-checksums', `${pluginName}.sha256`);
|
|
138
|
+
|
|
139
|
+
if (!fs.existsSync(checksumPath)) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return fs.readFileSync(checksumPath, 'utf-8').trim();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Verifies all installed plugins against their stored checksums.
|
|
148
|
+
*
|
|
149
|
+
* @param {string} projectRoot - Root directory
|
|
150
|
+
* @returns {{ total: number, valid: number, invalid: string[], unverified: string[] }}
|
|
151
|
+
*/
|
|
152
|
+
function verifyAllPlugins(projectRoot) {
|
|
153
|
+
const pluginsDir = path.join(projectRoot, AGENT_DIR, PLUGINS_DIR);
|
|
154
|
+
|
|
155
|
+
if (!fs.existsSync(pluginsDir)) {
|
|
156
|
+
return { total: 0, valid: 0, invalid: [], unverified: [] };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const entries = fs.readdirSync(pluginsDir, { withFileTypes: true })
|
|
160
|
+
.filter((e) => e.isDirectory());
|
|
161
|
+
|
|
162
|
+
const invalid = [];
|
|
163
|
+
const unverified = [];
|
|
164
|
+
let valid = 0;
|
|
165
|
+
|
|
166
|
+
for (const entry of entries) {
|
|
167
|
+
const pluginDir = path.join(pluginsDir, entry.name);
|
|
168
|
+
const storedChecksum = getStoredChecksum(projectRoot, entry.name);
|
|
169
|
+
|
|
170
|
+
if (!storedChecksum) {
|
|
171
|
+
unverified.push(entry.name);
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const result = verifyChecksum(pluginDir, storedChecksum);
|
|
176
|
+
if (result.valid) {
|
|
177
|
+
valid += 1;
|
|
178
|
+
} else {
|
|
179
|
+
invalid.push(entry.name);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
total: entries.length,
|
|
185
|
+
valid,
|
|
186
|
+
invalid,
|
|
187
|
+
unverified,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
module.exports = {
|
|
192
|
+
generateChecksum,
|
|
193
|
+
verifyChecksum,
|
|
194
|
+
storeChecksum,
|
|
195
|
+
getStoredChecksum,
|
|
196
|
+
verifyAllPlugins,
|
|
197
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Devran AI Kit — Rate Limiter
|
|
3
|
+
*
|
|
4
|
+
* Token bucket rate limiter for protecting external operations
|
|
5
|
+
* (marketplace git clones, API calls) from abuse and resource exhaustion.
|
|
6
|
+
*
|
|
7
|
+
* @module lib/rate-limiter
|
|
8
|
+
* @author Emre Dursun
|
|
9
|
+
* @since v3.2.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {object} RateLimiterOptions
|
|
16
|
+
* @property {number} [maxTokens=5] - Maximum tokens (burst capacity)
|
|
17
|
+
* @property {number} [refillRateMs=60000] - Time to refill one token (ms)
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {object} RateLimiterState
|
|
22
|
+
* @property {string} name - Limiter name
|
|
23
|
+
* @property {number} tokens - Current available tokens
|
|
24
|
+
* @property {number} maxTokens - Maximum capacity
|
|
25
|
+
* @property {number} refillRateMs - Refill interval per token
|
|
26
|
+
* @property {number} lastRefillTime - Last refill timestamp
|
|
27
|
+
* @property {number} totalAllowed - Lifetime allowed count
|
|
28
|
+
* @property {number} totalRejected - Lifetime rejected count
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Creates a new rate limiter instance.
|
|
33
|
+
*
|
|
34
|
+
* @param {string} name - Rate limiter name for identification
|
|
35
|
+
* @param {RateLimiterOptions} [options] - Configuration
|
|
36
|
+
* @returns {{ tryAcquire: Function, getState: Function, reset: Function }}
|
|
37
|
+
*/
|
|
38
|
+
function createRateLimiter(name, options = {}) {
|
|
39
|
+
const maxTokens = options.maxTokens || 5;
|
|
40
|
+
const refillRateMs = options.refillRateMs || 60000;
|
|
41
|
+
|
|
42
|
+
/** @type {RateLimiterState} */
|
|
43
|
+
const state = {
|
|
44
|
+
name,
|
|
45
|
+
tokens: maxTokens,
|
|
46
|
+
maxTokens,
|
|
47
|
+
refillRateMs,
|
|
48
|
+
lastRefillTime: Date.now(),
|
|
49
|
+
totalAllowed: 0,
|
|
50
|
+
totalRejected: 0,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Refills tokens based on elapsed time since last refill.
|
|
55
|
+
*
|
|
56
|
+
* @returns {void}
|
|
57
|
+
*/
|
|
58
|
+
function refill() {
|
|
59
|
+
const now = Date.now();
|
|
60
|
+
const elapsed = now - state.lastRefillTime;
|
|
61
|
+
const tokensToAdd = Math.floor(elapsed / refillRateMs);
|
|
62
|
+
|
|
63
|
+
if (tokensToAdd > 0) {
|
|
64
|
+
state.tokens = Math.min(maxTokens, state.tokens + tokensToAdd);
|
|
65
|
+
state.lastRefillTime = now;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Attempts to acquire a token for an operation.
|
|
71
|
+
*
|
|
72
|
+
* @returns {{ allowed: boolean, retryAfterMs?: number }}
|
|
73
|
+
*/
|
|
74
|
+
function tryAcquire() {
|
|
75
|
+
refill();
|
|
76
|
+
|
|
77
|
+
if (state.tokens > 0) {
|
|
78
|
+
state.tokens -= 1;
|
|
79
|
+
state.totalAllowed += 1;
|
|
80
|
+
return { allowed: true };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
state.totalRejected += 1;
|
|
84
|
+
const timeSinceLastRefill = Date.now() - state.lastRefillTime;
|
|
85
|
+
const retryAfterMs = Math.max(0, refillRateMs - timeSinceLastRefill);
|
|
86
|
+
|
|
87
|
+
return { allowed: false, retryAfterMs };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Returns a snapshot of the rate limiter state.
|
|
92
|
+
*
|
|
93
|
+
* @returns {RateLimiterState}
|
|
94
|
+
*/
|
|
95
|
+
function getState() {
|
|
96
|
+
refill();
|
|
97
|
+
return { ...state };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Resets the rate limiter to full capacity.
|
|
102
|
+
*
|
|
103
|
+
* @returns {void}
|
|
104
|
+
*/
|
|
105
|
+
function reset() {
|
|
106
|
+
state.tokens = maxTokens;
|
|
107
|
+
state.lastRefillTime = Date.now();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return { tryAcquire, getState, reset };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = { createRateLimiter };
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Devran AI Kit — Enhanced Security Scanner
|
|
3
|
+
*
|
|
4
|
+
* Runtime injection detection, secret scanning, and anomaly alerting
|
|
5
|
+
* for agent and skill files.
|
|
6
|
+
*
|
|
7
|
+
* @module lib/security-scanner
|
|
8
|
+
* @author Emre Dursun
|
|
9
|
+
* @since v3.0.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
const { AGENT_DIR } = require('./constants');
|
|
18
|
+
|
|
19
|
+
/** Paths that are known-safe and should be excluded from injection/secret scanning */
|
|
20
|
+
const ALLOWLISTED_DIRS = ['decisions', 'engine'];
|
|
21
|
+
|
|
22
|
+
/** Prompt injection patterns */
|
|
23
|
+
const INJECTION_PATTERNS = [
|
|
24
|
+
{ pattern: /ignore\s+(all\s+)?previous\s+instructions/gi, name: 'ignore-previous', severity: 'critical' },
|
|
25
|
+
{ pattern: /system\s+override/gi, name: 'system-override', severity: 'critical' },
|
|
26
|
+
{ pattern: /you\s+are\s+now\s+a/gi, name: 'role-hijacking', severity: 'critical' },
|
|
27
|
+
{ pattern: /act\s+as\s+(if\s+you\s+are|a\s+different)/gi, name: 'persona-override', severity: 'high' },
|
|
28
|
+
{ pattern: /forget\s+(everything|your|all)/gi, name: 'memory-wipe', severity: 'critical' },
|
|
29
|
+
{ pattern: /disregard\s+(all|your|the)/gi, name: 'disregard-instructions', severity: 'high' },
|
|
30
|
+
{ pattern: /\bDAN\b.*\bjailbreak\b/gi, name: 'jailbreak-reference', severity: 'critical' },
|
|
31
|
+
{ pattern: /bypass\s+(safety|security|filter|restriction)/gi, name: 'bypass-safety', severity: 'critical' },
|
|
32
|
+
{ pattern: /pretend\s+(you|that)\s+(are|have)\s+no\s+(restrictions|rules|limits)/gi, name: 'pretend-no-rules', severity: 'high' },
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
/** Secret patterns */
|
|
36
|
+
const SECRET_PATTERNS = [
|
|
37
|
+
{ pattern: /(?:api[_-]?key|apikey)\s*[:=]\s*['"][A-Za-z0-9_\-]{20,}['"]/gi, name: 'api-key', severity: 'critical' },
|
|
38
|
+
{ pattern: /ghp_[A-Za-z0-9_]{36,}/g, name: 'github-pat', severity: 'critical' },
|
|
39
|
+
{ pattern: /gho_[A-Za-z0-9_]{36,}/g, name: 'github-oauth', severity: 'critical' },
|
|
40
|
+
{ pattern: /sk-[A-Za-z0-9]{32,}/g, name: 'openai-key', severity: 'critical' },
|
|
41
|
+
{ pattern: /AKIA[A-Z0-9]{16}/g, name: 'aws-access-key', severity: 'critical' },
|
|
42
|
+
{ pattern: /-----BEGIN\s+(RSA\s+)?PRIVATE\s+KEY-----/g, name: 'private-key', severity: 'critical' },
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
/** Maximum expected file size for agent/skill files (100KB) */
|
|
46
|
+
const MAX_EXPECTED_SIZE = 100 * 1024;
|
|
47
|
+
/** Minimum expected file size for legitimate files */
|
|
48
|
+
const MIN_EXPECTED_SIZE = 50;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @typedef {object} SecurityFinding
|
|
52
|
+
* @property {string} type - Finding type (injection, secret, anomaly)
|
|
53
|
+
* @property {string} name - Pattern name
|
|
54
|
+
* @property {'critical' | 'high' | 'medium' | 'low'} severity - Finding severity
|
|
55
|
+
* @property {string} file - File where finding was detected
|
|
56
|
+
* @property {number} [line] - Line number
|
|
57
|
+
* @property {string} detail - Description of the finding
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @typedef {object} SecurityReport
|
|
62
|
+
* @property {number} filesScanned - Total files scanned
|
|
63
|
+
* @property {number} criticalCount - Critical findings count
|
|
64
|
+
* @property {number} highCount - High findings count
|
|
65
|
+
* @property {number} mediumCount - Medium findings count
|
|
66
|
+
* @property {number} lowCount - Low findings count
|
|
67
|
+
* @property {SecurityFinding[]} findings - All findings
|
|
68
|
+
* @property {boolean} clean - Whether no critical/high findings exist
|
|
69
|
+
*/
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Recursively collects scannable files from a directory.
|
|
73
|
+
*
|
|
74
|
+
* @param {string} dirPath - Directory to scan
|
|
75
|
+
* @param {string} projectRoot - Project root for relative paths
|
|
76
|
+
* @returns {string[]}
|
|
77
|
+
*/
|
|
78
|
+
function collectFiles(dirPath, projectRoot) {
|
|
79
|
+
/** @type {string[]} */
|
|
80
|
+
const files = [];
|
|
81
|
+
|
|
82
|
+
if (!fs.existsSync(dirPath)) {
|
|
83
|
+
return files;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
87
|
+
|
|
88
|
+
for (const entry of entries) {
|
|
89
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
90
|
+
|
|
91
|
+
if (entry.isDirectory() && entry.name !== 'node_modules' && entry.name !== '.git') {
|
|
92
|
+
files.push(...collectFiles(fullPath, projectRoot));
|
|
93
|
+
} else if (entry.isFile() && (entry.name.endsWith('.md') || entry.name.endsWith('.json') || entry.name.endsWith('.js'))) {
|
|
94
|
+
files.push(fullPath);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return files;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Scans for prompt injection patterns.
|
|
103
|
+
*
|
|
104
|
+
* @param {string} projectRoot - Root directory of the project
|
|
105
|
+
* @returns {SecurityFinding[]}
|
|
106
|
+
*/
|
|
107
|
+
function scanForInjection(projectRoot) {
|
|
108
|
+
const agentDir = path.join(projectRoot, AGENT_DIR);
|
|
109
|
+
const files = collectFiles(agentDir, projectRoot);
|
|
110
|
+
/** @type {SecurityFinding[]} */
|
|
111
|
+
const findings = [];
|
|
112
|
+
|
|
113
|
+
for (const file of files) {
|
|
114
|
+
const relativePath = path.relative(path.join(projectRoot, AGENT_DIR), file);
|
|
115
|
+
|
|
116
|
+
// Skip allowlisted directories (governance docs, engine configs)
|
|
117
|
+
if (ALLOWLISTED_DIRS.some((dir) => relativePath.startsWith(dir + path.sep) || relativePath.startsWith(dir + '/'))) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
121
|
+
const lines = content.split('\n');
|
|
122
|
+
|
|
123
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
|
124
|
+
const line = lines[lineIndex];
|
|
125
|
+
|
|
126
|
+
for (const { pattern, name, severity } of INJECTION_PATTERNS) {
|
|
127
|
+
// Reset regex lastIndex for global patterns
|
|
128
|
+
pattern.lastIndex = 0;
|
|
129
|
+
if (pattern.test(line)) {
|
|
130
|
+
findings.push({
|
|
131
|
+
type: 'injection',
|
|
132
|
+
name,
|
|
133
|
+
severity,
|
|
134
|
+
file: path.relative(projectRoot, file),
|
|
135
|
+
line: lineIndex + 1,
|
|
136
|
+
detail: `Prompt injection pattern "${name}" detected`,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return findings;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Scans for hardcoded secrets.
|
|
148
|
+
*
|
|
149
|
+
* @param {string} projectRoot - Root directory of the project
|
|
150
|
+
* @returns {SecurityFinding[]}
|
|
151
|
+
*/
|
|
152
|
+
function scanForSecrets(projectRoot) {
|
|
153
|
+
const agentDir = path.join(projectRoot, AGENT_DIR);
|
|
154
|
+
const files = collectFiles(agentDir, projectRoot);
|
|
155
|
+
/** @type {SecurityFinding[]} */
|
|
156
|
+
const findings = [];
|
|
157
|
+
|
|
158
|
+
for (const file of files) {
|
|
159
|
+
const relativePath = path.relative(path.join(projectRoot, AGENT_DIR), file);
|
|
160
|
+
|
|
161
|
+
// Skip allowlisted directories and skill documentation
|
|
162
|
+
if (ALLOWLISTED_DIRS.some((dir) => relativePath.startsWith(dir + path.sep) || relativePath.startsWith(dir + '/'))) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
167
|
+
const lines = content.split('\n');
|
|
168
|
+
|
|
169
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
|
170
|
+
const line = lines[lineIndex];
|
|
171
|
+
|
|
172
|
+
for (const { pattern, name, severity } of SECRET_PATTERNS) {
|
|
173
|
+
pattern.lastIndex = 0;
|
|
174
|
+
if (pattern.test(line)) {
|
|
175
|
+
findings.push({
|
|
176
|
+
type: 'secret',
|
|
177
|
+
name,
|
|
178
|
+
severity,
|
|
179
|
+
file: path.relative(projectRoot, file),
|
|
180
|
+
line: lineIndex + 1,
|
|
181
|
+
detail: `Potential ${name} found — never commit secrets`,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return findings;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Scans for file anomalies (unusual sizes, unexpected file types).
|
|
193
|
+
*
|
|
194
|
+
* @param {string} projectRoot - Root directory of the project
|
|
195
|
+
* @returns {SecurityFinding[]}
|
|
196
|
+
*/
|
|
197
|
+
function scanForAnomalies(projectRoot) {
|
|
198
|
+
const agentDir = path.join(projectRoot, AGENT_DIR);
|
|
199
|
+
/** @type {SecurityFinding[]} */
|
|
200
|
+
const findings = [];
|
|
201
|
+
|
|
202
|
+
if (!fs.existsSync(agentDir)) {
|
|
203
|
+
return findings;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const allFiles = collectAllFiles(agentDir);
|
|
207
|
+
|
|
208
|
+
for (const file of allFiles) {
|
|
209
|
+
const stats = fs.statSync(file);
|
|
210
|
+
const relativePath = path.relative(projectRoot, file);
|
|
211
|
+
const ext = path.extname(file).toLowerCase();
|
|
212
|
+
|
|
213
|
+
// Check for binary files in agent directories
|
|
214
|
+
const textExtensions = ['.md', '.json', '.js', '.ts', '.yaml', '.yml', '.txt', '.csv', '.toml'];
|
|
215
|
+
if (!textExtensions.includes(ext) && ext !== '') {
|
|
216
|
+
findings.push({
|
|
217
|
+
type: 'anomaly',
|
|
218
|
+
name: 'unexpected-file-type',
|
|
219
|
+
severity: 'medium',
|
|
220
|
+
file: relativePath,
|
|
221
|
+
detail: `Unexpected file type "${ext}" in agent directory`,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Check for oversized files
|
|
226
|
+
if (stats.size > MAX_EXPECTED_SIZE) {
|
|
227
|
+
findings.push({
|
|
228
|
+
type: 'anomaly',
|
|
229
|
+
name: 'oversized-file',
|
|
230
|
+
severity: 'high',
|
|
231
|
+
file: relativePath,
|
|
232
|
+
detail: `File size ${Math.round(stats.size / 1024)}KB exceeds ${MAX_EXPECTED_SIZE / 1024}KB limit`,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Check for suspiciously small files (possible stubs)
|
|
237
|
+
if (stats.size < MIN_EXPECTED_SIZE && stats.size > 0 && textExtensions.includes(ext)) {
|
|
238
|
+
findings.push({
|
|
239
|
+
type: 'anomaly',
|
|
240
|
+
name: 'tiny-file',
|
|
241
|
+
severity: 'low',
|
|
242
|
+
file: relativePath,
|
|
243
|
+
detail: `File is only ${stats.size} bytes — possible incomplete stub`,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return findings;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Collects ALL files (not just text) from a directory.
|
|
253
|
+
*
|
|
254
|
+
* @param {string} dirPath - Directory to scan
|
|
255
|
+
* @returns {string[]}
|
|
256
|
+
*/
|
|
257
|
+
function collectAllFiles(dirPath) {
|
|
258
|
+
/** @type {string[]} */
|
|
259
|
+
const files = [];
|
|
260
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
261
|
+
|
|
262
|
+
for (const entry of entries) {
|
|
263
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
264
|
+
|
|
265
|
+
// Security: skip symlinks to prevent path traversal
|
|
266
|
+
const stat = fs.lstatSync(fullPath);
|
|
267
|
+
if (stat.isSymbolicLink()) {
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (entry.isDirectory() && entry.name !== 'node_modules' && entry.name !== '.git') {
|
|
272
|
+
files.push(...collectAllFiles(fullPath));
|
|
273
|
+
} else if (entry.isFile()) {
|
|
274
|
+
files.push(fullPath);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return files;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Generates a comprehensive security report.
|
|
283
|
+
*
|
|
284
|
+
* @param {string} projectRoot - Root directory of the project
|
|
285
|
+
* @returns {SecurityReport}
|
|
286
|
+
*/
|
|
287
|
+
function getSecurityReport(projectRoot) {
|
|
288
|
+
const injectionFindings = scanForInjection(projectRoot);
|
|
289
|
+
const secretFindings = scanForSecrets(projectRoot);
|
|
290
|
+
const anomalyFindings = scanForAnomalies(projectRoot);
|
|
291
|
+
|
|
292
|
+
const allFindings = [...injectionFindings, ...secretFindings, ...anomalyFindings];
|
|
293
|
+
const agentDir = path.join(projectRoot, AGENT_DIR);
|
|
294
|
+
const filesScanned = fs.existsSync(agentDir) ? collectAllFiles(agentDir).length : 0;
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
filesScanned,
|
|
298
|
+
criticalCount: allFindings.filter((f) => f.severity === 'critical').length,
|
|
299
|
+
highCount: allFindings.filter((f) => f.severity === 'high').length,
|
|
300
|
+
mediumCount: allFindings.filter((f) => f.severity === 'medium').length,
|
|
301
|
+
lowCount: allFindings.filter((f) => f.severity === 'low').length,
|
|
302
|
+
findings: allFindings,
|
|
303
|
+
clean: allFindings.filter((f) => f.severity === 'critical' || f.severity === 'high').length === 0,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
module.exports = {
|
|
308
|
+
scanForInjection,
|
|
309
|
+
scanForSecrets,
|
|
310
|
+
scanForAnomalies,
|
|
311
|
+
getSecurityReport,
|
|
312
|
+
};
|