Most SaaS landing pages have a security section. It mentions encryption, compliance logos, and something about enterprise-grade infrastructure. You cannot tell whether any of it is true without signing an NDA.

CastorStack's home page lists specific protections. Each one maps to behavior you can verify in the repository or in automated tests that fail the build if someone removes it. This post walks through the seven that matter once you deploy your product.

Authentication and RLS on Supabase tables got their own article (The cost of the first login). What follows is everything else on that list.

Signed payment webhooks

Stripe, Paddle and Polar all deliver subscription events over HTTP POST. Anyone on the internet can hit that URL. The only thing standing between your database and a forged "payment succeeded" event is signature verification.

In CastorStack each provider has its own verifier: Stripe uses the standard webhook signature helper with HMAC-SHA256 and constant-time comparison; Paddle parses the Paddle-Signature header the same way; Polar checks webhook-signature and webhook-timestamp. All three reject events older than five minutes, which closes the replay window. Mismatched signatures fail with constant-time comparison, not a string comparison that leaks timing information.

Events are stored with an idempotency key before any business logic runs. A provider retrying the same event does not create a second subscription row.

Each provider has its own verification path in the API. Automated tests cover invalid signatures, stale timestamps, and replay attempts for Stripe, Paddle, and Polar.

Admin routes gated end to end

The admin panel is a separate Vite app with its own deployment. That separation is useful, but it is not the security boundary. The boundary is the API.

Every admin API endpoint requires an authenticated user with the admin role. There is no "public admin read" shortcut and no endpoint that checks the role in one place but forgets it in another. Authorization is applied route by route, and the tests assert that unauthenticated and non-admin callers get 401 or 403.

If you add a new admin endpoint and skip the attribute, you will notice quickly because the pattern is consistent across the existing controllers.

Encrypted PSP credentials

Payment provider secrets (Stripe secret key, Paddle API key, webhook signing secrets) are stored in the database because the admin panel needs to configure them at runtime. Storing them in plain text would mean anyone with read access to Postgres could charge cards on your behalf.

CastorStack encrypts them with ASP.NET Data Protection before persistence. Encryption keys are stored server-side, and that storage is protected like every other server-managed table. API responses never return the decrypted secret; the admin UI shows a masked placeholder after the initial save.

Rotating keys or moving between environments requires understanding Data Protection's key ring, which is documented in the infrastructure setup. It is more moving parts than environment variables, but environment variables do not survive "configure this in the admin panel" as a product requirement.

No raw SQL in the API

Entity Framework Core is the only database access path in the API project. There are no FromSqlRaw calls with interpolated strings, no Dapper queries, no hand-built SQL in controllers.

That is a deliberate constraint. Parameterized queries through EF eliminate the most common injection surface in a line-of-business API. When you need raw SQL, it belongs in a migration file where it is reviewed, versioned, and never built from user input.

RLS enforced by migration

This one almost slipped past us, and it deserves more than a bullet point.

Supabase exposes every table in public through PostgREST. The anonymous key ships in your frontend bundle. Anyone can read it. Row Level Security is the actual protection, and Postgres does not enable it automatically on tables your migrations create.

CastorStack ships database migrations that enable RLS on every server-managed table, revokes grants from public database roles, and changes the schema's default privileges so the next table created is born closed. A regression test in CI fails the build if a new table appears without row-level security enabled.

That test is the part I would not have thought to write until after someone pointed a REST client at production.

Strict CORS defaults

The API's CORS policy lists explicit allowed origins from configuration. Credentials are disabled. That combination means a browser on a random domain cannot make authenticated cross-origin requests to your API, which rules out a class of CSRF attacks against cookie-based sessions.

You configure the allowed origins per environment (Cors:AllowedOrigins in appsettings or the equivalent environment variable). Adding a new frontend domain is a config change, not a code change.

Checkout redirect allowlist

Checkout endpoints accept a returnTo URL so the user lands back on your app after payment. Without validation, that parameter becomes an open redirect: an attacker sends a victim through your checkout and out to a phishing page that looks like it came from you.

CastorStack validates returnTo against the same allowed origins as CORS. A URL on an unlisted domain returns 400. Automated tests cover both valid redirects and rejected URLs.

What is not here

This is not a SOC 2 certification. There is no WAF, no rate limiting middleware in the template, no automated dependency scanning in CI beyond what you add yourself. Penetration testing is your responsibility after you deploy.

Two-factor authentication is not implemented. Session listing and revocation are not implemented. The audit log records API-side changes, not Supabase login events.

The list on the home page is what ships in the repo today, not a roadmap dressed as a feature matrix. I would rather you find the gaps here than during a security review you did not schedule.

Why list verifiable claims

Security marketing that cannot be checked erodes trust faster than no security section at all. A buyer who clones the repo and runs the security-related test suite learns more about what they are getting than from any compliance badge.

If you are evaluating CastorStack, start with these items. Read the implementation. Run the tests. The home page is not asking you to take our word for it.