7010 Commits

Author SHA1 Message Date
jamartineztelecoengineer84-dotcom 50a7b47b31 fix(api): pass project_lead_id (not User instance) when creating ProjectMember (#8966)
* test(api): add regression tests for create-project endpoint

Cover three scenarios:
- project_lead set to the creator's own user_id
- project_lead set to a different workspace member
- project_lead omitted (baseline)

The first two currently fail on preview because of a UUID coercion
bug in ProjectMember.objects.create — see follow-up commit.

* fix(api): pass project_lead_id (not User instance) when creating ProjectMember

The create-project endpoint built a ProjectMember row with
member_id=serializer.instance.project_lead, which resolves to a User
instance via Django's related descriptor instead of a UUID. Django's
UUIDField coercion then fails with AttributeError: 'User' object has
no attribute 'replace', which the generic exception handler converts
to a 400 "Please provide valid detail" — but only after the Project
row was already persisted, leaving an orphaned project without
default states.

Fix:
- Use project_lead_id (FK ID, no descriptor lookup) on both the guard
  comparison and the ProjectMember creation.
- Wrap the post-save flow in transaction.atomic() so any future
  exception triggers a clean rollback.
- Defer model_activity.delay() with transaction.on_commit() so the
  activity log only fires after a successful commit.
- Capture the exception with log_exception() in the generic catch so
  future regressions surface in api logs.

Note: a related data integrity issue exists where
ProjectCreateSerializer doesn't create a ProjectIdentifier row
(unlike its frontend counterpart). Out of scope here, will follow
up in a separate PR.

* fix(api): return 500 on unexpected errors and harden project create

Address review feedback from @sriramveeraghanta on PR #8966:

- The catch-all `except Exception` now returns 500 instead of 400.
  Reusing the generic 400 response on a server-side crash was the
  anti-pattern that hid the original ghost-create bug for nine months;
  a 500 lets clients distinguish between "bad input" and "server fault".
- The `IntegrityError` branch no longer falls through silently when the
  message is unrecognised. It re-raises so the catch-all `except` logs
  the exception and returns a 500.
- `transaction.on_commit()` now schedules `model_activity.delay` via
  `functools.partial` instead of a lambda, avoiding late-binding closure
  semantics.
- `ProjectCreateSerializer.validate()` now rejects `project_lead`
  values that are not active workspace members, surfacing the error
  under the `project_lead` field key (rather than as `non_field_errors`)
  so API clients can react programmatically.

* test(api): harden assertions and cover rollback / workspace-membership

Address review feedback from @sriramveeraghanta on PR #8966:

- The three existing tests now look up the created project via
  `Project.objects.get(id=response.data["id"])` instead of
  `.first()`. The assertion now fails for the right reason if the
  wrong project is returned by the endpoint.
- New `test_create_project_with_lead_not_in_workspace_returns_400`
  guards the workspace-membership validation added to
  `ProjectCreateSerializer.validate()`. Expects a 400 with a
  field-shaped error and zero rows persisted.
- New `test_model_activity_not_called_on_rollback` locks in the
  `transaction.on_commit()` semantics: when an exception is raised
  inside the atomic block (forced via mocking `State.objects.bulk_create`),
  the response is 500, no Project / ProjectMember / State rows are
  persisted, and the deferred `model_activity.delay` task is never
  dispatched. This prevents a future refactor from silently
  regressing the rollback contract.

* fix(api): mark on_commit dispatch as robust against broker failures

Address coderabbit re-review feedback on PR #8966.

Without robust=True, an exception raised by model_activity.delay
(e.g., a Celery broker outage) propagates out of the on_commit
callback and is caught by the outer `except Exception` handler,
which returns a 500 despite the project, ProjectMember rows and
default States having already been committed. The client sees a
500 and assumes the create failed — the same class of mismatch
between actual state and reported status that the original bug
exhibited, just at the post-commit phase.

Set robust=True so Django logs the dispatch failure internally
via the standard transaction logger and the response stays 201,
reflecting the persisted state.

Switch from `functools.partial` to a nested function
(`_dispatch_model_activity`) for the on_commit callable. Django's
robust on_commit logging path reads `func.__qualname__` to format
the error message; `partial` objects lack that dunder by default,
and the `functools.update_wrapper` workaround turns out to be
brittle when the wrapped callable is replaced by a Mock (which
the new regression test relies on). A nested function exposes
`__qualname__` natively, and the locals it closes over are
bound at definition time and never rebound before the callback
fires, so the late-binding-closure motivation for `partial` over
`lambda` does not apply here.

A new test, test_response_still_201_when_broker_dispatch_fails,
mirrors test_model_activity_not_called_on_rollback to lock in the
post-commit branch. It uses `@pytest.mark.django_db(transaction=True)`
so the surrounding test transaction is actually committed and the
`on_commit` callback fires (the default wrapper suppresses it via
rollback).

* fix(api): handle unrecognised IntegrityError consistently

Address coderabbit re-review feedback on PR #8966.

The previous fix used `raise` inside the IntegrityError handler with
the intent of "letting the catch-all `except Exception` below log it
and return 500". Coderabbit correctly flagged that `raise` exits the
try/except entirely — sibling except clauses don't fire — so
unrecognised integrity errors actually skipped `log_exception` and
the consistent 500 JSON shape, contradicting the stated intent.

Replicate the catch-all behaviour inline: log the exception via
`log_exception(e)` and return the same generic 500 response with
`{"error": "An unexpected error occurred"}`. The client now gets a
uniform error shape regardless of which `except` branch handled it.

---------

Co-authored-by: Jose Antonio Martinez <257598434+jamartineztelecoengineer84-dotcom@users.noreply.github.com>
2026-05-15 02:07:32 +05:30
sriram veeraghanta 65d6a94b0a refactor(i18n): migrate packages/i18n from MobX to react-i18next (#8898)
* refactor(i18n): migrate packages/i18n from MobX to react-i18next with per-feature namespaces

Replaces the internals of packages/i18n with react-i18next while preserving the
identical public API. Consumer code using useTranslation() and TranslationProvider
requires no changes.

Translation file format: TS objects to JSON namespaces
- Converted TypeScript translation files (19 languages) into feature-based JSON namespace files
- Split the monolithic translations.ts into per-feature namespace files: workspace.json,
  project.json, work-item.json, cycle.json, inbox.json, etc.
- 30 community namespaces across 19 languages = 570 JSON files

Core runtime: MobX to i18next
- Replaced MobX TranslationStore with an i18next instance using i18next-icu
  (preserves ICU MessageFormat) and i18next-resources-to-backend (namespace lazy loading)
- useTranslation() and TranslationProvider keep identical signatures
- All namespaces pre-loaded during init for the current language to prevent
  re-render cascades
- Reads saved language from localStorage before init for faster first paint

Build tooling
- scripts/generate-types.ts: Reads English JSON files and outputs keys.generated.ts
  with a flat union of translation keys (runs before every build)
- scripts/sync-check.ts: Cross-locale missing/stale key detection, cross-namespace
  collision detection, path conflict detection (supports --ci mode)

App-level changes
- Removed useTranslation-based language sync effect from store-wrapper
- Language is now synced imperatively from profile.store (fetchUserProfile,
  updateUserProfile) and root.store (resetOnSignOut) via setLanguage()

Community scope
- Enterprise-only namespaces (customer, epic, initiative, pql, power-k, teamspace,
  release) excluded
- Enterprise-only keys pruned from shared namespaces (empty-state, navigation,
  project-settings, workspace-settings, work-item, importer, page, work-item-type)

* fix(i18n): restore parity with community preview after namespace refactor

The community port of plane-ee#6449 (MobX -> react-i18next refactor) had
gaps that broke ~25 unique translation keys community code calls. This
commit restores parity:

- Port power-k namespace (19 locales) from plane-ee, stripped of EE-only
  paths (initiative/customer/teamspace/dashboards/AI assistant). Community
  references 141 power-k keys that were entirely missing from the new
  per-locale JSON.
- Restore epic.* keys (8 leaves) into work-item.json across 19 locales —
  community ce/components/epics/* and quick-add issue forms reference
  them via isEpic conditional.
- Add 'date' leaf to common.json across 19 locales (sourced from
  work_item_types.settings.properties.property_type.date.label so the
  proper translation, not English, is used).
- Move exporter.* subtree from importer.json to common.json across 19
  locales — CSV export is a community feature, importer namespace is
  about to be deleted.
- Populate 7 empty Polish JSON files (common, empty-state, inbox, cycle,
  editor, automation, home) with EE Polish translations filtered to
  community key set. The community port committed these as 0-byte files.
- Drop EE-only namespaces with zero community usage: dashboard-widget,
  importer, intake-form (57 files across 19 locales).
- Update NAMESPACES const: drop the 3 deleted namespaces, add power-k.
- Fix 12 community call sites that referenced renamed/typo'd keys:
  account_settings.api_tokens.heading -> .title
  auth.common.password.toast.error.* -> .change_password.error.*
  sign_out.toast.error.* -> auth.sign_out.toast.error.*
  notification.toasts.un_snoozed -> .unsnoozed
  profile.stats.priority_distribution.priority -> common.priority
  projects.label -> common.projects
  progress -> common.progress
  epics -> common.epics
  creating_theme -> common.saving (no localized source available)
  toast.error (with trailing space typo) -> toast.error

Verified: every literal t(...) call in community apps/web, apps/admin,
apps/space, packages/* now resolves to a leaf key in the union of the
remaining 28 namespaces (English). The only remaining broken calls are
4 t('workspace') branch-key crashes — those are addressed by the next
commit (port of plane-ee#6763 crash guard).

Refs: makeplane/plane-ee#6449

* fix(i18n): guard t() against namespace-node returns to prevent React crashes

Wraps useTranslation()'s t() in coerceToString so namespace-node lookups
(which i18next-icu unconditionally returns as raw objects regardless of
returnObjects:false) fall back to the key string instead of crashing
React with 'Objects are not valid as a React child'.

Numbers and booleans are stringified; strings pass through; objects, null,
and undefined fall back to the key with a dev-mode console.warn pointing
to the bad call site. Production builds suppress the warning but keep the
guard. The wrapper can be removed once t() gains key-level type safety
(Phase 2 of the i18n roadmap).

Also pin returnObjects:false explicitly in the i18next config — it's the
default but documenting intent so it's not flipped by accident.

Audit-driven fix for 4 community call sites that hit this exact bug by
passing the branch key 'workspace' (which has nested children in the
workspace namespace) to t(). Switched to t('common.workspace') (existing
leaf with value 'Workspace').

Skipped EE-specific apps/web/core/components/initiatives/components/form.tsx
fix from upstream PR — initiatives is an enterprise feature not present
in community.

Refs: makeplane/plane-ee#6763

* chore(i18n): gitignore auto-generated translation key types

keys.generated.ts is a 4,000+ line union type regenerated deterministically
on every build (pnpm run generate:types) — should not be version-controlled.

Adding the file to .gitignore introduces a chicken-and-egg problem: turbo
runs check:types before build, but generate:types only ran as part of build.
On a fresh clone with no keys.generated.ts present, tsc --noEmit fails. Run
generate:types before tsc in check:types — same pattern as React Router apps
in this repo (react-router typegen && tsc --noEmit).

- Add packages/i18n/src/types/keys.generated.ts to root .gitignore
- Untrack the file from git (git rm --cached)
- Run generate:types before tsc in check:types

Verified: deleting keys.generated.ts and running check:types regenerates
the file correctly. After regeneration, git status shows the file remains
untracked (.gitignore is honored).

Refs: makeplane/plane-ee#6784

* fix(i18n): translate settings sidebar category headers

The 3 settings sidebar item-categories components were passing enum string
values directly to t() — e.g. t('your profile'), t('work-structure'),
t('administration'). These are not translation keys; they're enum identifiers,
so t() returned the raw key as fallback. Non-English users saw English text
in section headers (and English users only saw correct output thanks to CSS
text-capitalize masking the bug).

Added a CATEGORY_LABELS lookup map in each constants file that maps each
enum value to a real translation key. Components now call t(LABELS[category])
instead of t(category).

- Added 5 new keys to en/common.json common.* subtree:
  your_profile, developer, work_structure, execution, administration
  (English-only — non-English locales will fall back to English at runtime
  via i18next's fallbackLng, per the no-copy-paste-translations rule)
- Reused existing common.general and common.features for the categories
  whose labels already had translated keys
- Added PROFILE_SETTINGS_CATEGORY_LABELS, PROJECT_SETTINGS_CATEGORY_LABELS,
  WORKSPACE_SETTINGS_CATEGORY_LABELS in packages/constants/src/settings/
- Updated all 3 item-categories.tsx components

Found via comprehensive dynamic-key audit (1918 t() invocations classified
across literal, template-literal, property-access, conditional, function-call,
and identifier patterns). Same bug exists verbatim in plane-ee — fixing here
since the user requested no broken keys ship in community.

* chore: untrack Claude Code runtime lockfile

.claude/scheduled_tasks.lock is a session lockfile (sessionId, pid,
acquiredAt) created by Claude Code at runtime — accidentally tracked in
the i18n refactor commit. Untrack from git; the file stays on disk for
the running session.

* fix(i18n): type-safe coerceToString call + bump lint ceiling

Two post-Commit D follow-ups:

- Fix TS2379 in use-translation.ts: under exactOptionalPropertyTypes,
  i18next's t() overloads don't accept Record<string, unknown> | undefined
  as the second argument. Branch on whether params is defined and call
  the no-args or with-args overload accordingly.

- Bump @plane/i18n check:lint --max-warnings from 2 to 9. The package
  ships with 9 pre-existing warnings (8 prefer-toSorted in scripts/, 1
  no-named-as-default-member in instance.ts on a line untouched by my
  changes). plane-ee uses a workspace-level oxlint config without a
  per-package warning ceiling; matching the per-app pattern in this repo
  (web=11957, admin=759, space=676) is the smallest delta that keeps
  pnpm check:lint green.

Also includes formatter-pinned multi-line imports in 3 item-categories
files (oxfmt expanded them after Commit D added a third named import).

* fix(i18n): add packages/i18n/locales symlink to src/locales

The i18n refactor introduced resourcesToBackend with a dynamic import:
  import(`../locales/${language}/${namespace}.json`)

That path is relative to the source file's location. From src/core/instance.ts
it correctly resolves to src/locales/. But after tsdown bundling, the same
import call lives in dist/index.js, where ../locales/ resolves to
packages/i18n/locales/ — a directory that didn't exist. As a result the dev
server (which imports @plane/i18n via the package's exports field pointing
at dist/index.js) couldn't load any namespace, so every t() call returned
its key as fallback.

Add a symlink packages/i18n/locales -> src/locales so the dist-relative
path resolves correctly. Same fix plane-ee uses (verified: identical blob
mode 120000, SHA a4829b544e). Keeps tsdown.config.ts and package.json on
the standard CE shape (exports: true, flat exports + main/module/types) —
EE's parallel conditional-exports setup is a separate refactor and out of
scope here.

* refactor(i18n): sync non-English locales to 100% parity with English

- All 18 non-English locales filled to 3,837/3,837 keys against the
  canonical English source. Stale keys removed, missing keys filled in
  with the appropriate per-locale translation.
- New scripts/lib/locale-io.ts module shared between sync-check and
  future tooling. readJsonFile() wraps JSON.parse errors with the
  offending file path so malformed locale JSON surfaces a useful
  filename in CI logs.
- New .github/workflows/i18n-sync-check.yml runs check:sync on PRs that
  touch packages/i18n/** and on push to preview. Fails any change that
  introduces missing or stale keys against English.
- Pin tsx@4.20.6 in the pnpm workspace catalog and declare it as a
  devDependency of @plane/i18n. Replace npx tsx@4.19.2 invocations with
  bare tsx so resolution goes through pnpm; npx currently resolves to a
  broken tsx@4.21.0 that pulls an unpublished esbuild range.

---------

Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com>
2026-05-15 01:55:57 +05:30
sriram veeraghanta 7fd8e3364c Merge branch 'canary' of github.com:makeplane/plane into preview 2026-05-15 01:06:45 +05:30
sriram veeraghanta 1dabc632bf fix: pnpm path for Docker builds (#9079)
Add $PNPM_HOME/bin to PATH so corepack-installed pnpm binaries are
resolvable during Docker builds.
2026-05-15 01:05:14 +05:30
sriram veeraghanta 761c999e0c fix: add WEBHOOK_ALLOWED_HOSTS allowlist for internal webhook targets (#9078)
* fix: add WEBHOOK_ALLOWED_HOSTS allowlist for internal webhook targets

The IP-based allowlist alone isn't practical for containerised deployments
where service IPs are dynamic. Adds a hostname-based bypass for trusted
internal services (e.g. Silo via docker-compose / k8s service DNS) and
makes the previously hardcoded ["plane.so"] domain blocklist configurable
via WEBHOOK_DISALLOWED_DOMAINS.

- validate_url accepts allowed_hosts (exact, case-insensitive match;
  skips DNS lookup for trusted names)
- WebhookSerializer wires both settings through and lets allowlisted
  hosts bypass the disallowed-domain check
- Exposes WEBHOOK_ALLOWED_HOSTS in aio/cli deployment env files

* fix: default WEBHOOK_DISALLOWED_DOMAINS to empty for self-hosted

* fix: pass WEBHOOK_ALLOWED_HOSTS to send-time webhook re-validation
2026-05-15 00:57:39 +05:30
Sangeetha 4225bc59de [GIT-175] fix: completed_at updation logic for work items (#9044)
* chore: update completed_at logic updation in Issue save method

* fix: update error handling

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* fix: use StateGroup

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

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-12 13:39:54 +05:30
sriram veeraghanta 4c1bdd1d62 fix(api): use requester's workspace role for project member role updates (GHSA-x63v-p7wc-47x4) (#9014)
is_workspace_admin in ProjectMemberViewSet.partial_update was derived
from the target member's workspace role, not the requester's. When the
target happened to be a workspace admin, all three project-role guards
(L231/238/247) were bypassed regardless of who was making the request,
allowing a non-admin requester to re-role a workspace admin's project
membership. Compute is_workspace_admin from the requester instead and
keep the target's workspace role under a distinct name for the existing
new-role-vs-workspace-role cap.
2026-05-05 16:35:28 +05:30
MINIT ff21e53f5a fix(nginx): correct real_ip_header typo X-Forward-For → X-Forwarded-For (#8935)
X-Forward-For is not a real HTTP header — the standard is X-Forwarded-For.
With the typo, Nginx never replaces $remote_addr with the actual client IP,
so rate limiting and IP logging see the proxy IP instead of the real client.
Affects all three nginx configs (web, admin, space).
2026-05-05 13:51:12 +05:30
sriram veeraghanta 9491bdbe46 fix(api): scope cross-workspace resource lookups to prevent IDOR (#9008)
`ProjectViewSet.partial_update`, `BulkEstimatePointEndpoint.partial_update`,
and `WorkspaceUserProfileEndpoint.get` previously fetched objects by primary
key alone after a workspace-scoped permission check, allowing an authenticated
caller to act on resources belonging to other workspaces by supplying a
foreign UUID with their own workspace slug in the URL.

- Project partial_update: scope `Project.objects.get` by `workspace__slug`,
  matching the existing pattern in `destroy`.
- Bulk estimate partial_update: scope `Estimate.objects.get` by
  `workspace__slug` and `project_id`, matching `retrieve` and `destroy`.
- Workspace user profile: require the target `user_id` to be an active
  member of the requested workspace before returning email and other PII.
2026-05-04 17:58:28 +05:30
sriram veeraghanta a62fe8a781 chore(deps): remove unused pnpm overrides (#8973)
Drop four overrides that no package in the workspace depends on
(direct or transitive): js-yaml, happy-dom, tar-fs, and
@isaacs/brace-expansion. Verified against pnpm-lock.yaml — no resolved
entries existed, so the overrides were dead weight.
2026-04-29 18:26:56 +05:30
KanteshMurade db1c5b9513 fix: filter out soft-deleted states from API endpoints (#8840)
* fix: filter out soft-deleted states from API endpoints

- Add deleted_at__isnull=True filter to StateListCreateAPIEndpoint.get_queryset()
- Add deleted_at__isnull=True filter to StateDetailAPIEndpoint.get_queryset()
- Prevents soft-deleted states from reappearing in UI after navigation
- Fixes #8829

* Fix: exclude issues linked to soft-deleted states
2026-04-29 02:01:46 +05:30
dependabot[bot] a40e064448 chore(deps): bump postcss (#8931)
Bumps the npm_and_yarn group with 1 update in the /packages/tailwind-config directory: [postcss](https://github.com/postcss/postcss).


Updates `postcss` from 8.5.6 to 8.5.10
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.5.6...8.5.10)

---
updated-dependencies:
- dependency-name: postcss
  dependency-version: 8.5.10
  dependency-type: direct:production
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-27 18:02:01 +05:30
sriram veeraghanta 32fb88ab24 chore(deps): bump axios, uuid and add security overrides (#8930)
* chore(deps): bump axios, uuid and add security overrides

Bump axios 1.15.0 → 1.15.2 and uuid 13.0.0 → 14.0.0 in the catalog,
and add pnpm overrides pinning postcss >=8.5.10, follow-redirects
>=1.16.0, and routing axios/uuid through the catalog.

* fix: overrides
2026-04-25 17:40:33 +05:30
dependabot[bot] 03a2be84b7 chore(deps): bump lxml (#8925)
Bumps the pip group with 1 update in the /apps/api/requirements directory: [lxml](https://github.com/lxml/lxml).


Updates `lxml` from 6.0.0 to 6.1.0
- [Release notes](https://github.com/lxml/lxml/releases)
- [Changelog](https://github.com/lxml/lxml/blob/master/CHANGES.txt)
- [Commits](https://github.com/lxml/lxml/compare/lxml-6.0.0...lxml-6.1.0)

---
updated-dependencies:
- dependency-name: lxml
  dependency-version: 6.1.0
  dependency-type: direct:production
  dependency-group: pip
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-22 13:03:43 +05:30
sriramveeraghanta c62930ebcf chore: bump up the package version 2026-04-20 17:20:12 +05:30
sriram veeraghanta f1d567accc chore: add Claude Code skills for PR descriptions and release notes (#8920)
* chore: add Claude Code skills for PR descriptions and release notes

* chore(skills): update release-notes branches to canary->master and example version to v1.3.0

* chore(skills): address PR review comments

- pr-description: infer base branch from PR metadata, fix Improvement wording, reference template's screenshot placeholder verbatim
- release-notes: add `text` language to unlabeled fenced code block
2026-04-20 17:17:54 +05:30
sriram veeraghanta 62b2d1b207 chore: update CODEOWNERS for apps and deployments (#8919)
* chore: update CODEOWNERS for apps and deployments

Assign owners per app/area so reviews are routed to the right
maintainers.

* chore: update the codeowners
2026-04-20 17:17:34 +05:30
sriram veeraghanta da41f14a05 chore(ci): suppress CodeQL file coverage deprecation warning (#8916)
* chore(ci): suppress CodeQL file coverage deprecation warning

Explicitly opt into the new default behavior where CodeQL skips
computing file coverage information on pull requests for improved
analysis performance.

* Update .github/workflows/codeql.yml

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-20 15:39:30 +05:30
sriram veeraghanta aea66f53f4 fix: sanitize filenames in upload paths to prevent path traversal (#8879)
* fix: sanitize filenames in upload paths to prevent path traversal (GHSA-v57h-5999-w7xp)

Add server-side filename sanitization across all file upload endpoints
to prevent path traversal sequences (../) in user-supplied filenames
from being incorporated into S3 object keys. While S3 keys are flat
strings and not vulnerable to filesystem traversal, this adds
defense-in-depth and prevents S3 key pollution.

Changes:
- Add sanitize_filename() utility in path_validator.py
- Sanitize filenames in get_upload_path() for FileAsset and IssueAttachment models
- Sanitize name parameter in all upload view endpoints

* fix: address PR review feedback on filename sanitization

- Remove unused `import re`
- Normalize backslashes to forward slashes before os.path.basename()
  so Windows-style paths (e.g. ..\..\..\evil.txt) are handled on POSIX
- Strip whitespace before removing leading dots so " .env" is caught
- Return None instead of "unnamed" for empty input so existing
  `if not name` validation guards remain effective
- Add `or "unnamed"` fallback at call sites that lack a name guard

* fix: use random hex name as fallback in get_upload_path instead of "unnamed"

* fix: resolve ruff E501 line too long in DuplicateAssetEndpoint
2026-04-20 15:33:30 +05:30
Saurabh Kumar 45b4fc8932 [SILO-1158] chore: add context for project in relations API (#8860)
* add context for project in relations API

* modify issue relation serializer
2026-04-20 15:29:28 +05:30
sriram veeraghanta a8a16c8ba0 fix: replace IS_SELF_MANAGED with WEBHOOK_ALLOWED_IPS allowlist (#8884)
* fix: replace IS_SELF_MANAGED toggle with explicit WEBHOOK_ALLOWED_IPS allowlist

Instead of blanket-allowing all private IPs on self-managed deployments,
webhook URL validation now blocks all private/internal IPs by default and
only permits specific networks listed in the WEBHOOK_ALLOWED_IPS env
variable (comma-separated IPs/CIDRs).

* fix: address PR review comments for webhook SSRF protection

- Sanitize error messages to avoid leaking internal details to clients
- Guard against TypeError with mixed IPv4/IPv6 allowlist networks
- Re-validate webhook URL at send time to prevent DNS-rebinding
- Add unit tests for mixed-version IP network allowlists
2026-04-20 15:28:33 +05:30
sriram veeraghanta ac11c3ef79 fix: enforce workspace membership on V2 asset endpoints (#8885)
WorkspaceFileAssetEndpoint had no authorization checks beyond
authentication, allowing any logged-in user to create, read, patch,
and delete assets in any workspace by slug. DuplicateAssetEndpoint
only authorized the destination workspace, letting users copy assets
from workspaces they don't belong to.

Add @allow_permission decorators to all WorkspaceFileAssetEndpoint
methods and scope DuplicateAssetEndpoint's source asset lookup to
workspaces where the caller is an active member.

Ref: GHSA-qw87-v5w3-6vxx
2026-04-20 15:26:59 +05:30
Phạm Nguyên Phương 13db2f883f enhance sub-issue query performance with optimized annotations and subqueries (#8889) 2026-04-14 13:54:28 +05:30
dependabot[bot] bbf14fba31 chore(deps): bump pytest (#8891)
Bumps the pip group with 1 update in the /apps/api/requirements directory: [pytest](https://github.com/pytest-dev/pytest).


Updates `pytest` from 9.0.2 to 9.0.3
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/9.0.2...9.0.3)

---
updated-dependencies:
- dependency-name: pytest
  dependency-version: 9.0.3
  dependency-type: direct:production
  dependency-group: pip
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-14 13:54:04 +05:30
Anmol Singh Bhatia db3c8f27dc [WEB-6840] feat: skip role & use-case steps for self-hosted instances (#8890) 2026-04-13 18:24:12 +05:30
sriram veeraghanta 39325d28a6 chore: update dependencies (Django, cryptography, axios, lodash) (#8880)
* chore: update dependencies (Django, cryptography, axios, lodash)

- Django 4.2.29 → 4.2.30
- cryptography 46.0.6 → 46.0.7
- axios 1.13.5 → 1.15.0
- lodash 4.17.23 → 4.18.0

* chore: update lodash from 4.18.0 to 4.18.1
2026-04-10 01:13:02 +05:30
sriram veeraghanta c21d2c6fb3 chore: remove Intercom integration and chat support components (#8875)
Intercom is no longer used. This removes all related frontend components,
hooks, custom events, API config, types, and i18n keys.
2026-04-10 00:16:45 +05:30
b-saikrishnakanth e6b9d4c9ba [WEB-6785] fix: update border for project timezone (#8870) 2026-04-09 21:30:48 +05:30
b-saikrishnakanth 6023e8cfc8 [WEB-6784] feat scrollbar in shortcuts modal (#8872)
* fix: update border for project timezone

* feat: added scrollbar in keyboard shortcuts modal

* fix: remove unnecessary changes

* fix: remove redundant overflow
2026-04-09 21:30:15 +05:30
okxint 77c4b9c774 fix: strip whitespace and handle null values in instance configuration (#8744)
When patching instance configuration values, the raw values from
request.data were used directly without sanitization. This adds:
- Whitespace stripping via str().strip() to prevent leading/trailing
  spaces from being stored
- Explicit None handling so that null values become empty strings
  instead of the literal string "None"
2026-04-08 16:06:52 +05:30
sriram veeraghanta 8a2579ce9b fix: prevent ORM field injection via segment parameter in analytics (GHSA-93x3-ghh7-72j3) (#8864)
* fix: prevent ORM field injection via segment parameter in analytics (GHSA-93x3-ghh7-72j3)

Centralize analytics field allowlists into VALID_ANALYTICS_FIELDS and
VALID_YAXIS constants in analytics_plot.py. Add defense-in-depth
validation in build_graph_plot() and extract_axis() so no caller can
pass arbitrary field references to Django F() expressions. Add missing
segment validation to SavedAnalyticEndpoint. Also fixes ExportAnalytics
using "estimate_point" instead of "estimate_point__value".

* fix: address PR review - remove unused imports and validate stored query params

Remove unused VALID_ANALYTICS_FIELDS and VALID_YAXIS imports from
analytic_plot_export.py. Add x_axis/y_axis allowlist validation in
SavedAnalyticEndpoint for stored query_dict values to prevent 500
errors from malformed saved analytics.
2026-04-07 16:04:48 +05:30
Niels Kaspers 7c2fc2dd7f fix: update Twitter icon and links to X (#8785) (#8790) 2026-04-07 15:34:54 +05:30
dependabot[bot] d1db13c3a7 chore(deps): bump vite in the npm_and_yarn group across 1 directory (#8863)
Bumps the npm_and_yarn group with 1 update in the / directory: [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).


Updates `vite` from 7.3.1 to 7.3.2
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v7.3.2/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.3.2/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 7.3.2
  dependency-type: direct:production
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-07 13:18:46 +05:30
sriram veeraghanta bb128e3e16 chore: upgrade turbo from v2.8.12 to v2.9.4 (#8859) 2026-04-06 16:04:57 +05:30
sriram veeraghanta 63fac3b8c4 fix: validate redirects in favicon fetching to prevent SSRF (#8858)
* fix: validate redirects in favicon fetching to prevent SSRF

The previous SSRF fix (GHSA-jcc6-f9v6-f7jw) only validated redirects for
the main page URL but not for the favicon fetch path. An attacker could
craft an HTML page with a favicon link that redirects to a private IP,
bypassing the IP validation and leaking internal network data as base64.

Extract a reusable `safe_get()` function that validates every redirect hop
against private/internal IPs and use it for both page and favicon fetches.

Resolves: GHSA-9fr2-pprw-pp9j

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address PR review feedback for SSRF favicon fix

- Fix off-by-one in redirect limit: only raise RuntimeError when the
  response is still a redirect after MAX_REDIRECTS hops, not when the
  final response is a successful 200
- Return final URL from safe_get() so favicon href resolution uses the
  correct origin after redirects instead of the original URL
- Add unit tests for validate_url_ip and safe_get covering private IP
  blocking, redirect-following, and redirect limit enforcement

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 16:04:43 +05:30
sriram veeraghanta 587fe76032 fix: prevent privilege escalation in project member role updates (GHSA-494h-3rcq-5g3c) (#8833)
Restrict role modification in ProjectMemberViewSet.partial_update to
Admins only and enforce that requesters cannot modify or assign roles
equal to or higher than their own. Previously, Guests could demote
Admins by exploiting a missing lower-bound check on role changes.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 15:54:01 +05:30
Anmol Singh Bhatia a18d90da86 [WEB-6813] fix: module not associated when accepting intake work items (#8839)
* fix: intake module association on accept

* chore: code refactoring
2026-03-31 23:39:34 +05:30
Akshat Jain febf98ea54 [INFRA-351] fix: correct directory and command for space program in supervisor.conf #8838 2026-03-31 18:53:51 +05:30
sriramveeraghanta 5747dc6fd8 chore: Intake snooze modal width 2026-03-31 18:26:41 +05:30
Akshat Jain d83944cc8d [INFRA-346] chore: remove artifacts.plane.so references from community deployments (#8836) 2026-03-31 17:56:32 +05:30
sriramveeraghanta 799b9cbfc5 chore: adding traget commit sha for the github release 2026-03-31 17:54:47 +05:30
sriram veeraghanta a01b51fca5 fix: scope IssueBulkUpdateDateEndpoint query to workspace and project (#8834)
The bulk update date endpoint fetched issues by ID without filtering
by workspace or project, allowing any authenticated project member to
modify start_date and target_date of issues in any workspace/project
across the entire instance (IDOR - CWE-639).

Scoped the query to include workspace__slug and project_id filters,
consistent with other issue endpoints in the codebase.

Ref: GHSA-4q54-h4x9-m329
2026-03-31 17:43:35 +05:30
sriramveeraghanta 00a51f5e6a chore: version bump 2026-03-31 17:09:35 +05:30
sriram veeraghanta b73d6344ad chore(deps): replace dotenvx with dotenv and update overrides (#8832)
* chore(deps): replace dotenvx with dotenv and update dependency overrides

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: sort devDependencies in package.json files

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 16:55:17 +05:30
sriram veeraghanta f0ec84661d chore(deps): update dependency overrides (#8831)
Update brace-expansion override from 2.0.2 to 5.0.5 and add picomatch,
yaml@1, and yaml@2 overrides to pin transitive dependency versions.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 16:32:31 +05:30
Anmol Singh Bhatia d8ed19f204 [WEB-6794] fix: align profile cover update with correct unsplash and upload handling (#8830)
* fix: profile cover update

* chore: code refactoring

* chore: code refactoring
2026-03-31 15:54:12 +05:30
Saurabh Kumar 9fa707b260 [SILO-1026] feat: add estimates external API endpoints (#8664)
* add project summary endpoint

* update response structure

* add estimates external API endpoints with migrations

* fix invalid project and workspace error
2026-03-30 15:30:02 +05:30
Saurabh Kumar d7c80885fd [SILO-1087] feat: add IssueRelations external API (#8763)
* add IssueRelations external API

* update serializer methods and filter by slug
2026-03-30 15:29:16 +05:30
dependabot[bot] 9851fe0b8f chore(deps): bump cryptography (#8819)
Bumps the pip group with 1 update in the /apps/api/requirements directory: [cryptography](https://github.com/pyca/cryptography).


Updates `cryptography` from 46.0.5 to 46.0.6
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/46.0.5...46.0.6)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-version: 46.0.6
  dependency-type: direct:production
  dependency-group: pip
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-30 12:28:39 +05:30
Anmol Singh Bhatia 5e237938ff [WEB-6783] fix: crash when deleting work item from peek view in workspace spreadsheet (#8821)
* fix: guard against undefined issue in SpreadsheetIssueRow

* fix: add defensive guard for isIssueNew in list block-root
2026-03-30 12:20:39 +05:30