def to_violation(self, result: Dict[str, Any]) -> Violation: path = self.trim_base(result["path"]) abspath = self.base_path / path check_id = str(result["code"]) line = result["line"] return Violation( tool_id=PyreTool.TOOL_ID, check_id=check_id, path=path, line=line, column=result["column"], message=result["description"], severity=2, syntactic_context=fetch_line_in_file(abspath, line) or "<no source found>", link="https://pyre-check.org/docs/error-types.html", )
def parse(self, tool_output: str) -> List[Violation]: results = json.loads(tool_output) violations = [ Violation( check_id=r["code"].replace(self.CHECK_PREFIX, ""), tool_id=JinjalintTool.TOOL_ID, path=self.trim_base(r["file_path"]), severity=JinjalintParser.SEVERITY["MEDIUM"], line=r["line"], column=r["column"], message=r["message"], syntactic_context=r.get("physical_line", ""), link=self._get_link(r["code"]), ) for r in results ] return violations
def to_violation(self, result: Dict[str, Any]) -> Violation: source = (result["physical_line"] or "").rstrip() # Remove trailing whitespace path = self.trim_base(result["filename"]) check_id = result["code"] return Violation( tool_id=self.tool().tool_id(), check_id=self.id_to_name(check_id), path=path, line=result["line_number"], column=result["column_number"], message=result["text"], severity=2, syntactic_context=source, link=self.id_to_link(check_id), )
def to_violation(self, output_rule: Dict[str, Any]) -> Violation: output = output_rule["output"] check_id = output_rule["id"] message = output_rule.get("message") parts = output.split(":") path = parts[0] path = self.trim_base(path) line_no = int(parts[1]) code_snippet = ":".join(parts[2:]) return Violation( tool_id=GrepTool.TOOL_ID, check_id=check_id, path=path, line=line_no, column=1, message=message or code_snippet, severity=2, syntactic_context=code_snippet or "<no context>", )
def test_run(tmp_path: Path) -> None: tool = DlintTool(context_for(tmp_path, DlintTool.TOOL_ID, SIMPLE_INTEGRATION_PATH)) tool.setup() violations = tool.results(SIMPLE_TARGETS) expectation = [ Violation( check_id="regular-expression-catastrophic-backtracking", tool_id=DlintTool.TOOL_ID, path="baz.py", line=4, column=0, message='catastrophic "re" usage - denial-of-service possible', severity=2, syntactic_context="re.search(r'(a+)+b', 'TEST')", link="https://github.com/dlint-py/dlint/blob/master/docs/linters/DUO138.md", ) ] assert violations == expectation
def parse(self, tool_output: str) -> List[Violation]: results = json.loads(tool_output) violations = [ Violation( check_id=DLINT_TO_BENTO[result["code"]], tool_id=DlintTool.TOOL_ID, path=self.trim_base(filename), severity=DlintParser.SEVERITY["MEDIUM"], line=result["line_number"], column=result["column_number"], message=result["text"], syntactic_context=(result["physical_line"] or "").rstrip(), link=self._get_link(result["code"]), ) for filename, file_results in results.items() for result in file_results ] return violations
def to_violation(self, result: Dict[str, Any]) -> Violation: start_line = result["line"] column = result["column"] check_id = result["code"] message = result["message"] path = result["file"] path = self.trim_base(path) level = result["level"] if level == "error": severity = 2 elif level == "warning": severity = 1 elif level == "info": severity = 0 elif level == "style": severity = 0 if "DL" in check_id or check_id in ["SC2046", "SC2086"]: link = f"https://github.com/hadolint/hadolint/wiki/{check_id}" elif "SC" in check_id: link = f"https://github.com/koalaman/shellcheck/wiki/{check_id}" else: link = "" line_of_code = (fetch_line_in_file(self.base_path / path, start_line) or "<no source found>") if check_id == "DL1000": message = "Dockerfile parse error. Invalid docker instruction." return Violation( tool_id=HadolintTool.TOOL_ID, check_id=check_id, path=path, line=start_line, column=column, message=message, severity=severity, syntactic_context=line_of_code, link=link, )
def test_run(tmp_path: Path) -> None: base_path = BASE_PATH / "tests" / "integration" / "go" tool = GosecTool(context_for(tmp_path, GosecTool.tool_id(), base_path)) tool.setup() violations = tool.results([base_path / "bad.go"]) assert violations == [ Violation( tool_id="gosec", check_id="G101", path="bad.go", line=7, column=2, message="Potential hardcoded credentials", severity=2, syntactic_context= 'password := "******"\n', filtered=None, link="https://cwe.mitre.org/data/definitions/798.html", ) ]
def test_parse() -> None: with (THIS_PATH / "flake8_violation_simple.json").open() as json_file: json = json_file.read() result = Flake8Parser(BASE_PATH).parse(json) expectation = [ Violation( tool_id="flake8", check_id="E124", path="foo.py", line=2, column=0, message="closing bracket does not match visual indentation", severity=2, syntactic_context=" )", ) ] assert result == expectation
def parse(self, results: JsonR) -> List[Violation]: violations: List[Violation] = [] for check in results: path = self.trim_base(check["path"]) start_line = check["start"]["line"] start_col = check["start"]["col"] check_id = check["check_id"] violation = Violation( tool_id=CheckedReturnTool.tool_id(), check_id=check_id, path=path, line=start_line, column=start_col, message=check.get("extra", {}).get("message"), severity=2, syntactic_context=check.get("extra", {}).get("line"), ) violations.append(violation) return violations
def test_run(tmp_path_factory: tmp_path_factory) -> None: base_path = BASE_PATH / "tests/integration/python_taint" tool = PythonTaintTool( context_for(tmp_path_factory, PythonTaintTool.tool_id(), base_path)) tool.setup() violations = tool.results() expectation = [ Violation( tool_id="PythonTaint", check_id="5001: Possible shell injection", path="source.py", line=13, column=22, message= "Possible shell injection [5001]: Data from [UserControlled] source(s) may reach [RemoteCodeExecution] sink(s)", severity=2, syntactic_context=" image = get_image(image_link)\n", ) ] assert violations == expectation
def test_run_flask_violations(tmp_path: Path) -> None: tool = Boto3Tool(context_for(tmp_path, Boto3Tool.TOOL_ID, BOTO3_INTEGRATION_PATH)) tool.setup() violations = tool.results(BOTO3_TARGETS) expectation = [ Violation( tool_id="r2c.boto3", check_id="hardcoded-access-token", path="bad.py", line=4, column=11, message="Hardcoded access token detected. Consider using a config file or environment variables.", severity=2, syntactic_context="session = Session(aws_access_key_id='AKIA1235678901234567',", filtered=None, link="https://checks.bento.dev/en/latest/flake8-boto3/hardcoded-access-token", ) ] assert violations == expectation
def test_run(tmp_path: Path) -> None: base_path = BASE_PATH / "tests/integration/checked_return" tool = CheckedReturnTool( context_for(tmp_path, CheckedReturnTool.tool_id(), base_path) ) tool.setup() violations = tool.results([base_path / "checkedreturn.js"]) expectation = [ Violation( tool_id="r2c.checked_return", check_id="checked_return", path="checkedreturn.js", line=25, column=3, message="./checkedreturn.js:25:2: error unchecked return for must_be_used (used = 11, ignored = 1)", severity=2, syntactic_context=" must_be_used(); //maybe a bug, but not counted for now, maybe used for its throwing effect", ) ] assert violations == expectation
def test_missing_source() -> None: with (THIS_PATH / "eslint_violation_missing_source.json").open() as json_file: json_data = json_file.read() result = EslintParser(BASE_PATH).parse(json.loads(json_data)) expectation = [ Violation( tool_id="r2c.eslint", check_id="no-console", path="tests/integration/simple/init.js", line=0, column=0, message="Unexpected console statement.", severity=1, syntactic_context="", ) ] assert result == expectation
def test_run_flask_violations(tmp_path: Path) -> None: base_path = BASE_PATH / "tests/integration/flask" tool = FlaskTool(context_for(tmp_path, FlaskTool.TOOL_ID, base_path)) tool.setup() violations = tool.results([base_path / "bad.py"]) expectation = [ Violation( tool_id="r2c.flask", check_id="send-file-open", path="bad.py", line=4, column=1, message="Passing a file-like object to flask.send_file without the mimetype or attachment_filename keyword arg will raise a ValueError. If you are sending a static file, pass in a string path to the file instead. Otherwise, specify a mimetype or attachment_filename in flask.send_file.", severity=2, syntactic_context='flask.send_file(open("file.txt"))', filtered=None, link="https://bento.dev/checks/en/latest/flake8-flask/send-file-open", ) ] assert violations == expectation
def test_parse() -> None: with (THIS_PATH / "boto3_violation_simple.json").open() as json_file: json = json_file.read() result = Boto3Parser(BASE_PATH).parse(json) expectation = [ Violation( tool_id="r2c.boto3", check_id="hardcoded-access-token", path="bad.py", line=4, column=1, message="Hardcoded access token detected. Consider using a config file or environment variables.", severity=2, syntactic_context="Session(aws_access_key_id='AKIA1235678901234567',", filtered=None, link="https://checks.bento.dev/en/latest/flake8-boto3/hardcoded-access-token", ) ] assert result == expectation
def test_parse() -> None: with (THIS_PATH / "flask_violation_simple.json").open() as json_file: json = json_file.read() result = FlaskParser(BASE_PATH).parse(json) expectation = [ Violation( tool_id="r2c.flask", check_id="send-file-open", path="bad.py", line=4, column=1, message="Passing a file-like object to flask.send_file without the mimetype or attachment_filename keyword arg will raise a ValueError. If you are sending a static file, pass in a string path to the file instead. Otherwise, specify a mimetype or attachment_filename in flask.send_file.", severity=2, syntactic_context='flask.send_file(open("file.txt"))', filtered=None, link="https://bento.dev/checks/en/latest/flake8-flask/send-file-open", ) ] assert result == expectation
def test_run(tmp_path: Path) -> None: tool = PythonTaintTool( context_for(tmp_path, PythonTaintTool.tool_id(), TAINT_INTEGRATION_PATH)) tool.setup() violations = tool.results(TAINT_TARGETS) expectation = [ Violation( tool_id="PythonTaint", check_id="5001: Possible shell injection", path="source.py", line=13, column=22, message= "Possible shell injection [5001]: Data from [UserControlled] source(s) may reach [RemoteCodeExecution] sink(s)", severity=2, syntactic_context=" image = get_image(image_link)\n", ) ] assert violations == expectation
def parse(self, results: JsonR) -> List[Violation]: violations: List[Violation] = [] for check in results: path = self.trim_base(check["path"]) start_line = check["start"]["line"] start_col = check["start"]["col"] # Custom way to get check_name for sgrep-lint:0.1.10 message = check.get("extra", {}).get("message") check_name, message = message.split(":") violation = Violation( tool_id=SGrepTool.tool_id(), check_id=check_name, path=path, line=start_line, column=start_col, message=message, severity=2, syntactic_context=check.get("extra", {}).get("line"), ) violations.append(violation) return violations
def parse(self, results: JsonR) -> List[Violation]: violations: List[Violation] = [] for check in results: path = self.trim_base(check["path"]) start_line = check["start"]["line"] start_col = check["start"]["col"] check_id = check["check_id"] line_of_code = (fetch_line_in_file( self.base_path / path, start_line) or "<no source found>") violation = Violation( tool_id=PythonTaintTool.tool_id(), check_id=check_id, path=path, line=start_line, column=start_col, message=check.get("extra", {}).get("description"), severity=2, syntactic_context=line_of_code, ) violations.append(violation) return violations
def parse(self, results: JsonR) -> List[Violation]: violations: List[Violation] = [] for check in results: check_id = check["check_id"] path = self.trim_base(check["path"]) start_line = check["start"]["line"] start_col = check["start"]["col"] # Custom way to get check_name for sgrep-lint:0.1.10 message = check.get("extra", {}).get("message") source = (fetch_line_in_file(self.base_path / path, start_line) or "<no source found>").rstrip() violation = Violation( tool_id=self.tool_id(), check_id=check_id, path=path, line=start_line, column=start_col, message=message, severity=2, syntactic_context=source, ) violations.append(violation) return violations
def test_run(tmp_path: Path) -> None: tool = SgrepTool(context_for(tmp_path, SgrepTool.tool_id(), SGREP_PATH)) shutil.copy(SGREP_PATH / ".bento" / "sgrep.yml", tool.context.resource_path / "sgrep.yml") with _remote_docker(): tool.setup() violations = set(tool.results([SGREP_PATH / "flask_configs.py"])) print(violations) expectation = { Violation( tool_id="sgrep", check_id="bento.avoid_hardcoded_config_DEBUG", path="flask_configs.py", line=33, column=1, message= "Hardcoded variable `DEBUG` detected. Set this by using FLASK_DEBUG environment variable", severity=2, syntactic_context='app.config["DEBUG"] = False', filtered=None, link=None, ), Violation( tool_id="sgrep", check_id="bento.avoid_hardcoded_config_DEBUG", path="flask_configs.py", line=31, column=1, message= "Hardcoded variable `DEBUG` detected. Set this by using FLASK_DEBUG environment variable", severity=2, syntactic_context='app.config["DEBUG"] = True', filtered=None, link=None, ), Violation( tool_id="sgrep", check_id="bento.avoid_hardcoded_config_ENV", path="flask_configs.py", line=27, column=1, message= "Hardcoded variable `ENV` detected. Set this by using FLASK_ENV environment variable", severity=2, syntactic_context='app.config["ENV"] = "production"', filtered=None, link=None, ), Violation( tool_id="sgrep", check_id="bento.avoid_hardcoded_config_ENV", path="flask_configs.py", line=25, column=1, message= "Hardcoded variable `ENV` detected. Set this by using FLASK_ENV environment variable", severity=2, syntactic_context='app.config["ENV"] = "development"', filtered=None, link=None, ), Violation( tool_id="sgrep", check_id="bento.avoid_hardcoded_config_SECRET_KEY", path="flask_configs.py", line=19, column=1, message= "Hardcoded variable `SECRET_KEY` detected. Use environment variables or config files instead", severity=2, syntactic_context='app.config.update(SECRET_KEY="aaaa")', filtered=None, link=None, ), Violation( tool_id="sgrep", check_id="bento.avoid_hardcoded_config_TESTING", path="flask_configs.py", line=15, column=1, message= "Hardcoded variable `TESTING` detected. Use environment variables or config files instead", severity=2, syntactic_context="app.config.update(TESTING=True)", filtered=None, link=None, ), Violation( tool_id="sgrep", check_id="bento.avoid_hardcoded_config_TESTING", path="flask_configs.py", line=13, column=1, message= "Hardcoded variable `TESTING` detected. Use environment variables or config files instead", severity=2, syntactic_context='app.config["TESTING"] = False', filtered=None, link=None, ), Violation( tool_id="sgrep", check_id="bento.avoid_hardcoded_config_TESTING", path="flask_configs.py", line=11, column=1, message= "Hardcoded variable `TESTING` detected. Use environment variables or config files instead", severity=2, syntactic_context='app.config["TESTING"] = True', filtered=None, link=None, ), } assert violations == expectation
import textwrap import bento.result as result from bento.violation import Violation VIOLATIONS = [ Violation( tool_id="r2c.eslint", check_id="no-console", path="bento/test/integration/init.js", line=0, column=0, severity=1, message="Unexpected console statement.", syntactic_context="console.log(3)", ), Violation( tool_id="r2c.eslint", check_id="semi", path="bento/test/integration/init.js", line=0, column=0, message="Missing semicolon.", severity=2, syntactic_context="console.log(3)", ), ] YML_TEXT = textwrap.dedent(""" r2c_eslint: violations:
def test_run(tmp_path: Path) -> None: tool = ShellcheckTool( context_for(tmp_path, ShellcheckTool.tool_id(), SHELL_INTEGRATION_PATH) ) tool.setup() violations = set(tool.results(SHELL_TARGET)) assert violations == { Violation( tool_id="shellcheck", check_id="SC2068", path="foo.sh", line=3, column=6, message="Double quote array expansions to avoid re-splitting elements.", severity=2, syntactic_context="echo $@\n", filtered=None, link="https://github.com/koalaman/shellcheck/wiki/SC2068", ), Violation( tool_id="shellcheck", check_id="SC2068", path="foo", line=3, column=6, message="Double quote array expansions to avoid re-splitting elements.", severity=2, syntactic_context="echo $@\n", filtered=None, link="https://github.com/koalaman/shellcheck/wiki/SC2068", ), Violation( tool_id="shellcheck", check_id="SC2068", path="bar", line=3, column=6, message="Double quote array expansions to avoid re-splitting elements.", severity=2, syntactic_context="echo $@\n", filtered=None, link="https://github.com/koalaman/shellcheck/wiki/SC2068", ), Violation( tool_id="shellcheck", check_id="SC2068", path="baz", line=3, column=6, message="Double quote array expansions to avoid re-splitting elements.", severity=2, syntactic_context="echo $@\n", filtered=None, link="https://github.com/koalaman/shellcheck/wiki/SC2068", ), Violation( tool_id="shellcheck", check_id="SC1083", path="test.sh", line=5, column=33, message="This { is literal. Check expression (missing ;/\\n?) or quote it.", severity=1, syntactic_context='status_code=$(curl --write-out %{http_code} --silent --output /dev/null -X POST -H "Content-Type:application/json" -d \'{\n', filtered=None, link="https://github.com/koalaman/shellcheck/wiki/SC1083", ), Violation( tool_id="shellcheck", check_id="SC1083", path="test.sh", line=5, column=43, message="This } is literal. Check expression (missing ;/\\n?) or quote it.", severity=1, syntactic_context='status_code=$(curl --write-out %{http_code} --silent --output /dev/null -X POST -H "Content-Type:application/json" -d \'{\n', filtered=None, link="https://github.com/koalaman/shellcheck/wiki/SC1083", ), }
SIMPLE_INTEGRATION_PATH / "foo.py", SIMPLE_INTEGRATION_PATH / "init.js", SIMPLE_INTEGRATION_PATH / "package-lock.json", SIMPLE_INTEGRATION_PATH / "package.json", ] CLICK_INTEGRATION_PATH = BASE_PATH / "tests/integration/click" CLICK_TARGETS = [CLICK_INTEGRATION_PATH / "bad_examples.py"] EXPECTATIONS = [ Violation( tool_id="r2c.click", check_id="option-function-argument-check", path="bad_examples.py", line=12, column=1, message= "function `bad_option_one` missing parameter `d` for `@click.option`", severity=2, syntactic_context="@click.command()", filtered=None, link="", ), Violation( tool_id="r2c.click", check_id="names-are-well-formed", path="bad_examples.py", line=19, column=1, message="option 'd' should begin with a '-'", severity=2, syntactic_context="@click.command()",
def from_cache_repr(text: str) -> List[Violation]: parsed = json.loads(text) return [Violation(**kwargs) for kwargs in parsed]
def test_run(tmp_path_factory: tmp_path_factory) -> None: base_path = BASE_PATH / "tests/integration/sgrep" tool = SGrepTool( context_for(tmp_path_factory, SGrepTool.tool_id(), base_path)) tool.setup() violations = tool.results() print(violations) expectation = [ Violation( tool_id="r2c.sgrep", check_id="avoid_hardcoded_config_DEBUG", path="flask_configs.py", line=33, column=1, message= " Hardcoded variable `DEBUG` detected. Set this by using FLASK_DEBUG environment variable", severity=2, syntactic_context='app.config["DEBUG"] = False', filtered=None, link=None, ), Violation( tool_id="r2c.sgrep", check_id="avoid_hardcoded_config_DEBUG", path="flask_configs.py", line=31, column=1, message= " Hardcoded variable `DEBUG` detected. Set this by using FLASK_DEBUG environment variable", severity=2, syntactic_context='app.config["DEBUG"] = True', filtered=None, link=None, ), Violation( tool_id="r2c.sgrep", check_id="avoid_hardcoded_config_ENV", path="flask_configs.py", line=27, column=1, message= " Hardcoded variable `ENV` detected. Set this by using FLASK_ENV environment variable", severity=2, syntactic_context='app.config["ENV"] = "production"', filtered=None, link=None, ), Violation( tool_id="r2c.sgrep", check_id="avoid_hardcoded_config_ENV", path="flask_configs.py", line=25, column=1, message= " Hardcoded variable `ENV` detected. Set this by using FLASK_ENV environment variable", severity=2, syntactic_context='app.config["ENV"] = "development"', filtered=None, link=None, ), Violation( tool_id="r2c.sgrep", check_id="avoid_hardcoded_config_SECRET_KEY", path="flask_configs.py", line=21, column=1, message= " Hardcoded variable `SECRET_KEY` detected. Use environment variables or config files instead", severity=2, syntactic_context= 'app.config["SECRET_KEY"] = b\'_5#y2L"F4Q8z\\n\\xec]/\'', filtered=None, link=None, ), Violation( tool_id="r2c.sgrep", check_id="avoid_hardcoded_config_SECRET_KEY", path="flask_configs.py", line=19, column=1, message= " Hardcoded variable `SECRET_KEY` detected. Use environment variables or config files instead", severity=2, syntactic_context='app.config.update(SECRET_KEY="aaaa")', filtered=None, link=None, ), Violation( tool_id="r2c.sgrep", check_id="avoid_hardcoded_config_TESTING", path="flask_configs.py", line=15, column=1, message= " Hardcoded variable `TESTING` detected. Use environment variables or config files instead", severity=2, syntactic_context="app.config.update(TESTING=True)", filtered=None, link=None, ), Violation( tool_id="r2c.sgrep", check_id="avoid_hardcoded_config_TESTING", path="flask_configs.py", line=13, column=1, message= " Hardcoded variable `TESTING` detected. Use environment variables or config files instead", severity=2, syntactic_context='app.config["TESTING"] = False', filtered=None, link=None, ), Violation( tool_id="r2c.sgrep", check_id="avoid_hardcoded_config_TESTING", path="flask_configs.py", line=11, column=1, message= " Hardcoded variable `TESTING` detected. Use environment variables or config files instead", severity=2, syntactic_context='app.config["TESTING"] = True', filtered=None, link=None, ), ] assert violations == expectation