diff --git a/README.md b/README.md index c038ccf..1494a69 100644 --- a/README.md +++ b/README.md @@ -20,37 +20,38 @@ ## Bundled tools -| Programming Language | Tools | -| -------------------- | ----------------------------------- | -| ansible | ansible-lint | -| apex | pmd | -| arm | checkov | -| aws | checkov | -| bash | shellcheck | -| bom | cdxgen | -| credscan | gitleaks | -| depscan | dep-scan | -| go | gosec, staticcheck | -| groovy | find-sec-bugs | -| java | cdxgen, gradle, find-sec-bugs, pmd | -| jsp | pmd, find-sec-bugs | -| json | jq, jsondiff, jsonschema | -| kotlin | detekt, find-sec-bugs | -| scala | find-sec-bugs | -| kubernetes | checkov, kubesec, kube-score | -| nodejs | cdxgen, yarn, rush | -| php | psalm, phpstan (ide only) | -| plsql | pmd | -| python | cfg-scan (\*), bandit, cdxgen | -| ruby | dep-scan | -| rust | cdxgen | -| serverless | checkov | -| terraform | checkov, tfsec | -| Visual Force (vf) | pmd | -| Apache Velocity (vm) | pmd | -| yaml | yamllint | - -(\*) - Deep analyzer for Python is a built-in feature +| Programming Language | Tools | +| -------------------- | ---------------------------------- | +| ansible | ansible-lint | +| apex | pmd | +| arm | checkov | +| aws | checkov | +| bash | shellcheck | +| bom | cdxgen | +| credscan | gitleaks | +| depscan | dep-scan | +| go | gosec, staticcheck | +| groovy | find-sec-bugs | +| java | cdxgen, gradle, find-sec-bugs, pmd | +| jsp | pmd, find-sec-bugs | +| json | jq, jsondiff, jsonschema | +| kotlin | detekt, find-sec-bugs | +| scala | find-sec-bugs | +| kubernetes | checkov, kubesec, kube-score | +| nodejs | cdxgen, yarn, rush | +| php | psalm, phpstan (ide only) | +| plsql | pmd | +| python | cfg-scan (1), bandit, cdxgen | +| ruby | brakeman (2), dep-scan | +| rust | cdxgen | +| serverless | checkov | +| terraform | checkov, tfsec | +| Visual Force (vf) | pmd | +| Apache Velocity (vm) | pmd | +| yaml | yamllint | + +(1) - Deep analyzer for Python is a built-in feature +(2) - Brakeman is not bundled with scan. Use brakeman with an appropriate license and export the report in json format using `-o reports/source-ruby-report.json` ## Bundled languages/runtime diff --git a/lib/analysis.py b/lib/analysis.py index 4e59a2b..f52942b 100644 --- a/lib/analysis.py +++ b/lib/analysis.py @@ -183,7 +183,12 @@ def summary(sarif_files, depscan_files=None, aggregate_file=None, override_rules report_summary[tool_name].pop("total", None) else: for aresult in results: - sev = aresult["properties"]["issue_severity"].lower() + if aresult.get("properties"): + sev = aresult["properties"]["issue_severity"].lower() + else: + sev = config.get("exttool_default_severity").get( + tool_name.lower(), "medium" + ) report_summary[tool_name][sev] += 1 # Compare against the build break rule to determine status tool_rules = config.get("build_break_rules").get(tool_name, {}) diff --git a/lib/config.py b/lib/config.py index 0454d03..34337bf 100644 --- a/lib/config.py +++ b/lib/config.py @@ -525,6 +525,19 @@ def set(configName, value): ], }, "puppet": ["puppet-lint", "--error-level", "all", "--json", "%(src)s"], + "ruby-ide": { + "source-ruby": [ + "brakeman", + "--skip-libs", + "--no-exit-on-warn", + "--no-exit-on-error", + "-w", + "2", + "--ignore-protected", + "-o", + "%(report_fname_prefix)s.json", + ] + }, "scala": { "audit-scala": [ "java", @@ -758,6 +771,7 @@ def set(configName, value): "cpg": "ShiftLeft NextGen Analyzer", "inspect": "ShiftLeft NextGen Analyzer", "ng-sast": "ShiftLeft NextGen Analyzer", + "source-ruby": "Ruby Source Analyzer", "empty-scan": "Empty Scan Ignore", } @@ -1272,6 +1286,9 @@ def __hash__(self): "snyk-bot", ] +# Default severity for external tools incase the SARIF file is missing severity +exttool_default_severity = {"brakeman": "medium"} + def reload(): # Load any .sastscanrc file from the root diff --git a/lib/convert.py b/lib/convert.py index 84d6cf6..ae0d764 100644 --- a/lib/convert.py +++ b/lib/convert.py @@ -306,6 +306,9 @@ def extract_from_file( issues += rd.get("results", {}).get("failed_checks") else: issues = report_data.get("results", {}).get("failed_checks") + elif tool_name == "source-ruby": + issues = report_data.get("warnings", []) + issues += report_data.get("errors", []) elif isinstance(report_data, list): issues = report_data else: @@ -446,7 +449,6 @@ def report( repo_details = find_repo_details(working_dir) log_uuid = str(uuid.uuid4()) run_uuid = config.get("run_uuid") - # Populate metrics metrics = { "total": 0, diff --git a/lib/inspect.py b/lib/inspect.py index 5194791..89a3f7c 100644 --- a/lib/inspect.py +++ b/lib/inspect.py @@ -367,7 +367,7 @@ def convert_sarif(app_name, repo_context, sarif_files, findings_fname): if not short_desc: short_desc = result.get("message", {}).get("text") ngsev = convert_severity( - result.get("properties", {})["issue_severity"] + result.get("properties", {}).get("issue_severity", "medium") ) # Populate tags tags = [] @@ -405,9 +405,11 @@ def convert_sarif(app_name, repo_context, sarif_files, findings_fname): lineno = location.get("physicalLocation", {})["region"][ "startLine" ] - end_lineno = location.get("physicalLocation", {})[ - "contextRegion" - ]["endLine"] + end_lineno = ( + location.get("physicalLocation", {}) + .get("contextRegion", {}) + .get("endLine") + ) finding = { "app": app_name, "type": "extscan", @@ -419,9 +421,10 @@ def convert_sarif(app_name, repo_context, sarif_files, findings_fname): filename, lineno, end_lineno, - location.get("physicalLocation", {})["region"][ - "snippet" - ]["text"], + location.get("physicalLocation", {}) + .get("region", {}) + .get("snippet", {}) + .get("text", ""), short_desc, ), ), @@ -437,12 +440,14 @@ def convert_sarif(app_name, repo_context, sarif_files, findings_fname): "lineNumber": lineno, "ruleId": rule_id, "ruleName": rule.get("name"), - "contextText": location.get("physicalLocation", {})[ - "region" - ]["snippet"]["text"], - "snippetText": location.get("physicalLocation", {})[ - "contextRegion" - ]["snippet"]["text"], + "contextText": location.get("physicalLocation", {}) + .get("region", {}) + .get("snippet", {}) + .get("text", ""), + "snippetText": location.get("physicalLocation", {}) + .get("contextRegion", {}) + .get("snippet", {}) + .get("text", ""), }, "tags": tags, } diff --git a/lib/issue.py b/lib/issue.py index 46eaa3f..4834ce1 100644 --- a/lib/issue.py +++ b/lib/issue.py @@ -128,7 +128,6 @@ def get_code(self, max_lines=config.get("CODE_SNIPPET_MAX_LINES"), tabbed=False) tmplt = "%i\t%s" if tabbed else "%i %s" for line in moves.xrange(lmin, lmax): text = linecache.getline(self.fname, line) - if isinstance(text, bytes): text = text.decode("utf-8") @@ -240,6 +239,8 @@ def norm_severity(self, severity): def find_severity(self, data): severity = constants.SEVERITY_DEFAULT + if "confidence" in data: + severity = data["confidence"].upper() if "issue_severity" in data or "priority" in data: sev = data.get("issue_severity", data.get("priority")) severity = sev @@ -290,6 +291,8 @@ def get_test_id(self, data): test_id = data["test_id"] if "rule_id" in data: test_id = data["rule_id"] + if "check_name" in data: + test_id = data["check_name"] if "check_id" in data: test_id = data["check_id"] if "tag" in data: @@ -323,7 +326,7 @@ def from_dict(self, data, with_code=True): :param data: Data dictionary from the tools :param with_code: Boolean indicating if code snippet should get added """ - if "code" in data: + if "code" in data and data.get("code"): if str(data["code"]).isdigit(): self.test_id = str(data["code"]) elif len(data.get("code").split()) > 1: @@ -336,9 +339,13 @@ def from_dict(self, data, with_code=True): self.fname = data["filename"] if "fileName" in data: self.fname = data["fileName"] - if "location" in data and "filename" in data["location"]: + if ( + "location" in data + and data.get("location") + and "filename" in data["location"] + ): self.fname = data["location"]["filename"] - if "location" in data and "file" in data["location"]: + if "location" in data and data.get("location") and "file" in data["location"]: self.fname = data["location"]["file"] if "file" in data: self.fname = data["file"] @@ -348,13 +355,15 @@ def from_dict(self, data, with_code=True): self.fname = data["file_path"] self.severity = self.find_severity(data) if "issue_confidence" in data: - self.confidence = data["issue_confidence"] + self.confidence = data["issue_confidence"].upper() if "confidence" in data: - self.confidence = data["confidence"] + self.confidence = data["confidence"].upper() if "issue_text" in data: self.text = data["issue_text"] if "title" in data: self.text = data["title"] + if "warning_type" in data: + self.test = data["warning_type"] if "commitMessage" in data and "commit" in data: if data.get("commitMessage") == "***STAGED CHANGES***": self.text = "Credential in plaintext?\n\nRule: {}, Secret: {}".format( @@ -400,7 +409,7 @@ def from_dict(self, data, with_code=True): self.test = data["message"].replace("\\", " \\ ") else: self.test = data["type"] - if "check_name" in data: + if "check_name" in data and "check_id" in data: self.text = data["check_name"] self.severity = "HIGH" self.confidence = "HIGH" diff --git a/scan b/scan index de8e807..26dce8e 100755 --- a/scan +++ b/scan @@ -186,7 +186,7 @@ def scan_project_types( if dfn: pool.apply_async(dfn, (src, reports_dir, convert, repo_context)) else: - x_scan(type_str) + x_scan(type_str, src, reports_dir, convert, repo_context) except Exception as e: LOG.debug(e) LOG.warning( @@ -227,13 +227,38 @@ def scan(type_list, src, reports_dir, convert, scan_mode, repo_context): pool.join() -def x_scan(type_str): - """Default placeholder scan method for missing scanners""" - LOG.info( - "Is there any open-source scanner for {}? Please let us know :thumbsup:".format( - type_str - ) +def x_scan(type_str, src, reports_dir, convert, repo_context): + """ + Default placeholder scan method for missing scanners + + Args: + type_str Project type + src Project dir + reports_dir Directory for output reports + convert Boolean to enable normalisation of reports json + repo_context Repo context + """ + report_fname = utils.get_report_file( + f"source-{type_str}", reports_dir, convert, ext_name="json" ) + crep_fname = utils.get_report_file( + f"source-{type_str}", reports_dir, convert, ext_name="sarif" + ) + # If there is an existing report available simply use it + if os.path.exists(crep_fname): + LOG.info(f"Found an existing SARIF report at {crep_fname} :thumbsup:") + elif os.path.exists(report_fname) and convert: + convertLib.convert_file( + f"source-{type_str}", + [], + src, + report_fname, + crep_fname, + ) + else: + LOG.info( + f"Is there any open-source scanner for {type_str}? Please let us know :thumbsup:" + ) def python_scan(src, reports_dir, convert, repo_context): diff --git a/test/data/source-ruby.json b/test/data/source-ruby.json new file mode 100644 index 0000000..369d17c --- /dev/null +++ b/test/data/source-ruby.json @@ -0,0 +1,415 @@ +{ + "scan_info": { + "app_path": "/home/prabhu/sandbox/railsgoat", + "rails_version": "6.0.0", + "security_warnings": 17, + "start_time": "2020-12-11 10:44:56 +0000", + "end_time": "2020-12-11 10:44:58 +0000", + "duration": 1.297324975, + "checks_performed": [ + "BasicAuth", + "BasicAuthTimingAttack", + "CSRFTokenForgeryCVE", + "ContentTag", + "CookieSerialization", + "CreateWith", + "CrossSiteScripting", + "DefaultRoutes", + "Deserialize", + "DetailedExceptions", + "DigestDoS", + "DynamicFinders", + "EscapeFunction", + "Evaluation", + "Execute", + "FileAccess", + "FileDisclosure", + "FilterSkipping", + "ForgerySetting", + "HeaderDoS", + "I18nXSS", + "JRubyXML", + "JSONEncoding", + "JSONEntityEscape", + "JSONParsing", + "LinkTo", + "LinkToHref", + "MailTo", + "MassAssignment", + "MimeTypeDoS", + "ModelAttrAccessible", + "ModelAttributes", + "ModelSerialize", + "NestedAttributes", + "NestedAttributesBypass", + "NumberToCurrency", + "PageCachingCVE", + "PermitAttributes", + "QuoteTableName", + "Redirect", + "RegexDoS", + "Render", + "RenderDoS", + "RenderInline", + "ResponseSplitting", + "RouteDoS", + "SQL", + "SQLCVEs", + "SSLVerify", + "SafeBufferManipulation", + "SanitizeMethods", + "SelectTag", + "SelectVulnerability", + "Send", + "SendFile", + "SessionManipulation", + "SessionSettings", + "SimpleFormat", + "SingleQuotes", + "SkipBeforeFilter", + "SprocketsPathTraversal", + "StripTags", + "SymbolDoSCVE", + "TemplateInjection", + "TranslateBug", + "UnsafeReflection", + "ValidationRegex", + "WithoutProtection", + "XMLDoS", + "YAMLParsing" + ], + "number_of_controllers": 17, + "number_of_models": 12, + "number_of_templates": 27, + "ruby_version": "2.7.0", + "brakeman_version": "4.10.0" + }, + "warnings": [ + { + "warning_type": "Remote Code Execution", + "warning_code": 25, + "fingerprint": "07f5143982fb589796b35ec8252bef03d1696639ba57242317926977ae7e0d49", + "check_name": "Deserialize", + "message": "`Marshal.load` called with parameter value", + "file": "app/controllers/password_resets_controller.rb", + "line": 6, + "link": "https://brakemanscanner.org/docs/warning_types/unsafe_deserialization", + "code": "Marshal.load(Base64.decode64(params[:user]))", + "render_path": null, + "location": { + "type": "method", + "class": "PasswordResetsController", + "method": "reset_password" + }, + "user_input": "params[:user]", + "confidence": "Medium" + }, + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "27033d08c8870bed7adc52075447f220c78d5e3b2c42ad05dc2c36625a0f5774", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "app/models/analytics.rb", + "line": 3, + "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": "select(\"#{col}\")", + "render_path": null, + "location": { + "type": "method", + "class": "Analytics", + "method": "hits_by_ip" + }, + "user_input": "col", + "confidence": "Medium" + }, + { + "warning_type": "Dangerous Send", + "warning_code": 23, + "fingerprint": "46e6c67ae633f6424618e3efff2f8325e1babfd6fa09da557147c2cad905d052", + "check_name": "Send", + "message": "User controlled method execution", + "file": "app/controllers/dashboard_controller.rb", + "line": 16, + "link": "https://brakemanscanner.org/docs/warning_types/dangerous_send/", + "code": "self.try(params[:graph])", + "render_path": null, + "location": { + "type": "method", + "class": "DashboardController", + "method": "change_graph" + }, + "user_input": "params[:graph]", + "confidence": "High" + }, + { + "warning_type": "Session Setting", + "warning_code": 26, + "fingerprint": "686c3e9fd57abd6d1e64ec2fbadcc4a786a986c4657af270d73c661a55f2a1ab", + "check_name": "SessionSettings", + "message": "Session cookies should be set to HTTP only", + "file": "config/initializers/session_store.rb", + "line": 4, + "link": "https://brakemanscanner.org/docs/warning_types/session_setting/", + "code": null, + "render_path": null, + "location": null, + "user_input": null, + "confidence": "High" + }, + { + "warning_type": "Session Setting", + "warning_code": 29, + "fingerprint": "715ad9c0d76f57a6a657192574d528b620176a80fec969e2f63c88eacab0b984", + "check_name": "SessionSettings", + "message": "Session secret should not be included in version control", + "file": "config/initializers/secret_token.rb", + "line": 8, + "link": "https://brakemanscanner.org/docs/warning_types/session_setting/", + "code": null, + "render_path": null, + "location": null, + "user_input": null, + "confidence": "High" + }, + { + "warning_type": "Remote Code Execution", + "warning_code": 24, + "fingerprint": "71abb51a4f55c73468d91ccb62ff3719fe1d66f20032c8873d274f5c699d92fe", + "check_name": "UnsafeReflection", + "message": "Unsafe reflection method `constantize` called with parameter value", + "file": "app/controllers/api/v1/mobile_controller.rb", + "line": 17, + "link": "https://brakemanscanner.org/docs/warning_types/remote_code_execution/", + "code": "params[:class].classify.constantize", + "render_path": null, + "location": { + "type": "method", + "class": "Api::V1::MobileController", + "method": "index" + }, + "user_input": "params[:class].classify", + "confidence": "High" + }, + { + "warning_type": "Mass Assignment", + "warning_code": 105, + "fingerprint": "753fe10585146c8cd9f3734a7143946da237745ff2b6162b7f5cb333675b080f", + "check_name": "PermitAttributes", + "message": "Potentially dangerous key allowed for mass assignment", + "file": "app/controllers/users_controller.rb", + "line": 55, + "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/", + "code": "params.require(:user).permit(:email, :admin, :first_name, :last_name)", + "render_path": null, + "location": { + "type": "method", + "class": "UsersController", + "method": "user_params_without_password" + }, + "user_input": ":admin", + "confidence": "High" + }, + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "7fe869279cad8ef1a72c671c1dc746b25b5d310aadd645c8555dae1ff1ba0349", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "app/controllers/users_controller.rb", + "line": 29, + "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": "User.where(\"id = '#{params[:user][:id]}'\")", + "render_path": null, + "location": { + "type": "method", + "class": "UsersController", + "method": "update" + }, + "user_input": "params[:user][:id]", + "confidence": "High" + }, + { + "warning_type": "Cross-Site Scripting", + "warning_code": 114, + "fingerprint": "8275f584e7cced41c26890e574cdbf6804bddff54374058834a562294c99d6f6", + "check_name": "JSONEntityEscape", + "message": "HTML entities in JSON are not escaped by default", + "file": "config/environments/production.rb", + "line": 2, + "link": "https://brakemanscanner.org/docs/warning_types/cross-site_scripting/", + "code": "ActiveSupport::JSON::Encoding.escape_html_entities_in_json = false", + "render_path": null, + "location": null, + "user_input": null, + "confidence": "Medium" + }, + { + "warning_type": "Mass Assignment", + "warning_code": 70, + "fingerprint": "9f34c0a29e2cde79abdccddc790291d548128f0e47f75ed53f499da9249b66b8", + "check_name": "MassAssignment", + "message": "Specify exact keys allowed for mass assignment instead of using `permit!` which allows any keys", + "file": "app/controllers/users_controller.rb", + "line": 50, + "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/", + "code": "params.require(:user).permit!", + "render_path": null, + "location": { + "type": "method", + "class": "UsersController", + "method": "user_params" + }, + "user_input": null, + "confidence": "Medium" + }, + { + "warning_type": "Format Validation", + "warning_code": 30, + "fingerprint": "a21418b38aa77ef73946105fb1c9e3623b7be67a2419b960793871587200cbcc", + "check_name": "ValidationRegex", + "message": "Insufficient validation for `email` using `/.+@.+\\..+/i`. Use `\\A` and `\\z` as anchors", + "file": "app/models/user.rb", + "line": 13, + "link": "https://brakemanscanner.org/docs/warning_types/format_validation/", + "code": null, + "render_path": null, + "location": { + "type": "model", + "model": "User" + }, + "user_input": null, + "confidence": "High" + }, + { + "warning_type": "Command Injection", + "warning_code": 14, + "fingerprint": "b07b623a859b5fda9cd1dbd80aa3d19171cc257c1bce4c6e9204a45e563a85b3", + "check_name": "Execute", + "message": "Possible command injection", + "file": "app/models/benefits.rb", + "line": 15, + "link": "https://brakemanscanner.org/docs/warning_types/command_injection/", + "code": "system(\"cp #{full_file_name} #{data_path}/bak#{Time.zone.now.to_i}_#{file.original_filename}\")", + "render_path": null, + "location": { + "type": "method", + "class": "Benefits", + "method": "Benefits.make_backup" + }, + "user_input": "full_file_name", + "confidence": "Medium" + }, + { + "warning_type": "Remote Code Execution", + "warning_code": 24, + "fingerprint": "ba6443b7682abc8ba6c4ee8fdf13bbc6cfcd3aa7b7bdfcf812ec99f4e5b7a641", + "check_name": "UnsafeReflection", + "message": "Unsafe reflection method `constantize` called with parameter value", + "file": "app/controllers/api/v1/mobile_controller.rb", + "line": 10, + "link": "https://brakemanscanner.org/docs/warning_types/remote_code_execution/", + "code": "params[:class].classify.constantize", + "render_path": null, + "location": { + "type": "method", + "class": "Api::V1::MobileController", + "method": "show" + }, + "user_input": "params[:class].classify", + "confidence": "High" + }, + { + "warning_type": "Cross-Site Request Forgery", + "warning_code": 116, + "fingerprint": "c8697fda60549ca065789e2ea74c94effecef88b2b5483bae17ddd62ece47194", + "check_name": "CSRFTokenForgeryCVE", + "message": "Rails 6.0.0 has a vulnerability that may allow CSRF token forgery. Upgrade to Rails 6.0.3.1 or patch", + "file": "Gemfile.lock", + "line": 223, + "link": "https://groups.google.com/g/rubyonrails-security/c/NOjKiGeXUgw", + "code": null, + "render_path": null, + "location": null, + "user_input": null, + "confidence": "Medium" + }, + { + "warning_type": "File Access", + "warning_code": 16, + "fingerprint": "f63861a0e7ecd271e9f4211fbf6fe843bde57b48d3a11b475a80b27a9abf3759", + "check_name": "SendFile", + "message": "Parameter value used in file name", + "file": "app/controllers/benefit_forms_controller.rb", + "line": 12, + "link": "https://brakemanscanner.org/docs/warning_types/file_access/", + "code": "send_file(params[:type].constantize.new(params[:name]), :disposition => \"attachment\")", + "render_path": null, + "location": { + "type": "method", + "class": "BenefitFormsController", + "method": "download" + }, + "user_input": "params[:type].constantize.new(params[:name])", + "confidence": "High" + }, + { + "warning_type": "Cross-Site Scripting", + "warning_code": 2, + "fingerprint": "febb21e45b226bb6bcdc23031091394a3ed80c76357f66b1f348844a7626f4df", + "check_name": "CrossSiteScripting", + "message": "Unescaped cookie value", + "file": "app/views/layouts/application.html.erb", + "line": 12, + "link": "https://brakemanscanner.org/docs/warning_types/cross-site_scripting/", + "code": "cookies[:font]", + "render_path": [ + { + "type": "controller", + "class": "AdminController", + "method": "dashboard", + "line": 9, + "file": "app/controllers/admin_controller.rb", + "rendered": { + "name": "layouts/application", + "file": "app/views/layouts/application.html.erb" + } + } + ], + "location": { + "type": "template", + "template": "layouts/application" + }, + "user_input": null, + "confidence": "High" + }, + { + "warning_type": "Remote Code Execution", + "warning_code": 24, + "fingerprint": "ff21c7fa4c5ef7f975a711304bcbd91447abe9723c54c59cb8e75a675ef7bf21", + "check_name": "UnsafeReflection", + "message": "Unsafe reflection method `constantize` called with parameter value", + "file": "app/controllers/benefit_forms_controller.rb", + "line": 11, + "link": "https://brakemanscanner.org/docs/warning_types/remote_code_execution/", + "code": "params[:type].constantize", + "render_path": null, + "location": { + "type": "method", + "class": "BenefitFormsController", + "method": "download" + }, + "user_input": "params[:type]", + "confidence": "High" + } + ], + "ignored_warnings": [ + + ], + "errors": [ + + ], + "obsolete": [ + + ] +} \ No newline at end of file diff --git a/test/test_convert.py b/test/test_convert.py index be0784b..a7d5b7d 100644 --- a/test/test_convert.py +++ b/test/test_convert.py @@ -1023,3 +1023,60 @@ def test_pytaint_extract_issue(): "medium": 0, "low": 0, } + + +def test_ruby_convert_issue(): + with tempfile.NamedTemporaryFile(mode="w", encoding="utf-8", delete=True) as cfile: + data = convertLib.report( + "source-ruby", + [], + ".", + {}, + {}, + [ + { + "warning_type": "Remote Code Execution", + "warning_code": 25, + "fingerprint": "07f5143982fb589796b35ec8252bef03d1696639ba57242317926977ae7e0d49", + "check_name": "Deserialize", + "message": "`Marshal.load` called with parameter value", + "file": "app/controllers/password_resets_controller.rb", + "line": 6, + "link": "https://brakemanscanner.org/docs/warning_types/unsafe_deserialization", + "code": "Marshal.load(Base64.decode64(params[:user]))", + "render_path": "", + "location": { + "type": "method", + "class": "PasswordResetsController", + "method": "reset_password", + }, + "user_input": "params[:user]", + "confidence": "Medium", + }, + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "27033d08c8870bed7adc52075447f220c78d5e3b2c42ad05dc2c36625a0f5774", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "app/models/analytics.rb", + "line": 3, + "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": 'select("#{col}")', + "render_path": "", + "location": { + "type": "method", + "class": "Analytics", + "method": "hits_by_ip", + }, + "user_input": "col", + "confidence": "Medium", + }, + ], + cfile.name, + ) + jsondata = json.loads(data) + assert ( + jsondata["runs"][0]["results"][0]["message"]["text"] + == "`Marshal.load` called with parameter value." + )