CVE-2026-45342
Description
LinkAce is a self-hosted archive to collect website links. Prior to 2.5.6, LinkAce contains an Insecure Direct Object Reference vulnerability in the authorization policy layer that allows any authenticated user to modify resources owned by other users. The affected resource types are links, lists, tags, and notes. Both the web UI and the REST API are vulnerable. The root cause is in the update() methods of all four model policies: LinkPolicy, LinkListPolicy, TagPolicy, and NotePolicy. Each delegates to an access-check method (e.g., userCanAccessLink()) that returns true for any resource with non-private visibility, regardless of who owns it. This means any registered user can edit any public or internal resource across the entire instance. The delete() methods in the same policy files correctly require ownership via $link->user->is($user), which confirms that update was intended to be owner-only. The same flaw exists in the API layer through AuthorizesUserApiActions::userCanUpdateModel(), which mirrors the broken visibility-only check instead of the ownership check used by userCanDeleteModel(). Bulk edit operations via BulkEditController are also affected. This vulnerability is fixed in 2.5.6.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
LinkAce 2.5.5 had an insecure direct object reference allowing any authenticated user to modify other users' public or internal links, lists, tags, and notes. Fixed in 2.5.6.
Vulnerability
LinkAce versions prior to 2.5.6 contain an Insecure Direct Object Reference (IDOR) vulnerability in the authorization policy layer. The update() methods in LinkPolicy, LinkListPolicy, TagPolicy, and NotePolicy delegate to access-check methods (e.g., userCanAccessLink()) that return true for any resource with non-private visibility, regardless of ownership [1]. This allows any authenticated user to modify resources—links, lists, tags, and notes—that are public or internal on the instance. The same flaw exists in the API layer through AuthorizesUserApiActions::userCanUpdateModel() [1]. Bulk edit operations via BulkEditController are also affected [1].
Exploitation
An attacker needs only a registered account on the LinkAce instance (no special roles) [1]. By sending a PATCH request to the web UI endpoint (e.g., /links/{id}) or the API endpoint (e.g., /api/v2/links/{id}) with a valid session cookie or Bearer token, the attacker can overwrite any public or internal resource. The advisory confirms that a regular user can modify an admin user's link URL, title, and description [1]. Same applies to tags and lists [1]. Delete endpoints correctly require ownership, isolating the bug to update operations [1].
Impact
An attacker can alter any public or internal resource's content, such as redirecting a link to a phishing or malware site while preserving the original owner's name [1]. Titles, descriptions, and visibility settings can be changed. On shared instances, this enables low-privilege users to silently corrupt other users' bookmarks at scale [1].
Mitigation
The vulnerability is fixed in LinkAce version 2.5.6 [1]. Users should upgrade to 2.5.6 or later. No workarounds are provided in the advisory. There is no indication that this CVE is listed on KEV.
AI Insight generated on May 28, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
2Patches
12c121b4b179aProhibit users from editing entities that do not belong to them
19 files changed · +205 −32
app/Http/Controllers/API/LinkController.php+2 −0 modified@@ -60,6 +60,8 @@ public function show(Request $request, ApiLink $link): JsonResponse public function update(LinkUpdateRequest $request, ApiLink $link): JsonResponse { + $this->authorize('update', $link); + $updatedLink = LinkRepository::update($link, $request->all()); return response()->json($updatedLink);
app/Http/Controllers/API/ListController.php+2 −0 modified@@ -53,6 +53,8 @@ public function show(ApiLinkList $list): JsonResponse public function update(ListUpdateRequest $request, ApiLinkList $list): JsonResponse { + $this->authorize('update', $list); + $updatedList = ListRepository::update($list, $request->all()); return response()->json($updatedList);
app/Http/Controllers/API/NoteController.php+2 −0 modified@@ -26,6 +26,8 @@ public function store(NoteStoreRequest $request): JsonResponse public function update(NoteUpdateRequest $request, ApiNote $note): JsonResponse { + $this->authorize('update', $note); + $updatedNote = NoteRepository::update($note, $request->validated()); return response()->json($updatedNote);
app/Http/Controllers/API/TagController.php+2 −0 modified@@ -50,6 +50,8 @@ public function show(ApiTag $tag): JsonResponse public function update(TagUpdateRequest $request, ApiTag $tag): JsonResponse { + $this->authorize('update', $tag); + $updatedTag = TagRepository::update($tag, $request->all()); return response()->json($updatedTag);
app/Policies/Api/AuthorizesUserApiActions.php+1 −1 modified@@ -34,7 +34,7 @@ protected function userCanUpdateModel(User $user, Model $model): bool } return $user->tokenCan($this->updateAbility); } - return $model->visibility !== ModelAttribute::VISIBILITY_PRIVATE; + return false; } protected function userCanDeleteModel(User $user, Model $model): bool
app/Policies/LinkListPolicy.php+1 −1 modified@@ -28,7 +28,7 @@ public function create(User $user): bool public function update(User $user, LinkList $list): bool { - return $this->userCanAccessList($user, $list); + return $list->user->is($user); } public function delete(User $user, LinkList $list): bool
app/Policies/LinkPolicy.php+1 −1 modified@@ -28,7 +28,7 @@ public function create(User $user): bool public function update(User $user, Link $link): bool { - return $this->userCanAccessLink($user, $link); + return $link->user->is($user); } public function delete(User $user, Link $link): bool
app/Policies/NotePolicy.php+1 −1 modified@@ -28,7 +28,7 @@ public function create(User $user): bool public function update(User $user, Note $note): bool { - return $this->userCanAccessNote($user, $note); + return $note->user->is($user); } public function delete(User $user, Note $note): bool
app/Policies/TagPolicy.php+1 −1 modified@@ -28,7 +28,7 @@ public function create(User $user): bool public function update(User $user, Tag $tag): bool { - return $this->userCanAccessTag($user, $tag); + return $tag->user->is($user); } public function delete(User $user, Tag $tag): bool
tests/Controller/API/BulkEditApiTest.php+55 −0 modified@@ -107,6 +107,27 @@ public function test_alternative_links_edit(): void $this->assertEquals(ModelAttribute::VISIBILITY_PRIVATE, $otherLink->visibility); } + public function test_links_edit_skips_visible_links_owned_by_other_users(): void + { + $otherUser = User::factory()->create(); + $otherPublicLink = Link::factory()->for($otherUser)->create(); + $otherInternalLink = Link::factory()->for($otherUser)->create([ + 'visibility' => ModelAttribute::VISIBILITY_INTERNAL, + ]); + + $this->patchJson('api/v2/bulk/links', [ + 'models' => [$otherPublicLink->id, $otherInternalLink->id], + 'tags' => [], + 'tags_mode' => 'append', + 'lists' => [], + 'lists_mode' => 'append', + 'visibility' => ModelAttribute::VISIBILITY_PRIVATE, + ])->assertExactJson([null, null]); + + $this->assertEquals(ModelAttribute::VISIBILITY_PUBLIC, $otherPublicLink->refresh()->visibility); + $this->assertEquals(ModelAttribute::VISIBILITY_INTERNAL, $otherInternalLink->refresh()->visibility); + } + public function test_lists_edit(): void { Log::shouldReceive('warning')->once(); @@ -128,6 +149,23 @@ public function test_lists_edit(): void $this->assertEquals(ModelAttribute::VISIBILITY_PRIVATE, $otherList->visibility); } + public function test_lists_edit_skips_visible_lists_owned_by_other_users(): void + { + $otherUser = User::factory()->create(); + $otherPublicList = LinkList::factory()->for($otherUser)->create(); + $otherInternalList = LinkList::factory()->for($otherUser)->create([ + 'visibility' => ModelAttribute::VISIBILITY_INTERNAL, + ]); + + $this->patchJson('api/v2/bulk/lists', [ + 'models' => [$otherPublicList->id, $otherInternalList->id], + 'visibility' => ModelAttribute::VISIBILITY_PRIVATE, + ])->assertExactJson([null, null]); + + $this->assertEquals(ModelAttribute::VISIBILITY_PUBLIC, $otherPublicList->refresh()->visibility); + $this->assertEquals(ModelAttribute::VISIBILITY_INTERNAL, $otherInternalList->refresh()->visibility); + } + public function test_alternative_lists_edit(): void { Log::shouldReceive('warning')->once(); @@ -170,6 +208,23 @@ public function test_tags_edit(): void $this->assertEquals(ModelAttribute::VISIBILITY_PRIVATE, $otherTag->visibility); } + public function test_tags_edit_skips_visible_tags_owned_by_other_users(): void + { + $otherUser = User::factory()->create(); + $otherPublicTag = Tag::factory()->for($otherUser)->create(); + $otherInternalTag = Tag::factory()->for($otherUser)->create([ + 'visibility' => ModelAttribute::VISIBILITY_INTERNAL, + ]); + + $this->patchJson('api/v2/bulk/tags', [ + 'models' => [$otherPublicTag->id, $otherInternalTag->id], + 'visibility' => ModelAttribute::VISIBILITY_PRIVATE, + ])->assertExactJson([null, null]); + + $this->assertEquals(ModelAttribute::VISIBILITY_PUBLIC, $otherPublicTag->refresh()->visibility); + $this->assertEquals(ModelAttribute::VISIBILITY_INTERNAL, $otherInternalTag->refresh()->visibility); + } + public function test_alternative_tags_edit(): void { Log::shouldReceive('warning')->once();
tests/Controller/API/LinkApiTest.php+4 −1 modified@@ -436,7 +436,7 @@ public function test_update_request(): void 'lists' => [$list->id], 'is_private' => false, 'check_disabled' => false, - ])->assertOk()->assertJson(['url' => 'https://new-internal-link.com']); + ])->assertForbidden(); $this->patchJsonAuthorized('api/v2/links/3', [ 'url' => 'https://new-internal-link.com', @@ -446,6 +446,9 @@ public function test_update_request(): void 'is_private' => false, 'check_disabled' => false, ])->assertForbidden(); + + $this->assertEquals('https://internal-link.com', Link::find(2)->url); + $this->assertEquals('https://private-link.com', Link::find(3)->url); } public function test_update_request_with_system_token(): void
tests/Controller/API/ListApiTest.php+4 −5 modified@@ -125,17 +125,16 @@ public function test_update_request(): void 'name' => 'Updated Internal List', 'description' => 'Custom Description', 'visibility' => 1, - ]) - ->assertOk() - ->assertJson([ - 'name' => 'Updated Internal List', - ]); + ])->assertForbidden(); $this->patchJsonAuthorized('api/v2/lists/3', [ 'name' => 'Updated Internal List', 'description' => 'Custom Description', 'visibility' => 1, ])->assertForbidden(); + + $this->assertEquals('Internal List', LinkList::find(2)->name); + $this->assertEquals('Private List', LinkList::find(3)->name); } public function test_invalid_update_request(): void
tests/Controller/API/NoteApiTest.php+14 −8 modified@@ -59,10 +59,17 @@ public function test_invalid_create_request(): void public function test_update_request(): void { - $this->createTestLinks(); + $testData = $this->createTestLinks(); + $otherUser = $testData[3]; Note::factory()->create(['link_id' => 1]); - Note::factory()->create(['link_id' => 2]); // Note for internal link of other user - Note::factory()->create(['link_id' => 3]); // Note for private link of other user + Note::factory()->for($otherUser)->create([ + 'link_id' => 2, + 'note' => 'Internal Note', + ]); // Note for internal link of other user + Note::factory()->for($otherUser)->create([ + 'link_id' => 3, + 'note' => 'Private Note', + ]); // Note for private link of other user $this->patchJsonAuthorized('api/v2/notes/1', [ 'note' => 'Gallia est omnis divisa in partes tres, quarum.', @@ -80,16 +87,15 @@ public function test_update_request(): void $this->patchJsonAuthorized('api/v2/notes/2', [ 'note' => 'Gallia est omnis divisa in partes tres, quarum.', 'visibility' => 1, - ]) - ->assertOk() - ->assertJson([ - 'note' => 'Gallia est omnis divisa in partes tres, quarum.', - ]); + ])->assertForbidden(); $this->patchJsonAuthorized('api/v2/notes/3', [ 'note' => 'Gallia est omnis divisa in partes tres, quarum.', 'visibility' => 1, ])->assertForbidden(); + + $this->assertEquals('Internal Note', Note::find(2)->note); + $this->assertEquals('Private Note', Note::find(3)->note); } public function test_invalid_update_request(): void
tests/Controller/API/TagApiTest.php+4 −5 modified@@ -124,17 +124,16 @@ public function test_update_request(): void $this->patchJsonAuthorized('api/v2/tags/2', [ 'name' => 'Updated Internal Tag', 'visibility' => 1, - ]) - ->assertOk() - ->assertJson([ - 'name' => 'Updated Internal Tag', - ]); + ])->assertForbidden(); $this->patchJsonAuthorized('api/v2/tags/3', [ 'name' => 'Updated Private Tag', 'visibility' => 1, ]) ->assertForbidden(); + + $this->assertEquals('Internal Tag', Tag::find(2)->name); + $this->assertEquals('Private Tag', Tag::find(3)->name); } public function test_invalid_update_request(): void
tests/Controller/Models/BulkEditControllerTest.php+64 −0 modified@@ -119,6 +119,32 @@ public function test_links_edit_without_taxonomy(): void $this->assertEmpty($links[2]->tags()->pluck('id')->toArray()); } + public function test_links_edit_skips_visible_links_owned_by_other_users(): void + { + $otherUser = User::factory()->create(); + $otherPublicLink = Link::factory()->for($otherUser)->create([ + 'url' => 'https://other-public-link.com', + ]); + $otherInternalLink = Link::factory()->for($otherUser)->create([ + 'url' => 'https://other-internal-link.com', + 'visibility' => ModelAttribute::VISIBILITY_INTERNAL, + ]); + + $this->post('bulk-edit/update-links', [ + 'models' => $otherPublicLink->id . ',' . $otherInternalLink->id, + 'tags' => json_encode([]), + 'tags_mode' => 'append', + 'lists' => json_encode([]), + 'lists_mode' => 'append', + 'visibility' => ModelAttribute::VISIBILITY_PRIVATE, + ]) + ->assertRedirect('links') + ->assertSessionHas('flash_notification.0.message', 'Successfully updated 0 Links out of 2 selected ones.'); + + $this->assertEquals(ModelAttribute::VISIBILITY_PUBLIC, $otherPublicLink->refresh()->visibility); + $this->assertEquals(ModelAttribute::VISIBILITY_INTERNAL, $otherInternalLink->refresh()->visibility); + } + public function test_alternative_links_edit(): void { Log::shouldReceive('warning')->once(); @@ -177,6 +203,25 @@ public function test_lists_edit(): void $this->assertEquals(ModelAttribute::VISIBILITY_PRIVATE, $otherList->visibility); } + public function test_lists_edit_skips_visible_lists_owned_by_other_users(): void + { + $otherUser = User::factory()->create(); + $otherPublicList = LinkList::factory()->for($otherUser)->create(); + $otherInternalList = LinkList::factory()->for($otherUser)->create([ + 'visibility' => ModelAttribute::VISIBILITY_INTERNAL, + ]); + + $this->post('bulk-edit/update-lists', [ + 'models' => $otherPublicList->id . ',' . $otherInternalList->id, + 'visibility' => ModelAttribute::VISIBILITY_PRIVATE, + ]) + ->assertRedirect('lists') + ->assertSessionHas('flash_notification.0.message', 'Successfully updated 0 Lists out of 2 selected ones.'); + + $this->assertEquals(ModelAttribute::VISIBILITY_PUBLIC, $otherPublicList->refresh()->visibility); + $this->assertEquals(ModelAttribute::VISIBILITY_INTERNAL, $otherInternalList->refresh()->visibility); + } + public function test_alternative_lists_edit(): void { Log::shouldReceive('warning')->once(); @@ -223,6 +268,25 @@ public function test_tags_edit(): void $this->assertEquals(ModelAttribute::VISIBILITY_PRIVATE, $otherTag->visibility); } + public function test_tags_edit_skips_visible_tags_owned_by_other_users(): void + { + $otherUser = User::factory()->create(); + $otherPublicTag = Tag::factory()->for($otherUser)->create(); + $otherInternalTag = Tag::factory()->for($otherUser)->create([ + 'visibility' => ModelAttribute::VISIBILITY_INTERNAL, + ]); + + $this->post('bulk-edit/update-tags', [ + 'models' => $otherPublicTag->id . ',' . $otherInternalTag->id, + 'visibility' => ModelAttribute::VISIBILITY_PRIVATE, + ]) + ->assertRedirect('tags') + ->assertSessionHas('flash_notification.0.message', 'Successfully updated 0 Tags out of 2 selected ones.'); + + $this->assertEquals(ModelAttribute::VISIBILITY_PUBLIC, $otherPublicTag->refresh()->visibility); + $this->assertEquals(ModelAttribute::VISIBILITY_INTERNAL, $otherInternalTag->refresh()->visibility); + } + public function test_alternative_tags_edit(): void { Log::shouldReceive('warning')->once();
tests/Controller/Models/LinkControllerTest.php+7 −4 modified@@ -383,7 +383,7 @@ public function test_edit_view(): void $this->createTestLinks(); $this->get('links/1/edit')->assertOk()->assertSee('https://public-link.com'); - $this->get('links/2/edit')->assertOk()->assertSee('https://internal-link.com'); + $this->get('links/2/edit')->assertForbidden(); $this->get('links/3/edit')->assertForbidden(); } @@ -427,7 +427,7 @@ public function test_update_response(): void 'tags' => null, 'visibility' => 1, 'check_disabled' => '0', - ])->assertRedirect('links/2'); + ])->assertForbidden(); $this->patch('links/3', [ 'url' => 'https://private-link.com', @@ -438,6 +438,9 @@ public function test_update_response(): void 'visibility' => 1, 'check_disabled' => '0', ])->assertForbidden(); + + $this->assertEquals('https://internal-link.com', Link::find(2)->url); + $this->assertEquals('https://private-link.com', Link::find(3)->url); } public function test_update_with_malicious_url(): void @@ -537,7 +540,7 @@ public function test_check_toggle_request(): void // Check other links $this->post('links/toggle-check/2', [ 'toggle' => '1', - ])->assertRedirect('links/2'); + ])->assertForbidden(); $this->post('links/toggle-check/3', ['toggle' => '1'])->assertForbidden(); } @@ -559,7 +562,7 @@ public function test_mark_working_request(): void $link = Link::first(); $this->post('links/mark-working/1')->assertRedirect('links/1'); - $this->post('links/mark-working/2')->assertRedirect('links/2'); + $this->post('links/mark-working/2')->assertForbidden(); $this->post('links/mark-working/3')->assertForbidden(); $this->assertEquals(Link::STATUS_OK, $link->refresh()->status);
tests/Controller/Models/ListControllerTest.php+5 −2 modified@@ -206,7 +206,7 @@ public function test_edit_view(): void $this->createTestLists(); $this->get('lists/1/edit')->assertOk()->assertSee('Public List')->assertSee('Edit List'); - $this->get('lists/2/edit')->assertOk()->assertSee('Internal List')->assertSee('Edit List'); + $this->get('lists/2/edit')->assertForbidden(); $this->get('lists/3/edit')->assertForbidden(); } @@ -233,13 +233,16 @@ public function test_update_response(): void 'list_id' => 2, 'name' => 'New Internal List', 'visibility' => 1, - ])->assertRedirect('lists/2'); + ])->assertForbidden(); $this->patch('lists/3', [ 'list_id' => $list->id, 'name' => 'New Test List', 'visibility' => 1, ])->assertForbidden(); + + $this->assertEquals('Internal List', LinkList::find(2)->name); + $this->assertEquals('Private List', LinkList::find(3)->name); } public function test_missing_model_error_for_update(): void
tests/Controller/Models/NoteControllerTest.php+30 −0 modified@@ -162,6 +162,36 @@ public function test_update_response(): void $this->assertEquals('Lorem ipsum dolor est updated', $note->refresh()->note); } + public function test_other_users_visible_notes_cannot_be_updated(): void + { + $link = Link::factory()->create(); + $otherUser = User::factory()->create(); + $publicNote = Note::factory()->for($otherUser)->create([ + 'link_id' => $link->id, + 'note' => 'Original public note', + ]); + $internalNote = Note::factory()->for($otherUser)->create([ + 'link_id' => $link->id, + 'note' => 'Original internal note', + 'visibility' => 2, + ]); + + $this->patch('notes/' . $publicNote->id, [ + 'link_id' => $link->id, + 'note' => 'Updated public note', + 'visibility' => 1, + ])->assertForbidden(); + + $this->patch('notes/' . $internalNote->id, [ + 'link_id' => $link->id, + 'note' => 'Updated internal note', + 'visibility' => 1, + ])->assertForbidden(); + + $this->assertEquals('Original public note', $publicNote->refresh()->note); + $this->assertEquals('Original internal note', $internalNote->refresh()->note); + } + public function test_missing_model_error_for_update(): void { $this->patch('notes/1', [
tests/Controller/Models/TagControllerTest.php+5 −2 modified@@ -197,7 +197,7 @@ public function test_edit_view(): void $this->createTestTags(); $this->get('tags/1/edit')->assertOk()->assertSee('Public Tag'); - $this->get('tags/2/edit')->assertOk()->assertSee('Internal Tag'); + $this->get('tags/2/edit')->assertForbidden(); $this->get('tags/3/edit')->assertForbidden(); } @@ -224,13 +224,16 @@ public function test_update_response(): void 'tag_id' => 2, 'name' => 'New Internal Tag', 'visibility' => 1, - ])->assertRedirect('tags/2'); + ])->assertForbidden(); $this->patch('tags/3', [ 'tag_id' => 3, 'name' => 'New Private Tag', 'visibility' => 1, ])->assertForbidden(); + + $this->assertEquals('Internal Tag', Tag::find(2)->name); + $this->assertEquals('Private Tag', Tag::find(3)->name); } public function test_missing_model_error_for_update(): void
Vulnerability mechanics
Root cause
"The `update()` methods in all four model policies and the API authorization trait authorize modifications based on resource visibility (non-private) instead of requiring ownership, conflating read access with write authorization."
Attack vector
An attacker who is authenticated as any registered user (no admin role required) can send a PATCH request — either via the web UI (e.g., `/links/1/edit`) or the REST API (e.g., `PATCH /api/v2/links/1`) — targeting a resource owned by another user, provided that resource has a non-private visibility (Public or Internal). The policy layer incorrectly authorizes the update based on visibility rather than ownership, so the server accepts the modification and overwrites the resource's URL, title, description, or other fields. The attacker can thus replace a legitimate link's URL with a phishing or malware destination while the original owner's name remains displayed [ref_id=1].
Affected code
The vulnerability resides in the `update()` methods of `app/Policies/LinkPolicy.php`, `app/Policies/LinkListPolicy.php`, `app/Policies/TagPolicy.php`, and `app/Policies/NotePolicy.php` (each at line 31), which delegate to an access-check method (e.g., `userCanAccessLink()`) that returns `true` for any resource with non-private visibility instead of checking ownership. The same flawed logic exists in `app/Policies/Api/AuthorizesUserApiActions.php` (line 37) in `userCanUpdateModel()`, and the `app/Http/Controllers/Models/BulkEditController.php` also routes through these broken policies [ref_id=1].
What the fix does
The advisory recommends replacing the visibility-based check in each policy's `update()` method with an ownership check identical to the one already used by the corresponding `delete()` method — specifically `$link->user->is($user)` for `LinkPolicy`, and the analogous pattern for `LinkListPolicy`, `TagPolicy`, and `NotePolicy`. For the API layer, `userCanUpdateModel()` in `AuthorizesUserApiActions.php` should be updated to match the ownership check already present in `userCanDeleteModel()`. If cross-user update access is ever needed for admin roles, that logic should be added as an explicit separate condition rather than conflating read visibility with write authorization [ref_id=1]. No patch diff is provided in the bundle, but the fix is confirmed to be released in version 2.5.6 [ref_id=1].
Preconditions
- authAttacker must be an authenticated user on the LinkAce instance (any role, no admin required)
- configTarget resource (link, list, tag, or note) must have non-private visibility (Public or Internal)
- inputAttacker must know or guess the target resource's ID
- networkNetwork access to the LinkAce web UI or REST API endpoint
Reproduction
1. Deploy LinkAce v2.5.5 and create an admin account (User A) and a regular user (User B). 2. As User A, create a link with visibility set to Public and note its ID (e.g., 1). 3. As User B, navigate to `/links/1/edit` — the edit form loads User A's link data without an authorization error. 4. Change the URL to `https://evil.example.com/phishing-page` and the title to "HIJACKED", then submit. The update succeeds. 5. As User A, visit `/links/1` — the link now displays attacker-controlled content while still showing User A as the owner. 6. To confirm via API: `curl -X PATCH http://<target>/api/v2/links/1 -H "Authorization: Bearer <user_b_token>" -H "Accept: application/json" -H "Content-Type: application/json" -d '{"url":"https://evil.example.com","title":"HIJACKED","visibility":1}'` returns 200 OK. 7. To confirm delete is correctly restricted: `curl -X DELETE http://<target>/api/v2/links/1` with User B's token returns 403 Forbidden [ref_id=1].
Generated on May 28, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
1News mentions
0No linked articles in our index yet.