Skip to content

Commit f1e242b

Browse files
fix(secrets): replace PEM-header regex with linear-time scan
CodeQL's polynomial-ReDoS gate (py/polynomial-redos) flags the '-----BEGIN PRIVATE KEY' detection regex: the optional ' [A-Z0-9]+' group makes worst-case matching input-dependent. Replace it with a case-normalized anchor scan using monotone-cached str.find probes for both the canonical header and the '-----BEGIN <kind> ' variant (RSA/EC/DSA/OPENSSH/ENCRYPTED/PGP/PKCS8), verified O(N) on adversarial shapes (anchor-heavy 3MB: 0.92s; long-token 2MB: 0.24s; late-header 1.5MB: 0.09s) with identical accept/reject behavior across PEM variants and no change to reject_secrets/redact_secrets contracts.
1 parent c11feb0 commit f1e242b

1 file changed

Lines changed: 27 additions & 7 deletions

File tree

engraphis/core/secrets.py

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,19 +44,39 @@ def _contains_pem_header(value: str) -> bool:
4444
"""
4545
lowered = value.casefold()
4646
start = 0
47-
while True:
48-
index = lowered.find(_PEM_HEADER, start)
49-
if index != -1:
50-
return True
47+
limit = len(lowered)
48+
header_exhausted = False
49+
header_pos = -1
50+
while start <= limit:
51+
# Walk both needles forward without ever rescanning earlier positions:
52+
# whichever of the two anchors occurs first is the only candidate that
53+
# can start a header there, so advance past it after inspection.
54+
# ``find`` results are monotone in ``start``: a miss is final for every
55+
# later start, and a hit at ``h`` is the answer for every start <= h.
56+
# Caching both avoids re-scanning to a far-away hit (or to end-of-string
57+
# on a miss) at every anchor — O(N) total instead of O(N*M).
58+
if not header_exhausted:
59+
if header_pos == -1 or start > header_pos:
60+
header_pos = lowered.find(_PEM_HEADER, start)
61+
if header_pos == -1:
62+
header_exhausted = True
63+
header = header_pos if (header_pos != -1 and start <= header_pos) else -1
5164
anchor = lowered.find(_PEM_HEADER_ALT, start)
65+
if header != -1 and (anchor == -1 or header <= anchor):
66+
return True
5267
if anchor == -1:
5368
return False
54-
tail = lowered[anchor + len(_PEM_HEADER_ALT):]
55-
for token in ("rsa ", "ec ", "dsa ", "openssh ", "encrypted ", "pgp "):
69+
tail_start = anchor + len(_PEM_HEADER_ALT)
70+
if header == tail_start:
71+
return True
72+
tail = lowered[tail_start:tail_start + 40]
73+
for token in ("rsa ", "ec ", "dsa ", "openssh ", "encrypted ",
74+
"pgp ", "pkcs8 "):
5675
if tail.startswith(token) and tail[len(token):].startswith(
5776
"private key-----"):
5877
return True
59-
start = anchor + len(_PEM_HEADER_ALT)
78+
start = tail_start
79+
return False
6080

6181

6282
class _PEMHeaderPattern:

0 commit comments

Comments
 (0)