Skip to content
Merged
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
1 change: 1 addition & 0 deletions capiscio_mcp/_proto/capiscio/v1/mcp_pb2.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ class EvaluateConfig:
min_trust_level: int = 0
accept_level_zero: bool = False
allowed_tools: List[str] = field(default_factory=list)
require_badge: bool = False


@dataclass
Expand Down
29 changes: 19 additions & 10 deletions capiscio_mcp/connect.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,28 +92,37 @@ def _load_private_key_pem(pem_text: str) -> tuple[Ed25519PrivateKey, str, str, s
def _log_key_capture_hint(server_id: str, private_key_pem: str) -> None:
"""Write a one-time hint to stderr telling the user how to persist key material.

Uses ``print(..., file=sys.stderr)`` instead of the logger so the private
key never enters log aggregation pipelines. The hint is only emitted on
first-run key generation.
Only the key fingerprint is emitted — never the private key itself.
The hint is only emitted on first-run key generation.
"""
import sys as _sys # local import — only needed for this hint
import hashlib as _hashlib
import sys as _sys

# Compute fingerprint from public key (first 16 hex chars of SHA-256).
# Best-effort: never let fingerprint computation block identity setup.
fingerprint = "<unavailable>"
try:
key = load_pem_private_key(private_key_pem.encode(), password=None)
pub_raw = key.public_key().public_bytes(Encoding.Raw, PublicFormat.Raw)
fingerprint = _hashlib.sha256(pub_raw).hexdigest()[:16]
except Exception:
pass

escaped_pem = private_key_pem.replace("\n", "\\n")
hint = (
"\n"
" ╔══════════════════════════════════════════════════════════════╗\n"
" ║ New server identity generated — save key for persistence ║\n"
" ╚══════════════════════════════════════════════════════════════╝\n"
"\n"
f" Key fingerprint: {fingerprint}\n"
"\n"
" If this server runs in an ephemeral environment (containers,\n"
" serverless, CI) the identity will be lost on restart unless\n"
" you persist the private key.\n"
"\n"
" Add to your secrets manager / .env:\n"
"\n"
f' CAPISCIO_SERVER_PRIVATE_KEY_PEM="{escaped_pem}"\n'
"\n"
" The DID will be re-derived automatically on startup.\n"
" Set the CAPISCIO_SERVER_PRIVATE_KEY_PEM environment variable\n"
" to the contents of your key file. The DID will be re-derived\n"
" automatically on startup.\n"
)
print(hint, file=_sys.stderr, flush=True)
Comment on lines 111 to 127
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The behavior of the first-run key hint changed from printing an escaped private key to printing only a fingerprint. Add/adjust a test for _log_key_capture_hint() (patching sys.stderr) to assert the private key content is not present in the emitted hint and that a fingerprint line is included, so the security regression can’t reappear unnoticed.

Copilot uses AI. Check for mistakes.

Expand Down
1 change: 1 addition & 0 deletions capiscio_mcp/guard.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ async def evaluate_tool_access(
min_trust_level=effective_config.min_trust_level,
accept_level_zero=effective_config.accept_level_zero,
allowed_tools=effective_config.allowed_tools or [],
# TODO: wire require_badge once the proto field is added in capiscio-core
),
Comment on lines 285 to 289
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change introduces a new security-relevant config knob (require_badge) being sent to core, but there’s no test asserting the decorator/low-level API actually sets request.config.require_badge when @guard(require_badge=True) (or GuardConfig.require_badge) is used. Add a unit test (similar to the existing request-assertion tests) to prevent regressions in this wiring.

Copilot uses AI. Check for mistakes.
)

Expand Down
Loading