Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
b2b8711
perf(backend): has_sent_message를 딕셔너리에서 set으로 변경
seonghobae Aug 2, 2026
befb887
Merge b2b8711cb1d1b336330b793658c33a8514906d91 into d6359b2447a493f35…
seonghobae Aug 3, 2026
1a6ecc0
Merge branch 'develop' into bolt/optimize-has-sent-message-1619297502…
opencode-agent[bot] Aug 3, 2026
c15aaab
perf(backend): has_sent_message를 딕셔너리에서 set으로 변경
seonghobae Aug 3, 2026
4810c45
Merge branch 'develop' into bolt/optimize-has-sent-message-1619297502…
opencode-agent[bot] Aug 3, 2026
00e7af6
chore(ci): add one-shot PR 1214 scope repair
seonghobae Aug 3, 2026
0daf30c
fix(scope): retain current develop governance and calendar behavior
github-actions[bot] Aug 3, 2026
fc70205
chore(ci): refresh PR 1214 checks after scope repair
seonghobae Aug 3, 2026
d5d56fd
chore(ci): remove PR 1214 refresh marker
seonghobae Aug 3, 2026
004808f
merge(develop): refresh sent-grouping performance branch
seonghobae Aug 3, 2026
b005e9c
Merge branch 'develop' into bolt/optimize-has-sent-message-1619297502…
opencode-agent[bot] Aug 3, 2026
58b62d5
Merge branch 'develop' into bolt/optimize-has-sent-message-1619297502…
opencode-agent[bot] Aug 3, 2026
515c089
chore(frontend): update postcss to 8.5.25 to fix CVE-2026-69153
seonghobae Aug 3, 2026
ce09cb3
fix(scope): restore sent-grouping performance changes on current develop
seonghobae Aug 3, 2026
0ed4afb
Merge branch 'develop' into bolt/optimize-has-sent-message-1619297502…
opencode-agent[bot] Aug 4, 2026
956e080
🛡️ Sentinel: [CRITICAL/HIGH] Fix Strix Security Findings
seonghobae Aug 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .jules/palette.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,3 @@
## 2026-06-25 - Use semantic type="search" with custom clear buttons
**Learning:** Using `type="search"` on input fields improves mobile UX by rendering a semantic search keyboard (with a "Search" submit button instead of "Go/Enter"). However, when adding a custom clear button ('X') using UI components or Tailwind CSS, the native webkit clear button overlaps with it.
**Action:** When implementing search fields, always use `type="search"` instead of `type="text"` to get the semantic keyboard benefits. To prevent visual overlaps with custom clear icons, add the `[&::-webkit-search-cancel-button]:hidden` Tailwind utility class to hide the native webkit clear button.
## 2025-05-19 - Dynamic ARIA labels and robust disabled states for sidebar actions
**Learning:** Hardcoded ARIA labels in mockups (like "출시 회의 일정 삭제") are often left intact during implementation, leading to incorrect screen reader announcements when different items are selected. In addition, action buttons that depend on selection state often lack correct visual and functional disabled states.
**Action:** When implementing detail views or sidebars, always replace hardcoded mockup ARIA labels with dynamic data (e.g. `${event.title} 삭제`), and ensure action buttons are explicitly disabled (both functionally via `disabled` and visually via `opacity-50 cursor-not-allowed`) when their prerequisites (like a selected item or specific properties like location) are unmet.
10 changes: 9 additions & 1 deletion backend/api/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ def fetch_data(self) -> Any:
f"OIDC JWKS endpoint returned {response.status}"
)
jwk_set = json.loads(response_body.decode("utf-8"))
if "keys" in jwk_set:
for key in jwk_set["keys"]:
if key.get("alg") != "RS256":
raise ValueError("Only RS256 keys are supported in JWKS")
finally:
connection.close()
if self.jwk_set_cache is not None:
Expand Down Expand Up @@ -166,6 +170,10 @@ class AuthContext:
workspace_id: str
session_verifier: SessionVerifier = field(default="override", compare=False)

def __post_init__(self):
if self.role in TENANT_ADMIN_ROLES and self.session_verifier != "server":
raise ValueError("Admin roles require server session_verifier")


def ensure_organization_access(auth_context: AuthContext, organization_id: str) -> None:
if auth_context.organization_id != organization_id:
Expand Down Expand Up @@ -512,7 +520,7 @@ def _auth_context_from_session_payload(
if role_value not in ALLOWED_ROLES:
raise _authentication_error()
role = cast(RoleName, role_value)
if role in TENANT_ADMIN_ROLES and session_verifier not in ("server", "override"):
if role in TENANT_ADMIN_ROLES and session_verifier != "server":
raise _authentication_error()
organization_id = _optional_string_claim(payload, "org")
if organization_id is None:
Expand Down
11 changes: 4 additions & 7 deletions backend/api/dav.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,10 @@


def _normalize_dav_authorization_path(path: str) -> str:
normalized_path = path.replace("\\", "/")
for _ in range(100):
decoded_path = unquote(normalized_path).replace("\\", "/")
if decoded_path == normalized_path:
return normalized_path
normalized_path = decoded_path
raise HTTPException(status_code=400, detail="DAV path decoding limit exceeded")
lower_path = path.replace("\\", "/").lower()
if "%2e%2e" in lower_path or "%2f" in lower_path or "%5c" in lower_path:
raise HTTPException(status_code=400, detail="Invalid encoded path sequence detected")
return unquote(path).replace("\\", "/")


def _dav_path_owner_user_id(path: str) -> str | None:
Expand Down
22 changes: 22 additions & 0 deletions backend/api/tenant_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,25 @@ def _validate_imap_config(imap_server: str | None, imap_port: int | None) -> Non
) from exc


def _validate_oauth_redirect_uri(uri: str | None) -> None:
if not uri:
return
try:
from core.config import settings
allowed = [u.strip() for u in settings.ALLOWED_OAUTH_REDIRECT_URIS.split(",") if u.strip()]
if not allowed or uri not in allowed:
raise ValueError("OAuth redirect URI is not in the allowed list")
except ValueError as exc:
logger.warning(
"OAuth redirect URI validation failed",
extra={"error_type": type(exc).__name__},
)
raise HTTPException(
status_code=400,
detail="Invalid OAuth redirect URI configuration",
) from exc


def _validate_pop3_config(pop3_server: str | None, pop3_port: int | None) -> None:
try:
if pop3_server is not None and pop3_port is not None:
Expand Down Expand Up @@ -223,6 +242,9 @@ def validate_mail_config_update(
_validate_imap_config(imap_server, imap_port)
_validate_pop3_config(pop3_server, pop3_port)

oauth_redirect_uri = _field_value(config_data, db_config, "oauth_redirect_uri")
_validate_oauth_redirect_uri(oauth_redirect_uri)


@router.post("")
async def create_or_update_config(
Expand Down
1 change: 1 addition & 0 deletions backend/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ class Settings(BaseSettings):
# operator is permitted to promote work items to (SSRF host allowlist).
ALLOWED_SCOPEWEAVE_HOSTS: str = ""
ALLOWED_CORS_ORIGINS: str = ""
ALLOWED_OAUTH_REDIRECT_URIS: str = ""
ENABLE_PROMETHEUS_METRICS: bool = False
# Best-effort projection of imported-email content segments into the project
# semantic graph. Off by default; failure never affects email import.
Expand Down
Loading
Loading