Open-source SSH proxy with identity-aware shell (Rust)
Find a file
Tyler J King 31b76f2817
Some checks failed
CI / dco (push) Has been cancelled
CI / build (push) Has been cancelled
chore(workspace): fix cross-workspace path deps + document naming violation
Three path deps in this workspace's Cargo.toml (substrate-proto,
hfl-types, substrate-hfl) pointed at `../substrate-project/substrate/
crates/...`, which resolves correctly only when bascule-oss sits as a
sibling to a substrate-project-named directory at the parent level.
That's not the current layout: bascule-oss is now nested INSIDE the
substrate-project umbrella, as a working tree alongside substrate/
and other Guildhouse repos. The paths don't resolve in the current
nested layout; bascule-oss has been locally unbuildable since the
reorg (existing Cargo.lock survived only because nobody ran
`cargo update` or `cargo generate-lockfile` to force re-resolution).

Fix: update the three paths to `../substrate/crates/...`, matching
the sibling-checkout layout. Comment updated to name the current
convention explicitly rather than referencing an old side-by-side
CI claim that no longer matches CI's actual layout either (see
below on CI state).

What this fix does NOT change

- **The architectural rule in CLAUDE.md still stands** —
  "zero substrate/chronicle/gsap dependencies." This fix is a path
  correction, not a policy change.
- **The rule is already broken in both spirit and letter.** bascule-
  core depends on substrate-proto unconditionally; substrate-proto
  depends on substrate-common; both names are substrate-prefixed.
  The CI contamination check at .github/workflows/ci.yml:81-88
  greps Cargo.lock for "substrate|chronicle|gsap|hfl|metakernel"
  and fails on any match — current Cargo.lock contains substrate-
  common, substrate-hfl, substrate-proto, hfl-types, so CI would
  fail on a fresh run regardless of this patch.
- **This fix is a LOCAL buildability unblock only.** It makes
  `cargo check -p bascule-core` pass in the current layout. It does
  NOT fix CI, which is independently broken for the reasons above.

CI state

The bascule-oss CI workflow uses `actions/checkout@v4` with no
coordinating checkout of substrate-project or its substrate/
subrepo. Regardless of whether bascule-oss's paths use
`../substrate/...` or `../substrate-project/substrate/...`, CI
currently has nothing to resolve them against. And the
contamination check would flag the resulting Cargo.lock even if
the paths did resolve. CI is already red (or will be, on next
trigger).

Re-enabling CI against bascule-oss requires separate coordination
work: either adding a substrate-project checkout step to the
workflow, or renaming substrate-proto (see below), or amending
the contamination check to allow specific neutral-protocol crates
by name. That work is out of scope for this patch.

Architectural open item: substrate-proto naming violation

Documented in the new NOTES-SUBSTRATE-PROTO-NAMING-VIOLATION.md.
Summary: substrate-proto's CONTENT is a legitimate neutral
protocol contract (proto-generated SAT types bascule-core needs
to compose session attestations). The NAMING is substrate-
prefixed, which both violates CLAUDE.md's string-match rule and
creates an optical problem for CNCF sandbox positioning —
sandbox committees read dependency names more than content when
evaluating whether a project is a generic open-source substrate
or a specific company's product with open-source framing.

Proposed resolution (post-v1): rename substrate-proto → sat-proto
(or attestation-proto), move the consumed subset of substrate-
common's public surface out of substrate-common into the neutrally-
named crate, update bascule-core and any other consumers, regenerate
Cargo.lock, confirm the contamination check passes.

This is NOT v1-blocking. This IS CNCF-sandbox-positioning work —
worth doing in coordination with sandbox application prep so the
naming story and the institutional positioning story land
together.

Why we're shipping this narrower fix now

Phase 5.5c-c of the substrate foundation work (the adapter crate
bridging bascule-core session lifecycle to bascule-revocation-
ingress SessionLookup) requires depending on bascule-core as a
cross-workspace path dep. Without this path fix, substrate-side
crates can't pull bascule-core locally, blocking 5.5c-c
implementation. The path fix costs one line per dep and
introduces no new coupling; the deeper rename is several-repo
coordination that would delay 5.5c-c unnecessarily.

