A maioria das landing pages de SaaS tem uma seção de segurança. Menciona criptografia, logos de compliance e algo sobre infraestrutura enterprise. Você não consegue saber se alguma daquilo é verdade sem assinar um NDA.
A home do CastorStack lista oito proteções específicas. Cada uma aponta para código que você pode abrir, testes que quebram o build se alguém remover, ou um script que roda antes do ZIP do comprador sair do repositório. Este post percorre as oito.
Autenticação e RLS nas tabelas do Supabase têm artigo próprio (O custo do primeiro login). O que vem abaixo é todo o resto da lista.
Webhooks de pagamento assinados
Stripe, Paddle e Polar entregam eventos de assinatura via HTTP POST. Qualquer pessoa na internet pode bater naquela URL. A única coisa entre o seu banco e um evento forjado de "pagamento aprovado" é a verificação de assinatura.
No CastorStack cada provedor tem seu verificador: Stripe usa o helper padrão de assinatura de webhook com HMAC-SHA256 e comparação em tempo constante; Paddle interpreta o header Paddle-Signature da mesma forma; Polar checa webhook-signature e webhook-timestamp. Os três rejeitam eventos com mais de cinco minutos, o que fecha a janela de replay. Assinaturas que não batem falham com FixedTimeEquals, não com comparação de string que vaza informação de timing.
Os eventos são armazenados com chave de idempotência antes de qualquer regra de negócio rodar. Um retry do provedor com o mesmo evento não cria uma segunda linha de assinatura.
As implementações estão em PaddlePaymentProvider.cs, StandardWebhookSignature.cs e no provider Polar. Os testes em PaddlePaymentProviderTests.cs e StripeWebhookEndpointsTests.cs cobrem os casos de falha.
Rotas de admin bloqueadas de ponta a ponta
O admin panel é um app Vite separado com deploy próprio. Essa separação ajuda, mas não é o limite de segurança. O limite é a API.
Todo endpoint em /api/v1/admin/* exige um usuário autenticado com papel de admin. Não existe atalho de "leitura pública no admin" e nenhum endpoint que checa o papel em um lugar e esquece em outro. O atributo de autorização é aplicado rota por rota, e os testes garantem que chamadores não autenticados ou sem papel de admin recebem 401 ou 403.
Se você adicionar um endpoint de admin e pular o atributo, vai perceber rápido porque o padrão é consistente nos controllers existentes.
Credenciais de PSP criptografadas
Segredos de provedor de pagamento (chave secreta do Stripe, API key do Paddle, secrets de assinatura de webhook) ficam no banco porque o admin panel precisa configurá-los em runtime. Guardar em texto puro significaria que qualquer um com leitura no Postgres poderia cobrar cartões em seu nome.
O CastorStack criptografa com ASP.NET Data Protection antes de persistir. As chaves de criptografia moram na tabela DataProtectionKeys, e essa tabela tem RLS ligado como toda tabela gerenciada pelo servidor. Respostas da API nunca devolvem o segredo descriptografado; a UI do admin mostra um placeholder mascarado depois do primeiro save.
Rotacionar chaves ou mudar de ambiente exige entender o key ring do Data Protection, documentado no setup de infraestrutura. São mais peças do que variáveis de ambiente, mas variáveis de ambiente não sobrevivem ao requisito de "configurar isso no admin panel" como produto.
Sem SQL cru na API
Entity Framework Core é o único caminho de acesso ao banco no projeto da API. Não há FromSqlRaw com strings interpoladas, nem queries Dapper, nem SQL montado à mão em controllers.
É uma restrição deliberada. Queries parametrizadas via EF eliminam a superfície de injeção mais comum numa API de negócio. Quando precisar de SQL cru, ele pertence a um arquivo de migration — revisado, versionado e nunca montado a partir de input do usuário.
RLS imposto por migration
Este quase passou batido aqui, e merece mais que um bullet.
O Supabase expõe toda tabela em public via PostgREST. A chave anônima vai no bundle do frontend. Qualquer um pode lê-la. Row Level Security é a proteção real, e o Postgres não liga isso automaticamente nas tabelas que suas migrations criam.
O CastorStack tem a migration HardenRowLevelSecurity, que liga RLS em toda tabela gerenciada pelo servidor, revoga grants de anon e authenticated, e muda os privilégios padrão do schema para que o próximo CreateTable nasça fechado. Um teste de regressão (RowLevelSecurityCoverageTests) parseia o source de cada migration e quebra o build se uma tabela nova aparecer sem o ENABLE ROW LEVEL SECURITY correspondente.
Esse teste é a parte que eu não teria escrito até alguém apontar um cliente REST para produção.
Varredura do ZIP do comprador
Quando você empacota o CastorStack para um comprador, o scripts/package-release.mjs monta um ZIP e varre antes de liberar. A varredura procura chaves live do Stripe, segredos com cara de JWT, senhas hardcoded e e-mails pessoais que não deveriam viajar com o template.
Se algo bater, o script sai com erro e o ZIP não sai. É a última linha de defesa contra "commitei um segredo três meses atrás e esqueci".
CORS restrito por padrão
A política de CORS da API lista origens permitidas explicitamente na configuração. Credentials estão desligados. Essa combinação impede que um browser em domínio aleatório faça requisições cross-origin autenticadas à sua API, o que elimina uma classe de ataque CSRF contra sessões baseadas em cookie.
Você configura as origens por ambiente (Cors:AllowedOrigins no appsettings ou a variável de ambiente equivalente). Adicionar um domínio de frontend novo é mudança de config, não de código.
Allowlist de redirect no checkout
Endpoints de checkout aceitam uma URL returnTo para o usuário voltar ao seu app depois do pagamento. Sem validação, esse parâmetro vira open redirect: um atacante manda a vítima pelo seu checkout e para uma página de phishing que parece ter saído de você.
O CastorStack valida returnTo contra as mesmas origens permitidas do CORS. URL em domínio fora da lista retorna 400. Os testes em PublicCheckoutEndpointsTests.cs cobrem o caminho feliz e a rejeição.
O que não está aqui
Isto não é certificação SOC 2. Não há WAF, nem middleware de rate limiting no template, nem varredura automática de dependências no CI além do que você adicionar. Pentest é sua responsabilidade depois do deploy.
Autenticação em dois fatores não está implementada. Listagem e revogação de sessões também não. O audit log registra mudanças no lado da API, não eventos de login do Supabase.
A lista na home é o que está no repositório hoje, não um roadmap disfarçado de matriz de features. Prefiro que você encontre as lacunas aqui do que numa revisão de segurança que você não agendou.
Por que listar claims verificáveis
Marketing de segurança que não pode ser checado corrói confiança mais rápido do que não ter seção nenhuma. Um comprador que clona o repo e abre RowLevelSecurityCoverageTests.cs aprende mais sobre o que está levando do que com qualquer selo de compliance.
Se você está avaliando o CastorStack, comece por esses oito itens. Abra os arquivos. Rode os testes. A home não está pedindo para você acreditar na nossa palavra.