diff --git a/pygitweb/boards_permissions_test.py b/pygitweb/boards_permissions_test.py
index 10c3217..51fc65b 100644
--- a/pygitweb/boards_permissions_test.py
+++ b/pygitweb/boards_permissions_test.py
@@ -12,6 +12,7 @@ from fastapi.testclient import TestClient
 from pygitweb.auth_config import AuthConfig, LocalUser
 from pygitweb.config import settings
 from pygitweb.conftest import client_as_user
+from pygitweb.main import app
 from pygitweb.permissions import Permission, permission_key, permissions_with_defaults
 from pygitweb.tasks import board_router
 
@@ -101,6 +102,36 @@ def test_board_create_forbidden_without_grant(board_env: dict[str, str | TestCli
 	assert r.status_code == 403
 
 
+def test_board_create_get_forbidden_without_grant(board_env: dict[str, str | TestClient]) -> None:
+	with (
+		patch.object(settings, "AUTH", True),
+		patch("pygitweb.main.auth_config", _BOARDS_AUTH),
+	):
+		client = TestClient(app)
+		with client_as_user(client, "viewer"):
+			r = client.get(
+				"/board/create",
+				params={"project": board_env["project"]},
+				follow_redirects=False,
+			)
+	assert r.status_code == 403
+
+
+def test_board_create_get_allowed_with_grant(board_env: dict[str, str | TestClient]) -> None:
+	with (
+		patch.object(settings, "AUTH", True),
+		patch("pygitweb.main.auth_config", _BOARDS_AUTH),
+	):
+		client = TestClient(app)
+		with client_as_user(client, "admin"):
+			r = client.get(
+				"/board/create",
+				params={"project": board_env["project"]},
+				follow_redirects=False,
+			)
+	assert r.status_code == 303
+
+
 def test_board_delete_forbidden_without_grant(board_env: dict[str, str | TestClient]) -> None:
 	client = board_env["client"]
 	assert isinstance(client, TestClient)
diff --git a/pygitweb/comments_permissions_test.py b/pygitweb/comments_permissions_test.py
index 5362f92..6feeb61 100644
--- a/pygitweb/comments_permissions_test.py
+++ b/pygitweb/comments_permissions_test.py
@@ -9,7 +9,7 @@ import pytest
 from fastapi import FastAPI
 from fastapi.testclient import TestClient
 
-from pygittools.tasks import BOARD_REF_PREFIX, TASK_REF_PREFIX, Board, Task
+from pygittools.tasks import BOARD_REF_PREFIX, TASK_REF_PREFIX, Board, Comment, Task
 from pygitweb.auth_config import AuthConfig, LocalUser
 from pygitweb.config import settings
 from pygitweb.conftest import client_as_user
@@ -18,7 +18,11 @@ from pygitweb.tasks import EMPTY_TREE_OID, comment_router
 
 _COMMENTS_AUTH = AuthConfig(
 	auth_mode="local",
-	local_users=[LocalUser(user="admin", password="secret"), LocalUser(user="viewer", password="secret")],
+	local_users=[
+		LocalUser(user="admin", password="secret"),
+		LocalUser(user="viewer", password="secret"),
+		LocalUser(user="author", password="secret"),
+	],
 	oauth_permissions=permissions_with_defaults({
 		permission_key(Permission.COMMENTS, "demo"): ["viewer"],
 		permission_key(Permission.TASKS, "demo"): ["admin"],
@@ -52,6 +56,11 @@ def comment_env(tmp_path: Path) -> Generator[dict[str, str], None, None]:
 	task_ref = f"{TASK_REF_PREFIX}task_1"
 	task = Task(EMPTY_TREE_OID, task_ref, tagger="", title="Task", description="")
 	task_oid = task.write(repo)
+	comment = Comment(task_oid, "author <author@local>", "seed note")
+	comment_oid = comment.write(repo)
+	task.comments = [str(comment_oid)]
+	task.update_message()
+	task.write(repo)
 	board.tasks = [str(task_oid)]
 	board.update_message()
 	board.write(repo)
@@ -70,9 +79,93 @@ def comment_env(tmp_path: Path) -> Generator[dict[str, str], None, None]:
 			"project": "demo",
 			"board": "Tasks",
 			"task": task_ref,
+			"comment_oid": str(comment_oid),
 		}
 
 
+def test_comment_delete_forbidden_without_comments_grant(comment_env: dict[str, str]) -> None:
+	client = comment_env["client"]
+	assert isinstance(client, TestClient)
+	with patch.object(settings, "AUTH", True), client_as_user(client, "admin"):
+		r = client.post(
+			"/comments/delete",
+			params={
+				"project": comment_env["project"],
+				"board": comment_env["board"],
+				"comment": comment_env["comment_oid"],
+			},
+		)
+	assert r.status_code == 403
+
+
+def test_comment_delete_allowed_with_comments_grant(comment_env: dict[str, str]) -> None:
+	client = comment_env["client"]
+	assert isinstance(client, TestClient)
+	with patch.object(settings, "AUTH", True), client_as_user(client, "viewer"):
+		r = client.post(
+			"/comments/delete",
+			params={
+				"project": comment_env["project"],
+				"board": comment_env["board"],
+				"comment": comment_env["comment_oid"],
+			},
+		)
+	assert r.status_code == 200
+
+
+def test_comment_modify_allowed_for_author_without_comments_grant(comment_env: dict[str, str]) -> None:
+	client = comment_env["client"]
+	assert isinstance(client, TestClient)
+	with patch.object(settings, "AUTH", True), client_as_user(client, "author"):
+		r = client.post(
+			"/comments/modify",
+			params={
+				"project": comment_env["project"],
+				"board": comment_env["board"],
+				"task": comment_env["task"],
+				"comment": comment_env["comment_oid"],
+			},
+			json={"content": "edited by author"},
+		)
+	assert r.status_code == 200
+	assert r.json()["content"] == "edited by author"
+
+
+def test_comment_modify_forbidden_for_non_author_without_comments_grant(comment_env: dict[str, str]) -> None:
+	client = comment_env["client"]
+	assert isinstance(client, TestClient)
+	with patch.object(settings, "AUTH", True), client_as_user(client, "admin"):
+		r = client.post(
+			"/comments/modify",
+			params={
+				"project": comment_env["project"],
+				"board": comment_env["board"],
+				"task": comment_env["task"],
+				"comment": comment_env["comment_oid"],
+			},
+			json={"content": "edited by admin"},
+		)
+	assert r.status_code == 403
+
+
+def test_comment_modify_allowed_with_comments_grant(comment_env: dict[str, str]) -> None:
+	client = comment_env["client"]
+	assert isinstance(client, TestClient)
+	with patch.object(settings, "AUTH", True), client_as_user(client, "viewer"):
+		r = client.post(
+			"/comments/modify",
+			params={
+				"project": comment_env["project"],
+				"board": comment_env["board"],
+				"task": comment_env["task"],
+				"comment": comment_env["comment_oid"],
+			},
+			json={"content": "edited by moderator"},
+		)
+	assert r.status_code == 200
+	assert r.json()["content"] == "edited by moderator"
+
+
 def test_comment_create_open_when_auth_disabled(comment_env: dict[str, str]) -> None:
 	client = comment_env["client"]
 	assert isinstance(client, TestClient)
diff --git a/pygitweb/main.py b/pygitweb/main.py
index 2c2f646..302e9e0 100644
--- a/pygitweb/main.py
+++ b/pygitweb/main.py
@@ -57,7 +57,6 @@ from pygitweb.auth import (
 	can_read_project,
 	decode_access_token,
 	ensure_permission,
-	require_active_user_if_auth_enabled,
 	require_permission,
 )
 from pygitweb.auth_config import auth_config, is_auth_configured
@@ -412,7 +411,7 @@ def git_opml(token: Annotated[str | None, Depends(access_token_from_request)]):
 
 @app.get("/board/create", response_class=RedirectResponse)
 def board_create_page(
-	_: Annotated[None, Depends(require_active_user_if_auth_enabled)],
+	_: Annotated[None, Depends(require_permission(Permission.BOARDS, project_from_query=True))],
 	project: ValidatedBoardCreateProject,
 ) -> RedirectResponse:
 	"""Create a board named 'Tasks' (refs/tags/boards/Tasks) and redirect to the project summary."""
diff --git a/pygitweb/tasks.py b/pygitweb/tasks.py
index 6e1589d..531a911 100644
--- a/pygitweb/tasks.py
+++ b/pygitweb/tasks.py
@@ -27,8 +27,9 @@ from pygittools.tasks import (
 )
 from pygitweb.auth import (
 	access_token_from_request,
+	ensure_active_user_if_auth_enabled,
+	has_permission,
 	principal_from_session,
-	require_active_user_if_auth_enabled,
 	require_permission,
 )
 from pygitweb.config import settings
@@ -435,6 +436,31 @@ def _tagger_from_principal(principal: PermissionPrincipal | None) -> str:
 	return f"{name} <{email}>"
 
 
+def _principal_matches_comment_tagger(tagger: str, principal: PermissionPrincipal) -> bool:
+	identity = principal.identity
+	if not identity:
+		return False
+	raw = (tagger or "").strip()
+	if not raw:
+		return False
+	if raw == identity or raw.lower() == identity.lower():
+		return True
+	name, email = _parse_tagger_display(raw)
+	valid_email = email and email.lower() == identity.lower()
+	valid_name = name and (name == identity or name.lower() == identity.lower())
+	return valid_email or valid_name
+
+
+def _ensure_comment_modify_allowed(project: str, token: str | None, tagger: str) -> None:
+	ensure_active_user_if_auth_enabled(token)
+	if has_permission(Permission.COMMENTS, project, token=token):
+		return
+	principal = principal_from_session(token)
+	if principal is not None and _principal_matches_comment_tagger(tagger, principal):
+		return
+	raise HTTPException(status_code=403, detail="Insufficient permissions")
+
+
 def _comment_to_json(c: Comment) -> dict[str, Any]:
 	author, email = _parse_tagger_display(c.tagger)
 	return {
@@ -521,18 +547,18 @@ def comment_create(
 
 
 @comment_router.post(
-	"/update",
+	"/modify",
 	response_class=JSONResponse,
-	dependencies=[Depends(require_active_user_if_auth_enabled)],
 )
-async def comment_update(
+async def comment_modify(
 	request: Request,
 	project: ValidatedReadableQueryProject,
 	board: str = Query(..., description="Board name"),
 	task: str = Query(..., description="Task ref (to update task.comments)"),
 	comment_oid: str = Query(..., alias="comment", description="Comment OID hex"),
+	token: Annotated[str | None, Depends(access_token_from_request)] = None,
 ) -> JSONResponse:
-	"""Update a comment (body: content). Writes new comment object and updates task.comments."""
+	"""Modify a comment (body: content). Author or pgw.comments grant required."""
 	try:
 		body = await request.json() if request.headers.get("content-type", "").startswith("application/json") else {}
 	except Exception:
@@ -542,6 +568,7 @@ async def comment_update(
 	c = get_comment(repo, oid)
 	if not c:
 		raise HTTPException(status_code=404, detail="Comment not found")
+	_ensure_comment_modify_allowed(project, token, c.tagger)
 	if "content" in body:
 		c.content = body["content"]
 	from datetime import datetime
@@ -573,7 +600,7 @@ async def comment_update(
 @comment_router.post(
 	"/delete",
 	response_class=JSONResponse,
-	dependencies=[Depends(require_active_user_if_auth_enabled)],
+	dependencies=[Depends(require_permission(Permission.COMMENTS, project_from_query=True))],
 )
 def comment_delete(
 	project: ValidatedReadableQueryProject,
