Skip to content

Add Cloudflare Workers configuration#1

Open
cloudflare-workers-and-pages[bot] wants to merge 16 commits intomainfrom
cloudflare/workers-autoconfig
Open

Add Cloudflare Workers configuration#1
cloudflare-workers-and-pages[bot] wants to merge 16 commits intomainfrom
cloudflare/workers-autoconfig

Conversation

@cloudflare-workers-and-pages
Copy link
Copy Markdown

This PR configures your project for Cloudflare Workers deployment using Wrangler autoconfig.

Merging this PR commits the configuration to your repository, enabling faster deployments and version controlled settings.

Detected settings:

  • Framework: static

  • Deploy command: npx wrangler deploy

  • Version (non-production deploy) command: npx wrangler versions upload

Note: For this PR, we used these detected settings to generate a working preview. When merged, we'll override your build and deploy commands if they don't match what's currently configured, ensuring successful deployments for your setup.

Next steps after merging:
Your Worker configuration lives in wrangler.jsonc. You can now:


View build details · Join the discussion for questions or feedback

rschumann and others added 16 commits April 10, 2026 09:31
Comprehensive reverse-engineered audit of every API, feature, and data
flow. Server-side stack verified 100% working. Client-side tunnel has
a macOS-specific environmental bug on hosts with pre-existing VPN
interfaces.

All bugs found during audit have been fixed or documented as follow-ups.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Proxies to DAM's agent restart endpoint via Provider. After editing
user overlay configs (config.user.json for OpenClaw, config.user.yaml
for Hermes), run 'tytus restart' to apply the changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pairs with Provider's new GET /pod/user-key and the Scalesys
user_stable_keys table. The user configures OPENAI_BASE_URL +
OPENAI_API_KEY in Cursor/Claude/etc exactly once, forever.

* pods/src/user_key.rs (new): get_user_key(&client) fetches the
  user's stable pair from Provider /pod/user-key. Returns a typed
  (endpoint, key) tuple. 404 maps to a friendly "run tytus connect
  first" error.

* pods/src/request.rs: PodAllocation gains stable_ai_endpoint and
  stable_user_key fields — returned by Scalesys via Provider on
  every allocation so tytus-cli can cache them without a second
  round-trip.

* cli/src/state.rs: PodEntry gains stable_ai_endpoint and
  stable_user_key (both #[serde(default)] for migration from older
  state.json files).

* cli/src/main.rs: cmd_env is now async and takes an HttpClient.
  Default output (no flags) emits the stable pair:
    OPENAI_BASE_URL=http://10.42.42.1:18080/v1
    OPENAI_API_KEY=sk-tytus-user-<32hex>
  plus TYTUS_API_KEY + TYTUS_AI_GATEWAY as aliases.

  --raw emits the per-pod values (old behaviour, kept as an escape
  hatch for debugging and backwards compat). Users who want
  per-pod routing for specific tools can still use it.

  If state has no stable_user_key cached (pre-Phase-2 state.json),
  the command lazily fetches one via get_user_key and persists it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ytus

The metaphor: tytus-cli is a parasite, the AI agent (Claude Code,
OpenCode, KiloCode, Cursor, etc.) is the ant. Without rich docs the
ant has hands but no idea what levers exist. This commit hands over
the full lever map.

* New `llm-docs.md` at the workspace root — the canonical 320-line
  reference for AI agents. Covers: what Tytus is, names/concepts,
  plans + unit budgets, agents (nemoclaw=1u, hermes=2u), the FIXED
  set of models on the gateway (ail-compound, ail-image, ail-embed,
  minimax/ail-compound, minimax/ail-image — no others, no inventing),
  the stable URL (10.42.42.1) + stable user key model, every
  subcommand with its purpose, the seven MCP tools, five standard
  recipes, an error catalog, hard rules, state/storage layout, and
  what's intentionally not exposed.

* `tytus llm-docs` — new CLI subcommand that prints the reference.
  Backed by `include_str!("../../llm-docs.md")` so the binary doesn't
  need a separate file at runtime.

