deception technology

Implementing Deception-Based Detection with Canarytoken

Deploy and monitor Canary Tokens via the Thinkst Canary API for deception-based breach detection using web bug tokens, DNS tokens, document tokens, and AWS key tokens.

breach-detectioncanarytokendeceptionearly-warninghoneytokensthinkst-canarytripwire
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

Overview

Canary Tokens are lightweight tripwire mechanisms that alert when an attacker accesses a resource. This skill uses the Thinkst Canary REST API to programmatically create tokens (web bugs, DNS tokens, MS Word documents, AWS API keys), deploy them to strategic locations, monitor for triggered alerts, and generate deception coverage reports.

When to Use

  • When deploying or configuring implementing deception based detection with canarytoken capabilities in your environment
  • When establishing security controls aligned to compliance requirements
  • When building or improving security architecture for this domain
  • When conducting security assessments that require this implementation

Prerequisites

  • Thinkst Canary Console or canarytokens.org account
  • API auth token from Canary Console
  • Python 3.9+ with requests
  • File system access for deploying document and file tokens

Steps

  1. Authenticate to the Canary Console API using auth_token
  2. Create web bug (HTTP) tokens for embedding in documents and web pages
  3. Create DNS tokens for monitoring DNS resolution attempts
  4. Create MS Word document tokens for file share deployment
  5. List all active tokens and their trigger history
  6. Query recent alerts for triggered token events
  7. Generate deception coverage report with deployment recommendations

Expected Output

  • JSON report listing all deployed Canary Tokens, trigger history, alert details, and coverage analysis
  • Deployment map showing token types across network segments
Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 1

api-reference.md2.9 KB

API Reference — Implementing Deception-Based Detection with Canarytoken

Libraries Used

  • requests: HTTP client for Thinkst Canary Console REST API
  • json: JSON serialization for audit reports

CLI Interface

python agent.py --console abc123 --auth-token TOKEN ping
python agent.py --console abc123 --auth-token TOKEN list
python agent.py --console abc123 --auth-token TOKEN alerts
python agent.py --console abc123 --auth-token TOKEN create --kind http --memo "Web server token"
python agent.py --console abc123 --auth-token TOKEN create --kind dns --memo "DNS honeypot"
python agent.py --console abc123 --auth-token TOKEN coverage
python agent.py --console abc123 --auth-token TOKEN full

Core Functions

CanaryClient(console_domain, auth_token) — API client

Base URL: https://{console_domain}.canary.tools/api/v1 Auth: auth_token parameter on every request.

create_token(kind, memo, **kwargs) — Create Canarytoken

POST /canarytoken/create with kind, memo, auth_token. For doc-msword: uploads file via multipart form with MIME type application/vnd.openxmlformats-officedocument.wordprocessingml.document.

list_tokens() — List all deployed tokens

GET /canarytokens/fetch. Returns array of token objects with kind, memo, url, enabled.

get_alerts(newer_than) — Fetch triggered token alerts

GET /incidents/all. Optional newer_than timestamp filter. Returns src_host (source IP), description, timestamp, acknowledged status.

ack_alert(incident_id) — Acknowledge an alert

POST /incident/acknowledge with incident ID.

audit_token_coverage(client) — Coverage analysis

Calculates: tokens by kind, triggered vs untriggered, missing token types, coverage score as percentage of TOKEN_KINDS deployed.

full_audit(client) — Comprehensive deception audit

Canary Console API Endpoints

Endpoint Method Description
/ping GET Test API connectivity
/canarytoken/create POST Create new token
/canarytokens/fetch GET List all tokens
/canarytoken/fetch GET Get specific token
/canarytoken/delete POST Delete a token
/incidents/all GET Fetch all alerts
/canarytoken/incidents GET Alerts for specific token
/incident/acknowledge POST Acknowledge alert

Supported Token Types

Kind Description
http Web bug — triggers on HTTP request
dns DNS token — triggers on DNS resolution
doc-msword MS Word document with embedded beacon
pdf-acrobat-reader PDF with embedded beacon
aws-id Fake AWS API key pair
web-image Image with tracking pixel
cloned-web Cloned website detection
qr-code QR code with tracking URL
sensitive-cmd Triggers on command execution
windows-dir Windows folder open detection

