Operate
Security and privacy
Understand authentication, workspace permissions, private storage, integration risks, and operator responsibilities.
v0.3.0 referenceReviewed
On this page
Early implementation, not an audited or production-ready security product. These are current boundaries and operator obligations, not a guarantee against compromise.
Trust Boundaries
| Boundary | Enforcement |
|---|---|
| Adonis API | Owns authentication, authorization, validation, mutations; Astro/React are not authorization controls |
| Workspace resources | Tasks, Documents, Tables/columns/records, hierarchy, fields, discussion, notifications, attachments checked server-side on every operation |
| Untrusted inputs | Workspace/resource/parent/comment IDs, roles, anchors, AI arguments scoped, permission-checked, bounded |
| Site admin | Accounts/restricted audits only; does not grant membership or workspace discussion/data access |
| Owner | Protected; last active owner cannot be removed; roles cannot exceed caller’s delegation |
| SQL / audits | Bound values; relational mutation + audit transaction; no copied bodies/secrets/full AI prompts |
| Notification identity | Authenticated owner, never a supplied user ID; references/actor metadata, not copied bodies |
| Discussion | comments:create separate from write; comments:manage for moderation; user mention targets active same-workspace task readers, ≤20 per body; Document comments have task/structure links, not user-mention notifications |
Private photos and Tables
| Resource | Boundary |
|---|---|
| Profile mutation | Self-only, authenticated, same-origin, raster/size-bounded, transactionally audited |
| Photo reads | Self/shared-workspace/site-admin account access; credentials/sharing/revision rechecked before private/no-store bytes; URL grants no workspace access; no bytes in JSON/audit |
| Table permissions | Read/write/delete separate; metadata does not grant record/column access; existing update needs read; column deletion needs write + delete |
| Table limits | Workspace/Table-scoped IDs; counts/types/text/options/JSON/page/encoded bytes bounded; audits count/keys, not cell values; export needs tables:read |
| Query | Typed columns/operators/values validated; read-only POST keeps filter values out of URLs, retains same-origin checks |
| Scan resources | Four global / two per-account slots; byte-bounded page; 30-second deadline; live access checks |
| Cursor | Workspace/Table/schema/filter/order-bound position; anchor resolved inside snapshot; never credential |
Live SQL source trust and uncertain commits
- Management requires
credentials:manage; linking additionally needs Table read/write and exposes data through Table permissions. - AES-256-GCM binds configuration to workspace/connection under stable SQL/application key. Only listed host:port or canonical SQLite files within operator root are accepted; Hopya’s database excluded.
- Source identifiers quoted, values bound, no arbitrary SQL. Schema fingerprints/value revisions guard writes.
- Durable local audit intent precedes source transaction; applied audit follows commit. No cross-database atomic commit: incomplete outcomes need reload/reconciliation.
- General workspace export excludes secrets/remote rows, includes safe descriptors. Per-Table export returns authorized source rows. Same-origin mention links never bypass permissions when opened.
Authentication
| Control | Contract |
|---|---|
| First setup | Random operator SETUP_TOKEN; closes after an account exists; no shipped users/default passwords; registration opt-in |
| Recovery | Keep a local admin credential and restrict initial setup page |
| Password | Salted scrypt |
| Cookie | HttpOnly, SameSite=Lax; HTTPS APP_URL enables Secure |
| Session / API / reset token | Random, hash-only, expiry/revocation checked; disabled users cannot continue |
| Personal token | One-time reveal; owner’s current permissions, no separate scopes |
| Email/password change | Recheck authenticating credential inside committing transaction after hashing; revoke prior credentials; intervening logout/expiry/revocation cannot issue replacement session |
| Unsafe browser mutation | Exact Origin = APP_URL; bearer without cookies may omit Origin, but supplied Origin must match |
| Password recovery | Both SMTP_URL/SMTP_FROM; generic response, fragment-only 30-minute single-use link; all Hopya sessions/tokens revoked on use |
| MFA | No built-in MFA; use tested IdP policy and operator recovery plan |
Never disable Origin checks or use wildcard CORS for convenience. Mailbox delivery is not guaranteed/certified.
Rate limits and proxy trust
| Limit | Value |
|---|---|
| Login | 10 per normalized account/IP, 100 total per verified IP per fixed 15 minutes; invalid bodies/failures included |
| Reset | Three per normalized email/hour, ordinary 10-attempt verified-IP category |
| Login storage | Isolated; ≤1,000 IP windows, ≤100 account hashes each |
| Other categories | ≤10,000 windows, 10 attempts per category/IP; assistant also includes authenticated user |
| Capacity | Fail closed for new windows; every limit response has Retry-After |
- Rotating emails from one IP cannot consume unrelated auth slots; distributed attacks can saturate address capacity, shared NAT can hit aggregate limits. API restart clears in-memory accounting; this is not distributed abuse defense.
- Direct API
TRUST_PROXY_HOPS=0ignores forwarded headers. Compose sets 1 only behind private Nginx, which overwrites X-Forwarded-For/X-Real-IP and removes Forwarded. Never publish that API. - Outer TLS proxy: inner
set_real_ip_frommust name exact socket peer; usereal_ip_header X-Forwarded-For, with outer proxy overwriting a verified client address. No all-address/broad subnet trust. Otherwise users share buckets or attackers spoof them. - SSO-start/non-login limits are not account-partitioned. Add edge abuse controls; see HTTPS trust chain. Forged forwarding was tested only against supplied one-hop stack, not every proxy topology.
Secrets And Privacy
| Item | Operator obligation |
|---|---|
.env | Private; exclude from Git, images/build context, exports, diagnostics; secrets only to API |
| Host / Docker / storage admin | Trusted; can inspect environments/data; compromised host/daemon not defended against |
| Disk / backups | Encrypt as appropriate; APP_KEY does not encrypt database/files/audits/exports |
| Automation keyring | Separate API-only AUTOMATION_KEYRING; all keys referenced by retained rows must survive and be backed up encrypted |
| Automation secrets | AES-256-GCM versions/PKCE verifiers bound to workspace/credential/version/type; metadata-only APIs; values excluded from exports, audits, graph/run responses, web container |
| Missing/malformed/incomplete keyring | Explicit 503 for credential work, not core-task/cloud dependency |
| Nginx access logs | Omit query/body/cookie/auth/referrer; critical-only error logs avoid OIDC callback values |
| Outer logging / monitoring | Equivalent redaction; route IDs/IP/audit metadata also private; review before verbose provider/request logs |
| Core telemetry | No mandatory hosted service or silent telemetry; supplied Astro scripts/containers disable it |
| Optional services | S3/OIDC/AI disclose some data to selected providers; audit dependencies, not a blanket no-network promise |
Optional Services
OIDC and recovery
- Validate issuer/audience/state/nonce/PKCE; bind exact
(issuer,subject), never unverified email. Issuer case/trailing slash must exactly match discovery; URL equivalence is not identity equivalence. - Restrict allowed users, auto-provisioning, and insecure HTTP; test disablement/logout. Local logout is not provider-wide logout. See claim stability.
- Passwordless unlink must retain another identity for current exact issuer/configured client. Inactive links/disabled SSO do not count. This local check cannot prove upstream subject existence/availability; verify recovery first, suspend to quarantine.
- Provider sessions are separate. After incident/recovery, revoke them and revoke/suspend Hopya access. Restoring either old database can revive old credentials; repeat revocation before reopening.
Files and AI
| Service | Required caution |
|---|---|
| Attachments | Private API, bounded payload/generated keys/forced download, never application HTML; authorized file may be harmful; no malware-scan guarantee |
| S3 | Private bucket, least privileges, encryption, independent versioned backup; AWS guidance |
| AI | Inputs/output untrusted, task text may inject prompts; authorized context, proposals only; review actual task/workspace/fields before ordinary confirmed mutation |
| Provider | Operator-selected destination/key; restrict egress, review retention/billing; model facts remain unreliable |
Automations and credentials
| Operation | Boundary |
|---|---|
| All automation routes | automations:manage + items:read, including legacy/catalog/draft/publish/versions/preview/test/runs |
| Credential create/replace/OAuth/revoke | credentials:manage |
| Safe credential metadata | Either management permission; no extra task-read; secrets never readable |
| Migration | Both management rights granted to former workspace managers; review delegation |
| Publish | Explicit confirmation, optimistic draft revision, immutable version; draft retained/revision incremented atomically and monotonically |
| Preview | effect:"none", no side effects |
| Real test | May contact services; any update_item in graph → 400 before enqueue (no synthetic triggering task) |
| Run output | Bounded/sanitized; never intentionally put secrets in templates, public headers, or logs |
| Execution identity | Publisher’s current membership + items:read before run/every node; missing/revoked access fails closed |
| Update task | Triggering task only as publisher; immediate extra read/write check; actor/caller/model/browser cannot choose identity |
| Causation | Depth five; no same-automation re-entry; later failure does not undo original user mutation |
Outbound network, OAuth, and webhook safeguards
- Graph URLs are fixed HTTP(S) configuration, not templates, with no embedded credentials/fragment. Resolve once, pin checked address, retain Host/TLS hostname; bound time/body/output and reject redirects.
- Block IPv4 unspecified/loopback/link-local/multicast/reserved and IPv6 unspecified/loopback/link-local/multicast/mapped loopback/link-local. RFC1918/ULA private destinations intentionally allowed; public credential-bearing destinations require HTTPS.
- Exact-origin
AUTOMATION_NETWORK_EXCEPTIONSbypass address/HTTPS only, never credential exact-origin or exact/descendant path binding, never redirects. Network egress controls remain defense in depth. - OAuth: one-use hashed state, ten-minute encrypted PKCE S256 state, authenticated callback, bounded no-redirect exchanges/refresh; encrypted write-only token versions. External consent/revocation/refresh/provider behavior unverified; distinct from OIDC login.
- New/rotated webhook v2: exact-body HMAC-SHA256. Migrated v1: historical SHA-256 of
secret + "." + bodyuntil rotation. Verify by signing version and compare safely; coordinate receivers before rotating. - All webhook/linear/graph 3xx redirects rejected: intentional compatibility break. Configure final URLs; neither legacy digest nor network exceptions preserve redirects.
MCP
| Setting / call | Rule |
|---|---|
| Transports | Local stdio and optional admin-enabled SSE, disabled by default |
| SSE authentication | Personal bearer on stream and every POST; cookie/URL token rejected |
| Sessions | User-bound, per-user/instance bounded, 30 minutes; disabling closes sessions |
| Access | Same workspace permissions as REST; admin alone grants no task access |
| Writes | Read-only default; HOPYA_MCP_ALLOW_WRITES=true exposes tools, not action consent |
| Approval | Host must enforce each human approval; model/confirmed argument is not evidence; leave writes off if unsupported |
| Identity | Dedicated minimally authorized account; token inherits all its workspace access |
See integration guide and MCP transports.
Container Controls
| Supplied control | Limit |
|---|---|
| Non-root, read-only roots, bounded temp, dropped capabilities, no-new-privileges | API still writes /data; compromise can change/erase it |
| No Docker socket, no API/web published ports; loopback 8888 only | Private network does not isolate from compromised peer containers |
| Pinned dependencies/images | Pins do not automatically receive fixes; patch host/kernel/Docker/images/dependencies |
Do not use privileged containers or give application users Docker access. Review OWASP Docker security, including published-port firewall caveats.
Before Wider Use
- Verify HTTPS, Secure cookies, exact Origin, hostname routing.
- Test permissions/integrations: foreign workspace IDs, denied tokens.
- Rehearse cold restore with local files and separate S3 versions.
- Review dependencies/images, patch cadence, resource/abuse limits.
- Restrict registration/provisioning; review admin/Owner roles and unused tokens.
- Verify no raw credentials, callback codes, or complete AI prompts in logs.
- Establish private reporting; none designated upstream yet. Contact operator privately, never post live credentials.
Suspected compromise: isolate access, preserve protected evidence, revoke credentials, coordinate provider-key rotation, and restore only a verified trusted backup. Rotating APP_KEY alone is not revocation/recovery. Old backups can resurrect sessions/tokens; review/revoke before reopening.