├── .gitignore ├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── SonarAnalyzer.FSharp ├── README.md ├── SonarAnalyzer.FSharp.sln ├── paket.dependencies ├── paket.lock ├── pom.xml ├── src │ ├── FSharpAst │ │ ├── Config.fs │ │ ├── FSharpAst.fsproj │ │ ├── README.md │ │ ├── Tast.fs │ │ ├── TastExamples.fsx │ │ ├── TastVisitor.fs │ │ ├── TastVisitorExamples.fsx │ │ ├── Transformer.fs │ │ ├── TransformerApi.fs │ │ └── WellKnown.fs │ ├── FsSonarRunner │ │ ├── AnalysisConfigDto.fs │ │ ├── AssemblyInfo.fs │ │ ├── DiagnosticsDto.fs │ │ ├── ExportQualityProfile.fs │ │ ├── ExportRuleDefinitions.fs │ │ ├── FsSonarRunner.fsproj │ │ ├── ImportExportAnalysisConfig.fs │ │ ├── OutputDiagnostics.fs │ │ ├── Program.fs │ │ ├── README.md │ │ ├── RuleDefinitionDto.fs │ │ └── Utilities.fs │ └── SonarAnalyzer.FSharp │ │ ├── AssemblyInfo.fs │ │ ├── Domain.fs │ │ ├── InteractiveExamples.fsx │ │ ├── README.md │ │ ├── RspecStrings.fs │ │ ├── RspecStrings.resx │ │ ├── RuleHelpers.fs │ │ ├── RuleManager.fs │ │ ├── RuleRunner.fs │ │ ├── Rules.Description │ │ ├── S1104.html │ │ ├── S1313.html │ │ ├── S2068.html │ │ ├── S2070.html │ │ ├── S2077.html │ │ ├── S2092.html │ │ ├── S2228.html │ │ ├── S2245.html │ │ ├── S2255.html │ │ ├── S2278.html │ │ ├── S2386.html │ │ ├── S2486.html │ │ ├── S3011.html │ │ ├── S3330.html │ │ ├── S3884.html │ │ ├── S4211.html │ │ ├── S4212.html │ │ ├── S4426.html │ │ ├── S4432.html │ │ ├── S4433.html │ │ ├── S4507.html │ │ ├── S4564.html │ │ ├── S4784.html │ │ ├── S4787.html │ │ ├── S4790.html │ │ ├── S4792.html │ │ ├── S4818.html │ │ ├── S4823.html │ │ ├── S4829.html │ │ ├── S4834.html │ │ ├── S5042.html │ │ └── notes.txt │ │ ├── Rules │ │ └── Hotspots │ │ │ ├── S1313_HardcodedIpAddress.fs │ │ │ ├── S2077_ExecutingSqlQueries.fs │ │ │ ├── S2092_CookieShouldBeSecure.fs │ │ │ ├── S2245_DoNotUseRandom.fs │ │ │ ├── S2255_UsingCookies.fs │ │ │ ├── S3011_BypassingAccessibility.fs │ │ │ ├── S4507_DeliveringDebugFeaturesInProduction.fs │ │ │ ├── S4784_UsingRegularExpressions.fs │ │ │ ├── S4787_EncryptingData.fs │ │ │ ├── S4790_CreatingHashAlgorithms.fs │ │ │ ├── S4792_ConfiguringLoggers.fs │ │ │ ├── S4818_SocketsCreation.fs │ │ │ ├── S4823_UsingCommandLineArguments.fs │ │ │ ├── S4829_ReadingStandardInput.fs │ │ │ ├── S4834_ControllingPermissions.fs │ │ │ └── S5042_ExpandingArchiveFiles.fs │ │ ├── SonarAnalyzer.FSharp.fsproj │ │ └── props.txt ├── tests │ ├── FSharpAst.UnitTest │ │ ├── FSharpAst.UnitTest.fsproj │ │ └── TastVisitor.fs │ └── SonarAnalyzer.FSharp.UnitTest │ │ ├── NoncompliantLocationsTest.fs │ │ ├── RspecStringsTest.fs │ │ ├── RuleManagerTest.fs │ │ ├── SonarAnalyzer.FSharp.UnitTest.fsproj │ │ ├── SourceFileTests.fs │ │ ├── TestCases │ │ ├── S1313_HardcodedIpAddress.fs │ │ ├── S1313_HardcodedIpAddress.fsi │ │ ├── S2077_ExecutingSqlQueries.fs │ │ ├── S2092_CookieShouldBeSecure.fs │ │ ├── S2245_DoNotUseRandom.fs │ │ ├── S2255_UsingCookies.fs │ │ ├── S3011_BypassingAccessibility.fs │ │ ├── S4507_DeliveringDebugFeaturesInProduction.fs │ │ ├── S4784_UsingRegularExpressions.fs │ │ ├── S4787_EncryptingData.fs │ │ ├── S4790_CreatingHashAlgorithms.fs │ │ ├── S4792_ConfiguringLoggers_AspNetCore.fs │ │ ├── S4792_ConfiguringLoggers_Serilog.fs │ │ ├── S4818_SocketsCreation.fs │ │ ├── S4823_UsingCommandLineArguments.fs │ │ ├── S4829_ReadingStandardInput.fs │ │ ├── S4834_ControllingPermissions.fs │ │ └── S5042_ExpandingArchiveFiles.fs │ │ └── Verifier.fs └── zip-assembly.xml ├── appveyor.yml ├── docs ├── contributing-analyzer.md └── contributing-plugin.md ├── pom.xml └── sonar-fsharpsecurity-plugin ├── pom.xml └── src ├── license-header.txt ├── main ├── java │ └── org │ │ └── sonar │ │ └── plugins │ │ └── fsharp │ │ ├── FSharpAnalysisResultImporter.java │ │ ├── FSharpIssue.java │ │ ├── FSharpLanguage.java │ │ ├── FSharpPlugin.java │ │ ├── FSharpSensor.java │ │ ├── FSharpSonarRulesDefinition.java │ │ ├── FSharpSonarWayProfile.java │ │ ├── FsSonarRunnerExtractor.java │ │ └── utils │ │ ├── OSInfo.java │ │ └── UnZip.java └── resources │ ├── profile.txt │ └── rules.xml └── test ├── java └── org │ └── sonar │ └── plugins │ └── fsharp │ ├── FSharpAnalysisResultImporterTest.java │ ├── FSharpLanguageTest.java │ ├── FSharpPluginTest.java │ ├── FSharpSensorTest.java │ ├── FSharpSonarRulesDefinitionTest.java │ └── FSharpSonarWayProfileTest.java └── resources ├── profileExample.txt ├── rulesExample.xml └── sonarDiagnosticsExample.xml /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to SonarAnalyzer.FSharp 2 | 3 | Coming soon 4 | 5 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/README.md: -------------------------------------------------------------------------------- 1 | # SonarAnalyzer.FSharp 2 | 3 | This directory contains all the F# code for the F# SonarAnalyzer plugin. 4 | 5 | [Build instructions](../docs/contributing-analyzer.md) -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/SonarAnalyzer.FSharp.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28010.2041 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "SonarAnalyzer.FSharp", "src\SonarAnalyzer.FSharp\SonarAnalyzer.FSharp.fsproj", "{6C0CDC3D-E193-4BC4-ABB4-5A4166FF2270}" 7 | EndProject 8 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "SonarAnalyzer.FSharp.UnitTest", "tests\SonarAnalyzer.FSharp.UnitTest\SonarAnalyzer.FSharp.UnitTest.fsproj", "{DC59291E-4AE3-45A4-A4D6-2CFE20285D1B}" 9 | EndProject 10 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharpAst", "src\FSharpAst\FSharpAst.fsproj", "{6725F379-DE05-47EF-85C0-ED26E3F40C37}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{57C64AD9-8467-4CC9-812B-FE66953E4F42}" 13 | ProjectSection(SolutionItems) = preProject 14 | paket.dependencies = paket.dependencies 15 | EndProjectSection 16 | EndProject 17 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharpAst.UnitTest", "tests\FSharpAst.UnitTest\FSharpAst.UnitTest.fsproj", "{E9B8895E-375B-46F1-9F5D-C27D4BF28EDA}" 18 | EndProject 19 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FsSonarRunner", "src\FsSonarRunner\FsSonarRunner.fsproj", "{5A7EFA9B-2015-4C7A-9F3A-9D534A29B226}" 20 | EndProject 21 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "doc", "doc", "{A3E30814-9225-4601-B09B-577EB58EA62A}" 22 | ProjectSection(SolutionItems) = preProject 23 | ..\docs\contributing-analyzer.md = ..\docs\contributing-analyzer.md 24 | ..\docs\contributing-plugin.md = ..\docs\contributing-plugin.md 25 | ..\CONTRIBUTING.md = ..\CONTRIBUTING.md 26 | README.md = README.md 27 | ..\README.md = ..\README.md 28 | EndProjectSection 29 | EndProject 30 | Global 31 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 32 | Debug|Any CPU = Debug|Any CPU 33 | Release|Any CPU = Release|Any CPU 34 | EndGlobalSection 35 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 36 | {6C0CDC3D-E193-4BC4-ABB4-5A4166FF2270}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {6C0CDC3D-E193-4BC4-ABB4-5A4166FF2270}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {6C0CDC3D-E193-4BC4-ABB4-5A4166FF2270}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {6C0CDC3D-E193-4BC4-ABB4-5A4166FF2270}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {DC59291E-4AE3-45A4-A4D6-2CFE20285D1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {DC59291E-4AE3-45A4-A4D6-2CFE20285D1B}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {DC59291E-4AE3-45A4-A4D6-2CFE20285D1B}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {DC59291E-4AE3-45A4-A4D6-2CFE20285D1B}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {6725F379-DE05-47EF-85C0-ED26E3F40C37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {6725F379-DE05-47EF-85C0-ED26E3F40C37}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {6725F379-DE05-47EF-85C0-ED26E3F40C37}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {6725F379-DE05-47EF-85C0-ED26E3F40C37}.Release|Any CPU.Build.0 = Release|Any CPU 48 | {E9B8895E-375B-46F1-9F5D-C27D4BF28EDA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {E9B8895E-375B-46F1-9F5D-C27D4BF28EDA}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {E9B8895E-375B-46F1-9F5D-C27D4BF28EDA}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 | {E9B8895E-375B-46F1-9F5D-C27D4BF28EDA}.Release|Any CPU.Build.0 = Release|Any CPU 52 | {5A7EFA9B-2015-4C7A-9F3A-9D534A29B226}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 53 | {5A7EFA9B-2015-4C7A-9F3A-9D534A29B226}.Debug|Any CPU.Build.0 = Debug|Any CPU 54 | {5A7EFA9B-2015-4C7A-9F3A-9D534A29B226}.Release|Any CPU.ActiveCfg = Release|Any CPU 55 | {5A7EFA9B-2015-4C7A-9F3A-9D534A29B226}.Release|Any CPU.Build.0 = Release|Any CPU 56 | EndGlobalSection 57 | GlobalSection(SolutionProperties) = preSolution 58 | HideSolutionNode = FALSE 59 | EndGlobalSection 60 | GlobalSection(ExtensibilityGlobals) = postSolution 61 | SolutionGuid = {0F744F1A-CC7B-476C-BE57-B88EAB91EAC8} 62 | EndGlobalSection 63 | EndGlobal 64 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/paket.dependencies: -------------------------------------------------------------------------------- 1 | source https://www.nuget.org/api/v2 2 | 3 | nuget FSharp.Compiler.Service 4 | 5 | group Build 6 | //nuget FAKE 7 | 8 | 9 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/FSharpAst/Config.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpAst 2 | 3 | // =========================== 4 | // Configuration 5 | // =========================== 6 | 7 | /// Configuration for the Transformer 8 | type TransformerConfig = { 9 | 10 | /// The AST has location information for each object. This is useful for debugging and helful error messages. 11 | /// But it can interfere with testing (because of structural equality). 12 | /// To make testing easier, this flag allows an known empty location to be used instead. 13 | UseEmptyLocation : bool 14 | } 15 | with 16 | static member Default = { 17 | UseEmptyLocation = false 18 | } 19 | 20 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/FSharpAst/FSharpAst.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/FSharpAst/README.md: -------------------------------------------------------------------------------- 1 | # FSharpAst 2 | 3 | **WORK IN PROGRESS** 4 | 5 | **This project is still being worked on. It will be part of `SonarAnalyzer.FSharp` until it becomes stable!** 6 | 7 | The project aims to make the Fsharp Compiler Services easier to understand and use by: 8 | 9 | * Creating a set of AST types that well documented and are easier to understand 10 | * A process to convert the FSC types into this AST 11 | * Tools to work with the new AST 12 | 13 | ## Why do this? 14 | 15 | The FSC classes are designed for efficiency but are quite hard to understand because their design 16 | is mixed up with the implementation of the actual compiler. 17 | There are some helpers in the form of Active Patterns but these are not well documented. 18 | 19 | Some benefits of having a "domain-only" AST: 20 | 21 | * The entire AST is in one file, making it easier to understand and document. 22 | * Because this AST uses DUs rather than active patterns, we get the benefits of exhaustive pattern matching. 23 | * Finally, the names of the fields in the AST have been tweaked to make them more consistent, again to aid understanding. 24 | 25 | ## What are the downsides? 26 | 27 | This AST is better for batch-style processing. It is not meant to be used when high performance is needed. 28 | So for interactive tooling (such as Ionide) the original FSC is better. 29 | 30 | However, it follows the FSC model reasonably closely, so if you understand this AST, it is a good 31 | launching point to understand the real one. 32 | 33 | ## Potential uses 34 | 35 | * To implement batch-oriented source analyzers. 36 | In fact, the motivation for this project came from implementing a F# SonarQube scanner. 37 | * To implement a transpiler. E.g from F# to C#. 38 | The Fable implementation uses a similar approach of creating an intermediate AST. 39 | 40 | ## Notes on the design and implementation 41 | 42 | ### Concepts 43 | 44 | The overall hierarchy of the AST is: 45 | 46 | * A File contains "Declarations" 47 | * A Declaration is either an "Entity" (Namespace, Module, or Type) or a "Member, Function, or Value" 48 | * A Namespace contains a list of sub Declarations 49 | * A Module contains a list of sub Declarations 50 | * A "Member, Function, or Value" (or MFV for short) is the name given to any declaration that contains code. 51 | (as opposed to data declarations such as types, or containers such as modules). 52 | An MFV has a name, attributes, etc, and most importantly, a "Body" which is an "Expression". 53 | * Property members are compiled into two separate methods: `get_MyProp` and `set_MyProp`. 54 | * Top level values in a module are similar to read-only properties on a static class, 55 | but are compiled without the "get_" prefix. 56 | So just `myValue` rather than `get_myValue`. 57 | 58 | * An "Expression" represents executable code. There are lots of different kinds of expressions, such as constant expressions, 59 | calls, new object creation, etc. See the `Expression` DU for the complete list. 60 | * Expressions generally contain sub-expressions. For example, in a function call, the arguments are themselves expressions, 61 | and so on. 62 | 63 | ### Abbreviations 64 | 65 | Common abbreviations are: 66 | 67 | * `expr` for Expression 68 | * `ty` or `t` for Type 69 | * `arg` or `a` for Argument 70 | * `param` or `p` for Parameter 71 | * `prop` for Property 72 | * `mfv` for "Member, Function, or Value" 73 | * `m` for Member 74 | 75 | 76 | ### Naming conventions 77 | 78 | To assist in understandability, some common conventions are used: 79 | 80 | * "Parameters" vs "arguments". In this design a "parameter" is what is used at the declaration site. 81 | An argument is what is used at the call site. In some cases, the number of parameters is not the same 82 | as the number of arguments. 83 | * "typeArg" vs "argType". 84 | A `typeArg` is an argument applied to a generic type. E.g in `List`, `int` is the type arg. 85 | An `argType` is the type of an argument. E.g in `x(1)`, `1` is the first arg, and `int` is the corresponding argType. 86 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/FSharpAst/TastExamples.fsx: -------------------------------------------------------------------------------- 1 |  2 | (* ===================================================== 3 | To work with this project interactively, you need access to FSharp.Compiler.Service.dll from NuGet 4 | 5 | 1) Install paket in the "sonaranalyzer-fsharp" directory 6 | cd sonaranalyzer-fsharp 7 | dotnet tool install --tool-path ".paket" Paket --add-source https://api.nuget.org/v3/index.json 8 | 9 | 2) Run paket to install the packages 10 | .paket\paket.exe install 11 | 12 | ===================================================== *) 13 | 14 | 15 | #r @"..\..\packages\FSharp.Compiler.Service\lib\netstandard2.0\FSharp.Compiler.Service.dll" 16 | #r @"bin\Debug\netstandard2.0\FSharpAst.dll" 17 | 18 | open FSharpAst 19 | 20 | //let config = TransformerConfig.Default 21 | let config = {TransformerConfig.Default with UseEmptyLocation=true} 22 | let translateText text = FSharpAst.TextApi.translateText config text 23 | 24 | // --------------------------- 25 | // function call 26 | // --------------------------- 27 | 28 | let text = """ 29 | module MyModule = 30 | let test x = x 31 | let z = MyModule.test(1) 32 | """ 33 | 34 | let tast = translateText text 35 | 36 | // --------------------------- 37 | // static method call 38 | // --------------------------- 39 | 40 | let text = """ 41 | module MyModule = 42 | type X<'b>() = 43 | static member Test<'a>(x:'a) = [|x|] 44 | 45 | let z q = MyModule.X.Test(q) 46 | """ 47 | 48 | let tast = translateText text 49 | 50 | 51 | // --------------------------- 52 | // method call 53 | // --------------------------- 54 | 55 | let text = """ 56 | module MyModule = 57 | type X() = 58 | member this.Test<'b> x = x 59 | let x = MyModule.X() 60 | let z = x.Test(1) 61 | """ 62 | 63 | let tast = translateText text 64 | 65 | let text = """ 66 | module MyModule = 67 | type X<'a>() = class end 68 | let x = MyModule.X() 69 | """ 70 | 71 | let tast = translateText text 72 | 73 | let text = """ 74 | type HttpCookie(str:string) = 75 | member val Secure = false with get, set 76 | 77 | let x= HttpCookie("c", Secure = true) 78 | """ 79 | 80 | let tast = translateText text 81 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/FSharpAst/TastVisitorExamples.fsx: -------------------------------------------------------------------------------- 1 |  2 | (* ===================================================== 3 | To work with this project interactively, you need access to FSharp.Compiler.Service.dll from NuGet 4 | 5 | 1) Install paket in the "sonaranalyzer-fsharp" directory 6 | cd sonaranalyzer-fsharp 7 | dotnet tool install --tool-path ".paket" Paket --add-source https://api.nuget.org/v3/index.json 8 | 9 | 2) Run paket to install the packages 10 | .paket\paket.exe install 11 | 12 | ===================================================== *) 13 | 14 | 15 | #r @"..\..\packages\FSharp.Compiler.Service\lib\netstandard2.0\FSharp.Compiler.Service.dll" 16 | #r @"bin\Debug\netstandard2.0\FSharpAst.dll" 17 | #r @"netstandard" 18 | 19 | open FSharpAst 20 | 21 | //let config = TransformerConfig.Default 22 | let config = {TransformerConfig.Default with UseEmptyLocation=true} 23 | let translateText text = FSharpAst.TextApi.translateText config text 24 | 25 | let tryMatchType<'T> (node:obj) = 26 | match node with 27 | | :? 'T as node -> Some node 28 | | _ -> None 29 | 30 | let matchNamedType (node:Tast.FSharpType) typeDesc = 31 | match node with 32 | /// A type expression we don't know how to handle 33 | | Tast.NamedType namedType -> 34 | namedType.Descriptor = typeDesc 35 | | Tast.UnknownFSharpType _ 36 | | Tast.VariableType _ 37 | | Tast.TupleType _ 38 | | Tast.FunctionType _ 39 | | Tast.ArrayType _ 40 | | Tast.OtherType _ 41 | | Tast.ByRefType _ -> false 42 | 43 | 44 | let resultValue = function 45 | | Ok ok -> ok 46 | | Error _ -> failwith "expected OK" 47 | 48 | // --------------------------- 49 | // function call 50 | // --------------------------- 51 | 52 | let stringType : Tast.NamedTypeDescriptor = 53 | {AccessPath = "Microsoft.FSharp.Core"; DisplayName = "string"; CompiledName = "string"} 54 | let intType : Tast.NamedTypeDescriptor = 55 | {AccessPath = "Microsoft.FSharp.Core"; DisplayName = "int"; CompiledName = "int"} 56 | 57 | let text = """ 58 | let s = "s" 59 | module MyModule = 60 | let test x = x 61 | let z = MyModule.test("p") 62 | let z2 = MyModule.test(2) 63 | """ 64 | 65 | let tast = translateText text 66 | 67 | let results = ResizeArray() 68 | let accept (context:TastContext) = 69 | tryMatchType context.Node 70 | |> Option.map (fun node -> 71 | if matchNamedType node.Type intType then 72 | results.Add node.Value 73 | ) 74 | |> ignore 75 | true // keep going 76 | 77 | //let accept (context:TastContext)= 78 | // printfn "%A" context.Node 79 | 80 | let v = TastVisitor accept 81 | v.Visit(resultValue tast) 82 | results |> Seq.toList |> printfn "%A" 83 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/FsSonarRunner/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | module AssemblyInfo 2 | 3 | open System.Reflection 4 | 5 | [] 6 | [] 7 | [] 8 | [] 9 | 10 | () -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/FsSonarRunner/DiagnosticsDto.fs: -------------------------------------------------------------------------------- 1 | module rec DiagnosticsDto 2 | 3 | (* 4 | The issues are written to XML files using the schema below. 5 | 6 | IMPORTANT: Any changes to the schema must be coordinated with the java side, 7 | where it is used to import the issues (see AnalysisResultImporter.java) 8 | 9 | *) 10 | 11 | open System.Xml.Serialization 12 | open SonarAnalyzer.FSharp 13 | open System 14 | 15 | (* 16 | 17 | It should look like the example below. For now there are only issues, but in the future this could be expanded to metrics, etc. 18 | 19 | 20 | 21 | 22 | S1313 23 | Make sure using this hardcoded IP address '192.168.0.1' is safe here. 24 | P:/git_repos/sonar-fsharp-security/sonaranalyzer-fsharp/tests/SonarAnalyzer.FSharp.UnitTest/TestCases/S1313_HardcodedIpAddress.fs 25 | 18 26 | 14 27 | 18 28 | 27 29 | 30 | 31 | S1313 32 | Make sure using this hardcoded IP address '192.168.0.1' is safe here. 33 | P:/git_repos/sonar-fsharp-security/sonaranalyzer-fsharp/tests/SonarAnalyzer.FSharp.UnitTest/TestCases/S1313_HardcodedIpAddress.fs 34 | 26 35 | 14 <-- note: two different issues for the same file at different locations --> 36 | 26 37 | 54 38 | 39 | 40 | S2077 41 | Make sure that executing SQL queries is safe here. 42 | P:/git_repos/sonar-fsharp-security/sonaranalyzer-fsharp/tests/SonarAnalyzer.FSharp.UnitTest/TestCases/S2077_ExecutingSqlQueries.fs 43 | 26 44 | 14 45 | 26 46 | 54 47 | 48 | 49 | 50 | 51 | *) 52 | 53 | 54 | 55 | [] 56 | [] 57 | type RootDto = { 58 | 59 | [] 60 | [] 61 | Issues: IssueDto[] 62 | 63 | } 64 | 65 | [] 66 | [] 67 | type IssueDto = { 68 | 69 | [] 70 | RuleKey : string 71 | 72 | [] 73 | Message : string 74 | 75 | [] 76 | AbsoluteFilePath : string 77 | 78 | [] 79 | StartLine : int 80 | 81 | [] 82 | StartColumn : int 83 | 84 | [] 85 | EndLine : int 86 | 87 | [] 88 | EndColumn : int 89 | 90 | } 91 | 92 | let logger = Serilog.Log.Logger 93 | 94 | let absoluteFilePath filename = 95 | let f = System.IO.FileInfo(filename) 96 | f.FullName 97 | 98 | 99 | module IssueDto = 100 | 101 | let fromDiagnostic (diagnostic:Diagnostic) : IssueDto = 102 | 103 | let ruleKey = diagnostic.Descriptor.Id 104 | let message = diagnostic.Message 105 | let filename = diagnostic.Location.FileName 106 | let startLine = diagnostic.Location.StartLine 107 | let startColumn = diagnostic.Location.StartColumn 108 | let endLine = diagnostic.Location.EndLine 109 | let endColumn = diagnostic.Location.EndColumn 110 | 111 | // log it 112 | logger.Warning("{filename}({row},{col}): {RuleId}: {message}", 113 | filename, 114 | startLine, 115 | startColumn, 116 | ruleKey, 117 | message 118 | ) 119 | 120 | { 121 | RuleKey = ruleKey 122 | Message = message 123 | AbsoluteFilePath = filename |> absoluteFilePath 124 | StartLine = startLine 125 | StartColumn = startColumn 126 | EndLine = endLine 127 | EndColumn = endColumn 128 | } 129 | 130 | 131 | 132 | let toDto (diagnostics:Diagnostic list) : RootDto = 133 | 134 | let issues = diagnostics |> List.map IssueDto.fromDiagnostic 135 | { 136 | Issues = issues |> List.toArray 137 | } 138 | 139 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/FsSonarRunner/ExportQualityProfile.fs: -------------------------------------------------------------------------------- 1 | module ExportQualityProfile 2 | 3 | (* 4 | A QualityProfile file contains, for each rule, whether it is active, what it's severity is, etc. 5 | See https://docs.sonarqube.org/latest/instance-administration/quality-profiles/ 6 | 7 | The default (non-overridable) profile is called "Sonar Way" and is provided by the plugin. 8 | 9 | For this plugin, they will all be active by default, and the severity is default too. 10 | 11 | So all will do is write a list of rule keys, one per line. 12 | 13 | NOTE: There is no standard format for this file: it is a contract between this exporter and the corresponding importer 14 | at .\sonar-fsharpsecurity-plugin\src\main\java\org\sonar\plugins\fsharp\FSharpSonarWayProfile.java 15 | 16 | *) 17 | 18 | open System 19 | open System.IO 20 | open SonarAnalyzer.FSharp 21 | 22 | let profileFile = "profile.txt" 23 | 24 | let logger = Serilog.Log.Logger 25 | 26 | // ensure the directory exists 27 | let ensure dirname = Directory.CreateDirectory(dirname) |> ignore; dirname 28 | 29 | /// Write all available rules to the profile file 30 | let write(dirname) = 31 | 32 | let dirname = dirname |> ensure 33 | let profilePath = IO.Path.Combine(dirname, profileFile) 34 | 35 | logger.Information("Writing quality profile file to {profilePath}",profilePath) 36 | 37 | let rules = RuleManager.getAvailableRules() 38 | let ruleKeys = rules |> List.map (fun r -> r.RuleId) 39 | 40 | File.WriteAllLines(profilePath, ruleKeys) 41 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/FsSonarRunner/ExportRuleDefinitions.fs: -------------------------------------------------------------------------------- 1 | module ExportRuleDefinitions 2 | 3 | (* 4 | A RuleDefinitions file contains all the rule details in XML format 5 | 6 | This module contains logic to extract the available rules and export them as a file. 7 | *) 8 | 9 | open SonarAnalyzer.FSharp 10 | open RuleDefinitionDto 11 | open System 12 | open System.IO 13 | 14 | 15 | let ruleFile = "rules.xml" 16 | let lang = "fsharp" 17 | 18 | let logger = Serilog.Log.Logger 19 | 20 | // ensure the directory exists 21 | let ensure dirname = Directory.CreateDirectory(dirname) |> ignore; dirname 22 | 23 | /// Write all available rules to the rules file 24 | let write(dirname) = 25 | 26 | let dirname = dirname |> ensure 27 | let rulePath = IO.Path.Combine(dirname, ruleFile) 28 | 29 | logger.Information("Writing rule definitions file to {rulePath}",rulePath) 30 | 31 | let rules = RuleManager.getAvailableRules() 32 | let root = RuleDefinitionsDto.toDto rules 33 | Utilities.serializeToXmlFile rulePath root 34 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/FsSonarRunner/FsSonarRunner.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp2.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Never 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/FsSonarRunner/ImportExportAnalysisConfig.fs: -------------------------------------------------------------------------------- 1 | module ImportExportAnalysisConfig 2 | 3 | open SonarAnalyzer.FSharp 4 | 5 | let logger = Serilog.Log.Logger 6 | 7 | /// export the config to a file 8 | let export xmlFilename (root:AnalysisConfig.Root) = 9 | let dto = AnalysisConfigDto.fromDomain root 10 | Utilities.serializeToXmlFile xmlFilename dto 11 | 12 | /// import the config from a file 13 | let import xmlFilename : AnalysisConfig.Root = 14 | let dto = Utilities.deserializeFromXmlFile xmlFilename 15 | AnalysisConfigDto.toDomain dto 16 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/FsSonarRunner/OutputDiagnostics.fs: -------------------------------------------------------------------------------- 1 | module OutputDiagnostics 2 | 3 | (* 4 | The diagnostics are written to JSON files. 5 | 6 | This module contains the logic for writing them. 7 | *) 8 | 9 | open SonarAnalyzer.FSharp 10 | open Newtonsoft.Json 11 | 12 | let logger = Serilog.Log.Logger 13 | let loggerPrefix = "FsSonarRunner" 14 | 15 | /// Write the diagnostics to the output file 16 | let outputTo (xmlFilename:string) (diagnostics:Diagnostic list) = 17 | 18 | logger.Information("[{prefix}] Writing diagnostics to {filename}", loggerPrefix,xmlFilename) 19 | let dto = diagnostics |> DiagnosticsDto.toDto 20 | Utilities.serializeToXmlFile xmlFilename dto 21 | 22 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/FsSonarRunner/README.md: -------------------------------------------------------------------------------- 1 | # FsSonarRunner 2 | 3 | This is the command line that runs the analyzer. 4 | 5 | ## Options 6 | 7 | ``` 8 | "/I|/i:" 9 | "/F|/f:" 10 | "/O|/o:" 11 | "/D|/d:" 12 | "/displayrules" 13 | "/version" 14 | ``` 15 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/FsSonarRunner/Utilities.fs: -------------------------------------------------------------------------------- 1 | module Utilities 2 | 3 | (* 4 | Helper functions used throughout 5 | *) 6 | 7 | open System.Xml 8 | open System.IO 9 | open System.Xml.Serialization 10 | open System.Text 11 | 12 | let logger = Serilog.Log.Logger 13 | 14 | let serializeToXmlFile filePath (objectToSerialize:obj) = 15 | let settings = XmlWriterSettings(Indent = true,Encoding = Encoding.UTF8,IndentChars = " ") 16 | let serializer = new XmlSerializer(objectToSerialize.GetType()) 17 | let namespaces = XmlSerializerNamespaces [|XmlQualifiedName.Empty |] 18 | 19 | use stream = new MemoryStream() 20 | try 21 | use writer = XmlWriter.Create(stream, settings) 22 | serializer.Serialize(writer, objectToSerialize, namespaces) 23 | let ruleXml = Encoding.UTF8.GetString(stream.ToArray()) 24 | System.IO.File.WriteAllText(filePath, ruleXml) 25 | with 26 | | ex -> 27 | logger.Error("Failed to write file at {filePath}. Exception: {exception}",filePath, ex.Message) 28 | reraise() 29 | 30 | let deserializeFromXmlFile (filePath:string) : 'T = 31 | let settings = XmlReaderSettings() 32 | let serializer = new XmlSerializer( typeof<'T> ) 33 | 34 | try 35 | use textReader = new StreamReader(filePath) 36 | use reader = XmlReader.Create(textReader, settings) 37 | let obj = serializer.Deserialize(reader) 38 | obj :?> 'T 39 | with 40 | | ex -> 41 | logger.Error("Failed to read file at {filePath}. Missing or wrong format? Exception: {exception}",filePath, ex.Message) 42 | reraise() 43 | 44 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | module AssemblyInfo 2 | 3 | open System.Reflection 4 | 5 | [] 6 | [] 7 | [] 8 | [] 9 | 10 | () -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/InteractiveExamples.fsx: -------------------------------------------------------------------------------- 1 | (* ===================================================== 2 | To work with this project interactively, you need access to FSharp.Compiler.Service.dll from NuGet 3 | 4 | 1) Install paket in the "sonaranalyzer-fsharp" directory 5 | cd sonaranalyzer-fsharp 6 | dotnet tool install --tool-path ".paket" Paket --add-source https://api.nuget.org/v3/index.json 7 | 8 | 2) Run paket to install the packages 9 | .paket\paket.exe install 10 | 11 | ===================================================== *) 12 | 13 | 14 | #r @"..\..\packages\FSharp.Compiler.Service\lib\netstandard2.0\FSharp.Compiler.Service.dll" 15 | #r @"bin\Debug\netstandard2.0\FSharpAst.dll" 16 | #r @"bin\Debug\netstandard2.0\SonarAnalyzer.FSharp.dll" 17 | #r @"netstandard" 18 | 19 | open FSharpAst 20 | open SonarAnalyzer.FSharp 21 | open System.Reflection 22 | 23 | /// create a dummy context to run each rule on 24 | let dummyNode : Tast.ImplementationFile = {Name= "dummy"; Decls=[]} 25 | let ctx : TastContext = {Filename=dummyNode.Name; Node=dummyNode; Ancestors=[]} 26 | 27 | /// Return all the assemblies to analyze 28 | let assemblies() : Assembly list = 29 | [ 30 | typeof.Assembly 31 | ] 32 | 33 | let ruleS2092Method = 34 | let t = typeof 35 | let t = t.DeclaringType 36 | let t = t.DeclaringType 37 | let ruleMethod = t.GetMethods().[0] 38 | ruleMethod 39 | 40 | let ruleS2092 : Rule = 41 | fun ctx -> box (ruleS2092Method.Invoke(null,[|ctx|])) :?> Diagnostic option 42 | 43 | // invoke 44 | ruleS2092 ctx 45 | 46 | let allRuleMethods = 47 | assemblies() 48 | |> Seq.collect (fun assembly -> assembly.GetTypes()) 49 | |> Seq.collect (fun ty -> ty.GetMethods() ) 50 | |> Seq.filter (fun m -> m.GetCustomAttributes() |> Seq.isEmpty |> not ) 51 | |> Seq.toList 52 | 53 | let allRules = 54 | allRuleMethods 55 | |> List.map (fun m -> 56 | let r: Rule = fun ctx -> m.Invoke(null,[|ctx|]) :?> Diagnostic option 57 | let a:RuleAttribute = m.GetCustomAttributes() |> Seq.head 58 | a.Key,r 59 | ) 60 | 61 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/README.md: -------------------------------------------------------------------------------- 1 |  2 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/RspecStrings.fs: -------------------------------------------------------------------------------- 1 | namespace SonarAnalyzer.FSharp 2 | 3 | // The RspecStrings.resx is copied from the C# source 4 | 5 | /// A strongly-typed resource class, for looking up localized strings, etc. 6 | type RspecStrings() = 7 | 8 | static let mutable resourceMan : System.Resources.ResourceManager option = None 9 | 10 | /// Returns the cached ResourceManager instance used by this class. 11 | static member ResourceManager : System.Resources.ResourceManager = 12 | if resourceMan.IsNone then 13 | let temp = System.Resources.ResourceManager("SonarAnalyzer.FSharp.RspecStrings", typeof.Assembly) 14 | resourceMan <- Some temp 15 | resourceMan.Value 16 | 17 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/RuleRunner.fs: -------------------------------------------------------------------------------- 1 | module SonarAnalyzer.FSharp.RuleRunner 2 | 3 | open FSharpAst 4 | 5 | let logger = Serilog.Log.Logger 6 | let loggerPrefix = "SonarAnalyzer.FSharp.RuleRunner" 7 | 8 | (* =============================================== 9 | Run one or more rules 10 | =============================================== *) 11 | 12 | /// run a specific list of rules on a file 13 | let analyzeFileWithRules (rules:Rule list) (filename:string) :Diagnostic list = 14 | logger.Information("[{prefix}] Analyzing {filename} ", loggerPrefix, filename) 15 | 16 | let config = TransformerConfig.Default 17 | 18 | let results = ResizeArray() 19 | let accept ctx = 20 | for rule in rules do 21 | rule ctx |> Option.iter (fun result -> results.Add result) 22 | true // keep going 23 | let visitor = TastVisitor(accept) 24 | 25 | let tastResult = FileApi.translateFile config filename 26 | match tastResult with 27 | | Error errs -> 28 | errs |> Array.map Diagnostic.CompilationError |> Array.toList 29 | | Ok tast -> 30 | visitor.Visit(tast) 31 | results |> Seq.toList 32 | 33 | |> fun results -> logger.Information("[{prefix}] ... {count} issues found", loggerPrefix, results.Length); results 34 | 35 | /// run all the rules on a file 36 | let analyzeFileWithAllRules (filename:string) :Diagnostic list = 37 | 38 | let availableRules = RuleManager.getAvailableRules() 39 | let rulesToUse = 40 | availableRules 41 | |> List.map (fun rule -> rule.Rule) 42 | 43 | filename |> analyzeFileWithRules rulesToUse 44 | 45 | 46 | /// analyze everything as specified in the AnalysisConfig 47 | let analyzeConfig (root:AnalysisConfig.Root) :Diagnostic list = 48 | 49 | let availableRules = RuleManager.getAvailableRules() 50 | let rulesToUse = 51 | match root.RuleSelection with 52 | | AnalysisConfig.AllRules -> 53 | availableRules 54 | | AnalysisConfig.SelectedRules selectedRules -> 55 | let isSelected (availableRule:AvailableRule) = 56 | selectedRules |> List.exists (fun selectedRule -> selectedRule.Key = availableRule.RuleId) 57 | availableRules |> List.filter isSelected 58 | 59 | |> List.map (fun availableRule -> availableRule.Rule) 60 | 61 | logger.Information("[{prefix}] Analyzing {ruleCount} rules", loggerPrefix, rulesToUse.Length) 62 | 63 | match root.FileSelection with 64 | | AnalysisConfig.SelectedFiles files -> 65 | files 66 | |> fun files -> logger.Information("[{prefix}] Analyzing {fileCount} files", loggerPrefix, files.Length); files 67 | |> List.collect (fun file -> analyzeFileWithRules rulesToUse file.Filename) 68 | 69 | | AnalysisConfig.Projects _ -> failwithf "Projects not yet implemented" 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules.Description/S1104.html: -------------------------------------------------------------------------------- 1 |

