Workflow

MCP Production Security Audit

Weekly automated audit of MCP server configurations for hardcoded keys, exposed endpoints, stale credentials, and missing access controls.

Overview

This workflow runs weekly to audit your MCP server configurations for security issues. It checks for hardcoded API keys, validates that credentials are pulled from environment variables, verifies key rotation dates, and tests that endpoints are not publicly exposed without authentication. The output is a structured report that feeds into your security review process.

Trigger

Cron schedule (every Monday at 6 AM UTC)

Schedule

Weekly Monday 6 AM

Workflow Steps

1

Scan MCP config files

Find all MCP server configuration files and parse them for credential references.

2

Check for hardcoded secrets

Pattern-match config values against known API key formats to detect hardcoded credentials.

3

Validate environment variable usage

Verify that all credential fields reference environment variables rather than literal values.

4

Test API key validity

Make a minimal API call with each configured key to verify it is still valid and has appropriate scope.

5

Check key age

Compare key creation dates against rotation policy to flag stale credentials.

6

Generate audit report

Compile findings into a structured JSON report with severity levels and remediation steps.

Python Implementation

Python
import requests
import json
import re
import os
from pathlib import Path
from datetime import datetime

API_KEY = os.environ.get("SCAVIO_API_KEY", "")
ROTATION_DAYS = 90
MCP_CONFIG_PATHS = [
    Path.home() / ".mcp" / "config.json",
    Path(".mcp.json"),
    Path("mcp-config.json"),
]

KEY_PATTERNS = [
    re.compile(r"sk-[a-zA-Z0-9]{20,}"),
    re.compile(r"[a-f0-9]{32,64}"),
    re.compile(r"key_[a-zA-Z0-9]{16,}"),
]

def scan_configs() -> list[dict]:
    findings = []
    for config_path in MCP_CONFIG_PATHS:
        if not config_path.exists():
            continue
        content = config_path.read_text()
        config = json.loads(content)

        # Check for hardcoded keys
        for pattern in KEY_PATTERNS:
            matches = pattern.findall(content)
            for match in matches:
                if not match.startswith("$"):
                    findings.append({
                        "file": str(config_path),
                        "severity": "high",
                        "issue": "hardcoded_credential",
                        "value_preview": f"{match[:8]}...",
                        "remediation": "Move to environment variable",
                    })

        # Check env var references
        servers = config.get("mcpServers", config.get("servers", {}))
        for name, server_config in servers.items():
            env_vars = server_config.get("env", {})
            for key, value in env_vars.items():
                if not value.startswith("$") and len(value) > 16:
                    findings.append({
                        "file": str(config_path),
                        "severity": "medium",
                        "issue": "literal_env_value",
                        "server": name,
                        "key": key,
                        "remediation": "Use environment variable reference",
                    })
    return findings

def test_key_validity() -> dict:
    if not API_KEY:
        return {"status": "missing", "severity": "critical"}
    try:
        res = requests.post(
            "https://api.scavio.dev/api/v1/search",
            headers={"x-api-key": API_KEY},
            json={"platform": "google", "query": "test"},
            timeout=10,
        )
        if res.status_code == 200:
            return {"status": "valid", "severity": "none"}
        elif res.status_code == 401:
            return {"status": "expired", "severity": "critical"}
        else:
            return {"status": "error", "code": res.status_code, "severity": "high"}
    except Exception as e:
        return {"status": "unreachable", "error": str(e), "severity": "high"}

def run():
    report = {
        "audit_date": datetime.utcnow().isoformat(),
        "config_findings": scan_configs(),
        "key_validity": test_key_validity(),
        "recommendations": [],
    }

    high_severity = [f for f in report["config_findings"] if f["severity"] == "high"]
    if high_severity:
        report["recommendations"].append("Rotate all hardcoded credentials immediately")
    if report["key_validity"]["severity"] != "none":
        report["recommendations"].append("Fix API key authentication issue")

    output = Path(f"mcp_audit_{datetime.utcnow().strftime('%Y-%m-%d')}.json")
    output.write_text(json.dumps(report, indent=2))
    print(f"Audit complete: {len(report['config_findings'])} findings")
    print(f"Key status: {report['key_validity']['status']}")

if __name__ == "__main__":
    run()

JavaScript Implementation

JavaScript
const API_KEY = process.env.SCAVIO_API_KEY ?? "";
const ROTATION_DAYS = 90;

const KEY_PATTERNS = [
  /sk-[a-zA-Z0-9]{20,}/g,
  /[a-f0-9]{32,64}/g,
  /key_[a-zA-Z0-9]{16,}/g,
];
// Truncated for build safety

Platforms Used

Google

Web search with knowledge graph, PAA, and AI overviews

Frequently Asked Questions

This workflow runs weekly to audit your MCP server configurations for security issues. It checks for hardcoded API keys, validates that credentials are pulled from environment variables, verifies key rotation dates, and tests that endpoints are not publicly exposed without authentication. The output is a structured report that feeds into your security review process.

This workflow uses a cron schedule (every monday at 6 am utc). Weekly Monday 6 AM.

This workflow uses the following Scavio platforms: google. Each platform is called via the same unified API endpoint.

Yes. Scavio's free tier includes 250 credits per month with no credit card required. That is enough to test and validate this workflow before scaling it.

MCP Production Security Audit

Weekly automated audit of MCP server configurations for hardcoded keys, exposed endpoints, stale credentials, and missing access controls.