@massu/core 0.1.1 → 0.4.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/commands/_shared-preamble.md +76 -0
- package/commands/massu-audit-deps.md +211 -0
- package/commands/massu-changelog.md +174 -0
- package/commands/massu-cleanup.md +315 -0
- package/commands/massu-commit.md +481 -0
- package/commands/massu-create-plan.md +752 -0
- package/commands/massu-dead-code.md +131 -0
- package/commands/massu-debug.md +484 -0
- package/commands/massu-deploy.md +91 -0
- package/commands/massu-deps.md +374 -0
- package/commands/massu-doc-gen.md +279 -0
- package/commands/massu-docs.md +364 -0
- package/commands/massu-estimate.md +313 -0
- package/commands/massu-golden-path.md +973 -0
- package/commands/massu-guide.md +167 -0
- package/commands/massu-hotfix.md +480 -0
- package/commands/massu-loop-playwright.md +837 -0
- package/commands/massu-loop.md +775 -0
- package/commands/massu-new-feature.md +511 -0
- package/commands/massu-parity.md +214 -0
- package/commands/massu-plan.md +456 -0
- package/commands/massu-push-light.md +207 -0
- package/commands/massu-push.md +434 -0
- package/commands/massu-refactor.md +410 -0
- package/commands/massu-release.md +363 -0
- package/commands/massu-review.md +238 -0
- package/commands/massu-simplify.md +281 -0
- package/commands/massu-status.md +278 -0
- package/commands/massu-tdd.md +201 -0
- package/commands/massu-test.md +516 -0
- package/commands/massu-verify-playwright.md +281 -0
- package/commands/massu-verify.md +667 -0
- package/dist/cli.js +7772 -3140
- package/dist/hooks/cost-tracker.js +103 -40
- package/dist/hooks/post-edit-context.js +74 -8
- package/dist/hooks/post-tool-use.js +268 -106
- package/dist/hooks/pre-compact.js +167 -43
- package/dist/hooks/pre-delete-check.js +159 -42
- package/dist/hooks/quality-event.js +103 -40
- package/dist/hooks/security-gate.js +29 -0
- package/dist/hooks/session-end.js +143 -84
- package/dist/hooks/session-start.js +186 -49
- package/dist/hooks/user-prompt.js +189 -43
- package/package.json +10 -15
- package/src/adr-generator.ts +9 -2
- package/src/analytics.ts +9 -3
- package/src/audit-trail.ts +10 -3
- package/src/backfill-sessions.ts +5 -4
- package/src/cli.ts +6 -0
- package/src/cloud-sync.ts +14 -18
- package/src/commands/doctor.ts +193 -6
- package/src/commands/init.ts +230 -5
- package/src/commands/install-commands.ts +137 -0
- package/src/config.ts +68 -2
- package/src/cost-tracker.ts +11 -6
- package/src/db.ts +115 -2
- package/src/dependency-scorer.ts +9 -2
- package/src/docs-tools.ts +21 -16
- package/src/hooks/post-edit-context.ts +4 -4
- package/src/hooks/post-tool-use.ts +130 -0
- package/src/hooks/pre-compact.ts +23 -1
- package/src/hooks/pre-delete-check.ts +92 -4
- package/src/hooks/security-gate.ts +32 -0
- package/src/hooks/session-end.ts +3 -3
- package/src/hooks/session-start.ts +99 -6
- package/src/hooks/user-prompt.ts +46 -1
- package/src/import-resolver.ts +2 -1
- package/src/knowledge-db.ts +169 -0
- package/src/knowledge-indexer.ts +704 -0
- package/src/knowledge-tools.ts +1413 -0
- package/src/license.ts +482 -0
- package/src/memory-db.ts +1364 -23
- package/src/memory-tools.ts +14 -15
- package/src/observability-tools.ts +13 -2
- package/src/observation-extractor.ts +11 -4
- package/src/page-deps.ts +3 -2
- package/src/prompt-analyzer.ts +9 -2
- package/src/python/coupling-detector.ts +124 -0
- package/src/python/domain-enforcer.ts +83 -0
- package/src/python/impact-analyzer.ts +95 -0
- package/src/python/import-parser.ts +244 -0
- package/src/python/import-resolver.ts +135 -0
- package/src/python/migration-indexer.ts +115 -0
- package/src/python/migration-parser.ts +332 -0
- package/src/python/model-indexer.ts +70 -0
- package/src/python/model-parser.ts +279 -0
- package/src/python/route-indexer.ts +58 -0
- package/src/python/route-parser.ts +317 -0
- package/src/python-tools.ts +629 -0
- package/src/regression-detector.ts +9 -3
- package/src/security-scorer.ts +9 -2
- package/src/sentinel-db.ts +45 -89
- package/src/sentinel-tools.ts +8 -11
- package/src/server.ts +29 -7
- package/src/session-archiver.ts +4 -5
- package/src/team-knowledge.ts +9 -2
- package/src/tools.ts +1032 -44
- package/src/validate-features-runner.ts +0 -1
- package/src/validation-engine.ts +9 -2
- package/README.md +0 -40
- package/dist/server.js +0 -7008
- package/src/__tests__/adr-generator.test.ts +0 -260
- package/src/__tests__/analytics.test.ts +0 -282
- package/src/__tests__/audit-trail.test.ts +0 -382
- package/src/__tests__/backfill-sessions.test.ts +0 -690
- package/src/__tests__/cli.test.ts +0 -290
- package/src/__tests__/cloud-sync.test.ts +0 -261
- package/src/__tests__/config-sections.test.ts +0 -359
- package/src/__tests__/config.test.ts +0 -732
- package/src/__tests__/cost-tracker.test.ts +0 -348
- package/src/__tests__/db.test.ts +0 -177
- package/src/__tests__/dependency-scorer.test.ts +0 -325
- package/src/__tests__/docs-integration.test.ts +0 -178
- package/src/__tests__/docs-tools.test.ts +0 -199
- package/src/__tests__/domains.test.ts +0 -236
- package/src/__tests__/hooks.test.ts +0 -221
- package/src/__tests__/import-resolver.test.ts +0 -95
- package/src/__tests__/integration/path-traversal.test.ts +0 -134
- package/src/__tests__/integration/pricing-consistency.test.ts +0 -88
- package/src/__tests__/integration/tool-registration.test.ts +0 -146
- package/src/__tests__/memory-db.test.ts +0 -404
- package/src/__tests__/memory-enhancements.test.ts +0 -316
- package/src/__tests__/memory-tools.test.ts +0 -199
- package/src/__tests__/middleware-tree.test.ts +0 -177
- package/src/__tests__/observability-tools.test.ts +0 -595
- package/src/__tests__/observability.test.ts +0 -437
- package/src/__tests__/observation-extractor.test.ts +0 -167
- package/src/__tests__/page-deps.test.ts +0 -60
- package/src/__tests__/prompt-analyzer.test.ts +0 -298
- package/src/__tests__/regression-detector.test.ts +0 -295
- package/src/__tests__/rules.test.ts +0 -87
- package/src/__tests__/schema-mapper.test.ts +0 -29
- package/src/__tests__/security-scorer.test.ts +0 -238
- package/src/__tests__/security-utils.test.ts +0 -175
- package/src/__tests__/sentinel-db.test.ts +0 -491
- package/src/__tests__/sentinel-scanner.test.ts +0 -750
- package/src/__tests__/sentinel-tools.test.ts +0 -324
- package/src/__tests__/sentinel-types.test.ts +0 -750
- package/src/__tests__/server.test.ts +0 -452
- package/src/__tests__/session-archiver.test.ts +0 -524
- package/src/__tests__/session-state-generator.test.ts +0 -900
- package/src/__tests__/team-knowledge.test.ts +0 -327
- package/src/__tests__/tools.test.ts +0 -340
- package/src/__tests__/transcript-parser.test.ts +0 -195
- package/src/__tests__/trpc-index.test.ts +0 -25
- package/src/__tests__/validate-features-runner.test.ts +0 -517
- package/src/__tests__/validation-engine.test.ts +0 -300
- package/src/core-tools.ts +0 -685
- package/src/memory-queries.ts +0 -804
- package/src/memory-schema.ts +0 -546
- package/src/tool-helpers.ts +0 -41
|
@@ -3,12 +3,13 @@ import{createRequire as __cr}from"module";const require=__cr(import.meta.url);
|
|
|
3
3
|
|
|
4
4
|
// src/memory-db.ts
|
|
5
5
|
import Database from "better-sqlite3";
|
|
6
|
-
import { dirname as dirname2 } from "path";
|
|
6
|
+
import { dirname as dirname2, basename } from "path";
|
|
7
7
|
import { existsSync as existsSync2, mkdirSync } from "fs";
|
|
8
8
|
|
|
9
9
|
// src/config.ts
|
|
10
10
|
import { resolve, dirname } from "path";
|
|
11
11
|
import { existsSync, readFileSync } from "fs";
|
|
12
|
+
import { homedir } from "os";
|
|
12
13
|
import { parse as parseYaml } from "yaml";
|
|
13
14
|
import { z } from "zod";
|
|
14
15
|
var DomainConfigSchema = z.object({
|
|
@@ -140,6 +141,49 @@ var CloudConfigSchema = z.object({
|
|
|
140
141
|
audit: z.boolean().default(true)
|
|
141
142
|
}).default({ memory: true, analytics: true, audit: true })
|
|
142
143
|
}).optional();
|
|
144
|
+
var ConventionsConfigSchema = z.object({
|
|
145
|
+
claudeDirName: z.string().default(".claude").refine(
|
|
146
|
+
(s) => !s.includes("..") && !s.startsWith("/"),
|
|
147
|
+
{ message: 'claudeDirName must not contain ".." or start with "/"' }
|
|
148
|
+
),
|
|
149
|
+
sessionStatePath: z.string().default(".claude/session-state/CURRENT.md").refine(
|
|
150
|
+
(s) => !s.includes("..") && !s.startsWith("/"),
|
|
151
|
+
{ message: 'sessionStatePath must not contain ".." or start with "/"' }
|
|
152
|
+
),
|
|
153
|
+
sessionArchivePath: z.string().default(".claude/session-state/archive").refine(
|
|
154
|
+
(s) => !s.includes("..") && !s.startsWith("/"),
|
|
155
|
+
{ message: 'sessionArchivePath must not contain ".." or start with "/"' }
|
|
156
|
+
),
|
|
157
|
+
knowledgeCategories: z.array(z.string()).default([
|
|
158
|
+
"patterns",
|
|
159
|
+
"commands",
|
|
160
|
+
"incidents",
|
|
161
|
+
"reference",
|
|
162
|
+
"protocols",
|
|
163
|
+
"checklists",
|
|
164
|
+
"playbooks",
|
|
165
|
+
"critical",
|
|
166
|
+
"scripts",
|
|
167
|
+
"status",
|
|
168
|
+
"templates",
|
|
169
|
+
"loop-state",
|
|
170
|
+
"session-state",
|
|
171
|
+
"agents"
|
|
172
|
+
]),
|
|
173
|
+
knowledgeSourceFiles: z.array(z.string()).default(["CLAUDE.md", "MEMORY.md", "corrections.md"]),
|
|
174
|
+
excludePatterns: z.array(z.string()).default(["/ARCHIVE/", "/SESSION-HISTORY/"])
|
|
175
|
+
}).optional();
|
|
176
|
+
var PythonDomainConfigSchema = z.object({
|
|
177
|
+
name: z.string(),
|
|
178
|
+
packages: z.array(z.string()),
|
|
179
|
+
allowed_imports_from: z.array(z.string()).default([])
|
|
180
|
+
});
|
|
181
|
+
var PythonConfigSchema = z.object({
|
|
182
|
+
root: z.string(),
|
|
183
|
+
alembic_dir: z.string().optional(),
|
|
184
|
+
domains: z.array(PythonDomainConfigSchema).default([]),
|
|
185
|
+
exclude_dirs: z.array(z.string()).default(["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"])
|
|
186
|
+
}).optional();
|
|
143
187
|
var PathsConfigSchema = z.object({
|
|
144
188
|
source: z.string().default("src"),
|
|
145
189
|
aliases: z.record(z.string(), z.string()).default({ "@": "src" }),
|
|
@@ -174,7 +218,9 @@ var RawConfigSchema = z.object({
|
|
|
174
218
|
security: SecurityConfigSchema,
|
|
175
219
|
team: TeamConfigSchema,
|
|
176
220
|
regression: RegressionConfigSchema,
|
|
177
|
-
cloud: CloudConfigSchema
|
|
221
|
+
cloud: CloudConfigSchema,
|
|
222
|
+
conventions: ConventionsConfigSchema,
|
|
223
|
+
python: PythonConfigSchema
|
|
178
224
|
}).passthrough();
|
|
179
225
|
var _config = null;
|
|
180
226
|
var _projectRoot = null;
|
|
@@ -238,13 +284,24 @@ function getConfig() {
|
|
|
238
284
|
security: parsed.security,
|
|
239
285
|
team: parsed.team,
|
|
240
286
|
regression: parsed.regression,
|
|
241
|
-
cloud: parsed.cloud
|
|
287
|
+
cloud: parsed.cloud,
|
|
288
|
+
conventions: parsed.conventions,
|
|
289
|
+
python: parsed.python
|
|
242
290
|
};
|
|
291
|
+
if (!_config.cloud?.apiKey && process.env.MASSU_API_KEY) {
|
|
292
|
+
_config.cloud = {
|
|
293
|
+
enabled: true,
|
|
294
|
+
sync: { memory: true, analytics: true, audit: true },
|
|
295
|
+
..._config.cloud,
|
|
296
|
+
apiKey: process.env.MASSU_API_KEY
|
|
297
|
+
};
|
|
298
|
+
}
|
|
243
299
|
return _config;
|
|
244
300
|
}
|
|
245
301
|
function getResolvedPaths() {
|
|
246
302
|
const config = getConfig();
|
|
247
303
|
const root = getProjectRoot();
|
|
304
|
+
const claudeDirName = config.conventions?.claudeDirName ?? ".claude";
|
|
248
305
|
return {
|
|
249
306
|
codegraphDbPath: resolve(root, ".codegraph/codegraph.db"),
|
|
250
307
|
dataDbPath: resolve(root, ".massu/data.db"),
|
|
@@ -260,22 +317,43 @@ function getResolvedPaths() {
|
|
|
260
317
|
),
|
|
261
318
|
extensions: [".ts", ".tsx", ".js", ".jsx"],
|
|
262
319
|
indexFiles: ["index.ts", "index.tsx", "index.js", "index.jsx"],
|
|
263
|
-
patternsDir: resolve(root, "
|
|
264
|
-
claudeMdPath: resolve(root, "
|
|
320
|
+
patternsDir: resolve(root, claudeDirName, "patterns"),
|
|
321
|
+
claudeMdPath: resolve(root, claudeDirName, "CLAUDE.md"),
|
|
265
322
|
docsMapPath: resolve(root, ".massu/docs-map.json"),
|
|
266
323
|
helpSitePath: resolve(root, "../" + config.project.name + "-help"),
|
|
267
|
-
memoryDbPath: resolve(root, ".massu/memory.db")
|
|
324
|
+
memoryDbPath: resolve(root, ".massu/memory.db"),
|
|
325
|
+
knowledgeDbPath: resolve(root, ".massu/knowledge.db"),
|
|
326
|
+
plansDir: resolve(root, "docs/plans"),
|
|
327
|
+
docsDir: resolve(root, "docs"),
|
|
328
|
+
claudeDir: resolve(root, claudeDirName),
|
|
329
|
+
memoryDir: resolve(homedir(), claudeDirName, "projects", root.replace(/\//g, "-"), "memory"),
|
|
330
|
+
sessionStatePath: resolve(root, config.conventions?.sessionStatePath ?? `${claudeDirName}/session-state/CURRENT.md`),
|
|
331
|
+
sessionArchivePath: resolve(root, config.conventions?.sessionArchivePath ?? `${claudeDirName}/session-state/archive`),
|
|
332
|
+
mcpJsonPath: resolve(root, ".mcp.json"),
|
|
333
|
+
settingsLocalPath: resolve(root, claudeDirName, "settings.local.json")
|
|
268
334
|
};
|
|
269
335
|
}
|
|
270
336
|
|
|
271
|
-
// src/memory-
|
|
337
|
+
// src/memory-db.ts
|
|
338
|
+
function getMemoryDb() {
|
|
339
|
+
const dbPath = getResolvedPaths().memoryDbPath;
|
|
340
|
+
const dir = dirname2(dbPath);
|
|
341
|
+
if (!existsSync2(dir)) {
|
|
342
|
+
mkdirSync(dir, { recursive: true });
|
|
343
|
+
}
|
|
344
|
+
const db = new Database(dbPath);
|
|
345
|
+
db.pragma("journal_mode = WAL");
|
|
346
|
+
db.pragma("foreign_keys = ON");
|
|
347
|
+
initMemorySchema(db);
|
|
348
|
+
return db;
|
|
349
|
+
}
|
|
272
350
|
function initMemorySchema(db) {
|
|
273
351
|
db.exec(`
|
|
274
352
|
-- Sessions table (linked to Claude Code session IDs)
|
|
275
353
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
276
354
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
277
355
|
session_id TEXT UNIQUE NOT NULL,
|
|
278
|
-
project TEXT NOT NULL DEFAULT '
|
|
356
|
+
project TEXT NOT NULL DEFAULT 'my-project',
|
|
279
357
|
git_branch TEXT,
|
|
280
358
|
started_at TEXT NOT NULL,
|
|
281
359
|
started_at_epoch INTEGER NOT NULL,
|
|
@@ -330,9 +408,7 @@ function initMemorySchema(db) {
|
|
|
330
408
|
content_rowid='id'
|
|
331
409
|
);
|
|
332
410
|
`);
|
|
333
|
-
} catch (
|
|
334
|
-
process.stderr.write(`FTS5 setup warning: ${e instanceof Error ? e.message : String(e)}
|
|
335
|
-
`);
|
|
411
|
+
} catch (_e) {
|
|
336
412
|
}
|
|
337
413
|
db.exec(`
|
|
338
414
|
CREATE TRIGGER IF NOT EXISTS observations_ai AFTER INSERT ON observations BEGIN
|
|
@@ -392,9 +468,7 @@ function initMemorySchema(db) {
|
|
|
392
468
|
content_rowid='id'
|
|
393
469
|
);
|
|
394
470
|
`);
|
|
395
|
-
} catch (
|
|
396
|
-
process.stderr.write(`FTS5 setup warning: ${e instanceof Error ? e.message : String(e)}
|
|
397
|
-
`);
|
|
471
|
+
} catch (_e) {
|
|
398
472
|
}
|
|
399
473
|
db.exec(`
|
|
400
474
|
CREATE TRIGGER IF NOT EXISTS prompts_ai AFTER INSERT ON user_prompts BEGIN
|
|
@@ -464,9 +538,7 @@ function initMemorySchema(db) {
|
|
|
464
538
|
content_rowid=id
|
|
465
539
|
);
|
|
466
540
|
`);
|
|
467
|
-
} catch (
|
|
468
|
-
process.stderr.write(`FTS5 setup warning: ${e instanceof Error ? e.message : String(e)}
|
|
469
|
-
`);
|
|
541
|
+
} catch (_e) {
|
|
470
542
|
}
|
|
471
543
|
db.exec(`
|
|
472
544
|
CREATE TRIGGER IF NOT EXISTS ct_fts_insert AFTER INSERT ON conversation_turns BEGIN
|
|
@@ -490,7 +562,7 @@ function initMemorySchema(db) {
|
|
|
490
562
|
CREATE TABLE IF NOT EXISTS session_quality_scores (
|
|
491
563
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
492
564
|
session_id TEXT NOT NULL UNIQUE,
|
|
493
|
-
project TEXT NOT NULL DEFAULT '
|
|
565
|
+
project TEXT NOT NULL DEFAULT 'my-project',
|
|
494
566
|
score INTEGER NOT NULL DEFAULT 100,
|
|
495
567
|
security_score INTEGER NOT NULL DEFAULT 100,
|
|
496
568
|
architecture_score INTEGER NOT NULL DEFAULT 100,
|
|
@@ -513,7 +585,7 @@ function initMemorySchema(db) {
|
|
|
513
585
|
CREATE TABLE IF NOT EXISTS session_costs (
|
|
514
586
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
515
587
|
session_id TEXT NOT NULL UNIQUE,
|
|
516
|
-
project TEXT NOT NULL DEFAULT '
|
|
588
|
+
project TEXT NOT NULL DEFAULT 'my-project',
|
|
517
589
|
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
518
590
|
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
519
591
|
cache_read_tokens INTEGER NOT NULL DEFAULT 0,
|
|
@@ -740,24 +812,15 @@ function initMemorySchema(db) {
|
|
|
740
812
|
);
|
|
741
813
|
CREATE INDEX IF NOT EXISTS idx_pending_sync_created ON pending_sync(created_at ASC);
|
|
742
814
|
`);
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
}
|
|
753
|
-
const db = new Database(dbPath);
|
|
754
|
-
db.pragma("journal_mode = WAL");
|
|
755
|
-
db.pragma("foreign_keys = ON");
|
|
756
|
-
if (!initializedPaths.has(dbPath)) {
|
|
757
|
-
initMemorySchema(db);
|
|
758
|
-
initializedPaths.add(dbPath);
|
|
759
|
-
}
|
|
760
|
-
return db;
|
|
815
|
+
db.exec(`
|
|
816
|
+
CREATE TABLE IF NOT EXISTS license_cache (
|
|
817
|
+
api_key_hash TEXT PRIMARY KEY,
|
|
818
|
+
tier TEXT NOT NULL,
|
|
819
|
+
valid_until TEXT NOT NULL,
|
|
820
|
+
last_validated TEXT NOT NULL,
|
|
821
|
+
features TEXT DEFAULT '[]'
|
|
822
|
+
);
|
|
823
|
+
`);
|
|
761
824
|
}
|
|
762
825
|
|
|
763
826
|
// src/hooks/quality-event.ts
|
|
@@ -838,14 +901,14 @@ async function main() {
|
|
|
838
901
|
process.exit(0);
|
|
839
902
|
}
|
|
840
903
|
function readStdin() {
|
|
841
|
-
return new Promise((
|
|
904
|
+
return new Promise((resolve3) => {
|
|
842
905
|
let data = "";
|
|
843
906
|
process.stdin.setEncoding("utf-8");
|
|
844
907
|
process.stdin.on("data", (chunk) => {
|
|
845
908
|
data += chunk;
|
|
846
909
|
});
|
|
847
|
-
process.stdin.on("end", () =>
|
|
848
|
-
setTimeout(() =>
|
|
910
|
+
process.stdin.on("end", () => resolve3(data));
|
|
911
|
+
setTimeout(() => resolve3(data), 3e3);
|
|
849
912
|
});
|
|
850
913
|
}
|
|
851
914
|
main();
|
|
@@ -52,6 +52,24 @@ function checkFilePath(filePath) {
|
|
|
52
52
|
}
|
|
53
53
|
return null;
|
|
54
54
|
}
|
|
55
|
+
var DANGEROUS_PYTHON_PATTERNS = [
|
|
56
|
+
{ pattern: /\beval\s*\(/, label: "Python eval() \u2014 arbitrary code execution" },
|
|
57
|
+
{ pattern: /\bexec\s*\(/, label: "Python exec() \u2014 arbitrary code execution" },
|
|
58
|
+
{ pattern: /\b__import__\s*\(/, label: "Python __import__() \u2014 dynamic import (potential code injection)" },
|
|
59
|
+
{ pattern: /subprocess\.call\([^)]*shell\s*=\s*True/, label: "subprocess.call(shell=True) \u2014 shell injection risk" },
|
|
60
|
+
{ pattern: /subprocess\.Popen\([^)]*shell\s*=\s*True/, label: "subprocess.Popen(shell=True) \u2014 shell injection risk" },
|
|
61
|
+
{ pattern: /os\.system\s*\(/, label: "os.system() \u2014 shell injection risk" },
|
|
62
|
+
{ pattern: /\bf['"].*\{.*\}.*['"].*(?:execute|cursor|query)/, label: "f-string in SQL \u2014 SQL injection risk" },
|
|
63
|
+
{ pattern: /['"].*%s.*['"].*%.*(?:execute|cursor|query)/, label: "String formatting in SQL \u2014 SQL injection risk" }
|
|
64
|
+
];
|
|
65
|
+
function checkPythonContent(content) {
|
|
66
|
+
for (const { pattern, label } of DANGEROUS_PYTHON_PATTERNS) {
|
|
67
|
+
if (pattern.test(content)) {
|
|
68
|
+
return label;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
55
73
|
async function main() {
|
|
56
74
|
try {
|
|
57
75
|
const input = await readStdin();
|
|
@@ -77,6 +95,17 @@ Ensure this is intentional and no secrets will be exposed.`
|
|
|
77
95
|
}));
|
|
78
96
|
}
|
|
79
97
|
}
|
|
98
|
+
const pyContent = tool_input.content || tool_input.new_string;
|
|
99
|
+
if ((tool_name === "Write" || tool_name === "Edit") && tool_input.file_path?.endsWith(".py") && pyContent) {
|
|
100
|
+
const pyViolation = checkPythonContent(pyContent);
|
|
101
|
+
if (pyViolation) {
|
|
102
|
+
process.stdout.write(JSON.stringify({
|
|
103
|
+
message: `SECURITY GATE: Dangerous Python pattern detected: ${pyViolation}
|
|
104
|
+
File: ${tool_input.file_path}
|
|
105
|
+
Review carefully before proceeding.`
|
|
106
|
+
}));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
80
109
|
} catch {
|
|
81
110
|
}
|
|
82
111
|
process.exit(0);
|