From 9687e0661f88b2007b8919de7d7ae67ba84f6c1d Mon Sep 17 00:00:00 2001 From: Jeroen Willemsen Date: Mon, 20 Apr 2026 08:30:47 +0200 Subject: [PATCH 1/4] feat(sort_contibutors): migrate contributor generator to Go CLI --- scripts/sort_contibutors/Makefile | 37 ++ scripts/sort_contibutors/Pipfile | 11 - scripts/sort_contibutors/Pipfile.lock | 189 ------- .../__pycache__/main.cpython-311.pyc | Bin 5074 -> 0 bytes scripts/sort_contibutors/go.mod | 3 + scripts/sort_contibutors/main.go | 460 ++++++++++++++++++ scripts/sort_contibutors/main.py | 271 ----------- scripts/sort_contibutors/main_test.go | 149 ++++++ scripts/sort_contibutors/readme.md | 46 +- 9 files changed, 688 insertions(+), 478 deletions(-) create mode 100644 scripts/sort_contibutors/Makefile delete mode 100644 scripts/sort_contibutors/Pipfile delete mode 100644 scripts/sort_contibutors/Pipfile.lock delete mode 100644 scripts/sort_contibutors/__pycache__/main.cpython-311.pyc create mode 100644 scripts/sort_contibutors/go.mod create mode 100644 scripts/sort_contibutors/main.go delete mode 100644 scripts/sort_contibutors/main.py create mode 100644 scripts/sort_contibutors/main_test.go diff --git a/scripts/sort_contibutors/Makefile b/scripts/sort_contibutors/Makefile new file mode 100644 index 000000000..fb91374f9 --- /dev/null +++ b/scripts/sort_contibutors/Makefile @@ -0,0 +1,37 @@ +BINARY_NAME := sort-contributors +BUILD_DIR := bin +GO := go + +.PHONY: build run lint test vet security clean + +build: + mkdir -p $(BUILD_DIR) + $(GO) build -o $(BUILD_DIR)/$(BINARY_NAME) . + +run: build + ./$(BUILD_DIR)/$(BINARY_NAME) $(ARGS) + +lint: + @command -v golangci-lint >/dev/null 2>&1 || { \ + echo "golangci-lint not found. Install from https://golangci-lint.run/usage/install/"; \ + exit 1; \ + } + golangci-lint run ./... + +test: + $(GO) test ./... + +vet: + $(GO) vet ./... + +security: + @command -v gosec >/dev/null 2>&1 || { \ + echo "gosec not found. Installing with: go install github.com/securego/gosec/v2/cmd/gosec@latest"; \ + GOBIN=$$(pwd)/.tools $(GO) install github.com/securego/gosec/v2/cmd/gosec@latest || exit 1; \ + PATH=$$(pwd)/.tools:$$PATH gosec ./...; \ + exit $$?; \ + } + gosec ./... + +clean: + rm -rf $(BUILD_DIR) .tools diff --git a/scripts/sort_contibutors/Pipfile b/scripts/sort_contibutors/Pipfile deleted file mode 100644 index 0757494bb..000000000 --- a/scripts/sort_contibutors/Pipfile +++ /dev/null @@ -1,11 +0,0 @@ -[[source]] -url = "https://pypi.org/simple" -verify_ssl = true -name = "pypi" - -[packages] - -[dev-packages] - -[requires] -python_version = "3.11" diff --git a/scripts/sort_contibutors/Pipfile.lock b/scripts/sort_contibutors/Pipfile.lock deleted file mode 100644 index 8b082b79e..000000000 --- a/scripts/sort_contibutors/Pipfile.lock +++ /dev/null @@ -1,189 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "ed6d5d614626ae28e274e453164affb26694755170ccab3aa5866f093d51d3e4" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.11" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "certifi": { - "hashes": [ - "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", - "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7" - ], - "markers": "python_version >= '3.7'", - "version": "==2026.2.25" - }, - "charset-normalizer": { - "hashes": [ - "sha256:06a7e86163334edfc5d20fe104db92fcd666e5a5df0977cb5680a506fe26cc8e", - "sha256:0c173ce3a681f309f31b87125fecec7a5d1347261ea11ebbb856fa6006b23c8c", - "sha256:0e28d62a8fc7a1fa411c43bd65e346f3bce9716dc51b897fbe930c5987b402d5", - "sha256:0e901eb1049fdb80f5bd11ed5ea1e498ec423102f7a9b9e4645d5b8204ff2815", - "sha256:11afb56037cbc4b1555a34dd69151e8e069bee82e613a73bef6e714ce733585f", - "sha256:150b8ce8e830eb7ccb029ec9ca36022f756986aaaa7956aad6d9ec90089338c0", - "sha256:172985e4ff804a7ad08eebec0a1640ece87ba5041d565fff23c8f99c1f389484", - "sha256:197c1a244a274bb016dd8b79204850144ef77fe81c5b797dc389327adb552407", - "sha256:1ae6b62897110aa7c79ea2f5dd38d1abca6db663687c0b1ad9aed6f6bae3d9d6", - "sha256:1cf0a70018692f85172348fe06d3a4b63f94ecb055e13a00c644d368eb82e5b8", - "sha256:1ed80ff870ca6de33f4d953fda4d55654b9a2b340ff39ab32fa3adbcd718f264", - "sha256:22c6f0c2fbc31e76c3b8a86fba1a56eda6166e238c29cdd3d14befdb4a4e4815", - "sha256:231d4da14bcd9301310faf492051bee27df11f2bc7549bc0bb41fef11b82daa2", - "sha256:259695e2ccc253feb2a016303543d691825e920917e31f894ca1a687982b1de4", - "sha256:2a24157fa36980478dd1770b585c0f30d19e18f4fb0c47c13aa568f871718579", - "sha256:2b1a63e8224e401cafe7739f77efd3f9e7f5f2026bda4aead8e59afab537784f", - "sha256:2bd9d128ef93637a5d7a6af25363cf5dec3fa21cf80e68055aad627f280e8afa", - "sha256:2e1d8ca8611099001949d1cdfaefc510cf0f212484fe7c565f735b68c78c3c95", - "sha256:2ef7fedc7a6ecbe99969cd09632516738a97eeb8bd7258bf8a0f23114c057dab", - "sha256:2f7fdd9b6e6c529d6a2501a2d36b240109e78a8ceaef5687cfcfa2bbe671d297", - "sha256:30f445ae60aad5e1f8bdbb3108e39f6fbc09f4ea16c815c66578878325f8f15a", - "sha256:31215157227939b4fb3d740cd23fe27be0439afef67b785a1eb78a3ae69cba9e", - "sha256:34315ff4fc374b285ad7f4a0bf7dcbfe769e1b104230d40f49f700d4ab6bbd84", - "sha256:3516bbb8d42169de9e61b8520cbeeeb716f12f4ecfe3fd30a9919aa16c806ca8", - "sha256:3778fd7d7cd04ae8f54651f4a7a0bd6e39a0cf20f801720a4c21d80e9b7ad6b0", - "sha256:39f5068d35621da2881271e5c3205125cc456f54e9030d3f723288c873a71bf9", - "sha256:404a1e552cf5b675a87f0651f8b79f5f1e6fd100ee88dc612f89aa16abd4486f", - "sha256:419a9d91bd238052642a51938af8ac05da5b3343becde08d5cdeab9046df9ee1", - "sha256:423fb7e748a08f854a08a222b983f4df1912b1daedce51a72bd24fe8f26a1843", - "sha256:4482481cb0572180b6fd976a4d5c72a30263e98564da68b86ec91f0fe35e8565", - "sha256:461598cd852bfa5a61b09cae2b1c02e2efcd166ee5516e243d540ac24bfa68a7", - "sha256:47955475ac79cc504ef2704b192364e51d0d473ad452caedd0002605f780101c", - "sha256:48696db7f18afb80a068821504296eb0787d9ce239b91ca15059d1d3eaacf13b", - "sha256:4be9f4830ba8741527693848403e2c457c16e499100963ec711b1c6f2049b7c7", - "sha256:4d1d02209e06550bdaef34af58e041ad71b88e624f5d825519da3a3308e22687", - "sha256:4f41da960b196ea355357285ad1316a00099f22d0929fe168343b99b254729c9", - "sha256:517ad0e93394ac532745129ceabdf2696b609ec9f87863d337140317ebce1c14", - "sha256:51fb3c322c81d20567019778cb5a4a6f2dc1c200b886bc0d636238e364848c89", - "sha256:5273b9f0b5835ff0350c0828faea623c68bfa65b792720c453e22b25cc72930f", - "sha256:530d548084c4a9f7a16ed4a294d459b4f229db50df689bfe92027452452943a0", - "sha256:530e8cebeea0d76bdcf93357aa5e41336f48c3dc709ac52da2bb167c5b8271d9", - "sha256:54fae94be3d75f3e573c9a1b5402dc593de19377013c9a0e4285e3d402dd3a2a", - "sha256:572d7c822caf521f0525ba1bce1a622a0b85cf47ffbdae6c9c19e3b5ac3c4389", - "sha256:58c948d0d086229efc484fe2f30c2d382c86720f55cd9bc33591774348ad44e0", - "sha256:5d11595abf8dd942a77883a39d81433739b287b6aa71620f15164f8096221b30", - "sha256:5f8ddd609f9e1af8c7bd6e2aca279c931aefecd148a14402d4e368f3171769fd", - "sha256:5feb91325bbceade6afab43eb3b508c63ee53579fe896c77137ded51c6b6958e", - "sha256:60c74963d8350241a79cb8feea80e54d518f72c26db618862a8f53e5023deaf9", - "sha256:613f19aa6e082cf96e17e3ffd89383343d0d589abda756b7764cf78361fd41dc", - "sha256:659a1e1b500fac8f2779dd9e1570464e012f43e580371470b45277a27baa7532", - "sha256:695f5c2823691a25f17bc5d5ffe79fa90972cc34b002ac6c843bb8a1720e950d", - "sha256:69dd852c2f0ad631b8b60cfbe25a28c0058a894de5abb566619c205ce0550eae", - "sha256:6cceb5473417d28edd20c6c984ab6fee6c6267d38d906823ebfe20b03d607dc2", - "sha256:71be7e0e01753a89cf024abf7ecb6bca2c81738ead80d43004d9b5e3f1244e64", - "sha256:74119174722c4349af9708993118581686f343adc1c8c9c007d59be90d077f3f", - "sha256:74a2e659c7ecbc73562e2a15e05039f1e22c75b7c7618b4b574a3ea9118d1557", - "sha256:7504e9b7dc05f99a9bbb4525c67a2c155073b44d720470a148b34166a69c054e", - "sha256:79090741d842f564b1b2827c0b82d846405b744d31e84f18d7a7b41c20e473ff", - "sha256:7a6967aaf043bceabab5412ed6bd6bd26603dae84d5cb75bf8d9a74a4959d398", - "sha256:7bda6eebafd42133efdca535b04ccb338ab29467b3f7bf79569883676fc628db", - "sha256:7edbed096e4a4798710ed6bc75dcaa2a21b68b6c356553ac4823c3658d53743a", - "sha256:7f9019c9cb613f084481bd6a100b12e1547cf2efe362d873c2e31e4035a6fa43", - "sha256:802168e03fba8bbc5ce0d866d589e4b1ca751d06edee69f7f3a19c5a9fe6b597", - "sha256:80d0a5615143c0b3225e5e3ef22c8d5d51f3f72ce0ea6fb84c943546c7b25b6c", - "sha256:82060f995ab5003a2d6e0f4ad29065b7672b6593c8c63559beefe5b443242c3e", - "sha256:836ab36280f21fc1a03c99cd05c6b7af70d2697e374c7af0b61ed271401a72a2", - "sha256:8761ac29b6c81574724322a554605608a9960769ea83d2c73e396f3df896ad54", - "sha256:87725cfb1a4f1f8c2fc9890ae2f42094120f4b44db9360be5d99a4c6b0e03a9e", - "sha256:899d28f422116b08be5118ef350c292b36fc15ec2daeb9ea987c89281c7bb5c4", - "sha256:8bc5f0687d796c05b1e28ab0d38a50e6309906ee09375dd3aff6a9c09dd6e8f4", - "sha256:8bea55c4eef25b0b19a0337dc4e3f9a15b00d569c77211fa8cde38684f234fb7", - "sha256:8e5a94886bedca0f9b78fecd6afb6629142fd2605aa70a125d49f4edc6037ee6", - "sha256:90ca27cd8da8118b18a52d5f547859cc1f8354a00cd1e8e5120df3e30d6279e5", - "sha256:92734d4d8d187a354a556626c221cd1a892a4e0802ccb2af432a1d85ec012194", - "sha256:947cf925bc916d90adba35a64c82aace04fa39b46b52d4630ece166655905a69", - "sha256:95b52c68d64c1878818687a473a10547b3292e82b6f6fe483808fb1468e2f52f", - "sha256:97d0235baafca5f2b09cf332cc275f021e694e8362c6bb9c96fc9a0eb74fc316", - "sha256:9ca4c0b502ab399ef89248a2c84c54954f77a070f28e546a85e91da627d1301e", - "sha256:9cc4fc6c196d6a8b76629a70ddfcd4635a6898756e2d9cac5565cf0654605d73", - "sha256:9cc6e6d9e571d2f863fa77700701dae73ed5f78881efc8b3f9a4398772ff53e8", - "sha256:a056d1ad2633548ca18ffa2f85c202cfb48b68615129143915b8dc72a806a923", - "sha256:a26611d9987b230566f24a0a125f17fe0de6a6aff9f25c9f564aaa2721a5fb88", - "sha256:a4474d924a47185a06411e0064b803c68be044be2d60e50e8bddcc2649957c1f", - "sha256:a4ea868bc28109052790eb2b52a9ab33f3aa7adc02f96673526ff47419490e21", - "sha256:a9e68c9d88823b274cf1e72f28cb5dc89c990edf430b0bfd3e2fb0785bfeabf4", - "sha256:aa9cccf4a44b9b62d8ba8b4dd06c649ba683e4bf04eea606d2e94cfc2d6ff4d6", - "sha256:ab30e5e3e706e3063bc6de96b118688cb10396b70bb9864a430f67df98c61ecc", - "sha256:ac2393c73378fea4e52aa56285a3d64be50f1a12395afef9cce47772f60334c2", - "sha256:ad8faf8df23f0378c6d527d8b0b15ea4a2e23c89376877c598c4870d1b2c7866", - "sha256:b35b200d6a71b9839a46b9b7fff66b6638bb52fc9658aa58796b0326595d3021", - "sha256:b3694e3f87f8ac7ce279d4355645b3c878d24d1424581b46282f24b92f5a4ae2", - "sha256:b4ff1d35e8c5bd078be89349b6f3a845128e685e751b6ea1169cf2160b344c4d", - "sha256:bbc8c8650c6e51041ad1be191742b8b421d05bbd3410f43fa2a00c8db87678e8", - "sha256:bc72863f4d9aba2e8fd9085e63548a324ba706d2ea2c83b260da08a59b9482de", - "sha256:bf625105bb9eef28a56a943fec8c8a98aeb80e7d7db99bd3c388137e6eb2d237", - "sha256:c2274ca724536f173122f36c98ce188fd24ce3dad886ec2b7af859518ce008a4", - "sha256:c45a03a4c69820a399f1dda9e1d8fbf3562eda46e7720458180302021b08f778", - "sha256:c8ae56368f8cc97c7e40a7ee18e1cedaf8e780cd8bc5ed5ac8b81f238614facb", - "sha256:c907cdc8109f6c619e6254212e794d6548373cc40e1ec75e6e3823d9135d29cc", - "sha256:ca0276464d148c72defa8bb4390cce01b4a0e425f3b50d1435aa6d7a18107602", - "sha256:cd5e2801c89992ed8c0a3f0293ae83c159a60d9a5d685005383ef4caca77f2c4", - "sha256:d08ec48f0a1c48d75d0356cea971921848fb620fdeba805b28f937e90691209f", - "sha256:d1a2ee9c1499fc8f86f4521f27a973c914b211ffa87322f4ee33bb35392da2c5", - "sha256:d5f5d1e9def3405f60e3ca8232d56f35c98fb7bf581efcc60051ebf53cb8b611", - "sha256:d60377dce4511655582e300dc1e5a5f24ba0cb229005a1d5c8d0cb72bb758ab8", - "sha256:d73beaac5e90173ac3deb9928a74763a6d230f494e4bfb422c217a0ad8e629bf", - "sha256:d7de2637729c67d67cf87614b566626057e95c303bc0a55ffe391f5205e7003d", - "sha256:dad6e0f2e481fffdcf776d10ebee25e0ef89f16d691f1e5dee4b586375fdc64b", - "sha256:dda86aba335c902b6149a02a55b38e96287157e609200811837678214ba2b1db", - "sha256:df01808ee470038c3f8dc4f48620df7225c49c2d6639e38f96e6d6ac6e6f7b0e", - "sha256:e1f6e2f00a6b8edb562826e4632e26d063ac10307e80f7461f7de3ad8ef3f077", - "sha256:e25369dc110d58ddf29b949377a93e0716d72a24f62bad72b2b39f155949c1fd", - "sha256:e3c701e954abf6fc03a49f7c579cc80c2c6cc52525340ca3186c41d3f33482ef", - "sha256:e5bcc1a1ae744e0bb59641171ae53743760130600da8db48cbb6e4918e186e4e", - "sha256:e68c14b04827dd76dcbd1aeea9e604e3e4b78322d8faf2f8132c7138efa340a8", - "sha256:e8aeb10fcbe92767f0fa69ad5a72deca50d0dca07fbde97848997d778a50c9fe", - "sha256:e985a16ff513596f217cee86c21371b8cd011c0f6f056d0920aa2d926c544058", - "sha256:ecbbd45615a6885fe3240eb9db73b9e62518b611850fdf8ab08bd56de7ad2b17", - "sha256:ee4ec14bc1680d6b0afab9aea2ef27e26d2024f18b24a2d7155a52b60da7e833", - "sha256:ef5960d965e67165d75b7c7ffc60a83ec5abfc5c11b764ec13ea54fbef8b4421", - "sha256:f0cdaecd4c953bfae0b6bb64910aaaca5a424ad9c72d85cb88417bb9814f7550", - "sha256:f1ce721c8a7dfec21fcbdfe04e8f68174183cf4e8188e0645e92aa23985c57ff", - "sha256:f50498891691e0864dc3da965f340fada0771f6142a378083dc4608f4ea513e2", - "sha256:f5ea69428fa1b49573eef0cc44a1d43bebd45ad0c611eb7d7eac760c7ae771bc", - "sha256:f61aa92e4aad0be58eb6eb4e0c21acf32cf8065f4b2cae5665da756c4ceef982", - "sha256:f6e4333fb15c83f7d1482a76d45a0818897b3d33f00efd215528ff7c51b8e35d", - "sha256:f820f24b09e3e779fe84c3c456cb4108a7aa639b0d1f02c28046e11bfcd088ed", - "sha256:f98059e4fcd3e3e4e2d632b7cf81c2faae96c43c60b569e9c621468082f1d104", - "sha256:fcce033e4021347d80ed9c66dcf1e7b1546319834b74445f561d2e2221de5659" - ], - "markers": "python_version >= '3.7'", - "version": "==3.4.6" - }, - "idna": { - "hashes": [ - "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", - "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902" - ], - "markers": "python_version >= '3.8'", - "version": "==3.11" - }, - "requests": { - "hashes": [ - "sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b", - "sha256:c7ebc5e8b0f21837386ad0e1c8fe8b829fa5f544d8df3b2253bff14ef29d7652" - ], - "index": "pypi", - "markers": "python_version >= '3.10'", - "version": "==2.33.0" - }, - "urllib3": { - "hashes": [ - "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", - "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4" - ], - "markers": "python_version >= '3.9'", - "version": "==2.6.3" - } - }, - "develop": {} -} diff --git a/scripts/sort_contibutors/__pycache__/main.cpython-311.pyc b/scripts/sort_contibutors/__pycache__/main.cpython-311.pyc deleted file mode 100644 index c98e76ec99625c47b1c6ba7590fe418ae9c676ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5074 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\n\n", date) +} + +func renderHTML(createdDate string, leaders, topContributors, contributors, testers, specialThanks []Contributor) string { + var b strings.Builder + b.WriteString(renderCreatedComment(createdDate)) + b.WriteString("\n") + writeHTMLSection(&b, "OWASP Project Leaders", leaders) + writeHTMLSection(&b, "Top Contributors", topContributors) + writeHTMLSection(&b, "Contributors", contributors) + writeHTMLSection(&b, "Testers", testers) + writeHTMLSection(&b, "Special mentions for helping out", specialThanks) + b.WriteString("\n") + return b.String() +} + +func writeHTMLSection(b *strings.Builder, title string, users []Contributor) { + b.WriteString(title) + b.WriteString(":\n") + b.WriteString("
    \n") + for _, user := range users { + escUser := html.EscapeString(user.Username) + escName := html.EscapeString(user.Name) + fmt.Fprintf(b, "
  • %s @%s
  • \n", escUser, escName, escUser) + } + b.WriteString("
\n") +} + +func (a *app) getContributorsList() ([]Contributor, []Contributor, error) { + fmt.Println("[+] Fetching the Wrong Secrets CTF party contributors list ...") + ctfList, err := a.fetchRepository("wrongsecrets-ctf-party") + if err != nil { + return nil, nil, err + } + + fmt.Println("[+] Fetching the Wrong Secrets Binaries contributors list ...") + binariesList, err := a.fetchRepository("wrongsecrets-binaries") + if err != nil { + return nil, nil, err + } + + fmt.Println("[+] Fetching the Wrong Secrets contributors list ...") + wrongsecretsList, err := a.fetchRepository("wrongsecrets") + if err != nil { + return nil, nil, err + } + + merged := append(append(binariesList, ctfList...), wrongsecretsList...) + fmt.Println("[+] Sorting the list ..") + top, normal := mergeUsers(merged) + return top, normal, nil +} + +func (a *app) fetchRepository(project string) ([]Contributor, error) { + var contributors []Contributor + + for page := 1; ; page++ { + endpoint := fmt.Sprintf("%s/repos/OWASP/%s/contributors?per_page=100&page=%d", githubAPIBase, project, page) + statusCode, body, err := a.githubGet(endpoint) + if err != nil { + return nil, err + } + + if statusCode == http.StatusUnauthorized { + return nil, errInvalidToken + } + if statusCode == http.StatusForbidden { + return nil, fmt.Errorf("access denied while fetching %s; token may be missing repository access or is rate-limited", project) + } + if statusCode >= http.StatusBadRequest { + return nil, fmt.Errorf("failed to fetch %s contributors, status code %d", project, statusCode) + } + + var payload []contributorAPI + if err := json.Unmarshal(body, &payload); err != nil { + return nil, fmt.Errorf("decode contributors for %s: %w", project, err) + } + if len(payload) == 0 { + break + } + + contributors = append(contributors, a.parseContributorList(payload)...) + if len(payload) < 100 { + break + } + } + + return contributors, nil +} + +func (a *app) parseContributorList(items []contributorAPI) []Contributor { + contributors := make([]Contributor, 0, len(items)) + + for _, item := range items { + if item.Login == "" { + continue + } + if strings.Contains(item.Login, "[bot]") { + continue + } + if _, excluded := excludedContributors[item.Login]; excluded { + continue + } + + name, err := a.getFullName(item.Login) + if err != nil { + if errors.Is(err, errInvalidToken) { + fmt.Println("[!] Invalid token") + continue + } + fmt.Printf("[!] Failed to fetch user %s: %v\n", item.Login, err) + name = item.Login + } + if strings.TrimSpace(name) == "" { + name = item.Login + } + + contributors = append(contributors, Contributor{ + Username: item.Login, + Name: name, + Ranking: item.Contributions, + }) + } + + return contributors +} + +func (a *app) getFullName(username string) (string, error) { + if name, ok := knownNames[username]; ok { + return name, nil + } + if name, ok := a.nameCache[username]; ok { + return name, nil + } + + endpoint := fmt.Sprintf("%s/users/%s", githubAPIBase, url.PathEscape(username)) + statusCode, body, err := a.githubGet(endpoint) + if err != nil { + return username, err + } + + if statusCode == http.StatusUnauthorized { + return username, errInvalidToken + } + if statusCode == http.StatusNotFound { + return username, errors.New("not found") + } + if statusCode >= http.StatusBadRequest { + return username, fmt.Errorf("status code %d", statusCode) + } + + var payload struct { + Name string `json:"name"` + } + if err := json.Unmarshal(body, &payload); err != nil { + return username, err + } + if strings.TrimSpace(payload.Name) == "" { + a.nameCache[username] = username + return username, nil + } + + a.nameCache[username] = payload.Name + return payload.Name, nil +} + +func (a *app) githubGet(endpoint string) (int, []byte, error) { + if err := validateGitHubEndpoint(endpoint); err != nil { + return 0, nil, err + } + + req, err := http.NewRequest(http.MethodGet, endpoint, nil) + if err != nil { + return 0, nil, err + } + req.Header.Set("X-GitHub-Api-Version", "2022-11-28") + req.Header.Set("Accept", "application/vnd.github+json") + req.Header.Set("Authorization", "Bearer "+a.token) + + // #nosec G704 -- endpoint is validated in validateGitHubEndpoint before request execution. + resp, err := a.client.Do(req) + if err != nil { + return 0, nil, err + } + defer func() { + _ = resp.Body.Close() + }() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return resp.StatusCode, nil, err + } + + return resp.StatusCode, body, nil +} + +func validateGitHubEndpoint(endpoint string) error { + u, err := url.Parse(endpoint) + if err != nil { + return fmt.Errorf("invalid endpoint: %w", err) + } + + if u.Scheme != "https" { + return errors.New("invalid endpoint scheme") + } + + if !strings.EqualFold(u.Hostname(), "api.github.com") { + return errors.New("invalid endpoint host") + } + + return nil +} + +func mergeUsers(items []Contributor) ([]Contributor, []Contributor) { + byUser := map[string]Contributor{} + for _, item := range items { + current, ok := byUser[item.Username] + if !ok { + byUser[item.Username] = item + continue + } + current.Ranking += item.Ranking + if strings.TrimSpace(current.Name) == "" { + current.Name = item.Name + } + byUser[item.Username] = current + } + + merged := make([]Contributor, 0, len(byUser)) + for _, item := range byUser { + merged = append(merged, item) + } + + sort.SliceStable(merged, func(i, j int) bool { + if merged[i].Ranking == merged[j].Ranking { + return strings.ToLower(merged[i].Username) < strings.ToLower(merged[j].Username) + } + return merged[i].Ranking > merged[j].Ranking + }) + + top := make([]Contributor, 0, len(merged)) + normal := make([]Contributor, 0, len(merged)) + for _, item := range merged { + if item.Ranking >= 100 { + top = append(top, item) + continue + } + normal = append(normal, item) + } + return top, normal +} diff --git a/scripts/sort_contibutors/main.py b/scripts/sort_contibutors/main.py deleted file mode 100644 index 1b2ffeb48..000000000 --- a/scripts/sort_contibutors/main.py +++ /dev/null @@ -1,271 +0,0 @@ -import os -import requests - -# This function parses the contribution list, sorting -# the users per its ranks - - -def print_file(s: str, flag: bool) -> None: - # True for MD , false for HTML file - if flag: - f = open("contributors_file.md", "w") - f.write(s) - return - f = open("contributors_file.html", "w") - f.write(s) - - -def print_md(user_list: dict, label="") -> str: - string = "{}:\n\n".format(label) - for value in user_list: - string += "- [{} @{}](https://www.github.com/{})\n".format( - value["name"], value["username"], value["username"] - ) - return string + "\n" - - -def print_html( - leaders: dict, - top_contributors: dict, - contributors: dict, - testers: dict, - special_thanks: dict, -) -> str: - string = "\n" - - string += "OWASP Project Leaders:\n" - string += "
    \n" - for value in leaders: - string += "
  • {} @{}
  • \n".format( - value["username"], value["name"], value["username"] - ) - string += "
\n" - - string += "Top Contributors:\n" - string += "
    \n" - for value in top_contributors: - string += "
  • {} @{}
  • \n".format( - value["username"], value["name"], value["username"] - ) - string += "
\n" - - string += "Contributors:\n" - string += "
    \n" - for value in contributors: - string += "
  • {} @{}
  • \n".format( - value["username"], value["name"], value["username"] - ) - string += "
\n" - - string += "Testers:\n" - string += "
    \n" - for value in testers: - string += "
  • {} @{}
  • ".format( - value["username"], value["name"], value["username"] - ) - string += "
\n" - - string += "Special mentions for helping out:\n" - string += "
    \n" - for value in special_thanks: - string += "
  • {} @{}
  • \n".format( - value["username"], value["name"], value["username"] - ) - string += "
\n" - - string += "\n" - return string - - -def parse_contributor_list(user_list: list, user_token: str) -> list: - contributors = [] - - for element in user_list: - ranking = element["contributions"] - username = element["login"] - name = get_fullname(username, user_token) - if name == None: - name = username - - leaders_and_multijuicer = [ - "DerGut", - "bkimminich", - "MichaelEischer", - "rseedorff", - "jonasbg", - "scornelissen85", - "zadjadr", - "stuebingerb", - "sydseter", - "troygerber", - "skandix", - "saymolet", - "adrianeriksen", - "pseudobeard", - "coffemakingtoaster", - "wurstbrot", - "blucas-accela", - "fwijnholds", - "stefan-schaermeli", - "nickmalcolm", - "orangecola", - "commjoen", - "bendehaan", - "benno001", - ] - - # Filter the github bots - if "[bot]" not in username and username not in leaders_and_multijuicer: - contributors.append( - {"username": username, "name": name, "ranking": ranking} - ) - - return contributors - - -# Retrieves the list of fullnames of contributors of a repository in JSON format - - -def get_fullname(username: str, user_token: str) -> str: - name_dict = { - "puneeth072003": "Puneeth Y", - "f3rn0s": "Fern", - "Novice-expert": "Divyanshu Dev", - "neatzsche": "Chris Elbring Jr.", - } - if username in name_dict: - return name_dict[username] - headers = { - "X-GitHub-Api-Version": "2022-11-28", - "Accept": "application/vnd.github+json", - "Authorization": "Bearer " + user_token, - } - r = requests.get( - "https://api.github.com/users/" + username, headers=headers, timeout=20 - ) - if r.status_code == 401: - print("Invalid token") - os._exit(-1) - return r.json()["name"] - - -# Retrieves the list of contributors of a repository in JSON format - - -def fetch_repository(project: str, user_token: str) -> list: - headers = { - "X-GitHub-Api-Version": "2022-11-28", - "Accept": "application/vnd.github+json", - "Authorization": "Bearer " + user_token, - } - r = requests.get( - "https://api.github.com/repos/OWASP/" + project + "/contributors?per_page=100", - headers=headers, - timeout=20, - ) - if r.status_code == 401: - print("Invalid token") - os._exit(-1) - return parse_contributor_list(r.json(), token) - - -def merge_users(l: list) -> list: - username = dict() - ranking = dict() - - for a in l: - - if a["username"] in ranking: - ranking[a["username"]] += a["ranking"] - else: - ranking[a["username"]] = a["ranking"] - username[a["username"]] = { - "username": a["username"], - "name": a["name"], - "ranking": ranking[a["username"]], - } - - l = dict(sorted(username.items(), key=lambda x: x[1]["ranking"], reverse=True)) - - special_contributors = [] - contributors = [] - - for key, value in l.items(): - element = {"name": value["name"], "username": key, "ranking": value["ranking"]} - if element["ranking"] >= 100: - special_contributors.append(element) - else: - contributors.append(element) - return [special_contributors, contributors] - - -def get_contibutors_list(token: str) -> list: - print("[+] Fetching the Wrong Secrets CTF party contributors list ... ") - wrongsecrets_ctf_list = fetch_repository("wrongsecrets-ctf-party", token) - print("[+] Fetching the Wrong Secrets Binaries contributors list ... ") - wrongsecrets_binaries_list = fetch_repository("wrongsecrets-binaries", token) - print("[+] Fetching the Wrong Secrets contributors list ... ") - wrongsecrets_list = fetch_repository("wrongsecrets", token) - merged_list = wrongsecrets_binaries_list + wrongsecrets_ctf_list + wrongsecrets_list - print("[+] Sorting the list .. ") - return merge_users(merged_list) - - -# ============================================================= -# THE MAIN PROGRAM STARTS HERE -# ============================================================= - -# Make sure you load a user token in env -token = os.getenv("USER_TOKEN") - -if token is not None: - - contributors = [ - {"username": "f3rn0s", "name": "f3rn0s"}, - {"username": "szh", "name": "Shlomo Zalman Heigh"}, - ] - - testers = [ - {"username": "davevs", "name": "Dave van Stein"}, - {"username": "drnow4u", "name": "Marcin Nowak"}, - {"username": "mchangsp", "name": "Marc Chang Sing Pang"}, - {"username": "djvinnie", "name": "Vineeth Jagadeesh"}, - ] - leaders = [ - {"username": "bendehaan", "name": "Ben de Haan"}, - {"username": "commjoen", "name": "Jeroen Willemsen"}, - ] - special_thanks = [ - {"username": "madhuakula", "name": "Madhu Akula @madhuakula"}, - {"username": "nbaars", "name": "Nanne Baars @nbaars"}, - {"username": "bkimminich", "name": "Björn Kimminich"}, - {"username": "devsecops", "name": "Dan Gora"}, - {"username": "saragluna", "name": "Xiaolu Dai"}, - {"username": "jonathanGiles", "name": "Jonathan Giles"}, - ] - - l = get_contibutors_list(token) - special_list = l[0] - normal_list = l[1] - l = merge_users(special_list + normal_list) - - print("[+] Print to HTML file") - html_payload = print_html(leaders, l[0], l[1], testers, special_thanks) - print_file(html_payload, False) - - print("[+] Print to MD file") - md_payload = print_md(leaders, "Leaders") - md_payload += print_md(l[0], "Top contributors") - md_payload += print_md(l[1], "Contributors") - md_payload += print_md(testers, "Testers") - md_payload += print_md(special_thanks, "Special thanks") - print_file(md_payload, True) - - print("[+] Done") -else: - print( - "The variable USER_TOKEN does not exists. You must setup the variable" - "in the following way:" - ) - print("echo USER_TOKEN=github_pat_dfs1N_f>.env") - print('note that the USER_TOKEN requires "repository access" only') diff --git a/scripts/sort_contibutors/main_test.go b/scripts/sort_contibutors/main_test.go new file mode 100644 index 000000000..cd61b3293 --- /dev/null +++ b/scripts/sort_contibutors/main_test.go @@ -0,0 +1,149 @@ +package main + +import ( + "os" + "strings" + "testing" +) + +func TestResolveTokenPrecedence(t *testing.T) { + t.Parallel() + + env := func(key string) string { + switch key { + case "USER_TOKEN": + return "env-user" + case "GITHUB_TOKEN": + return "env-github" + default: + return "" + } + } + + token, err := resolveToken("flag-token", env) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if token != "flag-token" { + t.Fatalf("expected flag token, got %s", token) + } + + token, err = resolveToken("", env) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if token != "env-user" { + t.Fatalf("expected USER_TOKEN value, got %s", token) + } +} + +func TestResolveTokenMissing(t *testing.T) { + t.Parallel() + + token, err := resolveToken("", func(string) string { return "" }) + if err == nil { + t.Fatalf("expected an error, got nil and token=%s", token) + } +} + +func TestResolveOutputDir(t *testing.T) { + t.Parallel() + + outputDir, err := resolveOutputDir(".") + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if outputDir == "" { + t.Fatal("expected output directory path, got empty string") + } +} + +func TestResolveOutputDirMissing(t *testing.T) { + t.Parallel() + + outputDir, err := resolveOutputDir(" ") + if err == nil { + t.Fatalf("expected an error, got nil and outputDir=%s", outputDir) + } +} + +func TestMergeUsers(t *testing.T) { + t.Parallel() + + input := []Contributor{ + {Username: "alice", Name: "Alice", Ranking: 40}, + {Username: "bob", Name: "Bob", Ranking: 120}, + {Username: "alice", Name: "Alice", Ranking: 80}, + {Username: "charlie", Name: "Charlie", Ranking: 20}, + } + + top, normal := mergeUsers(input) + if len(top) != 2 { + t.Fatalf("expected 2 top contributors, got %d", len(top)) + } + if len(normal) != 1 { + t.Fatalf("expected 1 normal contributor, got %d", len(normal)) + } + if top[0].Username != "alice" || top[0].Ranking != 120 { + t.Fatalf("unexpected top[0]: %+v", top[0]) + } + if top[1].Username != "bob" || top[1].Ranking != 120 { + t.Fatalf("unexpected top[1]: %+v", top[1]) + } + if normal[0].Username != "charlie" { + t.Fatalf("unexpected normal contributor: %+v", normal[0]) + } +} + +func TestRenderMarkdown(t *testing.T) { + t.Parallel() + + payload := renderMarkdown([]Contributor{{Username: "octocat", Name: "The Cat"}}, "Contributors") + if !strings.Contains(payload, "Contributors:\n\n") { + t.Fatalf("payload missing section header: %s", payload) + } + if !strings.Contains(payload, "https://www.github.com/octocat") { + t.Fatalf("payload missing github url: %s", payload) + } +} + +func TestRenderCreatedComment(t *testing.T) { + t.Parallel() + + comment := renderCreatedComment("2026-04-20") + if comment != "\n\n" { + t.Fatalf("unexpected comment: %q", comment) + } +} + +func TestRenderHTMLIncludesCreatedComment(t *testing.T) { + t.Parallel() + + payload := renderHTML("2026-04-20", nil, nil, nil, nil, nil) + if !strings.HasPrefix(payload, "") { + t.Fatalf("html missing created comment prefix: %s", payload) + } +} + +func TestWriteFile(t *testing.T) { + t.Parallel() + + tmp, err := os.CreateTemp(t.TempDir(), "contributors-*.txt") + if err != nil { + t.Fatalf("create temp file: %v", err) + } + path := tmp.Name() + _ = tmp.Close() + + if err := writeFile(path, "hello"); err != nil { + t.Fatalf("writeFile returned error: %v", err) + } + + b, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read output file: %v", err) + } + if string(b) != "hello" { + t.Fatalf("unexpected file content: %s", string(b)) + } +} diff --git a/scripts/sort_contibutors/readme.md b/scripts/sort_contibutors/readme.md index 84d546881..eb183e6fb 100644 --- a/scripts/sort_contibutors/readme.md +++ b/scripts/sort_contibutors/readme.md @@ -3,23 +3,55 @@ ## About This script is used to generate the contributor-list for home.html and for the various markdown pages on Github where we list the contributors: [www-wrongsecrets](https://github.com/OWASP/www-project-wrongsecrets/blob/main/index.md#contributors) [wrongsecrets](https://github.com/OWASP/wrongsecrets/blob/master/README.md#special-thanks--contributors). +## Go version + +The script has been converted to a Go CLI that generates: +- `contributors_file.html` +- `contributors_file.md` + ## Installation and run 1. Make sure you get a fine grained access token from github to read the repositories (https://github.com/settings/tokens?type=beta): - go to https://github.com/settings/tokens?type=beta - create a token and make sure it has "repository access" only -- store the token in a safe secrets manager and export it before you run it (`export USER_TOKEN=github_pat`). +- store the token in a safe secrets manager and export it before you run it (`export USER_TOKEN=github_pat`) + +2. Build and run: + +```sh +make build +./bin/sort-contributors +``` + +3. You can also pass the token via CLI flag: + +```sh +./bin/sort-contributors -token github_pat +``` + +4. You can choose a custom output directory: + +```sh +./bin/sort-contributors -output-dir ./generated +``` -2. When on macos, install the requests library if you are using python 3.12 or higher: +5. The tool supports standard Go help output: ```sh -brew install pipenv -pipenv install requests +./bin/sort-contributors -h ``` -3. Now run the script; +6. You can run with Makefile (pass optional args through `ARGS`): ```sh -pipenv shell -python3 main.py +make run ARGS="-output-dir ./generated" ``` + +## Make targets + +- `make build`: compile binary to `bin/sort-contributors` +- `make run`: build and run the binary (supports `ARGS="..."`) +- `make lint`: run `golangci-lint` +- `make test`: run unit tests +- `make vet`: run `go vet` +- `make security`: run `gosec` From 73a2260647cce645e7c874fbabb8a6da6c0864e5 Mon Sep 17 00:00:00 2001 From: Jeroen Willemsen Date: Mon, 20 Apr 2026 08:31:16 +0200 Subject: [PATCH 2/4] ci(sort_contibutors): run Go checks on tool changes --- .github/workflows/sort-contributors-go.yml | 56 ++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 .github/workflows/sort-contributors-go.yml diff --git a/.github/workflows/sort-contributors-go.yml b/.github/workflows/sort-contributors-go.yml new file mode 100644 index 000000000..365e11ef1 --- /dev/null +++ b/.github/workflows/sort-contributors-go.yml @@ -0,0 +1,56 @@ +name: Sort Contributors Go Checks + +on: + push: + branches: + - master + paths: + - "scripts/sort_contibutors/**" + - ".github/workflows/sort-contributors-go.yml" + pull_request: + branches: [master] + paths: + - "scripts/sort_contibutors/**" + - ".github/workflows/sort-contributors-go.yml" + workflow_dispatch: + +permissions: + contents: read + +jobs: + go-checks: + name: build test lint security + runs-on: ubuntu-latest + defaults: + run: + working-directory: scripts/sort_contibutors + + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: scripts/sort_contibutors/go.mod + cache-dependency-path: scripts/sort_contibutors/go.mod + + - name: Build + run: make build + + - name: Test + run: make test + + - name: Vet + run: make vet + + - name: Install golangci-lint + run: | + go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest + echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH" + + - name: Lint + run: make lint + + - name: Security + run: make security From 4b9f7a05f38adf3abcdeb1a69e4e8b3c8f8fbb2e Mon Sep 17 00:00:00 2001 From: Jeroen Willemsen Date: Mon, 20 Apr 2026 08:31:49 +0200 Subject: [PATCH 3/4] chore(renovate): track sort_contibutors Go module updates --- renovate.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/renovate.json b/renovate.json index 2d225e022..ac13d64ad 100644 --- a/renovate.json +++ b/renovate.json @@ -94,6 +94,16 @@ ], "enabled": true }, + { + "groupName": "sort-contibutors-go", + "matchManagers": [ + "gomod" + ], + "matchFileNames": [ + "scripts/sort_contibutors/go.mod" + ], + "enabled": true + }, { "groupName": "pre-commit", "matchFileNames": [ From 96c5f549e99a64e6ccad38dee369a0dc97c97046 Mon Sep 17 00:00:00 2001 From: Jeroen Willemsen Date: Mon, 20 Apr 2026 08:32:15 +0200 Subject: [PATCH 4/4] docs(contributors): refresh contributor listings --- README.md | 3 +++ src/main/resources/templates/welcome.html | 24 +++++++++++------------ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 849bafe60..5cacf45df 100644 --- a/README.md +++ b/README.md @@ -417,6 +417,7 @@ Contributors: - [Adarsh A @adarsh-a-tw](https://www.github.com/adarsh-a-tw) - [Diamond Rivero @diamant3](https://www.github.com/diamant3) - [Norbert Wolniak @nwolniak](https://www.github.com/nwolniak) +- [Robert Felber @rfelber](https://www.github.com/rfelber) - [Filip Chyla @fchyla](https://www.github.com/fchyla) - [Dmitry Litosh @Dlitosh](https://www.github.com/Dlitosh) - [Vineeth Jagadeesh @djvinnie](https://www.github.com/djvinnie) @@ -429,6 +430,8 @@ Contributors: - [Madhu Akula @madhuakula](https://www.github.com/madhuakula) - [Mike Woudenberg @mikewoudenberg](https://www.github.com/mikewoudenberg) - [Spyros @northdpole](https://www.github.com/northdpole) +- [Seershan Mitra @seershan](https://www.github.com/seershan) +- [moeedrehman135 @moeedrehman135](https://www.github.com/moeedrehman135) - [RubenAtBinx @RubenAtBinx](https://www.github.com/RubenAtBinx) - [Alex Bender @alex-bender](https://www.github.com/alex-bender) - [Danny Lloyd @dannylloyd](https://www.github.com/dannylloyd) diff --git a/src/main/resources/templates/welcome.html b/src/main/resources/templates/welcome.html index a0b8edf5f..3e3931136 100644 --- a/src/main/resources/templates/welcome.html +++ b/src/main/resources/templates/welcome.html @@ -129,37 +129,37 @@
🚀 Ready to Start?
  • Marcin Nowak @drnow4u
  • Rodolfo Neves @roddas
  • Osama Magdy @osamamagdy
  • -
  • Shubham Patel @Shubham-Patel07 -
  • +
  • Pastekitoo @Pastekitoo
  • +
  • Shubham Patel @Shubham-Patel07
  • za @za
  • Divyanshu Dev @Novice-expert
  • -
  • Pastekitoo @Pastekitoo
  • Tibor Hercz @tiborhercz
  • -
  • Chris Elbring Jr. @neatzsche
  • Adarsh A @adarsh-a-tw
  • +
  • Chris Elbring Jr. @neatzsche
  • Diamond Rivero @diamant3
  • Norbert Wolniak @nwolniak
  • +
  • Robert Felber @rfelber
  • Filip Chyla @fchyla
  • -
  • Dmitry Litosh @Dlitosh
  • Vineeth Jagadeesh @djvinnie
  • -
  • Mahaputra Ilham Awal - @mahaputrailhamawal
  • +
  • Dmitry Litosh @Dlitosh
  • +
  • Mahaputra Ilham Awal @mahaputrailhamawal
  • Turjo Chowdhury @turjoc120
  • SndR @SndR85
  • -
  • Josh Grossman @tghosth
  • alphasec @alphasecio
  • CaduRoriz @CaduRoriz
  • +
  • Josh Grossman @tghosth
  • Madhu Akula @madhuakula
  • -
  • Mike Woudenberg @mikewoudenberg -
  • +
  • Mike Woudenberg @mikewoudenberg
  • Spyros @northdpole
  • +
  • moeedrehman135 @moeedrehman135
  • RubenAtBinx @RubenAtBinx
  • +
  • Seershan Mitra @seershan
  • Alex Bender @alex-bender
  • Danny Lloyd @dannylloyd
  • -
  • Nicolas Humblot @nhumblot
  • +
  • Fern @f3rn0s
  • Rick M @kingthorin
  • +
  • Nicolas Humblot @nhumblot
  • Shlomo Zalman Heigh @szh
  • -
  • Fern @f3rn0s
  • Jeff Tong @Wind010
  • Testers: