Skip to main content

Os perigos do assert em Python

Escrito por
Headshot of Dhruv Patel

Dhruv Patel

hero assert in python

18 de agosto de 2022

0 minutos de leitura

Há muitas maneiras de encontrar bugs no código Python: o depurador integrado (pdb), uma boa quantidade de testes unitários, um depurador em uma IDE como Pycharm ou Visual Studio, instruções try/catch, instruções if/else, assert statements ou a velha e conhecida prática de cobrir cada canto da sua base de código com instruções print(), como se isso nunca fosse sair de moda.

As instruções assert podem nos ajudar a encontrar bugs rapidamente e são muito menos intrusivas do que uma quantidade enorme de instruções print. No entanto, ao contrário das instruções print, as instruções assert podem ser inesperadamente arriscadas!

Este artigo explica como usar asserts com segurança e o que pode torná-los inseguros. Ao final, você saberá como usar assert da melhor forma sem se expor inadvertidamente a problemas de segurança.

Como usar instruções assert em Python

Usar assert é fácil! As instruções assert são instruções simples, o que significa que cabem em uma única linha de código Python.

Veja um exemplo simples de uma instrução assert:

assert expression

Na instrução acima, você está afirmando que a expressão é avaliada como True, de forma semelhante a uma verificação booleana.

Se a expressão for False, uma exceção será lançada. Por exemplo:

>>> assert 'hi' == 'there'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError

Para facilitar a análise de um rastreamento de pilha, você pode especificar uma mensagem de asserção, que será incluída no AssertionError. Isso pode ser útil em cenários complexos de depuração.

>>> assert 'hi' == 'there', 'Values do not match!'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError: Values do not match!

A variável integrada __debug__ do Python precisa estar definida como True para que as instruções assert sejam usadas. Se estiver definida como False, as instruções assert não serão executadas durante a execução do programa. Isso é uma preocupação séria, pois executar Python no modo otimizado (algo comum em ambientes de produção, definido pela flag de linha de comando PYTHONOPTIMIZE, -O) define a variável __debug__ como False, desativando as instruções assert, o que pode introduzir vulnerabilidades inadvertidamente no seu projeto. Vamos explorar isso em mais detalhes abaixo.

Quando usado corretamente, assert é uma ferramenta poderosa para depuração. No entanto, quanto mais complexo for o uso, maior será o risco de introduzir vulnerabilidades de segurança durante o desenvolvimento.

Resumo — As instruções assert são muito úteis para depurar aplicações Python, mas não devem ser usadas para o tratamento normal de erros nem para controlar o fluxo do programa, pois podem:

  • Interromper a aplicação ao lançar uma exceção, o que pode causar indisponibilidade em produção.

  • Causar efeitos colaterais inesperados (inclusive vulnerabilidades) na aplicação quando o modo de depuração está desativado e o runtime do Python ignora as instruções assert sem avisar.

Como usar assert com segurança

Vamos explorar como usar instruções de asserção para depurar um programa, analisando uma aplicação de exemplo que calcula juros simples.

Primeiro, instale uma versão estável do Python. Em seguida, abra uma pasta e crie um arquivo Python chamado safe_assert_example.py. Você pode usar qualquer editor de código compatível.

Adicione o código a seguir ao arquivo safe_assert_example.py:

def simpleInterest(p, t, r):
print('The principal is', p)
assert isinstance(p, int) and p > 0, "Principal must be a positive integer"
print('The time period is', t)
assert isinstance(t, int) and t > 0, "Time must be a positive integer"
print('The rate of interest is', r)
assert isinstance(r, int) and r > 0, "Rate of interest must be a positive integer"
simpleInterest = (p * t * r) / 100
print('The Simple Interest is', simpleInterest)
return simpleInterest

simpleInterest(3, 5, 8)

Este programa define a função simpleInterest e calcula juros simples usando o capital, o tempo e a taxa.

Para calcular os juros corretamente, você precisa informar números inteiros positivos para o capital, o tempo e a taxa. Este exemplo usa instruções assert para verificar se os valores p, t e r são números inteiros positivos. Se um valor não for um número inteiro positivo, o programa retornará uma mensagem de erro informativa.

Hora de testar! Execute o programa com o comando a seguir:

