| name | otp |
| description | Enable agents and skills to challenge users for fresh two-factor authentication proof before executing sensitive actions. Use this for identity verification in approval workflows - deploy commands, financial operations, data access, admin operations, and change control. |
| metadata | {"openclaw":{"emoji":"🔐","homepage":"https://github.com/ryancnelson/otp-challenger","requires":{"bins":["jq","python3"],"anyBins":["oathtool","node"]},"install":[{"id":"jq","kind":"brew","formula":"jq","bins":["jq"],"label":"Install jq via Homebrew","os":["darwin","linux"]},{"id":"python3","kind":"brew","formula":"python3","bins":["python3"],"label":"Install Python 3 via Homebrew","os":["darwin","linux"]},{"id":"oathtool","kind":"brew","formula":"oath-toolkit","bins":["oathtool"],"label":"Install OATH Toolkit via Homebrew","os":["darwin","linux"]}]}} |
OTP Identity Challenge Skill
Purpose: Enable agents and skills to challenge users for fresh two-factor authentication proof before executing sensitive actions.
What This Is For
This skill provides identity verification for approval workflows. When your agent needs to execute a command with change-control concerns (deploying code, modifying infrastructure, accessing sensitive data, financial transactions), it can challenge the user to prove their identity with a time-based one-time password (TOTP).
This is not about securing your chat channel—it's about verifying identity before specific actions.
Use Cases
- Deploy commands: Require fresh 2FA before
kubectl apply or terraform 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.sh with the user's ID and their 6-digit code
- The skill validates the code against your TOTP secret
- If valid, verification state is recorded with a timestamp
- Other scripts can check
check-status.sh to 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:
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"
Note: oathtool is optional - the skill includes a built-in TOTP generator, but oathtool provides additional validation.
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
- The base32 secret for manual entry
- Configuration instructions
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
security:
otp:
secret: "YOUR_BASE32_SECRET_HERE"
accountName: "user@example.com"
issuer: "OpenClaw"
intervalHours: 24
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
After configuring the secret, test that everything works:
./verify.sh "testuser" "123456"
./check-status.sh "testuser"
./verify.sh "testuser" "000000"
5. Optional: Run Test Suite
Full test suite available at: https://github.com/ryancnelson/otp-challenger
Usage
For Skill Authors
When your skill needs to verify user identity:
#!/bin/bash
source ../otp/verify.sh
USER_ID="$1"
OTP_CODE="$2"
if ! verify_otp "$USER_ID" "$OTP_CODE"; then
echo "❌ OTP verification failed. Run: /otp <code>"
exit 1
fi
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...
Scripts
verify.sh <user_id> <code> - Verify OTP code and update state
check-status.sh <user_id> - Check if user verification is still valid
generate-secret.sh <account_name> - Generate new TOTP secret
get-current-code.sh <secret> - Get current valid code (testing only)
Configuration
Set these in your OpenClaw config or environment:
OTP_SECRET - Base32 TOTP secret (required)
OTP_INTERVAL_HOURS - Verification expiry (default: 24)
OTP_GRACE_PERIOD_MINUTES - Grace period after expiry (default: 15)
OTP_STATE_FILE - State file path (default: memory/otp-state.json)
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
intervalHours reasonable (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)
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:
- oathtool - provides additional TOTP validation (skill has built-in generator)
- Node.js - only needed for
totp.mjs standalone CLI
- bats - for running test suite (see full repo)
Examples
Deploy Command with OTP
#!/bin/bash
source ../otp/verify.sh
USER="$1"
CODE="$2"
SERVICE="$3"
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..."
Payment Authorization
#!/bin/bash
source ../otp/check-status.sh
USER="$1"
AMOUNT="$2"
RECIPIENT="$3"
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"
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.
Troubleshooting
"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_SECRET environment variable
- Or configure
security.otp.secret in OpenClaw config
"State file not found"
- First verification creates the file
- Check
memory/ directory permissions
License
MIT
Contributing
Issues and PRs welcome at: https://github.com/ryancnelson/otp-challenger
See Also