Adds /mcp-tokens page that lets a logged-in user mint, list, and
revoke bearer tokens for the MCP server. Stacks on #92 (which added
the agent endpoints + middleware) — once both land, the loop is
closed: a user can copy a token from the dashboard straight into
their Claude Desktop / Cursor / ChatGPT MCP config.
UX
- "Create Token" button → inline form with name input.
- On submit, server function calls `POST /api/v1/mcp-tokens`. The
raw token is shown ONCE in a prominent yellow banner with a copy
button and a "won't be shown again" warning, then the user
dismisses it manually.
- List view: card per token with name, prefix `mcpt_xxxx…`, created
date, last_used (or "never"). Revoked tokens render dimmed with a
"revoked" pill. Active tokens have a trash button → confirm
modal → soft delete.
- Toast feedback on create/revoke success/failure.
Files
- infrastructure/mcp_tokens.rs (new) — three #[server] functions:
fetch_mcp_tokens, create_mcp_token, revoke_mcp_token. All go
through agent_client so the Keycloak Bearer is auto-attached;
the agent then enforces tenant scoping on every endpoint.
- pages/mcp_tokens.rs (new) — the page component itself.
- app.rs — adds Route::McpTokensPage at /mcp-tokens.
- pages/mod.rs, infrastructure/mod.rs — module + re-export wiring.
Timestamp format
- The agent serializes BSON DateTime as extended JSON
`{"$date":{"$numberLong":"..."}}`. Page has a small helper that
accepts that shape, plain ISO strings, or anything else
(best-effort). Same approach used elsewhere in the dashboard so
there's no new dependency.
Test plan
- cargo fmt --all clean
- cargo clippy -p compliance-dashboard --features server
-- -D warnings clean
- cargo clippy -p compliance-dashboard --features web
--no-default-features -- -D warnings clean
- cargo check on both feature sets clean
Followup
- No sidebar entry yet (matches mcp_servers — settings-style
pages are reached via direct URL today). Worth adding a
Settings sub-menu in a separate UX pass.
- Token expiry + per-tool scope when those land on the agent side
will need a small UI for the create modal (extra fields).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
LLM clients (Claude Desktop, Cursor, ChatGPT) can't run a Keycloak
OIDC flow, so the MCP server can't use JWTs for auth. This PR
introduces opaque static bearer tokens minted per-tenant via new
agent endpoints, validated by the MCP server, and used to route
incoming MCP requests to the caller's per-tenant database.
Until now, the MCP server connected to a single shared MongoDB DB
with no auth and no tenant awareness — every tool (list_findings,
list_sbom_packages, etc.) returned data across all tenants. After
M7.2 made the agent per-tenant, MCP was the lone cross-tenant data
leak. This closes it.
Design summary
- Token format: `mcpt_<43 url-safe random chars>` (48 chars total).
Opaque, never embeds tenant_id, never stored in plaintext.
- Storage: cross-tenant `<prefix>__admin.mcp_tokens` collection,
keyed by SHA-256 hash. Each row carries the tenant_id, name,
created_by, created_at, last_used_at, revoked flag.
- Agent endpoints (tenant-scoped via TenantCtx):
POST /api/v1/mcp-tokens → mint (returns raw token ONCE)
GET /api/v1/mcp-tokens → list (metadata + 12-char prefix,
never the hash)
DELETE /api/v1/mcp-tokens/id → soft revoke
- MCP middleware: extract `Authorization: Bearer mcpt_...`, sniff
the prefix, SHA-256 → lookup in admin DB → reject if missing or
revoked. Updates last_used_at fire-and-forget so it never blocks.
Sets `tokio::task_local!` TENANT_ID for the inner service call;
the rmcp tool handlers read it and resolve the per-tenant DB.
- task_local is scoped via TENANT_ID.scope(...) around next.run(req)
so the rmcp tool handlers downstream see the tenant_id without
modifying their (macro-generated) signatures.
Files
- compliance-core/src/models/mcp_token.rs (new) — McpToken +
McpTokenView (public projection without the hash).
- compliance-agent/src/database.rs — DatabasePool::admin_db() +
admin_db_name(): cross-tenant access for token storage.
- compliance-agent/src/api/handlers/mcp_tokens.rs (new) — three
endpoints. Token generation: 32 random bytes → URL-safe base64,
no padding. SHA-256 hex stored.
- compliance-mcp/src/database.rs — replaced single Database with
DatabasePool. Tenant-scoped Database constructed per request.
Same sanitization + 63-byte cap + hash fallback as the agent.
- compliance-mcp/src/auth.rs (new) — bearer middleware + task_local.
Includes a SHA-256 round-trip test against a known vector.
- compliance-mcp/src/main.rs — HTTP transport: bearer middleware
layered on /mcp (not /health, so orca's container probe still
works). stdio transport: falls back to STDIO_TENANT_ID env (defaults
to "dev") so local development still works; logged loudly as
not-for-production.
- compliance-mcp/src/server.rs — each of the 12 tool handlers
resolves the per-tenant DB via task_local before calling its tool
fn. Tool fns themselves are unchanged.
Token UX
- Generated by the dashboard (or curl + KC JWT) — user sees raw
token exactly once, copies it into their LLM client config.
- Dashboard UI for management is a follow-up; can use curl in the
meantime:
curl -X POST https://comp-dev.../api/v1/mcp-tokens \
-H "Authorization: Bearer $KC_JWT" \
-H "Content-Type: application/json" \
-d '{"name":"Claude Desktop"}'
Test plan
- cargo fmt --all clean
- cargo clippy --workspace --exclude compliance-dashboard
-- -D warnings clean
- cargo test -p compliance-core --lib — 7 pass
- cargo test -p compliance-agent --lib — 230 pass (+2 new for
token generation + sha256 stability)
- cargo test -p compliance-agent --test tenant_isolation — 6 pass
- cargo test -p compliance-mcp — 34 pass (+1 new sha256 vector)
What's deferred
- Dashboard UI for managing tokens (page + create modal + list/
revoke). Trivial once the API is live.
- Token expiry + per-tool scope (today every token grants access
to all 12 tools for its tenant).
- Lifting DatabasePool into compliance-core (duplicated for now
in compliance-mcp to keep this PR focused; lift if a third
consumer appears).
Production
- The `<prefix>__admin` DB needs to NOT collide with a tenant
DB. Sanitized tenant_id never starts with `_admin` for any
current tenant_id shape (UUIDs); flagged in the database.rs
docstring so tenant provisioning can reject `_admin*` ids
proactively.
- orca-infra MCP service block already has MONGODB_URI /
MONGODB_DATABASE — no new env needed. No KC creds since MCP
doesn't use Keycloak for its own auth.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
## Summary
- **Scan produces no results in Orca** — semgrep (`--config=auto`, unbounded memory) and syft (remote license network calls) were getting OOM-killed or hanging in resource-constrained Orca containers. Scan would "complete" with 0 findings/SBOMs silently because each scanner failure is caught and logged as a warning.
- **Dashboard Script error spam** — `document::Script` in Dioxus 0.7 needs a single text node child for inline scripts; `dangerous_inner_html` was invalid and spammed the error log on every unauthenticated page load.
## Changes
| File | Change |
|------|--------|
| `semgrep.rs` | Add `--max-memory 500 --jobs 1`; 10-minute timeout |
| `syft.rs` | Remove remote license lookup env vars; 5-minute timeout |
| `gitleaks.rs` | 5-minute timeout |
| `app_shell.rs` | Fix `dangerous_inner_html` → text child in `document::Script` |
## Test plan
- [ ] Trigger a scan on a repo in Orca — findings and SBOM entries should now appear
- [ ] Agent logs should show timeout/error warnings rather than silent empty results when tools are killed
- [ ] Navigate to dashboard unauthenticated — Script error gone from logs
- [ ] Verify scans work end-to-end with `docker compose up`
---------
Co-authored-by: Sharang Parnerkar <30073382+mighty840@users.noreply.github.com>
Reviewed-on: #78
Each deploy job now builds the per-service image, pushes to the
private registry as :latest and :sha, then triggers an HMAC-signed
orca redeploy webhook. Coolify webhooks are no longer used.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Summary
- Add HTTP response status checking to all Gitea tracker methods that were silently swallowing errors
- Add fallback in create_pr_review: if inline comments fail, retry as plain PR comment
## Test plan
- [ ] Deploy and trigger a PR review, check logs for actual error details
- [ ] Verify fallback posts summary comment when inline comments fail
Co-authored-by: Sharang Parnerkar <parnerkarsharang@gmail.com>
Co-authored-by: Sharang Parnerkar <30073382+mighty840@users.noreply.github.com>
Reviewed-on: #47
- Remove port 143 from mailserver (only expose 993/IMAPS)
- Enable SSL_TYPE=manual with Let's Encrypt certs
- Set DOVECOT_DISABLE_PLAINTEXT_AUTH=yes
- Add pentest_imap_tls config field (defaults to true)
Fixes CERT-Bund report: IMAP PLAIN/LOGIN without TLS on 46.225.100.82:143
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add CHROME_WS_URL env var support for PDF report generation via
Chrome DevTools Protocol over WebSocket (falls back to local binary)
- Update seeded MCP server endpoint URLs on boot when MCP_ENDPOINT_URL
env var differs from stored value (previously only seeded once)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the inline <pre> JSON display with a proper browser download using
Blob + URL.createObjectURL. Clicking "Download" now saves a .json file
(CycloneDX or SPDX format) directly to the user's downloads folder.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previous JSON used an incorrect format. Rewrote both dashboards using
the actual SigNoz v4 schema (widgets array, builder queryData, proper
layout entries, aggregateOperator/aggregateAttribute structure).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two dashboards with ClickHouse queries matching our tracing instrumentation:
- compliance-agent: API handler latency/errors, scan pipeline stage durations,
DAST/graph/chat API panels, and warn/error log tracking
- compliance-dashboard: server function performance, page load distribution,
agent connectivity health, and error log monitoring
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add repo_id, finding_id, and filter fields to tracing::instrument attributes
for better trace correlation in SigNoz. Replace all silently swallowed errors
(Err(_) => Vec::new()) with tracing::warn! logging across mod.rs, dast.rs,
graph.rs handlers. Add stage-level spans with .instrument() to pipeline
orchestrator for visibility into scan phases.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fetch SSH public key every time auth section opens (was only fetching
once and caching failures)
- Add mkdir for SSH key directory in Dockerfile.agent
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New `compliance-mcp` crate providing a Model Context Protocol server
with 7 tools: list/get/summarize findings, list SBOM packages, SBOM
vulnerability report, list DAST findings, and DAST scan summary.
Supports stdio (local dev) and Streamable HTTP (deployment via MCP_PORT).
Includes Dockerfile, CI clippy check, and Coolify deploy job.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Sharang Parnerkar <parnerkarsharang@gmail.com>
Reviewed-on: #5
Deploys agent, dashboard, and docs independently based on which
files changed. Only triggers on main after tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Restructured layout: avatar, truncated username, and logout icon
in a single row. Collapsed state stacks vertically. Logout button
uses a subtle icon-only style with red hover. Proper text ellipsis
prevents name overflow.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>