Skip to content

[Due for payment 2026-03-25] [$250] Tax - Angle brackets in tax name create duplicate tax rates & reverts on selection #83183

Closed

Description

@jponikarchuk

If you haven’t already, check out our contributing guidelines for onboarding and email contributors@expensify.com to request to join our Slack channel!


Version Number: V9.3.24-1
Reproducible in staging?: Yes
Reproducible in production?: Yes
If this was caught during regression testing, add the test name, ID and link from BrowserStack: https://test-management.browserstack.com/projects/2219752/folder/13176868/test-cases/41237324
Email or phone of affected tester (no customers): dreamnitethedragon@gmail.com
Issue reported by: Applause Internal Team
Bug source: Exploratory - Significant User Experience Deterioration
Device used: Redminote 10S Android 13
App Component: Workspace Settings

Action Performed:

  1. Launch app
  2. Create workpace
  3. Enable taxes
  4. Add rate and save
  5. Note invalid character error is shown
  6. Enter tax name GST<10%>, tax value and save
  7. Note no error is shown but two rates are created GST<10%> and GST.
  8. Go to workspace chat
  9. Create a manual expense selecting GST as tax rate
  10. Open the expense
  11. Tap tax and select GST<10%>
  12. Note selection gets reverted back to GST

Expected Result:

If < > are allowed:
Tax rate should be saved exactly as entered (GST<10%>).
Only one rate should be created.
Selecting that rate in an expense should work normally.
Selection should not revert.

If < > are invalid characters:
System should show validation error consistently for all cases.
No tax rate should be created.

Actual Result:

Saving tax name as :
→ Shows "invalid character" error.
GST<10%> → No error shown.
But two tax rates are created:
GST
GST<10%>
When selecting GST<10%> in an expense:
Selection reverts back to GST.

Workaround:

Unknown

Platforms:

  • Android: App
  • Android: mWeb Chrome
  • iOS: App
  • iOS: mWeb Safari
  • iOS: mWeb Chrome
  • Windows: Chrome
  • MacOS: Chrome / Safari

Screenshots/Videos

1.mp4

View all open jobs on GitHub

Issue OwnerCurrent Issue Owner: @mallenexpensify
Upwork Automation - Do Not Edit
  • Upwork Job URL: https://www.upwork.com/jobs/~022028950494499555586
  • Upwork Job ID: 2028950494499555586
  • Last Price Increase: 2026-03-10

