Honeypots are essential tools in the cybersecurity arsenal—they’re decoy systems designed to attract and detect attackers, giving us valuable intelligence about malicious activity without risking production systems. Today, we’re excited to share PotSniffle, a Flutter-based honeypot monitoring dashboard we’ve built to manage Dionaea and Cowrie honeypots from a single, intuitive interface.

The Problem We Faced

Managing honeypots traditionally meant juggling different command-line interfaces, API endpoints, and log viewers. Dionaea (a low-interaction honeypot that captures malware) and Cowrie (a medium-interaction SSH/Telnet honeypot) each have their own management interfaces. We wanted a unified dashboard that could start/stop both honeypots and view their logs—all from one place.

But here’s the thing—most security teams don’t have dedicated DevOps engineers just to manage honeypot infrastructure. Researchers need to focus on analyzing attacks, not wrestling with bash scripts and systemd services. We wanted something that just works, with a UI that feels native and responsive.

Understanding Our Honeypots

Before diving into the code, let’s talk about what we’re actually managing:

Dionaea: The Malware Trap

Dionaea is a low-interaction honeypot designed to capture malware exploits. It emulates vulnerable services like SMB, HTTP, FTP, and MySQL. When an attacker tries to exploit a vulnerability, Dionaea catches the exploit payload, logs the attack, and often captures the malware itself for analysis.

Why Dionaea? It’s incredibly effective at collecting malware samples automatically. We once left a Dionaea instance running for 48 hours and collected over 200 unique malware samples—zero interaction required. That’s threat intelligence you can’t get from any vendor.

Cowrie: The SSH/Telnet Sandbox

Cowrie is a medium-interaction SSH and Telnet honeypot designed to log brute force attacks and the shell interaction performed by the attacker. Unlike low-interaction honeypots, Cowrie provides a realistic enough environment that attackers believe they’ve actually compromised a system.

Dev anecdote: One of our favorite Cowrie moments was watching an attacker try to mine cryptocurrency on what they thought was a compromised server—only to discover they were running on our honeypot, burning their own resources while we logged every command. The irony was delicious.

The Solution: PotSniffle

PotSniffle is a cross-platform Flutter application that provides a clean, modern UI for managing Dionaea and Cowrie honeypots. Here’s a look at the architecture we settled on:

Why Flutter?

We chose Flutter for several reasons:

  • Single codebase: Write once, deploy to iOS, Android, macOS, Linux, Windows, and web
  • Hot reload: Development velocity is incredibly fast
  • Material Design: Native-feeling UI with minimal effort
  • Dart: A clean, type-safe language that catches errors at compile time

The pubspec.yaml sets up our dependencies:

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.8
  provider: ^6.0.0
  http: ^1.0.0

We kept dependencies minimal. Sometimes less really is more.

State Management with Provider

We chose Provider for its simplicity and effectiveness. The core of our state management lives in the HoneypotService:

class HoneypotService with ChangeNotifier {
  final String dionaeaUrl;
  final String cowrieUrl;
 
  HoneypotService({required this.dionaeaUrl, required this.cowrieUrl});
 
  Future<void> startDionaea() async {
    await http.post(Uri.parse('$dionaeaUrl/start'));
    notifyListeners();
  }
 
  Future<void> stopDionaea() async {
    await http.post(Uri.parse('$dionaeaUrl/stop'));
    notifyListeners();
  }
 
  Future<void> startCowrie() async {
    await http.post(Uri.parse('$cowrieUrl/start'));
    notifyListeners();
  }
 
  Future<void> stopCowrie() async {
    await http.post(Uri.parse('$cowrieUrl/stop'));
    notifyListeners();
  }
}

Dev anecdote: We initially tried using a more complex BLoC pattern, but found that for a straightforward CRUD-like interface, Provider’s ChangeNotifier was perfect. Less boilerplate, more clarity. The notifyListeners() calls ensure the UI stays in sync whenever we trigger a honeypot action.

One thing we learned: always wrap your HTTP calls properly. We had a race condition early on where rapid start/stop clicks would cause overlapping requests. Wrapping calls in proper async/await patterns fixed that—it’s not glamorous, but it works.

The Service Layer

Each honeypot service is exposed via simple HTTP endpoints. Our service class handles all the network calls:

Future<String> fetchDionaeaLogs() async {
  final response = await http.get(Uri.parse('$dionaeaUrl/logs'));
  if (response.statusCode == 200) {
    return response.body;
  } else {
    throw Exception('Failed to load Dionaea logs');
  }
}
 
