Repository: ballerina-platform/module-ballerina-file
Pull request #558: Add static code rules to the file module
Author: login: SachinAkash01
Created: 2025-02-24T06:34:52Z | Merged: 2025-03-05T15:18:19Z

## Purpose
> $subject

Related issues: [#7511](https://github.com/ballerina-platform/ballerina-library/issues/7511) [#7283](https://github.com/ballerina-platform/ballerina-library/issues/7283)
## Examples

## Checklist
- [ ] Linked to an issue
- [ ] Updated the changelog
- [ ] Added tests
- [ ] Updated the spec
- [ ] Checked native-image compatibility



Diff:
diff --git a/.gitignore b/.gitignore
index 17072500..9ea4c179 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,3 +41,5 @@ target
 # Ballerina
 velocity.log*
 *Ballerina.lock
+
+compiler-plugin-test/**/target
diff --git a/changelog.md b/changelog.md
index ba038d30..a23f50e5 100644
--- a/changelog.md
+++ b/changelog.md
@@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased]
 
+### Added
+- [Add static code rules](https://github.com/ballerina-platform/ballerina-library/issues/7283)
+
+
 ### Changed
 
 - [Change the listener configuration as an included parameter](https://github.com/ballerina-platform/ballerina-library/issues/7494)
diff --git a/compiler-plugin-test/build.gradle b/compiler-plugin-test/build.gradle
index 01b22c8c..c358e095 100644
--- a/compiler-plugin-test/build.gradle
+++ b/compiler-plugin-test/build.gradle
@@ -20,10 +20,25 @@ plugins {
     id 'java'
     id 'checkstyle'
     id 'com.github.spotbugs'
+    id 'jacoco'
 }
 
 description = 'Ballerina - file Compiler Plugin Test'
 
+jacoco {
+    toolVersion = "${jacocoVersion}"
+    reportsDirectory = file("$project.buildDir/reports/jacoco")
+}
+
+jacocoTestReport {
+    reports {
+        xml.required = true
+        csv.required = false
+        html.required = false
+    }
+    sourceSets project(':file-compiler-plugin').sourceSets.main
+}
+
 def ballerinaDist = "${project.rootDir}/target/ballerina-runtime"
 
 dependencies {
diff --git a/compiler-plugin-test/src/test/java/io/ballerina/stdlib/file/compiler/staticcodeanalyzer/ProcessOutputGobbler.java b/compiler-plugin-test/src/test/java/io/ballerina/stdlib/file/compiler/staticcodeanalyzer/ProcessOutputGobbler.java
new file mode 100644
index 00000000..54eb2acb
--- /dev/null
+++ b/compiler-plugin-test/src/test/java/io/ballerina/stdlib/file/compiler/staticcodeanalyzer/ProcessOutputGobbler.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.org)
+ *
+ * WSO2 LLC. licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package io.ballerina.stdlib.file.compiler.staticcodeanalyzer;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Helper class to consume the process streams.
+ */
+class ProcessOutputGobbler implements Runnable {
+    private final InputStream inputStream;
+    private final StringBuilder output;
+    private int exitCode;
+
+    public ProcessOutputGobbler(InputStream inputStream) {
+        this.inputStream = inputStream;
+        this.output = new StringBuilder();
+    }
+
+    @Override
+    public void run() {
+        try (BufferedReader reader = new BufferedReader(
+                new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                output.append(line).append("\n");
+            }
+        } catch (IOException e) {
+            this.output.append(e.getMessage());
+        }
+    }
+
+    public String getOutput() {
+        return output.toString();
+    }
+
+    public int getExitCode() {
+        return exitCode;
+    }
+
+    public void setExitCode(int exitCode) {
+        this.exitCode = exitCode;
+    }
+}
diff --git a/compiler-plugin-test/src/test/java/io/ballerina/stdlib/file/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java b/compiler-plugin-test/src/test/java/io/ballerina/stdlib/file/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java
new file mode 100644
index 00000000..ffdd2285
--- /dev/null
+++ b/compiler-plugin-test/src/test/java/io/ballerina/stdlib/file/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.org)
+ *
+ * WSO2 LLC. licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package io.ballerina.stdlib.file.compiler.staticcodeanalyzer;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.Test;
+import org.testng.internal.ExitCode;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+public class StaticCodeAnalyzerTest {
+    private static final Path RESOURCE_PACKAGES_DIRECTORY = Paths
+            .get("src", "test", "resources", "static_code_analyzer", "ballerina_packages").toAbsolutePath();
+    private static final Path EXPECTED_JSON_OUTPUT_DIRECTORY = Paths
+            .get("src", "test", "resources", "static_code_analyzer", "expected_output").toAbsolutePath();
+    private static final Path BALLERINA_PATH = getBalCommandPath();
+    private static final Path JSON_RULES_FILE_PATH = Paths
+            .get("../", "compiler-plugin", "src", "main", "resources", "rules.json").toAbsolutePath();
+    private static final String SCAN_COMMAND = "scan";
+
+    private static Path getBalCommandPath() {
+        String balCommand = isWindows() ? "bal.bat" : "bal";
+        return Paths.get("../", "target", "ballerina-runtime", "bin", balCommand).toAbsolutePath();
+    }
+
+    @BeforeSuite
+    public void pullScanTool() throws IOException, InterruptedException {
+        ProcessBuilder processBuilder = new ProcessBuilder(BALLERINA_PATH.toString(), "tool", "pull", SCAN_COMMAND);
+        ProcessOutputGobbler output = getOutput(processBuilder.start());
+        if (Pattern.compile("tool 'scan:.+\\..+\\..+' successfully set as the active version\\.")
+                .matcher(output.getOutput()).find() || Pattern.compile("tool 'scan:.+\\..+\\..+' is already active\\.")
+                .matcher(output.getOutput()).find()) {
+            return;
+        }
+        Assert.assertFalse(ExitCode.hasFailure(output.getExitCode()));
+    }
+
+    @Test
+    public void validateRulesJson() throws IOException {
+        String expectedRules = "[" + Arrays.stream(FileRule.values())
+                .map(FileRule::toString).collect(Collectors.joining(",")) + "]";
+        String actualRules = Files.readString(JSON_RULES_FILE_PATH);
+        assertJsonEqual(normalizeJson(actualRules), normalizeJson(expectedRules));
+    }
+
+    @Test
+    public void testStaticCodeRules() throws IOException, InterruptedException {
+        for (FileRule rule : FileRule.values()) {
+            String targetPackageName = "rule" + rule.getId();
+            String actualJsonReport = StaticCodeAnalyzerTest.executeScanProcess(targetPackageName);
+            String expectedJsonReport = Files
+                    .readString(EXPECTED_JSON_OUTPUT_DIRECTORY.resolve(targetPackageName + ".json"));
+            assertJsonEqual(actualJsonReport, expectedJsonReport);
+        }
+    }
+
+    private static String executeScanProcess(String targetPackage) throws IOException, InterruptedException {
+        ProcessBuilder processBuilder = new ProcessBuilder(BALLERINA_PATH.toString(), SCAN_COMMAND);
+        processBuilder.directory(RESOURCE_PACKAGES_DIRECTORY.resolve(targetPackage).toFile());
+        ProcessOutputGobbler output = getOutput(processBuilder.start());
+        Assert.assertFalse(ExitCode.hasFailure(output.getExitCode()));
+        return Files.readString(RESOURCE_PACKAGES_DIRECTORY.resolve(targetPackage)
+                .resolve("target").resolve("report").resolve("scan_results.json"));
+    }
+
+    private static ProcessOutputGobbler getOutput(Process process) throws InterruptedException {
+        ProcessOutputGobbler outputGobbler = new ProcessOutputGobbler(process.getInputStream());
+        ProcessOutputGobbler errorGobbler = new ProcessOutputGobbler(process.getErrorStream());
+        Thread outputThread = new Thread(outputGobbler);
+        Thread errorThread = new Thread(errorGobbler);
+        outputThread.start();
+        errorThread.start();
+        int exitCode = process.waitFor();
+        outputGobbler.setExitCode(exitCode);
+        errorGobbler.setExitCode(exitCode);
+        outputThread.join();
+        errorThread.join();
+        return outputGobbler;
+    }
+
+    private void assertJsonEqual(String actual, String expected) {
+        Assert.assertEquals(normalizeJson(actual), normalizeJson(expected));
+    }
+
+    private static String normalizeJson(String json) {
+        String normalizedJson = json.replaceAll("\\s*\"\\s*", "\"")
+                .replaceAll("\\s*:\\s*", ":")
+                .replaceAll("\\s*,\\s*", ",")
+                .replaceAll("\\s*\\{\\s*", "{")
+                .replaceAll("\\s*}\\s*", "}")
+                .replaceAll("\\s*\\[\\s*", "[")
+                .replaceAll("\\s*]\\s*", "]")
+                .replaceAll("\n", "")
+                .replaceAll(":\".*module-ballerina-file", ":\"module-ballerina-file");
+        return isWindows() ? normalizedJson.replaceAll("/", "\\\\\\\\") : normalizedJson;
+    }
+
+    private static boolean isWindows() {
+        return System.getProperty("os.name").toLowerCase(Locale.ENGLISH).startsWith("windows");
+    }
+}
diff --git a/compiler-plugin-test/src/test/resources/static_code_analyzer/ballerina_packages/rule1/Ballerina.toml b/compiler-plugin-test/src/test/resources/static_code_analyzer/ballerina_packages/rule1/Ballerina.toml
new file mode 100644
index 00000000..80ffe815
--- /dev/null
+++ b/compiler-plugin-test/src/test/resources/static_code_analyzer/ballerina_packages/rule1/Ballerina.toml
@@ -0,0 +1,8 @@
+[package]
+org = "sachink"
+name = "rule1"
+version = "0.1.0"
+distribution = "2201.11.0-20250127-101700-a4b67fe5"
+
+[build-options]
+observabilityIncluded = true
diff --git a/compiler-plugin-test/src/test/resources/static_code_analyzer/ballerina_packages/rule1/main.bal b/compiler-plugin-test/src/test/resources/static_code_analyzer/ballerina_packages/rule1/main.bal
new file mode 100644
index 00000000..0b7e47fe
--- /dev/null
+++ b/compiler-plugin-test/src/test/resources/static_code_analyzer/ballerina_packages/rule1/main.bal
@@ -0,0 +1,30 @@
+// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org)
+//
+// WSO2 LLC. licenses this file to you under the Apache License,
+// Version 2.0 (the "License"); you may not use this file except
+// in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import ballerina/file;
+import ballerina/os;
+import ballerina/io;
+
+public function main() {
+    string tempFolderPath = os:getEnv("TMP");
+    error? output = file:create(tempFolderPath + "/" + "myfile.txt");
+
+    if (output is error) {
+        io:println("Error occurred: " + output.message());
+    } else {
+        io:println("File created successfully: " + tempFolderPath + "/" + "myfile.txt");
+    }
+}
diff --git a/compiler-plugin-test/src/test/resources/static_code_analyzer/ballerina_packages/rule2/Ballerina.toml b/compiler-plugin-test/src/test/resources/static_code_analyzer/ballerina_packages/rule2/Ballerina.toml
new file mode 100644
index 00000000..696d66ce
--- /dev/null
+++ b/compiler-plugin-test/src/test/resources/static_code_analyzer/ballerina_packages/rule2/Ballerina.toml
@@ -0,0 +1,8 @@
+[package]
+org = "sachink"
+name = "rule2"
+version = "0.1.0"
+distribution = "2201.11.0"
+
+[build-options]
+observabilityIncluded = true
diff --git a/compiler-plugin-test/src/test/resources/static_code_analyzer/ballerina_packages/rule2/main.bal b/compiler-plugin-test/src/test/resources/static_code_analyzer/ballerina_packages/rule2/main.bal
new file mode 100644
index 00000000..5379270a
--- /dev/null
+++ b/compiler-plugin-test/src/test/resources/static_code_analyzer/ballerina_packages/rule2/main.bal
@@ -0,0 +1,30 @@
+// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org)
+//
+// WSO2 LLC. licenses this file to you under the Apache License,
+// Version 2.0 (the "License"); you may not use this file except
+// in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import ballerina/file;
+import ballerina/io;
+
+public function executeCommand(string fileName) returns file:Error? {
+    string unsafeFilePath = "./target/" + fileName;
+    file:Error? remove = file:remove(unsafeFilePath);
+
+    return remove;
+}
+
+public function main() {
+    file:Error? result = executeCommand("sample.json");
+    io:println("Result: ", result);
+}
diff --git a/compiler-plugin-test/src/test/resources/static_code_analyzer/expected_output/rule1.json b/compiler-plugin-test/src/test/resources/static_code_analyzer/expected_output/rule1.json
new file mode 100644
index 00000000..3025f772
--- /dev/null
+++ b/compiler-plugin-test/src/test/resources/static_code_analyzer/expected_output/rule1.json
@@ -0,0 +1,58 @@
+[ {
+  "location" : {
+    "filePath" : "main.bal",
+    "startLine" : 22,
+    "endLine" : 22,
+    "startColumn" : 20,
+    "endColumn" : 68,
+    "startOffset" : 800,
+    "length" : 48
+  },
+  "rule" : {
+    "id" : "ballerina/file:1",
+    "numericId" : 1,
+    "description" : "Avoid using publicly writable directories for file operations without proper access controls",
+    "ruleKind" : "VULNERABILITY"
+  },
+  "source" : "BUILT_IN",
+  "fileName" : "rule1/main.bal",
+  "filePath" : "/Users/sachink/Desktop/module-ballerina-file/compiler-plugin-test/src/test/resources/static_code_analyzer/ballerina_packages/rule1/main.bal"
+}, {
+  "location" : {
+    "filePath" : "main.bal",
+    "startLine" : 25,
+    "endLine" : 25,
+    "startColumn" : 8,
+    "endColumn" : 57,
+    "startOffset" : 886,
+    "length" : 49
+  },
+  "rule" : {
+    "id" : "ballerina/file:1",
+    "numericId" : 1,
+    "description" : "Avoid using publicly writable directories for file operations without proper access controls",
+    "ruleKind" : "VULNERABILITY"
+  },
+  "source" : "BUILT_IN",
+  "fileName" : "rule1/main.bal",
+  "filePath" : "/Users/sachink/Desktop/module-ballerina-file/compiler-plugin-test/src/test/resources/static_code_analyzer/ballerina_packages/rule1/main.bal"
+}, {
+  "location" : {
+    "filePath" : "main.bal",
+    "startLine" : 27,
+    "endLine" : 27,
+    "startColumn" : 8,
+    "endColumn" : 87,
+    "startOffset" : 958,
+    "length" : 79
+  },
+  "rule" : {
+    "id" : "ballerina/file:1",
+    "numericId" : 1,
+    "description" : "Avoid using publicly writable directories for file operations without proper access controls",
+    "ruleKind" : "VULNERABILITY"
+  },
+  "source" : "BUILT_IN",
+  "fileName" : "rule1/main.bal",
+  "filePath" : "/Users/sachink/Desktop/module-ballerina-file/compiler-plugin-test/src/test/resources/static_code_analyzer/ballerina_packages/rule1/main.bal"
+} ]
diff --git a/compiler-plugin-test/src/test/resources/static_code_analyzer/expected_output/rule2.json b/compiler-plugin-test/src/test/resources/static_code_analyzer/expected_output/rule2.json
new file mode 100644
index 00000000..5df553c3
--- /dev/null
+++ b/compiler-plugin-test/src/test/resources/static_code_analyzer/expected_output/rule2.json
@@ -0,0 +1,20 @@
+[ {
+  "location" : {
+    "filePath" : "main.bal",
+    "startLine" : 21,
+    "endLine" : 21,
+    "startColumn" : 28,
+    "endColumn" : 44,
+    "startOffset" : 762,
+    "length" : 16
+  },
+  "rule" : {
+    "id" : "ballerina/file:2",
+    "numericId" : 2,
+    "description" : "I/O function calls should not be vulnerable to path injection attacks",
+    "ruleKind" : "VULNERABILITY"
+  },
+  "source" : "BUILT_IN",
+  "fileName" : "rule2/main.bal",
+  "filePath" : "/Users/sachink/Desktop/module-ballerina-file/compiler-plugin-test/src/test/resources/static_code_analyzer/ballerina_packages/rule2/main.bal"
+} ]
diff --git a/compiler-plugin-test/src/test/resources/testng.xml b/compiler-plugin-test/src/test/resources/testng.xml
index cbd2756c..8af0568d 100644
--- a/compiler-plugin-test/src/test/resources/testng.xml
+++ b/compiler-plugin-test/src/test/resources/testng.xml
@@ -19,10 +19,14 @@
 
 <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
 
-<suite name="BallerinaGraphqlCompilerPluginTests">
+<suite name="BallerinaFileCompilerPluginTests">
     <test name="UnitTests">
+        <packages>
+            <package name="io.ballerina.stdlib.file.compiler.staticcodeanalyzer"/>
+        </packages>
         <classes>
             <class name="io.ballerina.stdlib.file.compiler.CompilerPluginTest"/>
+            <class name="io.ballerina.stdlib.file.compiler.staticcodeanalyzer.StaticCodeAnalyzerTest"/>
         </classes>
     </test>
 </suite>
diff --git a/compiler-plugin/build.gradle b/compiler-plugin/build.gradle
index dc9efe39..4fd1eae3 100644
--- a/compiler-plugin/build.gradle
+++ b/compiler-plugin/build.gradle
@@ -30,6 +30,8 @@ dependencies {
     implementation group: 'org.ballerinalang', name: 'ballerina-lang', version: "${ballerinaLangVersion}"
     implementation group: 'org.ballerinalang', name: 'ballerina-tools-api', version: "${ballerinaLangVersion}"
     implementation group: 'org.ballerinalang', name: 'ballerina-parser', version: "${ballerinaLangVersion}"
+    implementation group: 'io.ballerina.scan', name: 'scan-command', version: "${balScanVersion}"
+    implementation project(":file-native")
 }
 
 def excludePattern = '**/module-info.java'
@@ -70,3 +72,5 @@ compileJava {
         classpath = files()
     }
 }
