Skip to main content
Glama
stv-io

AWS Terraform MCP Server

by stv-io

RunCheckovScan

Scan Terraform code for security vulnerabilities and compliance issues using Checkov static analysis to identify misconfigurations in AWS infrastructure.

Instructions

Run Checkov security scan on Terraform code.

This tool runs Checkov to scan Terraform code for security and compliance issues,
identifying potential vulnerabilities and misconfigurations according to best practices.

Checkov (https://www.checkov.io/) is an open-source static code analysis tool that
can detect hundreds of security and compliance issues in infrastructure-as-code.

Parameters:
    working_directory: Directory containing Terraform files to scan
    framework: Framework to scan (default: terraform)
    check_ids: Optional list of specific check IDs to run
    skip_check_ids: Optional list of check IDs to skip
    output_format: Format for scan results (default: json)

Returns:
    A CheckovScanResult object containing scan results and identified vulnerabilities

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
working_directoryYesDirectory containing Terraform files
frameworkNoFramework to scan (terraform, cloudformation, etc.)terraform
check_idsNoSpecific check IDs to run
skip_check_idsNoCheck IDs to skip
output_formatNoOutput format (json, cli, etc.)json

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
statusYes
summaryNoSummary of scan results
raw_outputNoRaw output from Checkov
return_codeNo
error_messageNo
vulnerabilitiesNoList of found vulnerabilities
working_directoryYes

Implementation Reference

  • The core handler function that performs the Checkov scan: installs Checkov if needed, validates inputs for security, builds and executes the checkov subprocess command, parses JSON or CLI output into structured vulnerabilities, and returns CheckovScanResult.
    async def run_checkov_scan_impl(request: CheckovScanRequest) -> CheckovScanResult:
        """Run Checkov scan on Terraform code.
    
        Args:
            request: Details about the Checkov scan to execute
    
        Returns:
            A CheckovScanResult object containing scan results and vulnerabilities
        """
        logger.info(f'Running Checkov scan in {request.working_directory}')
    
        # Ensure Checkov is installed
        if not _ensure_checkov_installed():
            return CheckovScanResult(
                status='error',
                working_directory=request.working_directory,
                error_message='Failed to install Checkov. Please install it manually with: pip install checkov',
                vulnerabilities=[],
                summary={},
                raw_output=None,
            )
    
        # Security checks for parameters
    
        # Check framework parameter for allowed values
        allowed_frameworks = ['terraform', 'cloudformation', 'kubernetes', 'dockerfile', 'arm', 'all']
        if request.framework not in allowed_frameworks:
            logger.error(f'Security violation: Invalid framework: {request.framework}')
            return CheckovScanResult(
                status='error',
                working_directory=request.working_directory,
                error_message=f"Security violation: Invalid framework '{request.framework}'. Allowed frameworks are: {', '.join(allowed_frameworks)}",
                vulnerabilities=[],
                summary={},
                raw_output=None,
            )
    
        # Check output_format parameter for allowed values
        allowed_output_formats = [
            'cli',
            'csv',
            'cyclonedx',
            'cyclonedx_json',
            'spdx',
            'json',
            'junitxml',
            'github_failed_only',
            'gitlab_sast',
            'sarif',
        ]
        if request.output_format not in allowed_output_formats:
            logger.error(f'Security violation: Invalid output format: {request.output_format}')
            return CheckovScanResult(
                status='error',
                working_directory=request.working_directory,
                error_message=f"Security violation: Invalid output format '{request.output_format}'. Allowed formats are: {', '.join(allowed_output_formats)}",
                vulnerabilities=[],
                summary={},
                raw_output=None,
            )
    
        # Check for command injection patterns in check_ids and skip_check_ids
        dangerous_patterns = get_dangerous_patterns()
        logger.debug(f'Checking for {len(dangerous_patterns)} dangerous patterns')
    
        if request.check_ids:
            for check_id in request.check_ids:
                for pattern in dangerous_patterns:
                    if pattern in check_id:
                        logger.error(
                            f"Security violation: Potentially dangerous pattern '{pattern}' in check_id: {check_id}"
                        )
                        return CheckovScanResult(
                            status='error',
                            working_directory=request.working_directory,
                            error_message=f"Security violation: Potentially dangerous pattern '{pattern}' detected in check_id",
                            vulnerabilities=[],
                            summary={},
                            raw_output=None,
                        )
    
        if request.skip_check_ids:
            for skip_id in request.skip_check_ids:
                for pattern in dangerous_patterns:
                    if pattern in skip_id:
                        logger.error(
                            f"Security violation: Potentially dangerous pattern '{pattern}' in skip_check_id: {skip_id}"
                        )
                        return CheckovScanResult(
                            status='error',
                            working_directory=request.working_directory,
                            error_message=f"Security violation: Potentially dangerous pattern '{pattern}' detected in skip_check_id",
                            vulnerabilities=[],
                            summary={},
                            raw_output=None,
                        )
    
        # Build the command
        # Convert working_directory to absolute path if it's not already
        working_dir = request.working_directory
        if not os.path.isabs(working_dir):
            # Get the current working directory of the MCP server
            current_dir = os.getcwd()
            # Go up to the project root directory (assuming we're in src/terraform-mcp-server/awslabs/terraform_mcp_server)
            project_root = os.path.abspath(os.path.join(current_dir, '..', '..', '..', '..'))
            # Join with the requested working directory
            working_dir = os.path.abspath(os.path.join(project_root, working_dir))
    
        logger.info(f'Using absolute working directory: {working_dir}')
        cmd = ['checkov', '--quiet', '-d', working_dir]
    
        # Add framework if specified
        if request.framework:
            cmd.extend(['--framework', request.framework])
    
        # Add specific check IDs if provided
        if request.check_ids:
            cmd.extend(['--check', ','.join(request.check_ids)])
    
        # Add skip check IDs if provided
        if request.skip_check_ids:
            cmd.extend(['--skip-check', ','.join(request.skip_check_ids)])
    
        # Set output format
        cmd.extend(['--output', request.output_format])
    
        # Execute command
        try:
            logger.info(f'Executing command: {" ".join(cmd)}')
            process = subprocess.run(
                cmd,
                capture_output=True,
                text=True,
            )
    
            # Clean output text
            stdout = _clean_output_text(process.stdout)
            stderr = _clean_output_text(process.stderr)
    
            # Debug logging
            logger.info(f'Checkov return code: {process.returncode}')
            logger.info(f'Checkov stdout: {stdout}')
            logger.info(f'Checkov stderr: {stderr}')
    
            # Parse results if JSON output was requested
            vulnerabilities = []
            summary = {}
            if request.output_format == 'json' and stdout:
                vulnerabilities, summary = _parse_checkov_json_output(stdout)
    
            # For non-JSON output, try to parse vulnerabilities from the text output
            elif stdout and process.returncode == 1:  # Return code 1 means vulnerabilities were found
                # Simple regex to extract failed checks from CLI output
                failed_checks = re.findall(
                    r'Check: (CKV\w*_\d+).*?FAILED for resource: ([\w\.]+).*?File: ([\w\/\.-]+):(\d+)',
                    stdout,
                    re.DOTALL,
                )
                for check_id, resource, file_path, line in failed_checks:
                    vuln = CheckovVulnerability(
                        id=check_id,
                        type='terraform',
                        resource=resource,
                        file_path=file_path,
                        line=int(line),
                        description=f'Failed check: {check_id}',
                        guideline=None,
                        severity='MEDIUM',
                        fixed=False,
                        fix_details=None,
                    )
                    vulnerabilities.append(vuln)
    
                # Extract summary counts
                passed_match = re.search(r'Passed checks: (\d+)', stdout)
                failed_match = re.search(r'Failed checks: (\d+)', stdout)
                skipped_match = re.search(r'Skipped checks: (\d+)', stdout)
    
                summary = {
                    'passed': int(passed_match.group(1)) if passed_match else 0,
                    'failed': int(failed_match.group(1)) if failed_match else 0,
                    'skipped': int(skipped_match.group(1)) if skipped_match else 0,
                }
    
            # Prepare the result - consider it a success even if vulnerabilities were found
            # A return code of 1 from Checkov means vulnerabilities were found, not an error
            is_error = process.returncode not in [0, 1]
            result = CheckovScanResult(
                status='error' if is_error else 'success',
                return_code=process.returncode,
                working_directory=request.working_directory,
                vulnerabilities=vulnerabilities,
                summary=summary,
                raw_output=stdout,
            )
    
            return result
        except Exception as e:
            logger.error(f'Error running Checkov scan: {e}')
            return CheckovScanResult(
                status='error',
                working_directory=request.working_directory,
                error_message=str(e),
                vulnerabilities=[],
                summary={},
                raw_output=None,
            )
  • Pydantic models defining the input schema (CheckovScanRequest), output schema (CheckovScanResult), and vulnerability details (CheckovVulnerability) for type validation and serialization.
    class CheckovVulnerability(BaseModel):
        """Model representing a security vulnerability found by Checkov.
    
        Attributes:
            id: The Checkov check ID (e.g., CKV_AWS_1).
            type: The type of check (e.g., terraform_aws).
            resource: The resource identifier where the vulnerability was found.
            file_path: Path to the file containing the vulnerability.
            line: Line number where the vulnerability was found.
            description: Description of the vulnerability.
            guideline: Recommended fix or security guideline.
            severity: Severity level of the vulnerability.
            fixed: Whether the vulnerability has been fixed.
            fix_details: Details about how the vulnerability was fixed (if applicable).
        """
    
        id: str = Field(..., description='Checkov check ID')
        type: str = Field(..., description='Type of security check')
        resource: str = Field(..., description='Resource identifier')
        file_path: str = Field(..., description='Path to the file with the vulnerability')
        line: int = Field(..., description='Line number of the vulnerability')
        description: str = Field(..., description='Description of the vulnerability')
        guideline: Optional[str] = Field(None, description='Recommended fix or guideline')
        severity: str = Field('MEDIUM', description='Severity level (HIGH, MEDIUM, LOW)')
        fixed: bool = Field(False, description='Whether the vulnerability has been fixed')
        fix_details: Optional[str] = Field(None, description='Details about the fix applied')
    
    
    class CheckovScanRequest(BaseModel):
        """Request model for Checkov scan execution.
    
        Attributes:
            working_directory: Directory containing Terraform files to scan.
            framework: Framework to scan (default: terraform).
            check_ids: Optional list of specific check IDs to run.
            skip_check_ids: Optional list of check IDs to skip.
            output_format: Format for the scan results output.
        """
    
        working_directory: str = Field(..., description='Directory containing Terraform files')
        framework: str = Field(
            'terraform', description='Framework to scan (terraform, cloudformation, etc.)'
        )
        check_ids: Optional[List[str]] = Field(None, description='Specific check IDs to run')
        skip_check_ids: Optional[List[str]] = Field(None, description='Check IDs to skip')
        output_format: str = Field('json', description='Output format (json, cli, etc.)')
    
    
    class CheckovScanResult(BaseModel):
        """Result model for Checkov scan execution.
    
        Attributes:
            status: Execution status (success/error).
            return_code: The command's return code (0 for success).
            working_directory: Directory where the scan was executed.
            error_message: Optional error message if execution failed.
            vulnerabilities: List of vulnerabilities found by the scan.
            summary: Summary of the scan results.
            raw_output: Raw output from the Checkov command.
        """
    
        status: Literal['success', 'error']
        return_code: Optional[int] = None
        working_directory: str
        error_message: Optional[str] = None
        vulnerabilities: List[CheckovVulnerability] = Field(
            [], description='List of found vulnerabilities'
        )
        summary: Dict[str, Any] = Field({}, description='Summary of scan results')
        raw_output: Optional[str] = Field(None, description='Raw output from Checkov')
  • MCP tool registration using @mcp.tool decorator, defining the tool name, input parameters via Field descriptions (which match the schema), and delegating to the impl function.
    @mcp.tool(name='RunCheckovScan')
    async def run_checkov_scan(
        working_directory: str = Field(..., description='Directory containing Terraform files'),
        framework: str = Field(
            'terraform', description='Framework to scan (terraform, cloudformation, etc.)'
        ),
        check_ids: Optional[List[str]] = Field(None, description='Specific check IDs to run'),
        skip_check_ids: Optional[List[str]] = Field(None, description='Check IDs to skip'),
        output_format: str = Field('json', description='Output format (json, cli, etc.)'),
    ) -> CheckovScanResult:
        """Run Checkov security scan on Terraform code.
    
        This tool runs Checkov to scan Terraform code for security and compliance issues,
        identifying potential vulnerabilities and misconfigurations according to best practices.
    
        Checkov (https://www.checkov.io/) is an open-source static code analysis tool that
        can detect hundreds of security and compliance issues in infrastructure-as-code.
    
        Parameters:
            working_directory: Directory containing Terraform files to scan
            framework: Framework to scan (default: terraform)
            check_ids: Optional list of specific check IDs to run
            skip_check_ids: Optional list of check IDs to skip
            output_format: Format for scan results (default: json)
    
        Returns:
            A CheckovScanResult object containing scan results and identified vulnerabilities
        """
        request = CheckovScanRequest(
            working_directory=working_directory,
            framework=framework,
            check_ids=check_ids,
            skip_check_ids=skip_check_ids,
            output_format=output_format,
        )
        return await run_checkov_scan_impl(request)
  • Helper function to clean Checkov output by removing ANSI escapes, control characters, and replacing Unicode/box-drawing chars with ASCII equivalents for safe handling.
    def _clean_output_text(text: str) -> str:
        """Clean output text by removing or replacing problematic Unicode characters.
    
        Args:
            text: The text to clean
    
        Returns:
            Cleaned text with ASCII-friendly replacements
        """
        if not text:
            return text
    
        # First remove ANSI escape sequences (color codes, cursor movement)
        ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
        text = ansi_escape.sub('', text)
    
        # Remove C0 and C1 control characters (except common whitespace)
        control_chars = re.compile(r'[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F-\x9F]')
        text = control_chars.sub('', text)
    
        # Replace HTML entities
        html_entities = {
            '->': '->',  # Replace HTML arrow
            '<': '<',  # Less than
            '>': '>',  # Greater than
            '&': '&',  # Ampersand
        }
        for entity, replacement in html_entities.items():
            text = text.replace(entity, replacement)
    
        # Replace box-drawing and other special Unicode characters with ASCII equivalents
        unicode_chars = {
            '\u2500': '-',  # Horizontal line
            '\u2502': '|',  # Vertical line
            '\u2514': '+',  # Up and right
            '\u2518': '+',  # Up and left
            '\u2551': '|',  # Double vertical
            '\u2550': '-',  # Double horizontal
            '\u2554': '+',  # Double down and right
            '\u2557': '+',  # Double down and left
            '\u255a': '+',  # Double up and right
            '\u255d': '+',  # Double up and left
            '\u256c': '+',  # Double cross
            '\u2588': '#',  # Full block
            '\u25cf': '*',  # Black circle
            '\u2574': '-',  # Left box drawing
            '\u2576': '-',  # Right box drawing
            '\u2577': '|',  # Down box drawing
            '\u2575': '|',  # Up box drawing
        }
        for char, replacement in unicode_chars.items():
            text = text.replace(char, replacement)
    
        return text
  • Helper function to parse Checkov JSON output into list of vulnerabilities and summary dict.
    def _parse_checkov_json_output(output: str) -> Tuple[List[CheckovVulnerability], Dict[str, Any]]:
        """Parse Checkov JSON output into structured vulnerability data.
    
        Args:
            output: JSON output from Checkov scan
    
        Returns:
            Tuple of (list of vulnerabilities, summary dictionary)
        """
        try:
            data = json.loads(output)
            vulnerabilities = []
            summary = {
                'passed': 0,
                'failed': 0,
                'skipped': 0,
                'parsing_errors': 0,
                'resource_count': 0,
            }
    
            # Extract summary information
            if 'summary' in data:
                summary = data['summary']
    
            # Process check results
            if 'results' in data and 'failed_checks' in data['results']:
                for check in data['results']['failed_checks']:
                    vuln = CheckovVulnerability(
                        id=check.get('check_id', 'UNKNOWN'),
                        type=check.get('check_type', 'terraform'),
                        resource=check.get('resource', 'UNKNOWN'),
                        file_path=check.get('file_path', 'UNKNOWN'),
                        line=check.get('file_line_range', [0, 0])[0],
                        description=check.get('check_name', 'UNKNOWN'),
                        guideline=check.get('guideline', None),
                        severity=(check.get('severity', 'MEDIUM') or 'MEDIUM').upper(),
                        fixed=False,
                        fix_details=None,
                    )
                    vulnerabilities.append(vuln)
    
            return vulnerabilities, summary
        except json.JSONDecodeError as e:
            logger.error(f'Failed to parse Checkov JSON output: {e}')
            return [], {'error': 'Failed to parse JSON output'}
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It describes the tool as running a scan and returning results, which implies a read-only operation, but does not detail execution characteristics such as runtime, error handling, or external dependencies (e.g., Checkov installation). It adds context by linking to Checkov's website and noting it's open-source, but lacks specifics on permissions or side effects.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured and appropriately sized, with a clear purpose statement followed by parameter and return sections. It avoids redundancy, though the link to Checkov's website, while informative, could be considered slightly extraneous. Overall, it is efficient and front-loaded with key information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (security scanning with multiple parameters), the description is reasonably complete. It explains the tool's purpose, parameters, and return value, and an output schema exists to detail results. However, it could improve by addressing usage guidelines and behavioral aspects like execution limits or error scenarios, which are not covered by annotations or schema.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all parameters thoroughly. The description lists parameters with brief explanations (e.g., 'Directory containing Terraform files to scan'), but does not add significant meaning beyond what the schema provides, such as examples or constraints. The baseline score of 3 reflects adequate coverage without extra value.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose with specific verb ('run') and resource ('Checkov security scan on Terraform code'), distinguishing it from sibling tools like ExecuteTerraformCommand or ExecuteTerragruntCommand. It explicitly mentions scanning for 'security and compliance issues' and 'identifying potential vulnerabilities and misconfigurations,' providing a precise scope.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description lacks explicit guidance on when to use this tool versus alternatives. While it mentions scanning Terraform code, it does not specify prerequisites (e.g., whether Terraform files must be valid), exclusions (e.g., non-Terraform files), or comparisons to sibling tools like ExecuteTerraformCommand, which might handle other Terraform operations.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/stv-io/aws-terraform-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server