T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/calculate_targets.js:10
- Finding
- Missing Numeric and Range Validation Allows Corruption of Nutrition Calculations<![CDATA[ ## Vulnerability Details **File Locations**: - `scripts/calculate_targets.js:10-14` - `scripts/calculate_intake.js:26-34` - `scripts/calculate_intake.js:65-66` - `scripts/calculate_burn.js:18-24` **Vulnerability Type**: Improper input validation in health-related calculations **Risk Level**: Medium ### Vulnerable Code #### `scripts/calculate_targets.js:10-14` ```js function calculateBMR({ sex = 'unknown', weight_kg, height_cm, age }) { if (!weight_kg || !height_cm || !age) throw new Error('weight_kg, height_cm, and age are required'); const base = 10 * weight_kg + 6.25 * height_cm - 5 * age; if (sex === 'male') return round(base + 5, 0); if (sex === 'female') return round(base - 161, 0); return round(base - 78, 0); } ``` #### `scripts/calculate_intake.js:26-34` ```js function getWeightGrams(entry, item) { const unit = entry.unit || item.base_unit || 'g'; const amount = Number(entry.amount || entry.quantity || 0); if (unit === 'ml') return amount; if (UNIT_GRAMS[unit]) return amount * UNIT_GRAMS[unit]; if (item.grams_per_unit) return amount * item.grams_per_unit; return amount; } ``` #### `scripts/calculate_intake.js:65-66` ```js const addedWater = waterEntries.reduce((sum, e) => sum + Number(e.water_ml || 0), 0); res.totals.water_ml = round(res.totals.water_ml + addedWater, 1); ``` #### `scripts/calculate_burn.js:18-24` ```js function getExerciseBurn(exercise, weightKg) { const type = exercise.exercise_type || exercise.type; const intensity = exercise.intensity || 'moderate'; const minutes = Number(exercise.duration_min || exercise.duration || 0); const met = (MET[type] && MET[type][intensity]) || 5.0; return round((met * 3.5 * weightKg / 200) * minutes, 0); } ``` ### Technical Analysis The scripts accept profile, food, hydration, and exercise values from JSON input but do not verify that those values are finite numbers within reasonable positive ranges. In `calculate_targets.js`, the condition only checks ...[truncated 2639 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. **Introduce centralized numeric validation** - Require actual finite numbers, or explicitly parse supported numeric strings. - Reject `NaN`, `Infinity`, and `-Infinity` with `Number.isFinite()`. - Do not rely on truthiness to establish validity. 2. **Enforce reasonable input ranges** - Require positive and physiologically reasonable bounds for age, weight, and height. - Require food quantities and water intake to be nonnegative and capped at documented operational limits. - Require exercise duration to be nonnegative and limited to a reasonable daily maximum. 3. **Reject invalid inputs before calculation** - Return a clear validation error identifying the invalid field. - Avoid silently substituting zero for malformed values. - Validate arrays and each entry before reduction or aggregation. 4. **Prevent non-finite intermediate results** - Verify calculated BMR, target calories, nutrient totals, and exercise burn with `Number.isFinite()` before returning them. - Reject results outside documented calculation limits. 5. **Use a reusable validation helper** ```js function requireFiniteNumber(value, field, { min, max } = {}) { const number = typeof value === 'number' ? value : Number(value); if (!Number.isFinite(number)) { throw new TypeError(`${field} must be a finite number`); } if (min !== undefined && number < min) { throw new RangeError(`${field} must be at least ${min}`); } if (max !== undefined && number > max) { throw new RangeError(`${field} must not exceed ${max}`); } return number; } ``` Apply the helper to all profile measurements, food quantities, hydration values, and exercise durations before performing arithmetic. 6. **Add boundary and malformed-input tests** - Test negative values, zero where prohibited, numeric strings, empty strings, `null`, arrays, objects, invalid text, very large exponents, `NaN`, and infinite values. - Assert that in ...[truncated 84 chars]
