Security & architecture overview
The same document ships as SECURITY.md in every download. Need a vendor questionnaire filled in? sales@lookward.app.
Architecture and data location
- Self-hosted: one Linux service (Ubuntu 22.04/24.04, Debian 12), one macOS service (macOS 12 or newer) or one container on your network. There is no vendor cloud. Lookups, device names, History, reports and the audit log never leave your server unless you turn on a feature that sends something (listed under "Outbound connections").
- Domain controller agent → Lookward server (HTTP or HTTPS, a shared agent token on every request) → browsers and TV screens (HTTPS with your certificate, once turned on).
- All data is stored as files in
/var/lib/lookward(Docker: the/datavolume), owned by the unprivilegedlookwardservice account and not readable by other users (secrets 0600). History is a local SQLite file. There is no database server. - License keys are verified offline with a public key; there is no license check-in.
What the agent does on a domain controller
- Turns on the DNS Server's debug log for incoming client queries only (UDP and TCP questions; no responses,
no full packets, no outgoing traffic) with a size cap, after saving the previous settings, which
uninstall-agent.ps1restores. - Runs as a scheduled task under SYSTEM (needed to read the DNS log). It reads only that log file, never holds it open between reads, and makes outbound HTTP(S) connections to your Lookward server only. Nothing inbound is opened and nothing else is installed.
- Its settings, including the agent token, are in
C:\ProgramData\Lookward, readable by SYSTEM and Administrators only. The scripts are plain PowerShell you can read before installing. - Sends, per lookup: time, client IP address, record type and the name looked up, plus the DC's name, its UTC offset, the agent version, the log size and any error, as a heartbeat every few seconds.
Data Lookward keeps
- Live (memory): the last 60,000 lookups and per-minute counts for the last hour.
- History (disk): by default app lookups only (foreground apps and anything risky), 14 days, up to 3 GB
(
DQC_STORE,DQC_RETENTION_DAYS,DQC_MAX_DB_MB). Each row: time, DC, client IP, reverse-DNS name, site, type, name looked up, app, category, status, risk. - Reports: PDF/CSV files you create, kept 365 days (
DQC_REPORT_KEEP_DAYS); their register (IDs and SHA-256 fingerprints) is permanent. - Device names come from reverse DNS lookups by the server against your own resolvers. Lookward never sees user names, page contents, search terms or anything inside encrypted connections.
Outbound connections
Only these, and only when used:
- updates.lookward.app, to check for and download updates. Can be turned off (Settings → Updates → Never check);
offline update files work without it.
- App websites (and Google's or DuckDuckGo's favicon services), once per app, to download its icon. Off with
DQC_LOGOS=0.
- Your SMTP relay and/or Microsoft Teams webhook, if you set them up.
- api.anthropic.com, if you turn on AI insights (off by default) with your own API key.
- OpenStreetMap: overpass-api.de once if you build the street map, and nominatim.openstreetmap.org with the address
text when you search for a site's location.
- Microsoft or Google, if you turn on single sign-on.
- On macOS, once during installation: github.com, to download Lookward's Python (checksum-verified).
Roles
| Role | Can |
|---|---|
| Viewer | The live console and TV mode. The device drawer shows only recent lookups. |
| Analyst | + History search, device day summaries, CSV exports, compliance reports |
| Admin | + every setting: organization, sites, agents and the agent token, accounts, SSO, HTTPS, app policy, TV screens, AI, license, branding, updates, audit log, support bundles |
TV screens sign in with a separate TV key and get a view-only, 30-day session that can't open History, device
details, reports or settings. With &anon=1 or DQC_TV_DEVICES=0 no device names or IPs are sent to them at all,
and with risky activity hidden, no risky lookups or counts are sent either.
Accounts and sessions
- The built-in
adminaccount's password is set in the setup wizard and stored as a PBKDF2 hash (sudo lookward reset-admin-password). An optionalDQC_ADMIN_PASSWORDin/etc/lookward/lookward.envis plain text, so leave it unset unless you need it. - Passwords: PBKDF2-SHA256, 310,000 iterations, per-password salt, minimum 10 characters. Sign-in is rate limited (8 failures per address per 5 minutes).
- Two-step sign-in: TOTP (RFC 6238) with one-time recovery codes. Can be required for admins or everyone.
- Single sign-on: Microsoft Entra ID or Google, OpenID Connect authorization-code flow with PKCE, allowed-domain checks, and a default role (or none) for people without an account.
- Sessions: signed, HTTP-only cookies,
SameSite=Lax,Securewhen HTTPS is on, 12 hours (TVs 30 days). Role changes, deleted accounts and a replaced TV key take effect on the next request, and open live connections are re-checked every 30 seconds. - The setup wizard can only be started with a one-time code generated on the server.
- Invitations: single-use links stored as hashes, expiring after 72 hours.
Agent token
- One shared secret, generated per install (48 hex characters), compared in constant time. Replace it in Settings → Domain controllers; every agent then needs reinstalling with the new one.
- Limit which addresses may send lookups with
DQC_INGEST_ALLOW(your DCs' addresses). - Over plain HTTP the token and lookups cross your network unencrypted. Turn on HTTPS and reinstall the agents with the https address to encrypt them.
Browser protections
- Content-Security-Policy on pages (
default-src 'self',object-src 'none',base-uri 'none',frame-ancestors 'self'; scripts and styles also allow'unsafe-inline'for the console's own inline code),X-Frame-Options: SAMEORIGIN,X-Content-Type-Options: nosniff,Referrer-Policy: same-origin,Permissions-Policy, HSTS when HTTPS is on,Cache-Control: no-storeon API responses. - Cross-site request forgery: every state-changing request or WebSocket whose
Originisn't the console is refused (agents and scripts send none); cookies areSameSite=Laxtoo. - Everything shown from lookups (domain names, device names) is escaped before display. Uploaded logos: PNG, JPEG, WebP and script-free SVG only, served with a sandboxing Content-Security-Policy.
Secrets
- The agent token, session secret, TV key, SMTP password, Teams webhook, SSO client secret and the Claude API key are stored in files only the service account can read, and are never sent back to the browser in full (the agent token is shown to admins on the Domain controllers page).
- HTTPS private keys are kept in
data/tls/(0600); the previous certificate is kept for roll back. - Backups (
sudo lookward backup) contain History and secrets and are written 0600. - Support bundles contain settings and the agents' last reports, with passwords, hashes, two-step secrets, tokens, webhooks, API keys, TLS keys and the license key removed. They contain no lookups, History or reports. IP addresses can be masked.
AI insights (off by default)
Sends only aggregate per-site counts (devices per app now and 5 minutes ago, lookups per minute and, if TVs show risky activity, flagged lookup counts), the organization's name and kind and the local time, to Anthropic under your API key. No device names, IP addresses, user names or individual lookups are sent.
Telemetry
- Lookward sends no usage analytics.
- Update checks (every 6 hours) send the installed version, the release channel and, if licensed, the license ID; like any web request they also reveal your public IP address. Downloading an update also sends the license key so support can be confirmed. Turn checks off in Settings → Updates.
Updates
- Releases are signed with an Ed25519 release key that is separate from the license key. The console and the root updater each verify the feed's signature and the archive's SHA-256 before anything is installed.
- The console runs unprivileged and can't change its own program files. A root-owned updater re-verifies the release, backs up the data, installs it and rolls back automatically if the new version doesn't start.
Hardening of the service
- Runs as an unprivileged system account with systemd sandboxing (
ProtectSystem=strict,ProtectHome,PrivateTmp,NoNewPrivileges, kernel and control-group protections, write access only to its data folder). - On macOS: runs under launchd as a hidden
_lookwardaccount that can't log in; the program is owned by root. - The audit log (
audit.jsonl) records sign-ins and failures, TV sign-ins, History searches, device look-ups, exports, report creation, downloads, emails and verifications, and every settings, account, site, agent-token, license, branding and update change, with who, from where and when. Forward it to your SIEM for tamper evidence.
Student privacy
Lookward is designed to keep student data on your own server: there's no vendor cloud, no data sharing with Bartlett Electronics, retention limits are yours to set, and access is by role with an audit trail. You remain the controller of the data under FERPA, COPPA and your state's student-privacy laws; we're happy to sign your district's data-privacy agreement, since we never receive the data.
Reporting a vulnerability
Email security@lookward.app with details and how to reproduce it. We acknowledge reports within two business days, keep you informed, credit you if you wish, and ask for 90 days to ship a fix before publication. Please test only systems you own.