Silent Outposts is a local hackathon demo for finding robot outposts that have gone unexpectedly quiet and exchanging hidden messages through AI-generated carrier text.
- Vue 3, Naive UI, and Apache ECharts provide the operational dashboard.
- A Go
net/httpAPI owns data access, analysis, and steganography operations. - SQLite stores the imported station archive and public steganography transcript records.
- Incident analysis is currently a deterministic, evidence-citing stub. It does not call OpenAI or another hosted model.
- An optional local Python/Hugging Face process generates and decodes steganographic carrier text. The Go server starts it once and reuses it.
The server imports apps/server/data/source/sunken-garden-and-cs-building.zip into SQLite on startup. Import is transactional and idempotent, so deleting the development database and restarting reconstructs it from the source archive.
Requirements: Node.js 22+, pnpm 10+, Go 1.22+, and Python 3.10+ only if enabling steganography.
pnpm install
cp apps/server/.env.example apps/server/.env
pnpm dev:dataOpen http://localhost:5173. The API runs at http://localhost:3001, and Vite proxies /api requests to it.
On first startup, the server creates one account for every imported station. Use any station ID as the username, for example Outpost-Alpha, with the local demo password station-demo unless STATION_ACCOUNT_DEFAULT_PASSWORD is changed in apps/server/.env.
pnpm dev:data explicitly disables the Python model. The full dataset, dashboard, station history, SQLite persistence, and deterministic incident summary remain available.
Use this when you want the API without the Vite frontend. From the repository root, install dependencies once and create the server env file:
pnpm install
cp apps/server/.env.example apps/server/.envStart the Go API in data-only mode:
cd apps/server
STEGANOGRAPHY_ENABLED=false go run .The backend listens on http://localhost:3001 by default. On first startup it creates/migrates apps/server/data/silent-outposts.db, imports the source archive, and seeds one login account per station. Use a station ID such as Outpost-Alpha with password station-demo unless you changed STATION_ACCOUNT_DEFAULT_PASSWORD.
To use a different port:
cd apps/server
PORT=3002 STEGANOGRAPHY_ENABLED=false go run .Quick health and authenticated API checks:
curl http://localhost:3001/api/health
TOKEN="$(curl -s http://localhost:3001/api/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"Outpost-Alpha","password":"station-demo"}' \
| node -pe 'JSON.parse(require("node:fs").readFileSync(0, "utf8")).token')"
curl http://localhost:3001/api/dashboard -H "Authorization: Bearer $TOKEN"You can also run the backend through pnpm:
pnpm --filter @vts/server dev:dataEnable the full steganography backend only after setting up the Python model in the next section and setting STEGANOGRAPHY_ENABLED=true in apps/server/.env.
The steganography feature needs a local Hugging Face model. Set it up and run its preflight once:
python3 -m venv apps/server/.venv
apps/server/.venv/bin/python -m pip install --upgrade pip
apps/server/.venv/bin/python -m pip install --index-url https://download.pytorch.org/whl/cpu torch
apps/server/.venv/bin/python -m pip install transformers huggingface-hub
cd apps/server
printf '%s\n' '{"op":"info"}' | ./.venv/bin/python conversationstenography/python/hf_model.py --model gpt2 --revision main --device cpu --dtype float32The first preflight downloads GPT-2 and may take several minutes. Success prints JSON containing "ok":true and an hf: model fingerprint. Then set STEGANOGRAPHY_ENABLED=true in apps/server/.env, return to the repository root, and run:
pnpm devBoth participants must use the same model revision, tokenizer, protocol settings, conversation ID, message order, sender spelling, and shared phrase. Carrier text must be copied exactly.
See Local demo setup for configuration and troubleshooting, and Conversation steganography integration for its API and synchronization contract.
We follows the approach demonstrated by nethical6/conversation-steganography: it encrypts a private message, then represents the resulting ciphertext through the token choices of a GPT-2-generated conversation.
When sending a message:
- The shared secret phrase and conversation ID are passed through PBKDF2-HMAC-SHA-256 to derive a 256-bit symmetric key. The phrase is not used as a random seed for GPT-2.
- The plaintext is compressed and encrypted with authenticated AES-SIV. The authentication data binds the ciphertext to the conversation, sender, message index, sender sequence, and hash of the preceding public transcript.
- The encrypted bytes are converted to a bitstream. Starting from the synchronized conversation prompt, GPT-2 deterministically calculates the possible next tokens at each step.
- A coding scheme maps portions of the encrypted bitstream to candidates from that next-token distribution. The selected tokens are appended to the prompt and become ordinary-looking carrier text. GPT-2 therefore constrains the choices to plausible continuations while the particular choices encode the ciphertext.
The receiver runs the process in reverse. Using the exact carrier text and the same GPT-2 weights, tokenizer, prompt, coding settings, and transcript state, the receiver recomputes the candidate set for every token. Each observed token identifies the encoded bits; those bits reconstruct the authenticated ciphertext, which is then verified and decrypted with the key derived from the same shared phrase.
plaintext
-> compress and encrypt with the shared-key-derived AES-SIV key
-> encode ciphertext bits as deterministic GPT-2 token choices
-> transmit the generated carrier text unchanged
-> recompute the same token choices and recover the ciphertext bits
-> authenticate, decrypt, and decompress
-> recovered plaintext
This mechanism is synchronization-sensitive. Even a small difference in carrier text, model or tokenizer revision, protocol configuration, sender spelling, conversation ID, or prior-message order can change GPT-2's next-token distribution and prevent recovery.
- SQLite stores station/archive data and public carrier transcript records.
- Station accounts and expiring session token hashes are stored in SQLite; raw session tokens live only in the current browser tab's
sessionStorage. - Shared phrases, derived keys, and plaintext messages are never persisted.
- A phrase may be kept in the current browser tab's
sessionStorage; it must not enter URLs, logs, analytics, or durable browser storage. - Steganography is experimental proof-of-concept software, not audited cryptography and not guaranteed to be undetectable. Use TLS outside this local demo.
pnpm dev # dashboard plus steganography when enabled in apps/server/.env
pnpm dev:data # dashboard with the model forcibly disabled
pnpm typecheck # TypeScript checks and Go tests
pnpm build # production frontend and backend builds
pnpm start # run the previously built Go APIFocused backend checks:
cd apps/server
go test ./...
go vet ./...
cd conversationstenography
go test ./...
go vet ./...apps/server/conversationstenography is derived from GPL-3.0 software and is linked directly into the Go backend. Distributing the combined backend may impose GPL-3.0 obligations. Preserve its license and source notices, and review the integration notes before distributing or deploying the application. This is not legal advice.