SetFlow
SetFlow

Security architecture

The technical version of the marketing security page. What a complete breach of SetFlow would expose, what it wouldn't, and why.

Overview

SetFlow is designed so that a complete compromise of SetFlow's infrastructure cannot expose student records. This is not a policy statement. It is an architectural property — enforced by where data is stored and how cryptography is layered, not by a vendor pinky-swear.

  • Least access by default. Users see only what their role permits. Internal staff access to customer data is logged and audited.
  • Encryption everywhere. AES-256-GCM at rest for sensitive material, TLS 1.2+ in transit, no exceptions.
  • Tenant isolation. Every query is scoped to a tenant. Cross-tenant access is blocked at the application layer, not relied on at the network layer alone.
  • No selling, no surprise sharing. Customer data is for serving the customer, full stop.

BYODB — bring your own database

Under BYODB, your institution provisions a PostgreSQL database it owns and operates. SetFlow connects to it via a connection string you give us at registration. That connection string is the only piece of secret material SetFlow holds on your behalf.

Connection strings are stored as AES-256-GCM ciphertext. “GCM” (Galois/Counter Mode) provides authenticated encryption — tampering with the ciphertext is detectable, not just resisted. Keys and ciphertext are stored separately: even an attacker who exfiltrates the database table holding the encrypted string cannot decrypt it without separately compromising the key material.

“Zeroized after each request” means the decrypted connection string only exists in process memory for the duration of a single HTTP request. When the request returns, the string is overwritten with zeros and the reference released. It never touches the filesystem, never enters a log line, never crosses a process boundary.

What an attacker who breached SetFlow would get.

  • ✓ Encrypted database connection strings (useless without the keys)
  • ✓ Application source code (open-source-friendly; nothing secret in it)
  • ✓ Institution configuration (school names, subdomains)

What they would not get:

  • ✗ Student names
  • ✗ Student emails
  • ✗ Grades
  • ✗ Submissions
  • ✗ Any student PII whatsoever

LTI 1.3 security

When your IT admin embeds SetFlow inside Canvas / Blackboard / Brightspace / Moodle / Schoology, we verify the integration through two independent layers. Neither alone is sufficient.

Layer 1 — JWKS verification. The LMS signs every launch token with its private key. We fetch the LMS's public keys at jwksUrl (the same trust mechanism OAuth 2.0 uses) and verify the signature. We also check: issuer matches what we registered, audience equals our client ID, nonce matches the one we generated, expiration is in the future, and the LTI version claim is 1.3.x. Any failure → 401.

Layer 2 — DNS domain verification. At registration, the institution claims a domain (e.g. utexas.edu). SetFlow mints a random token and asks them to publish a _setflow-lti-verify.utexas.edu TXT record containing it. The admin clicks “Verify” once the record is live; SetFlow resolves DNS, sees the token, stamps verifiedDomainConfirmedAt. This is the same trust model SSL DV certificates use — if you can write the zone file, you own the domain.

Frame-ancestors. SetFlow content is iframable only by verified LMS origins. The default CSP for every route is frame-ancestors 'none' + X-Frame-Options: DENY. The middleware relaxes this only when a launch-session cookie is present AND the path is on the allowlist of LTI-iframable content routes (classroom + course + practice + lesson pages). The verified-LMS origin is the only one inserted into the relaxed directive.