Dependencies

  • requests >= 2.28.0
  • Thinkst Canary Console account with API auth token

Scripts 1

agent.py8.1 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Agent for deploying and monitoring Canary Tokens via the Thinkst Canary API."""

import json
import argparse
from datetime import datetime

try:
    import requests
except ImportError:
    requests = None

TOKEN_KINDS = {
    "http": "http",
    "dns": "dns",
    "doc-msword": "doc-msword",
    "pdf-acrobat-reader": "pdf-acrobat-reader",
    "web-image": "web-image",
    "cloned-web": "cloned-web",
    "aws-id": "aws-id",
    "qr-code": "qr-code",
    "sql": "sql",
    "svn": "svn",
    "smtp": "smtp",
    "windows-dir": "windows-dir",
    "sensitive-cmd": "sensitive-cmd",
}


class CanaryClient:
    """Client for the Thinkst Canary Console REST API."""

    def __init__(self, console_domain, auth_token):
        self.base_url = f"https://{console_domain}.canary.tools/api/v1"
        self.auth_token = auth_token

    def _get(self, endpoint, params=None):
        params = params or {}
        params["auth_token"] = self.auth_token
        resp = requests.get(f"{self.base_url}{endpoint}", params=params, timeout=30)
        resp.raise_for_status()
        return resp.json()

    def _post(self, endpoint, data=None, files=None):
        data = data or {}
        data["auth_token"] = self.auth_token
        resp = requests.post(f"{self.base_url}{endpoint}", data=data, files=files, timeout=30)
        resp.raise_for_status()
        return resp.json()

    def ping(self):
        """Test API connectivity."""
        return self._get("/ping")

    def create_token(self, kind, memo, **kwargs):
        """Create a new Canarytoken.

        Args:
            kind: Token type (http, dns, doc-msword, aws-id, etc.)
            memo: Description/reminder for the token
            **kwargs: Additional parameters (e.g., cloned_web for cloned-web type)
        """
        data = {"kind": kind, "memo": memo}
        data.update(kwargs)
        files = None
        if kind == "doc-msword" and "doc" in kwargs:
            doc_path = kwargs.pop("doc")
            data.pop("doc", None)
            files = {"doc": (doc_path, open(doc_path, "rb"),
                            "application/vnd.openxmlformats-officedocument.wordprocessingml.document")}
        return self._post("/canarytoken/create", data=data, files=files)

    def list_tokens(self):
        """List all Canarytokens on the console."""
        return self._get("/canarytokens/fetch")

    def get_token(self, canarytoken):
        """Get details for a specific token."""
        return self._get("/canarytoken/fetch", params={"canarytoken": canarytoken})

    def delete_token(self, canarytoken):
        """Delete a Canarytoken."""
        return self._post("/canarytoken/delete", data={"canarytoken": canarytoken})

    def get_alerts(self, newer_than=None):
        """Fetch recent alerts from triggered tokens."""
        params = {}
        if newer_than:
            params["newer_than"] = newer_than
        return self._get("/incidents/all", params=params)

    def get_token_alerts(self, canarytoken):
        """Fetch alerts for a specific token."""
        return self._get("/canarytoken/incidents", params={"canarytoken": canarytoken})

    def ack_alert(self, incident_id):
        """Acknowledge an alert."""
        return self._post("/incident/acknowledge", data={"incident": incident_id})


def create_deployment(client, deployment_plan):
    """Create multiple tokens based on a deployment plan."""
    results = []
    for token_spec in deployment_plan:
        kind = token_spec.get("kind", "http")
        memo = token_spec.get("memo", f"Canarytoken - {kind}")
        extra = {k: v for k, v in token_spec.items() if k not in ("kind", "memo")}
        try:
            resp = client.create_token(kind, memo, **extra)
            results.append({
                "kind": kind,
                "memo": memo,
                "status": "CREATED",
                "canarytoken": resp.get("canarytoken", {}).get("canarytoken", ""),
                "url": resp.get("canarytoken", {}).get("url", ""),
            })
        except Exception as e:
            results.append({"kind": kind, "memo": memo, "status": "FAILED", "error": str(e)})
    return results


def audit_token_coverage(client):
    """Audit deployed token coverage and generate report."""
    tokens_resp = client.list_tokens()
    tokens = tokens_resp.get("tokens", [])
    alerts_resp = client.get_alerts()
    alerts = alerts_resp.get("incidents", [])

    kind_counts = {}
    triggered_tokens = set()
    for token in tokens:
        kind = token.get("kind", "unknown")
        kind_counts[kind] = kind_counts.get(kind, 0) + 1

    for alert in alerts:
        triggered_tokens.add(alert.get("canarytoken", ""))

    untriggered = [t for t in tokens if t.get("canarytoken", "") not in triggered_tokens]
    recommended_types = []
    for kind_name in TOKEN_KINDS:
        if kind_name not in kind_counts:
            recommended_types.append(kind_name)

    return {
        "total_tokens": len(tokens),
        "total_alerts": len(alerts),
        "tokens_by_kind": kind_counts,
        "triggered_token_count": len(triggered_tokens),
        "untriggered_tokens": len(untriggered),
        "missing_token_types": recommended_types,
        "coverage_score": round(len(kind_counts) / len(TOKEN_KINDS) * 100, 1),
    }


def full_audit(client):
    """Run comprehensive Canarytoken deployment audit."""
    coverage = audit_token_coverage(client)
    tokens_resp = client.list_tokens()
    tokens = tokens_resp.get("tokens", [])
    alerts_resp = client.get_alerts()
    alerts = alerts_resp.get("incidents", [])

    token_details = []
    for t in tokens[:30]:
        token_details.append({
            "canarytoken": t.get("canarytoken"),
            "kind": t.get("kind"),
            "memo": t.get("memo"),
            "created": t.get("created_printable"),
            "enabled": t.get("enabled"),
            "url": t.get("url", ""),
        })

    alert_details = []
    for a in alerts[:20]:
        alert_details.append({
            "incident_id": a.get("id"),
            "description": a.get("description"),
            "source_ip": a.get("src_host"),
            "timestamp": a.get("created_printable"),
            "canarytoken": a.get("canarytoken"),
            "acknowledged": a.get("acknowledged"),
        })

    return {
        "audit_type": "Canarytoken Deception Coverage Audit",
        "timestamp": datetime.utcnow().isoformat(),
        "coverage": coverage,
        "deployed_tokens": token_details,
        "recent_alerts": alert_details,
        "recommendation": "Deploy missing token types to improve coverage"
            if coverage["coverage_score"] < 50 else "Good coverage — review untriggered tokens",
    }


def main():
    parser = argparse.ArgumentParser(description="Canarytoken Deception Detection Agent")
    parser.add_argument("--console", required=True, help="Canary Console domain (e.g., abc123)")
    parser.add_argument("--auth-token", required=True, help="API auth token")
    sub = parser.add_subparsers(dest="command")
    sub.add_parser("ping", help="Test API connectivity")
    sub.add_parser("list", help="List all deployed tokens")
    sub.add_parser("alerts", help="Fetch recent alerts")
    p_create = sub.add_parser("create", help="Create a new token")
    p_create.add_argument("--kind", required=True, choices=list(TOKEN_KINDS.keys()))
    p_create.add_argument("--memo", required=True)
    sub.add_parser("coverage", help="Audit token coverage")
    sub.add_parser("full", help="Full deception audit")
    args = parser.parse_args()

    client = CanaryClient(args.console, args.auth_token)

    if args.command == "ping":
        result = client.ping()
    elif args.command == "list":
        result = client.list_tokens()
    elif args.command == "alerts":
        result = client.get_alerts()
    elif args.command == "create":
        result = client.create_token(args.kind, args.memo)
    elif args.command == "coverage":
        result = audit_token_coverage(client)
    elif args.command == "full" or args.command is None:
        result = full_audit(client)
    else:
        parser.print_help()
        return
    print(json.dumps(result, indent=2, default=str))


if __name__ == "__main__":
    main()
Keep exploring