Inspiration

The opportunity. Learning security is intimidating, and standing up a Capture-The-Flag (CTF) event usually means wrestling heavyweight, hard-to-host tooling. We wanted a friendly, self-contained CTF that teaches real skills — and is trivial to run.

The solution. KFC is a playful, chicken-themed CTF platform with 20 challenges across 8 categories (Web, Pwn, Crypto, Reversing, Stego, Forensics, OSINT, Misc), from beginner "view-source" puzzles to a JWT alg:none bypass. It plays like a real competition: dynamic (decaying) scoring, point-cost hints, prerequisite chains, post-solve writeups, first-blood tracking, player profiles, and an admin dashboard. It runs zero-config on SQLite or, with one env var, on Postgres — which unlocks free hosting. It's live on Render's free tier, Dockerized, CI-tested (45 tests), and deploys on every push. The deliberately "vulnerable" challenges (path traversal, IDOR, JWT) are fully sandboxed, so they teach safely.

How we used Kiro. We built KFC spec-first. A Kiro spec (requirements.md in EARS format → design.mdtasks.md) turned "make a CTF" into a concrete, checkable plan we executed task by task. Steering files encoded the product, tech stack, structure, and hard guardrails — "flags stay server-side," "sandbox vulnerable challenges," "keep both DB backends in sync," "keep tests green" — so every agent session stayed consistent as the code grew. A Kiro agent hook watches the challenge seed and data-store files, prompting us to re-verify flags and run tests on every change. Kiro's agentic workflow drove the coding, testing, and PR-based deployment — yielding a complete platform, fast, with guardrails baked in.

What it does

KFC is a self-hosted CTF platform where players register, solve security and puzzle challenges to uncover hidden flags (always in the format CHICKEN{...}).

It ships with 20 challenges across 8 categories: Web, Pwn/Logic, Crypto, Reversing, Stego, Forensics, OSINT, and Misc; spanning skills from "view source" and Base64 all the way to path traversal, IDOR, and a JWT alg:none authentication bypass.

Beyond just challenges, it behaves like a real competition:

  • Unlockable hints that cost points, and prerequisite chains that gate advanced challenges behind easier ones.
  • Post-solve writeups that explain the solution (and the defensive lesson) once you crack it.

How we built it

The stack: Node.js + Express with EJS server-rendered views, and challenges defined in a single seed file as the source of truth. Data lives in SQLite by default (zero config) or Postgres when a DATABASE_URL is set, which unlocks free-tier and scalable hosting.

It was built feature by feature: challenges, then scoring, profiles, hints, prerequisites, an admin panel; each backed by an automated test suite. It's Docker-ready with a health check, has CI running the tests on every push, and can deploy to Fly.io/Render/Railway (though in progress). The intentionally "vulnerable" challenges (path traversal, IDOR, JWT) are carefully sandboxed to run on in-memory data so they teach the concept without ever exposing the real system.

Challenges we ran into

  • Making vulnerable challenges safe. Teaching path traversal and IDOR means writing "insecure" code on purpose — we had to sandbox them against in-memory data so ../../../etc/passwd truly can't escape.
  • Not leaking answers. Locked challenges, un-purchased hints, and writeups all had to be withheld server-side, never just hidden in the UI.
  • Dynamic scoring math. Getting the decay curve to feel fair — and recomputing everyone's score as solves rolled in — took care to keep consistent across the whole app.
  • Adding Postgres. Our SQLite driver is synchronous while the Postgres driver is async, so supporting both meant introducing a clean async data-access layer — and reworking a SQLite-only MIN() scoring trick so both backends behave identically.
  • Deploying to an actual web host due to cost considerations

Accomplishments that we're proud of

  • A complete CTF spanning 8 categories and 20 challenges, hints, prerequisites, writeups, profiles, and an admin panel.
  • A genuinely fun, cohesive chicken theme running through every challenge and page.
  • Two interchangeable database backends behind one interface, so it runs zero-config on SQLite or free-tier Postgres.
  • A safety-first approach to the vulnerable challenges.
  • Solid engineering hygiene: an automated test suite covering challenge integrity, CI, Docker, and health checks.

What we learned

  • How the attacker's and defender's sides of common web vulnerabilities actually work — and how to demonstrate them responsibly.
  • That designing good challenges is as hard as building the platform: difficulty tiers, hints, and prerequisites all shape the player's learning curve.
  • The practical realities of abstracting a data layer (sync vs. async, SQL dialect differences) to stay portable across databases.
  • How much automated tests and CI speed up confident iteration on a growing codebase.

What's next for KFC

  • Team-based play — group accounts into teams with combined scoreboards.
  • A live activity feed of solves and first-bloods (real-time updates).
  • More challenge tracks and harder tiers, plus community-contributed challenges via the authoring guide.
  • Richer admin tooling — challenge management and event scheduling from the dashboard.
  • Broader managed/serverless deployment now that the Postgres backend removes the single-instance limitation.
  • Possible integration with fly.io
Share this project:

Updates

posted an update

KFC is now live

Keep Finding Chickens (KFC), our chicken-themed Capture-The-Flag platform, is now publicly playable:

Live: https://kfc-j10g.onrender.com/

What's new

We got KFC running online for free, with automated deploys. The key unlock was making the app hosting-friendly:

  • Optional Postgres backend. KFC now runs on SQLite (zero-config, local) or Postgres, selected automatically by a DATABASE_URL. This matters because free web tiers have an ephemeral filesystem — SQLite files wouldn't survive a restart, but Postgres (plus DB-backed sessions) needs no local disk.
  • One-click Render Blueprint. A render.yaml provisions a free web service and a free Render Postgres, wiring everything up automatically.
  • Push-to-deploy via GitHub Actions. Every push to main runs the full test suite, then triggers a Render deploy — so nothing ships unless the tests are green.
  • Health checks. A /healthz endpoint reports live status and which backend is active. Right now it says:
  {"status":"ok","backend":"postgres","challenges":20,"uptime":294}

Log in or sign up for Devpost to join the conversation.