What happens when a teacher clicks SetFlow in Canvas.

  1. Canvas calls our /api/lti/login-init with iss, login_hint, target_link_uri.
  2. We look up the registered platform by issuer + clientId, mint state and nonce tokens, store them in short-lived HttpOnly; Secure; SameSite=None; Partitioned cookies, and 302-redirect to Canvas's OIDC authorization endpoint.
  3. Canvas authenticates the teacher (or recognizes they're already signed in) and 302s back to /api/lti/launch with a form_post carrying an id_token JWT.
  4. We verify the JWT against Canvas's JWKS, match the nonce against our cookie, cross-check the Referer header against Canvas's expected origin, and on success JIT-provision the teacher's SetFlow account.
  5. We mint a launch-session cookie carrying the verified LMS origin and redirect to the content (a quiz, a course, an assignment).
  6. Middleware reads that cookie on the content page and relaxes frame-ancestors to the verified Canvas origin — so the page renders inside Canvas's iframe but nowhere else.

Transport & headers

SetFlow emits the following security headers on every response:

  • Strict-Transport-Security: max-age=63072000; includeSubDomains; preload. Two-year HSTS cache, subdomain-inclusive.
  • Content-Security-Policy: Tight allow-list. connect-src + script-src locked to vetted origins (Anthropic, OpenAI, Google AI, Vercel Blob, PostHog, Cloudflare, our own support service). The frame-ancestors directive is set in middleware (not next.config) so it can be conditional on LTI launch.
  • frame-ancestors (LTI routes): 'none' by default; relaxed to the verified LMS origin only when the launch-session cookie is present and the path is iframable.
  • X-Frame-Options: DENY by default; omitted on LTI iframable routes for launched users (modern browsers prefer the CSP directive anyway).
  • X-Content-Type-Options: nosniff.
  • Referrer-Policy: strict-origin-when-cross-origin — full URL on same-origin requests, origin-only across sites.
  • Cross-Origin-Opener-Policy: same-origin.
  • Cross-Origin-Resource-Policy: same-origin.
  • Permissions-Policy: camera=(self), microphone=(self), geolocation=(), interest-cohort=().

Cookie security

  • Session cookies: HttpOnly; Secure; SameSite=Lax. They cannot be read by JavaScript and are not sent on cross-site navigations.
  • LTI OIDC handshake cookies (sf-lti-state, sf-lti-nonce, sf-lti-platform-id): HttpOnly; Secure; SameSite=None; Partitioned, 10-minute lifespan, single-use.
  • LTI launch-session cookie (sf-lti-launched): same attribute set, 24-hour lifespan to match the Lti13LaunchSession row's expiry.

Note

Why Partitioned matters. Chrome 132+ enforces the Partitioned attribute (CHIPS — Cookies Having Independent Partitioned State) on SameSite=None cookies in iframe contexts. Without it, third-party cookies in an LMS iframe are dropped silently and the launch breaks with no error message. Every LTI cookie SetFlow sets carries the attribute.

Encryption details

AES-256-GCM (Advanced Encryption Standard, 256-bit key, Galois/Counter Mode). GCM provides authenticated encryption with associated data (AEAD) — every ciphertext carries a 16-byte authentication tag that's verified during decryption. Modifying even one byte of ciphertext causes decryption to fail with an authentication error; you can't silently corrupt the payload. Used for BYODB connection strings, AI provider API keys when supplied per-user, and TOTP secrets.

Argon2id, per RFC 9106 (the Password Hashing Competition winner). Used for any secret-derivation step. Memory-hard — resistant to GPU and ASIC-based brute force in a way that PBKDF2 and bcrypt aren't.

Key management. Encryption keys are derived from ENCRYPTION_KEY (separate environment variable) via scrypt, and are held in process memory only. Ciphertext lives in Postgres rows; keys never share a storage backend with what they decrypt. Annual rotation cryptoperiod.

Regulatory alignment

SetFlow's posture toward education-data regulation is structural rather than contractual. We aim to make compliance enforceable by the architecture itself.

  • FERPA: BYODB satisfies the “direct control” requirement structurally — your institution holds the records database; SetFlow accesses it to provide service but never holds the records independently.
  • GDPR / UK GDPR: The institution is the unambiguous data controller. SetFlow is a processor that doesn't persist student PII, making sub-processor transfer audits trivially clean.
  • SOPIPA: The prohibition on commercial re-use of student data is enforced cryptographically — SetFlow can't repurpose what it doesn't hold.
  • CCPA / CPRA: Same structural argument as SOPIPA. No sale of personal information because we have none to sell.

Note

Consult your legal counsel for jurisdiction-specific advice. Architecture aligns with these regulations; final compliance determinations rest with your institution's legal review.

Reporting vulnerabilities

Found a vulnerability? Email [email protected] with a description, reproduction steps, and any proof-of-concept. We respond within 48 hours and credit researchers who report responsibly. Coordinated disclosure is appreciated; we patch confirmed issues within 7 days for critical findings, 30 days for non-critical.

Can't find what you need? Email support.