diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index 9d1c766f..fa6d59d8 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -4,7 +4,7 @@ on: pull_request: push: branches: - - master + - main env: MIX_ENV: test @@ -12,61 +12,59 @@ env: jobs: mix_test: name: mix test (Elixir ${{matrix.elixir}} | OTP ${{matrix.otp}}) - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: include: - - elixir: '1.7.x' - otp: 22.3.4.26 - - elixir: '1.8.x' - otp: 22.3.4.26 - - elixir: '1.9.x' - otp: 22.3.4.26 - - elixir: '1.10.x' - otp: 22.3.4.26 - - elixir: '1.11.x' - otp: 23.3.4.18 - - elixir: '1.12.x' + - elixir: "1.12.x" otp: 24 - - elixir: '1.13.x' + - elixir: "1.13.x" otp: 25.1 - - elixir: '1.14.x' + - elixir: "1.14.x" otp: 25.1 - - elixir: '1.15.x' + - elixir: "1.15.x" otp: 26.0 - - elixir: '1.16.x' + - elixir: "1.16.x" otp: 26.2 + - elixir: "1.17.x" + otp: 27 + - elixir: "1.18.x" + otp: 27 + - elixir: "1.19.x" + otp: 28 + # We only care about formatting and warnings for the latest version of Elixir + checkFormatAndWarnings: true steps: - - name: Setup Elixir - uses: erlef/setup-beam@v1 - with: - otp-version: ${{ matrix.otp }} - elixir-version: ${{ matrix.elixir }} + - name: Setup Elixir + uses: erlef/setup-beam@v1 + with: + otp-version: ${{ matrix.otp }} + elixir-version: ${{ matrix.elixir }} - - name: Checkout Code - uses: actions/checkout@v3 + - name: Checkout Code + uses: actions/checkout@v3 - - name: Install Dependencies - run: | - mix local.hex --force - mix local.rebar --force - mix deps.get --only test + - name: Install Dependencies + run: | + mix local.hex --force + mix local.rebar --force + mix deps.get --only test - - name: Hex Audit - run: mix hex.audit + - name: Hex Audit + run: mix hex.audit - - name: Check Formatting - if: ${{ matrix.elixir == '1.16.x' }} # we only care about formatting for latest version of Elixir - run: mix format --check-formatted + - name: Check Formatting + if: ${{ matrix.checkFormatAndWarnings }} + run: mix format --check-formatted - - name: Compiles w/o Warnings - if: ${{ matrix.elixir == '1.16.x' }} # we only care about warnings for latest version of Elixir - run: mix compile --warnings-as-errors + - name: Compiles w/o Warnings + if: ${{ matrix.checkFormatAndWarnings }} + run: mix compile --warnings-as-errors - - name: Credo - run: mix credo --all --strict + - name: Credo + run: mix credo --all --strict - - name: Run Tests - run: mix test + - name: Run Tests + run: mix test diff --git a/CHANGELOG.md b/CHANGELOG.md index 7877f36c..46b4c7e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,41 @@ # Changelog +## v0.14.1 + * Enhancements + * Implicitly use `.sobelow-conf` if detected in the root directory rather than + require `--config` switch. The `--no-config` switch is still supported to + prevent any settings from being read in from the file if needed. + * Added guidance for `warn_if_outdated` option in mix deps + * Added support for Elixir v1.19.x + * Bug fixes + * Handled extra config options for app releases in mix.exs + * Properly handle the use of CLI switches and config file settings in the same run. + These would previously clobber each other in unapparent ways leading to + confusing behavior. CLI switch take precedence. + * `.sobelow-conf` now sorted alphabetically + * Fix edwarning from zero argument functions + * Fixed broken skip funcationality + * Fixed broken GitHub Actions CI + * Misc + * Typo fix + +## v0.14.0 + * Removed + * Support for minimum Elixir versions 1.7 - 1.11 (**POTENTIALLY BREAKING** - only applies if you relied on Elixir 1.7 through 1.11, 1.12+ is still supported) + * Enhancements + * Added support for multiple variations of `SQL.query()` + * Added support for `System.shell' command introduced in Elixir v1.12 + * Ignore runtime config during `Config.HSTS` + * Updated developer dependencies (`ex_doc` & `credo`) + * Bug fixes + * Fixed `is_endpoint?` error in main + * Fixed findings normalization bug + * Fixed truncation error + * Misc + * GitHub Actions test matrix updated (hence the large drop in support for old Elixir versions) + * Addressed compiler warnings from Elixir v1.18.x + * Moved from `master` branch to `main` + ## v0.13.0 * Removed * Support for minimum Elixir versions 1.5 & 1.6 (**POTENTIALLY BREAKING** - only applies if you relied on Elixir 1.5 or 1.6, 1.7+ is still supported) @@ -18,7 +54,7 @@ * Compiler Warnings as Errors * Checks Formatting * Added helper `mix test.all` alias - + ## v0.12.2 * Bug fixes * Removed `:castore` and introduced `:verify_none` to quiet warning and unblock escript usage, see [#133](https://github.com/nccgroup/sobelow/issues/133) for more context on why this is necessary @@ -55,19 +91,19 @@ ## v0.11.1 * Enhancements * Sarif output with `--out` flag - * `--strict` flag, which throws compilation errors instead of suppressing them. + * `--strict` flag, which throws compilation errors instead of suppressing them. ## v0.11.0 * Enhancements * Sarif output for GitHub integration * `--flycheck` flag, which reverses output of `--compact` * Bug fixes - * Non-compiling files now return an empty syntax tree instead of + * Non-compiling files now return an empty syntax tree instead of causing Sobelow errors. * Command Injection finding description are properly formatted * Misc - * If you use Sobelow as a standalone utility (i.e. not as part of - a Phoenix application), you now need to install as an escript with + * If you use Sobelow as a standalone utility (i.e. not as part of + a Phoenix application), you now need to install as an escript with `mix escript.install hex sobelow`. * Custom JSON serialization replaced with Jason. @@ -105,7 +141,7 @@ ## v0.9.3 * Enhancements * Improved checks for all aliased functions - + * Bug Fixes * JSON output for Raw findings is now properly normalized * `send_download` correctly flags aliased function calls @@ -124,7 +160,7 @@ * Add `--mark-skip-all` and `--clear-skip` flags * New CSRF via action reuse checks * Sobelow can now be run in umbrella apps - + * Bug Fixes * Fix an error when printing some kinds of variables @@ -134,26 +170,26 @@ * All JSON findings contain `type`, `file`, and `line` keys * "Line" output now refers directly to the vulnerable line * Default output headers have been normalized - - **Note:** If you depend on the structure of the output, this - may be a breaking change. More information can be found at + + **Note:** If you depend on the structure of the output, this + may be a breaking change. More information can be found at [https://sobelow.io](https://sobelow.io). ## v0.7.8 * Enhancements * Add `--threshold` flag * Add module names to finding output - + * Deprecations - * File/Path check has been deprecated - + * File/Path check has been deprecated + * Bug Fixes * Fix inaccurate CSRF details ## v0.7.7 * Enhancements * Add check for insecure websocket settings - + * Bug Fixes * Accept module attributes for application name @@ -231,7 +267,7 @@ * Bug Fixes * Allow RCE module to be appropriately ignored. - + ## v0.6.4 * Enhancements @@ -241,8 +277,8 @@ * Enhancements * Add RCE module to check for code execution via `Code` and `EEx`. - + * Deprecations - * The `--with-code` flag has been changed to `--verbose`. The `--with-code` - flag will continue to work as expected until v1.0.0, but will print a + * The `--with-code` flag has been changed to `--verbose`. The `--with-code` + flag will continue to work as expected until v1.0.0, but will print a warning message. diff --git a/README.md b/README.md index c38bfb28..e8afd44c 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Hex Docs](https://img.shields.io/badge/hex-docs-lightgreen.svg)](https://hexdocs.pm/sobelow/) [![Total Download](https://img.shields.io/hexpm/dt/sobelow.svg)](https://hex.pm/packages/sobelow) [![License](https://img.shields.io/hexpm/l/sobelow.svg)](https://hex.pm/packages/sobelow) -[![Last Updated](https://img.shields.io/github/last-commit/nccgroup/sobelow.svg)](https://github.com/nccgroup/sobelow/commits/master) +[![Last Updated](https://img.shields.io/github/last-commit/sobelow/sobelow.svg)](https://github.com/sobelow/sobelow/commits/main) Sobelow is a security-focused static analysis tool for Elixir & the Phoenix framework. For security researchers, it is a useful @@ -58,7 +58,7 @@ To use Sobelow, you can add it to your application's dependencies. ```elixir def deps do [ - {:sobelow, "~> 0.13", only: [:dev, :test], runtime: false} + {:sobelow, "~> 0.14", only: [:dev, :test], runtime: false, warn_if_outdated: true} ] end ``` @@ -68,10 +68,10 @@ from the command line: $ mix escript.install hex sobelow -To install from the master branch, rather than the latest release, +To install from the `main` branch, rather than the latest release, the following command can be used: - $ mix escript.install github nccgroup/sobelow + $ mix escript.install github sobelow/sobelow ### To Use @@ -134,7 +134,7 @@ relative to the application root. line options. See [Configuration Files](#configuration-files) for more information. - * `--config` - Run Sobelow with configuration file. See [Configuration Files](#configuration-files) + * `--[no-]config` - Run Sobelow with or without configuration file. See [Configuration Files](#configuration-files) for more information. * `--mark-skip-all` - Mark all displayed findings as skippable. @@ -170,10 +170,9 @@ when you first start out using this package - the generated configuration file will be populated with the default values for each option. (This helps in quickly incorporating this package into a pre-existing codebase.) -Now if you want to run Sobelow with the saved configuration, -you can run Sobelow with the `--config` flag. - - $ mix sobelow --config +The `.sobelow-conf` file is automatically used if detected. CLI switches will +take precedence over options in the config file. You can also specify +`--no-config` to prevent any config file settings being used if needed. ## False Positives Sobelow favors over-reporting versus under-reporting. As such, @@ -213,7 +212,7 @@ This list, and other helpful information, can be found on the command line: $ mix help sobelow - + ## Umbrella Apps In order to run Sobelow against all child apps within an umbrella app with a single command, you can add an alias for sobelow in your root `mix.exs` file: @@ -226,7 +225,7 @@ defp aliases do end ``` -If you wish to use configuration files in an umbrella app, create a `.sobelow-conf` in each child application and use the `--config` flag. +If you wish to use configuration files in an umbrella app, create a `.sobelow-conf` in each child application. ## Updates When scanning a project, Sobelow will occasionally check for diff --git a/lib/mix/tasks/sobelow.ex b/lib/mix/tasks/sobelow.ex index 34cf37fa..bd170c05 100644 --- a/lib/mix/tasks/sobelow.ex +++ b/lib/mix/tasks/sobelow.ex @@ -30,7 +30,7 @@ defmodule Mix.Tasks.Sobelow do * `--quiet` - Return no output if there are no findings * `--compact` - Minimal, single-line findings * `--save-config` - Generates a configuration file based on command line options - * `--config` - Run Sobelow with configuration file + * `--[no-]config` - Run Sobelow with or without the configuration file. * `--version` - Output current version of Sobelow ## Ignoring modules @@ -121,7 +121,7 @@ defmodule Mix.Tasks.Sobelow do {opts, _, _} = OptionParser.parse(argv, aliases: @aliases, switches: @switches) root = Keyword.get(opts, :root, ".") - config = Keyword.get(opts, :config, false) + config = Keyword.get(opts, :config, true) conf_file = root <> "/.sobelow-conf" conf_file? = config && File.exists?(conf_file) @@ -134,15 +134,15 @@ defmodule Mix.Tasks.Sobelow do opts = if conf_file? do - {:ok, opts} = File.read!(conf_file) |> Code.string_to_quoted() - opts + {:ok, file_opts} = File.read!(conf_file) |> Code.string_to_quoted() + # CLI args take precedence + Keyword.merge(file_opts, opts) else opts end {verbose, diff, details, private, strict, skip, mark_skip_all, clear_skip, router, exit_on, - format, ignored, ignored_files, all_details, out, threshold, - version} = get_opts(opts, root, conf_file?) + format, ignored, ignored_files, all_details, out, threshold, version} = get_opts(opts, root) set_env(:verbose, verbose) @@ -198,7 +198,7 @@ defmodule Mix.Tasks.Sobelow do # This diff check is strictly used for testing/debugging and # isn't meant for general use. # - # Useful for comapring the output of two different runs of Sobelow + # Useful for comparing the output of two different runs of Sobelow def run_diff(argv) do diff_idx = Enum.find_index(argv, fn i -> i === "--diff" end) {_, list} = List.pop_at(argv, diff_idx) @@ -214,7 +214,7 @@ defmodule Mix.Tasks.Sobelow do Application.put_env(:sobelow, key, value) end - defp get_opts(opts, root, conf_file?) do + defp get_opts(opts, root) do verbose = Keyword.get(opts, :verbose, false) details = Keyword.get(opts, :details, nil) all_details = Keyword.get(opts, :all_details) @@ -249,22 +249,19 @@ defmodule Mix.Tasks.Sobelow do format = out_format(out, format) - {ignored, ignored_files} = - if conf_file? do - {Keyword.get(opts, :ignore, []), - Keyword.get(opts, :ignore_files, []) |> Enum.map(&Path.expand(&1, root))} - else - ignored = - Keyword.get(opts, :ignore, "") - |> String.split(",") + ignored = + case Keyword.get(opts, :ignore, []) do + ignore_str when is_binary(ignore_str) -> String.split(ignore_str, ",") + ignore -> ignore + end - ignored_files = - Keyword.get(opts, :ignore_files, "") - |> String.split(",") - |> Enum.reject(fn file -> file == "" end) - |> Enum.map(&Path.expand(&1, root)) + ignored_files = + case Keyword.get(opts, :ignore_files, []) do + ignore_files when is_list(ignore_files) -> + Enum.map(ignore_files, &Path.expand(&1, root)) - {ignored, ignored_files} + ignore_files_str when is_binary(ignore_files_str) -> + for i <- String.split(ignore_files_str, ",", trim: true), do: Path.expand(i, root) end threshold = diff --git a/lib/sobelow.ex b/lib/sobelow.ex index e7860564..51c0aa8e 100644 --- a/lib/sobelow.ex +++ b/lib/sobelow.ex @@ -209,14 +209,17 @@ defmodule Sobelow do end def log_finding(details, %Finding{} = finding) do - if loggable?(finding.fingerprint, finding.confidence) do + if loggable?(finding, finding.confidence) do Fingerprint.put(finding.fingerprint) FindingLog.add({details, finding}, finding.confidence) end end - def loggable?(fingerprint, severity) do - !(get_env(:skip) && Fingerprint.member?(fingerprint)) && + def loggable?(%Finding{} = finding, severity) do + legacy_skip = finding.legacy_fingerprint && Fingerprint.member?(finding.legacy_fingerprint) + new_skip = finding.fingerprint && Fingerprint.member?(finding.fingerprint) + + !(get_env(:skip) && (new_skip || legacy_skip)) && meets_threshold?(severity) end @@ -239,16 +242,16 @@ defmodule Sobelow do def save_config(conf_file) do conf = [ - verbose: get_env(:verbose), - private: get_env(:private), - skip: get_env(:skip), - router: get_env(:router), exit: get_env(:exit_on), format: get_env(:format), + ignore_files: get_env(:ignored_files), + ignore: get_env(:ignored), out: get_env(:out), + private: get_env(:private), + router: get_env(:router), + skip: get_env(:skip), threshold: get_env(:threshold), - ignore: get_env(:ignored), - ignore_files: get_env(:ignored_files), + verbose: get_env(:verbose), version: get_env(:version) ] @@ -260,7 +263,7 @@ defmodule Sobelow do end if yes? do - File.write!(conf_file, inspect(conf)) + File.write!(conf_file, inspect(conf, limit: :infinity, printable_limit: :infinity)) MixIO.info("Updated .sobelow-conf") end end @@ -474,10 +477,41 @@ defmodule Sobelow do [] -> nil - fingerprints -> + new_fingerprints -> + # Get all findings from the log to match fingerprints to finding details + %{high: highs, medium: meds, low: lows} = FindingLog.log() + all_findings = highs ++ meds ++ lows + + # Create a map of fingerprint -> finding for quick lookup + fingerprint_to_finding = + all_findings + |> Enum.map(fn {_details, finding} -> {finding.fingerprint, finding} end) + |> Map.new() + + # Build skip entries in new format: type,filename_line_n,hash + skip_entries = + new_fingerprints + |> Enum.map(fn fingerprint -> + case Map.get(fingerprint_to_finding, fingerprint) do + %Finding{} = finding -> + # Get relative filename (remove project root) + filename = + Utils.get_root() + |> Utils.normalize_path() + |> (&String.replace_prefix(finding.filename, &1, "")).() + |> Utils.normalize_path() + + "#{finding.type},#{filename}:#{finding.vuln_line_no},#{fingerprint}" + + nil -> + # Should not happen, each fingerprint should have a corresponding finding + fingerprint + end + end) + {:ok, iofile} = :file.open(cfile, [:append]) - fingerprints = Enum.join(fingerprints, "\n") - :file.write(iofile, ["\n", fingerprints]) + entries_str = Enum.join(skip_entries, "\n") + :file.write(iofile, ["\n", entries_str]) :file.close(iofile) end end @@ -493,8 +527,26 @@ defmodule Sobelow do end end - defp load_ignored_fingerprints({:ok, fingerprint}, iofile) do - to_string(fingerprint) |> String.trim() |> Fingerprint.put_ignore() + defp load_ignored_fingerprints({:ok, line}, iofile) do + line_str = to_string(line) |> String.trim() + + # Parse line - could be old format (just hash) or new format (type,filename_line_n,hash) + fingerprint = + case String.split(line_str, ",") do + [fingerprint] when fingerprint != "" -> + # Old format: just the fingerprint hash + fingerprint + + [_type, _filename_line_n, fingerprint] when fingerprint != "" -> + # New format: type,filename_line_n,phash2 - extract phash2 + fingerprint + + _ -> + # Invalid line, skip it + nil + end + + if fingerprint, do: Fingerprint.put_ignore(fingerprint) :file.read_line(iofile) |> load_ignored_fingerprints(iofile) end diff --git a/lib/sobelow/ci/system.ex b/lib/sobelow/ci/system.ex index be029ebe..f3c5c269 100644 --- a/lib/sobelow/ci/system.ex +++ b/lib/sobelow/ci/system.ex @@ -1,6 +1,6 @@ defmodule Sobelow.CI.System do @moduledoc """ - # Command Injection in `System.cmd` + # Command Injection via `System` This submodule of the `CI` module checks for Command Injection vulnerabilities through usage of the `System.cmd` function. @@ -12,7 +12,7 @@ defmodule Sobelow.CI.System do $ mix sobelow -i CI.System """ @uid 2 - @finding_type "CI.System: Command Injection in `System.cmd`" + @finding_type "CI.System: Command Injection via `System` function" use Sobelow.Finding @@ -22,9 +22,17 @@ defmodule Sobelow.CI.System do Finding.init(@finding_type, meta_file.filename, confidence) |> Finding.multi_from_def(fun, parse_def(fun)) |> Enum.each(&Print.add_finding(&1)) + + Finding.init(@finding_type, meta_file.filename, confidence) + |> Finding.multi_from_def(fun, parse_def_shell(fun)) + |> Enum.each(&Print.add_finding(&1)) end def parse_def(fun) do Parse.get_fun_vars_and_meta(fun, 0, :cmd, [:System]) end + + def parse_def_shell(fun) do + Parse.get_fun_vars_and_meta(fun, 0, :shell, [:System]) + end end diff --git a/lib/sobelow/finding.ex b/lib/sobelow/finding.ex index 69e7b4c7..025a329f 100644 --- a/lib/sobelow/finding.ex +++ b/lib/sobelow/finding.ex @@ -12,7 +12,8 @@ defmodule Sobelow.Finding do :fun_name, :fun_line_no, :fun_source, - :fingerprint + :fingerprint, + :legacy_fingerprint ] alias Sobelow.Utils @@ -42,7 +43,11 @@ defmodule Sobelow.Finding do end def fetch_fingerprint(%Sobelow.Finding{} = finding) do - %{finding | fingerprint: fingerprint(finding)} + %{ + finding + | fingerprint: fingerprint(finding), + legacy_fingerprint: legacy_fingerprint(finding) + } end def fingerprint(%Sobelow.Finding{} = finding) do @@ -52,6 +57,18 @@ defmodule Sobelow.Finding do |> (&String.replace_prefix(finding.filename, &1, "")).() |> Utils.normalize_path() + [finding.type, finding.vuln_source, filename, finding.vuln_line_no] + |> :erlang.phash2() + |> Integer.to_string(16) + end + + def legacy_fingerprint(%Sobelow.Finding{} = finding) do + filename = + Utils.get_root() + |> Utils.normalize_path() + |> (&String.replace_prefix(finding.filename, &1, "")).() + |> Utils.normalize_path() + [finding.type, finding.vuln_source, filename, finding.vuln_line_no] |> :erlang.term_to_binary() |> :erlang.md5() diff --git a/lib/sobelow/finding_log.ex b/lib/sobelow/finding_log.ex index 702ec696..2d9322c2 100644 --- a/lib/sobelow/finding_log.ex +++ b/lib/sobelow/finding_log.ex @@ -108,9 +108,21 @@ defmodule Sobelow.FindingLog do defp format_sarif(finding) do [mod, _] = String.split(finding.type, ":", parts: 2) + mod_struct = Sobelow.get_mod(mod) + + # 1) We got a module and exports id/0 ─ call it via apply/3 + rule_id = + if is_atom(mod_struct) and + Code.ensure_loaded?(mod_struct) and + function_exported?(mod_struct, :id, 0) do + apply(mod_struct, :id, []) + else + # 2) Anything else – we have no id + nil + end %{ - ruleId: Sobelow.get_mod(mod).id, + ruleId: rule_id, message: %{ text: finding.type }, diff --git a/lib/sobelow/parse.ex b/lib/sobelow/parse.ex index 27d1d265..6ee68e16 100644 --- a/lib/sobelow/parse.ex +++ b/lib/sobelow/parse.ex @@ -461,7 +461,7 @@ defmodule Sobelow.Parse do end end - defp create_fun_cap(fun, meta, idx) when is_number(idx) do + defp create_fun_cap(fun, meta, idx) when is_number(idx) and idx > 0 do opts = Enum.map(1..trunc(idx), fn i -> {:&, [], [i]} end) {fun, meta, opts} end diff --git a/lib/sobelow/print.ex b/lib/sobelow/print.ex index 6d8d501a..75de649a 100644 --- a/lib/sobelow/print.ex +++ b/lib/sobelow/print.ex @@ -25,7 +25,7 @@ defmodule Sobelow.Print do end def print_finding_metadata(%Finding{} = finding) do - if Sobelow.loggable?(finding.fingerprint, finding.confidence) do + if Sobelow.loggable?(finding, finding.confidence) do do_print_finding_metadata(finding) end end @@ -41,7 +41,7 @@ defmodule Sobelow.Print do end def print_custom_finding_metadata(%Finding{} = finding, headers) do - if Sobelow.loggable?(finding.fingerprint, finding.confidence) do + if Sobelow.loggable?(finding, finding.confidence) do do_print_custom_finding_metadata(finding, headers) end end @@ -65,7 +65,7 @@ defmodule Sobelow.Print do end defp print_compact_finding(finding, details) do - if Sobelow.loggable?(finding.fingerprint, finding.confidence) do + if Sobelow.loggable?(finding, finding.confidence) do do_print_compact_finding(details, finding.confidence) end end @@ -89,7 +89,7 @@ defmodule Sobelow.Print do end defp print_flycheck_finding(finding, details) do - if Sobelow.loggable?(finding.fingerprint, finding.confidence) do + if Sobelow.loggable?(finding, finding.confidence) do do_print_flycheck_finding(details, finding.confidence) end end diff --git a/lib/sobelow/utils.ex b/lib/sobelow/utils.ex index 59f58778..19113633 100644 --- a/lib/sobelow/utils.ex +++ b/lib/sobelow/utils.ex @@ -94,7 +94,8 @@ defmodule Sobelow.Utils do end defp extract_app_name(ast, acc) do - if Keyword.keyword?(ast) && Keyword.get(ast, :app) do + # Only extract the first app name found, don't overwrite with subsequent ones + if acc == [] && Keyword.keyword?(ast) && Keyword.get(ast, :app) do {ast, Keyword.get(ast, :app)} else {ast, acc} diff --git a/mix.exs b/mix.exs index 4bbf6418..21c899a6 100644 --- a/mix.exs +++ b/mix.exs @@ -1,17 +1,25 @@ defmodule Sobelow.Mixfile do use Mix.Project - @source_url "https://github.com/nccgroup/sobelow" - @version "0.13.0" + @source_url "https://github.com/sobelow/sobelow" + @version "0.14.1" def project do [ app: :sobelow, version: @version, - elixir: "~> 1.7", + elixir: "~> 1.12", build_embedded: Mix.env() == :prod, start_permanent: Mix.env() == :prod, deps: deps(), + test_coverage: [tool: ExCoveralls], + preferred_cli_env: [ + coveralls: :test, + "coveralls.detail": :test, + "coveralls.post": :test, + "coveralls.html": :test, + "coveralls.cobertura": :test + ], package: package(), description: "Security-focused static analysis for Elixir & the Phoenix framework", name: "Sobelow", @@ -32,8 +40,9 @@ defmodule Sobelow.Mixfile do {:jason, "~> 1.0"}, # Dev / Test Dependencies - {:ex_doc, "~> 0.20", only: :dev}, - {:credo, "~> 1.6 or ~> 1.7", only: [:dev, :test], runtime: false} + {:ex_doc, "~> 0.37", only: :dev}, + {:credo, "~> 1.7.12", only: [:dev, :test], runtime: false}, + {:excoveralls, "~> 0.18", only: :test} ] end @@ -42,7 +51,7 @@ defmodule Sobelow.Mixfile do licenses: ["Apache-2.0"], maintainers: ["Griffin Byatt", "Holden Oullette"], links: %{ - "Changelog" => "#{@source_url}/blob/master/CHANGELOG.md", + "Changelog" => "#{@source_url}/blob/main/CHANGELOG.md", "GitHub" => @source_url } ] diff --git a/mix.lock b/mix.lock index 9517bce7..8cd47a8f 100644 --- a/mix.lock +++ b/mix.lock @@ -1,12 +1,13 @@ %{ "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, - "credo": {:hex, :credo, "1.7.4", "68ca5cf89071511c12fd9919eb84e388d231121988f6932756596195ccf7fd35", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9cf776d062c78bbe0f0de1ecaee183f18f2c3ec591326107989b054b7dddefc2"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.29", "149d50dcb3a93d9f3d6f3ecf18c918fb5a2d3c001b5d3305c926cddfbd33355b", [:mix], [], "hexpm", "4902af1b3eb139016aed210888748db8070b8125c2342ce3dcae4f38dcc63503"}, - "ex_doc": {:hex, :ex_doc, "0.29.1", "b1c652fa5f92ee9cf15c75271168027f92039b3877094290a75abcaac82a9f77", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "b7745fa6374a36daf484e2a2012274950e084815b936b1319aeebcf7809574f6"}, - "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"}, - "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, - "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"}, - "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"}, + "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, + "ex_doc": {:hex, :ex_doc, "0.37.3", "f7816881a443cd77872b7d6118e8a55f547f49903aef8747dbcb345a75b462f9", [:mix], [{:earmark_parser, "~> 1.4.42", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "e6aebca7156e7c29b5da4daa17f6361205b2ae5f26e5c7d8ca0d3f7e18972233"}, + "excoveralls": {:hex, :excoveralls, "0.18.5", "e229d0a65982613332ec30f07940038fe451a2e5b29bce2a5022165f0c9b157e", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "523fe8a15603f86d64852aab2abe8ddbd78e68579c8525ae765facc5eae01562"}, + "file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"}, + "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, + "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, + "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, + "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, } diff --git a/test/fixtures/utils/mix_with_releases.exs b/test/fixtures/utils/mix_with_releases.exs new file mode 100644 index 00000000..d413f301 --- /dev/null +++ b/test/fixtures/utils/mix_with_releases.exs @@ -0,0 +1,18 @@ +defmodule App.Mixfile do + use Mix.Project + + def project do + [ + app: :app, + version: "0.1.0", + releases: [ + app: [ + applications: [ + opentelemetry_exporter: :permanent, + opentelemetry: :temporary + ] + ] + ] + ] + end +end diff --git a/test/fixtures/utils/mix_with_releases_my_app.exs b/test/fixtures/utils/mix_with_releases_my_app.exs new file mode 100644 index 00000000..6a9eb53e --- /dev/null +++ b/test/fixtures/utils/mix_with_releases_my_app.exs @@ -0,0 +1,18 @@ +defmodule MyApp.Mixfile do + use Mix.Project + + def project do + [ + app: :my_app, + version: "0.1.0", + releases: [ + my_app: [ + applications: [ + opentelemetry_exporter: :permanent, + opentelemetry: :temporary + ] + ] + ] + ] + end +end diff --git a/test/fixtures/utils/mix_with_releases_test.exs b/test/fixtures/utils/mix_with_releases_test.exs new file mode 100644 index 00000000..4e016d76 --- /dev/null +++ b/test/fixtures/utils/mix_with_releases_test.exs @@ -0,0 +1,18 @@ +defmodule Test.Mixfile do + use Mix.Project + + def project do + [ + app: :test, + version: "0.1.0", + releases: [ + test: [ + applications: [ + opentelemetry_exporter: :permanent, + opentelemetry: :temporary + ] + ] + ] + ] + end +end diff --git a/test/utils_test.exs b/test/utils_test.exs index fe64a682..dedf5e76 100644 --- a/test/utils_test.exs +++ b/test/utils_test.exs @@ -4,4 +4,18 @@ defmodule SobelowTest.UtilsTest do test "Utils.get_app_name/1 understands module attributes" do assert Sobelow.Utils.get_app_name("./test/fixtures/utils/mix.exs") == "foo_bar" end + + test "Utils.get_app_name/1 handles releases config correctly for app named :app" do + assert Sobelow.Utils.get_app_name("./test/fixtures/utils/mix_with_releases.exs") == "app" + end + + test "Utils.get_app_name/1 handles releases config correctly for app named :my_app" do + assert Sobelow.Utils.get_app_name("./test/fixtures/utils/mix_with_releases_my_app.exs") == + "my_app" + end + + test "Utils.get_app_name/1 handles releases config correctly for app named :test" do + assert Sobelow.Utils.get_app_name("./test/fixtures/utils/mix_with_releases_test.exs") == + "test" + end end