HTTP server, authentication and TLS¶
A shared server (team, CI/CD) is made of two processes sharing the same database (see Architecture and deployment):
- the MCP server (
security-review-mcp serve --transport streamable-http):/mcpendpoint, public enrollment (/api/v1/enroll) and/healthzprobe; it is the one exposed to the agents and to the CI; - the controller (
security-review-controller serve):/api/v1REST API,/ui/web interface and/healthzprobe; it can stay on an internal network.
MCP authentication and TLS are optional and independent; the controller API always requires a token. With stdio, the host starts the process itself and the controller is not needed.
security-review-controller db upgrade # creates or migrates the schema
security-review-controller serve --host 127.0.0.1 --port 8080
# web interface: http://127.0.0.1:8080/ui/
security-review-mcp serve --transport streamable-http --host 127.0.0.1 --port 8000
# MCP endpoint: http://127.0.0.1:8000/mcp
With SQLite, both processes must run on the same machine (same file); use PostgreSQL as soon as they are on different machines or pods.
Bearer tokens¶
Tokens are stored in the database (apitoken table): only their SHA-256 digest is kept, the
secret is shown only once, at creation. Manage them with the token command (see the
CLI):
security-review-controller token create --label ci # mcp role by default
security-review-controller token create --label jenkins --role ci --project shop-api
security-review-controller token create --label ops-script --role admin
# {"id": "6f1c…", "label": "ci", "role": "mcp", "project": null, "created_at": "…", "token": "UJB7…"}
security-review-controller token list [--project shop-api] # never the secret nor the digest
security-review-controller token revoke ci # by label or by id
Developers can also create a personal MCP token in the web interface, after enrolling their project: see enrollment.
- Give
tokento the client; the uniquelabelappears in the logs. --roleismcpby default (MCP server);adminreaches the MCP server and the whole REST API of the controller, never the web interface;ci(always with--project) only reaches the pipeline routes used bysecurity-review-ci: see the roles;metrics(never with--project) only reaches the Prometheus metrics of the controller. Tokens are for machines: there is noreaderortriagertoken, a person reads and triages with a user account and its groups. Theapitokens of the REST API and its Swagger, which hold the rights of a user or of a role, are created in the web interface (API tokens).--expires-in-dayssets the validity of the token: see expiry and rotation.--projectlimits the token to one or more projects (repeat the option), for the MCP tools and the pipeline routes (mcpandciroles; refused foradminandmetrics). See tokens restricted to a project. Administrators can also create and revoke restrictedmcpandcitokens from the Tokens screen.- The server reads the database at every request: a creation or a revocation applies immediately, including to the other instances sharing the same PostgreSQL database.
The MCP endpoint and the controller API require Authorization: Bearer <token>. Still public: /healthz on both processes, /api/v1/enroll
on the MCP server, and on the controller the static files of /ui/, the /api/favicon.svg
icon, the /api/health liveness of the API and, with SECREVIEW_SWAGGER_ENABLED=true (disabled by default), the Swagger
documentation (/api/docs, /api/openapi.json schema, /api/swagger/ files). While no token exists, the controller API answers 503 and the MCP server stays closed: a
client without a token only reaches enrollment, any
other request gets 401, whatever the bearer value (a warning is logged at start-up). /mcp is
only open in that case when the server listens on localhost (127.0.0.1, ::1) or when
SECREVIEW_ALLOW_UNAUTHENTICATED=true, to keep for tests on an isolated network: as soon as a
token exists, it is required in every case. token revoke warns on stderr when it deletes the
last token.
Every creation, rotation and revocation of a token, and every enrollment, is written to the audit log, with who made it.
Expiry and rotation of tokens¶
A token may expire: after its expiry date it authenticates nothing (401, error="invalid_token",
description The bearer token has expired, followed on /mcp by how to
get a new one), but it stays listed so that an administrator
rotates or revokes it. SECREVIEW_TOKEN_MAX_DAYS (default 0) sets the longest validity of a
token created or rotated, and its default validity; with 0, tokens do not expire unless a
validity is asked for.
security-review-controller token create --label shop-ci --project shop-api --expires-in-days 90
security-review-controller token rotate shop-ci --grace-hours 24 # new secret, shown once
# {"id": "6f1c…", "label": "shop-ci", …, "expires_at": "…", "rotated_at": "…", "grace_until": "…", "token": "pX4q…"}
A rotation replaces the secret of a token: it keeps its id, label, role and projects, and gets a
new validity (--expires-in-days, default as at creation). During --grace-hours (0 by
default, at most 168) the previous secret keeps working, the time to update the clients
(grace_until in token list); after that, only the new one authenticates. The previous secret
of an expired token never comes back. After a leak, rotate without a grace period (or revoke):
the leaked secret then stops at once. A personal token cannot be rotated: its user creates a
new one. In the web interface, the Tokens screen rotates the project tokens (button
Rotate, grace period chosen above the list).
Expired tokens still count as configured tokens: a server whose tokens have all expired stays closed, it never becomes open.
Tokens restricted to a project¶
On a server shared by several teams, give each pipeline a token limited to its project:
security-review-controller token create --label shop-ci --project shop-api # mcp role
security-review-controller token create --label shop-gate --role ci --project shop-api
A token without --project reaches every project. In the web interface, the
Tokens screen creates a token for some projects
or for All projects, and changes the projects of an existing token (except admin and
metrics; a ci token always keeps at least one project). A restricted token:
- only saves reports (
save_security_report) for its project; - only reads, lists, exports and proposes a status for (
propose_finding_status) the reports and findings of its project;list_security_reportswithout a filter only returns those; - gets, for any other project, report or finding, the same "not found" error as for a missing object, so as not to reveal the other projects;
- cannot call
reload_rules.
The restriction also applies to the report:// resource and to the triage-report prompt.
With stdio, there is no token, hence no restriction.
Initial administrator password¶
At its first start, the controller creates the admin
account of the web interface
(SECREVIEW_ADMIN_USERNAME), with a random password that nobody knows: set it with the CLI,
which prints it once (the old one is invalidated at once), see the
first administrator. This account only opens
the web interface and the REST API, never /mcp: for an agent, create a token
(token create).
security-review-controller user reset-password admin
Enrollment: request the project¶
The enrollment never creates a token: it requests the project, which an administrator
approves, and the developer creates a personal MCP token in the web interface. It is enabled by
default (SECREVIEW_ENROLLMENT=true), even before the first token is created (except on an
open server, which needs no token).
-
Request. The client gives the name of its project (usually the repository name). A new project is pending: it is not a project yet and gives no access. A project that already exists, or is already pending, is unchanged, and the answer is the same in every case, so that it does not tell which projects exist. Three ways to enroll:
- From the agent, at the first review. An MCP client that connects without an
Authorizationheader is not rejected: it reaches a restricted MCP server (samesecurity-reviewname) that only exposes therequest_enrollmenttool, and prompts named like the reviews (security-review,codebase-review…) that explain the steps. The agent callsrequest_enrollmentwith the project name. -
From the command line, from the developer workstation or the CI:
security-review-mcp enroll --url https://secreview.example.internal:8443 \ --project shop-api --requested-by "alice (shop team)" # {"project": "shop-api", "web_url": "https://secreview.example.internal/ui", "next_steps": "…"} -
Through the MCP server API:
POST /api/v1/enroll(without a token) with{"project": "shop-api"}, answered with202. The controller does not serve this endpoint: it exposes no unauthenticated write route. - Approval. A user holding
projects.create(theadminrole among others) approves or rejects the request in the Pending projects section of the Projects screen, or from the CLI:
security-review-controller project list # requester, address, expiry security-review-controller project approve 3 # id of the list security-review-controller project reject 4The approval creates the project, with its group
project:<project>without any member: nobody gains any access, the approver included. A request nobody approved withinSECREVIEW_PROJECT_ENROLLMENT_TTL_DAYS(7 days by default) is deleted. 3. Personal token. The answer of the enrollment gives the URL of the web interface (web_url, fromSECREVIEW_WEB_URL) and the next steps: the user signs in, creates a personal MCP token in My account (see personal MCP tokens) and puts it in the host configuration. The token reaches the projects the user may read, at each request. 4. Access. An administrator gives the user access to the project by adding them to the groupproject:<project>(Groups screen, see access control); the personal token reaches the project from the next request, without reconfiguration. - From the agent, at the first review. An MCP client that connects without an
An agent that already has a token may also call request_enrollment (for instance when
save_security_report refuses a project its token does not reach): the project is requested
the same way, and the token gains no project. When a user with projects.create
creates the project directly, its pending request is
settled.
Rejected token: sign in again¶
A client whose token is unknown, revoked or expired gets 401 on /mcp
(error="invalid_token"): the description tells the user to sign in to the web interface (with
its URL when SECREVIEW_WEB_URL is set) and to create a new personal MCP token, then to replace
it in the host configuration. Such a client never
reaches the review tools nor the enrollment server. The rejection is logged as a warning by the
MCP server (Rejected unauthenticated request / Rejected expired token).
security-review-mcp scan-deps gives the same advice.
Safeguards: anybody who reaches the server may enroll, so at most 20 projects are pending at a
time, a client address enrolls at most 5 times per hour (429 beyond; behind a reverse proxy,
set SECREVIEW_FORWARDED_ALLOW_IPS), and a pending project
expires after SECREVIEW_PROJECT_ENROLLMENT_TTL_DAYS. Every request, approval, rejection and
expiry is written to the audit log (enrollment.requested,
enrollment.approved, enrollment.rejected, with the requester and the client address). To disable the enrollment: SECREVIEW_ENROLLMENT=false
(clients without a token then get 401, with the same advice).
--url is the URL of the MCP server (with or without /mcp): it receives the requests.
Tip
The token commands run on the controller side (or in its container), with the same
SECREVIEW_DATABASE_URL as the controller. Tokens are never passed as command-line
arguments.
TLS¶
Uvicorn serves HTTPS directly when a certificate is given, for the MCP server as for the controller (same options, same variables):
security-review-mcp serve --transport streamable-http --host 0.0.0.0 --port 8443 \
--tls-certfile /etc/secreview/tls/cert.pem --tls-keyfile /etc/secreview/tls/key.pem
security-review-controller serve --host 0.0.0.0 --port 9443 \
--tls-certfile /etc/secreview/tls/cert.pem --tls-keyfile /etc/secreview/tls/key.pem
# or SECREVIEW_TLS_CERTFILE / SECREVIEW_TLS_KEYFILE (+ SECREVIEW_TLS_KEYFILE_PASSWORD)
SECREVIEW_TLS_CLIENT_CA also enables mutual TLS: only the clients presenting a
certificate signed by this CA can connect.
Certificate authority¶
The server never generates a certificate: provide one signed by a certificate authority
trusted by the clients, a public CA (for example Let's Encrypt, or cert-manager in Kubernetes)
or the internal CA of your organization. Its subjectAltName must contain the names and
addresses used by the clients (for example DNS:secreview.lan,IP:192.168.1.1); otherwise the
clients refuse the connection even when they trust the CA. The key file (key.pem) never
leaves the server: keep it readable by the server account only (chmod 600).
With an internal CA, the clients must trust its certificate: see Host configuration.
HTTPS server with an internal CA¶
When the server certificate is signed by an
internal CA, the host refuses the connection
(unable to verify the first certificate, unable to get local issuer certificate). Neither VS Code nor
Claude has a per-server TLS option in its MCP configuration: the certificate is added to the
trusted authorities of the host process. Claude Code and VS Code run on Node.js, which reads
the NODE_EXTRA_CA_CERTS environment variable (PEM file) for this, when the process starts.
-
Get the certificate of the internal CA (PEM) from its administrators, save it as
~/.config/secreview/ca.pem, and check that the name or address of the MCPurlis in the server certificate:openssl s_client -connect 192.168.1.1:8000 -CAfile ~/.config/secreview/ca.pem </dev/null \ 2>/dev/null | openssl x509 -noout -ext subjectAltName -
Declare it to the host, then restart it:
In the shell profile (~/.bashrc, ~/.zshrc), before starting claude:
export NODE_EXTRA_CA_CERTS="$HOME/.config/secreview/ca.pem"
The Claude Code documentation also gives
the env key of settings.json
("env": { "NODE_EXTRA_CA_CERTS": "/home/me/.config/secreview/ca.pem" }). The variable
applies to every connection of Claude Code, not only to the MCP server.
Start VS Code from a shell where the variable is set (code .), or set it at system
level:
export NODE_EXTRA_CA_CERTS="$HOME/.config/secreview/ca.pem"
code .
Another option: add the certificate to the certificate store of the system (Windows:
Trusted Root Certification Authorities; macOS: Keychain, Always Trust; Linux:
/usr/local/share/ca-certificates/ then sudo update-ca-certificates). VS Code loads the
system certificates (http.systemCertificates setting, enabled by default).
In a dev container, the connection leaves from the container: mount the file and set
the variable in devcontainer.json ("remoteEnv": { "NODE_EXTRA_CA_CERTS": "..." }), then
rebuild or reload the window.
Warning
Do not use NODE_TLS_REJECT_UNAUTHORIZED=0: it disables the verification of every
connection of the process (API, extensions, updates), not only the one to the MCP server,
and lets an interception through.
The remote connectors added from the Claude web interface (claude.ai, Claude Desktop) are contacted from Anthropic's infrastructure, not from the workstation: they require a server reachable from the Internet with a publicly trusted certificate, which rules out an internal CA.
For the enrollment from the CLI, security-review-mcp enroll accepts --cafile directly (see
below).
Client configuration¶
VS Code, with a token asked to the user (.vscode/mcp.json, see
Host configuration):
{
"servers": {
"security-review": {
"type": "http",
"url": "https://secreview.example.internal:8443/mcp",
"headers": { "Authorization": "Bearer ${input:secreview-token}" }
}
},
"inputs": [
{ "id": "secreview-token", "type": "promptString", "description": "Token", "password": true }
]
}
For Claude and to get a token from the host, see Host configuration.
Behind a reverse proxy¶
A reverse proxy (TLS, OIDC, rate limiting) remains a valid option: in that case, leave these variables empty.
Warning
If the server listens elsewhere than on localhost without authentication or without TLS, a
warning is logged. Any token with the mcp or admin role gives access to every tool.