If you discover a potential security vulnerability, please do not open a public issue. Instead, report it privately via email to ensure the safety of our users.
Email: security@eduvault.io (or your-email@example.com)
EduVault is a non-custodial platform:
- Private Keys: We never store or transmit private keys. Wallet interactions happen client-side via Reown/AppKit.
- Transactions: Users must manually approve all on-chain actions.
- Infrastructure: Sensitive keys are managed through secure environment variables.
API rate limits are enforced by withApiHardening (src/lib/api/hardening.js). Client identification uses the following trust hierarchy:
| Header | Trusted? | Notes |
|---|---|---|
x-vercel-forwarded-for |
Always | Injected by Vercel's edge network; not overwritable by clients. |
x-real-ip |
Only with TRUSTED_PROXY_COUNT env |
Trusted only when a known reverse proxy strips client forwarding headers. |
x-forwarded-for |
Never | Client-controllable; never used for rate-limit bucketing. |
x-real-ip (no proxy) |
Never | Falls back to metadata-based composite key. |
When no trusted IP header is available, the rate limiter derives a per-caller bucket from semi-stable request metadata (user-agent + accept-language), preventing a single shared "local" bucket while still resisting header-spoofing resets.
Environment variables:
TRUSTED_PROXY_COUNT— Set to a positive integer when deploying behind a reverse proxy that overwritesx-real-ip. Only set this when you control the proxy layer.
| Component | Status |
|---|---|
| EduVault Frontend | In Scope |
| EduVault API Routes | In Scope |
| Smart Contracts | In Scope |
| 3rd Party Services (Clerk, MongoDB) | Out of Scope |
Disaster recovery procedures must enforce zero-trust entitlement security and strict secret isolation:
- Decryption Key Verification: During restore verification drills (
scripts/restore-verification.mjs),JWT_SECRETmust be present and validated for minimum key length (≥ 32 characters). - Zero-Trust Access Probes: Restored data must be validated against entitlement decision probes to ensure unentitled callers receive no access leaks and revoked/refunded buyers cannot decrypt materials.
- Secret Isolation: Production secrets must never be logged or saved in unencrypted restore drill output. Restore drills must take place in isolated staging environments.
Access to protected material is explicitly audited with reason codes to allow creators and maintainers to investigate access patterns and identify potential leaks or unauthorized support access.
- Reason Codes: The system distinguishes between
preview(publicly accessible chunks or low-res versions),buyer_download(entitled purchaser),admin_review(platform administration and moderation), andsupport(explicitly granted support access). - Redaction: Sensitive tokens (e.g. Bearer tokens, capability tokens) and URLs in audit logs are redacted before being recorded to standard output.
EduVault enforces strict isolation and access policies during content takedowns:
- Immediate Public Delisting: Takedown actions immediately delist materials from the public catalog and block all new purchase transactions.
- Buyer Safety vs. Access Retention: Severe malware/security violations result in instant entitlement revocation and automated escrow refund. Copyright and creator deprecation preserve historical buyer snapshot access while disputes are resolved.
- Private Evidence Retention: Cryptographic hashes, IPFS storage CIDs, and violation records are preserved in private immutable audit storage for legal and compliance verification without public exposure. See
docs/content-takedown-and-evidence-retention.mdfor the full operational policy.
Every place user- or creator-supplied content reaches a page is either escaped, sanitized or rejected:
- Plain text (titles, summaries, notification text) is rendered as JSX text, and React escapes it. Never pass user content to
dangerouslySetInnerHTML. - Rich text / HTML (
descriptionon upload and import) goes throughsanitizeRichTextinsrc/lib/api/contentSanitizer.js. It keeps a short allowlist of inline tags, removes scripts, event handlers,style, SVG/MathML and forms, and addsrel="noopener noreferrer nofollow"to links. - Stored URLs (profile social/avatar links, material cover/thumbnail URLs, import rows) are checked at write time with
validateOptionalUrl/isSafeUrl(src/lib/api/safeUrl.js). Onlyhttp,https,mailtoand relative paths are accepted. The URL is parsed with the WHATWGURLparser, so tricks likejava\tscript:, a leading space, or a\\hostprotocol-relative URL are resolved the same way a browser would and then rejected. - Outbound links are rendered with
safeExternalLinkProps(url). It returnsnullfor anything that isn't absolutehttp(s), and otherwise setstarget="_blank"andrel="noopener noreferrer nofollow".SocialLinksalso shows the destination hostname in the link title, so a link can't hide where it goes. - Notification deep links must be internal paths (
/...);notify()refuses absolute or protocol-relative links.
When you add a new surface that renders user content, reuse these helpers rather than writing a new check. Test cases live in src/lib/api/contentSanitizer.test.js.
We commit to acknowledging all reports within 48 hours and will work to resolve valid issues as quickly as possible.