Segurança em nível de linha é um mecanismo de banco de dados que filtra quais linhas cada usuário pode ver ou alterar, aplicado por políticas em toda consulta. O modelo mental é uma cláusula WHERE invisível e obrigatória: a condição que você teria de lembrar em toda consulta vira uma propriedade da própria tabela — aplicada pelo engine, em todo caminho de acesso, incluindo os que o código da sua aplicação nunca vê.
Principais pontos
| Pergunta | Resposta |
|---|---|
| O que é | Controle de acesso por linha, aplicado pelo próprio engine do banco |
| O modelo mental | Uma cláusula WHERE que não pode ser esquecida |
| O caso de uso matador | Isolamento multi-tenant e dados por usuário, na camada que violações não conseguem pular |
| As letras miúdas | Roles de bypass, negar por padrão, contexto de pooling — as pegadinhas são operacionais |
| A falha que previne | Um filtro esquecido no código da aplicação = vazamento entre tenants |
RLS em dez linhas de SQL
CREATE TABLE invoices (
tenant_id uuid NOT NULL,
total numeric(10,2)
);
ALTER TABLE invoices ENABLE ROW LEVEL SECURITY; -- daqui em diante: negar por padrão
CREATE POLICY tenant_isolation ON invoices
USING (tenant_id = current_setting('app.tenant')::uuid) -- o que você pode ler
WITH CHECK (tenant_id = current_setting('app.tenant')::uuid); -- o que você pode escrever
-- Toda consulta agora se comporta como se terminasse com WHERE tenant_id = <o seu>.
-- Uma consulta que esquece o filtro retorna nada — não tudo.
A mesma garantia existe em bancos de documentos como controle de acesso na própria linha — cada objeto carrega sua ACL, e a plataforma a aplica em toda requisição:
// JavaScript / Node.js — Back4app JS SDK
// Row-level security as data: this row is readable by its owner, period
const note = new Parse.Object('Note');
note.set('text', 'Q3 salary planning');
note.setACL(new Parse.ACL(Parse.User.current())); // owner-only, on the row itself
await note.save();
// Later, any query by any user — the row exists only for its owner
const notes = await new Parse.Query('Note').find(); // others never see it // Flutter / Dart — Back4app Flutter SDK
// Row-level security as data: this row is readable by its owner, period
final user = await ParseUser.currentUser() as ParseUser;
final acl = ParseACL(owner: user); // owner-only, on the row itself
final note = ParseObject('Note')
..set('text', 'Q3 salary planning')
..setACL(acl);
await note.save(); // other users' queries never return this row // iOS / Swift — Back4app Swift SDK
// Row-level security as data: this row is readable by its owner, period
var note = Note()
note.text = "Q3 salary planning"
note.ACL = try ParseACL.defaultACL() // owner-only, on the row itself
note.save { result in
if case .success = result {
print("saved — other users' queries never return this row")
}
} // Android / Kotlin — Back4app Android SDK
// Row-level security as data: this row is readable by its owner, period
val note = ParseObject("Note").apply {
put("text", "Q3 salary planning")
acl = ParseACL(ParseUser.getCurrentUser()) // owner-only, on the row itself
}
note.saveInBackground { e ->
if (e == null) Log.d("Notes", "saved — invisible to every other user")
} O que o engine faz com uma consulta
USING vs. WITH CHECK, permissivas vs. restritivas
| Conceito | Governa | Regra de bolso |
|---|---|---|
USING | O que existe para você: leituras e linhas elegíveis para update/delete | ”Posso ver isto?” |
WITH CHECK | O que você pode escrever: inserts e valores pós-update | ”Posso criar desta forma?” |
| Políticas permissivas (padrão) | Combinadas com OR — qualquer uma que case concede | Conceder acesso |
| Políticas restritivas | Somadas com AND — todas precisam passar | Impor limites obrigatórios |
Duas regras de composição que valem memorizar da referência do PostgreSQL: omitir WITH CHECK reaproveita o USING para escritas, e pelo menos uma política permissiva precisa passar antes de as restritivas serem sequer consultadas — só restritivas significa que ninguém entra.
Onde o RLS é suportado
| Família de engine | Mecanismo |
|---|---|
| PostgreSQL (9.5+) e derivados | CREATE POLICY — a implementação de referência |
| SQL Server (2016+) | Políticas de segurança sobre funções de predicado inline (filter + block) |
| SQL distribuído (CockroachDB, YugabyteDB) | Políticas compatíveis com PostgreSQL |
| Data warehouses em nuvem | Políticas de acesso por linha, no dialeto de cada fornecedor |
| Bancos de documentos | Não baseados em política — ACLs por objeto aplicadas pela camada de plataforma |
A última linha é a que interessa a este glossário: em bancos de documentos a fronteira em nível de linha tipicamente é dado na própria linha (ACLs) em vez de um predicado no engine — mesma garantia, mecanismo diferente, detalhado no artigo de engenharia do Back4app.
As pegadinhas, finalmente em um só lugar
- Surpresas do negar por padrão. Habilitar o RLS sem nenhuma política bloqueia todo mundo, exceto o dono — metade das histórias de “o RLS derrubou a produção” é isso.
- A matriz de bypass. Superusuários, roles com
BYPASSRLSe donos de tabela pulam as políticas — definaFORCE ROW LEVEL SECURITYse o dono também é o usuário da aplicação, e audite quem tem o quê. - Views avaliam como o dono delas por padrão — uma view sobre uma tabela com RLS pode contorná-lo silenciosamente, a menos que criada com direitos do invocador (
security_invokerno PostgreSQL). - Constraints vazam existência. Uma violação de unicidade ou de chave estrangeira pode revelar que uma linha invisível existe — o canal encoberto documentado; trate unicidade sobre valores protegidos de acordo.
- Erros podem vazar valores. Expressões forjadas que falham em dados específicos (uma divisão por zero quando um valor oculto casa) exfiltram por mensagens de erro — a classe de side-channel que a documentação do SQL Server descreve.
- Dumps e pools têm modos. Ferramentas de backup podem precisar do row security desligado para exportar tudo; poolers em modo transação precisam de contexto com escopo de transação (
SET LOCAL), ou tenants sangram entre requisições.
Performance: políticas são código no hot path
Três regras cobrem a maior parte da experiência de campo: indexe toda coluna que uma política referencia (o predicado roda antes dos filtros da sua própria consulta — sem índice, é um scan); mantenha os predicados livres de joins, empurrando lookups para funções ou verificações de role; e faça funções por requisição serem avaliadas uma vez por consulta, não uma vez por linha, envolvendo-as em uma subquery escalar. Bem medido, o RLS custa um dígito percentual; mal medido, é a lentidão misteriosa em toda tabela que você protegeu.
Casos de uso comuns
- SaaS multi-tenant. O caso canônico — isolamento de tenant aplicado abaixo da aplicação, onde um filtro esquecido não consegue virar violação.
- Dados por usuário. Mensagens, documentos, prontuários: usuários veem as próprias linhas, ponto final.
- Fronteiras departamentais e regionais. Vendas vê a sua região; auditores veem tudo, somente leitura — camadas de política em ação.
- Regimes de compliance. Controle de acesso demonstrável na camada de dados, que é onde os auditores gostam.
- Acesso analítico compartilhado. Analistas consultam réplicas de produção diretamente, vendo apenas o que a role permite — seguro porque o banco aplica, não o dashboard.
Você deveria aplicar em nível de linha? Matriz de decisão
| Aplique com RLS/ACLs quando… | Filtragem na aplicação pode bastar quando… |
|---|---|
| Múltiplos tenants ou usuários compartilham tabelas | O banco tem exatamente um chamador confiável |
| Algo além do app toca o banco | Sem ferramentas de BI, sem SQL de admin, sem um segundo serviço |
| Filtro esquecido significa violação, não bug | O escopo é conveniência, não segurança |
| O compliance quer prova na camada de dados | Os dados não são sensíveis |
| Você quer a fronteira testada uma vez, centralmente | Você gosta de auditar toda consulta para sempre |
A síntese honesta das duas colunas: mantenha o escopo na aplicação pela legibilidade — e aplique no banco mesmo assim. Defesa em profundidade é o ponto inteiro.
Limitações e trade-offs
- Políticas são invisíveis por design — o que torna depurar o “cadê minhas linhas?” uma habilidade genuína; logue o contexto de sessão primeiro.
- Autorização complexa ultrapassa predicados. Regras que precisam de estado de workflow ou de lógica entre entidades pertencem às camadas de autorização da aplicação, com o RLS como retaguarda.
- A superfície operacional é real. A matriz de bypass, os modos de dump e o contexto de pooling são conhecimento operacional contínuo, não configuração única.
- Avaliação por linha é um imposto — pequeno quando bem projetado, ilimitado quando não.
- Protege linhas, não colunas. Esconder campos é segurança em nível de coluna; combine as duas para efeito em nível de célula.
Segurança em nível de linha no Back4app
O Back4app é uma plataforma open-source de Backend as a Service (BaaS) que combina banco de dados gerenciado, APIs REST e GraphQL geradas automaticamente, autenticação, armazenamento de arquivos e funções serverless com Cloud Code. Seu modelo em nível de linha é o mecanismo de ACL-como-dado das abas acima: todo objeto carrega sua lista de acesso, papéis expressam tenants e times, e a plataforma aplica os dois em toda requisição — SDKs, REST, GraphQL e o dashboard incluídos, com as permissões em nível de classe como a camada restritiva por cima. É a garantia da cláusula WHERE obrigatória, entregue em um banco de documentos, com a lista de pegadinhas acima absorvida pela plataforma em vez de atribuída a você.
Perguntas frequentes
O que é segurança em nível de linha em termos simples?
Uma cláusula WHERE invisível e obrigatória. Você anexa políticas a uma tabela e o banco aplica as condições delas a toda consulta automaticamente — cada usuário vê e modifica apenas as linhas que a política permite, não importa a consulta que escreva ou a ferramenta que use. O filtro vive no banco, então nenhum caminho de código da aplicação consegue esquecê-lo.
Como funciona a segurança em nível de linha?
Você habilita o RLS em uma tabela e cria políticas com predicados booleanos — tipicamente comparando a coluna de dono ou de tenant da linha com o usuário atual ou uma variável de sessão. Na consulta, o engine avalia o predicado por linha antes das suas próprias condições, filtrando leituras e bloqueando escritas não permitidas. Com o RLS habilitado e nenhuma política, o padrão é negar tudo.
Qual a diferença entre USING e WITH CHECK?
Direção. USING filtra o que existe para você: linhas visíveis ao SELECT e elegíveis para UPDATE ou DELETE. WITH CHECK valida o que você escreve: linhas sendo inseridas e os novos valores após um update. Omitido o WITH CHECK, o predicado do USING vale para os dois — mas tabelas em que usuários leem amplo e escrevem estreito precisam dos dois, configurados de forma diferente.
Quem pode contornar a segurança em nível de linha?
No PostgreSQL: superusuários, roles com BYPASSRLS e — o que todo mundo esquece — o dono da tabela, a menos que você defina FORCE ROW LEVEL SECURITY. Auditar essa matriz de bypass faz parte de colocar RLS em produção; uma política perfeita não protege nada se a aplicação conecta como o dono da tabela.
Segurança em nível de linha prejudica a performance?
Pode — o predicado da política é avaliado contra as linhas candidatas em toda consulta. As mitigações são consistentes na experiência de produção: indexe as colunas que as políticas referenciam, mantenha os predicados livres de joins (use funções ou roles de lookup) e faça funções por requisição serem avaliadas uma vez por consulta, não uma vez por linha. Política sobre coluna sem índice é um table scan com crachá de segurança.
Qual a diferença entre RLS e filtragem na aplicação?
Onde vive a fronteira. A filtragem na aplicação escopa consultas no código — flexível, mas duplicada em cada caminho e ignorada por qualquer coisa que fale com o banco diretamente. O RLS aplica no engine, cobrindo todo caminho de acesso, incluindo ferramentas de admin e outros serviços. O consenso é defesa em profundidade: escope na aplicação pela clareza, aplique no banco pela segurança.
Como o RLS funciona com connection pooling?
Com cuidado. Aplicações com pool conectam como um único usuário de banco, então as políticas dependem de uma variável de sessão por requisição, e não da identidade da conexão — definida no checkout, lida pela política, resetada na devolução. Com poolers em modo transação, a configuração precisa ter escopo de transação, ou o contexto de um tenant vaza para a próxima requisição na mesma conexão. Essa interação é o bug de RLS mais comum em produção.
Como testar políticas de segurança em nível de linha?
Impersone cada role e rode as quatro operações — select, insert, update, delete — verificando tanto o que aparece quanto o que é recusado. Adicione os dois casos esquecidos: o contexto não definido (nenhuma variável de tenant deveria significar nenhuma linha, não todas) e a matriz de bypass (dono, superusuário, caminhos de replicação). O RLS conquista confiança com testes adversariais, não com a política bem escrita.