npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@devix-tecnologia/anonymizer-core

v0.1.0

Published

DSL e engine de anonimização determinística para bancos de dados

Readme

@devix-tecnologia/anonymizer-core

@devix-tecnologia/anonymizer-core

DSL e engine de anonimização determinística para PostgreSQL. Recebe um perfil declarativo — que tabelas entram, que colunas mascarar, como — e devolve o SQL que constrói uma cópia anonimizada dos dados.

O pacote é puro: não abre conexão, não executa nada, não lê arquivo. Ele gera texto SQL. Quem executa é o consumidor — no caso da Devix, o @devix-tecnologia/db-backup.


A ideia

A anonimização acontece dentro do banco, num schema temporário:

CREATE SCHEMA IF NOT EXISTS backup_tmp;

CREATE TABLE backup_tmp.usuario AS
SELECT CASE WHEN "email" IS NULL THEN NULL
            ELSE ... END AS email,
       ...
FROM public.usuario;

O dump sai desse schema, nunca do public. Nenhum dado sensível trafega para fora do PostgreSQL para ser mascarado — o que também torna a operação viável em bases grandes, já que tudo é feito em SQL sobre o conjunto inteiro.

Uso

import {
  buildPlan,
  defineProfile,
  emailMask,
  generateFullPipelineSql,
  hash,
  mask,
  nullify,
} from '@devix-tecnologia/anonymizer-core';

const perfil = defineProfile({
  version: '1',
  tables: {
    usuario: {
      include: 'data',
      anonymize: {
        email: emailMask({ preserveLeft: 2 }),
        cpf: hash('hmac-sha256', process.env.ANON_SECRET),
        telefone: mask({ preserveRight: 4 }),
        observacoes: nullify(),
      },
    },
    // Estrutura sim, linhas não: a tabela existe vazia no dump.
    auditoria: { include: 'schema' },
    // Fora do dump.
    sessao: { include: 'exclude' },
    // Recorte por linha.
    pedido: { include: 'data', filter: { where: "criado_em > '2026-01-01'" } },
  },
});

const sql = generateFullPipelineSql(buildPlan(perfil), 'backup_tmp');

defineProfile valida com zod e lança na hora — um perfil errado não chega a virar SQL.

As três decisões de um perfil

include: o que entra

| Valor | Efeito | | --- | --- | | data | estrutura e linhas, com as transformações aplicadas | | schema | só a estrutura; a tabela vem vazia | | exclude | a tabela não entra no dump |

anonymize: como cada coluna é mascarada

| Transformação | O que faz | Preserva | | --- | --- | --- | | hash(algoritmo, segredo?) | sha256 nativo ou hmac-sha256 do pgcrypto | unicidade e junções | | emailMask({ preserveLeft, hashLen }) | [email protected] → [email protected] | o domínio, e a forma de e-mail | | mask({ preserveLeft, preserveRight, char }) | substitui o miolo por * | as pontas, e o comprimento | | constant(valor) | o mesmo literal em toda linha | nada | | nullify() | NULL | nada |

Toda transformação preserva NULL como NULL — uma coluna nula continua nula, e não vira o hash da string vazia.

filter: que linhas entram

filter.where é interpolado literalmente na cláusula WHERE. É recurso de operador, não entrada de usuário: quem monta o perfil controla o texto.


Determinismo, e o que ele custa

A mesma entrada produz sempre a mesma saída. Isso é o que permite anonimizar tabelas separadamente e ainda assim juntá-las: se usuario.email e pedido.email_contato recebem a mesma transformação, continuam casando depois de mascarados.

O preço é conhecido e precisa ser dito: hash determinístico sem segredo é vulnerável a ataque de dicionário quando o domínio de valores é pequeno e adivinhável. CPF tem 10¹¹ possibilidades; e-mail de um domínio conhecido, menos ainda. Quem tem o dump anonimizado e a lista de candidatos recupera o original testando um por um.

A defesa é hash('hmac-sha256', segredo): sem o segredo, a lista de candidatos não ajuda. Use-o para todo identificador de pessoa.

Duas ressalvas sobre o segredo:

  • ele é interpolado no SQL gerado (com aspas escapadas). Se esse SQL for registrado em log, o segredo vai junto;
  • hmac() exige a extensão pgcrypto no banco de origem. Sem ela, a execução falha — o sha256 nativo não tem essa dependência.

Onde a anonimização NÃO alcança

Mascarar coluna não anonimiza um conjunto de dados. Continuam identificáveis:

  • combinações: CEP, data de nascimento e sexo bastam para singularizar boa parte de uma população, mesmo com nome e CPF mascarados;
  • campos livres: observacoes com um nome digitado dentro passa intacto se a coluna não for tratada — daí nullify() ser o padrão sensato para texto livre;
  • linhas raras: um registro único numa coluna preservada continua único.

O pacote dá a ferramenta; decidir o que é suficiente para o caso é trabalho de quem escreve o perfil.

API

| Export | Papel | | --- | --- | | defineProfile | valida o perfil e o devolve tipado | | hash, emailMask, mask, constant, nullify | construtores de transformação | | isIncluded, isData | consultas sobre uma regra de tabela | | buildPlan | perfil → plano, com a expressão SQL de cada coluna | | generateFullPipelineSql | plano → SQL do pipeline inteiro | | generateCreateSchemaSql, generateCreateTableSql, generateDropSchemaSql | os pedaços, para quem quer montar o próprio |

Testes

pnpm --filter @devix-tecnologia/anonymizer-core test

23 testes, incluindo verificação baseada em propriedades (fast-check): para qualquer perfil e qualquer nome de coluna, o SQL gerado é sintaticamente válido. Nenhum toca banco.