* `tytus_docs` — new MCP tool that returns the same content. Uses
  the same include_str! source so the cli and mcp binaries can never
  drift. Added to .mcp.json's alwaysAllow list when `tytus infect`
  scaffolds a project so AI agents can call it without prompting.

* Rewrote CLAUDE_MD_BLOCK (Claude Code), AGENTS_MD_BLOCK (OpenCode /
  Codex / Gemini), CLAUDE_COMMAND_TYTUS, KILO_COMMAND_TYTUS, and
  ARCHON_COMMAND_TYTUS. Each one now points first at `tytus llm-docs`
  as the source of truth, then summarizes the relevant subset for
  the target tool (slash command body, AGENTS.md block, etc.).
  The old constants referenced phantom models (qwen3-8b,
  llama-3.1-8b-instruct, "383+ models") that don't exist on this
  product — all replaced with the real five-model catalog.

* Rewrote every MCP tool description in mcp/src/main.rs:
  - tytus_docs:        cache the reference at session start
  - tytus_status:      always call first; explain the response shape
  - tytus_env:         document the stable vs raw distinction loudly
  - tytus_models:      list the actual five models in the description
  - tytus_chat:        constrain `model` to enum of valid ids;
                       explain MiniMax reasoning_content quirk re max_tokens
  - tytus_revoke:      mark DESTRUCTIVE, require user confirmation
  - tytus_setup_guide: tell the agent to fall through to it on no-pod state

After running `tytus infect .` in a project, an AI agent like Claude
Code will see CLAUDE.md, AGENTS.md, .claude/commands/tytus.md,
.kilo/command/tytus.md, .archon/commands/tytus.md, .mcp.json, and
.tytus-env.sh — all with content that points at `tytus llm-docs` for
the full picture and contains enough inline guidance to bootstrap
any operation without further hand-holding.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three coordinated changes that make Tytus adoptable by any AI CLI on the
user's machine with a single copy-paste prompt — matching the 2md pattern.

=== .agents/skills/tytus/SKILL.md (new, hosted on GitHub) ===

