Read this first
Private beta No certifications TLS in transit Hashed API keys Mandatory pre-moderation
wandkit is in private beta. There is no SOC 2 report, no ISO certification, no third-party penetration test, and no signed data processing agreement we can put in front of you today. If your procurement process requires any of those, wandkit is not ready for you yet, and you should learn that here rather than three calls in.
What follows is the set of controls that exist in the code. Where a control has a limit, the limit is written down next to it.
Credentials
Three key families, one job each, kept apart at the authentication boundary.
// three prefixes, three jobs. there is no wk_live_ / wk_test_ split
wk_ SDK key. ships inside your app, reaches SDK endpoints only
wks_ server key. your backend only, never in the app
wkp_ personal access token. CLI and API, scoped and expiring
Storage
Keys are stored as hashes
Every key carries 32 bytes from the system random source. The plaintext is shown once, in the dialog that creates it, and never again. What gets persisted is the SHA-256 hash and a short display prefix, so the key cannot be read back out of the database by anyone, including us.
Separation
A key used in the wrong place is refused
An SDK key ships inside your app and reaches SDK endpoints only. A server key reaches server-to-server endpoints only. The check runs in the auth middleware before any handler sees the request, and a mismatch returns 403.
Personal access tokens
Scoped, expiring, revocable
A wkp_ token carries a read or a write scope, and a read token is rejected on anything that is not a GET, HEAD or OPTIONS. Expiry is not optional, every token can be revoked, and each one records when it was last used. A token can also be pinned to one organization, and some operations are session-only and refuse tokens outright.
Scope
Keys belong to one project
There is no account-wide key. Every SDK and server key resolves to exactly one project. Organization roles are ranked, the ones you can hand out are owner, admin and member, and the destructive work (project settings, key creation, moderation, bans, erasure) requires admin.
Third-party credentials you hand us
Connecting App Store Connect, Google Play, RevenueCat or APNs means giving wandkit a private key or a service account file. Those values are sealed with AES-256-GCM under a master key held in the environment, are never returned by the API, and appear in the dashboard as the last four characters only.
Be clear about the scope of that: it covers integration secrets specifically. It is not a claim about full database encryption, and we are not going to make one.
The API is served over HTTPS. The SDK's default API host is HTTPS, the SDK has no plaintext fallback path, and it ships no App Transport Security exception of its own.
On your user's device
iOS 15 and up, a Swift package with zero runtime dependencies.
Screenshot reports
The photo library is never touched
Screenshot reporting is off unless you turn it on. iOS tells the SDK that a screenshot happened but hands over no image. Rather than ask for photo library access, the SDK re-renders your own app's windows into a fresh image. It never reads Photos and triggers no permission prompt.
Anonymous sessions
Read-only on the board until you identify
Until your app calls identify(), a board session can read posts and nothing more. Posting, voting and commenting all require an identified user. Surveys are a different path: survey triggers and responses run on the SDK key and do not need identify(), so a survey response can arrive from someone you have not identified.
Abuse controls on the feedback board
The board is user-generated content rendered inside your app, which makes it your exposure before it is ours. Three things are always on.
- Pre-moderation is mandatory. Every post created through the SDK lands pending and stays invisible until someone on your team approves it, and its attachments are held pending with it. There is no setting to switch this off. The column that once controlled it was removed from the schema.
- Write budgets, per project and per end user: 5 posts an hour, 30 comments an hour, 20 reports a day. The windows slide rather than reset on the hour, so a burst at 10:59 does not collect a fresh budget at 11:00.
- Reports and bans. End users report content, reports land in a moderation queue, and an admin can ban an end user from writing to that project. The ban is re-checked on every write.
One limit worth stating plainly: the rate limiter holds its counters in the API process. Run more than one instance and the effective budget multiplies by the instance count. Pre-moderation, not the limiter, is what actually stands between an abusive post and your users.
Attachments
Images and video attached to a post go straight to object storage through a signed URL that expires in 15 minutes. The declared content type and byte length are bound into the signature, so the storage layer rejects a file that does not match what was asked for. The allowlist is PNG, JPEG, HEIC and WebP for images, MP4 and QuickTime for video. Images cap at 10 MB, video at 50 MB, five attachments per post. Upload slots minted but never bound to a post are deleted, object first, after 24 hours.
Retention and deletion
A cleanup worker runs on a timer and drops data that has aged out.
- Expired sign-in tokens are deleted.
- Invitations are marked expired, and terminal ones are deleted once past their retention window.
- Referral browser fingerprints are deleted after 7 days, referral match attempts after 14.
- Push device registrations that have not checked in for 90 days are deleted. A live install refreshes on every launch, so anything that stale is an uninstall.
- Attachment upload slots never bound to a post are deleted after 24 hours.
Erasing one end user
There is a right-to-erasure endpoint. An organization admin calls it with a project and an external user id, and it runs as a single transaction, because a half-finished erasure that leaves the identifier on some rows is worse than none at all.
POST /api/v1/projects/{project_id}/sdk-users/{external_user_id}/erase
What it actually does, precisely. Posts and comments are anonymized rather than deleted: the identifier comes off, the text stays, because other people's threads would otherwise lose their context. Votes and follows are rewritten to one fresh placeholder so counts stay correct and nothing links back to the person. Reports, push device registrations, any ban row and the end user record itself are deleted. The response returns counts for the posts and comments anonymized, the votes and follows rewritten, and the reports and push devices deleted.
Today this is an API call, not a button in the dashboard. If you need to run one, write to us and we will walk you through it.
Not in place
Named here on purpose. A security page that lists only what exists is a security page that lies by omission.
- No SOC 2, no ISO 27001, no third-party audit, no penetration test report.
- No SAML, no SCIM, no enforced multi-factor authentication. Dashboard sign-in is Google only.
- No admin audit log. Individual posts keep their own activity timeline, but there is no reviewable trail of what your team did across the dashboard.
- No region selection and no data residency guarantee. There is no concept of a region in the product.
- No signed data processing agreement, and no published privacy policy or terms.
- No uptime commitment, no status page, no SLA.
Some of these we intend to build. None of them are things you should plan around until they exist.
Reporting something
Write to info@wandkit.app. That is the address that reaches us. There is no security@ alias, no bug bounty and no formal disclosure program yet. Send us what you found and we will tell you what we are doing about it.