+
+build.dependsOn ":file-native:build"
diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/file/compiler/Constants.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/file/compiler/Constants.java
new file mode 100644
index 00000000..65d53104
--- /dev/null
+++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/file/compiler/Constants.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.org)
+ *
+ * WSO2 LLC. licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package io.ballerina.stdlib.file.compiler;
+
+import java.util.List;
+
+/**
+ * Constants related to compiler plugin implementation.
+ */
+public class Constants {
+    private Constants() {}
+
+    public static final String SCANNER_CONTEXT = "ScannerContext";
+    public static final String OS = "os";
+    public static final String GET_ENV = "getEnv";
+    public static final String FILE = "file";
+    public static final String BALLERINA_ORG = "ballerina";
+
+    public static final List<String> FILE_FUNCTIONS = List.of(
+            "getAbsolutePath",
+            "isAbsolutePath",
+            "basename",
+            "parentPath",
+            "normalizePath",
+            "splitPath",
+            "joinPath",
+            "relativePath",
+            "joinPath",
+            "test",
+            "copy",
+            "readDir",
+            "read",
+            "write",
+            "remove",
+            "create",
+            "getMetaData",
+            "createTemp",
+            "createTempDir"
+    );
+
+    public static final List<String> PUBLIC_DIRECTORIES = List.of(
+            "\"TMP\"",
+            "\"TEMP\"",
+            "\"TMPDIR\""
+    );
+}
diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/file/compiler/FileCompilerPlugin.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/file/compiler/FileCompilerPlugin.java
index da8a2083..80f4320a 100644
--- a/compiler-plugin/src/main/java/io/ballerina/stdlib/file/compiler/FileCompilerPlugin.java
+++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/file/compiler/FileCompilerPlugin.java
@@ -20,6 +20,10 @@
 
 import io.ballerina.projects.plugins.CompilerPlugin;
 import io.ballerina.projects.plugins.CompilerPluginContext;
