diff --git a/Pipfile.lock b/Pipfile.lock index 7fa76826b9..f486329a09 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -202,54 +202,53 @@ }, "ruamel.yaml": { "hashes": [ - "sha256:2080c7a02b8a30fb3c06727cdf3e254a64055eedf3aa2d17c2b669639c04971b", - "sha256:5c56aa0bff2afceaa93bffbfc78b450b7dc1e01d5edb80b3a570695286ae62b1" + "sha256:801046a9caacb1b43acc118969b49b96b65e8847f29029563b29ac61d02db61b", + "sha256:b105e3e6fc15b41fdb201ba1b95162ae566a4ef792b9f884c46b4ccc5513a87a" ], "markers": "python_version >= '3'", - "version": "==0.17.33" + "version": "==0.17.35" }, "ruamel.yaml.clib": { "hashes": [ - "sha256:045e0626baf1c52e5527bd5db361bc83180faaba2ff586e763d3d5982a876a9e", - "sha256:15910ef4f3e537eea7fe45f8a5d19997479940d9196f357152a09031c5be59f3", - "sha256:184faeaec61dbaa3cace407cffc5819f7b977e75360e8d5ca19461cd851a5fc5", - "sha256:1a6391a7cabb7641c32517539ca42cf84b87b667bad38b78d4d42dd23e957c81", - "sha256:1f08fd5a2bea9c4180db71678e850b995d2a5f4537be0e94557668cf0f5f9497", - "sha256:2aa261c29a5545adfef9296b7e33941f46aa5bbd21164228e833412af4c9c75f", - "sha256:3110a99e0f94a4a3470ff67fc20d3f96c25b13d24c6980ff841e82bafe827cac", - "sha256:3243f48ecd450eddadc2d11b5feb08aca941b5cd98c9b1db14b2fd128be8c697", - "sha256:370445fd795706fd291ab00c9df38a0caed0f17a6fb46b0f607668ecb16ce763", - "sha256:40d030e2329ce5286d6b231b8726959ebbe0404c92f0a578c0e2482182e38282", - "sha256:41d0f1fa4c6830176eef5b276af04c89320ea616655d01327d5ce65e50575c94", - "sha256:4a4d8d417868d68b979076a9be6a38c676eca060785abaa6709c7b31593c35d1", - "sha256:4b3a93bb9bc662fc1f99c5c3ea8e623d8b23ad22f861eb6fce9377ac07ad6072", - "sha256:5bc0667c1eb8f83a3752b71b9c4ba55ef7c7058ae57022dd9b29065186a113d9", - "sha256:763d65baa3b952479c4e972669f679fe490eee058d5aa85da483ebae2009d231", - "sha256:7bdb4c06b063f6fd55e472e201317a3bb6cdeeee5d5a38512ea5c01e1acbdd93", - "sha256:8831a2cedcd0f0927f788c5bdf6567d9dc9cc235646a434986a852af1cb54b4b", - "sha256:91a789b4aa0097b78c93e3dc4b40040ba55bef518f84a40d4442f713b4094acb", - "sha256:92460ce908546ab69770b2e576e4f99fbb4ce6ab4b245345a3869a0a0410488f", - "sha256:99e77daab5d13a48a4054803d052ff40780278240a902b880dd37a51ba01a307", - "sha256:9c7617df90c1365638916b98cdd9be833d31d337dbcd722485597b43c4a215bf", - "sha256:a234a20ae07e8469da311e182e70ef6b199d0fbeb6c6cc2901204dd87fb867e8", - "sha256:a7b301ff08055d73223058b5c46c55638917f04d21577c95e00e0c4d79201a6b", - "sha256:be2a7ad8fd8f7442b24323d24ba0b56c51219513cfa45b9ada3b87b76c374d4b", - "sha256:bf9a6bc4a0221538b1a7de3ed7bca4c93c02346853f44e1cd764be0023cd3640", - "sha256:c3ca1fbba4ae962521e5eb66d72998b51f0f4d0f608d3c0347a48e1af262efa7", - "sha256:d000f258cf42fec2b1bbf2863c61d7b8918d31ffee905da62dede869254d3b8a", - "sha256:d5859983f26d8cd7bb5c287ef452e8aacc86501487634573d260968f753e1d71", - "sha256:d5e51e2901ec2366b79f16c2299a03e74ba4531ddcfacc1416639c557aef0ad8", - "sha256:da538167284de58a52109a9b89b8f6a53ff8437dd6dc26d33b57bf6699153122", - "sha256:debc87a9516b237d0466a711b18b6ebeb17ba9f391eb7f91c649c5c4ec5006c7", - "sha256:df5828871e6648db72d1c19b4bd24819b80a755c4541d3409f0f7acd0f335c80", - "sha256:ecdf1a604009bd35c674b9225a8fa609e0282d9b896c03dd441a91e5f53b534e", - "sha256:efa08d63ef03d079dcae1dfe334f6c8847ba8b645d08df286358b1f5293d24ab", - "sha256:f01da5790e95815eb5a8a138508c01c758e5f5bc0ce4286c4f7028b8dd7ac3d0", - "sha256:f34019dced51047d6f70cb9383b2ae2853b7fc4dce65129a5acd49f4f9256646", - "sha256:f6d3d39611ac2e4f62c3128a9eed45f19a6608670c5a2f4f07f24e8de3441d38" - ], - "markers": "python_version < '3.12' and platform_python_implementation == 'CPython'", - "version": "==0.2.7" + "sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001", + "sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462", + "sha256:1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615", + "sha256:25ac8c08322002b06fa1d49d1646181f0b2c72f5cbc15a85e80b4c30a544bb15", + "sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b", + "sha256:3f215c5daf6a9d7bbed4a0a4f760f3113b10e82ff4c5c44bec20a68c8014f675", + "sha256:3fcc54cb0c8b811ff66082de1680b4b14cf8a81dce0d4fbf665c2265a81e07a1", + "sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7", + "sha256:56f4252222c067b4ce51ae12cbac231bce32aee1d33fbfc9d17e5b8d6966c312", + "sha256:665f58bfd29b167039f714c6998178d27ccd83984084c286110ef26b230f259f", + "sha256:700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91", + "sha256:7048c338b6c86627afb27faecf418768acb6331fc24cfa56c93e8c9780f815fa", + "sha256:75e1ed13e1f9de23c5607fe6bd1aeaae21e523b32d83bb33918245361e9cc51b", + "sha256:7f67a1ee819dc4562d444bbafb135832b0b909f81cc90f7aa00260968c9ca1b3", + "sha256:84b554931e932c46f94ab306913ad7e11bba988104c5cff26d90d03f68258cd5", + "sha256:955eae71ac26c1ab35924203fda6220f84dce57d6d7884f189743e2abe3a9fbe", + "sha256:9eb5dee2772b0f704ca2e45b1713e4e5198c18f515b52743576d196348f374d3", + "sha256:a5aa27bad2bb83670b71683aae140a1f52b0857a2deff56ad3f6c13a017a26ed", + "sha256:a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337", + "sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248", + "sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d", + "sha256:b5edda50e5e9e15e54a6a8a0070302b00c518a9d32accc2346ad6c984aacd279", + "sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf", + "sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512", + "sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069", + "sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb", + "sha256:c58ecd827313af6864893e7af0a3bb85fd529f862b6adbefe14643947cfe2942", + "sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d", + "sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31", + "sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92", + "sha256:d92f81886165cb14d7b067ef37e142256f1c6a90a65cd156b063a43da1708cfd", + "sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5", + "sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1", + "sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2", + "sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875", + "sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412" + ], + "markers": "python_version < '3.13' and platform_python_implementation == 'CPython'", + "version": "==0.2.8" }, "semgrep": { "hashes": [ @@ -287,12 +286,12 @@ }, "urllib3": { "hashes": [ - "sha256:7a7c7003b000adf9e7ca2a377c9688bbc54ed41b985789ed576570342a375cd2", - "sha256:b19e1a85d206b56d7df1d5e683df4a7725252a964e3993648dd0fb5a1c157564" + "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84", + "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==2.0.6" + "version": "==2.0.7" } }, "develop": { @@ -642,54 +641,53 @@ }, "ruamel.yaml": { "hashes": [ - "sha256:2080c7a02b8a30fb3c06727cdf3e254a64055eedf3aa2d17c2b669639c04971b", - "sha256:5c56aa0bff2afceaa93bffbfc78b450b7dc1e01d5edb80b3a570695286ae62b1" + "sha256:801046a9caacb1b43acc118969b49b96b65e8847f29029563b29ac61d02db61b", + "sha256:b105e3e6fc15b41fdb201ba1b95162ae566a4ef792b9f884c46b4ccc5513a87a" ], "markers": "python_version >= '3'", - "version": "==0.17.33" + "version": "==0.17.35" }, "ruamel.yaml.clib": { "hashes": [ - "sha256:045e0626baf1c52e5527bd5db361bc83180faaba2ff586e763d3d5982a876a9e", - "sha256:15910ef4f3e537eea7fe45f8a5d19997479940d9196f357152a09031c5be59f3", - "sha256:184faeaec61dbaa3cace407cffc5819f7b977e75360e8d5ca19461cd851a5fc5", - "sha256:1a6391a7cabb7641c32517539ca42cf84b87b667bad38b78d4d42dd23e957c81", - "sha256:1f08fd5a2bea9c4180db71678e850b995d2a5f4537be0e94557668cf0f5f9497", - "sha256:2aa261c29a5545adfef9296b7e33941f46aa5bbd21164228e833412af4c9c75f", - "sha256:3110a99e0f94a4a3470ff67fc20d3f96c25b13d24c6980ff841e82bafe827cac", - "sha256:3243f48ecd450eddadc2d11b5feb08aca941b5cd98c9b1db14b2fd128be8c697", - "sha256:370445fd795706fd291ab00c9df38a0caed0f17a6fb46b0f607668ecb16ce763", - "sha256:40d030e2329ce5286d6b231b8726959ebbe0404c92f0a578c0e2482182e38282", - "sha256:41d0f1fa4c6830176eef5b276af04c89320ea616655d01327d5ce65e50575c94", - "sha256:4a4d8d417868d68b979076a9be6a38c676eca060785abaa6709c7b31593c35d1", - "sha256:4b3a93bb9bc662fc1f99c5c3ea8e623d8b23ad22f861eb6fce9377ac07ad6072", - "sha256:5bc0667c1eb8f83a3752b71b9c4ba55ef7c7058ae57022dd9b29065186a113d9", - "sha256:763d65baa3b952479c4e972669f679fe490eee058d5aa85da483ebae2009d231", - "sha256:7bdb4c06b063f6fd55e472e201317a3bb6cdeeee5d5a38512ea5c01e1acbdd93", - "sha256:8831a2cedcd0f0927f788c5bdf6567d9dc9cc235646a434986a852af1cb54b4b", - "sha256:91a789b4aa0097b78c93e3dc4b40040ba55bef518f84a40d4442f713b4094acb", - "sha256:92460ce908546ab69770b2e576e4f99fbb4ce6ab4b245345a3869a0a0410488f", - "sha256:99e77daab5d13a48a4054803d052ff40780278240a902b880dd37a51ba01a307", - "sha256:9c7617df90c1365638916b98cdd9be833d31d337dbcd722485597b43c4a215bf", - "sha256:a234a20ae07e8469da311e182e70ef6b199d0fbeb6c6cc2901204dd87fb867e8", - "sha256:a7b301ff08055d73223058b5c46c55638917f04d21577c95e00e0c4d79201a6b", - "sha256:be2a7ad8fd8f7442b24323d24ba0b56c51219513cfa45b9ada3b87b76c374d4b", - "sha256:bf9a6bc4a0221538b1a7de3ed7bca4c93c02346853f44e1cd764be0023cd3640", - "sha256:c3ca1fbba4ae962521e5eb66d72998b51f0f4d0f608d3c0347a48e1af262efa7", - "sha256:d000f258cf42fec2b1bbf2863c61d7b8918d31ffee905da62dede869254d3b8a", - "sha256:d5859983f26d8cd7bb5c287ef452e8aacc86501487634573d260968f753e1d71", - "sha256:d5e51e2901ec2366b79f16c2299a03e74ba4531ddcfacc1416639c557aef0ad8", - "sha256:da538167284de58a52109a9b89b8f6a53ff8437dd6dc26d33b57bf6699153122", - "sha256:debc87a9516b237d0466a711b18b6ebeb17ba9f391eb7f91c649c5c4ec5006c7", - "sha256:df5828871e6648db72d1c19b4bd24819b80a755c4541d3409f0f7acd0f335c80", - "sha256:ecdf1a604009bd35c674b9225a8fa609e0282d9b896c03dd441a91e5f53b534e", - "sha256:efa08d63ef03d079dcae1dfe334f6c8847ba8b645d08df286358b1f5293d24ab", - "sha256:f01da5790e95815eb5a8a138508c01c758e5f5bc0ce4286c4f7028b8dd7ac3d0", - "sha256:f34019dced51047d6f70cb9383b2ae2853b7fc4dce65129a5acd49f4f9256646", - "sha256:f6d3d39611ac2e4f62c3128a9eed45f19a6608670c5a2f4f07f24e8de3441d38" - ], - "markers": "python_version < '3.12' and platform_python_implementation == 'CPython'", - "version": "==0.2.7" + "sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001", + "sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462", + "sha256:1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615", + "sha256:25ac8c08322002b06fa1d49d1646181f0b2c72f5cbc15a85e80b4c30a544bb15", + "sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b", + "sha256:3f215c5daf6a9d7bbed4a0a4f760f3113b10e82ff4c5c44bec20a68c8014f675", + "sha256:3fcc54cb0c8b811ff66082de1680b4b14cf8a81dce0d4fbf665c2265a81e07a1", + "sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7", + "sha256:56f4252222c067b4ce51ae12cbac231bce32aee1d33fbfc9d17e5b8d6966c312", + "sha256:665f58bfd29b167039f714c6998178d27ccd83984084c286110ef26b230f259f", + "sha256:700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91", + "sha256:7048c338b6c86627afb27faecf418768acb6331fc24cfa56c93e8c9780f815fa", + "sha256:75e1ed13e1f9de23c5607fe6bd1aeaae21e523b32d83bb33918245361e9cc51b", + "sha256:7f67a1ee819dc4562d444bbafb135832b0b909f81cc90f7aa00260968c9ca1b3", + "sha256:84b554931e932c46f94ab306913ad7e11bba988104c5cff26d90d03f68258cd5", + "sha256:955eae71ac26c1ab35924203fda6220f84dce57d6d7884f189743e2abe3a9fbe", + "sha256:9eb5dee2772b0f704ca2e45b1713e4e5198c18f515b52743576d196348f374d3", + "sha256:a5aa27bad2bb83670b71683aae140a1f52b0857a2deff56ad3f6c13a017a26ed", + "sha256:a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337", + "sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248", + "sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d", + "sha256:b5edda50e5e9e15e54a6a8a0070302b00c518a9d32accc2346ad6c984aacd279", + "sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf", + "sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512", + "sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069", + "sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb", + "sha256:c58ecd827313af6864893e7af0a3bb85fd529f862b6adbefe14643947cfe2942", + "sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d", + "sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31", + "sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92", + "sha256:d92f81886165cb14d7b067ef37e142256f1c6a90a65cd156b063a43da1708cfd", + "sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5", + "sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1", + "sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2", + "sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875", + "sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412" + ], + "markers": "python_version < '3.13' and platform_python_implementation == 'CPython'", + "version": "==0.2.8" }, "semgrep": { "hashes": [ @@ -735,12 +733,12 @@ }, "urllib3": { "hashes": [ - "sha256:7a7c7003b000adf9e7ca2a377c9688bbc54ed41b985789ed576570342a375cd2", - "sha256:b19e1a85d206b56d7df1d5e683df4a7725252a964e3993648dd0fb5a1c157564" + "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84", + "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==2.0.6" + "version": "==2.0.7" } } } diff --git a/apex/lang/best-practice/ncino/accessModifiers/GlobalAccessModifiers.cls b/apex/lang/best-practice/ncino/accessModifiers/GlobalAccessModifiers.cls new file mode 100644 index 0000000000..e7ae63173f --- /dev/null +++ b/apex/lang/best-practice/ncino/accessModifiers/GlobalAccessModifiers.cls @@ -0,0 +1,49 @@ +/* + * global javadoc + * javadoc with global in it + * javadoc global + */ +// ruleid: global-access-modifiers +global without sharing class GlobalAccessModifiers { + // ruleid: global-access-modifiers + global String globalInstanceVariable = 'globalInstanceVariable'; + + // ok: global-access-modifiers + public String publicInstanceVariable = 'publicInstanceVariable'; + + // ok: global-access-modifiers + private String privateInstanceVariable = 'privateInstanceVariable'; + + // ok: global-access-modifiers + // This is a test comment that has the word global in it + // ruleid: global-access-modifiers + global static void myGlobalMethod() { } + + // ok: global-access-modifiers + public static void myPublicMethod() { } + + // ok: global-access-modifiers + private static void myPrivateMethod() { } + + // ok: global-access-modifiers + // This is another test comment with global + // ruleid: global-access-modifiers + global with sharing class TestGlobalClass { } + + // ok: global-access-modifiers + public with sharing class TestPublicClass { } + + // ok: global-access-modifiers + private without sharing class SystemMode { } + + // ok: global-access-modifiers + // Global test comment - last one + // ruleid: global-access-modifiers + global static String globalStaticVariable = 'globalStaticVariable'; + + // ok: global-access-modifiers + public static String publicStaticVariable = 'publicStaticVariable'; + + // ok: global-access-modifiers + private static String privateStaticVariable = 'privateStaticVariable'; +} diff --git a/apex/lang/best-practice/ncino/accessModifiers/GlobalAccessModifiers.yaml b/apex/lang/best-practice/ncino/accessModifiers/GlobalAccessModifiers.yaml new file mode 100644 index 0000000000..a08fae9c43 --- /dev/null +++ b/apex/lang/best-practice/ncino/accessModifiers/GlobalAccessModifiers.yaml @@ -0,0 +1,25 @@ +rules: + - id: global-access-modifiers + min-version: 1.44.0 + severity: WARNING + languages: + - apex + metadata: + cwe: + - 'CWE-284: Improper Access Control' + category: best-practice + technology: + - salesforce + references: + - https://cwe.mitre.org/data/definitions/284.html + message: >- + Global classes, methods, and variables should be avoided (especially in managed packages) as they can + never be deleted or changed in signature. Always check twice if something needs to be global. + patterns: + - pattern-regex: global [A-Za-z0-9_]{3,} + - pattern-not-regex: //(\s+([a-zA-Z]+\s+)+)[a-zA-Z]+ + - pattern-not-regex: '[*](\s+([a-zA-Z]+\s+)+)[a-zA-Z]+' + paths: + exclude: + - "*Test*" + - "*test*" diff --git a/apex/lang/best-practice/ncino/tests/UseAssertClass.cls b/apex/lang/best-practice/ncino/tests/UseAssertClass.cls new file mode 100644 index 0000000000..11cb3ea3a7 --- /dev/null +++ b/apex/lang/best-practice/ncino/tests/UseAssertClass.cls @@ -0,0 +1,13 @@ +public class UseAssertClass { + // ruleid: use-assert-class + System.assert(true); + + // ruleid: use-assert-class + System.assertEquals(1,1); + + // ruleid: use-assert-class + System.assertNotEquals(1,1); + + // ok: use-assert-class + Assert.areEqual(1,1); +} diff --git a/apex/lang/best-practice/ncino/tests/UseAssertClass.yaml b/apex/lang/best-practice/ncino/tests/UseAssertClass.yaml new file mode 100644 index 0000000000..e020153ac3 --- /dev/null +++ b/apex/lang/best-practice/ncino/tests/UseAssertClass.yaml @@ -0,0 +1,20 @@ +rules: + - id: use-assert-class + min-version: 1.44.0 + severity: WARNING + languages: + - generic + metadata: + category: best-practice + references: + - https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_class_System_Assert.htm + technology: + - salesforce + message: >- + Assert methods in the System class have been replaced with the Assert class: + https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_class_System_Assert.htm + pattern-regex: System\.assert + paths: + include: + - "*.cls" + - "UseAssertClass.cls" diff --git a/apex/lang/best-practice/ncino/urls/AbsoluteUrls.cls b/apex/lang/best-practice/ncino/urls/AbsoluteUrls.cls new file mode 100644 index 0000000000..9341825b2b --- /dev/null +++ b/apex/lang/best-practice/ncino/urls/AbsoluteUrls.cls @@ -0,0 +1,51 @@ +/* + * Test Controller with Absolute URLs + */ +public with sharing class AbsoluteUrls { + + public PageReference absoluteSalesforceUrlExample() { + // ruleid: absolute-urls + String strUrl = 'https://na8.salesforce.com/TestVFPage?AccountId=999'; + PageReference newUrl = new PageReference(strUrl); + newURL.setRedirect(true); + + return newURL; + } + + public PageReference absoluteNonSalesforceUrlExample() { + // ok: absolute-urls + String strUrl = 'https://www.website.com'; + PageReference newUrl = new PageReference(strUrl); + newURL.setRedirect(true); + + return newURL; + } + + public PageReference nonAbsoluteSalesforceUrlExample() { + // ok: absolute-urls + String strUrl = URL.getSalesforceBaseUrl().toExternalForm() + '/TestVFPage?AccountId=999'; + PageReference newUrl = new PageReference(strUrl); + newURL.setRedirect(true); + + return newURL; + } + + // Absolute Salesforce URL comment example + // ruleid: absolute-urls + // https://na8.salesforce.com/TestVFPage?AccountId=999 + + // Absolute non-Salesforce URL comment example + // ok: absolute-urls + // https://www.website.com + + // Non-absolute Salesforce URL comment example + // ok: absolute-urls + // URL.getSalesforceBaseUrl().toExternalForm() + '/TestVFPage?AccountId=999 + + // ruleid: absolute-urls + private static final String ABSOLUTE_SF_URL_CONSTANT = 'https://na8.salesforce.com/TestVFPage?AccountId=999'; + // ok: absolute-urls + private static final String ABSOLUTE_NON_SF_URL_CONSTANT = 'https://www.website.com'; + // ok: absolute-urls + private static final String NON_ASBOLUTE_SF_URL_CONSTANT = URL.getOrgDomainUrl().toExternalForm() + '/TestVFPage?AccountId=999'; +} diff --git a/apex/lang/best-practice/ncino/urls/AbsoluteUrls.yaml b/apex/lang/best-practice/ncino/urls/AbsoluteUrls.yaml new file mode 100644 index 0000000000..2df4bb46f1 --- /dev/null +++ b/apex/lang/best-practice/ncino/urls/AbsoluteUrls.yaml @@ -0,0 +1,23 @@ +rules: + - id: absolute-urls + min-version: 1.44.0 + severity: WARNING + languages: + - apex + metadata: + category: best-practice + references: + - '' + technology: + - salesforce + message: >- + Using absolute URLs to Salesforce Pages is bug prone. Different sandboxes and production + environments will have different instance names (like "na10", "na15" etc.). Code using + absolute URLs will only work when it runs in the corresponding salesforce instances. It + will break as soon as it is deployed in another one. Thus only relative URLs, i.e. without + the domain and subdomain names, should be used when pointing to a salesforce page. + pattern-regex: (http|https)://.*(salesforce|force|visualforce)\.com\.* + paths: + exclude: + - "*Test*" + - "*test*" diff --git a/apex/lang/performance/ncino/operationsInLoops/AvoidNativeDmlInLoops.cls b/apex/lang/performance/ncino/operationsInLoops/AvoidNativeDmlInLoops.cls new file mode 100644 index 0000000000..218d567234 --- /dev/null +++ b/apex/lang/performance/ncino/operationsInLoops/AvoidNativeDmlInLoops.cls @@ -0,0 +1,146 @@ +public class AvoidNativeDmlInLoops { + public void insertInsideLoop() { + for (Integer i = 0; i < 151; i++) { + // ruleid: avoid-native-dml-in-loops + insert account; + // ruleid: avoid-native-dml-in-loops + Database.insert(a); + } + } + + public void insertInsideLoop2() { + for (Account a : accounts) { + // ruleid: avoid-native-dml-in-loops + insert account; + // ruleid: avoid-native-dml-in-loops + Database.insert(a); + } + } + + public void insertInsideLoop3() { + while (someCondition) { + // ruleid: avoid-native-dml-in-loops + insert account; + // ruleid: avoid-native-dml-in-loops + Database.insert(a); + } + } + + public void insertInsideLoop4() { + do { + // ruleid: avoid-native-dml-in-loops + insert account; + // ruleid: avoid-native-dml-in-loops + Database.insert(a); + } while (someCondition); + } + + public void updateInsideLoop() { + for (Integer i = 0; i < 151; i++) { + // ruleid: avoid-native-dml-in-loops + update account; + // ruleid: avoid-native-dml-in-loops + Database.update(a); + } + } + + public void updateInsideLoop2() { + for (Account a : accounts) { + // ruleid: avoid-native-dml-in-loops + update account; + // ruleid: avoid-native-dml-in-loops + Database.update(a); + } + } + + public void updateInsideLoop3() { + while (someCondition) { + // ruleid: avoid-native-dml-in-loops + update account; + // ruleid: avoid-native-dml-in-loops + Database.update(a); + } + } + + public void updateInsideLoop4() { + do { + // ruleid: avoid-native-dml-in-loops + update account; + // ruleid: avoid-native-dml-in-loops + Database.update(a); + } while (someCondition); + } + + public void upsertInsideLoop() { + for (Integer i = 0; i < 151; i++) { + // ruleid: avoid-native-dml-in-loops + upsert account; + // ruleid: avoid-native-dml-in-loops + Database.upsert(a); + } + } + + public void upsertInsideLoop2() { + for (Account a : accounts) { + // ruleid: avoid-native-dml-in-loops + upsert account; + // ruleid: avoid-native-dml-in-loops + Database.upsert(a); + } + } + + public void upsertInsideLoop3() { + while (someCondition) { + // ruleid: avoid-native-dml-in-loops + upsert account; + // ruleid: avoid-native-dml-in-loops + Database.upsert(a); + } + } + + public void upsertInsideLoop4() { + do { + // ruleid: avoid-native-dml-in-loops + upsert account; + // ruleid: avoid-native-dml-in-loops + Database.upsert(a); + } while (someCondition); + } + + public void deleteInsideLoop() { + for (Integer i = 0; i < 151; i++) { + // ruleid: avoid-native-dml-in-loops + delete account; + // ruleid: avoid-native-dml-in-loops + Database.delete(a); + } + } + + public void deleteInsideLoop2() { + for (Account a : accounts) { + // ruleid: avoid-native-dml-in-loops + delete account; + // ruleid: avoid-native-dml-in-loops + Database.delete(a); + } + } + + public void deleteInsideLoop3() { + while (someCondition) { + // ruleid: avoid-native-dml-in-loops + delete account; + // ruleid: avoid-native-dml-in-loops + Database.delete(a); + } + } + + public void deleteInsideLoop4() { + do { + // ruleid: avoid-native-dml-in-loops + delete account; + // ruleid: avoid-native-dml-in-loops + Database.delete(a); + } while (someCondition); + } + +} diff --git a/apex/lang/performance/ncino/operationsInLoops/AvoidNativeDmlInLoops.yaml b/apex/lang/performance/ncino/operationsInLoops/AvoidNativeDmlInLoops.yaml new file mode 100644 index 0000000000..12026ac661 --- /dev/null +++ b/apex/lang/performance/ncino/operationsInLoops/AvoidNativeDmlInLoops.yaml @@ -0,0 +1,47 @@ +rules: + - id: avoid-native-dml-in-loops + min-version: 1.44.0 + severity: ERROR + languages: + - generic + metadata: + category: performance + references: + - https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_gov_limits.htm + technology: + - salesforce + message: >- + Avoid DML statements inside loops to avoid hitting the DML governor limit. + Instead, try to batch up the data into a list and invoke your DML once on + that list of data outside the loop. + patterns: + - pattern-either: + - pattern-inside: | + for (...) { + ... + } + - pattern-inside: | + while (...) { + ... + } + - pattern-inside: | + do { + ... + } while (...); + - pattern-either: + - pattern: | + insert $DATA; + - pattern: | + update $DATA; + - pattern: | + upsert $DATA; + - pattern: | + delete $DATA; + - pattern: | + Database.insert($DATA); + - pattern: | + Database.update($DATA); + - pattern: | + Database.upsert($DATA); + - pattern: | + Database.delete($DATA); diff --git a/apex/lang/performance/ncino/operationsInLoops/AvoidOperationsWithLimitsInLoops.cls b/apex/lang/performance/ncino/operationsInLoops/AvoidOperationsWithLimitsInLoops.cls new file mode 100644 index 0000000000..fa32ca309a --- /dev/null +++ b/apex/lang/performance/ncino/operationsInLoops/AvoidOperationsWithLimitsInLoops.cls @@ -0,0 +1,121 @@ +public class AvoidOperationsWithLimitsInLoops { + public void messageInsideOfLoop() { + for (Integer i = 0; i < 151; i++) { + Messaging.SingleEmailMessage email = new Messaging.SingleEmailMessage(); + // ruleid: avoid-operations-with-limits-in-loops + Messaging.sendEmail(new Messaging.SingleEmailMessage[]{email}); + } + } + + public void messageInsideOfLoop2() { + for (Account a : accounts) { + Messaging.SingleEmailMessage email = new Messaging.SingleEmailMessage(); + // ruleid: avoid-operations-with-limits-in-loops + Messaging.sendEmail(new Messaging.SingleEmailMessage[]{email}); + } + } + + public void messageInsideOfLoop3() { + while (someCondition) { + Messaging.SingleEmailMessage email = new Messaging.SingleEmailMessage(); + // ruleid: avoid-operations-with-limits-in-loops + Messaging.sendEmail(new Messaging.SingleEmailMessage[]{email}); + } + } + + public void messageInsideOfLoop4() { + do { + Messaging.SingleEmailMessage email = new Messaging.SingleEmailMessage(); + // ruleid: avoid-operations-with-limits-in-loops + Messaging.sendEmail(new Messaging.SingleEmailMessage[]{email}); + } while (someCondition); + } + + public void approvalInsideOfLoop() { + for (Integer i = 0; i < 151; i++) { + // ruleid: avoid-operations-with-limits-in-loops + Approval.ProcessSubmitRequest req = new Approval.ProcessSubmitRequest(); + req.setObjectId(acc.Id); + Approval.process(req); + Approval.lock(acc); + Approval.unlock(acc); + } + } + + public void approvalInsideOfLoop2() { + for (Account a : accounts) { + // ruleid: avoid-operations-with-limits-in-loops + Approval.ProcessSubmitRequest req = new Approval.ProcessSubmitRequest(); + req.setObjectId(acc.Id); + Approval.process(req); + Approval.lock(acc); + Approval.unlock(acc); + } + } + + public void approvalInsideOfLoop3() { + while (someCondition) { + // ruleid: avoid-operations-with-limits-in-loops + Approval.ProcessSubmitRequest req = new Approval.ProcessSubmitRequest(); + req.setObjectId(acc.Id); + Approval.process(req); + Approval.lock(acc); + Approval.unlock(acc); + } + } + + public void approvalInsideOfLoop4() { + do { + // ruleid: avoid-operations-with-limits-in-loops + Approval.ProcessSubmitRequest req = new Approval.ProcessSubmitRequest(); + req.setObjectId(acc.Id); + Approval.process(req); + Approval.lock(acc); + Approval.unlock(acc); + } while (someCondition); + } + + public void asyncInsideOfLoop() { + for (Integer i = 0; i < 151; i++) { + // ruleid: avoid-operations-with-limits-in-loops + System.enqueueJob(new MyQueueable()); + // ruleid: avoid-operations-with-limits-in-loops + System.schedule('x', '0 0 0 1 1 ?', new MySchedule()); + // ruleid: avoid-operations-with-limits-in-loops + System.scheduleBatch(new MyBatch(), 'x', 1); + } + } + + public void asyncInsideOfLoop2() { + for (Account a : accounts) { + // ruleid: avoid-operations-with-limits-in-loops + System.enqueueJob(new MyQueueable()); + // ruleid: avoid-operations-with-limits-in-loops + System.schedule('x', '0 0 0 1 1 ?', new MySchedule()); + // ruleid: avoid-operations-with-limits-in-loops + System.scheduleBatch(new MyBatch(), 'x', 1); + } + } + + public void asyncInsideOfLoop3() { + while (someCondition) { + // ruleid: avoid-operations-with-limits-in-loops + System.enqueueJob(new MyQueueable()); + // ruleid: avoid-operations-with-limits-in-loops + System.schedule('x', '0 0 0 1 1 ?', new MySchedule()); + // ruleid: avoid-operations-with-limits-in-loops + System.scheduleBatch(new MyBatch(), 'x', 1); + } + } + + public void asyncInsideOfLoop4() { + do { + // ruleid: avoid-operations-with-limits-in-loops + System.enqueueJob(new MyQueueable()); + // ruleid: avoid-operations-with-limits-in-loops + System.schedule('x', '0 0 0 1 1 ?', new MySchedule()); + // ruleid: avoid-operations-with-limits-in-loops + System.scheduleBatch(new MyBatch(), 'x', 1); + } while (someCondition); + } +} diff --git a/apex/lang/performance/ncino/operationsInLoops/AvoidOperationsWithLimitsInLoops.yaml b/apex/lang/performance/ncino/operationsInLoops/AvoidOperationsWithLimitsInLoops.yaml new file mode 100644 index 0000000000..6c919a60d9 --- /dev/null +++ b/apex/lang/performance/ncino/operationsInLoops/AvoidOperationsWithLimitsInLoops.yaml @@ -0,0 +1,43 @@ +rules: + - id: avoid-operations-with-limits-in-loops + min-version: 1.44.0 + severity: ERROR + languages: + - generic + metadata: + category: performance + references: + - https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_gov_limits.htm + technology: + - salesforce + message: >- + Database class methods, DML operations, SOQL queries, SOSL queries, + Approval class methods, Email sending, async scheduling or queueing + within loops can cause governor limit exceptions. Instead, try to + batch up the data into a list and invoke the operation once on that + list of data outside the loop. + patterns: + - pattern-either: + - pattern-inside: | + for (...) { + ... + } + - pattern-inside: | + while (...) { + ... + } + - pattern-inside: | + do { + ... + } while (...); + - pattern-either: + - pattern: | + Messaging.sendEmail(...); + - pattern: | + Approval.ProcessSubmitRequest $REQUEST = new Approval.ProcessSubmitRequest(); + - pattern: | + System.enqueueJob(...); + - pattern: | + System.schedule(...); + - pattern: | + System.scheduleBatch(...); diff --git a/apex/lang/performance/ncino/operationsInLoops/AvoidSoqlInLoops.cls b/apex/lang/performance/ncino/operationsInLoops/AvoidSoqlInLoops.cls new file mode 100644 index 0000000000..42c06541c6 --- /dev/null +++ b/apex/lang/performance/ncino/operationsInLoops/AvoidSoqlInLoops.cls @@ -0,0 +1,37 @@ +public class AvoidSoqlInLoops { + public void SoqlInsideLoop() { + for (Integer i = 0; i < 151; i++) { + // ruleid: avoid-soql-in-loops + List accounts = [SELECT Id FROM Account]; + // ruleid: avoid-soql-in-loops + Account[] accounts = [SELECT Id FROM Account]; + } + } + + public void SoqlInsideLoop2() { + for (Account a : accounts) { + // ruleid: avoid-soql-in-loops + List accounts = [SELECT Id FROM Account]; + // ruleid: avoid-soql-in-loops + Account[] accounts = [SELECT Id FROM Account]; + } + } + + public void SoqlInsideLoop5() { + while (someCondition) { + // ruleid: avoid-soql-in-loops + List accounts = [SELECT Id FROM Account]; + // ruleid: avoid-soql-in-loops + Account[] accounts = [SELECT Id FROM Account]; + } + } + + public void SoqlInsideLoop7() { + do { + // ruleid: avoid-soql-in-loops + List accounts = [SELECT Id FROM Account]; + // ruleid: avoid-soql-in-loops + Account[] accounts = [SELECT Id FROM Account]; + } while (someCondition); + } +} diff --git a/apex/lang/performance/ncino/operationsInLoops/AvoidSoqlInLoops.yaml b/apex/lang/performance/ncino/operationsInLoops/AvoidSoqlInLoops.yaml new file mode 100644 index 0000000000..2768916a08 --- /dev/null +++ b/apex/lang/performance/ncino/operationsInLoops/AvoidSoqlInLoops.yaml @@ -0,0 +1,34 @@ +rules: + - id: avoid-soql-in-loops + min-version: 1.44.0 + severity: ERROR + languages: + - generic + metadata: + category: performance + references: + - https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_gov_limits.htm + technology: + - salesforce + message: >- + Database class methods, DML operations, SOQL queries, SOSL queries, + Approval class methods, Email sending, async scheduling or queueing + within loops can cause governor limit exceptions. Instead, try to + batch up the data into a list and invoke the operation once on that + list of data outside the loop. + patterns: + - pattern-either: + - pattern-inside: | + for (...) { + ... + } + - pattern-inside: | + while (...) { + ... + } + - pattern-inside: | + do { + ... + } while (...); + - pattern: | + $OBJECTS = [...SELECT...FROM...]; diff --git a/apex/lang/performance/ncino/operationsInLoops/AvoidSoslInLoops.cls b/apex/lang/performance/ncino/operationsInLoops/AvoidSoslInLoops.cls new file mode 100644 index 0000000000..ed28bfca2d --- /dev/null +++ b/apex/lang/performance/ncino/operationsInLoops/AvoidSoslInLoops.cls @@ -0,0 +1,45 @@ +public class AvoidSoslLoops { + public void SoslInsideLoop() { + for (Integer i = 0; i < 151; i++) { + // ruleid: avoid-sosl-in-loops + List objects = (List) Search.query( + 'soslString' + )[0]; + // ruleid: avoid-sosl-in-loops + List> searchList = [FIND 'map*' IN ALL FIELDS RETURNING Account (Id, Name), Contact, Opportunity, Lead]; + } + } + + public void SoslInsideLoop2() { + for (Account a : accounts) { + // ruleid: avoid-sosl-in-loops + Object[] objects = (Object[]) Search.query( + 'soslString' + )[0]; + // ruleid: avoid-sosl-in-loops + List> searchList = [FIND 'map*' IN ALL FIELDS RETURNING Account (Id, Name), Contact, Opportunity, Lead]; + } + } + + public void SoslInsideLoop3() { + while (someCondition) { + // ruleid: avoid-sosl-in-loops + List objects = (List) Search.query( + 'soslString' + )[0]; + // ruleid: avoid-sosl-in-loops + List> searchList = [FIND 'map*' IN ALL FIELDS RETURNING Account (Id, Name), Contact, Opportunity, Lead]; + } + } + + public void SoslInsideLoop4() { + do { + // ruleid: avoid-sosl-in-loops + Object[] objects = (Object[]) Search.query( + 'soslString' + )[0]; + // ruleid: avoid-sosl-in-loops + List> searchList = [FIND 'map*' IN ALL FIELDS RETURNING Account (Id, Name), Contact, Opportunity, Lead]; + } while (someCondition); + } +} diff --git a/apex/lang/performance/ncino/operationsInLoops/AvoidSoslInLoops.yaml b/apex/lang/performance/ncino/operationsInLoops/AvoidSoslInLoops.yaml new file mode 100644 index 0000000000..ad588bd035 --- /dev/null +++ b/apex/lang/performance/ncino/operationsInLoops/AvoidSoslInLoops.yaml @@ -0,0 +1,37 @@ +rules: + - id: avoid-sosl-in-loops + min-version: 1.44.0 + severity: ERROR + languages: + - generic + metadata: + category: performance + references: + - https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_gov_limits.htm + technology: + - salesforce + message: >- + Database class methods, DML operations, SOQL queries, SOSL queries, + Approval class methods, Email sending, async scheduling or queueing + within loops can cause governor limit exceptions. Instead, try to + batch up the data into a list and invoke the operation once on that + list of data outside the loop. + patterns: + - pattern-either: + - pattern-inside: | + for (...) { + ... + } + - pattern-inside: | + while (...) { + ... + } + - pattern-inside: | + do { + ... + } while (...); + - pattern-either: + - pattern: | + $OBJECTS = ... Search.query(...) + - pattern: | + $OBJECTS = ... [FIND...IN ALL FIELDS RETURNING...] diff --git a/apex/lang/security/ncino/dml/ApexCSRFConstructor.cls b/apex/lang/security/ncino/dml/ApexCSRFConstructor.cls new file mode 100644 index 0000000000..31fb9c981a --- /dev/null +++ b/apex/lang/security/ncino/dml/ApexCSRFConstructor.cls @@ -0,0 +1,55 @@ +/* + * Test Controller with DML in constuctors + */ +public class ApexCSRFConstructor { + + public ApexCSRFConstructor() { + // ruleid: apex-csrf-constructor + insert data; + } + + private ApexCSRFConstructor() { + // ruleid: apex-csrf-constructor + insert data; + } + + public ApexCSRFConstructor() { + // ruleid: apex-csrf-constructor + update data; + } + + private ApexCSRFConstructor() { + // ruleid: apex-csrf-constructor + update data; + } + + public ApexCSRFConstructor() { + // ruleid: apex-csrf-constructor + upsert data; + } + + private ApexCSRFConstructor() { + // ruleid: apex-csrf-constructor + upsert data; + } + + public ApexCSRFConstructor() { + // ruleid: apex-csrf-constructor + delete data; + } + + private ApexCSRFConstructor() { + // ruleid: apex-csrf-constructor + delete data; + } + + public ApexCSRFConstructor() { + // ok: apex-csrf-constructor + notADmlCall(); + } + + private ApexCSRFConstructor() { + // ok: apex-csrf-constructor + notADmlCall(); + } +} diff --git a/apex/lang/security/ncino/dml/ApexCSRFConstructor.yaml b/apex/lang/security/ncino/dml/ApexCSRFConstructor.yaml new file mode 100644 index 0000000000..eb89e2d86e --- /dev/null +++ b/apex/lang/security/ncino/dml/ApexCSRFConstructor.yaml @@ -0,0 +1,51 @@ +rules: + - id: apex-csrf-constructor + min-version: 1.44.0 + severity: ERROR + languages: + - apex + metadata: + cwe: + - 'CWE-352: Cross-Site Request Forgery (CSRF)' + owasp: + - A01:2021 - Broken Access Control + cwe2020-top25': true + cwe2021-top25': true + cwe2022-top25': true + impact: HIGH + likelihood: MEDIUM + confidence: HIGH + category: security + subcategory: + - vuln + technology: + - salesforce + references: + - https://cwe.mitre.org/data/definitions/352.html + message: >- + Having DML operations in Apex class constructor or initializers can + have unexpected side effects: By just accessing a page, the DML statements + would be executed and the database would be modified. Just querying the + database is permitted. + patterns: + - pattern-either: + - pattern-inside: public class $CLASSNAME {...} + - pattern-inside: private class $CLASSNAME {...} + - pattern-inside: public $SOME sharing class $CLASSNAME {...} + - pattern-inside: private $SOME sharing class $CLASSNAME {...} + - pattern-either: + - pattern-inside: public $CLASSNAME() {...} + - pattern-inside: private $CLASSNAME() {...} + - pattern-either: + - pattern: | + insert $DATA; + - pattern: | + update $DATA; + - pattern: | + upsert $DATA; + - pattern: | + delete $DATA; + paths: + exclude: + - "*Test*" + - "*test*" diff --git a/apex/lang/security/ncino/dml/ApexCSRFStaticConstructor.cls b/apex/lang/security/ncino/dml/ApexCSRFStaticConstructor.cls new file mode 100644 index 0000000000..5ca09e5f83 --- /dev/null +++ b/apex/lang/security/ncino/dml/ApexCSRFStaticConstructor.cls @@ -0,0 +1,30 @@ +/* + * Test Controller with DML in constuctors + */ +public class ApexCSRFStaticConstructor { + + static { + // ruleid: apex-csrf-static-constructor + insert data; + } + + static { + // ruleid: apex-csrf-static-constructor + update data; + } + + static { + // ruleid: apex-csrf-static-constructor + upsert data; + } + + static { + // ruleid: apex-csrf-static-constructor + delete data; + } + + static { + // ok: apex-csrf-static-constructor + notADmlCall(); + } +} diff --git a/apex/lang/security/ncino/dml/ApexCSRFStaticConstructor.yaml b/apex/lang/security/ncino/dml/ApexCSRFStaticConstructor.yaml new file mode 100644 index 0000000000..e4706a9434 --- /dev/null +++ b/apex/lang/security/ncino/dml/ApexCSRFStaticConstructor.yaml @@ -0,0 +1,44 @@ +rules: + - id: apex-csrf-static-constructor + min-version: 1.44.0 + severity: ERROR + languages: + - generic + metadata: + cwe: + - 'CWE-352: Cross-Site Request Forgery (CSRF)' + owasp: + - A01:2021 - Broken Access Control + cwe2020-top25': true + cwe2021-top25': true + cwe2022-top25': true + impact: HIGH + likelihood: MEDIUM + confidence: HIGH + category: security + subcategory: + - vuln + technology: + - salesforce + references: + - https://cwe.mitre.org/data/definitions/352.html + message: >- + Having DML operations in Apex class constructor or initializers can + have unexpected side effects: By just accessing a page, the DML statements + would be executed and the database would be modified. Just querying the + database is permitted. + patterns: + - pattern-inside: static {...} + - pattern-either: + - pattern: | + insert $DATA; + - pattern: | + update $DATA; + - pattern: | + upsert $DATA; + - pattern: | + delete $DATA; + paths: + exclude: + - "*Test*" + - "*test*" diff --git a/apex/lang/security/ncino/dml/DmlNativeStatements.cls b/apex/lang/security/ncino/dml/DmlNativeStatements.cls new file mode 100644 index 0000000000..46afacfac1 --- /dev/null +++ b/apex/lang/security/ncino/dml/DmlNativeStatements.cls @@ -0,0 +1,205 @@ +public class DMLNativeStatements { + public void dmlInsert() { + Account act = new Account( + // ok: dml-native-statements + Name = 'insert' + ); + // ruleid: dml-native-statements + insert act; + } + + public void databaseInsert() { + Account act = new Account( + // ok: dml-native-statements + Name = 'insert' + ); + // ruleid: dml-native-statements + Database.insert(act); + } + + public void utilityInsert() { + Account act = new Account( + // ok: dml-native-statements + Name = 'insert' + ); + // ok: dml-native-statements + nFORCE.DMLUtility.insertObj(act); + } + + public void dmlUpsert() { + Account[] acctsList = [ + SELECT + Id, + Name, + BillingCity + FROM + Account + WHERE + BillingCity = 'Bombay' + ]; + + for (Account a : acctsList) { + a.BillingCity = 'Mumbai'; + } + + // ok: dml-native-statements + Account newAcct = new Account(Name = 'upsert', BillingCity = 'San Francisco'); + acctsList.add(newAcct); + + // ruleid: dml-native-statements + upsert acctsList; + } + + public void databaseUpsert() { + Account[] acctsList = [ + SELECT + Id, + Name, + BillingCity + FROM + Account + WHERE + BillingCity = 'Bombay' + ]; + + for (Account a : acctsList) { + a.BillingCity = 'Mumbai'; + } + + // ok: dml-native-statements + Account newAcct = new Account(Name = 'upsert', BillingCity = 'San Francisco'); + acctsList.add(newAcct); + + // ruleid: dml-native-statements + Database.upsert(acctsList); + } + + public void utilityUpsert() { + Account[] acctsList = [ + SELECT + Id, + Name, + BillingCity + FROM + Account + WHERE + BillingCity = 'Bombay' + ]; + + for (Account a : acctsList) { + a.BillingCity = 'Mumbai'; + } + + // ok: dml-native-statements + Account newAcct = new Account(Name = 'upsert', BillingCity = 'San Francisco'); + acctsList.add(newAcct); + + // ok: dml-native-statements + nFORCE.DMLUtility.upsertObjs(acctsList); + } + + public void dmlUpdate() { + Account updtAct = [ + SELECT + BillingCity + FROM + Account + WHERE + // ok: dml-native-statements + Name='update' + LIMIT 1 + ]; + + // ok: dml-native-statements + updtAct.BillingCity = 'update'; + + // ruleid: dml-native-statements + update updtAct; + } + + public void databaseUpdate() { + Account updtAct = [ + SELECT + BillingCity + FROM + Account + WHERE + // ok: dml-native-statements + Name='update' + LIMIT 1 + ]; + + // ok: dml-native-statements + updtAct.BillingCity = 'update'; + + // ruleid: dml-native-statements + Database.update(updtAct); + } + + public void utilityUpdate() { + Account updtAct = [ + SELECT + BillingCity + FROM + Account + WHERE + // ok: dml-native-statements + Name='update' + LIMIT 1 + ]; + + // ok: dml-native-statements + updtAct.BillingCity = 'update'; + + // ok: dml-native-statements + nFORCE.DMLUtility.updateObj(updtAct); + } + + public void dmlDelete() { + Account dltAct = [ + SELECT + Id + FROM + Account + WHERE + // ok: dml-native-statements + Name='delete' + LIMIT 1 + ]; + + // ruleid: dml-native-statements + delete dltAct; + } + + public void databaseDelete() { + Account dltAct = [ + SELECT + Id + FROM + Account + WHERE + // ok: dml-native-statements + Name='delete' + LIMIT 1 + ]; + + // ruleid: dml-native-statements + Database.delete(dltAct); + } + + public void utilityDelete() { + Account dltAct = [ + SELECT + Id + FROM + Account + WHERE + // ok: dml-native-statements + Name='delete' + LIMIT 1 + ]; + + // ok: dml-native-statements + nFORCE.DMLUtility.deleteObj(dltAct); + } +} diff --git a/apex/lang/security/ncino/dml/DmlNativeStatements.yaml b/apex/lang/security/ncino/dml/DmlNativeStatements.yaml new file mode 100644 index 0000000000..3c892d3bd6 --- /dev/null +++ b/apex/lang/security/ncino/dml/DmlNativeStatements.yaml @@ -0,0 +1,35 @@ +rules: + - id: dml-native-statements + min-version: 1.44.0 + severity: WARNING + languages: + - apex + metadata: + cwe: + - 'CWE-863: Incorrect Authorization' + owasp: + - A01:2021 - Broken Access Control + - A04:2021 - Insecure Design + impact: HIGH + likelihood: LOW + confidence: LOW + category: security + subcategory: + - audit + technology: + - salesforce + references: + - https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_dml_section.htm + - https://cwe.mitre.org/data/definitions/863.html + - https://owasp.org/Top10/A04_2021-Insecure_Design/ + message: >- + Native Salesforce DML operations execute in system context, ignoring the current user's permissions, + field-level security, organization-wide defaults, position in the role hierarchy, and sharing rules. + Be mindful when using native Salesforce DML operations. + patterns: + - pattern-either: + - pattern-regex: '(insert|upsert|update|delete)[\s]' + - pattern-regex: '(insert|upsert|update|delete)[(]' + - pattern-not-regex: '[\/\/].*(insert|upsert|update|delete).*' + - pattern-not-regex: '[\/\/].*(insert|upsert|update|delete)[\n]' + - pattern-not-regex: '.*[=].*(insert|upsert|update|delete).*[,;]' diff --git a/apex/lang/security/ncino/encryption/BadCrypto.cls b/apex/lang/security/ncino/encryption/BadCrypto.cls new file mode 100644 index 0000000000..5c3c94b7ed --- /dev/null +++ b/apex/lang/security/ncino/encryption/BadCrypto.cls @@ -0,0 +1,77 @@ +/* +* Test controller with bad Crypto class usage +*/ +public with sharing class BadCrypto { + + public void badCryptoEncryption() { + // ruleid: bad-crypto + Blob hardCodedIV = Blob.valueOf('Hardcoded IV 123'); + // ruleid: bad-crypto + Blob hardCodedKey = Blob.valueOf('0000000000000000'); + Blob data = Blob.valueOf('Data to be encrypted'); + Blob encrypted = Crypto.encrypt('AES128', hardCodedKey, hardCodedIV, data); + } + + public void badCryptoEncryptionHarcodedIvOnly() { + // ruleid: bad-crypto + Blob hardCodedIV = Blob.valueOf('Hardcoded IV 123'); + // ok: bad-crypto + Blob key = Blob.valueOf(generateEncryptionKey()); + Blob data = Blob.valueOf('Data to be encrypted'); + Blob encrypted = Crypto.encrypt('AES128', key, hardCodedIV, data); + } + + public void badCryptoEncryptionHarcodedKeyOnly() { + // ok: bad-crypto + Blob IV = Blob.valueOf(generateEncryptionIV()); + // ruleid: bad-crypto + Blob hardCodedKey = Blob.valueOf('0000000000000000'); + Blob data = Blob.valueOf('Data to be encrypted'); + Blob encrypted = Crypto.encrypt('AES128', hardCodedKey, IV, data); + } + + public void goodCryptoEncryption() { + // ok: bad-crypto + Blob IV = Blob.valueOf(getRandomValue()); + // ok: bad-crypto + Blob key = Blob.valueOf(getRandomValue()); + Blob data = Blob.valueOf('Data to be encrypted'); + Blob encrypted = Crypto.encrypt('AES128', key, IV, data); + } + + public void badCryptoDecryption() { + Blob encryptedCipherText = Blob.valueOf('Some encrypted cipher text'); + // ruleid: bad-crypto + Blob hardCodedIV = Blob.valueOf('Hardcoded IV 123'); + // ruleid: bad-crypto + Blob hardCodedKey = Blob.valueOf('0000000000000000'); + Blob decryptedCipherText = Crypto.decrypt('AES128', hardCodedKey, hardCodedIV, encryptedCipherText); + } + + public void badCryptoDecryptionHarcodedIvOnly() { + Blob encryptedCipherText = Blob.valueOf('Some encrypted cipher text'); + // ruleid: bad-crypto + Blob hardCodedIV = Blob.valueOf('Hardcoded IV 123'); + // ok: bad-crypto + Blob key = Blob.valueOf(generateEncryptionKey()); + Blob encrypted = Crypto.encrypt('AES128', key, hardCodedIV, data); + } + + public void badCryptoDecryptionHarcodedKeyOnly() { + Blob encryptedCipherText = Blob.valueOf('Some encrypted cipher text'); + // ok: bad-crypto + Blob IV = Blob.valueOf(generateEncryptionIV()); + // ruleid: bad-crypto + Blob hardCodedKey = Blob.valueOf('0000000000000000'); + Blob encrypted = Crypto.encrypt('AES128', hardCodedKey, IV, data); + } + + public void goodCryptoDecryption() { + Blob encryptedCipherText = Blob.valueOf('Some encrypted cipher text'); + // ok: bad-crypto + Blob IV = Blob.valueOf(generateEncryptionIV()); + // ok: bad-crypto + Blob key = Blob.valueOf(getRandomValue()); + Blob encrypted = Crypto.encrypt('AES128', key, IV, data); + } +} diff --git a/apex/lang/security/ncino/encryption/BadCrypto.yaml b/apex/lang/security/ncino/encryption/BadCrypto.yaml new file mode 100644 index 0000000000..38690ec108 --- /dev/null +++ b/apex/lang/security/ncino/encryption/BadCrypto.yaml @@ -0,0 +1,34 @@ +rules: + - id: bad-crypto + min-version: 1.44.0 + severity: ERROR + languages: + - apex + metadata: + cwe: + - 'CWE-321: Use of Hard-coded Cryptographic Key' + owasp: + - A02:2021 - Cryptographic Failures + impact: HIGH + likelihood: LOW + confidence: LOW + category: security + subcategory: + - audit + technology: + - salesforce + references: + - https://cwe.mitre.org/data/definitions/321.html + message: >- + The rule makes sure you are using randomly generated IVs and keys + for Crypto calls. Hard-coding these values greatly compromises the + security of encrypted data. + pattern-either: + - pattern: Blob $IV = Blob.valueOf('$STRING');...Crypto.encrypt($ONE, $TWO, $IV, $FOUR); + - pattern: Blob $IV = Blob.valueOf('$STRING');...Crypto.decrypt($ONE, $TWO, $IV, $FOUR); + - pattern: Blob $KEY = Blob.valueOf('$STRING');...Crypto.encrypt($ONE, $KEY, $THREE, $FOUR); + - pattern: Blob $KEY = Blob.valueOf('$STRING');...Crypto.decrypt($ONE, $KEY, $THREE, $FOUR); + paths: + exclude: + - "*Test*" + - "*test*" diff --git a/apex/lang/security/ncino/endpoints/InsecureHttpRequest.cls b/apex/lang/security/ncino/endpoints/InsecureHttpRequest.cls new file mode 100644 index 0000000000..8d4a241bbc --- /dev/null +++ b/apex/lang/security/ncino/endpoints/InsecureHttpRequest.cls @@ -0,0 +1,54 @@ +/* + * A test controller containing http requests. + * Javadoc insecure endpoint comment test: + * http://www.website.com + * Javadoc secure endpoint comment test: + * https://www.website.com + */ +public with sharing class InsecureHttpRequest { + + // Test comment with insecure endpoint in it + // (comments shouldn't be flagged regardless) + // ok: insecure-http-request + // http://www.website.com + public static void insecureRequestMethod() { + HttpRequest req = new HttpRequest(); + // ruleid: insecure-http-request + req.setEndpoint('http://www.website.com'); + } + + // Test comment with secure endpoint in it + // (comments shouldn't be flagged regardless) + // ok: insecure-http-request + // https://www.website.com + public static void secureRequestMethod() { + HttpRequest req = new HttpRequest(); + // ok: insecure-http-request + req.setEndpoint('https://www.website.com'); + } + + public static void httpRequestUsingLifeCycleCommunicatorConfigObject() { + Communication_Provider__c insecureCommunicator = new Communication_Provider__c( + Name = MY_COMMUNICATOR, + // ruleid: insecure-http-request + Destination__c = 'http://www.website.com', + Billable__c = true, + IsActive__c = true, + Class_Name__c = LifeCycleOAuth2Communicator.class.getName() + ); + + Communication_Provider__c secureCommunicator = new Communication_Provider__c( + Name = MY_COMMUNICATOR, + // ok: insecure-http-request + Destination__c = 'https://www.website.com', + Billable__c = true, + IsActive__c = true, + Class_Name__c = LifeCycleOAuth2Communicator.class.getName() + ); + } + + // ruleid: insecure-http-request + private static final String insecureEndpoint = 'http://www.website.com'; + // ok: insecure-http-request + private static final String secureEndpoint = 'https://www.website.com'; +} diff --git a/apex/lang/security/ncino/endpoints/InsecureHttpRequest.yaml b/apex/lang/security/ncino/endpoints/InsecureHttpRequest.yaml new file mode 100644 index 0000000000..e399c97ecd --- /dev/null +++ b/apex/lang/security/ncino/endpoints/InsecureHttpRequest.yaml @@ -0,0 +1,30 @@ +rules: + - id: insecure-http-request + min-version: 1.44.0 + severity: ERROR + languages: + - apex + metadata: + cwe: + - 'CWE-319: Cleartext Transmission of Sensitive Information' + impact: MEDIUM + likelihood: LOW + confidence: MEDIUM + category: security + subcategory: + - vuln + technology: + - salesforce + references: + - https://cwe.mitre.org/data/definitions/319.html + message: >- + The software transmits sensitive or security-critical data in cleartext in + a communication channel that can be sniffed by unauthorized actors. + patterns: + - pattern-regex: http[:][/][/] + - pattern-not-regex: //.* + - pattern-not-regex: '[*].*' + paths: + exclude: + - "*Test*" + - "*test*" diff --git a/apex/lang/security/ncino/endpoints/NamedCredentialsConstantMatch.cls b/apex/lang/security/ncino/endpoints/NamedCredentialsConstantMatch.cls new file mode 100644 index 0000000000..55c626e13c --- /dev/null +++ b/apex/lang/security/ncino/endpoints/NamedCredentialsConstantMatch.cls @@ -0,0 +1,28 @@ +/* + * Test controller with hard-coded credentials in header + */ +public with sharing class NamedCredentialsConstantMatch { + + public void calloutWithHardcodedCredentials() { + HttpRequest req = new HttpRequest(); + + Blob headerValue = Blob.valueOf('someUsername:somePasswod'); + String authorizationHeader = 'BASIC ' + EncodingUtil.base64Encode(headerValue); + // ruleid: named-credentials-constant-match + req.setHeader(AUTHORIZATION_HEADER, authorizationHeader); + req.setEndpoint('https://www.website.com'); + + HTTPResponse res = new Http().send(req); + } + + public void calloutWithNamedCredential() { + HttpRequest req = new HttpRequest(); + + // ok: named-credentials-constant-match + req.setEndpoint('callout:My_Named_Credential/some_path'); + + HTTPResponse res = new Http().send(req); + } + + public static final String AUTHORIZATION_HEADER = 'Authorization'; +} diff --git a/apex/lang/security/ncino/endpoints/NamedCredentialsConstantMatch.yaml b/apex/lang/security/ncino/endpoints/NamedCredentialsConstantMatch.yaml new file mode 100644 index 0000000000..e3d06ac627 --- /dev/null +++ b/apex/lang/security/ncino/endpoints/NamedCredentialsConstantMatch.yaml @@ -0,0 +1,32 @@ +rules: + - id: named-credentials-constant-match + min-version: 1.44.0 + mode: taint + severity: ERROR + languages: + - apex + metadata: + cwe: + - 'CWE-540: Inclusion of Sensitive Information in Source Code' + impact: HIGH + likelihood: LOW + confidence: HIGH + category: security + subcategory: + - vuln + technology: + - salesforce + references: + - https://cwe.mitre.org/data/definitions/540.html + message: >- + Named Credentials (and callout endpoints) should be used instead of hard-coding credentials. + 1. Hard-coded credentials are hard to maintain when mixed in with application code. + 2. It is particularly hard to update hard-coded credentials when they are used amongst different classes. + 3. Granting a developer access to the codebase means granting knowledge of credentials, and thus keeping a two-level access is not possible. + 4. Using different credentials for different environments is troublesome and error-prone. + pattern-sources: + - pattern: ...String $X = 'Authorization'; + pattern-sinks: + - patterns: + - pattern: req.setHeader($X, ...); + - focus-metavariable: $X diff --git a/apex/lang/security/ncino/endpoints/NamedCredentialsStringMatch.cls b/apex/lang/security/ncino/endpoints/NamedCredentialsStringMatch.cls new file mode 100644 index 0000000000..9f8212effd --- /dev/null +++ b/apex/lang/security/ncino/endpoints/NamedCredentialsStringMatch.cls @@ -0,0 +1,26 @@ +/* + * Test controller with hard-coded credentials in header + */ +public with sharing class NamedCredentialsStringMatch { + + public void calloutWithHardcodedCredentials(String username, String password) { + HttpRequest req = new HttpRequest(); + + Blob headerValue = Blob.valueOf(username + ':' + password); + String authorizationHeader = 'BASIC ' + EncodingUtil.base64Encode(headerValue); + // ruleid: named-credentials-string-match + req.setHeader('Authorization', authorizationHeader); + req.setEndpoint('https://www.website.com'); + + HTTPResponse res = new Http().send(req); + } + + public void calloutWithNamedCredential() { + HttpRequest req = new HttpRequest(); + + // ok: named-credentials-string-match + req.setEndpoint('callout:My_Named_Credential/some_path'); + + HTTPResponse res = new Http().send(req); + } +} diff --git a/apex/lang/security/ncino/endpoints/NamedCredentialsStringMatch.yaml b/apex/lang/security/ncino/endpoints/NamedCredentialsStringMatch.yaml new file mode 100644 index 0000000000..6e8fdd6876 --- /dev/null +++ b/apex/lang/security/ncino/endpoints/NamedCredentialsStringMatch.yaml @@ -0,0 +1,26 @@ +rules: + - id: named-credentials-string-match + min-version: 1.44.0 + severity: ERROR + languages: + - apex + metadata: + cwe: + - 'CWE-540: Inclusion of Sensitive Information in Source Code' + impact: HIGH + likelihood: LOW + confidence: HIGH + category: security + subcategory: + - vuln + technology: + - salesforce + references: + - https://cwe.mitre.org/data/definitions/540.html + message: >- + Named Credentials (and callout endpoints) should be used instead of hard-coding credentials. + 1. Hard-coded credentials are hard to maintain when mixed in with application code. + 2. It is particularly hard to update hard-coded credentials when they are used amongst different classes. + 3. Granting a developer access to the codebase means granting knowledge of credentials, and thus keeping a two-level access is not possible. + 4. Using different credentials for different environments is troublesome and error-prone. + pattern: $REQUEST.setHeader('Authorization', $AUTHSTRING); diff --git a/apex/lang/security/ncino/injection/ApexSOQLInjectionFromUnescapedURLParam.cls b/apex/lang/security/ncino/injection/ApexSOQLInjectionFromUnescapedURLParam.cls new file mode 100644 index 0000000000..88c7aa54a2 --- /dev/null +++ b/apex/lang/security/ncino/injection/ApexSOQLInjectionFromUnescapedURLParam.cls @@ -0,0 +1,39 @@ +/* + * Test controller utilizing unescaped URL params + */ +public class ApexSOQLInjectionFromUnescapedURLParam { + + public void test1() { + String unescapedString = ApexPage.getCurrentPage().getParameters.get('url_param'); + // ruleid: soql-injection-unescaped-url-param + Database.query('SELECT Id FROM Account' + unescapedString); + } + + public void test2() { + String unescapedString = ApexPage.getCurrentPage().getParameters.get('url_param'); + String someQueryClause = someOtherString + unescapedString; + // ruleid: soql-injection-unescaped-url-param + Database.query('SELECT Id FROM Account' + someQueryClause); + } + + public void test3() { + String unescapedString = ApexPage.getCurrentPage().getParameters.get('url_param'); + String escapedString = String.escapeSingleQuotes(unescapedString); + // ok: soql-injection-unescaped-url-param + Database.query('SELECT Id FROM Account' + escapedString); + } + + public void test4() { + String unescapedString = ApexPage.getCurrentPage().getParameters.get('url_param'); + String escapedString = String.escapeSingleQuotes(unescapedString); + String someQueryClause = someOtherString + escapedString; + // ok: soql-injection-unescaped-url-param + Database.query('SELECT Id FROM Account' + someQueryClause); + } + + public void test5() { + String unescapedString = ApexPage.getCurrentPage().getParameters.get('url_param'); + // ok: soql-injection-unescaped-url-param + Database.query('SELECT Id FROM Account' + String.escapeSingleQuotes(unescapedString)); + } +} diff --git a/apex/lang/security/ncino/injection/ApexSOQLInjectionFromUnescapedURLParam.yaml b/apex/lang/security/ncino/injection/ApexSOQLInjectionFromUnescapedURLParam.yaml new file mode 100644 index 0000000000..05f217056f --- /dev/null +++ b/apex/lang/security/ncino/injection/ApexSOQLInjectionFromUnescapedURLParam.yaml @@ -0,0 +1,41 @@ +rules: + - id: soql-injection-unescaped-url-param + min-version: 1.44.0 + mode: taint + severity: ERROR + languages: + - apex + metadata: + category: security + subcategory: + - vuln + technology: + - salesforce + cwe: + - 'CWE-943: Improper Neutralization of Special Elements in Data Query Logic' + owasp: + - A03:2021 - Injection + references: + - https://cwe.mitre.org/data/definitions/943.html + impact: HIGH + likelihood: HIGH + confidence: HIGH + message: >- + If a dynamic query must be used,leverage nFORCE Query Builder. + In other programming languages, the related flaw is known as SQL injection. + Apex doesn't use SQL, but uses its own database query language, SOQL. SOQL is + much simpler and more limited in functionality than SQL. The risks are much + lower for SOQL injection than for SQL injection, but the attacks are nearly + identical to traditional SQL injection. SQL/SOQL injection takes user-supplied + input and uses those values in a dynamic SOQL query. If the input isn't validated, + it can include SOQL commands that effectively modify the SOQL statement and trick + the application into performing unintended commands. + pattern-sources: + - by-side-effect: true + pattern: ApexPage.getCurrentPage().getParameters.get($URLPARAM); + pattern-sanitizers: + - pattern: String.escapeSingleQuotes(...) + pattern-sinks: + - patterns: + - pattern: Database.query($SINK,...); + - focus-metavariable: $SINK diff --git a/apex/lang/security/ncino/injection/ApexSOQLInjectionUnescapedParam.cls b/apex/lang/security/ncino/injection/ApexSOQLInjectionUnescapedParam.cls new file mode 100644 index 0000000000..054bcacdc0 --- /dev/null +++ b/apex/lang/security/ncino/injection/ApexSOQLInjectionUnescapedParam.cls @@ -0,0 +1,63 @@ +public class ApexSOQLInjectionUnescapedParam { + public void test1(String t1) { + // ruleid: soql-injection-unescaped-param + Database.query('SELECT Id FROM Account' + t1); + } + + public void test2(String t1) { + String t2 = t1; + // ruleid: soql-injection-unescaped-param + Database.query('SELECT Id FROM Account' + t2); + } + + public void test3(String t1) { + String escapedStr = String.escapeSingleQuotes(t1); + // ok: soql-injection-unescaped-param + Database.query('SELECT Id FROM Account' + escapedStr); + } + + public void test4(String t1) { + String t2 = t1; + String escapedStr = String.escapeSingleQuotes(t2); + // ok: soql-injection-unescaped-param + Database.query('SELECT Id FROM Account' + escapedStr); + } + + public void test5(Integer t0, String t1, Integer t2) { + // ruleid: soql-injection-unescaped-param + Database.query('SELECT Id FROM Account' + t1); + } + + public void test6(Integer t0, String t1, Integer t2) { + String t2 = t1; + // ruleid: soql-injection-unescaped-param + Database.query('SELECT Id FROM Account' + t2); + } + + public void test7(Integer t0, String t1, Integer t2) { + String escapedStr = String.escapeSingleQuotes(t1); + // ok: soql-injection-unescaped-param + Database.query('SELECT Id FROM Account' + escapedStr); + } + + public void test8(Integer t0, String t1, Integer t2) { + String t2 = t1; + String escapedStr = String.escapeSingleQuotes(t2); + // ok: soql-injection-unescaped-param + Database.query('SELECT Id FROM Account' + escapedStr); + } + + @AuraEnabled(Cacheable=true) + public static List findObjects(String objectName, String searchKey) { + // ok: soql-injection-unescaped-param + List results = Database.query( + 'SELECT Id, Name ' + + 'FROM ' + objectName + ' ' + + 'WHERE Name LIKE \'%' + String.escapeSingleQuotes(searchKey) + '%\' ' + + 'WITH SECURITY_ENFORCED ' + + 'LIMIT 25' + ); + + return results; + } +} diff --git a/apex/lang/security/ncino/injection/ApexSOQLInjectionUnescapedParam.yaml b/apex/lang/security/ncino/injection/ApexSOQLInjectionUnescapedParam.yaml new file mode 100644 index 0000000000..eda51746fa --- /dev/null +++ b/apex/lang/security/ncino/injection/ApexSOQLInjectionUnescapedParam.yaml @@ -0,0 +1,43 @@ +rules: + - id: soql-injection-unescaped-param + min-version: 1.44.0 + mode: taint + severity: ERROR + languages: + - apex + metadata: + category: security + subcategory: + - vuln + technology: + - salesforce + cwe: + - 'CWE-943: Improper Neutralization of Special Elements in Data Query Logic' + owasp: + - A03:2021 - Injection + references: + - https://cwe.mitre.org/data/definitions/943.html + impact: HIGH + likelihood: HIGH + confidence: HIGH + message: >- + If a dynamic query must be used,leverage nFORCE Query Builder. + In other programming languages, the related flaw is known as SQL injection. + Apex doesn't use SQL, but uses its own database query language, SOQL. SOQL is + much simpler and more limited in functionality than SQL. The risks are much + lower for SOQL injection than for SQL injection, but the attacks are nearly + identical to traditional SQL injection. SQL/SOQL injection takes user-supplied + input and uses those values in a dynamic SOQL query. If the input isn't validated, + it can include SOQL commands that effectively modify the SOQL statement and trick + the application into performing unintended commands. + pattern-sources: + - by-side-effect: true + patterns: + - pattern: $M(...,String $P,...) { ... } + - focus-metavariable: $P + pattern-sanitizers: + - pattern-either: + - pattern: String.escapeSingleQuotes($P) + - pattern: Database.query(<... String.escapeSingleQuotes($P) ...>) + pattern-sinks: + - pattern: Database.query(<... $P ...>) diff --git a/apex/lang/security/ncino/sharing/SpecifySharingLevel.cls b/apex/lang/security/ncino/sharing/SpecifySharingLevel.cls new file mode 100644 index 0000000000..02938fc63d --- /dev/null +++ b/apex/lang/security/ncino/sharing/SpecifySharingLevel.cls @@ -0,0 +1,54 @@ +// ruleid: specify-sharing-level +global class SpecifySharingLevel { + // ruleid: specify-sharing-level + public class InnerClass { + public void foo(){ + } + } + + // ruleid: specify-sharing-level + public abstract class InnerClass2 { + public void foo(){ + } + } + + // ruleid: specify-sharing-level + private virtual class InnerClass3 { + public void foo(){ + } + } + + // ok: specify-sharing-level + public inherited sharing class OtherInnerClass { + private void bar(){ + } + } + + // ok: specify-sharing-level + public class SomeExceptionClass extends Exception { + } + + // ok: specify-sharing-level + public with sharing abstract class InnerClass2 { + public void foo(){ + } + } + + // ok: specify-sharing-level + private inherited sharing virtual class InnerClass3 { + public void foo(){ + } + } + + // ok: specify-sharing-level + public inherited sharing virtual class InnerClass3 extends AnotherClass { + public void foo(){ + } + } + + // Observed False Positive + // ok: specify-sharing-level + global virtual override Type forName(String className) { + return Type.forName(className); + } +} diff --git a/apex/lang/security/ncino/sharing/SpecifySharingLevel.yaml b/apex/lang/security/ncino/sharing/SpecifySharingLevel.yaml new file mode 100644 index 0000000000..83cfbcd1ac --- /dev/null +++ b/apex/lang/security/ncino/sharing/SpecifySharingLevel.yaml @@ -0,0 +1,32 @@ +rules: + - id: specify-sharing-level + min-version: 1.44.0 + severity: WARNING + languages: + - apex + metadata: + cwe: + - 'CWE-284: Improper Access Control' + owasp: + - A04:2021 - Insecure Design + references: + - https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_keywords_sharing.htm + - https://cwe.mitre.org/data/definitions/284.html + - https://owasp.org/Top10/A04_2021-Insecure_Design/ + category: security + subcategory: + - vuln + technology: + - salesforce + impact: MEDIUM + likelihood: MEDIUM + confidence: HIGH + message: >- + Every Apex class should have an explicit sharing mode declared. Use the `with sharing` + or `without sharing` keywords on a class to specify whether sharing rules must be enforced. + Use the `inherited sharing` keyword on an Apex class to run the class in the sharing mode + of the class that called it. + patterns: + - pattern-regex: (private|public|global).*\s(class)\s.*[{] + - pattern-not-regex: (private|public|global).*[with|without|inherited]\s[sharing].*\s(class)\s.*[{] + - pattern-not-regex: (private|public|global).*\s(class)\s.*(extends)\s(Exception).*[{] diff --git a/apex/lang/security/ncino/system/SystemDebug.cls b/apex/lang/security/ncino/system/SystemDebug.cls new file mode 100644 index 0000000000..440af2e817 --- /dev/null +++ b/apex/lang/security/ncino/system/SystemDebug.cls @@ -0,0 +1,13 @@ +/* +A test controller containing a debug log. +*/ +public class SystemDebug { + public static void debugMethod() { + // ruleid: system-debug + System.debug('foo'); + } + + public static void noDebugMethod() { + insert new Object(); + } +} diff --git a/apex/lang/security/ncino/system/SystemDebug.yaml b/apex/lang/security/ncino/system/SystemDebug.yaml new file mode 100644 index 0000000000..46978c955b --- /dev/null +++ b/apex/lang/security/ncino/system/SystemDebug.yaml @@ -0,0 +1,30 @@ +rules: + - id: system-debug + min-version: 1.44.0 + severity: WARNING + languages: + - apex + metadata: + cwe: + - 'CWE-489: Active Debug Code' + - 'CWE-779: Logging of Excessive Data' + category: security + subcategory: + - vuln + technology: + - vuln + references: + - https://cwe.mitre.org/data/definitions/489.html + - https://cwe.mitre.org/data/definitions/779.html + impact: MEDIUM + likelihood: LOW + confidence: HIGH + message: >- + In addition to debug statements potentially logging data excessively, debug statements + also contribute to longer transactions and consume Apex CPU time even when debug logs + are not being captured. + pattern: System.debug(...) + paths: + exclude: + - "*Test*" + - "*test*" diff --git a/csharp/lang/security/regular-expression-dos/regular-expression-dos.yaml b/csharp/lang/security/regular-expression-dos/regular-expression-dos.yaml index 7cda65a6b1..8bcc7b2672 100644 --- a/csharp/lang/security/regular-expression-dos/regular-expression-dos.yaml +++ b/csharp/lang/security/regular-expression-dos/regular-expression-dos.yaml @@ -19,9 +19,9 @@ rules: likelihood: LOW impact: MEDIUM message: >- - An attacker can then cause a program using a regular expression to enter these extreme situations - and then hang for a - very long time. + When using `System.Text.RegularExpressions` to process untrusted input, pass a timeout. + A malicious user can provide input to `RegularExpressions` that abuses the backtracking behaviour of this regular expression engine. + This will lead to excessive CPU usage, causing a Denial-of-Service attack patterns: - pattern-inside: | using System.Text.RegularExpressions; diff --git a/generic/secrets/security/detected-twitter-oauth.yaml b/generic/secrets/security/detected-twitter-oauth.yaml index 3c4aa6b960..3a84212e9c 100644 --- a/generic/secrets/security/detected-twitter-oauth.yaml +++ b/generic/secrets/security/detected-twitter-oauth.yaml @@ -27,3 +27,6 @@ rules: - audit likelihood: LOW impact: MEDIUM + paths: + exclude: + - "*.css" diff --git a/generic/visualforce/security/ncino/html/UseSRIForCDNs.page b/generic/visualforce/security/ncino/html/UseSRIForCDNs.page new file mode 100644 index 0000000000..2244068677 --- /dev/null +++ b/generic/visualforce/security/ncino/html/UseSRIForCDNs.page @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/generic/visualforce/security/ncino/html/UseSRIForCDNs.yaml b/generic/visualforce/security/ncino/html/UseSRIForCDNs.yaml new file mode 100644 index 0000000000..8276a48050 --- /dev/null +++ b/generic/visualforce/security/ncino/html/UseSRIForCDNs.yaml @@ -0,0 +1,49 @@ +rules: + - id: use-SRI-for-CDNs + languages: + - generic + severity: WARNING + message: >- + Consuming CDNs without including a SubResource Integrity (SRI) can expose your + application and its users to compromised code. SRIs allow you to consume specific + versions of content where if even a single byte is compromised, the resource will + not be loaded. Add an integrity attribute to your + + + diff --git a/generic/visualforce/security/ncino/vf/XSSFromUnescapedURLParam.yaml b/generic/visualforce/security/ncino/vf/XSSFromUnescapedURLParam.yaml new file mode 100644 index 0000000000..529b73921e --- /dev/null +++ b/generic/visualforce/security/ncino/vf/XSSFromUnescapedURLParam.yaml @@ -0,0 +1,47 @@ +rules: + - id: xss-from-unescaped-url-param + languages: + - generic + severity: ERROR + message: >- + To remediate this issue, ensure that all URL parameters are properly + escaped before including them in scripts. Please update your code + to use either the JSENCODE method to escape URL parameters + or the escape="true" attribute on tags. + Passing URL parameters directly into scripts and DOM sinks creates + an opportunity for Cross-Site Scripting attacks. Cross-Site + Scripting (XSS) attacks are a type of injection, in which malicious + scripts are injected into otherwise benign and trusted websites. To + remediate this issue, ensure that all URL parameters are properly + escaped before including them in scripts. + metadata: + cwe: + - "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')" + owasp: + - A07:2017 - Cross-Site Scripting (XSS) + - A03:2021 - Injection + references: + - https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/pages_security_tips_xss.htm + category: security + subcategory: + - vuln + technology: + - salesforce + - visualforce + cwe2022-top25: true + cwe2021-top25: true + likelihood: HIGH + impact: MEDIUM + confidence: MEDIUM + patterns: + - pattern-either: + # Cannot use full VF syntax of {!$...} because Semgrep thinks CurrentPage is a metavariable + - pattern: + - pattern: + - pattern: + - pattern-not: + paths: + include: + - "*.component" + - "*.page" + diff --git a/generic/visualforce/security/ncino/xml/CSPHeaderAttribute.page b/generic/visualforce/security/ncino/xml/CSPHeaderAttribute.page new file mode 100644 index 0000000000..fa8a22c7de --- /dev/null +++ b/generic/visualforce/security/ncino/xml/CSPHeaderAttribute.page @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/generic/visualforce/security/ncino/xml/CSPHeaderAttribute.yaml b/generic/visualforce/security/ncino/xml/CSPHeaderAttribute.yaml new file mode 100644 index 0000000000..edcd02bf81 --- /dev/null +++ b/generic/visualforce/security/ncino/xml/CSPHeaderAttribute.yaml @@ -0,0 +1,35 @@ +rules: + - id: csp-header-attribute + languages: + - generic + severity: INFO + message: >- + Visualforce Pages must have the cspHeader attribute set to true. + This attribute is available in API version 55 or higher. + metadata: + cwe: + - "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')" + owasp: + - A07:2017 - Cross-Site Scripting (XSS) + - A03:2021 - Injection + references: + - https://help.salesforce.com/s/articleView?id=sf.csp_trusted_sites.htm&type=5 + category: security + subcategory: + - vuln + technology: + - salesforce + - visualforce + cwe2022-top25: true + cwe2021-top25: true + likelihood: HIGH + impact: MEDIUM + confidence: HIGH + patterns: + - pattern: ... + - pattern-not: ... + - pattern-not: ...... + - pattern-not: ...... + paths: + include: + - "*.page" diff --git a/generic/visualforce/security/ncino/xml/VisualForceAPIVersion.page-meta.xml b/generic/visualforce/security/ncino/xml/VisualForceAPIVersion.page-meta.xml new file mode 100644 index 0000000000..872df366ec --- /dev/null +++ b/generic/visualforce/security/ncino/xml/VisualForceAPIVersion.page-meta.xml @@ -0,0 +1,85 @@ + + + + 2.0 + true + false + + + + + 15.0 + true + false + + + + + 20.0 + true + false + + + + + 33.0 + true + false + + + + + 49.0 + true + false + + + + + 50.0 + true + false + + + + + 51.2 + true + false + + + + + 52.0 + true + false + + + + + 53.0 + true + false + + + + + 54.0 + true + false + + + + + 55.0 + true + false + + + + + 62.0 + true + false + + diff --git a/generic/visualforce/security/ncino/xml/VisualForceAPIVersion.yaml b/generic/visualforce/security/ncino/xml/VisualForceAPIVersion.yaml new file mode 100644 index 0000000000..732e730237 --- /dev/null +++ b/generic/visualforce/security/ncino/xml/VisualForceAPIVersion.yaml @@ -0,0 +1,35 @@ +rules: + - id: visualforce-page-api-version + languages: + - generic + severity: WARNING + message: Visualforce Pages must use API version 55 or higher for required use of the cspHeader attribute set to true. + metadata: + cwe: + - "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')" + owasp: + - A07:2017 - Cross-Site Scripting (XSS) + - A03:2021 - Injection + references: + - https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/meta_pages.htm + category: security + subcategory: + - vuln + technology: + - salesforce + - visualforce + cwe2022-top25: true + cwe2021-top25: true + likelihood: HIGH + impact: MEDIUM + confidence: HIGH + patterns: + - pattern-inside: + - pattern-either: + - pattern-regex: '[>][0-9].[0-9][<]' + - pattern-regex: '[>][1-4][0-9].[0-9][<]' + - pattern-regex: '[>][5][0-4].[0-9][<]' + paths: + include: + - "*.page-meta.xml" + diff --git a/python/aws-lambda/security/dangerous-subprocess-use.py b/python/aws-lambda/security/dangerous-subprocess-use.py index c0d74f5e56..f2c03fed03 100644 --- a/python/aws-lambda/security/dangerous-subprocess-use.py +++ b/python/aws-lambda/security/dangerous-subprocess-use.py @@ -1,5 +1,6 @@ import subprocess import sys +import shlex def handler(event, context): # ok:dangerous-subprocess-use @@ -9,29 +10,39 @@ def handler(event, context): subprocess.call(["echo", "a", ";", "rm", "-rf", "/"]) # ruleid:dangerous-subprocess-use + subprocess.call("grep -R {} .".format(event['id']), shell=True) + + # ok:dangerous-subprocess-use subprocess.call("grep -R {} .".format(event['id'])) cmd = event['id'].split() - # ruleid:dangerous-subprocess-use + # ok:dangerous-subprocess-use subprocess.call([cmd[0], cmd[1], "some", "args"]) # ruleid:dangerous-subprocess-use - subprocess.call("grep -R {} .".format(event['id']), shell=True) + subprocess.call([cmd[0], cmd[1], "some", "args"], shell=True) # ruleid:dangerous-subprocess-use - subprocess.call("grep -R {} .".format(event['id']), shell=True, cwd="/home/user") - - # ruleid:dangerous-subprocess-use - subprocess.run("grep -R {} .".format(event['id']), shell=True) + subprocess.call("grep -R {} .".format(event['id']), shell=True) # ruleid:dangerous-subprocess-use - subprocess.run(["bash", "-c", event['id']], shell=True) + subprocess.call("grep -R {} .".format(event['id']), shell=True, cwd="/home/user") python_file = f""" print("What is your name?") name = input() print("Hello " + {event['id']}) """ - # ruleid:dangerous-subprocess-use + # ok:dangerous-subprocess-use program = subprocess.Popen(['python2', python_file], stdin=subprocess.PIPE, text=True) + + # ruleid:dangerous-subprocess-use + program = subprocess.Popen(['python2', python_file], stdin=subprocess.PIPE, text=True, shell=True) + + # ruleid:dangerous-subprocess-use + program = subprocess.Popen(['python2', python_file], stdin=subprocess.PIPE, shell=True, text=True) + + # ok:dangerous-subprocess-use + program = subprocess.Popen(['python2', shlex.quote(python_file)], stdin=subprocess.PIPE, shell=True, text=True) + program.communicate(input=payload, timeout=1) diff --git a/python/aws-lambda/security/dangerous-subprocess-use.yaml b/python/aws-lambda/security/dangerous-subprocess-use.yaml index dbe1e54c9e..b8e9c8145e 100644 --- a/python/aws-lambda/security/dangerous-subprocess-use.yaml +++ b/python/aws-lambda/security/dangerous-subprocess-use.yaml @@ -2,11 +2,12 @@ rules: - id: dangerous-subprocess-use mode: taint message: >- - Detected subprocess function with argument tainted by `event` object. If this - data can be controlled by a malicious actor, it may be an instance of - command injection. Audit the use of this call to ensure it is not - controllable by an external resource. You may consider using - 'shlex.escape()'. + Detected subprocess function with argument tainted by an `event` object. + If this data can be controlled by a malicious actor, it may be an instance of + command injection. The default option for `shell` is False, and this is secure by default. + Consider removing the `shell=True` or setting it to False explicitely. + Using `shell=False` means you have to split the command string into an array of + strings for the command and its arguments. You may consider using 'shlex.split()' for this purpose. metadata: owasp: - A01:2017 - Injection @@ -44,13 +45,8 @@ rules: ... pattern-sinks: - patterns: - - focus-metavariable: $CMD - - pattern-either: - - pattern: subprocess.$FUNC($CMD, ...) - - pattern: subprocess.$FUNC([$CMD,...], ...) - - pattern: subprocess.$FUNC("=~/(sh|bash|ksh|csh|tcsh|zsh)/", "-c", $CMD, ...) - - pattern: subprocess.$FUNC(["=~/(sh|bash|ksh|csh|tcsh|zsh)/", "-c", $CMD, ...], ...) - - pattern: subprocess.$FUNC("=~/(python)/", $CMD, ...) - - pattern: subprocess.$FUNC(["=~/(python)/",$CMD,...],...) + - pattern: subprocess.$FUNC(..., shell=True, ...) pattern-sanitizers: - - pattern: shlex.escape(...) + - pattern: shlex.split(...) + - pattern: pipes.quote(...) + - pattern: shlex.quote(...) diff --git a/python/django/security/hashids-with-django-secret.py b/python/django/security/hashids-with-django-secret.py new file mode 100644 index 0000000000..fae7d267c4 --- /dev/null +++ b/python/django/security/hashids-with-django-secret.py @@ -0,0 +1,63 @@ +# https://github.com/crowdresearch/daemo/blob/36e3b70d4e2c06b4853e9209a4916f8301ed6464/crowdsourcing/serializers/task.py#L435-L437 +from django.conf import settings +from hashids import Hashids +# ruleid: hashids-with-django-secret +identifier = Hashids(salt=settings.SECRET_KEY, min_length=settings.ID_HASH_MIN_LENGTH) + +# https://github.com/pythonitalia/pycon-quiz/blob/7fe11ab96815edad4cf1ed0bdd8ba52d9438ffa0/backend/django_hashids/hashids.py +from django.conf import settings +from hashids import Hashids + + +def get_hashids(): +# ruleid: hashids-with-django-secret + return Hashids( + salt=settings.SECRET_KEY, min_length=4, alphabet="abcdefghijklmnopqrstuvwxyz" + ) + +# https://github.com/made-with-future/django-common/blob/dc68c93209a71c63dbf0241b997ab8e67697b3a5/common/models.py#L45 +class UIDMixin(models.Model): + + objects = UIDManager() + + _hashids = None + + def __init__(self, *args, **kwargs): + super(UIDMixin, self).__init__(*args, **kwargs) + + @classmethod + def hashids(cls): + if not cls._hashids: + md5 = hashlib.md5() + md5.update('{}{}'.format(settings.SECRET_KEY, cls.__name__)) +# ok: hashids-with-django-secret + cls._hashids = Hashids(salt=md5.hexdigest(), min_length=16) + return cls._hashids + +# https://github.com/duthaho/aicontest/blob/f6bdcc785b66842be65a8086938d198d65f27650/coding/services/util.py +from contextlib import suppress +import random +import string + +from django.conf import settings +from hashids import Hashids + + +def get_random_string(length: int) -> str: + # choose from all lowercase letter + letters = string.ascii_lowercase + string.digits + return "".join(random.choice(letters) for i in range(length)) + + +def id_to_hash(id: int, length: int = 6) -> str: + alphabet = string.ascii_letters + string.digits +# ruleid: hashids-with-django-secret + return Hashids(settings.SECRET_KEY, min_length=length, alphabet=alphabet).encrypt( + id + ) + + +def safe_int(num: any, default: int = 0) -> int: + with suppress(Exception): + return int(num) + return default diff --git a/python/django/security/hashids-with-django-secret.yaml b/python/django/security/hashids-with-django-secret.yaml new file mode 100644 index 0000000000..a7df84f182 --- /dev/null +++ b/python/django/security/hashids-with-django-secret.yaml @@ -0,0 +1,28 @@ +rules: +- id: hashids-with-django-secret + languages: + - python + message: >- + The Django secret key is used as salt in HashIDs. The HashID mechanism is not secure. + By observing sufficient HashIDs, the salt used to construct them can be recovered. + This means the Django secret key can be obtained by attackers, through the HashIDs. + metadata: + category: security + subcategory: + - vuln + cwe: + - "CWE-327: Use of a Broken or Risky Cryptographic Algorithm" + owasp: + - A02:2021 – Cryptographic Failures + references: + - https://docs.djangoproject.com/en/4.2/ref/settings/#std-setting-SECRET_KEY + - http://carnage.github.io/2015/08/cryptanalysis-of-hashids + technology: + - django + likelihood: LOW + impact: HIGH + confidence: HIGH + pattern-either: + - pattern: hashids.Hashids(..., salt=django.conf.settings.SECRET_KEY, ...) + - pattern: hashids.Hashids(django.conf.settings.SECRET_KEY, ...) + severity: ERROR diff --git a/python/flask/security/audit/secure-set-cookie.py b/python/flask/security/audit/secure-set-cookie.py index 3aa1163e70..1f62354ced 100644 --- a/python/flask/security/audit/secure-set-cookie.py +++ b/python/flask/security/audit/secure-set-cookie.py @@ -1,8 +1,22 @@ def test1(): import flask + response = flask.make_response() + # ruleid:secure-set-cookie response.set_cookie("cookie_name", "cookie_value") + + # ruleid:secure-set-cookie + response.set_cookie("username","DrewDennison") + # ruleid:secure-set-cookie + response.set_cookie("cartTotal", + generate_cookie_value("DrewDennison"), + secure=False) + + # ok:secure-set-cookie + response.set_cookie("user—rights", "admin", secure=True, + httponly=True, samesite="Lax") + return response def test2(): @@ -72,3 +86,4 @@ def merge_cookies(cookiejar, cookies): # ok:secure-set-cookie cookiejar.set_cookie(cookie_in_jar) return cookiejar + diff --git a/python/flask/security/hashids-with-flask-secret.py b/python/flask/security/hashids-with-flask-secret.py new file mode 100644 index 0000000000..c34a5cb33a --- /dev/null +++ b/python/flask/security/hashids-with-flask-secret.py @@ -0,0 +1,20 @@ +from hashids import Hashids +from flask import Flask + +from flask import current_app as app +# ruleid: hashids-with-flask-secret +hash_id = Hashids(salt=app.config['SECRET_KEY'], min_length=34) +# ruleid: hashids-with-flask-secret +hashids = Hashids(min_length=4, salt=app.config['SECRET_KEY']) + +from flask import current_app +# ruleid: hashids-with-flask-secret +hashids = Hashids(min_length=5, salt=current_app.config['SECRET_KEY']) + +foo = Flask(__name__) +# ruleid: hashids-with-flask-secret +hashids = Hashids(min_length=4, salt=foo.config['SECRET_KEY']) + +app = Flask(__name__.split('.')[0]) +# ruleid: hashids-with-flask-secret +app._hashids = Hashids(salt=app.config['SECRET_KEY']) diff --git a/python/flask/security/hashids-with-flask-secret.yaml b/python/flask/security/hashids-with-flask-secret.yaml new file mode 100644 index 0000000000..b29ca9373b --- /dev/null +++ b/python/flask/security/hashids-with-flask-secret.yaml @@ -0,0 +1,35 @@ +rules: +- id: hashids-with-flask-secret + languages: + - python + message: >- + The Flask secret key is used as salt in HashIDs. The HashID mechanism is not secure. + By observing sufficient HashIDs, the salt used to construct them can be recovered. + This means the Flask secret key can be obtained by attackers, through the HashIDs. + metadata: + category: security + subcategory: + - vuln + cwe: + - "CWE-327: Use of a Broken or Risky Cryptographic Algorithm" + owasp: + - A02:2021 – Cryptographic Failures + references: + - https://flask.palletsprojects.com/en/2.2.x/config/#SECRET_KEY + - http://carnage.github.io/2015/08/cryptanalysis-of-hashids + technology: + - flask + likelihood: LOW + impact: HIGH + confidence: HIGH + pattern-either: + - pattern: hashids.Hashids(..., salt=flask.current_app.config['SECRET_KEY'], ...) + - pattern: hashids.Hashids(flask.current_app.config['SECRET_KEY'], ...) + - patterns: + - pattern-inside: | + $APP = flask.Flask(...) + ... + - pattern-either: + - pattern: hashids.Hashids(..., salt=$APP.config['SECRET_KEY'], ...) + - pattern: hashids.Hashids($APP.config['SECRET_KEY'], ...) + severity: ERROR diff --git a/python/lang/correctness/test-is-missing-assert.py b/python/lang/correctness/test-is-missing-assert.py new file mode 100644 index 0000000000..fff1f34e1b --- /dev/null +++ b/python/lang/correctness/test-is-missing-assert.py @@ -0,0 +1,44 @@ + +import unittest + +class TestSomething(unittest.TestCase): + def test_something(self): + # ruleid: test-is-missing-assert + a == b + + # ruleid: test-is-missing-assert + a == b, "message" + + # ok: test-is-missing-assert + assert a == b, "message" + + # ok: test-is-missing-assert + 1 == 1 and print("hello world") + + # ok: test-is-missing-assert + a = (1 == 1, "hello world") + + # ok: test-is-missing-assert + print(1 == 1, "hello world") + + # ok: test-is-missing-assert + a[1 == 1] = 1 + + # ok: test-is-missing-assert + while a == b: + pass + + # ok: test-is-missing-assert + a += b == 'b' + + # ok: test-is-missing-assert + a = 3 if a == b else 4 + + # ok: test-is-missing-assert + yield a == b + + # ok: test-is-missing-assert + a |= b == c + + # ok: test-is-missing-assert + a &= b == c diff --git a/python/lang/correctness/test-is-missing-assert.yaml b/python/lang/correctness/test-is-missing-assert.yaml new file mode 100644 index 0000000000..05cbfff87f --- /dev/null +++ b/python/lang/correctness/test-is-missing-assert.yaml @@ -0,0 +1,41 @@ +rules: + - id: test-is-missing-assert + languages: + - python + message: >- + Comparison without assertion. The result of this + comparison is not used. Perhaps this expression + is missing an `assert` keyword. + patterns: + - pattern: $A == $B + - pattern-not-inside: assert ... + - pattern-not-inside: $X = ... + - pattern-not-inside: $X += ... + - pattern-not-inside: $X |= ... + - pattern-not-inside: $X &= ... + - pattern-not-inside: yield $X + - pattern-not-inside: $X and $Y + - pattern-not-inside: $X or $Y + - pattern-not-inside: return ... + - pattern-not-inside: $FUNC(...) + - pattern-not-inside: | + while $EXPR: + ... + - pattern-not-inside: | + with (...): + ... + - pattern-not-inside: | + [...] + - pattern-not-inside: | + $EXPR[...] + - pattern-not-inside: | + if ...: + ... + severity: WARNING + paths: + include: + - test*.py + metadata: + category: correctness + technology: + - python