Skip to content

fix(security): remove private key from log output#52

Merged
beonde merged 2 commits intomainfrom
fix/ga-key-logging
Apr 17, 2026
Merged

fix(security): remove private key from log output#52
beonde merged 2 commits intomainfrom
fix/ga-key-logging

Conversation

@beonde
Copy link
Copy Markdown
Member

@beonde beonde commented Apr 17, 2026

GA-006: Remove private key from logger

Priority: P0 — private key leakage

Problem

_log_agent_key_capture_hint() in connect.py was logging the full private JWK (including the d parameter) via logger.warning(). If logging is configured to write to a file, stdout capture, or any log aggregation service, the agent's private key would be exposed.

Fix

  • Replaced logger.warning() call that included json.dumps(private_jwk) with a print() to stderr
  • The hint now shows only the key ID (kid) and directs users to:
    • capiscio identity export --format env (CLI)
    • ~/.capiscio/identities/<agent>/private.jwk (file path)
  • No key material appears in any log call

Verification

  • grep -rn "compact_json\|private.*log\|jwk.*warning" capiscio_sdk/ returns no source code matches (only proto binary/cache files)

GA-006: Replace logger.warning() that included full private JWK with
a stderr hint directing users to 'capiscio identity export' or the
identity directory. Key material is never logged.
Copilot AI review requested due to automatic review settings April 17, 2026 04:33
@github-actions
Copy link
Copy Markdown

✅ Documentation validation passed!

Unified docs will be deployed from capiscio-docs repo.

@github-actions
Copy link
Copy Markdown

✅ All checks passed! Ready for review.

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 17, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@github-actions
Copy link
Copy Markdown

✅ SDK server contract tests passed (test_server_integration.py). Cross-product scenarios are validated in capiscio-e2e-tests.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Removes private key material from CapiscIO.connect()’s user-facing “capture hint” output to prevent leaking private JWKs (including d) via logging sinks.

Changes:

  • Replaces the previous logger-based hint that included the full private JWK with a stderr hint that only shows kid and guidance for exporting/copying the key.
  • Adds an explicit SECURITY note in the hint helper to discourage logging key material.

Comment thread capiscio_sdk/connect.py Outdated
Comment on lines 57 to 87
@@ -66,11 +72,18 @@ def _log_agent_key_capture_hint(agent_id: str, private_jwk: dict) -> None:
" 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"
f" Key ID: {kid}\n"
"\n"
" To export the private key for your secrets manager:\n"
"\n"
" capiscio identity export --format env\n"
"\n"
" Or copy the key file from:\n"
"\n"
" CAPISCIO_AGENT_PRIVATE_KEY_JWK='" + compact_json + "'\n"
" ~/.capiscio/identities/<agent>/private.jwk\n"
"\n"
" The DID will be recovered automatically from the JWK on startup.\n"
" The DID will be recovered automatically from the JWK on startup.\n",
file=sys.stderr,
)
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

Security regression prevention: there’s no unit test asserting that the capture hint output never includes private key material (e.g., the JWK d value) and doesn’t emit the full JWK. Since this is a P0 leakage fix, consider adding a test that captures stderr/logging and verifies only non-sensitive identifiers (e.g., kid) appear.

Copilot uses AI. Check for mistakes.
Comment thread capiscio_sdk/connect.py
Comment on lines +81 to 84
" Or copy the key file from:\n"
"\n"
" CAPISCIO_AGENT_PRIVATE_KEY_JWK='" + compact_json + "'\n"
" ~/.capiscio/identities/<agent>/private.jwk\n"
"\n"
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

The hint message points users to ~/.capiscio/identities/<agent>/private.jwk, but this module’s default storage location is DEFAULT_KEYS_DIR / agent_id (i.e., ~/.capiscio/keys/<agent_id>/private.jwk) or a user-provided keys_dir. This will send users to a non-existent/wrong path—consider printing the actual on-disk path (or at least the correct default path) and using the existing agent_id parameter to fill in the directory name.

