otp-challenger
This skill provides OTP verification for TOTP and YubiKey with helper scripts for secret management and status checks. It reads secrets (OTP_SECRET, YUBIKEY_SECRET_KEY), calls https://api.yubico.com/wsapi/2.0/verify, and executes user-configurable hooks ($OTP_FAILURE_HOOK) that run arbitrary shell commands.
OTP Identity Challenge
Identity verification for approval workflows in OpenClaw.
This skill enables agents to challenge users for fresh two-factor authentication proof before executing sensitive actions—deployments, financial operations, data access, or any command with change-control concerns.
Purpose
This is not chat channel security—it's identity verification for specific actions.
When your agent needs to execute something sensitive, it challenges the user to prove their identity with a time-based one-time password (TOTP) or YubiKey, providing cryptographic proof they authorized the action.
Quick Start
# Install via ClawHub
clawhub install otp
# Or clone manually
cd ~/.openclaw/skills
git clone https://github.com/ryancnelson/otp-skill.git otp
Use Cases
- Deploy commands: Require fresh 2FA before
kubectl applyorterraform apply - Financial operations: Verify identity before wire transfers or payment approvals
- Data access: Challenge before exporting customer data or PII
- Admin operations: Verify before user account modifications or permission changes
- Change control: Enforce approval workflows with cryptographic proof of identity
How It Works
- Your agent or skill calls
verify.shwith the user's ID and their code - The skill validates the code (TOTP or YubiKey)
- If valid, verification state is recorded with a timestamp
- Other scripts can check
check-status.shto see if verification is still fresh - Verification expires after a configured interval (default: 24 hours)
Installation
Via ClawHub (recommended)
clawhub install otp
Manual
cd ~/.openclaw/skills
git clone https://github.com/ryancnelson/otp-skill.git otp
Check Dependencies
After installation, verify required dependencies:
# Check what's available
which jq && echo "✅ jq available" || echo "❌ Install: brew install jq"
which python3 && echo "✅ python3 available" || echo "❌ Install: brew install python3"
which oathtool && echo "✅ oathtool available" || echo "❌ Install: brew install oath-toolkit"
which qrencode && echo "✅ qrencode available" || echo "⚠️ Optional: brew install qrencode (for QR codes)"
Note: oathtool is optional - the skill includes a built-in TOTP generator, but oathtool provides additional validation.
TOTP Setup
1. Generate a TOTP Secret
Use the included secret generator:
cd ~/.openclaw/skills/otp
./generate-secret.sh "your-email@example.com"
This will display:
- A QR code to scan with your authenticator app (if
qrencodeis installed) - The base32 secret for manual entry
- Configuration instructions
Note: QR code display requires the qrencode utility. Install with brew install qrencode (macOS) or apt install qrencode (Ubuntu). Without it, you'll only see the base32 secret for manual entry.
Alternative: Use any other TOTP secret generator. You need a base32-encoded secret.
2. Scan QR Code
Add the secret to your authenticator app:
- Google Authenticator
- Authy
- 1Password
- Bitwarden
- Any RFC 6238 compatible app
3. Store the Secret
Option A: In your OpenClaw config
# ~/.openclaw/config.yaml
security:
otp:
secret: "YOUR_BASE32_SECRET_HERE"
accountName: "user@example.com"
issuer: "OpenClaw"
intervalHours: 24 # Re-verify every 24 hours
Option B: In environment variable
export OTP_SECRET="YOUR_BASE32_SECRET_HERE"
Option C: In 1Password (recommended for security)
security:
otp:
secret: "op://vault/OpenClaw OTP/totp"
4. Test Your Setup
# Get current code from your authenticator app (6 digits)
./verify.sh "testuser" "123456" # Replace with actual code
# Should show: ✅ OTP verified for testuser (valid for 24 hours)
# Check verification status
./check-status.sh "testuser"
# Should show: ✅ Valid for 23 more hours
YubiKey Setup (Alternative to TOTP)
If you have a YubiKey, you can use touch-to-verify instead of typing 6-digit codes.
1. Get Yubico API Credentials
- Go to https://upgrade.yubico.com/getapikey/
- Enter your email address
- Touch your YubiKey to generate an OTP in the form field
- Submit — you'll receive a Client ID and Secret Key
Troubleshooting "Invalid OTP" during registration:
If Yubico's site rejects your OTP, your key may not be registered with Yubico's cloud:
- Install YubiKey Manager from https://www.yubico.com/support/download/yubikey-manager/
- Open it, go to Applications → OTP → Configure Slot 1
- Select Yubico OTP and check "Upload to Yubico"
- This re-registers your key with Yubico's servers
- Try getting API credentials again
2. Configure Credentials
Option A: In your OpenClaw config
# ~/.openclaw/config.yaml
security:
yubikey:
clientId: "12345"
secretKey: "your-base64-secret-key"
Option B: In environment variables
export YUBIKEY_CLIENT_ID="12345"
export YUBIKEY_SECRET_KEY="your-base64-secret-key"
3. Test YubiKey Verification
# Touch your YubiKey when prompted
./verify.sh "testuser" "cccccccccccc..." # paste YubiKey output
# Should show: ✅ YubiKey verified for testuser (valid for 24 hours)
Using Both TOTP and YubiKey
You can configure both methods. The script auto-detects which to use based on the code format:
- 6 digits → TOTP validation
- 44 characters → YubiKey validation
This lets you use TOTP on your phone and YubiKey at your desk.
Configuration Reference
Set these in your OpenClaw config or environment:
| Variable | Description | Default |
|---|---|---|
OTP_SECRET | Base32 TOTP secret | (required for TOTP) |
YUBIKEY_CLIENT_ID | Yubico API client ID | (required for YubiKey) |
YUBIKEY_SECRET_KEY | Yubico API secret key (base64) | (required for YubiKey) |
OTP_INTERVAL_HOURS | Verification expiry | 24 |
OTP_GRACE_PERIOD_MINUTES | Grace period after expiry | 15 |
OTP_STATE_FILE | State file path | memory/otp-state.json |
OTP_MAX_FAILURES | Failed attempts before rate limiting | 3 |
OTP_FAILURE_HOOK | Script to run on failures | (none) |
OTP_AUDIT_LOG | Audit log file path | (none) |
Usage Examples
For Skill Authors
When your skill needs to verify user identity:
#!/bin/bash
# In your sensitive-action.sh
# Source the OTP skill
source ../otp/verify.sh
USER_ID="$1"
OTP_CODE="$2"
# Challenge the user
if ! verify_otp "$USER_ID" "$OTP_CODE"; then
echo "❌ OTP verification failed. Run: /otp <code>"
exit 1
fi
# Proceed with sensitive action
echo "✅ Identity verified. Proceeding with deployment..."
kubectl apply -f production.yaml
Checking Verification Status
#!/bin/bash
source ../otp/check-status.sh
if check_otp_status "$USER_ID"; then
echo "✅ User verified within last 24 hours"
else
echo "⚠️ Verification expired. User must verify again."
fi
For End Users
When prompted by a skill:
User: deploy to production
Agent: 🔒 This action requires identity verification. Please provide your OTP code.
User: /otp 123456
Agent: ✅ Identity verified. Deploying to production...
Deploy Command with OTP
#!/bin/bash
# skills/deploy/production.sh
source ../otp/verify.sh
USER="$1"
CODE="$2"
SERVICE="$3"
# Require OTP for production deploys
if ! verify_otp "$USER" "$CODE"; then
echo "🔒 Production deployment requires OTP verification"
echo "Usage: deploy production <service> --otp <code>"
exit 1
fi
echo "✅ Identity verified. Deploying $SERVICE to production..."
# ... deployment logic ...
Payment Authorization
#!/bin/bash
# skills/finance/transfer.sh
source ../otp/check-status.sh
USER="$1"
AMOUNT="$2"
RECIPIENT="$3"
# Check if user verified recently
if ! check_otp_status "$USER"; then
echo "💳 Large transfer requires fresh identity verification"
echo "Please verify with: /otp <code>"
exit 1
fi
echo "✅ Processing transfer of \$$AMOUNT to $RECIPIENT"
# ... transfer logic ...
Failure Hook - Alert on Failed Attempts
Configure OTP_FAILURE_HOOK to run a script when verification fails:
# Example: Shut down OpenClaw after 3 failures (impersonation defense)
export OTP_FAILURE_HOOK="/path/to/shutdown-if-impersonator.sh"
The hook receives these environment variables:
OTP_HOOK_EVENT-VERIFY_FAIL(bad code) orRATE_LIMIT_HIT(too many failures)OTP_HOOK_USER- The user ID that failedOTP_HOOK_FAILURE_COUNT- Number of consecutive failuresOTP_HOOK_TIMESTAMP- ISO 8601 timestamp
#!/bin/bash
# shutdown-if-impersonator.sh
if [ "$OTP_HOOK_EVENT" = "RATE_LIMIT_HIT" ]; then
echo "🚨 OTP rate limit hit for $OTP_HOOK_USER at $OTP_HOOK_TIMESTAMP" | \
slack-notify "#alerts"
pkill -f openclaw
fi
Security Considerations
What This Protects Against
- Session hijacking: Even if someone steals your chat session, they can't execute protected actions without your physical device
- Replay attacks: Codes are time-based and expire quickly
- Unauthorized actions: Cryptographic proof that you authorized the specific action
What This Doesn't Protect Against
- Compromised agent: If someone has shell access to your OpenClaw instance, they can read the secret
- Phishing: Users can still be tricked into providing codes to malicious actors
- Device theft: If someone has your authenticator device, they can generate codes
Best Practices
- Store secrets securely: Use 1Password/Bitwarden references, not plaintext in config
- Short expiry: Keep
intervalHoursreasonable (8-24 hours) - Audit logs: Skills should log verification events
- Scope carefully: Only require OTP for truly sensitive actions
- Clear prompts: Always tell users WHY they're being asked for OTP
Technical Details
TOTP Implementation
- Standard: RFC 6238 (Time-Based One-Time Password)
- Algorithm: HMAC-SHA1 (standard TOTP)
- Time window: 30 seconds (configurable)
- Code length: 6 digits
- Clock skew: ±1 window tolerance (90 seconds total)
YubiKey OTP Implementation
- API: Yubico Cloud (api.yubico.com)
- Protocol: HMAC-SHA1 signed requests
- OTP Format: 44-character ModHex (alphabet: cbdefghijklnrtuv)
- Public ID: First 12 characters identify the physical key
- Replay Protection: Handled by Yubico servers
- Network: Requires HTTPS to api.yubico.com
State Management
Verification state is stored in memory/otp-state.json:
{
"verifications": {
"user@example.com": {
"verifiedAt": 1706745600000,
"expiresAt": 1706832000000
}
}
}
No secrets are stored in state—only timestamps.
Dependencies
Required:
- jq - for JSON state file manipulation
- python3 - for secure YAML config parsing
Optional:
- qrencode - displays QR code during secret generation (without it, you'll only get the base32 secret)
- oathtool - provides additional TOTP validation (skill has built-in generator)
- Node.js - only needed for
totp.mjsstandalone CLI - bats - for running test suite
Troubleshooting
TOTP Issues
"OTP verification failed"
- Check your authenticator app has the correct secret
- Verify system time is synchronized (NTP)
- Try the code from the previous/next 30-second window
"Secret not configured"
- Set
OTP_SECRETenvironment variable - Or configure
security.otp.secretin OpenClaw config
"State file not found"
- First verification creates the file
- Check
memory/directory permissions
YubiKey Issues
"YUBIKEY_CLIENT_ID not set"
- Get API credentials from https://upgrade.yubico.com/getapikey/
- Set
YUBIKEY_CLIENT_IDandYUBIKEY_SECRET_KEYin environment or config
"Invalid OTP" when getting API key
- Your YubiKey may not be registered with Yubico's cloud
- Use YubiKey Manager to reconfigure Slot 1 with "Upload to Yubico" checked
"YubiKey API signature mismatch"
- Check that
YUBIKEY_SECRET_KEYis correct (should be base64) - Try regenerating API credentials from Yubico
"Failed to contact Yubico API"
- Check internet connectivity
- Yubico API requires HTTPS (port 443)
- Try:
curl -I https://api.yubico.com/wsapi/2.0/verify
"YubiKey OTP already used"
- Each YubiKey press generates a unique code
- Touch your YubiKey again to generate a fresh code
- Don't copy-paste old codes
Philosophy
OTP should be invisible when not needed, obvious when required.
Don't force users to verify for every action—that trains them to treat it as a meaningless ritual. Only challenge when:
- The action has real-world consequences
- The risk justifies the friction
- You need cryptographic proof of intent
Think of OTP like sudo—you use it before commands that matter, not every command.
Agent Documentation
For agent/skill integration details, see SKILL.md.
Full Source & Tests
Complete repository with test suite: https://github.com/ryancnelson/otp-challenger
See Also
- RFC 6238 - TOTP specification
- Yubico OTP - YubiKey OTP documentation
License
MIT
Author
Ryan Nelson
Contributing
Issues and PRs welcome at: https://github.com/ryancnelson/otp-challenger