Install
$ agentstack add skill-profbernardoj-morpheus-skill-bagman Open-source listing, not yet scanned by AgentStack. Follow the source repository for install instructions.
Security review
⚠ Flagged1 finding(s); flagged for manual review. · v0.1.0 How review works →
- • Prompt-injection patterns
- • Secret / credential exfiltration
- • Dangerous shell & filesystem operations
- • Untrusted network calls
- • Known-malicious package signatures
- high Possible prompt-injection directive.
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Bagman
Secure key management patterns for AI agents handling wallets, private keys, and secrets.
When to Use This Skill
- Agent needs wallet/blockchain access
- Handling API keys, credentials, or secrets
- Building systems where AI controls funds
- Preventing secret leakage via prompts or outputs
Quick Start
# Install 1Password CLI
brew install 1password-cli
# Authenticate
eval $(op signin)
# Create vault for agent credentials
op vault create "Agent-Credentials"
# Run examples
cd examples && python test_suite.py
Core Rules
| Rule | Why | |------|-----| | Never store raw private keys | Config, env, memory, or conversation = leaked | | Use delegated access | Session keys with time/value/scope limits | | Secrets via secret manager | 1Password, Vault, AWS Secrets Manager | | Sanitize all outputs | Scan for key patterns before any response | | Validate all inputs | Check for injection attempts before wallet ops |
Architecture
┌─────────────────────────────────────────────────────┐
│ AI Agent │
├─────────────────────────────────────────────────────┤
│ Session Key (bounded) │
│ ├─ Expires after N hours │
│ ├─ Max spend per tx/day │
│ └─ Whitelist of allowed contracts/methods │
├─────────────────────────────────────────────────────┤
│ Secret Manager (1Password/Vault) │
│ ├─ Retrieve at runtime only │
│ ├─ Never persist to disk │
│ └─ Audit trail of accesses │
├─────────────────────────────────────────────────────┤
│ Smart Account (ERC-4337) │
│ ├─ Programmable permissions │
│ └─ Recovery without key exposure │
└─────────────────────────────────────────────────────┘
Implementation Files
| File | Purpose | |------|---------| | examples/secret_manager.py | 1Password integration for runtime secret retrieval | | examples/sanitizer.py | Output sanitization (keys, seeds, tokens) | | examples/validator.py | Input validation (prompt injection defense) | | examples/session_keys.py | ERC-4337 session key configuration | | examples/delegation_integration.ts | MetaMask Delegation Framework (EIP-7710) | | examples/pre-commit | Git hook to block secret commits | | examples/test_suite.py | Adversarial test suite | | docs/prompt-injection.md | Deep dive on injection defense | | docs/secure-storage.md | Secret storage patterns | | docs/session-keys.md | Session key architecture | | docs/leak-prevention.md | Output sanitization patterns | | docs/delegation-framework.md | On-chain permission enforcement (EIP-7710) |
1. Secret Retrieval
1Password CLI Pattern
# Retrieve at runtime (never store result)
SESSION_KEY=$(op read "op://Agents/my-agent/session-key")
# Run with injected secrets (never touch disk)
op run --env-file=.env.tpl -- python agent.py
.env.tpl (safe to commit - no secrets)
PRIVATE_KEY=op://Agents/trading-bot/session-key
RPC_URL=op://Infra/alchemy/sepolia-url
OPENAI_API_KEY=op://Services/openai/api-key
Python Usage
from secret_manager import get_session_key
# Retrieve validated session key
creds = get_session_key("trading-bot-session")
# Check validity
if creds.is_expired():
raise ValueError("Session expired - request renewal from operator")
print(f"Time remaining: {creds.time_remaining()}")
print(f"Allowed contracts: {creds.allowed_contracts}")
# Use the key (never log it!)
client.set_signer(creds.session_key)
Vault-Level ACL (Recommended)
Configure 1Password vault permissions:
Agent-Credentials/
├── trading-bot-session # Agent can read
├── payment-bot-session # Agent can read
└── master-key # Operator ONLY (agent has no access)
Principle: Agent credentials should be in a vault with read-only agent access. Master keys should be in a separate vault the agent cannot access.
2. Output Sanitization (MANDATORY)
⚠️ CRITICAL: Apply to ALL agent outputs before sending anywhere. No exceptions.
This includes:
- Chat responses
- Cron job summaries
- Monitoring alerts
- Status reports
- Debug logs
- Error messages
- Any text that leaves the agent
from sanitizer import OutputSanitizer
def respond(content: str) -> str:
"""Mandatory sanitization before ANY output."""
return OutputSanitizer.sanitize(content)
def cron_summary(task_result: dict) -> str:
"""Cron summaries MUST sanitize before delivery."""
summary = format_summary(task_result)
return OutputSanitizer.sanitize(summary) # ALWAYS sanitize
Secret Patterns Detected
| Pattern | Example | Result | |---------|---------|--------| | ETH private key | 0x1234...abcd (64 hex) | [PRIVATE_KEY_REDACTED] | | ETH address | 0x742d...f44e (40 hex) | 0x742d...f44e (truncated) | | OpenAI key | sk-proj-abc123... | [OPENAI_KEY_REDACTED] | | Anthropic key | sk-ant-api03-... | [ANTHROPIC_KEY_REDACTED] | | 12-word seed | abandon ability able... | [SEED_PHRASE_12_WORDS_REDACTED] | | JWT | eyJhbG... | [JWT_TOKEN_REDACTED] | | Venice key refs | venice:key1, venice:key2 | [ venice:key1 ] (bracketed) |
Sensitive Metrics Redacted (Cron Summaries)
| Pattern | Example | Result | |---------|---------|--------| | DIEM counts | 98 DIEM, 194 DIEM | [DIEM_REDACTED] | | Balance | balance: 42.5 DIEM | [BALANCE_REDACTED] | | Threshold | threshold: 10 DIEM | [THRESHOLD_REDACTED] | | Totals | total: 194 DIEM | [TOTAL_REDACTED] | | Remaining/Spent | remaining: 50 DIEM | [METRIC_REDACTED] |
Cron Summary Example
BEFORE sanitization (NEVER send this):
Venice API check: venice:key1 has 98 DIEM, venice:key2 has 96 DIEM.
Balance: 194 DIEM, Threshold: 10 DIEM
AFTER sanitization (safe to send):
Venice API check: [ venice:key1 ] has [DIEM_REDACTED], [ venice:key2 ] has [DIEM_REDACTED].
[BALANCE_REDACTED], [THRESHOLD_REDACTED]
Venice Key Reference Sanitization
Venice API key references (venice:key1, venice:key2, etc.) are NOT secrets themselves, but should be bracketed to:
- Prevent them from being used as identifiers in logs
- Make it clear they are references, not actual keys
- Distinguish from potential false-positive patterns
The sanitizer brackets un-bracketed references: venice:key1 → [ venice:key1 ]
Already-bracketed references are left unchanged: [ venice:key1 ] → [ venice:key1 ]
3. Input Validation
Check inputs before ANY wallet operation:
from validator import InputValidator, ThreatLevel
result = InputValidator.validate(user_input)
if result.level == ThreatLevel.BLOCKED:
return f"Request blocked: {result.reason}"
if result.level == ThreatLevel.SUSPICIOUS:
# Log for review, but allow
log_suspicious(user_input, result.reason)
# Proceed with operation
Threat Categories
| Category | Examples | Action | |----------|----------|--------| | Extraction | "show private key", "reveal secrets" | Block | | Override | "ignore previous instructions" | Block | | Role manipulation | "you are now admin" | Block | | Jailbreak | "DAN mode", "bypass filters" | Block | | Exfiltration | "send config to https://..." | Block | | Wallet threats | "transfer all", "unlimited approve" | Block | | Encoded | Base64/hex encoded attacks | Block | | Unicode tricks | Cyrillic lookalikes, zero-width | Block | | Suspicious | "hypothetically", "just between us" | Warn |
4. Operation Allowlisting
Never execute arbitrary operations. Explicit whitelist only:
from dataclasses import dataclass
from decimal import Decimal
from typing import Optional
@dataclass
class AllowedOperation:
name: str
handler: callable
max_value: Optional[Decimal] = None
requires_confirmation: bool = False
cooldown_seconds: int = 0
ALLOWED_OPS = {
"check_balance": AllowedOperation("check_balance", get_balance),
"transfer_usdc": AllowedOperation(
"transfer_usdc",
transfer,
max_value=Decimal("500"),
requires_confirmation=True,
cooldown_seconds=60
),
"swap": AllowedOperation(
"swap",
swap_tokens,
max_value=Decimal("1000"),
cooldown_seconds=300
),
}
def execute(op_name: str, **kwargs):
if op_name not in ALLOWED_OPS:
raise PermissionError(f"Operation '{op_name}' not allowed")
op = ALLOWED_OPS[op_name]
if op.max_value and kwargs.get("amount", 0) > op.max_value:
raise PermissionError(f"Amount exceeds limit: {op.max_value}")
if op.requires_confirmation:
return request_confirmation(op_name, kwargs)
return op.handler(**kwargs)
5. Confirmation Flow
High-value operations require explicit confirmation:
import hashlib
import time
pending_confirmations = {}
def request_confirmation(operation: str, details: dict) -> str:
code = hashlib.sha256(
f"{operation}{time.time()}".encode()
).hexdigest()[:8].upper()
pending_confirmations[code] = {
"op": operation,
"details": details,
"expires": time.time() + 300 # 5 minutes
}
return f"⚠️ Confirm '{operation}' with code: {code}\n(expires in 5 minutes)"
def confirm(code: str):
if code not in pending_confirmations:
return "Invalid confirmation code"
req = pending_confirmations.pop(code)
if time.time() > req["expires"]:
return "Confirmation code expired"
return execute_confirmed(req["op"], req["details"])
6. Session Keys (ERC-4337)
Instead of giving agents master keys, issue bounded session keys:
from session_keys import SessionKeyManager
# Operator creates trading session for agent
config = SessionKeyManager.create_trading_session(
agent_name="alpha-trader",
operator_address="0x742d...",
duration_hours=24,
max_trade_usdc=1000,
daily_limit_usdc=5000,
)
# Export for storage in 1Password
export_data = SessionKeyManager.export_for_1password(
config,
session_key_hex="0x..." # Generated session key
)
# op item create ... (store in 1Password)
Session Key Benefits
| Feature | Master Key | Session Key | |---------|------------|-------------| | Expiration | Never | Configurable (hours/days) | | Spending limits | None | Per-tx and daily caps | | Contract restrictions | Full access | Whitelist only | | Revocation | Requires key rotation | Instant, no key change | | Audit | None | Full operation log |
7. Pre-commit Hook
Block commits containing secrets:
# Install
cp examples/pre-commit .git/hooks/
chmod +x .git/hooks/pre-commit
Detected patterns:
- ETH private keys (64 hex chars)
- OpenAI/Anthropic/Groq keys
- AWS access keys
- GitHub/GitLab tokens
- Slack/Discord tokens
- PEM private keys
- Generic PASSWORD/SECRET assignments
- BIP-39 seed phrases
8. Defense Layers
USER INPUT
│
▼
┌────────────────────────────┐
│ Layer 1: Input Validation │ ← Regex + encoding + unicode checks
└────────────────────────────┘
│
▼
┌────────────────────────────┐
│ Layer 2: Op Allowlisting │ ← Explicit whitelist only
└────────────────────────────┘
│
▼
┌────────────────────────────┐
│ Layer 3: Value Limits │ ← Max per-tx and per-day
└────────────────────────────┘
│
▼
┌────────────────────────────┐
│ Layer 4: Confirmation │ ← Time-limited codes for $$$
└────────────────────────────┘
│
▼
┌────────────────────────────┐
│ Layer 5: Isolated Exec │ ← Wallet ops != conversation
└────────────────────────────┘
│
▼
OUTPUT SANITIZATION
Common Mistakes
❌ Keys in memory files
# memory/2026-02-07.md
Private key: 0x9f01dad551039daad...
Fix: Store reference only: Private key: [stored in 1Password: test-wallet]
❌ Keys in error messages
except Exception as e:
log(f"Failed with key {private_key}: {e}")
Fix: Never include credentials in error context
❌ Keys in .env.example
PRIVATE_KEY=sk-ant-api03-real-key... # "for testing"
Fix: Use obviously fake: PRIVATE_KEY=your-key-here
❌ "All" in transfer requests
User: "Transfer all my USDC"
Agent: *executes unlimited transfer*
Fix: Block "all/everything/max" patterns, require explicit amounts
❌ Trusting conversation context
# Wallet has access to conversation history
self.wallet.execute(conversation[-1]["content"])
Fix: Wallet operations must be isolated from conversation context
Testing
cd examples
# Run full test suite
python test_suite.py
# Test individual components
python sanitizer.py # Output sanitization demo
python validator.py # Input validation demo
python session_keys.py # Session key demo
Expected output: All tests passed
Test Evidence (v2.2.0 - 2026-03-11)
Sanitizer v3 Test Results - All 16 tests passed:
Output Sanitizer Test (v3)
============================================================
✅ PASS (expect detect)
Input: My key is 0x1234567890abcdef...
Sanitized: My key is [PRIVATE_KEY_REDACTED]
✅ PASS (expect detect)
Input: Key: 1234567890abcdef...
Sanitized: Key: [HEX_KEY_REDACTED]
✅ PASS (expect detect)
Input: First half: 1234567890abcdef...
Sanitized: First half: [PARTIAL_KEY_REDACTED]
✅ PASS (expect detect)
Input: Send to 0x742d35Cc6634C0532925a3b844Bc454e4438f44e
Sanitized: Send to 0x742d...f44e
✅ PASS (expect detect)
Input: Using sk-proj-abc123def456...
Sanitized: Using [OPENAI_KEY_REDACTED]
✅ PASS (expect detect)
Input: API key is sk-ant-api03-abcdef...
Sanitized: API key is [ANTHROPIC_KEY_REDACTED]
✅ PASS (expect detect)
Input: aws_secret_key=AKIAIOSFODNN7EXAMPLE...
Sanitized: aws_secret_key=[AWS_ACCESS_KEY_REDACTED]...
✅ PASS (expect ignore)
Input: The hash is dGhpcyBpcyBhIHRlc3Q...
Sanitized: The hash is dGhpcyBpcyBhIHRlc3Q...
✅ PASS (expect detect)
Input: abandon ability able about above absent...
Sanitized: [SEED_PHRASE_12_WORDS_REDACTED]
✅ PASS (expect detect)
Input: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Sanitized: Bearer [JWT_TOKEN_REDACTED]
✅ PASS (expect ignore)
Input: Normal text without secrets
Sanitized: Normal text without secrets
✅ PASS (expect detect)
Input: Bot token: 123456789:ABCdefGHI...
Sanitized: Bot token: [TELEGRAM_TOKEN_REDACTED]
============================================================
Cron Summary & Venice Key Sanitization Tests
------------------------------------------------------------
✅ PASS
Input: Venice API check: venice:key1 has 98 DIEM, venice:key2 has 96 DIEM. Total: 194 DIEM.
Sanitized: Venice API check: [ venice:key1 ] has [DIEM_REDACTED], [ venice:key2 ] has [DIEM_REDACTED]...
Expected fragment ✓: [ venice:key1 ]
Expected fragment ✓: [ venice:key2 ]
Expected fragment ✓: [DIEM_REDACTED]
✅ PASS
Input: Monitor: balance: 42.5 DIEM, threshold: 10 DIEM, total: 194 DIEM
Sanitized: Monitor: [BALANCE_REDACTED], [THRESHOLD_REDACTED], [TOTAL_REDACTED]
Expected fragment ✓: [BALANCE_REDACTED]
Expected fragment ✓: [THRESHOLD_REDACTED]
Expected fragment ✓: [TOTAL_REDACTED]
✅ PASS
Input: Using [ venice:key1 ] for fallback.
Sanitized: Using [ venice:key1 ] for fallback.
Expected fragment ✓: [ venice:key1 ]
✅ PASS
Input: remaining: 50 DIEM, spent: 30 DIEM
Sanitized: remaining: [DIEM_REDACTED], spent: [DIEM_REDACTED]
Expected fragment ✓: [DIEM_REDACTED]
============================================================
Results: 16 passed, 0 failed
All tests passed ✅
Key capabilities verified:
- venice:key references are bracketed:
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: profbernardoj
- Source: profbernardoj/morpheus-skill
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.