{"id":33170819,"url":"https://github.com/atgreen/ctfg","last_synced_at":"2026-02-01T22:33:32.194Z","repository":{"id":300227802,"uuid":"1005577180","full_name":"atgreen/ctfg","owner":"atgreen","description":"A simple Capture-The-Flag engine","archived":false,"fork":false,"pushed_at":"2025-09-20T19:21:19.000Z","size":24049,"stargazers_count":10,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-09-20T19:21:27.418Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/atgreen.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2025-06-20T12:58:21.000Z","updated_at":"2025-09-20T19:21:22.000Z","dependencies_parsed_at":"2025-06-20T14:39:57.353Z","dependency_job_id":"70cadc7b-5d68-4c01-a172-c0fbe860523b","html_url":"https://github.com/atgreen/ctfg","commit_stats":null,"previous_names":["atgreen/ctfg"],"tags_count":34,"template":false,"template_full_name":null,"purl":"pkg:github/atgreen/ctfg","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atgreen%2Fctfg","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atgreen%2Fctfg/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atgreen%2Fctfg/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atgreen%2Fctfg/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/atgreen","download_url":"https://codeload.github.com/atgreen/ctfg/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atgreen%2Fctfg/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28993253,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-01T22:01:47.507Z","status":"ssl_error","status_checked_at":"2026-02-01T21:58:37.335Z","response_time":56,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-11-16T01:00:43.666Z","updated_at":"2026-02-01T22:33:32.187Z","avatar_url":"https://github.com/atgreen.png","language":"JavaScript","readme":"# ctfg\n\nThis is a simple Capture-The-Flag game engine.\n\n![alt text](./example.png)\n\n\n## Building and Running\n\n`ctfg` can run locally on your machine or in a container.\n\n### On your machine\n\nYou will need to install a few dependencies first.  If you are running homebrew,\nrun...\n```sh\nbrew install make sbcl ocicl gcc@11\n```\n\nOtherwise, install `sbcl` from your OS distribution, and then `ocicl` from source at https://github.com/ocicl/ocicl.\n\nOnce `ocicl` is available, run `ocicl install` to download the Common\nLisp dependencies.\n\nAnd then...\n\n* To build `ctfg`: `make`\n* To test `ctfg`: `make check`\n* To run `ctfg`: `ctfg --help`\n\n```\nNAME:\n  ctfg - A Capture-The-Flag Game Engine\n\nUSAGE:\n  ctfg\n\nOPTIONS:\n      --help                   display usage information and exit\n      --version                display version and exit\n  -b, --dbdir \u003cVALUE\u003e          database directory [default: .]\n  -d, --developer-mode         enable developer mode\n  -p, --port \u003cINT\u003e             port [default: 8080]\n  -s, --slynk-port \u003cINT\u003e       slynk-port\n  -w, --websocket-url \u003cVALUE\u003e  websocket-url [default: ws://localhost:12345/scorestream]\n\nEXAMPLES:\n\n  Run web service on port 9090:\n\n    ctfg -p 9090\n\nAUTHORS:\n  Anthony Green\n\nLICENSE:\n  MIT\n```\n\n### In a container\n\nFirst, build the image:\n\n```sh\n# docker and podman can be used interchangeably here\npodman build -t ctfg .\n```\n\nAfterwards, use `docker run` or `podman run` to start a new `ctfg` game server\nwith the options shown above:\n\n```sh\npodman run -v $PWD/credentials.csv:/data/credentials.csv \\\n    -v $PWD/challenges.json:/data/challenges.json \\\n    -v $PWD/challenges.json:/data/game-clusters.yaml \\\n    ctfg --help\n```\n\nSince `ctfg` creates a database to track player and game activity, moutning a\ncontainer volume to `/data` and copying game configuration into it is\nrecommended:\n\n```sh\npodman volume create ctfg-data\npodman run --name copier -v ctfg-data:/data bash:5 sleep infinity\nfor f in challenges.json credentials.csv game-clusters.yaml\ndo podman cp \"$f\" \"copier:/data/$f\"\ndone\npodman rm -f copier\n```\n\n---\n\nThe `--developer-mode` option disables caching of static content, and\nreloads the challenges.json every time the Challenge page is\nrendered.  This allows you view your changes in real time as you are\ndeveloping content.\n\nClient browsers must establish websocket connections back to the game\nengine on the `/scorestream` endpoint.  Use the `--websocket-url`\noption to tell those clients what the URL is.  For instance, if you\nare hosting ctfg on an OpenShift kubernetes cluster, you might create\na TLS terminated route for your ctfg service and connect to it thusly:\n`-w wss://scorestream-ctfg.apps.ocp.example.com:443/scorestream`\n\n\n## Configuring your Game\n\n1. Player credentials should live in a file called `credentials.csv`.  It's a simple `username,password` csv file.\n\n2. Challenges are defined in `challenges.json`. This should be a JSON array containing challenge objects with the following structure:\n\n```json\n{\n    \"id\": 5,\n    \"title\": \"SQL Injection Login\",\n    \"category\": \"Web\",\n    \"difficulty\": \"Easy\",\n    \"points\": 150,\n    \"description\": \"Challenge description supporting both Markdown and HTML\",\n    \"flag\": \"^regexp flag goes here$\",\n    \"testflag\": \"exact_flag_for_testing\",\n    \"hints\": [\n        {\n            \"id\": 1,\n            \"text\": \"First hint text (supports Markdown/HTML)\",\n            \"cost\": 10\n        },\n        {\n            \"id\": 2,\n            \"text\": \"Second hint reveals after first is purchased\",\n            \"cost\": 20\n        }\n    ],\n    \"requirements\": [2, 3],\n    \"content\": \"Optional additional content field\"\n}\n```\n\n### Challenge Fields\n\n- **id** (required): Unique integer identifier for the challenge\n- **title** (required): Challenge name displayed in the UI\n- **category** (required): Category for grouping challenges (e.g., \"Web\", \"Crypto\", \"Forensics\")\n- **difficulty** (required): Difficulty level (e.g., \"Easy\", \"Medium\", \"Hard\")\n- **points** (required): Point value awarded for solving\n- **description** (required): Challenge description that supports both Markdown syntax and HTML. The marked library renders Markdown while preserving HTML tags\n- **flag** (required): Regular expression pattern for validating flag submissions\n- **testflag** (optional): Exact flag value used for automated testing\n- **hints** (optional): Array of hint objects with:\n  - **id**: Unique identifier within the challenge\n  - **text**: Hint content (supports Markdown and HTML)\n  - **cost**: Points deducted when hint is revealed\n  - Hints are revealed sequentially - players must purchase earlier hints first\n- **requirements** (optional): Array of challenge IDs that must be solved before this challenge becomes available\n- **content** (optional): Additional content field for extended challenge information\n\n### Text Formatting\n\nBoth challenge descriptions and hint texts support:\n- **Markdown**: Headers, bold/italic, code blocks, tables, lists, blockquotes\n- **HTML**: Direct HTML tags like `\u003cbr\u003e`, `\u003cstrong\u003e`, `\u003cem\u003e`, `\u003cmark\u003e`, `\u003ccode\u003e`\n- **Mixed content**: Markdown and HTML can be used together\n\n### Dynamic Placeholders\n\nThe following placeholders in challenge descriptions are automatically replaced at runtime:\n\n- **@USERNAME@**: The player's login username\n- **@USERID@**: The player's numeric user ID\n- **@DISPLAYNAME@**: The player's chosen display name (or \"[unset]\" if not configured)\n- **@OBFUSCATED_DISPLAYNAME@**: An XOR-masked and checksummed version of the display name (for anti-cheating purposes)\n- **@CONTROL_CLUSTER@**: The control Kubernetes cluster from game-clusters.yaml\n- **@PLAYER_CLUSTER@**: The player's assigned Kubernetes cluster (assigned round-robin from the player clusters list)\n\n3. Banner image: place your banner at `images/banner.jpg` (preferred) or `images/banner.png`. If `.jpg` exists it will be used; otherwise the app falls back to `.png`.\n\n4. Edit `game-clusters.yaml` to point at the Kubernetes cluster hosting this app,\n   as well as the list of player clusters (all possibly the same).\n   Users are assigned to the different player clusters in a\n   round-robin format as they join.\n\n## Load Testing\n\nFor testing server performance under high concurrent load, use the included `player-emulator.js`:\n\n```bash\n# Test with N concurrent players (requires Node.js and npm install)\n./player-emulator.js \u003cserver_url\u003e challenges.json credentials.csv \u003cN\u003e\n\n# Examples:\n./player-emulator.js http://localhost:8080 challenges.json credentials.csv 70\n./player-emulator.js https://ctfg.example.com challenges.json credentials.csv 390\n```\n\nThe emulator simulates realistic browser behavior:\n- Fetches static files (HTML, CSS, JS, images)\n- Logs in with credentials from CSV file\n- Sets unique display names\n- Establishes WebSocket connections\n- Solves all available challenges using testflags\n- Validates WebSocket messages and logs detailed progress\n\nPerfect for stress testing before game day!\n\n## API\n\nMost REST endpoints in ctfg are intended for use by the browser\nclient.  However, ctfg does provide one endpoint intended for use\nby an external non-browser client.\n\nPosting to the `/api/award` endpoint emulates a successful flag\nsubmission for a specific `username` and challenge `id`.  Use this API\nfor any automated flag submission by an external judge process.  For\nexample, posting the following json will tell ctfg to behave as\nthrough player `player1` had submitted the correct flag for challenge\nnumber `5`.\n\n```\n{\n  \"username\": \"player1\",\n  \"id\": \"5\"\n}\n```\n\nAn `AUTHORIZATION` token must be provided in the http header for this\nAPI.  Specify this token when you launch ctfg by setting the\n`CTFG_API_TOKEN` environment variable.\n\n## Admin Reset (Zero-Restart)\n\nCTFG includes a safe, zero-restart admin reset that wipes game state mid-run.\n\n- Configure an admin token by setting `CTFG_ADMIN_TOKEN` in the environment (or `.env`).\n- Visit `/reset` in a browser to open a minimal admin page, enter the token, and confirm.\n- Or call the action directly: `POST /admin/reset` with body `token=...\u0026confirm=yes`.\n\nWhat reset does:\n- Deletes all rows from the `events` table (transactional)\n- Clears all user display names (sets `users.displayname = NULL`)\n- Invalidates all browser sessions without restart (auth epoch increment)\n- Broadcasts a WebSocket `system/reset` with `logout=true` and closes sockets\n- Clears in-memory caches (scoreboard/solves) so no stale \"solved\" remains\n\nEffect on users:\n- All clients are logged out immediately and must log in again\n- Scoreboard resets to empty, and users will be prompted to set a display name again\n\nExample curl:\n\n```bash\n# Using form-encoded body\ncurl -X POST \\\n  -H 'Content-Type: application/x-www-form-urlencoded' \\\n  -d 'token=YOUR_ADMIN_TOKEN\u0026confirm=yes' \\\n  http://localhost:8080/admin/reset\n\n# Using JSON\ncurl -X POST \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"token\":\"YOUR_ADMIN_TOKEN\"}' \\\n  'http://localhost:8080/admin/reset?confirm=yes'\n```\n\n## Author and License\n\n`ctfg` was written by Anthony Green and is distributed\nunder the terms of the MIT license.\n","funding_links":[],"categories":["Applications"],"sub_categories":["Games"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fatgreen%2Fctfg","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fatgreen%2Fctfg","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fatgreen%2Fctfg/lists"}