88from pathlib import Path
99from typing import Any
1010
11+ try :
12+ import tomllib
13+ except ModuleNotFoundError : # pragma: no cover - Python 3.10 fallback
14+ import tomli as tomllib
15+
1116from .core import CORE_VERSION , DECISION_POLICY_NAME
1217from .hook import ScanSubject , max_decision , object_subject
1318
2025}
2126
2227REVIEW_FLAG_IDS = {
23- "env-secrets" ,
24- "remote-http" ,
2528 "browser-access" ,
2629 "filesystem-access" ,
2730 "code-execution" ,
28- "email-or-identity" ,
29- "repo-or-ticketing-access" ,
3031}
3132
3233
@@ -57,7 +58,9 @@ def default_config_candidates(cwd: Path | None = None) -> list[ConfigCandidate]:
5758 working_dir = cwd or Path .cwd ()
5859 candidates = [
5960 ConfigCandidate ("Claude Desktop" , home / "Library/Application Support/Claude/claude_desktop_config.json" ),
60- ConfigCandidate ("Claude Code" , home / ".claude/mcp.json" ),
61+ ConfigCandidate ("Claude Code Global" , home / ".claude.json" ),
62+ ConfigCandidate ("Claude Code MCP" , home / ".claude/mcp.json" ),
63+ ConfigCandidate ("Codex" , home / ".codex/config.toml" ),
6164 ConfigCandidate ("Cursor" , home / ".cursor/mcp.json" ),
6265 ConfigCandidate ("Cursor User" , home / "Library/Application Support/Cursor/User/mcp.json" ),
6366 ConfigCandidate ("Windsurf" , home / ".codeium/windsurf/mcp_config.json" ),
@@ -85,19 +88,29 @@ def config_candidates_from_target(target: str | Path | None, cwd: Path | None =
8588 return [ConfigCandidate ("custom" , Path (target ).expanduser ())]
8689
8790
88- def load_json_config (path : Path , max_file_bytes : int ) -> tuple [dict [str , Any ] | None , str | None ]:
91+ def load_structured_config (path : Path , max_file_bytes : int ) -> tuple [dict [str , Any ] | None , str | None ]:
8992 try :
9093 raw = path .read_bytes ()
9194 except OSError as exc :
9295 return None , f"read_error: { exc } "
9396 if len (raw ) > max_file_bytes :
9497 return None , f"file_too_large: { len (raw )} bytes > { max_file_bytes } "
9598 try :
96- payload = json . loads ( raw .decode ("utf-8" ) )
99+ text = raw .decode ("utf-8" )
97100 except UnicodeDecodeError as exc :
98101 return None , f"decode_error: { exc } "
99- except json .JSONDecodeError as exc :
100- return None , f"json_error: { exc } "
102+
103+ if path .suffix == ".toml" :
104+ try :
105+ payload = tomllib .loads (text )
106+ except tomllib .TOMLDecodeError as exc :
107+ return None , f"toml_error: { exc } "
108+ else :
109+ try :
110+ payload = json .loads (text )
111+ except json .JSONDecodeError as exc :
112+ return None , f"json_error: { exc } "
113+
101114 if not isinstance (payload , dict ):
102115 return None , f"unsupported_json_type: { type (payload ).__name__ } "
103116 return payload , None
@@ -147,7 +160,7 @@ def collect_config_servers(
147160 configs .append (public_config )
148161 continue
149162
150- payload , error = load_json_config (path , max_file_bytes )
163+ payload , error = load_structured_config (path , max_file_bytes )
151164 if error is not None or payload is None :
152165 public_config .update ({"status" : "invalid" , "error" : error })
153166 configs .append (public_config )
@@ -194,7 +207,7 @@ def inventory_flags(server: ConfigServer) -> list[dict[str, str]]:
194207 flags .append (
195208 {
196209 "id" : "env-secrets" ,
197- "level" : "review " ,
210+ "level" : "info " ,
198211 "reason" : "server config includes environment variables; values are redacted" ,
199212 }
200213 )
@@ -203,7 +216,7 @@ def inventory_flags(server: ConfigServer) -> list[dict[str, str]]:
203216 flags .append (
204217 {
205218 "id" : "remote-http" ,
206- "level" : "review " ,
219+ "level" : "info " ,
207220 "reason" : "server connects to a remote MCP endpoint" ,
208221 }
209222 )
@@ -248,7 +261,7 @@ def inventory_flags(server: ConfigServer) -> list[dict[str, str]]:
248261 flags .append (
249262 {
250263 "id" : "email-or-identity" ,
251- "level" : "review " ,
264+ "level" : "info " ,
252265 "reason" : "server appears connected to email, calendar, OAuth, or identity data" ,
253266 }
254267 )
@@ -257,7 +270,7 @@ def inventory_flags(server: ConfigServer) -> list[dict[str, str]]:
257270 flags .append (
258271 {
259272 "id" : "repo-or-ticketing-access" ,
260- "level" : "review " ,
273+ "level" : "info " ,
261274 "reason" : "server appears connected to code repositories or ticketing systems" ,
262275 }
263276 )
@@ -276,7 +289,7 @@ def product_decision_for_config_scan(risk: dict[str, Any], flags: list[dict[str,
276289
277290 scanner_decision = str (risk .get ("decision" , "allow" ))
278291 static_score = float (risk .get ("static_score" ) or 0.0 )
279- review_flags = [flag for flag in flags if flag .get ("id" ) in REVIEW_FLAG_IDS ]
292+ review_flags = [flag for flag in flags if flag .get ("level" ) == "review" and flag . get ( " id" ) in REVIEW_FLAG_IDS ]
280293 reasons : list [str ] = []
281294
282295 if scanner_decision == "block" :
0 commit comments