| Field | Value |
|---|---|
| Date | 2026-09-13 |
| Harness version | live-sketch-20 → phone-chat-21 |
| Models | No real inference; deterministic save and synthetic deletion tool response |
| Evidence grade | C; actual Tailscale HTTPS transport, not live model quality |
| Pre-registered | Yes, before implementation; baseline 781 passing tests from preceding naming change |
| Source record | PHONE-CHAT.md; evidence/RL-031/live-check.json |
Observation
User: “I want to be able to use tiny chat from my phone right now, the chat will be hosted on the computer but the phone will send requests to the pc, it should be done securely through a qr code or something”. Installation approval: “Yes go ahead, thank you for asking”. Continuation: “Continue”.
Hypotheses
H1: a loopback-only, Tailscale HTTPS-proxied companion with same-account identity checks, one-use 2-minute QR invitation, explicit local approval, 8-hour revocable session and current-conversation binding can expose Chat without exposing the desktop IPC/tool catalogue or changing its approval gates.
Intervention
PhoneChat server and phone web client; six explicit desktop IPC methods, local pairing dialog and remote-turn desktop events. Same ordinary host run path, no new model prompts, tools or per-inference context. No generic host dispatcher over HTTP. Changing Chat revokes all devices/invitations immediately, including when switching away and back before the phone polls. QR/session secrets are memory-only digests. Phone submission IDs are idempotent for the host lifetime, with a 1,000-message cap instead of eviction that could permit replay.
Method
Test unauthorized/expired/replayed pairing, origin/host/identity rejection, revocation, conversation switches, duplicate messages, bounded bodies and errors, phone layout and the real host path. Synthetic tests establish boundaries, not an independent security audit. Verify the actual HTTPS endpoint separately.
Research: Tailscale Serve, CLI, OWASP TLS, OWASP sessions.
Dependency decision: QR encoding with error correction and PNG output is not a safe 20-line implementation. Add pinned qrcode 1.5.4 (local generation only) and its TypeScript definitions; no remote QR service ever receives pairing secrets. Use Node's built-in cryptography, not custom encryption. No agent framework.
Results
| Condition | Before | After | n |
|---|---|---|---|
| Full deterministic suite | 781 passing, no phone surface | 789/789 passing | One final full run; added eight phone tests |
| Typecheck/build | Existing passing baseline | Both pass | One final typecheck, repeated build while implementing |
| Dedicated phone boundary suite | Not present | 8/8 pass | One final targeted run |
| Actual private HTTPS + desktop/phone-sized browser | Not present | Six workflow checks pass | Final instrument once, with earlier failures and earlier passing revision retained below |
| Actual user iPhone Safari session | Not paired | Not tested | 0 |
| Actual model generation | Not measured | No inference or allowance consumed by these checks | 0 |
The live check used an isolated SQLite database and temporary browser profile. It exercised real private Serve and certificate validation; only the model response was synthetic. It saved a note through the deterministic host path, requested deletion, verified an actual pending approval and desktop card, and confirmed the phone has no approval endpoint. It checked remote transcript updates, 390px phone layout, 600px desktop dialog fit, switch-away-and-back revocation and stop. Screenshots were visually inspected after fixing capture timing. No public Funnel or LAN listener was enabled.
The PC accepts neither Tailscale routes nor DNS. Its browser cannot ordinarily resolve the tailnet hostname. The test maps the verified name to the PC's Tailscale IP inside the test browser only, preserving TLS/SNI validation. The user's phone must resolve the hostname through its own Tailscale DNS.
Evidence record
User prompts (verbatim)
- “I want to be able to use tiny chat from my phone right now, the chat will be hosted on the computer but the phone will send requests to the pc, it should be done securely through a qr code or something”
- “Yes go ahead, thank you for asking”
- “Continue”
- The supplied screenshot showed the iPhone connected to Tailscale. It did not show a successful TinyAgent pairing or grant any broader permissions.
Model prompts and responses
No real model was invoked. Exact synthetic model messages and raw responses
are in evidence/RL-031/synthetic-model.jsonl. Its response was:
{"type":"tool","tool":"delete_note","arguments":{"id":1}}
Phone input remember that my favourite animal is a dog produced the
deterministic processed reply Noted: my favourite animal is a dog.
Phone input delete note 1 produced the processed phone reply
Delete note 1? This cannot be undone - Approve this action on your PC.
The underlying existing harness wording said reply yes to confirm; only
the phone presentation changes that clause to the actual PC approval boundary.
live-check.json keeps these rendered outputs (including the TINY speaker label),
pending approval and six checks. live-failure.txt keeps the distinct failed
fixture result. suite-tail.txt is the raw final test/typecheck output tail,
not the entire TAP stream. Screenshots remain local evidence, not site assets.
Issues encountered, in order
- Tailscale Serve initially needed account-side HTTPS consent. The user enabled it during implementation; configuration then showed a private HTTPS proxy, no AllowFunnel flag. Until that happened live HTTPS was untested.
- Five initial unit tests returned 403 because Node fetch replaced the supplied Host header. Corrected the instrument to Node HTTP; production Host checking was not weakened. The first test also needed an explicitly typed header fixture array to pass TypeScript.
- Review found invitation expiry needed rechecking after an asynchronous body read, revocation needed to invalidate grants immediately on chat changes, pending devices must not submit messages, synchronous callback failures must release busy state, and logout responses must not repaint disconnected content. Fixed in code, not prompts.
- The initial Electron test stalled at top-level await for app readiness. Replaced it with the app readiness callback; terminated only that test's identified process. The user's running app was not stopped.
- Normal PC browser lookup failed with
ERR_NAME_NOT_RESOLVED; corrected the test's DNS mapping, not system DNS or certificate checks. A generated test QR appeared in that tool error; it was consumed/expired and the test server stopped. Errors written by the final instrument redact pairing fragments. - The no-inference fixture first blocked the deletion request entirely, then
rejected the harness-wrapped rather than bare user prompt. The processed
failure was
cannot reach model at http://127.0.0.1:11434/v1 (Unexpected synthetic prompt). Added a narrowly allowed synthetic deletion response and preserved the failure. - Review found that ordinary host events alone did not add remote messages or their approval card to desktop Chat. Added a narrow remote-turn event through main/preload and verified desktop transcript plus approval card in the test. Accessing the initially unknown result also needed a TypeScript type guard.
- Hidden-window capture first showed an old empty frame despite updated DOM. Enabled offscreen rendering and waited for paint before capture. Final phone screenshot visibly contains both messages and responses.
- Full npm audit reported existing high-severity Electron and extract-zip advisories. Production-dependency-only audit reported zero, but excludes Electron despite Electron being the shipped runtime. No unqualified clean security claim is justified. A major Electron upgrade was not attempted.
Earlier full raw outputs for the initial Host fixture failure, stalled test and DNS failure were not saved to files; the diagnostics above were transcribed from tool results. The final suite output is deliberately only its captured tail.
Assumptions and verdicts
| Assumption | Verdict | Evidence |
|---|---|---|
| PC and iPhone share the same account and are connected | Correct for observed status | Tailscale status and supplied screenshot; not proof of Safari reachability |
| PC can resolve MagicDNS without accepting Tailscale DNS | Incorrect | Live ERR_NAME_NOT_RESOLVED; isolated test-browser mapping needed |
| Valid QR should grant Chat immediately | Rejected design | Pending until explicit matching-code PC approval |
| Same Host header can be supplied through Node fetch | Incorrect | Initial five 403s; direct HTTP fixture fixed this |
| Host run events automatically update remote-message desktop Chat | Incorrect, corrected | Added remote-turn event; final UI check sees transcript and approval |
| Closed app/stopped server means the Serve config is removed | Incorrect | Proxy intentionally persists; server and grants do not; documented |
| Owner identity + QR replaces PC approval for tools | Incorrect | Existing ApprovalService gate and pending-deletion check |
| Production-only npm audit covers shipped Electron | Incorrect | Electron is marked devDependency; high advisories remain |
| Phone voice, full files, arbitrary previews or iOS reliability are covered | Untested/out of scope | Text-only companion; no such claim |
| Prompt or model changes are needed | Not supported | Existing harness path; no inference in transport tests |
Correct: private TLS transport, QR plus code approval, bounded text surface, revocation and host PC approval work in the tested workflow. Incorrect and corrected: the HTTP fixture, test DNS assumption, renderer sync and stale-frame capture. Still open: actual user-phone pairing and existing runtime advisories.
Threats to validity
These tests are not an independent penetration test, iOS Safari test, sustained network/load benchmark or model-quality measurement. The local PC and paired devices/account are trusted. A paired phone can ask the normal assistant about PC data; selected-conversation binding is not a separate data-access sandbox. Already started requests continue after revocation. Users must restart their existing desktop app to load the implementation. Tailscale Serve configuration persists while the application server is off. See PHONE-CHAT.md for boundaries.
Conclusion
H1 supported at grade C for the tested request and state boundaries, with real private HTTPS transport. No claim of improved model intelligence, universal security, iPhone success or latency guarantee. Public claims reviewed through RL-031; optional phone sharing is distinguished from future NinaCorp research sharing. No website deployment was requested.
Follow-ups
Pair the user's actual iPhone after restarting TinyAgent. Check Safari keyboard, sleep/reconnect and mobile-data transitions. Separately upgrade/test the existing Electron runtime before calling the desktop security posture production-ready.
Reproduce
npm test
npm run typecheck
node --import tsx --test tests/phoneChat.test.ts
npm run check:phone
The final command explicitly uses real installed Tailscale Serve and reserves port 43127. Stop normal Phone Chat first. It uses a temporary database, not the user's conversation history. The existing running app is left untouched.