secufusion-mcp 1.0.5 → 1.0.7
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 +67 -207
- package/index.js +152 -166
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
| Tool | Phase | What it does |
|
|
16
16
|
|---|---|---|
|
|
17
|
-
| `
|
|
17
|
+
| `manage_branch_state` | Planning & Execution | Manages a `.secufusion-state.json` tracker tied to the active Git branch |
|
|
18
18
|
| `log_rejected_pattern` | Course Correction | Records bad patterns to `.rejected-patterns.json` so they are never repeated |
|
|
19
19
|
| `run_pre_pr_checks` | PR Handoff | Discovers modified microservices across the workspace and gates PRs via AST-level linters + structural checks |
|
|
20
20
|
|
|
@@ -102,69 +102,40 @@ Open Cline settings → MCP Servers → Add:
|
|
|
102
102
|
|
|
103
103
|
## Tools Reference
|
|
104
104
|
|
|
105
|
-
### 1.
|
|
105
|
+
### 1. manage_branch_state
|
|
106
106
|
|
|
107
|
-
|
|
107
|
+
Manages a structured JSON state file (`.secufusion-state.json`) keyed to the current Git branch. This replaces unstructured Markdown parsing and ensures the AI can resume perfectly.
|
|
108
108
|
|
|
109
109
|
**Parameters:**
|
|
110
110
|
|
|
111
111
|
| Parameter | Type | Required | Description |
|
|
112
112
|
|---|---|---|---|
|
|
113
|
-
| `action` |
|
|
114
|
-
| `task_description` | string |
|
|
115
|
-
| `
|
|
116
|
-
| `
|
|
117
|
-
| `
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
```
|
|
122
|
-
Ask your AI assistant:
|
|
123
|
-
"Start working on WI-1042: Add tenant-scoped audit log export to CSV.
|
|
124
|
-
Reference file: src/services/AuditService.java"
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
The AI will call:
|
|
113
|
+
| `action` | string | Yes | `initialize`, `read`, or `update` |
|
|
114
|
+
| `task_description` | string | No | (For `initialize`) Summary of the work |
|
|
115
|
+
| `pending_acs` | array | No | Array of strings for pending tasks (For `initialize`/`update`) |
|
|
116
|
+
| `completed_acs` | array | No | Array of completed AC strings (For `update`) |
|
|
117
|
+
| `next_step` | string | No | **CRITICAL for `update`**: A clear instruction on what to do next to allow instant resumption. |
|
|
118
|
+
| `reference_file_path` | string | No | Path to a reference file with standard coding patterns |
|
|
119
|
+
|
|
120
|
+
**Example — Starting a new task:**
|
|
128
121
|
```json
|
|
129
122
|
{
|
|
130
|
-
"action": "
|
|
131
|
-
"task_description": "
|
|
132
|
-
"
|
|
133
|
-
"reference_file_path": "src/services/AuditService.java"
|
|
123
|
+
"action": "initialize",
|
|
124
|
+
"task_description": "Implement DeviceActivitySummaryDTO with browserUsage map.",
|
|
125
|
+
"pending_acs": ["Add browserUsage map", "Add unit tests", "Update OpenAPI"]
|
|
134
126
|
}
|
|
135
127
|
```
|
|
136
128
|
|
|
137
|
-
|
|
138
|
-
- Task description
|
|
139
|
-
- Pre-filled guardrails checklist
|
|
140
|
-
- Acceptance criteria placeholders
|
|
141
|
-
- Reference code snippet
|
|
142
|
-
- Session log
|
|
143
|
-
|
|
144
|
-
**Example — Tick off completed work:**
|
|
145
|
-
|
|
146
|
-
```
|
|
147
|
-
Ask: "Mark the tenantId scoping and unit tests as done in the spec."
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
The AI will call:
|
|
129
|
+
**Example — Updating progress:**
|
|
151
130
|
```json
|
|
152
131
|
{
|
|
153
132
|
"action": "update",
|
|
154
|
-
"
|
|
133
|
+
"pending_acs": ["Update OpenAPI"],
|
|
134
|
+
"completed_acs": ["Add browserUsage map", "Add unit tests"],
|
|
135
|
+
"next_step": "Generate the OpenAPI YAML for DeviceActivitySummaryDTO and test generation."
|
|
155
136
|
}
|
|
156
137
|
```
|
|
157
138
|
|
|
158
|
-
**Example — Read current spec:**
|
|
159
|
-
|
|
160
|
-
```
|
|
161
|
-
Ask: "Where did we leave off? Read the current spec."
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
```json
|
|
165
|
-
{ "action": "read" }
|
|
166
|
-
```
|
|
167
|
-
|
|
168
139
|
---
|
|
169
140
|
|
|
170
141
|
### 2. `log_rejected_pattern`
|
|
@@ -219,15 +190,16 @@ This writes to `.rejected-patterns.json`:
|
|
|
219
190
|
|---|---|---|---|
|
|
220
191
|
| `work_item_id` | string | Yes | Azure DevOps work item ID |
|
|
221
192
|
| `root_dir` | string | No | Directory to scan (defaults to cwd) |
|
|
222
|
-
| `skip_checks` | array | No | `
|
|
193
|
+
| `skip_checks` | array | No | `branch_state`, `console_logs`, `tenant_isolation`, `hardcoded_urls`, `flyway_migrations` |
|
|
223
194
|
|
|
224
195
|
**Guardrail checks:**
|
|
225
196
|
|
|
226
197
|
| Check | Fails when |
|
|
227
198
|
|---|---|
|
|
228
|
-
| **
|
|
199
|
+
| **STATE** | `.secufusion-state.json` has pending ACs for the active branch |
|
|
229
200
|
| **LINTING** | AST linters (ESLint, Checkstyle) fail in any discovered microservice. Fix natively, do not suppress! (Falls back to regex if no linters exist) |
|
|
230
201
|
| **SECURITY** | Hardcoded IPs or `uat.*` / `prod.*` / `staging.*` URLs in source or config files |
|
|
202
|
+
| **TENANT ISOLATION** | `*Repository.java` files contain query methods or `@Query` annotations that do NOT include `tenant` filtering |
|
|
231
203
|
| **FLYWAY** | `@Entity`-annotated Java files exist but no `V*__.sql` Flyway migrations found |
|
|
232
204
|
|
|
233
205
|
**Example — Run checks before PR:**
|
|
@@ -247,14 +219,14 @@ The AI will call:
|
|
|
247
219
|
```
|
|
248
220
|
# ✅ Pre-PR Checks PASSED — Work Item #1042
|
|
249
221
|
|
|
250
|
-
✅ [
|
|
222
|
+
✅ [STATE] All branch tasks completed.
|
|
251
223
|
✅ [LOGGING] No console.log / System.out.println found.
|
|
252
224
|
✅ [SECURITY] No hardcoded UAT/Prod IPs or environment URLs found.
|
|
253
225
|
✅ [FLYWAY] Flyway migration coverage looks good.
|
|
254
226
|
|
|
255
227
|
## PR Summary
|
|
256
228
|
- Work Item: #1042
|
|
257
|
-
-
|
|
229
|
+
- State: All pending ACs resolved ✅
|
|
258
230
|
- Ready to raise PR 🚀
|
|
259
231
|
```
|
|
260
232
|
|
|
@@ -262,7 +234,7 @@ The AI will call:
|
|
|
262
234
|
```
|
|
263
235
|
# ❌ Pre-PR Checks FAILED — Work Item #1042
|
|
264
236
|
|
|
265
|
-
❌ [
|
|
237
|
+
❌ [STATE] 3 pending AC(s) remain in .secufusion-state.json:
|
|
266
238
|
• Flyway SQL migration created for every modified JPA @Entity
|
|
267
239
|
• Unit tests written / updated for new business logic
|
|
268
240
|
• API contract updated if endpoints changed
|
|
@@ -283,7 +255,7 @@ These rules are enforced automatically — the AI will never violate them:
|
|
|
283
255
|
✅ No console.log() or System.out.println() in any source file
|
|
284
256
|
✅ No hardcoded UAT/Prod IPs or environment URLs
|
|
285
257
|
✅ Every JPA @Entity change accompanied by a Flyway .sql migration
|
|
286
|
-
✅
|
|
258
|
+
✅ State in .secufusion-state.json must be fully resolved before PR is raised
|
|
287
259
|
```
|
|
288
260
|
|
|
289
261
|
---
|
|
@@ -292,7 +264,7 @@ These rules are enforced automatically — the AI will never violate them:
|
|
|
292
264
|
|
|
293
265
|
| File | Description |
|
|
294
266
|
|---|---|
|
|
295
|
-
| `.
|
|
267
|
+
| `.secufusion-state.json` | Living task blueprint — tracks pending/completed ACs per branch |
|
|
296
268
|
| `.rejected-patterns.json` | Cumulative log of all rejected patterns across sessions |
|
|
297
269
|
|
|
298
270
|
> **Tip:** Commit both files to your repo so the entire team benefits from the shared knowledge.
|
|
@@ -305,11 +277,11 @@ These rules are enforced automatically — the AI will never violate them:
|
|
|
305
277
|
┌─────────────────────────────────────────────────────┐
|
|
306
278
|
│ SecuFusion MCP Workflow │
|
|
307
279
|
├──────────────┬──────────────────────────────────────┤
|
|
308
|
-
│ Phase 1 │
|
|
309
|
-
│ Planning │ →
|
|
280
|
+
│ Phase 1 │ manage_branch_state (action=init) │
|
|
281
|
+
│ Planning │ → Initializes .secufusion-state.json │
|
|
310
282
|
├──────────────┼──────────────────────────────────────┤
|
|
311
|
-
│ Phase 2 │
|
|
312
|
-
│ Execution │ →
|
|
283
|
+
│ Phase 2 │ manage_branch_state (action=update) │
|
|
284
|
+
│ Execution │ → Resolve ACs & define next_step │
|
|
313
285
|
├──────────────┼──────────────────────────────────────┤
|
|
314
286
|
│ Phase 3 │ log_rejected_pattern │
|
|
315
287
|
│ Correction │ → Record mistakes to avoid repeat │
|
|
@@ -372,46 +344,14 @@ Admin users must be forced through TOTP verification before
|
|
|
372
344
|
accessing any dashboard route. Exempt service accounts.
|
|
373
345
|
```
|
|
374
346
|
|
|
375
|
-
**The AI automatically calls `
|
|
376
|
-
```
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
Add MFA enforcement for admin users on login...
|
|
384
|
-
|
|
385
|
-
## Guardrails Checklist
|
|
386
|
-
- [ ] All DB queries / event payloads are scoped with `tenantId`
|
|
387
|
-
- [ ] No `console.log` / `System.out.println` left in source files
|
|
388
|
-
- [ ] No hardcoded UAT/Prod IPs or environment URLs
|
|
389
|
-
- [ ] Flyway SQL migration created for every modified JPA `@Entity`
|
|
390
|
-
- [ ] Unit tests written / updated for new business logic
|
|
391
|
-
- [ ] API contract updated if endpoints changed
|
|
392
|
-
|
|
393
|
-
## Acceptance Criteria
|
|
394
|
-
- [ ] AC-1: (fill in from ticket)
|
|
395
|
-
- [ ] AC-2: (fill in from ticket)
|
|
396
|
-
|
|
397
|
-
## Session Log
|
|
398
|
-
| 2026-08-26T14:35:00Z | Spec created |
|
|
399
|
-
```
|
|
400
|
-
|
|
401
|
-
You can paste as little or as much as you want:
|
|
402
|
-
|
|
403
|
-
```
|
|
404
|
-
# Minimal — one liner
|
|
405
|
-
WI-2847: MFA for admin login with TOTP.
|
|
406
|
-
|
|
407
|
-
# Full ticket paste — straight from Azure
|
|
408
|
-
WI-2847
|
|
409
|
-
Title: Add MFA enforcement for admin users
|
|
410
|
-
Description: Admin users must complete TOTP verification...
|
|
411
|
-
Acceptance Criteria:
|
|
412
|
-
- Given admin logs in, When MFA not done, Then redirect to /mfa
|
|
413
|
-
- Service accounts in GROUP_SERVICE_ACCOUNTS are exempt
|
|
414
|
-
Priority: High
|
|
347
|
+
**The AI automatically calls `manage_branch_state` and creates/initializes `.secufusion-state.json`:**
|
|
348
|
+
```json
|
|
349
|
+
{
|
|
350
|
+
"task": "WI-2847: Add MFA enforcement",
|
|
351
|
+
"pending_acs": ["AC-1: Auth flow", "AC-2: Redirects", "AC-3: Exemptions"],
|
|
352
|
+
"completed_acs": [],
|
|
353
|
+
"next_step": "Implement MFA logic in AuthController"
|
|
354
|
+
}
|
|
415
355
|
```
|
|
416
356
|
|
|
417
357
|
---
|
|
@@ -434,23 +374,20 @@ As you finish pieces of the feature, tell the AI:
|
|
|
434
374
|
|
|
435
375
|
```
|
|
436
376
|
I've finished the tenantId scoping on all queries and written the unit tests.
|
|
437
|
-
Update the
|
|
377
|
+
Update the state.
|
|
438
378
|
```
|
|
439
379
|
|
|
440
|
-
The AI calls `
|
|
441
|
-
|
|
442
|
-
```markdown
|
|
443
|
-
# Before
|
|
444
|
-
- [ ] All DB queries / event payloads are scoped with `tenantId`
|
|
445
|
-
- [ ] Unit tests written / updated for new business logic
|
|
380
|
+
The AI calls `manage_branch_state` with `action=update` and the state updates:
|
|
446
381
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
-
|
|
382
|
+
```json
|
|
383
|
+
{
|
|
384
|
+
"task": "WI-2847: Add MFA enforcement",
|
|
385
|
+
"pending_acs": ["AC-3: Exemptions"],
|
|
386
|
+
"completed_acs": ["Add tenantId scoping", "Add unit tests"],
|
|
387
|
+
"next_step": "Implement exemption logic for service accounts"
|
|
388
|
+
}
|
|
450
389
|
```
|
|
451
390
|
|
|
452
|
-
A new row is also appended to the Session Log automatically, so you always have an audit trail of progress across sessions.
|
|
453
|
-
|
|
454
391
|
---
|
|
455
392
|
|
|
456
393
|
### Step 5 — Course correction (if the AI does something wrong)
|
|
@@ -473,23 +410,6 @@ The AI **immediately** calls `log_rejected_pattern`:
|
|
|
473
410
|
}
|
|
474
411
|
```
|
|
475
412
|
|
|
476
|
-
This gets appended to `.rejected-patterns.json`:
|
|
477
|
-
|
|
478
|
-
```json
|
|
479
|
-
[
|
|
480
|
-
{
|
|
481
|
-
"id": 1,
|
|
482
|
-
"timestamp": "2026-08-26T15:10:00.000Z",
|
|
483
|
-
"category": "security",
|
|
484
|
-
"pattern": "Hardcoded staging URL https://staging.secufusion.io in source files",
|
|
485
|
-
"reason": "Must use @Value from application.properties...",
|
|
486
|
-
"file_context": "src/services/NotificationService.java"
|
|
487
|
-
}
|
|
488
|
-
]
|
|
489
|
-
```
|
|
490
|
-
|
|
491
|
-
From this point on, the AI checks this file before every suggestion — the mistake will never be repeated, even in future sessions.
|
|
492
|
-
|
|
493
413
|
---
|
|
494
414
|
|
|
495
415
|
### Step 6 — Pre-PR checks (PR Handoff phase)
|
|
@@ -507,104 +427,44 @@ The AI scans your entire repo and produces a report.
|
|
|
507
427
|
# ✅ Pre-PR Checks PASSED — Work Item #2847
|
|
508
428
|
|
|
509
429
|
Scanned directory: C:\projects\secufusion-backend
|
|
510
|
-
Timestamp: 2026-08-26T16:00:00.000Z
|
|
511
430
|
|
|
512
|
-
✅ [
|
|
431
|
+
✅ [STATE] All tasks resolved.
|
|
513
432
|
✅ [LOGGING] No console.log / System.out.println found.
|
|
514
433
|
✅ [SECURITY] No hardcoded UAT/Prod IPs or environment URLs found.
|
|
515
434
|
✅ [FLYWAY] Flyway migration coverage looks good.
|
|
516
435
|
|
|
517
436
|
## PR Summary
|
|
518
437
|
- Work Item: #2847
|
|
519
|
-
-
|
|
520
|
-
- Guardrails:
|
|
438
|
+
- State: All pending ACs resolved ✅
|
|
439
|
+
- Guardrails: tenant isolation secured, no debug logs, no hardcoded URLs, Flyway covered ✅
|
|
521
440
|
- Ready to raise PR 🚀
|
|
522
|
-
|
|
523
|
-
🚫 Rejected Patterns Reminder (1 on record)
|
|
524
|
-
- [security] Hardcoded staging URL https://staging.secufusion.io in source files
|
|
525
|
-
```
|
|
526
|
-
|
|
527
|
-
**❌ Checks fail — you must fix before PR:**
|
|
528
|
-
```
|
|
529
|
-
# ❌ Pre-PR Checks FAILED — Work Item #2847
|
|
530
|
-
|
|
531
|
-
## Errors (must fix before PR)
|
|
532
|
-
|
|
533
|
-
❌ [SPEC] 2 unchecked item(s) in the spec:
|
|
534
|
-
• Flyway SQL migration created for every modified JPA @Entity
|
|
535
|
-
• API contract (OpenAPI / TS types) updated if endpoints changed
|
|
536
|
-
|
|
537
|
-
❌ [LINTING] 2 linter error(s) across workspace — fix natively in their respective microservices:
|
|
538
|
-
• [ESLint] apps/admin-dashboard/src/components/MfaSetup.tsx:47 — Unexpected console statement (no-console)
|
|
539
|
-
• [Checkstyle/Maven] Build failed: [ERROR] AuditService.java:[112] Line contains System.out.println
|
|
540
|
-
|
|
541
|
-
## Passed
|
|
542
|
-
✅ [SECURITY] No hardcoded UAT/Prod IPs or environment URLs found.
|
|
543
|
-
✅ [FLYWAY] Flyway migration coverage looks good.
|
|
544
441
|
```
|
|
545
442
|
|
|
546
|
-
Fix the issues, run checks again, and only raise the PR once it's fully green.
|
|
547
|
-
|
|
548
|
-
---
|
|
549
|
-
|
|
550
|
-
### Quick cheat sheet
|
|
551
|
-
|
|
552
|
-
| What you want to do | What to say to the AI |
|
|
553
|
-
|---|---|
|
|
554
|
-
| Start a new task | `WI-XXXX: [paste description from Azure]` |
|
|
555
|
-
| Check where you left off | `Read the current spec` |
|
|
556
|
-
| Mark work as done | `Mark the tenantId scoping as complete in the spec` |
|
|
557
|
-
| Record a mistake | `Never do [X] again because [Y]` |
|
|
558
|
-
| Gate the PR | `Run pre-PR checks for WI-XXXX` |
|
|
559
|
-
| Resume after a break | `What's left on the current task?` |
|
|
560
|
-
| Use a reference file | `WI-XXXX: [desc]. Reference: src/services/MyService.java` |
|
|
561
|
-
|
|
562
443
|
---
|
|
563
444
|
|
|
564
|
-
## How It
|
|
565
|
-
|
|
566
|
-
There are two layers that need to be in place for the MCP server to work automatically.
|
|
567
|
-
|
|
568
|
-
### Layer 1 — `mcp_config.json` (makes tools available)
|
|
569
|
-
|
|
570
|
-
This tells the IDE to start the MCP server process when it launches. The tools become registered and ready, but nothing calls them yet.
|
|
571
|
-
|
|
572
|
-
```
|
|
573
|
-
IDE starts → reads mcp_config.json → spawns node index.js → tools registered
|
|
574
|
-
```
|
|
575
|
-
|
|
576
|
-
### Layer 2 — `AGENTS.md` (the invoker — tells AI when to call each tool)
|
|
577
|
-
|
|
578
|
-
Without this, the tools sit idle. The AI doesn't know when to invoke them.
|
|
579
|
-
Create `.agents/AGENTS.md` in your project root:
|
|
445
|
+
## How It Works (The 2 Layers)
|
|
580
446
|
|
|
581
|
-
|
|
582
|
-
# SecuFusion MCP Workflow Rules
|
|
447
|
+
To prevent AI amnesia or generic bad habits across your projects, the SecuFusion MCP relies on structured branch-aware JSON and native AST tooling.
|
|
583
448
|
|
|
584
|
-
|
|
449
|
+
## Phase 0 — Resume (TRIGGER: new session, switching branches, or user says "resume" or "continue")
|
|
450
|
+
- Call `manage_branch_state` with `action: "read"`. The tool will automatically detect the current Git branch and return the structured JSON state for that specific branch. Do not re-read requirements; immediately begin executing the item explicitly listed in the `next_step` field.
|
|
585
451
|
|
|
586
452
|
## Phase 1 — Planning (TRIGGER: user assigns a task or work item)
|
|
587
|
-
-
|
|
588
|
-
- Determine which microservices are likely affected. Pass a `reference_file_path` to observe exact coding patterns.
|
|
589
|
-
- Pass the user's requirements into `task_description` to generate the workspace-level `.current-task-spec.md` blueprint.
|
|
590
|
-
- Wait for the spec to be generated before writing any code.
|
|
453
|
+
- Call `manage_branch_state` with `action: "initialize"`. Pass the `task_description` and `reference_file_path`. The tool will generate a branch-bound JSON state file tracking the Acceptance Criteria (ACs) as a structured array of pending tasks, keyed to your active Git branch.
|
|
591
454
|
|
|
592
|
-
## Phase 2 — Execution (TRIGGER: as you complete
|
|
593
|
-
-
|
|
594
|
-
-
|
|
595
|
-
- If you are resuming a session, always call `manage_feature_spec` with `action=read` first to know exactly where you left off.
|
|
455
|
+
## Phase 2 — Execution (TRIGGER: as you complete ACs, or before ending a session/response)
|
|
456
|
+
- Call `manage_branch_state` with `action: "update"`. Move completed items from `pending_acs` to `completed_acs`.
|
|
457
|
+
- CRITICAL: You must explicitly write a clear, actionable instruction into the `next_step` parameter (e.g., "Implement browserUsage map in DeviceActivitySummaryDTO"). This ensures your future self can resume instantly without parsing the full spec.
|
|
596
458
|
|
|
597
459
|
## Phase 3 — Course Correction (TRIGGER: user corrects you or rejects an approach)
|
|
598
|
-
- Immediately call `log_rejected_pattern`.
|
|
599
|
-
- Pass the bad `pattern` you used and the `reason`/correction the user provided.
|
|
600
|
-
- Always check `.rejected-patterns.json` implicitly before suggesting architectural choices to ensure you never repeat past mistakes.
|
|
460
|
+
- Immediately call `log_rejected_pattern`. Pass the bad `pattern` and the `reason`. Always check `.rejected-patterns.json` implicitly before suggesting architectural choices.
|
|
601
461
|
|
|
602
|
-
## Phase 4 — PR Handoff (TRIGGER: user says "prepare PR"
|
|
603
|
-
- Call `run_pre_pr_checks` with the Azure DevOps `
|
|
604
|
-
- If
|
|
462
|
+
## Phase 4 — PR Handoff (TRIGGER: user says "prepare PR" or "run checks")
|
|
463
|
+
- Call `run_pre_pr_checks` with the Azure DevOps `workItemId`. The tool checks the structured branch state to ensure `pending_acs` is empty, then discovers modified microservices to run native linters, Flyway checks, and the Tenant-Isolation scanner.
|
|
464
|
+
- If an error is thrown, YOU MUST navigate to that microservice, FIX THE ERROR natively, and rerun until the workspace passes.
|
|
605
465
|
|
|
606
466
|
## Guardrails (enforce always, no exceptions)
|
|
607
|
-
-
|
|
467
|
+
- TENANT SAFETY IS AUTOMATED: The PR Gatekeeper actively scans all `*Repository.java` files. If you write a database query (derived method or `@Query`) that does not explicitly filter by `tenantId` or contain the word `tenant`, the PR will be blocked. Write secure, tenant-isolated queries on your first attempt.
|
|
608
468
|
- Do not ignore linter errors. AST-level tools (ESLint, Checkstyle/Maven) are the source of truth for hygiene. Fix them natively.
|
|
609
469
|
- Never hardcode UAT/Prod IPs or URLs. Use environment variables or configuration properties.
|
|
610
470
|
- If you modify a JPA `@Entity` in any backend repo, you MUST create the corresponding Flyway `.sql` migration script before running PR checks.
|
|
@@ -619,9 +479,9 @@ mcp_config.json → starts the server (tools available)
|
|
|
619
479
|
↓
|
|
620
480
|
You say: "WI-1042: Add audit log export"
|
|
621
481
|
↓
|
|
622
|
-
AI reads AGENTS.md → Phase 1 triggered → calls
|
|
482
|
+
AI reads AGENTS.md → Phase 1 triggered → calls manage_branch_state
|
|
623
483
|
↓
|
|
624
|
-
.
|
|
484
|
+
.secufusion-state.json initialized for your Git branch
|
|
625
485
|
```
|
|
626
486
|
|
|
627
487
|
### Reusing across projects
|
|
@@ -647,14 +507,14 @@ Once both layers are in place, you interact completely naturally:
|
|
|
647
507
|
| Situation | What you say |
|
|
648
508
|
|---|---|
|
|
649
509
|
| 🆕 New task | `WI-XXXX: [paste description from Azure]` |
|
|
650
|
-
| ✅ Done a chunk | `Done with the tenantId scoping, update the
|
|
510
|
+
| ✅ Done a chunk | `Done with the tenantId scoping, update the state` |
|
|
651
511
|
| ❌ AI did something wrong | `Don't do X, do Y instead` |
|
|
652
512
|
| 🚀 Ready for PR | `Run checks for WI-XXXX` or `Prepare PR` |
|
|
653
513
|
| 🔄 Resuming after a break | `What's left?` or `Resume the current task` |
|
|
654
514
|
|
|
655
515
|
**Before `AGENTS.md`:** You had to remember which tool to invoke and when.
|
|
656
516
|
|
|
657
|
-
**After `AGENTS.md`:** You just describe work. The AI follows the
|
|
517
|
+
**After `AGENTS.md`:** You just describe work. The AI follows the 5-phase workflow automatically — no commands, no syntax, no manual tool calls.
|
|
658
518
|
|
|
659
519
|
---
|
|
660
520
|
|
package/index.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - run_pre_pr_checks : gate PRs via AST-level linters (ESLint / Maven Checkstyle) + structural checks
|
|
9
9
|
*
|
|
10
10
|
* Guardrails enforced:
|
|
11
|
-
* 1.
|
|
11
|
+
* 1. TENANT SAFETY AUTOMATED: Active scanning of *Repository.java for tenantId scoping.
|
|
12
12
|
* 2. Code hygiene (console.log, unused vars, formatting) enforced via ESLint / Checkstyle — not regex.
|
|
13
13
|
* 3. No hardcoded UAT/Prod IPs or environment URLs.
|
|
14
14
|
* 4. Every modified JPA @Entity must have a matching Flyway SQL migration.
|
|
@@ -22,7 +22,7 @@ import { execSync } from "child_process";
|
|
|
22
22
|
// ─────────────────────────────────────────────
|
|
23
23
|
// Constants / helpers
|
|
24
24
|
// ─────────────────────────────────────────────
|
|
25
|
-
const
|
|
25
|
+
const STATE_FILE = ".secufusion-state.json";
|
|
26
26
|
const REJECTED_FILE = ".rejected-patterns.json";
|
|
27
27
|
/** Resolve a path relative to cwd (where the MCP server is invoked). */
|
|
28
28
|
function resolve(file) {
|
|
@@ -40,56 +40,13 @@ function writeFile(filePath, content) {
|
|
|
40
40
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
41
41
|
fs.writeFileSync(filePath, content, "utf-8");
|
|
42
42
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
: "";
|
|
51
|
-
const refSection = referenceSnippet
|
|
52
|
-
? `\n## Reference Code Pattern\n\n> Extracted from the provided reference file to guide implementation style.\n\n\`\`\`\n${referenceSnippet.slice(0, 800)}\n\`\`\`\n`
|
|
53
|
-
: "";
|
|
54
|
-
return `# SecuFusion Feature Spec
|
|
55
|
-
<!-- AUTO-GENERATED — do not remove the checkboxes; they are used by run_pre_pr_checks -->
|
|
56
|
-
|
|
57
|
-
**Generated:** ${now}
|
|
58
|
-
${wiLine}
|
|
59
|
-
## Task Description
|
|
60
|
-
|
|
61
|
-
${taskDescription}
|
|
62
|
-
${refSection}
|
|
63
|
-
## Guardrails Checklist
|
|
64
|
-
|
|
65
|
-
- [ ] All DB queries / event payloads are scoped with \`tenantId\`
|
|
66
|
-
- [ ] No \`console.log\` / \`System.out.println\` left in source files
|
|
67
|
-
- [ ] No hardcoded UAT/Prod IPs or environment URLs
|
|
68
|
-
- [ ] Flyway SQL migration created for every modified JPA \`@Entity\`
|
|
69
|
-
- [ ] Unit tests written / updated for new business logic
|
|
70
|
-
- [ ] API contract (OpenAPI / TS types) updated if endpoints changed
|
|
71
|
-
|
|
72
|
-
## Acceptance Criteria
|
|
73
|
-
|
|
74
|
-
- [ ] AC-1: (fill in from ticket)
|
|
75
|
-
- [ ] AC-2: (fill in from ticket)
|
|
76
|
-
- [ ] AC-3: (fill in from ticket)
|
|
77
|
-
|
|
78
|
-
## Implementation Notes
|
|
79
|
-
|
|
80
|
-
> Add architecture decisions, edge-cases, and external dependency notes here.
|
|
81
|
-
|
|
82
|
-
## Session Log
|
|
83
|
-
|
|
84
|
-
| Timestamp | Update |
|
|
85
|
-
|-----------|--------|
|
|
86
|
-
| ${now} | Spec created |
|
|
87
|
-
`;
|
|
88
|
-
}
|
|
89
|
-
function appendSessionLog(existing, update) {
|
|
90
|
-
const ts = new Date().toISOString();
|
|
91
|
-
const logRow = `| ${ts} | ${update} |`;
|
|
92
|
-
return existing.replace(/(\| [^\n]+ \|)\s*$/, `$1\n${logRow}`);
|
|
43
|
+
function getCurrentBranch(cwd) {
|
|
44
|
+
try {
|
|
45
|
+
return execSync("git rev-parse --abbrev-ref HEAD", { cwd, stdio: "pipe" }).toString().trim();
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return "unknown-branch";
|
|
49
|
+
}
|
|
93
50
|
}
|
|
94
51
|
// ─────────────────────────────────────────────
|
|
95
52
|
// run_pre_pr_checks helpers
|
|
@@ -100,34 +57,29 @@ const CONSOLE_LOG_REGEX = [
|
|
|
100
57
|
];
|
|
101
58
|
const HARDCODED_URL_PATTERN = /(?:https?:\/\/)?(?:\d{1,3}\.){3}\d{1,3}(?::\d+)?|(?:uat|prod|staging|preprod)\.[a-z0-9.-]+\.[a-z]{2,}/gi;
|
|
102
59
|
function walkDir(dir, exts) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
if (entry.isDirectory()) {
|
|
109
|
-
if (["node_modules", ".git", "dist", "build", "target", ".idea"].includes(entry.name))
|
|
60
|
+
let results = [];
|
|
61
|
+
try {
|
|
62
|
+
const list = fs.readdirSync(dir);
|
|
63
|
+
for (const file of list) {
|
|
64
|
+
if (file === "node_modules" || file === ".git" || file === "dist" || file === "target")
|
|
110
65
|
continue;
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
66
|
+
const filePath = path.join(dir, file);
|
|
67
|
+
const stat = fs.statSync(filePath);
|
|
68
|
+
if (stat && stat.isDirectory()) {
|
|
69
|
+
results = results.concat(walkDir(filePath, exts));
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
if (exts.some(ext => file.endsWith(ext))) {
|
|
73
|
+
results.push(filePath);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
115
76
|
}
|
|
116
77
|
}
|
|
78
|
+
catch (e) {
|
|
79
|
+
/* ignore */
|
|
80
|
+
}
|
|
117
81
|
return results;
|
|
118
82
|
}
|
|
119
|
-
function checkSpecFile() {
|
|
120
|
-
const content = readFileSafe(resolve(SPEC_FILE));
|
|
121
|
-
if (!content)
|
|
122
|
-
return { unchecked: [], missing: true };
|
|
123
|
-
const unchecked = [];
|
|
124
|
-
for (const line of content.split("\n")) {
|
|
125
|
-
const match = line.match(/^- \[ \] (.+)/);
|
|
126
|
-
if (match)
|
|
127
|
-
unchecked.push(match[1].trim());
|
|
128
|
-
}
|
|
129
|
-
return { unchecked, missing: false };
|
|
130
|
-
}
|
|
131
83
|
// ── AST-level linting via ESLint (preferred over regex for JS/TS) ─────────────
|
|
132
84
|
function runEslint(rootDir) {
|
|
133
85
|
// Check if ESLint is configured in the project
|
|
@@ -246,6 +198,31 @@ function findProjectRoots(dir, depth = 0) {
|
|
|
246
198
|
}
|
|
247
199
|
return [...new Set(roots)];
|
|
248
200
|
}
|
|
201
|
+
// ── Automated Tenant Isolation Scanner ────────────────────────────────────────
|
|
202
|
+
function checkTenantIsolation(rootDir) {
|
|
203
|
+
const violations = [];
|
|
204
|
+
const files = walkDir(rootDir, [".java"]).filter(f => f.endsWith("Repository.java"));
|
|
205
|
+
const queryMethodRegex = /\b(?:find|get|read|query|search|stream|count|exists|delete|remove)(?:All)?By[A-Z0-9]/;
|
|
206
|
+
const atQueryRegex = /@Query\s*\(/;
|
|
207
|
+
for (const file of files) {
|
|
208
|
+
const content = readFileSafe(file);
|
|
209
|
+
if (!content)
|
|
210
|
+
continue;
|
|
211
|
+
const lines = content.split("\n");
|
|
212
|
+
for (let i = 0; i < lines.length; i++) {
|
|
213
|
+
const line = lines[i];
|
|
214
|
+
if (queryMethodRegex.test(line) || atQueryRegex.test(line)) {
|
|
215
|
+
// Look at current line and the next to handle simple wrapping
|
|
216
|
+
const combinedContext = line + (lines[i + 1] || "");
|
|
217
|
+
if (!/tenant/i.test(combinedContext)) {
|
|
218
|
+
violations.push(`Missing tenant isolation in ${path.relative(rootDir, file)}:${i + 1} — query lacks 'tenantId':\n` +
|
|
219
|
+
` ${line.trim()}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return violations;
|
|
225
|
+
}
|
|
249
226
|
function checkFlywayMigrations(rootDir) {
|
|
250
227
|
const warnings = [];
|
|
251
228
|
const javaFiles = walkDir(rootDir, [".java"]);
|
|
@@ -279,128 +256,113 @@ const server = new McpServer({
|
|
|
279
256
|
name: "secufusion-mcp",
|
|
280
257
|
version: "1.0.0",
|
|
281
258
|
});
|
|
282
|
-
// ─── Tool 1:
|
|
283
|
-
server.tool("
|
|
284
|
-
|
|
285
|
-
|
|
259
|
+
// ─── Tool 1: manage_branch_state ─────────────────────────────────────────────
|
|
260
|
+
server.tool("manage_branch_state", "Manage the structured JSON state for the current Git branch. Use to initialize new tasks, track pending/completed ACs, and leave explicit next steps for resuming.", {
|
|
261
|
+
action: z
|
|
262
|
+
.enum(["initialize", "read", "update"])
|
|
263
|
+
.describe("Action to perform on the branch state."),
|
|
286
264
|
task_description: z
|
|
287
265
|
.string()
|
|
288
266
|
.optional()
|
|
289
|
-
.describe("
|
|
290
|
-
|
|
267
|
+
.describe("Used with 'initialize' to describe the task."),
|
|
268
|
+
reference_file_path: z
|
|
291
269
|
.string()
|
|
292
270
|
.optional()
|
|
293
|
-
.describe("
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
.string()
|
|
271
|
+
.describe("Used with 'initialize' to note a reference file."),
|
|
272
|
+
pending_acs: z
|
|
273
|
+
.array(z.string())
|
|
297
274
|
.optional()
|
|
298
|
-
.describe("
|
|
299
|
-
|
|
275
|
+
.describe("Array of ACs. Used with 'initialize' or 'update' to set pending tasks."),
|
|
276
|
+
completed_acs: z
|
|
277
|
+
.array(z.string())
|
|
278
|
+
.optional()
|
|
279
|
+
.describe("Array of completed ACs. Used with 'update'."),
|
|
280
|
+
next_step: z
|
|
300
281
|
.string()
|
|
301
282
|
.optional()
|
|
302
|
-
.describe("
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
if (!content) {
|
|
315
|
-
return {
|
|
316
|
-
content: [
|
|
317
|
-
{
|
|
318
|
-
type: "text",
|
|
319
|
-
text: `No spec file found at ${SPEC_FILE}. Run with action='create' to generate one.`,
|
|
320
|
-
},
|
|
321
|
-
],
|
|
322
|
-
};
|
|
283
|
+
.describe("Explicit actionable instruction for resuming. Required for 'update'."),
|
|
284
|
+
}, async ({ action, task_description, reference_file_path, pending_acs, completed_acs, next_step }) => {
|
|
285
|
+
const cwd = process.cwd();
|
|
286
|
+
const branch = getCurrentBranch(cwd);
|
|
287
|
+
const statePath = path.resolve(STATE_FILE);
|
|
288
|
+
let state = {};
|
|
289
|
+
if (fs.existsSync(statePath)) {
|
|
290
|
+
try {
|
|
291
|
+
state = JSON.parse(readFileSafe(statePath) || "{}");
|
|
292
|
+
}
|
|
293
|
+
catch {
|
|
294
|
+
state = {};
|
|
323
295
|
}
|
|
324
|
-
return { content: [{ type: "text", text: content }] };
|
|
325
296
|
}
|
|
326
|
-
if (action === "
|
|
327
|
-
|
|
297
|
+
if (action === "read") {
|
|
298
|
+
const branchState = state[branch];
|
|
299
|
+
if (!branchState) {
|
|
328
300
|
return {
|
|
329
301
|
content: [
|
|
330
302
|
{
|
|
331
303
|
type: "text",
|
|
332
|
-
text:
|
|
304
|
+
text: `No state found for branch '${branch}'. Please initialize first.`,
|
|
333
305
|
},
|
|
334
306
|
],
|
|
335
|
-
isError: true,
|
|
336
307
|
};
|
|
337
308
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
309
|
+
return {
|
|
310
|
+
content: [{ type: "text", text: JSON.stringify(branchState, null, 2) }],
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
if (action === "initialize") {
|
|
314
|
+
state[branch] = {
|
|
315
|
+
task_description: task_description || "",
|
|
316
|
+
reference_file_path: reference_file_path || "",
|
|
317
|
+
pending_acs: pending_acs || [],
|
|
318
|
+
completed_acs: [],
|
|
319
|
+
next_step: "Review requirements and begin implementation.",
|
|
320
|
+
last_updated: new Date().toISOString()
|
|
321
|
+
};
|
|
322
|
+
writeFile(statePath, JSON.stringify(state, null, 2));
|
|
346
323
|
return {
|
|
347
324
|
content: [
|
|
348
325
|
{
|
|
349
326
|
type: "text",
|
|
350
|
-
text: `✅
|
|
351
|
-
`Review and fill in the Acceptance Criteria before starting implementation.\n\n` +
|
|
352
|
-
`---\n\n${spec}`,
|
|
327
|
+
text: `✅ Branch state initialized for '${branch}'.\n\nState:\n${JSON.stringify(state[branch], null, 2)}`,
|
|
353
328
|
},
|
|
354
329
|
],
|
|
355
330
|
};
|
|
356
331
|
}
|
|
357
332
|
if (action === "update") {
|
|
358
|
-
if (!
|
|
333
|
+
if (!state[branch]) {
|
|
359
334
|
return {
|
|
335
|
+
isError: true,
|
|
360
336
|
content: [
|
|
361
|
-
{
|
|
362
|
-
type: "text",
|
|
363
|
-
text: "ERROR: `update_content` is required when action='update'.",
|
|
364
|
-
},
|
|
337
|
+
{ type: "text", text: `No state found for branch '${branch}'. Initialize first.` },
|
|
365
338
|
],
|
|
366
|
-
isError: true,
|
|
367
339
|
};
|
|
368
340
|
}
|
|
369
|
-
|
|
370
|
-
if (!existing) {
|
|
341
|
+
if (!next_step) {
|
|
371
342
|
return {
|
|
343
|
+
isError: true,
|
|
372
344
|
content: [
|
|
373
|
-
{
|
|
374
|
-
type: "text",
|
|
375
|
-
text: `ERROR: No spec file found at ${SPEC_FILE}. Create one first with action='create'.`,
|
|
376
|
-
},
|
|
345
|
+
{ type: "text", text: "ERROR: 'next_step' is required when updating." },
|
|
377
346
|
],
|
|
378
|
-
isError: true,
|
|
379
347
|
};
|
|
380
348
|
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
}
|
|
389
|
-
updated = appendSessionLog(updated, update_content.replace(/\n/g, " ").slice(0, 120));
|
|
390
|
-
writeFile(specPath, updated);
|
|
349
|
+
if (pending_acs)
|
|
350
|
+
state[branch].pending_acs = pending_acs;
|
|
351
|
+
if (completed_acs)
|
|
352
|
+
state[branch].completed_acs = completed_acs;
|
|
353
|
+
state[branch].next_step = next_step;
|
|
354
|
+
state[branch].last_updated = new Date().toISOString();
|
|
355
|
+
writeFile(statePath, JSON.stringify(state, null, 2));
|
|
391
356
|
return {
|
|
392
357
|
content: [
|
|
393
358
|
{
|
|
394
359
|
type: "text",
|
|
395
|
-
text: `✅
|
|
360
|
+
text: `✅ Branch state updated for '${branch}'. Next step recorded:\n> ${next_step}`,
|
|
396
361
|
},
|
|
397
362
|
],
|
|
398
363
|
};
|
|
399
364
|
}
|
|
400
|
-
return {
|
|
401
|
-
content: [{ type: "text", text: "Unknown action." }],
|
|
402
|
-
isError: true,
|
|
403
|
-
};
|
|
365
|
+
return { content: [{ type: "text", text: "Invalid action." }] };
|
|
404
366
|
});
|
|
405
367
|
// ─── Tool 2: log_rejected_pattern ────────────────────────────────────────────
|
|
406
368
|
server.tool("log_rejected_pattern", "Record a coding pattern that was rejected by the team so it is never repeated. " +
|
|
@@ -464,9 +426,9 @@ server.tool("log_rejected_pattern", "Record a coding pattern that was rejected b
|
|
|
464
426
|
});
|
|
465
427
|
// ─── Tool 3: run_pre_pr_checks ───────────────────────────────────────────────
|
|
466
428
|
server.tool("run_pre_pr_checks", "Run all SecuFusion pre-PR guardrail checks across the workspace. " +
|
|
467
|
-
"Automatically discovers modified microservices and runs
|
|
468
|
-
"
|
|
469
|
-
"Linter errors must be fixed natively — do not suppress warnings to pass the gate. " +
|
|
429
|
+
"Automatically discovers modified microservices and runs native linters, database script checks, and strict tenant-isolation scanners. " +
|
|
430
|
+
"Validates: spec checkboxes, hardcoded IPs/URLs, tenantId in Repositories, and Flyway migration coverage for JPA entities. " +
|
|
431
|
+
"Linter/isolation errors must be fixed natively — do not suppress warnings to pass the gate. " +
|
|
470
432
|
"MUST pass with zero errors before a PR is raised.", {
|
|
471
433
|
work_item_id: z
|
|
472
434
|
.string()
|
|
@@ -479,6 +441,7 @@ server.tool("run_pre_pr_checks", "Run all SecuFusion pre-PR guardrail checks acr
|
|
|
479
441
|
.array(z.enum([
|
|
480
442
|
"spec_boxes",
|
|
481
443
|
"console_logs",
|
|
444
|
+
"tenant_isolation",
|
|
482
445
|
"hardcoded_urls",
|
|
483
446
|
"flyway_migrations",
|
|
484
447
|
]))
|
|
@@ -489,23 +452,32 @@ server.tool("run_pre_pr_checks", "Run all SecuFusion pre-PR guardrail checks acr
|
|
|
489
452
|
const errors = [];
|
|
490
453
|
const warnings = [];
|
|
491
454
|
const passed = [];
|
|
492
|
-
// ── 1.
|
|
455
|
+
// ── 1. Branch state check ─────────────────────────────────────────────
|
|
493
456
|
if (!skip_checks.includes("spec_boxes")) {
|
|
494
|
-
const
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
457
|
+
const branch = getCurrentBranch(scanRoot);
|
|
458
|
+
const statePath = path.resolve(scanRoot, STATE_FILE);
|
|
459
|
+
let branchState = null;
|
|
460
|
+
if (fs.existsSync(statePath)) {
|
|
461
|
+
try {
|
|
462
|
+
const stateData = JSON.parse(readFileSafe(statePath) || "{}");
|
|
463
|
+
branchState = stateData[branch];
|
|
464
|
+
}
|
|
465
|
+
catch { }
|
|
466
|
+
}
|
|
467
|
+
if (!branchState) {
|
|
468
|
+
errors.push(`❌ [STATE] No branch state found for '${branch}' in \`${STATE_FILE}\`. ` +
|
|
469
|
+
`Call \`manage_branch_state\` with action='initialize' before raising a PR.`);
|
|
498
470
|
}
|
|
499
|
-
else if (
|
|
500
|
-
errors.push(`❌ [
|
|
501
|
-
|
|
471
|
+
else if (branchState.pending_acs && branchState.pending_acs.length > 0) {
|
|
472
|
+
errors.push(`❌ [STATE] ${branchState.pending_acs.length} pending AC(s) remain for branch '${branch}':\n` +
|
|
473
|
+
branchState.pending_acs.map((u) => ` • ${u}`).join("\n"));
|
|
502
474
|
}
|
|
503
475
|
else {
|
|
504
|
-
passed.push(
|
|
476
|
+
passed.push(`✅ [STATE] Branch state for '${branch}' is clear (no pending ACs).`);
|
|
505
477
|
}
|
|
506
478
|
}
|
|
507
479
|
else {
|
|
508
|
-
warnings.push("⚠️ [
|
|
480
|
+
warnings.push("⚠️ [STATE] Branch state check SKIPPED (manually bypassed).");
|
|
509
481
|
}
|
|
510
482
|
// ── 2. AST-level linting (Workspace Discovery → ESLint/Checkstyle → regex fallback)
|
|
511
483
|
if (!skip_checks.includes("console_logs")) {
|
|
@@ -556,7 +528,21 @@ server.tool("run_pre_pr_checks", "Run all SecuFusion pre-PR guardrail checks acr
|
|
|
556
528
|
else {
|
|
557
529
|
warnings.push("⚠️ [LINTING] Linter check SKIPPED (manually bypassed).");
|
|
558
530
|
}
|
|
559
|
-
// ── 3.
|
|
531
|
+
// ── 3. Tenant Isolation Scanner ──────────────────────────────────────────
|
|
532
|
+
if (!skip_checks.includes("tenant_isolation")) {
|
|
533
|
+
const tenantViolations = checkTenantIsolation(scanRoot);
|
|
534
|
+
if (tenantViolations.length > 0) {
|
|
535
|
+
errors.push(`❌ [SECURITY] Tenant Isolation Failed: Found ${tenantViolations.length} query method(s) in *Repository.java without tenant scoping:\n` +
|
|
536
|
+
tenantViolations.map((v) => ` • ${v}`).join("\n"));
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
passed.push("✅ [SECURITY] Tenant isolation active: All database queries correctly scoped with tenantId.");
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
else {
|
|
543
|
+
warnings.push("⚠️ [SECURITY] Tenant isolation check SKIPPED (manually bypassed).");
|
|
544
|
+
}
|
|
545
|
+
// ── 4. Hardcoded URL check (regex) ───────────────────────────────────────
|
|
560
546
|
if (!skip_checks.includes("hardcoded_urls")) {
|
|
561
547
|
const urlViolations = checkHardcodedUrls(scanRoot);
|
|
562
548
|
if (urlViolations.length > 0) {
|