Security
How authentication, sessions, and setup tokens actually work today — not a compliance claim. Nastar makes no certification or legal compliance claims; this page documents mechanism, not attestation.
Email + OTP login
- No passwords. A 6-digit code, cryptographically random (
crypto.randomInt), valid for 10 minutes. - Stored as an HMAC-SHA256 hash (server secret + code), never in plaintext, alongside a hash of the requesting email and IP.
- Single-use: verification atomically consumes the challenge with an
UPDATE ... WHERE consumed_at IS NULL RETURNING— a race between two verify requests for the same code can only succeed once. - Up to 5 wrong-code attempts per challenge before it's locked out; a fresh request issues a new challenge.
- Verify and request responses are generic ("invalid or expired code" / "rate limited") — they don't reveal whether an email address has an account, to avoid enumeration.
Rate limits
| Limit | Value |
|---|---|
| Resend cooldown | 60 seconds |
| Requests per email address / hour | 5 |
| Requests per IP address / hour | 20 |
Sessions
- Cookie name
nastar_session, 30-day expiry,HttpOnly,SameSite=Lax,Secureon every deployment served over HTTPS. - A new session is issued on every successful OTP verification;
POST /v1/auth/logoutrevokes it server-side, not just client-side cookie deletion. - Every device you're signed in on is a separate session row — the account page lists them all and can revoke any one individually (
GET/DELETE /v1/auth/sessions), without logging out your other devices.
CORS and CSRF
- Cookie-authenticated routes (
/v1/auth,/v1/organizations,/v1/sites) allow only the configuredDASHBOARD_ORIGIN, with credentials. - Public and docs routes (
/v1/public/*,/v1/docs,/v1/openapi.json) allow any origin — they carry no credentials, so there's nothing to protect. - All mutating requests must be
Content-Type: application/json; non-JSON mutation attempts are rejected before reaching any handler, which also blocks simple cross-site form submissions from acting as CSRF vectors against session routes.
Setup tokens
- Format
nst_..., hashed at rest the same way as OTP codes (HMAC-SHA256), never stored or logged in plaintext. - Shown once, at creation, in the dashboard response — not retrievable again afterward, even by an org admin.
- Bound to one organization and, optionally, one site; carries an explicit subset of
sites:read/sites:write/sites:publish. - Only organization admins can create, list, or revoke tokens.
- Revoked or expired tokens are rejected with
401on every/v1/agent/*route immediately — no caching of validity.
Invites
- Single-use tokens (
nvt_...), hashed at rest the same way as OTP codes and setup tokens, expiring after 7 days. - Only organization admins can send or revoke an invite.
- Accepting requires being signed in as the exact invited email — logging in with a different account shows a mismatch instead of silently granting access.
- Re-inviting the same address replaces its earlier pending invite rather than leaving both valid.
Tenant isolation
Applied consistently across organizations, sites, and tokens:
- Not a member of the organization at all →
404(the resource's existence isn't revealed to non-members). - A member, but the action needs a role/scope you don't have →
403.
A setup token bound to a specific site is rejected with 403 on any other site, even within the same organization.
Consent gating
The SDK fails closed: if the published config can't be fetched, or stored consent is malformed or version-mismatched, every optional category is treated as not consented — see SDK reference for the full contract.
What isn't built yet
- No OAuth device flow or MCP server for automation — a scoped setup token created manually in the dashboard is the only automation credential today. This is an intentional scope decision, not an oversight.
- No 2FA beyond the OTP login itself (there's no separate password to protect).
- No public security disclosure process or bug bounty yet — the service is deployed and serving, but no intake channel for reports has been set up.