Bascule now supports two session modes:
Local — spawns a PTY on this machine (default, existing)
Proxy — forwards the session to a target SSH host (NEW)
Proxy mode:
SSH client ←→ bascule (auth + hooks) ←→ target SSH host
Authenticates client via configured auth provider
Connects to upstream SSH host via russh client
Bridges I/O between client and upstream channels
PTY, shell, and exec requests forwarded to target
Exit status propagated back to client
Config:
[proxy]
target_host = "192.168.1.100"
target_port = 22
target_user = "deploy" # optional, defaults to principal
target_key_path = "/etc/bascule/target_key"
accept_target_host_key = false # dev only
SessionHandler hooks fire in both modes:
on_session_start, on_exec, on_session_end
Custom handlers can enforce policy regardless of mode
New file: proxy.rs (152 lines)
UpstreamHandler — minimal russh client handler
connect_upstream — connects + authenticates to target
bridge_upstream_to_client — bidirectional I/O bridge
Binary: 6.3MB, zero substrate deps.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New crate: bascule-auth-agent-id
Microsoft Entra Agent ID authentication for AI agents
Validates OAuth tokens against Entra JWKS (60min cache)
Extracts agent metadata: type, blueprint, sponsor, scopes
Detects on-behalf-of (delegated) agents
Token-as-password pattern for SSH auth
Cleanup:
Removed all governance-specific references from comments
SessionHandler trait is the only extension point
Zero substrate/chronicle/gsap dependencies
Config example uses neutral terminology
Config:
[auth.agent_id] section for Entra configuration
tenant_id, audiences, multi_tenant fields
3 crates: bascule-core, bascule-server, bascule-auth-agent-id
938 lines total, 5.6MB binary, 0 substrate deps.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>