2
0
Code
logikonline 4dc9c34bcc
All checks were successful
Build and Release / Tests (push) Successful in 1m11s
Build and Release / Lint (push) Successful in 1m41s
Build and Release / Create Release (push) Successful in 0s
fix(plugin): use DriverName instead of Dialect for DB detection
Replace x.Dialect().URI().DBType with x.DriverName() for more reliable database driver detection. Add support for 'pgx' and 'sqlite' driver variants alongside existing 'postgres' and 'sqlite3'. Improve logging with driver information and error messages for better migration debugging.
2026-02-08 11:16:07 -05:00
2026-01-26 00:58:05 -05:00
2026-01-17 08:42:53 -05:00
2026-01-26 00:58:05 -05:00

GitCaddy Vault

Encrypted Secrets Management for GitCaddy

GitCaddy Vault is a commercial module compiled directly into GitCaddy Server that provides enterprise-grade secrets management within your GitCaddy repositories. Store, version, and securely access credentials, API keys, certificates, and other sensitive data without leaving your Git workflow.

Features

Core Capabilities

  • Encrypted Storage - All secrets encrypted at rest using AES-256-GCM with per-repository encryption keys
  • Version History - Full version tracking with rollback capability for all secrets
  • Audit Logging - Complete audit trail of all secret access and modifications
  • CI/CD Tokens - Scoped tokens for secure automated access during builds and deployments

Secret Types

Type Description
key-value Simple key-value pairs
env-file Environment file format (KEY=value)
file Arbitrary file content
certificate TLS/SSL certificates
ssh-key SSH private keys

Lockbox (End-to-End Encryption)

Lockbox provides optional client-side encryption where the server never sees your plaintext secrets. Perfect for highly sensitive data where you don't want even the server administrator to have access.

Feature Standard Mode Lockbox Mode
Server sees plaintext Yes No
Passphrase required No Yes
Recovery if passphrase lost Yes No
Web UI viewing Yes CLI/SDK only

How Lockbox Works:

  1. Client encrypts secret with your passphrase using Argon2id + AES-256-GCM
  2. Encrypted blob is sent to server in lockbox:v1:... format
  3. Server wraps the blob with repository DEK (double encryption)
  4. On retrieval, server unwraps DEK layer, returns lockbox blob
  5. Client decrypts with your passphrase

Encryption Scheme:

  • Key derivation: Argon2id (time=1, memory=64MB, parallelism=4)
  • Cipher: AES-256-GCM with 12-byte nonce
  • Salt: 16 bytes random per secret
  • Format: lockbox:v1:<base64(salt)>:<base64(nonce||ciphertext||tag)>

Security Architecture

┌─────────────────────────────────────────────────────────┐
│                    GitCaddy Server                       │
├─────────────────────────────────────────────────────────┤
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐  │
│  │   Web UI    │    │   REST API  │    │  CI/CD API  │  │
│  └──────┬──────┘    └──────┬──────┘    └──────┬──────┘  │
│         │                  │                  │          │
│         └──────────────────┼──────────────────┘          │
│                            │                             │
│                   ┌────────▼────────┐                    │
│                   │  Vault Service  │                    │
│                   └────────┬────────┘                    │
│                            │                             │
│         ┌──────────────────┼──────────────────┐          │
│   ┌─────▼─────┐     ┌──────▼──────┐    ┌──────▼──────┐  │
│   │  Crypto   │     │   Models    │    │  License    │  │
│   │  Engine   │     │   (XORM)    │    │  Manager    │  │
│   └───────────┘     └─────────────┘    └─────────────┘  │
│                                                          │
│               (Compiled into GitCaddy Server)            │
└─────────────────────────────────────────────────────────┘

Encryption Hierarchy:

  1. Master Key (KEK) - Server-level key encryption key
  2. Repository DEK - Per-repository data encryption key, encrypted by KEK
  3. Secret Values - Encrypted using repository DEK with AES-256-GCM

Installation

Requirements

  • GitCaddy Server v1.0.0 or later (Vault is included automatically)
  • Valid GitCaddy Vault license

Setup

GitCaddy Vault is compiled directly into GitCaddy Server - no separate installation required.

  1. Add your license key via environment variable or file:

    # Option 1: Environment variable
    export GITCADDY_LICENSE_KEY="<your-base64-license>"
    
    # Option 2: License file
    cp license.key /etc/gitcaddy/license.key
    
  2. Restart GitCaddy Server to activate the license

Configuration

Environment Variables

Variable Description Default
GITCADDY_LICENSE_KEY Base64-encoded license key -
GITCADDY_LICENSE_FILE Path to license file /etc/gitcaddy/license.key
GITCADDY_VAULT_KEK Master key encryption key (32 bytes, hex) Auto-generated
GITCADDY_DEV_MODE Skip license validation (dev only) 0

License File Locations

GitCaddy Server searches for license files in this order:

  1. Path specified by GITCADDY_LICENSE_FILE
  2. /etc/gitcaddy/license.key
  3. ./custom/license.key
  4. ./license.key

Usage

Web Interface

Access the Vault tab in any repository where you have admin permissions:

https://your-gitcaddy-instance/owner/repo/vault

Managing Secrets:

  1. Navigate to Repository > Vault > Secrets
  2. Click "New Secret" to create a secret
  3. Use dot notation for organization: prod.database.password, staging.api.key

Creating CI/CD Tokens:

  1. Navigate to Repository > Vault > CI/CD Tokens
  2. Click "New Token"
  3. Define the scope (e.g., read:*, read:prod.*, write:db.credentials)
  4. Set expiration time
  5. Copy the token immediately (shown only once)

REST API

All API endpoints require a valid vault token in the Authorization header.

Authentication:

# Bearer token format
Authorization: Bearer gvt_abc123...

# Or token format
Authorization: token gvt_abc123...

List Secrets:

curl -H "Authorization: Bearer $VAULT_TOKEN" \
  https://gitcaddy.example.com/api/v1/repos/owner/repo/vault/secrets

Get Secret Value:

curl -H "Authorization: Bearer $VAULT_TOKEN" \
  https://gitcaddy.example.com/api/v1/repos/owner/repo/vault/secrets/prod.database.password

Create Secret:

curl -X POST \
  -H "Authorization: Bearer $VAULT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "prod.database.password",
    "description": "Production database credentials",
    "type": "key-value",
    "value": "secret-password-here"
  }' \
  https://gitcaddy.example.com/api/v1/repos/owner/repo/vault/secrets

Update Secret:

curl -X PUT \
  -H "Authorization: Bearer $VAULT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "key-value",
    "value": "new-secret-value",
    "comment": "Rotated credentials"
  }' \
  https://gitcaddy.example.com/api/v1/repos/owner/repo/vault/secrets/prod.database.password

Delete Secret:

curl -X DELETE \
  -H "Authorization: Bearer $VAULT_TOKEN" \
  https://gitcaddy.example.com/api/v1/repos/owner/repo/vault/secrets/prod.database.password

CI/CD Integration

GitHub Actions / Gitea Actions:

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Fetch secrets
        run: |
          DB_PASSWORD=$(curl -s -H "Authorization: Bearer ${{ secrets.VAULT_TOKEN }}" \
            "${{ github.server_url }}/api/v1/repos/${{ github.repository }}/vault/secrets/prod.database.password" \
            | jq -r '.value')
          echo "::add-mask::$DB_PASSWORD"
          echo "DB_PASSWORD=$DB_PASSWORD" >> $GITHUB_ENV

