-
-
Notifications
You must be signed in to change notification settings - Fork 921
DSL Engine Integration #1143
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
DSL Engine Integration #1143
Conversation
WalkthroughAdds a new DSLMatcher for parsing/evaluating version DSLs, integrates it into HTTP response condition checks (version_dsl and cve_version_match), introduces a VMware Aria Operations vulnerability module using the DSL, updates packaging dependencies, and applies minor README/YAML formatting tweaks. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant Module as Vulnerability Module
participant CoreHTTP as core.module_protocols.core_http
participant DSL as core.dsl_matcher.DSLMatcher
participant Target as Target Server
User->>Module: run payload
Module->>CoreHTTP: build and send HTTP request
CoreHTTP->>Target: HTTP request
Target-->>CoreHTTP: HTTP response (status, headers, body)
Note over CoreHTTP: assemble searchable content (headers + body)
alt version_dsl condition
CoreHTTP->>DSL: extract_version_from_response(content, patterns)
DSL-->>CoreHTTP: extracted_version
CoreHTTP->>DSL: parse_dsl_expression(expression, extracted_version)
DSL-->>CoreHTTP: match boolean
CoreHTTP-->>Module: condition result (apply reverse if set)
else cve_version_match condition
CoreHTTP->>DSL: extract_version_from_response(content, patterns)
DSL-->>CoreHTTP: extracted_version
CoreHTTP->>DSL: match_cve_version_range(extracted_version, affected_versions)
DSL-->>CoreHTTP: vulnerable? (boolean)
CoreHTTP-->>Module: condition result (apply reverse if set)
end
Module-->>User: final match/outcome
sequenceDiagram
participant DSL as DSLMatcher
participant Expr as DSL Expression
participant Ver as Target Version
DSL->>DSL: _clean_version(Ver)
alt comma-separated / multiple
DSL->>DSL: _evaluate_multiple(Expr, Ver)
else range ("to" or "-")
DSL->>DSL: _evaluate_range(Expr, Ver)
else wildcard
DSL->>DSL: _evaluate_wildcard(Expr, Ver)
else comparator (~ ^ >= <= > < == !=)
DSL->>DSL: _compare_version(v1, v2, op)
Note over DSL: uses packaging, semver, or numeric-part fallback\n_tilde_/_caret_ handled by helpers
end
DSL-->>Expr: boolean result
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like a good addition to nettacker. I would suggest making a separate PR for this and removing this file from here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please clean this PR and give a better explanation of what you're trying to do. It seems like you're created a new folder and file core_http.py
based of http.py
. It would be better if you integrate your solution within the existing framework.
Thank you @pUrGe12!
I've modified I will also remove CVE_2025_41244.yaml before merging - I thought it would be useful to include as a showcase for what this integration would look like in a template. |
@pUrGe12, with your familiarity with the project I invite you test these changes. I've:
Purpose of this PR: Thanks! |
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
README.md
(0 hunks)core/dsl_matcher.py
(1 hunks)core/module_protocols/core_http.py
(3 hunks)modules/vuln/apache_cve_2021_41773.yaml
(1 hunks)modules/vuln/aria_operations_cve_2025_41244.yaml
(1 hunks)requirements.txt
(1 hunks)
💤 Files with no reviewable changes (1)
- README.md
🧰 Additional context used
🪛 Ruff (0.13.3)
core/dsl_matcher.py
1-1: Shebang is present but file is not executable
(EXE001)
49-49: Do not catch blind exception: Exception
(BLE001)
125-125: Do not catch blind exception: Exception
(BLE001)
182-182: Do not catch blind exception: Exception
(BLE001)
215-215: Consider moving this statement to an else
block
(TRY300)
217-217: Do not catch blind exception: Exception
(BLE001)
247-247: Consider moving this statement to an else
block
(TRY300)
249-249: Do not catch blind exception: Exception
(BLE001)
271-271: Do not catch blind exception: Exception
(BLE001)
307-307: Do not catch blind exception: Exception
(BLE001)
322-322: Do not catch blind exception: Exception
(BLE001)
361-361: Do not catch blind exception: Exception
(BLE001)
434-435: try
-except
-continue
detected, consider logging the exception
(S112)
434-434: Do not catch blind exception: Exception
(BLE001)
479-479: Do not catch blind exception: Exception
(BLE001)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: test
amazing! I will work on these changes today/this weekend! |
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (1)
core/dsl_matcher.py (1)
204-266
: Consider extracting shared logic between tilde and caret operators.
_tilde_compare
and_caret_compare
share significant code structure (version splitting, padding, part comparison, boundary calculation). The primary difference is which version component gets incremented (minor vs. major). Extracting common logic into a helper method would improve maintainability.Example refactor:
def _semantic_range_compare(self, version1, version2, boundary_index): """ Helper for tilde/caret comparison. Args: boundary_index: 0 for major (caret), 1 for minor (tilde) """ try: v1_parts = [int(x) for x in version1.split('.')] v2_parts = [int(x) for x in version2.split('.')] max_len = max(len(v1_parts), len(v2_parts)) v1_parts.extend([0] * (max_len - len(v1_parts))) v2_parts.extend([0] * (max_len - len(v2_parts))) if not self._compare_version_parts(v1_parts, v2_parts, '>='): return False if len(v2_parts) > boundary_index: next_version = v2_parts.copy() next_version[boundary_index] += 1 for i in range(boundary_index + 1, len(next_version)): next_version[i] = 0 return self._compare_version_parts(v1_parts, next_version, '<') return True except Exception: return False def _tilde_compare(self, version1, version2): return self._semantic_range_compare(version1, version2, boundary_index=1) def _caret_compare(self, version1, version2): return self._semantic_range_compare(version1, version2, boundary_index=0)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
core/dsl_matcher.py
(1 hunks)
🧰 Additional context used
🪛 Ruff (0.13.3)
core/dsl_matcher.py
1-1: Shebang is present but file is not executable
(EXE001)
49-49: Do not catch blind exception: Exception
(BLE001)
141-141: Do not catch blind exception: Exception
(BLE001)
198-198: Do not catch blind exception: Exception
(BLE001)
231-231: Consider moving this statement to an else
block
(TRY300)
233-233: Do not catch blind exception: Exception
(BLE001)
263-263: Consider moving this statement to an else
block
(TRY300)
265-265: Do not catch blind exception: Exception
(BLE001)
298-298: Do not catch blind exception: Exception
(BLE001)
334-334: Do not catch blind exception: Exception
(BLE001)
349-349: Do not catch blind exception: Exception
(BLE001)
388-388: Do not catch blind exception: Exception
(BLE001)
461-462: try
-except
-continue
detected, consider logging the exception
(S112)
461-461: Do not catch blind exception: Exception
(BLE001)
506-506: Do not catch blind exception: Exception
(BLE001)
🔇 Additional comments (2)
core/dsl_matcher.py (2)
100-111
: Logical connector parsing successfully implemented.The previous critical issue regarding unparsed logical connectors has been addressed. The code now correctly handles OR (
||
,or
) and AND (&&
,and
) operators with case-insensitive matching for word-based operators, splitting expressions before operator dispatch.
268-297
: Range parsing correctly handles prerelease versions.The previous major issue regarding hyphenated prerelease versions being misdetected as ranges has been fixed. The code now requires spaces around the hyphen (
-
) to treat it as a range separator, avoiding misinterpretation of versions like"8.18.0-beta1"
.
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (4)
core/dsl_matcher.py (4)
336-341
: Escape regex safely when translating wildcardsOnly '.' is escaped; other regex metacharacters can misbehave. Prefer re.escape, then restore wildcard tokens.
Apply this diff:
- # Convert wildcard to regex - pattern = dsl_expression.replace('.', r'\.') - pattern = pattern.replace('*', '.*') - pattern = pattern.replace('?', '.') - pattern = f'^{pattern}$' + # Convert wildcard to regex: escape everything then expand wildcards + escaped = re.escape(dsl_expression) + pattern = '^' + escaped.replace(r'\*', '.*').replace(r'\?', '.') + '$'
433-447
: Swallowed regex errors in extraction; log for diagnosticsExceptions are silently ignored. Log at debug to aid troubleshooting and satisfy static analysis (S112).
Apply this diff:
- except Exception: - continue + except Exception as e: + self.logger.debug(f"extract_version_from_response: pattern {pattern!r} failed: {e}") + continue
34-36
: Broad Exception catches reduce debuggabilityMultiple blind excepts hide real errors and complicate testing. Narrow to specific exceptions (e.g., InvalidVersion, ValueError, RecursionError) and log expression/context. At minimum, convert to “except Exception as e: logger.debug(..., exc_info=True)”.
Example pattern (apply similarly across methods):
- except Exception as e: - self.logger.debug(f"DSL evaluation error: {e}") + except RecursionError as e: + self.logger.debug(f"DSL evaluation recursion error for {dsl_expression!r}: {e}", exc_info=True) + except Exception as e: + self.logger.debug(f"DSL evaluation error for {dsl_expression!r}: {e}", exc_info=True)Ruff BLE001 flagged these spots. As per coding guidelines
Also applies to: 126-128, 183-187, 222-223, 258-259, 291-293, 327-329, 342-343, 381-383, 444-445
1-3
: Shebang without executable bitEither remove the shebang or make the file executable to satisfy EXE001.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
core/dsl_matcher.py
(1 hunks)
🧰 Additional context used
🪛 Ruff (0.13.3)
core/dsl_matcher.py
1-1: Shebang is present but file is not executable
(EXE001)
34-34: Do not catch blind exception: Exception
(BLE001)
126-126: Do not catch blind exception: Exception
(BLE001)
183-183: Do not catch blind exception: Exception
(BLE001)
220-220: Consider moving this statement to an else
block
(TRY300)
222-222: Do not catch blind exception: Exception
(BLE001)
256-256: Consider moving this statement to an else
block
(TRY300)
258-258: Do not catch blind exception: Exception
(BLE001)
291-291: Do not catch blind exception: Exception
(BLE001)
327-327: Do not catch blind exception: Exception
(BLE001)
342-342: Do not catch blind exception: Exception
(BLE001)
381-381: Do not catch blind exception: Exception
(BLE001)
444-445: try
-except
-continue
detected, consider logging the exception
(S112)
444-444: Do not catch blind exception: Exception
(BLE001)
478-478: Do not catch blind exception: Exception
(BLE001)
# Handle logical connectors (OR and AND) before operator dispatch | ||
# Check for OR operators first (|| or 'or') | ||
for splitter, combiner in [('||', any), (' or ', any), ('&&', all), (' and ', all)]: | ||
# Use case-insensitive search for word-based operators | ||
if splitter in ['||', '&&']: | ||
check = splitter in normalized | ||
else: | ||
check = re.search(rf'\s+{re.escape(splitter.strip())}\s+', normalized, re.IGNORECASE) | ||
|
||
if check: | ||
parts = [part.strip() for part in re.split(rf'\s*(?:{re.escape(splitter)})\s*', normalized, flags=re.IGNORECASE) if part.strip()] | ||
return combiner(self._evaluate_dsl(part, target_version) for part in parts) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Connector split bug can cause deep recursion; unify detection and splitting
Detection allows multi-space around textual operators, but split pattern requires single spaces, so expressions like "a or b" won’t split and recurse until RecursionError. Split using compiled regexes and check len(parts) > 1.
Apply this diff:
- # Handle logical connectors (OR and AND) before operator dispatch
- # Check for OR operators first (|| or 'or')
- for splitter, combiner in [('||', any), (' or ', any), ('&&', all), (' and ', all)]:
- # Use case-insensitive search for word-based operators
- if splitter in ['||', '&&']:
- check = splitter in normalized
- else:
- check = re.search(rf'\s+{re.escape(splitter.strip())}\s+', normalized, re.IGNORECASE)
-
- if check:
- parts = [part.strip() for part in re.split(rf'\s*(?:{re.escape(splitter)})\s*', normalized, flags=re.IGNORECASE) if part.strip()]
- return combiner(self._evaluate_dsl(part, target_version) for part in parts)
+ # Handle logical connectors with robust splitting (AND has higher precedence)
+ connectors = [
+ (re.compile(r'\s*\|\|\s*'), any),
+ (re.compile(r'\s*\bOR\b\s*', re.IGNORECASE), any),
+ ]
+ for regex, combiner in connectors:
+ parts = [p.strip() for p in regex.split(normalized) if p.strip()]
+ if len(parts) > 1:
+ return combiner(self._evaluate_dsl(part, target_version) for part in parts)
+ # AND after OR to preserve precedence within each OR branch
+ connectors_and = [
+ (re.compile(r'\s*&&\s*'), all),
+ (re.compile(r'\s*\bAND\b\s*', re.IGNORECASE), all),
+ ]
+ for regex, combiner in connectors_and:
+ parts = [p.strip() for p in regex.split(normalized) if p.strip()]
+ if len(parts) > 1:
+ return combiner(self._evaluate_dsl(part, target_version) for part in parts)
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
# Handle logical connectors (OR and AND) before operator dispatch | |
# Check for OR operators first (|| or 'or') | |
for splitter, combiner in [('||', any), (' or ', any), ('&&', all), (' and ', all)]: | |
# Use case-insensitive search for word-based operators | |
if splitter in ['||', '&&']: | |
check = splitter in normalized | |
else: | |
check = re.search(rf'\s+{re.escape(splitter.strip())}\s+', normalized, re.IGNORECASE) | |
if check: | |
parts = [part.strip() for part in re.split(rf'\s*(?:{re.escape(splitter)})\s*', normalized, flags=re.IGNORECASE) if part.strip()] | |
return combiner(self._evaluate_dsl(part, target_version) for part in parts) | |
# Handle logical connectors with robust splitting (AND has higher precedence) | |
connectors = [ | |
(re.compile(r'\s*\|\|\s*'), any), | |
(re.compile(r'\s*\bOR\b\s*', re.IGNORECASE), any), | |
] | |
for regex, combiner in connectors: | |
parts = [p.strip() for p in regex.split(normalized) if p.strip()] | |
if len(parts) > 1: | |
return combiner(self._evaluate_dsl(part, target_version) for part in parts) | |
# AND after OR to preserve precedence within each OR branch | |
connectors_and = [ | |
(re.compile(r'\s*&&\s*'), all), | |
(re.compile(r'\s*\bAND\b\s*', re.IGNORECASE), all), | |
] | |
for regex, combiner in connectors_and: | |
parts = [p.strip() for p in regex.split(normalized) if p.strip()] | |
if len(parts) > 1: | |
return combiner(self._evaluate_dsl(part, target_version) for part in parts) |
🤖 Prompt for AI Agents
In core/dsl_matcher.py around lines 85 to 97, the connector detection and
splitting are inconsistent: detection allows variable whitespace for textual
operators like " or " but the split pattern requires single spaces which can
leave strings unsplit and trigger deep recursion; replace ad-hoc checks with
precompiled regex patterns for each connector (OR: r'\s*\|\|\s*' and r'\s+or\s+'
and AND: r'\s*&&\s*' and r'\s+and\s+', all with re.IGNORECASE) use the same
regex both to test for a match and to split, and after splitting validate with
if len(parts) > 1 before recursing/combining to avoid accidental recursion.
Ensure parts are stripped and pass them to combiner(self._evaluate_dsl(part,
target_version) for part in parts).
# Check if v1 < next minor version | ||
if len(v2_parts) >= 2: | ||
next_minor = v2_parts.copy() | ||
next_minor[1] += 1 | ||
for i in range(2, len(next_minor)): | ||
next_minor[i] = 0 | ||
|
||
return self._compare_version_parts(v1_parts, next_minor, '<') | ||
|
||
return True | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tilde (~) semantics incorrect when only major is provided
When version2 has only a major segment (e.g., "~1"), the function returns True unconditionally. Use a sane bound (>=1.0.0 and <2.0.0) or return False.
Apply this diff to cap at next major:
- # Check if v1 < next minor version
- if len(v2_parts) >= 2:
+ # Check upper bound: next minor if minor present, else next major
+ if len(v2_parts) >= 2:
next_minor = v2_parts.copy()
next_minor[1] += 1
for i in range(2, len(next_minor)):
next_minor[i] = 0
return self._compare_version_parts(v1_parts, next_minor, '<')
-
- return True
+ else:
+ next_major = v2_parts.copy()
+ next_major[0] += 1
+ for i in range(1, len(next_major)):
+ next_major[i] = 0
+ return self._compare_version_parts(v1_parts, next_major, '<')
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
# Check if v1 < next minor version | |
if len(v2_parts) >= 2: | |
next_minor = v2_parts.copy() | |
next_minor[1] += 1 | |
for i in range(2, len(next_minor)): | |
next_minor[i] = 0 | |
return self._compare_version_parts(v1_parts, next_minor, '<') | |
return True | |
# Check upper bound: next minor if minor present, else next major | |
if len(v2_parts) >= 2: | |
next_minor = v2_parts.copy() | |
next_minor[1] += 1 | |
for i in range(2, len(next_minor)): | |
next_minor[i] = 0 | |
return self._compare_version_parts(v1_parts, next_minor, '<') | |
else: | |
next_major = v2_parts.copy() | |
next_major[0] += 1 | |
for i in range(1, len(next_major)): | |
next_major[i] = 0 | |
return self._compare_version_parts(v1_parts, next_major, '<') |
🧰 Tools
🪛 Ruff (0.13.3)
220-220: Consider moving this statement to an else
block
(TRY300)
🤖 Prompt for AI Agents
In core/dsl_matcher.py around lines 211 to 221, the tilde (~) handling currently
returns True when version2 has only a major (e.g., "~1"); change it to cap at
the next major (treat "~1" as >=1.0.0 and <2.0.0). Specifically, when
len(v2_parts) == 1, build next_major by copying v2_parts, incrementing the major
(next_major[0] += 1) and zeroing any remaining segments, then use
self._compare_version_parts(v1_parts, next_major, '<') instead of returning
True; keep existing behavior for the case with at least two segments.
Sorry for going AWOL, this is still on my to-do list. I will create a new PR with the base branch being |
Proposed change
Your PR description goes here.
Type of change
Checklist
make pre-commit
, it didn't generate any changesmake test
, all tests passed locallySummary by CodeRabbit
New Features
Documentation
Style
Chores