T09 · Insecure Skill Coding Practices
- Location
scripts/fetch_company_data.py:23- Finding
Arbitrary Command Execution Through Unsanitized Company Input
- Content
View full analysis
Vulnerability Details
File Location:
scripts/fetch_company_data.py, lines 23-28 and 37-42
Vulnerability Type: OS command injection
Risk Level: HighVulnerable Code
python def mcporter(tool, **kwargs): args = " ".join(f"{k}={v}" for k, v in kwargs.items()) cmd = f"mcporter call {tool} {args}" try: r = subprocess.run( cmd, shell=True, capture_output=True, text=True, env=ENV, timeout=60 )python def mcporter_to_file(tool, out_path, **kwargs): args = " ".join(f"{k}={v}" for k, v in kwargs.items()) cmd = f"mcporter call {tool} {args}" try: with open(out_path, "w") as f: subprocess.run( cmd, shell=True, stdout=f, stderr=subprocess.DEVNULL, env=ENV, timeout=60 )Technical Analysis
The functions concatenate argument names and values into a single command string and then execute it using
shell=True. Thekeywordvalue originates fromsys.argv[1]and is repeatedly passed to these functions without validation or shell escaping.Consequently, shell metacharacters in a crafted company keyword are interpreted by the operating-system shell instead of being treated as literal company-name characters. The affected helpers are used for company information, financial statements, valuation, market data, shareholder information, and industry queries.
Merely applying string quoting would remain fragile. The correct security boundary is to avoid invoking a shell and pass every argument as a distinct element in an argument vector.
Attack Path
- An attacker supplies a crafted company name or ticker containing shell syntax.
main()reads the value fromsys.argv[1].fetch_company_data()passes it as the ` ...[truncated 1114 chars]
- Remediation
View remediation
Remediation Suggestions
-
Eliminate
shell=Trueand construct an argument list:python def mcporter(tool, **kwargs): cmd = ["mcporter", "call", tool] cmd.extend(f"{key}={value}" for key, value in kwargs.items()) try: result = subprocess.run( cmd, shell=False, capture_output=True, text=True, env=ENV, timeout=60, check=True, ) if result.stdout.strip(): return json.loads(result.stdout) except ( subprocess.CalledProcessError, subprocess.TimeoutExpired, json.JSONDecodeError, ): return None -
Apply an allowlist to MCP tool names rather than accepting arbitrary tool identifiers.
-
Validate company codes and tickers against market-specific formats.
-
For company names that require broader character support, enforce a reasonable length limit and reject control characters.
-
Check subprocess return codes and handle failures explicitly.
-
Run the data collector under a restricted operating-system account without unnecessary repository, credential, or sensitive-file access.
-
Add regression tests containing shell metacharacters and verify that they are passed as literal argument content.
-