GitLab CI:

deploy:
  script:
    - |
      export DB_PASSWORD=$(curl -s -H "Authorization: Bearer $VAULT_TOKEN" \
        "$CI_SERVER_URL/api/v1/repos/$CI_PROJECT_PATH/vault/secrets/prod.database.password" \
        | jq -r '.value')

Token Scopes

Token scopes control access to secrets using a simple grammar:

Scope Description
read:* Read access to all secrets
write:* Read and write access to all secrets
read:prod.* Read access to secrets starting with prod.
write:db.credentials Write access to specific secret db.credentials
admin Full administrative access

Multiple scopes: Separate with commas: read:prod.*,write:staging.*

License Tiers

Feature Solo Pro Team Enterprise
Users 1 5 25 Unlimited
Secrets per repo 5 Unlimited Unlimited Unlimited
Audit retention 7 days 90 days 1 year Custom
Version history No Yes Yes Yes
CI/CD tokens No Yes Yes Yes
SSO integration No No Yes Yes
Priority support No No No Yes

Database Schema

GitCaddy Vault uses the following tables:

  • vault_secret - Secret metadata
  • vault_secret_version - Versioned secret values (encrypted)
  • vault_repo_key - Per-repository encryption keys
  • vault_token - CI/CD access tokens
  • vault_audit_entry - Audit log entries

Development

Architecture: Vault ↔ Server Sync

Important for contributors and AI assistants:

GitCaddy Vault is the source of truth for vault-related templates and locales. Due to Go plugin compilation limitations, vault code is compiled directly into GitCaddy Server rather than loaded as a dynamic plugin.