YAML-frontmattered agent skill file at
.agents/skills/tytus/SKILL.md, mirroring traylinx/2md's
.agents/skills/2md/SKILL.md layout. Any AI tool can be bootstrapped via
a single URL fetch of the raw.githubusercontent.com link. Contents:

  1. How to check if `tytus` is installed, and how to install it
     (`curl -sSfL .../install.sh | sh`) if not.
  2. How to read the full reference with `tytus llm-docs`.
  3. How to branch on `tytus status --json` for setup flow.
  4. The stable URL/key pair, plan tiers, the FIVE real models on the
     pod gateway (ail-compound, ail-image, ail-embed, minimax/*), the
     agent types (nemoclaw=1u, hermes=2u).
  5. Five recipes (chat, use in local tool, switch agent, edit overlay,
     link a project), error catalog, hard rules.

=== install.sh (rewritten) ===

Previous version relied on GitHub releases that don't exist yet and
would error out on `Error: Could not find release`. New version:

  1. Detects OS + arch (macOS / Linux, x86_64 / aarch64).
  2. Tries a prebuilt tarball download from the releases endpoint.
  3. Falls back to `cargo install --git` if no release is published.
  4. If cargo is missing, prompts (via /dev/tty so it works when piped
     from curl) to install Rust via rustup.
  5. Sets up passwordless sudoers for `tytus tunnel-up` so `tytus
     connect` never prompts for a password (opt-out with
     TYTUS_SKIP_SUDOERS=1).
  6. Prints colored next-steps: `tytus setup`, `tytus connect`,
     `tytus env --export`, `tytus link .`, `tytus bootstrap-prompt`,
     `tytus llm-docs`.

Env vars: TYTUS_INSTALL_DIR, TYTUS_SKIP_SUDOERS, TYTUS_FORCE_SOURCE.
Idempotent — re-running upgrades in place.

=== tytus infect → tytus link ===

"Infect" was too negative for users. New primary name: `tytus link`
(link this project to Tytus). The old name remains as a hidden alias
(#[command(alias = "infect")]) so existing docs and muscle memory keep
working. User-facing output now says "Tytus linked into <dir>" instead
of "Tytus integration injected". JSON output key is "linked".

=== tytus bootstrap-prompt (new command) ===

Prints a short prompt mirroring 2md's "Read this URL and follow the
instructions" pattern. Hostname-stable reference to
raw.githubusercontent.com/traylinx/tytus-cli/main/.agents/skills/tytus/SKILL.md
plus a one-line install command. Users copy-paste it into any AI tool
to enable Tytus natively.

=== Docs updated ===

* llm-docs.md: infect→link in the command reference, noted the alias,
  added a bootstrap-prompt entry.
* cli/src/main.rs: all slash-command bodies and status-hint messages
  use `link` + `bootstrap-prompt` where appropriate.

Verified locally: `tytus link .` writes the full integration stack,
`tytus infect .` still works via alias, `tytus bootstrap-prompt`
prints the one-liner, `tytus llm-docs` prints the updated 330+ line
reference. install.sh passes both bash -n and sh -n syntax checks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comprehensive security audit before flipping the repo to public so the
2md-style hosted-skill bootstrap pattern can use raw.githubusercontent.com.
Full report in docs/SECURITY-AUDIT.md. 13 findings across 7 phases; 1
CRITICAL, 3 HIGH, 3 MEDIUM, 6 LOW, 2 INFO. All blockers fixed.

=== CRITICAL — sudoers privilege escalation ===

The installer's sudoers entry included `/bin/kill -TERM *` which let any
local user SIGTERM ANY process as root (PID 1, system services, other
users' procs, security daemons). Real privilege escalation, not just bad
hygiene.

Fix: new hidden subcommand `tytus tunnel-down <pid>` that validates the
target PID against /tmp/tytus/tunnel-*.pid before signalling, refuses
PID <= 1, and verifies the process is alive (cleaning stale pidfiles).
The sudoers entry is now scoped to ONLY:

  ${USER} ALL=(root) NOPASSWD: ${BIN_PATH} tunnel-up *, ${BIN_PATH} tunnel-down *

cmd_disconnect now invokes `sudo -n <self> tunnel-down <pid>` instead
of `sudo -n kill -TERM <pid>`. The validation lives in the Rust binary
where it's tamper-evident, not in the sudoers grammar.

=== HIGH — README.md leaks + outdated content ===

Old README contained:
* `sk-566cecd...09a0` and `sk-c939e2...2318` — truncated display forms of
  real production pod 01/02 keys (prefix+suffix exposed)
* `10.18.1.1`, `10.18.2.1` — production internal pod gateway IPs
* phantom models like `qwen3-8b` and "383+ models" (the real catalog has
  five models: ail-compound, ail-image, ail-embed, minimax/ail-compound,
  minimax/ail-image)
* a broken install URL: tytus.traylinx.com/install.sh doesn't exist
* "zombie fungus" / "parasitize" / "infect" wording

Full rewrite. Uses placeholder/stable values throughout
(http://10.42.42.1:18080/v1, sk-tytus-user-<32hex>), the accurate
five-model catalog, the correct raw.githubusercontent.com install URL,
the new positive `tytus link` verb, and a Security section that links
to the audit report.

=== HIGH — docs/VERIFICATION-2026-04-10.md is internal only ===

A 6.7KB engineering verification report committed under docs/. Contained
the production droplet IP (212.227.205.146), droplet ID (strato-eu-001),
exact resource specs, internal commit hashes from sibling private repos,
K8s deployment names, internal architecture details, and a "what's broken"
section. Never intended for public consumption.

Fix: file deleted from the working tree. (History rewrite is flagged as
operator-decision follow-up #2 in the audit report — the file remains in
git history until the operator decides whether to filter-repo it out
before flipping visibility.)

=== HIGH — docs/WIZARDS.md leaked internal IP ===

Used `http://10.18.1.1:18080` in a "Returning user" example. Replaced
with the stable `http://10.42.42.1:18080` and clarified "(stable, never
changes)".

=== MEDIUM — RUSTSEC-2026-0037 in quinn-proto 0.11.13 ===

Known high-severity vulnerability (CVSS 8.7) in the QUIC implementation
pulled in transitively via reqwest. Fixed by `cargo update -p quinn-proto`
to 0.11.14. Cargo.lock committed.

=== MEDIUM — CLAUDE.md outdated ===

Engineering CLAUDE.md still said `tytus infect`, missed the new
link/bootstrap-prompt/llm-docs/tunnel-down commands, and had stale
architecture notes. Rewritten to reflect current state, hidden
subcommands, security invariants, the stable URL/key model, and
contributing guidelines.

=== MEDIUM — broken install URL in mcp/src/tools.rs ===

The tytus_setup_guide MCP tool returned a step pointing at the dead
tytus.traylinx.com/install.sh URL. Replaced with the correct
raw.githubusercontent.com path. Also removed the now-incorrect "requires
sudo for TUN device" wording (the elevation chain handles it internally).

=== LOW — .gitignore expanded ===

Was just target/, *.swp, .DS_Store. Added explicit blocks for .env*,
*.pem, *.key, *.p12, *.pfx, *.crt, secrets/, state.json, **/state.json,
*.log, logs/, .idea/, .vscode/, *.iml, .cache/, with !.env.example to
allow committing example templates.

=== LOW — clippy clean ===

23 warnings (style, dead-code, needless borrow, map_or → is_none_or,
match → matches!). Auto-fixed the trivial ones via `cargo clippy --fix`,
hand-fixed the four that needed annotations:
- auth: #[allow(dead_code)] on WannolotPassResponse with explanation
- pods: #[allow(dead_code)] on post_with_retry with API-symmetry note
- tunnel/monitor: rewrote match → matches!
- cli: removed misplaced #[allow(dead_code)] above CLAUDE_MD_BLOCK

Result: `cargo clippy --workspace --all-targets` returns ZERO warnings.

=== LOW — Cargo.toml metadata ===

Added description, homepage, repository, documentation, readme, keywords,
categories, rust-version. Crate is now ready for `cargo publish` if/when
we want it on crates.io.

=== LOW — Source comments sanitized ===

tunnel/src/lib.rs and tunnel/src/monitor.rs comments used concrete past
production droplet octets (10.17.8.X, 10.18.1.X) as examples. Replaced
with placeholder format (10.X.Y.Z) plus a note that the stable address
10.42.42.1 is now appended to AllowedIPs by the bootstrap.

=== Findings NOT requiring code changes ===

* Hardcoded production HTTPS endpoints (tytus.traylinx.com,
  api.makakoo.com, sentinel.traylinx.com) are public SaaS endpoints — they
  WILL appear in strings(1) output of any compiled binary regardless of
  storage form. Including them in source is correct for a SaaS client.
* Keychain service name `com.traylinx.atomek` (legacy codename) — changing
  it would invalidate existing user keychain entries. Documented as
  do-not-touch in CLAUDE.md.
* fxhash and number_prefix unmaintained-crate warnings — neither is a
  vulnerability. Tracked for next dependency sweep.

=== Verification gate (all green) ===

* `cargo build --release -p atomek-cli -p tytus-mcp` — clean
* `cargo clippy --workspace --all-targets` — 0 warnings
* `cargo audit` — 0 vulnerabilities, 2 unmaintained-crate warnings
* `cargo test --workspace` — passes (0 tests; backlog item LOW-3)
* `sh -n install.sh && bash -n install.sh` — both clean
* `tytus tunnel-down 1` — refuses PID 1 with clear error
* `tytus tunnel-down <random>` — refuses unknown PID
* README has no truncated key fingerprints
* README has no internal IPs
* docs/VERIFICATION-*.md removed

=== Operator sign-off ===

After reviewing docs/SECURITY-AUDIT.md, the operator can flip the repo:

  gh repo edit traylinx/tytus-cli --visibility public \
    --accept-visibility-change-consequences

The audit report's "Operator sign-off" section lists the post-flip
verification steps (test the bootstrap prompt with a fresh install,
cut v0.1.0 release, etc).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pre-flip-to-public secret sweep caught two illustrative examples that
happened to match real production internal IPs:

* llm-docs.md §5: the "tytus env --raw" example used http://10.18.2.1:18080
  and sk-c939e21c... (matches pod 02 on the live droplet). Replaced with
  placeholder format http://10.X.Y.1:18080 + sk-<48 hex> and clarified
  these change on pod rotation, droplet migration, or octet reassignment.

* tunnel/src/wireguard.rs:133 doc comment used "10.18.1.0/24" as the
  AllowedIPs format example. Replaced with "10.X.Y.0/24".

After this commit, the only file in the repo that still contains the
documented leak strings is docs/SECURITY-AUDIT.md itself — that is by
design, since a security audit must describe what it found. Verified
via:

  grep -rnE 'sk-[a-f0-9]{40,}|212\.227\.205\.146|sk-566cecd|sk-c939e2|10\.18\.[0-9]+\.[0-9]+|10\.17\.[0-9]+\.[0-9]+' \
    --include='*.rs' --include='*.sh' --include='*.md' --include='*.toml' \
    --include='*.json' --include='*.yml' --include='*.yaml' . \
  | grep -v docs/SECURITY-AUDIT.md
  # → empty

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… + ui

Sprint doc: docs/sprints/SPRINT-TYTUS-PAYING-CUSTOMER-READY.md (in ProjectWannolot).
Mandate: a paying customer with only their subscription and tytus CLI must go
zero-to-working-pod with zero errors. Triggered by live debug session where
every failure mode below was hit.

FIX-1 wg keepalive + handshake watchdog (tunnel/src/wireguard.rs)
  Force persistent_keepalive=25 regardless of server config. Add 90s idle-RX
  watchdog that re-initiates handshake via format_handshake_initiation(force=true)
  throttled at 15s. Upgrade silently-swallowed debug! on encap/decap/UDP/TUN
  errors to warn! so real failures show at default log level. Solves the
  "tunnel silently dies after ~20min of idle" split-brain.

FIX-2 disconnect reaps from pidfile (cli/src/tunnel_reap.rs + disconnect handler)
  Pidfile at /tmp/tytus/tunnel-NN.pid is authoritative, not state.json.tunnel_iface
  (which gets cleared on revoke, leaving disconnect blind). New shared module
  with ReapOutcome { Reaped, NoPidfile, StalePidfile, ReapFailed }, uses scoped
  sudoers 'tytus tunnel-down <pid>' helper — no password prompt. 500ms SIGTERM
  grace. 12 integration tests.

FIX-3 revoke reaps before wiping (revoke handler)
  Revoke now calls reap_tunnel_for_pod() before the Provider API call + state
  wipe. Invariant: after revoke returns, zero orphan processes. Revoke still
  completes if reap fails — user explicitly asked to destroy. 3 integration tests.

FIX-4 packet-loop exit detection (cmd_tunnel_up)
  tokio::select! on ctrl_c() vs. packet-loop JoinHandle so unexpected task
  completion logs FATAL to /tmp/tytus/tunnel-NN.log and exits code 2 instead
  of sitting there pretending to be alive while utun was dropped. Adds
  TunnelHandle::cancel_token() + take_task() to atomek-tunnel.

FIX-5 THE BIG ONE — daemon detach (cmd_tunnel_up)
  Daemon died 3-4 minutes after every tytus connect. Root cause: stdout/stderr
  piped back to parent tytus connect so parent could read TUNNEL_READY. Once
  parent exited, pipes closed, the first subsequent write (keepalive tick,
  tracing warn!, watchdog log) hit SIGPIPE → default handler killed daemon.
  Plus: daemon inherited parent's session so closing terminal sent SIGHUP to
  whole session. Fix: libc::setsid() for own session, signal(SIGHUP, SIG_IGN),
  signal(SIGPIPE, SIG_IGN), dup2(/dev/null, {0,1,2}) after flushing TUNNEL_READY.
  Without this, every real user closing their terminal would kill their tunnel.
  Verified: daemon survived 6m37s+ past previous death zone.

FIX-6 autostart on boot (new subcommand: tytus autostart install|uninstall|status)
  macOS: writes ~/Library/LaunchAgents/com.traylinx.tytus.plist + launchctl load -w.
  Linux: writes ~/.config/systemd/user/tytus.service + systemctl --user enable --now.
  After reboot, the LaunchAgent/unit runs 'tytus connect' at login, reuses
  the stable pair (http://10.42.42.1:18080 + sk-tytus-user-*) from state.json.
  User's apps keep working across reboots with zero reconfig — the
  "like Ollama" experience.

tytus ui — new subcommand for OpenClaw Control UI access
  Browsers refuse WebCrypto / device-identity APIs on non-localhost HTTP.
  Direct http://10.18.X.1:3000 gets the "control ui requires device identity"
  banner. tytus ui starts a 127.0.0.1:3000 TCP forwarder to the pod's agent
  port via tokio::io::copy_bidirectional, opens the default browser, blocks
  on Ctrl+C. --port override for conflicts, auto-fallback to next 5 ports,
  --no-open for headless.

Test suite: 24/24 green (tunnel_reap unit 9 + disconnect_pidfile 12 + revoke_reaps_daemon 3).
cargo build --release --workspace clean. cargo clippy --workspace --all-targets -- -D warnings clean.

Validator lane: opencode PASS on FIX-1/2/3. gemini-cli second opinion PASS on
FIX-2/3 merge strategy. FIX-4/5/6 shipped inline during live diagnosis and
pending post-commit opencode review.

Known follow-up: cleanup_files in tunnel_reap.rs silently fails on root-owned
stale pidfiles because disconnect runs as user. Fix in a follow-up sprint by
routing deletion through the scoped tunnel-down helper or daemon-side 0666 perms.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ctural audit

Sprint 1: Headless Token Refresh & Startup Reliability
- ensure_token() returns Result<()> — all 10 callers handle errors explicitly (F2)
- Headless detection: --headless flag + TYTUS_HEADLESS=1 env var (F3)
- Proactive token refresh in 5-10min window before expiry (F4)
- Server-side token validation via GET /oauth/token/info (F1)
- Structured diagnostic log to /tmp/tytus/autostart.log (F7)
- save_critical() for token rotation — prevents RT loss on disk write failure
- SIGTERM handler in tunnel daemon — prevents silent tunnel death
- Stale tunnel detection in tytus status via reap_dead_tunnels()
- LaunchAgent plist updated with TYTUS_HEADLESS=1

Sprint 2: Daemon Skeleton (Phase 1)
- New daemon.rs module: Unix socket server at /tmp/tytus/daemon.sock
- JSON-line protocol: ping, status, refresh, shutdown commands
- Background token refresh loop (5-minute interval)
- CLI subcommands: tytus daemon run/stop/status
- Graceful shutdown: SIGTERM + SIGINT via tokio::signal

Sprint 3: System Tray (tytus-tray)
- New tray/ crate: menu bar icon for macOS (tray-icon + objc2-app-kit)
- Dynamic "Open in" submenu: detects installed AI CLIs on PATH
- Launch flow: write temp script → tytus link --only <filter> → export env vars → launch CLI
- CLI mapping: Claude→claude, OpenCode→opencode, Gemini/Codex→agents, Aider→shell
- Self-deleting temp script (no API keys persist on disk)
- Terminal detection: iTerm2 > Terminal.app

Documentation: llm-docs.md, CLAUDE.md updated with new features.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ting

Six user-facing guides in docs/guides/:
- Getting Started: install → setup → first chat in 2 minutes
- Use with AI Tools: Claude Code, Cursor, OpenCode, Gemini, Aider, Vibe
- Plans, Agents, and Models: tiers, unit budgets, model catalog
- Auto-Start and Daemon: survive reboots, tray icon, background refresh
- Common Use Cases: copy-paste recipes for real scenarios (Python, curl,
  CI/CD, image generation, embeddings, team sharing)
- Troubleshooting: fix common issues in 30 seconds, tytus doctor guide

All guides are use-case centric, not developer-oriented. Written for
end users who want to set up their pod and start coding.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CRITICAL SECURITY FIX: tytus status/connect no longer exposes:
- droplet_id (infrastructure naming → hosting provider identification)
- droplet_ip (public IP → enables direct SSH brute-force)
- ai_endpoint (internal 10.18.x.y IPs → pod subnet mapping)
- agent_endpoint (internal agent port → attack surface)
- pod_api_key (raw per-pod key → credential sprawl risk)

User-facing output now shows ONLY:
- pod_id, agent_type, tunnel_iface (operational)
- stable_ai_endpoint (10.42.42.1:18080 — designed for exposure)
- stable_user_key (masked in human output, full in JSON)

Infrastructure details still accessible via `tytus env --raw` for
debugging (explicit opt-in).

Full security audit documented in docs/SECURITY-HARDENING-2026-04-12.md:
- Network scan: cross-pod isolation verified (PASS)
- Metadata API blocked (PASS)
- K8s not reachable through tunnel (PASS)
- Droplet SSH open on public internet (CRITICAL — flagged for infra team)
- /metrics endpoint unauthenticated (MEDIUM — flagged for infra team)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Full reverse-engineering security audit by 3 parallel auditors with
independent review by OpenCode (MiniMax) and Gemini CLI.

CRITICAL (1):
- C1: install.sh downloads binary with no checksum + grants passwordless
  root via sudoers wildcard — unauthenticated path to root

HIGH (5):
- H1: Hardcoded API key in binary (extractable via strings)
- H2: Refresh token in plaintext state.json (contradicts security docs)
- H3: Sudoers wildcard "tunnel-up *" allows arbitrary file read as root
- H4: WG private key in predictable temp file with race window
- H5: MCP tytus_env returns raw keys instead of stable values

MEDIUM (12), LOW (8), INFO (8) — see docs/SECURITY-DEEP-AUDIT-2026-04-12.md

Team consensus: C1 is launch-blocking. H1-H5 must fix before launch.
Network isolation (cross-pod, metadata, K8s) verified PASS.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Active exploitation of every finding from the deep audit.
Each rated EXPLOITED (proven) or MITIGATED (proven).

EXPLOITED (proven with live exploit):
- E1 (10/10): /bin/kill -TERM * in sudoers — any process can kill
  ANY system process as root including PID 1. IMMEDIATE fix needed.
- E2 (9/10): refresh_token in plaintext state.json — stolen and used
  to get new access_token. Full account takeover from one file.
- E3 (8/10): MCP tytus_env returns raw per-pod API key + internal
  IPs to AI agents. Never updated when CLI was fixed.
- E4 (4/10): Daemon socket status response leaks ai_endpoint (internal IP)
- E5 (6/10): /tmp/tytus/ directory is 744 (world-readable)

MITIGATED (proven safe):
- CLI status output (fixed this session)
- Cross-pod isolation (verified by network scan)
- DNS through tunnel (no internal resolution)
- Subnet isolation (docker bridge, host network unreachable)
- Hardcoded key not trivially extractable (partially mitigated)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rew tap

Grandma-grade public install surface:

  curl -fsSL https://tytus.traylinx.com/install.sh | bash
  powershell -c "irm https://tytus.traylinx.com/install.ps1 | iex"
  brew install traylinx/tap/tytus

Security (from pentest C1):
  - install.sh now downloads SHA256SUMS and verifies before extracting;
    refuses to install if the sums file is missing or a mismatch is detected.
    Escape hatch: TYTUS_SKIP_CHECKSUM=1 (not recommended).
  - Sudoers wildcard tightened from `tunnel-up *` to
    `tunnel-up /tmp/tytus/tunnel-*.json` so an attacker can no longer point
    tunnel-up at arbitrary files. tunnel-down helper already validates PIDs.
  - release.yml generates SHA256SUMS across all artifacts so the verification
    path actually has something to verify against.

New files:
  - install.ps1         Windows installer (release path + cargo fallback)
  - web/index.html      single-file landing page (no JS frameworks)
  - web/build.sh        Cloudflare Pages build step
  - web/_redirects      routing for future expansion
  - contrib/homebrew/tytus.rb       formula template with {{VERSION}} + SHAs
  - .github/workflows/homebrew.yml  auto-publish to traylinx/homebrew-tap
  - docs/PUBLISHING.md  end-to-end release + infra runbook

Release workflow changes:
  - Added aarch64-unknown-linux-gnu target via cross
  - Flattens artifacts into dist/ then emits SHA256SUMS
  - Attaches SHA256SUMS to the release so install.sh can fetch it

Note: Windows tunnel support is still experimental (wintun.dll not bundled
yet). install.ps1 prints a clear warning. Login/chat/env/link/mcp all work.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Closes every exploited finding from docs/PENTEST-RESULTS-2026-04-12.md
except the infra-team items (SSH on droplet, /metrics auth).

  E2/H2  refresh_token no longer in state.json
  E3/H5  MCP tytus_env returns stable values by default
  E4     daemon socket status drops ai_endpoint
  E5     /tmp/tytus/ is 0700 and files inside are 0600
  H1     hardcoded Api-Key formally documented as public client ID

# E2/H2 — refresh_token out of state.json
`cli/src/state.rs` and `mcp/src/state.rs` now mark refresh_token as
`#[serde(default, skip_serializing)]`. Load() reads it from the OS keychain
on every call, so the rest of the CLI continues to work unchanged.
Legacy state.json files that still contain a refresh_token are migrated
eagerly in load(): the value is copied into the keychain, then save_critical()
rewrites the file immediately without the field. This closes the window
where a failed downstream call would leave plaintext tokens on disk.

CRITICAL FIX: `keyring = "3"` ships with NO backends by default — the
default bundle is a stub that silently accepts writes and never persists.
Enabled features: apple-native, linux-native-sync-persistent,
sync-secret-service, windows-native. Before this fix, every previous release
that relied on "store in keychain" was actually a no-op. Verified
end-to-end: legacy state.json with RT is now migrated into the real macOS
login keychain, and subsequent reads load it back.

# E3/H5 — MCP stable-by-default
mcp/src/tools.rs: tytus_env now returns stable_ai_endpoint +
stable_user_key by default. Added `raw` boolean parameter for debug mode
(legacy per-pod values). tytus_status no longer leaks ai_endpoint or
agent_endpoint. tytus_models and tytus_chat prefer stable values and fall
back to per-pod for robustness. mcp/src/main.rs schema updated.

# E4 — daemon status redaction
cli/src/daemon.rs: removed ai_endpoint from the `status` socket response.
Now emits only pod_id, agent_type, tunnel_iface, stable_ai_endpoint,
stable_user_key. The CLI's print_*_status() already redacts the same way;
the daemon must not leak more.

# E5 — /tmp/tytus/ permissions
Added `secure_tytus_tmp_dir()` + `secure_chmod_600()` helpers in main.rs
and wired every /tmp/tytus/ create/write site: tunnel-up (runs as root),
daemon socket + pidfile, autostart.log, tray launcher script, connect's
pid+iface files. Directory is 0700, every file inside is 0600. The live
machine's existing files were also repermissioned.

# H1 — hardcoded Api-Key decision
Consulted opencode (MiniMax) + gemini CLI. Weight of evidence: this is
a public client identifier, not a secret. Every endpoint that uses it
also requires user credentials. Documented as PUBLIC_CLIENT_API_KEY in
auth/src/login.rs and auth/src/sentinel.rs with a long comment explaining
the threat model. New docs/SECURITY.md captures the full security model
including why this is shipped intentionally (same pattern as Firebase API
keys, Auth0 client_id, Stripe publishable keys).

Not rotatable without breaking every installed binary — if Rails ever
adds an endpoint that trusts Api-Key alone, this ceases to be safe and
must be caught in Rails review, not CLI review.

# Testing
- cargo check --workspace: clean
- cargo clippy --workspace: clean (one dead_code warning fixed)
- cargo test --workspace: 24 tests passed
- End-to-end migration test: legacy state.json → keychain + stripped file
- /tmp/tytus perms verified: 0700 dir, 0600 files
- Daemon status response verified redacted
- MCP tytus_env verified stable-by-default

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages
Copy link
Copy Markdown
Author

cloudflare-workers-and-pages Bot commented Apr 12, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
tytus-cli 4bcaaf9 Commit Preview URL

Branch Preview URL
Apr 12 2026, 10:19 PM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant