From b473f850a00b7ab7d15c67e3fb1f93a76e0468ce Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Thu, 23 Jul 2026 14:40:00 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[=EC=84=B1=EB=8A=A5=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0]=20redact=5Fsensitive=5Flog=EC=9D=98=20O(N^2?= =?UTF-8?q?)=20=EB=B3=91=EB=AA=A9=20=ED=98=84=EC=83=81=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit scripts/ci/redact_sensitive_log.py 내부의 _consume_sensitive_assignment 함수가 일치하지 않는 문자열을 한 글자씩 확인하며 문자열을 생성하던 구조적 결함을 수정하여 슬라이싱을 이용해 한 번에 스킵하도록 개선했습니다. 이를 통해 대용량 로그 파일 스캔 시 성능이 비약적으로 향상되었습니다. --- .jules/bolt.md | 4 +++ scripts/ci/redact_sensitive_log.py | 56 +++++++++++++++++------------- 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index a86b7aafd..cee8782c6 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -43,3 +43,7 @@ ## 2026-07-09 - Avoid N+1 API blocking in SBOM aggregator **Learning:** The `collect_inventories` function in `scripts/ci/sbom_inventory_aggregator.py` was fetching SBOMs from the GitHub dependency graph synchronously for every repository in the organization. For large organizations (up to 500 repos), this N+1 network/CLI bottleneck significantly stalled the aggregation workflow. **Action:** Use `concurrent.futures.ThreadPoolExecutor` to fetch SBOMs concurrently when multiple repositories are provided, bounded by a `max_workers` limit (e.g., 10) to avoid overwhelming the CLI/API, while preserving the fast serial path for single-item inputs. + +## 2024-05-18 - [Optimize redact_sensitive_log O(N^2) bottlenecks] +**Learning:** `_consume_sensitive_assignment` inside `redact_sensitive_log.py` had a severe performance issue because when it checked for sensitive words, it skipped non-matching characters very slowly (only incrementing `cursor` by 1 on failure to find an exact start). Using standard logic to quickly skip sequences and avoid copying slices significantly improves parsing speed on large log texts. +**Action:** Replace `_consume_sensitive_assignment` with a more efficient scanning implementation. diff --git a/scripts/ci/redact_sensitive_log.py b/scripts/ci/redact_sensitive_log.py index cb89fe67b..7d9978767 100644 --- a/scripts/ci/redact_sensitive_log.py +++ b/scripts/ci/redact_sensitive_log.py @@ -44,41 +44,42 @@ def _redact_json(value: Any) -> Any: return value -def _consume_sensitive_assignment(text: str, start: int) -> tuple[str, int] | None: - """Return a redacted key/value assignment parsed in linear time.""" +def _consume_sensitive_assignment(text: str, start: int) -> tuple[str, int] | int: + """Return a redacted assignment, or the next index to scan if no match.""" cursor = start key_quote = "" - if cursor < len(text) and text[cursor] in "\"'": + length = len(text) + if cursor < length and text[cursor] in "\"'": key_quote = text[cursor] cursor += 1 key_start = cursor - if cursor >= len(text) or text[cursor] not in KEY_CHARS or text[cursor].isdigit(): - return None - while cursor < len(text) and text[cursor] in KEY_CHARS: + if cursor >= length or text[cursor] not in KEY_CHARS or text[cursor].isdigit(): + return start + 1 + while cursor < length and text[cursor] in KEY_CHARS: cursor += 1 key = text[key_start:cursor] if key_quote: - if cursor >= len(text) or text[cursor] != key_quote: - return None + if cursor >= length or text[cursor] != key_quote: + return start + 1 cursor += 1 if not SENSITIVE_KEY_RE.search(key): - return None - while cursor < len(text) and text[cursor].isspace(): + return cursor + while cursor < length and text[cursor].isspace(): cursor += 1 - if cursor >= len(text) or text[cursor] not in ":=": - return None + if cursor >= length or text[cursor] not in ":=": + return cursor cursor += 1 - while cursor < len(text) and text[cursor].isspace(): + while cursor < length and text[cursor].isspace(): cursor += 1 - if cursor >= len(text): - return None + if cursor >= length: + return cursor value_start = cursor if text[cursor] in "\"'": value_quote = text[cursor] cursor += 1 escaped = False - while cursor < len(text): + while cursor < length: char = text[cursor] cursor += 1 if escaped: @@ -88,10 +89,10 @@ def _consume_sensitive_assignment(text: str, start: int) -> tuple[str, int] | No elif char == value_quote: break else: - while cursor < len(text) and not text[cursor].isspace() and text[cursor] not in ",}": + while cursor < length and not text[cursor].isspace() and text[cursor] not in ",}": cursor += 1 if cursor == value_start: - return None + return cursor return text[start:value_start] + REDACTED, cursor @@ -99,14 +100,21 @@ def _redact_assignments(text: str) -> str: """Redact sensitive key/value assignments without backtracking regexes.""" output: list[str] = [] cursor = 0 - while cursor < len(text): - match = _consume_sensitive_assignment(text, cursor) - if match is None: - output.append(text[cursor]) - cursor += 1 + last_append = 0 + length = len(text) + while cursor < length: + result = _consume_sensitive_assignment(text, cursor) + if isinstance(result, int): + cursor = result continue - replacement, cursor = match + replacement, next_cursor = result + if cursor > last_append: + output.append(text[last_append:cursor]) output.append(replacement) + cursor = next_cursor + last_append = cursor + if last_append < length: + output.append(text[last_append:]) return "".join(output)