diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index fe91660..8d14cf0 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @SocketDev/eng +* @socketdev/eng diff --git a/.gitignore b/.gitignore index a15e002..8b11f57 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,10 @@ venv *.zip *.pyc test.py -.venv \ No newline at end of file +.venv +build +dist +*.build +*.dist +*.egg-info +*.cpython-312.pyc \ No newline at end of file diff --git a/LICENSE b/LICENSE index bccaae5..b5008f6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Socket +Copyright (c) 2022 Socket Inc Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +SOFTWARE. \ No newline at end of file diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..6898670 --- /dev/null +++ b/Pipfile @@ -0,0 +1,15 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +requests = "*" + +[dev-packages] +twine = "*" +wheel = "*" +build = "*" + +[requires] +python_version = "3.12" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..0af4cb2 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,472 @@ +{ + "_meta": { + "hash": { + "sha256": "0789e3796a2c5709f50b6a4c5ef9802ad22c9ecd38fa61b127d075075584ca1d" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.12" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", + "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9" + ], + "markers": "python_version >= '3.6'", + "version": "==2024.8.30" + }, + "charset-normalizer": { + "hashes": [ + "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", + "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", + "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", + "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", + "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", + "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", + "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", + "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", + "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", + "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", + "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", + "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", + "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", + "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", + "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", + "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", + "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", + "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", + "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", + "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", + "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", + "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", + "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", + "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", + "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", + "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", + "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", + "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", + "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", + "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", + "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", + "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", + "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", + "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", + "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", + "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", + "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", + "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", + "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", + "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", + "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", + "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", + "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", + "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", + "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", + "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", + "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", + "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", + "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", + "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", + "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", + "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", + "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", + "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", + "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", + "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", + "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", + "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", + "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", + "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", + "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", + "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", + "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", + "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", + "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", + "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", + "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", + "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", + "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", + "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", + "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", + "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", + "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", + "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", + "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", + "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", + "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", + "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", + "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", + "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", + "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", + "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", + "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", + "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", + "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", + "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", + "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", + "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", + "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", + "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==3.3.2" + }, + "idna": { + "hashes": [ + "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac", + "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603" + ], + "markers": "python_version >= '3.6'", + "version": "==3.8" + }, + "requests": { + "hashes": [ + "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", + "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==2.32.3" + }, + "urllib3": { + "hashes": [ + "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", + "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9" + ], + "markers": "python_version >= '3.8'", + "version": "==2.2.3" + } + }, + "develop": { + "build": { + "hashes": [ + "sha256:119b2fb462adef986483438377a13b2f42064a2a3a4161f24a0cca698a07ac8c", + "sha256:277ccc71619d98afdd841a0e96ac9fe1593b823af481d3b0cea748e8894e0613" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==1.2.2" + }, + "certifi": { + "hashes": [ + "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", + "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9" + ], + "markers": "python_version >= '3.6'", + "version": "==2024.8.30" + }, + "charset-normalizer": { + "hashes": [ + "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", + "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", + "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", + "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", + "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", + "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", + "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", + "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", + "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", + "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", + "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", + "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", + "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", + "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", + "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", + "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", + "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", + "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", + "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", + "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", + "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", + "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", + "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", + "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", + "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", + "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", + "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", + "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", + "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", + "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", + "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", + "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", + "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", + "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", + "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", + "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", + "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", + "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", + "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", + "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", + "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", + "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", + "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", + "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", + "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", + "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", + "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", + "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", + "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", + "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", + "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", + "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", + "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", + "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", + "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", + "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", + "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", + "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", + "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", + "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", + "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", + "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", + "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", + "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", + "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", + "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", + "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", + "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", + "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", + "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", + "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", + "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", + "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", + "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", + "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", + "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", + "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", + "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", + "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", + "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", + "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", + "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", + "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", + "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", + "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", + "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", + "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", + "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", + "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", + "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==3.3.2" + }, + "docutils": { + "hashes": [ + "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", + "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2" + ], + "markers": "python_version >= '3.9'", + "version": "==0.21.2" + }, + "idna": { + "hashes": [ + "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac", + "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603" + ], + "markers": "python_version >= '3.6'", + "version": "==3.8" + }, + "importlib-metadata": { + "hashes": [ + "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b", + "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7" + ], + "markers": "python_version >= '3.8'", + "version": "==8.5.0" + }, + "jaraco.classes": { + "hashes": [ + "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", + "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790" + ], + "markers": "python_version >= '3.8'", + "version": "==3.4.0" + }, + "jaraco.context": { + "hashes": [ + "sha256:9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3", + "sha256:f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4" + ], + "markers": "python_version >= '3.8'", + "version": "==6.0.1" + }, + "jaraco.functools": { + "hashes": [ + "sha256:3460c74cd0d32bf82b9576bbb3527c4364d5b27a21f5158a62aed6c4b42e23f5", + "sha256:c9d16a3ed4ccb5a889ad8e0b7a343401ee5b2a71cee6ed192d3f68bc351e94e3" + ], + "markers": "python_version >= '3.8'", + "version": "==4.0.2" + }, + "keyring": { + "hashes": [ + "sha256:8d85a1ea5d6db8515b59e1c5d1d1678b03cf7fc8b8dcfb1651e8c4a524eb42ef", + "sha256:8d963da00ccdf06e356acd9bf3b743208878751032d8599c6cc89eb51310ffae" + ], + "markers": "python_version >= '3.8'", + "version": "==25.3.0" + }, + "markdown-it-py": { + "hashes": [ + "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", + "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb" + ], + "markers": "python_version >= '3.8'", + "version": "==3.0.0" + }, + "mdurl": { + "hashes": [ + "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", + "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba" + ], + "markers": "python_version >= '3.7'", + "version": "==0.1.2" + }, + "more-itertools": { + "hashes": [ + "sha256:037b0d3203ce90cca8ab1defbbdac29d5f993fc20131f3664dc8d6acfa872aef", + "sha256:5482bfef7849c25dc3c6dd53a6173ae4795da2a41a80faea6700d9f5846c5da6" + ], + "markers": "python_version >= '3.8'", + "version": "==10.5.0" + }, + "nh3": { + "hashes": [ + "sha256:0411beb0589eacb6734f28d5497ca2ed379eafab8ad8c84b31bb5c34072b7164", + "sha256:14c5a72e9fe82aea5fe3072116ad4661af5cf8e8ff8fc5ad3450f123e4925e86", + "sha256:19aaba96e0f795bd0a6c56291495ff59364f4300d4a39b29a0abc9cb3774a84b", + "sha256:34c03fa78e328c691f982b7c03d4423bdfd7da69cd707fe572f544cf74ac23ad", + "sha256:36c95d4b70530b320b365659bb5034341316e6a9b30f0b25fa9c9eff4c27a204", + "sha256:3a157ab149e591bb638a55c8c6bcb8cdb559c8b12c13a8affaba6cedfe51713a", + "sha256:42c64511469005058cd17cc1537578eac40ae9f7200bedcfd1fc1a05f4f8c200", + "sha256:5f36b271dae35c465ef5e9090e1fdaba4a60a56f0bb0ba03e0932a66f28b9189", + "sha256:6955369e4d9f48f41e3f238a9e60f9410645db7e07435e62c6a9ea6135a4907f", + "sha256:7b7c2a3c9eb1a827d42539aa64091640bd275b81e097cd1d8d82ef91ffa2e811", + "sha256:8ce0f819d2f1933953fca255db2471ad58184a60508f03e6285e5114b6254844", + "sha256:94a166927e53972a9698af9542ace4e38b9de50c34352b962f4d9a7d4c927af4", + "sha256:a7f1b5b2c15866f2db413a3649a8fe4fd7b428ae58be2c0f6bca5eefd53ca2be", + "sha256:c8b3a1cebcba9b3669ed1a84cc65bf005728d2f0bc1ed2a6594a992e817f3a50", + "sha256:de3ceed6e661954871d6cd78b410213bdcb136f79aafe22aa7182e028b8c7307", + "sha256:f0eca9ca8628dbb4e916ae2491d72957fdd35f7a5d326b7032a345f111ac07fe" + ], + "version": "==0.2.18" + }, + "packaging": { + "hashes": [ + "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", + "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124" + ], + "markers": "python_version >= '3.8'", + "version": "==24.1" + }, + "pkginfo": { + "hashes": [ + "sha256:5df73835398d10db79f8eecd5cd86b1f6d29317589ea70796994d49399af6297", + "sha256:889a6da2ed7ffc58ab5b900d888ddce90bce912f2d2de1dc1c26f4cb9fe65097" + ], + "markers": "python_version >= '3.6'", + "version": "==1.10.0" + }, + "pygments": { + "hashes": [ + "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", + "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a" + ], + "markers": "python_version >= '3.8'", + "version": "==2.18.0" + }, + "pyproject-hooks": { + "hashes": [ + "sha256:4b37730834edbd6bd37f26ece6b44802fb1c1ee2ece0e54ddff8bfc06db86965", + "sha256:7ceeefe9aec63a1064c18d939bdc3adf2d8aa1988a510afec15151578b232aa2" + ], + "markers": "python_version >= '3.7'", + "version": "==1.1.0" + }, + "readme-renderer": { + "hashes": [ + "sha256:2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151", + "sha256:8712034eabbfa6805cacf1402b4eeb2a73028f72d1166d6f5cb7f9c047c5d1e1" + ], + "markers": "python_version >= '3.9'", + "version": "==44.0" + }, + "requests": { + "hashes": [ + "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", + "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==2.32.3" + }, + "requests-toolbelt": { + "hashes": [ + "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", + "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.0.0" + }, + "rfc3986": { + "hashes": [ + "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", + "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c" + ], + "markers": "python_version >= '3.7'", + "version": "==2.0.0" + }, + "rich": { + "hashes": [ + "sha256:1760a3c0848469b97b558fc61c85233e3dafb69c7a071b4d60c38099d3cd4c06", + "sha256:8260cda28e3db6bf04d2d1ef4dbc03ba80a824c88b0e7668a0f23126a424844a" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==13.8.1" + }, + "twine": { + "hashes": [ + "sha256:215dbe7b4b94c2c50a7315c0275d2258399280fbb7d04182c7e55e24b5f93997", + "sha256:9aa0825139c02b3434d913545c7b847a21c835e11597f5255842d457da2322db" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==5.1.1" + }, + "urllib3": { + "hashes": [ + "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", + "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9" + ], + "markers": "python_version >= '3.8'", + "version": "==2.2.3" + }, + "wheel": { + "hashes": [ + "sha256:2376a90c98cc337d18623527a97c31797bd02bad0033d41547043a1cbfbe448f", + "sha256:a29c3f2817e95ab89aa4660681ad547c0e9547f20e75b0562fe7723c9a2a9d49" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==0.44.0" + }, + "zipp": { + "hashes": [ + "sha256:9960cd8967c8f85a56f920d5d507274e74f9ff813a0ab8889a5b5be2daf44064", + "sha256:c22b14cc4763c5a5b04134207736c107db42e9d3ef2d9779d465f5f1bcba572b" + ], + "markers": "python_version >= '3.8'", + "version": "==3.20.1" + } + } +} diff --git a/README.rst b/README.rst index 9ddd301..91a8141 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,6 @@ -socketdev-python-sdk -############### +socket-python-sdk +################# Purpose ------- @@ -14,8 +14,13 @@ Initializing the module .. code-block:: - from socketdev import SocketDev - socket = SocketDev("REPLACE_ME") + from socketdev import socketdev + socket = socketdev(token="REPLACE_ME", timeout=30) + +**PARAMETERS:** + +- **token (str)** - The Socket API Key for your Organization +- **Timeout (int)** - The number of seconds to wait before failing the connection Supported Functions ------------------- @@ -28,8 +33,8 @@ Retrieve the Issues associated with a package and version. .. code-block:: - from socketdev import SocketDev - socket = SocketDev("REPLACE_ME") + from socketdev import socketdev + socket = socketdev(token="REPLACE_ME") print(socket.npm.issues("hardhat-gas-report", "1.1.25")) **PARAMETERS:** @@ -45,8 +50,8 @@ Retrieve the Issues associated with a package and version. .. code-block:: - from socketdev import SocketDev - socket = SocketDev("REPLACE_ME") + from socketdev import socketdev + socket = socketdev(token="REPLACE_ME") print(socket.npm.score("hardhat-gas-report", "1.1.25")) **PARAMETERS:** @@ -55,15 +60,15 @@ Retrieve the Issues associated with a package and version. - **version (str)** - The version of the NPM Package. dependencies.get(limit, offset) -"""""""""""""""""" +""""""""""""""""""""""""""""""" Retrieve the dependencies for the organization associated with the API Key **Usage:** .. code-block:: - from socketdev import SocketDev - socket = SocketDev("REPLACE_ME") + from socketdev import socketdev + socket = socketdev(token="REPLACE_ME") print(socket.dependencies.get(10, 0)) **PARAMETERS:** @@ -79,8 +84,8 @@ Retrieve the dependencies for the organization associated with the API Key .. code-block:: - from socketdev import SocketDev - socket = SocketDev("REPLACE_ME") + from socketdev import socketdev + socket = socketdev(token="REPLACE_ME") file_names = [ "path/to/package.json" ] @@ -103,8 +108,8 @@ Retrieve the Socket.dev org information .. code-block:: - from socketdev import SocketDev - socket = SocketDev("REPLACE_ME") + from socketdev import socketdev + socket = socketdev(token="REPLACE_ME") print(socket.org.get()) quota.get() @@ -115,8 +120,8 @@ Retrieve the the current quota available for your API Key .. code-block:: - from socketdev import SocketDev - socket = SocketDev("REPLACE_ME") + from socketdev import socketdev + socket = socketdev(token="REPLACE_ME") print(socket.quota.get()) report.list() @@ -127,9 +132,13 @@ Retrieve the list of all reports for the organization .. code-block:: - from socketdev import SocketDev - socket = SocketDev("REPLACE_ME") - print(socket.report.list()) + from socketdev import socketdev + socket = socketdev(token="REPLACE_ME") + print(socket.report.list(from_time=1726183485)) + +**PARAMETERS:** + +- **from_time (int)** - The Unix Timestamp in Seconds to limit the reports pulled report.delete(report_id) """""""""""""""""""""""" @@ -139,8 +148,8 @@ Delete the specified report .. code-block:: - from socketdev import SocketDev - socket = SocketDev("REPLACE_ME") + from socketdev import socketdev + socket = socketdev(token="REPLACE_ME") print(socket.report.delete("report-id")) **PARAMETERS:** @@ -155,8 +164,8 @@ Retrieve the information for a Project Health Report .. code-block:: - from socketdev import SocketDev - socket = SocketDev("REPLACE_ME") + from socketdev import socketdev + socket = socketdev(token="REPLACE_ME") print(socket.report.view("report_id")) **PARAMETERS:** @@ -171,8 +180,8 @@ Retrieve the supported types of manifest files for creating a report .. code-block:: - from socketdev import SocketDev - socket = SocketDev("REPLACE_ME") + from socketdev import socketdev + socket = socketdev(token="REPLACE_ME") print(socket.report.supported()) report.create(files) @@ -183,8 +192,8 @@ Create a new project health report with the provided files .. code-block:: - from socketdev import SocketDev - socket = SocketDev("REPLACE_ME") + from socketdev import socketdev + socket = socketdev(token="REPLACE_ME") files = [ "/path/to/manifest/package.json" ] @@ -202,8 +211,8 @@ Get a list of information about the tracked repositores .. code-block:: - from socketdev import SocketDev - socket = SocketDev("REPLACE_ME") + from socketdev import socketdev + socket = socketdev(token="REPLACE_ME") print(socket.repositories.get()) settings.get() @@ -214,20 +223,20 @@ Retrieve the Socket Organization Settings .. code-block:: - from socketdev import SocketDev - socket = SocketDev("REPLACE_ME") + from socketdev import socketdev + socket = socketdev(token="REPLACE_ME") print(socket.settings.get()) sbom.view(report_id) -"""""""""""""""""""""" +"""""""""""""""""""" Retrieve the information for a SBOM Report **Usage:** .. code-block:: - from socketdev import SocketDev - socket = SocketDev("REPLACE_ME") + from socketdev import socketdev + socket = socketdev(token="REPLACE_ME") print(socket.sbom.view("report_id")) **PARAMETERS:** @@ -235,15 +244,15 @@ Retrieve the information for a SBOM Report - **report_id (str)** - The report ID of the report to view purl.post(license, components) -"""""""""""""""""""""" +"""""""""""""""""""""""""""""" Retrieve the package information for a purl post **Usage:** .. code-block:: - from socketdev import SocketDev - socket = SocketDev("REPLACE_ME") + from socketdev import socketdev + socket = socketdev(token="REPLACE_ME") license = "true" components = [ { @@ -261,15 +270,15 @@ Retrieve the package information for a purl post - **components (array{dict})** - The components list of packages urls fullscans.get(org_slug) -"""""""""""""""""""""" +""""""""""""""""""""""" Retrieve the Fullscans information for around Organization **Usage:** .. code-block:: - from socketdev import SocketDev - socket = SocketDev("REPLACE_ME") + from socketdev import socketdev + socket = socketdev(token="REPLACE_ME") print(socket.fullscans.get("org_slug")) **PARAMETERS:** @@ -277,15 +286,15 @@ Retrieve the Fullscans information for around Organization - **org_slug (str)** - The organization name fullscans.post(files, params) -"""""""""""""""""""""" +""""""""""""""""""""""""""""" Create a full scan from a set of package manifest files. Returns a full scan including all SBOM artifacts. **Usage:** .. code-block:: - from socketdev import SocketDev - socket = SocketDev("REPLACE_ME") + from socketdev import socketdev + socket = socketdev(token="REPLACE_ME") files = [ "/path/to/manifest/package.json" ] @@ -335,15 +344,15 @@ Create a full scan from a set of package manifest files. Returns a full scan inc +------------------------+------------+-------------------------------------------------------------------------------+ fullscans.delete(org_slug, full_scan_id) -"""""""""""""""""""""" +"""""""""""""""""""""""""""""""""""""""" Delete an existing full scan. **Usage:** .. code-block:: - from socketdev import SocketDev - socket = SocketDev("REPLACE_ME") + from socketdev import socketdev + socket = socketdev(token="REPLACE_ME") print(socket.fullscans.delete(org_slug, full_scan_id)) **PARAMETERS:** @@ -352,15 +361,15 @@ Delete an existing full scan. - **full_scan_id (str)** - The ID of the full scan fullscans.stream(org_slug, full_scan_id) -"""""""""""""""""""""" +"""""""""""""""""""""""""""""""""""""""" Stream all SBOM artifacts for a full scan. **Usage:** .. code-block:: - from socketdev import SocketDev - socket = SocketDev("REPLACE_ME") + from socketdev import socketdev + socket = socketdev(token="REPLACE_ME") print(socket.fullscans.stream(org_slug, full_scan_id)) **PARAMETERS:** @@ -369,15 +378,15 @@ Stream all SBOM artifacts for a full scan. - **full_scan_id (str)** - The ID of the full scan fullscans.metadata(org_slug, full_scan_id) -"""""""""""""""""""""" +"""""""""""""""""""""""""""""""""""""""""" Get metadata for a single full scan **Usage:** .. code-block:: - from socketdev import SocketDev - socket = SocketDev("REPLACE_ME") + from socketdev import socketdev + socket = socketdev(token="REPLACE_ME") print(socket.fullscans.metadata(org_slug, full_scan_id)) **PARAMETERS:** diff --git a/pyproject.toml b/pyproject.toml index 7ef85f5..6c9d610 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,55 @@ [build-system] -requires = ["setuptools"] -build-backend = "setuptools.build_meta" \ No newline at end of file +requires = ["setuptools >= 61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "socket-sdk-python" +dynamic = ["version"] +requires-python = ">= 3.9" +dependencies = [ + 'requests' +] +readme = "README.rst" +license = {file = "LICENSE"} +description = "Socket Security Python SDK" +keywords = ["socketsecurity", "socket.dev", "sca", "oss", "security", "sdk"] +authors = [ + {name = "Douglas Coburn", email = "douglas@socket.dev"} +] +maintainers = [ + {name = "Douglas Coburn", email = "douglas@socket.dev"} +] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14" +] + +[project.urls] +Homepage = "https://github.com/socketdev/socket-sdk-python" + +[tool.setuptools.packages.find] +include = [ + "socketdev", + "socketdev.core", + "socketdev.dependencies", + "socketdev.fullscans", + "socketdev.npm", + "socketdev.openapi", + "socketdev.org", + "socketdev.purl", + "socketdev.quota", + "socketdev.report", + "socketdev.repositories", + "socketdev.sbom", + "socketdev.settings", + "socketdev.tools", +] + +[tool.setuptools.dynamic] +version = {attr = "socketdev.__version__"} \ No newline at end of file diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 0000000..4c61053 --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +VERSION=$(grep -o "__version__.*" socketdev/__init__.py | awk '{print $3}' | tr -d "'" | tr -d '\r') +ENABLE_PYPI_BUILD=$1 + +if [ -z $ENABLE_PYPI_BUILD ]; then + echo "$0 pypi-prod=enable" + printf "\tpypi-build: Build and publish a new version of the package to pypi. If disabled will push to test pypi" + exit +fi + +if [ "$ENABLE_PYPI_BUILD" = "pypi-prod=enable" ]; then + echo "Doing production build of version $VERSION" + python -m build --wheel --sdist + twine upload dist/*$VERSION* +else + echo "Doing test build of version $VERSION" + python -m build --wheel --sdist \ + && ls dist/*$VERSION*\ + && twine upload --repository testpypi "dist/*$VERSION*" +fi + diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index e48e254..0000000 --- a/setup.cfg +++ /dev/null @@ -1,17 +0,0 @@ -[metadata] - -name = socketdev-sdk -version = 0.0.1 -author = Douglas Coburn -author_email = douglas@dactbc.com -description = A python SDK for the socket.dev API -long_description = file: README.rst -long_description_content_type: text/x-rst -license = MIT -classifiers = - Programming Language :: Python :: 3 - -[options] -python_requires = >=3.6 -install_requires = - requests \ No newline at end of file diff --git a/socketdev/__init__.py b/socketdev/__init__.py index 5ffcfcc..2d9e48e 100644 --- a/socketdev/__init__.py +++ b/socketdev/__init__.py @@ -13,19 +13,22 @@ from socketdev.fullscans import FullScans from socketdev.repositories import Repositories from socketdev.settings import Settings -from socketdev.socket_classes import Dependency, Org, Response +from socketdev.core.classes import Response from socketdev.exceptions import APIKeyMissing, APIFailure, APIAccessDenied, APIInsufficientQuota, APIResourceNotFound __author__ = 'socket.dev' -__version__ = '0.0.1' +__version__ = '1.0.9' __all__ = [ - "SocketDev", + "socketdev" ] global encoded_key +encoded_key: str + api_url = "https://api.socket.dev/v0" +request_timeout = 30 log = logging.getLogger("socketdev") log.addHandler(logging.NullHandler()) @@ -40,15 +43,24 @@ def do_request( headers: dict = None, payload: [dict, str] = None, files: list = None, - method: str = "GET", -): + method: str = "GET" +) -> Response: + """ + Shared function for performing the requests against the API. + :param path: String path of the URL + :param headers: Optional dictionary of the headers to include in the request. Defaults to None + :param payload: Optional dictionary or string of the payload to POST. Defaults to None + :param files: Optional list of files to send. Defaults to None + :param method: Optional string of the method for the Request. Defaults to GET + """ + if encoded_key is None or encoded_key == "": raise APIKeyMissing if headers is None: headers = { 'Authorization': f"Basic {encoded_key}", - 'User-Agent': 'SocketPythonScript/0.0.1', + 'User-Agent': f'SocketPythonScript/{__version__}', "accept": "application/json" } url = f"{api_url}/{path}" @@ -58,7 +70,8 @@ def do_request( url, headers=headers, data=payload, - files=files + files=files, + timeout=request_timeout ) if response.status_code >= 400: raise APIFailure("Bad Request") @@ -80,10 +93,26 @@ def do_request( return response -class SocketDev: - def __init__(self, token: str): +class socketdev: + token: str + timeout: int + dependencies: Dependencies + npm: NPM + openapi: OpenAPI + org: Orgs + quota: Quota + report: Report + sbom: Sbom + purl: purl + fullscans: FullScans + repositories: Report + settings: Settings + + def __init__(self, token: str, timeout: int = 30): self.token = token + ":" encode_key(self.token) + self.timeout = timeout + socketdev.set_timeout(self.timeout) self.dependencies = Dependencies() self.npm = NPM() self.openapi = OpenAPI() @@ -95,3 +124,8 @@ def __init__(self, token: str): self.fullscans = FullScans() self.repositories = Repositories() self.settings = Settings() + + @staticmethod + def set_timeout(timeout: int): + global request_timeout + request_timeout = timeout diff --git a/socketdev/core/classes.py b/socketdev/core/classes.py new file mode 100644 index 0000000..3a21401 --- /dev/null +++ b/socketdev/core/classes.py @@ -0,0 +1,148 @@ +import json + + +class Score: + supplyChain: float + quality: float + maintenance: float + license: float + overall: float + vulnerability: float + + def __init__(self, **kwargs): + if kwargs: + for key, value in kwargs.items(): + setattr(self, key, value) + for score_name in self.__dict__: + score = getattr(self, score_name) + if score <= 1: + score = score * 100 + setattr(self, score_name, score) + + def __str__(self): + return json.dumps(self.__dict__) + + +class Package: + type: str + name: str + version: str + release: str + id: str + direct: bool + manifestFiles: list + author: list + size: int + score: dict + scores: Score + alerts: list + error_alerts: list + alert_counts: dict + topLevelAncestors: list + url: str + transitives: int + license: str + license_text: str + purl: str + + def __init__(self, **kwargs): + if kwargs: + for key, value in kwargs.items(): + setattr(self, key, value) + if not hasattr(self, "direct"): + self.direct = False + else: + if str(self.direct).lower() == "true": + self.direct = True + self.url = f"https://socket.dev/{self.type}/package/{self.name}/overview/{self.version}" + if hasattr(self, 'score'): + self.scores = Score(**self.score) + if not hasattr(self, "alerts"): + self.alerts = [] + if not hasattr(self, "topLevelAncestors"): + self.topLevelAncestors = [] + if not hasattr(self, "manifestFiles"): + self.manifestFiles = [] + if not hasattr(self, "transitives"): + self.transitives = 0 + if not hasattr(self, "author"): + self.author = [] + if not hasattr(self, "size"): + self.size = 0 + self.alert_counts = { + "critical": 0, + "high": 0, + "middle": 0, + "low": 0 + } + self.error_alerts = [] + if not hasattr(self, "license"): + self.license = "NoLicenseFound" + if not hasattr(self, "license_text"): + self.license_text = "" + self.url = f"https://socket.dev/{self.type}/package/{self.name}/overview/{self.version}" + self.purl = f"{self.type}/{self.name}@{self.version}" + + +class Dependency: + branch: str + id: int + name: str + type: str + version: str + namespace: str + repository: str + + def __init__(self, **kwargs): + if kwargs: + for key, value in kwargs.items(): + setattr(self, key, value) + + def __str__(self): + return json.dumps(self.__dict__) + + +class Org: + id: int + image: str + name: str + plan: str + + def __init__(self, **kwargs): + if kwargs: + for key, value in kwargs.items(): + setattr(self, key, value) + + def __str__(self): + return json.dumps(self.__dict__) + + +class Response: + text: str + error: bool + status_code: int + + def __init__(self, text: str, error: bool, status_code: int): + self.text = text + self.error = error + self.status_code = status_code + + def __str__(self): + return json.dumps(self.__dict__) + + def json(self): + return self.__dict__ + + +class DependGetData: + url: str + headers: dict + payload: str + + def __init__(self, **kwargs): + if kwargs: + for key, value in kwargs.items(): + setattr(self, key, value) + + def __str__(self): + return json.dumps(self.__dict__) diff --git a/socketdev/core/issues.py b/socketdev/core/issues.py new file mode 100644 index 0000000..d712056 --- /dev/null +++ b/socketdev/core/issues.py @@ -0,0 +1,2101 @@ +import json + + +__all__ = [ + "AllIssues", + "badEncoding", + "badSemver", + "badSemverDependency", + "bidi", + "binScriptConfusion", + "chronoAnomaly", + "criticalCVE", + "cve", + "debugAccess", + "deprecated", + "deprecatedException", + "explicitlyUnlicensedItem", + "unidentifiedLicense", + "noLicenseFound", + "copyleftLicense", + "nonpermissiveLicense", + "miscLicenseIssues", + "deprecatedLicense", + "didYouMean", + "dynamicRequire", + "emptyPackage", + "envVars", + "extraneousDependency", + "fileDependency", + "filesystemAccess", + "gitDependency", + "gitHubDependency", + "hasNativeCode", + "highEntropyStrings", + "homoglyphs", + "httpDependency", + "installScripts", + "gptSecurity", + "gptAnomaly", + "gptMalware", + "potentialVulnerability", + "invalidPackageJSON", + "invisibleChars", + "licenseChange", + "licenseException", + "longStrings", + "missingTarball", + "majorRefactor", + "malware", + "manifestConfusion", + "mediumCVE", + "mildCVE", + "minifiedFile", + "missingAuthor", + "missingDependency", + "missingLicense", + "mixedLicense", + "ambiguousClassifier", + "modifiedException", + "modifiedLicense", + "networkAccess", + "newAuthor", + "noAuthorData", + "noBugTracker", + "noREADME", + "noRepository", + "noTests", + "noV1", + "noWebsite", + "nonFSFLicense", + "nonOSILicense", + "nonSPDXLicense", + "notice", + "obfuscatedFile", + "obfuscatedRequire", + "peerDependency", + "semverAnomaly", + "shellAccess", + "shellScriptOverride", + "suspiciousString", + "telemetry", + "trivialPackage", + "troll", + "typeModuleCompatibility", + "uncaughtOptionalDependency", + "unclearLicense", + "shrinkwrap", + "unmaintained", + "unpublished", + "unresolvedRequire", + "unsafeCopyright", + "unstableOwnership", + "unusedDependency", + "urlStrings", + "usesEval", + "zeroWidth", + "floatingDependency", + "unpopularPackage", +] +class badEncoding: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Source files are encoded using a non-standard text encoding." + self.props = {"encoding": "Encoding"} + self.suggestion = "Ensure all published files are encoded using a standard encoding such as UTF8, UTF16, UTF32, SHIFT-JIS, etc." + self.title = "Bad text encoding" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What is bad text encoding?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class badSemver: + description: str + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Package version is not a valid semantic version (semver)." + self.suggestion = "All versions of all packages on npm should use use a valid semantic version. Publish a new version of the package with a valid semantic version. Semantic version ranges do not work with invalid semantic versions." + self.title = "Bad semver" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What is bad semver?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class badSemverDependency: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Package has dependencies with an invalid semantic version. This could be a sign of beta, low quality, or unmaintained dependencies." + self.props = {"packageName": "Package name", "packageVersion": "Package version"} + self.suggestion = "Switch to a version of the dependency with valid semver or override the dependency version if it is determined to be problematic." + self.title = "Bad dependency semver" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What is bad dependency semver?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class bidi: + description: str + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Source files contain bidirectional unicode control characters. This could indicate a Trojan source supply chain attack. See: trojansource.codes for more information." + self.suggestion = "Remove bidirectional unicode control characters, or clearly document what they are used for." + self.title = "Bidirectional unicode control characters" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What are bidirectional unicode control characters?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class binScriptConfusion: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "This package has multiple bin scripts with the same name. This can cause non-deterministic behavior when installing or could be a sign of a supply chain attack" + self.props = {"binScript": "Bin script"} + self.suggestion = "Consider removing one of the conflicting packages. Packages should only export bin scripts with their name" + self.title = "Bin script confusion" + self.emoji = "\ud83d\ude35\u200d\ud83d\udcab" + self.nextStepTitle = "What is bin script confusion?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class chronoAnomaly: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Semantic versions published out of chronological order." + self.props = {"prevChronoDate": "Previous chronological date", "prevChronoVersion": "Previous chronological version", "prevSemverDate": "Previous semver date", "prevSemverVersion": "Previous semver version"} + self.suggestion = "This could either indicate dependency confusion or a patched vulnerability." + self.title = "Chronological version anomaly" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What is a chronological version anomaly?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class criticalCVE: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Contains a Critical Common Vulnerability and Exposure (CVE)." + self.props = {"cveId": "CVE ID", "cwes": "CWEs", "cvss": "CVSS", "description": "Description", "firstPatchedVersionIdentifier": "Patched version", "ghsaId": "GHSA ID", "id": "Id", "severity": "Severity", "title": "Title", "url": "URL", "vulnerableVersionRange": "Vulnerable versions"} + self.suggestion = "Remove or replace dependencies that include known critical CVEs. Consumers can use dependency overrides or npm audit fix --force to remove vulnerable dependencies." + self.title = "Critical CVE" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What is a critical CVE?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class cve: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Contains a high severity Common Vulnerability and Exposure (CVE)." + self.props = {"cveId": "CVE ID", "cwes": "CWEs", "cvss": "CVSS", "description": "Description", "firstPatchedVersionIdentifier": "Patched version", "ghsaId": "GHSA ID", "id": "Id", "severity": "Severity", "title": "Title", "url": "URL", "vulnerableVersionRange": "Vulnerable versions"} + self.suggestion = "Remove or replace dependencies that include known high severity CVEs. Consumers can use dependency overrides or npm audit fix --force to remove vulnerable dependencies." + self.title = "High CVE" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What is a CVE?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class debugAccess: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Uses debug, reflection and dynamic code execution features." + self.props = {"module": "Module"} + self.suggestion = "Removing the use of debug will reduce the risk of any reflection and dynamic code execution." + self.title = "Debug access" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What is debug access?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class deprecated: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "The maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed." + self.props = {"reason": "Reason"} + self.suggestion = "Research the state of the package and determine if there are non-deprecated versions that can be used, or if it should be replaced with a new, supported solution." + self.title = "Deprecated" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What is a deprecated package?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class deprecatedException: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "(Experimental) Contains a known deprecated SPDX license exception." + self.props = {"comments": "Comments", "exceptionId": "Exception id"} + self.suggestion = "Fix the license so that it no longer contains deprecated SPDX license exceptions." + self.title = "Deprecated SPDX exception" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What is a deprecated SPDX exception?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class explicitlyUnlicensedItem: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "(Experimental) Something was found which is explicitly marked as unlicensed" + self.props = {"location": "Location"} + self.suggestion = "Manually review your policy on such materials" + self.title = "Explicitly Unlicensed Item" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What do I need to know about license files?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class unidentifiedLicense: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "(Experimental) Something that seems like a license was found, but its contents could not be matched with a known license" + self.props = {"comments": "Comments", "exceptionId": "Exception id", "location": "Location"} + self.suggestion = "Manually review the license contents." + self.title = "Unidentified License" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What do I need to know about license files?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class noLicenseFound: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "(Experimental) License information could not be found" + self.props = {"comments": "Comments", "exceptionId": "Exception id"} + self.suggestion = "Manually review the licensing" + self.title = "No License Found" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What do I need to know about license files?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class copyleftLicense: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "(Experimental) Copyleft license information was found" + self.props = {"comments": "Comments", "licenseId": "License Identifiers"} + self.suggestion = "Determine whether use of copyleft material works for you" + self.title = "Copyleft License" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What do I need to know about license files?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class nonpermissiveLicense: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "(Experimental) A license not known to be considered permissive was found" + self.props = {"comments": "Comments", "licenseId": "License Identifier"} + self.suggestion = "Determine whether use of material not offered under a known permissive license works for you" + self.title = "Non-permissive License" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What do I need to know about license files?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class miscLicenseIssues: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "(Experimental) A package's licensing information has fine-grained problems" + self.props = {"description": "Description", "location": "The location where the issue originates from"} + self.suggestion = "Determine whether use of material not offered under a known permissive license works for you" + self.title = "Nonpermissive License" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What do I need to know about license files?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class deprecatedLicense: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "(Experimental) License is deprecated which may have legal implications regarding the package's use." + self.props = {"licenseId": "License id"} + self.suggestion = "Update or change the license to a well-known or updated license." + self.title = "Deprecated license" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What is a deprecated license?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class didYouMean: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Package name is similar to other popular packages and may not be the package you want." + self.props = {"alternatePackage": "Alternate package", "downloads": "Downloads", "downloadsRatio": "Download ratio", "editDistance": "Edit distance"} + self.suggestion = "Use care when consuming similarly named packages and ensure that you did not intend to consume a different package. Malicious packages often publish using similar names as existing popular packages." + self.title = "Possible typosquat attack" + self.emoji = "\ud83e\uddd0" + self.nextStepTitle = "What is a typosquat?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class dynamicRequire: + description: str + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Dynamic require can indicate the package is performing dangerous or unsafe dynamic code execution." + self.suggestion = "Packages should avoid dynamic imports when possible. Audit the use of dynamic require to ensure it is not executing malicious or vulnerable code." + self.title = "Dynamic require" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What is dynamic require?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class emptyPackage: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Package does not contain any code. It may be removed, is name squatting, or the result of a faulty package publish." + self.props = {"linesOfCode": "Lines of code"} + self.suggestion = "Remove dependencies that do not export any code or functionality and ensure the package version includes all of the files it is supposed to." + self.title = "Empty package" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What is an empty package?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class envVars: + description: str + props: dict + suggestion: str + title: str + emoji: str + capabilityName: str + nextStepTitle: str + + def __init__(self): + self.description = "Package accesses environment variables, which may be a sign of credential stuffing or data theft." + self.props = {"envVars": "Environment variables"} + self.suggestion = "Packages should be clear about which environment variables they access, and care should be taken to ensure they only access environment variables they claim to." + self.title = "Environment variable access" + self.emoji = "\u26a0\ufe0f" + self.capabilityName = "environment" + self.nextStepTitle = "What is environment variable access?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class extraneousDependency: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Package optionally loads a dependency which is not specified within any of the package.json dependency fields. It may inadvertently be importing dependencies specified by other packages." + self.props = {"name": "Name"} + self.suggestion = "Specify all optionally loaded dependencies in optionalDependencies within package.json." + self.title = "Extraneous dependency" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What are extraneous dependencies?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class fileDependency: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Contains a dependency which resolves to a file. This can obfuscate analysis and serves no useful purpose." + self.props = {"filePath": "File path", "packageName": "Package name"} + self.suggestion = "Remove the dependency specified by a file resolution string from package.json and update any bare name imports that referenced it before to use relative path strings." + self.title = "File dependency" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What are file dependencies?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class filesystemAccess: + description: str + props: dict + suggestion: str + title: str + emoji: str + capabilityName: str + nextStepTitle: str + + def __init__(self): + self.description = "Accesses the file system, and could potentially read sensitive data." + self.props = {"module": "Module"} + self.suggestion = "If a package must read the file system, clarify what it will read and ensure it reads only what it claims to. If appropriate, packages can leave file system access to consumers and operate on data passed to it instead." + self.title = "Filesystem access" + self.emoji = "\u26a0\ufe0f" + self.capabilityName = "filesystem" + self.nextStepTitle = "What is filesystem access?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class gitDependency: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Contains a dependency which resolves to a remote git URL. Dependencies fetched from git URLs are not immutable can be used to inject untrusted code or reduce the likelihood of a reproducible install." + self.props = {"packageName": "Package name", "url": "URL"} + self.suggestion = "Publish the git dependency to npm or a private package repository and consume it from there." + self.title = "Git dependency" + self.emoji = "\ud83c\udf63" + self.nextStepTitle = "What are git dependencies?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class gitHubDependency: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Contains a dependency which resolves to a GitHub URL. Dependencies fetched from GitHub specifiers are not immutable can be used to inject untrusted code or reduce the likelihood of a reproducible install." + self.props = {"commitsh": "Commit-ish (commit, branch, tag or version)", "githubRepo": "GitHub repo", "githubUser": "GitHub user", "packageName": "Package name"} + self.suggestion = "Publish the GitHub dependency to npm or a private package repository and consume it from there." + self.title = "GitHub dependency" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What are GitHub dependencies?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class hasNativeCode: + description: str + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Contains native code which could be a vector to obscure malicious code, and generally decrease the likelihood of reproducible or reliable installs." + self.suggestion = "Ensure that native code bindings are expected. Consumers may consider pure JS and functionally similar alternatives to avoid the challenges and risks associated with native code bindings." + self.title = "Native code" + self.emoji = "\ud83e\udee3" + self.nextStepTitle = "What's wrong with native code?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class highEntropyStrings: + description: str + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Contains high entropy strings. This could be a sign of encrypted data, leaked secrets or obfuscated code." + self.suggestion = "Please inspect these strings to check if these strings are benign. Maintainers should clarify the purpose and existence of high entropy strings if there is a legitimate purpose." + self.title = "High entropy strings" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What are high entropy strings?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class homoglyphs: + description: str + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Contains unicode homoglyphs which can be used in supply chain confusion attacks." + self.suggestion = "Remove unicode homoglyphs if they are unnecessary, and audit their presence to confirm legitimate use." + self.title = "Unicode homoglyphs" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What are unicode homoglyphs?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class httpDependency: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Contains a dependency which resolves to a remote HTTP URL which could be used to inject untrusted code and reduce overall package reliability." + self.props = {"packageName": "Package name", "url": "URL"} + self.suggestion = "Publish the HTTP URL dependency to npm or a private package repository and consume it from there." + self.title = "HTTP dependency" + self.emoji = "\ud83e\udd69" + self.nextStepTitle = "What are http dependencies?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class installScripts: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Install scripts are run when the package is installed. The majority of malware in npm is hidden in install scripts." + self.props = {"script": "Script", "source": "Source"} + self.suggestion = "Packages should not be running non-essential scripts during install and there are often solutions to problems people solve with install scripts that can be run at publish time instead." + self.title = "Install scripts" + self.emoji = "\ud83d\udcdc" + self.nextStepTitle = "What is an install script?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class gptSecurity: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "AI has determined that this package may contain potential security issues or vulnerabilities." + self.props = {"notes": "AI-based analysis of the package's code and behavior", "confidence": "Confidence of this analysis", "severity": "Impact of this threat"} + self.suggestion = "An AI system identified potential security problems in this package. It is advised to review the package thoroughly and assess the potential risks before installation. You may also consider reporting the issue to the package maintainer or seeking alternative solutions with a stronger security posture." + self.title = "AI detected security risk" + self.emoji = "\ud83e\udd16" + self.nextStepTitle = "What are AI detected security risks?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class gptAnomaly: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "AI has identified unusual behaviors that may pose a security risk." + self.props = {"notes": "AI-based analysis of the package's code and behavior", "confidence": "Confidence of this analysis", "severity": "Impact of this threat", "risk": "Risk level"} + self.suggestion = "An AI system found a low-risk anomaly in this package. It may still be fine to use, but you should check that it is safe before proceeding." + self.title = "AI detected anomaly" + self.emoji = "\ud83e\udd14" + self.nextStepTitle = "What is an AI detected anomaly?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class gptMalware: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "AI has identified this package as malware. This is a strong signal that the package may be malicious." + self.props = {"notes": "AI-based analysis of the package's code and behavior", "confidence": "Confidence of this analysis", "severity": "Impact of this behavior"} + self.suggestion = "Given the AI system's identification of this package as malware, extreme caution is advised. It is recommended to avoid downloading or installing this package until the threat is confirmed or flagged as a false positive." + self.title = "AI detected potential malware" + self.emoji = "\ud83e\udd16" + self.nextStepTitle = "What is AI detected malware?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class potentialVulnerability: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Initial human review suggests the presence of a vulnerability in this package. It is pending further analysis and confirmation." + self.props = {"note": "AI detection + human review", "risk": "Risk level"} + self.suggestion = "It is advisable to proceed with caution. Engage in a review of the package's security aspects and consider reaching out to the package maintainer for the latest information or patches." + self.title = "Potential vulnerability" + self.emoji = "\ud83d\udea7" + self.nextStepTitle = "Navigating potential vulnerabilities" + + def __str__(self): + return json.dumps(self.__dict__) + + +class invalidPackageJSON: + description: str + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Package has an invalid manifest file and can cause installation problems if you try to use it." + self.suggestion = "Fix syntax errors in the manifest file and publish a new version. Consumers can use npm overrides to force a version that does not have this problem if one exists." + self.title = "Invalid manifest file" + self.emoji = "\ud83e\udd12" + self.nextStepTitle = "What is an invalid manifest file?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class invisibleChars: + description: str + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Source files contain invisible characters. This could indicate source obfuscation or a supply chain attack." + self.suggestion = "Remove invisible characters. If their use is justified, use their visible escaped counterparts." + self.title = "Invisible chars" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What are invisible characters?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class licenseChange: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "(Experimental) Package license has recently changed." + self.props = {"newLicenseId": "New license id", "prevLicenseId": "Previous license id"} + self.suggestion = "License changes should be reviewed carefully to inform ongoing use. Packages should avoid making major changes to their license type." + self.title = "License change" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What is a license change?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class licenseException: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "(Experimental) Contains an SPDX license exception." + self.props = {"comments": "Comments", "exceptionId": "Exception id"} + self.suggestion = "License exceptions should be carefully reviewed." + self.title = "License exception" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What is a license exception?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class longStrings: + description: str + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Contains long string literals, which may be a sign of obfuscated or packed code." + self.suggestion = "Avoid publishing or consuming obfuscated or bundled code. It makes dependencies difficult to audit and undermines the module resolution system." + self.title = "Long strings" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What's wrong with long strings?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class missingTarball: + description: str + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "This package is missing it's tarball. It could be removed from the npm registry or there may have been an error when publishing." + self.suggestion = "This package cannot be analyzed or installed due to missing data." + self.title = "Missing package tarball" + self.emoji = "\u2754" + self.nextStepTitle = "What is a missing tarball?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class majorRefactor: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Package has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes." + self.props = {"changedPercent": "Change percentage", "curSize": "Current amount of lines", "linesChanged": "Lines changed", "prevSize": "Previous amount of lines"} + self.suggestion = "Consider waiting before upgrading to see if any issues are discovered, or be prepared to scrutinize any bugs or subtle changes the major refactor may bring. Publishers my consider publishing beta versions of major refactors to limit disruption to parties interested in the new changes." + self.title = "Major refactor" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What is a major refactor?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class malware: + description: str + props: dict + title: str + suggestion: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "This package is malware. We have asked the package registry to remove it." + self.props = {"id": "Id", "note": "Note"} + self.title = "Known malware" + self.suggestion = "It is strongly recommended that malware is removed from your codebase." + self.emoji = "\u2620\ufe0f" + self.nextStepTitle = "What is known malware?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class manifestConfusion: + description: str + props: dict + title: str + suggestion: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "This package has inconsistent metadata. This could be malicious or caused by an error when publishing the package." + self.props = {"key": "Key", "description": "Description"} + self.title = "Manifest confusion" + self.suggestion = "Packages with inconsistent metadata may be corrupted or malicious." + self.emoji = "\ud83e\udd78" + self.nextStepTitle = "What is manifest confusion?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class mediumCVE: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Contains a medium severity Common Vulnerability and Exposure (CVE)." + self.props = {"cveId": "CVE ID", "cwes": "CWEs", "cvss": "CVSS", "description": "Description", "firstPatchedVersionIdentifier": "Patched version", "ghsaId": "GHSA ID", "id": "Id", "severity": "Severity", "title": "Title", "url": "URL", "vulnerableVersionRange": "Vulnerable versions"} + self.suggestion = "Remove or replace dependencies that include known medium severity CVEs. Consumers can use dependency overrides or npm audit fix --force to remove vulnerable dependencies." + self.title = "Medium CVE" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What is a medium CVE?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class mildCVE: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Contains a low severity Common Vulnerability and Exposure (CVE)." + self.props = {"cveId": "CVE ID", "cwes": "CWEs", "cvss": "CVSS", "description": "Description", "firstPatchedVersionIdentifier": "Patched version", "ghsaId": "GHSA ID", "id": "Id", "severity": "Severity", "title": "Title", "url": "URL", "vulnerableVersionRange": "Vulnerable versions"} + self.suggestion = "Remove or replace dependencies that include known low severity CVEs. Consumers can use dependency overrides or npm audit fix --force to remove vulnerable dependencies." + self.title = "Low CVE" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What is a mild CVE?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class minifiedFile: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "This package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code." + self.props = {"confidence": "Confidence"} + self.suggestion = "In many cases minified code is harmless, however minified code can be used to hide a supply chain attack. Consider not shipping minified code on npm." + self.title = "Minified code" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What's wrong with minified code?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class missingAuthor: + description: str + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "The package was published by an npm account that no longer exists." + self.suggestion = "Packages should have active and identified authors." + self.title = "Non-existent author" + self.emoji = "\ud83e\udee5" + self.nextStepTitle = "What is a non-existent author?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class missingDependency: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "A required dependency is not declared in package.json and may prevent the package from working." + self.props = {"name": "Name"} + self.suggestion = "The package should define the missing dependency inside of package.json and publish a new version. Consumers may have to install the missing dependency themselves as long as the dependency remains missing. If the dependency is optional, add it to optionalDependencies and handle the missing case." + self.title = "Missing dependency" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What is a missing dependency?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class missingLicense: + description: str + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "(Experimental) Package does not have a license and consumption legal status is unknown." + self.suggestion = "A new version of the package should be published that includes a valid SPDX license in a license file, package.json license field or mentioned in the README." + self.title = "Missing license" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What is a missing license?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class mixedLicense: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "(Experimental) Package contains multiple licenses." + self.props = {"licenseId": "License Ids"} + self.suggestion = "A new version of the package should be published that includes a single license. Consumers may seek clarification from the package author. Ensure that the license details are consistent across the LICENSE file, package.json license field and license details mentioned in the README." + self.title = "Mixed license" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What is a mixed license?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class ambiguousClassifier: + props: dict + description: str + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.props = {"classifier": "The classifier"} + self.description = "(Experimental) An ambiguous license classifier was found." + self.suggestion = "A specific license or licenses should be identified" + self.title = "Ambiguous License Classifier" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What is an ambiguous license classifier?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class modifiedException: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "(Experimental) Package contains a modified version of an SPDX license exception. Please read carefully before using this code." + self.props = {"comments": "Comments", "exceptionId": "Exception id", "similarity": "Similarity"} + self.suggestion = "Packages should avoid making modifications to standard license exceptions." + self.title = "Modified license exception" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What is a modified license exception?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class modifiedLicense: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "(Experimental) Package contains a modified version of an SPDX license. Please read carefully before using this code." + self.props = {"licenseId": "License id", "similarity": "Similarity"} + self.suggestion = "Packages should avoid making modifications to standard licenses." + self.title = "Modified license" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What is a modified license?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class networkAccess: + description: str + props: dict + suggestion: str + title: str + emoji: str + capabilityName: str + nextStepTitle: str + + def __init__(self): + self.description = "This module accesses the network." + self.props = {"module": "Module"} + self.suggestion = "Packages should remove all network access that is functionally unnecessary. Consumers should audit network access to ensure legitimate use." + self.title = "Network access" + self.emoji = "\u26a0\ufe0f" + self.capabilityName = "network" + self.nextStepTitle = "What is network access?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class newAuthor: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "A new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package." + self.props = {"newAuthor": "New author", "prevAuthor": "Previous author"} + self.suggestion = "Scrutinize new collaborator additions to packages because they now have the ability to publish code into your dependency tree. Packages should avoid frequent or unnecessary additions or changes to publishing rights." + self.title = "New author" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What is new author?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class noAuthorData: + description: str + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Package does not specify a list of contributors or an author in package.json." + self.suggestion = "Add a author field or contributors array to package.json." + self.title = "No contributors or author data" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "Why is contributor and author data important?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class noBugTracker: + description: str + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Package does not have a linked bug tracker in package.json." + self.suggestion = "Add a bugs field to package.json. https://docs.npmjs.com/cli/v8/configuring-npm/package-json#bugs" + self.title = "No bug tracker" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "Why are bug trackers important?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class noREADME: + description: str + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Package does not have a README. This may indicate a failed publish or a low quality package." + self.suggestion = "Add a README to to the package and publish a new version." + self.title = "No README" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "Why are READMEs important?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class noRepository: + description: str + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Package does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package." + self.suggestion = "Add a repository field to package.json. https://docs.npmjs.com/cli/v8/configuring-npm/package-json#repository" + self.title = "No repository" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "Why are missing repositories important?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class noTests: + description: str + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Package does not have any tests. This is a strong signal of a poorly maintained or low quality package." + self.suggestion = "Add tests and publish a new version of the package. Consumers may look for an alternative package with better testing." + self.title = "No tests" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What does no tests mean?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class noV1: + description: str + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Package is not semver >=1. This means it is not stable and does not support ^ ranges." + self.suggestion = "If the package sees any general use, it should begin releasing at version 1.0.0 or later to benefit from semver." + self.title = "No v1" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What is wrong with semver < v1?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class noWebsite: + description: str + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Package does not have a website." + self.suggestion = "Add a homepage field to package.json. https://docs.npmjs.com/cli/v8/configuring-npm/package-json#homepage" + self.title = "No website" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What is a missing website?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class nonFSFLicense: + description: str + props: dict + title: str + suggestion: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "(Experimental) Package has a non-FSF-approved license." + self.props = {"licenseId": "License id"} + self.title = "Non FSF license" + self.suggestion = "Consider the terms of the license for your given use case." + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What is a non FSF license?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class nonOSILicense: + description: str + props: dict + title: str + suggestion: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "(Experimental) Package has a non-OSI-approved license." + self.props = {"licenseId": "License id"} + self.title = "Non OSI license" + self.suggestion = "Consider the terms of the license for your given use case." + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What is a non OSI license?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class nonSPDXLicense: + description: str + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "(Experimental) Package contains a non-standard license somewhere. Please read carefully before using." + self.suggestion = "Package should adopt a standard SPDX license consistently across all license locations (LICENSE files, package.json license fields, and READMEs)." + self.title = "Non SPDX license" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What is a non SPDX license?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class notice: + description: str + title: str + suggestion: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "(Experimental) Package contains a legal notice. This could increase your exposure to legal risk when using this project." + self.title = "Legal notice" + self.suggestion = "Consider the implications of the legal notice for your given use case." + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What is a legal notice?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class obfuscatedFile: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Obfuscated files are intentionally packed to hide their behavior. This could be a sign of malware" + self.props = {"confidence": "Confidence"} + self.suggestion = "Packages should not obfuscate their code. Consider not using packages with obfuscated code" + self.title = "Obfuscated code" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What is obfuscated code?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class obfuscatedRequire: + description: str + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Package accesses dynamic properties of require and may be obfuscating code execution." + self.suggestion = "The package should not access dynamic properties of module. Instead use import or require directly." + self.title = "Obfuscated require" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What is obfuscated require?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class peerDependency: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Package specifies peer dependencies in package.json." + self.props = {"name": "Name"} + self.suggestion = "Peer dependencies are fragile and can cause major problems across version changes. Be careful when updating this dependency and its peers." + self.title = "Peer dependency" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What are peer dependencies?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class semverAnomaly: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Package semver skipped several versions, this could indicate a dependency confusion attack or indicate the intention of disruptive breaking changes or major priority shifts for the project." + self.props = {"newVersion": "New version", "prevVersion": "Previous version"} + self.suggestion = "Packages should follow semantic versions conventions by not skipping subsequent version numbers. Consumers should research the purpose of the skipped version number." + self.title = "Semver anomaly" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What are semver anomalies?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class shellAccess: + description: str + props: dict + suggestion: str + title: str + emoji: str + capabilityName: str + nextStepTitle: str + + def __init__(self): + self.description = "This module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code." + self.props = {"module": "Module"} + self.suggestion = "Packages should avoid accessing the shell which can reduce portability, and make it easier for malicious shell access to be introduced." + self.title = "Shell access" + self.emoji = "\u26a0\ufe0f" + self.capabilityName = "shell" + self.nextStepTitle = "What is shell access?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class shellScriptOverride: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "This package re-exports a well known shell command via an npm bin script. This is possibly a supply chain attack" + self.props = {"binScript": "Bin script"} + self.suggestion = "Packages should not export bin scripts which conflict with well known shell commands" + self.title = "Bin script shell injection" + self.emoji = "\ud83e\udd80" + self.nextStepTitle = "What is bin script shell injection?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class suspiciousString: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "This package contains suspicious text patterns which are commonly associated with bad behavior" + self.props = {"explanation": "Explanation", "pattern": "Pattern"} + self.suggestion = "The package code should be reviewed before installing" + self.title = "Suspicious strings" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What are suspicious strings?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class telemetry: + description: str + props: dict + title: str + suggestion: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "This package contains telemetry which tracks how it is used." + self.props = {"id": "Id", "note": "Note"} + self.title = "Telemetry" + self.suggestion = "Most telemetry comes with settings to disable it. Consider disabling telemetry if you do not want to be tracked." + self.emoji = "\ud83d\udcde" + self.nextStepTitle = "What is telemetry?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class trivialPackage: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Packages less than 10 lines of code are easily copied into your own project and may not warrant the additional supply chain risk of an external dependency." + self.props = {"linesOfCode": "Lines of code"} + self.suggestion = "Removing this package as a dependency and implementing its logic will reduce supply chain risk." + self.title = "Trivial Package" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What are trivial packages?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class troll: + description: str + props: dict + title: str + suggestion: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "This package is a joke, parody, or includes undocumented or hidden behavior unrelated to its primary function." + self.props = {"id": "Id", "note": "Note"} + self.title = "Protestware or potentially unwanted behavior" + self.suggestion = "Consider that consuming this package my come along with functionality unrelated to its primary purpose." + self.emoji = "\ud83e\uddcc" + self.nextStepTitle = "What is protestware?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class typeModuleCompatibility: + description: str + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Package is CommonJS, but has a dependency which is type: \"module\". The two are likely incompatible." + self.suggestion = "The package needs to switch to dynamic import on the esmodule dependency, or convert to esm itself. Consumers may experience errors resulting from this incompatibility." + self.title = "CommonJS depending on ESModule" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "Why can't CJS depend on ESM?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class uncaughtOptionalDependency: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Package uses an optional dependency without handling a missing dependency exception. If you install it without the optional dependencies then it could cause runtime errors." + self.props = {"name": "Name"} + self.suggestion = "Package should handle the loading of the dependency when it is not present, or convert the optional dependency into a regular dependency." + self.title = "Uncaught optional dependency" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "Why are uncaught optional dependencies?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class unclearLicense: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Package contains a reference to a license without a matching LICENSE file." + self.props = {"possibleLicenseId": "Possible license id"} + self.suggestion = "Add a LICENSE file that matches the license field in package.json. https://docs.npmjs.com/cli/v8/configuring-npm/package-json#license" + self.title = "Unclear license" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What are unclear licenses?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class shrinkwrap: + description: str + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Package contains a shrinkwrap file. This may allow the package to bypass normal install procedures." + self.suggestion = "Packages should never use npm shrinkwrap files due to the dangers they pose." + self.title = "NPM Shrinkwrap" + self.emoji = "\ud83e\uddca" + self.nextStepTitle = "What is a shrinkwrap file?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class unmaintained: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Package has not been updated in more than 5 years and may be unmaintained. Problems with the package may go unaddressed." + self.props = {"lastPublish": "Last publish"} + self.suggestion = "Package should publish periodic maintenance releases if they are maintained, or deprecate if they have no intention in further maintenance." + self.title = "Unmaintained" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What are unmaintained packages?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class unpublished: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Package version was not found on the registry. It may exist on a different registry and need to be configured to pull from that registry." + self.props = {"version": "The version that was not found"} + self.suggestion = "Packages can be removed from the registry by manually un-publishing, a security issue removal, or may simply never have been published to the registry. Reliance on these packages will cause problem when they are not found." + self.title = "Unpublished package" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What are unpublished packages?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class unresolvedRequire: + description: str + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Package imports a file which does not exist and may not work as is. It could also be importing a file that will be created at runtime which could be a vector for running malicious code." + self.suggestion = "Fix imports so that they require declared dependencies or existing files." + self.title = "Unresolved require" + self.emoji = "\ud83d\udd75\ufe0f" + self.nextStepTitle = "What is unresolved require?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class unsafeCopyright: + description: str + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "(Experimental) Package contains a copyright but no license. Using this package may expose you to legal risk." + self.suggestion = "Clarify the license type by adding a license field to package.json and a LICENSE file." + self.title = "Unsafe copyright" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What is unsafe copyright?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class unstableOwnership: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "A new collaborator has begun publishing package versions. Package stability and security risk may be elevated." + self.props = {"author": "Author"} + self.suggestion = "Try to reduce the amount of authors you depend on to reduce the risk to malicious actors gaining access to your supply chain. Packages should remove inactive collaborators with publishing rights from packages on npm." + self.title = "Unstable ownership" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What is unstable ownership?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class unusedDependency: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Package has unused dependencies. This package depends on code that it does not use. This can increase the attack surface for malware and slow down installation." + self.props = {"name": "Name", "version": "Version"} + self.suggestion = "Packages should only specify dependencies that they use directly." + self.title = "Unused dependency" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What are unused dependencies?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class urlStrings: + description: str + props: dict + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Package contains fragments of external URLs or IP addresses, which may indicate that it covertly exfiltrates data." + self.props = {"urlFragment": "URL Fragment"} + self.suggestion = "Avoid using packages that make connections to the network, since this helps to leak data." + self.title = "URL strings" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What are URL strings?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class usesEval: + description: str + props: dict + suggestion: str + title: str + emoji: str + capabilityName: str + nextStepTitle: str + + def __init__(self): + self.description = "Package uses eval() which is a dangerous function. This prevents the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior." + self.props = {"evalType": "Eval type"} + self.suggestion = "Avoid packages that use eval, since this could potentially execute any code." + self.title = "Uses eval" + self.emoji = "\u26a0\ufe0f" + self.capabilityName = "eval" + self.nextStepTitle = "What is eval?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class zeroWidth: + description: str + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "Package files contain zero width unicode characters. This could indicate a supply chain attack." + self.suggestion = "Packages should remove unnecessary zero width unicode characters and use their visible counterparts." + self.title = "Zero width unicode chars" + self.emoji = "\u26a0\ufe0f" + self.nextStepTitle = "What are zero width unicode characters?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class floatingDependency: + description: str + suggestion: str + title: str + emoji: str + nextStepTitle: str + props: dict + + def __init__(self): + self.description = "Package has a dependency with a floating version range. This can cause issues if the dependency publishes a new major version." + self.suggestion = "Packages should specify properly semver ranges to avoid version conflicts." + self.title = "Floating dependency" + self.emoji = "\ud83c\udf88" + self.nextStepTitle = "What are floating dependencies?" + self.props = {"dependency": "Dependency"} + + def __str__(self): + return json.dumps(self.__dict__) + + +class unpopularPackage: + description: str + suggestion: str + title: str + emoji: str + nextStepTitle: str + + def __init__(self): + self.description = "This package is not very popular." + self.suggestion = "Unpopular packages may have less maintenance and contain other problems." + self.title = "Unpopular package" + self.emoji = "\ud83c\udfda\ufe0f" + self.nextStepTitle = "What are unpopular packages?" + + def __str__(self): + return json.dumps(self.__dict__) + + +class AllIssues: + badEncoding: badEncoding + badSemver: badSemver + badSemverDependency: badSemverDependency + bidi: bidi + binScriptConfusion: binScriptConfusion + chronoAnomaly: chronoAnomaly + criticalCVE: criticalCVE + cve: cve + debugAccess: debugAccess + deprecated: deprecated + deprecatedException: deprecatedException + explicitlyUnlicensedItem: explicitlyUnlicensedItem + unidentifiedLicense: unidentifiedLicense + noLicenseFound: noLicenseFound + copyleftLicense: copyleftLicense + nonpermissiveLicense: nonpermissiveLicense + miscLicenseIssues: miscLicenseIssues + deprecatedLicense: deprecatedLicense + didYouMean: didYouMean + dynamicRequire: dynamicRequire + emptyPackage: emptyPackage + envVars: envVars + extraneousDependency: extraneousDependency + fileDependency: fileDependency + filesystemAccess: filesystemAccess + gitDependency: gitDependency + gitHubDependency: gitHubDependency + hasNativeCode: hasNativeCode + highEntropyStrings: highEntropyStrings + homoglyphs: homoglyphs + httpDependency: httpDependency + installScripts: installScripts + gptSecurity: gptSecurity + gptAnomaly: gptAnomaly + gptMalware: gptMalware + potentialVulnerability: potentialVulnerability + invalidPackageJSON: invalidPackageJSON + invisibleChars: invisibleChars + licenseChange: licenseChange + licenseException: licenseException + longStrings: longStrings + missingTarball: missingTarball + majorRefactor: majorRefactor + malware: malware + manifestConfusion: manifestConfusion + mediumCVE: mediumCVE + mildCVE: mildCVE + minifiedFile: minifiedFile + missingAuthor: missingAuthor + missingDependency: missingDependency + missingLicense: missingLicense + mixedLicense: mixedLicense + ambiguousClassifier: ambiguousClassifier + modifiedException: modifiedException + modifiedLicense: modifiedLicense + networkAccess: networkAccess + newAuthor: newAuthor + noAuthorData: noAuthorData + noBugTracker: noBugTracker + noREADME: noREADME + noRepository: noRepository + noTests: noTests + noV1: noV1 + noWebsite: noWebsite + nonFSFLicense: nonFSFLicense + nonOSILicense: nonOSILicense + nonSPDXLicense: nonSPDXLicense + notice: notice + obfuscatedFile: obfuscatedFile + obfuscatedRequire: obfuscatedRequire + peerDependency: peerDependency + semverAnomaly: semverAnomaly + shellAccess: shellAccess + shellScriptOverride: shellScriptOverride + suspiciousString: suspiciousString + telemetry: telemetry + trivialPackage: trivialPackage + troll: troll + typeModuleCompatibility: typeModuleCompatibility + uncaughtOptionalDependency: uncaughtOptionalDependency + unclearLicense: unclearLicense + shrinkwrap: shrinkwrap + unmaintained: unmaintained + unpublished: unpublished + unresolvedRequire: unresolvedRequire + unsafeCopyright: unsafeCopyright + unstableOwnership: unstableOwnership + unusedDependency: unusedDependency + urlStrings: urlStrings + usesEval: usesEval + zeroWidth: zeroWidth + floatingDependency: floatingDependency + unpopularPackage: unpopularPackage + def __init__(self): + self.badEncoding = badEncoding() + self.badSemver = badSemver() + self.badSemverDependency = badSemverDependency() + self.bidi = bidi() + self.binScriptConfusion = binScriptConfusion() + self.chronoAnomaly = chronoAnomaly() + self.criticalCVE = criticalCVE() + self.cve = cve() + self.debugAccess = debugAccess() + self.deprecated = deprecated() + self.deprecatedException = deprecatedException() + self.explicitlyUnlicensedItem = explicitlyUnlicensedItem() + self.unidentifiedLicense = unidentifiedLicense() + self.noLicenseFound = noLicenseFound() + self.copyleftLicense = copyleftLicense() + self.nonpermissiveLicense = nonpermissiveLicense() + self.miscLicenseIssues = miscLicenseIssues() + self.deprecatedLicense = deprecatedLicense() + self.didYouMean = didYouMean() + self.dynamicRequire = dynamicRequire() + self.emptyPackage = emptyPackage() + self.envVars = envVars() + self.extraneousDependency = extraneousDependency() + self.fileDependency = fileDependency() + self.filesystemAccess = filesystemAccess() + self.gitDependency = gitDependency() + self.gitHubDependency = gitHubDependency() + self.hasNativeCode = hasNativeCode() + self.highEntropyStrings = highEntropyStrings() + self.homoglyphs = homoglyphs() + self.httpDependency = httpDependency() + self.installScripts = installScripts() + self.gptSecurity = gptSecurity() + self.gptAnomaly = gptAnomaly() + self.gptMalware = gptMalware() + self.potentialVulnerability = potentialVulnerability() + self.invalidPackageJSON = invalidPackageJSON() + self.invisibleChars = invisibleChars() + self.licenseChange = licenseChange() + self.licenseException = licenseException() + self.longStrings = longStrings() + self.missingTarball = missingTarball() + self.majorRefactor = majorRefactor() + self.malware = malware() + self.manifestConfusion = manifestConfusion() + self.mediumCVE = mediumCVE() + self.mildCVE = mildCVE() + self.minifiedFile = minifiedFile() + self.missingAuthor = missingAuthor() + self.missingDependency = missingDependency() + self.missingLicense = missingLicense() + self.mixedLicense = mixedLicense() + self.ambiguousClassifier = ambiguousClassifier() + self.modifiedException = modifiedException() + self.modifiedLicense = modifiedLicense() + self.networkAccess = networkAccess() + self.newAuthor = newAuthor() + self.noAuthorData = noAuthorData() + self.noBugTracker = noBugTracker() + self.noREADME = noREADME() + self.noRepository = noRepository() + self.noTests = noTests() + self.noV1 = noV1() + self.noWebsite = noWebsite() + self.nonFSFLicense = nonFSFLicense() + self.nonOSILicense = nonOSILicense() + self.nonSPDXLicense = nonSPDXLicense() + self.notice = notice() + self.obfuscatedFile = obfuscatedFile() + self.obfuscatedRequire = obfuscatedRequire() + self.peerDependency = peerDependency() + self.semverAnomaly = semverAnomaly() + self.shellAccess = shellAccess() + self.shellScriptOverride = shellScriptOverride() + self.suspiciousString = suspiciousString() + self.telemetry = telemetry() + self.trivialPackage = trivialPackage() + self.troll = troll() + self.typeModuleCompatibility = typeModuleCompatibility() + self.uncaughtOptionalDependency = uncaughtOptionalDependency() + self.unclearLicense = unclearLicense() + self.shrinkwrap = shrinkwrap() + self.unmaintained = unmaintained() + self.unpublished = unpublished() + self.unresolvedRequire = unresolvedRequire() + self.unsafeCopyright = unsafeCopyright() + self.unstableOwnership = unstableOwnership() + self.unusedDependency = unusedDependency() + self.urlStrings = urlStrings() + self.usesEval = usesEval() + self.zeroWidth = zeroWidth() + self.floatingDependency = floatingDependency() + self.unpopularPackage = unpopularPackage() diff --git a/socketdev/report/__init__.py b/socketdev/report/__init__.py index efbc367..0c6376d 100644 --- a/socketdev/report/__init__.py +++ b/socketdev/report/__init__.py @@ -1,10 +1,22 @@ import socketdev +from datetime import datetime, timedelta, timezone class Report: @staticmethod - def list() -> dict: + def list(from_time: int = None) -> dict: + """ + This function will return all reports from time specified. + :param from_time: Unix epoch time in seconds. Will default to 30 days + """ + if from_time is None: + from_time = int((datetime.now(timezone.utc) - timedelta(days=30)).timestamp()) + else: + from_time = int((datetime.now(timezone.utc) - timedelta(seconds=from_time)).timestamp()) + path = "report/list" + if from_time is not None: + path += f"?from={from_time}" response = socketdev.do_request(path=path) if response.status_code == 200: reports = response.json() diff --git a/socketdev/sbom/__init__.py b/socketdev/sbom/__init__.py index f466b14..13f8e37 100644 --- a/socketdev/sbom/__init__.py +++ b/socketdev/sbom/__init__.py @@ -1,9 +1,11 @@ import socketdev import json +from socketdev.core.classes import Package + class Sbom: @staticmethod - def view(report_id: str) -> list: + def view(report_id: str) -> dict[str, dict]: path = f"sbom/view/{report_id}" response = socketdev.do_request(path=path) if response.status_code == 200: @@ -20,4 +22,30 @@ def view(report_id: str) -> list: sbom_dict[val['id']] = val else: sbom_dict = {} - return sbom_dict \ No newline at end of file + return sbom_dict + + @staticmethod + def create_packages_dict(sbom: dict[str, dict]) -> dict[str, Package]: + """ + Converts the SBOM Artifacts from the FulLScan into a Dictionary for parsing + :param sbom: list - Raw artifacts for the SBOM + :return: + """ + packages = {} + top_level_count = {} + for package_id in sbom: + item = sbom[package_id] + package = Package(**item) + if package.id in packages: + print(f"Duplicate package_id: {package_id}") + else: + packages[package.id] = package + for top_id in package.topLevelAncestors: + if top_id not in top_level_count: + top_level_count[top_id] = 1 + else: + top_level_count[top_id] += 1 + if len(top_level_count) > 0: + for package_id in top_level_count: + packages[package_id].transitives = top_level_count[package_id] + return packages diff --git a/socketdev/settings/__init__.py b/socketdev/settings/__init__.py index be4fa6a..b1e3106 100644 --- a/socketdev/settings/__init__.py +++ b/socketdev/settings/__init__.py @@ -1,11 +1,18 @@ +import json + import socketdev class Settings: @staticmethod - def get() -> dict: + def get(org_id: str) -> dict: path = f"settings" - response = socketdev.do_request(path=path) + payload = [ + { + "organization": org_id + } + ] + response = socketdev.do_request(path=path, method="POST", payload=json.dumps(payload)) if response.status_code == 200: settings = response.json() else: diff --git a/socketdev/socket_classes.py b/socketdev/socket_classes.py deleted file mode 100644 index 9156b1c..0000000 --- a/socketdev/socket_classes.py +++ /dev/null @@ -1,62 +0,0 @@ -import json - - -class Dependency: - branch: str - id: int - name: str - type: str - version: str - namespace: str - repository: str - - def __init__(self, **kwargs): - if kwargs: - for key, value in kwargs.items(): - setattr(self, key, value) - - def __str__(self): - return json.dumps(self.__dict__) - - -class Org: - id: int - image: str - name: str - plan: str - - def __init__(self, **kwargs): - if kwargs: - for key, value in kwargs.items(): - setattr(self, key, value) - - def __str__(self): - return json.dumps(self.__dict__) - - -class Response: - text: str - error: bool - status_code: int - - def __init__(self, text: str, error: bool, status_code: int): - self.text = text - self.error = error - self.status_code = status_code - - def __str__(self): - return json.dumps(self.__dict__) - - -class DependGetData: - url: str - headers: dict - payload: str - - def __init__(self, **kwargs): - if kwargs: - for key, value in kwargs.items(): - setattr(self, key, value) - - def __str__(self): - return json.dumps(self.__dict__)