Stash is a client and library for attestation storage, providing tools for managing cryptographic attestations in software supply chain security workflows.
- Batch Attestation Management: Upload, retrieve, list, and delete attestations
- Digital Signature Verification: Verify attestation signatures against stored public keys
- Advanced Filtering: Query attestations by predicate type, subject characteristics, signer identity, and more
- Content-based Addressing: Reference attestations by ID, content hash, or predicate hash
- Public Key Management: Store and manage public keys for attestation verification
- Flexible Output: JSON or human-readable formatting
# Clone the repository
git clone https://github.com/carabiner-dev/stash.git
cd stash
# Build the CLI
make build
# Install to $GOPATH/bin
make installgo install github.com/carabiner-dev/stash/cmd/stash@latestThe stash CLI provides a command-line interface for interacting with a Stash server.
Configure the CLI using environment variables, flags, or configuration files:
- Server URL: Set via
STASH_URLenvironment variable or--urlflag (default:http://localhost:8080) - Authentication Token: Set via
STASH_TOKENenvironment variable,--tokenflag, or store in~/.stash/token
Configuration precedence: CLI flags > environment variables > config files
Push one or more attestation files to the server:
# Push from files
stash push attestation1.json attestation2.json
# Push from stdin
cat attestation.json | stash push --stdin
# Push multiple files with pattern
stash push attestations/*.jsonSupports batch uploads of up to 100 attestations at once.
Retrieve attestations by ID or hash:
# Get full attestation with metadata
stash get <attestation-id>
# Get only raw attestation JSON
stash get <attestation-id> --raw
# Get only predicate JSON
stash get <attestation-id> --predicate
# Query by content hash
stash get sha256:a1b2c3d4...
# Output as JSON
stash get <attestation-id> --jsonQuery attestations with filtering and pagination:
# List all attestations
stash list
# Filter by predicate type
stash list --predicate-type "https://slsa.dev/provenance/v1"
# Filter by subject name (exact match)
stash list --subject.name "my-artifact"
# Filter by subject name (regex)
stash list --subject-regex.name "my-.*"
# Filter by subject URI
stash list --subject-regex.uri "pkg:.*"
# Filter by subject digest
stash list --subject.digest.algorithm sha256 --subject.digest.value a1b2c3...
# Filter by signer identity
stash list --signer "user@example.com"
# Filter by validation status
stash list --signed --validated
# Pagination
stash list --limit 50 --cursor <next-cursor>
# JSON output
stash list --jsonRemove attestations by ID or hash:
stash delete <attestation-id>
stash delete sha256:a1b2c3d4...Verify attestation signatures against stored public keys:
stash verify <attestation-id>Manage public keys for attestation verification:
# Upload a public key
stash publickey upload key.pem
# List public keys
stash publickey list
stash publickey list --json
# Get specific public key
stash publickey get <key-id>
# Delete public key
stash publickey delete <key-id>Display version information:
stash versionThe pkg/client package provides a Go client library for programmatic interaction with a Stash server.
go get github.com/carabiner-dev/stash/pkg/clientimport (
"github.com/carabiner-dev/stash/pkg/client"
"github.com/carabiner-dev/stash/pkg/client/config"
)
// Create configuration
cfg := &config.Config{
BaseURL: "https://stash.example.com",
Token: "your-auth-token",
}
// Initialize client
stashClient := client.NewClient(cfg)attestations := []map[string]interface{}{
{
"_type": "https://in-toto.io/Statement/v1",
"subject": []map[string]interface{}{
{
"name": "my-artifact",
"digest": map[string]string{
"sha256": "a1b2c3d4...",
},
},
},
"predicateType": "https://slsa.dev/provenance/v1",
"predicate": map[string]interface{}{
"buildDefinition": map[string]interface{}{
"buildType": "https://example.com/build/v1",
},
},
},
}
ctx := context.Background()
response, err := stashClient.UploadAttestations(ctx, attestations)
if err != nil {
log.Fatalf("Upload failed: %v", err)
}
for _, att := range response.Attestations {
fmt.Printf("Uploaded: %s\n", att.ID)
}// Get full attestation
attestation, err := stashClient.GetAttestation(ctx, "attestation-id")
// Get raw attestation JSON only
rawJSON, err := stashClient.GetAttestationRaw(ctx, "attestation-id")
// Get predicate only
predicate, err := stashClient.GetAttestationPredicate(ctx, "attestation-id")import "github.com/carabiner-dev/stash/pkg/client/filters"
// Create filter options
opts := &filters.ListOptions{
PredicateType: "https://slsa.dev/provenance/v1",
Subject: &filters.SubjectFilter{
Name: "my-artifact",
},
Signed: true,
Validated: true,
Limit: 50,
}
response, err := stashClient.ListAttestations(ctx, opts)
if err != nil {
log.Fatalf("List failed: %v", err)
}
for _, att := range response.Attestations {
fmt.Printf("ID: %s, Type: %s\n", att.ID, att.PredicateType)
}
// Handle pagination
if response.NextCursor != "" {
opts.Cursor = response.NextCursor
// Fetch next page...
}// Filter with regex patterns
opts := &filters.ListOptions{
SubjectRegex: &filters.SubjectRegexFilter{
Name: "my-.*",
URI: "pkg:.*",
},
Signer: "user@example.com",
}
// Filter by subject digest
opts := &filters.ListOptions{
Subject: &filters.SubjectFilter{
Digest: &filters.DigestFilter{
Algorithm: "sha256",
Value: "a1b2c3d4...",
},
},
}err := stashClient.DeleteAttestation(ctx, "attestation-id")
if err != nil {
log.Fatalf("Delete failed: %v", err)
}// Upload public key
keyPEM := []byte(`-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----`)
publicKey, err := stashClient.UploadPublicKey(ctx, keyPEM)
if err != nil {
log.Fatalf("Upload key failed: %v", err)
}
// List public keys
keys, err := stashClient.ListPublicKeys(ctx)
// Get specific key
key, err := stashClient.GetPublicKey(ctx, "key-id")
// Delete key
err = stashClient.DeletePublicKey(ctx, "key-id")The Stash client implements the Carabiner Attestation Framework repository interfaces, allowing it to be used as a backend for attestation storage and retrieval in the framework.
Implemented Interfaces:
attestation.Fetcher- Retrieve attestationsattestation.FetcherBySubject- Filter by subjectattestation.FetcherByPredicateType- Filter by predicate typeattestation.FetcherByPredicateTypeAndSubject- Combined filteringattestation.Storer- Store attestations
import (
"github.com/carabiner-dev/attestation"
"github.com/carabiner-dev/stash/pkg/client"
)
// Create a Stash client
stashClient := client.NewClient("https://stash.example.com", "your-token")
// Wrap it with the repository client
repo := client.NewRepositoryClient(stashClient, "my-org", "default")
// Use attestation framework interfaces
ctx := context.Background()
// Fetch all attestations
opts := attestation.FetchOptions{Limit: 50}
envelopes, err := repo.Fetch(ctx, opts)
// Fetch by predicate type
predicateTypes := []attestation.PredicateType{
"https://slsa.dev/provenance/v1",
}
envelopes, err = repo.FetchByPredicateType(ctx, opts, predicateTypes)
// Fetch by subject
var subjects []attestation.Subject // Constructed from parsed attestations
envelopes, err = repo.FetchBySubject(ctx, opts, subjects)
// Store attestations
var envelopesToStore []attestation.Envelope
storeOpts := attestation.StoreOptions{}
err = repo.Store(ctx, storeOpts, envelopesToStore)Use Cases:
- Integrate Stash with attestation processing pipelines
- Use Stash as a backend for the Carabiner verification framework
- Build attestation tools that work with multiple repository types
- Leverage the attestation framework's query and filtering capabilities
See pkg/client/examples_test.go for more examples.
- Batch Operations: Upload up to 100 attestations per request
- Flexible Filtering: Support for exact matches, regex patterns, and compound queries
- Pagination Support: Cursor-based pagination with configurable limits (up to 1000)
- Error Handling: Structured error responses with HTTP status codes
- Authentication: Bearer token authentication
- Timeout Management: 30-second request timeout
- JSON Handling: Automatic marshaling/unmarshaling of attestation data
Attestations include the following metadata:
- ID: Unique identifier
- OrganizationID: Organization that owns the attestation
- ContentHash: SHA-256 hash of the full attestation content
- PredicateHash: SHA-256 hash of the predicate portion
- PredicateType: Type URI of the predicate (e.g., SLSA provenance)
- Signed: Whether the attestation has a signature
- Validated: Whether the signature has been validated
- SignerIdentities: List of signer identity strings
- Subjects: Array of attestation subjects
- CreatedAt: Creation timestamp
- UpdatedAt: Last update timestamp
Each attestation subject contains:
- Name: Subject name/identifier
- Digest: Algorithm and value (e.g., sha256)
- URI: Resource URI (e.g., pkg: URL)
- DownloadLocation: Where to download the artifact
- MediaType: MIME type
- Annotations: Additional key-value metadata
Public keys include:
- ID: Unique key identifier
- Algorithm: Cryptographic algorithm (e.g., RSA, ECDSA)
- Key: PEM-encoded public key data
- CreatedAt: Upload timestamp
- OrganizationID: Owning organization
# Build binary
make build
# Output: bin/stash# Run tests with coverage
make test
# Run linters
make lint# Download and tidy dependencies
make deps.
├── cmd/stash/ # CLI entry point
├── internal/cli/ # CLI command implementations
│ ├── root.go
│ ├── upload.go
│ ├── read.go
│ ├── list.go
│ ├── delete.go
│ ├── update.go
│ ├── verify.go
│ └── publickey.go
├── pkg/client/ # Go client library
│ ├── client.go # HTTP client
│ ├── attestations.go # Attestation operations
│ ├── publickeys.go # Public key operations
│ ├── filters.go # Query filters
│ └── config/ # Configuration management
├── go.mod
├── Makefile
└── LICENSE
- Go 1.24.1 or later
Apache License 2.0 - See LICENSE for details.
- Managing software supply chain attestations (SLSA provenance)
- Storing and verifying cryptographic proofs of artifact origins
- Organizing attestations by artifact characteristics
- Querying attestation history with complex filtering
- Integrating attestation storage into CI/CD pipelines