Public fields in public classes do not respect the encapsulation principle and has three main disadvantages:

2 |
    3 |
  • Additional behavior such as validation cannot be added.
  • 4 |
  • The internal representation is exposed, and cannot be changed afterwards.
  • 5 |
  • Member values are subject to change from anywhere in the code and may not meet the programmer's assumptions.
  • 6 |
7 |

By using private fields and public properties (set and get), unauthorized modifications are prevented. Properties also benefit from additional 8 | protection (security) features such as Link Demands.

9 |

Note that due to optimizations on simple properties, public fields provide only very little performance gain.

10 |

Noncompliant Code Example

11 |
12 | public class Foo
13 | {
14 |     public int instanceData = 32; // Noncompliant
15 | }
16 | 
17 |

Compliant Solution

18 |
19 | public class Foo
20 | {
21 |     private int instanceData = 32;
22 | 
23 |     public int InstanceData
24 |     {
25 |         get { return instanceData; }
26 | 	set { instanceData = value ; }
27 |     }
28 | }
29 | 
30 |

Exceptions

31 |

Fields marked as readonly or const are ignored by this rule.

32 |

Fields inside classes or structs annotated with the StructLayoutAttribute are ignored by this rule.

33 |

See

34 |
    35 |
  • MITRE, CWE-493 - Critical Public Variable Without Final Modifier
  • 36 |
37 | 38 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules.Description/S1313.html: -------------------------------------------------------------------------------- 1 |

Hardcoding IP addresses is security-sensitive. It has led in the past to the following vulnerabilities:

2 | 6 |

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 7 | 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, 8 | delivery and deployment:

9 |
    10 |
  • The developers will have to do a rapid fix every time this happens, instead of having an operation team change a configuration file.
  • 11 |
  • It forces the same address to be used in every environment (dev, sys, qa, prod).
  • 12 |
13 |

Last but not least it has an effect on application security. Attackers might be able to decompile the code and thereby discover a potentially 14 | sensitive address. They can perform a Denial of Service attack on the service at this address or spoof the IP address. Such an attack is always 15 | possible, but in the case of a hardcoded IP address the fix will be much slower, which will increase an attack's impact.

16 |

Recommended Secure Coding Practices

17 |
    18 |
  • make the IP address configurable.
  • 19 |
20 |

Noncompliant Code Example

21 |
22 | let ip = "192.168.12.42"
23 | let address = IPAddress.Parse(ip)
24 | 
25 |

Compliant Solution

26 |
27 | let ip = ConfigurationManager.AppSettings.["myapplication.ip"]
28 | let address = IPAddress.Parse(ip)
29 | 
30 |

Exceptions

31 |
    32 |
  • Although "::" is a valid IPv6 address, the rule doesn't report on it.
  • 33 |
  • No issue is reported for 127.0.0.1 because loopback is not considered as sensitive
  • 34 |
35 |

See

36 | 41 | 42 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules.Description/S2068.html: -------------------------------------------------------------------------------- 1 |

Because it is easy to extract strings from a compiled application, credentials should never be hard-coded. Do so, and they're almost guaranteed to 2 | end up in the hands of an attacker. This is particularly true for applications that are distributed.

3 |

Credentials should be stored outside of the code in a strongly-protected encrypted configuration file or database.

4 |

Noncompliant Code Example

5 |
 6 | string username = "admin";
 7 | string password = "Password123"; // Noncompliant
 8 | string usernamePassword  = "user=admin&password=Password123"; // Noncompliant
 9 | string usernamePassword2 = "user=admin&" + "password=" + password; // Noncompliant
10 | 
11 |

Compliant Solution

12 |
13 | string username = "admin";
14 | string password = GetEncryptedPassword();
15 | string usernamePassword = string.Format("user={0}&password={1}", GetEncryptedUsername(), GetEncryptedPassword());
16 | 
17 |

See

18 | 26 | 27 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules.Description/S2070.html: -------------------------------------------------------------------------------- 1 |

The MD5 algorithm and its successor, SHA-1, are no longer considered secure, because it is too easy to create hash collisions with them. That is, 2 | it takes too little computational effort to come up with a different input that produces the same MD5 or SHA-1 hash, and using the new, same-hash 3 | value gives an attacker the same access as if he had the originally-hashed value. This applies as well to the other Message-Digest algorithms: MD2, 4 | MD4, MD6.

5 |

This rule tracks usage of the System.Security.Cryptography.CryptoConfig.CreateFromName(), and 6 | System.Security.Cryptography.HashAlgorithm.Create() methods to instantiate MD5, DSA, HMACMD5, HMACRIPEMD160, RIPEMD-160 or SHA-1 7 | algorithms, and of derived class instances of System.Security.Cryptography.SHA1 and System.Security.Cryptography.MD5.

8 |

Consider using safer alternatives, such as SHA-256, or SHA-3.

9 |

Noncompliant Code Example

10 |
11 | var hashProvider1 = new MD5CryptoServiceProvider(); //Noncompliant
12 | var hashProvider2 = (HashAlgorithm)CryptoConfig.CreateFromName("MD5"); //Noncompliant
13 | var hashProvider3 = new SHA1Managed(); //Noncompliant
14 | var hashProvider4 = HashAlgorithm.Create("SHA1"); //Noncompliant
15 | 
16 |

Compliant Solution

17 |
18 | var hashProvider1 = new SHA256Managed();
19 | var hashProvider2 = (HashAlgorithm)CryptoConfig.CreateFromName("SHA256Managed");
20 | var hashProvider3 = HashAlgorithm.Create("SHA256Managed");
21 | 
22 |

See

23 | 31 |

Deprecated

32 |

This rule is deprecated; use {rule:csharpsquid:S4790} instead.

33 | 34 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules.Description/S2092.html: -------------------------------------------------------------------------------- 1 |

The "secure" attribute prevents cookies from being sent over plaintext connections such as HTTP, where they would be easily eavesdropped upon. 2 | Instead, cookies with the secure attribute are only sent over encrypted HTTPS connections.

3 |

Recommended Secure Coding Practices

4 |
    5 |
  • set the field Secure to true on the HttpCookie object
  • 6 |
7 |

Noncompliant Code Example

8 |
 9 | HttpCookie myCookie = new HttpCookie("UserSettings");
10 | myCookie.Secure = false; // Noncompliant; explicitly set to false
11 | ...
12 | Response.Cookies.Add(myCookie);
13 | 
14 |
15 | HttpCookie myCookie = new HttpCookie("UserSettings"); // Noncompliant; the default value of 'Secure' is used (=false)
16 | ...
17 | Response.Cookies.Add(myCookie);
18 | 
19 |

Compliant Solution

20 |
21 | HttpCookie myCookie = new HttpCookie("UserSettings");
22 | myCookie.Secure = true; // Compliant
23 | ...
24 | Response.Cookies.Add(myCookie);
25 | 
26 |

See

27 | 36 | 37 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules.Description/S2228.html: -------------------------------------------------------------------------------- 1 |

Debug statements are always useful during development. But include them in production code - particularly in code that runs client-side - and you 2 | run the risk of inadvertently exposing sensitive information.

3 |

Noncompliant Code Example

4 |
 5 | private void DoSomething()
 6 | {
 7 |     // ...
 8 |     Console.WriteLine("so far, so good..."); // Noncompliant
 9 |     // ...
10 | }
11 | 
12 |

Exceptions

13 |

The following are ignored by this rule:

