Critical fixes: - F-01: SatScope array form support (single pointer → slice with polymorphic JSON) - F-02: Add governance-intent@guildhouse.dev as 10th Shellstream extension - F-06: Replace os.Exit(1) stubs with go-plugin Serve() boilerplate in all cmd/ - F-13: Validate SatScope.ResourcePattern is non-empty High priority: - F-03: Add normative Accord policy syntax note to credential-governance.md §8.2 - F-04: Replace OID XXXXX placeholder with explicit PEN reference and IANA TODO - F-05: Document CredentialComposer hook mapping in spec and plugin-types.md - F-07/F-08: Commit CI pipeline (.github/workflows/ci.yaml) - F-09: Add hashicorp/go-plugin v1.6.3 to go.mod Medium priority: - F-10: Wire sample-ssh-cert-extensions.json fixture into shellstream tests - F-11: Cross-reference merkle proof depth limit (256 leaves) in governance spec - F-12: Add YAML format clarification headers to deploy configs - F-14: Expand README with project status, docs links, and quick-start Low priority: - F-15: Standardize "SSH SVID" → "SSH-SVID" terminology across docs - F-16: Add GovernanceEpochSeconds to PluginConfig and deploy configs - F-17: Add troubleshooting section to deployment.md, error handling to OIDC docs Global: Rename all extension keys from @guildhouse.io to @guildhouse.dev Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
19 KiB
Shellstream SSH Certificate Extensions
1. Abstract
Shellstream extensions are a set of SSH certificate extensions using the
@guildhouse.dev vendor suffix that carry structured governance metadata
within SSH certificates issued by SPIRE. These extensions encode
authorization scope, tenant context, governance ceremony references, and
merkle audit proofs, enabling SSH servers to make fine-grained
authorization decisions based on Guildhouse platform state without
requiring separate API calls at connection time.
2. Status
Draft Specification -- This document is a working draft and subject to change. It defines the normative behavior that conforming implementations MUST follow once finalized.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
3. Terminology
- Shellstream Extension
- An SSH certificate extension whose name uses the
@guildhouse.devvendor suffix and whose value carries Guildhouse governance metadata as defined in this specification. - SSH Certificate Extension
- A key-value string pair embedded in an OpenSSH certificate as defined in the OpenSSH PROTOCOL.certkeys document. Extensions are advisory and do not restrict the certificate holder; they convey additional information to the server.
- Vendor Suffix
- The
@domainportion of an extension name that designates the namespace owner, following the OpenSSH convention for avoiding collisions between independently defined extensions. - SAT (Substrate Attestation Token)
- A signed token issued by the Quartermaster credential service that binds a SPIFFE identity to a set of permitted operations on a specific registry type. The SAT is the primary authorization artifact in the Guildhouse platform.
- SatScope
- A structured object within a SAT that defines the registry type, permitted verbs, and resource pattern for which the token grants access.
- Governance Ceremony
- A structured approval workflow managed by the Bascule CeremonyService that authorizes privilege elevation. Ceremony types range from self-granted to quorum-based approval.
- Merkle Anchor
- A cryptographic commitment (merkle tree root hash) representing the complete governance state at a point in time. Each governance mutation is recorded as a leaf in the tree.
- MutationEnvelope
- A signed wrapper around a governance state change that is recorded as a leaf in the governance merkle tree.
- Accord Policy
- A tenant-scoped authorization policy evaluated by the Accord policy engine (backed by OPA) that defines roles, permissions, and ceremony requirements for a tenant.
- Trust Domain
- A SPIFFE trust domain (e.g.,
guildhouse.io) that defines the boundary within which SPIFFE identities and their associated attestations are valid.
4. Introduction
SSH certificates, as defined by OpenSSH's PROTOCOL.certkeys, support
arbitrary extensions as key-value string pairs. By convention, vendor-
specific extensions use the name@domain format to avoid collisions with
extensions defined by other parties or by future OpenSSH releases.
Shellstream extensions embed Guildhouse governance metadata into SSH certificates so that SSH servers can make authorization decisions based on platform state without issuing separate API calls. When SPIRE issues an SSH certificate for a Guildhouse workload or user, the certificate may include Shellstream extensions that describe:
- What the holder is authorized to do (SAT scope).
- Who the holder is acting as (tenant identity, roles).
- Why elevated access was granted (ceremony reference).
- When the governance state was captured (merkle root, epoch).
This decoupling of authorization metadata from runtime API lookups enables offline authorization decisions, improves latency, and creates a cryptographically verifiable audit trail linking SSH sessions to governance state.
5. Extension Format
5.1 Naming Convention
All Shellstream extensions use the @guildhouse.dev vendor suffix.
Extension names MUST be lowercase, hyphen-separated identifiers followed
by the suffix:
<name>@guildhouse.dev
where <name> matches the regular expression [a-z][a-z0-9]*(-[a-z0-9]+)*.
5.2 Value Encoding
Extension values are UTF-8 strings, as constrained by the SSH certificate extension format (RFC 4251 string type). All values MUST be valid UTF-8.
Binary data MUST be encoded as follows:
- Cryptographic hashes: lowercase hexadecimal encoding.
- Proofs and opaque binary payloads: base64 encoding using the standard alphabet with padding (RFC 4648 Section 4).
JSON-structured values MUST use compact encoding with no unnecessary whitespace (no spaces after colons or commas, no newlines or indentation).
6. Extension Registry
This section defines each Shellstream extension. Extensions marked REQUIRED MUST be present in every Shellstream-bearing SSH certificate. Extensions marked OPTIONAL MAY be omitted.
6.1 sat-scope@guildhouse.dev
Presence: REQUIRED when a SAT is associated with the certificate.
Value: A JSON object (or JSON array of objects when multiple scopes exist) with the following structure:
{"registry_type":"<type>","verbs":["<verb>",...],"resource_pattern":"<pattern>"}
Fields:
| Field | Type | Description |
|---|---|---|
registry_type |
string | Identifies the registry kind. Values include "oci", "helm", "git", "database". |
verbs |
array of string | Permitted operations. Examples: ["read","write"], ["push","pull"]. |
resource_pattern |
string | Glob pattern for resource matching. Examples: "tenant-a/*", "ns/repo:*". |
When a single SAT scope exists, the value MUST be a single JSON object. When multiple SAT scopes exist, the value MUST be a JSON array of scope objects. Implementations MUST accept both forms.
Example (single scope):
sat-scope@guildhouse.dev = {"registry_type":"oci","verbs":["push","pull"],"resource_pattern":"acme-corp/*"}
Example (multiple scopes):
sat-scope@guildhouse.dev = [{"registry_type":"oci","verbs":["pull"],"resource_pattern":"acme-corp/*"},{"registry_type":"helm","verbs":["read"],"resource_pattern":"charts/*"}]
6.2 sat-hash@guildhouse.dev
Presence: REQUIRED when a SAT is associated with the certificate.
Value: Lowercase hex-encoded SHA-256 hash of the raw SAT bytes.
The value MUST be exactly 64 hexadecimal characters ([0-9a-f]{64}).
This hash is used for audit correlation -- it links the SSH session to the
SAT without embedding the SAT itself in the certificate.
Example:
sat-hash@guildhouse.dev = a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2
6.3 tenant-id@guildhouse.dev
Presence: REQUIRED.
Value: UUID string formatted per RFC 4122, using lowercase hexadecimal digits with hyphens in the 8-4-4-4-12 grouping.
The value MUST match the regular expression:
[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}
This extension identifies the tenant context for the SSH session. All authorization decisions MUST be scoped to this tenant.
Example:
tenant-id@guildhouse.dev = 7b2a91c4-3f8e-4d12-b5a6-9c0e1d2f3a4b
6.4 roles@guildhouse.dev
Presence: REQUIRED.
Value: Comma-separated list of role names with no whitespace. Each
role name MUST match [a-z][a-z0-9_]*.
Roles are defined by the tenant's Accord policy and represent the set of permissions the certificate holder has been granted for this session.
Example:
roles@guildhouse.dev = analyst,viewer
roles@guildhouse.dev = administrator
6.5 ceremony-id@guildhouse.dev
Presence: OPTIONAL -- present only for elevated sessions that were authorized through a governance ceremony.
Value: UUID string formatted per RFC 4122, using the same format specified in Section 6.3.
This extension references the governance ceremony that authorized the privilege elevation for this session.
Example:
ceremony-id@guildhouse.dev = e4f5a6b7-8c9d-0e1f-2a3b-4c5d6e7f8a9b
6.6 ceremony-type@guildhouse.dev
Presence: OPTIONAL -- MUST be present when ceremony-id@guildhouse.dev
is present. MUST NOT be present when ceremony-id@guildhouse.dev is
absent.
Value: One of the following string literals, corresponding to the
CeremonyType enum from the Bascule CeremonyService:
| Value | Description |
|---|---|
self_grant |
The holder approved their own elevation (permitted by policy). |
single_approval |
A single designated approver authorized the elevation. |
quorum_approval |
A quorum of approvers collectively authorized the elevation. |
emergency_break_glass |
Emergency access granted outside normal approval flow, with enhanced audit. |
Example:
ceremony-type@guildhouse.dev = quorum_approval
6.7 merkle-root@guildhouse.dev
Presence: OPTIONAL.
Value: Lowercase hex-encoded SHA-256 merkle root hash. The value MUST
be exactly 64 hexadecimal characters ([0-9a-f]{64}).
This is the root of the governance merkle tree at the time the certificate was issued. It allows offline verification that the certificate was issued during a known governance state.
Example:
merkle-root@guildhouse.dev = 4d7a9c2e1f3b5a8d0e6c4b2a9f7e5d3c1b0a8f6e4d2c0b9a7f5e3d1c0b8a7f
6.8 merkle-proof@guildhouse.dev
Presence: OPTIONAL -- MUST be present only when
merkle-root@guildhouse.dev is present. MAY be omitted even when
merkle-root is present (the root alone is useful for epoch pinning).
Value: Base64-encoded (standard alphabet with padding, per RFC 4648 Section 4) serialized inclusion proof.
Proof wire format: The proof is a byte sequence consisting of concatenated 32-byte SHA-256 sibling hashes followed by a single final byte encoding the direction bits. Each bit in the direction byte indicates whether the corresponding sibling is on the left (0) or right (1) of the path, with the least-significant bit corresponding to the first sibling. This limits proofs to trees of depth 8 (256 leaves). For deeper trees, a multi-byte direction encoding will be specified in a future revision.
Example:
merkle-proof@guildhouse.dev = QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ehQ=
6.9 governance-epoch@guildhouse.dev
Presence: OPTIONAL.
Value: Decimal string representation of an unsigned 64-bit integer.
The value MUST match [1-9][0-9]*|0 (no leading zeros except for the
value zero itself).
The governance epoch is a monotonically increasing counter that increments with each governance state change. It enables stale-state detection without requiring full merkle verification: if a server's last-known epoch is greater than the certificate's epoch, the certificate was issued against stale governance state.
Example:
governance-epoch@guildhouse.dev = 42
6.10 governance-intent@guildhouse.dev
Presence: OPTIONAL -- present when the certificate was issued through the governance flow and has an associated MutationIntent.
Value: UUID string formatted per RFC 4122, using the same format specified in Section 6.3.
This extension references the MutationIntent that authorized the credential issuance. It enables audit correlation between the SSH certificate and the governance audit trail maintained by the GovernanceService.
Example:
governance-intent@guildhouse.dev = c8d9e0f1-2a3b-4c5d-6e7f-8a9b0c1d2e3f
7. Encoding Rules
The following encoding rules apply to all Shellstream extension values:
- All values MUST be valid UTF-8 (RFC 3629).
- Hash values (in
sat-hash,merkle-root) MUST be lowercase hexadecimal ([0-9a-f]+). Uppercase hex digits MUST be rejected. - Base64 values (in
merkle-proof) MUST use the standard alphabet (A-Z,a-z,0-9,+,/) with=padding as specified in RFC 4648 Section 4. URL-safe base64 MUST NOT be used. - JSON values (in
sat-scope) MUST use compact encoding: no spaces after:or,, no newlines, no indentation. Parsers SHOULD accept non-compact JSON for robustness but generators MUST produce compact JSON. - UUID values (in
tenant-id,ceremony-id,governance-intent) MUST be lowercase with hyphens in 8-4-4-4-12 format. Uppercase UUID strings MUST be rejected. - Numeric values (in
governance-epoch) MUST be decimal strings without leading zeros (except for the literal"0"). - Comma-separated values (in
roles) MUST NOT contain whitespace between items.
8. Validation Requirements
Implementations receiving SSH certificates with Shellstream extensions MUST validate the following constraints before using extension values for authorization or audit purposes:
8.1 Structural Validation
- Each extension value MUST conform to the format specified in its registry entry (Section 6).
- Values that fail format validation MUST be treated as absent. The server MAY log a warning but MUST NOT terminate the SSH connection solely due to a malformed extension value.
8.2 Co-occurrence Constraints
The following co-occurrence rules MUST be enforced:
| If present | Then MUST also be present |
|---|---|
sat-scope@guildhouse.dev |
sat-hash@guildhouse.dev |
sat-hash@guildhouse.dev |
sat-scope@guildhouse.dev |
ceremony-id@guildhouse.dev |
ceremony-type@guildhouse.dev |
ceremony-type@guildhouse.dev |
ceremony-id@guildhouse.dev |
merkle-proof@guildhouse.dev |
merkle-root@guildhouse.dev |
Note: merkle-root@guildhouse.dev MAY be present without
merkle-proof@guildhouse.dev (root-only pinning).
Note: governance-intent@guildhouse.dev has no co-occurrence
constraints. It MAY appear independently or alongside merkle
extensions.
8.3 Required Extensions
The following extensions MUST always be present in a Shellstream-bearing SSH certificate:
tenant-id@guildhouse.devroles@guildhouse.dev
A certificate that contains any @guildhouse.dev extension but is missing
either tenant-id or roles MUST be treated as invalid. The server
SHOULD reject the session.
8.4 Forward Compatibility
Unknown extensions bearing the @guildhouse.dev suffix MUST be ignored.
Implementations MUST NOT reject a certificate solely because it contains
unrecognized @guildhouse.dev extensions. This ensures that new
extensions can be introduced without breaking existing deployments.
9. Security Considerations
9.1 Extension Visibility
SSH certificate extensions are not encrypted. Any entity that can read the certificate (including intermediate proxies, logging infrastructure, and the SSH server) can read extension values. Accordingly:
- Implementations MUST NOT embed secrets, bearer tokens, private keys, or other sensitive credentials in extension values.
- The
sat-hashextension contains a SHA-256 hash of the SAT, not the SAT itself. The SAT MUST be validated through a separate, secure channel.
9.2 Authorization Boundary
- Merkle proofs provide auditability, not authorization. A valid merkle inclusion proof means the credential issuance event was recorded in the governance tree; it does not assert that the event was correct or authorized. Authorization decisions MUST be based on SAT scope and Accord policy, not on merkle proof validity alone.
- The
governance-epochextension enables stale-state detection but MUST NOT be used as the sole authorization signal. A stale epoch indicates that governance state may have changed since issuance; the server SHOULD require re-attestation in this case.
9.3 Parsing Safety
Extension values are strings, not executable code. Implementations MUST NOT evaluate extension values as code, shell commands, SQL, or any other executable format. JSON values MUST be parsed with a standard JSON parser; hand-rolled parsers are strongly discouraged.
9.4 Size Constraints
The total size of all Shellstream extension values (keys and values
combined) SHOULD NOT exceed 4096 bytes (4 KB). Exceeding this limit may
cause compatibility issues with certain SSH implementations that impose
limits on certificate size. Implementations MAY reject certificates whose
total @guildhouse.dev extension payload exceeds this threshold.
9.5 Replay and Freshness
SSH certificates have their own validity period (valid-after to
valid-before). Shellstream extensions inherit this validity window.
Implementations SHOULD treat extension values as valid only within the
certificate's validity period. The governance-epoch extension provides
an additional freshness signal beyond the certificate's temporal validity.
10. Compatibility
10.1 SSH Protocol Compatibility
All Shellstream extensions use the standard SSH certificate extension format: a string key mapping to a string value. This is fully compatible with OpenSSH and any SSH implementation that conforms to the PROTOCOL.certkeys specification.
10.2 Transparent Degradation
SSH servers that do not understand Shellstream extensions will ignore them entirely. This is standard OpenSSH behavior for unrecognized extensions. Certificates bearing Shellstream extensions remain valid SSH certificates and can be used for authentication on non-Guildhouse SSH servers (though governance-based authorization will not be available).
10.3 Additive Evolution
Extensions are additive. New @guildhouse.dev extensions can be
introduced in future revisions of this specification without breaking
existing parsers, per the forward compatibility rule in Section 8.4.
10.4 Version Negotiation
Version negotiation is implicit via extension presence. There is no explicit version field. If a future revision deprecates or changes the semantics of an extension, it MUST do so under a new extension name. The original extension name retains its original semantics indefinitely.
11. References
-
OpenSSH PROTOCOL.certkeys -- OpenSSH certificate format and extension mechanism. https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.certkeys
-
RFC 2119 -- Key words for use in RFCs to Indicate Requirement Levels. Bradner, S. March 1997. https://datatracker.ietf.org/doc/html/rfc2119
-
RFC 3629 -- UTF-8, a transformation format of ISO 10646. Yergeau, F. November 2003. https://datatracker.ietf.org/doc/html/rfc3629
-
RFC 4122 -- A Universally Unique IDentifier (UUID) URN Namespace. Leach, P., Mealling, M., and R. Salz. July 2005. https://datatracker.ietf.org/doc/html/rfc4122
-
RFC 4251 -- The Secure Shell (SSH) Protocol Architecture. Ylonen, T. and C. Lonvick, Ed. January 2006. https://datatracker.ietf.org/doc/html/rfc4251
-
RFC 4648 -- The Base16, Base32, and Base64 Data Encodings. Josefsson, S. October 2006. https://datatracker.ietf.org/doc/html/rfc4648
-
RFC 8785 -- JSON Canonicalization Scheme (JCS). Rundgren, B., Jordan, B., and S. Erdtman. June 2020. https://datatracker.ietf.org/doc/html/rfc8785