Skip to content
Shortspan

Search documentation

Find a page by title, section, or summary.

How it works

The path a credential takes from the encrypted store to a process on your laptop, and everything that is recorded along the way.

The pieces

Three things, and one of them is optional.

The CLI

A single Go binary on your machine. It holds an API token in your OS keyring and nothing else - no cached secrets, no key material, no credential files.

The API

A Laravel application holding the encrypted store, the permission model, and the audit log. Every credential request goes through it.

The console

A React interface on the same application, used to register resources, approve access, and read the audit log. You can run a whole team without opening it after setup.

text
  your laptop                       the platform
  ───────────                       ────────────

  shortspan ssh prod-web-1
        |
        |  POST /api/v1/ssh-targets/{id}/credential
        |  Authorization: Bearer <token>
        |----------------------------->  auth     is the token valid?
        |                                active   is the account suspended?
        |                                role     may this user reach this project?
        |                                plan     is this feature in their tier?
        |                                grant    is a temporary grant required?
        |                                   |
        |                                decrypt  unwrap MEK, unwrap DEK,
        |                                   |     decrypt the credential
        |                                audit    write the access record
        |  <---------------------------------+
        |  proxy token (single use, short lived)
        |
        |  POST /api/v1/ssh/exchange-credential
        |----------------------------->  redeem the proxy token
        |  <-----------------------------  the credential itself
        |
   held in memory, zeroed after the
   SSH handshake. never written to disk.
The two-step exchange means the credential is not in the response to the request that was authorized - a logged or cached response body does not contain it.

What every request passes through

A credential request is checked several times before anything is decrypted. Each check is a separate middleware, so none of them can be skipped by a controller that forgets to call it.

GateQuestionFailure

secure.transport

Did this arrive over TLS?

426 Upgrade Required

cli.version

Is this CLI version withdrawn?

426 with an upgrade instruction

auth:sanctum

Is the bearer token valid, unexpired, and scoped for this?

401

account.active

Is the account suspended or under review?

403 account_suspended

onboarded

Does this account have a workspace yet?

Redirect or 409 with a setup instruction

project.permission

Does this user hold the required role on this project?

403

plan.feature / plan.limit

Does the workspace's tier include this?

402 with an upgrade path

throttle:api-reveal

Has this token asked for too many secrets too fast?

429

How the store is encrypted

Envelope encryption, three layers deep. Each layer exists so that compromising one thing does not compromise everything.

  1. 1

    Every secret has its own data key

    A random 32-byte DEK encrypts the value with libsodium's secretbox (XSalsa20-Poly1305). The DEK is stored alongside the ciphertext, itself encrypted.

  2. 2

    Every workspace has its own master key

    The MEK wraps that workspace's data keys. Two workspaces on the same deployment share no key material, so a flaw that exposes one workspace's MEK exposes nothing about any other.

  3. 3

    One root key wraps every master key

    The root key lives in SHORTSPAN_MASTER_KEY, deliberately separate from APP_KEY. Rotating the root key rewrites one row per workspace, not one row per secret.

What is recorded

Every action that touches a credential writes an audit entry before the response is returned. The entry names the actor, the resource, the action, the source address, and the CLI version, and it is written whether the action succeeded or was refused - a denied request is often the more interesting record.

What is deliberately absent: the secret value, the SSH key, the database password, the proxy token, and the contents of anything injected into a process. The audit log records that a credential was released, never which credential it was.

CLI commands additionally report their own start and completion, so a session that was opened and a session that actually connected are distinguishable. See Audit log.

Why nothing lands on disk

The CLI writes exactly two things: a YAML config file recording which organization and project you are working in, and - through your operating system keyring, not a file - your API token.

Credentials are held in process memory and zeroed after use. shortspan aws spawns a subshell with credentials in its environment rather than writing ~/.aws/credentials; when the subshell exits they are gone. shortspan run passes a secret group into a child process's environment and never renders it to the terminal. shortspan ssh builds the SSH auth method from an in-memory string.

Something here wrong or missing? Tell us.