fix(ui): guard i18n label lookups against prototype-named values

A server value such as "toString" used as a key into a label map
resolved to an Object.prototype member and was passed to t(), which
throws. Check own properties before translating so those values fall
back to the raw string as before
This commit is contained in:
Yuneng Jiang 2026-10-31 19:07:49 -07:00
parent bfe84f4dee
commit daf620353d
No known key found for this signature in database
12 changed files with 48 additions and 23 deletions

View file

@ -23,7 +23,9 @@ const RedisTypeSelector: React.FC<RedisTypeSelectorProps> = ({ redisType, redisT
<label className="text-sm font-medium">{t("caching.settings.redisType.label")}</label>
<Select value={redisType} onValueChange={(value) => value !== null && onTypeChange(value)}>
<SelectTrigger className="w-full">
<SelectValue>{REDIS_TYPE_LABELS[redisType] ? t(REDIS_TYPE_LABELS[redisType]) : redisType}</SelectValue>
<SelectValue>
{Object.hasOwn(REDIS_TYPE_LABELS, redisType) ? t(REDIS_TYPE_LABELS[redisType]) : redisType}
</SelectValue>
</SelectTrigger>
<SelectContent>
{Object.entries(REDIS_TYPE_LABELS).map(([value, label]) => (
@ -34,7 +36,10 @@ const RedisTypeSelector: React.FC<RedisTypeSelectorProps> = ({ redisType, redisT
</SelectContent>
</Select>
<p className="text-xs text-muted-foreground">
{t(redisTypeDescriptions[redisType] || "caching.settings.redisType.fallbackDescription")}
{t(
(Object.hasOwn(redisTypeDescriptions, redisType) && redisTypeDescriptions[redisType]) ||
"caching.settings.redisType.fallbackDescription",
)}
</p>
</div>
);

View file

@ -22,7 +22,11 @@ const CoordinationRedisTypeSelector: React.FC<CoordinationRedisTypeSelectorProps
</label>
<Select value={redisType} onValueChange={(value) => value !== null && onTypeChange(value)}>
<SelectTrigger id="coordination-redis-type" className="w-full">
<SelectValue>{t(COORDINATION_REDIS_TYPE_LABELS[redisType])}</SelectValue>
<SelectValue>
{Object.hasOwn(COORDINATION_REDIS_TYPE_LABELS, redisType)
? t(COORDINATION_REDIS_TYPE_LABELS[redisType])
: ""}
</SelectValue>
</SelectTrigger>
<SelectContent>
{COORDINATION_REDIS_TYPES.map((type) => (

View file

@ -156,6 +156,6 @@ export const sourceBadge = (
t: TFunction = i18n.t.bind(i18n),
): SourceBadgeDescriptor => {
const known: Readonly<Record<string, SourceBadgeKeys>> = SOURCE_BADGES;
const badge = (source && known[source]) || NOT_CONFIGURED_BADGE;
const badge = source && Object.hasOwn(known, source) ? known[source] : NOT_CONFIGURED_BADGE;
return { tone: badge.tone, label: t(badge.label), tooltip: t(badge.tooltip) };
};

View file

@ -80,7 +80,7 @@ export default function CyberArk() {
onSuccess: () => {
toast.success(
t("adminPanel.cyberArk.toasts.fieldCleared", {
field: (FIELD_LABELS[clearingField] && t(FIELD_LABELS[clearingField])) ?? clearingField,
field: (Object.hasOwn(FIELD_LABELS, clearingField) && t(FIELD_LABELS[clearingField])) || clearingField,
}),
);
setClearingField(null);
@ -103,7 +103,7 @@ export default function CyberArk() {
variant="ghost"
size="icon-sm"
aria-label={t("adminPanel.cyberArk.actions.clearField", {
field: (FIELD_LABELS[key] && t(FIELD_LABELS[key])) ?? key,
field: (Object.hasOwn(FIELD_LABELS, key) && t(FIELD_LABELS[key])) || key,
})}
onClick={() => setClearingField(key)}
>
@ -191,7 +191,7 @@ export default function CyberArk() {
<dl className="divide-y divide-border overflow-hidden rounded-md border border-border">
<DetailRow label={t("adminPanel.cyberArk.authMethodLabel")}>{detectAuthMethod(rawValues, t)}</DetailRow>
{fieldsToShow.map(([key]) => (
<DetailRow key={key} label={(FIELD_LABELS[key] && t(FIELD_LABELS[key])) ?? key}>
<DetailRow key={key} label={(Object.hasOwn(FIELD_LABELS, key) && t(FIELD_LABELS[key])) || key}>
{renderValue(key)}
</DetailRow>
))}
@ -229,7 +229,9 @@ export default function CyberArk() {
<DeleteResourceModal
isOpen={clearingField !== null}
title={t("adminPanel.cyberArk.clearModal.title", {
field: clearingField ? (FIELD_LABELS[clearingField] && t(FIELD_LABELS[clearingField])) ?? clearingField : "",
field: clearingField
? (Object.hasOwn(FIELD_LABELS, clearingField) && t(FIELD_LABELS[clearingField])) || clearingField
: "",
})}
message={t("adminPanel.cyberArk.clearModal.message")}
resourceInformationTitle={t("adminPanel.cyberArk.clearModal.resourceTitle")}
@ -237,7 +239,7 @@ export default function CyberArk() {
{
label: t("adminPanel.cyberArk.clearModal.fieldLabel"),
value: clearingField
? (FIELD_LABELS[clearingField] && t(FIELD_LABELS[clearingField])) ?? clearingField
? (Object.hasOwn(FIELD_LABELS, clearingField) && t(FIELD_LABELS[clearingField])) || clearingField
: "",
},
]}

View file

@ -139,7 +139,7 @@ const EditCyberArkModal: React.FC<EditCyberArkModalProps> = ({ isVisible, onCanc
key={fieldName}
control={form.control}
name={fieldName}
label={(FIELD_LABELS[fieldName] && t(FIELD_LABELS[fieldName])) ?? fieldName}
label={(Object.hasOwn(FIELD_LABELS, fieldName) && t(FIELD_LABELS[fieldName])) || fieldName}
>
{({ ref, ...field }) =>
isSensitive ? (

View file

@ -146,7 +146,7 @@ const EditHashicorpVaultModal: React.FC<EditHashicorpVaultModalProps> = ({ isVis
key={fieldName}
control={form.control}
name={fieldName}
label={(FIELD_LABELS[fieldName] && t(FIELD_LABELS[fieldName])) ?? fieldName}
label={(Object.hasOwn(FIELD_LABELS, fieldName) && t(FIELD_LABELS[fieldName])) || fieldName}
>
{({ ref, ...field }) =>
isSensitive ? (

View file

@ -81,7 +81,7 @@ export default function HashicorpVault() {
onSuccess: () => {
toast.success(
t("adminPanel.hashicorpVault.toasts.fieldCleared", {
field: (FIELD_LABELS[clearingField] && t(FIELD_LABELS[clearingField])) ?? clearingField,
field: (Object.hasOwn(FIELD_LABELS, clearingField) && t(FIELD_LABELS[clearingField])) || clearingField,
}),
);
setClearingField(null);
@ -105,7 +105,7 @@ export default function HashicorpVault() {
variant="ghost"
size="icon-sm"
aria-label={t("adminPanel.hashicorpVault.actions.clearField", {
field: (FIELD_LABELS[key] && t(FIELD_LABELS[key])) ?? key,
field: (Object.hasOwn(FIELD_LABELS, key) && t(FIELD_LABELS[key])) || key,
})}
onClick={() => setClearingField(key)}
>
@ -193,7 +193,7 @@ export default function HashicorpVault() {
{detectAuthMethod(rawValues, t)}
</DetailRow>
{fieldsToShow.map(([key]) => (
<DetailRow key={key} label={(FIELD_LABELS[key] && t(FIELD_LABELS[key])) ?? key}>
<DetailRow key={key} label={(Object.hasOwn(FIELD_LABELS, key) && t(FIELD_LABELS[key])) || key}>
{renderValue(key)}
</DetailRow>
))}
@ -226,7 +226,9 @@ export default function HashicorpVault() {
<DeleteResourceModal
isOpen={clearingField !== null}
title={t("adminPanel.hashicorpVault.clearModal.title", {
field: clearingField ? (FIELD_LABELS[clearingField] && t(FIELD_LABELS[clearingField])) ?? clearingField : "",
field: clearingField
? (Object.hasOwn(FIELD_LABELS, clearingField) && t(FIELD_LABELS[clearingField])) || clearingField
: "",
})}
message={t("adminPanel.hashicorpVault.clearModal.message")}
resourceInformationTitle={t("adminPanel.hashicorpVault.clearModal.resourceTitle")}
@ -234,7 +236,7 @@ export default function HashicorpVault() {
{
label: t("adminPanel.hashicorpVault.clearModal.fieldLabel"),
value: clearingField
? (FIELD_LABELS[clearingField] && t(FIELD_LABELS[clearingField])) ?? clearingField
? (Object.hasOwn(FIELD_LABELS, clearingField) && t(FIELD_LABELS[clearingField])) || clearingField
: "",
},
]}

View file

@ -387,7 +387,9 @@ export const SSOProviderSelectField = () => {
<Logo
src={logo}
label={
ssoProviderDisplayNames[optionValue] ? t(ssoProviderDisplayNames[optionValue]) : optionValue
Object.hasOwn(ssoProviderDisplayNames, optionValue)
? t(ssoProviderDisplayNames[optionValue])
: optionValue
}
className="h-6 w-6 mr-3 object-contain"
/>
@ -477,7 +479,7 @@ const DEFAULT_ROLE_OPTIONS: ReadonlyArray<{ value: string; label: ParseKeys }> =
];
const providerOptionLabel = (value: string, t: TFunction) =>
ssoProviderDisplayNames[value]
Object.hasOwn(ssoProviderDisplayNames, value)
? t(ssoProviderDisplayNames[value])
: t("adminPanel.ssoSettings.providers.fallback", { name: value.charAt(0).toUpperCase() + value.slice(1) });

View file

@ -27,7 +27,13 @@ export default function RoleMappings({ roleMappings }: { roleMappings: RoleMappi
id: "role",
accessorKey: "role",
header: t("adminPanel.ssoSettings.roleMappings.columns.role"),
cell: ({ row }) => <strong className="font-semibold">{t(defaultRoleDisplayNames[row.original.role])}</strong>,
cell: ({ row }) => (
<strong className="font-semibold">
{Object.hasOwn(defaultRoleDisplayNames, row.original.role)
? t(defaultRoleDisplayNames[row.original.role])
: ""}
</strong>
),
},
{
id: "groups",
@ -69,7 +75,11 @@ export default function RoleMappings({ roleMappings }: { roleMappings: RoleMappi
{t("adminPanel.ssoSettings.roleMappings.defaultRole")}
</h5>
<div>
<strong className="font-semibold">{t(defaultRoleDisplayNames[roleMappings.default_role])}</strong>
<strong className="font-semibold">
{Object.hasOwn(defaultRoleDisplayNames, roleMappings.default_role)
? t(defaultRoleDisplayNames[roleMappings.default_role])
: ""}
</strong>
</div>
</div>
</div>

View file

@ -248,7 +248,7 @@ export default function SSOSettings() {
<Logo
src={ssoProviderLogoMap[selectedProvider]}
label={
ssoProviderDisplayNames[selectedProvider]
Object.hasOwn(ssoProviderDisplayNames, selectedProvider)
? t(ssoProviderDisplayNames[selectedProvider])
: selectedProvider
}

View file

@ -126,7 +126,7 @@ export const getLoggingCallbacksTableColumns = (
return (
<StatusBadge
tone={callbackModeTone(mode)}
label={CALLBACK_MODE_LABELS[mode] ? t(CALLBACK_MODE_LABELS[mode]) : mode}
label={Object.hasOwn(CALLBACK_MODE_LABELS, mode) ? t(CALLBACK_MODE_LABELS[mode]) : mode}
/>
);
},

View file

@ -9,4 +9,4 @@ const STRATEGY_LABELS: Readonly<Record<string, ParseKeys>> = {
};
export const formatStrategyLabel = (strategy: string, t: TFunction): string =>
STRATEGY_LABELS[strategy] ? t(STRATEGY_LABELS[strategy]) : strategy;
Object.hasOwn(STRATEGY_LABELS, strategy) ? t(STRATEGY_LABELS[strategy]) : strategy;