DEV Community

Cover image for From Hackathon Wreckage to Production-Ready: How I Rebuilt My Kubernetes Attack Path Visualiser with GitHub Copilot
Saptarshi Sarkar
Saptarshi Sarkar

Posted on

From Hackathon Wreckage to Production-Ready: How I Rebuilt My Kubernetes Attack Path Visualiser with GitHub Copilot

GitHub “Finish-Up-A-Thon” Challenge Submission

This is a submission for the GitHub Finish-Up-A-Thon Challenge


What I Built

Security teams managing Kubernetes clusters face a brutal reality: the cluster's attack surface is a complex web of interconnected resources — pods, service accounts, RBAC bindings, secrets, nodes — and understanding how an attacker could move between them is genuinely hard. Most tools either give you a flat list of CVEs or a sea of alerts with no context about what actually matters.

The idea behind K8s Attack Map is different and, I think, beautifully intuitive: model the entire Kubernetes cluster as a graph, where nodes are cluster resources and edges are relationships that an attacker could exploit. Once you have a graph, you get the entire arsenal of graph algorithms for free — Dijkstra for finding the most dangerous attack path, BFS for mapping blast radius, graph ablation for identifying the single node or edge whose removal destroys the most attack paths. It turns a hard security problem into an elegant graph theory problem.

I built the first version of this at a hackathon organised by my college. Our team of four chose the Kubernetes attack path visualiser track. The code worked — barely — but it was held together with duct tape: broken risk scoring, no real test coverage, incomplete docs, and a messy architecture that made every change feel risky. We didn't win. I shelved the project.

Two weeks later, curiosity pulled me back. That intuition — graph theory maps perfectly onto how attackers move through a cluster — wouldn't let go. So I opened GitHub Copilot and decided to do it right.

GitHub Repository: github.com/SaptarshiSarkar12/K8sAttackMap


Demo

Screenshot of tool output using live cluster configuration
Screenshot of tool output using live cluster configuration
Screenshot of executive section of pdf report
Screenshot of executive section of pdf report
Screenshot of interactive graph visualisation of cluster
Screenshot of interactive graph visualisation of cluster
Screenshot of attack paths discovered
Screenshot of attack paths discovered

The Comeback Story

Where It Started: The Hackathon Code

The hackathon version worked well enough for a demo, but it had deep structural problems that I discovered only after stepping back.

The biggest flaw: a completely wrong EdgeRiskScorer. The original scorer assigned risk based entirely on the edge relationship string — things like "admin-grant" getting a score of 9.8, "reaches" getting 3.0. It knew nothing about the actual security properties of the source or target nodes. A privileged pod exec-ing into a secret was scored the same as a non-privileged pod doing the same thing. The friction values fed into Dijkstra were essentially made-up numbers with no grounding in real security context.

// Old approach: just matching relationship strings
case "admin-grant", "admin-over", "grants-access-to" -> { return 9.8; }
case "reaches", "routes-to", "calls" -> { return 3.0; }
Enter fullscreen mode Exit fullscreen mode

Other major problems I found:

  • No SecurityFacts model — security properties of nodes (privileged containers, hostPID, RBAC wildcards, credential material) weren't captured structurally
  • TrivyCache saved scan results but had no proper workspace isolation — the cache path was hardcoded relative to a vague "app directory" with no cross-platform support and no path traversal protection
  • ANSI colour output was hardcoded — it would spray escape sequences into CI logs, pipes, and terminals that didn't support them
  • No EdgeType enum — relationship types were raw strings scattered across the codebase, making refactoring a nightmare
  • The analysis logic (attack paths, blast radius, choke point detection) was tangled into Main.java instead of separated into its own layer
  • The PDF report was barebones — useful for developers, unreadable by anyone else
  • Zero tests. Not a single JUnit test.

The Rebuild: What Changed

After Copilot audited the full codebase, the flaw list was long. I sketched a new architecture, shared it with Copilot, and we went back and forth refining it to align with Java best practices. Here is what the finished version looks like.

1. A Completely Redesigned EdgeRiskScorer

The new scorer starts from node-level SecurityFacts and builds friction values that actually reflect security reality. A privileged container reduces friction by 2.0 (easier for an attacker to move). An RBAC wildcard verb on the target reduces friction further. NODE_ESCAPE and EXEC_INTO edge types carry their own base friction adjustments. Friction is clamped to [0.1, 25.0] for numeric stability. The Dijkstra path found now genuinely represents the path of least resistance for an attacker.

// New approach: security facts drive friction
if (sourceFacts.isPrivilegedContainer()) friction -= 2.0;
if (sourceFacts.isHostPID() || sourceFacts.isHostNetwork()) friction -= 1.5;
if (targetFacts.isRbacHasEscalate() || targetFacts.isRbacHasBind()) friction -= 2.5;
if (targetFacts.isCredentialMaterial()) friction -= 1.8;
Enter fullscreen mode Exit fullscreen mode

2. Trivy Cache with Cross-Platform Workspace Isolation

One of the features I personally implemented is proper centralised caching for Trivy image scans. Scan results (CVSS scores and CVE IDs) are now persisted to ~/.k8sattackmap on Linux/macOS and %LOCALAPPDATA%\k8sattackmap on Windows. The WorkspaceManager validates paths against traversal attacks (.. sequences, separator characters) and verifies the resolved path stays within the base directory.

