Skip to main content

Development setup

Requirements:
  • Python 3.11+
  • uv (recommended) or pip
  • Docker (for integration tests)
  • Ollama (optional, for persona generation)
Install: Verify:
make lint    # ruff check + mypy
make test    # pytest
make run     # start locally

Code style

  • Python 3.11+ -modern syntax (| unions, match statements)
  • Type hints everywhere -all function signatures, return types, class attributes
  • Ruff for linting and formatting (line length 100)
  • mypy in strict mode
  • Google-style docstrings on all public functions and classes
make lint    # check
make fmt     # auto-format

Making changes

1

Create a branch

git checkout -b your-feature-name
2

Write code

Follow the code style above. Key principles:
  • No TODOs in code -future work goes in ROADMAP.md or GitHub issues
  • Tests for every change
  • Never execute user-supplied code (see threat model)
3

Test

make test           # unit tests
make test-coverage  # with coverage
make lint           # linting + types
make audit          # security (pip-audit + bandit)
4

Submit a PR

Clear title and description, reference related issues, ensure CI passes.

Areas for contribution

AreaExamples
Persona packsNew industry themes (e-commerce, IoT, gaming, government)
Trap typesGraphQL, gRPC, WebSocket, SSH
Fingerprinting signalsNew detection heuristics
ResearchDeploy Sundew and share anonymized findings

Adding a persona pack

Persona packs let Sundew run without an LLM. Create a JSON file in src/sundew/persona/packs/:
sundew generate --persona your-industry --export src/sundew/persona/packs/your-industry.json
Requirements:
  • Realistic company name and industry context
  • At least 5 REST endpoints with varied response structures
  • MCP tools matching the industry theme
  • Realistic fake data (valid UUIDs, plausible emails, real timestamps)
  • Error responses matching the persona’s error style

Adding a trap type

Create a module in src/sundew/traps/:
# src/sundew/traps/your_trap.py
from fastapi import APIRouter, Request
from sundew.models import Persona
from sundew.fingerprint import FingerprintCollector

def create_router(persona: Persona, collector: FingerprintCollector) -> APIRouter:
    router = APIRouter()

    @router.get("/your-endpoint")
    async def your_endpoint(request: Request) -> dict:
        collector.record(request, signal="your_signal")
        return persona.get_response("/your-endpoint", "GET")

    return router
Key requirements:
  • Every trap must read from the persona -no hardcoded responses
  • Every request must be recorded via FingerprintCollector
  • No LLM calls at runtime
  • No execution of user-supplied input

Questions?