{"id":18329801,"url":"https://github.com/ctfd/tourist","last_synced_at":"2025-04-06T01:32:45.038Z","repository":{"id":88815709,"uuid":"563790534","full_name":"CTFd/tourist","owner":"CTFd","description":"Tourist is an HTTP API around Microsoft Playwright for CTF challenges","archived":false,"fork":false,"pushed_at":"2023-09-18T09:57:58.000Z","size":674,"stargazers_count":11,"open_issues_count":6,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-21T15:11:56.359Z","etag":null,"topics":["ctf","ctf-platform"],"latest_commit_sha":null,"homepage":"https://blog.ctfd.io/introducing-tourist/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/CTFd.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","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}},"created_at":"2022-11-09T10:45:55.000Z","updated_at":"2025-01-09T14:25:08.000Z","dependencies_parsed_at":null,"dependency_job_id":"ccef8c27-5020-4665-b056-f1f794b76708","html_url":"https://github.com/CTFd/tourist","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CTFd%2Ftourist","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CTFd%2Ftourist/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CTFd%2Ftourist/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CTFd%2Ftourist/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/CTFd","download_url":"https://codeload.github.com/CTFd/tourist/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247423467,"owners_count":20936621,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["ctf","ctf-platform"],"created_at":"2024-11-05T19:18:42.293Z","updated_at":"2025-04-06T01:32:45.032Z","avatar_url":"https://github.com/CTFd.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Tourist\n\n![next](https://github.com/ctfd/tourist/actions/workflows/docker-next.yml/badge.svg)\n![test](https://github.com/ctfd/tourist/actions/workflows/test.yml/badge.svg)\n\n## What is Tourist?\n\nSimply put - Tourist is an agent that will visit web applications and perform given actions.\nIt was designed for use with CTF challenges that require visiting by a real browser to trigger vulnerabilities.\n\nInstead of packaging a headless browser inside all of your challenges via puppeteer/playwright/selenium - you can\ndeploy a single instance of Tourist somewhere and have all your challenges talk with it to schedule visits.\n\nIt can also perform additional actions, for example if your challenge requires generating PDF files from HTML\nyou can also outsource this task to Tourist.\n\n## Getting Started\n\n1. First you need to deploy Tourist.\n   * We recommend using our docker image from `ghcr.io/ctfd/tourist`\n   * You can also reference the `docker-compose.yml` file\n2. With authentication enabled, you need to copy the issuer token generated for you during the application start up.\n3. Next, using that token, you need to create a token for your application to schedule jobs:\n\n    ```python\n    # Issue a non-strict, visit token for visiting example.com valid for 7 days.\n    import requests\n    \n    url = \"http://localhost:3000/api/v1/issue-token\"\n    token = \"\u003cissuer-token\u003e\"\n    \n    headers = {\n      \"Authorization\": f\"Bearer {token}\"\n    }\n    \n    data = {\n      \"scope\": \"https://example.com\",\n    }\n    \n    response = requests.post(url, json=data, headers=headers)\n    print(response.json()[\"token\"])\n    ```\n\n4. In response to that request, you will receive a visit token (by default valid for 7 days), which you can use to\n   schedule jobs:\n\n    ```python\n    # Go to https://example.com and take a screenshot synchronously\n    import base64\n    import requests\n    \n    url = \"http://localhost:3000/api/v1/sync-job\"\n    token = \"\u003cvisit-token\u003e\"\n    \n    headers = {\n      \"Authorization\": f\"Bearer {token}\"\n    }\n    \n    data = {\n      \"steps\": [\n        {\"url\": \"https://example.com\"}\n      ],\n      # You can create a video and a pdf the same way by using additional options: \"RECORD\" and \"PDF\"\n      \"options\": [\"SCREENSHOT\"]\n    }\n    \n    response = requests.post(url, json=data, headers=headers).json()\n\n    if response[\"status\"] == \"success\":\n      screenshot_b64 = response[\"result\"][\"screenshot\"]\n      screenshot = base64.b64decode(screenshot_b64)\n    \n      with open(\"screenshot.png\", \"wb+\") as screenshot_file:\n        screenshot_file.write(screenshot)\n    ```\n\nFor additional guidance, be sure to check:\n\n* The [docs](./docs)\n* The [examples directory](./docs/examples)\n* OpenAPI docs (by navigating to a deployed Tourist endpoint)\n* Unit tests: [`runner.test.ts`](./tests/runner.test.ts), [`async-job.test.ts`](./tests/async-job.test.ts) and\n  [`sync-job.test.ts`](./tests/sync-job.test.ts)\n\n## Configuring Tourist\n\nWe recommend using Tourist in Docker, and configuring it with environmental variables. Example (default) config can be\nseen in the `.env.example` file which is self-explanatory.\n\nThe only required setting is the `REDIS_URL`. It's also recommend to provide a securely random `SECRET` - Tourist will\ngenerate a random secret key on startup, however if you happen to restart the application your old tokens will become\ninvalid.\n\nFor full reference please check our guide on [installing Tourist](./docs/01-installing-tourist.md#configuration-reference)\n\n### Authentication\n\nToken authentication is enabled by default. It can be disabled with environmental variables.\n\nTourist expects the `Authorization` header with a value of `Bearer \u003ctoken\u003e`.\n\nFor full reference please check our guide on [Tourist authentication](./docs/02-authentication.md)\n\n## Using actions\n\nYou can specify actions to be performed during each step. Actions are an array of strings (code) to be passed to\nplaywright inside an isolated sandbox. There are a few guidelines for you to follow:\n\n* You will want to execute methods off of the provided `page`, and `context` variables - which will be a playwright\n  [`Page`](https://playwright.dev/docs/api/class-page), and\n  [`BrowserContext`](https://playwright.dev/docs/api/class-browsercontext) objects respectively - already after\n  navigation to the specified url.\n  * You can register event handlers by using [`page.on()`](https://playwright.dev/docs/api/class-page#events) -\n    which will be registered before the page loads.\n  * Use camelCased methods - as in the playwright docs.\n  * Use JavaScript syntax - TypeScript will not be precompiled.\n  * Do not use `screenshot`/`record` in actions - instead specify this in Tourist options when dispatching the request.\n* Treat actions as top-level synchronous code. They may return either a concrete value, or a Promise - which will be\nawaited, however it's not an async context, so if you need to use `async`/`await` wrap your action in an\n[IIFE](https://developer.mozilla.org/en-US/docs/Glossary/IIFE#execute_an_async_function) expression.\n  * As actions can return a Promise - it's completely fine to execute simple playwright calls which return a Promise,\n    without the added complexity - for example `page.click('a')` is a valid action (although\n    [depreciated](https://playwright.dev/docs/api/class-page#page-click), which will be awaited.\n  * Multi-line statements are allowed inside an IIFE expression, so follow the same pattern if you need to use for\n    example, a for loop.\n\nFor full reference please check our guide on [Tourist actions](./docs/04-using-actions.md).\n\n### Legacy API\n\nWe have been using a previous version of Tourist internally at CTFd for some time. The legacy API matches our previous\nsimpler specification - most notably it **does not support authentication**, and does not allow choosing between\nsynchronous and asynchronous jobs. It's provided as a compatibility layer for us and should be considered deprecated.\n\n### Sentry\n\nTourist can capture exceptions to Sentry by configuring the `SENTRY_DSN` environment variable.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fctfd%2Ftourist","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fctfd%2Ftourist","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fctfd%2Ftourist/lists"}