diff --git a/.env.example b/.env.example
index a338241e..11bf6c25 100644
--- a/.env.example
+++ b/.env.example
@@ -112,4 +112,32 @@ ANALYTICS_ID="anonymous"
OP_SERVICE_ACCOUNT_TOKEN=""
# Enable recording skyvern logs as artifacts
-ENABLE_LOG_ARTIFACTS=false
\ No newline at end of file
+ENABLE_LOG_ARTIFACTS=false
+
+# =============================================================================
+# SKYVERN BITWARDEN CONFIGURATION
+# =============================================================================
+# Your organization ID in official Bitwarden server or vaultwarden (if using organizations)
+SKYVERN_AUTH_BITWARDEN_ORGANIZATION_ID=your-org-id-here
+
+# These should match the values for bitwarden cli server for consistency
+SKYVERN_AUTH_BITWARDEN_MASTER_PASSWORD=your-master-password-here
+SKYVERN_AUTH_BITWARDEN_CLIENT_ID=user.your-client-id-here
+SKYVERN_AUTH_BITWARDEN_CLIENT_SECRET=your-client-secret-here
+
+# The CLI server will run on localhost:8002 by default
+# Optional, because by default Bitwarden is used directly
+# BITWARDEN_SERVER=http://localhost
+# BITWARDEN_SERVER_PORT=8002
+
+# =============================================================================
+# OPTIONAL: ADDITIONAL SKYVERN CONFIGURATION
+# =============================================================================
+# If you need to override the default Bitwarden server settings in Skyvern
+# These will be automatically set by the Docker Compose, but you can override them here
+
+# Maximum number of retries for Bitwarden operations
+# BITWARDEN_MAX_RETRIES=3
+
+# Timeout in seconds for Bitwarden operations
+# BITWARDEN_TIMEOUT_SECONDS=60
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000..a5aab2e3
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,27 @@
+# Set default behavior to automatically normalize line endings
+* text=auto
+
+# Force Unix LF line endings for shell scripts
+*.sh text eol=lf
+bitwarden-cli-server/entrypoint.sh text eol=lf
+
+# Force Unix LF line endings for Python files
+*.py text eol=lf
+
+# Force Unix LF line endings for Docker files
+Dockerfile text eol=lf
+*.dockerfile text eol=lf
+
+# Force Unix LF line endings for YAML and config files
+*.yml text eol=lf
+*.yaml text eol=lf
+*.json text eol=lf
+*.md text eol=lf
+
+# Binary files
+*.png binary
+*.jpg binary
+*.jpeg binary
+*.gif binary
+*.ico binary
+*.pdf binary
diff --git a/.yamlfmt b/.yamlfmt
new file mode 100644
index 00000000..509e8773
--- /dev/null
+++ b/.yamlfmt
@@ -0,0 +1,10 @@
+# yamlfmt configuration
+# Force Unix LF line endings for all YAML files
+line_ending: lf
+
+# Additional formatting options
+formatter:
+ type: basic
+ indent: 2
+ include_document_start: false
+ pad_line_comments: 1
diff --git a/bitwarden-cli-server/Dockerfile b/bitwarden-cli-server/Dockerfile
new file mode 100644
index 00000000..85d56b21
--- /dev/null
+++ b/bitwarden-cli-server/Dockerfile
@@ -0,0 +1,29 @@
+FROM node:24-alpine
+
+# Install dependencies
+# Install Bitwarden CLI
+RUN apk add --no-cache curl bash && \
+ npm install -g @bitwarden/cli
+
+# Create non-root user for security
+# Create directory for Bitwarden config
+RUN addgroup -g 1001 -S bw && adduser -S bw -u 1001 -G bw && \
+ mkdir -p /app/.config && \
+ chown -R bw:bw /app/.config
+
+# Copy entrypoint script
+COPY entrypoint.sh /entrypoint.sh
+RUN chmod +x /entrypoint.sh
+
+# Switch to non-root user
+USER bw
+WORKDIR /app
+
+# Expose port for bw serve
+EXPOSE 8087
+
+# Health check
+HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
+ CMD curl -f http://localhost:8087/status || exit 1
+
+ENTRYPOINT ["/entrypoint.sh"]
diff --git a/bitwarden-cli-server/README.md b/bitwarden-cli-server/README.md
new file mode 100644
index 00000000..05ff7e60
--- /dev/null
+++ b/bitwarden-cli-server/README.md
@@ -0,0 +1,104 @@
+# Bitwarden CLI Server for Skyvern
+
+This Docker setup provides a Bitwarden CLI server with `bw serve` functionality that enables Skyvern to work with vaultwarden (or official Bitwarden) instances.
+
+## Architecture
+
+```text
+Usual setup (in cloud):
+Skyvern → official Bitwarden
+
+Local from docker compose:
+Skyvern → bw serve (CLI Server) → vaultwarden Server
+```
+
+The CLI server acts as a bridge between Skyvern and vaultwarden, providing the REST API endpoints that Skyvern expects.
+
+## Setup
+
+This container is part of the main Skyvern Docker Compose setup. Configure your environment variables in the main `.env` file:
+
+```bash
+# Skyvern Bitwarden Configuration
+SKYVERN_AUTH_BITWARDEN_ORGANIZATION_ID=your-org-id-here
+SKYVERN_AUTH_BITWARDEN_MASTER_PASSWORD=your-master-password-here
+SKYVERN_AUTH_BITWARDEN_CLIENT_ID=user.your-client-id-here
+SKYVERN_AUTH_BITWARDEN_CLIENT_SECRET=your-client-secret-here
+
+# Vaultwarden Configuration
+BW_HOST=https://your-vaultwarden-server.com
+BW_CLIENTID=${SKYVERN_AUTH_BITWARDEN_CLIENT_ID}
+BW_CLIENTSECRET=${SKYVERN_AUTH_BITWARDEN_CLIENT_SECRET}
+BW_PASSWORD=${SKYVERN_AUTH_BITWARDEN_MASTER_PASSWORD}
+```
+
+Then start the service:
+
+```bash
+docker-compose up -d bitwarden-cli
+```
+
+## Available Endpoints
+
+Once running, the CLI server provides these endpoints on port 8002:
+
+- `GET /status` - Check server status
+- `POST /unlock` - Unlock vault
+- `GET /list/object/items` - List vault items
+- `GET /object/item/{id}` - Get specific item
+- `POST /object/item` - Create new item
+- `GET /object/template/item` - Get item template
+- And more...
+
+## Troubleshooting
+
+### Container won't start
+
+1. **Check logs**:
+ ```bash
+ docker-compose -f docker-compose.bitwarden.yml logs bitwarden-cli
+ ```
+
+2. **Common issues**:
+ - Invalid API credentials
+ - Wrong vaultwarden server URL
+ - Network connectivity issues
+ - Incorrect master password
+
+### Health check fails
+
+The container includes a health check that calls `/status`. If it fails:
+
+1. Check if the CLI server is actually running inside the container
+2. Verify the unlock process succeeded
+3. Check network configuration
+
+### API calls fail
+
+1. **Test the CLI server directly**:
+ ```bash
+ # Check status
+ curl http://localhost:8002/status
+
+ # List items (after unlock)
+ curl http://localhost:8002/list/object/items
+ ```
+
+2. **Check Skyvern configuration**:
+ - Ensure `BITWARDEN_SERVER` points to the CLI server
+ - Verify `BITWARDEN_SERVER_PORT` is correct
+
+## Security Notes
+
+- The container runs as a non-root user for security
+- Only binds to localhost by default
+- API credentials are passed via environment variables
+- Consider using Docker secrets for production deployments
+
+## Production Considerations
+
+1. **Secrets Management**: Use Docker secrets or external secret management
+2. **Monitoring**: Add proper logging and monitoring
+3. **Backup**: Ensure your vaultwarden instance is properly backed up
+4. **Updates**: Regularly update the Bitwarden CLI version
+5. **Network Security**: Use proper network isolation and firewalls
\ No newline at end of file
diff --git a/bitwarden-cli-server/entrypoint.sh b/bitwarden-cli-server/entrypoint.sh
new file mode 100644
index 00000000..ce315637
--- /dev/null
+++ b/bitwarden-cli-server/entrypoint.sh
@@ -0,0 +1,183 @@
+#!/bin/bash
+set -euo pipefail
+
+# Color codes for better logging
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+NC='\033[0m' # No Color
+
+# Update log function to use color codes
+log() {
+ echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1"
+}
+
+log_success() {
+ echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1"
+}
+
+log_warning() {
+ echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1"
+}
+
+log_error() {
+ echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1"
+}
+
+log "Starting entrypoint script..."
+log "Current user: $(whoami)"
+log "Current directory: $(pwd)"
+
+# Check required environment variables
+if [[ -z "${BW_HOST:-}" ]]; then
+ log_error "BW_HOST environment variable is required"
+ exit 1
+fi
+
+if [[ -z "${BW_CLIENTID:-}" ]]; then
+ log_error "BW_CLIENTID environment variable is required"
+ exit 1
+fi
+
+if [[ -z "${BW_CLIENTSECRET:-}" ]]; then
+ log_error "BW_CLIENTSECRET environment variable is required"
+ exit 1
+fi
+
+if [[ -z "${BW_PASSWORD:-}" ]]; then
+ log_error "BW_PASSWORD environment variable is required"
+ exit 1
+fi
+
+# Test network connectivity first
+log "Testing connectivity to vaultwarden server: $BW_HOST"
+if ! curl -s --connect-timeout 10 "$BW_HOST" > /dev/null; then
+ log_warning "Cannot reach $BW_HOST - this might be normal if the server doesn't respond to GET requests"
+fi
+
+# Logout first to clear any existing session
+log "Logging out to clear any existing session..."
+bw logout > /dev/null 2>&1 || true # Ignore errors if not logged in
+
+# Configure Bitwarden CLI to use vaultwarden server
+log "Configuring Bitwarden CLI to use server: $BW_HOST"
+
+# Temporarily disable pipefail to capture the output properly
+set +e
+config_output=$(bw config server "$BW_HOST" 2>&1)
+config_result=$?
+set -e
+
+log "Config command result: $config_result"
+log "Config command output: $config_output"
+
+if [[ $config_result -ne 0 ]]; then
+ log_error "Failed to configure server. Error output:"
+ log_error "$config_output"
+ exit 1
+fi
+
+log_success "Server configuration successful"
+
+# Login using API key with retry logic for rate limiting
+log "Logging in to Bitwarden using API key..."
+
+# Retry login with exponential backoff
+max_retries=3
+retry_count=0
+login_success=false
+
+while [[ $retry_count -lt $max_retries ]]; do
+ if [[ $retry_count -gt 0 ]]; then
+ delay=$((retry_count * retry_count * 5)) # 5, 20, 45 seconds
+ log "Rate limited. Waiting ${delay} seconds before retry $((retry_count + 1))/$max_retries..."
+ sleep $delay
+ fi
+
+ set +e
+ login_output=$(bw login --apikey 2>&1)
+ login_result=$?
+ set -e
+
+ log "Login attempt $((retry_count + 1)): result=$login_result"
+ log "Login output: '$login_output'"
+
+ if [[ $login_result -eq 0 ]]; then
+ login_success=true
+ break
+ elif [[ "$login_output" == *"Rate limit exceeded"* ]]; then
+ log_warning "Rate limit exceeded on attempt $((retry_count + 1))"
+ ((retry_count++))
+ else
+ log_error "Failed to login with API key. Error output:"
+ log_error "$login_output"
+ log_error "Please check:"
+ log_error "1. BW_HOST is correct and accessible: $BW_HOST"
+ log_error "2. BW_CLIENTID is valid: ${BW_CLIENTID:0:20}..."
+ log_error "3. BW_CLIENTSECRET is correct"
+ log_error "4. API key is enabled in vaultwarden"
+ exit 1
+ fi
+done
+
+if [[ "$login_success" != "true" ]]; then
+ log_error "Failed to login after $max_retries attempts due to rate limiting"
+ log_error "Please wait a few minutes and try again"
+ exit 1
+fi
+
+log_success "Successfully logged in"
+
+# Now unlock to get the session token
+log "Unlocking vault to get session token..."
+set +e
+unlock_output=$(bw unlock --passwordenv BW_PASSWORD --raw 2>&1)
+unlock_result=$?
+set -e
+
+log "Unlock command result: $unlock_result"
+log "Unlock command output: '$unlock_output'"
+
+if [[ $unlock_result -ne 0 ]]; then
+ log_error "Failed to unlock vault. Error output:"
+ log_error "$unlock_output"
+ log_error "Please check BW_PASSWORD is correct"
+ exit 1
+fi
+
+# Extract session token from unlock output
+export BW_SESSION="$unlock_output"
+log "Session token length: ${#BW_SESSION}"
+
+if [[ -z "$BW_SESSION" ]]; then
+ log_error "Session token is empty after unlock"
+ log_error "Raw unlock output was: '$unlock_output'"
+ exit 1
+fi
+
+log_success "Vault unlocked successfully"
+
+# Sync vault
+log "Syncing vault..."
+bw sync --session "$BW_SESSION" > /dev/null 2>&1 || {
+ log_warning "Sync failed, but continuing anyway"
+}
+
+log_success "Vault sync completed"
+
+# Start the server
+log "Starting Bitwarden CLI server on port 8087..."
+log "Server will be accessible at http://localhost:8087"
+log "Available endpoints:"
+log " - GET /status - Check server status"
+log " - POST /unlock - Unlock vault"
+log " - GET /list/object/items - List vault items"
+log " - GET /object/item/{id} - Get specific item"
+log " - And more..."
+
+# Start bw serve with proper error handling
+exec bw serve --hostname 0.0.0.0 --port 8087 --session "$BW_SESSION" || {
+ log_error "Failed to start Bitwarden CLI server"
+ exit 1
+}
diff --git a/docker-compose.yml b/docker-compose.yml
index b4dfc93c..662f5b2b 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -114,10 +114,11 @@ services:
# Bitwarden Settings
# If you are looking to integrate Skyvern with a password manager (eg Bitwarden), you can use the following environment variables.
# - BITWARDEN_SERVER=http://localhost # OPTIONAL IF YOU ARE SELF HOSTING BITWARDEN
- # - BITWARDEN_SERVER_PORT=8002 # OPTIONAL IF YOU ARE SELF HOSTING BITWARDEN
- # - BITWARDEN_CLIENT_ID=FILL_ME_IN_PLEASE
- # - BITWARDEN_CLIENT_SECRET=FILL_ME_IN_PLEASE
- # - BITWARDEN_MASTER_PASSWORD=FILL_ME_IN_PLEASE
+ # - BITWARDEN_SERVER_PORT=8002 # IF YOU ARE SELF HOSTING BITWARDEN AND USE THIS COMPOSE FILE, PORT IS 8002 UNLESS CHANGED
+ # - SKYVERN_AUTH_BITWARDEN_ORGANIZATION_ID=your-org-id-here
+ # - SKYVERN_AUTH_BITWARDEN_CLIENT_ID=user.your-client-id-here
+ # - SKYVERN_AUTH_BITWARDEN_CLIENT_SECRET=your-client-secret-here
+ # - SKYVERN_AUTH_BITWARDEN_MASTER_PASSWORD=your-master-password-here
# 1Password Integration
# If you are looking to integrate Skyvern with 1Password, you can use the following environment variables.
@@ -158,3 +159,51 @@ services:
depends_on:
skyvern:
condition: service_healthy
+
+# uncomment for local usage of `vaultwarden` & bitwarden-cli - see more at: https://github.com/dani-garcia/vaultwarden
+# First this container needs to be started and configured to sign up, create master password and organization
+# Once created, under SETTINGS/SECURITY/KEYS/API you should be able to get client id and secret for CLI & Skyvern integrations
+# vaultwarden:
+# image: vaultwarden/server:latest-alpine
+# container_name: vaultwarden
+# restart: unless-stopped
+# environment:
+# # DOMAIN: "https://vaultwarden.example.com" # required when using a reverse proxy; your domain; vaultwarden needs to know it's https to work properly with attachments
+# SIGNUPS_ALLOWED: "true" # Deactivate this with "false" after you have created your account so that no strangers can register
+# volumes:
+# - ~/vw-data/:/data/ # the path before the : can be changed
+# ports:
+# - 127.0.0.1:11002:80 # you can replace the 11002 with your preferred port
+
+# Bitwarden CLI Server (provides REST API endpoints for Skyvern)
+# Once you have master password and api credentials, you can set them below and this CLI should start providing secure access for Skyvern to Vaultwarden
+# bitwarden-cli:
+# build:
+# context: ./bitwarden-cli-server
+# dockerfile: Dockerfile
+# environment:
+# # Vaultwarden server URL
+# BW_HOST: "http://vaultwarden"
+# # API credentials for vaultwarden
+# BW_CLIENTID: "user.your-client-id-here"
+# BW_CLIENTSECRET: "your-client-secret-here"
+# # Master password for unlocking vault
+# BW_PASSWORD: "your-master-password-here"
+# ports:
+# # Bind to localhost only for security
+# - "127.0.0.1:8002:8087"
+# restart: unless-stopped
+# healthcheck:
+# test: [ "CMD", "curl", "-f", "http://localhost:8087/status" ]
+# interval: 30s
+# timeout: 10s
+# retries: 5
+# start_period: 30s
+# depends_on:
+# vaultwarden:
+# condition: service_healthy
+# volumes:
+# # Optional: persist Bitwarden CLI config
+# - ~/bitwarden-cli-config:/app/.config
+# labels:
+# - "traefik.enable=false" # Don't expose via reverse proxy
diff --git a/fern/credentials/bitwarden.mdx b/fern/credentials/bitwarden.mdx
index 03bbf351..323d818f 100644
--- a/fern/credentials/bitwarden.mdx
+++ b/fern/credentials/bitwarden.mdx
@@ -40,3 +40,109 @@ Please contact sales@skyvern.com to set up the integration for this step.
### Bitwarden Integration in Open Source
+
+Skyvern can integrate with self-hosted Bitwarden-compatible services like [vaultwarden](https://github.com/dani-garcia/vaultwarden). Since vaultwarden only implements the client API (not the server endpoints), we use a Bitwarden CLI server as a bridge.
+
+#### Architecture
+
+```text
+Skyvern → bw serve (CLI Server) → vaultwarden
+```
+
+The CLI server provides the REST API endpoints that Skyvern expects, while connecting to your vaultwarden instance.
+
+#### Quick Setup
+
+**Step 1: Get vaultwarden API Credentials**
+
+1. Log into your vaultwarden web interface
+2. Go to **Account Settings → Security → API Key**
+3. Click **View API Key**
+4. Save the `client_id` and `client_secret`
+
+**Step 2: Configure Environment Variables**
+
+Add these to your `.env` file:
+
+```bash
+# Skyvern Bitwarden Configuration
+SKYVERN_AUTH_BITWARDEN_ORGANIZATION_ID=your-org-id-here
+SKYVERN_AUTH_BITWARDEN_MASTER_PASSWORD=your-master-password-here
+SKYVERN_AUTH_BITWARDEN_CLIENT_ID=user.your-client-id-here
+SKYVERN_AUTH_BITWARDEN_CLIENT_SECRET=your-client-secret-here
+
+# Vaultwarden Configuration
+BW_HOST=https://your-vaultwarden-server.com
+BW_CLIENTID=${SKYVERN_AUTH_BITWARDEN_CLIENT_ID}
+BW_CLIENTSECRET=${SKYVERN_AUTH_BITWARDEN_CLIENT_SECRET}
+BW_PASSWORD=${SKYVERN_AUTH_BITWARDEN_MASTER_PASSWORD}
+
+# CLI Server Configuration (defaults are correct)
+BITWARDEN_SERVER=http://localhost
+BITWARDEN_SERVER_PORT=8002
+```
+
+**Step 3: Start the Services**
+
+The Bitwarden CLI server is included in the main Docker Compose setup:
+
+```bash
+docker-compose up -d bitwarden-cli
+```
+
+**Step 4: Verify Setup**
+
+Test that the CLI server is working:
+
+```bash
+# Check status
+curl http://localhost:8002/status
+
+# List items from your vault
+curl http://localhost:8002/list/object/items
+```
+
+#### How It Works
+
+1. **vaultwarden** - Your existing password manager server
+2. **bitwarden-cli container** - Runs `bw serve` to provide REST API endpoints
+3. **Skyvern** - Uses the CLI server's REST API to access credentials
+
+#### Available API Endpoints
+
+The CLI server provides these endpoints on port 8002:
+
+- `GET /status` - Server status
+- `POST /unlock` - Unlock vault
+- `GET /list/object/items` - List all items
+- `GET /object/item/{id}` - Get specific item
+- `POST /object/item` - Create new item
+- `GET /object/template/item` - Get item template
+
+#### Troubleshooting
+
+**CLI Server Won't Start**
+
+Check the container logs:
+```bash
+docker-compose logs bitwarden-cli
+```
+
+Common issues:
+- Invalid API credentials
+- Wrong vaultwarden server URL
+- Network connectivity issues
+- Incorrect master password
+
+**Skyvern Can't Connect**
+
+1. Verify CLI server is running: `curl http://localhost:8002/status`
+2. Check that `BITWARDEN_SERVER=http://localhost` and `BITWARDEN_SERVER_PORT=8002`
+3. Ensure proper organization ID and credentials are set
+
+#### Security Notes
+
+- The CLI container runs as a non-root user
+- Only binds to localhost by default for security
+- Vault remains encrypted until explicitly unlocked
+- Uses API key authentication with vaultwarden
diff --git a/skyvern-frontend/src/router.tsx b/skyvern-frontend/src/router.tsx
index cbf15c21..edc930f8 100644
--- a/skyvern-frontend/src/router.tsx
+++ b/skyvern-frontend/src/router.tsx
@@ -26,6 +26,7 @@ import { WorkflowRunOverview } from "./routes/workflows/workflowRun/WorkflowRunO
import { WorkflowRunRecording } from "./routes/workflows/workflowRun/WorkflowRunRecording";
import { WorkflowRunCode } from "@/routes/workflows/workflowRun/WorkflowRunCode";
import { DebugStoreProvider } from "@/store/DebugStoreContext";
+import { CredentialsPage } from "@/routes/credentials/CredentialsPage.tsx";
const router = createBrowserRouter([
{
@@ -201,6 +202,16 @@ const router = createBrowserRouter([
},
],
},
+ {
+ path: "credentials",
+ element: