VYPR
Moderate severityNVD Advisory· Published Dec 25, 2021· Updated Aug 3, 2024

Cross-Site Request Forgery (CSRF) in archivy/archivy

CVE-2021-4162

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.

PackageAffected versionsPatched versions
archivyPyPI
< 1.6.21.6.2

Affected products

2
  • ghsa-coords
    Range: < 1.6.2
  • archivy/archivy/archivyv5
    Range: unspecified

Patches

1
796c3ae318ee

better CSRF protection; change delete route to POST

https://github.com/archivy/archivyUzay-GDec 24, 2021via ghsa
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

News mentions

0

No linked articles in our index yet.