python safe_assert_example.py

Você deverá ver a saída abaixo, com juros simples de 1.2:

PS E:\Sample\Extra\Assert Demo> python safe_assert_example.py
The principal is 3
The time period is 5
The rate of interest is 8
The Simple Interest is 1.2

Como você pode ver, o programa é executado sem problemas e não retorna uma mensagem de erro.

Agora, veja o que acontece quando você tenta executar o programa com uma entrada inválida. Altere o valor de t de 5 para a string "time":

simpleInterest(3, "time", 8)

Agora, execute o programa novamente com este comando:

python safe_assert_example.py

Desta vez, você deverá ver o seguinte erro:

PS E: \Sample\Extra\Assert Demo» python safe assert example.py
The principal is 3
The time period is time
Traceback (most recent call last):
    File "E: \Sample\Extra\Assert Demo\safe assert example.py", line 12, in <module> simpleInterest(3, "time", 8)
    File "E: \Sample\Extra\Assert Demo\safe assert example.py", line 5, in simpleInterest assert isinstance(t, int) and t > 0, "Time must be a positive integer"
AssertionError: Time must be a positive integer

Essa saída inclui um AssertionError. Essa exceção mostra que a instrução assert funciona como esperado, pois detectou o argumento inválido da função.

Neste exemplo, a asserção nos ajudou a depurar o programa e interrompeu a execução assim que falhou.

Lembre-se de que executar o programa no modo otimizado (definindo a flag de linha de comando PYTHONOPTIMIZE, -O) desativa as instruções assert sem avisar. Agora, veja como esse comportamento afeta a aplicação.

Mantendo "`time=" como valor de t, execute o programa de juros simples no modo otimizado com o comando a seguir:

python -O safe_assert_example.py

O resultado será:

PS E:\Sample\Extra\Assert Demo› python -0 safe assert example.py
The principal is 3
The time period is time
The rate of interest is 8
Traceback (most recent call last):
    File "E:\Sample\Extra\Assert Demo\safe assert example.py", line 12, in <module> simpleInterest(3, "time", 8)
    File "E:\Sample\Extra\Assert Demo\safe assert example.py", line 8, in simpleInterest simpleInterest = (p * t * r)/100
TypeError: unsupported operand type(s) for /: 'str' and 'int'

Observe que, embora o programa tenha um argumento de função inválido, a saída acima não inclui o AssertionError esperado. Em vez disso, o Python exibe um TypeError, pois você tentou multiplicar variáveis do tipo inteiro e string. Isso acontece porque as instruções assert não foram executadas e indica que devemos considerar alternativas, como:

  • Adicionar dicas de tipo para ajudar a detectar chamadas incorretas a simpleInterest antes da execução do código.

  • Usar instruções if em vez de asserções para verificar tipos durante a execução.

Como usar assert de forma insegura

Assert deve ser usado apenas para testes e depuração — não em ambientes de produção. Como as instruções assert só são executadas quando a variável __debug__ está definida como True, o interpretador Python pode desativá-las.

Como demonstrado no exemplo acima, executar Python no modo otimizado usando -O impede que as instruções assert funcionem, pois isso define __debug__ como False.

Por isso, não é seguro usar instruções assert para nada que precise ser executado em produção. Por exemplo, nunca use asserts para validar tokens ou entradas de usuários. Essas instruções não serão executadas em produção, então tokens ou entradas de usuários não serão validados, o que pode introduzir vulnerabilidades na sua aplicação.

Para destacar a gravidade de usar assert para validação em um ambiente de produção, considere uma função de autorização de usuários que recebe uma lista de usuários e permite que eles avancem com base no nível de acesso. Primeiro, o programa usa assert para verificar se a lista não é nula e se é uma instância de lista. Em seguida, de acordo com a função, o administrador recebe acesso completo à aplicação. Se houver uma função de administrador na lista de usuários, nenhum erro de asserção será exibido. Mas, se não houver uma função de administrador na lista, será lançado um erro.

Em seguida, cole este código em um arquivo chamado unsafe_assert_example.py:

def authorize_admin_user(user_roles):
    assert isinstance(user_roles,list) and user_roles != [], "No user roles found"

    assert 'admin' in user_roles, "No admin role found."
    print("You have full access to the application.")