┌─────────────────────────────────────────────────────────────────┐
│                     gitcaddy-vault (this repo)                   │
│  SOURCE OF TRUTH for:                                           │
│  • templates/repo/vault/*.tmpl  → UI templates                  │
│  • locale/*.json                → Translation strings           │
│  • models/, services/, crypto/  → Business logic                │
│  • license/                     → License validation            │
└─────────────────────────┬───────────────────────────────────────┘
                          │
                    BUILD TIME SYNC
                    (scripts/sync-vault.sh)
                          │
                          ▼
┌─────────────────────────────────────────────────────────────────┐
│                     gitcaddy-server                              │
│  RECEIVES from vault:                                           │
│  • templates/repo/vault/*.tmpl  ← Copied from vault             │
│  • options/locale/*.json        ← vault.* keys merged           │
│                                                                  │
│  SERVER-ONLY (not in vault):                                    │
│  • templates/repo/vault/feature_upgrade.tmpl                    │
│  • templates/repo/vault/not_installed.tmpl                      │
│  • templates/repo/vault/upgrade.tmpl                            │
│  • routers/web/repo/vault/vault.go  ← Router glue code          │
│  • services/vault/vault.go          ← Service wrappers          │
└─────────────────────────────────────────────────────────────────┘

When making changes:

  • Edit templates/locales in gitcaddy-vault (this repo)
  • The CI build automatically syncs to gitcaddy-server
  • Server-specific templates (upgrade prompts) stay in server repo
  • Go router code stays in server repo (thin integration layer)

Why not Go plugins? Go plugins require exact compiler version and dependency matches between plugin and host. This is fragile in practice, so we compile vault directly into the server binary.

Building

The Vault module is compiled directly into GitCaddy Server. To build the server with Vault:

# Clone GitCaddy Server (includes Vault)
git clone https://git.marketally.com/gitcaddy/server.git
cd server

# Build the server (Vault is included automatically)
make build

# Run tests
go test ./...

Keygen Utility

The license key generation tool is built separately:

# Clone the vault repository
git clone https://git.marketally.com/gitcaddy/vault.git
cd vault

# Build the keygen utility
go build -o keygen ./cmd/keygen

Generating License Keys

# Generate a new keypair (do this once, keep private key secure!)
go run ./cmd/keygen -generate-keys

# Sign a license
go run ./cmd/keygen -sign \
  -email customer@example.com \
  -tier pro \
  -duration 365d \
  -private-key /secure/path/private.key

Local Development

Set GITCADDY_DEV_MODE=1 to skip license validation during development:

export GITCADDY_DEV_MODE=1

Key Management

Master Key Configuration

The vault uses a master Key Encryption Key (KEK) to encrypt repository-level Data Encryption Keys (DEKs). Configure the master key using one of these methods (in priority order):

  1. app.ini (recommended for production):

    [vault]
    MASTER_KEY = <64-character-hex-string>
    
  2. Environment variable:

    export GITCADDY_VAULT_KEY="<64-character-hex-string>"
    
  3. Key file:

    export GITCADDY_VAULT_KEY_FILE="/etc/gitcaddy/vault.key"
    
  4. Fallback (not recommended): If none of the above are set, Gitea's SECRET_KEY is used as a fallback.

Generate a secure master key:

openssl rand -hex 32

Key Migration

If you change your master key or need to migrate from the fallback key to a dedicated master key, use the Key Migration feature.

When to use key migration:

  • You changed the MASTER_KEY in app.ini and existing secrets are now inaccessible
  • Secrets were created using the fallback key before a dedicated master key was configured
  • You see "Encryption Key Mismatch" errors when accessing vault secrets

Web UI:

  1. Navigate to Repository > Vault > Key Migration (admin only)
  2. Enter the old master key (the previous MASTER_KEY or Gitea's SECRET_KEY)
  3. Choose the migration scope (this repository or all repositories)
  4. Click "Start Migration"

API:

curl -X POST \
  -H "Authorization: Bearer $VAULT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "old_key": "<previous-master-key-or-secret-key>",
    "repo_id": 0
  }' \
  https://gitcaddy.example.com/owner/repo/-/vault/api/migrate-key

The old_key can be:

  • A 64-character hex string (will be decoded to 32 bytes)
  • Raw text (will be used as-is, padded/truncated to 32 bytes)

Set repo_id to 0 to migrate the current repository, or specify a repo ID for a specific repository. Instance admins can migrate all repositories at once.

DEK Rotation (Enterprise)

For enhanced security, Enterprise license holders can rotate the Data Encryption Key (DEK) for a repository. This generates a new DEK and re-encrypts all secret versions.

Web UI:

  1. Navigate to Repository > Vault > Key Migration
  2. Click "Rotate DEK" in the DEK Rotation section

API:

curl -X POST \
  -H "Authorization: Bearer $VAULT_TOKEN" \
  https://gitcaddy.example.com/owner/repo/-/vault/api/rotate-key

Security Considerations

  1. Key Management - The master KEK should be stored securely (HSM, KMS, or secure environment variable)
  2. Token Storage - Vault tokens are hashed with SHA-256 before storage
  3. Audit Trail - All access is logged with IP address and user information
  4. Soft Delete - Deleted secrets are retained for recovery before permanent deletion
  5. TLS Required - Always use HTTPS in production

Support

License

Business Source License 1.1 - See LICENSE file for details.

Copyright 2026 MarketAlly. All rights reserved.

GitCaddy Vault API Reference

Version: v1.0.0 (dev)
License: Business Source License 1.1
Copyright: 2026 MarketAlly. All rights reserved.

GitCaddy Vault is a secure secrets management plugin for GitCaddy (Gitea fork) that provides encrypted storage for sensitive data like API keys, passwords, certificates, and environment variables. It uses AES-256-GCM encryption with a two-tier key architecture (master KEK + per-repository DEK) and includes features like version history, audit logging, and scoped API tokens.


Table of Contents


Overview

GitCaddy Vault provides:

  • Encrypted Secret Storage: AES-256-GCM encryption for all secrets
  • Version History: Track changes to secrets over time with rollback capability
  • Audit Logging: Complete audit trail of all vault operations
  • Scoped API Tokens: Fine-grained access control for CI/CD pipelines
  • Key Rotation: Enterprise-tier DEK rotation for enhanced security
  • Multi-language Support: 25+ locale translations included
  • Lockbox Mode: Client-side end-to-end encryption support

The plugin integrates directly into GitCaddy's repository interface, adding a "Vault" tab to each repository for managing secrets.


Architecture

Encryption Model

GitCaddy Vault uses a two-tier key architecture:

  1. Master Key (KEK - Key Encryption Key): A 256-bit key configured at the instance level
  2. Data Encryption Keys (DEK): Per-repository 256-bit keys encrypted with the master KEK

All secret values are encrypted with the repository's DEK, which itself is encrypted with the master KEK. This allows for key rotation without re-encrypting all secrets.

Key Hierarchy

Master Key (KEK) - 256-bit AES key
    └── Repository DEK - 256-bit AES key (encrypted with KEK)
        └── Secret Value - Encrypted with DEK using AES-256-GCM

Database Schema

The plugin adds five database tables:

  • vault_secret: Secret metadata (name, type, description)
  • vault_secret_version: Encrypted secret values with version history
  • vault_repo_key: Per-repository encrypted DEKs
  • vault_token: API access tokens for CI/CD
  • vault_audit_entry: Complete audit log

Installation

Prerequisites

  • GitCaddy v3.0+ (Gitea fork)
  • Go 1.21+ (for building from source)
  • PostgreSQL, MySQL, or SQLite database

Building

# Clone the repository
git clone https://git.marketally.com/gitcaddy/gitcaddy-vault.git
cd gitcaddy-vault

# Build the plugin
go build -ldflags "-X git.marketally.com/gitcaddy/gitcaddy-vault.Version=v1.0.0"

# The plugin is automatically registered via init() in plugin.go

Installation

  1. Copy the built plugin to your GitCaddy plugins directory
  2. Configure the master encryption key (see Configuration)
  3. Restart GitCaddy
  4. The plugin will automatically create database tables on first startup

Configuration

Master Key Configuration

The master encryption key can be configured in three ways (priority order):

  1. app.ini configuration (recommended):
[vault]
MASTER_KEY = 64_character_hex_string_here
  1. Environment variable:
export GITCADDY_VAULT_KEY=64_character_hex_string_here
  1. Key file:
# Create key file
echo "your_64_char_hex_key" > /etc/gitcaddy/vault.key

# Or specify custom location
export GITCADDY_VAULT_KEY_FILE=/path/to/vault.key

Generating a Master Key

# Generate a 256-bit (32-byte) key as hex
openssl rand -hex 32

Important: Store the master key securely. If lost, all encrypted secrets become permanently unrecoverable.

Fallback Behavior

If no master key is configured, the vault will fall back to using Gitea's SECRET_KEY. This is not recommended for production as changing the SECRET_KEY will break all vault secrets.

License Configuration

Place your license file at one of these locations:

  • /etc/gitcaddy/license.key
  • ./custom/license.key
  • ./license.key

Or set via environment:

export GITCADDY_LICENSE_KEY="base64_encoded_license_json"
export GITCADDY_LICENSE_FILE=/path/to/license.key

Development Mode

Skip license checks during development:

export GITCADDY_DEV_MODE=1
export GITCADDY_SKIP_LICENSE_CHECK=1

Authentication

Web UI Authentication

Web routes use GitCaddy's standard session-based authentication. Users must have repository access (read for viewing, write for creating/updating).

API Authentication

API routes support two authentication methods:

1. GitCaddy Session Token

curl -H "Authorization: Bearer <gitea_token>" \
  https://git.example.com/api/v1/repos/owner/repo/vault/secrets

2. Vault Token (CI/CD)

curl -H "Authorization: Bearer gvt_<vault_token>" \
  https://git.example.com/api/v1/repos/owner/repo/vault/secrets/SECRET_NAME

Vault tokens are prefixed with gvt_ and provide scoped access to secrets.


API Endpoints

All API endpoints are under /api/v1/repos/{owner}/{repo}/vault.

Secrets

List Secrets

GET /api/v1/repos/{owner}/{repo}/vault/secrets

Query Parameters:

Parameter Type Default Description
include_deleted boolean false Include soft-deleted secrets

Response:

[
  {
    "name": "DATABASE_URL",
    "description": "Production database connection string",
    "type": "password",
    "encryption_mode": "standard",
    "current_version": 3,
    "created_at": 1704067200,
    "updated_at": 1704153600,
    "is_deleted": false
  }
]

Supported Types: env-file, key-value, file, certificate, ssh-key


Get Secret

GET /api/v1/repos/{owner}/{repo}/vault/secrets/{name}

Query Parameters:

Parameter Type Default Description
version integer current Specific version to retrieve

Response:

{
  "name": "DATABASE_URL",
  "description": "Production database connection string",
  "type": "password",
  "encryption_mode": "standard",
  "current_version": 3,
  "created_at": 1704067200,
  "updated_at": 1704153600,
  "value": "postgresql://user:pass@localhost/db"
}

Authentication: Requires read permission or valid vault token with read scope.


Create or Update Secret

PUT /api/v1/repos/{owner}/{repo}/vault/secrets/{name}

Request Body:

{
  "name": "DATABASE_URL",
  "description": "Production database connection string",
  "type": "password",
  "value": "postgresql://user:pass@localhost/db",
  "comment": "Updated password",
  "encryption_mode": "standard"
}

Parameters:

Field Type Required Description
name string Yes Secret name (alphanumeric, _, -)
value string Yes Secret value (will be encrypted)
description string No Human-readable description
type string No Secret type (default: env-file)
comment string No Version comment
encryption_mode string No standard or lockbox (default: standard)

Encryption Modes:

  • standard: Server-side encryption (default)
  • lockbox: Client-side E2E encryption (value must be pre-encrypted with lockbox:v1: prefix)

Response (201 Created or 200 OK):

{
  "name": "DATABASE_URL",
  "description": "Production database connection string",
  "type": "password",
  "encryption_mode": "standard",
  "current_version": 1,
  "created_at": 1704067200,
  "updated_at": 1704067200
}

Authentication: Requires write permission or vault token with write scope.


Delete Secret

DELETE /api/v1/repos/{owner}/{repo}/vault/secrets/{name}

Soft-deletes the secret (can be restored later).

Response:

{
  "message": "Secret deleted"
}

Authentication: Requires write permission.


Restore Secret

POST /api/v1/repos/{owner}/{repo}/vault/secrets/{name}/restore

Restores a soft-deleted secret.

Response:

{
  "message": "Secret restored"
}

Authentication: Requires write permission.


Versions

List Versions

GET /api/v1/repos/{owner}/{repo}/vault/secrets/{name}/versions

Response:

[
  {
    "version": 3,
    "comment": "Updated password",
    "created_by": 123,
    "created_at": 1704153600
  },
  {
    "version": 2,
    "comment": "Rotated credentials",
    "created_by": 123,
    "created_at": 1704110400
  },
  {
    "version": 1,
    "comment": "",
    "created_by": 123,
    "created_at": 1704067200
  }
]

Rollback Secret

POST /api/v1/repos/{owner}/{repo}/vault/secrets/{name}/rollback

Request Body:

{
  "version": 2
}

Creates a new version with the content from the specified version.

Response:

{
  "message": "Secret rolled back to version 2"
}

Authentication: Requires write permission.


Tokens

List Tokens

GET /api/v1/repos/{owner}/{repo}/vault/tokens

Response:

[
  {
    "id": 1,
    "description": "CI/CD Pipeline",
    "scope": "read:*",
    "created_at": 1704067200,
    "expires_at": 1706745600,
    "last_used_at": 1704153600,
    "used_count": 42,
    "is_revoked": false
  }
]

Authentication: Requires admin permission.


Create Token

POST /api/v1/repos/{owner}/{repo}/vault/tokens

Request Body:

{
  "description": "CI/CD Pipeline",
  "scope": "read:*",
  "ttl": "30d"
}

Parameters:

Field Type Required Description
description string Yes Token description
scope string No Access scope (default: read)
ttl string No Time to live (default: 30d)

Scope Format:

  • read:* - Read all secrets
  • write:* - Read and write all secrets
  • read:prod.* - Read secrets starting with prod.
  • write:DATABASE_URL - Write only DATABASE_URL
  • admin - Full admin access (manage tokens, rotate keys)

TTL Format:

  • 24h - 24 hours
  • 7d - 7 days
  • 30d - 30 days
  • 1y - 1 year
  • 0 - Never expires

Response (201 Created):

{
  "id": 1,
  "description": "CI/CD Pipeline",
  "scope": "read:*",
  "created_at": 1704067200,
  "expires_at": 1706745600,
  "token": "gvt_a1b2c3d4e5f6..."
}

Important: The token field is only shown once. Store it securely.

Authentication: Requires admin permission.


Revoke Token

DELETE /api/v1/repos/{owner}/{repo}/vault/tokens/{id}

Response:

{
  "message": "Token revoked"
}

Authentication: Requires admin permission.


Token Introspection

GET /api/v1/repos/{owner}/{repo}/vault/token/info

Returns information about the current token (useful for clients to check their permissions).

Authentication: Requires valid vault token in Authorization header.

Response:

{
  "scope": "read:*",
  "description": "CI/CD Pipeline",
  "expires_at": 1706745600,
  "can_read": true,
  "can_write": false,
  "is_admin": false
}

Audit Log

Get Audit Entries

GET /api/v1/repos/{owner}/{repo}/vault/audit

Query Parameters:

Parameter Type Default Description
page integer 1 Page number
page_size integer 50 Entries per page (max 100)

Response:

{
  "entries": [
    {
      "id": 123,
      "secret_id": 45,
      "action": "read",
      "user_id": 1,
      "ip_address": "192.168.1.100",
      "success": true,
      "message": "",
      "timestamp": 1704067200
    }
  ],
  "total": 150,
  "page": 1,
  "pages": 3
}

Actions: list, read, write, delete, restore, rollback, rotate-key

Authentication: Requires admin permission.


Key Management

Rotate DEK (Enterprise)

POST /api/v1/repos/{owner}/{repo}/vault/rotate-key

Generates a new Data Encryption Key (DEK) for the repository and re-encrypts all secrets.

Response:

{
  "message": "DEK rotation completed successfully"
}

Authentication: Requires admin permission and Enterprise license.

Note: This operation can take several minutes for repositories with many secrets.


Migrate Key

POST /api/v1/repos/{owner}/{repo}/vault/migrate-key

Migrates secrets from an old master key to the current master key. Used when the master KEK changes.

Request Body:

{
  "old_key": "old_64_char_hex_key_here",
  "repo_id": 0
}

Parameters:

Field Type Required Description
old_key string Yes Old master key (hex or raw)
repo_id integer No Specific repo (0 = all repos, admin only)

Response:

{
  "message": "Key migration completed",
  "success_count": 10,
  "failed_count": 0,
  "failed_repos": []
}

Authentication: Requires admin permission.


Go Package API

Crypto Package

The crypto package provides encryption primitives.

Manager

type Manager struct {
    masterKey     []byte
    usingFallback bool
    keySource     string
}

Methods:

// NewManager creates a new crypto manager
func NewManager() *Manager

// LoadMasterKey loads the master key from configured source
func (m *Manager) LoadMasterKey() error

// HasMasterKey returns true if a master key is loaded
func (m *Manager) HasMasterKey() bool

// IsUsingFallbackKey returns true if using Gitea's SECRET_KEY
func (m *Manager) IsUsingFallbackKey() bool

// KeySource returns where the master key was loaded from
func (m *Manager) KeySource() string

// SetKey sets the master key directly (32 bytes)
func (m *Manager) SetKey(key []byte)

// Encrypt encrypts plaintext using AES-256-GCM
func (m *Manager) Encrypt(plaintext []byte, key []byte) ([]byte, error)

// Decrypt decrypts ciphertext using AES-256-GCM
func (m *Manager) Decrypt(ciphertext []byte, key []byte) ([]byte, error)

// EncryptWithMasterKey encrypts plaintext using the master KEK
func (m *Manager) EncryptWithMasterKey(plaintext []byte) ([]byte, error)

// DecryptWithMasterKey decrypts ciphertext using the master KEK
func (m *Manager) DecryptWithMasterKey(ciphertext []byte) ([]byte, error)

// EncryptDEK encrypts a DEK with the master KEK
func (m *Manager) EncryptDEK(dek []byte) ([]byte, error)

// DecryptDEK decrypts a DEK with the master KEK
func (m *Manager) DecryptDEK(encryptedDEK []byte) ([]byte, error)

Package-level Functions:

// LoadMasterKey loads the master key using the default manager
func LoadMasterKey() error

// HasMasterKey checks if the default manager has a master key
func HasMasterKey() bool

// IsUsingFallbackKey checks if using Gitea's SECRET_KEY
func IsUsingFallbackKey() bool

// KeySource returns the key source of the default manager
func KeySource() string

// EncryptWithMasterKey encrypts using the default manager
func EncryptWithMasterKey(plaintext []byte) ([]byte, error)

// DecryptWithMasterKey decrypts using the default manager
func DecryptWithMasterKey(ciphertext []byte) ([]byte, error)

// EncryptDEK encrypts a DEK using the default manager
func EncryptDEK(dek []byte) ([]byte, error)

// DecryptDEK decrypts a DEK using the default manager
func DecryptDEK(encryptedDEK []byte) ([]byte, error)

// EncryptSecret encrypts a secret using the default manager
func EncryptSecret(plaintext []byte, dek []byte) ([]byte, error)

// DecryptSecret decrypts a secret using the default manager
func DecryptSecret(ciphertext []byte, dek []byte) ([]byte, error)

// GenerateDEK generates a new 32-byte DEK
func GenerateDEK() ([]byte, error)

// GetFallbackKey returns Gitea's SECRET_KEY as bytes
func GetFallbackKey() []byte

// HasDedicatedMasterKey returns true if not using fallback
func HasDedicatedMasterKey() bool

Errors:

var (
    ErrInvalidKey       = errors.New("invalid encryption key")
    ErrDecryptionFailed = errors.New("decryption failed")
    ErrNoMasterKey      = errors.New("no master key configured")
)

License Package

The license package manages license validation.

Manager

type Manager struct {
    license   *License
    publicKey ed25519.PublicKey
}

Methods:

// NewManager creates a new license manager
func NewManager() *Manager

// Load loads the license from file or environment
func (m *Manager) Load() error

// Validate validates the current license
func (m *Manager) Validate() error

// Info returns information about the current license
func (m *Manager) Info() *Info

// IsValid returns true if the license is valid
func (m *Manager) IsValid() bool

// GetTier returns the current license tier
func (m *Manager) GetTier() Tier

// GetLimits returns the feature limits for the current tier
func (m *Manager) GetLimits() Limits

// CanUseFeature checks if a feature is available
func (m *Manager) CanUseFeature(feature string) bool

Types

type Tier string

const (
    TierSolo       Tier = "solo"
    TierPro        Tier = "pro"
    TierTeam       Tier = "team"
    TierEnterprise Tier = "enterprise"
)

type Limits struct {
    Users              int  `json:"users"`
    SecretsPerRepo     int  `json:"secrets_per_repo"` // -1 = unlimited
    AuditRetentionDays int  `json:"audit_retention_days"`
    SSOEnabled         bool `json:"sso_enabled"`
    VersioningEnabled  bool `json:"versioning_enabled"`
    CICDTokensEnabled  bool `json:"cicd_tokens_enabled"`
}

type License struct {
    LicenseID     string `json:"license_id"`
    CustomerEmail string `json:"customer_email"`
    Tier          Tier   `json:"tier"`
    Limits        Limits `json:"limits"`
    IssuedAt      int64  `json:"issued_at"`
    ExpiresAt     int64  `json:"expires_at"`
    Signature     string `json:"signature"`
}

type Info struct {
    Valid         bool
    Tier          string
    CustomerEmail string
    ExpiresAt     int64
    GracePeriod   bool
    Limits        Limits
}

Default Limits:

func DefaultLimitsForTier(tier Tier) Limits
Tier Users Secrets/Repo Audit Retention SSO Versioning CI/CD Tokens
Solo 1 5 7 days No No No
Pro 5 Unlimited 90 days No Yes Yes
Team 25 Unlimited 365 days Yes Yes Yes
Enterprise Unlimited Unlimited Custom Yes Yes Yes

Errors:

var (
    ErrNoLicense        = errors.New("no license file found")
    ErrInvalidLicense   = errors.New("invalid license")
    ErrExpiredLicense   = errors.New("license expired")
    ErrInvalidSignature = errors.New("invalid license signature")
)

Models Package

The models package defines database models.

VaultSecret

type VaultSecret struct {
    ID             int64
    RepoID         int64
    Name           string
    Description    string
    Type           SecretType
    CurrentVersion int
    CreatedUnix    timeutil.TimeStamp
    CreatedBy      int64
    UpdatedUnix    timeutil.TimeStamp
    EncryptionMode string // "standard" or "lockbox"
    DeletedUnix    timeutil.TimeStamp // Soft delete
    DeletedBy      int64
    PurgedUnix     timeutil.TimeStamp // Hard delete
}

type SecretType string

const (
    SecretTypeEnvFile     SecretType = "env-file"
    SecretTypeKeyValue    SecretType = "key-value"
    SecretTypeFile        SecretType = "file"
    SecretTypeCertificate SecretType = "certificate"
    SecretTypeSSHKey      SecretType = "ssh-key"
)

Methods:

func (s *VaultSecret) IsActive() bool
func (s *VaultSecret) IsDeleted() bool
func (s *VaultSecret) IsPurged() bool

Functions:

func CreateVaultSecret(ctx context.Context, secret *VaultSecret) error
func GetVaultSecretByID(ctx context.Context, id int64) (*VaultSecret, error)
func GetVaultSecretByName(ctx context.Context, repoID int64, name string) (*VaultSecret, error)
func UpdateVaultSecret(ctx context.Context, secret *VaultSecret, cols ...string) error
func SoftDeleteVaultSecret(ctx context.Context, secret *VaultSecret, deletedBy int64) error
func RestoreVaultSecret(ctx context.Context, secret *VaultSecret) error
func PurgeVaultSecret(ctx context.Context, secret *VaultSecret) error
func GetVaultSecretsByRepo(ctx context.Context, repoID int64, includeDeleted bool) ([]*VaultSecret, error)
func CountVaultSecretsByRepo(ctx context.Context, repoID int64) (int64, error)

VaultSecretVersion

type VaultSecretVersion struct {
    ID             int64
    SecretID       int64
    Version        int
    EncryptedValue []byte
    CreatedUnix    timeutil.TimeStamp
    CreatedBy      int64
    Comment        string
    CreatorName    string // Non-DB field
    Creator        any    // Non-DB field (for UI)
}

Functions:

func CreateVaultSecretVersion(ctx context.Context, version *VaultSecretVersion) error
func GetVaultSecretVersion(ctx context.Context, secretID int64, versionNum int) (*VaultSecretVersion, error)
func GetLatestVaultSecretVersion(ctx context.Context, secretID int64) (*VaultSecretVersion, error)
func GetVaultSecretVersions(ctx context.Context, secretID int64) ([]*VaultSecretVersion, error)
func CountVaultSecretVersions(ctx context.Context, secretID int64) (int64, error)
func DeleteOldestVaultSecretVersion(ctx context.Context, secretID int64) error
func PruneVaultSecretVersions(ctx context.Context, secretID int64, maxVersions int) error
func CreateNewVersion(ctx context.Context, secret *VaultSecret, encryptedValue []byte, createdBy int64, comment string, maxVersions int) (*VaultSecretVersion, error)
func RollbackToVersion(ctx context.Context, secret *VaultSecret, targetVersion int, createdBy int64, maxVersions int) (*VaultSecretVersion, error)

VaultRepoKey

type VaultRepoKey struct {
    ID              int64
    RepoID          int64
    EncryptedDEK    []byte // DEK encrypted with master KEK
    KeyVersion      int
    CreatedUnix     timeutil.TimeStamp
    RotatedUnix     timeutil.TimeStamp
    RotatedBy       int64
    PreviousKeyData []byte // Old key for migration
}

Functions:

func GenerateDEK() ([]byte, error)
func GetVaultRepoKey(ctx context.Context, repoID int64) (*VaultRepoKey, error)
func CreateVaultRepoKey(ctx context.Context, key *VaultRepoKey) error
func UpdateVaultRepoKey(ctx context.Context, key *VaultRepoKey, cols ...string) error
func GetOrCreateVaultRepoKey(ctx context.Context, repoID int64, encryptDEK func([]byte) ([]byte, error)) (*VaultRepoKey, error)
func RotateVaultRepoKey(ctx context.Context, key *VaultRepoKey, userID int64, encryptDEK func([]byte) ([]byte, error), reEncryptSecrets func(oldDEK, newDEK []byte) error, decryptDEK func([]byte) ([]byte, error)) error
func MigrateVaultRepoKey(ctx context.Context, key *VaultRepoKey, decryptWithOldKey func([]byte) ([]byte, error), encryptWithNewKey func([]byte) ([]byte, error)) error
func GetAllVaultRepoKeys(ctx context.Context) ([]*VaultRepoKey, error)
func DeleteVaultRepoKey(ctx context.Context, repoID int64) error

VaultToken

type VaultToken struct {
    ID           int64
    RepoID       int64
    TokenHash    string // SHA-256 hash
    Scope        TokenScope
    Description  string
    ExpiresUnix  timeutil.TimeStamp
    CreatedUnix  timeutil.TimeStamp
    CreatedBy    int64
    UsedCount    int64
    LastUsedUnix timeutil.TimeStamp
    RevokedUnix  timeutil.TimeStamp
}

type TokenScope string

Methods:

func (t *VaultToken) IsActive() bool
func (t *VaultToken) IsExpired() bool
func (t *VaultToken) IsRevoked() bool

func (scope TokenScope) Allows(action string, secretName string) bool
func (scope TokenScope) CanRead(secretName string) bool
func (scope TokenScope) CanWrite(secretName string) bool
func (scope TokenScope) IsAdmin() bool

Functions:

func GenerateToken() (plaintext string, hash string)
func HashToken(plaintext string) string
func CreateVaultToken(ctx context.Context, token *VaultToken) error
func GetVaultTokenByHash(ctx context.Context, hash string) (*VaultToken, error)
func GetVaultTokenByID(ctx context.Context, id int64) (*VaultToken, error)
func GetVaultTokensByRepo(ctx context.Context, repoID int64, includeRevoked bool) ([]*VaultToken, error)
func RevokeVaultToken(ctx context.Context, token *VaultToken) error
func RecordTokenUse(ctx context.Context, token *VaultToken) error
func ValidateAndUseToken(ctx context.Context, plaintext string, repoID int64) (*VaultToken, error)
func DeleteExpiredTokens(ctx context.Context, retentionDays int) (int64, error)

VaultAuditEntry

type VaultAuditEntry struct {
    ID         int64
    RepoID     int64
    SecretID   int64
    VersionID  int64
    Action     AuditAction
    UserID     int64
    RunnerID   int64 // CI/CD runner
    TokenID    int64 // Token used
    IPAddress  string
    UserAgent  string
    Timestamp  timeutil.TimeStamp
    Success    bool
    FailReason string
    SecretName string // Non-DB field
    UserName   string // Non-DB field
}

type AuditAction string

const (
    AuditActionList      AuditAction = "list"
    AuditActionRead      AuditAction = "read"
    AuditActionWrite     AuditAction = "write"
    AuditActionDelete    AuditAction = "delete"
    AuditActionRestore   AuditAction = "restore"
    AuditActionPurge     AuditAction = "purge"
    AuditActionRollback  AuditAction = "rollback"
    AuditActionRotateKey AuditAction = "rotate-key"
)

Functions:

func CreateVaultAuditEntry(ctx context.Context, entry *VaultAuditEntry) error
func LogVaultAccess(ctx context.Context, repoID, secretID, versionID, userID, runnerID, tokenID int64, action AuditAction, ipAddress, userAgent string, success bool, failReason string) error
func GetVaultAuditEntries(ctx context.Context, opts FindVaultAuditOptions) ([]*VaultAuditEntry, error)
func CountVaultAuditEntries(ctx context.Context, opts FindVaultAuditOptions) (int64, error)
func DeleteVaultAuditEntriesOlderThan(ctx context.Context, repoID int64, before timeutil.TimeStamp) (int64, error)
func PruneVaultAuditEntries(ctx context.Context, repoID int64, retentionDays int) (int64, error)

Services Package

The services package provides high-level business logic.

Secrets

type CreateSecretOptions struct {
    Name           string
    Description    string
    Type           string
    Value          string
    EncryptionMode string // "standard" or "lockbox"
    CreatorID      int64
}

type UpdateSecretOptions struct {
    Type      string
    Value     string
    Comment   string
    UpdaterID int64
}

Functions:

func ListSecrets(ctx context.Context, repoID int64, includeDeleted bool) ([]*models.VaultSecret, error)
func GetSecret(ctx context.Context, repoID int64, name string) (*models.VaultSecret, error)
func GetSecretValue(ctx context.Context, repoID int64, name string, version int) (string, error)
func CreateSecret(ctx context.Context, repoID int64, opts CreateSecretOptions, limits *license.Limits) (*models.VaultSecret, error)
func UpdateSecret(ctx context.Context, repoID int64, name string, opts UpdateSecretOptions) (*models.VaultSecret, error)
func DeleteSecret(ctx context.Context, repoID int64, name string, userID int64) error
func RestoreSecret(ctx context.Context, repoID int64, name string) error
func RollbackSecret(ctx context.Context, repoID int64, name string, version int, userID int64) error
func ListVersions(ctx context.Context, repoID int64, name string) ([]*models.VaultSecretVersion, error)
func GetOrCreateRepoKey(ctx context.Context, repoID int64) (*models.VaultRepoKey, error)

Errors:

var (
    ErrSecretNotFound      = errors.New("secret not found")
    ErrVersionNotFound     = errors.New("version not found")
    ErrSecretExists        = errors.New("secret already exists")
    ErrEncryptionFailed    = errors.New("encryption failed")
    ErrDecryptionFailed    = errors.New("decryption failed")
    ErrSecretLimitReached  = errors.New("secret limit reached for this tier")
    ErrVersionLimitReached = errors.New("version limit reached for this tier")
)

Key Management

type MigrateRepoKeyOptions struct {
    OldKey    []byte // Old master key (32 bytes)
    RepoID    int64  // 0 = all repos
    UserID    int64
    IPAddress string
}

type MigrateRepoKeyResult struct {
    RepoID  int64
    Success bool
    Error   string
}

type RotateRepoKeyOptions struct {
    RepoID    int64
    UserID    int64
    IPAddress string
}

Functions:

func MigrateRepoKeys(ctx context.Context, opts MigrateRepoKeyOptions) ([]MigrateRepoKeyResult, error)
func RotateRepoKey(ctx context.Context, opts RotateRepoKeyOptions) error

Tokens

type CreateTokenOptions struct {
    Description string
    Scope       string
    TTL         string
    CreatorID   int64
}

Functions:

func ListTokens(ctx context.Context, repoID int64) ([]*models.VaultToken, error)
func CreateToken(ctx context.Context, repoID int64, opts CreateTokenOptions, limits *license.Limits) (*models.VaultToken, string, error)
func RevokeToken(ctx context.Context, repoID int64, tokenID int64) error
func ValidateToken(ctx context.Context, rawToken string, action string, secretName string) (*models.VaultToken, error)
func GetTokenInfo(ctx context.Context, rawToken string) (*models.VaultToken, error)

Errors:

var (
    ErrTokenNotFound     = errors.New("token not found")
    ErrTokenRevoked      = errors.New("token revoked")
    ErrTokenExpired      = errors.New("token expired")
    ErrInvalidScope      = errors.New("invalid token scope")
    ErrAccessDenied      = errors.New("access denied")
    ErrInvalidToken      = errors.New("invalid token")
    ErrTokenLimitReached = errors.New("token limit reached for this tier")
)

Audit

func ListAuditEntries(ctx context.Context, repoID int64, page, pageSize int) ([]*models.VaultAuditEntry, int64, error)
func CreateAuditEntry(ctx context.Context, entry *models.VaultAuditEntry) error

Error Codes

HTTP Status Codes

Code Status Description
200 OK Request successful
201 Created Resource created
400 Bad Request Invalid request parameters
401 Unauthorized Authentication required
402 Payment Required License upgrade required
403 Forbidden Insufficient permissions
404 Not Found Resource not found
409 Conflict Encryption key mismatch
500 Internal Server Error Server error

Error Response Format

{
  "error": "error_code",
  "message": "Human-readable error message"
}

Common Error Codes

Code HTTP Status Description
unauthorized 401 Missing or invalid authentication
forbidden 403 Insufficient permissions
not_found 404 Secret, token, or version not found
already_exists 409 Secret with name already exists
invalid_request 400 Invalid JSON or missing required fields
invalid_version 400 Invalid version number
invalid_token 401 Invalid or expired vault token
token_expired 401 Vault token has expired
token_revoked 401 Vault token has been revoked
access_denied 403 Token scope insufficient
limit_reached 402 License limit reached
enterprise_required 402 Feature requires Enterprise license
key_mismatch 409 Encryption key changed
decryption_failed 500 Failed to decrypt secret
encryption_failed 500 Failed to encrypt secret
internal_error 500 Unexpected server error

Code Examples

cURL Examples

Create a Secret

curl -X PUT \
  -H "Authorization: Bearer <gitea_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "DATABASE_URL",
    "description": "Production database connection",
    "type": "password",
    "value": "postgresql://user:pass@localhost/db"
  }' \
  https://git.example.com/api/v1/repos/owner/repo/vault/secrets/DATABASE_URL

Read a Secret (with Vault Token)

curl -H "Authorization: Bearer gvt_a1b2c3d4e5f6..." \
  https://git.example.com/api/v1/repos/owner/repo/vault/secrets/DATABASE_URL

List All Secrets

curl -H "Authorization: Bearer <gitea_token>" \
  https://git.example.com/api/v1/repos/owner/repo/vault/secrets

Rollback to Previous Version

curl -X POST \
  -H "Authorization: Bearer <gitea_token>" \
  -H "Content-Type: application/json" \
  -d '{"version": 2}' \
  https://git.example.com/api/v1/repos/owner/repo/vault/secrets/DATABASE_URL/rollback

Create a CI/CD Token

curl -X POST \
  -H "Authorization: Bearer <gitea_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "description": "GitHub Actions",
    "scope": "read:*",
    "ttl": "90d"
  }' \
  https://git.example.com/api/v1/repos/owner/repo/vault/tokens

Go Examples

Using the Crypto Package

package main

import (
    "fmt"
    "git.marketally.com/gitcaddy/gitcaddy-vault/crypto"
)

func main() {
    // Load master key
    if err := crypto.LoadMasterKey(); err != nil {
        panic(err)
    }

    // Generate a DEK
    dek, err := crypto.GenerateDEK()
    if err != nil {
        panic(err)
    }

    // Encrypt the DEK with the master key
    encryptedDEK, err := crypto.EncryptDEK(dek)
    if err != nil {
        panic(err)
    }

    // Encrypt a secret value with the DEK
    plaintext := []byte("my-secret-value")
    ciphertext, err := crypto.EncryptSecret(plaintext, dek)
    if err != nil {
        panic(err)
    }

    // Decrypt the secret
    decrypted, err := crypto.DecryptSecret(ciphertext, dek)
    if err != nil {
        panic(err)
    }

    fmt.Println(string(decrypted)) // "my-secret-value"
}

Using the Services Package

package main

import (
    "context"
    "fmt"
    "git.marketally.com/gitcaddy/gitcaddy-vault/services"
)

func main() {
    ctx := context.Background()
    repoID := int64(123)

    // Create a secret
    secret, err := services.CreateSecret(ctx, repoID, services.CreateSecretOptions{
        Name:        "API_KEY",
        Description: "Third-party API key",
        Type:        "api_key",
        Value:       "sk_live_abc123",
        CreatorID:   1,
    }, nil)
    if err != nil {
        panic(err)
    }

    // Get the secret value
    value, err := services.GetSecretValue(ctx, repoID, "API_KEY", 0)
    if err != nil {
        panic(err)
    }
    fmt.Println(value) // "sk_live_abc123"

    // Update the secret
    _, err = services.UpdateSecret(ctx, repoID, "API_KEY", services.UpdateSecretOptions{
        Type:      "api_key",
        Value:     "sk_live_xyz789",
        Comment:   "Rotated key",
        UpdaterID: 1,
    })
    if err != nil {
        panic(err)
    }

    // List versions
    versions, err := services.ListVersions(ctx, repoID, "API_KEY")
    if err != nil {
        panic(err)
    }
    fmt.Printf("Secret has %d versions\n", len(versions))

    // Rollback to version 1
    err = services.RollbackSecret(ctx, repoID, "API_KEY", 1, 1)
    if err != nil {
        panic(err)
    }
}

Token Validation

package main

import (
    "context"
    "fmt"
    "git.marketally.com/gitcaddy/gitcaddy-vault/services"
)

func main() {
    ctx := context.Background()
    rawToken := "gvt_a1b2c3d4e5f6..."

    // Validate token for reading a specific secret
    token, err := services.ValidateToken(ctx, rawToken, "read", "DATABASE_URL")
    if err != nil {
        fmt.Println("Token validation failed:", err)
        return
    }

    fmt.Printf("Token is valid (scope: %s)\n", token.Scope)

    // Get token info (introspection)
    info, err := services.GetTokenInfo(ctx, rawToken)
    if err != nil {
        panic(err)
    }

    fmt.Printf("Token expires at: %d\n", info.ExpiresUnix)
    fmt.Printf("Used %d times\n", info.UsedCount)
}

Lockbox (E2E Encrypted) Secrets

Lockbox secrets are encrypted client-side before being sent to the server. The server never sees the plaintext value. Use the SDKs for simplified lockbox operations.

Go SDK

import vault "git.marketally.com/gitcaddy/vault-tools/sdk/go"

client := vault.NewClient("https://git.example.com", "owner", "repo", token)

// Create a lockbox secret
err := client.CreateLockbox(ctx, "prod.master-key", "super-secret-value", "my-passphrase")
if err != nil {
    log.Fatal(err)
}

// Get and decrypt a lockbox secret
value, err := client.GetLockbox(ctx, "prod.master-key", "my-passphrase")
if err != nil {
    log.Fatal(err)
}
fmt.Println(value) // "super-secret-value"

TypeScript/JavaScript SDK

import { VaultClient } from '@gitcaddy/vault-sdk';

const client = new VaultClient('https://git.example.com', 'owner', 'repo', token);

// Create a lockbox secret
await client.createLockbox('prod.master-key', 'super-secret-value', 'my-passphrase');

// Get and decrypt a lockbox secret
const value = await client.getLockbox('prod.master-key', 'my-passphrase');
console.log(value); // "super-secret-value"

Python SDK

from gitcaddy_vault import VaultClient

client = VaultClient('https://git.example.com', 'owner', 'repo', token)

# Create a lockbox secret
client.create_lockbox('prod.master-key', 'super-secret-value', 'my-passphrase')

# Get and decrypt a lockbox secret
value = client.get_lockbox('prod.master-key', 'my-passphrase')
print(value)  # "super-secret-value"

Raw API (Manual Encryption)

For manual lockbox operations, you must encrypt the value client-side using Argon2id + AES-256-GCM in the lockbox format before sending:

# Create lockbox secret (value must be pre-encrypted)
curl -X PUT \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "prod.master-key",
    "value": "lockbox:v1:BASE64_SALT:BASE64_NONCE_CIPHERTEXT_TAG",
    "encryption_mode": "lockbox",
    "type": "key-value"
  }' \
  https://git.example.com/api/v1/repos/owner/repo/vault/secrets/prod.master-key

Lockbox Format: lockbox:v1:<base64(salt)>:<base64(nonce||ciphertext||tag)>

Encryption Parameters:

  • Key derivation: Argon2id (time=1, memory=64MB, parallelism=4, keyLen=32)
  • Cipher: AES-256-GCM (nonce=12 bytes, tag=16 bytes)
  • Salt: 16 bytes random

Python Examples

Using requests

import requests

BASE_URL = "https://git.example.com/api/v1"
TOKEN = "your_gitea_token"
REPO = "owner/repo"

headers = {
    "Authorization": f"Bearer {TOKEN}",
    "Content-Type": "application/json"
}

# Create a secret
response = requests.put(
    f"{BASE_URL}/repos/{REPO}/vault/secrets/API_KEY",
    headers=headers,
    json={
        "name": "API_KEY",
        "description": "Third-party API key",
        "type": "api_key",
        "value": "sk_live_abc123"
    }
)
print(response.json())

# Get secret value
response = requests.get(
    f"{BASE_URL}/repos/{REPO}/vault/secrets/API_KEY",
    headers=headers
)
secret = response.json()
print(f"Secret value: {secret['value']}")

# List all secrets
response = requests.get(
    f"{BASE_URL}/repos/{REPO}/vault/secrets",
    headers=headers
)
secrets = response.json()
for s in secrets:
    print(f"{s['name']}: v{s['current_version']}")

JavaScript/Node.js Examples

const axios = require('axios');

const BASE_URL = 'https://git.example.com/api/v1';
const TOKEN = 'your_gitea_token';
const REPO = 'owner/repo';

const client = axios.create({
  baseURL: BASE_URL,
  headers: {
    'Authorization': `Bearer ${TOKEN}`,
    'Content-Type': 'application/json'
  }
});

// Create a secret
async function createSecret() {
  const response = await client.put(`/repos/${REPO}/vault/secrets/DATABASE_URL`, {
    name: 'DATABASE_URL',
    description: 'Production database',
    type: 'password',
    value: 'postgresql://user:pass@localhost/db'
  });
  console.log('Created:', response.data);
}

// Get secret value
async function getSecret() {
  const response = await client.get(`/repos/${REPO}/vault/secrets/DATABASE_URL`);
  console.log('Value:', response.data.value);
}

// Create a token
async function createToken() {
  const response = await client.post(`/repos/${REPO}/vault/tokens`, {
    description: 'CI/CD Pipeline',
    scope: 'read:*',
    ttl: '30d'
  });
  console.log('Token:', response.data.token);
  console.log('Save this token - you won\'t see it again!');
}

// Run examples
(async () => {
  await createSecret();
  await getSecret();
  await createToken();
})();

GitHub Actions Example

name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Get secrets from vault
        env:
          VAULT_TOKEN: ${{ secrets.GITCADDY_VAULT_TOKEN }}
          VAULT_URL: https://git.example.com/api/v1/repos/owner/repo/vault
        run: |
          # Get database URL
          DB_URL=$(curl -s -H "Authorization: Bearer $VAULT_TOKEN" \
            "$VAULT_URL/secrets/DATABASE_URL" | jq -r '.value')
          echo "::add-mask::$DB_URL"
          echo "DATABASE_URL=$DB_URL" >> $GITHUB_ENV

          # Get API key
          API_KEY=$(curl -s -H "Authorization: Bearer $VAULT_TOKEN" \
            "$VAULT_URL/secrets/API_KEY" | jq -r '.value')
          echo "::add-mask::$API_KEY"
          echo "API_KEY=$API_KEY" >> $GITHUB_ENV

      - name: Deploy
        run: |
          echo "Deploying with DATABASE_URL and API_KEY from vault..."
          # Your deployment script here

GitLab CI Example

deploy:
  stage: deploy
  script:
    - |
      # Get secrets from vault
      export DATABASE_URL=$(curl -s -H "Authorization: Bearer $VAULT_TOKEN" \
        "$VAULT_URL/secrets/DATABASE_URL" | jq -r '.value')
      
      export API_KEY=$(curl -s -H "Authorization: Bearer $VAULT_TOKEN" \
        "$VAULT_URL/secrets/API_KEY" | jq -r '.value')
      
      # Deploy
      ./deploy.sh
  variables:
    VAULT_URL: https://git.example.com/api/v1/repos/owner/repo/vault
  only:
    - main

License Tiers

GitCaddy Vault uses a tiered licensing model:

Solo (Free)

  • Users: 1
  • Secrets per repo: 5
  • Audit retention: 7 days
  • Features: Basic secret storage
  • CI/CD tokens: No
  • Versioning: No
  • SSO: No

Pro

  • Users: 5
  • Secrets per repo: Unlimited
  • Audit retention: 90 days
  • Features: Version history, rollback
  • CI/CD tokens: Yes
  • Versioning: Yes
  • SSO: No
  • Price: Contact sales

Team

  • Users: 25
  • Secrets per repo: Unlimited
  • Audit retention: 365 days
  • Features: All Pro features + SSO
  • CI/CD tokens: Yes
  • Versioning: Yes
  • SSO: Yes
  • Price: Contact sales

Enterprise

  • Users: Unlimited
  • Secrets per repo: Unlimited
  • Audit retention: Custom
  • Features: All Team features + DEK rotation
  • CI/CD tokens: Yes
  • Versioning: Yes
  • SSO: Yes
  • DEK rotation: Yes
  • Price: Contact sales

Support


Copyright © 2026 MarketAlly. All rights reserved.
License: Business Source License 1.1

# Business Source License 1.1 License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved. “Business Source License” is a trademark of MariaDB Corporation Ab. --- ## Parameters **Licensor:** \ MarketAlly **Licensed Work:** \ GitCaddy Vault The Licensed Work is (c) 2026 MarketAlly **Additional Use Grant:** \ You may use the Licensed Work for non-production purposes, including development, testing, personal projects, educational use, and internal evaluation. You may also use the Licensed Work for production use with **up to five (5) users** at no cost (the “Solo Tier”). Production use beyond five (5) users, or use that exceeds Solo Tier limits, requires a valid commercial license obtained from the Licensor. **Change Date:** \ January 17, 2030 **Change License:** \ Apache License, Version 2.0 --- ## Terms The Licensor hereby grants you the right to copy, modify, create derivative works, redistribute, and make non-production use of the Licensed Work. The Licensor makes an Additional Use Grant, above, permitting limited production use. Effective on the Change Date, or the fourth anniversary of the first publicly available distribution of a specific version of the Licensed Work under this License, whichever comes first, the Licensor hereby grants you the rights described in the Change License. The rights granted under this License will terminate automatically if you violate any of the restrictions of this License. Upon termination, you must cease all use of the Licensed Work and destroy all copies. This License does not grant you any right in any trademark or logo of the Licensor or its affiliates. TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND TITLE. MariaDB hereby grants you permission to use this License’s text to license your works, and to refer to it using the trademark “Business Source License”, as long as you comply with the Covenants of Licensor below. --- ## Covenants of Licensor In consideration of the right to use this License’s text and the “Business Source License” name and trademark, Licensor covenants to MariaDB, and to all other recipients of the Licensed Work, that Licensor will: 1. Specify as the Change License a license that is compatible with version 2.0 of the Apache License, GPL version 2.0 or later, or a license that is OSI-approved. 2. Specify as the Change Date a date no later than four years after the first publicly available distribution of a specific version of the Licensed Work. 3. Not modify this License in any other way.
Description
Encrypted Secrets Management for GitCaddy
http://www.gitcaddy.com
Readme BSL-1.1 1.1 MiB
2026-02-08 16:16:48 +00:00
Languages
Go 100%