14 |
    15 |
  • Console Applications
  • 16 |
  • Calls in methods decorated with [Conditional ("DEBUG")]
  • 17 |
  • Calls included in DEBUG preprocessor branches (#if DEBUG)
  • 18 |
19 |

See

20 | 24 | 25 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules.Description/S2245.html: -------------------------------------------------------------------------------- 1 |

Using pseudorandom number generators (PRNGs) is security-sensitive. For example, it has led in the past to the following vulnerabilities:

2 |

* CVE-2013-6386

3 |

* CVE-2006-3419

4 |

* CVE-2008-4102

5 |

When software generates predictable values in a context requiring unpredictability, it may be possible for an attacker to guess the next value that 6 | will be generated, and use this guess to impersonate another user or access sensitive information.

7 |

As the System.Random class relies on a pseudorandom number generator, it should not be used for security-critical applications or for 8 | protecting sensitive data. In such context, the System.Cryptography.RandomNumberGenerator class which relies on a cryptographically 9 | strong random number generator (RNG) should be used in place.

10 |

Ask Yourself Whether

11 |
    12 |
  • the code using the generated value requires it to be unpredictable. It is the case for all encryption mechanisms or when a secret value, such 13 | as a password, is hashed.
  • 14 |
  • the function you use generates a value which can be predicted (pseudo-random).
  • 15 |
  • the generated value is used multiple times.
  • 16 |
  • an attacker can access the generated value.
  • 17 |
18 |

You are at risk if you answered yes to the first question and any of the following ones.

19 |

Recommended Secure Coding Practices

20 |
    21 |
  • Only use random number generators which are recommended by OWASP or any other 23 | trusted organization.
  • 24 |
  • Use the generated random values only once.
  • 25 |
  • You should not expose the generated random value. If you have to store it, make sure that the database or file is secure.
  • 26 |
27 |

Sensitive Code Example

28 |
29 | var random = new Random(); // Sensitive use of Random
30 | byte[] data = new byte[16];
31 | random.NextBytes(data);
32 | return BitConverter.ToString(data); // Check if this value is used for hashing or encryption
33 | 
34 |

Compliant Solution

35 |
36 | using System.Security.Cryptography;
37 | ...
38 | var randomGenerator = RandomNumberGenerator.Create(); // Compliant for security-sensitive use cases
39 | byte[] data = new byte[16];
40 | randomGenerator.GetBytes(data);
41 | return BitConverter.ToString(data);
42 | 
43 |

See

44 | 59 | 60 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules.Description/S2255.html: -------------------------------------------------------------------------------- 1 |

Using cookies is security-sensitive. It has led in the past to the following vulnerabilities:

2 | 6 |

Attackers can use widely-available tools to read cookies, sensitive information written by the server will be exposed.

7 |

This rule flags code that writes cookies.

8 |

Ask Yourself Whether

9 |
    10 |
  • sensitive information is stored inside the cookie.
  • 11 |
12 |

You are at risk if you answered yes to this question.

13 |

Recommended Secure Coding Practices

14 |

Cookies should only be used to manage the user session. The best practice is to keep all user-related information server-side and link them to the 15 | user session, never sending them to the client. In a very few corner cases, cookies can be used for non-sensitive information that need to live longer 16 | than the user session.

17 |

Do not try to encode sensitive information in a non human-readable format before writing them in a cookie. The encoding can be reverted and the 18 | original information will be exposed.

19 |

Using cookies only for session IDs doesn't make them secure. Follow OWASP best practices when you configure your cookies.

21 |

As a side note, every information read from a cookie should be Sanitized.

23 |

Sensitive Code Example

24 |
25 | // === .Net Framework ===
26 | 
27 | HttpCookie myCookie = new HttpCookie("UserSettings");
28 | myCookie["CreditCardNumber"] = "1234 1234 1234 1234"; // Sensitive; sensitive data stored
29 | myCookie.Values["password"] = "5678"; // Sensitive
30 | myCookie.Value = "mysecret"; // Sensitive
31 | ...
32 | Response.Cookies.Add(myCookie);
33 | 
34 | 
35 | // === .Net Core ===
36 | 
37 | Response.Headers.Add("Set-Cookie", ...); // Sensitive
38 | Response.Cookies.Append("mykey", "myValue"); // Sensitive
39 | 
40 |

See

41 | 50 | 51 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules.Description/S2278.html: -------------------------------------------------------------------------------- 1 |

According to the US National Institute of Standards and Technology (NIST), the Data Encryption Standard (DES) is no longer considered secure:

2 |
3 |

Adopted in 1977 for federal agencies to use in protecting sensitive, unclassified information, the DES is being withdrawn because it no longer 4 | provides the security that is needed to protect federal government information.

5 |

Federal agencies are encouraged to use the Advanced Encryption Standard, a faster and stronger algorithm approved as FIPS 197 in 2001.

6 |
7 |

For similar reasons, RC2 should also be avoided.

8 |

Noncompliant Code Example

9 |
10 | using (var tripleDES = new TripleDESCryptoServiceProvider()) //Noncompliant
11 | {
12 |   //...
13 | }
14 | 
15 |

Compliant Solution

16 |
17 | using (var aes = new AesCryptoServiceProvider())
18 | {
19 |   //...
20 | }
21 | 
22 |

See

23 | 32 | 33 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules.Description/S2386.html: -------------------------------------------------------------------------------- 1 |

public static mutable fields of classes which are accessed directly should be protected to the degree possible. This can be done by 2 | reducing the accessibility of the field or by changing the return type to an immutable type.

3 |

This rule raises issues for public static fields with a type inheriting/implementing System.Array or 4 | System.Collections.Generic.ICollection<T>.

5 |

Noncompliant Code Example

6 |
 7 | public class A
 8 | {
 9 |   public static string[] strings1 = {"first","second"};  // Noncompliant
10 |   public static List<String> strings3 = new List<String>();  // Noncompliant
11 |   // ...
12 | }
13 | 
14 |

Compliant Solution

15 |
16 | public class A
17 | {
18 |   protected static string[] strings1 = {"first","second"};
19 |   protected static List<String> strings3 = new List<String>();
20 |   // ...
21 | }
22 | 
23 |

Exceptions

24 |

No issue is reported:

25 |
    26 |
  • If the type of the field inherits/implements one (at least) of the following types: 27 |
      28 |
    • System.Collections.ObjectModel.ReadOnlyCollection<T>
    • 29 |
    • System.Collections.ObjectModel.ReadOnlyDictionary<TKey, TValue>
    • 30 |
    • System.Collections.Immutable.IImmutableArray<T>
    • 31 |
    • System.Collections.Immutable.IImmutableDictionary<TKey, TValue>
    • 32 |
    • System.Collections.Immutable.IImmutableList<T>
    • 33 |
    • System.Collections.Immutable.IImmutableSet<T>
    • 34 |
    • System.Collections.Immutable.IImmutableStack<T>
    • 35 |
    • System.Collections.Immutable.IImmutableQueue<T>
    • 36 |
  • 37 |
  • If the field is readonly and is initialized inline with an immutable type (i.e. inherits/implements one of the types in the 38 | previous list) or null.
  • 39 |
40 |

See

41 |
    42 |
  • MITRE, CWE-582 - Array Declared Public, Final, and Static
  • 43 |
  • MITRE, CWE-607 - Public Static Final Field References Mutable Object
  • 44 |
  • CERT, OBJ01-J. - Limit accessibility of fields
  • 45 |
  • CERT, OBJ13-J. - Ensure that references to mutable objects are not exposed 46 |
  • 47 |
48 | 49 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules.Description/S2486.html: -------------------------------------------------------------------------------- 1 |

When exceptions occur, it is usually a bad idea to simply ignore them. Instead, it is better to handle them properly, or at least to log them.

2 |

This rule only reports on empty catch clauses that catch generic Exceptions.

3 |

Noncompliant Code Example

4 |
 5 | string text = "";
 6 | try
 7 | {
 8 |     text = File.ReadAllText(fileName);
 9 | }
10 | catch (Exception exc) // Noncompliant
11 | {
12 | }
13 | 
14 |

Compliant Solution

15 |
16 | string text = "";
17 | try
18 | {
19 |     text = File.ReadAllText(fileName);
20 | }
21 | catch (Exception exc)
22 | {
23 |     logger.Log(exc);
24 | }
25 | 
26 |

Exceptions

27 |

When a block contains a comment, it is not considered to be empty.

28 |

See

29 | 34 | 35 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules.Description/S3011.html: -------------------------------------------------------------------------------- 1 |

Changing or bypassing accessibility is security-sensitive. For example, it has led in the past to the following vulnerability:

2 | 5 |

private methods were made private for a reason, and the same is true of every other visibility level. Altering or 6 | bypassing the accessibility of classes, methods, or fields violates the encapsulation principle and could introduce security holes.

7 |

This rule raises an issue when reflection is used to change the visibility of a class, method or field, and when it is used to directly update a 8 | field value.

9 |

Ask Yourself Whether

10 |
    11 |
  • there is a good reason to override the existing accessibility level of the method/field. This is very rarely the case. Accessing hidden fields 12 | and methods will make your code unstable as they are not part of the public API and may change in future versions.
  • 13 |
  • this method is called by untrusted code. *
  • 14 |
  • it is possible to modify or bypass the accessibility of sensitive methods or fields using this code. *
  • 15 |
16 |

* You are at risk if you answered yes to those questions.

17 |

Recommended Secure Coding Practices

18 |

Don't change or bypass the accessibility of any method or field if possible.

19 |

If untrusted code can execute this method, make sure that it cannot decide which method or field's accessibility can be modified or bypassed.

20 |

Sensitive Code Example

21 |
22 | using System.Reflection;
23 | 
24 | Type dynClass = Type.GetType("MyInternalClass");
25 | // Sensitive. Using BindingFlags.NonPublic will return non-public members
26 | BindingFlags bindingAttr = BindingFlags.NonPublic | BindingFlags.Static;
27 | MethodInfo dynMethod = dynClass.GetMethod("mymethod", bindingAttr);
28 | object result = dynMethod.Invoke(dynClass, null);
29 | 
30 |

See

31 | 37 | 38 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules.Description/S3330.html: -------------------------------------------------------------------------------- 1 |

The HttpOnly cookie attribute tells the browser to prevent client-side scripts from reading cookies with the attribute, and its use 2 | can go a long way to defending against Cross-Site Scripting (XSS) attacks. Thus, as a precaution, the attribute should be set by default on all 3 | cookies set server-side, such as session id cookies.

4 |

When implementing Cross Site Request Forgery (XSRF) protection, a JavaScript-readable session cookie, generally named XSRF-TOKEN, should be created 5 | on the first HTTP GET request. For such a cookie, the HttpOnly attribute should be set to "false".

6 |

Setting the attribute can be done either programmatically, or globally via configuration files.

7 |

Noncompliant Code Example

8 |
 9 | HttpCookie myCookie = new HttpCookie("UserSettings");
10 | myCookie.HttpOnly = false; // Noncompliant; explicitly set to false
11 | ...
12 | Response.Cookies.Add(myCookie);
13 | 
14 |
15 | HttpCookie myCookie = new HttpCookie("UserSettings"); // Noncompliant; the default value of 'HttpOnly' is used (=false)
16 | ...
17 | Response.Cookies.Add(myCookie);
18 | 
19 |

Compliant Solution

20 |
21 | HttpCookie myCookie = new HttpCookie("UserSettings");
22 | myCookie.HttpOnly = true; // Compliant
23 | ...
24 | Response.Cookies.Add(myCookie);
25 | 
26 |

See

27 | 37 | 38 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules.Description/S3884.html: -------------------------------------------------------------------------------- 1 |

CoSetProxyBlanket and CoInitializeSecurity both work to set the permissions context in which the process invoked 2 | immediately after is executed. Calling them from within that process is useless because it's too late at that point; the permissions context has 3 | already been set.

4 |

Specifically, these methods are meant to be called from non-managed code such as a C++ wrapper that then invokes the managed, i.e. C# or VB.NET, 5 | code.

6 |

Noncompliant Code Example

7 |
 8 | [DllImport("ole32.dll")]
 9 | static extern int CoSetProxyBlanket([MarshalAs(UnmanagedType.IUnknown)]object pProxy, uint dwAuthnSvc, uint dwAuthzSvc,
10 | 	[MarshalAs(UnmanagedType.LPWStr)] string pServerPrincName, uint dwAuthnLevel, uint dwImpLevel, IntPtr pAuthInfo,
11 | 	uint dwCapabilities);
12 | 
13 | public enum RpcAuthnLevel
14 | {
15 | 	Default = 0,
16 | 	None = 1,
17 | 	Connect = 2,
18 | 	Call = 3,
19 | 	Pkt = 4,
20 | 	PktIntegrity = 5,
21 | 	PktPrivacy = 6
22 | }
23 | 
24 | public enum RpcImpLevel
25 | {
26 | 	Default = 0,
27 | 	Anonymous = 1,
28 | 	Identify = 2,
29 | 	Impersonate = 3,
30 | 	Delegate = 4
31 | }
32 | 
33 | public enum EoAuthnCap
34 | {
35 | 	None = 0x00,
36 | 	MutualAuth = 0x01,
37 | 	StaticCloaking = 0x20,
38 | 	DynamicCloaking = 0x40,
39 | 	AnyAuthority = 0x80,
40 | 	MakeFullSIC = 0x100,
41 | 	Default = 0x800,
42 | 	SecureRefs = 0x02,
43 | 	AccessControl = 0x04,
44 | 	AppID = 0x08,
45 | 	Dynamic = 0x10,
46 | 	RequireFullSIC = 0x200,
47 | 	AutoImpersonate = 0x400,
48 | 	NoCustomMarshal = 0x2000,
49 | 	DisableAAA = 0x1000
50 | }
51 | 
52 | [DllImport("ole32.dll")]
53 | public static extern int CoInitializeSecurity(IntPtr pVoid, int cAuthSvc, IntPtr asAuthSvc, IntPtr pReserved1,
54 | 	RpcAuthnLevel level, RpcImpLevel impers, IntPtr pAuthList, EoAuthnCap dwCapabilities, IntPtr pReserved3);
55 | 
56 | static void Main(string[] args)
57 | {
58 | 	var hres1 = CoSetProxyBlanket(null, 0, 0, null, 0, 0, IntPtr.Zero, 0); // Noncompliant
59 | 
60 | 	var hres2 = CoInitializeSecurity(IntPtr.Zero, -1, IntPtr.Zero, IntPtr.Zero, RpcAuthnLevel.None,
61 | 		RpcImpLevel.Impersonate, IntPtr.Zero, EoAuthnCap.None, IntPtr.Zero); // Noncompliant
62 | }
63 | 
64 |

See

65 | 69 | 70 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules.Description/S4211.html: -------------------------------------------------------------------------------- 1 |

Transparency attributes, SecurityCriticalAttribute and SecuritySafeCriticalAttribute are used to identify code that 2 | performs security-critical operations. The second one indicates that it is safe to call this code from transparent, while the first one does not. 3 | Since the transparency attributes of code elements with larger scope take precedence over transparency attributes of code elements that are contained 4 | in the first element a class, for instance, with a SecurityCriticalAttribute can not contain a method with a 5 | SecuritySafeCriticalAttribute.

6 |

This rule raises an issue when a member is marked with a System.Security security attribute that has a different transparency than the 7 | security attribute of a container of the member.

8 |

Noncompliant Code Example

9 |
10 | using System;
11 | using System.Security;
12 | 
13 | namespace MyLibrary
14 | {
15 | 
16 |     [SecurityCritical]
17 |     public class Foo
18 |     {
19 |         [SecuritySafeCritical] // Noncompliant
20 |         public void Bar()
21 |         {
22 |         }
23 |     }
24 | }
25 | 
26 |

Compliant Solution

27 |
28 | using System;
29 | using System.Security;
30 | 
31 | namespace MyLibrary
32 | {
33 | 
34 |     [SecurityCritical]
35 |     public class Foo
36 |     {
37 |         public void Bar()
38 |         {
39 |         }
40 |     }
41 | }
42 | 
43 |

See

44 | 48 | 49 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules.Description/S4212.html: -------------------------------------------------------------------------------- 1 |

Because serialization constructors allocate and initialize objects, security checks that are present on regular constructors must also be present 2 | on a serialization constructor. Failure to do so would allow callers that could not otherwise create an instance to use the serialization constructor 3 | to do this.

4 |

This rule raises an issue when a type implements the System.Runtime.Serialization.ISerializable interface, is not a delegate or 5 | interface, is declared in an assembly that allows partially trusted callers and has a constructor that takes a 6 | System.Runtime.Serialization.SerializationInfo object and a System.Runtime.Serialization.StreamingContext object which is 7 | not secured by a security check, but one or more of the regular constructors in the type is secured.

8 |

Noncompliant Code Example

9 |
10 | using System;
11 | using System.IO;
12 | using System.Runtime.Serialization;
13 | using System.Runtime.Serialization.Formatters.Binary;
14 | using System.Security;
15 | using System.Security.Permissions;
16 | 
17 | [assembly: AllowPartiallyTrustedCallersAttribute()]
18 | namespace MyLibrary
19 | {
20 |     [Serializable]
21 |     public class Foo : ISerializable
22 |     {
23 |         private int n;
24 | 
25 |         [FileIOPermissionAttribute(SecurityAction.Demand, Unrestricted = true)]
26 |         public Foo()
27 |         {
28 |            n = -1;
29 |         }
30 | 
31 |         protected Foo(SerializationInfo info, StreamingContext context) // Noncompliant
32 |         {
33 |            n = (int)info.GetValue("n", typeof(int));
34 |         }
35 | 
36 |         void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
37 |         {
38 |            info.AddValue("n", n);
39 |         }
40 |     }
41 | }
42 | 
43 |

Compliant Solution

44 |
45 | using System;
46 | using System.IO;
47 | using System.Runtime.Serialization;
48 | using System.Runtime.Serialization.Formatters.Binary;
49 | using System.Security;
50 | using System.Security.Permissions;
51 | 
52 | [assembly: AllowPartiallyTrustedCallersAttribute()]
53 | namespace MyLibrary
54 | {
55 |     [Serializable]
56 |     public class Foo : ISerializable
57 |     {
58 |         private int n;
59 | 
60 |         [FileIOPermissionAttribute(SecurityAction.Demand, Unrestricted = true)]
61 |         public Foo()
62 |         {
63 |            n = -1;
64 |         }
65 | 
66 |         [FileIOPermissionAttribute(SecurityAction.Demand, Unrestricted = true)]
67 |         protected Foo(SerializationInfo info, StreamingContext context)
68 |         {
69 |            n = (int)info.GetValue("n", typeof(int));
70 |         }
71 | 
72 |         void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
73 |         {
74 |            info.AddValue("n", n);
75 |         }
76 |     }
77 | }
78 | 
79 |

See

80 | 84 | 85 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules.Description/S4426.html: -------------------------------------------------------------------------------- 1 |

When generating cryptograpic keys (or key pairs), it is important to use a key length that provides enough entropy against brute-force attacks. For 2 | the RSA algorithm the key should be at

3 |

least 2048 bits long.

4 |

This rule raises an issue when a RSA key-pair generator is initialized with too small a length parameter.

5 |

Noncompliant Code Example

6 |
 7 | using System;
 8 | using System.Security.Cryptography;
 9 | 
10 | namespace MyLibrary
11 | {
12 |     public class MyCryptoClass
13 |     {
14 |         static void Main()
15 |         {
16 |             RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(1024); // Noncompliant
17 |             // ...
18 |         }
19 |     }
20 | }
21 | 
22 |

Compliant Solution

23 |
24 | using System;
25 | using System.Security.Cryptography;
26 | 
27 | namespace MyLibrary
28 | {
29 |     public class MyCryptoClass
30 |     {
31 |         static void Main()
32 |         {
33 |             RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(2048);
34 |             // ...
35 |         }
36 |     }
37 | }
38 | 
39 |

See

40 | 51 | 52 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules.Description/S4432.html: -------------------------------------------------------------------------------- 1 |

Encryption algorithms can be used with various modes. Some combinations are not secured:

2 |
    3 |
  • Electronic Codebook (ECB) mode: Under a given key, any given plaintext block always gets encrypted to the same ciphertext block. Thus, it does 4 | not hide data patterns well. In some senses, it doesn't provide serious message confidentiality, and it is not recommended for use in cryptographic 5 | protocols at all.
  • 6 |
  • Cipher Block Chaining (CBC) with PKCS#5 padding (or PKCS#7) is susceptible to padding oracle attacks. CBC + PKCS#7 can be used if combined with 7 | an authenticity check (HMAC-SHA256 for example) on the cipher text.
  • 8 |
9 |

In both cases, Galois/Counter Mode (GCM) with no padding should be preferred. As the .NET framework doesn't provide this natively, the use of a 10 | certified third party lib is recommended.

11 |

This rule raises an issue when any of the following CipherMode is detected: ECB, CBC, OFB, CFB, CTS.

12 |

Noncompliant Code Example

13 |
14 | AesManaged aes = new AesManaged
15 | {
16 |   KeySize = 128,
17 |   BlockSize = 128,
18 |   Mode = CipherMode.OFB, // Noncompliant
19 |   Padding = PaddingMode.PKCS7
20 | };
21 | 
22 |

See

23 | 34 | 35 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules.Description/S4433.html: -------------------------------------------------------------------------------- 1 |

An un-authenticated LDAP connection can lead to transactions without access control. Authentication, and with it, access control, are the last line 2 | of defense against LDAP injections and should not be disabled.

3 |

This rule raises an issue when an LDAP connection is created with AuthenticationTypes.Anonymous or 4 | AuthenticationTypes.None.

5 |

Noncompliant Code Example

6 |
 7 | DirectoryEntry myDirectoryEntry = new DirectoryEntry(adPath);
 8 | myDirectoryEntry.AuthenticationType = AuthenticationTypes.None; // Noncompliant
 9 | 
10 | DirectoryEntry myDirectoryEntry = new DirectoryEntry(adPath, "u", "p", AuthenticationTypes.None); // Noncompliant
11 | 
12 |

Compliant Solution

13 |
14 | DirectoryEntry myDirectoryEntry = new DirectoryEntry(myADSPath); // Compliant; default DirectoryEntry.AuthenticationType property value is "Secure" since .NET Framework 2.0
15 | 
16 | DirectoryEntry myDirectoryEntry = new DirectoryEntry(myADSPath, "u", "p", AuthenticationTypes.Secure);
17 | 
18 |

See

19 | 25 | 26 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules.Description/S4507.html: -------------------------------------------------------------------------------- 1 |

Delivering code in production with debug features activated is security-sensitive. It has led in the past to the following vulnerabilities:

2 | 7 |

An application's debug features enable developers to find bugs more easily. It often gives access to detailed information on both the system 8 | running the application and users. Sometime it even enables the execution of custom commands. Thus deploying on production servers an application 9 | which has debug features activated is extremely dangerous.

10 |

Ask Yourself Whether

11 |
    12 |
  • the code or configuration enabling the application debug features is deployed on production servers.
  • 13 |
  • the application runs by default with debug features activated.
  • 14 |
15 |

You are at risk if you answered yes to any of these questions.

16 |

Recommended Secure Coding Practices

17 |

Do not enable debug features on production servers.

18 |

The .Net Core framework offers multiple features which help during debug. 19 | Microsoft.AspNetCore.Builder.IApplicationBuilder.UseDeveloperExceptionPage and 20 | Microsoft.AspNetCore.Builder.IApplicationBuilder.UseDatabaseErrorPage are two of them. Make sure that those features are disabled in 21 | production.

22 |

Use if (env.IsDevelopment()) to disable debug code.

23 |

Sensitive Code Example

24 |

This rule raises issues when the following .Net Core methods are called: 25 | Microsoft.AspNetCore.Builder.IApplicationBuilder.UseDeveloperExceptionPage, 26 | Microsoft.AspNetCore.Builder.IApplicationBuilder.UseDatabaseErrorPage. No Issue is raised when those calls are disabled by if 27 | (env.IsDevelopment()).

28 |
29 | using Microsoft.AspNetCore.Builder;
30 | using Microsoft.AspNetCore.Hosting;
31 | 
32 | namespace mvcApp
33 | {
34 |     public class Startup2
35 |     {
36 |         public void Configure(IApplicationBuilder app, IHostingEnvironment env)
37 |         {
38 |             if (env.IsDevelopment())
39 |             {
40 |                 // The following calls are ok because they are disabled in production
41 |                 app.UseDeveloperExceptionPage();
42 |                 app.UseDatabaseErrorPage();
43 |             }
44 |             // Those calls are Sensitive because it seems that they will run in production
45 |             app.UseDeveloperExceptionPage(); // Sensitive
46 |             app.UseDatabaseErrorPage(); // Sensitive
47 |         }
48 |     }
49 | }
50 | 
51 | 
52 |

Exceptions

53 |

This rule does not analyze configuration files. Make sure that debug mode is not enabled by default in those files.

54 |

See

55 | 61 | 62 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules.Description/S4564.html: -------------------------------------------------------------------------------- 1 |

ASP.Net has a feature to validate HTTP requests to prevent potentially dangerous content to perform a cross-site scripting (XSS) attack. There is 2 | no reason to disable this mechanism even if other checks to prevent XXS attacks are in place.

3 |

This rule raises an issue if a method with parameters is marked with System.Web.Mvc.HttpPostAttribute and not 4 | System.Web.Mvc.ValidateInputAttribute(true).

5 |

Noncompliant Code Example

6 |
 7 | public class FooBarController : Controller
 8 | {
 9 |     [HttpPost] // Noncompliant
10 |     [ValidateInput(false)]
11 |     public ActionResult Purchase(string input)
12 |     {
13 |         return Foo(input);
14 |     }
15 | 
16 |     [HttpPost] // Noncompliant
17 |     public ActionResult PurchaseSomethingElse(string input)
18 |     {
19 |         return Foo(input);
20 |     }
21 | }
22 | 
23 |

Compliant Solution

24 |
25 | public class FooBarController : Controller
26 | {
27 |     [HttpPost]
28 |     [ValidateInput(true)] // Compliant
29 |     public ActionResult Purchase(string input)
30 |     {
31 |         return Foo(input);
32 |     }
33 | }
34 | 
35 |

Exceptions

36 |

Parameterless methods marked with System.Web.Mvc.HttpPostAttribute will not trigger this issue.

37 |

See

38 | 46 | 47 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules.Description/S4790.html: -------------------------------------------------------------------------------- 1 |

Hashing data is security-sensitive. It has led in the past to the following vulnerabilities:

2 | 7 |

Cryptographic hash functions are used to uniquely identify information without storing their original form. When not done properly, an attacker can 8 | steal the original information by guessing it (ex: with a rainbow table), or replace the 9 | original data with another one having the same hash.

10 |

This rule flags code that initiates hashing.

11 |

Ask Yourself Whether

12 |
    13 |
  • the hashed value is used in a security context.
  • 14 |
  • the hashing algorithm you are using is known to have vulnerabilities.
  • 15 |
  • salts are not automatically generated and applied by the hashing function. 16 |
  • 17 |
  • any generated salts are cryptographically weak or not credential-specific.
  • 18 |
19 |

You are at risk if you answered yes to the first question and any of the following ones.

20 |

Recommended Secure Coding Practices

21 |
    22 |
  • for security related purposes, use only hashing algorithms which are currently known to be strong. Avoid using algorithms like MD5 and SHA1 24 | completely in security contexts.
  • 25 |
  • do not define your own hashing- or salt algorithms as they will most probably have flaws.
  • 26 |
  • do not use algorithms that compute too quickly, like SHA256, as it must remain beyond modern hardware capabilities to perform brute force and 27 | dictionary based attacks.
  • 28 |
  • use a hashing algorithm that generate its own salts as part of the hashing. If you generate your own salts, make sure that a cryptographically 29 | strong salt algorithm is used, that generated salts are credential-specific, and finally, that the salt is applied correctly before the hashing. 30 |
  • 31 |
  • save both the salt and the hashed value in the relevant database record; during future validation operations, the salt and hash can then be 32 | retrieved from the database. The hash is recalculated with the stored salt and the value being validated, and the result compared to the stored 33 | hash.
  • 34 |
  • the strength of hashing algorithms often decreases over time as hardware capabilities increase. Check regularly that the algorithms you are 35 | using are still considered secure. If needed, rehash your data using a stronger algorithm.
  • 36 |
37 |

Sensitive Code Example

38 |
39 | using System.Security.Cryptography;
40 | 
41 | void ComputeHash()
42 | {
43 |     // Review all instantiations of classes that inherit from HashAlgorithm, for example:
44 |     HashAlgorithm hashAlgo = HashAlgorithm.Create(); // Sensitive
45 |     HashAlgorithm hashAlgo2 = HashAlgorithm.Create("SHA1"); // Sensitive
46 |     SHA1 sha = new SHA1CryptoServiceProvider(); // Sensitive
47 |     MD5 md5 = new MD5CryptoServiceProvider(); // Sensitive
48 |     // ...
49 | }
50 | 
51 | class MyHashAlgorithm : HashAlgorithm // Sensitive
52 | {
53 |     // ...
54 | }
55 | 
56 |

See

57 | 68 | 69 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules.Description/S4818.html: -------------------------------------------------------------------------------- 1 |

Using sockets is security-sensitive. It has led in the past to the following vulnerabilities:

2 | 7 |

Sockets are vulnerable in multiple ways:

8 |
    9 |
  • They enable a software to interact with the outside world. As this world is full of attackers it is necessary to check that they cannot receive 10 | sensitive information or inject dangerous input.
  • 11 |
  • The number of sockets is limited and can be exhausted. Which makes the application unresponsive to users who need additional sockets.
  • 12 |
13 |

This rules flags code that creates sockets. It matches only the direct use of sockets, not use through frameworks or high-level APIs such as the 14 | use of http connections.

15 |

Ask Yourself Whether

16 |
    17 |
  • sockets are created without any limit every time a user performs an action.
  • 18 |
  • input received from sockets is used without being sanitized.
  • 19 |
  • sensitive data is sent via sockets without being encrypted.
  • 20 |
21 |

You are at risk if you answered yes to any of these questions.

22 |

Recommended Secure Coding Practices

23 |
    24 |
  • In many cases there is no need to open a socket yourself. Use instead libraries and existing protocols.
  • 25 |
  • Encrypt all data sent if it is sensitive. Usually it is better to encrypt it even if the data is not sensitive as it might change later.
  • 26 |
  • Sanitize any input read from the socket.
  • 27 |
  • Limit the number of sockets a given user can create. Close the sockets as soon as possible.
  • 28 |
29 |

Sensitive Code Example

30 |
31 | using System.Net.Sockets;
32 | 
33 | class TestSocket
34 | {
35 |     public static void Run()
36 |     {
37 |         // Sensitive
38 |         Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
39 | 
40 |         // TcpClient and UdpClient simply abstract the details of creating a Socket
41 |         TcpClient client = new TcpClient("example.com", 80); // Sensitive
42 |         UdpClient listener = new UdpClient(80); // Sensitive
43 |     }
44 | }
45 | 
46 |

See

47 | 56 | 57 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules.Description/S4823.html: -------------------------------------------------------------------------------- 1 |

Using command line arguments is security-sensitive. It has led in the past to the following vulnerabilities:

2 | 7 |

Command line arguments can be dangerous just like any other user input. They should never be used without being first validated and sanitized.

8 |

Remember also that any user can retrieve the list of processes running on a system, which makes the arguments provided to them visible. Thus 9 | passing sensitive information via command line arguments should be considered as insecure.

10 |

This rule raises an issue when on every program entry points (main methods) when command line arguments are used. The goal is to guide 11 | security code reviews.

12 |

Ask Yourself Whether

13 |
    14 |
  • any of the command line arguments are used without being sanitized first.
  • 15 |
  • your application accepts sensitive information via command line arguments.
  • 16 |
17 |

If you answered yes to any of these questions you are at risk.

18 |

Recommended Secure Coding Practices

19 |

Sanitize all command line arguments before using them.

20 |

Any user or application can list running processes and see the command line arguments they were started with. There are safer ways of providing 21 | sensitive information to an application than exposing them in the command line. It is common to write them on the process' standard input, or give the 22 | path to a file containing the information.

23 |

Sensitive Code Example

24 |
25 | namespace MyNamespace
26 | {
27 |     class Program
28 |     {
29 |         static void Main(string[] args) // Sensitive if there is a reference to "args" in the method.
30 |         {
31 |             string myarg = args[0];
32 |             // ...
33 |         }
34 |     }
35 | }
36 | 
37 |

See

38 | 44 | 45 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules.Description/S4829.html: -------------------------------------------------------------------------------- 1 |

Reading Standard Input is security-sensitive. It has led in the past to the following vulnerabilities:

2 | 6 |

It is common for attackers to craft inputs enabling them to exploit software vulnerabilities. Thus any data read from the standard input (stdin) 7 | can be dangerous and should be validated.

8 |

This rule flags code that reads from the standard input.

9 |

Ask Yourself Whether

10 |
    11 |
  • data read from the standard input is not sanitized before being used.
  • 12 |
13 |

You are at risk if you answered yes to this question.

14 |

Recommended Secure Coding Practices

15 |

Sanitize all data read from the standard input before using it.

16 |

Sensitive Code Example

17 |
18 | using System;
19 | public class C
20 | {
21 |     public void Main()
22 |     {
23 |         Console.In; // Sensitive
24 |         var code = Console.Read(); // Sensitive
25 |         var keyInfo = Console.ReadKey(...); // Sensitive
26 |         var text = Console.ReadLine(); // Sensitive
27 |         Console.OpenStandardInput(...); // Sensitive
28 |     }
29 | }
30 | 
31 |

Exceptions

32 |

This rule does not raise issues when the return value of the Console.Read Console.ReadKey or 33 | Console.ReadLine methods is ignored.

34 |
35 | using System;
36 | public class C
37 | {
38 |     public void Main()
39 |     {
40 |         Console.ReadKey(...); // Return value is ignored
41 |         Console.ReadLine(); // Return value is ignored
42 |     }
43 | }
44 | 
45 |

See:

46 | 49 | 50 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules.Description/S4834.html: -------------------------------------------------------------------------------- 1 |

Controlling permissions is security-sensitive. It has led in the past to the following vulnerabilities:

2 | 7 |

Attackers can only damage what they have access to. Thus limiting their access is a good way to prevent them from wreaking havoc, but it has to be 8 | done properly.

9 |

This rule flags code that controls the access to resources and actions or configures this access. The goal is to guide security code reviews.

10 |

Ask Yourself Whether

11 |
    12 |
  • at least one accessed action or resource is security-sensitive.
  • 13 |
  • there is no access control in place or it does not cover all sensitive actions and resources.
  • 14 |
  • users have permissions they don't need.
  • 15 |
  • the access control is based on a user input or on some other unsafe data.
  • 16 |
  • permissions are difficult to remove or take a long time to be updated.
  • 17 |
18 |

You are at risk if you answered yes to the first question and any of the following ones.

19 |

Recommended Secure Coding Practices

20 |

The first step is to restrict all sensitive actions to authenticated users.

21 |

Each user should have the lowest privileges possible. The access control granularity should match the sensitivity of each resource or action. The 22 | more sensitive it is, the less people should have access to it.

23 |

Do not base the access control on a user input or on a value which might have been tampered with. For example, the developer should not read a 24 | user's permissions from an HTTP cookie as it can be modified client-side.

25 |

Check that the access to each action and resource is properly restricted.

26 |

Enable administrators to swiftly remove permissions when necessary. This enables them to reduce the time an attacker can have access to your 27 | systems when a breach occurs.

28 |

Log and monitor refused access requests as they can reveal an attack.

29 |

Sensitive Code Example

30 |
31 | using System.Threading;
32 | using System.Security.Permissions;
33 | using System.Security.Principal;
34 | using System.IdentityModel.Tokens;
35 | 
36 | class SecurityPrincipalDemo
37 | {
38 |     class MyIdentity : IIdentity // Sensitive, custom IIdentity implementations should be reviewed
39 |     {
40 |         // ...
41 |     }
42 | 
43 |     class MyPrincipal : IPrincipal // Sensitive, custom IPrincipal implementations should be reviewed
44 |     {
45 |         // ...
46 |     }
47 |     [System.Security.Permissions.PrincipalPermission(SecurityAction.Demand, Role = "Administrators")] // Sensitive. The access restrictions enforced by this attribute should be reviewed.
48 |     static void CheckAdministrator()
49 |     {
50 |         WindowsIdentity MyIdentity = WindowsIdentity.GetCurrent(); // Sensitive
51 |         HttpContext.User = ...; // Sensitive: review all reference (set and get) to System.Web HttpContext.User
52 |         AppDomain domain = AppDomain.CurrentDomain;
53 |         domain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal); // Sensitive
54 |         MyIdentity identity = new MyIdentity(); // Sensitive
55 |         MyPrincipal MyPrincipal = new MyPrincipal(MyIdentity); // Sensitive
56 |         Thread.CurrentPrincipal = MyPrincipal; // Sensitive
57 |         domain.SetThreadPrincipal(MyPrincipal); // Sensitive
58 | 
59 |         // All instantiation of PrincipalPermission should be reviewed.
60 |         PrincipalPermission principalPerm = new PrincipalPermission(null, "Administrators"); // Sensitive
61 |         principalPerm.Demand();
62 | 
63 |         SecurityTokenHandler handler = ...;
64 |         // Sensitive: this creates an identity.
65 |         ReadOnlyCollection<ClaimsIdentity> identities = handler.ValidateToken(…);
66 |     }
67 | 
68 |      // Sensitive: review how this function uses the identity and principal.
69 |     void modifyPrincipal(MyIdentity identity, MyPrincipal principal)
70 |     {
71 |         // ...
72 |     }
73 | }
74 | 
75 |