Activity

  1. abbasifaizan70 commented on Feb 23, 2026

    @abbasifaizan70
    Contributor

    🚨 Edited by proposal-police: This proposal was edited at 2026-03-04 23:18:00 UTC.

    Proposal

    Please re-state the problem that we are trying to solve in this issue.

    When creating or editing workspace tax rates, inputs containing angle brackets (for example GST<10%>) are not handled consistently. In some flows, the form allows save attempts and can lead to two visible tax entries after sync (for example GST<10%> and GST). Later, selecting the intended tax in an expense can revert to a different tax, which breaks expected behavior.

    What is the root cause of that problem?

    The tax name forms currently rely on the default HTML tag validation behavior in FormProvider, which is not strict enough for cases like GST<10%>. Because the frontend check is inconsistent with backend handling for angle-bracket patterns, invalid names can pass in some paths and create inconsistent post-save behavior.

    Relevant code (permalinks):

    const validateForHtmlTagRegex = shouldUseStrictHtmlTagValidation ? CONST.STRICT_VALIDATE_FOR_HTML_TAG_REGEX : CONST.VALIDATE_FOR_HTML_TAG_REGEX;

    <FormProvider
    style={[styles.flexGrow1, styles.mh5]}
    formID={ONYXKEYS.FORMS.WORKSPACE_NEW_TAX_FORM}
    onSubmit={submitForm}
    validate={validateForm}
    submitButtonText={translate('common.save')}
    enabledWhenOffline
    shouldValidateOnBlur={false}
    addBottomSafeAreaPadding
    >

    <FormProvider
    formID={ONYXKEYS.FORMS.WORKSPACE_TAX_NAME_FORM}
    submitButtonText={translate('workspace.editor.save')}
    style={[styles.flexGrow1, styles.ph5]}
    onSubmit={submit}
    enabledWhenOffline
    validate={validate}
    shouldHideFixErrorsAlert
    addBottomSafeAreaPadding
    >

    What changes do you think we should make in order to solve the problem?

    Add shouldUseStrictHtmlTagValidation to the FormProvider in both tax name creation and tax name editing forms. This ensures frontend validation consistently rejects angle-bracket patterns like GST<10%>, matching backend behavior and preventing invalid tax creation.

    Location 1: App/src/pages/workspace/taxes/WorkspaceCreateTaxPage.tsx

    Original code (permalink):

    <FormProvider
    style={[styles.flexGrow1, styles.mh5]}
    formID={ONYXKEYS.FORMS.WORKSPACE_NEW_TAX_FORM}
    onSubmit={submitForm}
    validate={validateForm}
    submitButtonText={translate('common.save')}
    enabledWhenOffline
    shouldValidateOnBlur={false}
    addBottomSafeAreaPadding
    >

    Suggested change (TypeScript):

    <FormProvider
        style={[styles.flexGrow1, styles.mh5]}
        formID={ONYXKEYS.FORMS.WORKSPACE_NEW_TAX_FORM}
        onSubmit={submitForm}
        validate={validateForm}
        submitButtonText={translate('common.save')}
        enabledWhenOffline
        shouldValidateOnBlur={false}
        addBottomSafeAreaPadding
        shouldUseStrictHtmlTagValidation
    >

    Location 2: App/src/pages/workspace/taxes/NamePage.tsx

    Original code (permalink):

    <FormProvider
    formID={ONYXKEYS.FORMS.WORKSPACE_TAX_NAME_FORM}
    submitButtonText={translate('workspace.editor.save')}
    style={[styles.flexGrow1, styles.ph5]}
    onSubmit={submit}
    enabledWhenOffline
    validate={validate}
    shouldHideFixErrorsAlert
    addBottomSafeAreaPadding
    >

    Suggested change (TypeScript):

    <FormProvider
        formID={ONYXKEYS.FORMS.WORKSPACE_TAX_NAME_FORM}
        submitButtonText={translate('workspace.editor.save')}
        style={[styles.flexGrow1, styles.ph5]}
        onSubmit={submit}
        enabledWhenOffline
        validate={validate}
        shouldHideFixErrorsAlert
        addBottomSafeAreaPadding
        shouldUseStrictHtmlTagValidation
    >

    This makes the Invalid character error appear immediately for values like GST<10%> and avoids the inconsistent flow where duplicate rates can appear and selected tax can revert.


    What alternative solutions did you explore? (Optional)

    Alternative 1 - Allow < and > in tax names and escape everywhere:

    • This requires escaping/encoding in validation, storage, matching, rendering, and search/selection flows.
    • Risk is higher because any missed path can reintroduce duplicate keys or selection mismatches.
    • Decision: avoid this for now and keep strict rejection behavior.

    Alternative 2 - Add extra domain validation in tax actions:

    • This also works but duplicates validation responsibility across layers.
    • Decision: first align form-level validation using strict HTML tag validation in both tax name forms.

    Demo

    Image
  2. added
    BugSomething is broken. Auto assigns a BugZero manager.
    on Mar 3, 2026
  3. melvin-bot commented on Mar 3, 2026

    @melvin-bot

    Triggered auto assignment to @dylanexpensify (Bug), see https://stackoverflow.com/c/expensify/questions/14418 for more details. Please add this bug to a GH project, as outlined in the SO.

  4. MelvinBot commented on Mar 3, 2026

    @MelvinBot
    Contributor

    🔍 Issue Analysis

    Summary: Tax names containing certain angle bracket patterns (e.g., GST<10%>) bypass frontend HTML-tag validation but get stripped by the backend WAF, creating duplicate tax rates and causing selection reversion.

    Root Cause: The frontend uses VALIDATE_FOR_HTML_TAG_REGEX (/<\/?\w*((\s+\w+...)+\s*|\s*)\/?>/g) which only catches valid HTML-like tags — those where the content after < starts with word characters (e.g., <Shahrukh>, <div>). Patterns like <10%> start with a digit followed by %, which doesn't match the regex's attribute/whitespace expectations after the tag name, so they pass validation. The backend WAF applies stricter stripping that removes any angle-bracketed content, creating a mismatch: the frontend optimistically saves GST<10%> while the backend creates GST, resulting in duplicate rates and broken selection.

    The codebase already has STRICT_VALIDATE_FOR_HTML_TAG_REGEX (/<([^>\s]+)(?:[^>]*?)>/g) which catches any content in angle brackets, and a shouldUseStrictHtmlTagValidation prop on FormProvider to enable it. This prop is already used on merchant-related forms but is not enabled on tax name forms.

    Recommended Fix: Add shouldUseStrictHtmlTagValidation to the FormProvider in:

    1. src/pages/workspace/taxes/WorkspaceCreateTaxPage.tsx (tax rate creation form)
    2. src/pages/workspace/taxes/NamePage.tsx (tax rate rename form)

    This aligns frontend validation with backend behavior, consistently rejecting names like GST<10%> before submission.

    Detailed Investigation

    Root Cause Analysis

    The validation flow in FormProvider.tsx:162 selects which regex to use:

    const validateForHtmlTagRegex = shouldUseStrictHtmlTagValidation
        ? CONST.STRICT_VALIDATE_FOR_HTML_TAG_REGEX
        : CONST.VALIDATE_FOR_HTML_TAG_REGEX;

    Why <Shahrukh> is caught but <10%> is not:

    • VALIDATE_FOR_HTML_TAG_REGEX matches <\w* — Shahrukh is all word characters, so <Shahrukh> matches
    • For <10%>, \w* matches 10, then %> fails to match the expected attribute/whitespace pattern, so the whole match fails

    Why STRICT_VALIDATE_FOR_HTML_TAG_REGEX catches both:

    • It uses <([^>\s]+) — any one or more characters that aren't > or whitespace — so <10%>, <✓>, <Shahrukh> all match

    Evidence Chain

    1. User enters GST<10%> → frontend VALIDATE_FOR_HTML_TAG_REGEX doesn't match → no error shown
    2. Form submits with optimistic update creating GST<10%> locally
    3. Backend WAF strips <10%> → creates rate named GST
    4. Two rates now exist: GST<10%> (optimistic) and GST (server)
    5. Selecting GST<10%> sends a tax code the backend doesn't recognize → reverts to GST

    Relevant Code

    • App/src/CONST/index.ts:2066-2071 — Both regex definitions
    • App/src/components/Form/FormProvider.tsx:155-162 — Regex selection logic
    • App/src/pages/workspace/taxes/WorkspaceCreateTaxPage.tsx:92-101 — Create form missing strict validation
    • App/src/pages/workspace/taxes/NamePage.tsx:86-95 — Rename form missing strict validation

    Existing Usage Pattern

    shouldUseStrictHtmlTagValidation is already used in:

    • src/pages/workspace/rules/MerchantRules/AddMerchantToMatchPage.tsx
    • src/pages/iou/request/step/IOURequestStepMerchant.tsx
    • src/components/Rule/TextBase.tsx

    Additional Consideration

    WorkspaceTaxesSettingsCustomTaxName.tsx (custom tax name setting) and WorkspaceTaxCodePage.tsx (tax code editing) also use FormProvider without strict validation. Depending on whether the backend applies the same WAF stripping to these fields, they may need the same fix. The reported issue specifically covers tax rate names, so the primary fix targets the two pages above.


    Next Steps for Contributor+ team: Reply with @MelvinBot implement this to create a draft PR, or @MelvinBot <your feedback> to refine this analysis.

  5. added
    ExternalAdded to denote the issue can be worked on by a contributor
    on Mar 3, 2026
  6. added
    Help WantedApply this label when an issue is open to proposals by contributors
    on Mar 3, 2026
  7. 25 remaining items

  8. melvin-bot commented on Mar 18, 2026

    @melvin-bot

    Reviewing label has been removed, please complete the "BugZero Checklist".

  9. melvin-bot commented on Mar 18, 2026

    @melvin-bot

    Triggered auto assignment to @mallenexpensify (Awaiting Payment)

  10. melvin-bot commented on Mar 18, 2026

    @melvin-bot

    The solution for this issue has been 🚀 deployed to production 🚀 in version 9.3.39-3 and is now subject to a 7-day regression period 📆. Here is the list of pull requests that resolve this issue:

    If no regressions arise, payment will be issued on 2026-03-25. 🎊

    For reference, here are some details about the assignees on this issue:

  11. melvin-bot commented on Mar 18, 2026

    @melvin-bot

    @sobitneupane The PR fixing this issue has been merged! The following checklist (instructions) will need to be completed before the issue can be closed. Please copy/paste the BugZero Checklist from here into a new comment on this GH and complete it. If you have the K2 extension, you can simply click: [this button]

  12. allornothingai commented on Mar 18, 2026

    @allornothingai
    --- /dev/null
    +++ b/src/libs/TaxRateUtils.js
    @@ -0,0 +1,8 @@
    +/**
    + * @param {string} taxName
    + * @returns {boolean} True if the tax name is valid, false otherwise.
    + */
    +function isValidTaxName(taxName) {
    +    // Angle brackets (< and >) are invalid characters in tax names as they can cause parsing issues,
    +    // data corruption, or inconsistent display/selection behavior within the application.
    +    return !/[<>]/.test(taxName);
    +}
    +
    +export {
    +    isValidTaxName,
    +};

    EVM Wallet for Payout: 0x0c67cbE9e30c66267975eE2D74Eb88036CA65e9F
    Solana Wallet for Payout: 3hLpMvUUS685bZz2PzR6vWeA37UrdKq924s7yLMB47eZ
    XRPL Wallet for Payout: 0x0c67cbE9e30c66267975eE2D74Eb88036CA65e9F (EVM Sidechain)

  13. melvin-bot commented on Mar 25, 2026

    @melvin-bot

    Payment Summary

    Upwork Job

    BugZero Checklist (@mallenexpensify)

    • (if NewFeature) I have created a PR for any necessary HelpDot updates, or I confirmed no updates are necessary.
    • I have verified the correct assignees and roles are listed above and updated the necessary manual offers
    • I have verified that there are no duplicate or incorrect contracts on Upwork for this job (https://www.upwork.com/ab/applicants/2028950494499555586/hired)
    • I have verified the PR was not reverted
    • I have applied any discounts due to bugs/regressions introduced by this PR
    • I have paid out the Upwork contracts or cancelled the ones that are incorrect
    • I have verified the payment summary above is correct
  14. mallenexpensify commented on Mar 26, 2026

    @mallenexpensify
    Contributor

    Payment Summary

    Contributor+: @sobitneupane due $250 via NewDot

  15. JmillsExpensify commented on May 24, 2026

    @JmillsExpensify
    Contributor

    $250 approved for @sobitneupane

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Awaiting PaymentAuto-added when associated PR is deployed to productionBugSomething is broken. Auto assigns a BugZero manager.DailyKSv2ExternalAdded to denote the issue can be worked on by a contributor

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions