Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f222098
Refactor routing managers to partials and annotate tests
Aug 22, 2026
d4e3e7f
chore(frontend): sweep frontend, split large components and add missi…
Aug 22, 2026
da74e2d
fix(frontend): remove REQ- prefix from requirement IDs and update cat…
Aug 22, 2026
43ad06a
chore: sweep auth vulnerabilities and add security warnings
Aug 22, 2026
5e16506
fix(secrets): improve url validation and UI error handling for secret…
Aug 22, 2026
f3cfd60
chore: update GitHub Actions to latest versions and fix AGENTS.md link
Aug 22, 2026
28218f5
Fix CI failures in C# and React tests
Aug 22, 2026
bbd7c62
chore: refactor exception handling to prevent leaking sensitive info
Aug 22, 2026
e40b9ee
Fix NRE in unit tests and restore validation message actionability
Aug 22, 2026
71399c7
docs: Update architecture flow diagrams and improve test requirements
Aug 22, 2026
09dc2e0
docs: add user, admin, and admin-mcp guides
Aug 22, 2026
1cf303d
docs: add missing features to features-guide.md
Aug 22, 2026
9ef680c
docs: create deployment guide and support matrix
Aug 22, 2026
b663a81
Fix CodeQL Path Traversal warnings in CapabilityEndpoints
Aug 22, 2026
609e5e5
Fix CodeQL Path Traversal warnings with Path.GetFileName
Aug 22, 2026
60d95bc
chore(release): bump version to 4.28.0
Aug 22, 2026
c9719f2
chore: fix codeql security alerts
Aug 22, 2026
0326d37
chore: update mirrored skill template for X-Frame-Options
Aug 22, 2026
ca16035
chore: fix warnings
Aug 22, 2026
de102ee
chore(release): bump version to 4.29.0
Aug 22, 2026
d272950
docs: fix broken links
Aug 22, 2026
b4cecae
chore: eliminate nullability compiler warnings
Aug 22, 2026
34f9dd9
chore: fix remaining nullability warnings for Method
Aug 22, 2026
8688eff
chore: fix remaining codeql path injection and log forging alerts
Aug 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .agents/skills/mcp-router-setup/templates/web.config
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,10 @@
<requestLimits maxAllowedContentLength="52428800" />
</requestFiltering>
</security>
<httpProtocol>
<customHeaders>
<add name="X-Frame-Options" value="SAMEORIGIN" />
</customHeaders>
</httpProtocol>
</system.webServer>
</configuration>
28 changes: 14 additions & 14 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v4

- name: Set up Python 3.12
uses: actions/setup-python@v7
uses: actions/setup-python@v5
with:
python-version: '3.12'

Expand All @@ -34,10 +34,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v4

- name: Set up .NET 10
uses: actions/setup-dotnet@v6
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'

Expand All @@ -54,7 +54,7 @@ jobs:

- name: Publish test results & coverage
if: always()
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v4
with:
name: backend-test-results
path: |
Expand All @@ -66,10 +66,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v4

- name: Set up Node.js 22 LTS
uses: actions/setup-node@v7
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'npm'
Expand Down Expand Up @@ -97,7 +97,7 @@ jobs:

- name: Publish frontend coverage
if: always()
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v4
with:
name: frontend-coverage
path: frontend/coverage
Expand All @@ -108,15 +108,15 @@ jobs:
needs: [backend, frontend, release-verification]
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v4

- name: Set up .NET 10
uses: actions/setup-dotnet@v6
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'

- name: Set up Node.js 22 LTS
uses: actions/setup-node@v7
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'npm'
Expand Down Expand Up @@ -238,7 +238,7 @@ jobs:

- name: Upload server logs on failure
if: failure()
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v4
with:
name: integration-smoke-logs
path: ./smoke-run/server.log
Expand All @@ -248,13 +248,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4

- name: Build Docker image (dry-run without publish)
uses: docker/build-push-action@v7
uses: docker/build-push-action@v5
with:
context: .
push: false
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v4

