Weather Witness

First Flight #40
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Impact: low
Likelihood: low
Invalid

Weather ID Mapping Logic Issues in GetWeather.js

Summary

The GetWeather.js file contains logic issues in the weather ID mapping, where any unmatched weather condition defaults to WINDY, potentially leading to incorrect weather representation for NFTs.

Vulnerability Details

In GetWeather.js, the code uses a default fallback to WINDY (enum value 4) for any unmatched weather condition:

// thunderstorm
if (weather_id_x === 2) weather_enum = 3;
// rain
else if (weather_id_x === 3 || weather_id_x === 5) weather_enum = 2;
// snow
else if (weather_id_x === 6) weather_enum = 5;
// clear
else if (weather_id === 800) weather_enum = 0;
// cloudy
else if (weather_id_x === 8) weather_enum = 1;
// windy
else weather_enum = 4;

This is a known issue mentioned in the project documentation, but it could lead to incorrect weather representation for NFTs when the actual weather condition doesn't match any of the explicitly handled cases.

Proof of Concept

// SPDX-License-Identifier: MIT
// This is a JavaScript test file that demonstrates the weather ID mapping issue
// Mock the Chainlink Functions environment
const Functions = {
makeHttpRequest: async (params) => {
// Mock implementation that returns predefined responses
if (params.url.includes("geo/1.0/zip")) {
return {
data: {
lat: 40.7128,
lon: -74.0060
}
};
} else if (params.url.includes("data/2.5/weather")) {
// Return different weather conditions for testing
return {
data: {
weather: [
{
id: mockWeatherId // This will be set in the test cases
}
],
main: {
temp: 20
}
}
};
}
}
};
// Import the GetWeather function (mocked for testing)
const getWeather = (args, secrets) => {
// This is a simplified version of the actual GetWeather.js logic
return new Promise(async (resolve) => {
try {
// Get location data
const geoCodingRequest = await Functions.makeHttpRequest({
url: "http://api.openweathermap.org/geo/1.0/zip",
method: "GET",
params: { zip: `${args[0]},${args[1]}`, appid: secrets.apiKey }
});
// Get weather data
const weatherRequest = await Functions.makeHttpRequest({
url: "https://api.openweathermap.org/data/2.5/weather",
method: "GET",
params: {
lat: geoCodingRequest.data.lat,
lon: geoCodingRequest.data.lon,
appid: secrets.apiKey
}
});
// Extract weather ID and map to enum
const weather_id = weatherRequest.data.weather[0].id;
const weather_id_x = Math.floor(weather_id / 100);
let weather_enum;
// This is the problematic mapping logic
// thunderstorm
if (weather_id_x === 2) weather_enum = 3;
// rain
else if (weather_id_x === 3 || weather_id_x === 5) weather_enum = 2;
// snow
else if (weather_id_x === 6) weather_enum = 5;
// clear
else if (weather_id === 800) weather_enum = 0;
// cloudy
else if (weather_id_x === 8) weather_enum = 1;
// windy
else weather_enum = 4;
// Return the result
resolve([weather_enum, Math.round(weatherRequest.data.main.temp)]);
} catch (error) {
resolve([0, 0]); // Default to SUNNY and 0 temperature on error
}
});
};
// Test cases to demonstrate the issue
async function runTests() {
const args = ["10001", "US"]; // NYC zip code
const secrets = { apiKey: "mock_api_key" };
console.log("Testing Weather ID Mapping Issues:");
// Test case 1: Weather ID 701 (Mist - Group 7xx: Atmosphere)
console.log("\nTest Case 1: Weather ID 701 (Mist)");
mockWeatherId = 701;
const result1 = await getWeather(args, secrets);
console.log(`Weather ID: ${mockWeatherId}, Mapped to enum: ${result1[0]} (${getWeatherName(result1[0])})`);
console.log(`Expected mapping: Atmosphere condition should map to CLOUDY (1) or have its own category`);
console.log(`Actual mapping: ${getWeatherName(result1[0])} (${result1[0]})`);
console.log(`Issue: Mist is mapped to WINDY instead of a more appropriate category`);
// Test case 2: Weather ID 762 (Volcanic Ash - Group 7xx: Atmosphere)
console.log("\nTest Case 2: Weather ID 762 (Volcanic Ash)");
mockWeatherId = 762;
const result2 = await getWeather(args, secrets);
console.log(`Weather ID: ${mockWeatherId}, Mapped to enum: ${result2[0]} (${getWeatherName(result2[0])})`);
console.log(`Expected mapping: Extreme condition should have its own category`);
console.log(`Actual mapping: ${getWeatherName(result2[0])} (${result2[0]})`);
console.log(`Issue: Volcanic ash is mapped to WINDY which is misleading`);
// Test case 3: Weather ID 781 (Tornado - Group 7xx: Atmosphere)
console.log("\nTest Case 3: Weather ID 781 (Tornado)");
mockWeatherId = 781;
const result3 = await getWeather(args, secrets);
console.log(`Weather ID: ${mockWeatherId}, Mapped to enum: ${result3[0]} (${getWeatherName(result3[0])})`);
console.log(`Expected mapping: Extreme condition should map to THUNDERSTORM (3) or have its own category`);
console.log(`Actual mapping: ${getWeatherName(result3[0])} (${result3[0]})`);
console.log(`Issue: Tornado is mapped to WINDY which understates the severity`);
// Test case 4: Weather ID 900 (Unknown condition - Group 9xx: Extreme)
console.log("\nTest Case 4: Weather ID 900 (Unknown extreme condition)");
mockWeatherId = 900;
const result4 = await getWeather(args, secrets);
console.log(`Weather ID: ${mockWeatherId}, Mapped to enum: ${result4[0]} (${getWeatherName(result4[0])})`);
console.log(`Expected mapping: Extreme condition should have appropriate mapping`);
console.log(`Actual mapping: ${getWeatherName(result4[0])} (${result4[0]})`);
console.log(`Issue: Unknown extreme condition is mapped to WINDY by default`);
}
// Helper function to convert enum to weather name
function getWeatherName(enumValue) {
const weatherNames = ["SUNNY", "CLOUDY", "RAINY", "THUNDERSTORM", "WINDY", "SNOW"];
return weatherNames[enumValue] || "UNKNOWN";
}
// Run the tests
runTests();