See

76 | 80 | 81 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules.Description/S5042.html: -------------------------------------------------------------------------------- 1 |

Expanding archive files is security-sensitive. For example, expanding archive files has led in the past to the following vulnerabilities:

2 | 6 |

Applications that expand archive files (zip, tar, jar, war, 7z, ...) should verify the path where the archive's files are expanded and not trust 7 | blindly the content of the archive. Archive's files should not be expanded outside of the root directory where the archive is supposed to be expanded. 8 | Also, applications should control the size of the expanded data to not be a victim of Zip Bomb attack. Failure to do so could allow an attacker to use 9 | a specially crafted archive that holds directory traversal paths (e.g. ../../attacker.sh) or the attacker could overload the file system, processors 10 | or memory of the operating system where the archive is expanded making the target OS completely unusable.

11 |

This rule raises an issue when code handle archives. The goal is to guide security code reviews.

12 |

Ask Yourself Whether

13 |
    14 |
  • there is no validation of the name of the archive entry
  • 15 |
  • there is no validation of the effective path where the archive entry is going to be expanded
  • 16 |
  • there is no validation of the size of the expanded archive entry
  • 17 |
  • there is no validation of the ratio between the compressed and uncompressed archive entry
  • 18 |
19 |

You are at risk if you answered yes to any of those questions.

20 |

21 |

Recommended Secure Coding Practices

22 |

Validate the full path of the extracted file against the full path of the directory where files are expanded.

23 |
    24 |
  • the canonical path of the expanded file must start with the canonical path of the directory where files are extracted.
  • 25 |
  • the name of the archive entry must not contain "..", i.e. reference to a parent directory.
  • 26 |
27 |

Stop extracting the archive if any of its entries has been tainted with a directory traversal path.

28 |

Define and control the ratio between compressed and uncompress bytes.

29 |

Define and control the maximum allowed expanded file size.

30 |

Count the number of file entries extracted from the archive and abort the extraction if their number is greater than a predefined threshold.

31 |

Sensitive Code Example

32 |
33 | foreach (ZipArchiveEntry entry in archive.Entries)
34 | {
35 |     //  entry.FullName could contain parent directory references ".." and the destinationPath variable could become outside of the desired path
36 |     string destinationPath = Path.GetFullPath(Path.Combine(path, entry.FullName));
37 | 
38 |     entry.ExtractToFile(destinationPath); // Sensitive, extracts the entry in a file
39 | 
40 |     Stream stream;
41 |     stream = entry.Open(); // Sensitive, the entry is about to be extracted
42 | }
43 | 
44 |

See

