Threat model, Ollama proxy hardening, profile ZIP archive import, and deployment best practices.
| Component | Data | Server persistence |
|---|---|---|
| Browser | Prompts, history, Ollama token (localStorage), proxy token (sessionStorage), audio blobs | None server-side |
PHP proxy (lib/proxy/ollama/olama.php) |
POST body to Ollama (prompts, contexts, base64 vision images) | In-memory transit only — no application disk write |
| Ollama | LLM inference | Depends on Ollama installation |
Profile API (lib/api/*.php) |
Markdown read → JSON | Read-only disk |
The proxy does not replace an application firewall. CORS is restricted in production (PDM_ENV=prod) to official origins; in official pre-production, self-hosting, or development, CORS is open (*) — do not expose pre-prod or a clone on the Internet without network protection.
Profile export / import is 100% client-side: the ZIP archive (and the pdm-config it contains) is never sent to the PHP server. UI import accepts .zip only — a standalone JSON file is rejected. Risks concern the user who imports (local XSS, token theft in localStorage, browser DoS), not server compromise via the archive.
Without hardening, any HTTP client can:
- Relay requests to local Ollama (
127.0.0.1:11434) or the LAN viaX-Ollama-Url - Consume GPU/CPU (resource abuse)
- Probe the private network (RFC 1918 allowed by host filter)
Measures implemented in olama.php:
| Measure | Variable / behavior |
|---|---|
| Optional authentication | PDM_PROXY_TOKEN — header Authorization: Bearer … or X-PDM-Proxy-Token |
| Rate limiting | 120 req/min/IP in prod, 300 in pre-prod / self-hosted (temp files) |
| POST body limit | PDM_PROXY_MAX_BODY (default 10 MB) |
| No HTTP redirect | follow_location disabled (anti-SSRF via redirect) |
| Sanitized headers | CRLF stripped in relayed User-Agent / Accept |
| Generic errors | Detailed messages hidden when PDM_ENV=prod |
The proxy token protects the operator PHP relay to the prod server Ollama. Visitors do not need one: they use their local Ollama via direct browser connection.
-
Server (Apache or Nginx) — official prod (operator flow only):
Apache
# /etc/apache2/envvars export PDM_ENV=prod export PDM_PROXY_TOKEN="your-long-random-secret"
# /etc/apache2/conf-available/pdm-env.conf PassEnv PDM_ENV PassEnv PDM_PROXY_TOKEN
Then
sudo a2enconf pdm-env && sudo systemctl restart apache2.Nginx + PHP-FPM (equivalent)
In the FPM pool (e.g.
/etc/php/8.x/fpm/pool.d/www.confor dedicated pool):env[PDM_ENV] = prod env[PDM_PROXY_TOKEN] = your-long-random-secret
Then
sudo systemctl reload php*-fpm.
Alternative:fastcgi_paraminserver/location ~ \.php$:fastcgi_param PDM_ENV prod; fastcgi_param PDM_PROXY_TOKEN "your-long-random-secret";
IP allowlist mandatory on
/lib/proxy/(Apache vhost, Nginxlocation, or reverse proxy in front) — the token does not replace this filtering.Nginx example:
location /lib/proxy/ { allow 127.0.0.1; allow ::1; allow YOUR.OPERATOR.IP; deny all; # … include snippets/fastcgi-php.conf / proxy_pass depending on stack }
Nginx: CSP, noindex
/lib/, anti-listing, etc. must be replicated in the Nginx vhost config (Apache HTTP header equivalents). -
Operator (authorized IP) — uncheck "I don't have a token", enter the Proxy token for the session:
- Options → LLM: checkbox + token field on one line.
- Storage:
sessionStorage—pdm_token_proxy(secret) andpdm_llm_direct_local(flow A/B) — reset on each browser session.
-
Prod visitors (flow A) — leave "I don't have a token" checked; install Ollama locally; default URL
http://localhost:11434in Options → LLM. CORS required on the workstation:OLLAMA_ORIGINS=https://promptdemerde.com ollama serve
Linux (systemd):
Environment="OLLAMA_ORIGINS=https://promptdemerde.com"in/etc/systemd/system/ollama.service.d/environment.conf, thensystemctl daemon-reload && systemctl restart ollama.Without this variable:
Failed to fetchon LLM test even if Ollama is running. IP allowlist on/lib/proxy/does not fix this case (it only concerns flow B).
Internet prod recommendation: combine
PDM_PROXY_TOKENwith an IP allowlist on/lib/proxy/— do not open the relay to visitors.
| Claim | True? |
|---|---|
| "Importing my validated full ZIP export restores my session" | Yes |
| "Importing any third-party archive is safe" | No — impossible without removing all workspace HTML richness and all user trust |
"Importing a standalone .json from the UI" |
No — rejected (importJsonDeprecated); ZIP container mandatory |
Attacker ──► distributes evil-profile.zip (pdm-config + prompts)
│
▼
Victim imports on https://promptdemerde.com
│
┌───────────┼───────────┐
▼ ▼ ▼
Local XSS Token theft Browser DoS
(guard) localStorage (large JSON/blobs)
│
└─────► fetch Ollama proxy (from victim browser, if token present)
| Threat | Reaches PHP server? | Reaches other users? | Status v1.14.0 |
|---|---|---|---|
| RCE / disk write via profile import | No | No | Mitigated (client-only import) |
| Cross-user data theft | No | No | Mitigated (no multi-tenant) |
XSS → theft of pdm_token_* |
Not directly | No | Mitigated (sanitize + reject) |
| Client DoS (RAM, localStorage) | No | No | Mitigated (size limits) |
| Proxy abuse via victim session | Indirect | No | Depends on PDM_PROXY_TOKEN + infra |
| LLM prompt injection | No | No | By design (not a bug) |
| # | Measure | File(s) |
|---|---|---|
| 1 | HTML allowlist promptGuardHtml (a[href^="#"], strong, em, span.inject-*) |
config-schema-sanitize.js |
| 2 | Sanitization on import + render (defense in depth) | config-schema-build.js, workspace-ui-profile.js |
| 3 | Reject if <script, onerror=, javascript: before normalization |
containsDangerousWorkspaceHtml |
| 4 | Import archive cap 20 MB (ZIP) | settings-ui.js, config-schema-security.js |
| 5 | String length caps + JSON size estimate | config-schema-security.js |
| 6 | Reject keys __proto__, constructor, prototype (recursive) |
config-schema-security.js |
| 7 | pdm_audio_blobs: segment format, max 50 refs, 50 MB base64 total |
validator + storage-config-audio.js |
| 8 | pdm_token_ollama cleared by default on import (stripTokens) |
storage-config-import.js |
| 9 | "Unsigned file / unknown source" dialog — UX consent, not crypto | settings-ui.js |
| 10 | SHA-256 checksum of ZIP bytes: if expectedChecksum provided and mismatch → reject |
profile-bundle-integrity.js, storage-config-import.js |
| 11 | Manual test fixtures | tests/fixtures/evil-*.json |
| Layer | Status (v1.23.0) |
|---|---|
| SHA-256 on ZIP bytes | Yes — export (buildZipPackage), creator (profile-zip-checksum.mjs) |
| Import verification | Yes if options.expectedChecksum / checksum_sha256 (otherwise unsigned dialog only) |
archive_trust.signed |
Always false until asymmetric signature is verified |
| Ed25519 / maintainer HMAC | Out of scope — separate ticket; future keys off git |
ZIP checksums: verify client-side integrity on import (profile bundle modules) — no dedicated server tool in the public clone.
Content-Security-Policy:script-src 'self' 'unsafe-inline' 'unsafe-eval'— inline forbase hrefscript;unsafe-evalfor Vosk (Workernew Function());connect-srcincludes'self' blob: data:(STT WASM) and local/LAN Ollama (localhost,127.0.0.1, RFC1918 ranges) for prod flow A;img-src/frame-srcallow footer thumbnails and YouTube embedsPermissions-Policy:microphone=(self)(dictation); camera and geolocation deniedX-Content-Type-Options: nosniffReferrer-Policy: strict-origin-when-cross-origin- Under
assets/profiles/, onlyspeech2texte/andindex.jsonare served (zone doc:docs/Profiles.md) — everything else returns 403 (server HTTP rule)
- Browser / sanitizer zero-day
- Intentional prompt injection (
pdm_system_prompt, contexts) - Social engineering ("import this profile")
- Workstation compromise or malicious extension
- Third-party self-hosting: an admin other than the official maintainer who enabled HTTP body logging (verbose access/error logs, reverse proxy debug) — outside product promise, outside official stack
Out of scope "residual" for official prod (promptdemerde.com): the DreamProjectAI maintainer / operator does not log application request bodies (olama.php = RAM transit only). This is not a ~5% residual risk on that server — it is an operational policy (practical risk ~0% as long as this policy holds).
Export produces a ZIP archive ({slug}-promptdemerde-profile-v{version}.zip) containing a pdm-config object (51 pdm_* keys + metadata), Markdown prompts, and — in maximal preset — UI dictionaries. The machine schema assets/config/pdm-config.schema.json declares the same 51 keys as CS.PDM_KEYS (plus optional i18n/langs).
Export includes pdm_token_ollama in plain text if present. User confirmation is requested before export. Never share an archive containing tokens.
The proxy token (pdm_token_proxy) and flow A preference (pdm_llm_direct_local) are not included in export — they live in sessionStorage only.
On import, pdm_token_ollama is cleared by default when the archive does not clearly come from a recent application export (protection against third-party archives).
| Environment | Access-Control-Allow-Origin |
|---|---|
PDM_ENV=prod |
https://promptdemerde.com, https://www.promptdemerde.com only |
PDM_ENV=preprod / absent / other |
* (official pre-prod, self-hosted, local development) |
CORS protects reading responses by third-party sites; it does not prevent server-side proxy execution.
pdm_workspace_ui.texts.promptGuardHtml may contain intentional HTML (anchor links to the Prompts tab). Only profiles and exports from trusted sources should be imported.
History rendering, LLM errors, and workspace identity use textContent or PDM.UI.escapeHtml. promptGuardHtml is the only HTML field rendered via innerHTML, with allowlist sanitization.
Contact dreamproject-ai@proton.me by email (sole support channel).
GitHub is not a project follow-up channel for DreamProjectAI: no code review or issue triage on our side on the public repository. A security report via GitHub issue will therefore not be handled as a maintained ticket — prefer email.
DreamProjectAI publishes versioned updates from time to time on this repository via git tags only — no GitHub Releases. The 1.x line is currently RC (not yet stable). No public schedule commitment.
-
PDM_ENV=preprod+ Apache/Nginx vhost withOptions -IndexesandSetEnv PDM_ENV preprod - Test:
GET /lib/andGET /assets/stt/→ 403 (no Apache listing) - Test:
GET /README.md→ 403 (project doc root not exposed) - HTTrack-type re-mirror: listings inaccessible, prompts/assets still served via direct GET
-
PDM_ENV=prod(footer badge PROD) -
PDM_PROXY_TOKENset server-side; operator enters token each session (modal or Options → LLM) - Reverse proxy with auth or IP allowlist
- Ollama not exposed directly on the Internet (127.0.0.1 or Unix socket)
- HTTPS mandatory
- CSP and security headers active (Apache/Nginx / vhost config)
-
.envand secrets off Git (see.gitignore) - Test: Ollama proxy returns 401 without token from the Internet
- Test: profile ZIP import triggers no server POST with archive content
| Document | Role |
|---|---|
README.md |
Product pitch (EN) |
docs/Documentation.md |
Technical documentation (EN) |
CONTRIBUTING.md |
Contributing (EN) |
SECURITY.md |
Security (EN) |
THIRD_PARTY_NOTICES.md |
Third-party notices (EN) |
docs/Stt.md |
STT zone (EN) |
docs/Stt-vosk.md |
Vosk catalogue (EN) |
docs/Profiles.md |
Profiles zone (EN) |
docs/Vendor.md |
Vendor zone (EN) |