Commit graph

16 commits

Author SHA1 Message Date
sriram veeraghanta
08a7d12b9d
fix: resolve open CodeQL security alerts (#9505)
- adapter/oauth.py: stop logging request headers on user-info fetch
  failure; they carry the Bearer access token (py/clear-text-logging-sensitive-data)
- adapter/base.py: drop the email value from the invalid-email warning
  log (py/clear-text-logging-sensitive-data)
- provider/oauth/github.py: stop logging organization id / user login
  on org-membership failure (py/clear-text-logging-sensitive-data)
- editor custom-link: rewrite the C0-control strip regex with escaped,
  non-overlapping ranges instead of raw control bytes overlapping \s
  (js/overly-large-range); also fixes the file being detected as binary
2026-07-29 20:28:09 +05:30
Manish Gupta
b91b61c379
[WEB-7778] fix(security): reject unverified OAuth provider emails to prevent ATO (Cluster E) (#9289)
* [WEB-7778] fix(security): reject unverified OAuth provider emails to prevent ATO

An attacker controlling a self-hosted OAuth provider (Gitea, GitLab) could
assert any email address in the OAuth response and be matched to an existing
Plane account, bypassing authentication entirely.

- Add OAUTH_PROVIDER_UNVERIFIED_EMAIL (5124) error code
- GitHub: require both primary=True AND verified=True on email (was primary-only)
- Google: check verified_email=False field in userinfo response
- GitLab: check confirmed_at is non-null before accepting email
- Gitea __get_email: remove unverified fallbacks (primary-unverified, any-unverified)
- Gitea set_user_data: remove fast-path using .email from user object (no
  verification flag); always go through __get_email() which enforces verified

Fixes GHSA-7j95-vh8g-f365 (critical ATO).
Note: GHSA-cv9p-325g-wmv5 and GHSA-hx79-5pj5-qh42 (avatar SSRF) were
already fixed in PR #9163.

Co-authored-by: Plane AI <noreply@plane.so>

* fix(security): add read:user scope to Gitea; fail-closed on absent Google verified_email

Gitea's /api/v1/user/emails endpoint requires the read:user granular
scope — openid+email+profile alone is insufficient and __get_email()
would return a 401/403. Add read:user to the scope string.

Google: change default from True to fail-closed (is not True) so a
userinfo response that omits verified_email is rejected rather than
trusted. The service-account justification was incorrect — service
accounts do not go through the interactive OAuth2 callback flow.

Co-authored-by: Plane AI <noreply@plane.so>

---------

Co-authored-by: Plane AI <noreply@plane.so>
2026-07-09 16:31:33 +05:30
sriram veeraghanta
4fc79a2d7e
fix(security): block bot user logins (#9368)
Bot service accounts (User.is_bot=True, e.g. the WORKSPACE_SEED bot) are
internal identities meant to act only through API tokens. Nothing stopped
one from being driven through the interactive login flow if its email was
known, letting a human assume a service identity.

Reject bot accounts at the shared login chokepoint,
Adapter.complete_login_or_signup(), right beside the existing
deactivated-account check. This covers every interactive provider in one
place: email/password, magic code, and all OAuth providers (Google, GitHub,
GitLab, Gitea) across both the app and space surfaces. Bot API-token access
is left untouched, since that is how bots are meant to operate.

Also add a defense-in-depth is_bot guard to InstanceAdminSignInEndpoint,
which mints its own admin session outside the chokepoint (a bot is never an
InstanceAdmin today, so this is not currently reachable, but it closes the
path regardless).

Surface the rejection with a new dedicated error code
BOT_USER_LOGIN_FORBIDDEN (5017), plumbed into the app and space frontend
error helpers as well as the shared @plane/constants and @plane/utils
packages (message map + banner-alert list) so any consumer of the shared
auth-error handler renders it correctly. The admin path reuses the existing
ADMIN_AUTHENTICATION_FAILED code so it discloses no bot-specific error.

Add contract regression tests: a bot blocked via password and via magic
code, a bot blocked at the admin sign-in endpoint, and a non-bot control
that still logs in.
2026-07-08 01:51:37 +05:30
Manish Gupta
1e8f3630c7
[WEB-7787] fix(auth): restore activation flow and narrow deactivation guard (#9304)
* fix(auth): restore activation flow and narrow deactivation guard (GHSA-rmmf-rj2q-3rrg)

PR #9290 introduced two regressions in adapter/base.py:

1. is_signup = bool(user) was inverted — True when the user EXISTS means
   the IDP sync ran on signup instead of login, and the callback received
   the wrong value. Fixed to is_signup = not bool(user) matching EE.

2. The deactivation check blocked ALL inactive users, including accounts
   provisioned with is_active=False that have never completed a first
   login. Fixed by adding `and user.last_login_time is not None` — only
   accounts that have previously logged in (and were then explicitly
   deactivated by an admin) are rejected. Provisioned/never-logged-in
   accounts still pass through to save_user_data().

3. Restore is_active=True and user_activation_email in save_user_data()
   so provisioned accounts are properly activated on first login.

Co-authored-by: Plane AI <noreply@plane.so>

* fix(auth): save before email, use last_logout_time as deactivation discriminator

Two CR fixes on PR #9304:

1. save_user_data(): capture was_inactive flag, save() first, then send
   activation email as a best-effort side-effect so a failed enqueue
   cannot abort account activation.

2. complete_login_or_signup(): switch deactivation discriminator from
   last_login_time to last_logout_time. The deactivation endpoint always
   sets last_logout_time, making it a direct signal of explicit
   deactivation. A provisioned account that was never deactivated has
   last_logout_time=None and is correctly allowed through for first login,
   even if it also has no last_login_time.

Co-authored-by: Plane AI <noreply@plane.so>

---------

Co-authored-by: Plane AI <noreply@plane.so>
2026-06-24 14:40:24 +05:30
Manish Gupta
6c9dbb5043
[WEB-7787] fix(security): block deactivated user login and fix WorkspaceOwnerPermission (#9290)
GHSA-rmmf-rj2q-3rrg: save_user_data() was unconditionally setting
is_active=True on every login, silently reactivating any admin-deactivated
account. Fix: add an early guard in complete_login_or_signup() that raises
USER_ACCOUNT_DEACTIVATED (5019) before any session or save logic if the
existing user's is_active=False. Remove the is_active=True assignment and
the associated user_activation_email call from save_user_data(). Also
remove the now-unused user_activation_email and base_host imports.

GHSA-wjgv-cq7w-258v: WorkspaceOwnerPermission in both app/permissions/
and utils/permissions/ was filtering WorkspaceMember without is_active=True,
allowing a deactivated workspace owner/admin to retain API access. Add
is_active=True to both copies to match every other permission class.

Co-authored-by: Plane AI <noreply@plane.so>
2026-06-23 18:10:40 +05:30
sriram veeraghanta
04622ce118
fix: harden webhook/link/OAuth-avatar SSRF (advisory clusters A/B/C/E) (#9163)
* fix(api): harden webhook & link-unfurl SSRF (advisory clusters A/B/C)

Resolves three overlapping SSRF advisory clusters around webhook delivery
and work-item link unfurling:

- Cluster A (private-IP validation + PATCH bypass): the webhook PATCH
  handler passed context={request: request} (the request object as the
  dict key) so the loopback/disallowed-domain guard silently no-op'd —
  now context={"request": request}. Hardened IP classification
  (is_blocked_ip) to also block multicast, unspecified, CGNAT
  (100.64.0.0/10), and IPv4 embedded in IPv6 transition addresses
  (IPv4-mapped, NAT64, 6to4, Teredo), robust across Python versions.

- Cluster B (DNS-rebinding TOCTOU): validators resolved DNS, then
  requests resolved it again at connect time. New pinned-IP client
  (plane/utils/url_security.py) resolves+validates once and connects to
  the validated IP literal so urllib3 performs no second lookup, while
  preserving Host header, TLS SNI and certificate verification against
  the real hostname.

- Cluster C (redirect SSRF): webhook delivery never follows redirects;
  the link crawler follows them manually, re-resolving + re-validating +
  re-pinning every hop.

Also: pin requests==2.33.0 in base.txt (imported directly; the pinning
adapter needs the >=2.32 get_connection_with_tls_context hook), and log
webhook URL-validation rejections to WebhookLog instead of swallowing
them.

Tests: new test_url_security.py (pinning, rebinding, redirect
re-validation, IP edge cases, TLS SNI) + updated link-task tests.
Full unit suite: 178 passed.

* fix(api): block OAuth avatar SSRF + add per-advisory SSRF regression tests

Verified every SSRF-class advisory against the current code. The webhook /
link / favicon reports — including the published CVE-2026-30242 and
CVE-2026-39843 and the newer "still bypassable" reports (DNS rebinding
GHSA-3856/-fgcv/-9292/-whh3/-4mjx/-6p39/-fv24/-8wvv, IP-classification gaps
GHSA-75fg, redirect GHSA-6v37/-jw6g/-mq87) — are resolved by the pinned-IP
client + hardened classifier in this branch.

The one SSRF family still unresolved was the OAuth avatar path:
download_and_upload_avatar() fetched the provider-supplied avatar_url with a
raw requests.get (no IP validation, default redirect following), so an
attacker-controlled avatar could reach internal addresses and be exfiltrated
via the static-asset endpoint (GHSA-cv9p-325g-wmv5, and the avatar hop of the
Gitea SSRF GHSA-hx79-5pj5-qh42). It now uses pinned_fetch_following_redirects,
which validates + pins every hop and blocks internal targets.

Adds test_ssrf_advisories.py: a per-advisory regression map covering webhook
IP validation, the PATCH context-key guard, webhook DNS rebinding, webhook
redirect, favicon redirect + rebinding, and OAuth avatar SSRF.

docker compose test: 199 unit tests pass.

* fix(api): address PR review feedback on the SSRF pinned client

- url_security: preserve URL-embedded credentials (user:pass@host) as Basic
  Auth instead of silently dropping them when rewriting to the IP literal
  (Copilot); bracket IPv6-literal hostnames in the Host header (Copilot);
  add stream=True support that keeps the session open until the response is
  closed, and release intermediate redirect hops.
- ip_address / work_item_link_task: treat UnicodeError (IDNA failures) from
  getaddrinfo as a resolution failure, not an uncaught exception (CodeRabbit).
- authentication/adapter/base: stream the avatar download so the size cap
  actually bounds memory, upload the size-bounded buffer (not response.content),
  and always close the response (CodeRabbit, major).
- tests: cover auth preservation, IPv6 Host bracketing, IDNA handling, and
  streamed session lifetime; drop an unused import.

docker compose test: 204 unit tests pass.
2026-05-31 00:12:23 +05:30
Nikhil
351344ecbb
[WEB-5225] feat: enhance authentication logging with detailed error and info message (#7998)
* feat: enhance authentication logging with detailed error and info messages

- Added logging for various authentication events in the Adapter and its subclasses, including email validation, user existence checks, and password strength validation.
- Implemented error handling for GitHub OAuth email retrieval, ensuring proper logging of unexpected responses and missing primary emails.
- Updated logging configuration in local and production settings to include a dedicated logger for authentication events.

* chore: address copilot comments

* chore: addressed some additional comments

* chore: update log

* fix: lint
2026-03-03 19:35:34 +05:30
Jayash Tripathy
53b3358a63
[GIT-44] refactor(auth): add PASSWORD_TOO_WEAK error code (#8522)
* refactor(auth): add PASSWORD_TOO_WEAK error code and update related error handling in password change flow

* fix(auth): update import to use type for EAuthenticationErrorCodes in security page

* Update apps/web/app/(all)/profile/security/page.tsx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/security/page.tsx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* refactor: updated auth error exception accross zxcvbn usages

* fix: improve error handling for password strength validation and update error messages

* i18n(ru): update Russian translations for stickies and automation description

Added translation for 'stickies' and improved formatting of the automation description in Russian locale.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-13 18:51:33 +05:30
sriram veeraghanta
02d0ee3e0f
chore: add copyright (#8584)
* feat: adding new copyright info on all files

* chore: adding CI
2026-01-27 13:54:22 +05:30
Nikhil
c2ce21e56c
[WEB-5657] feat: add synchronization configuration for multiple providers in authentication adapter (#8336)
* feat: add sync functionality for OAuth providers

- Implemented `check_sync_enabled` method to verify if sync is enabled for Google, GitHub, GitLab, and Gitea.
- Added `sync_user_data` method to update user details, including first name, last name, display name, and avatar.
- Updated configuration variables to include sync options for each provider.
- Integrated sync check into the login/signup process.

* feat: add sync toggle for OAuth providers in configuration forms

* fix: remove default value for sync options in OAuth configuration forms

* chore: delete old avatar and upload a new one

* chore: update class method

* chore: add email nullable

* refactor: streamline sync check for multiple providers and improve avatar deletion logic

* fix: ensure ENABLE_SYNC configurations default to "0" for Gitea, Github, Gitlab, and Google forms

* fix: simplify toggle switch value handling in ControllerSwitch component

---------

Co-authored-by: b-saikrishnakanth <bsaikrishnakanth97@gmail.com>
2025-12-22 12:23:39 +05:30
sriram veeraghanta
97e21ba21c
chore: format files in API server (#8292) 2025-12-10 23:50:01 +05:30
Nikhil
2240ac0e74
[WEB-5583]feat: add avatar download and upload functionality in authentication adapter (#8247)
* feat: add avatar download and upload functionality in authentication adapter

- Implemented `download_and_upload_avatar` method to fetch and store user avatars from OAuth providers.
- Enhanced user data saving process to include avatar handling.
- Updated `S3Storage` class with a new `upload_file` method for direct file uploads to S3.

* feat: enhance avatar download functionality with size limit checks

- Added checks for content length before downloading avatar images to ensure they do not exceed the maximum allowed size.
- Implemented chunked downloading of avatar images to handle large files efficiently.
- Updated the upload process to return None if the upload fails, improving error handling.

* feat: improve avatar filename generation with content type handling

- Refactored avatar download logic to determine file extension based on the content type from the response headers.
- Removed redundant code for extension mapping, ensuring a cleaner implementation.
- Enhanced error handling by returning None for unsupported content types.

* fix: remove authorization header for avatar download

- Updated the avatar download logic to remove the Authorization header when token data is not present, ensuring compatibility with scenarios where authentication is not required.

* feat: add method for avatar download headers

- Introduced `get_avatar_download_headers` method to centralize header management for avatar downloads.
- Updated `download_and_upload_avatar` method to utilize the new header method, improving code clarity and maintainability.
2025-12-09 15:48:27 +05:30
Nikhil
1126ca30b0
[WEB-5262] feat: gitea sso (#8022)
* Feature/7137/gitea sso (#7940)

* added gitea auth to admin panel with configs , added api calls

* added gitea to oauth root (for signup and signin)

* removed log

* replace github oauth with gitea ouath error messages

* added gitea to auth root

* fix: update token expiration handling and remove unused variable in Gitea callback

* fix: include Gitea in OAuth enabled checks

* fix: improve error handling when fetching emails from Gitea

* chore : remove logs and add semicolons

* refactor: update Gitea authentication components and imports for consistency

* fix: enhance Gitea authentication form to auto-populate host value and improve OAuth checks

* refactor: enhance Gitea OAuth provider with improved error handling and URL validation

* fix: update authentication success messages to check for string value "1"

---------

Co-authored-by: Shivam Jain <shivam.clgstash@gmail.com>
Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com>
2025-10-28 18:53:54 +05:30
Yaroslav Halchenko
2ca8620246
Add codespell support (config, workflow to detect/not fix) and make it fix some typos (#7864)
* Add github action to codespell preview on push and PRs

* Add rudimentary codespell config

* [DATALAD RUNCMD] chore: run codespell throughout fixing a few typos interactively

=== Do not change lines below ===
{
 "chain": [],
 "cmd": "codespell -w -i 3 -C 4 ./apps/web/core/components/workspace/delete-workspace-form.tsx ./deployments/cli/community/README.md",
 "exit": 0,
 "extra_inputs": [],
 "inputs": [],
 "outputs": [],
 "pwd": "."
}
^^^ Do not change lines above ^^^

* Adjust coespell regex to ignore all camelCased words

* [DATALAD RUNCMD] chore: run codespell throughout fixing a few  new typos automagically

=== Do not change lines below ===
{
 "chain": [],
 "cmd": "codespell -w",
 "exit": 0,
 "extra_inputs": [],
 "inputs": [],
 "outputs": [],
 "pwd": "."
}
^^^ Do not change lines above ^^^
2025-10-07 17:42:49 +05:30
sriram veeraghanta
9237f568dd
[WEB-5044] fix: ruff lint and format errors (#7868)
* fix: lint errors

* fix: file formatting

* fix: code refactor
2025-09-29 19:15:32 +05:30
sriram veeraghanta
fdbe4c2ca6
chore: rename server to api (#7342) 2025-07-04 15:32:21 +05:30