Technical Aspects
freezr is an open-source open-protocol personal server built on Node.js. It provides a thin layer of functionality - providing identity, permissions, and data routing — connecting apps to the data owner's files, database, compute, and LLM resources!
Architecture (Protocols)
At its core, a freezr app is just a static front-end bundle — HTML, CSS, ES-module JavaScript — served from the user's freezr server. To ensure security, app logic runs in the browser and no server-side code can run directly on freezr. Data, files, permissions, messaging, LLM calls, and serverless invocations go through the freezr JavaScript API, which is auto-injected into every app page as a global object. Server-side code can run on serverless instances connected to each user and given the appropriate permisisonsas described below.
The server is organised into three layers of protocols:
| Layer | Responsibility | |
|---|---|---|
| api | The freezr API uses HTTP endpoints under /ceps/* (standard) and /feps/* (freezr extensions). Primarily handles authentication, and permission checks for data and app access. See below for more details |
|
| rendering | Serves app pages at /apps/<app_id>/<page>. Wraps inner-body HTML with the document skeleton, injects freezrApiV2.js, applies CSP headers with per-request script nonces, and issues path-scoped app tokens. |
|
| adapters | Pluggable resource layer: file-system, database, serverless compute, and LLM connectors. File systems can be local or on the clkoud. Database adapters need to be mongoDb adaptible. See here for more details |
Every API call carries two identities: the logged-in user (a session on the global / cookie) and the calling app (a path-scoped bearer token, issued when the user visits the app's page). Permission checks run against the pair — an app can only touch data it is authorised to touch for the current user. See security below for more details or review the href="/security">security deepdive.
Security model
freezr runs third-party apps on the same origin as the user's own data. Isolation is built from four complementary mechanisms: path-scoped cookies, token-bound API calls, a strict Content Security Policy,< and per-user permission checks on every request. Below is a summary of the security model - a deeper dive into security can be found here.
Also of note: pubicly accessible pages do NOT use cookies and have less strict CSP.
Isolation model
The core trick: each app is served from its own URL path (/apps/<app_id>/*) and receives a bearer token in a path-scoped cookie. The browser only delivers that cookie on requests inside the app's path, so one app literally cannot see another app's token. The API then ties each token to both the app and the user, so a stolen token from app A is useless against app B's data.
Set-Cookie: app_token_<user_id>=<random>;
Path=/apps/com.you.notes;
Secure; SameSite=Strict
// httpOnly=false — JavaScript must read this to build the
// Authorization header; see the path-scope protection above
Additional mechanisms layered on top:
- Session cookie —
httpOnly,SameSite=Strict, 30-day TTL. JavaScript cannot read or forge it. - Sec-Fetch-Mode check — server-rendered pages with sensitive data require
Sec-Fetch-Mode: navigate;fetch()of another app's HTML is blocked. - App-name validation — app_table access is checked with a dot-boundary comparison;
com.example.mycannot readcom.example.myapp.posts. - Grantee validation — for any cross-user access the requesting user must appear in the record's permission grantees list.
- Token type — tokens are explicitly typed
'browser'(session-bound) or'oauth'(cookieless, for programmatic access). Browser tokens are invalidated when the session ends.
Content Security Policy
App pages are served with a strict CSP. The default header is:
default-src 'self'; script-src 'self' 'nonce-<per-request>'; style-src 'self' 'unsafe-inline'; img-src * data:; connect-src 'self'; form-action 'self'; frame-src 'none'; object-src 'none';
The nonce is crypto.randomBytes(16) generated per request — only scripts explicitly listed in the app manifest and injected by the renderer receive it. Inline <script> tags, onclick="..." handlers, and HTML-injected scripts are blocked even if the page is otherwise compromised.
Apps may request CSP relaxations via manifest permissions. These are user-grantable at install time and currently include:
| Permission | Effect |
|---|---|
| allow_self_frames | frame-src 'self' blob: — same-origin iframes / blob previews. |
| external_fetch | Adds whitelisted domains to connect-src. |
| external_scripts | Adds whitelisted domains to script-src; SRI required. |
| unsafe_eval | Adds 'unsafe-eval' to script-src. |
img-src is a future option for higher-security deployments.
Tokens & sessions
| Credential | Scope | Lifetime | Storage |
|---|---|---|---|
| session cookie | Global (/) | 30 days | httpOnly, SameSite=Strict, server-side session file |
| browser app token | Path-scoped to /apps/<app> | Issued per page load, session-bound | Secure, readable from JS (required) |
| oauth app token | App-bound, cookieless | 6 months | Returned to client once, used as Authorization header |
Server-side protections
- Rate limiting — 300 requests/minute per authenticated user across CEPS + FEPS routes, with graduated throttling and a
429 Retry-Afterresponse when exceeded. - Per-record size limit — 2 MB max per record on create / update / replace.
- Regex safety —
$regexvalues validated to reject nested-quantifier catastrophic patterns; alternatively, operator can be disabled per deployment. - Operator blocking —
$where,$function,$accumulator,$exprstripped from user queries before they reach MongoDB. - Input validation — permission names, function names, and table ids are all restricted to
[a-zA-Z0-9._-]+. Zip extraction enforces zip-slip protection. - Public routes — path-isolated from the app cookie namespace.
preventCookiesOnPublicRoutesmiddleware ensures public pages never see auth credentials. - Reserved prefixes —
publicids starting with/app,/admin,/accountetc. are blocked, even for admins.
File Structure
A quick tour of what lives where in the freezr checkout.
Folders
The top-level folders in the freezr repo:
| Folder | What's in it |
|---|---|
| server.mjs | The entry point. Boots Express, wires middleware, loads the feature routers, and starts listening. |
| features/ | The actual product, split by domain: account/, admin/, apps/, creator/, oauth/, public/, register/. Each feature has its own routes, controllers, and services. |
| froutes/ | Top-level route composition — the index that mounts every feature's router onto the Express app. |
| middleware/ | Cross-cutting middleware: auth/ (session, API tokens, OAuth), permissions/, tokens/, plus the request logger, session store, and dev assertions. |
| adapters/ | Pluggable back-ends: datastore/ (NeDB, MongoDB), http/ (file-system connectors for local, S3, Azure, Dropbox, GoogleDrive), llmConnectors/ (Claude, ChatGPT), and rendering/. |
| common/ | Shared helpers that don't belong to a single feature: helpers/, startup/ sequencing, loganalytics/, and small utilities under misc/. |
| freezrsystmapps/ | The built-in system apps that ship with every freezr server: info.freezr.account, info.freezr.admin, info.freezr.creator, info.freezr.public, info.freezr.register. |
| users_freezr/ | Runtime data for the local FS adapter — every user gets a folder here (see below). When you use S3/Azure/Dropbox this folder would be on your cloud storage. |
| users_3Pfunctions/ | Scratch space for locally-registered third-party microservices (the "Add a Local Microservice" admin feature). |
| users_temp_logs/ | Per-request log files produced by the request logger. Rotated; safe to delete. |
| test/ | Mocha test suites: unit/, integration/ (ceps, feps, oauth, coreApiV1/V2), and e2e/. |
| node_modules/ | Installed dependencies (see the next sub-section). |
Inside users_freezr/, each user (including the admin) has a folder with the
same three sub-folders:
| Path | What's in it |
|---|---|
| users_freezr/<user>/files/ | Per-app data directories, one per app the user has installed (e.g. cards.hiper.freezr/, com.salmanff.notery/). Plus @ and @public sharing directories and app_files/ for pages/assets served up to other apps. |
| users_freezr/<user>/apps/ | The installed code for each app (HTML, CSS, JS, manifest). Separate from files/ so that data survives an app upgrade. Admin accounts also keep zipped backups here. |
| users_freezr/<user>/db/ | NeDB on-disk databases when the user's DB adapter is nedb. With Mongo this is empty. |
A number userIds are reservede - eg fradmin (the built-in "freezr admin" system user) and public/ where pulished items live.
Node modules
freezr tries to keep its dependency list small. The sections below split the runtime modules into the Express core, general-purpose utilities, and the back-end adapter SDKs.
Core
The Express stack every request passes through.
| Module | Why it's here |
|---|---|
| express | The HTTP server and routing framework. |
| express-session | Cookie-based sessions for logged-in users. |
| cookie-parser | Parses cookies on incoming requests (used by the auth middleware). |
| multer | Multipart form parsing for file uploads (profile pictures, app zips, user content). |
Utilities
Cross-cutting helpers used throughout the server.
| Module | Why it's here |
|---|---|
| bcryptjs | Password hashing — user passwords are never stored in plain text. |
| node-cache | In-memory cache for hot records, preferences, and permission lookups. |
| node-fetch | HTTP client for outbound calls (inter-freezr federation, some adapters, LLM streaming). |
| fflate | zip/unzip utility — used when installing apps from .zip bundles and when exporting user data. |
| sharp | Image processing: resizing profile pictures and rasterising LLM-generated SVGs to PNG. |
| async | Flow-control helpers (waterfalls, parallel limits). Currently used by some legacy file-system adapters and will be removed in the future. |
| (redis) | This is included here as an aspirational entry - hopefully it will be added to the package.json in the future. |
Adapters
SDKs for the pluggable databases, file systems, and LLM providers. Each is only loaded when the corresponding adapter is active.
| Module | Why it's here |
|---|---|
| nedb-asyncfs | The embedded, file-backed database used by the nedb datastore adapter — zero-config default for laptops and small servers. |
| mongodb | Official MongoDB driver, used by the mongodb and cosmosformongostring adapters. |
| @aws-sdk/client-s3 | S3 reads/writes for the aws file-system adapter. |
| @aws-sdk/client-iam | Used when S3 bootstrap needs to create / check buckets and policies. |
| @aws-sdk/client-lambda | Invokes user-supplied Lambda functions for the serverless compute adapter. |
| @azure/storage-blob | Blob reads/writes for the azure file-system adapter. |
| @azure/identity | Azure credential discovery (managed identity, service principals) used by the Azure adapter. |
| dropbox | Dropbox SDK for the dropbox file-system adapter (OAuth + file API). |
| googleapis | Google SDK for the googleDrive file-system adapter. |
| @anthropic-ai/sdk | Anthropic client for the Claude LLM connector. |
| openai | OpenAI client for the ChatGPT / gpt-image LLM connector. |
Dev-only (devDependencies) adds mocha + chai for tests,
standard for linting, and the @codemirror/* and
rollup packages that power the in-browser code editor bundles used by the
built-in creator app.
You can learn more about how to connect to these adapters on the install page.
HTTP endpoints
To use freezr, the API provides all you need. The freezr API itself is a wrapper over two HTTP surfaces. CEPS (Common Endpoints) is the portable layer any CEPS-compatible server speaks. FEPS (freezr Extensions) is the freezr-specific layer: bulk operations, uploads, permissions, messaging, LLM, and serverless.
These endpoints are listed here for reference, but they should not normally be used by apps. Apps should access these via the freezr API.
Authorization: Bearer <app_token> header. The token is read from the path-scoped app_token_<user_id> cookie issued at page load. A few endpoints (ping, login, public query, self-register) are explicitly unauthenticated.
CEPS — data
<app_id>.<table>._id assigned by server.POST /ceps/write/com.you.notes.entries { "title": "Hello", "body": "First note" }
{
"_id": "abc123"
}
GET /ceps/read/com.you.notes.entries/abc123
{
"_id": "abc123",
"title": "Hello",
"body": "First note",
"_date_modified": 1740000000000
}
$where, $function, $accumulator, $expr are rejected.POST /ceps/query/com.you.notes.entries { "q": { "archived": false }, "sort": { "_date_modified": -1 }, "count": 20 }
[
{ "_id": "abc123", "title": "Hello", ... }
]
PUT /ceps/update/com.you.notes.entries/abc123 { "title": "Updated", "body": "New" }
DELETE /ceps/delete/com.you.notes.entries/abc123
CEPS — permissions & messaging
| Endpoint | Purpose |
|---|---|
| GET /ceps/perms/get | List permissions granted to the calling app |
| POST /ceps/perms/share_records | Grant or revoke access to records for specific grantees (or _public) |
| POST /ceps/perms/validationtoken/set | Issue a cross-server validation token (data-owner federation) |
| GET /ceps/perms/validationtoken/validate | Validate a federated data-owner token |
| POST /ceps/message/initiate | Send a record-linked message |
| POST /ceps/message/mark_read | Mark messages as read (by id or all) |
| GET /ceps/messages | List messages for the calling app |
| GET /ceps/ping | Health check; returns server_type and version |
| POST /public/query / /public/query/@<owner> | Query public records (no auth) |
FEPS — freezr extensions
FEPS endpoints cover operations beyond the portable baseline: upserts with forced ids, field-level updates, bulk delete, file upload, LLM, and serverless invocation. They also carry permission context in the body (permission_name, owner_id, requestee_app) for cross-app access.
| Endpoint | Purpose |
|---|---|
| POST /feps/write/<app_table>[/<id>] | Create with body { _entity, data_object_id?, upsert?, permission_name?, owner_id?, requestee_app? } |
| PUT /feps/update/<app_table>[/<id>] | Partial update, or bulk update by q. Pass ?replaceAllFields=true for full replacement. |
| DELETE /feps/delete/<app_table>[/<id>] | Delete by id or by query q. |
| PUT /feps/upload/<app> | Multipart file upload. Form fields: file, options (JSON). |
| GET /feps/userfiles/<app>/<user>/<file_id> | Stream a stored file. Content-Type derived from extension. |
| GET /feps/manifest[/<app_name>] | Fetch an installed app's manifest JSON. |
| PUT /feps/llm/ask | Streaming LLM completion (SSE: delta, thinking, done, error events). |
| PUT /feps/llm/generate_image | Image generation (returns base64 PNG or SVG). |
| PUT /feps/serverless/<task> | Serverless dispatch. task is one of invokeserverless, invokelocalservice, createinvokeserverless, upsertserverless, updateserverless, deleteserverless, rolecreateserverless, deleterole, upsertlocalservice, deletelocalfunction. |
| GET /feps/serverless/getalllocalfunctions | List installed local functions. |
Account API
| Endpoint | Purpose |
|---|---|
| GET /acctapi/getAppList | List installed apps |
| GET /acctapi/getUserPrefs | User preferences (theme, defaults, etc.) |
| GET /acctapi/getAppResourceUsage | Per-app storage / request usage |
| PUT /acctapi/app_install_from_zipfile | Upload a packaged app zip; filename must match the app identifier |
| POST /acctapi/login | Session login |