4747import java .util .List ;
4848import java .util .Set ;
4949import java .util .concurrent .Callable ;
50+ import java .util .function .Predicate ;
5051import java .util .regex .Pattern ;
5152
5253import static picocli .CommandLine .Command ;
@@ -84,6 +85,23 @@ public class MainApplication implements Callable<Integer>, ExitCodeGenerator {
8485
8586 private static final String DEFAULT_COMMAND = "lsp" ;
8687
88+ /**
89+ * Опция осознанного включения отладочного режима Spring Boot.
90+ * <p>
91+ * Без неё любой запрос на отладку — флаги {@code --debug}/{@code --trace} или переменные среды
92+ * {@code DEBUG}/{@code TRACE} (Spring привязывает их к свойствам {@code debug}/{@code trace}
93+ * по relaxed binding) — игнорируется: см. {@link #guardSpringDebugMode(String[])}.
94+ */
95+ static final String ENABLE_DEBUG_OPTION = "--enable-debug-i-know-what-i-am-doing" ;
96+
97+ /** {@link #ENABLE_DEBUG_OPTION} с необязательным значением; группа 1 — значение ({@code null}, если его нет). */
98+ private static final Pattern ENABLE_DEBUG_OPTION_PATTERN =
99+ CaseInsensitivePattern .compile (Pattern .quote (ENABLE_DEBUG_OPTION ) + "(?:=(.*))?" );
100+
101+ /** Отладочные флаги Spring Boot: {@code --debug}/{@code --trace} с необязательным {@code =value}. */
102+ private static final Pattern DEBUG_TRACE_FLAG_PATTERN =
103+ CaseInsensitivePattern .compile ("--(?:debug|trace)(?:=.*)?" );
104+
87105 @ Option (
88106 names = {"-h" , "--help" },
89107 usageHelp = true ,
@@ -107,14 +125,16 @@ public class MainApplication implements Callable<Integer>, ExitCodeGenerator {
107125 CaseInsensitivePattern .compile ("--server\\ ..*" ),
108126 // Опции команды по умолчанию (lsp), допустимые без явного указания команды: `--mcp`, `--mcp-path`.
109127 CaseInsensitivePattern .compile ("--mcp(-path)?(=.*)?" ),
110- CaseInsensitivePattern .compile ("--debug" )
128+ DEBUG_TRACE_FLAG_PATTERN ,
129+ ENABLE_DEBUG_OPTION_PATTERN
111130 );
112131
113132 private final CommandLine .IFactory picocliFactory ;
114133
115134 private int exitCode ;
116135
117136 public static void main (String [] args ) {
137+ args = guardSpringDebugMode (args );
118138 applyMcpEndpointPath (args );
119139
120140 var applicationContext = new SpringApplicationBuilder (MainApplication .class )
@@ -253,6 +273,58 @@ private static boolean isMcpSubcommandOverHttp(String[] args) {
253273 return protocol .equals ("sse" ) || protocol .equals ("streamable" );
254274 }
255275
276+ /**
277+ * Защита от случайного включения отладочного режима Spring Boot.
278+ * <p>
279+ * Отладка ({@code --debug}/{@code --trace}, свойства {@code debug}/{@code trace} или переменные
280+ * среды {@code DEBUG}/{@code TRACE}) включает объёмный лог core-логгеров и отчёт об
281+ * автоконфигурации. Для LSP по stdio это особенно опасно: посторонний вывод способен замусорить
282+ * канал протокола. При этом {@code DEBUG} — распространённое имя переменной среды, её легко
283+ * выставить в окружении случайно, а переименовать/отключить ключ на уровне Spring Boot нельзя.
284+ * <p>
285+ * Поэтому при явной передаче {@link #ENABLE_DEBUG_OPTION} аргументы отдаются как есть. Иначе
286+ * отладка гасится до старта контекста: свойства {@code debug}/{@code trace} принудительно
287+ * выставляются в {@code false} (системное свойство приоритетнее переменной среды, поэтому случайно
288+ * заданный {@code DEBUG}/{@code TRACE} тоже не сработает), а флаги {@code --debug}/{@code --trace}
289+ * убираются из аргументов.
290+ *
291+ * @param args исходные аргументы командной строки
292+ * @return исходные аргументы при осознанном включении; иначе — аргументы без флагов
293+ * {@code --debug}/{@code --trace}
294+ */
295+ static String [] guardSpringDebugMode (String [] args ) {
296+ if (hasDebugOptIn (args )) {
297+ // Пользователь осознанно включил отладку — аргументы не трогаем.
298+ return args ;
299+ }
300+
301+ System .setProperty ("debug" , "false" );
302+ System .setProperty ("trace" , "false" );
303+
304+ var cleaned = removeArgs (args , arg -> DEBUG_TRACE_FLAG_PATTERN .matcher (arg ).matches ());
305+ if (cleaned .length != args .length ) {
306+ System .err .println (
307+ "Ignoring --debug/--trace: verbose Spring Boot debug logging can corrupt the LSP stdout "
308+ + "channel. Pass " + ENABLE_DEBUG_OPTION + "=true to enable it intentionally." );
309+ }
310+ return cleaned ;
311+ }
312+
313+ private static boolean hasDebugOptIn (String [] args ) {
314+ for (var arg : args ) {
315+ var matcher = ENABLE_DEBUG_OPTION_PATTERN .matcher (arg );
316+ // Голый флаг (значение null) и =true включают отладку; =false — нет.
317+ if (matcher .matches () && !"false" .equalsIgnoreCase (matcher .group (1 ))) {
318+ return true ;
319+ }
320+ }
321+ return false ;
322+ }
323+
324+ private static String [] removeArgs (String [] args , Predicate <String > drop ) {
325+ return Arrays .stream (args ).filter (drop .negate ()).toArray (String []::new );
326+ }
327+
256328 /**
257329 * Перенести значение {@code --mcp-path} в системное свойство до старта контекста:
258330 * эндпоинт Streamable HTTP регистрируется автоконфигурацией на refresh, раньше выполнения команды.
0 commit comments