T09 · Insecure Skill Coding Practices
- Location
scripts/query-onehai-live-chrome.mjs:163- Finding
JavaScript Injection Through Unescaped City Arguments in Authenticated Chrome
- Content
View full analysis
Vulnerability Details
File Location:
scripts/query-onehai-live-chrome.mjs, lines 163–182
Vulnerability Type: JavaScript injection caused by unsafe string interpolation
Risk Level: HighVulnerable Code
js if (!pickupCity) { return JSON.stringify({ ok: false, reason: "一嗨站内城市列表未找到 ${query.pickup.city}。" }); } if (!dropoffCity) { return JSON.stringify({ ok: false, reason: "一嗨站内城市列表未找到 ${query.dropoff.city}。" }); } var pickupStores = JSON.parse(post("https://www.1hai.cn/Premises/RegionalStore", { cityId: pickupCity.cityId })).data || []; var queryScene = ${JSON.stringify(query.pickup.scene)}; var pickupStore = pickStore(pickupStores, ${JSON.stringify(query.pickup.location)}); if (!pickupStore) { return JSON.stringify({ ok: false, reason: "${query.pickup.city} 当前未返回可预订门店。" }); } var returnStore = pickupStore; if (${JSON.stringify(query.dropoff.city !== query.pickup.city || Boolean(query.dropoff.location))}) { var returnStores = JSON.parse(post("https://www.1hai.cn/Premises/RegionalStore", { cityId: dropoffCity.cityId })).data || []; queryScene = ${JSON.stringify(query.dropoff.scene)}; returnStore = pickStore(returnStores, ${JSON.stringify(query.dropoff.location)}); if (!returnStore) { return JSON.stringify({ ok: false, reason: "${query.dropoff.city} 当前未返回可预订还车门店。" }); } }The relevant input normalization in
scripts/query.mjs, lines 4–6, does not escape JavaScript metacharacters:js function normalizeString(value) { return value === undefined || value === null ? "" : String(value).trim(); }Technical Analysis
The skill constructs JavaScript as a template string and executes it in a OneHai browser tab through AppleScript. Pickup and drop-off city values originate from command-line arguments and may ultimately reflect user-provided rental-search parameters.
Although most dynamic values in
buildResolveBookingJavaScript()are safely serialized withJSON.stringify(), four city-name occurrences are ...[truncated 2643 chars]- Remediation
View remediation
Remediation Suggestions
-
Never insert raw user-controlled values into generated JavaScript source. Serialize every city value with
JSON.stringify()before including it in the script. -
Define safely serialized variables once and use concatenation inside static JavaScript:
js const pickupCityLiteral = JSON.stringify(query.pickup.city); const dropoffCityLiteral = JSON.stringify(query.dropoff.city); return ` (function () { var requestedPickupCity = ${pickupCityLiteral}; var requestedDropoffCity = ${dropoffCityLiteral}; // ... if (!pickupCity) { return JSON.stringify({ ok: false, reason: "OneHai did not contain the requested pickup city: " + requestedPickupCity }); } if (!dropoffCity) { return JSON.stringify({ ok: false, reason: "OneHai did not contain the requested drop-off city: " + requestedDropoffCity }); } })() `;-
Apply the same safe serialization pattern to all four affected error messages, including the pickup-store and return-store branches.
-
Prefer separating code from data entirely. Keep the browser JavaScript static and pass query data through a serialized JSON object:
js const serializedQuery = JSON.stringify(query); return ` (function (query) { // Static implementation using query.pickup.city and query.dropoff.city. })(${serializedQuery}) `;-
Validate city values against a strict format and reasonable length before generating browser code. This is defense in depth and must not replace contextual serialization.
-
Add regression tests containing quotes, backslashes, line terminators, template-string syntax, and JavaScript fragments. Verify that these values remain data and cannot alter the generated program’s syntax.
-
Audit every future interpolation in generated AppleScript and browser JavaScript according to its destination context. JavaScript values should use JSON serialization; AppleScript arguments should continue to be passed through ar ...[truncated 50 chars]
-
