Pular para o conteúdo

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.

dataforge
// 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.

dataforge
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.

dataforge
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ívelExiste aqui?O que ele realmente contém
Arcane.Capacidadesimo adopt; não contém código já autorizado
Restrição por processoparcialP.map_processosisola memória; não isola disco nem rede
Contêinergerado, não executadodataforge devops escreve o Dockerfile com USER forge
VM / microVMnão existeé infraestrutura
Executar código não confiávelnão façanã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.

ControleComo aqui
zero dependência no runtimedataforge/ usa só a stdlib do Python — a superfície de terceiros é zero
lockfile lidoforge.lock fixa a versão e o sha256, e install o honra
integridade conferidao sha256 do lock é comparado com o que chegou — é o ataque do tarball trocado
conflito de versão é erroduas cópias em versões diferentes geram bug irreproduzível
extração recusa ../ e link simbólicoum pacote não escreve fora da própria pasta
tarball reprodutívelmtime=0, uid/gid zerados — sem isso o sha256 mudaria a cada empacotamento e a verificação não significaria nada
SBOMdataforge devops sbom