Copilot uses AI. Check for mistakes.
Comment thread capiscio_sdk/connect.py
Comment on lines 65 to 87
@@ -66,11 +72,18 @@ def _log_agent_key_capture_hint(agent_id: str, private_jwk: dict) -> None:
" 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"
f" Key ID: {kid}\n"
"\n"
" To export the private key for your secrets manager:\n"
"\n"
" capiscio identity export --format env\n"
"\n"
" Or copy the key file from:\n"
"\n"
" CAPISCIO_AGENT_PRIVATE_KEY_JWK='" + compact_json + "'\n"
" ~/.capiscio/identities/<agent>/private.jwk\n"
"\n"
" The DID will be recovered automatically from the JWK on startup.\n"
" The DID will be recovered automatically from the JWK on startup.\n",
file=sys.stderr,
)
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

This helper now prints unconditionally to sys.stderr during CapiscIO.connect() identity initialization. Unlike the rest of this module (which uses logger.*), this output can’t be suppressed/redirected via logging configuration and may pollute stderr in library use (e.g., ASGI apps, tests, serverless logs). Consider switching back to logger.warning/info with sanitized content (no key material) or gating the hint behind an explicit opt-in/verbosity flag.

Copilot uses AI. Check for mistakes.
Comment thread capiscio_sdk/connect.py Outdated
Comment on lines +57 to +65
def _log_agent_key_capture_hint(agent_id: str, private_jwk: dict) -> None:
"""Log a one-time hint telling the user how to persist key material."""
compact_json = json.dumps(private_jwk, separators=(",", ":"))
logger.warning(
"""Log a one-time hint telling the user how to persist key material.

SECURITY: Never log private key material. Instead, direct users to
export the key using the CLI or check the identity directory.
"""
# Derive the key ID (kid) from the public portion for identification only
kid = private_jwk.get("kid", "unknown")
print(
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

Naming/docs mismatch: _log_agent_key_capture_hint (and its docstring) still says it "Log[s]" a hint, but the implementation uses print(). Also the inline comment says "Derive" the kid from the public portion, but the code just reads private_jwk.get("kid"). Consider renaming/updating the docstring/comment to reflect what the function actually does, and/or explicitly label kid as the DID in this SDK (since elsewhere kid is treated as the DID).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment thread capiscio_sdk/connect.py Outdated
Comment on lines +63 to +65
# Derive the key ID (kid) from the public portion for identification only
kid = private_jwk.get("kid", "unknown")
print(
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

The inline comment says the kid is derived “from the public portion”, but the code reads it directly from private_jwk. This is functionally fine, but the comment is inaccurate; either derive kid from _public_jwk_from_private(private_jwk) or reword the comment to match the implementation.

Copilot uses AI. Check for mistakes.
- Rename _log_agent_key_capture_hint → _print_agent_key_capture_hint
  to match actual behavior (print to stderr, not logger)
- Fix key path in hint: ~/.capiscio/keys/{agent_id}/private.jwk
  (was incorrectly showing ~/.capiscio/identities/<agent>/...)
- Add TestPrintAgentKeyCaptureHint regression tests:
  - Verify no private key material (d, x) in output
  - Verify correct key file path in output
@github-actions
Copy link
Copy Markdown

✅ Documentation validation passed!

Unified docs will be deployed from capiscio-docs repo.

@github-actions
Copy link
Copy Markdown

✅ All checks passed! Ready for review.

@github-actions
Copy link
Copy Markdown

✅ SDK server contract tests passed (test_server_integration.py). Cross-product scenarios are validated in capiscio-e2e-tests.

@beonde beonde merged commit 3a42731 into main Apr 17, 2026
13 checks passed
@beonde beonde deleted the fix/ga-key-logging branch April 17, 2026 04:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants