"""Plugin: canary token generator -- plant realistic fake credentials.""" from __future__ import annotations import json import secrets import string from datetime import datetime, timezone from derp.plugin import command _MAX_PER_CHANNEL = 50 def _gen_token() -> str: """40-char hex string (looks like API key / SHA1).""" return secrets.token_hex(20) def _gen_aws() -> dict[str, str]: """AWS-style keypair: AKIA + 16 alnum access key, 40-char base64 secret.""" chars = string.ascii_uppercase + string.digits access = "AKIA" + "".join(secrets.choice(chars) for _ in range(16)) # 30 random bytes -> 40-char base64 secret = secrets.token_urlsafe(30) return {"access_key": access, "secret_key": secret} def _gen_basic() -> dict[str, str]: """Random user:pass pair.""" alnum = string.ascii_lowercase + string.digits user = "svc" + "".join(secrets.choice(alnum) for _ in range(5)) pw = secrets.token_urlsafe(16) return {"user": user, "pass": pw} _TYPES = { "token": "API token (40-char hex)", "aws": "AWS keypair (AKIA access + secret)", "basic": "Username:password pair", } def _load(bot, channel: str) -> dict: """Load canary store for a channel.""" raw = bot.state.get("canary", channel) if not raw: return {} try: return json.loads(raw) except (json.JSONDecodeError, TypeError): return {} def _save(bot, channel: str, store: dict) -> None: """Persist canary store for a channel.""" bot.state.set("canary", channel, json.dumps(store)) def _format_token(entry: dict) -> str: """Format a canary entry for display.""" ttype = entry["type"] value = entry["value"] if ttype == "aws": return f"Access: {value['access_key']} Secret: {value['secret_key']}" if ttype == "basic": return f"{value['user']}:{value['pass']}" return value @command("canary", help="Canary tokens: !canary gen [type]