authorize_admin_user(['admin','user'])

A função authorize_admin_user verifica as funções do usuário. Se ele for administrador, a função indicará que temos acesso completo à aplicação. Em geral, as funções do usuário são carregadas do banco de dados; se uma delas for de administrador, o usuário recebe acesso completo ao app. Caso contrário, a asserção falhará e lançará uma exceção.

A última linha do arquivo chama a função authorize_admin_user, passando uma lista que contém duas funções: admin e user.

Run python unsafe_assert_example.py:

$ python unsafe_assert_example.py
You have full access to the application

Como esperado, o usuário recebe acesso completo à aplicação.

Vamos chamar a função de usuário sem incluir a função de administrador na lista. Altere a última linha do código em unsafe_assert_example.py para:

authorize_admin_user(['user'])

Em seguida, execute o programa e observe a saída:

$ python unsafe_assert_example.py
Traceback (most recent call last):
  File "C:\Projects\ContentLabEditing\python\unsafe_assert_example.py", line 6, in <module>
    authorize_admin_user(['user'])
  File "C:\Projects\ContentLabEditing\python\unsafe_assert_example.py", line 3, in authorize_admin_user
    assert 'admin' in user_roles, "No admin role found."
AssertionError: No admin role found.

Como esperado, a função lança um AssertionError porque admin não estava na lista de funções.

Agora, execute o mesmo programa com a saída otimizada:

python -O unsafe_assert_example.py

A saída será igual à imagem abaixo. Embora não houvesse uma função admin na lista, a autorização de administrador foi bem-sucedida porque a instrução assert não foi executada.

PS E:\Sample\Extra\Assert Demo> python -0 unsafe_assert_example.py
You have full access to the application
You have full access to the application
PS E:\Sample\Extra\Assert Demo>

Além disso, alterar a chamada da função para usar uma lista de números inteiros nessa mesma execução com saída otimizada ainda demonstraria que assert deve ser usado apenas para depuração, e não para validação.

Altere a chamada da função para authorize_admin_user([1,2]) e execute-a com o mesmo comando: python -O unsafe_assert_example.py.

A saída do programa continua a mesma.

No exemplo da calculadora de juros simples, as consequências foram pequenas quando as instruções assert não foram executadas. Embora não tenhamos recebido a mensagem de erro desejada, pudemos usar a mensagem de erro do Python para entender o que aconteceu. Mais importante ainda, não introduzimos vulnerabilidades, pois usamos as instruções assert para uma depuração segura.

Neste exemplo, usamos assert de forma insegura e deixamos nossa aplicação Python vulnerável ao conceder acesso de administrador a um usuário que não era administrador.

Se quiser testar esses exemplos mais a fundo, você pode encontrar todo o código deste artigo neste repositório.

O que fazer e o que evitar ao usar assert

Assert ajuda desenvolvedores a depurar código Python com facilidade e a escrever menos linhas de código. No entanto, se não for usado com segurança, pode introduzir problemas de segurança nas suas aplicações.

Desativar asserts em um ambiente de produção pode ter consequências devastadoras. Essa prática pode introduzir várias portas dos fundos e pontos de interrupção na aplicação, que agentes mal-intencionados podem explorar.

A principal função de um assert deve ser isolar a causa raiz de um bug; ele nunca deve ser usado para o tratamento padrão de erros. Embora asserts possam ser úteis, é essencial usá-los para depuração e testes, e não para controlar o fluxo lógico do programa ou da aplicação. Use-os apenas em situações seguras. Se você procura uma alternativa ao assert, testes unitários, depuradores, condicionais e validadores são opções melhores e mais seguras.

Agora que você sabe como usar assert com segurança, leia o guia rápido de boas práticas em Python para saber mais sobre como manter suas aplicações seguras em todas as plataformas e ambientes.

Mais recursos de desenvolvimento e segurança em Python

Você também pode experimentar nossa ferramenta gratuita online para verificar código Python e ver como o mecanismo do Snyk Code analisa seu código em busca de problemas de segurança e qualidade.

Comece a jogar Capture the Flag

Aprenda a resolver desafios de Capture the Flag assistindo à gravação sob demanda do nosso workshop virtual introdutório.