This means if you scan the same container image twice — maybe across different analysis runs on the same cluster — the expensive Trivy call is skipped and the cached result is reused instantly.

3. NO_COLOR and FORCE_COLOR Support

With Colour Without Colour
Screenshot of terminal output with colour Screenshot of terminal output without colour

I added full support for the NO_COLOR and FORCE_COLOR environment variable standards, plus a --no-color CLI flag. The TerminalCapabilities class detects CI environments, non-TTY output (pipes, file redirection), and the TERM=dumb case. When colours are disabled, ConsoleColors.disableColors() sets all ANSI escape constants to empty strings — so all the existing output code stays unchanged and just naturally produces plain text.

4. A Proper Analysis Architecture

The monolithic Main.java was dismantled. Analysis now flows through a clean layered structure:

AnalysisOrchestrator
 ├── analysis/graph/       → AttackPathDiscovery, Dijkstra, PrivilegeLoopDetector
 ├── analysis/blast/       → BlastRadiusAnalyzer
 ├── analysis/chokepoint/  → ChokePointIdentifier, ChokePointRemediationAdvisor
 └── analysis/remediation/ → ImpactRemediationAdvisor
Enter fullscreen mode Exit fullscreen mode

Each component is independently testable. The AnalysisOrchestrator coordinates them and produces a structured AnalysisResult.

5. A Much Better PDF Report

Old PDF Report New PDF Report
Screenshot of first page of old pdf report Screenshot of first page of new pdf report
Screenshot of remediation plan in old report Screenshot of remediation plans in new report

Copilot was invaluable here. The old PDF was a developer dump — raw node lists, edge tables, and numbers without explanation. The new report has a proper executive summary section explaining the cluster's overall security posture in plain language, a risk heatmap, prioritised findings with severity context, and a technical section with actual kubectl remediation commands for each finding. The idea: a CISO reads the first half, an SRE acts on the second half.

6. JUnit Test Coverage

Tests Started Tests Completed
Screenshot of JUnit Tests running Screenshot of JUnit Tests completed

We designed test suites for every meaningful component: EdgeRiskScorerTest, AttackPathDiscoveryTest, BlastRadiusAnalyzerTest, ChokePointIdentifierTest, DijkstraTest, PrivilegeLoopDetectorTest, K8sJsonParserTest, CommandParserTest, and more. Starting from zero tests to having a complete test pyramid covering the security-critical paths felt like the project finally growing up.

7. A modern website featuring elegant, developer-friendly documentation

Previously, there was no website (with docs) for this project. GitHub copilot created the complete website and ensured the design is consistent with the project logo colour palate. It used Next.Js with fumadocs to create this good looking and accessible website. The website design was working for mobile phones from the start!

View the live website: https://k8sattackmap.vercel.app/

Screenshot of website


My Experience with GitHub Copilot

I want to be honest about this because I think the experience was genuinely interesting rather than just "Copilot wrote the code."

The most useful thing Copilot did at the very start was the audit. I asked it to read through the hackathon code and identify structural and correctness issues. The list it produced was long and surprisingly accurate — it caught the false positive risk scoring problem, the lack of security context in nodes, the string-based edge type fragility, the missing workspace isolation, the absence of tests. Some of these I suspected; others I had missed entirely. Having that comprehensive list upfront let me design the new architecture with all the problems in mind rather than discovering them one by one mid-rebuild.

The architecture review was the second genuinely valuable moment. I drew up my proposed new package structure and class responsibilities and shared it with Copilot. It pushed back on a few naming choices (the original design had some non-standard Java conventions), suggested splitting AnalysisResult from AnalysisInput more cleanly, and recommended the AnalysisOrchestrator as an explicit coordination layer rather than having the CLI command parser call analysis code directly.

For implementation, we mostly pair-programmed. I wrote the core logic for the new EdgeRiskScorer, the WorkspaceManager path safety checks, the NO_COLOR/FORCE_COLOR terminal detection, and the Trivy cache improvements myself — these felt like the security-critical pieces where I wanted to think carefully rather than accept suggestions blindly. For the BlastRadiusAnalyzer, ChokePointRemediationAdvisor, the PDF report templates, and the JUnit tests, Copilot drafted the structure and I reviewed and adjusted. The tests in particular — Copilot is very good at generating test cases that cover edge conditions you might not think to write yourself.

The one place I pushed back on Copilot significantly was the PDF report design. Its first drafts were still too technical — good structure, but the executive summary read like a developer wrote it. I had to be very explicit: "Write this paragraph as if explaining to a VP of Engineering who has never seen a Kubernetes cluster. No jargon. Tell them what the risk means to the business, not what the RBAC binding is." That iterative prompting to get the tone right took several rounds, but the result was worth it.

Overall: Copilot dramatically shortened the time from "identified problem" to "working solution" — especially for boilerplate, test structure, and documentation. But the architectural thinking, the security-specific design decisions, and the judgment calls about what actually matters still needed to come from me. That felt like the right balance.


If you're working on Kubernetes security tooling or just interested in the graph-theory-as-security-analysis approach, I'd love to hear from you. The project is open source — issues, ideas, and PRs are very welcome.

Top comments (0)