fix: A10 — autoescape Jinja2 activé partout via un Environment partagé
- `app/templating.py` : `ENV = Environment(loader=FileSystemLoader("app/templates"), autoescape=select_autoescape(["html"]))`
- les 29 instantiations `Environment(loader=FileSystemLoader("app/templates"))` (9 routers) remplacées par `env = ENV` — plus aucune interpolation `{{ … }}` servie crue, les `|safe` redeviennent efficaces
- re-tri des `|safe` : `card_detail.html` corps d'issue et commentaires échappés (XSS stocké), placeholder de description sorti du ternaire, `sidebar_config` passé en dict + `|tojson` (échappement `</script>` en contexte script)
- `|safe` conservé sur `right_actions` (HTML fabriqué dans les templates, fiable)
- `ruff check app tests` OK · suite **1016/1016 verts**
This commit is contained in:
+32
-36
@@ -252,10 +252,9 @@ def _sidebar_data(request: Request, repos: list[dict], include_workspace: bool =
|
||||
@router.get("/trash", response_class=HTMLResponse)
|
||||
async def trash_page(request: Request, owner: str = Query(default=""), repo: str = Query(default="")):
|
||||
"""Trash page — scoped to workspace if owner/repo provided."""
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
|
||||
from app.routers.board import _sidebar_data as board_sidebar
|
||||
env = Environment(loader=FileSystemLoader("app/templates"))
|
||||
from app.templating import ENV
|
||||
env = ENV
|
||||
sidebar = board_sidebar(request, owner, repo)
|
||||
with get_conn() as conn:
|
||||
ws_key = f"{owner}/{repo}" if owner and repo else ""
|
||||
@@ -283,10 +282,9 @@ async def library_page(request: Request, owner: str = Query(default=""), repo: s
|
||||
|
||||
Sidebar data is kept intact. Tab content is loaded client-side via /api/library/* endpoints.
|
||||
"""
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
|
||||
from app.routers.board import _sidebar_data as board_sidebar
|
||||
env = Environment(loader=FileSystemLoader("app/templates"))
|
||||
from app.templating import ENV
|
||||
env = ENV
|
||||
sidebar = board_sidebar(request, owner, repo)
|
||||
# Pass active workspace for breadcrumb nav menu
|
||||
ws_key_ws = f"{owner}/{repo}" if owner and repo else ""
|
||||
@@ -312,10 +310,9 @@ async def view_page_root(request: Request, page_id: int):
|
||||
"""Render a Markdown page at root level with workspace context — or file viewer.
|
||||
?embed=1 — minimal mode for side peek (editor only, no header)."""
|
||||
embed = request.query_params.get("embed") == "1"
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
|
||||
from app.routers.board import _sidebar_data as board_sidebar
|
||||
env = Environment(loader=FileSystemLoader("app/templates"))
|
||||
from app.templating import ENV
|
||||
env = ENV
|
||||
with get_conn() as conn:
|
||||
row = conn.execute("SELECT * FROM pages WHERE id=?", (page_id,)).fetchone()
|
||||
if not row:
|
||||
@@ -437,8 +434,8 @@ async def view_page_root(request: Request, page_id: int):
|
||||
@router.get("/accounts", response_class=HTMLResponse)
|
||||
async def accounts_page(request: Request):
|
||||
"""Account management panel."""
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
env = Environment(loader=FileSystemLoader("app/templates"))
|
||||
from app.templating import ENV
|
||||
env = ENV
|
||||
sidebar = _sidebar_data(request, [])
|
||||
user = SessionManager.decode_session(request.cookies.get("flowdeck_session", ""))
|
||||
with get_conn() as conn:
|
||||
@@ -451,8 +448,8 @@ async def accounts_page(request: Request):
|
||||
@router.get("/help", response_class=HTMLResponse)
|
||||
async def help_page(request: Request):
|
||||
"""Comprehensive help & documentation page."""
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
env = Environment(loader=FileSystemLoader("app/templates"))
|
||||
from app.templating import ENV
|
||||
env = ENV
|
||||
sidebar = _sidebar_data(request, [])
|
||||
# Render via a block-based template so content_html lands in {% block content %}
|
||||
block_tpl = env.from_string(
|
||||
@@ -656,8 +653,8 @@ favorites, tags, sharing, sprints and templates; pagination (<code>?limit=&o
|
||||
@router.get("/accounts/settings", response_class=HTMLResponse)
|
||||
async def settings_page(request: Request):
|
||||
"""User settings page — profile, forges, tokens."""
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
env = Environment(loader=FileSystemLoader("app/templates"))
|
||||
from app.templating import ENV
|
||||
env = ENV
|
||||
sidebar = _sidebar_data(request, [])
|
||||
user = SessionManager.decode_session(request.cookies.get("flowdeck_session", ""))
|
||||
if not user:
|
||||
@@ -769,14 +766,14 @@ async def dashboard(
|
||||
with get_conn() as conn:
|
||||
count = conn.execute("SELECT COUNT(*) FROM users").fetchone()[0]
|
||||
if count == 0:
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
env = Environment(loader=FileSystemLoader("app/templates"))
|
||||
from app.templating import ENV
|
||||
env = ENV
|
||||
template = env.get_template("landing.html")
|
||||
return template.render()
|
||||
except Exception:
|
||||
pass
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
env = Environment(loader=FileSystemLoader("app/templates"))
|
||||
from app.templating import ENV
|
||||
env = ENV
|
||||
template = env.get_template("landing.html")
|
||||
return template.render()
|
||||
|
||||
@@ -821,8 +818,8 @@ async def dashboard(
|
||||
logger.error("Dashboard error: %s", e)
|
||||
repos = []
|
||||
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
env = Environment(loader=FileSystemLoader("app/templates"))
|
||||
from app.templating import ENV
|
||||
env = ENV
|
||||
sidebar = _sidebar_data(request, repos)
|
||||
template = env.get_template("dashboard.html")
|
||||
return template.render(request=request, repos=repos, search=search,
|
||||
@@ -834,8 +831,8 @@ async def dashboard(
|
||||
@router.get("/workspace", response_class=HTMLResponse)
|
||||
async def workspace_page(request: Request):
|
||||
"""Unified workspace showing all projects."""
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
env = Environment(loader=FileSystemLoader("app/templates"))
|
||||
from app.templating import ENV
|
||||
env = ENV
|
||||
sidebar = _sidebar_data(request, [])
|
||||
user = SessionManager.decode_session(request.cookies.get("flowdeck_session", ""))
|
||||
if not user:
|
||||
@@ -853,8 +850,8 @@ async def gitea_workspace_page(request: Request):
|
||||
"""Gitea workspace — browse repo files."""
|
||||
import json
|
||||
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
env = Environment(loader=FileSystemLoader("app/templates"))
|
||||
from app.templating import ENV
|
||||
env = ENV
|
||||
sidebar = _sidebar_data(request, [])
|
||||
user = SessionManager.decode_session(request.cookies.get("flowdeck_session", ""))
|
||||
if not user:
|
||||
@@ -1030,8 +1027,8 @@ async def local_workspace_page(request: Request, folder: int = None):
|
||||
|
||||
If ?folder=ID is provided, shows that folder's contents with breadcrumb.
|
||||
"""
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
env = Environment(loader=FileSystemLoader("app/templates"))
|
||||
from app.templating import ENV
|
||||
env = ENV
|
||||
sidebar = _sidebar_data(request, [])
|
||||
user = SessionManager.decode_session(request.cookies.get("flowdeck_session", ""))
|
||||
if not user:
|
||||
@@ -1735,8 +1732,8 @@ def _get_active_workspace(request: Request, user_id: int = None) -> dict | None:
|
||||
@router.get("/workspaces", response_class=HTMLResponse)
|
||||
async def workspaces_page(request: Request):
|
||||
"""Workspaces list page."""
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
env = Environment(loader=FileSystemLoader("app/templates"))
|
||||
from app.templating import ENV
|
||||
env = ENV
|
||||
sidebar = _sidebar_data(request, [], include_workspace=False)
|
||||
user = SessionManager.decode_session(request.cookies.get("flowdeck_session", ""))
|
||||
if not user:
|
||||
@@ -1838,8 +1835,8 @@ async def select_workspace(request: Request, ws_id: int):
|
||||
@router.get("/settings", response_class=HTMLResponse)
|
||||
async def app_settings_page(request: Request):
|
||||
"""Settings & configuration page."""
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
env = Environment(loader=FileSystemLoader("app/templates"))
|
||||
from app.templating import ENV
|
||||
env = ENV
|
||||
sidebar = _sidebar_data(request, [])
|
||||
user = SessionManager.decode_session(request.cookies.get("flowdeck_session", ""))
|
||||
if not user:
|
||||
@@ -2156,9 +2153,8 @@ async def sidebar_workspace_tree(request: Request):
|
||||
Called by appState().refreshSidebarTree() after CRUD operations
|
||||
in the main content area to keep the sidebar in sync.
|
||||
"""
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
|
||||
from app.routers.board import _load_workspace_pages
|
||||
from app.templating import ENV
|
||||
|
||||
user = SessionManager.decode_session(request.cookies.get("flowdeck_session", ""))
|
||||
if not user:
|
||||
@@ -2194,7 +2190,7 @@ async def sidebar_workspace_tree(request: Request):
|
||||
)
|
||||
|
||||
# Render the tree using the extracted macro
|
||||
env = Environment(loader=FileSystemLoader("app/templates"))
|
||||
env = ENV
|
||||
template = env.from_string(
|
||||
"{% from '_workspace_tree_macro.html' import render_workspace_tree %}"
|
||||
"{{ render_workspace_tree(pages) }}"
|
||||
@@ -2215,7 +2211,7 @@ async def sidebar_workspace_tree(request: Request):
|
||||
@router.get("/p/{slug}", response_class=HTMLResponse)
|
||||
async def public_published_page(request: Request, slug: str):
|
||||
"""Serve a published page at /p/<slug> — no auth required."""
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
from app.templating import ENV
|
||||
|
||||
with get_conn() as conn:
|
||||
row = conn.execute(
|
||||
@@ -2235,7 +2231,7 @@ h1{font-size:3rem;opacity:.3}</style></head><body><h1>404</h1></body></html>""",
|
||||
)
|
||||
|
||||
page = dict(row)
|
||||
env = Environment(loader=FileSystemLoader("app/templates"))
|
||||
env = ENV
|
||||
|
||||
# Convert blocks to HTML for rendering
|
||||
content_html = ""
|
||||
|
||||
Reference in New Issue
Block a user