diff --git a/db/migrations/6.0.19_tic_2807_add_cw_task_visibility.php b/db/migrations/6.0.19_tic_2807_add_cw_task_visibility.php
new file mode 100644
index 0000000000000000000000000000000000000000..4e06c33ebcbbeec8bdfeb0860bf2217949dbcc66
--- /dev/null
+++ b/db/migrations/6.0.19_tic_2807_add_cw_task_visibility.php
@@ -0,0 +1,26 @@
+<?php
+
+
+class Tic2807AddCWTaskVisibility extends Migration
+{
+    public function description()
+    {
+        return 'Adds a visibility column to courseware tasks.';
+    }
+
+    protected function up()
+    {
+        DBManager::get()->exec(
+            "ALTER TABLE `cw_tasks`
+            ADD COLUMN `visible` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0 AFTER `renewal_date`"
+        );
+    }
+
+    protected function down()
+    {
+        DBManager::get()->exec(
+            "ALTER TABLE `cw_tasks`
+            DROP COLUMN `visible`"
+        );
+    }
+}
diff --git a/lib/classes/JsonApi/Routes/Courseware/Authority.php b/lib/classes/JsonApi/Routes/Courseware/Authority.php
index 2acf83e3a6ba54ce389dfb7c7ffac0fd226614e3..7ed609fc65d5a5580c014851f0aa3ee94ec930de 100644
--- a/lib/classes/JsonApi/Routes/Courseware/Authority.php
+++ b/lib/classes/JsonApi/Routes/Courseware/Authority.php
@@ -324,7 +324,7 @@ class Authority
 
     public static function canShowTask(User $user, Task $resource): bool
     {
-        return self::canUpdateTask($user, $resource);
+        return self::canUpdateTask($user, $resource) || $resource->visible;
     }
 
     public static function canIndexTasks(User $user): bool
diff --git a/lib/classes/JsonApi/Routes/Courseware/TasksUpdate.php b/lib/classes/JsonApi/Routes/Courseware/TasksUpdate.php
index 33b51ad1ae8b50f5ab9f2647b5a027c981f1c542..dd9f648f295146f4a03c01d36899cd1fefef065f 100644
--- a/lib/classes/JsonApi/Routes/Courseware/TasksUpdate.php
+++ b/lib/classes/JsonApi/Routes/Courseware/TasksUpdate.php
@@ -81,6 +81,10 @@ class TasksUpdate extends JsonApiController
             $resource->requestRenewal();
         }
 
+        if (self::arrayHas($json, 'data.attributes.visible')) {
+            $resource->setVisibility(self::arrayGet($json, 'data.attributes.visible'));
+        }
+
         return $resource;
     }
 
diff --git a/lib/classes/JsonApi/Schemas/Courseware/Task.php b/lib/classes/JsonApi/Schemas/Courseware/Task.php
index 81c7a0d18a55f55985022c35be32bd192641a6b7..c612333886cfd7c270eee6aec3b7c009cced1f8d 100644
--- a/lib/classes/JsonApi/Schemas/Courseware/Task.php
+++ b/lib/classes/JsonApi/Schemas/Courseware/Task.php
@@ -36,6 +36,7 @@ class Task extends SchemaProvider
             'submitted' => (bool) $resource['submitted'],
             'renewal' => empty($resource['renewal']) ? null : (string) $resource['renewal'],
             'renewal-date' => date('c', $resource['renewal_date']),
+            'visible' => (bool) $resource['visible'],
             'mkdate' => date('c', $resource['mkdate']),
             'chdate' => date('c', $resource['chdate']),
         ];
diff --git a/lib/models/Courseware/StructuralElement.php b/lib/models/Courseware/StructuralElement.php
index e72cc4f34ef203a279b04dd05347d1ff170b4f73..8e06a396af93aa52f362ba4a27090c0a3f5a4474 100644
--- a/lib/models/Courseware/StructuralElement.php
+++ b/lib/models/Courseware/StructuralElement.php
@@ -391,6 +391,19 @@ class StructuralElement extends \SimpleORMap implements \PrivacyObject, \Feedbac
                         return true;
                     }
 
+                    if ($task->isSubmitted()) {
+                        if ($task->visible) {
+                            return true;
+                        }
+                        $solvers = $task->getTaskGroup()->getSolvers();
+                        foreach ($solvers as $solver) {
+                            if ($solver->id === $user->id) {
+                                return true;
+                            }
+                        }
+                        return false;
+                    }
+
                     return $task->userIsASolver($user);
                 }
 
@@ -1240,4 +1253,9 @@ SQL;
             [$this->id, self::class]
         );
     }
+
+    public function isTaskVisible(): bool
+    {
+        return $this->payload['task-visibility'];
+    }
 }
diff --git a/lib/models/Courseware/Task.php b/lib/models/Courseware/Task.php
index d409676ca3cfad784b1738029da5f0e6d4b7a400..2a1a28d2b00b20321d20fb3ceda9c45b43493a35 100644
--- a/lib/models/Courseware/Task.php
+++ b/lib/models/Courseware/Task.php
@@ -21,6 +21,7 @@ use User;
  * @property int $submitted database column
  * @property string|null $renewal database column
  * @property int $renewal_date database column
+ * @property int $visible database column
  * @property int|null $feedback_id database column
  * @property int $mkdate database column
  * @property int $chdate database column
@@ -90,6 +91,11 @@ class Task extends \SimpleORMap
         parent::configure($config);
     }
 