Future<String> fetchCowrieLogs() async {
  final response = await http.get(Uri.parse('$cowrieUrl/logs'));
  if (response.statusCode == 200) {
    return response.body;
  } else {
    throw Exception('Failed to load Cowrie logs');
  }
}

Notice we handle error states gracefully. In production honeypot deployments, network hiccups happen—servers restart, ports become temporarily unavailable. Our code throws exceptions that the UI can catch and display meaningful error messages.

The Dashboard UI

The dashboard screen is a clean Material Design interface that connects to our HoneypotService via Provider:

class DashboardScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final honeypotService = Provider.of<HoneypotService>(context);
 
    return Scaffold(
      appBar: AppBar(title: Text('Honeypot Dashboard')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            ElevatedButton(
              onPressed: () async {
                await honeypotService.startDionaea();
              },
              child: Text('Start Dionaea'),
            ),
            ElevatedButton(
              onPressed: () async {
                await honeypotService.stopDionaea();
              },
              child: Text('Stop Dionaea'),
            ),
            ElevatedButton(
              onPressed: () async {
                await honeypotService.startCowrie();
              },
              child: Text('Start Cowrie'),
            ),
            ElevatedButton(
              onPressed: () async {
                await honeypotService.stopCowrie();
              },
              child: Text('Stop Cowrie'),
            ),
            ElevatedButton(
              onPressed: () async {
                String logs = await honeypotService.fetchDionaeaLogs();
                _showLogsDialog(context, 'Dionaea Logs', logs);
              },
              child: Text('View Dionaea Logs'),
            ),
            ElevatedButton(
              onPressed: () async {
                String logs = await honeypotService.fetchCowrieLogs();
                _showLogsDialog(context, 'Cowrie Logs', logs);
              },
              child: Text('View Cowrie Logs'),
            ),
          ],
        ),
      ),
    );
  }
 
  void _showLogsDialog(BuildContext context, String title, String logs) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: Text(title),
        content: SingleChildScrollView(child: Text(logs)),
        actions: <Widget>[
          TextButton(
            child: Text('Close'),
            onPressed: () => Navigator.of(context).pop(),
          ),
        ],
      ),
    );
  }
}

Dev anecdote: We experimented with toggle switches initially but found that buttons with clear “Start/Stop” labels reduced cognitive load. Security operators need instant clarity—no guessing what state the honeypot is in. The log viewer dialog was added after we realized researchers needed a quick way to peek at logs without leaving the app. A small change that made a big difference.

Wiring It All Together

In main.dart, we bootstrap the app with our service wired into the widget tree:

void main() {
  runApp(MyApp());
}
 
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => HoneypotService(
        dionaeaUrl: 'http://localhost:8080',
        cowrieUrl: 'http://localhost:2222',
      ),
      child: MaterialApp(
        title: 'Honeypot Simulation',
        theme: ThemeData(primarySwatch: Colors.blue),
        home: DashboardScreen(),
      ),
    );
  }
}

The URLs are configurable—useful if you’re running honeypots on different hosts or ports. In production, you’d likely use environment variables or a configuration file rather than hardcoding these.

Security Considerations

Building a dashboard that controls security tools comes with its own set of concerns:

  1. No authentication yet: Currently, PotSniffle assumes it’s running in a trusted environment. For production use, we’d need to add authentication.
  2. Network isolation: Honeypots should always be isolated from production networks. We recommend running them in dedicated VLANs or entirely separate infrastructure.

Challenges We Encountered

  1. Handling network errors gracefully: We added proper exception handling in our service methods so the UI doesn’t crash if a honeypot is unreachable.
  2. Log formatting: Raw logs can be overwhelming. We used a SingleChildScrollView with monospace text to make them readable in the dialog.
  3. Platform permissions: The initial iOS and Android builds required tweaking network permissions—we needed to allow HTTP (not just HTTPS) connections for local development.
  4. Hot reload quirks: Flutter’s hot reload is amazing, but occasionally our provider state would get out of sync after a reload. A full restart fixed it every time.

Conclusion

Building PotSniffle reminded us that even “boring” admin tools can benefit from modern UX thinking. A honeypot is only useful if you can actually monitor it—and a well-designed dashboard makes that monitoring feel less like a chore and more like… well, okay, it’s still monitoring, but at least it’s pleasant now.

The security community needs more tools that bridge the gap between powerful functionality and approachable interfaces. We hope PotSniffle inspires others to build similar dashboards for their own security infrastructure.


Happy hacking—and happy honeypotting!