This PoC demonstrates how various weather conditions that don't fit into the explicitly handled cases are all defaulted to WINDY (enum value 4), which can lead to misleading or incorrect weather representation in the NFTs. For example:

  1. Atmospheric conditions like mist, fog, or haze (Group 7xx) are mapped to WINDY

  2. Extreme weather conditions like tornadoes or hurricanes (Group 7xx/9xx) are also mapped to WINDY

  3. Any future or uncommon weather IDs added by OpenWeatherMap would default to WINDY

Impact

The impact of this issue includes:

  • Misrepresentation of actual weather conditions in the NFTs

  • Confusion for users who see WINDY displayed when the actual weather is different

  • Reduced accuracy and reliability of the weather data representation

  • Potential loss of user trust in the system's accuracy


Recommendations

Expand the weather condition mapping to cover more cases and provide more accurate representations:

// Group 2xx: Thunderstorm
if (weather_id_x === 2) weather_enum = 3;
// Group 3xx: Drizzle and 5xx: Rain
else if (weather_id_x === 3 || weather_id_x === 5) weather_enum = 2;
// Group 6xx: Snow
else if (weather_id_x === 6) weather_enum = 5;
// Group 7xx: Atmosphere (fog, mist, etc.) - Map to cloudy
else if (weather_id_x === 7) weather_enum = 1;
// Clear sky (800)
else if (weather_id === 800) weather_enum = 0;
// Group 80x: Clouds
else if (weather_id_x === 8) weather_enum = 1;
// Group 9xx: Extreme weather - Could map to appropriate enum based on specific condition
else if (weather_id_x === 9) {
// Map extreme weather conditions appropriately
if (weather_id >= 950 && weather_id <= 956) {
weather_enum = 4; // Windy for wind-related conditions
} else if (weather_id >= 960 && weather_id <= 961) {
weather_enum = 3; // Thunderstorm for storm conditions
} else {
weather_enum = 4; // Default to windy for other extreme conditions
}
}
// Default fallback
else weather_enum = 4;

Consider adding more weather states to the enum to better represent the full range of possible weather conditions.

Updates

Appeal created

bube Lead Judge 5 days ago
Submission Judgement Published
Invalidated
Reason: Known issue

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.