Packages
mob_dev
0.5.11
0.6.23
0.6.22
0.6.21
0.6.20
0.6.19
0.6.18
0.6.17
0.6.16
0.6.15
0.6.14
0.6.13
0.6.12
0.6.11
0.6.10
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.17
0.5.16
0.5.15
0.5.14
0.5.13
0.5.12
0.5.11
0.5.10
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.0
0.3.37
0.3.35
0.3.34
0.3.33
0.3.28
0.3.26
0.3.23
0.3.21
0.3.19
0.3.18
0.3.17
0.3.16
0.3.15
0.3.14
0.3.13
0.3.12
0.3.11
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.18
0.2.17
0.2.15
0.2.14
0.2.13
0.2.12
0.2.11
0.2.10
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.0
Development tooling for the Mob mobile framework
Current section
Files
Jump to
Current section
Files
lib/mob_dev/security_scan/layers/gradle_deps.ex
defmodule MobDev.SecurityScan.Layers.GradleDeps do
@moduledoc """
Audits Android dependencies via `osv-scanner` recursively over
the `android/` directory.
## What gets scanned
`osv-scanner` understands these Android-relevant manifests:
* `gradle.lockfile` — the result of Gradle's [dependency locking][1].
Captures the exact transitive dep tree.
* `buildscript-gradle.lockfile` — same idea, for buildscript classpath.
* `pom.xml` — Maven, occasionally appears in Gradle projects.
Mob's Android template does NOT enable dependency locking by default,
so a fresh `mix mob.new` app will report `:not_applicable` for this
layer until the user opts in. The layer's notes spell out the
remediation.
## Enabling Gradle dependency locking
// android/build.gradle
allprojects {
configurations.all {
resolutionStrategy.activateDependencyLocking()
}
}
// android/app/build.gradle
dependencyLocking {
lockAllConfigurations()
}
Then `cd android && ./gradlew :app:dependencies --write-locks`
creates `gradle.lockfile`.
[1]: https://docs.gradle.org/current/userguide/dependency_locking.html
"""
@behaviour MobDev.SecurityScan.Layer
alias MobDev.SecurityScan.{LayerResult, OsvScanner}
@impl true
def name, do: :gradle_deps
@impl true
def run(opts) do
project_root = Keyword.get(opts, :project_root, File.cwd!())
android_dir = Path.join(project_root, "android")
if File.dir?(android_dir) do
run_scan(android_dir, opts)
else
%LayerResult{
name: :gradle_deps,
status: :not_applicable,
notes: ["no android/ directory at #{android_dir}"]
}
end
end
defp run_scan(android_dir, opts) do
osv_scan = Keyword.get(opts, :osv_scan_fn, &OsvScanner.scan/3)
case osv_scan.({:directory, android_dir}, :gradle_deps, []) do
{:ok, findings} when findings == [] ->
%LayerResult{
name: :gradle_deps,
status: :ok,
findings: [],
tools_used: ["osv-scanner"],
notes: notes_with_lockfile_guidance(android_dir)
}
{:ok, findings} ->
%LayerResult{
name: :gradle_deps,
status: :ok,
findings: findings,
tools_used: ["osv-scanner"],
notes: ["osv-scanner: #{length(findings)} finding(s) under #{android_dir}"]
}
{:error, :not_installed} ->
%LayerResult{
name: :gradle_deps,
status: :tool_missing,
notes: [
"osv-scanner not installed — install: brew install osv-scanner",
"without it Android Gradle deps are not audited"
]
}
{:error, {:scan_failed, reason}} ->
%LayerResult{
name: :gradle_deps,
status: :error,
tools_used: ["osv-scanner"],
error: "osv-scanner failed: #{reason}"
}
{:error, {:not_found, path}} ->
%LayerResult{
name: :gradle_deps,
status: :not_applicable,
notes: ["target path missing: #{path}"]
}
end
end
defp notes_with_lockfile_guidance(android_dir) do
lockfile = Path.join([android_dir, "app", "gradle.lockfile"])
base = "osv-scanner ran cleanly under #{android_dir} (0 findings)"
if File.exists?(lockfile) do
[base, "scanned manifests including gradle.lockfile"]
else
[
base,
"NOTE: no gradle.lockfile present — only declared deps in build.gradle were checked",
"for transitive coverage, enable Gradle dependency locking and run `./gradlew :app:dependencies --write-locks`"
]
end
end
end