[VPAT-55] chore(security): implement input validation across authentication and workspace forms (#8528)

* chore(security): implement input validation across authentication and workspace forms

  - Add OWASP-compliant autocomplete attributes to all auth input fields
  - Create centralized validation utilities blocking injection-risk characters
  - Apply validation to names, display names, workspace names, and slugs
  - Block special characters: < > ' " % # { } [ ] * ^ !
  - Secure sensitive input fields across admin, web, and space apps

* chore: add missing workspace name validation to settings and admin forms

* feat: enhance validation regex for international names and usernames

- Updated regex patterns to support Unicode characters for person names, display names, company names, and slugs.
- Improved validation functions to block injection-risk characters in names and slugs.
This commit is contained in:
Prateek Shourya 2026-02-17 00:18:46 +05:30 committed by GitHub
parent 55e89cb8fc
commit 49fc6aa0a0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 281 additions and 54 deletions

View file

@ -14,6 +14,7 @@ import { Button, getButtonStyling } from "@plane/propel/button";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import { InstanceWorkspaceService } from "@plane/services";
import type { IWorkspace } from "@plane/types";
import { validateSlug, validateWorkspaceName } from "@plane/utils";
// components
import { CustomSelect, Input } from "@plane/ui";
// hooks
@ -96,14 +97,7 @@ export function WorkspaceCreateForm() {
control={control}
name="name"
rules={{
required: "This is a required field.",
validate: (value) =>
/^[\w\s-]*$/.test(value) ||
`Workspaces names can contain only (" "), ( - ), ( _ ) and alphanumeric characters.`,
maxLength: {
value: 80,
message: "Limit your name to 80 characters.",
},
validate: (value) => validateWorkspaceName(value, true),
}}
render={({ field: { value, ref, onChange } }) => (
<Input
@ -135,11 +129,7 @@ export function WorkspaceCreateForm() {
control={control}
name="slug"
rules={{
required: "The URL is a required field.",
maxLength: {
value: 48,
message: "Limit your URL to 48 characters.",
},
validate: (value) => validateSlug(value),
}}
render={({ field: { onChange, value, ref } }) => (
<Input

View file

@ -13,7 +13,7 @@ import { API_BASE_URL, E_PASSWORD_STRENGTH } from "@plane/constants";
import { Button } from "@plane/propel/button";
import { AuthService } from "@plane/services";
import { Checkbox, Input, PasswordStrengthIndicator, Spinner } from "@plane/ui";
import { getPasswordStrength } from "@plane/utils";
import { getPasswordStrength, validatePersonName, validateCompanyName } from "@plane/utils";
// components
import { AuthHeader } from "@/app/(all)/(home)/auth-header";
import { Banner } from "../common/banner";
@ -173,9 +173,15 @@ export function InstanceSetupForm() {
inputSize="md"
placeholder="Wilber"
value={formData.first_name}
onChange={(e) => handleFormChange("first_name", e.target.value)}
autoComplete="on"
onChange={(e) => {
const validation = validatePersonName(e.target.value);
if (validation === true || e.target.value === "") {
handleFormChange("first_name", e.target.value);
}
}}
autoComplete="off"
autoFocus
maxLength={50}
/>
</div>
<div className="w-full space-y-1">
@ -190,8 +196,14 @@ export function InstanceSetupForm() {
inputSize="md"
placeholder="Wright"
value={formData.last_name}
onChange={(e) => handleFormChange("last_name", e.target.value)}
autoComplete="on"
onChange={(e) => {
const validation = validatePersonName(e.target.value);
if (validation === true || e.target.value === "") {
handleFormChange("last_name", e.target.value);
}
}}
autoComplete="off"
maxLength={50}
/>
</div>
</div>
@ -229,7 +241,13 @@ export function InstanceSetupForm() {
inputSize="md"
placeholder="Company name"
value={formData.company_name}
onChange={(e) => handleFormChange("company_name", e.target.value)}
onChange={(e) => {
const validation = validateCompanyName(e.target.value, false);
if (validation === true || e.target.value === "") {
handleFormChange("company_name", e.target.value);
}
}}
maxLength={80}
/>
</div>