Mon, 15 Jun 2020 19:01:02 +0200

Detlev Offenbach <>
Mon, 15 Jun 2020 19:01:02 +0200
changeset 7619
parent 7616
child 7622

Code Style Checker: continued to implement checker for security related issues.

# -*- coding: utf-8 -*-

# Copyright (c) 2020 Detlev Offenbach <>

Module implementing the security checker.

import sys
import ast
import collections

from . import Checks
from .SecurityNodeVisitor import SecurityNodeVisitor

class SecurityChecker(object):
    Class implementing a checker for security issues.
    Codes = [
        # assert used
        # exec used
        # bad file permissions
        # bind to all interfaces
        # hardcoded passwords
        "S105", "S106", "S107"
        # hardcoded tmp directory
        # try-except
        "S110", "S112",
        # flask app
        # insecure function calls (blacklisted)
        "S301", "S302", "S303", "S304", "S305", "S306", "S307", "S308", "S309",
        "S310", "S311", "S312", "S313", "S314", "S315", "S316", "S317", "S318",
        "S319", "S320", "S321", "S322", "S323", "S324",
        # insecure imports (blacklisted)
        "S401", "S402", "S403", "S404", "S405", "S406", "S407", "S408", "S409",
        "S410", "S411", "S412", "S413",
        # insecure certificate usage
        # insecure SSL/TLS protocol version
        "S502", "S503", "S504",
        # weak cryptographic keys
        # YAML load
        # SSH host key verification
        # Shell injection
        "S601", "S602", "S603", "S604", "S605", "S606", "S607",
        # SQL injection
        # Wildcard injection
        # Django SQL injection
        "S610", "S611",
        # Jinja2 templates
        # Mako templates
        # Django XSS vulnerability
        # Syntax error
    def __init__(self, source, filename, select, ignore, expected, repeat,
        @param source source code to be checked
        @type list of str
        @param filename name of the source file
        @type str
        @param select list of selected codes
        @type list of str
        @param ignore list of codes to be ignored
        @type list of str
        @param expected list of expected codes
        @type list of str
        @param repeat flag indicating to report each occurrence of a code
        @type bool
        @param args dictionary of arguments for the security checks
        @type dict
        self.__select = tuple(select)
        self.__ignore = ('',) if select else tuple(ignore)
        self.__expected = expected[:]
        self.__repeat = repeat
        self.__filename = filename
        self.__source = source[:]
        self.__args = args
        # statistics counters
        self.counters = {}
        # collection of detected errors
        self.errors = []
        checkersWithCodes = Checks.generateCheckersDict()
        self.__checkers = collections.defaultdict(list)
        for checkType, checkersList in checkersWithCodes.items():
            for checker, codes in checkersList:
                if any(not (code and self.__ignoreCode(code))
                       for code in codes):
    def __ignoreCode(self, code):
        Private method to check if the message code should be ignored.

        @param code message code to check for
        @type str
        @return flag indicating to ignore the given code
        @rtype bool
        return (code.startswith(self.__ignore) and
                not code.startswith(self.__select))
    def reportError(self, lineNumber, offset, code, severity, confidence,
        Public method to record an issue.
        @param lineNumber line number of the issue
        @type int
        @param offset position within line of the issue
        @type int
        @param code message code
        @type str
        @param severity severity code (H = high, M = medium, L = low,
            U = undefined)
        @type str
        @param confidence confidence code (H = high, M = medium, L = low,
            U = undefined)
        @type str
        @param args arguments for the message
        @type list
        if self.__ignoreCode(code):
        if code in self.counters:
            self.counters[code] += 1
            self.counters[code] = 1
        # Don't care about expected codes
        if code in self.__expected:
        if code and (self.counters[code] == 1 or self.__repeat):
            # record the issue with one based line number
                "file": self.__filename,
                "line": lineNumber + 1,
                "offset": offset,
                "code": code,
                "args": args,
                "severity": severity,
                "confidence": confidence,
    def __reportInvalidSyntax(self):
        Private method to report a syntax error.
        exc_type, exc = sys.exc_info()[:2]
        if len(exc.args) > 1:
            offset = exc.args[1]
            if len(offset) > 2:
                offset = offset[1:3]
            offset = (1, 0)
        self.reportError(offset[0] - 1,
                         offset[1] or 0,
                         exc_type.__name__, exc.args[0])
    def __generateTree(self):
        Private method to generate an AST for our source.
        @return generated AST
        @rtype ast.AST
        source = "".join(self.__source)
        # Check type for py2: if not str it's unicode
        if sys.version_info[0] == 2:
                source = source.encode('utf-8')
            except UnicodeError:
        return compile(source, self.__filename, 'exec', ast.PyCF_ONLY_AST)
    def getConfig(self):
        Public method to get the configuration dictionary.
        @return dictionary containing the configuration
        @rtype dict
        return self.__args
    def run(self):
        Public method to check the given source against security related
        if not self.__filename:
            # don't do anything, if essential data is missing
        if not self.__checkers:
            # don't do anything, if no codes were selected
            self.__tree = self.__generateTree()
        except (SyntaxError, TypeError):
        securityNodeVisitor = SecurityNodeVisitor(
            self, self.__checkers, self.__filename)

eric ide