OWASP Top 10 For Flutter – M2: Inadequate Supply Chain Security in Flutter
Last updated
Was this helpful?
Last updated
Was this helpful?
In the first installment of this series, OWASP Top 10 For Flutter – M1: Mastering Credential Security in Flutter, we explored the pitfalls of storing and handling credentials in your Flutter apps. That conversation underscored how a single compromised credential can jeopardize user data and brand trust.
Now, let’s turn our focus to M2: Inadequate Supply Chain Security—an equally pressing issue in modern mobile development. Safeguarding your Flutter supply chain is critical, as malicious actors continuously seek footholds through third-party dependencies, SDKs, pipelines, and distribution channels.
When we talk about supply chain security in mobile app development, we mean protecting every component that goes into your app—from third-party libraries and SDKs to build tools and distribution channels. Modern apps depend on these external components to deliver features quickly. Yet, each component also introduces risk since attackers can target them as a “weak link.”Let me remind you with some real-world incidents:
XcodeGhost: A compromised version of Apple’s Xcode tool injected malware into iOS apps, leading to large-scale tampering in the App Store.
Mintegral (SourMint) SDK: A widely used advertising SDK secretly committed ad fraud and spied on user clicks, impacting 1,200+ iOS apps.
These examples highlight that attackers can still infiltrate your app via third-party components even if you don't write malicious code yourself. Flutter developers need to learn from these incidents, especially as the Flutter ecosystem grows rapidly on pub.dev.
To effectively secure your Flutter application, you must clearly understand where and how your supply chain can be compromised. Below is a diagram illustrating the journey from source code to end users:
Let’s delve deeper into each risk area.
Flutter apps rely heavily on packages hosted on pub.dev. While this ecosystem boosts productivity, it can also introduce vulnerabilities:
Malicious or Compromised Packages: Attackers may create Trojan packages disguised as legitimate ones or compromise widely-used packages to inject malicious code. For example, imagine you include a popular HTTP client package (fake_http
) to simplify networking in your app:
If an attacker infiltrates this package on pub.dev, the malicious code can silently intercept user data:
o mitigate this, always review package changes, prefer verified publishers, and monitor for suspicious behaviors.
Outdated Dependencies: Old package versions may contain known vulnerabilities, potentially exposing your app to exploits. Regularly audit and update your dependencies to protect against such risks.
Dependency Confusion: If your build environment isn’t strictly configured, Flutter might pull packages from unintended sources, resulting in compromised or malicious code integration. For example, your internal package named internal_logging
could inadvertently pull a malicious external version if pub.dev is prioritized:
Misconfiguration or missing the private registry could fetch the package from the public source, creating a confusion attack scenario. Configure repositories and enforce private hosting policies.
Third-party SDKs are often integrated directly into Flutter via plugins. Each SDK you use is a potential risk vector:
Obfuscated or Closed-Source SDKs: Closed-source or obfuscated SDKs (common with advertising or analytics plugins) may conceal malicious logic. As a real-world example, Mintegral SDK in 2020 secretly committed ad fraud and collected user data.
Compromised APIs: Attackers can leverage vulnerabilities in third-party backend APIs or services your Flutter app interacts with (Firebase, cloud configurations, etc.). For example, if your app fetches remote config via an insecure API endpoint:
attackers could intercept and inject malicious configurations. Consistently enforce HTTPS, validate responses, and regularly audit third-party APIs.
Unvetted Native Binaries: Flutter plugins that integrate native code via Gradle (Android) or CocoaPods (iOS) carry additional supply chain risks, as native binaries can be tampered with. Always prefer plugins with transparent source codes and verified binaries.
Your CI/CD pipeline itself is vulnerable and can be exploited:
CI/CD Pipeline Access: Attackers gaining access to your pipeline could insert malicious build steps. For example, a compromised GitHub Action could introduce harmful commands:
Securing pipeline access through least privilege, MFA, and audit logging is essential. Always make sure when you are using Curl
and .sh
files, make sure you understand what you are doing.
Exposed Signing Keys If Android (.jks
) or iOS (.p12
) signing keys are compromised, attackers could publish malicious app updates. Secure keys using encrypted storage (e.g., GitHub Secrets, AWS KMS) and regularly rotate keys to mitigate potential damage.
Artifact Tampering: Without verifying final binaries (.apk
, .aab
, .ipa
), an attacker could replace artifacts. As a best practice, generate and store cryptographic hashes post-build:
Compare these hashes before deploying or distributing to ensure artifact integrity.
Managing Flutter dependencies securely is the bedrock of supply chain protection. Here are the essentials:
Let's explore what is possible for vetting packages.
Choosing Trusted Packages: Before including a dependency, thoroughly assess its reliability and security:
Prefer packages with active maintainers, regular updates, and transparent changelogs.
Look for a high popularity score and verified publishers (indicated by a "verified publisher" badge on pub.dev).
Regularly review the package's repository for suspicious or unusual activities.
Limiting Dependencies: Every additional package increases your risk exposure. Evaluate each dependency critically:
Assess if the functionality is critical or can be efficiently implemented internally.
Prefer fewer, well-vetted dependencies over numerous less secure packages.
For example, if you only need a small portion of a large UI toolkit, consider implementing the required component yourself, reducing potential vulnerabilities:
Here is a comprehensive checklist that I recommend you:
[ ] Verified publisher
[ ] Frequent updates (recent commits within the last three months)
[ ] Active community and responsive maintainers
[ ] Comprehensive documentation
[ ] No unresolved critical vulnerabilities
[ ] Clear license terms (MIT, Apache, BSD, etc.)
[ ] Minimal and justified permissions required
[ ] Well-tested with good code coverage
pubspec.lock
)I want to start by mentioning what the Dart team recommends from the official website:
The
pubspec.lock
file is a special case, similar to Ruby'sGemfile.lock
.
For regular packages, don't commit the
pubspec.lock
file. Regenerating thepubspec.lock
file lets you test your package against the latest compatible versions of its dependencies.
For application packages, we recommend that you commit the
pubspec.lock
file. Versioning thepubspec.lock
file ensures changes to transitive dependencies are explicit. Each time the dependencies change due todart pub upgrade
or a change inpubspec.yaml
the difference will be apparent in the lock file.
Now that you understand, let's review what you must do for the lock file.
Commit the Lockfile: Always commit your pubspec.lock
file to your version control system. It records exact dependency versions and their cryptographic hashes, ensuring consistency across builds:
Dependency Hash Checking: Flutter automatically verifies dependency hashes in pubspec.lock
whenever dependencies are fetched:
Suppose the package content changes unexpectedly on the pub.dev, Flutter identifies the discrepancy, protecting you from dependency tampering.
A warning or error appears, alerting you to investigate the issue.
Enforcing Lockfile Integrity in CI/CD: To ensure your CI/CD pipeline uses only validated dependencies, consistently enforce lockfile integrity during builds:
If the content hash of any dependency doesn't match the lockfile, the build process will fail, immediately highlighting the potential security issue:
Here is a quick review in an illustration below:
Protecting your Flutter application’s supply chain requires proactive monitoring and comprehensive documentation of your dependencies. Here’s how to implement robust vulnerability scanning and maintain an accurate Software Bill of Materials (SBOM) to enhance security posture.
Integrating automated vulnerability scanning tools helps identify and mitigate security issues promptly. These tools continuously monitor your project's dependencies listed in your pubspec.lock
, alerting you to potential vulnerabilities and recommending necessary actions.Popular tools for Flutter include:
Snyk: This offers robust integration with GitHub and other CI/CD tools, automatically scanning your dependencies for known vulnerabilities and providing actionable alerts and fixes.
GitHub Dependabot: Automatically scans your repository for outdated or vulnerable dependencies, generates pull requests to update them, and provides detailed vulnerability information.
To integrate Dependabot into your GitHub repository, create a file .github/dependabot.yml
in your repository root:
This configuration ensures Dependabot checks your dependencies daily, automatically creating pull requests if vulnerabilities or updates are found, keeping your Flutter app secure and up-to-date.
An SBOM is a detailed, machine-readable inventory of all software components included in your application, along with their respective versions. Maintaining an SBOM lets you quickly pinpoint vulnerabilities within your software components, significantly reducing response times during security incidents. Here are some benefits of the SBOM that I can think of:
Comprehensive visibility into your application’s dependencies.
Faster identification and remediation of vulnerable components.
Enhanced compliance with security standards and regulatory requirements.
CycloneDX is a widely used standard for generating SBOMs. You can automate SBOM creation in your CI pipeline using tools like CycloneDX CLI:
To use the CLI for Flutter projects, you can follow the steps below on macOS:
However, there are two more straightforward ways to generate SBOM.Either you can use cdxgen
or you can use sbom dart package. I have not used this one myself yet.Let's continue with cdxgen
This command generates an SBOM in JSON format (sbom.json
), which you can store with your artifacts or utilize during security audits.Then, after generating this file we can analyze it with the following command:
Here is an example of the sbom.json
You can analyze your SBOM for security vulnerabilities using:
OWASP Dependency-Track (Setup Guide)
CycloneDX Online Tools (Official Site)
Snyk, OSS Index, or GitHub Advanced Security
Combining Scanning and SBOM in Your CI/CD WorkflowIncorporating vulnerability scanning and SBOM generation into your CI/CD pipeline strengthens your supply chain security. Consider uploading and tracking the SBOM with proper tools from the CD/CI.Now, let's look at the Native and Dart runtime security aspects.
Even with vigilant dependency management, you might still face tampering or repackaging. Flutter offers various ways to verify integrity at runtime:
Packages like freeRASP
and app_integrity_checker
can retrieve the checksums and signing certificate details of your app. Compare these values against known-good references on your server.
Limit Permissions: If a plugin doesn’t need sensitive permissions, don’t grant them. The OS sandbox can prevent malicious code from accessing off-limits features.
Feature Flags: Wrap calls to third-party SDKs or modules in toggles so you can remotely disable them if a supply chain breach is discovered.
freeRASP by Talsec provides runtime application self-protection (RASP) features, including checks for:
Repackaging or signature changes
Debugger and hook detection
Root/Jailbreak detection
FreeRASP configuration is pretty straightforward, and in Flutter, the application is seamless.
Below is an example of using freeRASP in a real Flutter application.
If freeRASP detects suspicious activity (e.g., your app was re-signed), it triggers callbacks. You can warn users, disable certain features, or shut down the app to protect sensitive data.
Your CI/CD pipeline is critical infrastructure; a single vulnerability here can compromise your entire Flutter application. Protecting your build pipeline is just as important as protecting your source code. Here’s how you can comprehensively secure your pipeline:
CI/CD environments should be tightly controlled to prevent unauthorized access and minimize attack surfaces:
Restrict Access: Limit who can access your CI/CD infrastructure and securely store sensitive credentials (signing keys, API tokens).
Ephemeral Build Agents: Utilize ephemeral (temporary) build agents or Docker containers that reset after each build, ensuring clean, uncontaminated environments.
Logging and Auditing: Enable comprehensive logging and auditing features to track changes in CI configurations and identify who triggered builds, facilitating rapid incident response.
Your app’s signing keys are highly sensitive; compromise means attackers could distribute malicious updates:
Android: Store your .jks
keystore files securely outside source control, preferably using encrypted storage such as GitHub Secrets, AWS KMS, or HashiCorp Vault.
iOS: Store your .p12
certificates securely or leverage Apple’s automated code signing capabilities.
Here is an example from Github Action
Ensure the integrity of your build artifacts to detect any unauthorized changes:
Cryptographic Hashing: Generate cryptographic hashes (e.g., SHA256) for your build artifacts. Verify these hashes before deployment.
Achieving reproducible builds allows detection of unauthorized modifications:
Deterministic Environments: Pin exact Flutter and Dart SDK versions, dependencies, and environmental configurations.
Build Provenance: Create and maintain a Software Bill of Materials (SBOM) and integrate the SLSA framework to document build inputs and ensure reproducibility.
Implementing human oversight in your CI/CD processes greatly enhances security:
Manual Approval Steps: Even with automated deployments, integrate manual approval processes to provide additional verification points.
Peer Code Reviews: Enforce mandatory code reviews and pair programming for sensitive changes, especially CI configuration updates.
This workflow triggers a manual approval in GitHub before proceeding with the deployment.
I made this checklist for you to make it easier to ensure you are following best practices.
[ ] CI/CD access and audit regularly.
[ ] Store sensitive credentials securely (GitHub Secrets, AWS KMS, HashiCorp Vault).
[ ] Use ephemeral build environments to ensure isolation.
[ ] Generate cryptographic hashes or signatures for build artifacts.
[ ] Achieve reproducible builds through deterministic configurations.
[ ] Implement SBOM generation and artifact signing tools.
[ ] Enforce manual approvals and thorough code reviews for critical deployments.
Adopting advanced security measures significantly strengthens your application against sophisticated supply chain threats. These techniques provide deeper assurances and comprehensive oversight of your Flutter app development and distribution processes.
The SLSA framework, developed by Google, defines incremental security maturity levels for software artifacts, ensuring transparency and trustworthiness in your build and deployment processes:
Level 1 - Documented Builds: Ensure a repeatable, documented build process.
Level 2 - Signed Provenance: Sign your build artifacts cryptographically to prove authenticity.
Level 3 - Auditable Builds: Conduct your builds in controlled, secure environments, ideally ephemeral or isolated.
Level 4 - Hermetic and Reproducible: Achieve fully reproducible builds with high security standards.
Here is a hypothetical example of achieving Level 2 compliance in Flutter CI using Sigstore:
Reproducible builds allow you to verify that the same source code always produces an identical binary. This enables the detection of tampering and unauthorized modifications:
Pin exact versions of your Flutter and Dart SDKs.
Use standardized Docker or VM environments for consistency across builds.
Here is an example of a deterministic Docker setup:
Continuously audit your dependencies and pipeline:
Regularly review dependency changes for unusual patterns.
Integrate automatic alerts for dependency or artifact integrity issues.
Have a well-defined response plan in case of supply chain compromise:
Prepare rapid revocation strategies for compromised credentials.
Implement remote kill-switches or feature flags to disable compromised components quickly.
Considering what we have learned so far, I have prepared a checklist that you can use to evaluate your process.
[ ] Adopt SLSA principles progressively.
[ ] Establish reproducible and deterministic builds.
[ ] Regularly generate and store SBOMs.
[ ] Implement continuous security scanning and monitoring.
[ ] Use cryptographic signing and artifact attestation.
[ ] Plan and rehearse an emergency response strategy.
Inadequate Supply Chain Security (OWASP M2) goes beyond just picking “safe” packages—it’s about securing the entire lifecycle of your Flutter app, from development to distribution. Attackers increasingly target the supply chain to inject malicious code or tamper with final builds.
Harden Dependencies: Vet packages, lock versions, and monitor vulnerabilities.
Embed Runtime Protection: Tools like freeRASP can detect tampering, ensuring the app your users run is the one you built.
Secure Your CI/CD: Lock down secrets, sign artifacts, and enforce integrity checks in every pipeline stage.
Adopt Advanced Techniques: SLSA, reproducible builds, and SBOMs can give you deeper assurances against hidden threats.
Stay proactive—attackers evolve, and supply chain security must constantly adapt in response. Implement these strategies today to ensure your Flutter apps remain safe, reliable, and worthy of your users’ trust.
Majid Hajian - Azure & AI advocate, Dart & Flutter community leader, Organizer, author