Autorização e capacidades
RBAC, autorização por recurso, a fronteira de autoridade do Arcane.Capacidade, segurança de dependências e os limites do sandbox.
Autenticação diz quem é. Autorização diz o que pode. A segunda é onde mora a maioria das falhas exploradas na prática, e é a menos testada — porque exige pensar no usuário legítimo agindo fora do seu papel.
A falha mais comum: autorização por objeto#
Conferir o papel e esquecer o dono é a vulnerabilidade mais frequente em aplicações web. O usuário está autenticado, tem o papel certo, e lê o pedido de outra pessoa trocando o número na URL.
// ERRADO: confere o papel, e nao o dono.
action ver_pedido_errado(usuario, id):
given usuario["papel"] isnt "cliente":
trigger "sem permissao"
yield buscar(id) // qualquer id, de qualquer um
// CERTO: a consulta carrega o dono. A autorizacao vira uma
// condicao do WHERE, e nao um 'given' que alguem pode esquecer.
action ver_pedido(usuario, id):
pedido := buscar_do_dono(id, usuario["id"])
given pedido is void:
// 404, e nao 403: dizer "existe, mas nao e seu" ja entrega
// que aquele id existe.
trigger "nao encontrado"
yield pedido
action buscar(id):
yield {"id": id, "dono": 99}
action buscar_do_dono(id, dono):
p := buscar(id)
yield p given p["dono"] is dono otherwise void
monitor:
ver_pedido({"id": 7, "papel": "cliente"}, 1234)
assert no
handle Error as e:
out "o pedido de outro dono nao e alcancavel"RBAC#
Papéis são a forma mais usada, e funcionam bem quando as permissões são do sistema. Quando dependem do dado (este pedido, este tenant), papel sozinho não basta — a checagem tem de descer ao recurso.
steady PERMISSOES := {
"leitor": ["pedido:ler"],
"editor": ["pedido:ler", "pedido:escrever"],
"admin": ["pedido:ler", "pedido:escrever", "pedido:apagar",
"usuario:gerir"]
}
action pode(papel, permissao):
yield permissao in (PERMISSOES[papel] ?? [])
// O padrao e NEGAR: um papel desconhecido nao ganha nada. A lista
// vazia do '??' e o que garante isso — sem ela, indexar um vault
// sem a chave levantaria, e um 'monitor' mal colocado viraria um
// 'permitido'.
assert pode("admin", "usuario:gerir")
assert pode("leitor", "pedido:apagar") is no
assert pode("papel-que-nao-existe", "pedido:ler") is no
// E a separacao de funcoes: quem aprova nao e quem solicita.
action aprovar(solicitante, aprovador, valor):
given solicitante is aprovador:
trigger "quem solicita nao aprova"
given pode(aprovador["papel"], "pedido:escrever") is no:
trigger "sem permissao"
yield {"aprovado": yes, "valor": valor}
monitor:
ana := {"id": 1, "papel": "editor"}
aprovar(ana, ana, 10000)
assert no
handle Error as e:
out $"recusado: {e.message}"Capacidades: a outra escola#
No modelo de capacidade, poder não é consultado numa tabela — é passado. Quem tem a referência ao recurso pode usá-lo, e quem não tem não consegue nem nomeá-lo. Isso elimina por construção a confusão do deputado confuso, em que um componente privilegiado é enganado a agir em nome de outro.
adopt Arcane.Capacidade as Cap
// 'limites()' devolve a lista em EXECUCAO. Um modulo que
// prometesse contencao sem dizer o que alcanca seria usado onde
// nao pode, e a descoberta viria por incidente.
out $"capacidades deste arquivo: {len(Cap.limites())}"
// A fronteira e o 'adopt': um modulo fora da lista e recusado pelo
// NOME da capacidade que falta — e nao com "erro de import".Sandbox — o veredito#
| Nível | Existe aqui? | O que ele realmente contém |
|---|---|---|
Arcane.Capacidade | sim | o adopt; não contém código já autorizado |
| Restrição por processo | parcial — P.map_processos | isola memória; não isola disco nem rede |
| Contêiner | gerado, não executado | dataforge devops escreve o Dockerfile com USER forge |
| VM / microVM | não existe | é infraestrutura |
| Executar código não confiável | não faça | não há mecanismo que torne isso seguro aqui |
Segurança de dependências#
A cadeia de suprimentos é hoje um dos vetores mais explorados: o código que você não escreveu roda com a mesma autoridade do que você escreveu.
| Controle | Como aqui |
|---|---|
| zero dependência no runtime | dataforge/ usa só a stdlib do Python — a superfície de terceiros é zero |
| lockfile lido | forge.lock fixa a versão e o sha256, e install o honra |
| integridade conferida | o sha256 do lock é comparado com o que chegou — é o ataque do tarball trocado |
| conflito de versão é erro | duas cópias em versões diferentes geram bug irreproduzível |
extração recusa ../ e link simbólico | um pacote não escreve fora da própria pasta |
| tarball reprodutível | mtime=0, uid/gid zerados — sem isso o sha256 mudaria a cada empacotamento e a verificação não significaria nada |
| SBOM | dataforge devops sbom |