Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,9 @@ Video context uses `OPENAI_VISION_MODEL`, defaulting to `gpt-4.1-mini` for faste
python -m fluent_ai.web --port 7860
```

Open `http://127.0.0.1:7860`. The UI exposes Lesson Mode, Conversation Mode, video on/off, visible object context, live profile state, and agent logs.
Open `http://127.0.0.1:7860` (or `http://localhost:7860`). The UI exposes Lesson Mode, Conversation Mode, video on/off, visible object context, live profile state, and agent logs.

The browser server is local-only and rejects other websites. See [local web security](docs/GAP_AND_DESIGN.md#local-web-security) for the token and request rules that scripts must follow.

## Desktop app

Expand Down
6 changes: 5 additions & 1 deletion desktop/electron/renderer.html
Original file line number Diff line number Diff line change
Expand Up @@ -2460,10 +2460,14 @@ <h3>Text fallback</h3>
}

if (!window.fluentAI) {
// Browser mode only: fluent_ai.web injects a per-launch token into the
// served page. Electron uses preload IPC and never takes this path.
const apiToken = document.querySelector('meta[name="fluentai-api-token"]')?.content || "";

async function bridge(command, payload, options = {}) {
const response = await fetchWithTimeout(`/api/bridge/${command}`, {
method: "POST",
headers: {"Content-Type": "application/json"},
headers: {"Content-Type": "application/json", "X-FluentAI-Token": apiToken},
body: JSON.stringify(payload || {})
}, { timeoutMs: 20000, retries: options.idempotent ? 1 : 0 });
return response.json();
Expand Down
2 changes: 1 addition & 1 deletion docs/DEMO.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Open `http://127.0.0.1:7860`, use a separate learner profile, complete placement

## Offline development harness

`scripts/portfolio_demo.py` and `scripts/record_portfolio_demo.cjs` remain available as explicit mock-provider development tools. They write the older labelled workflow capture under ignored `cache/offline-recording/`, keeping it separate from the published live API assets. Tests continue to mock providers so CI requires no credentials or paid requests.
`scripts/portfolio_demo.py` and `scripts/record_portfolio_demo.cjs` remain available as explicit mock-provider development tools. The harness uses `FluentAIServer`. The recorder copies the page's per-launch token into its direct API check. They write the older labelled workflow capture under ignored `cache/offline-recording/`, keeping it separate from the published live API assets. Tests continue to mock providers so CI requires no credentials or paid requests.

## Validation

Expand Down
19 changes: 18 additions & 1 deletion docs/GAP_AND_DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,29 @@
| `fluent_ai/agent.py`, `conversation.py` | Topic selection, quizzes, scoring, review scheduling, and conversation progress |
| `fluent_ai/openai_provider.py` | OpenAI lesson generation, grading, tutor responses, Realtime, vision, and phrase audio |
| `fluent_ai/desktop_bridge.py` | Shared commands for interactive sessions, onboarding, memory controls, and checkpoints |
| `fluent_ai/web.py` | Local HTTP server exposing the bridge and shared renderer |
| `fluent_ai/web.py` | Loopback HTTP server exposing the bridge and shared renderer behind Host/Origin/token checks |
| `desktop/electron/` | Electron main/preload processes and shared browser/desktop renderer |
| `fluent_ai/state.py` | Versioned local JSON state, migrations, transactions, and reset boundaries |

Electron invokes a Python bridge process per command in development and a bundled bridge executable when packaged. The web UI uses HTTP bridge endpoints. The CLI uses the same core lesson, conversation, provider, and state modules. The obsolete Tk launcher has been retired.

## Local web security

`fluent_ai.web` protects learner memory and paid provider routes from other websites, including no-cors/form CSRF and DNS rebinding:

- Binds only to `127.0.0.1`, `localhost`, or `::1` (`--host` rejects other values). Every request needs exactly one `Host` equal to `127.0.0.1`, `localhost`, or `[::1]` with the listening port; other names get 421.
- Each server launch creates a random token. `GET /` embeds it in a `fluentai-api-token` meta tag, served `no-store` and unframeable. Every `/api/*` GET and POST must send it as `X-FluentAI-Token`. Tokens never appear in URLs, logs, state, or the static renderer.
- A sent `Origin` must exactly match the app origin. A sent `Sec-Fetch-Site` must be `same-origin` or `none`. There are no CORS grants, and OPTIONS returns 405.
- POST bodies must be one JSON object with `Content-Type: application/json` (optional `charset=utf-8`), a single ASCII `Content-Length`, and no `Transfer-Encoding`. They are capped at 64,000 bytes and 64 nesting levels. Send `{}` when there are no options.
- Rejections return `{"ok": false, "error", "error_code"}` with a 4xx status. They happen before any state read, provider construction, or bridge dispatch.

Migration notes:

- **Local scripts:** fetch `GET /` with a loopback Host, read the meta token, and send it on each request. Scripts may omit `Origin`, but they still need Host and token.
- **After a server restart:** pages holding an old token get 403 `invalid_token` until reloaded.
- **Embedding code:** construct `FluentAIServer`; plain `ThreadingHTTPServer` has no token and fails closed.
- **Electron and CLI:** unaffected. Electron uses preload IPC, and the CLI bridge reads stdin JSON.

## State schema v2

`data/progress.json` is the development default. Packaged desktop storage is described in [packaging](PACKAGING_DESIGN.md). The top-level structure is:
Expand Down
2 changes: 2 additions & 0 deletions docs/ONBOARDING_HOME_DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ The schema's `privacy.local_only` describes local learner-memory storage. The ap

Raw microphone recordings and raw camera video are not persisted as learner memory. Transcripts, corrections, session checkpoints, and camera summaries can contain personal context. Generated phrase audio is cached under `cache/tts` beside the state file and removed by delete-all. Camera sampling skips unchanged frames before inference, with explicit capture and a bounded refresh; fake test feeds must not become claimed real-world objects.

The browser server only answers same-origin loopback requests that carry the per-launch app token. Other websites cannot read or delete memory through it (see [local web security](GAP_AND_DESIGN.md#local-web-security)). Any local process that can load the page can still obtain the token, just as it could read `data/` directly.

The privacy fields document intent and supported controls; do not treat them as a general consent engine or a guarantee about provider retention. Keep API keys out of progress, exports, and logs. Electron key storage and precedence are described in [packaging](PACKAGING_DESIGN.md).

## Validation limits
Expand Down
7 changes: 7 additions & 0 deletions docs/notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ Current behavior is documented in [architecture](GAP_AND_DESIGN.md), [curriculum
- The PyInstaller bridge passed its outside-checkout status/missing-key/SDK-import smokes. Electron packaging succeeded using the installed lockfile-matching Electron distribution; ad-hoc signing and strict deep signature verification passed. The host required Command Line Tools via a per-command `DEVELOPER_DIR` because its selected Xcode loader was broken. The default Electron download was blocked by sandbox DNS; no source/build-config workaround was committed.
- No paid API calls or physical-camera/clean-account GUI acceptance were performed.

## Local web request security (2026-10-04)

- Decision: the browser server is loopback-only. It validates Host/Origin/fetch metadata and requires a per-launch token for every API request, plus strict JSON bodies. This closes foreign-page `text/plain` POSTs to delete-all, provider, and other bridge routes, and DNS rebinding. Contract and migration: [architecture](GAP_AND_DESIGN.md#local-web-security).
- Validation: socket-level tests cover every bridge command and the legacy lesson/conversation routes. They exercise foreign/null/cross-port/wrong-scheme Origin, cross-site fetch metadata, missing/wrong/stale/duplicate tokens, rebinding/invalid/duplicate Host, unsupported media types, bad/oversize/5,000-digit/superscript lengths, chunked bodies, malformed/non-object/NaN/deep JSON, and OPTIONS/PUT/DELETE/PATCH. Spies show rejected requests never reach state, providers, or dispatch, and files stay byte-identical. Unauthenticated malformed lengths keep their 403/421 status.
- Authorized browser-shaped and originless requests persist disposable state. VM tests check that both the shared and fallback browser renderers send the token, and that Electron IPC makes no HTTP calls. 145 Python tests, JS checks, Ruff, and mocked smoke passed.
- Limits: no real-browser, Electron window, camera, or paid-provider run was part of this validation. Intentional breaks are non-loopback `--host`, tokenless scripts, empty or non-object POST bodies, and plain `ThreadingHTTPServer` embedding.

## Provenance

The full construction log and original five design proposals remain available in [the pre-cleanup Git snapshot](https://github.com/Jo2234/FluentAI/tree/d923a6a49243ed19cbad3c3470e165157f2c34b0). In that snapshot, `notes.md` records dated work and validation, and `docs/` contains the original north-star, schema, onboarding, curriculum, and packaging proposals. Their work-package assignments, future-tense descriptions, and old source line numbers are historical, not current implementation instructions. Git history retains authorship and the evolution of those decisions.
Expand Down
Loading
Loading