45 | 53 | 54 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules/Hotspots/S1313_HardcodedIpAddress.fs: -------------------------------------------------------------------------------- 1 | module SonarAnalyzer.FSharp.Rules.S1313_HardcodedIpAddress 2 | 3 | open SonarAnalyzer.FSharp 4 | open SonarAnalyzer.FSharp.RuleHelpers 5 | open FSharpAst 6 | open System.Net 7 | 8 | // ================================================= 9 | // #1313 Using hardcoded IP addresses is security-sensitive 10 | // https://rules.sonarsource.com/csharp/type/Security%20Hotspot/RSPEC-1313 11 | // ================================================= 12 | 13 | module Private = 14 | 15 | [] 16 | let DiagnosticId = "S1313" 17 | let messageFormat = "Make sure using this hardcoded IP address '{0}' is safe here." 18 | let rule = DiagnosticDescriptor.Create(DiagnosticId, messageFormat, RspecStrings.ResourceManager) 19 | 20 | exception EarlyReturn 21 | 22 | let checkWithEarlyReturn f node = 23 | try 24 | f node 25 | with 26 | | :? EarlyReturn -> 27 | None 28 | 29 | 30 | let parseIpAddress str = 31 | match System.Net.IPAddress.TryParse(str) with 32 | | false,_ -> 33 | None 34 | | true, address -> 35 | Some address 36 | 37 | let isIp4Address (address:IPAddress) = 38 | address.AddressFamily = System.Net.Sockets.AddressFamily.InterNetwork 39 | 40 | let isValidIpAddress literalValue = 41 | option { 42 | let! address = parseIpAddress literalValue 43 | if (isIp4Address address) && 44 | // must have 4 parts 45 | literalValue.Split('.').Length <> 4 then 46 | return false 47 | else 48 | return true 49 | // if address parsing fails, return false 50 | } |> Option.defaultValue false 51 | 52 | 53 | let ignoredNames = ["VERSION"; "ASSEMBLY"] 54 | 55 | let isIgnoredMemberName (ctx:TastContext) = 56 | option { 57 | // if there is a containing call, get it, else drop through and return false later 58 | let! _,node = tryContainingCall ctx 59 | let isContainedInMember name = 60 | node.Member.CompiledName.ToUpperInvariant().Contains(name) 61 | // if any matches found, return true 62 | return ignoredNames |> List.exists isContainedInMember 63 | } |> Option.defaultValue false 64 | 65 | let isIgnoredClassName (ctx:TastContext) = 66 | option { 67 | // if there is a containing call, get it, else drop through and return false later 68 | let! _,node = tryContainingNewObjectExpr ctx 69 | let! entity = node.Ctor.DeclaringEntity 70 | let isContainedInClassName name = 71 | entity.CompiledName.ToUpperInvariant().Contains(name) 72 | // if any matches found, return true 73 | return ignoredNames |> List.exists isContainedInClassName 74 | } |> Option.defaultValue false 75 | 76 | 77 | let checkNode (ctx:TastContext) (node:Tast.ConstantExpr) = 78 | if node.Type |> isType WellKnownType.string |> not then raise EarlyReturn 79 | 80 | let literalValue = (string node.Value) // .Trim() // null is converted to "" by `string` so this is safe 81 | // Note Trim() is not used in the C# version 82 | 83 | let allowedValues = [""; "::"; "127.0.0.1"] 84 | if allowedValues |> List.contains literalValue then raise EarlyReturn 85 | 86 | if not (isValidIpAddress literalValue) then raise EarlyReturn 87 | 88 | // check for ignored names such as "ASSEMBLY" 89 | if isIgnoredClassName ctx then raise EarlyReturn 90 | if isIgnoredMemberName ctx then raise EarlyReturn 91 | 92 | // OK if used in an attribute 93 | // NB attributes are not normally visited in the AST. Must be asked for explicitly. 94 | 95 | Diagnostic.Create(rule, node.Location, literalValue) |> Some 96 | 97 | open Private 98 | 99 | /// The implementation of the rule 100 | [] 101 | let Rule : Rule = fun ctx -> 102 | 103 | // only trigger for constants 104 | ctx.Try() 105 | |> Option.bind (checkWithEarlyReturn (checkNode ctx)) 106 | 107 | 108 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules/Hotspots/S2245_DoNotUseRandom.fs: -------------------------------------------------------------------------------- 1 | module SonarAnalyzer.FSharp.Rules.S2245_DoNotUseRandom 2 | 3 | open SonarAnalyzer.FSharp 4 | open SonarAnalyzer.FSharp.RuleHelpers 5 | open FSharpAst 6 | 7 | // ================================================= 8 | // #2245 Using pseudorandom number generators (PRNGs) is security-sensitive 9 | // https://rules.sonarsource.com/csharp/type/Security%20Hotspot/RSPEC-2245 10 | // ================================================= 11 | 12 | module Private = 13 | 14 | [] 15 | let DiagnosticId = "S2245"; 16 | let messageFormat = "Make sure that using this pseudorandom number generator is safe here."; 17 | let rule = DiagnosticDescriptor.Create(DiagnosticId, messageFormat, RspecStrings.ResourceManager) 18 | 19 | /// Checks to see if the ctor for System.Random is ever called. 20 | let checkForRandomConstructor (ctx: TastContext) = 21 | let ctorForRandom : Tast.MemberDescriptor = 22 | { 23 | DeclaringEntity = Some { AccessPath = "System"; DisplayName = "Random"; CompiledName = "Random" } 24 | CompiledName = ".ctor" 25 | DisplayName = ".ctor" 26 | } 27 | 28 | option { 29 | let! call = ctx.Try() 30 | if call.Ctor = ctorForRandom then 31 | return! Diagnostic.Create(rule, call.Location, call.Ctor.CompiledName) |> Some 32 | else 33 | return! None 34 | } 35 | 36 | open Private 37 | 38 | /// The implementation of the rule 39 | [] 40 | let Rule : Rule = fun ctx -> 41 | let rule = 42 | checkForRandomConstructor 43 | rule ctx 44 | 45 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules/Hotspots/S2255_UsingCookies.fs: -------------------------------------------------------------------------------- 1 | module SonarAnalyzer.FSharp.Rules.S2255_UsingCookies 2 | 3 | open SonarAnalyzer.FSharp 4 | open SonarAnalyzer.FSharp.RuleHelpers 5 | open FSharpAst 6 | open System.Net 7 | 8 | // ================================================= 9 | // #2255 Writing cookies is security-sensitive 10 | // https://rules.sonarsource.com/csharp/type/Security%20Hotspot/RSPEC-2255 11 | // ================================================= 12 | 13 | module Private = 14 | 15 | [] 16 | let DiagnosticId = "S2255"; 17 | let messageFormat = "Make sure that this cookie is written safely."; 18 | let rule = DiagnosticDescriptor.Create(DiagnosticId, messageFormat, RspecStrings.ResourceManager) 19 | 20 | exception EarlyReturn 21 | 22 | open Private 23 | 24 | /// The implementation of the rule 25 | [] 26 | let Rule : Rule = fun ctx -> 27 | None 28 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules/Hotspots/S3011_BypassingAccessibility.fs: -------------------------------------------------------------------------------- 1 | module SonarAnalyzer.FSharp.Rules.S3011_BypassingAccessibility 2 | 3 | open SonarAnalyzer.FSharp 4 | open SonarAnalyzer.FSharp.RuleHelpers 5 | open FSharpAst 6 | 7 | // ================================================= 8 | // #3011 Changing or bypassing accessibility is security-sensitive 9 | // https://rules.sonarsource.com/csharp/type/Security%20Hotspot/RSPEC-3011 10 | // ================================================= 11 | 12 | module Private = 13 | 14 | [] 15 | let DiagnosticId = "S3011"; 16 | let messageFormat = "Make sure that this accessibility bypass is safe here."; 17 | let rule = DiagnosticDescriptor.Create(DiagnosticId, messageFormat, RspecStrings.ResourceManager) 18 | 19 | /// Checks to see if the constant for BindingFlags.NonPublic is ever used. 20 | let checkForNonPublicFlag (ctx: TastContext) = 21 | let isBindingFlags (t:FSharpAst.Tast.NamedType) = 22 | let bindingFlagDescriptor : Tast.NamedTypeDescriptor = 23 | { 24 | AccessPath = "System.Reflection" 25 | CompiledName = "BindingFlags" 26 | DisplayName = "BindingFlags" 27 | } 28 | t.Descriptor = bindingFlagDescriptor 29 | 30 | let isNonPublic (constExpr:Tast.ConstantExpr) = 31 | constExpr.Value = (32 |> box) 32 | 33 | option { 34 | let! call = ctx.Try() 35 | match call.Type with 36 | | FSharpAst.Tast.NamedType t -> 37 | let isFailure = isBindingFlags t && isNonPublic call 38 | 39 | if isFailure then 40 | return! Diagnostic.Create(rule, call.Location, t.Descriptor.CompiledName) |> Some 41 | else 42 | return! None 43 | | _ -> return! None 44 | } 45 | 46 | open Private 47 | 48 | /// The implementation of the rule 49 | [] 50 | let Rule : Rule = fun ctx -> 51 | let rule = 52 | checkForNonPublicFlag 53 | rule ctx 54 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules/Hotspots/S4507_DeliveringDebugFeaturesInProduction.fs: -------------------------------------------------------------------------------- 1 | module SonarAnalyzer.FSharp.Rules.S4507_DeliveringDebugFeaturesInProduction 2 | 3 | open SonarAnalyzer.FSharp 4 | open SonarAnalyzer.FSharp.RuleHelpers 5 | open FSharpAst 6 | open System.Net 7 | 8 | // ================================================= 9 | // #4507 Delivering code in production with debug features activated is security-sensitive 10 | // https://rules.sonarsource.com/csharp/type/Security%20Hotspot/RSPEC-4507 11 | // ================================================= 12 | 13 | 14 | module Private = 15 | 16 | [] 17 | let DiagnosticId = "S4507"; 18 | let messageFormat = "Make sure this debug feature is deactivated before delivering the code in production."; 19 | let rule = DiagnosticDescriptor.Create(DiagnosticId, messageFormat, RspecStrings.ResourceManager) 20 | 21 | exception EarlyReturn 22 | 23 | open Private 24 | 25 | /// The implementation of the rule 26 | [] 27 | let Rule : Rule = fun ctx -> 28 | None 29 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules/Hotspots/S4787_EncryptingData.fs: -------------------------------------------------------------------------------- 1 | module SonarAnalyzer.FSharp.Rules.S4787_EncryptingData 2 | 3 | open SonarAnalyzer.FSharp 4 | open SonarAnalyzer.FSharp.RuleHelpers 5 | open FSharpAst 6 | open System.Net 7 | 8 | // ================================================= 9 | // #4787 Encrypting data is security-sensitive 10 | // https://rules.sonarsource.com/csharp/type/Security%20Hotspot/RSPEC-4787 11 | // ================================================= 12 | 13 | module Private = 14 | 15 | [] 16 | let DiagnosticId = "S4787"; 17 | let messageFormat = "Make sure that encrypting data is safe here."; 18 | let rule = DiagnosticDescriptor.Create(DiagnosticId, messageFormat, RspecStrings.ResourceManager) 19 | 20 | exception EarlyReturn 21 | 22 | open Private 23 | 24 | /// The implementation of the rule 25 | [] 26 | let Rule : Rule = fun ctx -> 27 | None 28 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules/Hotspots/S4790_CreatingHashAlgorithms.fs: -------------------------------------------------------------------------------- 1 | module SonarAnalyzer.FSharp.Rules.S4790_CreatingHashAlgorithms 2 | 3 | open SonarAnalyzer.FSharp 4 | open SonarAnalyzer.FSharp.RuleHelpers 5 | open FSharpAst 6 | open System.Net 7 | 8 | // ================================================= 9 | // #4790 Hashing data is security-sensitive 10 | // https://rules.sonarsource.com/csharp/type/Security%20Hotspot/RSPEC-4790 11 | // ================================================= 12 | 13 | module Private = 14 | 15 | [] 16 | let DiagnosticId = "S4790"; 17 | let messageFormat = "Make sure that hashing data is safe here."; 18 | let rule = DiagnosticDescriptor.Create(DiagnosticId, messageFormat, RspecStrings.ResourceManager) 19 | 20 | exception EarlyReturn 21 | 22 | open Private 23 | 24 | /// The implementation of the rule 25 | [] 26 | let Rule : Rule = fun ctx -> 27 | None 28 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules/Hotspots/S4792_ConfiguringLoggers.fs: -------------------------------------------------------------------------------- 1 | module SonarAnalyzer.FSharp.Rules.S4792_ConfiguringLoggers 2 | 3 | open SonarAnalyzer.FSharp 4 | open SonarAnalyzer.FSharp.RuleHelpers 5 | open FSharpAst 6 | open System.Net 7 | 8 | // ================================================= 9 | // #4792 Configuring loggers is security-sensitive 10 | // https://rules.sonarsource.com/csharp/type/Security%20Hotspot/RSPEC-4792 11 | // ================================================= 12 | 13 | module Private = 14 | 15 | [] 16 | let DiagnosticId = "S4792"; 17 | let messageFormat = "Make sure that this logger's configuration is safe."; 18 | let rule = DiagnosticDescriptor.Create(DiagnosticId, messageFormat, RspecStrings.ResourceManager) 19 | 20 | exception EarlyReturn 21 | 22 | open Private 23 | 24 | /// The implementation of the rule 25 | [] 26 | let Rule : Rule = fun ctx -> 27 | None 28 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules/Hotspots/S4818_SocketsCreation.fs: -------------------------------------------------------------------------------- 1 | module SonarAnalyzer.FSharp.Rules.S4818_SocketsCreation 2 | 3 | open SonarAnalyzer.FSharp 4 | open SonarAnalyzer.FSharp.RuleHelpers 5 | open FSharpAst 6 | open System.Net 7 | 8 | // ================================================= 9 | // #4818 Using Sockets is security-sensitive 10 | // https://rules.sonarsource.com/csharp/type/Security%20Hotspot/RSPEC-4818 11 | // ================================================= 12 | 13 | 14 | module Private = 15 | 16 | [] 17 | let DiagnosticId = "S4818"; 18 | let messageFormat = "Make sure that sockets are used safely here."; 19 | let rule = DiagnosticDescriptor.Create(DiagnosticId, messageFormat, RspecStrings.ResourceManager) 20 | 21 | exception EarlyReturn 22 | 23 | open Private 24 | 25 | /// The implementation of the rule 26 | [] 27 | let Rule : Rule = fun ctx -> 28 | None 29 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules/Hotspots/S4823_UsingCommandLineArguments.fs: -------------------------------------------------------------------------------- 1 | module SonarAnalyzer.FSharp.Rules.S4823_UsingCommandLineArguments 2 | 3 | open SonarAnalyzer.FSharp 4 | open SonarAnalyzer.FSharp.RuleHelpers 5 | open FSharpAst 6 | open System.Net 7 | 8 | // ================================================= 9 | // #4823 Using command line arguments is security-sensitive 10 | // https://rules.sonarsource.com/csharp/type/Security%20Hotspot/RSPEC-4823 11 | // ================================================= 12 | 13 | module Private = 14 | 15 | [] 16 | let DiagnosticId = "S4823"; 17 | let messageFormat = "Make sure that command line arguments are used safely here."; 18 | let rule = DiagnosticDescriptor.Create(DiagnosticId, messageFormat, RspecStrings.ResourceManager) 19 | 20 | exception EarlyReturn 21 | 22 | open Private 23 | 24 | /// The implementation of the rule 25 | [] 26 | let Rule : Rule = fun ctx -> 27 | None 28 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules/Hotspots/S4829_ReadingStandardInput.fs: -------------------------------------------------------------------------------- 1 | module SonarAnalyzer.FSharp.Rules.S4829_ReadingStandardInput 2 | 3 | open SonarAnalyzer.FSharp 4 | open SonarAnalyzer.FSharp.RuleHelpers 5 | open FSharpAst 6 | open System.Net 7 | 8 | // ================================================= 9 | // #4829 Reading the Standard Input is security-sensitive 10 | // https://rules.sonarsource.com/csharp/type/Security%20Hotspot/RSPEC-4829 11 | // ================================================= 12 | 13 | module Private = 14 | 15 | [] 16 | let DiagnosticId = "S4829"; 17 | let messageFormat = "Make sure that reading the standard input is safe here."; 18 | let rule = DiagnosticDescriptor.Create(DiagnosticId, messageFormat, RspecStrings.ResourceManager) 19 | 20 | exception EarlyReturn 21 | 22 | open Private 23 | 24 | /// The implementation of the rule 25 | [] 26 | let Rule : Rule = fun ctx -> 27 | None 28 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules/Hotspots/S4834_ControllingPermissions.fs: -------------------------------------------------------------------------------- 1 | module SonarAnalyzer.FSharp.Rules.S4834_ControllingPermissions 2 | 3 | open SonarAnalyzer.FSharp 4 | open SonarAnalyzer.FSharp.RuleHelpers 5 | open FSharpAst 6 | open System.Net 7 | 8 | // ================================================= 9 | // #4834 Controlling permissions is security-sensitive 10 | // https://rules.sonarsource.com/csharp/type/Security%20Hotspot/RSPEC-4834 11 | // ================================================= 12 | 13 | module Private = 14 | 15 | [] 16 | let DiagnosticId = "S4834"; 17 | let messageFormat = "Make sure that permissions are controlled safely here."; 18 | let rule = DiagnosticDescriptor.Create(DiagnosticId, messageFormat, RspecStrings.ResourceManager) 19 | 20 | exception EarlyReturn 21 | 22 | open Private 23 | 24 | /// The implementation of the rule 25 | [] 26 | let Rule : Rule = fun ctx -> 27 | None 28 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/Rules/Hotspots/S5042_ExpandingArchiveFiles.fs: -------------------------------------------------------------------------------- 1 | module SonarAnalyzer.FSharp.Rules.S5042_ExpandingArchiveFiles 2 | 3 | open SonarAnalyzer.FSharp 4 | open SonarAnalyzer.FSharp.RuleHelpers 5 | open FSharpAst 6 | open System.Net 7 | 8 | // ================================================= 9 | // #5042 Expanding archive files is security-sensitive 10 | // https://rules.sonarsource.com/csharp/type/Security%20Hotspot/RSPEC-5042 11 | // ================================================= 12 | 13 | module Private = 14 | 15 | [] 16 | let DiagnosticId = "S5042"; 17 | let messageFormat = "Make sure that decompressing this archive file is safe."; 18 | let rule = DiagnosticDescriptor.Create(DiagnosticId, messageFormat, RspecStrings.ResourceManager) 19 | 20 | exception EarlyReturn 21 | 22 | open Private 23 | 24 | /// The implementation of the rule 25 | [] 26 | let Rule : Rule = fun ctx -> 27 | None 28 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/src/SonarAnalyzer.FSharp/SonarAnalyzer.FSharp.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/tests/FSharpAst.UnitTest/FSharpAst.UnitTest.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.1 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/tests/SonarAnalyzer.FSharp.UnitTest/NoncompliantLocationsTest.fs: -------------------------------------------------------------------------------- 1 | module SonarAnalyzer.FSharp.UnitTest.NoncompliantLocationsTest 2 | 3 | open NUnit.Framework 4 | 5 | // check that the NonCompliantLocations logic is working 6 | 7 | [] 8 | let ``check that noncompliant location parsing works correctly``() = 9 | let lines = [ 10 | @"1. // Compliant" 11 | @"2. normal code // Noncompliant" 12 | @"2. // commented out code // Noncompliant" 13 | @"3. Noncompliant() // a call not a comment" 14 | ] 15 | let actual = Verifier.getNoncompliantLocations lines |> sprintf "%A" 16 | let expected = [2] |> sprintf "%A" 17 | Assert.AreEqual(expected,actual) 18 | 19 | 20 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/tests/SonarAnalyzer.FSharp.UnitTest/RspecStringsTest.fs: -------------------------------------------------------------------------------- 1 | module SonarAnalyzer.FSharp.UnitTest.RspecStringsTest 2 | 3 | open SonarAnalyzer.FSharp 4 | open NUnit.Framework 5 | 6 | // check that the RspecStrings resources has been embedded and access logic is working 7 | 8 | [] 9 | let getStringSucceeds() = 10 | let rm = RspecStrings.ResourceManager 11 | let str = rm.GetString("S1313_Title") 12 | Assert.IsNotNull(str) 13 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/tests/SonarAnalyzer.FSharp.UnitTest/RuleManagerTest.fs: -------------------------------------------------------------------------------- 1 | module SonarAnalyzer.FSharp.UnitTest.RuleFinderTest 2 | 3 | open SonarAnalyzer.FSharp 4 | open NUnit.Framework 5 | open FSharpAst 6 | 7 | // check that the Rule manager logic work 8 | 9 | /// Check that all rules loaded in the assembly actually run witout error 10 | [] 11 | let ``check all AvailableRules can run without error``() = 12 | 13 | let availableRules = RuleManager.getAvailableRules() 14 | if Seq.length availableRules = 0 then 15 | Assert.Fail("Expect non empty list of rules") 16 | 17 | /// create a dummy context to run each rule on 18 | let dummyNode : Tast.ImplementationFile = {Name= "dummy"; Decls=[]} 19 | let ctx : TastContext = {Filename=dummyNode.Name; Node=dummyNode; Ancestors=[]} 20 | 21 | let failedRules = ResizeArray() 22 | for availableRule in availableRules do 23 | let ruleId = availableRule.RuleId 24 | try 25 | let _result = availableRule.Rule ctx 26 | () 27 | with 28 | | ex -> 29 | let msg = sprintf "Rule %s failed with exception '%s'\n%s\n===============" ruleId ex.Message ex.StackTrace 30 | failedRules.Add msg 31 | 32 | if failedRules.Count > 0 then 33 | let msg = String.concat "\n" failedRules 34 | Assert.Fail(msg) 35 | 36 | /// Check that all rules loaded in the assembly can be converted into RuleDetails 37 | /// and that their associated resources are available. 38 | [] 39 | let ``check all RuleDetails can be created``() = 40 | 41 | let availableRules = RuleManager.getAvailableRules() 42 | if Seq.length availableRules = 0 then 43 | Assert.Fail("Expect non empty list of rules") 44 | 45 | let failedRules = ResizeArray() 46 | for availableRule in availableRules do 47 | try 48 | let _ruleDetail = RuleManager.toRuleDetail availableRule 49 | () 50 | with 51 | | ex -> 52 | let msg = sprintf "Rule %s failed with exception '%s'\n%s\n===============" availableRule.RuleId ex.Message ex.StackTrace 53 | failedRules.Add msg 54 | 55 | if failedRules.Count > 0 then 56 | let msg = String.concat "\n" failedRules 57 | Assert.Fail(msg) 58 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/tests/SonarAnalyzer.FSharp.UnitTest/SourceFileTests.fs: -------------------------------------------------------------------------------- 1 | module SonarAnalyzer.FSharp.UnitTest.SourceFileTests 2 | 3 | open System 4 | open NUnit.Framework 5 | open SonarAnalyzer.FSharp 6 | 7 | [] 8 | let S1313_HardcodedIpAddress() = 9 | let rule = Rules.S1313_HardcodedIpAddress.Rule 10 | Verifier.verify @"TestCases/S1313_HardcodedIpAddress.fs" rule 11 | 12 | [] 13 | let S2077_ExecutingSqlQueries() = 14 | let rule = Rules.S2077_ExecutingSqlQueries.Rule 15 | Verifier.verify @"TestCases/S2077_ExecutingSqlQueries.fs" rule 16 | 17 | [] 18 | let S2092_CookieShouldBeSecure() = 19 | let rule = Rules.S2092_CookieShouldBeSecure.Rule 20 | Verifier.verify @"TestCases/S2092_CookieShouldBeSecure.fs" rule 21 | 22 | [] 23 | let S2245_DoNotUseRandom() = 24 | let rule = Rules.S2245_DoNotUseRandom.Rule 25 | Verifier.verify @"TestCases/S2245_DoNotUseRandom.fs" rule 26 | 27 | [] 28 | []//Requires NuGet 29 | let S2255_UsingCookies() = 30 | let rule = Rules.S2255_UsingCookies.Rule 31 | Verifier.verify @"TestCases/S2255_UsingCookies.fs" rule 32 | 33 | [] 34 | let S3011_BypassingAccessibility() = 35 | let rule = Rules.S3011_BypassingAccessibility.Rule 36 | Verifier.verify @"TestCases/S3011_BypassingAccessibility.fs" rule 37 | 38 | [] 39 | []//Requires NuGet 40 | let S4507_DeliveringDebugFeaturesInProduction() = 41 | let rule = Rules.S4507_DeliveringDebugFeaturesInProduction.Rule 42 | Verifier.verify @"TestCases/S4507_DeliveringDebugFeaturesInProduction.fs" rule 43 | 44 | [] 45 | let S4784_UsingRegularExpressions() = 46 | let rule = Rules.S4784_UsingRegularExpressions.Rule 47 | Verifier.verify @"TestCases/S4784_UsingRegularExpressions.fs" rule 48 | 49 | [] 50 | [] 51 | let S4787_EncryptingData() = 52 | let rule = Rules.S4787_EncryptingData.Rule 53 | Verifier.verify @"TestCases/S4787_EncryptingData.fs" rule 54 | 55 | [] 56 | [] 57 | let S4790_CreatingHashAlgorithms() = 58 | let rule = Rules.S4790_CreatingHashAlgorithms.Rule 59 | Verifier.verify @"TestCases/S4790_CreatingHashAlgorithms.fs" rule 60 | 61 | [] 62 | []//Requires NuGet 63 | let S4792_ConfiguringLoggers_AspNetCore() = 64 | let rule = Rules.S4792_ConfiguringLoggers.Rule 65 | Verifier.verify @"TestCases/S4792_ConfiguringLoggers_AspNetCore.fs" rule 66 | 67 | [] 68 | []//Requires NuGet 69 | let S4792_ConfiguringLoggers_Serilog() = 70 | let rule = Rules.S4792_ConfiguringLoggers.Rule 71 | Verifier.verify @"TestCases/S4792_ConfiguringLoggers_Serilog.fs" rule 72 | 73 | [] 74 | [] 75 | let S4818_SocketsCreation() = 76 | let rule = Rules.S4818_SocketsCreation.Rule 77 | Verifier.verify @"TestCases/S4818_SocketsCreation.fs" rule 78 | 79 | [] 80 | [] 81 | let S4823_UsingCommandLineArguments() = 82 | let rule = Rules.S4823_UsingCommandLineArguments.Rule 83 | Verifier.verify @"TestCases/S4823_UsingCommandLineArguments.fs" rule 84 | 85 | [] 86 | [] 87 | let S4829_ReadingStandardInput() = 88 | let rule = Rules.S4829_ReadingStandardInput.Rule 89 | Verifier.verify @"TestCases/S4829_ReadingStandardInput.fs" rule 90 | 91 | [] 92 | []//Requires NuGet 93 | let S4834_ControllingPermissions() = 94 | let rule = Rules.S4834_ControllingPermissions.Rule 95 | Verifier.verify @"TestCases/S4834_ControllingPermissions.fs" rule 96 | 97 | [] 98 | [] 99 | let S5042_ExpandingArchiveFiles() = 100 | let rule = Rules.S5042_ExpandingArchiveFiles.Rule 101 | Verifier.verify @"TestCases/S5042_ExpandingArchiveFiles.fs" rule 102 | 103 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/tests/SonarAnalyzer.FSharp.UnitTest/TestCases/S1313_HardcodedIpAddress.fs: -------------------------------------------------------------------------------- 1 | module SonarAnalyzer.FSharp.UnitTest.TestCases.S1313_HardcodedIpAddress 2 | 3 | /// Dummy class for testing 4 | type AnyAssemblyClass(s:string) = class end 5 | 6 | /// Dummy class for testing 7 | type SomeAttribute(s:string) = 8 | inherit System.Attribute() 9 | 10 | /// Dummy function for testing 11 | let writeAssemblyInfo(assemblyName:string, version:string, author:string, description:string, title:string) = 12 | () 13 | 14 | 15 | [] // this is mainly for assembly versions 16 | let hardcodedIpAddress() = 17 | 18 | let ip1 = "192.168.0.1" // Noncompliant {{Make sure using this hardcoded IP address '192.168.0.1' is safe here.}} 19 | // ^^^^^^^^^^^^^ 20 | 21 | let ip2 = "300.0.0.0" // Compliant, not a valid IP 22 | let ip3 = "127.0.0.1" // Compliant, this is an exception in the rule (see: https://github.com/SonarSource/sonar-csharp/issues/1540) 23 | let ip4 = " 127.0.0.0 " // Compliant 24 | let ip5 = @" ""127.0.0.0"" " // Compliant 25 | 26 | let ip6 = "2001:db8:1234:ffff:ffff:ffff:ffff:ffff" // Noncompliant 27 | let ip7 = "::/0" // Compliant, not recognized as IPv6 address 28 | let ip8 = "::" // Compliant, this is an exception in the rule 29 | 30 | let ip9 = "2" // Compliant, should not be recognized as 0.0.0.2 31 | 32 | let v = System.Version("127.0.0.0") //Compliant 33 | let a = AnyAssemblyClass("127.0.0.0") //Compliant 34 | 35 | //Compliant 36 | writeAssemblyInfo("Project","1.2.0.0","Thomas","Content","Package") 37 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/tests/SonarAnalyzer.FSharp.UnitTest/TestCases/S1313_HardcodedIpAddress.fsi: -------------------------------------------------------------------------------- 1 | module SonarAnalyzer.FSharp.UnitTest.TestCases.S1313_HardcodedIpAddress 2 | 3 | (* 4 | This tests whether FSI files can be parsed too without error 5 | *) -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/tests/SonarAnalyzer.FSharp.UnitTest/TestCases/S2092_CookieShouldBeSecure.fs: -------------------------------------------------------------------------------- 1 | module SonarAnalyzer.FSharp.UnitTest.TestCases.S2092_CookieShouldBeSecure 2 | 3 | open System 4 | 5 | type HttpCookie(str:string) = 6 | member val Secure = false with get, set 7 | member val HttpOnly = false with get, set 8 | 9 | type Program() = 10 | 11 | let mutable field1 = HttpCookie("c") // Noncompliant 12 | let mutable field2 = None 13 | 14 | member val Property1 = HttpCookie("c") with get, set // Noncompliant 15 | member val Property2 = None with get, set 16 | 17 | member this.CtorSetsAllowedValue() = 18 | // none 19 | () 20 | 21 | member this.CtorSetsNotAllowedValue() = 22 | HttpCookie("c") |> ignore // Noncompliant {{Make sure creating this cookie without setting the 'Secure' property is safe here.}} 23 | 24 | member this.InitializerSetsAllowedValue() = 25 | HttpCookie("c", Secure = true) |> ignore 26 | 27 | member this.InitializerSetsNotAllowedValue() = 28 | HttpCookie("c", Secure = false) |> ignore // Noncompliant 29 | // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 30 | HttpCookie("c") |> ignore // Noncompliant 31 | HttpCookie("c", HttpOnly = true) |> ignore // Noncompliant 32 | 33 | member this.PropertySetsNotAllowedValue() = 34 | let c = new HttpCookie("c", Secure = true) 35 | c.Secure <- false // Noncompliant 36 | // ^^^^^^^^^^^^^^^^ 37 | 38 | field1.Secure <- false // Noncompliant 39 | //this.field1.Secure <- false // Noncompliant 40 | 41 | //Property1.Secure <- false // Noncompliant 42 | this.Property1.Secure <- false // Noncompliant 43 | 44 | member this.PropertySetsAllowedValue(foo:bool) = 45 | let c1 = HttpCookie("c") // Compliant, Secure is set below 46 | c1.Secure <- true 47 | 48 | field1 <- HttpCookie("c") // Compliant, Secure is set below 49 | field1.Secure <- true 50 | 51 | field2 <- Some (HttpCookie("c")) // Compliant, Secure is set below 52 | field2.Value.Secure <- true 53 | 54 | this.Property1 <- HttpCookie("c") // Compliant, Secure is set below 55 | this.Property1.Secure <- true 56 | 57 | this.Property2 <- Some (HttpCookie("c")) // Compliant, Secure is set below 58 | this.Property2.Value.Secure <- true 59 | 60 | //let c2 = HttpCookie("c") // Noncompliant, Secure is set conditionally 61 | //if foo then 62 | // c2.Secure <- true 63 | 64 | let c3 = HttpCookie("c") // Compliant, Secure is set after the if 65 | if foo then 66 | // do something 67 | () 68 | c3.Secure <- true 69 | 70 | let mutable c4 : HttpCookie = Unchecked.defaultof 71 | //if foo then 72 | // c4 <- HttpCookie("c") // Noncompliant, Secure is not set in the same scope 73 | c4.Secure <- true 74 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/tests/SonarAnalyzer.FSharp.UnitTest/TestCases/S2245_DoNotUseRandom.fs: -------------------------------------------------------------------------------- 1 | module SonarAnalyzer.FSharp.UnitTest.TestCases.S2245_DoNotUseRandom 2 | 3 | open System 4 | open System.Security.Cryptography 5 | 6 | module Main = 7 | 8 | let r1 = Random() // Noncompliant {{Make sure that using this pseudorandom number generator is safe here.}} 9 | // ^^^^^^^^^^^^ 10 | let r2 = Random(1) // Noncompliant 11 | 12 | let r3 = EventArgs() // Compliant, not Random 13 | 14 | let r4 = RandomNumberGenerator.Create() // Compliant, using cryptographically strong RNG 15 | 16 | () 17 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/tests/SonarAnalyzer.FSharp.UnitTest/TestCases/S2255_UsingCookies.fs: -------------------------------------------------------------------------------- 1 | module SonarAnalyzer.FSharp.UnitTest.TestCases.S2255_UsingCookies 2 | 3 | open Microsoft.AspNetCore.Http 4 | open Microsoft.Extensions.Primitives 5 | 6 | module Program = 7 | 8 | let responses(response:HttpResponse ) = 9 | 10 | // Response headers 11 | response.Headers.Add("Set-Cookie", StringValues "") // Noncompliant 12 | response.Headers.["Set-Cookie"] <- StringValues "" // Noncompliant 13 | let value = response.Headers.["Set-Cookie"] // Compliant 14 | 15 | // Not the Set-Cookie header 16 | response.Headers.Add("something", StringValues "") 17 | response.Headers.["something"] <- value 18 | let value = response.Headers.["something"] 19 | 20 | // Response headers as variable 21 | let responseHeaders = response.Headers 22 | responseHeaders.Add("Set-Cookie", StringValues "") // Noncompliant 23 | responseHeaders.["Set-Cookie"] <- StringValues "" // Noncompliant 24 | let value = responseHeaders.["Set-Cookie"] // Compliant 25 | 26 | responseHeaders.Remove("Set-Cookie") |> ignore // Compliant 27 | responseHeaders.Remove("") |> ignore // Compliant 28 | 29 | // Response cookies as property 30 | response.Cookies.Append("", "") // Noncompliant 31 | response.Cookies.Append("", "", CookieOptions() ) // Noncompliant 32 | 33 | // Response cookies as variable 34 | let responseCookies = response.Cookies 35 | responseCookies.Append("", "") // Noncompliant 36 | responseCookies.Append("", "", CookieOptions() ) // Noncompliant 37 | 38 | responseCookies.Delete("") // Compliant 39 | 40 | let requests(request:HttpRequest )= 41 | 42 | let value = StringValues "" 43 | 44 | // Request headers 45 | request.Headers.Add("Set-Cookie", StringValues "") // Noncompliant 46 | request.Headers.["Set-Cookie"] <- value // Noncompliant 47 | let value = request.Headers.["Set-Cookie"] // Compliant 48 | 49 | // Not the Set-Cookie header 50 | request.Headers.Add("something", StringValues "") 51 | request.Headers.["something"] <- value 52 | let value = request.Headers.["something"] 53 | 54 | // Request headers as variable 55 | let requestHeaders = request.Headers 56 | requestHeaders.Add("Set-Cookie", StringValues "") // Noncompliant 57 | requestHeaders.["Set-Cookie"] <- value // Noncompliant 58 | let value = requestHeaders.["Set-Cookie"] // Compliant 59 | 60 | requestHeaders.Remove("Set-Cookie") |> ignore // Compliant 61 | requestHeaders.Remove("") |> ignore // Compliant 62 | 63 | // Request cookies as property 64 | let value = request.Cookies.[""] // Compliant 65 | let v = request.Cookies.TryGetValue("") // Compliant 66 | 67 | // Request cookies as variable 68 | let requestCookies = request.Cookies 69 | let value = requestCookies.[""] // Compliant 70 | let v = requestCookies.TryGetValue("") // Compliant 71 | 72 | () 73 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/tests/SonarAnalyzer.FSharp.UnitTest/TestCases/S3011_BypassingAccessibility.fs: -------------------------------------------------------------------------------- 1 | module SonarAnalyzer.FSharp.UnitTest.TestCases.S3011_BypassingAccessibility 2 | 3 | open System 4 | open System.Reflection 5 | 6 | module Program = 7 | 8 | let test() = 9 | // RSPEC: https://jira.sonarsource.com/browse/RSPEC-3011 10 | let dynClass = Type.GetType("MyInternalClass") 11 | // Questionable. Using BindingFlags.NonPublic will return non-public members 12 | let bindingAttr = BindingFlags.NonPublic ||| BindingFlags.Static // Noncompliant 13 | // ^^^^^^^^^^^^^^^^^^^^^^ {{Make sure that this accessibility bypass is safe here.}} 14 | let dynMethod = dynClass.GetMethod("mymethod", bindingAttr) 15 | let result = dynMethod.Invoke(dynClass, null) 16 | 17 | () 18 | 19 | 20 | let additionalChecks(t:System.Type) : BindingFlags = 21 | // Using other binding attributes should be ok 22 | let bindingAttr = 23 | BindingFlags.Static ||| BindingFlags.CreateInstance ||| BindingFlags.DeclaredOnly ||| 24 | BindingFlags.ExactBinding ||| BindingFlags.GetField ||| BindingFlags.InvokeMethod // et cetera... 25 | let dynMeth = t.GetMember("mymethod", bindingAttr) 26 | 27 | // We don't detect casts to the forbidden value 28 | let nonPublic : BindingFlags = enum 32 29 | let dynMeth = t.GetMember("mymethod", nonPublic) 30 | 31 | let v = Enum.TryParse("NonPublic") 32 | let dynMeth = t.GetMember("mymethod", nonPublic) 33 | 34 | let bindingAttr = (((BindingFlags.NonPublic)) ||| BindingFlags.Static) // Noncompliant 35 | // ^^^^^^^^^^^^^^^^^^^^^^ 36 | let dynMeth = t.GetMember("mymethod", (BindingFlags.NonPublic)) // Noncompliant 37 | // ^^^^^^^^^^^^^^^^^^^^^^ 38 | let v = (int)BindingFlags.NonPublic // Noncompliant 39 | BindingFlags.NonPublic // Noncompliant 40 | 41 | let defaultAccess = BindingFlags.OptionalParamBinding ||| BindingFlags.NonPublic // Noncompliant 42 | // ^^^^^^^^^^^^^^^^^^^^^^ 43 | 44 | let private access1 = BindingFlags.NonPublic // Noncompliant 45 | 46 | let access2 = BindingFlags.NonPublic // Noncompliant 47 | let getBindingFlags() = BindingFlags.NonPublic // Noncompliant 48 | 49 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/tests/SonarAnalyzer.FSharp.UnitTest/TestCases/S4507_DeliveringDebugFeaturesInProduction.fs: -------------------------------------------------------------------------------- 1 | module SonarAnalyzer.FSharp.UnitTest.TestCases.S4507_DeliveringDebugFeaturesInProduction 2 | 3 | open Microsoft.AspNetCore.Builder 4 | open Microsoft.AspNetCore.Hosting 5 | 6 | module Startup = 7 | 8 | let Configure(app:IApplicationBuilder, env:IHostingEnvironment) = 9 | // Invoking as extension methods 10 | if (env.IsDevelopment()) then 11 | app.UseDeveloperExceptionPage() |> ignore // Compliant 12 | app.UseDatabaseErrorPage() |> ignore // Compliant 13 | 14 | // Invoking as static methods 15 | if (HostingEnvironmentExtensions.IsDevelopment(env)) then 16 | DeveloperExceptionPageExtensions.UseDeveloperExceptionPage(app) |> ignore // Compliant 17 | DatabaseErrorPageExtensions.UseDatabaseErrorPage(app) |> ignore // Compliant 18 | 19 | // Not in development 20 | if not (env.IsDevelopment()) then 21 | DeveloperExceptionPageExtensions.UseDeveloperExceptionPage(app) |> ignore // Noncompliant 22 | DatabaseErrorPageExtensions.UseDatabaseErrorPage(app) |> ignore // Noncompliant 23 | 24 | // Custom conditions are deliberately ignored 25 | let isDevelopment = env.IsDevelopment() 26 | if (isDevelopment) then 27 | app.UseDeveloperExceptionPage() |> ignore // Noncompliant, False Positive 28 | app.UseDatabaseErrorPage() |> ignore // Noncompliant, False Positive 29 | 30 | // These are called unconditionally 31 | app.UseDeveloperExceptionPage() |> ignore // Noncompliant 32 | app.UseDatabaseErrorPage() |> ignore // Noncompliant 33 | DeveloperExceptionPageExtensions.UseDeveloperExceptionPage(app) |> ignore // Noncompliant 34 | DatabaseErrorPageExtensions.UseDatabaseErrorPage(app) // Noncompliant 35 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/tests/SonarAnalyzer.FSharp.UnitTest/TestCases/S4787_EncryptingData.fs: -------------------------------------------------------------------------------- 1 | module SonarAnalyzer.FSharp.UnitTest.TestCases.S4787_EncryptingData 2 | 3 | 4 | open System 5 | open System.Security.Cryptography 6 | 7 | // RSPEC example: https://jira.sonarsource.com/browse/RSPEC-4938 8 | type MyClass() = 9 | 10 | member this.Main() = 11 | let data :Byte[] = [| 1uy; 1uy; 1uy |] 12 | 13 | let myRSA = RSA.Create() 14 | let padding = RSAEncryptionPadding.CreateOaep(HashAlgorithmName.SHA1) 15 | 16 | // Review all base RSA class' Encrypt/Decrypt calls 17 | myRSA.Encrypt(data, padding) |> ignore // Noncompliant 18 | // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ {{Make sure that encrypting data is safe here.}} 19 | myRSA.EncryptValue(data) |> ignore // Noncompliant 20 | myRSA.Decrypt(data, padding) |> ignore // Noncompliant 21 | myRSA.DecryptValue(data) |> ignore // Noncompliant 22 | 23 | let myRSAC = new RSACryptoServiceProvider() 24 | // Review the use of any TryEncrypt/TryDecrypt and specific Encrypt/Decrypt of RSA subclasses. 25 | myRSAC.Encrypt(data, false) |> ignore // Noncompliant 26 | myRSAC.Decrypt(data, false) |> ignore // Noncompliant 27 | 28 | 29 | // Note: TryEncrypt/TryDecrypt are only in .NET Core 2.1+ 30 | // myRSAC.TryEncrypt(data, Span.Empty, padding, out written) // Non compliant 31 | // myRSAC.TryDecrypt(data, Span.Empty, padding, out written) // Non compliant 32 | 33 | let rgbKey : byte[] = [| 1uy; 2uy; 3uy |] 34 | let rgbIV : byte[] = [| 4uy; 5uy; 6uy |] 35 | let rijn = SymmetricAlgorithm.Create() 36 | // Review the creation of Encryptors from any SymmetricAlgorithm instance. 37 | rijn.CreateEncryptor() |> ignore // Noncompliant 38 | // ^^^^^^^^^^^^^^^^^^^^^^ {{Make sure that encrypting data is safe here.}} 39 | rijn.CreateEncryptor(rgbKey, rgbIV) |> ignore // Noncompliant 40 | rijn.CreateDecryptor() |> ignore // Noncompliant 41 | rijn.CreateDecryptor(rgbKey, rgbIV) |> ignore // Noncompliant 42 | 43 | 44 | type MyAsymmetricCrypto() = // Noncompliant 45 | inherit System.Security.Cryptography.AsymmetricAlgorithm() 46 | 47 | 48 | type MySymmetricCrypto() = // Noncompliant 49 | inherit System.Security.Cryptography.SymmetricAlgorithm() 50 | 51 | override this.CreateDecryptor(rgbKey, rgbIV) = null 52 | override this.CreateEncryptor(rgbKey, rgbIV) = null 53 | override this.GenerateIV() = () 54 | override this.GenerateKey() = () 55 | 56 | type MyRSA() = 57 | inherit System.Security.Cryptography.RSA() // Noncompliant 58 | 59 | // Dummy methods with the same names as the additional methods added in Net Core 2.1. 60 | member this.TryEncrypt() = () 61 | member this.TryEncrypt(dummyMethod) = () 62 | member this.TryDecrypt() = () 63 | member this.TryDecrypt(dummyMethod) = () 64 | 65 | member this.OtherMethod() = () 66 | 67 | // Abstract methods 68 | override this.ExportParameters(includePrivateParameters) = new RSAParameters() 69 | override this.ImportParameters(parameters) = () 70 | 71 | 72 | type Class2() = 73 | 74 | member this.AdditionalTests(data:Byte[], padding:RSAEncryptionPadding) = 75 | let customAsymProvider = new MyRSA() 76 | 77 | // Should raise on derived asymmetric classes 78 | customAsymProvider.Encrypt(data, padding) |> ignore // Noncompliant 79 | customAsymProvider.EncryptValue(data) |> ignore // Noncompliant 80 | customAsymProvider.Decrypt(data, padding) |> ignore // Noncompliant 81 | customAsymProvider.DecryptValue(data) |> ignore // Noncompliant 82 | 83 | // Should raise on the Try* methods added in NET Core 2.1 84 | // Note: this test is cheating - we can't currently referencing the 85 | // real 2.1 assemblies since the test project is targetting an older 86 | // NET Framework, so we're testing against a custom subclass 87 | // to which we've added the new method names. 88 | customAsymProvider.TryEncrypt() // Noncompliant 89 | customAsymProvider.TryEncrypt(null) // Noncompliant 90 | customAsymProvider.TryDecrypt() // Noncompliant 91 | customAsymProvider.TryDecrypt(null) // Noncompliant 92 | 93 | customAsymProvider.OtherMethod() 94 | 95 | // Should raise on derived symmetric classes 96 | let customSymProvider = new MySymmetricCrypto() 97 | customSymProvider.CreateEncryptor() |> ignore // Noncompliant 98 | customSymProvider.CreateDecryptor() |> ignore // Noncompliant 99 | 100 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/tests/SonarAnalyzer.FSharp.UnitTest/TestCases/S4790_CreatingHashAlgorithms.fs: -------------------------------------------------------------------------------- 1 | module rec SonarAnalyzer.FSharp.UnitTest.TestCases.S4790_CreatingHashAlgorithms 2 | 3 | open System.Security.Cryptography 4 | 5 | type TestClass() = 6 | 7 | // RSPEC 4790: https://jira.sonarsource.com/browse/RSPEC-4790 8 | member this.ComputeHash() = 9 | // Review all instantiations of classes that inherit from HashAlgorithm, for example: 10 | let hashAlgo = HashAlgorithm.Create() // Noncompliant 11 | // ^^^^^^^^^^^^^^^^^^^^^^ {{Make sure that hashing data is safe here.}} 12 | let hashAlgo2 = HashAlgorithm.Create("SHA1") // Noncompliant 13 | // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ {{Make sure that hashing data is safe here.}} 14 | 15 | let sha = new SHA1CryptoServiceProvider() // Noncompliant 16 | // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ {{Make sure that hashing data is safe here.}} 17 | 18 | let md5 = new MD5CryptoServiceProvider() // Noncompliant 19 | // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ {{Make sure that hashing data is safe here.}} 20 | () 21 | 22 | member this.AdditionalTests(sha1:SHA1CryptoServiceProvider ) = 23 | use myHash = new MyHashAlgorithm() // Noncompliant 24 | use myHash = new MyHashAlgorithm(123) // Noncompliant 25 | 26 | let myHash = MyHashAlgorithm.Create() // Noncompliant 27 | let myHash = MyHashAlgorithm.Create(42) // Noncompliant 28 | 29 | let myHash = MyHashAlgorithm.CreateHash() // compliant - method name is not Create 30 | let myHash = MyHashAlgorithm.DoCreate() // compliant - method name is not Create 31 | 32 | // Other methods are not checked 33 | let hash = sha1.ComputeHash(null:byte[]) 34 | let hash = sha1.Hash 35 | let canReuse = sha1.CanReuseTransform 36 | sha1.Clear() 37 | 38 | type MyHashAlgorithm(data:int) = 39 | inherit HashAlgorithm() // Noncompliant 40 | // ^^^^^^^^^^^^^ 41 | new () = new MyHashAlgorithm(1) 42 | static member Create() : MyHashAlgorithm = failwith "not implemented" 43 | static member Create(data) : MyHashAlgorithm = failwith "not implemented" 44 | 45 | static member CreateHash() : MyHashAlgorithm = failwith "not implemented" 46 | static member DoCreate() :MyHashAlgorithm = failwith "not implemented" 47 | 48 | override this.Initialize() = () 49 | override this.HashCore(array, ibStart, cbSize) = () 50 | override this.HashFinal() = failwith "not implemented" 51 | 52 | 53 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/tests/SonarAnalyzer.FSharp.UnitTest/TestCases/S4792_ConfiguringLoggers_Serilog.fs: -------------------------------------------------------------------------------- 1 | module rec SonarAnalyzer.FSharp.UnitTest.TestCases.S4792_ConfiguringLoggers_Serilog 2 | 3 | open Serilog 4 | open Serilog.Core 5 | 6 | 7 | type SerilogLogging() = 8 | 9 | // RSPEC-4792: https://jira.sonarsource.com/browse/RSPEC-4792 10 | member this.Foo() = 11 | new Serilog.LoggerConfiguration() // Noncompliant 12 | // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ {{Make sure that this logger's configuration is safe.}} 13 | 14 | member this.AdditionalTests() = 15 | let config = LoggerConfiguration() // Noncompliant 16 | let config = MyConfiguration() // Noncompliant 17 | 18 | // Using the logger shouldn't raise issues 19 | let levelSwitch = new LoggingLevelSwitch() 20 | levelSwitch.MinimumLevel <- Serilog.Events.LogEventLevel.Warning 21 | 22 | let newLog = 23 | config.MinimumLevel.ControlledBy(levelSwitch) 24 | .WriteTo.Console() 25 | .CreateLogger() 26 | 27 | Log.Logger <- newLog 28 | Log.Information("logged info") 29 | Log.CloseAndFlush() 30 | 31 | type MyConfiguration() = 32 | inherit LoggerConfiguration() 33 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/tests/SonarAnalyzer.FSharp.UnitTest/TestCases/S4818_SocketsCreation.fs: -------------------------------------------------------------------------------- 1 | module rec SonarAnalyzer.FSharp.UnitTest.TestCases.S4818_SocketsCreation 2 | 3 | open System.Net.Sockets 4 | 5 | type TestSocket() = 6 | 7 | // RSpec example: https://jira.sonarsource.com/browse/RSPEC-4944 8 | static member Run() = 9 | let socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) // Noncompliant 10 | // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ {{Make sure that sockets are used safely here.}} 11 | 12 | // TcpClient and UdpClient simply abstract the details of creating a Socket 13 | let client = new TcpClient("example.com", 80) // Noncompliant 14 | // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ {{Make sure that sockets are used safely here.}} 15 | 16 | let listener = new UdpClient(80) // Noncompliant 17 | // ^^^^^^^^^^^^^^^^^ {{Make sure that sockets are used safely here.}} 18 | () 19 | 20 | member this.Tests(socket:Socket, tcp:TcpClient, udp:UdpClient) = 21 | // Ok to call other methods and properties 22 | socket.Accept() |> ignore 23 | let isAvailable = tcp.Available 24 | udp.DontFragment <- true 25 | 26 | // Creating of subclasses is not checked 27 | let s = new MySocket() 28 | let s = new MyTcpClient() 29 | let s = new MyUdpClient() 30 | () 31 | 32 | type MySocket() = 33 | inherit Socket(new SocketInformation()) 34 | 35 | type MyTcpClient() = 36 | inherit TcpClient() 37 | 38 | type MyUdpClient() = 39 | inherit UdpClient() 40 | 41 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/tests/SonarAnalyzer.FSharp.UnitTest/TestCases/S4823_UsingCommandLineArguments.fs: -------------------------------------------------------------------------------- 1 | module SonarAnalyzer.FSharp.UnitTest.TestCases.S4823_UsingCommandLineArguments 2 | 3 | open System 4 | 5 | type Program1() = 6 | 7 | static member Main([] args:string[]) = // Noncompliant {{Make sure that command line arguments are used safely here.}} 8 | // ^^^^ 9 | Console.WriteLine(args.[0]) 10 | 11 | type Program2() = 12 | 13 | static member Main([] args:string[]) = // Compliant, args is not used 14 | () 15 | 16 | static member Main(arg:string ) = // Compliant, doesn't conform to signature for a Main method 17 | Console.WriteLine(arg) 18 | 19 | static member Main(x:int, [] args:string[]) = // Compliant, doesn't conform to signature for a Main method 20 | Console.WriteLine(args) 21 | 22 | type Program3() = 23 | static let staticArgs : string[] = [||] 24 | 25 | static member Main([] args:string[]) = // Compliant, args is not used 26 | Console.WriteLine(staticArgs) 27 | 28 | type Program4() = 29 | static member Main([] args:string[]) : string = // Compliant, doesn't conform to signature for a Main method 30 | Console.WriteLine(args) 31 | "" 32 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/tests/SonarAnalyzer.FSharp.UnitTest/TestCases/S4829_ReadingStandardInput.fs: -------------------------------------------------------------------------------- 1 | module SonarAnalyzer.FSharp.UnitTest.TestCases.S4829_ReadingStandardInput 2 | 3 | open System 4 | type Con = System.Console 5 | 6 | type MyConsole() = 7 | static member Read() = 1 8 | static member ReadKey() = 1 9 | static member In = 10 | new System.IO.StringReader("") :> System.IO.TextReader 11 | 12 | type Program() = 13 | 14 | member this.Method() = 15 | let code = System.Console.Read() // Noncompliant {{Make sure that reading the standard input is safe here.}} 16 | // ^^^^^^^^^^^^^^^^^^^^^ 17 | let code = Con.Read() // Noncompliant 18 | 19 | let value = Console.ReadLine() // Noncompliant 20 | let code = Console.Read() // Noncompliant 21 | let key = Console.ReadKey() // Noncompliant 22 | let key = Console.ReadKey(true) // Noncompliant 23 | 24 | Console.Read() |> ignore // Compliant, value is ignored 25 | Console.ReadLine() |> ignore // Compliant, value is ignored 26 | Console.ReadKey() |> ignore // Compliant, value is ignored 27 | Console.ReadKey(true) |> ignore // Compliant, value is ignored 28 | 29 | Console.OpenStandardInput() |> ignore // Noncompliant 30 | Console.OpenStandardInput(100) |> ignore // Noncompliant 31 | 32 | let x = System.Console.In // Noncompliant 33 | // ^^^^^^^^^^^^^^^^^ 34 | let x = Console.In // Noncompliant 35 | let x = Con.In // Noncompliant 36 | Console.In.Read() |> ignore // Noncompliant 37 | 38 | // Other Console methods 39 | Console.Write(1) 40 | Console.WriteLine(1) 41 | // Other classes 42 | MyConsole.Read() |> ignore 43 | MyConsole.In.Read() |> ignore 44 | () 45 | 46 | 47 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/tests/SonarAnalyzer.FSharp.UnitTest/TestCases/S4834_ControllingPermissions.fs: -------------------------------------------------------------------------------- 1 | module rec SonarAnalyzer.FSharp.UnitTest.TestCases.S4834_ControllingPermissions 2 | 3 | open System 4 | open Microsoft.IdentityModel.Tokens 5 | open System.Security.Permissions 6 | open System.Security.Principal 7 | open System.Threading 8 | open System.Web 9 | 10 | module Program = 11 | open Microsoft.AspNetCore.Http 12 | 13 | type MyIdentity() = 14 | 15 | interface IIdentity with // Noncompliant {{Make sure that permissions are controlled safely here.}} 16 | // ^^^^^^^^^ 17 | member this.Name = raise (NotImplementedException()) 18 | member this.AuthenticationType = raise (NotImplementedException()) 19 | member this.IsAuthenticated = raise (NotImplementedException()) 20 | 21 | 22 | type MyPrincipal() = 23 | interface IPrincipal with // Noncompliant 24 | member this.Identity = raise (NotImplementedException()) 25 | member this.IsInRole(role) = raise (NotImplementedException()) 26 | 27 | // Indirectly implementing IIdentity 28 | type MyWindowsIdentity() = 29 | inherit WindowsIdentity("") // Noncompliant 30 | 31 | [] 32 | let SecuredMethod() = () // Noncompliant, decorated with PrincipalPermission 33 | // ^^^^^^^^^^^^^ 34 | 35 | let ValidateSecurityToken(handler:SecurityTokenHandler, securityToken:SecurityToken) = 36 | //handler.ValidateToken(securityToken) // Noncompliant 37 | () 38 | 39 | let CreatingPermissions() = 40 | WindowsIdentity.GetCurrent() |> ignore // Noncompliant 41 | // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 42 | 43 | // All instantiations of PrincipalPermission 44 | let principalPermission = new PrincipalPermission(PermissionState.None) // Noncompliant 45 | let principalPermission = new PrincipalPermission("", "") // Noncompliant 46 | let principalPermission = new PrincipalPermission("", "", true) // Noncompliant 47 | () 48 | 49 | let HttpContextUser(httpContext:HttpContext) = 50 | let user = httpContext.User // Noncompliant 51 | // ^^^^^^^^^^^^^^^^ 52 | httpContext.User <- user // Noncompliant 53 | // ^^^^^^^^^^^^^^^^ 54 | 55 | let AppDomainSecurity(appDomain:AppDomain, principal:IPrincipal) = // Noncompliant, IPrincipal parameter, see another section with tests 56 | appDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal) // Noncompliant 57 | // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 58 | appDomain.SetThreadPrincipal(principal) // Noncompliant 59 | appDomain.ExecuteAssembly("") // Compliant, not one of the tracked methods 60 | 61 | let ThreadSecurity(principal:IPrincipal) = // Noncompliant, IPrincipal parameter, see another section with tests 62 | Thread.CurrentPrincipal <- principal // Noncompliant 63 | // ^^^^^^^^^^^^^^^^^^^^^^^ 64 | let principal = Thread.CurrentPrincipal // Noncompliant 65 | () 66 | 67 | 68 | let CreatingPrincipalAndIdentity(windowsIdentity:WindowsIdentity) = // Noncompliant, IIdentity parameter, see another section with tests 69 | let identity = new MyIdentity() // Noncompliant, creation of type that implements IIdentity 70 | // ^^^^^^^^^^^^^^^^ 71 | let identity = new WindowsIdentity("") // Noncompliant 72 | let principal = new MyPrincipal() // Noncompliant, creation of type that implements IPrincipal 73 | let principal = new WindowsPrincipal(windowsIdentity) // Noncompliant 74 | () 75 | 76 | // Method declarations that accept IIdentity or IPrincipal 77 | let AcceptIdentity(identity:MyIdentity ) = () // Noncompliant 78 | // ^^^^^^^^^^^^^^ 79 | let AcceptIdentity2(identity:IIdentity) = () // Noncompliant 80 | let AcceptPrincipal(principal:MyPrincipal) = () // Noncompliant 81 | let AcceptPrincipal2(principal:IPrincipal) = () // Noncompliant 82 | 83 | 84 | type Properties() = 85 | 86 | let mutable identity: IIdentity = null 87 | 88 | member this.Identity 89 | with get() = identity 90 | and set value = // Compliant, we do not raise for property accessors 91 | identity <- value 92 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/tests/SonarAnalyzer.FSharp.UnitTest/TestCases/S5042_ExpandingArchiveFiles.fs: -------------------------------------------------------------------------------- 1 | module SonarAnalyzer.FSharp.UnitTest.TestCases.S5042_ExpandingArchiveFiles 2 | 3 | open System 4 | open System.IO 5 | open System.IO.Compression 6 | open System.Linq 7 | 8 | type Class1() = 9 | 10 | member this.ExtractArchive(archive:ZipArchive) = 11 | for entry in archive.Entries do 12 | entry.ExtractToFile("") // Noncompliant 13 | // ^^^^^^^^^^^^^^^^^^^^^^^ 14 | 15 | for i = 0 to archive.Entries.Count - 1 do 16 | archive.Entries.[i].ExtractToFile("") // Noncompliant 17 | 18 | archive.Entries.ToList() 19 | |> Seq.iter (fun e -> e.ExtractToFile("")) // Noncompliant 20 | // ^^^^^^^^^^^^^^^^^^^ 21 | 22 | member this.ExtractEntry(entry:ZipArchiveEntry) = 23 | entry.ExtractToFile("") // Noncompliant 24 | entry.ExtractToFile("", true) // Noncompliant 25 | 26 | ZipFileExtensions.ExtractToFile(entry, "") // Noncompliant 27 | ZipFileExtensions.ExtractToFile(entry, "", true) // Noncompliant 28 | 29 | let stream = entry.Open() // Noncompliant 30 | 31 | entry.Delete() // Compliant, method is not tracked 32 | 33 | let fullName = entry.FullName // Compliant, properties are not tracked 34 | 35 | this.ExtractToFile(entry) // Compliant, method is not tracked 36 | 37 | //this.Invoke(ZipFileExtensions.ExtractToFile) // Compliant, not an invocation, but could be considered as FN 38 | 39 | member this.ExtractToFile(entry:ZipArchiveEntry) = () 40 | 41 | member this.Invoke(action: Action ) = () 42 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/tests/SonarAnalyzer.FSharp.UnitTest/Verifier.fs: -------------------------------------------------------------------------------- 1 | module SonarAnalyzer.FSharp.UnitTest.Verifier 2 | 3 | (* 4 | Run a rule on a file and verify that the errors detected by the rule 5 | match the errors we expect. 6 | 7 | To get the locations of the expected errors, parse the file 8 | looking for lines tagged with a "// Noncompliant" comment. 9 | *) 10 | 11 | 12 | open System 13 | open System.Text.RegularExpressions 14 | open NUnit.Framework 15 | 16 | let getNoncompliantLocations lines = 17 | 18 | // The pattern means match "//Noncompliant" 19 | // but "" can't contain "/". 20 | // This prevents commented-out lines from showing up. 21 | let regexPattern = @"^[^/]*//\s*Noncompliant" 22 | 23 | let getLocation lineNo lineText = 24 | let m = Regex.Match(lineText, regexPattern) 25 | if (m.Success) then 26 | Some (lineNo + 1) 27 | else 28 | None 29 | 30 | lines 31 | |> List.mapi getLocation 32 | |> List.choose id 33 | 34 | (* 35 | getNoncompliantLocations [ 36 | @"1. // Compliant" 37 | @"2. normal code // Noncompliant" 38 | @"2. // commented out code // Noncompliant" 39 | @"3. Noncompliant() // a call not a comment" 40 | ] 41 | *) 42 | 43 | let applyRule fileName rule = 44 | let config = FSharpAst.TransformerConfig.Default 45 | let tast = FSharpAst.FileApi.translateFile config fileName 46 | [] 47 | 48 | let verify fileName rule = 49 | try 50 | let lines = IO.File.ReadAllLines fileName |> List.ofArray 51 | let expectedLocations = 52 | getNoncompliantLocations lines 53 | |> sprintf "Line numbers %A" // convert to a string for easier testing 54 | let actualLocations = 55 | SonarAnalyzer.FSharp.RuleRunner.analyzeFileWithRules [rule] fileName 56 | |> List.map (fun diag -> diag.Location.StartLine) 57 | |> List.distinct |> List.sort 58 | |> sprintf "Line numbers %A" // convert to a string for easier testing 59 | Assert.AreEqual(expectedLocations, actualLocations) 60 | with 61 | | ex -> 62 | Assert.Fail(sprintf "[%s] %s" fileName ex.Message) 63 | reraise() 64 | -------------------------------------------------------------------------------- /SonarAnalyzer.FSharp/zip-assembly.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | zip-assembly 5 | 6 | zip 7 | 8 | false 9 | 10 | 11 | src/FsSonarRunner/publish 12 | \ 13 | 14 | win-x86/** 15 | linux-x86/** 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # appveyor.yml reference see https://www.appveyor.com/docs/appveyor-yml/ 2 | 3 | #---------------------------------# 4 | # general configuration # 5 | #---------------------------------# 6 | 7 | # version format 8 | version: 1.0.0.{build} 9 | 10 | #---------------------------------# 11 | # environment configuration # 12 | #---------------------------------# 13 | 14 | # Build worker image (VM template) 15 | image: Visual Studio 2017 16 | 17 | # build cache to preserve files/folders between builds 18 | cache: 19 | - C:\Users\appveyor\.m2 20 | 21 | # scripts that run after cloning repository 22 | install: 23 | - cmd: mvn versions:set -DnewVersion=%APPVEYOR_BUILD_VERSION% 24 | 25 | # .NET Core project files patching 26 | dotnet_csproj: 27 | patch: true 28 | file: '**\*.csproj;**\*.props;**\*.fsproj;**\*.xml' 29 | version: '{version}' 30 | package_version: '{version}' 31 | assembly_version: '{version}' 32 | file_version: '{version}' 33 | informational_version: '{version}' 34 | 35 | #---------------------------------# 36 | # build configuration # 37 | #---------------------------------# 38 | 39 | # build Configuration, i.e. Debug, Release, etc. 40 | configuration: Release 41 | 42 | # Build settings, not to be confused with "before_build" and "after_build". 43 | # "project" is relative to the original build directory and not influenced by directory changes in "before_build". 44 | #build: 45 | 46 | # scripts to run before build 47 | #before_build: 48 | 49 | # to run your custom scripts instead of automatic MSBuild 50 | build_script: 51 | - mvn -Dconfiguration=%CONFIGURATION% clean install 52 | # - mvn -Dconfiguration=%CONFIGURATION% -P sonar -Dsonar.branch.name=%APPVEYOR_REPO_BRANCH% sonar:sonar 53 | 54 | # scripts to run after build (working directory and environment changes are persisted from the previous steps) 55 | #after_build: 56 | 57 | # scripts to run *after* solution is built and *before* automatic packaging occurs (web apps, NuGet packages, Azure Cloud Services) 58 | #before_package: 59 | 60 | #---------------------------------# 61 | # tests configuration # 62 | #---------------------------------# 63 | 64 | # to run tests against only selected assemblies and/or categories 65 | #test: 66 | 67 | # scripts to run before tests (working directory and environment changes are persisted from the previous steps such as "before_build") 68 | #before_test: 69 | 70 | # to run your custom scripts instead of automatic tests 71 | #test_script: 72 | 73 | # scripts to run after tests 74 | after_test: 75 | - ps: | 76 | $url = "https://ci.appveyor.com/api/testresults/junit/$($env:APPVEYOR_JOB_ID)" 77 | $wc = New-Object 'System.Net.WebClient' 78 | $files = Get-ChildItem -Recurse .\TEST-*.xml 79 | foreach ($f in $files) { 80 | $wc.UploadFile($url, (Resolve-Path $f)) 81 | } 82 | 83 | # to disable automatic tests 84 | #test: off 85 | 86 | #---------------------------------# 87 | # artifacts configuration # 88 | #---------------------------------# 89 | 90 | artifacts: 91 | - path: 'sonar-fsharpsecurity-plugin/target/*.jar' 92 | name: sonar-fsharpsecurity-plugin 93 | 94 | #---------------------------------# 95 | # deployment configuration # 96 | #---------------------------------# 97 | 98 | #---------------------------------# 99 | # global handlers # 100 | #---------------------------------# 101 | 102 | # on successful build 103 | #on_success: 104 | 105 | # on build failure 106 | #on_failure: 107 | 108 | # after build failure or success 109 | #on_finish: 110 | 111 | #---------------------------------# 112 | # notifications # 113 | #---------------------------------# 114 | -------------------------------------------------------------------------------- /docs/contributing-analyzer.md: -------------------------------------------------------------------------------- 1 | # Building, Testing and Debugging the F# Analyzer 2 | 3 | The F# analyzer can be run as a standalone executable, outside of Sonar. 4 | 5 | This can be useful for local testing before committing, or as a commit hook, etc. 6 | 7 | ## Getting the code 8 | 9 | * Clone [this repository](https://github.com/swlaschin/sonar-fsharpsecurity-plugin.git) 10 | 11 | ## To build and test 12 | 13 | The root of the F# code is `./SonarAnalyzer.FSharp` 14 | 15 | So to build, change to that directory and build as normal. 16 | 17 | ``` 18 | cd .\SonarAnalyzer.FSharp 19 | dotnet build SonarAnalyzer.FSharp.sln 20 | ``` 21 | 22 | To run the F# unit tests: 23 | 24 | ``` 25 | dotnet test SonarAnalyzer.FSharp.sln 26 | ``` 27 | 28 | ## To run against F# code without using the Sonar server 29 | 30 | The main executable is `FsSonarRunner`, so to run it on its own: 31 | 32 | ``` 33 | cd .\SonarAnalyzer.FSharp\src\FsSonarRunner 34 | dotnet run 35 | ``` 36 | 37 | This will show the available options. 38 | 39 | As a demonstration, try running it on the test cases which are part of the test suite 40 | 41 | ``` 42 | cd .\SonarAnalyzer.FSharp\src\FsSonarRunner 43 | dotnet run -- -d ..\..\tests\SonarAnalyzer.FSharp.UnitTest\TestCases 44 | ``` 45 | 46 | To convert the dotnet executable into a standalone: 47 | 48 | ``` 49 | cd .\SonarAnalyzer.FSharp\src\FsSonarRunner 50 | dotnet publish --output publish/ --runtime 51 | ``` 52 | 53 | where `` is `win-x64`, `linux-x64`, etc 54 | 55 | 56 | ## Contributing 57 | 58 | Please see [Contributing Code](../CONTRIBUTING.md) for details on contributing changes back to the code. 59 | 60 | -------------------------------------------------------------------------------- /docs/contributing-plugin.md: -------------------------------------------------------------------------------- 1 | # Building, Testing and Debugging the SonarQube plugin 2 | 3 | This page documents how to develop with the Java side of the plugin. 4 | 5 | See [here for building, testing and debugging the F# analyzer](contributing-analyzer.md). 6 | 7 | ## How the plugin works 8 | 9 | The plugin is a mix of Java (under `sonar-fsharpsecurity-plugin`) and F# (under `SonarAnalyzer.FSharp`). 10 | 11 | The plugin itself is written in Java and is loaded when `sonar-scanner` is used. The way it works is that Sonar provides a number of abstract classes which 12 | the plugin then implements. 13 | 14 | * Sonar asks the plugin what file extensions it wants. The plugin returns `.fs` and `.fsproj`, etc. 15 | * Sonar asks the plugin what rules it supports. In this case, the plugin returns the list using an XML file generated by the F# executable called `FsSolarRunner.exe` (using the `--export` option). 16 | * The Sonar server may know that the user has asked for some of these rules not to run (to avoid the same errors over and over again) 17 | * Sonar then passes the rules and the files into the plugin 18 | * The plugin analyzes those files. In this case the plugin: 19 | * writes those rules and files to an XML file 20 | * then executes the F# executable (`FsSolarRunner.exe`) passing that input file as parameter 21 | * the F# exe dumps out the results as files. 22 | * the plugin then reads these files in to memory 23 | * Finally the plugin returns them to the Solar framework which stores them in the database associated with the server. 24 | 25 | ## Installing Java and Maven on Windows 26 | 27 | To install on Windows, I recommend using the [Chocolatey package manager](https://chocolatey.org/). 28 | 29 | You'll need to install: 30 | 31 | * [OpenJDK](https://chocolatey.org/packages/openjdk) using `choco install openjdk` 32 | * For building, you'll also need [Maven](https://chocolatey.org/packages/maven) using `choco install maven` 33 | 34 | ## Installing Java and Maven on other platforms 35 | 36 | Use your preferred package manager, such as `apt-get`. 37 | 38 | 39 | ## Getting the code 40 | 41 | * Clone [this repository](https://github.com/swlaschin/sonar-fsharpsecurity-plugin.git) 42 | * Download sub-modules `git submodule update --init --recursive` 43 | 44 | ## To build and test 45 | 46 | To build the Java plugin .jar file (from the root): 47 | 48 | ``` 49 | mvn clean install 50 | ``` 51 | 52 | To run the Java unit tests: 53 | 54 | ``` 55 | mvn clean test 56 | ``` 57 | 58 | To create the .jar file that can be copied to the SonarQube plugins directory: 59 | 60 | ``` 61 | mvn clean package 62 | ``` 63 | 64 | The .jar file is output to `\sonar-fsharpsecurity-plugin\target` 65 | 66 | To run the same script that AppVeyor uses: 67 | 68 | ``` 69 | mvn -Dconfiguration=Release clean install 70 | ``` 71 | 72 | ## Developing with VS Code 73 | 74 | Install: 75 | 76 | * Language Support for Java by Red Hat -- [redhat.java](https://marketplace.visualstudio.com/items?itemName=redhat.java) 77 | * Microsoft Debugger for Java -- [vscjava.vscode-java-debug](https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-debug) 78 | 79 | To debug a plugin, see [the instructions on the SonarQube site](https://docs.sonarqube.org/latest/extend/developing-plugin/) 80 | 81 | ## Developing with Eclipse or IntelliJ 82 | 83 | When working with Eclipse or IntelliJ please follow the [sonar guidelines](https://github.com/SonarSource/sonar-developer-toolset) 84 | 85 | ## Understanding the Sonar Plugin API 86 | 87 | See http://javadocs.sonarsource.org/7.9.1/apidocs/ 88 | 89 | 90 | ## Contributing 91 | 92 | Please see [Contributing Code](../CONTRIBUTING.md) for details on contributing changes back to the code. 93 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | swlaschin.sonarqube.fsharp 9 | sonar-fsharpsecurity-analyzer 10 | 0.0.0.1 11 | pom 12 | 13 | org.sonarsource.parent 14 | parent 15 | 52 16 | 17 | 18 | SonarAnalyzer.FSharp 19 | sonar-fsharpsecurity-plugin 20 | 21 | 22 | Debug 23 | 0.8.4 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | org.sonarsource.scanner.maven 32 | sonar-maven-plugin 33 | 3.6.0.1398 34 | 35 | 36 | verify 37 | 38 | sonar 39 | 40 | 41 | 42 | 43 | 44 | org.jacoco 45 | jacoco-maven-plugin 46 | ${jacoco.version} 47 | 48 | 49 | 50 | 51 | 52 | org.jacoco 53 | jacoco-maven-plugin 54 | 55 | 56 | 57 | prepare-agent 58 | 59 | 60 | 61 | report 62 | 63 | report 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | SonarQube F# Security Analyzer Plugin 73 | SonarQube F# Security Analyzer Plugin 74 | https://github.com/swlaschin/sonar-fsharpsecurity-plugin 75 | 2015 76 | 77 | 78 | GNU Lesser General Public License, version 3 79 | https://opensource.org/licenses/LGPL-3.0 80 | manual 81 | 82 | 83 | 84 | swlaschin 85 | https://github.com/swlaschin 86 | 87 | 88 | 89 | 90 | swlaschin 91 | Scott Wlaschin 92 | swlaschin 93 | https://github.com/swlaschin 94 | 95 | owner 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | GitHub 106 | https://github.com/swlaschin/sonar-fsharpsecurity-plugin/issues 107 | 108 | 109 | AppVeyor 110 | https://ci.appveyor.com/project/swlaschin/sonar-fsharpsecurity-plugin 111 | 112 | 113 | scm:git:https://github.com/swlaschin/sonar-fsharpsecurity-plugin.git 114 | https://github.com/swlaschin/sonar-fsharpsecurity-plugin 115 | 116 | 117 | 118 | sonar 119 | 120 | https://sonarcloud.io 121 | sonar-fsharpsecurity-plugin 122 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /sonar-fsharpsecurity-plugin/src/license-header.txt: -------------------------------------------------------------------------------- 1 | Sonar FSharpSecurity Plugin, open source software quality management tool. 2 | 3 | Sonar FSharpSecurity Plugin is free software; you can redistribute it and/or 4 | modify it under the terms of the GNU Lesser General Public 5 | License as published by the Free Software Foundation; either 6 | version 3 of the License, or (at your option) any later version. 7 | 8 | Sonar FSharpSecurity Plugin is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | Lesser General Public License for more details. -------------------------------------------------------------------------------- /sonar-fsharpsecurity-plugin/src/main/java/org/sonar/plugins/fsharp/FSharpIssue.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar FSharpSecurity Plugin, open source software quality management tool. 3 | * 4 | * Sonar FSharpSecurity Plugin is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 3 of the License, or (at your option) any later version. 8 | * 9 | * Sonar FSharpSecurity Plugin is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | */ 14 | 15 | package org.sonar.plugins.fsharp; 16 | 17 | /* 18 | Represents an issue detected by the scanner. 19 | 20 | Designed to be easily compatible with http://javadocs.sonarsource.org/7.9.1/apidocs/org/sonar/api/batch/sensor/issue/NewIssue.html 21 | */ 22 | public class FSharpIssue { 23 | 24 | private final String ruleKey; 25 | private final String message; 26 | private final String absoluteFilePath; 27 | private final int startLine; 28 | private final int startLineOffset; 29 | private final int endLine; 30 | private final int endLineOffset; 31 | 32 | public FSharpIssue(String ruleKey, String message, String absoluteFilePath, int startLine, int startLineOffset, int endLine, int endLineOffset) { 33 | this.ruleKey = ruleKey; 34 | this.message = message; 35 | this.absoluteFilePath = absoluteFilePath; 36 | this.startLine = startLine; 37 | this.startLineOffset = startLineOffset; 38 | this.endLine = endLine; 39 | this.endLineOffset = endLineOffset; 40 | } 41 | 42 | public String ruleKey() { 43 | return ruleKey; 44 | } 45 | 46 | public String message() { 47 | return message; 48 | } 49 | 50 | public String absoluteFilePath() { 51 | return absoluteFilePath; 52 | } 53 | 54 | public int startLine() { 55 | return startLine; 56 | } 57 | 58 | public int startLineOffset() { 59 | return startLineOffset; 60 | } 61 | 62 | public int endLine() { 63 | return endLine; 64 | } 65 | 66 | public int endLineOffset() { 67 | return endLineOffset; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /sonar-fsharpsecurity-plugin/src/main/java/org/sonar/plugins/fsharp/FSharpLanguage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar FSharpSecurity Plugin, open source software quality management tool. 3 | * 4 | * Sonar FSharpSecurity Plugin is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 3 of the License, or (at your option) any later version. 8 | * 9 | * Sonar FSharpSecurity Plugin is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | */ 14 | package org.sonar.plugins.fsharp; 15 | 16 | import org.apache.commons.lang.StringUtils; 17 | import org.sonar.api.config.Configuration; 18 | import org.sonar.api.resources.AbstractLanguage; 19 | import java.util.Optional; 20 | 21 | /* 22 | This class defines properties of the FSharp Language, such as FILE_SUFFIXES 23 | (which is actually a constant defined in the top-level plugin class FSharpPlugin) 24 | */ 25 | 26 | public class FSharpLanguage extends AbstractLanguage { 27 | 28 | private final Configuration configuration; 29 | 30 | public FSharpLanguage(Configuration configuration) { 31 | super(FSharpPlugin.LANGUAGE_KEY, FSharpPlugin.LANGUAGE_NAME); 32 | this.configuration = configuration; 33 | } 34 | 35 | @Override 36 | public boolean equals(Object obj) { 37 | if (!super.equals(obj)) { 38 | return false; 39 | } 40 | 41 | FSharpLanguage fobj = (FSharpLanguage) obj; 42 | // added field is tested 43 | return configuration.equals(fobj.configuration); 44 | } 45 | 46 | @Override 47 | public int hashCode() { 48 | int result = super.hashCode(); 49 | result = 31 * result + configuration.hashCode(); 50 | return result; 51 | } 52 | 53 | @Override 54 | public String[] getFileSuffixes() { 55 | String suffixesStr = configuration.get(FSharpPlugin.FILE_SUFFIXES_KEY).orElse(FSharpPlugin.FILE_SUFFIXES_DEFVALUE); 56 | String[] suffixes = StringUtils.split(suffixesStr, ","); 57 | return suffixes; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /sonar-fsharpsecurity-plugin/src/main/java/org/sonar/plugins/fsharp/FSharpPlugin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar FSharpSecurity Plugin, open source software quality management tool. 3 | * 4 | * Sonar FSharpSecurity Plugin is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 3 of the License, or (at your option) any later version. 8 | * 9 | * Sonar FSharpSecurity Plugin is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | */ 14 | package org.sonar.plugins.fsharp; 15 | 16 | import org.sonar.api.Properties; 17 | import org.sonar.api.Property; 18 | 19 | import org.sonar.api.Plugin; 20 | 21 | /* 22 | This class is the top-level plugin class, called by SonarScanner. 23 | It installs some extensions, listed below. 24 | 25 | All code is in this project is adaptedwith gratitude from 26 | https://github.com/jmecsoftware/sonar-fsharp-plugin 27 | https://github.com/SonarSource/sonar-csharp 28 | */ 29 | 30 | 31 | @Properties({ 32 | @Property( 33 | key = FSharpPlugin.FILE_SUFFIXES_KEY, 34 | defaultValue = FSharpPlugin.FILE_SUFFIXES_DEFVALUE, 35 | name = "File suffixes", 36 | description = "Comma-separated list of suffixes of files to analyze.", 37 | project = true, global = true 38 | ) 39 | }) 40 | public class FSharpPlugin implements Plugin { 41 | 42 | public static final String LANGUAGE_KEY = "fs"; 43 | public static final String LANGUAGE_NAME = "F#"; 44 | 45 | public static final String FILE_SUFFIXES_KEY = "sonar.fs.file.suffixes"; 46 | public static final String FILE_SUFFIXES_DEFVALUE = ".fs,.fsx,.fsi"; 47 | 48 | public static final String FSHARP_WAY_PROFILE = "Sonar way"; 49 | 50 | public static final String REPOSITORY_KEY = "fsharpsecurity"; 51 | public static final String REPOSITORY_NAME = "SonarQube"; 52 | 53 | @Override 54 | public void define(Context context) { 55 | context.addExtension(FSharpLanguage.class); // the F# language properties 56 | context.addExtension(FSharpSonarRulesDefinition.class); // the list of rules available 57 | context.addExtension(FSharpSonarWayProfile.class); // the quality profile 58 | context.addExtension(FsSonarRunnerExtractor.class); // a utility to unzip the F# executable 59 | context.addExtension(FSharpSensor.class); // the main analyzer 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /sonar-fsharpsecurity-plugin/src/main/java/org/sonar/plugins/fsharp/FSharpSonarRulesDefinition.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar FSharpSecurity Plugin, open source software quality management tool. 3 | * 4 | * Sonar FSharpSecurity Plugin is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 3 of the License, or (at your option) any later version. 8 | * 9 | * Sonar FSharpSecurity Plugin is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | */ 14 | 15 | 16 | // based on plugins from from https://github.com/SonarSource 17 | package org.sonar.plugins.fsharp; 18 | 19 | import java.io.InputStream; 20 | import java.nio.charset.StandardCharsets; 21 | import org.sonar.api.rule.Severity; 22 | import org.sonar.api.server.rule.RuleParamType; 23 | import org.sonar.api.server.rule.RulesDefinition; 24 | import org.sonar.api.server.rule.RulesDefinitionXmlLoader; 25 | import org.sonar.api.utils.log.Logger; 26 | import org.sonar.api.utils.log.Loggers; 27 | 28 | /* 29 | This class loads the available rules into the repository. 30 | The rules come from a XML resource file. 31 | 32 | 33 | IMPORTANT: the schema must match the one defined by the F# code in FsSonarRunner/RuleDefinitionDto 34 | and also as defined by Sonar at http://javadocs.sonarsource.org/7.9.1/apidocs/org/sonar/api/server/rule/RulesDefinitionXmlLoader.html 35 | 36 | */ 37 | 38 | public class FSharpSonarRulesDefinition implements RulesDefinition { 39 | private static final String PATH_TO_RULES_XML = "/rules.xml"; 40 | private static final Logger LOG = Loggers.get(FSharpSonarRulesDefinition.class); 41 | 42 | private void defineRulesForLanguage(Context context, String repositoryKey, String repositoryName, String languageKey, String filename) { 43 | NewRepository repository = context.createRepository(repositoryKey, languageKey).setName(repositoryName); 44 | LOG.info("Reading rules definition. File: '" + filename + "' repositoryKey:" + repositoryKey + " repositoryName:" + repositoryName + " languageKey:" + languageKey); 45 | 46 | InputStream rulesXml = this.getClass().getResourceAsStream(filename); 47 | if (rulesXml != null) { 48 | RulesDefinitionXmlLoader rulesLoader = new RulesDefinitionXmlLoader(); 49 | rulesLoader.load(repository, rulesXml, StandardCharsets.UTF_8.name()); 50 | } 51 | else { 52 | LOG.error("No resource found for rules definition"); 53 | } 54 | 55 | repository.done(); 56 | } 57 | 58 | @Override 59 | public void define(Context context) { 60 | defineFromFile(context,PATH_TO_RULES_XML); 61 | } 62 | 63 | public void defineFromFile(Context context, String filename) { 64 | defineRulesForLanguage(context, FSharpPlugin.REPOSITORY_KEY, FSharpPlugin.REPOSITORY_NAME, FSharpPlugin.LANGUAGE_KEY,filename); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /sonar-fsharpsecurity-plugin/src/main/java/org/sonar/plugins/fsharp/FSharpSonarWayProfile.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar FSharpSecurity Plugin, open source software quality management tool. 3 | * 4 | * Sonar FSharpSecurity Plugin is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 3 of the License, or (at your option) any later version. 8 | * 9 | * Sonar FSharpSecurity Plugin is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | */ 14 | 15 | 16 | package org.sonar.plugins.fsharp; 17 | 18 | import java.util.List; 19 | import java.util.ArrayList; 20 | import java.io.File; 21 | import java.io.InputStream; 22 | import java.nio.file.Files; 23 | import java.nio.file.Path; 24 | import java.nio.file.Paths; 25 | import java.nio.charset.StandardCharsets; 26 | import java.net.URL; 27 | import java.io.IOException; 28 | import java.io.BufferedReader; 29 | import java.io.InputStreamReader; 30 | import java.util.stream.Stream; 31 | import java.util.stream.Collectors; 32 | import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; 33 | import org.sonar.api.utils.log.Logger; 34 | import org.sonar.api.utils.log.Loggers; 35 | 36 | 37 | /* 38 | This class defines a Quality Profile, which captures which rules are available and what the severities are. 39 | See https://docs.sonarqube.org/latest/instance-administration/quality-profiles/ 40 | 41 | The default (non-overridable) profile is called "Sonar Way" and is provided by the plugin. 42 | */ 43 | 44 | 45 | public class FSharpSonarWayProfile implements BuiltInQualityProfilesDefinition { 46 | private static final String PATH_TO_PROFILE_TXT = "/profile.txt"; 47 | public static final Logger LOG = Loggers.get(FSharpSonarWayProfile.class); 48 | 49 | @Override 50 | public void define(Context context) { 51 | URL profileUrl = getClass().getResource(PATH_TO_PROFILE_TXT); 52 | defineFromUrl(context,profileUrl); 53 | } 54 | 55 | public void defineFromUrl(Context context, URL profileUrl) { 56 | LOG.info("FSharpSonarWayProfile: reading profile " + profileUrl.toString()); 57 | NewBuiltInQualityProfile profile = context.createBuiltInQualityProfile(FSharpPlugin.FSHARP_WAY_PROFILE, FSharpPlugin.LANGUAGE_KEY); 58 | profile.setDefault(true); 59 | 60 | List ruleKeys = importRuleKeysFromUrl(profileUrl); 61 | ruleKeys.forEach( (ruleKey) -> profile.activateRule(FSharpPlugin.REPOSITORY_KEY, ruleKey) ); 62 | 63 | profile.done(); 64 | } 65 | 66 | public List importRuleKeysFromUrl(URL profileUrl) { 67 | List ruleKeys = new ArrayList(); 68 | 69 | try (InputStream stream = profileUrl.openStream()) { 70 | ruleKeys = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8)).lines().collect(Collectors.toList()); 71 | LOG.info("FSharpSonarWayProfile: #rules found: " + Integer.toString(ruleKeys.size()) ); 72 | } catch (IOException e) { 73 | LOG.error("Unable to read profile stream: {} => {}", profileUrl.toString(), e.getMessage()); 74 | } 75 | 76 | return ruleKeys; 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /sonar-fsharpsecurity-plugin/src/main/java/org/sonar/plugins/fsharp/FsSonarRunnerExtractor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar FSharpSecurity Plugin, open source software quality management tool. 3 | * 4 | * Sonar FSharpSecurity Plugin is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 3 of the License, or (at your option) any later version. 8 | * 9 | * Sonar FSharpSecurity Plugin is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | */ 14 | 15 | package org.sonar.plugins.fsharp; 16 | 17 | import org.sonar.api.batch.InstantiationStrategy; 18 | 19 | import java.io.File; 20 | import java.io.IOException; 21 | import java.io.InputStream; 22 | import java.nio.file.Files; 23 | import org.sonar.api.batch.ScannerSide; 24 | import org.sonar.api.utils.log.Logger; 25 | import org.sonar.api.utils.log.Loggers; 26 | import org.sonar.plugins.fsharp.utils.OSInfo; 27 | import org.sonar.plugins.fsharp.utils.UnZip; 28 | 29 | /* 30 | Extract the FsSolarRunner from the zip file 31 | */ 32 | 33 | // adapted from https://github.com/SonarSource/sonar-csharp 34 | @InstantiationStrategy(InstantiationStrategy.PER_BATCH) 35 | @ScannerSide() 36 | public class FsSonarRunnerExtractor { 37 | public static final Logger LOG = Loggers.get(FsSonarRunnerExtractor.class); 38 | private static final String SONARQUBE_ANALYZER_EXE = "FsSonarRunner"; 39 | private static final String SONARQUBE_ANALYZER_ZIP = "SonarAnalyzer.FSharp.zip"; 40 | 41 | private File file = null; 42 | 43 | public File executableFile(String workDir) throws IOException { 44 | // once loaded, file is cached between calls 45 | if (file == null) { 46 | String filePath; 47 | switch (OSInfo.getOs()) { 48 | case WINDOWS: 49 | filePath = "win-x86" + File.separator + SONARQUBE_ANALYZER_EXE + ".exe"; 50 | break; 51 | case LINUX: 52 | filePath = "linux-x86" + File.separator + SONARQUBE_ANALYZER_EXE; 53 | break; 54 | default: 55 | String msg = "Operation system `" + OSInfo.getOs().toString() + "`not supported"; 56 | LOG.error(msg); 57 | throw new UnsupportedOperationException(msg); 58 | } 59 | 60 | file = unzipAnalyzerFile(filePath, workDir); 61 | if (!file.canExecute() && !file.setExecutable(true)) { 62 | LOG.error("Could not set executable permission"); 63 | } 64 | } 65 | 66 | return file; 67 | } 68 | 69 | private File unzipAnalyzerFile(String fileName, String workDir) throws IOException { 70 | File toolWorkingDir = new File(workDir, "ProjectTools"); 71 | File zipFile = new File(workDir, SONARQUBE_ANALYZER_ZIP); 72 | 73 | if (zipFile.exists()) { 74 | return new File(toolWorkingDir, fileName); 75 | } 76 | 77 | try { 78 | try (InputStream is = getClass().getResourceAsStream("/" + SONARQUBE_ANALYZER_ZIP)) { 79 | Files.copy(is, zipFile.toPath()); 80 | } 81 | 82 | UnZip unZip = new UnZip(); 83 | unZip.unZipIt(zipFile.getAbsolutePath(), toolWorkingDir.getAbsolutePath()); 84 | return new File(toolWorkingDir, fileName); 85 | } catch (IOException e) { 86 | LOG.error("Unable to unzip File: {} => {}", fileName, e.getMessage()); 87 | throw e; 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /sonar-fsharpsecurity-plugin/src/main/java/org/sonar/plugins/fsharp/utils/OSInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar FSharpSecurity Plugin, open source software quality management tool. 3 | * 4 | * Sonar FSharpSecurity Plugin is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 3 of the License, or (at your option) any later version. 8 | * 9 | * Sonar FSharpSecurity Plugin is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | */ 14 | 15 | // copied with thanks from https://github.com/jmecsoftware/sonar-fsharp-plugin 16 | 17 | package org.sonar.plugins.fsharp.utils; 18 | 19 | import java.io.IOException; 20 | import java.util.Locale; 21 | 22 | /** 23 | * Detect OS Name and Version Java 24 | * 25 | * Java is Platform independent and can run everywhere. Knowing this, 26 | * it can be worth knowing in what operation system the application is 27 | * running. To detect the current operation system, you can use the 28 | * OSInfo class below, which retrieves the information from the 29 | * system properties and returns an OS enum that holds the name and 30 | * version of the operating system the application is running on. 31 | * 32 | * @see original code at MemoryNotFound.org 33 | */ 34 | public class OSInfo { 35 | 36 | public enum OS { 37 | WINDOWS, 38 | UNIX, 39 | POSIX_UNIX, 40 | MAC, 41 | LINUX, 42 | OTHER; 43 | 44 | private String version; 45 | 46 | public String getVersion() { 47 | return version; 48 | } 49 | 50 | private void setVersion(String version) { 51 | this.version = version; 52 | } 53 | } 54 | 55 | private static OS os = OS.OTHER; 56 | 57 | static { 58 | try { 59 | String osName = System.getProperty("os.name"); 60 | if (osName == null) { 61 | throw new IOException("os.name not found"); 62 | } 63 | osName = osName.toLowerCase(Locale.ENGLISH); 64 | if (osName.contains("windows")) { 65 | os = OS.WINDOWS; 66 | } else if (osName.contains("linux")) { 67 | os = OS.LINUX; 68 | } else if (osName.contains("mpe/ix") 69 | || osName.contains("freebsd") 70 | || osName.contains("irix") 71 | || osName.contains("digital unix") 72 | || osName.contains("unix")) { 73 | os = OS.UNIX; 74 | } else if (osName.contains("mac os")) { 75 | os = OS.MAC; 76 | } else if (osName.contains("sun os") 77 | || osName.contains("sunos") 78 | || osName.contains("solaris")) { 79 | os = OS.POSIX_UNIX; 80 | } else if (osName.contains("hp-ux") 81 | || osName.contains("aix")) { 82 | os = OS.POSIX_UNIX; 83 | } else { 84 | os = OS.OTHER; 85 | } 86 | 87 | } catch (Exception ex) { 88 | os = OS.OTHER; 89 | } finally { 90 | os.setVersion(System.getProperty("os.version")); 91 | } 92 | } 93 | 94 | public static OS getOs() { 95 | return os; 96 | } 97 | } -------------------------------------------------------------------------------- /sonar-fsharpsecurity-plugin/src/main/resources/profile.txt: -------------------------------------------------------------------------------- 1 | S5042 2 | S4834 3 | S4829 4 | S4823 5 | S4818 6 | S4792 7 | S4790 8 | S4787 9 | S4784 10 | S4507 11 | S3011 12 | S2255 13 | S2245 14 | S2092 15 | S2077 16 | S1313 17 | -------------------------------------------------------------------------------- /sonar-fsharpsecurity-plugin/src/test/java/org/sonar/plugins/fsharp/FSharpAnalysisResultImporterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar FSharpSecurity Plugin, open source software quality management tool. 3 | * 4 | * Sonar FSharpSecurity Plugin is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 3 of the License, or (at your option) any later version. 8 | * 9 | * Sonar FSharpSecurity Plugin is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | */ 14 | 15 | package org.sonar.plugins.fsharp; 16 | 17 | import java.io.BufferedWriter; 18 | import java.io.File; 19 | import java.io.FileInputStream; 20 | import java.io.FileWriter; 21 | import java.io.IOException; 22 | import java.io.InputStreamReader; 23 | import java.nio.charset.StandardCharsets; 24 | import java.nio.file.Paths; 25 | import java.util.Base64; 26 | import java.util.Collection; 27 | import java.util.HashMap; 28 | import java.util.HashSet; 29 | import java.util.Map; 30 | import java.util.Map.Entry; 31 | import java.util.List; 32 | import java.util.ArrayList; 33 | 34 | import static org.junit.jupiter.api.Assertions.assertEquals; 35 | import static org.junit.jupiter.api.Assertions.assertNotNull; 36 | import static org.junit.jupiter.api.Assertions.assertTrue; 37 | 38 | import org.junit.jupiter.api.Disabled; 39 | import org.junit.jupiter.api.Test; 40 | 41 | import org.sonar.api.utils.log.Logger; 42 | import org.sonar.api.utils.log.LogTester; 43 | import org.sonar.api.utils.log.LoggerLevel; 44 | 45 | public class FSharpAnalysisResultImporterTest { 46 | 47 | private static final LogTester logTester = new LogTester(); 48 | private static final String analysisOutput = "/sonarDiagnosticsExample.xml"; 49 | 50 | @Test 51 | public void moreThanOneIssueLoaded() { 52 | // Arrange 53 | logTester.setLevel(LoggerLevel.DEBUG); 54 | File file = new File(getClass().getResource(analysisOutput).getFile()); 55 | 56 | // Act 57 | List issues = new FSharpAnalysisResultImporter().parse(file); 58 | 59 | // Assert 60 | boolean moreThanOneRule = issues.size() > 0; 61 | assertTrue(moreThanOneRule, "Expecting more than one issue from the example file"); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /sonar-fsharpsecurity-plugin/src/test/java/org/sonar/plugins/fsharp/FSharpLanguageTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar FSharpSecurity Plugin, open source software quality management tool. 3 | * 4 | * Sonar FSharpSecurity Plugin is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 3 of the License, or (at your option) any later version. 8 | * 9 | * Sonar FSharpSecurity Plugin is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | */ 14 | 15 | package org.sonar.plugins.fsharp; 16 | 17 | import static org.junit.jupiter.api.Assertions.assertEquals; 18 | import static org.junit.jupiter.api.Assertions.assertFalse; 19 | import static org.junit.jupiter.api.Assertions.assertNotEquals; 20 | import static org.junit.jupiter.api.Assertions.assertTrue; 21 | 22 | import org.apache.commons.lang.StringUtils; 23 | import org.junit.jupiter.api.Test; 24 | import org.sonar.api.config.Configuration; 25 | import org.sonar.api.config.internal.MapSettings; 26 | 27 | /* 28 | Check that the FSharp Language class overrides equals/hash etc 29 | */ 30 | 31 | 32 | public class FSharpLanguageTest { 33 | @Test 34 | public void baseClassConstructorCall() { 35 | Configuration configuration = (new MapSettings()).asConfig(); 36 | FSharpLanguage fsharp = new FSharpLanguage(configuration); 37 | 38 | assertEquals(FSharpPlugin.LANGUAGE_KEY, fsharp.getKey(), "Language Key"); 39 | assertEquals(FSharpPlugin.LANGUAGE_NAME, fsharp.getName(), "Language Name"); 40 | } 41 | 42 | @Test 43 | public void hashcode_equals_true() { 44 | Configuration configuration = (new MapSettings()).asConfig(); 45 | FSharpLanguage fsharp1 = new FSharpLanguage(configuration); 46 | FSharpLanguage fsharp2 = new FSharpLanguage(configuration); 47 | 48 | boolean areEqual = fsharp1.equals(fsharp2); 49 | 50 | assertTrue(areEqual); 51 | assertEquals(fsharp1.hashCode(), fsharp2.hashCode()); 52 | } 53 | 54 | @Test 55 | public void hashcode_equals_false() { 56 | Configuration configuration1 = (new MapSettings()).asConfig(); 57 | Configuration configuration2 = (new MapSettings()).asConfig(); 58 | FSharpLanguage fsharp1 = new FSharpLanguage(configuration1); 59 | FSharpLanguage fsharp2 = new FSharpLanguage(configuration2); 60 | 61 | boolean areEqual = fsharp1.equals(fsharp2); 62 | 63 | assertFalse(areEqual); 64 | assertNotEquals(fsharp1.hashCode(), fsharp2.hashCode()); 65 | } 66 | 67 | @Test 68 | public void fileSuffixes_default() { 69 | Configuration configuration = (new MapSettings()).asConfig(); 70 | FSharpLanguage fsharp = new FSharpLanguage(configuration); 71 | 72 | String[] suffixes = fsharp.getFileSuffixes(); 73 | 74 | assertEquals(3, suffixes.length); 75 | for (String suffix : suffixes) { 76 | assertTrue(FSharpPlugin.FILE_SUFFIXES_DEFVALUE.contains(suffix), "`" + suffix + "` not found"); 77 | } 78 | } 79 | 80 | @Test 81 | public void fileSuffixes_userDefined() { 82 | String keys = ".fs,.fsx"; 83 | int no_keys = 1 + StringUtils.countMatches(keys, ","); 84 | MapSettings settings = new MapSettings(); 85 | settings.setProperty(FSharpPlugin.FILE_SUFFIXES_KEY, keys); 86 | Configuration configuration = settings.asConfig(); 87 | FSharpLanguage fsharp = new FSharpLanguage(configuration); 88 | 89 | String[] suffixes = fsharp.getFileSuffixes(); 90 | 91 | assertEquals(no_keys, suffixes.length); 92 | for (String suffix : suffixes) { 93 | assertTrue(keys.contains(suffix), "`" + suffix + "` not found"); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /sonar-fsharpsecurity-plugin/src/test/java/org/sonar/plugins/fsharp/FSharpPluginTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar FSharpSecurity Plugin, open source software quality management tool. 3 | * 4 | * Sonar FSharpSecurity Plugin is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 3 of the License, or (at your option) any later version. 8 | * 9 | * Sonar FSharpSecurity Plugin is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | */ 14 | 15 | package org.sonar.plugins.fsharp; 16 | 17 | import static org.junit.jupiter.api.Assertions.assertEquals; 18 | import static org.mockito.Mockito.mock; 19 | 20 | import org.junit.jupiter.api.Test; 21 | import org.sonar.api.Plugin; 22 | import org.sonar.api.SonarRuntime; 23 | 24 | 25 | /* 26 | Check that 5 extensions are loaded in FSharpPlugin 27 | */ 28 | 29 | public class FSharpPluginTest { 30 | @Test 31 | public void addExtensions_expectedNumber() { 32 | // Arrange 33 | Plugin.Context context = new Plugin.Context(mock(SonarRuntime.class)); 34 | FSharpPlugin plugin = new FSharpPlugin(); 35 | 36 | // Act 37 | plugin.define(context); 38 | 39 | // Assert 40 | assertEquals(5, context.getExtensions().size()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /sonar-fsharpsecurity-plugin/src/test/java/org/sonar/plugins/fsharp/FSharpSensorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar FSharpSecurity Plugin, open source software quality management tool. 3 | * 4 | * Sonar FSharpSecurity Plugin is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 3 of the License, or (at your option) any later version. 8 | * 9 | * Sonar FSharpSecurity Plugin is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | */ 14 | 15 | package org.sonar.plugins.fsharp; 16 | 17 | import static org.junit.jupiter.api.Assertions.assertEquals; 18 | import static org.junit.jupiter.api.Assertions.assertTrue; 19 | import static org.mockito.Mockito.mock; 20 | 21 | import org.junit.jupiter.api.Test; 22 | import org.sonar.api.batch.fs.FileSystem; 23 | import org.sonar.api.batch.sensor.Sensor; 24 | import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor; 25 | import org.sonar.api.issue.NoSonarFilter; 26 | import org.sonar.api.measures.FileLinesContextFactory; 27 | 28 | import org.sonar.api.utils.log.Logger; 29 | import org.sonar.api.utils.log.LogTester; 30 | import org.sonar.api.utils.log.LoggerLevel; 31 | 32 | /* 33 | Check that the analyzer works. 34 | 35 | The real unit tests are in the F# code, so we just need to check that it can be called without crashing. 36 | */ 37 | 38 | public class FSharpSensorTest { 39 | 40 | private static final LogTester logTester = new LogTester(); 41 | 42 | 43 | @Test 44 | public void describe_languageAndKey_asExpected() { 45 | // Arrange 46 | logTester.setLevel(LoggerLevel.DEBUG); 47 | FsSonarRunnerExtractor extractor = new FsSonarRunnerExtractor(); 48 | FileSystem fs = mock(FileSystem.class); 49 | FileLinesContextFactory fileLinesContextFactory = mock(FileLinesContextFactory.class); 50 | NoSonarFilter noSonarFilter = new NoSonarFilter(); 51 | Sensor sensor = new FSharpSensor(extractor, fs, fileLinesContextFactory, noSonarFilter); 52 | 53 | DefaultSensorDescriptor descriptor = new DefaultSensorDescriptor(); 54 | 55 | // Act 56 | sensor.describe(descriptor); 57 | 58 | // Assert 59 | assertEquals(FSharpPlugin.LANGUAGE_NAME, descriptor.name()); 60 | assertEquals(1, descriptor.languages().size()); 61 | assertTrue(descriptor.languages().contains(FSharpPlugin.LANGUAGE_KEY), "LANGUAGE_KEY not found"); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /sonar-fsharpsecurity-plugin/src/test/java/org/sonar/plugins/fsharp/FSharpSonarRulesDefinitionTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar FSharpSecurity Plugin, open source software quality management tool. 3 | * 4 | * Sonar FSharpSecurity Plugin is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 3 of the License, or (at your option) any later version. 8 | * 9 | * Sonar FSharpSecurity Plugin is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | */ 14 | 15 | package org.sonar.plugins.fsharp; 16 | 17 | import static org.junit.jupiter.api.Assertions.assertEquals; 18 | import static org.junit.jupiter.api.Assertions.assertNotNull; 19 | import static org.junit.jupiter.api.Assertions.assertTrue; 20 | 21 | import org.junit.jupiter.api.Disabled; 22 | import org.junit.jupiter.api.Test; 23 | import org.sonar.api.server.rule.RulesDefinition.Context; 24 | 25 | import org.sonar.api.utils.log.Logger; 26 | import org.sonar.api.utils.log.LogTester; 27 | import org.sonar.api.utils.log.LoggerLevel; 28 | 29 | public class FSharpSonarRulesDefinitionTest { 30 | private static final String rulesFile = "/rulesExample.xml"; 31 | private static final LogTester logTester = new LogTester(); 32 | 33 | @Test 34 | public void repositories_exactlyOne() { 35 | // Arrange 36 | logTester.setLevel(LoggerLevel.DEBUG); 37 | Context context = new Context(); 38 | assertEquals(0, context.repositories().size()); 39 | 40 | // Act 41 | new FSharpSonarRulesDefinition().defineFromFile(context,rulesFile); 42 | 43 | // Assert 44 | assertEquals(1, context.repositories().size()); 45 | } 46 | 47 | @Test 48 | public void repository_expectedNameAndKey() { 49 | // Arrange 50 | logTester.setLevel(LoggerLevel.DEBUG); 51 | Context context = new Context(); 52 | 53 | // Act 54 | new FSharpSonarRulesDefinition().defineFromFile(context,rulesFile); 55 | 56 | // Assert 57 | assertEquals(FSharpPlugin.REPOSITORY_NAME, context.repositories().get(0).name()); 58 | assertNotNull(context.repository(FSharpPlugin.REPOSITORY_KEY)); 59 | } 60 | 61 | @Test 62 | public void moreThanOneRuleLoaded() { 63 | // Arrange 64 | logTester.setLevel(LoggerLevel.DEBUG); 65 | Context context = new Context(); 66 | assertEquals(0, context.repositories().size()); 67 | 68 | // Act 69 | new FSharpSonarRulesDefinition().defineFromFile(context,rulesFile); 70 | 71 | // Assert 72 | boolean moreThanOneRule = context.repository(FSharpPlugin.REPOSITORY_KEY).rules().size() > 0; 73 | assertTrue(moreThanOneRule, "Expecting more than one rule"); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /sonar-fsharpsecurity-plugin/src/test/java/org/sonar/plugins/fsharp/FSharpSonarWayProfileTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar FSharpSecurity Plugin, open source software quality management tool. 3 | * 4 | * Sonar FSharpSecurity Plugin is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 3 of the License, or (at your option) any later version. 8 | * 9 | * Sonar FSharpSecurity Plugin is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | */ 14 | 15 | package org.sonar.plugins.fsharp; 16 | 17 | import java.io.BufferedWriter; 18 | import java.io.File; 19 | import java.io.FileInputStream; 20 | import java.io.FileWriter; 21 | import java.io.IOException; 22 | import java.io.InputStreamReader; 23 | import java.nio.charset.StandardCharsets; 24 | import java.nio.file.Paths; 25 | import java.util.Base64; 26 | import java.util.Collection; 27 | import java.util.HashMap; 28 | import java.util.HashSet; 29 | import java.util.Map; 30 | import java.util.Map.Entry; 31 | import java.util.List; 32 | import java.util.ArrayList; 33 | import java.net.URL; 34 | 35 | import static org.junit.jupiter.api.Assertions.assertEquals; 36 | import static org.junit.jupiter.api.Assertions.assertNotNull; 37 | import static org.junit.jupiter.api.Assertions.assertTrue; 38 | 39 | import org.junit.jupiter.api.Disabled; 40 | import org.junit.jupiter.api.Test; 41 | 42 | import org.sonar.api.utils.log.Logger; 43 | import org.sonar.api.utils.log.LogTester; 44 | import org.sonar.api.utils.log.LoggerLevel; 45 | 46 | import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; 47 | import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.NewBuiltInQualityProfileImpl; 48 | 49 | public class FSharpSonarWayProfileTest { 50 | 51 | private static final LogTester logTester = new LogTester(); 52 | private static final String PROFILE_EXAMPLE_PATH = "/profileExample.txt"; 53 | 54 | @Test 55 | public void moreThanOneRuleLoaded() { 56 | // Arrange 57 | logTester.setLevel(LoggerLevel.DEBUG); 58 | URL profileUrl = getClass().getResource(PROFILE_EXAMPLE_PATH); 59 | 60 | // Act 61 | List ruleKeys = new FSharpSonarWayProfile().importRuleKeysFromUrl(profileUrl); 62 | 63 | // Assert 64 | boolean moreThanOneRule = ruleKeys.size() > 0; 65 | assertTrue(moreThanOneRule, "Expecting more than one issue from the example file"); 66 | } 67 | 68 | @Test 69 | public void checkProfileWorks() { 70 | // Arrange 71 | logTester.setLevel(LoggerLevel.DEBUG); 72 | 73 | URL profileUrl = getClass().getResource(PROFILE_EXAMPLE_PATH); 74 | 75 | BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context(); 76 | 77 | // Act 78 | new FSharpSonarWayProfile().defineFromUrl(context, profileUrl); 79 | 80 | // Assert 81 | // just make sure it doesn't crash 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /sonar-fsharpsecurity-plugin/src/test/resources/profileExample.txt: -------------------------------------------------------------------------------- 1 | S5042 2 | S4834 3 | S4829 4 | S4823 5 | S4818 6 | S4792 7 | S4790 8 | S4787 9 | S4784 10 | S4507 11 | S3011 12 | S2255 13 | S2245 14 | S2092 15 | S2077 16 | S1313 17 | -------------------------------------------------------------------------------- /sonar-fsharpsecurity-plugin/src/test/resources/rulesExample.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | S5042 5 | Expanding archive files is security-sensitive 6 | Expanding archive files is security-sensitive. For example, expanding archive files has led in the past to the following vulnerabilities:

7 | 12 | 13 | ]]>
14 | CRITICAL 15 | VULNERABILITY 16 | 17 | 25 | cwe 26 | owasp-a1 27 | CONSTANT_ISSUE 28 | 10min 29 |
30 |
-------------------------------------------------------------------------------- /sonar-fsharpsecurity-plugin/src/test/resources/sonarDiagnosticsExample.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | S1313 6 | Make sure using this hardcoded IP address '192.168.0.1' is safe here. 7 | P:\git_repos\_mine\sonar-fsharp-security\SonarAnalyzer.FSharp\tests\SonarAnalyzer.FSharp.UnitTest\TestCases\S1313_HardcodedIpAddress.fs 8 | 18 9 | 14 10 | 18 11 | 27 12 | 13 | 14 | S1313 15 | Make sure using this hardcoded IP address '2001:db8:1234:ffff:ffff:ffff:ffff:ffff' is safe here. 16 | P:\git_repos\_mine\sonar-fsharp-security\SonarAnalyzer.FSharp\tests\SonarAnalyzer.FSharp.UnitTest\TestCases\S1313_HardcodedIpAddress.fs 17 | 26 18 | 14 19 | 26 20 | 54 21 | 22 | 23 | --------------------------------------------------------------------------------