diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S1313.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S1313.html index a183443581..0ba24b69b4 100644 --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S1313.html +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S1313.html @@ -1,42 +1,23 @@ -
Hardcoding IP addresses is security-sensitive. It has led in the past to the following vulnerabilities:
+IP addresses hardcoded in source code couple the application to a specific infrastructure configuration. Today’s services have an ever-changing +architecture due to their scaling and redundancy needs. When an IP address changes, every hardcoded occurrence must be found and updated, which has an +impact on development, delivery, and deployment:
Today’s services have an ever-changing architecture due to their scaling and redundancy needs. It is a mistake to think that a service will always -have the same IP address. When it does change, the hardcoded IP will have to be modified too. This will have an impact on the product development, -delivery, and deployment:
-Last but not least it has an effect on application security. Attackers might be able to decompile the code and thereby discover a potentially -sensitive address. They can perform a Denial of Service attack on the service, try to get access to the system, or try to spoof the IP address to -bypass security checks. Such attacks can always be possible, but in the case of a hardcoded IP address solving the issue will take more time, which -will increase an attack’s impact.
-The disclosed IP address is sensitive, e.g.:
-There is a risk if you answered yes to any of these questions.
-Don’t hard-code the IP address in the source code, instead make it configurable with environment variables, configuration files, or a similar -approach. Alternatively, if confidentially is not required a domain name can be used since it allows to change the destination quickly without having -to rebuild the software.
--String ip = "192.168.12.42"; // Sensitive -Socket socket = new Socket(ip, 6667); --
-String ip = System.getenv("IP_ADDRESS"); // Compliant
-Socket socket = new Socket(ip, 6667);
-
-No issue is reported for the following cases because they are not considered sensitive:
+Hardcoding an IP address embeds infrastructure configuration directly into the application. This means any change to the network environment—such +as moving a service to a different host or scaling horizontally—requires a code modification and a full redeployment. Unlike a domain name, a +hardcoded address also makes it harder to use different values across environments such as development, staging, and production.
+A hardcoded IP address is the same in every environment the application runs in. This makes it difficult to point development, staging, and +production builds at different infrastructure without modifying the source code.
+Any change to the target host—such as migrating a service, scaling out, or rotating infrastructure—requires a code change and a full redeployment +cycle. This prevents operational teams from making infrastructure adjustments independently and slows down incident response.
+No issue is reported for the following well-known, special-purpose addresses, as they do not represent configurable infrastructure endpoints:
The following code contains a hardcoded IP address instead of reading it from configuration or environment variables.
++String ip = "192.168.12.42"; // Noncompliant +Socket socket = new Socket(ip, 6667); ++
+String ip = System.getenv("IP_ADDRESS");
+Socket socket = new Socket(ip, 6667);
+
+The use of a non-standard algorithm is dangerous because a determined attacker may be able to break the algorithm and compromise whatever data has
-been protected. Standard algorithms like SHA-256, SHA-384, SHA-512, … should be used instead.
This rule tracks creation of java.security.MessageDigest subclasses.
+Cryptographic operations should use proven, standard algorithms rather than custom implementations.
+Why is this an issue?
+Non-standard cryptographic algorithms are those that have not been publicly vetted by the security community or that implement cryptographic +primitives in a custom way. Creating a custom cryptographic algorithm by subclassing standard cryptographic base classes bypasses the rigorous testing +and peer review that established algorithms undergo. Custom implementations are likely to contain subtle flaws that could be exploited to break the +protection the algorithm is supposed to provide.
+What is the potential impact?
+Data compromise
+When an attacker discovers a flaw in a custom cryptographic algorithm, they may be able to decrypt any data protected by it. Depending on the +application, this could expose passwords, personal data, financial records, or other sensitive information.
+How to fix it
+This rule detects custom implementations of
+java.security.MessageDigest.Code examples
+Noncompliant code example
+public class MyCryptographicAlgorithm extends MessageDigest { ... }-Compliant Solution
-+Compliant solution
+MessageDigest digest = MessageDigest.getInstance("SHA-256");-See
+Resources
+Documentation
+
JNDI supports the deserialization of objects from LDAP directories, which can lead to remote code execution.
-This rule raises an issue when an LDAP search query is executed with SearchControls configured to allow deserialization.
There is a risk if you answered yes to any of those questions.
-It is recommended to disable deserialization of LDAP objects.
-+Why is this an issue?
+JNDI can deserialize Java objects returned by an LDAP directory when
+SearchControlsis configured with the +returningObjFlagparameter set totrue. If the LDAP directory is untrusted or has been compromised, an attacker can inject a +malicious serialized object that executes arbitrary code on the server when deserialized.What is the potential impact?
+If successfully exploited, an attacker who can control the content of the LDAP directory can craft a malicious serialized object that, when +deserialized, executes arbitrary code on the server. This can lead to full system compromise, including data exfiltration, malware installation, or +lateral movement within the network.
+How to fix it
+Set the
+returningObjFlagparameter tofalsewhen constructingSearchControlsto prevent JNDI from +deserializing objects returned by the LDAP directory.Code examples
+Noncompliant code example
+DirContext ctx = new InitialDirContext(); // ... ctx.search(query, filter, new SearchControls(scope, countLimit, timeLimit, attributes, - true, // Noncompliant; allows deserialization + true, // Noncompliant: allows deserialization deref));-Compliant Solution
-+Compliant solution
+DirContext ctx = new InitialDirContext(); // ... ctx.search(query, filter, new SearchControls(scope, countLimit, timeLimit, attributes, - false, // Compliant + false, deref));-See
+Resources
+Articles & blog posts
+
Setting JavaBean properties is security sensitive. Doing it with untrusted values has led in the past to the following vulnerability:
-JavaBeans can have their properties or nested properties set by population functions. An attacker can leverage this feature to push into the -JavaBean malicious data that can compromise the software integrity. A typical attack will try to manipulate the ClassLoader and finally execute -malicious code.
-This rule raises an issue when:
-class.classLoaderThere is a risk if you answered yes to any of those questions.
-Sanitize all values used as JavaBean properties.
-Don’t set any sensitive properties. Keep full control over which properties are set. If the property names are provided by an unstrusted source, -filter them with a whitelist.
-++Setting JavaBean properties from untrusted user input can allow an attacker to manipulate arbitrary object properties, including sensitive +internals such as
+class.classLoader.Why is this an issue?
+JavaBean property population functions such as
+BeanUtils.populate(),BeanUtils.setProperty(), +BeanUtilsBean.populate(), andBeanUtilsBean.setProperty()from Apache Commons BeanUtils, and +BeanWrapper.setPropertyValue()andBeanWrapper.setPropertyValues()from Spring, allow setting arbitrary bean properties by +name. When the property names or values are derived from untrusted input without validation, an attacker can set sensitive properties — for example, +class.classLoader— and use them to load and execute malicious code.What is the potential impact?
+If successfully exploited, this vulnerability can lead to remote code execution, full application compromise, data exfiltration, or lateral +movement within the network.
+How to fix it
+Code examples
+Noncompliant code example
+Company bean = new Company(); HashMap map = new HashMap(); Enumeration names = request.getParameterNames(); @@ -33,17 +20,35 @@-Sensitive Code Example
String name = (String) names.nextElement(); map.put(name, request.getParameterValues(name)); } -BeanUtils.populate(bean, map); // Sensitive: "map" is populated with data coming from user input, here "request.getParameterNames()" +BeanUtils.populate(bean, map); // Noncompliant: "map" is populated with data coming from user input, here "request.getParameterNames()"See
+Compliant solution
++Company bean = new Company(); +HashMap map = new HashMap(); +Set<String> allowedProperties = Set.of("name", "address"); // define allowed properties +Enumeration names = request.getParameterNames(); +while (names.hasMoreElements()) { + String name = (String) names.nextElement(); + if (allowedProperties.contains(name)) { + map.put(name, request.getParameterValues(name)); + } +} +BeanUtils.populate(bean, map); ++Resources
+Articles & blog posts
+
Using unsafe Jackson deserialization configuration is security-sensitive. It has led in the past to the following vulnerabilities:
-Jackson can be configured to allow Polymorphic Type Handling, which may expose the application to deserialization attacks.
+When Jackson is configured to allow Polymorphic Type Handling (aka PTH), formerly known as Polymorphic Deserialization, "deserialization gadgets" may allow an attacker to perform remote code execution.
This rule raises an issue when:
enableDefaultTyping() is called on an instance of com.fasterxml.jackson.databind.ObjectMapper or
org.codehaus.jackson.map.ObjectMapper.@JsonTypeInfo is set at class, interface or field levels and configured with use =
+ - The annotation
@JsonTypeInfo is set at class, interface or field levels and configured with use =
JsonTypeInfo.Id.CLASS or use = Id.MINIMAL_CLASS.
There is a risk if you answered yes to any of those questions.
-jackson-databind blocking the already discovered "deserialization gadgets".ObjectMapper.enableDefaultTyping().@JsonTypeInfo(use = Id.NAME) instead of @JsonTypeInfo(use = Id.CLASS) or @JsonTypeInfo(use =
- Id. MINIMAL_CLASS) and so rely on @JsonTypeName and @JsonSubTypes.++What is the potential impact?
+If an attacker can control the serialized data, they can craft malicious payloads that exploit deserialization gadgets present on the classpath. +This can lead to remote code execution, allowing the attacker to run arbitrary commands on the server.
+How to fix it
+Code examples
+Noncompliant code example
+ObjectMapper mapper = new ObjectMapper(); -mapper.enableDefaultTyping(); // Sensitive +mapper.enableDefaultTyping(); // Noncompliant + +@JsonTypeInfo(use = Id.CLASS) // Noncompliant +abstract class PhoneNumber { +}--@JsonTypeInfo(use = Id.CLASS) // Sensitive +Compliant solution
++ObjectMapper mapper = new ObjectMapper(); + +@JsonTypeInfo(use = Id.NAME) abstract class PhoneNumber { }-See
+Resources
+Documentation
+
This rule is deprecated, and will eventually be removed.
Successful Zip Bomb attacks occur when an application expands untrusted archive files without controlling the size of the expanded data, which can lead to denial of service. A Zip bomb is usually a malicious archive file of a few kilobytes of compressed data but turned into gigabytes of uncompressed data. To achieve this extreme compression ratio, attackers will compress irrelevant data (eg: a long string of repeated bytes).
-Archives to expand are untrusted and:
-There is a risk if you answered yes to any of those questions.
-+Why is this an issue?
+Expanding archive files without controlling the size of the extracted data can lead to denial of service. A Zip bomb is a malicious archive of a +few kilobytes of compressed data that expands into gigabytes of uncompressed data by compressing highly repetitive content. Applications that fail to +validate the number of entries, total uncompressed size, or compression ratio of an archive are vulnerable to this attack.
+What is the potential impact?
+Denial of service
+An attacker who can supply a malicious archive can exhaust the server’s disk space, memory, or CPU by triggering unbounded decompression. This can +make the application completely unavailable to legitimate users and may require manual intervention to recover the affected system.
+How to fix it in Java SE
+Validate the number of entries, total uncompressed size, and compression ratio when extracting archive files. Do not rely on getSize to retrieve the uncompressed size, as this value +comes from archive headers that can be forged; calculate the actual size while reading.
+Code examples
+Noncompliant code example
+File f = new File("ZipBomb.zip"); ZipFile zipFile = new ZipFile(f); -Enumeration<? extends ZipEntry> entries = zipFile.entries(); // Sensitive +Enumeration<? extends ZipEntry> entries = zipFile.entries(); // Noncompliant while(entries.hasMoreElements()) { ZipEntry ze = entries.nextElement(); @@ -30,19 +28,16 @@-Sensitive Code Example
Files.copy(zipFile.getInputStream(ze), out.toPath(), StandardCopyOption.REPLACE_EXISTING); }Compliant Solution
-Do not rely on getsize to retrieve the size of an -uncompressed entry because this method returns what is defined in the archive headers which can be forged by attackers, instead calculate the actual -entry size when unzipping it:
-+Compliant solution
+File f = new File("ZipBomb.zip"); ZipFile zipFile = new ZipFile(f); Enumeration<? extends ZipEntry> entries = zipFile.entries(); int THRESHOLD_ENTRIES = 10000; -int THRESHOLD_SIZE = 1000000000; // 1 GB +long THRESHOLD_SIZE = 1000000000L; // 1 GB double THRESHOLD_RATIO = 10; -int totalSizeArchive = 0; +long totalSizeArchive = 0; int totalEntryArchive = 0; while(entries.hasMoreElements()) { @@ -56,7 +51,7 @@-Compliant Solution
byte[] buffer = new byte[2048]; int totalSizeEntry = 0; - while((nBytes = in.read(buffer)) > 0) { // Compliant + while((nBytes = in.read(buffer)) > 0) { out.write(buffer, 0, nBytes); totalSizeEntry += nBytes; totalSizeArchive += nBytes; @@ -79,7 +74,12 @@Compliant Solution
} }See
+Resources
+Articles & blog posts
+
Having a permissive Cross-Origin Resource Sharing policy is security-sensitive. It has led in the past to the following vulnerabilities:
-Same origin policy in browsers prevents, by default and for -security-reasons, a javascript frontend to perform a cross-origin HTTP request to a resource that has a different origin (domain, protocol, or port) -from its own. The requested target can append additional HTTP headers in response, called CORS, that act like directives for the browser and change the access control policy -/ relax the same origin policy.
-Access-Control-Allow-Origin: untrustedwebsite.com.Access-Control-Allow-Origin: *origin header.There is a risk if you answered yes to any of those questions.
-Access-Control-Allow-Origin header should be set only for a trusted origin and for specific resources.Access-Control-Allow-Origin header. Prefer whitelisting domains over blacklisting or
- allowing any domain (do not use * wildcard nor blindly return the Origin header content without any checks).Java servlet framework:
-+Setting an overly permissive Cross-Origin Resource Sharing (CORS) policy allows malicious websites to read responses from your application on +behalf of authenticated users.
+Why is this an issue?
+Same-origin policy in browsers prevents JavaScript from +making cross-origin HTTP requests to resources with a different origin (domain, protocol, or port). The Cross-Origin Resource Sharing (CORS) mechanism allows servers to relax this +restriction by including
+Access-Control-Allow-Originresponse headers that tell browsers which origins are permitted.Setting the
+Access-Control-Allow-Originheader to a wildcard (*) or dynamically reflecting a user-supplied +Originheader without validation completely disables same-origin protection for the affected resource.What is the potential impact?
+Sensitive data exposure
+When CORS restrictions are disabled, a malicious website visited by an authenticated user can issue cross-origin requests to the vulnerable +application and read the responses. This allows attackers to steal sensitive data accessible to the victim, such as account details, API keys, or +private application data.
+Account takeover
+If the application is also configured with
+Access-Control-Allow-Credentials: true, the browser will include cookies and HTTP +authentication headers in cross-origin requests. Attackers can then perform authenticated operations on behalf of the victim, potentially leading to +full account takeover or unauthorized data modification.How to fix it in Servlet
+Set the
+Access-Control-Allow-Originheader to a specific trusted origin.Code examples
+Noncompliant code example
+@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setHeader("Content-Type", "text/plain; charset=utf-8"); - resp.setHeader("Access-Control-Allow-Origin", "*"); // Sensitive + resp.setHeader("Access-Control-Allow-Origin", "*"); // Noncompliant resp.setHeader("Access-Control-Allow-Credentials", "true"); resp.setHeader("Access-Control-Allow-Methods", "GET"); resp.getWriter().write("response"); }-Spring MVC framework:
-
-@CrossOrigin // Sensitive +-Compliant solution
++@Override +protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + resp.setHeader("Content-Type", "text/plain; charset=utf-8"); + resp.setHeader("Access-Control-Allow-Origin", "https://trustedwebsite.com"); + resp.setHeader("Access-Control-Allow-Credentials", "true"); + resp.setHeader("Access-Control-Allow-Methods", "GET"); + resp.getWriter().write("response"); +} ++How to fix it in Spring
+Use Spring’s CORS configuration to restrict allowed origins to specific trusted domains, and validate user-supplied origins against an +allow-list.
+Code examples
+Noncompliant code example
+CrossOrigin +annotation:
++@CrossOrigin // Noncompliant @RequestMapping("") public class TestController { public String home(ModelMap model) { @@ -49,56 +58,39 @@-Sensitive Code Example
} }
+-cors.CorsConfiguration +class:
+CorsConfiguration config = new CorsConfiguration(); -config.addAllowedOrigin("*"); // Sensitive -config.applyPermitDefaultValues(); // Sensitive +config.addAllowedOrigin("*"); // Noncompliant +config.applyPermitDefaultValues(); // Noncompliant-
+servlet.config.annotation.CorsRegistration:
+class Insecure implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") - .allowedOrigins("*"); // Sensitive + .allowedOrigins("*"); // Noncompliant } }User-controlled origin:
-+public ResponseEntity<String> userControlledOrigin(@RequestHeader("Origin") String origin) { HttpHeaders responseHeaders = new HttpHeaders(); - responseHeaders.add("Access-Control-Allow-Origin", origin); // Sensitive + responseHeaders.add("Access-Control-Allow-Origin", origin); // Noncompliant return new ResponseEntity<>("content", responseHeaders, HttpStatus.CREATED); }-Compliant Solution
-Java Servlet framework:
--@Override -protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - resp.setHeader("Content-Type", "text/plain; charset=utf-8"); - resp.setHeader("Access-Control-Allow-Origin", "trustedwebsite.com"); // Compliant - resp.setHeader("Access-Control-Allow-Credentials", "true"); - resp.setHeader("Access-Control-Allow-Methods", "GET"); - resp.getWriter().write("response"); -} --Spring MVC framework:
-
-@CrossOrigin("trustedwebsite.com") // Compliant
+Compliant solution
+CrossOrigin
+annotation:
+
+@CrossOrigin("https://trustedwebsite.com")
@RequestMapping("")
public class TestController {
public String home(ModelMap model) {
@@ -107,29 +99,26 @@ Compliant Solution
}
}
-
-+-cors.CorsConfiguration +class:
+CorsConfiguration config = new CorsConfiguration(); -config.addAllowedOrigin("http://domain2.com"); // Compliant +config.addAllowedOrigin("http://domain2.com");-
+servlet.config.annotation.CorsConfiguration:
+class Safe implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") - .allowedOrigins("safe.com"); // Compliant + .allowedOrigins("https://safe.com"); } }User-controlled origin validated with an allow-list:
-+public ResponseEntity<String> userControlledOrigin(@RequestHeader("Origin") String origin) { HttpHeaders responseHeaders = new HttpHeaders(); if (trustedOrigins.contains(origin)) { @@ -139,17 +128,21 @@-Compliant Solution
return new ResponseEntity<>("content", responseHeaders, HttpStatus.CREATED); }See
+Resources
+Documentation
+
In Android applications, broadcasting intents is security-sensitive. For example, it has led in the past to the following vulnerability:
-By default, broadcasted intents are visible to every application, exposing all sensitive information they contain.
-This rule raises an issue when an intent is broadcasted without specifying any "receiver permission".
-There is a risk if you answered yes to any of those questions.
-Restrict the access to broadcasted intents. See Android documentation for more -information.
-+Broadcasted intents in Android are visible to every application by default, which can expose sensitive information.
+Why is this an issue?
+By default, broadcasted intents are visible to every application on the device, exposing all sensitive information that intents contain. This rule +raises an issue when an intent is broadcasted without specifying a receiver permission.
+Methods like
+sendBroadcast,sendBroadcastAsUser,sendOrderedBroadcast, and +sendOrderedBroadcastAsUserthat are called without a receiver permission parameter or withnullfor the permission allow any +application to receive the broadcast.What is the potential impact?
+Information disclosure
+If an intent contains sensitive data such as user credentials, personal information, or internal application state, any malicious application +installed on the same device can intercept and read this data.
+Privilege escalation
+A malicious application could listen for broadcasted intents to trigger unauthorized actions or manipulate application behavior, potentially +gaining access to functionality that should be restricted.
+How to fix it
+Code examples
+The following code broadcasts an intent without specifying a receiver permission, making it accessible to all applications on the device.
+Noncompliant code example
+import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -31,20 +32,20 @@-Sensitive Code Example
BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras, String broadcastPermission) { - context.sendBroadcast(intent); // Sensitive - context.sendBroadcastAsUser(intent, user); // Sensitive + context.sendBroadcast(intent); // Noncompliant + context.sendBroadcastAsUser(intent, user); // Noncompliant // Broadcasting intent with "null" for receiverPermission - context.sendBroadcast(intent, null); // Sensitive - context.sendBroadcastAsUser(intent, user, null); // Sensitive - context.sendOrderedBroadcast(intent, null); // Sensitive + context.sendBroadcast(intent, null); // Noncompliant + context.sendBroadcastAsUser(intent, user, null); // Noncompliant + context.sendOrderedBroadcast(intent, null); // Noncompliant context.sendOrderedBroadcastAsUser(intent, user, null, resultReceiver, - scheduler, initialCode, initialData, initialExtras); // Sensitive + scheduler, initialCode, initialData, initialExtras); // Noncompliant } }Compliant Solution
-+Compliant solution
+import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -64,12 +65,18 @@-Compliant Solution
context.sendBroadcast(intent, broadcastPermission); context.sendBroadcastAsUser(intent, user, broadcastPermission); context.sendOrderedBroadcast(intent, broadcastPermission); - context.sendOrderedBroadcastAsUser(intent, user,broadcastPermission, resultReceiver, + context.sendOrderedBroadcastAsUser(intent, user, broadcastPermission, resultReceiver, scheduler, initialCode, initialData, initialExtras); } }See
+Resources
+Documentation
+
Android applications can receive broadcasts from the system or other applications. Receiving intents is security-sensitive. For example, it has led -in the past to the following vulnerabilities:
-Receivers can be declared in the manifest or in the code to make them context-specific. If the receiver is declared in the manifest Android will -start the application if it is not already running once a matching broadcast is received. The receiver is an entry point into the application.
-Other applications can send potentially malicious broadcasts, so it is important to consider broadcasts as untrusted and to limit the applications -that can send broadcasts to the receiver.
-Permissions can be specified to restrict broadcasts to authorized applications. Restrictions can be enforced by both the sender and receiver of a -broadcast. If permissions are specified when registering a broadcast receiver, then only broadcasters who were granted this permission can send a -message to the receiver.
-This rule raises an issue when a receiver is registered without specifying any broadcast permission.
-There is a risk if you answered yes to any of those questions.
-Restrict the access to broadcasted intents. See the Android documentation for more -information.
-+Android applications can receive broadcasts from the system or other applications through registered broadcast receivers.
+Why is this an issue?
+A broadcast receiver registered or declared without a broadcast permission can receive intents from any application on the device, making it an +unrestricted entry point into the application. Malicious or compromised applications can send crafted broadcasts that trigger unintended behavior, +bypass access controls, or feed untrusted data into the application’s processing logic. This rule raises an issue when a receiver is registered in +code without a
+broadcastPermissionargument, or when a receiver is declared in the manifest as exported without an +android:permissionattribute.What is the potential impact?
+Unauthorized access
+An attacker controlling a malicious application can send arbitrary broadcasts to the unprotected receiver, potentially triggering sensitive +operations such as changing application state or invoking privileged functionality without the user’s knowledge.
+Data injection
+Without restriction, any application can supply arbitrary intent data to the receiver. If that data is processed without validation, it can lead to +logic errors or further exploitation within the application.
+How to fix it
+Code examples
+The following code registers a broadcast receiver without specifying a broadcast permission, allowing any application to send intents to it.
+Noncompliant code example
+import android.content.BroadcastReceiver; import android.content.Context; import android.content.IntentFilter; @@ -39,17 +32,17 @@-Sensitive Code Example
String broadcastPermission, Handler scheduler, int flags) { - context.registerReceiver(receiver, filter); // Sensitive - context.registerReceiver(receiver, filter, flags); // Sensitive + context.registerReceiver(receiver, filter); // Noncompliant + context.registerReceiver(receiver, filter, flags); // Noncompliant // Broadcasting intent with "null" for broadcastPermission - context.registerReceiver(receiver, filter, null, scheduler); // Sensitive - context.registerReceiver(receiver, filter, null, scheduler, flags); // Sensitive + context.registerReceiver(receiver, filter, null, scheduler); // Noncompliant + context.registerReceiver(receiver, filter, null, scheduler, flags); // Noncompliant } }Compliant Solution
-+Compliant solution
+import android.content.BroadcastReceiver; import android.content.Context; import android.content.IntentFilter; @@ -71,8 +64,16 @@-Compliant Solution
} }See
+Resources
+Documentation
+
Storing data locally is a common task for mobile applications. Such data includes files among other things. One convenient way to store files is to -use the external file storage which usually offers a larger amount of disc space compared to internal storage.
-Files created on the external storage are globally readable and writable. Therefore, a malicious application having the permissions
-WRITE_EXTERNAL_STORAGE or READ_EXTERNAL_STORAGE could try to read sensitive information from the files that other
-applications have stored on the external storage.
External storage can also be removed by the user (e.g. when based on SD card) making the files unavailable to the application.
-Your application uses external storage to:
-There is a risk if you answered yes to any of those questions.
-Android applications can store files on external storage (such as an SD card or shared storage), which is globally readable and writable by other +applications.
+External storage in Android is globally readable and writable by any application that holds the READ_EXTERNAL_STORAGE or
+WRITE_EXTERNAL_STORAGE permissions. Files stored there can be read, modified, or deleted by other applications, making external storage
+unsuitable for sensitive data. External storage can also be physically removed by the user, causing files to become unavailable at any time. This rule
+raises an issue when an application accesses external storage directories via APIs such as getExternalFilesDir,
+getExternalStorageDirectory, or equivalent.
A malicious application with storage permissions can read sensitive files stored in external storage, leading to exposure of user credentials, +personal data, or application secrets.
+An attacker can modify or delete files in external storage, corrupting application data or injecting malicious content that the application will +later process.
+The following code accesses external storage, which is globally readable and writable by other applications and therefore should not be used to +store sensitive data.
+
import android.content.Context;
public class AccessExternalFiles {
public void accessFiles(Context context) {
- context.getExternalFilesDir(null); // Sensitive
+ context.getExternalFilesDir(null); // Noncompliant
}
}
-import android.content.Context; @@ -43,11 +39,15 @@-Compliant Solution
} }
Operating systems have global directories where any user has write access. Those folders are mostly used as temporary storage areas like
-/tmp in Linux based systems. An application manipulating files from these folders is exposed to race conditions on filenames: a malicious
-user can try to create a file with a predictable name before the application does. A successful attack can result in other files being accessed,
-modified, corrupted or deleted. This risk is even higher if the application runs with elevated permissions.
In the past, it has led to the following vulnerabilities:
-This rule raises an issue whenever it detects a hard-coded path to a publicly writable directory like /tmp (see examples below). It
-also detects access to environment variables that point to publicly writable directories, e.g., TMP and TMPDIR.
Using publicly writable directories such as /tmp to store temporary files exposes an application to race condition
+vulnerabilities.
Operating systems provide globally writable directories—such as /tmp on Linux or \Windows\Temp on Windows—where any user
+can create, read, and modify files. When an application creates files in these directories with predictable names, it becomes vulnerable to race
+conditions: an attacker can create a file with the same name before the application does, potentially causing the application to read or write
+attacker-controlled content.
This rule raises an issue when it detects hard-coded paths to publicly writable directories, such as:
/tmp/var/tmp\Windows\Temp\Temp\TMP%USERPROFILE%\AppData\Local\TempThere is a risk if you answered yes to any of those questions.
-
-new File("/tmp/myfile.txt"); // Sensitive
-Paths.get("/tmp/myfile.txt"); // Sensitive
+It also raises an issue when it detects reads of environment variables that point to publicly writable directories: TMP,
+TMPDIR, and TEMP.
+What is the potential impact?
+Information disclosure
+By winning the race condition, an attacker can access files written by the application to a publicly writable directory. If those files contain
+sensitive data—credentials, session tokens, or personal information—the attacker can read them before the application removes them.
+Data tampering
+An attacker can replace or modify a file before the application reads it, causing the application to process attacker-controlled content. This can
+result in data corruption, unexpected behavior, or indirect code execution. The risk is significantly higher when the application runs with elevated
+privileges.
+How to fix it in Java SE
+Use the secure-by-design APIs from java.io and java.nio that create temporary files with unpredictable names and
+appropriate permissions.
+Code examples
+Noncompliant code example
+
+new File("/tmp/myfile.txt"); // Noncompliant
+Paths.get("/tmp/myfile.txt"); // Noncompliant
-java.io.File.createTempFile("prefix", "suffix"); // Sensitive, will be in the default temporary-file directory.
-java.nio.file.Files.createTempDirectory("prefix"); // Sensitive, will be in the default temporary-file directory.
+java.io.File.createTempFile("prefix", "suffix"); // Noncompliant: will be in the default temporary-file directory.
+java.nio.file.Files.createTempDirectory("prefix"); // Noncompliant: will be in the default temporary-file directory.
-
+
Map<String, String> env = System.getenv();
-env.get("TMP"); // Sensitive
+env.get("TMP"); // Noncompliant
-Compliant Solution
-
+Compliant solution
+
new File("/myDirectory/myfile.txt"); // Compliant
File.createTempFile("prefix", "suffix", new File("/mySecureDirectory")); // Compliant
@@ -71,7 +66,15 @@ Compliant Solution
f.setExecutable(true, true);
}
-See
+
+File.createTempFile("prefix", "suffix", new File("/mySecureDirectory")); // Compliant
+
+Resources
+Articles & blog posts
+
+Disclosure of version information, usually overlooked by developers but disclosed by default by the systems and frameworks in use, can pose a -significant security risk depending on the production environment.
-Once this information is public, attackers can use it to identify potential security holes or vulnerabilities specific to that version.
-Furthermore, if the published version information indicates the use of outdated or unsupported software, it becomes easier for attackers to exploit -known vulnerabilities. They can search for published vulnerabilities related to that version and launch attacks that specifically target those -vulnerabilities.
-There is a risk if you answered yes to any of these questions.
-In general, it is recommended to keep internal technical information within internal systems to control what attackers know about the underlying -architectures. This is known as the "need to know" principle.
-The most effective solution is to remove version information disclosure from what end users can see, such as the "x-powered-by" header.
-
- This can be achieved directly through the web application code, server (nginx, apache) or firewalls.
Disabling the server signature provides additional protection by reducing the amount of information available to attackers. Note, however, that
-this does not provide as much protection as regular updates and patches.
-
- Security by obscurity is the least foolproof solution of all. It should never be the only defense mechanism and should always be combined with other
- security measures.
+Web application frameworks and servers often disclose version information by default through HTTP headers.
+Why is this an issue?
+Version information disclosed by default through HTTP headers is often overlooked by developers, yet it can pose a security risk. Once this +information is public, attackers can use it to identify potential vulnerabilities specific to that version. This rule raises an issue when version +information is disclosed through HTTP headers such as
+x-powered-byorServer.What is the potential impact?
+If the disclosed version information indicates the use of outdated or unsupported software, it becomes easier for attackers to exploit known +vulnerabilities. They can search for published vulnerabilities related to that version and launch targeted attacks.
+How to fix it
+Do not disclose version information unless necessary. The
+x-powered-byorServerHTTP headers should not be used.Code examples
+Noncompliant code example
+@GetMapping(value = "/example") public ResponseEntity<String> example() { HttpHeaders responseHeaders = new HttpHeaders(); - responseHeaders.set("x-powered-by", "myproduct"); // Sensitive + responseHeaders.set("x-powered-by", "myproduct1.2.3"); // Noncompliant return new ResponseEntity<String>( "example", @@ -34,13 +22,24 @@-Sensitive Code Example
HttpStatus.CREATED); }Compliant Solution
-Do not disclose version information unless necessary. The
-x-powered-byorServerHTTP headers should not be used.See
+Compliant solution
++@GetMapping(value = "/example") +public ResponseEntity<String> example() { + return new ResponseEntity<String>( + "example", + HttpStatus.CREATED); +} ++Resources
+Documentation
Rejecting requests with significant content length is a good practice to control the network traffic intensity and thus resource consumption in -order to prevent DoS attacks.
-There is a risk if you answered yes to any of those questions.
-It is recommended to customize the rule with the limit values that correspond to the web application.
-With default limit value of 8388608 (8MB).
-A 100 MB file is allowed to be uploaded:
-+Enforcing a maximum HTTP request content length limits how much data the server must accept per request, which helps control resource use and +reduces the risk of denial-of-service attacks.
+Why is this an issue?
+Accepting HTTP requests without an upper bound on their content length exposes the application to Denial of Service (DoS) attacks. An attacker can +send arbitrarily large requests that exhaust server memory, disk space, or processing capacity before the application can reject them. This rule +detects when no maximum content length is configured, or when the configured limit exceeds the recommended thresholds (8 MB for file uploads, 2 MB for +other requests).
+What is the potential impact?
+Denial of Service
+An attacker who can send oversized HTTP requests can exhaust server resources—memory, CPU threads, or network bandwidth—causing the application to +slow down or become completely unavailable. Even a single large upload can tie up a worker process and prevent other users from being served.
+How to fix it in Spring
+Limit the upload size by setting a maximum value on the multipart resolver or multipart configuration factory.
+Code examples
+Noncompliant code example
+@Bean(name = "multipartResolver") public CommonsMultipartResolver multipartResolver() { CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(); - multipartResolver.setMaxUploadSize(104857600); // Sensitive (100MB) + multipartResolver.setMaxUploadSize(104857600); // Noncompliant return multipartResolver; } @Bean(name = "multipartResolver") public CommonsMultipartResolver multipartResolver() { - CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(); // Sensitive, by default if maxUploadSize property is not defined, there is no limit and thus it's insecure + CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(); // Noncompliant return multipartResolver; } @Bean public MultipartConfigElement multipartConfigElement() { - MultipartConfigFactory factory = new MultipartConfigFactory(); // Sensitive, no limit by default + MultipartConfigFactory factory = new MultipartConfigFactory(); // Noncompliant return factory.createMultipartConfig(); }-Compliant Solution
-File upload size is limited to 8 MB:
-+Compliant solution
+@Bean(name = "multipartResolver") public CommonsMultipartResolver multipartResolver() { - multipartResolver.setMaxUploadSize(8388608); // Compliant (8 MB) + CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(); + multipartResolver.setMaxUploadSize(8388608); return multipartResolver; }-See
+Resources
+Articles & blog posts
+
User enumeration refers to the ability to guess existing usernames in a web application database. This can happen, for example, when using -"sign-in/sign-on/forgot password" functionalities of a website.
-When an user tries to "sign-in" to a website with an incorrect username/login, the web application should not disclose that the username doesn’t -exist with a message similar to "this username is incorrect", instead a generic message should be used like "bad credentials", this way it’s not -possible to guess whether the username or password was incorrect during the authentication.
-If a user-management feature discloses information about the existence of a username, attackers can use brute force attacks to retrieve a large -amount of valid usernames that will impact the privacy of corresponding users and facilitate other attacks (phishing, password guessing etc …).
-There is a risk if you answered yes to any of those questions.
-When a user performs a request involving a username, it should not be possible to spot differences between a valid and incorrect username:
-In a Spring-security web application the username leaks when:
-+Authentication mechanisms should not reveal whether a username exists in the system.
+Why is this an issue?
+User enumeration occurs when an application discloses whether a given username exists in its database, for example through sign-in, sign-on, or +forgot-password functionalities. When error messages or exception handling differ depending on whether a username is valid, attackers can use +brute-force techniques to harvest valid usernames. This facilitates further attacks such as credential stuffing, phishing, and targeted password +guessing, and impacts the privacy of the affected users.
+What is the potential impact?
+If an attacker can enumerate valid usernames, it significantly increases the success rate of credential stuffing or social engineering. They can +launch targeted credential-stuffing or password-guessing campaigns against confirmed accounts, and use the harvested usernames in phishing schemes. +This also degrades user privacy, since the mere existence of an account can reveal personal information.
+How to fix it in Spring
+Code examples
+The following code leaks information about the existence of usernames by using distinct error messages, throwing +
+UsernameNotFoundExceptionoutside theloadUserByUsernamemethod, or disablingHideUserNotFoundExceptions.Noncompliant code example
+public String authenticate(String username, String password) { - // .... + MyUserDetailsService s1 = new MyUserDetailsService(); MyUserPrincipal u1 = s1.loadUserByUsername(username); if(u1 == null) { - throw new BadCredentialsException(username+" doesn't exist in our database"); // Sensitive + throw new BadCredentialsException(username+" doesn't exist in our database"); // Noncompliant } - // .... + } --
-public String authenticate(String username, String password) {
- // ....
+
+public String authenticate2(String username, String password) {
+
+ MyUserDetailsService s2 = new MyUserDetailsService();
+ MyUserPrincipal user = s2.loadUserByUsername(username);
+
if(user == null) {
- throw new UsernameNotFoundException("user not found"); // Sensitive
+ throw new UsernameNotFoundException("user not found"); // Noncompliant
}
- // ....
+
+}
+
+public void configure() {
+ DaoAuthenticationProvider daoauth = new DaoAuthenticationProvider();
+ daoauth.setUserDetailsService(new MyUserDetailsService());
+ daoauth.setPasswordEncoder(new BCryptPasswordEncoder());
+ daoauth.setHideUserNotFoundExceptions(false); // Noncompliant
+ builder.authenticationProvider(daoauth);
}
--DaoAuthenticationProvider daoauth = new DaoAuthenticationProvider(); -daoauth.setUserDetailsService(new MyUserDetailsService()); -daoauth.setPasswordEncoder(new BCryptPasswordEncoder()); -daoauth.setHideUserNotFoundExceptions(false); // Sensitive -builder.authenticationProvider(daoauth); --
In a Spring-security web application:
-
-public String authenticate(String username, String password) throws AuthenticationException {
+Compliant solution
+
+public boolean authenticate(String username, String password) throws AuthenticationException {
+ verifyCredentials(username, password);
+ return true;
+}
+
+private void verifyCredentials(String username, String password) throws AuthenticationException {
Details user = null;
try {
user = loadUserByUsername(username);
} catch (UsernameNotFoundException | DataAccessException e) {
- // Hide this exception reason to not disclose that the username doesn't exist
+ // Hide the reason to avoid disclosing user existence.
}
if (user == null || !user.isPasswordCorrect(password)) {
- // User should not be able to guess if the bad credentials message is related to the username or the password
throw new BadCredentialsException("Bad credentials");
}
}
+
+public void configure() {
+ DaoAuthenticationProvider daoauth = new DaoAuthenticationProvider();
+ daoauth.setUserDetailsService(new MyUserDetailsService());
+ daoauth.setPasswordEncoder(new BCryptPasswordEncoder());
+ daoauth.setHideUserNotFoundExceptions(true);
+ builder.authenticationProvider(daoauth);
+}
+Resources
+Documentation
-DaoAuthenticationProvider daoauth = new DaoAuthenticationProvider(); -daoauth.setUserDetailsService(new MyUserDetailsService()); -daoauth.setPasswordEncoder(new BCryptPasswordEncoder()); -daoauth.setHideUserNotFoundExceptions(true); // Compliant -builder.authenticationProvider(daoauth); --
In AWS, long-term access keys will be valid until you manually revoke them. This makes them highly sensitive as any exposure can have serious -consequences and should be used with care.
-This rule will trigger when encountering an instantiation of com.amazonaws.auth.BasicAWSCredentials.
For more information, see Use IAM roles -instead of long-term access keys.
-There is a risk if you answered yes to any of those questions.
-Consider using IAM roles or other features of the AWS Security Token Service that provide temporary credentials, limiting the risks.
-+In AWS, long-term access keys provide persistent programmatic access to AWS services but carry a higher risk than temporary credentials because +they do not expire automatically.
+Why is this an issue?
+This rule detects uses of
+com.amazonaws.auth.BasicAWSCredentials, a class that creates long-term credential objects. Unlike temporary +credentials, long-term access keys remain valid indefinitely until manually revoked, making them a persistent risk if exposed. Using long-term keys in +application code increases the risk of accidental exposure and makes credential rotation harder to enforce.What is the potential impact?
+If a long-term access key is exposed through source code, configuration files, or logs, an attacker gains persistent access to AWS resources until +the key is manually revoked. This can lead to unauthorized access to sensitive data, privilege escalation, or significant financial costs from +resource abuse.
+How to fix it
+The preferred approach is to avoid static credentials entirely by using IAM roles, which provide short-lived credentials automatically. On EC2, +ECS, or Lambda, assign an IAM role to the instance or task — the AWS SDK picks up the credentials without any code changes via the default credential +provider chain.
+Where temporary credentials must be obtained programmatically, use AWS +STS to request short-lived session credentials.
+Code examples
+Noncompliant code example
+import com.amazonaws.auth.AWSCredentials; import com.amazonaws.auth.BasicAWSCredentials; // ... -AWSCredentials awsCredentials = new BasicAWSCredentials(accessKeyId, secretAccessKey); +AWSCredentials awsCredentials = new BasicAWSCredentials(accessKeyId, secretAccessKey); // Noncompliant-Compliant Solution
-Example for AWS STS (see Getting Temporary Credentials -with AWS STS).
-+Compliant solution
++// session_creds obtained via an STS AssumeRole call BasicSessionCredentials sessionCredentials = new BasicSessionCredentials( session_creds.getAccessKeyId(), session_creds.getSecretAccessKey(), session_creds.getSessionToken());-See
+Resources
+Documentation
+
Android KeyStore is a secure container for storing key materials, in particular it prevents key materials extraction, i.e. when the application -process is compromised, the attacker cannot extract keys but may still be able to use them. It’s possible to enable an Android security feature, user -authentication, to restrict usage of keys to only authenticated users. The lock screen has to be unlocked with defined credentials -(pattern/PIN/password, biometric).
-There is a risk if you answered yes to any of those questions.
-It’s recommended to enable user authentication (by setting setUserAuthenticationRequired to true during key generation)
-to use keys for a limited duration of time (by setting appropriate values to setUserAuthenticationValidityDurationSeconds), after which
-the user must re-authenticate.
Any user can use the key:
-+Android KeyStore is a secure container for storing cryptographic key material. It prevents key extraction: even if the application process is +compromised, an attacker cannot extract keys from the KeyStore, though they may still use them to perform cryptographic operations. Enabling user +authentication restricts key usage to authenticated users only, requiring the lock screen to be unlocked with a defined credential (PIN, password, +pattern, or biometric).
+Why is this an issue?
+When cryptographic keys are configured without requiring user authentication, any code running within the application process can use those keys +freely. On Android, this happens when a
+KeyGenParameterSpec.Builderis built without calling +setUserAuthenticationRequired(true).What is the potential impact?
+If the application process is compromised, an attacker can use unprotected keys to decrypt sensitive data, forge digital signatures, or impersonate +the user without their knowledge. Although the key material itself cannot be extracted from the secure storage, it can still be used to perform +cryptographic operations on behalf of the attacker.
+How to fix it
+Call
+setUserAuthenticationRequired(true)on theKeyGenParameterSpec.Builderto restrict key use to authenticated users +only. The lock screen must be unlocked with a defined credential (PIN, password, pattern, or biometric) before the key can be used. Use +setUserAuthenticationParametersto configure the authentication timeout and the accepted credential types; after the timeout expires, the +user must re-authenticate.Code examples
+The following code creates a cryptographic key without requiring user authentication, allowing any code running in the application process to use +it.
+Noncompliant code example
+KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"); -KeyGenParameterSpec builder = new KeyGenParameterSpec.Builder("test_secret_key_noncompliant", KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) // Noncompliant +KeyGenParameterSpec builder = new KeyGenParameterSpec.Builder("example-key", KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) // Noncompliant .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .build(); keyGenerator.init(builder);-Compliant Solution
-The use of the key is limited to authenticated users (for a duration of time defined to 60 seconds):
-+Compliant solution
+KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"); -KeyGenParameterSpec builder = new KeyGenParameterSpec.Builder("test_secret_key", KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) +KeyGenParameterSpec builder = new KeyGenParameterSpec.Builder("example-key", KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .setUserAuthenticationRequired(true) - .setUserAuthenticationParameters (60, KeyProperties.AUTH_DEVICE_CREDENTIAL) + .setUserAuthenticationParameters (60, KeyProperties.AUTH_DEVICE_CREDENTIAL) // 60 seconds .build(); -keyGenerator.init(builder) +keyGenerator.init(builder);-See
+Resources
+Documentation
+
Android comes with Android KeyStore, a secure container for storing key materials. It’s possible to define certain keys to be unlocked when users -authenticate using biometric credentials. This way, even if the application process is compromised, the attacker cannot access keys, as presence of -the authorized user is required.
-These keys can be used, to encrypt, sign or create a message authentication code (MAC) as proof that the authentication result has not been
-tampered with. This protection defeats the scenario where an attacker with physical access to the device would try to hook into the application
-process and call the onAuthenticationSucceeded method directly. Therefore he would be unable to extract the sensitive data or to perform
-the critical operations protected by the biometric authentication.
The application contains:
-There is a risk if you answered yes to this question.
-It’s recommended to tie the biometric authentication to a cryptographic operation by using a CryptoObject during authentication.
A CryptoObject is not used during authentication:
+Biometric authentication on Android should be tied to a cryptographic operation to prevent attackers from bypassing the authentication result.
+Why is this an issue?
+Android KeyStore allows defining keys that require biometric authentication before use. When
+BiometricPrompt.authenticateis called +without aCryptoObject, the authentication result can be tampered with. An attacker with physical access to the device could hook into +the application process and callonAuthenticationSucceededdirectly, bypassing the biometric check entirely.What is the potential impact?
+Authentication bypass
+If biometric authentication is not tied to a cryptographic operation, an attacker with physical access to the device can bypass the authentication. +This could allow unauthorized access to sensitive data or critical operations that the biometric check was meant to protect.
+How to fix it
+Code examples
+A
+CryptoObjectshould be passed to theauthenticatemethod to bind the biometric authentication to a cryptographic +operation.Noncompliant code example
+// ... BiometricPrompt biometricPrompt = new BiometricPrompt(activity, executor, callback); // ... biometricPrompt.authenticate(promptInfo); // Noncompliant-Compliant Solution
-A
-CryptoObjectis used during authentication:+Compliant solution
+// ... BiometricPrompt biometricPrompt = new BiometricPrompt(activity, executor, callback); // ... -biometricPrompt.authenticate(promptInfo, new BiometricPrompt.CryptoObject(cipher)); // Compliant +biometricPrompt.authenticate(promptInfo, new BiometricPrompt.CryptoObject(cipher));-See
+Resources
+Documentation
+
Granting file access to WebViews, particularly through the file:// scheme, introduces a risk of local file inclusion vulnerabilities.
-The severity of this risk depends heavily on the specific settings configured for the WebView. Overly permissive settings can allow malicious scripts
-to access a wide range of local files, potentially exposing sensitive data such as Personally Identifiable Information (PII) or private application
-data, leading to data breaches and other security compromises.
There is a risk if you answered yes to any of these questions.
-Avoid opening file:// URLs from external sources in WebView components. If your application accepts arbitrary URLs from external
-sources, do not enable this functionality.
On Android, it is recommended to use androidx.webkit.WebViewAssetLoader to access files, including assets and resources, via a custom,
-controllable scheme.
On iOS, it is recommended to use Bundles to access local files, keeping access limited a controlled subset using the
-allowingReadAccessTo parameter of the loadFileURL method. If allowFileAccessFromFileURLs and
-allowUniversalAccessFromFileURLs are not enabled, it is not possible to access files outside the intended directory. It is also possible
-to create a custom scheme to access local files, but this is more complex and might lead to unintended security issues.
For enhanced security, ensure that the options to load file:// URLs are explicitly set to false.
+Granting file access to WebViews, particularly through the
+file://scheme, can expose sensitive local files to malicious scripts.Why is this an issue?
+WebViews can be configured to allow scripts to access local files through the
+file://scheme. When file access settings are enabled, +JavaScript running within the WebView can read files from the device’s local file system, including sensitive application data.Settings such as
+setAllowFileAccess,setAllowContentAccess,allowFileAccessFromFileURLs, and +allowUniversalAccessFromFileURLscontrol this behavior on Android.What is the potential impact?
+Data exposure
+If a malicious script is loaded in a WebView with file access enabled, it can read arbitrary files from the device’s local file system. This +includes sensitive application data, user credentials, authentication tokens, and Personally Identifiable Information (PII) stored in local files.
+Loss of confidentiality
+Exposed data can be exfiltrated to attacker-controlled servers, leading to account compromise, identity theft, and data breaches.
+How to fix it
+Use
+WebViewAssetLoaderto load local files instead of directly accessing them viafile://URLs. This approach serves +assets over a securehttps://appassets.androidplatform.netURL, effectively isolating the WebView from the local file system.The file access settings are disabled by default in modern Android versions. To prevent possible security issues in +
+Build.VERSION_CODES.Qand earlier, it is still recommended to explicitly set those values to false.Code examples
+Noncompliant code example
+import android.webkit.WebView; +import android.webkit.WebViewClient; WebView webView = (WebView) findViewById(R.id.webview); -webView.getSettings().setAllowFileAccess(true); // Sensitive -webView.getSettings().setAllowContentAccess(true); // Sensitive +webView.setWebViewClient(new WebViewClient()); +webView.getSettings().setAllowFileAccess(true); // Noncompliant +webView.getSettings().setAllowFileAccessFromFileURLs(true); // Noncompliant +webView.getSettings().setAllowUniversalAccessFromFileURLs(true); // Noncompliant +webView.getSettings().setAllowContentAccess(true); // Noncompliant +webView.loadUrl("file:///android_asset/example.html");-Compliant Solution
-+Compliant solution
++import android.net.Uri; +import android.webkit.WebResourceRequest; +import android.webkit.WebResourceResponse; import android.webkit.WebView; +import android.webkit.WebViewClient; +import androidx.annotation.RequiresApi; +import androidx.webkit.WebViewAssetLoader; WebView webView = (WebView) findViewById(R.id.webview); +WebViewAssetLoader assetLoader = new WebViewAssetLoader.Builder() + .addPathHandler("/assets/", new WebViewAssetLoader.AssetsPathHandler(this)) + .build(); + +webView.setWebViewClient(new WebViewClient() { + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + @Override + public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { + return assetLoader.shouldInterceptRequest(request.getUrl()); + } + + @SuppressWarnings("deprecation") + @Override + public WebResourceResponse shouldInterceptRequest(WebView view, String url) { + return assetLoader.shouldInterceptRequest(Uri.parse(url)); + } +}); + webView.getSettings().setAllowFileAccess(false); +webView.getSettings().setAllowFileAccessFromFileURLs(false); +webView.getSettings().setAllowUniversalAccessFromFileURLs(false); webView.getSettings().setAllowContentAccess(false); + +webView.loadUrl("https://appassets.androidplatform.net/assets/example.html");-See
+Resources
+Documentation
+
Using JavaScript interfaces in WebViews to expose Java objects is unsafe. Doing so allows JavaScript to invoke Java methods, potentially giving -attackers access to data or sensitive app functionality. WebViews might include untrusted sources such as third-party iframes, making this -functionality particularly risky. As JavaScript interfaces are passed to every frame in the WebView, those iframes are also able to access the exposed -Java object.
-There is a risk if you answered yes to any of these questions.
-If it is possible to disable JavaScript in the WebView, this is the most secure option. By default, JavaScript is disabled in a WebView, so
-webSettings.setJavaScriptEnabled(false) does not need to be explicitly called. Of course, sometimes it is necessary to enable JavaScript,
-in which case the following recommendations should be considered.
JavaScript interfaces can be removed at a later point. It is recommended to remove the JavaScript interface when it is no longer needed. If it is
-needed for a longer time, consider removing it before loading untrusted content. This can be done by calling
-webView.removeJavascriptInterface("interfaceName").
A good place to do this is inside the shouldInterceptRequest method of a WebViewClient, where you can check the URL or
-resource being loaded and remove the interface if the content is untrusted.
If a native bridge has to be added to the WebView, and it is impossible to remove it at a later point, consider using an alternative method that
-offers more control over the communication flow. WebViewCompat.postWebMessage/WebViewCompat.addWebMessageListener and
-WebMessagePort.postMessage offer more ways to validate incoming and outgoing messages, such as by being able to restrict the origins that
-can send messages to the JavaScript bridge.
+WebView JavaScript interfaces expose native application methods to JavaScript code running in an embedded web view. If the web view loads untrusted +content, any script — including attacker-controlled code — can call those native methods directly.
+Why is this an issue?
+Once a JavaScript interface is registered on a web view, the exposed native object is accessible to all JavaScript running in that web view, +including scripts in untrusted third-party iframes. Web views may load content from sources that are not fully trusted, and JavaScript interfaces are +propagated to every frame, not just the top-level page. An attacker who can inject or control JavaScript in any frame can therefore call the exposed +native methods directly.
+What is the potential impact?
+Unauthorized access to sensitive data or functionality
+An attacker who controls JavaScript in the WebView — for example, through a malicious third-party iframe embedded in an otherwise legitimate page — +can invoke exposed native methods without restriction. Depending on what the methods expose, this can lead to unauthorized access to sensitive user +data, execution of privileged application functionality, or full compromise of the application’s security model.
+How to fix it
+Code examples
+The following code is vulnerable because it registers a native interface that is accessible to all JavaScript running in the WebView, including +JavaScript from untrusted sources such as third-party iframes.
+Noncompliant code example
+public class ExampleActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { @@ -35,7 +23,7 @@-Sensitive Code Example
WebView webView = new WebView(this); webView.getSettings().setJavaScriptEnabled(true); - webView.addJavascriptInterface(new JavaScriptBridge(), "androidBridge"); // Sensitive + webView.addJavascriptInterface(new JavaScriptBridge(), "androidBridge"); // Noncompliant } public static class JavaScriptBridge { @@ -46,40 +34,8 @@Sensitive Code Example
} }Compliant Solution
-The most secure option is to disable JavaScript entirely. {rule:java:S6362} further explains why it should not be enabled unless absolutely -necessary.
--public class ExampleActivity extends AppCompatActivity { - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - WebView webView = new WebView(this); - webView.getSettings().setJavaScriptEnabled(false); - } -} --If possible, remove the JavaScript interface after it is no longer needed, or before loading any untrusted content.
--public class ExampleActivity extends AppCompatActivity { - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - WebView webView = new WebView(this); - webView.getSettings().setJavaScriptEnabled(true); - - webView.addJavascriptInterface(new JavaScriptBridge(), "androidBridge"); - - // Sometime later, before unsafe content is loaded, remove the JavaScript interface - webView.removeJavascriptInterface("androidBridge"); - } -} --If a JavaScript bridge must be used, consider using
-WebViewCompat.addWebMessageListenerinstead. This allows you to restrict the -origins that can send messages to the JavaScript bridge.+Compliant solution
+public class ExampleActivity extends AppCompatActivity { private static final Set<String> ALLOWED_ORIGINS = Collections.singleton("https://example.com"); @@ -110,17 +66,36 @@-Compliant Solution
} }See
+
| Note | +If the interface cannot be replaced immediately, it can be removed before loading untrusted content. | +
+webView.addJavascriptInterface(new JavaScriptBridge(), "androidBridge");
+// ...
+// Remove the interface before loading untrusted content
+webView.removeJavascriptInterface("androidBridge");
+webView.loadUrl("https://untrusted.example.com");
+
+Mobile devices expose unique identifiers that can be used to identify users across applications or devices. These identifiers put user privacy at -risk, as they might allow the tracking of user activity without consent, while making it difficult or impossible for users to reset them.
-Privacy violations can cause apps to be removed from app stores and can result in legal action or loss of trust from users.
-There is a risk if you answer yes to any of these questions.
-For ads use cases, use the Advertising ID provided by the platform. This identifier is designed to be reset by the user and has an associated -Personalized Ads flag.
-For non-ads use cases, the most privacy-friendly identifiers that can be used are:
-Mobile devices expose unique identifiers that can be used to track users across applications without their consent.
+Mobile platforms provide access to device identifiers such as the Android ID or the iOS Identifier for Vendors. These identifiers are persistent +across app sessions and can be used to track user activity across applications and devices. This rule raises an issue when code accesses such +persistent device identifiers.
+Using persistent unique identifiers without user consent can lead to privacy violations. Users may be tracked across applications or devices +without their knowledge, and the identifiers may be linked to personally identifiable information. Privacy violations can cause apps to be removed +from app stores and can result in legal action or loss of trust from users.
+Instead of using a persistent device identifier, generate a random UUID. The UUID should be persisted in secure local storage (e.g. +SharedPreferences, Keychain) so the same value is reused across sessions. This approach gives users control over their privacy, as the identifier is +reset when the app is reinstalled.
+
-String uid = Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID); // Sensitive
+String uid = Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID); // Noncompliant
User user = new User(
uid,
"John",
- "Doe",
+ "Doe"
);
-
String uid = UUID.randomUUID().toString();
User user = new User(
uid,
"John",
- "Doe",
+ "Doe"
);
-