Cross-Site Request Forgery (CSRF) in archivy/archivy
Description
archivy is vulnerable to Cross-Site Request Forgery (CSRF)
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
archivy is vulnerable to Cross-Site Request Forgery (CSRF) allowing attackers to perform unauthorized actions on behalf of authenticated users.
## Vulnerability archivy, a self-hosted knowledge base, is vulnerable to Cross-Site Request Forgery (CSRF) due to missing CSRF token validation on state-changing endpoints [1]. The vulnerability affects all versions prior to the commit 796c3ae (2021-12-25) [2]. The application did not use Flask-WTF's CSRFProtect and did not include CSRF tokens in forms, allowing attackers to forge requests on behalf of authenticated users.
Exploitation
An attacker can exploit this by crafting a malicious HTML page or link that, when visited by an authenticated archivy user, triggers a request to archivy endpoints such as the delete route (/dataobj/delete/) [2]. The original delete route accepted GET and DELETE methods, making it trivially exploitable via a simple `` tag or form submission. No authentication bypass is needed; the attacker only needs to lure a logged-in user to the malicious page.
Impact
Successful exploitation allows an attacker to perform arbitrary state-changing operations on the victim's archivy instance, including deleting data objects, modifying content, or executing other actions that the victim is authorized to perform [1]. This can lead to data loss, integrity compromise, and potential disruption of the knowledge base.
Mitigation
The fix was implemented in commit 796c3ae, which integrates Flask-WTF's CSRFProtect, adds CSRF token validation to all forms, and changes the delete route to only accept POST requests [2]. Users should update to a version containing this commit or apply the patch manually. No workaround is available other than upgrading. The vulnerability is not listed in CISA's Known Exploited Vulnerabilities catalog as of the publication date.
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
archivyPyPI | < 1.6.2 | 1.6.2 |
Affected products
2- archivy/archivy/archivyv5Range: unspecified
Patches
1796c3ae318eebetter CSRF protection; change delete route to POST
6 files changed · +9 −8
archivy/click_web/resources/cmd_exec.py+2 −4 modified@@ -137,10 +137,8 @@ def _get_download_link(field_info): class RequestToCommandArgs: def __init__(self): - field_infos = [ - FieldInfo.factory(key) - for key in list(request.form.keys()) + list(request.files.keys()) - ] + keys = [key for key in list(request.form.keys()) + list(request.files.keys())] + field_infos = [FieldInfo.factory(key) for key in keys if key != "csrf_token"] # important to sort them so they will be in expected order on command line self.field_infos = list(sorted(field_infos))
archivy/__init__.py+2 −0 modified@@ -6,6 +6,7 @@ from flask import Flask from flask_compress import Compress from flask_login import LoginManager +from flask_wtf.csrf import CSRFProtect from archivy import helpers from archivy.api import api_bp @@ -77,6 +78,7 @@ login_manager.login_view = "login" login_manager.init_app(app) app.register_blueprint(api_bp, url_prefix="/api") +csrf = CSRFProtect(app) # compress files Compress(app)
archivy/routes.py+1 −1 modified@@ -232,7 +232,7 @@ def move_item(dataobj_id): return redirect(f"/dataobj/{dataobj_id}") -@app.route("/dataobj/delete/<int:dataobj_id>", methods=["DELETE", "GET"]) +@app.route("/dataobj/delete/<int:dataobj_id>", methods=["POST"]) def delete_data(dataobj_id): try: data.delete_item(dataobj_id)
archivy/templates/click_web/command_form.html+1 −0 modified@@ -29,6 +29,7 @@ <h3 class="command-title">{{ command.name|title }}</h3> </div> {% endfor %} {% endfor %} + <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <button type="submit" id="submit_btn" class="btn btn-primary m-2">Run</button> </form>
archivy/templates/dataobjs/show.html+1 −1 modified@@ -71,7 +71,7 @@ <h2 id="post-title"> <svg class="octicon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M11.013 1.427a1.75 1.75 0 012.474 0l1.086 1.086a1.75 1.75 0 010 2.474l-8.61 8.61c-.21.21-.47.364-.756.445l-3.251.93a.75.75 0 01-.927-.928l.929-3.25a1.75 1.75 0 01.445-.758l8.61-8.61zm1.414 1.06a.25.25 0 00-.354 0L10.811 3.75l1.439 1.44 1.263-1.263a.25.25 0 000-.354l-1.086-1.086zM11.189 6.25L9.75 4.81l-6.286 6.287a.25.25 0 00-.064.108l-.558 1.953 1.953-.558a.249.249 0 00.108-.064l6.286-6.286z"></path></svg> <span>Edit</span> </button> - <form action="/dataobj/delete/{{ dataobj['id'] }}" method="delete" onsubmit="return confirm('Delete this item permanently?')" novalidate> + <form action="/dataobj/delete/{{ dataobj['id'] }}" method="POST" onsubmit="return confirm('Delete this item permanently?')" novalidate> {{ form.hidden_tag() }} <button class="btn btn-delete"> <svg class="octicon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M6.5 1.75a.25.25 0 01.25-.25h2.5a.25.25 0 01.25.25V3h-3V1.75zm4.5 0V3h2.25a.75.75 0 010 1.5H2.75a.75.75 0 010-1.5H5V1.75C5 .784 5.784 0 6.75 0h2.5C10.216 0 11 .784 11 1.75zM4.496 6.675a.75.75 0 10-1.492.15l.66 6.6A1.75 1.75 0 005.405 15h5.19c.9 0 1.652-.681 1.741-1.576l.66-6.6a.75.75 0 00-1.492-.149l-.66 6.6a.25.25 0 01-.249.225h-5.19a.25.25 0 01-.249-.225l-.66-6.6z"></path></svg>
tests/functional/test_routes.py+2 −2 modified@@ -61,12 +61,12 @@ def test_get_dataobj(test_app, client: FlaskClient, note_fixture): def test_get_delete_dataobj_not_found(test_app, client: FlaskClient): - response = client.get("/dataobj/delete/1") + response = client.post("/dataobj/delete/1") assert response.status_code == 302 def test_get_delete_dataobj(test_app, client: FlaskClient, note_fixture): - response = client.get("/dataobj/delete/1") + response = client.post("/dataobj/delete/1") assert response.status_code == 302
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-9236-8w7q-rmrvghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2021-4162ghsaADVISORY
- github.com/archivy/archivy/commit/796c3ae318eea183fc88c87ec5a27355b0f6a99dghsax_refsource_MISCWEB
- github.com/archivy/archivy/releases/tag/v1.6.2ghsaWEB
- github.com/pypa/advisory-database/tree/main/vulns/archivy/PYSEC-2021-869.yamlghsaWEB
- huntr.dev/bounties/e204a768-2129-4b6f-abad-e436309c7c32ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.