mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 05:16:23 -06:00
fix: harden native i18n identifier filtering (#99098)
Summary: - The PR replaces native i18n conditional-branch regex filtering with an exported linear ASCII scanner and adds focused coverage. - PR surface: Tests +9, Other +36. Total +45 across 2 files. - Reproducibility: yes. from source inspection: current main routes conditional-branch native literals through the backtracking-prone regex before inventory output. I did not run a timing benchmark against current main. Automerge notes: - No ClawSweeper repair was needed after automerge opt-in. Validation: - ClawSweeper review passed for head97464c9635. - Required merge gates passed before the squash merge. Prepared head SHA:97464c9635Review: https://github.com/openclaw/openclaw/pull/99098#issuecomment-4866733462 Co-authored-by: Mason Huang <masonxhuang@tencent.com> Approved-by: hxy91819
This commit is contained in:
@@ -170,6 +170,42 @@ const EXCLUDED_FILE_RE = /(?:Tests?|UITests?|Previews?|Testing)\.(?:swift|kt|kts
|
||||
const BUILD_SETTING_RE = /\$\([A-Za-z0-9_.-]+\)/gu;
|
||||
const NATIVE_I18N_LOCALE_SET = new Set<string>(NATIVE_I18N_LOCALES);
|
||||
|
||||
function isAsciiLowercaseLetter(character: string): boolean {
|
||||
return character >= "a" && character <= "z";
|
||||
}
|
||||
|
||||
function isAsciiUppercaseLetter(character: string): boolean {
|
||||
return character >= "A" && character <= "Z";
|
||||
}
|
||||
|
||||
function isAsciiAlphaNumeric(character: string): boolean {
|
||||
return (
|
||||
isAsciiLowercaseLetter(character) ||
|
||||
isAsciiUppercaseLetter(character) ||
|
||||
(character >= "0" && character <= "9")
|
||||
);
|
||||
}
|
||||
|
||||
export function isConditionalBranchIdentifier(source: string): boolean {
|
||||
let index = 0;
|
||||
while (index < source.length && isAsciiLowercaseLetter(source[index])) {
|
||||
index += 1;
|
||||
}
|
||||
|
||||
// Keep this scanner linear: PR-controlled native source passes through CI,
|
||||
// so a backtracking regex here can become a cheap native-i18n DoS trigger.
|
||||
if (index === 0 || index >= source.length || !isAsciiUppercaseLetter(source[index])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (index += 1; index < source.length; index += 1) {
|
||||
if (!isAsciiAlphaNumeric(source[index])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function isTranslatableCandidate(source: string, kind: string): boolean {
|
||||
if (BUILD_SETTING_RE.test(source)) {
|
||||
BUILD_SETTING_RE.lastIndex = 0;
|
||||
@@ -183,7 +219,7 @@ function isTranslatableCandidate(source: string, kind: string): boolean {
|
||||
if (!isDirectUiText && (/^[a-z0-9_.:/$-]+$/u.test(source) || /^[A-Z0-9_.:/$-]+$/u.test(source))) {
|
||||
return false;
|
||||
}
|
||||
if (kind === "conditional-branch" && /^[a-z]+(?:[A-Z][A-Za-z0-9]*)+$/u.test(source)) {
|
||||
if (kind === "conditional-branch" && isConditionalBranchIdentifier(source)) {
|
||||
return false;
|
||||
}
|
||||
if (/[{}[\]]/u.test(source) && !/(?:\\\(|\$\{)/u.test(source)) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
collectNativeI18nEntries,
|
||||
isConditionalBranchIdentifier,
|
||||
NATIVE_I18N_LOCALES,
|
||||
parseNativeI18nCommand,
|
||||
syncNativeLocale,
|
||||
@@ -11,6 +12,14 @@ import {
|
||||
import { cleanupTempDirs, makeTempDir } from "../helpers/temp-dir.js";
|
||||
|
||||
describe("native app i18n inventory", () => {
|
||||
it("detects conditional branch identifiers without regex backtracking", () => {
|
||||
expect(isConditionalBranchIdentifier("isEnabled")).toBe(true);
|
||||
expect(isConditionalBranchIdentifier("hasFA2Enabled")).toBe(true);
|
||||
expect(isConditionalBranchIdentifier("abc123A")).toBe(false);
|
||||
expect(isConditionalBranchIdentifier("already_lowercase")).toBe(false);
|
||||
expect(isConditionalBranchIdentifier(`a${"A".repeat(4_096)}!`)).toBe(false);
|
||||
});
|
||||
|
||||
it("collects stable Android and Apple UI entries", async () => {
|
||||
const entries = await collectNativeI18nEntries();
|
||||
const surfaces = new Set(entries.map((entry) => entry.surface));
|
||||
|
||||
Reference in New Issue
Block a user