From a69d96c6525883dabfbcd032ad41745c273aa642 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sat, 10 Oct 2026 14:28:13 +0000 Subject: [PATCH] =?UTF-8?q?=EC=84=B1=EB=8A=A5=20=EA=B0=9C=EC=84=A0:=20any(?= =?UTF-8?q?)=20=EC=A0=9C=EB=84=88=EB=A0=88=EC=9D=B4=ED=84=B0=20=EC=B2=B4?= =?UTF-8?q?=EC=9D=B8=EC=9D=84=20=EB=AA=85=EC=8B=9C=EC=A0=81=20or=20?= =?UTF-8?q?=EC=97=B0=EC=82=B0=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .jules/bolt.md | 4 +++ appguardrail_core/audit_events.py | 58 ++++++++++++++++++++----------- appguardrail_core/issueops.py | 29 ++++++++++------ 3 files changed, 60 insertions(+), 31 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index 8ce7ce8f7..1ac6bfd37 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -77,3 +77,7 @@ ## 2024-11-20 - Optimize multiple tuple generation from a single collection **Learning:** `build_rule_metadata` derives exactly two collections, `owasp` and `cwe`, from the same references. Replacing its two generator traversals with one explicit loop reduces element visits from about 2N to N. Both versions remain O(N), so this is a constant-factor optimization rather than an asymptotic complexity improvement. **Action:** Combine repeated traversal when fixed derived collections share one source, while preserving ordering and classification semantics. Benchmark the production hot path before claiming a material wall-clock improvement. + +## 2025-02-23 - any() generator overhead in hot loops +**Learning:** In Python, using `any()` with a generator expression (e.g., `any(x in text for x in TUPLE)`) introduces significant overhead in hot loops. While explicit `or` chains bypass this overhead, never hardcode string values directly into `or` chains if it leaves original module-level constants as dead code, as this creates a severe maintainability and potential security risk. Ensure any optimization fully refactors or removes the original constants. +**Action:** Replace `any()` with an explicit `or` chain in hot loops, and explicitly remove the associated module-level constant to prevent dead code and maintainability risks. diff --git a/appguardrail_core/audit_events.py b/appguardrail_core/audit_events.py index a77383e6b..3b841c260 100644 --- a/appguardrail_core/audit_events.py +++ b/appguardrail_core/audit_events.py @@ -15,25 +15,12 @@ from datetime import datetime from typing import Any, Iterable, Mapping - GENESIS_EVENT_HASH = "0" * 64 MAX_AUDIT_SUMMARY_BYTES = 16_384 MAX_AUDIT_SUMMARY_DEPTH = 8 _EVENT_TYPE_RE = re.compile(r"\A[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*\Z") _HASH_RE = re.compile(r"\A[0-9a-f]{64}\Z") _UTC_TIMESTAMP_RE = re.compile(r"\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z\Z") -_SENSITIVE_KEY_PARTS = ( - "api_key", - "authorization", - "credential", - "findings", - "password", - "raw_evidence", - "secret", - "snippet", - "token", - "webhook_url", -) _SENSITIVE_TEXT_RE = re.compile( r"(?i)(?:\bbearer\b|\bauthorization\b|\bapi[ _-]?key\b|" r"\bpassword\b|\bsecret\b|\btoken\b|\bagk_[A-Za-z0-9_-]+|" @@ -57,7 +44,11 @@ def _canonical_utc(value: str, field_name: str) -> str: def _identifier(value: str, field_name: str) -> str: """Return one non-empty, bounded identifier without control characters.""" identifier = str(value or "").strip() - if not identifier or len(identifier) > 256 or any(ord(char) < 32 for char in identifier): + if ( + not identifier + or len(identifier) > 256 + or any(ord(char) < 32 for char in identifier) + ): raise ValueError(f"{field_name} must be a non-empty bounded identifier") return identifier @@ -65,7 +56,18 @@ def _identifier(value: str, field_name: str) -> str: def _sensitive_key(key: str) -> bool: """Return whether a key names secret or raw customer evidence material.""" normalized = key.casefold().replace("-", "_").replace(" ", "_") - return any(part in normalized for part in _SENSITIVE_KEY_PARTS) + return ( + "api_key" in normalized + or "authorization" in normalized + or "credential" in normalized + or "findings" in normalized + or "password" in normalized + or "raw_evidence" in normalized + or "secret" in normalized + or "snippet" in normalized + or "token" in normalized + or "webhook_url" in normalized + ) def _sanitize_value(value: Any, *, depth: int) -> Any: @@ -87,7 +89,9 @@ def _sanitize_value(value: Any, *, depth: int) -> Any: if not key: raise ValueError("audit summary object must use non-empty string keys") if key in sanitized: - raise ValueError("audit summary object contains a duplicate normalized key") + raise ValueError( + "audit summary object contains a duplicate normalized key" + ) sanitized[key] = ( "[REDACTED]" if _sensitive_key(key) @@ -177,7 +181,9 @@ def _event_payload( normalized_event_id = _identifier(event_id, "event_id") normalized_event_type = str(event_type or "").strip() if not _EVENT_TYPE_RE.fullmatch(normalized_event_type): - raise ValueError("event_type must use lowercase dot, underscore, or hyphen segments") + raise ValueError( + "event_type must use lowercase dot, underscore, or hyphen segments" + ) normalized_previous_hash = str(previous_event_hash or "").strip().lower() if not _HASH_RE.fullmatch(normalized_previous_hash): raise ValueError("previous_event_hash must be a lowercase SHA-256 hex digest") @@ -279,7 +285,9 @@ def verify_audit_chain( if expected_head_hash is not None: normalized_expected_hash = str(expected_head_hash or "").strip().lower() if not _HASH_RE.fullmatch(normalized_expected_hash): - raise ValueError("expected_head_hash must be a lowercase SHA-256 hex digest") + raise ValueError( + "expected_head_hash must be a lowercase SHA-256 hex digest" + ) expected_previous_hash = GENESIS_EVENT_HASH expected_sequence = 1 @@ -296,12 +304,20 @@ def verify_audit_chain( if event.event_hash != recompute_event_hash(event): raise ValueError("event hash mismatch") except ValueError as exc: - raise ValueError(f"audit chain invalid at sequence {expected_sequence}: {exc}") from exc + raise ValueError( + f"audit chain invalid at sequence {expected_sequence}: {exc}" + ) from exc expected_previous_hash = event.event_hash expected_sequence += 1 actual_count = expected_sequence - 1 - if normalized_expected_count is not None and actual_count != normalized_expected_count: + if ( + normalized_expected_count is not None + and actual_count != normalized_expected_count + ): raise ValueError("audit chain checkpoint event count mismatch") - if normalized_expected_hash is not None and expected_previous_hash != normalized_expected_hash: + if ( + normalized_expected_hash is not None + and expected_previous_hash != normalized_expected_hash + ): raise ValueError("audit chain checkpoint head hash mismatch") diff --git a/appguardrail_core/issueops.py b/appguardrail_core/issueops.py index 2c5695a10..638e8d2ec 100644 --- a/appguardrail_core/issueops.py +++ b/appguardrail_core/issueops.py @@ -7,14 +7,6 @@ from typing import Any FAILURES = {"failure", "cancelled", "timed_out", "action_required"} -SECURITY_TERMS = ( - "strix", - "opencode", - "appguardrail", - "trivy", - "codeql", - "security process", -) MARKER_PREFIX = "" DEFAULT_MAX_LOG_CHARS = 30_000 @@ -33,7 +25,17 @@ _LINE_SEPARATOR_TRANSLATION = str.maketrans( { separator: "\n" - for separator in ("\r", "\v", "\f", "\x1c", "\x1d", "\x1e", "\x85", "\u2028", "\u2029") + for separator in ( + "\r", + "\v", + "\f", + "\x1c", + "\x1d", + "\x1e", + "\x85", + "\u2028", + "\u2029", + ) } ) SECRET_RE = [ @@ -73,7 +75,14 @@ def is_failure(conclusion: str | None) -> bool: def is_security_name(*names: str | None) -> bool: """Return whether workflow or job names look security-relevant.""" text = " ".join(name or "" for name in names).lower() - return any(term in text for term in SECURITY_TERMS) + return ( + "strix" in text + or "opencode" in text + or "appguardrail" in text + or "trivy" in text + or "codeql" in text + or "security process" in text + ) def parse_run_url(url: str) -> tuple[str, int]: