Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ This release delivers **REST API Passthrough Capabilities**, **API & UI Paginati
- **πŸ§ͺ Quality & Testing** - Complete build pipeline verification, enhanced linting, mutation testing, and fuzzing
- **⚑ Performance Optimizations** - Response compression middleware (Brotli, Zstd, GZip) reducing bandwidth by 30-70% + orjson JSON serialization providing 5-6x faster JSON encoding
- **πŸ¦€ Rust Plugin Framework** - Optional Rust-accelerated plugins with 5-100x performance improvements
- **πŸ’» Admin UI** - Quality of life improvements for admins when managing MCP servers

### Added

Expand Down Expand Up @@ -167,6 +168,10 @@ This release delivers **REST API Passthrough Capabilities**, **API & UI Paginati
- **Implementation**: `mcpgateway/utils/orjson_response.py` configured as default FastAPI response class
- **Test Coverage**: 29 comprehensive unit tests with 100% code coverage

#### **πŸ’» Admin UI enhancements** (#1336)
* **Inspectable auth passwords, tokens and headers** (#1336) - Admins can now view and verify passwords, tokens and custom headers they set when creating or editing MCP servers.


### Fixed

#### **πŸ› Critical Multi-Tenancy & RBAC Bugs**
Expand Down
55 changes: 49 additions & 6 deletions mcpgateway/static/admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -10359,6 +10359,35 @@ window.updateAvailableTags = updateAvailableTags;
// MULTI-HEADER AUTHENTICATION MANAGEMENT
// ===================================================================

/**
* Toggle masking for sensitive text inputs (passwords, tokens, headers)
* @param {HTMLElement|string} inputOrId - Target input element or its ID
* @param {HTMLElement} button - Button triggering the toggle
*/
function toggleInputMask(inputOrId, button) {
const input =
typeof inputOrId === "string"
? document.getElementById(inputOrId)
: inputOrId;

if (!input || !button) {
return;
}

const revealing = input.type === "password";
input.type = revealing ? "text" : "password";

const label = input.getAttribute("data-sensitive-label") || "value";
button.textContent = revealing ? "Hide" : "Show";
button.setAttribute("aria-pressed", revealing ? "true" : "false");
button.setAttribute(
"aria-label",
`${revealing ? "Hide" : "Show"} ${label}`.trim(),
);
}

window.toggleInputMask = toggleInputMask;

/**
* Global counter for unique header IDs
*/
Expand All @@ -10376,6 +10405,7 @@ function addAuthHeader(containerId) {
}

const headerId = `auth-header-${++headerCounter}`;
const valueInputId = `${headerId}-value`;

const headerRow = document.createElement("div");
headerRow.className = "flex items-center space-x-2";
Expand All @@ -10391,12 +10421,25 @@ function addAuthHeader(containerId) {
/>
</div>
<div class="flex-1">
<input
type="password"
placeholder="Header Value"
class="auth-header-value block w-full rounded-md border border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:bg-gray-900 dark:placeholder-gray-300 dark:text-gray-300 text-sm"
oninput="updateAuthHeadersJSON('${containerId}')"
/>
<div class="relative">
<input
type="password"
id="${valueInputId}"
placeholder="Header Value"
data-sensitive-label="header value"
class="auth-header-value block w-full rounded-md border border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:bg-gray-900 dark:placeholder-gray-300 dark:text-gray-300 text-sm pr-16"
oninput="updateAuthHeadersJSON('${containerId}')"
/>
<button
type="button"
class="absolute inset-y-0 right-0 flex items-center px-2 text-xs font-medium text-indigo-600 hover:text-indigo-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:text-indigo-300"
onclick="toggleInputMask('${valueInputId}', this)"
aria-pressed="false"
aria-label="Show header value"
>
Show
</button>
</div>
</div>
<button
type="button"
Expand Down
94 changes: 73 additions & 21 deletions mcpgateway/templates/admin.html
Original file line number Diff line number Diff line change
Expand Up @@ -4528,23 +4528,49 @@ <h3 class="text-lg font-bold mb-4 dark:text-gray-200">
<label class="block text-sm font-medium text-gray-700"
>Password</label
>
<input
type="password"
name="auth_password"
class="mt-1 block w-full rounded-md border border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:bg-gray-900 dark:placeholder-gray-300 dark:text-gray-300"
/>
<div class="relative">
<input
type="password"
name="auth_password"
id="auth-password-gw"
data-sensitive-label="password"
class="mt-1 block w-full rounded-md border border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:bg-gray-900 dark:placeholder-gray-300 dark:text-gray-300 pr-16"
/>
<button
type="button"
class="absolute inset-y-0 right-0 flex items-center px-3 text-xs font-medium text-indigo-600 hover:text-indigo-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:text-indigo-300"
onclick="toggleInputMask('auth-password-gw', this)"
aria-pressed="false"
aria-label="Show password"
>
Show
</button>
</div>
</div>
</div>
<div id="auth-bearer-fields-gw" style="display: none">
<div>
<label class="block text-sm font-medium text-gray-700"
>Token</label
>
<input
type="password"
name="auth_token"
class="mt-1 block w-full rounded-md border border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:bg-gray-900 dark:placeholder-gray-300 dark:text-gray-300"
/>
<div class="relative">
<input
type="password"
name="auth_token"
id="auth-token-gw"
data-sensitive-label="token"
class="mt-1 block w-full rounded-md border border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:bg-gray-900 dark:placeholder-gray-300 dark:text-gray-300 pr-16"
/>
<button
type="button"
class="absolute inset-y-0 right-0 flex items-center px-3 text-xs font-medium text-indigo-600 hover:text-indigo-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:text-indigo-300"
onclick="toggleInputMask('auth-token-gw', this)"
aria-pressed="false"
aria-label="Show token"
>
Show
</button>
</div>
</div>
</div>
<div id="auth-headers-fields-gw" style="display: none">
Expand Down Expand Up @@ -7602,29 +7628,55 @@ <h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">
class="mt-1 block w-full rounded-md border border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:bg-gray-900 dark:placeholder-gray-300 dark:text-gray-300"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700"
>Password</label
>
<div>
<label class="block text-sm font-medium text-gray-700"
>Password</label
>
<div class="relative">
<input
type="password"
name="auth_password"
class="mt-1 block w-full rounded-md border border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:bg-gray-900 dark:placeholder-gray-300 dark:text-gray-300"
id="auth-password-gw-edit"
data-sensitive-label="password"
class="mt-1 block w-full rounded-md border border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:bg-gray-900 dark:placeholder-gray-300 dark:text-gray-300 pr-16"
/>
<button
type="button"
class="absolute inset-y-0 right-0 flex items-center px-3 text-xs font-medium text-indigo-600 hover:text-indigo-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:text-indigo-300"
onclick="toggleInputMask('auth-password-gw-edit', this)"
aria-pressed="false"
aria-label="Show password"
>
Show
</button>
</div>
</div>
<div id="auth-bearer-fields-gw-edit" style="display: none">
<div>
<label class="block text-sm font-medium text-gray-700"
>Token</label
>
</div>
<div id="auth-bearer-fields-gw-edit" style="display: none">
<div>
<label class="block text-sm font-medium text-gray-700"
>Token</label
>
<div class="relative">
<input
type="password"
name="auth_token"
class="mt-1 block w-full rounded-md border border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:bg-gray-900 dark:placeholder-gray-300 dark:text-gray-300"
id="auth-token-gw-edit"
data-sensitive-label="token"
class="mt-1 block w-full rounded-md border border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:bg-gray-900 dark:placeholder-gray-300 dark:text-gray-300 pr-16"
/>
<button
type="button"
class="absolute inset-y-0 right-0 flex items-center px-3 text-xs font-medium text-indigo-600 hover:text-indigo-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:text-indigo-300"
onclick="toggleInputMask('auth-token-gw-edit', this)"
aria-pressed="false"
aria-label="Show token"
>
Show
</button>
</div>
</div>
</div>
<div id="auth-headers-fields-gw-edit" style="display: none">
<div class="space-y-3">
<div class="flex items-center justify-between">
Expand Down
8 changes: 4 additions & 4 deletions tests/security/test_security_performance_compatibility.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,19 @@ def test_endpoint():

# Time without security
client_no_security = TestClient(app_no_security)
start_time = time.time()
start_time = time.perf_counter()
for i in range(iterations):
response = client_no_security.get("/test")
assert response.status_code == 200
time_without_security = time.time() - start_time
time_without_security = time.perf_counter() - start_time

# Time with security
client_with_security = TestClient(app_with_security)
start_time = time.time()
start_time = time.perf_counter()
for i in range(iterations):
response = client_with_security.get("/test")
assert response.status_code == 200
time_with_security = time.time() - start_time
time_with_security = time.perf_counter() - start_time

# Security overhead should be reasonable (< 3x increase)
overhead_ratio = time_with_security / time_without_security
Expand Down
Loading