- name: Set up .NET (for C# analysis)
if: matrix.language == 'csharp'
uses: actions/setup-dotnet@v6
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/dependency-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v4

- name: Dependency Review
uses: actions/dependency-review-action@v5
uses: actions/dependency-review-action@v4
continue-on-error: true
12 changes: 6 additions & 6 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,18 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v4

- name: Log in to the Container registry
uses: docker/login-action@v4
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata (tags, labels) for standard Docker image
id: meta-standard
uses: docker/metadata-action@v6
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
Expand All @@ -53,7 +53,7 @@ jobs:

- name: Extract metadata (tags, labels) for full Docker image
id: meta-full
uses: docker/metadata-action@v6
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
flavor: |
Expand All @@ -70,7 +70,7 @@ jobs:
uses: docker/setup-buildx-action@v4

- name: Build and push standard Docker image
uses: docker/build-push-action@v7
uses: docker/build-push-action@v5
with:
context: .
target: runtime
Expand All @@ -81,7 +81,7 @@ jobs:
cache-to: type=gha,mode=max,scope=runtime

- name: Build and push full Docker image
uses: docker/build-push-action@v7
uses: docker/build-push-action@v5
with:
context: .
target: runtime-full
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Run tests via `dotnet test McpRouter.slnx`.
dotnet run --project scripts/CatalogGenerator
dotnet run --project scripts/CatalogGenerator -- --verify-only
```
- For full taxonomy, category codes (`AUTH`, `DB`, `GUARD`, `MCP`, `SEC`, `TRANS`, `UI`), and conventions, refer to [`docs/test-catalog-guide.md`](file:///containers/dev/csharp-mcp-router/docs/test-catalog-guide.md).
- For full taxonomy, category codes (`AUTH`, `DB`, `GUARD`, `MCP`, `SEC`, `TRANS`, `UI`), and conventions, refer to [`docs/test-catalog-guide.md`](docs/test-catalog-guide.md).
- Do not use string manipulation (`string.Replace`) for JSON payloads. Use `JsonNode` (see `ClientSession.RewriteRequestJson`).
- Do not commit mockups to `docs/assets/`. Use actual UI screenshots.
- Ensure that you use atomic commits for logical changes.
19 changes: 19 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,3 +262,22 @@ For comprehensive guides covering each individual subsystem in depth, refer to t
---

*Last Updated: Release `v4.19.0`*

### Transport Execution Flow

```mermaid
sequenceDiagram
autonumber
actor Client
participant Router
participant StdioTransport
participant Process

Client->>Router: Execute tool
Router->>StdioTransport: Request execution
StdioTransport->>Process: Launch subprocess (Node/Python)
Process-->>StdioTransport: Return stdout/stderr
StdioTransport-->>Router: Format as JSON-RPC
Router-->>Client: Deliver response
```

2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ For summary details and quick references, see [README.md](README.md).

| Version | Release Date | Summary of Key Changes |
| :--- | :--- | :--- |
| **`v4.29.0`** | 2026-08-22 | Security fixes: resolved CodeQL alerts for log forging, path injection, cleartext sensitive info storage, and missing X-Frame-Options headers. |
| **`v4.28.0`** | 2026-08-22 | chore(repo): large repository health sweep refactoring backend, frontend, auth, secrets, CI, and rewriting all documentation guides |
| **`v4.27.2`** | 2026-08-22 | refactor(reqs): normalize requirement taxonomy IDs across all C#, Vitest, and Playwright test suites to eliminate `REQ-` prefixes and strictly enforce standard category codes (`AUTH-`, `UI-`, `DB-`, `GUARD-`) |
| **`v4.27.1`** | 2026-08-22 | test(e2e): add comprehensive Playwright E2E test suites for self-service personal AppKeys, personal quota limits, and admin custom user quota overrides |
| **`v4.27.0`** | 2026-08-22 | feat(auth): self-service personal AppKeys, App-Level keys separation, UserQuotas table & management endpoints, and role-adaptive frontend UI |
Expand Down
21 changes: 14 additions & 7 deletions Components/AppKeys/AppKeysController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ public async Task<IActionResult> GetAppKeys([FromQuery] string? keyType = null,
}
catch (Exception ex)
{
return StatusCode(500, new { error = ex.Message });
HttpContext?.RequestServices?.GetService<Microsoft.Extensions.Logging.ILoggerFactory>()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred.");
return StatusCode(500, new { error = "An unexpected error occurred." });
}
}

Expand Down Expand Up @@ -148,7 +149,8 @@ public async Task<IActionResult> GetAppKeysLimits()
}
catch (Exception ex)
{
return StatusCode(500, new { error = ex.Message });
HttpContext?.RequestServices?.GetService<Microsoft.Extensions.Logging.ILoggerFactory>()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred.");
return StatusCode(500, new { error = "An unexpected error occurred." });
}
}

Expand Down Expand Up @@ -311,7 +313,8 @@ await _auditLogger.LogAdminActionAsync(
}
catch (Exception ex)
{
return StatusCode(500, new { error = ex.Message });
HttpContext?.RequestServices?.GetService<Microsoft.Extensions.Logging.ILoggerFactory>()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred.");
return StatusCode(500, new { error = "An unexpected error occurred." });
}
}

Expand Down Expand Up @@ -351,7 +354,8 @@ await _auditLogger.LogAdminActionAsync(
}
catch (Exception ex)
{
return StatusCode(500, new { error = ex.Message });
HttpContext?.RequestServices?.GetService<Microsoft.Extensions.Logging.ILoggerFactory>()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred.");
return StatusCode(500, new { error = "An unexpected error occurred." });
}
}

Expand All @@ -370,7 +374,8 @@ public async Task<IActionResult> GetUserQuotas()
}
catch (Exception ex)
{
return StatusCode(500, new { error = ex.Message });
HttpContext?.RequestServices?.GetService<Microsoft.Extensions.Logging.ILoggerFactory>()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred.");
return StatusCode(500, new { error = "An unexpected error occurred." });
}
}

Expand Down Expand Up @@ -405,7 +410,8 @@ await _auditLogger.LogAdminActionAsync(
}
catch (Exception ex)
{
return StatusCode(500, new { error = ex.Message });
HttpContext?.RequestServices?.GetService<Microsoft.Extensions.Logging.ILoggerFactory>()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred.");
return StatusCode(500, new { error = "An unexpected error occurred." });
}
}

Expand Down Expand Up @@ -435,7 +441,8 @@ await _auditLogger.LogAdminActionAsync(
}
catch (Exception ex)
{
return StatusCode(500, new { error = ex.Message });
HttpContext?.RequestServices?.GetService<Microsoft.Extensions.Logging.ILoggerFactory>()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred.");
return StatusCode(500, new { error = "An unexpected error occurred." });
}
}

Expand Down
9 changes: 6 additions & 3 deletions Components/AppKeys/UserCredentialsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ public async Task<IActionResult> GetConfiguredCredentials()
}
catch (Exception ex)
{
return StatusCode(500, new { error = ex.Message });
HttpContext?.RequestServices?.GetService<Microsoft.Extensions.Logging.ILoggerFactory>()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred.");
return StatusCode(500, new { error = "An unexpected error occurred." });
}
}

Expand Down Expand Up @@ -81,7 +82,8 @@ public async Task<IActionResult> SaveCredential(string serverId, [FromBody] Save
}
catch (Exception ex)
{
return StatusCode(500, new { error = ex.Message });
HttpContext?.RequestServices?.GetService<Microsoft.Extensions.Logging.ILoggerFactory>()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred.");
return StatusCode(500, new { error = "An unexpected error occurred." });
}
}

Expand All @@ -103,7 +105,8 @@ public async Task<IActionResult> DeleteCredential(string serverId)
}
catch (Exception ex)
{
return StatusCode(500, new { error = ex.Message });
HttpContext?.RequestServices?.GetService<Microsoft.Extensions.Logging.ILoggerFactory>()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred.");
return StatusCode(500, new { error = "An unexpected error occurred." });
}
}
}
Expand Down
18 changes: 12 additions & 6 deletions Components/Authorization/PermissionsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ public async Task<IActionResult> GetPolicies()
}
catch (Exception ex)
{
return StatusCode(500, new { error = ex.Message });
HttpContext?.RequestServices?.GetService<Microsoft.Extensions.Logging.ILoggerFactory>()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred.");
return StatusCode(500, new { error = "An unexpected error occurred." });
}
}

Expand Down Expand Up @@ -98,8 +99,9 @@ WHEN NOT MATCHED THEN
}
catch (Exception ex)
{
HttpContext?.RequestServices?.GetService<Microsoft.Extensions.Logging.ILoggerFactory>()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred.");
await _auditLogger.LogAdminActionAsync(username, "policy.save", policy.TargetId ?? policy.Id ?? "", System.Text.Json.JsonSerializer.Serialize(policy), false, ex.Message);
return StatusCode(500, new { error = ex.Message });
return StatusCode(500, new { error = "An unexpected error occurred." });
}
}

Expand All @@ -117,8 +119,9 @@ public async Task<IActionResult> DeletePolicy(string id)
}
catch (Exception ex)
{
HttpContext?.RequestServices?.GetService<Microsoft.Extensions.Logging.ILoggerFactory>()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred.");
await _auditLogger.LogAdminActionAsync(username, "policy.delete", id, "", false, ex.Message);
return StatusCode(500, new { error = ex.Message });
return StatusCode(500, new { error = "An unexpected error occurred." });
}
}

Expand All @@ -136,7 +139,8 @@ public async Task<IActionResult> GetMappings()
}
catch (Exception ex)
{
return StatusCode(500, new { error = ex.Message });
HttpContext?.RequestServices?.GetService<Microsoft.Extensions.Logging.ILoggerFactory>()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred.");
return StatusCode(500, new { error = "An unexpected error occurred." });
}
}

Expand Down Expand Up @@ -191,8 +195,9 @@ WHEN NOT MATCHED THEN
}
catch (Exception ex)
{
HttpContext?.RequestServices?.GetService<Microsoft.Extensions.Logging.ILoggerFactory>()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred.");
await _auditLogger.LogAdminActionAsync(username, "mapping.save", mapping.ExternalId ?? mapping.Id ?? "", System.Text.Json.JsonSerializer.Serialize(mapping), false, ex.Message);
return StatusCode(500, new { error = ex.Message });
return StatusCode(500, new { error = "An unexpected error occurred." });
}
}

Expand All @@ -210,8 +215,9 @@ public async Task<IActionResult> DeleteMapping(string id)
}
catch (Exception ex)
{
HttpContext?.RequestServices?.GetService<Microsoft.Extensions.Logging.ILoggerFactory>()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred.");
await _auditLogger.LogAdminActionAsync(username, "mapping.delete", id, "", false, ex.Message);
return StatusCode(500, new { error = ex.Message });
return StatusCode(500, new { error = "An unexpected error occurred." });
}
}
}
Expand Down
Loading
Loading