Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions checkers/external_tools_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#!/usr/bin/env python3
#
# Copyright 2026 Enflame. All Rights Reserved.
#

import subprocess
import os
import re
import sys
import json
from pathlib import Path

CHECKERS_DIR = Path(__file__).resolve().parent
REPO_DIR = CHECKERS_DIR.parent
sys.path.append(str(REPO_DIR))
from common.static_check_common import QualityCodexCommitee, CICheckerCommon, StaticCheck

CONFIG_PATH = REPO_DIR / 'config' / 'sast.json'

class CIChecker(CICheckerCommon):
def __init__(self, api_init=None, args=None, check_api_type=None, static_check=StaticCheck):
self.check_name = "external tools check"
super().__init__(api_init, args, check_api_type, self.check_name, static_check)
self.local_ci_check = True
self.local_workspace_check = True
self.pass_flag = True
self.files_static_check_status = {}
self.command_output = {}

def _load_tools(self):
try:
with open(CONFIG_PATH, 'r') as f:
cfg = json.load(f)
return cfg.get('external_tools', [])
except Exception:
return []

def check_func(self):
tools = self._load_tools()
# run each configured tool command; commands should be shell commands (bash)
for tool in tools:
name = tool.get('name')
cmd = tool.get('command')
guide = tool.get('guide_link','')
if not cmd or not name:
continue
print("Running {}: {}".format(name, cmd))
pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True, executable='/bin/bash')
stdout, _ = pipe.communicate()
out = stdout.decode('utf-8', errors='ignore') if stdout else ''
ret = pipe.returncode
if ret != 0:
self.pass_flag = False
self.command_output[name] = out
else:
# still capture output for information
self.command_output[name] = out

return self.check_report()

def check_report(self):
# Prepare a synthetic files_static_check_status entry to integrate with existing output format
self.files_static_check_status['external_tools'] = {"check_status": self.pass_flag}
QualityCodexCommitee.FormatOutputSimple(self.check_name, self.pass_flag, self.id, self.files_static_check_status, CHECK_LEVEL)

# Print outputs for failed tools
for tool_name, output in self.command_output.items():
if output:
print('\t=== {} output ==='.format(tool_name))
for line in output.splitlines():
print('\t\t' + line)

if not self.pass_flag:
print('\tPlease review guide links in config/sast.json for remediation.')
assert self.pass_flag, "Failed {}".format(self.id)

if __name__ == '__main__':
c = CIChecker(None, None, None)
c.check()
49 changes: 48 additions & 1 deletion config/sast.json
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,54 @@
},
"checks_group":{
"external_checks":[
"codespell_check","cpplint_check","gitleaks_check","hardcode_check","jsonlint_check","line_terminators_check"
"codespell_check","cpplint_check","gitleaks_check","hardcode_check","jsonlint_check","line_terminators_check","external_tools_check"
]
},
"external_tools":[
{
"name":"bandit",
"command":"bandit -r . -f json -o bandit_report.json",
"guide_link":"https://bandit.readthedocs.io/"
},
{
"name":"mypy",
"command":"mypy . --ignore-missing-imports --show-error-codes",
"guide_link":"https://mypy-lang.org/"
},
{
"name":"pylint",
"command":"pylint $(git ls-files '*.py') || true",
"guide_link":"https://pylint.pycqa.org/"
},
{
"name":"safety",
"command":"safety check -r requirements.txt --full-report || true",
"guide_link":"https://pyup.io/safety/"
},
{
"name":"detect-secrets",
"command":"detect-secrets scan --all-files --json > .secrets.json || true",
"guide_link":"https://github.com/Yelp/detect-secrets"
},
{
"name":"checkov",
"command":"checkov -d . -o json || true",
"guide_link":"https://www.checkov.io/"
},
{
"name":"hadolint",
"command":"hadolint Dockerfile -f json || true",
"guide_link":"https://github.com/hadolint/hadolint"
},
{
"name":"tfsec",
"command":"tfsec . --format json --out tfsec_report.json || true",
"guide_link":"https://tfsec.dev/"
},
{
"name":"yamllint",
"command":"yamllint -f parsable . || true",
"guide_link":"https://yamllint.readthedocs.io/"
}
]
}
9 changes: 9 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
chardet==4.0.0
Requests==2.32.4
tomli==2.2.1

bandit
mypy
pylint
safety
detect-secrets
checkov
yamllint