Launchlet
Launchlet — конструктор приложений с preview, панелью деплоя и API данных. На старте есть URL сервиса. В клиентских манифестах находятся токены, идентификаторы и описания трёх серверных операций с неполной проверкой прав.
## Решение
Открываем DevTools и смотрим, что загружает интерфейс:
| Ресурс | Что узнаём |
|---|---|
| `/api/app/bootstrap` | Параметры preview и API данных |
| `/assets/build-manifest.json` | Токен получения build receipt |
| `/assets/mock-supabase-client.js` | Ключ API и гостевую сессию |
| `/assets/server-reference-manifest.json` | Идентификатор серверного действия |
### Токен receipt
В `build-manifest.json` находим проект `launchlet-demo-board`, `previewReceipt = lrec_preview_demo_2026` и адрес `/api/receipts/redeem`. Пробуем предъявить найденный токен:
```bash
curl -sS http://:31380/api/receipts/redeem \
-H 'Content-Type: application/json' \
--data '{"receipt":"lrec_preview_demo_2026","project":"launchlet-demo-board"}'
```
Сервер принимает его как право прочитать receipt и возвращает первый флаг. Build pipeline оставил в публичном артефакте повторно используемый токен доступа.
### Доступ к workspace
Из bootstrap или клиентского JS берём `anonKey = ll_anon_mock_publishable` и сессию `preview_guest_session`. Интерфейс читает `workspace_notes` с фильтром `workspace_id=eq.ws-launchlet-public`.
Проверяем, ограничивает ли сессия доступ к строкам. В имени workspace заменяем `public` на `internal`, сохраняя гостевой ключ и сессию:
```bash
curl -sS 'http://:31380/api/db/rest/v1/workspace_notes?select=workspace_id,title,body&workspace_id=eq.ws-launchlet-internal' \
-H 'apikey: ll_anon_mock_publishable' \
-H 'Authorization: Bearer preview_guest_session'
```
Получаем внутреннюю заметку со вторым флагом. API проверяет ключ и наличие preview-сессии, но выбирает доступные строки по переданному query-параметру. Workspace сессии с ним не сверяется.
### Область серверного действия
В `server-reference-manifest.json` открываем запись `serverActions.inspectDeploy`. Из неё берём идентификатор `act_6f4b9a2c1d0e8a71` и endpoint `/api/actions/invoke`. Манифест также описывает `intendedScope` — область действия. Проверяем, привязывает ли сервер переданные аргументы к этой области.
Для вызова нужны оба идентификатора, `workspaceId` и `projectId`. Проверяем пару `ws-launchlet-internal` и `proj-enterprise-rollout`, сохраняя публичный проект в заголовке `X-Launchlet-Preview`.
```bash
curl -sS http://:31380/api/actions/invoke \
-H 'Content-Type: application/json' \
-H 'X-Launchlet-Preview: launchlet-demo-board' \
--data '{
"actionId":"act_6f4b9a2c1d0e8a71",
"args":{
"workspaceId":"ws-launchlet-internal",
"projectId":"proj-enterprise-rollout"
}
}'
```
Dispatcher принимает чужие workspace и project с гостевым preview-контекстом и возвращает третий флаг. Проверка идентификатора действия есть, а привязки аргументов к разрешённой области нет. В этой ветке, как и при чтении заметок, параметры клиента позволяют выбрать чужие данные.
[Солвер](solve/solve.py).
## Все этапы
| Этап | Флаг |
|---|---|
| 1 | `caplag{preview_manifest_kept_the_receipt}` |
| 2 | `caplag{anon_key_needs_real_rls}` |
| 3 | `caplag{server_actions_need_scope_checks}` |