+    public function getTaskGroup(): TaskGroup
+    {
+        return $this->task_group;
+    }
+
     /**
      * Returns the structural element of this task.
      * This structural element and all its children are part of the task.
@@ -130,6 +136,16 @@ class Task extends \SimpleORMap
                 if ($this->solver_id === $user->id) {
                     return true;
                 }
+
+                if ($this->visible) {
+                    $solvers = $this->getTaskGroup()->getSolvers();
+                    foreach ($solvers as $solver) {
+                        if ($solver->id === $user->id) {
+                            return true;
+                        }
+                    }
+                }
+
                 break;
 
             case 'group':
@@ -235,6 +251,12 @@ class Task extends \SimpleORMap
         $this->store();
     }
 
+    public function setVisibility(bool $visibility): void
+    {
+        $this->visible = (int) $visibility;
+        $this->store();
+    }
+
     private function getStructuralElementProgress(StructuralElement $structural_element): float
     {
         $containers = Container::findBySQL('structural_element_id = ?', [intval($structural_element->id)]);
diff --git a/resources/vue/components/courseware/tasks/CoursewareDashboardTasks.vue b/resources/vue/components/courseware/tasks/CoursewareDashboardTasks.vue
index 35384fce41f69512557b67fc19a016e3b66b2a58..cb3577ad5638f2b927e7519f2fcb78f746feb829 100644
--- a/resources/vue/components/courseware/tasks/CoursewareDashboardTasks.vue
+++ b/resources/vue/components/courseware/tasks/CoursewareDashboardTasks.vue
@@ -2,7 +2,15 @@
     <div class="cw-dashboard-tasks-wrapper">
         <table v-if="tasks.length > 0" class="default">
             <colgroup>
-                <col />
+                <col style="width: 5%" />
+                <col style="width: 20%" />
+                <col style="width: 10%" />
+                <col style="width: 10%" />
+                <col style="width: 5%" />
+                <col style="width: 15%" />
+                <col style="width: 15%" />
+                <col style="width: 15%" />
+                <col style="width: 5%" />
             </colgroup>
             <thead>
                 <tr>
@@ -12,6 +20,7 @@
                     <th>{{ $gettext('Abgabefrist') }}</th>
                     <th>{{ $gettext('Abgabe') }}</th>
                     <th class="responsive-hidden">{{ $gettext('Verlängerungsanfrage') }}</th>
+                    <th class="responsive-hidden">{{ $gettext('Für Teilnehmende freigeben') }}</th>
                     <th class="responsive-hidden">{{ $gettext('Anmerkung') }}</th>
                     <th class="actions">{{ $gettext('Aktionen') }}</th>
                 </tr>
@@ -54,6 +63,23 @@
                             {{ $gettext('verlängert bis') }}: {{ getReadableDate(task.attributes['renewal-date']) }}
                         </span>
                     </td>
+                    <td class="responsive-hidden">
+                        <span v-if="task.attributes.submitted">
+                            <button
+                                class="button"
+                                v-if="!task.attributes.visible"
+                                @click="toggleVisibilityOn(task)"
+                            >
+                                {{ $gettext('Freigeben') }}
+                            </button>
+                            <button
+                                class="button"
+                                v-if="task.attributes.visible"
+                                @click="toggleVisibilityOff(task)">
+                                {{ $gettext('Freigabe widerrufen') }}
+                            </button>
+                        </span>
+                    </td>
                     <td class="responsive-hidden">
                         <studip-icon
                             v-if="feedback"
@@ -166,6 +192,13 @@ export default {
                 return result;
             });
         },
+        taskVisibilities() {
+            let visibilities = [];
+            for (const task of this.tasks) {
+                visibilities[`${task.task.id}`] = task.element.attributes.payload['task-visibility'];
+            }
+            return visibilities;
+        }
     },
     methods: {
         ...mapActions({
@@ -175,6 +208,7 @@ export default {
             companionSuccess: 'companionSuccess',
             companionError: 'companionError',
             createCoursewareUnit: 'courseware-units/create',
+            loadStructuralElement: 'courseware-structural-elements/loadById'
         }),
         getTaskMenuItems(task, status, element) {
             let menuItems = [];
@@ -277,6 +311,37 @@ export default {
             this.showFeedbackDialog = true;
             this.currentTaskFeedback = feedback.attributes.content;
         },
+        toggleVisibilityOn(task) {
+            let attributes = task.attributes;
+            attributes['visible'] = true;
+            this.toggleVisibility(task, attributes);
+        },
+        toggleVisibilityOff(task) {
+            let attributes = task.attributes;
+            attributes['visible'] = false;
+            this.toggleVisibility(task, attributes);
+        },
+        async toggleVisibility(task, attributes) {
+            await this.updateTask({
+                attributes: attributes,
+                taskId: task.id,
+            });
+
+            const taskGroup = this.getTaskGroupById({ id: task.relationships['task-group'].data.id });
+            const taskTitle = taskGroup.attributes.title;
+
+            if (attributes.visible) {
+                this.companionSuccess({
+                    info: this.$gettextInterpolate(this.$gettext('"%{ title }" wurde freigegeben.'),
+                    { title: taskTitle }),
+                });
+            } else {
+                this.companionSuccess({
+                    info: this.$gettextInterpolate(this.$gettext('Die Freigabe für %{ "title }" wurde zurückgenommen.'),
+                    { title: taskTitle }),
+                });
+            }
+        }
     },
 };
 </script>