Fix-what's-blocking, document-what's-underlying, keep moving.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Tyler J King <tking@guildhouse.dev>
2026-04-20 18:14:28 -04:00
.github/workflows docs: DCO, NOTICE, and governance framework 2026-04-05 11:13:20 -04:00
charts/bascule docs: comprehensive documentation + developer experience polish 2026-04-05 10:53:08 -04:00
config docs: update all documentation for management API + dashboard 2026-04-05 17:17:18 -04:00
crates chore: silence bascule-shell config dead-code warnings 2026-04-08 13:49:32 -04:00
docs docs: update all documentation for management API + dashboard 2026-04-05 17:17:18 -04:00
images feat: structured logging, tracing spans, comprehensive documentation 2026-04-04 23:45:03 -04:00
.editorconfig docs: comprehensive documentation + developer experience polish 2026-04-05 10:53:08 -04:00
.gitignore feat: Bascule — identity-aware SSH proxy 2026-04-04 22:25:33 -04:00
Cargo.lock feat(hfl): bascule-core SAT compose routes through HFL when available 2026-04-08 10:41:09 -04:00
Cargo.toml chore(workspace): fix cross-workspace path deps + document naming violation 2026-04-20 18:14:28 -04:00
CLAUDE.md docs: update all documentation for management API + dashboard 2026-04-05 17:17:18 -04:00
CONTRIBUTING.md docs: update all documentation for management API + dashboard 2026-04-05 17:17:18 -04:00
DCO docs: DCO, NOTICE, and governance framework 2026-04-05 11:13:20 -04:00
docker-compose.yml feat: bascule-shell — identity-aware shell with TPM attestation 2026-04-05 09:47:46 -04:00
Dockerfile feat: bascule-shell — identity-aware shell with TPM attestation 2026-04-05 09:47:46 -04:00
GOVERNANCE.md docs: DCO, NOTICE, and governance framework 2026-04-05 11:13:20 -04:00
LICENSE feat: bascule-shell — identity-aware shell with TPM attestation 2026-04-05 09:47:46 -04:00
Makefile docs: comprehensive documentation + developer experience polish 2026-04-05 10:53:08 -04:00
NOTES-SUBSTRATE-PROTO-NAMING-VIOLATION.md chore(workspace): fix cross-workspace path deps + document naming violation 2026-04-20 18:14:28 -04:00
NOTICE docs: DCO, NOTICE, and governance framework 2026-04-05 11:13:20 -04:00
README.md docs: update all documentation for management API + dashboard 2026-04-05 17:17:18 -04:00
rustfmt.toml docs: comprehensive documentation + developer experience polish 2026-04-05 10:53:08 -04:00

Bascule

Identity-aware SSH proxy for modern infrastructure.

Bascule authenticates operators via SSH keys or AI agent tokens, then connects them to a local shell, remote host, or ephemeral container. No agents to install. No control plane. One binary.

Quick Start

cargo build --release -p bascule-server
./target/release/bascule --config config/bascule.example.toml
# In another terminal:
ssh -p 2222 localhost

See docs/quickstart.md for Docker, Helm, and container mode.

Features

Session Backends

Mode Config Description
Local PTY (default) Spawn a local shell process
Remote Proxy [proxy] Forward to a remote SSH host
Container [container] Ephemeral container per session (Docker/Podman/nerdctl)
Kubernetes [k8s] Shared jumphost with shell sidecar (config ready, runtime coming)

Authentication

  • SSH Keys — standard OpenSSH authorized_keys files
  • Accept All — development only, accepts any key
  • Entra Agent ID — Microsoft AI agent identity (--features agent-id)
  • SPIFFE/SPIRE — workload identity (config ready, runtime coming)

Security

  • Session limiting (semaphore-based max_sessions)
  • Container hardening (--cap-drop ALL, --security-opt no-new-privileges)
  • Container config validation (injection prevention)
  • Read-only rootfs option
  • NetworkPolicy for Kubernetes deployments

Management API + Dashboard

Built-in HTTP management API (port 9090, --features dashboard):

  • GET /api/sessions — active sessions with auth/backend/TPM status
  • GET /api/stats — aggregate analytics (auth breakdown, peak concurrent, TPM %)
  • GET /api/health — server health and version
  • WASM dashboard at /dashboard/ (coming soon)

Observability

  • Structured JSON logging (BASCULE_LOG_FORMAT=json)
  • Tracing spans on auth, session lifecycle, exec requests
  • Management API for real-time session monitoring

Client: bascule-shell

Identity-aware login shell with TPM attestation:

./target/release/bascule-shell --info
╔═══════════════════════════════════════════════════════╗
║  Bascule Shell v0.1.0                                 ║
║  Principal:  tking                                     ║
║  Method:     ssh-key                                   ║
║  TPM:        available (6 PCRs verified)               ║
║  Platform:   sha256:e9b95f002f54222d...                ║
╚═══════════════════════════════════════════════════════╝

See docs/bascule-shell.md.

Comparison

Bascule Teleport Boundary
License Apache 2.0 AGPL / Commercial MPL / Commercial
Agents required No Yes Yes
Control plane No Required Required
Container sessions Yes No No
AI Agent Identity Yes (Entra Agent ID) No No
Binary size ~7MB ~150MB ~100MB
Built-in dashboard Yes (port 9090) Yes No

See docs/comparison.md.

Deployment

  • Standalone: cargo build --release -p bascule-server
  • Docker: docker build -t bascule .
  • Kubernetes: helm install bascule charts/bascule/ — see docs/kubernetes.md

Extending Bascule

Implement SessionHandler to add custom policy:

use bascule_core::hooks::{SessionHandler, SessionInfo};

struct AuditHandler;

#[async_trait]
impl SessionHandler for AuditHandler {
    async fn on_session_start(&self, s: &SessionInfo) -> anyhow::Result<()> {
        println!("{} connected from {}", s.principal, s.source_ip);
        Ok(())
    }
}

See docs/architecture.md.

Governance

Bascule is maintained by Guildhouse LLC. Contributions are accepted under the DCO — you retain copyright to your contributions.

The SessionHandler and AuthProvider traits are public APIs. Implementations are the intellectual property of their authors. See GOVERNANCE.md.

Roadmap

Not yet implemented:

  • OIDC authentication (Keycloak, Entra, Okta)
  • K8s API exec backend runtime
  • SPIFFE/SPIRE auth runtime
  • OpenTelemetry OTLP exporter
  • Prometheus metrics endpoint
  • Session recording
  • Per-session Pod isolation

Documentation

License

Apache 2.0