diff --git a/setup.py b/setup.py index 034c1b7..052b9b3 100755 --- a/setup.py +++ b/setup.py @@ -34,7 +34,7 @@ def read(fname): setup( name="sublime-cli", - version="0.0.31", + version="0.0.32", description="Abstraction to interact with the Sublime API.", url="https://sublimesecurity.com/", author="Sublime Security", @@ -56,6 +56,7 @@ def read(fname): ], entry_points={"console_scripts": ["sublime = sublime.cli:main"]}, zip_safe=False, - keywords=["security", "phishing", "analysts", "soc", "threat intelligence", "security-automation", "email security"], + keywords=["security", "phishing", "analysts", "soc", + "threat intelligence", "security-automation", "email security"], download_url="https://github.com/sublime-security/sublime-cli", ) diff --git a/src/sublime/__version__.py b/src/sublime/__version__.py index b938ac7..8c528c8 100644 --- a/src/sublime/__version__.py +++ b/src/sublime/__version__.py @@ -5,4 +5,4 @@ __maintainer__ = "Sublime Security" __email__ = "hi@sublimesecurity.com" __status__ = "BETA" -__version__ = "0.0.30" +__version__ = "0.0.32" diff --git a/src/sublime/cli/formatter.py b/src/sublime/cli/formatter.py index c41e0f6..27ff667 100644 --- a/src/sublime/cli/formatter.py +++ b/src/sublime/cli/formatter.py @@ -15,7 +15,7 @@ from jinja2 import Environment, PackageLoader JINJA2_ENV = Environment(loader=PackageLoader("sublime.cli"), - extensions=['jinja2.ext.loopcontrols']) + extensions=['jinja2.ext.loopcontrols']) colorama.init() ANSI_MARKUP = ansimarkup.AnsiMarkup( @@ -60,14 +60,22 @@ def json_formatter(result, verbose=False, indent=4, offset=0): return string +def filter_none_recursive(item): + """Recursive Filter Out Values""" + if isinstance(item, list): + return [filter_none_recursive(sub_item) for sub_item in item if sub_item is not None and (not isinstance(sub_item, list) or any(sub_sub_item is not None for sub_sub_item in sub_item))] + return item + + @colored_output def analyze_formatter(results, verbose): """Convert Analyze output into human-readable text.""" mql_offset = 3 json_offset = 2 - template_file = "analyze_multi.txt.j2" if len(results) > 1 else "analyze.txt.j2" + template_file = "analyze_multi.txt.j2" if len( + results) > 1 else "analyze.txt.j2" template = JINJA2_ENV.get_template(template_file) - + # calculate total stats sample_result = next(iter(results.values())) summary_stats = { @@ -77,7 +85,7 @@ def analyze_formatter(results, verbose): } rules = [rule for rule in sample_result['rule_results']] queries = [query for query in sample_result['query_results']] - + # separate matched/unmatched messages and distinguish flagged/unflagged rules flagged_messages = [] unflagged_messages = [] @@ -87,9 +95,17 @@ def analyze_formatter(results, verbose): falsey_queries = [] failed_queries = [] for query in result['query_results']: - if query['result']: + result_value = query.get('result') + if isinstance(result_value, list): + # Filter out None values from the result array + filtered_result = filter_none_recursive(result_value) + if filtered_result and any(item is not None for item in filtered_result): + query['result'] = filtered_result + normal_queries.append(query) + elif result_value: + # If the result is not a list and exists (is truthy) normal_queries.append(query) - elif query['success']: + elif query.get('success'): falsey_queries.append(query) else: failed_queries.append(query) @@ -101,10 +117,12 @@ def analyze_formatter(results, verbose): unflagged_rules = [] failed_rules = [] for rule in result['rule_results']: - if rule['result']: + if rule.get('matched'): flagged_rules.append(rule) - all_flagged_rules.add(rule['name']+rule['source']) # no unique identifier - elif rule['success']: + # no unique identifier + all_flagged_rules.add( + rule.get("rule").get('name')+rule.get("rule").get('source')) + elif rule.get('success'): unflagged_rules.append(rule) else: failed_rules.append(rule) @@ -116,30 +134,29 @@ def analyze_formatter(results, verbose): flagged_messages.append(result) else: unflagged_messages.append(result) - + # calculate flagged stats summary_stats['flagged_rules'] = len(all_flagged_rules) summary_stats['flagged_messages'] = len(flagged_messages) # format mql and json outputs - for msg in flagged_messages + unflagged_messages: + for msg in flagged_messages + unflagged_messages: for result in msg['rule_results'] + msg['query_results']: if 'result' in result and (isinstance(result['result'], dict) or isinstance(result['result'], list)): result['result'] = json_formatter( - result['result'], - offset=json_offset, - indent=2) - + result['result'], + offset=json_offset, + indent=2) # TO DO: sort each list of messages by extension and file name (or directory?) return template.render( - stats=summary_stats, - flagged_messages=flagged_messages, - unflagged_messages=unflagged_messages, - rules=rules, - queries=queries, - verbose=verbose) + stats=summary_stats, + flagged_messages=flagged_messages, + unflagged_messages=unflagged_messages, + rules=rules, + queries=queries, + verbose=verbose) def mdm_formatter(results, verbose): @@ -153,6 +170,7 @@ def mdm_formatter(results, verbose): # template = JINJA2_ENV.get_template("message_data_model.txt.j2") # return template.render(results=results, verbose=verbose) + @colored_output def me_formatter(result, verbose): """Convert 'me' output into human-readable text.""" @@ -160,6 +178,7 @@ def me_formatter(result, verbose): return template.render(result=result, verbose=verbose) + @colored_output def feedback_formatter(result, verbose): """Convert 'feedback' output into human-readable text.""" diff --git a/src/sublime/cli/templates/analyze.txt.j2 b/src/sublime/cli/templates/analyze.txt.j2 index 519c5dc..19aa26a 100644 --- a/src/sublime/cli/templates/analyze.txt.j2 +++ b/src/sublime/cli/templates/analyze.txt.j2 @@ -33,7 +33,7 @@ {%- set max_elements = 20 %} {%- set elements_slice = msg.unflagged_rule_results[:max_elements if verbose < 1 else None] %} {%- for rule in elements_slice %} - - {{ rule.name }} + - {{ rule.rule.name }} {%- if verbose %} Source: {{ rule.source }} {# new line #} diff --git a/src/sublime/cli/templates/macros.txt.j2 b/src/sublime/cli/templates/macros.txt.j2 index a6e8265..4bf34ea 100644 --- a/src/sublime/cli/templates/macros.txt.j2 +++ b/src/sublime/cli/templates/macros.txt.j2 @@ -3,7 +3,7 @@
FLAGGED RULES
{# new line #} {%- for rule in msg.flagged_rule_results %} - - {{ rule.name }} + - {{ rule.rule.name}} {%- if verbose %} Source: {{ rule.source }} {# new line #} @@ -16,7 +16,7 @@
FAILED RULES
{# new line #} {%- for rule in msg.failed_rule_results %} - - {{ rule.name }} + - {{ rule.rule.name }} Error: {{ rule.error }} {%- if verbose %} Source: {{ rule.source }} @@ -34,8 +34,8 @@ {%- if msg.normal_query_results | length > 0 %} {%- for query in (msg.normal_query_results) %} -{%- if query.name %} - - {{ query.name }} +{%- if query.query.name %} + - {{ query.query.name }} {%- else %} - Query {{ loop.index }} {%- endif %} @@ -49,8 +49,8 @@ {%- if (verbose and (msg.falsey_query_results | length > 0)) or msg.falsey_query_results|length == 1 %} {%- for query in (msg.falsey_query_results) %} -{%- if query.name %} - - {{ query.name }} +{%- if query.query.name %} + - {{ query.query.name }} {%- else %} - Query {{ loop.index }} {%- endif %} @@ -68,8 +68,8 @@
FAILED QUERIES
{# new line #} {%- for query in msg.failed_query_results %} -{%- if query.name %} - - {{ query.name }} +{%- if query.query.name %} + - {{ query.query.name }} {%- else %} - Query {{ loop.index }} {%- endif %}