+import io.ballerina.scan.ScannerContext;
+import io.ballerina.stdlib.file.compiler.staticcodeanalyzer.FileStaticCodeAnalyzer;
+
+import static io.ballerina.stdlib.file.compiler.Constants.SCANNER_CONTEXT;
 
 /**
  * File compiler plugin.
@@ -27,7 +31,11 @@
 public class FileCompilerPlugin extends CompilerPlugin {
 
     @Override
-    public void init(CompilerPluginContext compilerPluginContext) {
-        compilerPluginContext.addCodeAnalyzer(new FileCodeAnalyzer());
+    public void init(CompilerPluginContext context) {
+        context.addCodeAnalyzer(new FileCodeAnalyzer());
+        Object object = context.userData().get(SCANNER_CONTEXT);
+        if (object instanceof ScannerContext scannerContext) {
+            context.addCodeAnalyzer(new FileStaticCodeAnalyzer(scannerContext.getReporter()));
+        }
     }
 }
diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/file/compiler/staticcodeanalyzer/FilePathInjectionAnalyzer.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/file/compiler/staticcodeanalyzer/FilePathInjectionAnalyzer.java
new file mode 100644
index 00000000..1c70feb3
--- /dev/null
+++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/file/compiler/staticcodeanalyzer/FilePathInjectionAnalyzer.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.org)
+ *
+ * WSO2 LLC. licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package io.ballerina.stdlib.file.compiler.staticcodeanalyzer;
+
+import io.ballerina.compiler.syntax.tree.BinaryExpressionNode;
+import io.ballerina.compiler.syntax.tree.CaptureBindingPatternNode;
+import io.ballerina.compiler.syntax.tree.ExpressionNode;
+import io.ballerina.compiler.syntax.tree.FunctionArgumentNode;
+import io.ballerina.compiler.syntax.tree.FunctionBodyBlockNode;
+import io.ballerina.compiler.syntax.tree.FunctionCallExpressionNode;
+import io.ballerina.compiler.syntax.tree.FunctionDefinitionNode;
+import io.ballerina.compiler.syntax.tree.ImportOrgNameNode;
+import io.ballerina.compiler.syntax.tree.ImportPrefixNode;
+import io.ballerina.compiler.syntax.tree.ModulePartNode;
+import io.ballerina.compiler.syntax.tree.Node;
+import io.ballerina.compiler.syntax.tree.NodeList;
+import io.ballerina.compiler.syntax.tree.ParameterNode;
+import io.ballerina.compiler.syntax.tree.PositionalArgumentNode;
+import io.ballerina.compiler.syntax.tree.RequiredParameterNode;
+import io.ballerina.compiler.syntax.tree.SimpleNameReferenceNode;
+import io.ballerina.compiler.syntax.tree.StatementNode;
+import io.ballerina.compiler.syntax.tree.SyntaxKind;
+import io.ballerina.compiler.syntax.tree.VariableDeclarationNode;
+import io.ballerina.projects.Document;
+import io.ballerina.projects.plugins.AnalysisTask;
+import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext;
+import io.ballerina.scan.Reporter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static io.ballerina.stdlib.file.compiler.Constants.BALLERINA_ORG;
+import static io.ballerina.stdlib.file.compiler.Constants.FILE;
+import static io.ballerina.stdlib.file.compiler.Constants.FILE_FUNCTIONS;
+import static io.ballerina.stdlib.file.compiler.staticcodeanalyzer.FileRule.AVOID_PATH_INJECTION;
+
+/**
+ * Analyzer to detect potential file path injection vulnerabilities.
+ */
+public class FilePathInjectionAnalyzer implements AnalysisTask<SyntaxNodeAnalysisContext> {
+
+    private final Reporter reporter;
+
+    public FilePathInjectionAnalyzer(Reporter reporter) {
+        this.reporter = reporter;
+    }
+
+    @Override
+    public void perform(SyntaxNodeAnalysisContext context) {
+        if (!(context.node() instanceof FunctionCallExpressionNode functionCall)) {
+            return;
+        }
+
+        Document document = getDocument(context);
+        List<String> importPrefix = new ArrayList<>();
+        if (document.syntaxTree().rootNode() instanceof ModulePartNode modulePartNode) {
+            importPrefix = modulePartNode.imports().stream()
+                    .filter(importDeclarationNode -> {
+                        ImportOrgNameNode importOrgNameNode = importDeclarationNode.orgName().orElse(null);
+                        return importOrgNameNode != null && BALLERINA_ORG.equals(importOrgNameNode.orgName().text());
+                    })
+                    .filter(importDeclarationNode -> importDeclarationNode.moduleName().stream().anyMatch(
+                            moduleNameNode -> FILE.equals(moduleNameNode.text())))
+                    .map(importDeclarationNode -> {
+                        ImportPrefixNode importPrefixNode = importDeclarationNode.prefix().orElse(null);
+                        return importPrefixNode != null ? importPrefixNode.prefix().text() : FILE;
+                    }).toList();
+        }
+
+        String functionName = functionCall.functionName().toString();
+
+        boolean isFileOperation = importPrefix.stream().anyMatch(prefix ->
+                FILE_FUNCTIONS.stream().anyMatch(func -> functionName.equals(prefix + ":" + func))
+        );
+
+        if (isFileOperation && !isSafePath(functionCall)) {
+            this.reporter.reportIssue(document, functionCall.location(), AVOID_PATH_INJECTION.getId());
+        }
+    }
+
+    public static Document getDocument(SyntaxNodeAnalysisContext context) {
+        return context.currentPackage().module(context.moduleId()).document(context.documentId());
+    }
+
+    private boolean isSafePath(FunctionCallExpressionNode functionCall) {
+        NodeList<FunctionArgumentNode> arguments = functionCall.arguments();
+        if (arguments.isEmpty()) {
+            return true;
+        }
+
+        FunctionArgumentNode firstArg = arguments.get(0);
+        ExpressionNode argument;
+
+        if (firstArg instanceof PositionalArgumentNode) {
+            argument = ((PositionalArgumentNode) firstArg).expression();
+        } else {
+            return true;
+        }
+
+        if (argument instanceof BinaryExpressionNode binaryExpression &&
+                binaryExpression.operator().kind() == SyntaxKind.PLUS_TOKEN) {
+            return false;
+        }
+
+        if (argument instanceof SimpleNameReferenceNode variableRef) {
+            return isVariableSafe(variableRef);
+        }
+        return true;
+    }
+
+    private boolean isVariableSafe(SimpleNameReferenceNode variableRef) {
+        String variableName = variableRef.name().text();
+        Node currentNode = variableRef.parent();
+
+        while (currentNode != null) {
+            if (currentNode instanceof FunctionBodyBlockNode functionBody) {
+                for (StatementNode statement : functionBody.statements()) {
+                    if (statement instanceof VariableDeclarationNode varDecl &&
+                            isMatchingVariable(varDecl, variableName)) {
+                        if (hasConcatenationAssignment(varDecl) || isAssignedFromFunctionParameter(varDecl)) {
+                            return isFunctionParameter(variableRef);
+                        }
+                        return true;
+                    }
+                }
+            }
+            currentNode = currentNode.parent();
+        }
+        return true;
+    }
+
+    private boolean isMatchingVariable(VariableDeclarationNode varDecl, String variableName) {
+        return varDecl.typedBindingPattern().bindingPattern() instanceof CaptureBindingPatternNode bindingPattern
+                && bindingPattern.variableName().text().equals(variableName);
+    }
+
+    private boolean hasConcatenationAssignment(VariableDeclarationNode varDecl) {
+        return varDecl.initializer().orElse(null) instanceof BinaryExpressionNode binaryExpr
+                && binaryExpr.operator().kind() == SyntaxKind.PLUS_TOKEN;
+    }
+
+    private boolean isAssignedFromFunctionParameter(VariableDeclarationNode varDecl) {
+        return varDecl.initializer().orElse(null) instanceof SimpleNameReferenceNode;
+    }
+
+    private boolean isFunctionParameter(SimpleNameReferenceNode variableRef) {
+        String paramName = variableRef.name().text();
+        Node currentNode = variableRef.parent();
+
+        while (currentNode != null) {
+            if (currentNode instanceof FunctionDefinitionNode functionDef
+                    && (hasDirectParameterReference(functionDef, paramName) ||
+                    hasIndirectParameterReference(variableRef, functionDef))) {
+                return false;
+            }
+            currentNode = currentNode.parent();
+        }
+        return true;
+    }
+
+    private boolean hasDirectParameterReference(FunctionDefinitionNode functionDef, String paramName) {
+        for (ParameterNode param : functionDef.functionSignature().parameters()) {
+            if (param instanceof RequiredParameterNode reqParam && reqParam.paramName().isPresent()
+                    && reqParam.paramName().get().text().equals(paramName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean hasIndirectParameterReference(SimpleNameReferenceNode variableRef,
+                                                  FunctionDefinitionNode functionDef) {
+        for (ParameterNode param : functionDef.functionSignature().parameters()) {
+            if (param instanceof RequiredParameterNode reqParam && isIndirectFunctionParameter(variableRef, reqParam)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean isIndirectFunctionParameter(SimpleNameReferenceNode variableRef, RequiredParameterNode reqParam) {
+        Node currentNode = variableRef.parent();
+
+        while (currentNode != null) {
+            if (currentNode instanceof FunctionBodyBlockNode functionBody) {
+                for (StatementNode statement : functionBody.statements()) {
+                    if (statement instanceof VariableDeclarationNode varDecl
+                            && isBindingPatternMatch(varDecl, variableRef)) {
+                        return checkVariableInitializer(varDecl, reqParam);
+                    }
+                }
+            }
+            currentNode = currentNode.parent();
+        }
+        return false;
+    }
+
+    private boolean isBindingPatternMatch(VariableDeclarationNode varDecl, SimpleNameReferenceNode variableRef) {
+        return varDecl.typedBindingPattern().bindingPattern() instanceof CaptureBindingPatternNode bindingPattern
+                && bindingPattern.variableName().text().equals(variableRef.name().text());
+    }
+
+    private boolean checkVariableInitializer(VariableDeclarationNode varDecl, RequiredParameterNode reqParam) {
+        ExpressionNode initializer = varDecl.initializer().orElse(null);
+        return switch (initializer) {
+            case null -> false;
+            case SimpleNameReferenceNode initializerRef ->
+                    initializerRef.name().text().equals(reqParam.paramName().get().text());
+            case BinaryExpressionNode binaryExpr -> binaryExpr.operator().kind() == SyntaxKind.PLUS_TOKEN
+                    && isIndirectFunctionParameterFromBinary(binaryExpr, reqParam);
+            default -> false;
+        };
+    }
+
+    private boolean isIndirectFunctionParameterFromBinary(BinaryExpressionNode binaryExpr,
+                                                          RequiredParameterNode reqParam) {
+        if (binaryExpr.lhsExpr() instanceof SimpleNameReferenceNode leftRef &&
+                leftRef.name().text().equals(reqParam.paramName().get().text())) {
+            return true;
+        }
+        return binaryExpr.rhsExpr() instanceof SimpleNameReferenceNode rightRef &&
+                rightRef.name().text().equals(reqParam.paramName().get().text());
+    }
+}
diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/file/compiler/staticcodeanalyzer/FileRule.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/file/compiler/staticcodeanalyzer/FileRule.java
new file mode 100644
index 00000000..bb20dd5f
--- /dev/null
+++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/file/compiler/staticcodeanalyzer/FileRule.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.org)
+ *
+ * WSO2 LLC. licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package io.ballerina.stdlib.file.compiler.staticcodeanalyzer;
+
+import io.ballerina.scan.Rule;
+
+import static io.ballerina.scan.RuleKind.VULNERABILITY;
+import static io.ballerina.stdlib.file.compiler.staticcodeanalyzer.RuleFactory.createRule;
+
+/**
+ * Represents static code rules specific to the Ballerina File package.
+ */
+public enum FileRule {
+    AVOID_INSECURE_DIRECTORY_ACCESS(createRule(1, "Avoid using publicly writable directories for file " +
+                    "operations without proper access controls", VULNERABILITY)),
+    AVOID_PATH_INJECTION(createRule(2, "I/O function calls should not be vulnerable to path injection " +
+                    "attacks", VULNERABILITY));
+
+    private final Rule rule;
+
+    FileRule(Rule rule) {
+        this.rule = rule;
+    }
+
+    public int getId() {
+        return this.rule.numericId();
+    }
+
+    public Rule getRule() {
+        return this.rule;
+    }
+
+    @Override
+    public String toString() {
+        return "{\"id\":" + this.getId() + ", \"kind\":\"" + this.rule.kind() + "\"," +
+                " \"description\" : \"" + this.rule.description() + "\"}";
+    }
+}
diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/file/compiler/staticcodeanalyzer/FileStaticCodeAnalyzer.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/file/compiler/staticcodeanalyzer/FileStaticCodeAnalyzer.java
new file mode 100644
index 00000000..e4cc1c4e
--- /dev/null
+++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/file/compiler/staticcodeanalyzer/FileStaticCodeAnalyzer.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.org)
+ *
+ * WSO2 LLC. licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package io.ballerina.stdlib.file.compiler.staticcodeanalyzer;
+
+import io.ballerina.compiler.syntax.tree.SyntaxKind;
+import io.ballerina.projects.plugins.CodeAnalysisContext;
+import io.ballerina.projects.plugins.CodeAnalyzer;
+import io.ballerina.scan.Reporter;
+
+/**
+ * File static code analyser.
+ */
+public class FileStaticCodeAnalyzer extends CodeAnalyzer {
+    private final Reporter reporter;
+
+    public FileStaticCodeAnalyzer(Reporter reporter) {
+        this.reporter = reporter;
+    }
+
+    @Override
+    public void init(CodeAnalysisContext codeAnalysisContext) {
+        codeAnalysisContext.addSyntaxNodeAnalysisTask(new InsecureDirectoryAccessAnalyzer(reporter),
+                SyntaxKind.FUNCTION_CALL);
+        codeAnalysisContext.addSyntaxNodeAnalysisTask(new FilePathInjectionAnalyzer(reporter),
+                SyntaxKind.FUNCTION_CALL);
+    }
+}
diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/file/compiler/staticcodeanalyzer/InsecureDirectoryAccessAnalyzer.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/file/compiler/staticcodeanalyzer/InsecureDirectoryAccessAnalyzer.java
new file mode 100644
index 00000000..0fbde346
--- /dev/null
+++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/file/compiler/staticcodeanalyzer/InsecureDirectoryAccessAnalyzer.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.org)
+ *
+ * WSO2 LLC. licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package io.ballerina.stdlib.file.compiler.staticcodeanalyzer;
+
+import io.ballerina.compiler.syntax.tree.BasicLiteralNode;
+import io.ballerina.compiler.syntax.tree.ExpressionNode;
+import io.ballerina.compiler.syntax.tree.FunctionArgumentNode;
+import io.ballerina.compiler.syntax.tree.FunctionCallExpressionNode;
+import io.ballerina.compiler.syntax.tree.Node;
+import io.ballerina.compiler.syntax.tree.PositionalArgumentNode;
+import io.ballerina.compiler.syntax.tree.QualifiedNameReferenceNode;
+import io.ballerina.compiler.syntax.tree.SeparatedNodeList;
+import io.ballerina.projects.Document;
+import io.ballerina.projects.plugins.AnalysisTask;
+import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext;
+import io.ballerina.scan.Reporter;
+import io.ballerina.tools.diagnostics.Location;
+import org.ballerinalang.model.tree.expressions.StringTemplateLiteralNode;
+
+import static io.ballerina.stdlib.file.compiler.Constants.FILE_FUNCTIONS;
+import static io.ballerina.stdlib.file.compiler.Constants.OS;
+import static io.ballerina.stdlib.file.compiler.Constants.GET_ENV;
+import static io.ballerina.stdlib.file.compiler.Constants.FILE;
+import static io.ballerina.stdlib.file.compiler.Constants.PUBLIC_DIRECTORIES;
+import static io.ballerina.stdlib.file.compiler.staticcodeanalyzer.FileRule.AVOID_INSECURE_DIRECTORY_ACCESS;
+
+public class InsecureDirectoryAccessAnalyzer implements AnalysisTask<SyntaxNodeAnalysisContext> {
+    private final Reporter reporter;
+
+    public InsecureDirectoryAccessAnalyzer(Reporter reporter) {
+        this.reporter = reporter;
+    }
+
+    @Override
+    public void perform(SyntaxNodeAnalysisContext context) {
+        if (!(context.node() instanceof FunctionCallExpressionNode functionCall)) {
+            return;
+        }
+
+        if (!isFileMethodCall(functionCall)) {
+            return;
+        }
+
+        Document document = getDocument(context);
+        Location location = functionCall.location();
+        this.reporter.reportIssue(document, location, AVOID_INSECURE_DIRECTORY_ACCESS.getId());
+    }
+
+    public static Document getDocument(SyntaxNodeAnalysisContext context) {
+        return context.currentPackage().module(context.moduleId()).document(context.documentId());
+    }
+
+    private boolean isFileMethodCall(FunctionCallExpressionNode functionCall) {
+        Node currentNode = functionCall;
+        while (currentNode != null) {
+            if (currentNode instanceof FunctionCallExpressionNode currentMethodCall) {
+                if (currentMethodCall.functionName() instanceof QualifiedNameReferenceNode qNode) {
+                    String modulePrefix = qNode.modulePrefix().text();
+                    String functionName = qNode.identifier().text();
+                    if (modulePrefix.equals(OS) && functionName.equals(GET_ENV)) {
+                        String envVarName = currentMethodCall.arguments().get(0).toString();
+                        if (PUBLIC_DIRECTORIES.contains(envVarName)) {
+                            return true;
+                        }
+                    }
+                }
+
+                if (currentMethodCall.functionName() instanceof QualifiedNameReferenceNode fileCallNode) {
+                    String fileModulePrefix = fileCallNode.modulePrefix().text();
+                    String fileFunctionName = fileCallNode.identifier().text();
+                    if (fileModulePrefix.equals(FILE) && FILE_FUNCTIONS.contains(fileFunctionName)) {
+                        SeparatedNodeList<FunctionArgumentNode> arguments = currentMethodCall.arguments();
+                        if (arguments != null && !arguments.isEmpty()) {
+                            FunctionArgumentNode pathArg = arguments.get(0);
+                            if (pathArg instanceof PositionalArgumentNode posArg) {
+                                ExpressionNode pathExpr = posArg.expression();
+                                if (pathExpr instanceof BasicLiteralNode pathLiteral) {
+                                    String filePath = pathLiteral.toString().trim();
+                                    if (isInsecureDirectory(filePath)) {
+                                        return true;
+                                    }
+                                } else if (pathExpr instanceof StringTemplateLiteralNode templatePath) {
+                                    String filePath = templatePath.toString().trim();
+                                    if (isInsecureDirectory(filePath)) {
+                                        return true;
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            currentNode = currentNode.parent();
+        }
+        return false;
+    }
+
+    private boolean isInsecureDirectory(String filePath) {
+        return PUBLIC_DIRECTORIES.contains(filePath);
+    }
+}
diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/file/compiler/staticcodeanalyzer/RuleFactory.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/file/compiler/staticcodeanalyzer/RuleFactory.java
new file mode 100644
index 00000000..cb0b7136
--- /dev/null
+++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/file/compiler/staticcodeanalyzer/RuleFactory.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.org)
+ *
+ * WSO2 LLC. licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package io.ballerina.stdlib.file.compiler.staticcodeanalyzer;
+
+import io.ballerina.scan.Rule;
+import io.ballerina.scan.RuleKind;
+
+/**
+ * {@code RuleFactory} contains the logic to create a {@link Rule}.
+ */
+public class RuleFactory {
+
+    private RuleFactory() {
+    }
+
+    public static Rule createRule(int id, String description, RuleKind kind) {
+        return new RuleImpl(id, description, kind);
+    }
+}
diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/file/compiler/staticcodeanalyzer/RuleImpl.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/file/compiler/staticcodeanalyzer/RuleImpl.java
new file mode 100644
index 00000000..e4628b86
--- /dev/null
+++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/file/compiler/staticcodeanalyzer/RuleImpl.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.org)
+ *
+ * WSO2 LLC. licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package io.ballerina.stdlib.file.compiler.staticcodeanalyzer;
+
+import io.ballerina.scan.Rule;
+import io.ballerina.scan.RuleKind;
+
+public class RuleImpl implements Rule {
+    private final int id;
+    private final String description;
+    private final RuleKind kind;
+
+    RuleImpl(int id, String description, RuleKind kind) {
+        this.id = id;
+        this.description = description;
+        this.kind = kind;
+    }
+
+    @Override
+    public String id() {
+        return Integer.toString(this.id);
+    }
+
+    @Override
+    public int numericId() {
+        return this.id;
+    }
+
+    @Override
+    public String description() {
+        return this.description;
+    }
+
+    @Override
+    public RuleKind kind() {
+        return this.kind;
+    }
+}
diff --git a/compiler-plugin/src/main/java/module-info.java b/compiler-plugin/src/main/java/module-info.java
index 8140c1b5..4adeb76a 100644
--- a/compiler-plugin/src/main/java/module-info.java
+++ b/compiler-plugin/src/main/java/module-info.java
@@ -17,8 +17,11 @@
  */
 
 module io.ballerina.stdlib.file.plugin {
+    requires io.ballerina.scan;
     requires io.ballerina.lang;
     requires io.ballerina.parser;
     requires io.ballerina.tools.api;
+    requires java.xml;
     exports io.ballerina.stdlib.file.compiler;
+    exports io.ballerina.stdlib.file.compiler.staticcodeanalyzer;
 }
diff --git a/compiler-plugin/src/main/resources/rules.json b/compiler-plugin/src/main/resources/rules.json
new file mode 100644
index 00000000..74f2815d
--- /dev/null
+++ b/compiler-plugin/src/main/resources/rules.json
@@ -0,0 +1,12 @@
+[
+  {
+    "id":  1,
+    "kind": "VULNERABILITY",
+    "description": "Avoid using publicly writable directories for file operations without proper access controls"
+  },
+  {
+    "id": 2,
+    "kind": "VULNERABILITY",
+    "description": "I/O function calls should not be vulnerable to path injection attacks"
+  }
+]
diff --git a/gradle.properties b/gradle.properties
index 41cc8d19..4aee1758 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -19,3 +19,6 @@ stdlibLogVersion=2.12.0-20250305-165600-b4606dd
 stdlibOsVersion=1.10.0-20250305-165700-ad282f0
 observeVersion=1.5.0-20250304-140800-d60274b
 observeInternalVersion=1.5.0-20250305-160800-51dc066
+
+jacocoVersion=0.8.10
+balScanVersion=0.5.0


Reviews:
user: login: daneshk
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-25T07:36:39Z
user: login: SachinAkash01
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T05:58:29Z
user: login: daneshk
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T15:14:34Z
user: login: daneshk
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-05T15:17:00Z

Review comments:
diff_hunk: @@ -0,0 +1,237 @@
+/*
+ * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.org)
+ *
+ * WSO2 LLC. licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package io.ballerina.stdlib.file.compiler.staticcodeanalyzer;
+
+import io.ballerina.compiler.syntax.tree.BinaryExpressionNode;
+import io.ballerina.compiler.syntax.tree.CaptureBindingPatternNode;
+import io.ballerina.compiler.syntax.tree.ExpressionNode;
+import io.ballerina.compiler.syntax.tree.FunctionArgumentNode;
+import io.ballerina.compiler.syntax.tree.FunctionBodyBlockNode;
+import io.ballerina.compiler.syntax.tree.FunctionCallExpressionNode;
+import io.ballerina.compiler.syntax.tree.FunctionDefinitionNode;
+import io.ballerina.compiler.syntax.tree.Node;
+import io.ballerina.compiler.syntax.tree.NodeList;
+import io.ballerina.compiler.syntax.tree.ParameterNode;
+import io.ballerina.compiler.syntax.tree.PositionalArgumentNode;
+import io.ballerina.compiler.syntax.tree.RequiredParameterNode;
+import io.ballerina.compiler.syntax.tree.SimpleNameReferenceNode;
+import io.ballerina.compiler.syntax.tree.StatementNode;
+import io.ballerina.compiler.syntax.tree.SyntaxKind;
+import io.ballerina.compiler.syntax.tree.VariableDeclarationNode;
+import io.ballerina.projects.Document;
+import io.ballerina.projects.plugins.AnalysisTask;
+import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext;
+import io.ballerina.scan.Reporter;
+import io.ballerina.tools.diagnostics.Location;
+
+import static io.ballerina.stdlib.file.compiler.Constants.FILE_REMOVE;
+import static io.ballerina.stdlib.file.compiler.Constants.FILE_READ;
+import static io.ballerina.stdlib.file.compiler.Constants.FILE_WRITE;
+import static io.ballerina.stdlib.file.compiler.staticcodeanalyzer.FileRule.AVOID_PATH_INJECTION;
+
+/**
+ * Analyzer to detect potential file path injection vulnerabilities.
+ */
+public class FilePathInjectionAnalyzer implements AnalysisTask<SyntaxNodeAnalysisContext> {
+
+    private final Reporter reporter;
+
+    public FilePathInjectionAnalyzer(Reporter reporter) {
+        this.reporter = reporter;
+    }
+
+    @Override
+    public void perform(SyntaxNodeAnalysisContext context) {
+        if (!(context.node() instanceof FunctionCallExpressionNode functionCall)) {
+            return;
+        }
+
+        Document document = getDocument(context);
+
+        String functionName = functionCall.functionName().toString();
+
+        // Detect vulnerable file function calls
+        if (FILE_REMOVE.equals(functionName) || FILE_READ.equals(functionName) ||
+                FILE_WRITE.equals(functionName)) {
path: compiler-plugin/src/main/java/io/ballerina/stdlib/file/compiler/staticcodeanalyzer/FilePathInjectionAnalyzer.java
user: login: daneshk
body: We need to consider import aliases as well here. Functions may not always be in the `file:` format
created_at: 2025-02-25T07:36:39Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,237 @@
+/*
+ * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.org)
+ *
+ * WSO2 LLC. licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package io.ballerina.stdlib.file.compiler.staticcodeanalyzer;
+
+import io.ballerina.compiler.syntax.tree.BinaryExpressionNode;
+import io.ballerina.compiler.syntax.tree.CaptureBindingPatternNode;
+import io.ballerina.compiler.syntax.tree.ExpressionNode;
+import io.ballerina.compiler.syntax.tree.FunctionArgumentNode;
+import io.ballerina.compiler.syntax.tree.FunctionBodyBlockNode;
+import io.ballerina.compiler.syntax.tree.FunctionCallExpressionNode;
+import io.ballerina.compiler.syntax.tree.FunctionDefinitionNode;
+import io.ballerina.compiler.syntax.tree.Node;
+import io.ballerina.compiler.syntax.tree.NodeList;
+import io.ballerina.compiler.syntax.tree.ParameterNode;
+import io.ballerina.compiler.syntax.tree.PositionalArgumentNode;
+import io.ballerina.compiler.syntax.tree.RequiredParameterNode;
+import io.ballerina.compiler.syntax.tree.SimpleNameReferenceNode;
+import io.ballerina.compiler.syntax.tree.StatementNode;
+import io.ballerina.compiler.syntax.tree.SyntaxKind;
+import io.ballerina.compiler.syntax.tree.VariableDeclarationNode;
+import io.ballerina.projects.Document;
+import io.ballerina.projects.plugins.AnalysisTask;
+import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext;
+import io.ballerina.scan.Reporter;
+import io.ballerina.tools.diagnostics.Location;
+
+import static io.ballerina.stdlib.file.compiler.Constants.FILE_REMOVE;
+import static io.ballerina.stdlib.file.compiler.Constants.FILE_READ;
+import static io.ballerina.stdlib.file.compiler.Constants.FILE_WRITE;
+import static io.ballerina.stdlib.file.compiler.staticcodeanalyzer.FileRule.AVOID_PATH_INJECTION;
+
+/**
+ * Analyzer to detect potential file path injection vulnerabilities.
+ */
+public class FilePathInjectionAnalyzer implements AnalysisTask<SyntaxNodeAnalysisContext> {
+
+    private final Reporter reporter;
+
+    public FilePathInjectionAnalyzer(Reporter reporter) {
+        this.reporter = reporter;
+    }
+
+    @Override
+    public void perform(SyntaxNodeAnalysisContext context) {
+        if (!(context.node() instanceof FunctionCallExpressionNode functionCall)) {
+            return;
+        }
+
+        Document document = getDocument(context);
+
+        String functionName = functionCall.functionName().toString();
+
+        // Detect vulnerable file function calls
+        if (FILE_REMOVE.equals(functionName) || FILE_READ.equals(functionName) ||
+                FILE_WRITE.equals(functionName)) {
path: compiler-plugin/src/main/java/io/ballerina/stdlib/file/compiler/staticcodeanalyzer/FilePathInjectionAnalyzer.java
user: login: SachinAkash01
body: Fixed with https://github.com/ballerina-platform/module-ballerina-file/pull/558/commits/f91a8d53c2409c3205a782bddb6f6252793037f2
created_at: 2025-03-03T05:58:29Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,8 @@
+[package]
+org = "sachink"
+name = "rule2"
+version = "0.1.0"
+distribution = "2201.11.0-20250127-101700-a4b67fe5"
path: compiler-plugin-test/src/test/resources/static_code_analyzer/ballerina_packages/rule2/Ballerina.toml
user: login: daneshk
body: ```suggestion
distribution = "2201.11.0"
```
created_at: 2025-03-05T15:14:34Z
author_association: MEMBER

PR comments:
user: login: codecov[bot]
created_at: 2025-02-27T09:37:49Z
body: ## [Codecov](https://app.codecov.io/gh/ballerina-platform/module-ballerina-file/pull/558?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ballerina-platform) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 82.20%. Comparing base [(`e9a08f6`)](https://app.codecov.io/gh/ballerina-platform/module-ballerina-file/commit/e9a08f6761339264c6a1d9d128f098194ba35248?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ballerina-platform) to head [(`00483cf`)](https://app.codecov.io/gh/ballerina-platform/module-ballerina-file/commit/00483cf8b6dbbe07339888d2f8aa4948244f2046?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ballerina-platform).
> Report is 22 commits behind head on master.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff            @@
##             master     #558   +/-   ##
=========================================
  Coverage     82.20%   82.20%           
  Complexity       91       91           
=========================================
  Files            18       18           
  Lines           770      770           
  Branches        164      164           
=========================================
  Hits            633      633           
  Misses          118      118           
  Partials         19       19           
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/ballerina-platform/module-ballerina-file/pull/558?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ballerina-platform).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ballerina-platform).

author_association: NONE
user: login: sonarqubecloud[bot]
created_at: 2025-03-05T15:15:00Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=ballerina-platform_module-ballerina-file&pullRequest=558) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [2 New issues](https://sonarcloud.io/project/issues?id=ballerina-platform_module-ballerina-file&pullRequest=558&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=ballerina-platform_module-ballerina-file&pullRequest=558&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=ballerina-platform_module-ballerina-file&pullRequest=558&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=ballerina-platform_module-ballerina-file&pullRequest=558&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=ballerina-platform_module-ballerina-file&pullRequest=558&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=ballerina-platform_module-ballerina-file&pullRequest=558)


author_association: NONE