From 31458845ae996a06e44f2e7ce565cc64c847c21f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Oct 2025 04:35:52 +0000 Subject: [PATCH 01/17] Initial plan From a7d10e9021b1f88b5015d71a56888eef473382a9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Oct 2025 04:38:19 +0000 Subject: [PATCH 02/17] Initial exploration - understanding current architecture Co-authored-by: devnomadic <14085319+devnomadic@users.noreply.github.com> --- wwwroot/sitemap.xml | 48 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/wwwroot/sitemap.xml b/wwwroot/sitemap.xml index e69de29..88049e0 100644 --- a/wwwroot/sitemap.xml +++ b/wwwroot/sitemap.xml @@ -0,0 +1,48 @@ + + + + + + https://albatross.devnomadic.com/ + 2025-10-28 + weekly + 1.0 + + + https://albatross.devnomadic.com/ip-manifests/AWS.json + 2025-10-28 + daily + 0.8 + + + https://albatross.devnomadic.com/ip-manifests/Azure.json + 2025-10-28 + daily + 0.8 + + + https://albatross.devnomadic.com/ip-manifests/GCP.json + 2025-10-28 + daily + 0.8 + + + https://albatross.devnomadic.com/ip-manifests/Oracle.json + 2025-10-28 + daily + 0.8 + + + https://albatross.devnomadic.com/robots.txt + 2025-10-28 + monthly + 0.3 + + + + + + From 1b6817c315b2ea745f0801326db4e757be24340a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Oct 2025 04:41:24 +0000 Subject: [PATCH 03/17] Add AI reputation check using Cloudflare Workers AI Co-authored-by: devnomadic <14085319+devnomadic@users.noreply.github.com> --- Services/AbuseIPDBService.cs | 44 +++++++++- cloudflare-worker.template.js | 157 ++++++++++++++++++++++++++++++++-- wrangler.toml | 4 + 3 files changed, 198 insertions(+), 7 deletions(-) diff --git a/Services/AbuseIPDBService.cs b/Services/AbuseIPDBService.cs index 9dcc224..da5b5b6 100644 --- a/Services/AbuseIPDBService.cs +++ b/Services/AbuseIPDBService.cs @@ -13,7 +13,7 @@ namespace Albatross.Services { /// - /// Complete API response model for AbuseIPDB with integrated ASN information + /// Complete API response model for AbuseIPDB with integrated ASN information and AI reputation /// public class AbuseIPDBApiResponse { @@ -23,6 +23,9 @@ public class AbuseIPDBApiResponse [JsonPropertyName("asnInfo")] public AsnInfo? AsnInfo { get; set; } + [JsonPropertyName("aiReputation")] + public AIReputation? AIReputation { get; set; } + [JsonPropertyName("abuseIPDBError")] public string? AbuseIPDBError { get; set; } @@ -30,6 +33,45 @@ public class AbuseIPDBApiResponse public WorkerInfo? WorkerInfo { get; set; } } + /// + /// AI-generated reputation analysis from Cloudflare Workers AI + /// + public class AIReputation + { + [JsonPropertyName("success")] + public bool Success { get; set; } + + [JsonPropertyName("error")] + public string? Error { get; set; } + + [JsonPropertyName("analysis")] + public AIAnalysis? Analysis { get; set; } + + [JsonPropertyName("model")] + public string? Model { get; set; } + + [JsonPropertyName("timestamp")] + public string? Timestamp { get; set; } + } + + /// + /// AI analysis details + /// + public class AIAnalysis + { + [JsonPropertyName("riskLevel")] + public string? RiskLevel { get; set; } + + [JsonPropertyName("trustScore")] + public int TrustScore { get; set; } + + [JsonPropertyName("summary")] + public string? Summary { get; set; } + + [JsonPropertyName("recommendations")] + public List? Recommendations { get; set; } + } + /// /// ASN information from Cloudflare Radar API /// diff --git a/cloudflare-worker.template.js b/cloudflare-worker.template.js index 9334a85..cbbf59f 100644 --- a/cloudflare-worker.template.js +++ b/cloudflare-worker.template.js @@ -4,11 +4,13 @@ * This worker provides comprehensive IP analysis by combining multiple data sources: * 1. AbuseIPDB API - IP abuse reputation checking * 2. Cloudflare Radar API - ASN and network information lookup + * 3. Cloudflare Workers AI - AI-powered reputation analysis using Llama 3.1 70B * * Features: * - Protects API keys from client exposure * - Handles CORS for browser requests - * - Fetches both APIs in parallel for optimal performance + * - Fetches multiple APIs in parallel for optimal performance + * - Generates AI-powered risk assessments and recommendations * - Returns combined data in a single JSON response * - Uses HMAC-based authentication for security with build-time generated keys * - Provides fallback data when one API fails @@ -24,6 +26,18 @@ * "data": [ ... Cloudflare Radar ASN data ... ], * "error": null/string * }, + * "aiReputation": { + * "success": true/false, + * "error": null/string, + * "analysis": { + * "riskLevel": "low|medium|high|critical", + * "trustScore": <0-100>, + * "summary": "", + * "recommendations": ["", ""] + * }, + * "model": "@cf/meta/llama-3.1-70b-instruct", + * "timestamp": "" + * }, * "workerInfo": { ... metadata ... } * } */ @@ -86,20 +100,139 @@ const ALLOWED_ORIGINS = [ ]; addEventListener('fetch', event => { - event.respondWith(handleRequest(event.request)); + event.respondWith(handleRequest(event.request, event.env)); }); -async function handleRequest(request) { +async function handleRequest(request, env) { // Handle CORS preflight requests if (request.method === "OPTIONS") { return handleCORS(request); } // All requests go through the combined handler - return handleCombinedRequest(request); + return handleCombinedRequest(request, env); +} + +/** + * Generate AI-based IP reputation analysis using Cloudflare Workers AI + * @param {object} env - Worker environment with AI binding + * @param {string} ipAddress - The IP address to analyze + * @param {object} abuseData - AbuseIPDB data for context + * @param {object} asnData - ASN data for context + * @returns {Promise} AI reputation analysis + */ +async function generateAIReputation(env, ipAddress, abuseData, asnData) { + try { + // Check if AI binding is available + if (!env || !env.AI) { + console.log('AI binding not available in environment'); + return { + success: false, + error: 'AI service not available', + analysis: null + }; + } + + // Build context from available data + const abuseScore = abuseData?.data?.abuseConfidenceScore || 0; + const totalReports = abuseData?.data?.totalReports || 0; + const countryCode = abuseData?.data?.countryCode || 'Unknown'; + const isp = abuseData?.data?.isp || 'Unknown'; + const usageType = abuseData?.data?.usageType || 'Unknown'; + const asnName = asnData?.result?.[0]?.asn?.name || 'Unknown'; + const asnNumber = asnData?.result?.[0]?.asn?.asn || 'Unknown'; + + // Create a prompt for the AI to analyze the IP reputation + const prompt = `You are a cybersecurity expert analyzing IP address reputation. Based on the following real-time data, provide a concise risk assessment and reputation summary. + +IP Address: ${ipAddress} +Country: ${countryCode} +ISP: ${isp} +Usage Type: ${usageType} +ASN: ${asnNumber} (${asnName}) +Abuse Confidence Score: ${abuseScore}% (0-100 scale, higher is worse) +Total Abuse Reports: ${totalReports} + +Provide a JSON response with the following structure: +{ + "riskLevel": "low|medium|high|critical", + "trustScore": , + "summary": "<2-3 sentence assessment>", + "recommendations": ["", ""] +} + +Focus on actionable insights based on the abuse score, report count, and network information. Keep the summary concise and professional.`; + + console.log('Calling Workers AI with Llama 3.1 70B for IP reputation analysis...'); + + // Call Cloudflare Workers AI using Llama 3.1 70B Instruct model + const response = await env.AI.run('@cf/meta/llama-3.1-70b-instruct', { + messages: [ + { + role: 'system', + content: 'You are a cybersecurity expert. Respond only with valid JSON, no markdown formatting or code blocks.' + }, + { + role: 'user', + content: prompt + } + ], + max_tokens: 500, + temperature: 0.3, // Lower temperature for more consistent, factual responses + }); + + console.log('AI response received:', response); + + // Parse the AI response + let analysis = null; + if (response && response.response) { + try { + // The response.response contains the AI's text output + const aiText = response.response.trim(); + + // Try to extract JSON from the response (handle cases where AI might add extra text) + let jsonText = aiText; + if (aiText.includes('{')) { + const startIdx = aiText.indexOf('{'); + const endIdx = aiText.lastIndexOf('}'); + if (startIdx >= 0 && endIdx > startIdx) { + jsonText = aiText.substring(startIdx, endIdx + 1); + } + } + + analysis = JSON.parse(jsonText); + console.log('AI analysis parsed successfully:', analysis); + } catch (parseError) { + console.error('Error parsing AI response:', parseError); + // Fallback to basic analysis if JSON parsing fails + analysis = { + riskLevel: abuseScore > 75 ? 'critical' : abuseScore > 50 ? 'high' : abuseScore > 25 ? 'medium' : 'low', + trustScore: Math.max(0, 100 - abuseScore), + summary: `IP from ${countryCode} with ${abuseScore}% abuse confidence score and ${totalReports} reports.`, + recommendations: ['Review the abuse reports for details', 'Consider blocking if risk level is high'] + }; + } + } + + return { + success: true, + error: null, + analysis: analysis, + model: '@cf/meta/llama-3.1-70b-instruct', + timestamp: new Date().toISOString() + }; + + } catch (error) { + console.error('AI reputation generation error:', error); + return { + success: false, + error: error.message || 'Failed to generate AI reputation', + analysis: null + }; + } } -async function handleCombinedRequest(request) { +async function handleCombinedRequest(request, env) { // Get the request origin const origin = request.headers.get('Origin') || ''; @@ -336,6 +469,14 @@ async function handleCombinedRequest(request) { radarError = `Cloudflare Radar API error: ${status} ${statusText}`; } + // Generate AI-based reputation analysis using the collected data + console.log('Generating AI reputation analysis...'); + const aiReputation = await generateAIReputation(env, ipAddress, abuseIPDBData, radarData); + console.log('AI reputation analysis result:', { + success: aiReputation.success, + error: aiReputation.error + }); + // Combine the responses into a single response object const combinedResponse = { // AbuseIPDB data (maintain original structure for compatibility) @@ -348,6 +489,9 @@ async function handleCombinedRequest(request) { error: radarError }, + // Add AI reputation analysis + aiReputation: aiReputation, + // Metadata and errors abuseIPDBError: abuseIPDBError, workerInfo: { @@ -356,7 +500,8 @@ async function handleCombinedRequest(request) { requestId: generateRequestId(), sources: { abuseipdb: abuseIPDBError ? 'error' : 'success', - radar: radarError ? 'error' : 'success' + radar: radarError ? 'error' : 'success', + ai: aiReputation.success ? 'success' : 'error' } } }; diff --git a/wrangler.toml b/wrangler.toml index 9e4d61f..6c9bd23 100644 --- a/wrangler.toml +++ b/wrangler.toml @@ -7,6 +7,10 @@ compatibility_flags = ["nodejs_compat"] [observability.logs] enabled = true +# AI binding for Workers AI +[ai] +binding = "AI" + # Production environment (default) [env.production] name = "abuseipdb" From 47792ad3ac9396827f3a1680d2f885b63031f34b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Oct 2025 04:42:49 +0000 Subject: [PATCH 04/17] Update README with AI reputation feature documentation Co-authored-by: devnomadic <14085319+devnomadic@users.noreply.github.com> --- README.md | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b3b95e8..de67c24 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,14 @@ # Albatross -A modern Blazor WebAssembly application that provides comprehensive IP address analysis and abuse checking functionality. Albatross combines cloud IP range detection with AbuseIPDB reputation checking through a secure Cloudflare Worker proxy. +A modern Blazor WebAssembly application that provides comprehensive IP address analysis and abuse checking functionality. Albatross combines cloud IP range detection with AbuseIPDB reputation checking and AI-powered risk assessment through a secure Cloudflare Worker proxy. ## Features - **IP Abuse Checking**: Query the AbuseIPDB API to check if an IP address has been reported for malicious activity +- **AI-Powered Reputation Analysis**: Advanced risk assessment using Cloudflare Workers AI with Llama 3.1 70B Instruct model - **Cloud IP Range Detection**: Identify if an IP address belongs to major cloud providers (AWS, Azure, GCP, Oracle Cloud) - **Flexible Input Format**: Support for IP addresses with custom report age (e.g., `8.8.8.8;60` for 60 days of history) -- **Combined Data Sources**: Integrated AbuseIPDB and Cloudflare Radar API data for comprehensive IP analysis +- **Combined Data Sources**: Integrated AbuseIPDB, Cloudflare Radar API, and Workers AI for comprehensive IP analysis - **Secure Authentication**: Build-time generated HMAC authentication with timestamp validation for enhanced security - **CORS Protection**: Cloudflare Worker proxy handles CORS and protects API keys from client exposure - **SEO-Optimized**: Static HTML prerendering for improved search engine indexing and web crawler accessibility @@ -317,9 +318,39 @@ Users can specify custom report age limits using a semicolon delimiter: ### Combined API Integration - **AbuseIPDB**: IP reputation and abuse reports - **Cloudflare Radar**: ASN information and network details -- **Parallel Processing**: Both APIs are queried simultaneously for optimal performance +- **Cloudflare Workers AI**: AI-powered risk assessment using Llama 3.1 70B Instruct model +- **Parallel Processing**: All APIs are queried simultaneously for optimal performance - **Graceful Degradation**: Partial results if one API fails +### AI-Powered Reputation Analysis +The worker uses Cloudflare Workers AI with the Llama 3.1 70B Instruct model to generate intelligent risk assessments: +- **Real-time Analysis**: Analyzes data from AbuseIPDB and Cloudflare Radar APIs +- **Risk Levels**: Categorizes threats as low, medium, high, or critical +- **Trust Scores**: Provides 0-100 trust score for quick assessment +- **AI Summary**: Natural language explanation of the IP's reputation +- **Actionable Recommendations**: Specific steps to take based on the analysis +- **Model**: `@cf/meta/llama-3.1-70b-instruct` (70 billion parameter model) + +Example AI Response: +```json +{ + "aiReputation": { + "success": true, + "analysis": { + "riskLevel": "medium", + "trustScore": 65, + "summary": "This IP from US shows moderate abuse activity with 15 reports. ISP indicates datacenter usage which is common for both legitimate and malicious traffic.", + "recommendations": [ + "Review the specific abuse reports for patterns", + "Consider rate limiting if used for API access" + ] + }, + "model": "@cf/meta/llama-3.1-70b-instruct", + "timestamp": "2025-10-28T04:45:00.000Z" + } +} +``` + ## Current Status ✅ **Authentication System**: Fully implemented and working @@ -329,6 +360,7 @@ Users can specify custom report age limits using a semicolon delimiter: ✅ **GitHub Actions**: Automated CI/CD pipeline functional ✅ **Security Scanning**: CodeQL and dependency review workflows active ✅ **Code Quality**: Automated formatting and testing in CI/CD +✅ **AI Integration**: Cloudflare Workers AI with Llama 3.1 70B for reputation analysis ## Image Credits From 740a3fa7d77fbe9d6d4e8f77ececfd5c7428a9e5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Oct 2025 04:44:19 +0000 Subject: [PATCH 05/17] Fix documentation to clarify API processing order Co-authored-by: devnomadic <14085319+devnomadic@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index de67c24..6094fe2 100644 --- a/README.md +++ b/README.md @@ -319,7 +319,7 @@ Users can specify custom report age limits using a semicolon delimiter: - **AbuseIPDB**: IP reputation and abuse reports - **Cloudflare Radar**: ASN information and network details - **Cloudflare Workers AI**: AI-powered risk assessment using Llama 3.1 70B Instruct model -- **Parallel Processing**: All APIs are queried simultaneously for optimal performance +- **Optimized Processing**: AbuseIPDB and Radar APIs are queried in parallel; AI analysis processes their results - **Graceful Degradation**: Partial results if one API fails ### AI-Powered Reputation Analysis From d00a8882acec1abbb9cb982a597430bccacc628f Mon Sep 17 00:00:00 2001 From: Drew Kennedy Date: Tue, 28 Oct 2025 15:30:56 +1000 Subject: [PATCH 06/17] Refactor Cloudflare Worker to use ES Module export for fetch handling and improve CORS request management --- cloudflare-worker.template.js | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/cloudflare-worker.template.js b/cloudflare-worker.template.js index cbbf59f..88b1c4d 100644 --- a/cloudflare-worker.template.js +++ b/cloudflare-worker.template.js @@ -99,19 +99,18 @@ const ALLOWED_ORIGINS = [ "http://localhost:5044" ]; -addEventListener('fetch', event => { - event.respondWith(handleRequest(event.request, event.env)); -}); - -async function handleRequest(request, env) { - // Handle CORS preflight requests - if (request.method === "OPTIONS") { - return handleCORS(request); - } +// ES Module export for Cloudflare Workers (required for AI binding) +export default { + async fetch(request, env, ctx) { + // Handle CORS preflight requests + if (request.method === "OPTIONS") { + return handleCORS(request); + } - // All requests go through the combined handler - return handleCombinedRequest(request, env); -} + // All requests go through the combined handler + return handleCombinedRequest(request, env); + } +}; /** * Generate AI-based IP reputation analysis using Cloudflare Workers AI From 16da0b351e02bff3e5dc474b73f19e5e1f4bcd50 Mon Sep 17 00:00:00 2001 From: Drew Kennedy Date: Tue, 28 Oct 2025 16:03:53 +1000 Subject: [PATCH 07/17] Add events summary to AI reputation analysis and enhance prompt structure --- Services/AbuseIPDBService.cs | 3 +++ cloudflare-worker.template.js | 37 +++++++++++++++++++++++++++++------ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/Services/AbuseIPDBService.cs b/Services/AbuseIPDBService.cs index da5b5b6..7748d92 100644 --- a/Services/AbuseIPDBService.cs +++ b/Services/AbuseIPDBService.cs @@ -68,6 +68,9 @@ public class AIAnalysis [JsonPropertyName("summary")] public string? Summary { get; set; } + [JsonPropertyName("eventsSummary")] + public string? EventsSummary { get; set; } + [JsonPropertyName("recommendations")] public List? Recommendations { get; set; } } diff --git a/cloudflare-worker.template.js b/cloudflare-worker.template.js index 88b1c4d..da99e76 100644 --- a/cloudflare-worker.template.js +++ b/cloudflare-worker.template.js @@ -140,6 +140,25 @@ async function generateAIReputation(env, ipAddress, abuseData, asnData) { const usageType = abuseData?.data?.usageType || 'Unknown'; const asnName = asnData?.result?.[0]?.asn?.name || 'Unknown'; const asnNumber = asnData?.result?.[0]?.asn?.asn || 'Unknown'; + const numDistinctUsers = abuseData?.data?.numDistinctUsers || 0; + const reports = abuseData?.data?.reports || []; + + // Extract sample abuse event details + let eventSummary = ''; + if (reports.length > 0) { + const recentReports = reports.slice(0, 5); + const categories = recentReports.flatMap(r => r.categories || []); + const uniqueCategories = [...new Set(categories)]; + const comments = recentReports.map(r => r.comment).filter(c => c && c.trim()); + + eventSummary = `\n\nAbuse Events (${totalReports} reports from ${numDistinctUsers} users):`; + if (uniqueCategories.length > 0) { + eventSummary += `\nEvent Types: ${uniqueCategories.join(', ')}`; + } + if (comments.length > 0) { + eventSummary += `\nSample Reports:\n${comments.slice(0, 3).map((c, i) => ` ${i + 1}. ${c.substring(0, 100)}${c.length > 100 ? '...' : ''}`).join('\n')}`; + } + } // Create a prompt for the AI to analyze the IP reputation const prompt = `You are a cybersecurity expert analyzing IP address reputation. Based on the following real-time data, provide a concise risk assessment and reputation summary. @@ -150,17 +169,18 @@ ISP: ${isp} Usage Type: ${usageType} ASN: ${asnNumber} (${asnName}) Abuse Confidence Score: ${abuseScore}% (0-100 scale, higher is worse) -Total Abuse Reports: ${totalReports} +Total Abuse Reports: ${totalReports}${eventSummary} Provide a JSON response with the following structure: { "riskLevel": "low|medium|high|critical", "trustScore": , - "summary": "<2-3 sentence assessment>", + "summary": "<2-3 sentence overall risk assessment>", + "eventsSummary": "<1-2 sentence summary of abuse event patterns, or null if no events>", "recommendations": ["", ""] } -Focus on actionable insights based on the abuse score, report count, and network information. Keep the summary concise and professional.`; +Focus on actionable insights based on the abuse score, report count, network information, and abuse event patterns. Keep summaries concise and professional.`; console.log('Calling Workers AI with Llama 3.1 70B for IP reputation analysis...'); @@ -204,10 +224,15 @@ Focus on actionable insights based on the abuse score, report count, and network } catch (parseError) { console.error('Error parsing AI response:', parseError); // Fallback to basic analysis if JSON parsing fails + const eventsFallback = reports.length > 0 + ? `Reported ${totalReports} times for ${[...new Set(reports.flatMap(r => r.categories || []))].slice(0, 3).join(', ')}.` + : null; + analysis = { riskLevel: abuseScore > 75 ? 'critical' : abuseScore > 50 ? 'high' : abuseScore > 25 ? 'medium' : 'low', trustScore: Math.max(0, 100 - abuseScore), summary: `IP from ${countryCode} with ${abuseScore}% abuse confidence score and ${totalReports} reports.`, + eventsSummary: eventsFallback, recommendations: ['Review the abuse reports for details', 'Consider blocking if risk level is high'] }; } @@ -478,6 +503,9 @@ async function handleCombinedRequest(request, env) { // Combine the responses into a single response object const combinedResponse = { + // Add AI reputation analysis + aiReputation: aiReputation, + // AbuseIPDB data (maintain original structure for compatibility) data: abuseIPDBData?.data || null, @@ -488,9 +516,6 @@ async function handleCombinedRequest(request, env) { error: radarError }, - // Add AI reputation analysis - aiReputation: aiReputation, - // Metadata and errors abuseIPDBError: abuseIPDBError, workerInfo: { From c76f326da81395884cb6e525d361773ab04ec898 Mon Sep 17 00:00:00 2001 From: Drew Kennedy Date: Tue, 28 Oct 2025 16:27:47 +1000 Subject: [PATCH 08/17] Add AI reputation analysis to combined response in Cloudflare Worker --- cloudflare-worker.template.js | 1 + 1 file changed, 1 insertion(+) diff --git a/cloudflare-worker.template.js b/cloudflare-worker.template.js index da99e76..0ba80ca 100644 --- a/cloudflare-worker.template.js +++ b/cloudflare-worker.template.js @@ -503,6 +503,7 @@ async function handleCombinedRequest(request, env) { // Combine the responses into a single response object const combinedResponse = { + // Add AI reputation analysis aiReputation: aiReputation, From 0ebdf6857921189b3080a196d034ffeb34d18ba3 Mon Sep 17 00:00:00 2001 From: Drew Kennedy Date: Tue, 28 Oct 2025 16:40:53 +1000 Subject: [PATCH 09/17] Enforce property order in JSON response for combined API results --- cloudflare-worker.template.js | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/cloudflare-worker.template.js b/cloudflare-worker.template.js index 0ba80ca..cb533bc 100644 --- a/cloudflare-worker.template.js +++ b/cloudflare-worker.template.js @@ -542,7 +542,17 @@ async function handleCombinedRequest(request, env) { radar: hasRadarData ? 'success' : 'failed' }); - return new Response(JSON.stringify(combinedResponse), { + // Use custom replacer to enforce property order + const orderedKeys = ['aiReputation', 'data', 'asnInfo', 'abuseIPDBError', 'workerInfo']; + return new Response(JSON.stringify(combinedResponse, (key, value) => { + if (value && typeof value === 'object' && !Array.isArray(value)) { + return orderedKeys.reduce((obj, k) => { + if (k in value) obj[k] = value[k]; + return obj; + }, {}); + } + return value; + }), { status: 200, headers: { 'Content-Type': 'application/json', @@ -552,7 +562,18 @@ async function handleCombinedRequest(request, env) { } else { // Both APIs failed console.error('Both APIs failed'); - return new Response(JSON.stringify(combinedResponse), { + + // Use custom replacer to enforce property order + const orderedKeys = ['aiReputation', 'data', 'asnInfo', 'abuseIPDBError', 'workerInfo']; + return new Response(JSON.stringify(combinedResponse, (key, value) => { + if (value && typeof value === 'object' && !Array.isArray(value)) { + return orderedKeys.reduce((obj, k) => { + if (k in value) obj[k] = value[k]; + return obj; + }, {}); + } + return value; + }), { status: 502, headers: { 'Content-Type': 'application/json', From 7789f7dadcaa8cac3c12ee631ce27408817c5c94 Mon Sep 17 00:00:00 2001 From: Drew Kennedy Date: Tue, 28 Oct 2025 16:46:00 +1000 Subject: [PATCH 10/17] Ensure consistent property order in JSON response by manually constructing the response object --- cloudflare-worker.template.js | 30 ++++++++---------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/cloudflare-worker.template.js b/cloudflare-worker.template.js index cb533bc..9d29ccb 100644 --- a/cloudflare-worker.template.js +++ b/cloudflare-worker.template.js @@ -542,17 +542,10 @@ async function handleCombinedRequest(request, env) { radar: hasRadarData ? 'success' : 'failed' }); - // Use custom replacer to enforce property order - const orderedKeys = ['aiReputation', 'data', 'asnInfo', 'abuseIPDBError', 'workerInfo']; - return new Response(JSON.stringify(combinedResponse, (key, value) => { - if (value && typeof value === 'object' && !Array.isArray(value)) { - return orderedKeys.reduce((obj, k) => { - if (k in value) obj[k] = value[k]; - return obj; - }, {}); - } - return value; - }), { + // Manually construct JSON to guarantee property order + const responseJson = `{"aiReputation":${JSON.stringify(aiReputation)},"data":${JSON.stringify(abuseIPDBData?.data || null)},"asnInfo":${JSON.stringify({success: radarData?.success || false, data: radarData?.result || null, error: radarError})},"abuseIPDBError":${JSON.stringify(abuseIPDBError)},"workerInfo":${JSON.stringify(combinedResponse.workerInfo)}}`; + + return new Response(responseJson, { status: 200, headers: { 'Content-Type': 'application/json', @@ -563,17 +556,10 @@ async function handleCombinedRequest(request, env) { // Both APIs failed console.error('Both APIs failed'); - // Use custom replacer to enforce property order - const orderedKeys = ['aiReputation', 'data', 'asnInfo', 'abuseIPDBError', 'workerInfo']; - return new Response(JSON.stringify(combinedResponse, (key, value) => { - if (value && typeof value === 'object' && !Array.isArray(value)) { - return orderedKeys.reduce((obj, k) => { - if (k in value) obj[k] = value[k]; - return obj; - }, {}); - } - return value; - }), { + // Manually construct JSON to guarantee property order + const responseJson = `{"aiReputation":${JSON.stringify(aiReputation)},"data":${JSON.stringify(abuseIPDBData?.data || null)},"asnInfo":${JSON.stringify({success: radarData?.success || false, data: radarData?.result || null, error: radarError})},"abuseIPDBError":${JSON.stringify(abuseIPDBError)},"workerInfo":${JSON.stringify(combinedResponse.workerInfo)}}`; + + return new Response(responseJson, { status: 502, headers: { 'Content-Type': 'application/json', From 6b6fe86045c8fb7bc76a9addc4ce362d94c0e228 Mon Sep 17 00:00:00 2001 From: Drew Kennedy Date: Tue, 28 Oct 2025 16:54:14 +1000 Subject: [PATCH 11/17] Refactor JSON response handling to use combined response object and remove manual construction for property order --- Services/AbuseIPDBService.cs | 6 +++--- cloudflare-worker.template.js | 11 ++--------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/Services/AbuseIPDBService.cs b/Services/AbuseIPDBService.cs index 7748d92..bd84e81 100644 --- a/Services/AbuseIPDBService.cs +++ b/Services/AbuseIPDBService.cs @@ -17,15 +17,15 @@ namespace Albatross.Services /// public class AbuseIPDBApiResponse { + [JsonPropertyName("aiReputation")] + public AIReputation? AIReputation { get; set; } + [JsonPropertyName("data")] public AbuseIPDBData? Data { get; set; } [JsonPropertyName("asnInfo")] public AsnInfo? AsnInfo { get; set; } - [JsonPropertyName("aiReputation")] - public AIReputation? AIReputation { get; set; } - [JsonPropertyName("abuseIPDBError")] public string? AbuseIPDBError { get; set; } diff --git a/cloudflare-worker.template.js b/cloudflare-worker.template.js index 9d29ccb..0ba80ca 100644 --- a/cloudflare-worker.template.js +++ b/cloudflare-worker.template.js @@ -542,10 +542,7 @@ async function handleCombinedRequest(request, env) { radar: hasRadarData ? 'success' : 'failed' }); - // Manually construct JSON to guarantee property order - const responseJson = `{"aiReputation":${JSON.stringify(aiReputation)},"data":${JSON.stringify(abuseIPDBData?.data || null)},"asnInfo":${JSON.stringify({success: radarData?.success || false, data: radarData?.result || null, error: radarError})},"abuseIPDBError":${JSON.stringify(abuseIPDBError)},"workerInfo":${JSON.stringify(combinedResponse.workerInfo)}}`; - - return new Response(responseJson, { + return new Response(JSON.stringify(combinedResponse), { status: 200, headers: { 'Content-Type': 'application/json', @@ -555,11 +552,7 @@ async function handleCombinedRequest(request, env) { } else { // Both APIs failed console.error('Both APIs failed'); - - // Manually construct JSON to guarantee property order - const responseJson = `{"aiReputation":${JSON.stringify(aiReputation)},"data":${JSON.stringify(abuseIPDBData?.data || null)},"asnInfo":${JSON.stringify({success: radarData?.success || false, data: radarData?.result || null, error: radarError})},"abuseIPDBError":${JSON.stringify(abuseIPDBError)},"workerInfo":${JSON.stringify(combinedResponse.workerInfo)}}`; - - return new Response(responseJson, { + return new Response(JSON.stringify(combinedResponse), { status: 502, headers: { 'Content-Type': 'application/json', From 982ed7e53418056bbc828d497f26a1e8d5d8a0fb Mon Sep 17 00:00:00 2001 From: Drew Kennedy Date: Tue, 28 Oct 2025 17:02:58 +1000 Subject: [PATCH 12/17] Add build timestamp and ID generation to authentication key script --- .github/workflows/deploy-dev.yml | 21 +++++++++++++++++++-- .github/workflows/deploy-production.yml | 6 ++++-- Generate-AuthKey.ps1 | 21 ++++++++++++++++++--- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index e82df2f..a4e9617 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -66,6 +66,21 @@ jobs: - name: Install npm dependencies run: npm ci + - name: Generate Version + id: version + run: | + # Generate build timestamp in the same format as the app (yyyyMMdd-HHmm) + BUILD_TIMESTAMP=$(date -u +"%Y%m%d-%H%M") + BUILD_ID=$(echo "${{ github.sha }}" | cut -c1-8) + VERSION="${BUILD_TIMESTAMP}-${BUILD_ID}" + + echo "version=${VERSION}" >> $GITHUB_OUTPUT + echo "build-timestamp=${BUILD_TIMESTAMP}" >> $GITHUB_OUTPUT + echo "build-id=${BUILD_ID}" >> $GITHUB_OUTPUT + echo "Preview version: ${VERSION}" + echo "Build timestamp: ${BUILD_TIMESTAMP}" + echo "Build ID: ${BUILD_ID}" + - name: Generate Preview Name id: preview run: | @@ -95,9 +110,11 @@ jobs: CLOUDFLARE_RADAR_API_TOKEN: ${{ secrets.CLOUDFLARE_RADAR_API_TOKEN_DEV }} SkipCodeGeneration: false ALBATROSS_ENVIRONMENT: preview + BUILD_TIMESTAMP: ${{ steps.version.outputs.build-timestamp }} + BUILD_ID: ${{ steps.version.outputs.build-id }} run: | - # Generate authentication key with environment context - ./Generate-AuthKey.ps1 -OutputPath "Generated" -Environment "preview" -Verbose + # Generate authentication key with environment context and build info + ./Generate-AuthKey.ps1 -OutputPath "Generated" -Environment "preview" -BuildTimestamp "$env:BUILD_TIMESTAMP" -BuildId "$env:BUILD_ID" -Verbose # Build application (this generates both SPA and Worker files) dotnet build --configuration Release --no-restore diff --git a/.github/workflows/deploy-production.yml b/.github/workflows/deploy-production.yml index 3d89970..7169636 100644 --- a/.github/workflows/deploy-production.yml +++ b/.github/workflows/deploy-production.yml @@ -92,9 +92,11 @@ jobs: CLOUDFLARE_RADAR_API_TOKEN: ${{ secrets.CLOUDFLARE_RADAR_API_TOKEN }} SkipCodeGeneration: false ALBATROSS_ENVIRONMENT: production + BUILD_TIMESTAMP: ${{ steps.version.outputs.build-timestamp }} + BUILD_ID: ${{ steps.version.outputs.build-id }} run: | - # Generate authentication key with environment context - ./Generate-AuthKey.ps1 -OutputPath "Generated" -Environment "production" -Verbose + # Generate authentication key with environment context and build info + ./Generate-AuthKey.ps1 -OutputPath "Generated" -Environment "production" -BuildTimestamp "$env:BUILD_TIMESTAMP" -BuildId "$env:BUILD_ID" -Verbose # Build application (this generates both SPA and Worker files) dotnet build --configuration Release --no-restore diff --git a/Generate-AuthKey.ps1 b/Generate-AuthKey.ps1 index 53af0e7..02790d4 100644 --- a/Generate-AuthKey.ps1 +++ b/Generate-AuthKey.ps1 @@ -6,6 +6,8 @@ param( [string]$OutputPath = ".", [string]$KeyLength = "32", [string]$Environment = "production", + [string]$BuildTimestamp = "", + [string]$BuildId = "", [switch]$Verbose ) @@ -64,9 +66,22 @@ try { Write-BuildLog "Created output directory: $OutputPath" } - # Generate build timestamp - $buildTimestamp = Get-Date -Format "yyyyMMdd-HHmm" - $buildGuid = [System.Guid]::NewGuid().ToString("N").Substring(0, 8) + # Generate build timestamp and ID (use provided values or generate new ones) + if ([string]::IsNullOrWhiteSpace($BuildTimestamp)) { + $buildTimestamp = Get-Date -Format "yyyyMMdd-HHmm" + Write-BuildLog "Generated build timestamp: $buildTimestamp" + } else { + $buildTimestamp = $BuildTimestamp + Write-BuildLog "Using provided build timestamp: $buildTimestamp" + } + + if ([string]::IsNullOrWhiteSpace($BuildId)) { + $buildGuid = [System.Guid]::NewGuid().ToString("N").Substring(0, 8) + Write-BuildLog "Generated build ID: $buildGuid" + } else { + $buildGuid = $BuildId + Write-BuildLog "Using provided build ID: $buildGuid" + } # Create the generated constants file for C# $csharpContent = @" From bb346a111350976171f7b357edca9fbd7353ceb4 Mon Sep 17 00:00:00 2001 From: Drew Kennedy Date: Tue, 28 Oct 2025 17:12:11 +1000 Subject: [PATCH 13/17] Enhance README and Home.razor to include AI-powered analysis features and event pattern summaries --- Pages/Home.razor | 6 +++++- README.md | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Pages/Home.razor b/Pages/Home.razor index f8a40f1..e109c56 100644 --- a/Pages/Home.razor +++ b/Pages/Home.razor @@ -142,6 +142,7 @@
  • Cloud Detection: Identifies if IPs belong to AWS, Azure, GCP, or Oracle Cloud
  • Abuse Checking: Queries AbuseIPDB for security reputation and threat intelligence
  • Network Information: Provides ASN details and network ownership via Cloudflare Radar
  • +
  • AI-Powered Analysis: Uses Cloudflare Workers AI (Llama 3.1 70B) to generate risk assessments and actionable recommendations
  • 📝 Input Formats:
    @@ -151,10 +152,13 @@
  • 2001:4860:4860::8888 - IPv6 addresses supported
  • +
    🤖 AI Reputation Analysis:
    +

    Automatically analyzes IP reputation data using Cloudflare Workers AI with Llama 3.1 70B Instruct model. Provides risk level assessment (low/medium/high/critical), trust score (0-100), summary of abuse event patterns, and security recommendations based on real-time threat intelligence.

    +
    🔒 Security:

    All requests use HMAC-SHA256 authentication with timestamp validation. Only public routable IP addresses are accepted (private networks like 10.x.x.x, 192.168.x.x, and 127.x.x.x are blocked).

    -

    Data provided by AbuseIPDB, Cloudflare Radar, and official cloud provider IP manifests.

    +

    Data provided by AbuseIPDB, Cloudflare Radar, Cloudflare Workers AI, and official cloud provider IP manifests.

    diff --git a/README.md b/README.md index 6094fe2..429049f 100644 --- a/README.md +++ b/README.md @@ -328,6 +328,7 @@ The worker uses Cloudflare Workers AI with the Llama 3.1 70B Instruct model to g - **Risk Levels**: Categorizes threats as low, medium, high, or critical - **Trust Scores**: Provides 0-100 trust score for quick assessment - **AI Summary**: Natural language explanation of the IP's reputation +- **Event Pattern Analysis**: Summarizes abuse event types and patterns from historical reports - **Actionable Recommendations**: Specific steps to take based on the analysis - **Model**: `@cf/meta/llama-3.1-70b-instruct` (70 billion parameter model) @@ -340,6 +341,7 @@ Example AI Response: "riskLevel": "medium", "trustScore": 65, "summary": "This IP from US shows moderate abuse activity with 15 reports. ISP indicates datacenter usage which is common for both legitimate and malicious traffic.", + "eventsSummary": "Reported 15 times for Port Scan, Brute-Force, SSH activity over the past 30 days.", "recommendations": [ "Review the specific abuse reports for patterns", "Consider rate limiting if used for API access" From ccb7f8260ce8084e0c2e9fb282d2ce20a28987ba Mon Sep 17 00:00:00 2001 From: Drew Kennedy Date: Fri, 31 Oct 2025 18:44:00 +1000 Subject: [PATCH 14/17] Replace static ASCII art with pixel-matrix renderer, add styles, and minor docs/date updates - Home.razor: remove static ascii image and render an inline pixel matrix using AsciiArtPixelMatrix, BuildAsciiArtPixelMatrix and GetPixelShadeClass; add matrix dimensions/constants and wire up markup - wwwroot/css/app.css: add pixel-matrix styles and responsive pixel sizing; reduce JSON/code font-size and remove redundant wide-screen font-size overrides for inputs - cloudflare-worker.template.js: expand eventsSummary comment to include targeted services and longer summary guidance - wwwroot/sitemap.xml: update lastmod timestamps to 2025-10-31 --- Pages/Home.razor | 83 ++++++++++++++++++++++++++++++++++- cloudflare-worker.template.js | 2 +- wwwroot/css/app.css | 79 ++++++++++++++++++++++++++++++--- wwwroot/sitemap.xml | 12 ++--- 4 files changed, 160 insertions(+), 16 deletions(-) diff --git a/Pages/Home.razor b/Pages/Home.razor index e109c56..e216b6f 100644 --- a/Pages/Home.razor +++ b/Pages/Home.razor @@ -109,7 +109,18 @@ --> CloudIPSearch
    ascii-text-art-albatross

    -Albatross

    + +

    @@ -285,7 +296,12 @@ -@code { private string ipAddress = string.Empty;private string statusMessage = string.Empty; +@code { + private const int AsciiArtPixelMatrixColumns = 240; + private const int AsciiArtPixelMatrixRows = 44; + private static readonly int[][] AsciiArtPixelMatrix = BuildAsciiArtPixelMatrix(); + + private string ipAddress = string.Empty;private string statusMessage = string.Empty; private List? azureMatchedServices; private List? awsMatchedServices; private List? gcpMatchedServices; @@ -296,6 +312,69 @@ private bool showJson = true; // Default to showing JSON private string formattedJson = string.Empty; private Albatross.Services.AbuseIPDBApiResponse? abuseIpResult; + + private static string GetPixelShadeClass(int value) => value switch + { + 1 => "pixel-matrix__pixel--edge", + 2 => "pixel-matrix__pixel--mid", + 3 => "pixel-matrix__pixel--dark", + 4 => "pixel-matrix__pixel--core", + _ => "pixel-matrix__pixel--empty" + }; + + private static int[][] BuildAsciiArtPixelMatrix() + { + string rawMatrix = @"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011111222222222222222221000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022223444444444444444442100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000122223433333333333333332100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001222223333344444333334343444433332100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001222223433444444444434333334334342100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000000000000011111111111222333334433344443333334333434434343222200000000000000000000000000000000000000000000000111111222222222222222222222111100000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000000000000022222222223444444444434334342121244343434434334444310000000000000000000000000000000000000000000000122222344444444444444444444222100000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000000000000022222222223433444444433334343222233333334333333334311000000000000000000000000000000000000000000111222222343433333333333444434222110000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000122222222222233444444443444334444334434343444343444434343434334321000000000000000000000000000122223433443444444444444444444433433343433344433334211000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000122222222222244444444443333333333334334333334333334334333433334311000000000000000000000000000122223333333444444444444444444444433444444443443334211000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000111111111222333333333332333444443434333434334334333434333434434333434334333221100000000000000001111111233333444444444444444444444444322334322323234334333333221100000000000000 +000000000000000000000000000000000000000000000000000000000000000001222222222223444444444421213443443434333434334334333434333334434333434333444442110000000000000002222222344443444444443333333333333334221344211212224224344444432100000000000000 +000000000000000000000000000000000000000000000000000000000011111111222222222223333443333322223333333333333333334334333334333334333333433333333332211100001111110012222222243444444444443344433333433334322334322222223223333333332211000000000000 +000000000000000000000000000000000000000000000000000011222234444444444444444443222221111122222222233444334444334434343434343444434343434343444334444322223444442222222222243444444443333344433343434343444433444444432222222222344444211000000000 +000000000000000000000000000000000000000000000000000001222234333334444444444443222221111122222222244433333333334333333333333334333333333333334333334322223333332222222222343444444444444444434333333333433333333333432222222222344444211000000000 +000000000000000000000000000000111111111111112333333333443444444343222222222221000000000000112211222233333434334334343434343444434333434333444333434333333344333333332222223444444322222222332343433333443433343433433333333333222222333210000000 +000000000000000000000000000000111111111111222444444444444444444443222221111110000000000000011211222244433433334334333334333334434333434333444333333444444344334444342222123344434322222222341344444444433433343433344444444444222222344210000000 +000000000000000000000001111111111111111111222333333333333333333332111111111110000000000000001111222233333333334334333444333334434333434333434333333333333344333334443333234333333211111221232333333333343333343333343333333334322222333322100000 +000000000000000000000012222222222111122222211111111111111111111110000000000000000000000000000111222222234434334334343444343444434343434343444333444333444344343444444444444322101000000011112222211112343433343433344433343433444444434444211000 +000000000000000000000012222222222111122222211111111111111111111110000000000000000000000000000111222222233433334334333444333334333333333333334333333333333344333334444444434322111000000011112222221112343433343333333433333333333333433333211000 +000000000000000111111111111111111111111111111233333333322333333321110000000000000000000000000001111112222223434444444444343444434343434343444333444333444344343434444444444433322111000000000001111112222124332333333432343434344434434343333211 +000000000000000111111111111110111111111011111234444444422444443421110000000000000000000000000000111112222223434444444334333334434333434333434333333333333344333433333344444434312101000000000001111112222114431344444431343333333333433333434210 +000000000000112111111111111122222222222222111122222222222233333422111211000000000000000000000000100001111212334444444334333334434333434333434333334333334344333434343434444434323222211100000000000011111112221222222332343344444344444444444322 +000000000111234222222222222234444444444443211111111111111112443422223421100000000000000000000000000000000111234333333434343444434333434333444333434333434344343434343433333343444444442100000000000000000001111111110344444444444444444444444444 +000000000111233222222222222233333333333343211111111111111112333322222422100000000000000000000000000000000111233334333334333334333333433333334333333333333344333433333434344333333333332110000000000000000001111111111333333344444344444444444333 +000111221111111111111122333333334333344443333321000000000000111111233333333110000000000000000000000000000000012244343434343444434343434343444333444333444344343434343434344344344443433333332000000000000000000000000111111111111111111111111111 +000111222111111111111112334444344444443333444421000000000000111111234444443210000000000000000000000000000000011233333334333334334333434333334333333333333344333434333434344333344443333444432000000000000000000000000111111111111111111111111111 +011111111100000011111122333333433333334343322221000000000000000001122222222111000000000000000000000000001112223344333434333434434333434333444333334333334344333434343434344343334443333343433210000000000000000000000000000000000000000000000000 +112111111100000001111122443222422222234443211110000000000000000000111111111121110000000000000000000000111223444434343434343434434333434333444333334333334344343434343434344343433333333344434310000000000000000000000000000000000000000000000000 +112111111100000001111112333222322222233333211110000000000000000000111111111121110000000000000000000001111223434334333334333334333333433333334333333333333344333433333433344333333333333343334320000000000000000000000000000000000000000000000000 +111000000000000000000000111111112222422111100000000000000000000000000111111111111111222222222222222222222223434434323434343444434343434343444333444333444344343434343434344344433444433444434222210000000000000000000000000000000000000000000000 +111000000000000000000000111111112222432111100000000000000000000000000111111111111111222222222222222222222223444334313444333334334333434333334333333333333344333433333433344333333333333343334222210000000000000000000000000000000000000000000000 +000000000000000000000000000000001111111000000000000000000000000000001222222222222222222222222222222222222222223434333323433434433333433333434333434333434344343434343434344333333343333343434333331100000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000001222222222222222222222222222222222222222213434342213433434444344444344444333334333334344343434343434344444444443444444433444432100000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000001222222222222222222222222222222222222222223434343223433343333333333333333333333333333344333433333433333333333343333333333443432210000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000011122222222222222222222222222222244443244334334323433342222222222222223433334333334344333434333433332222222344322222343333434310000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000011122222222222222222222222222222233342234334444323444442222222222222223444444444444444344444344444332222222344322222344444444310000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000001111122222222222112223333333333344343334343222222222222222222222222222222222222222222222222222222343333332222221111111122222220000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000111122222222221111223443444444444334444442122222222222222222222222222222222222222222222222222211244444442222221111111112222220000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000111122222222221112223433443333344333333332222222222222222222222222222222222222222222222222222222333333332222111111111111111110000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000111111111111122223433443434333434322222222222222222244444442222222222222222222222222222223444322221111111000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000111111111111122223443443444343434322222222222222222243333342222222222222222222222222222223434322221111111000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011112222222222211111111111111111111111111122222222222222222222222222222222221111111110000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011111222222222111111111111111111111111111122222222222222222222222222222222221111111110000000000000000000000000000000000000000000000000000000000000000000"; + + return rawMatrix + .Split('\n') + .Where(line => !string.IsNullOrEmpty(line)) + .Select(row => row.Select(ch => ch - '0').ToArray()) + .ToArray(); + } // Called when component is initialized protected override void OnInitialized() diff --git a/cloudflare-worker.template.js b/cloudflare-worker.template.js index 0ba80ca..b05f915 100644 --- a/cloudflare-worker.template.js +++ b/cloudflare-worker.template.js @@ -176,7 +176,7 @@ Provide a JSON response with the following structure: "riskLevel": "low|medium|high|critical", "trustScore": , "summary": "<2-3 sentence overall risk assessment>", - "eventsSummary": "<1-2 sentence summary of abuse event patterns, or null if no events>", + "eventsSummary": "<2-3 sentence summary of abuse event patterns & targeted services or null if no events>", "recommendations": ["", ""] } diff --git a/wwwroot/css/app.css b/wwwroot/css/app.css index 59dadae..351a2c8 100644 --- a/wwwroot/css/app.css +++ b/wwwroot/css/app.css @@ -304,7 +304,7 @@ code { width: 500px; max-width: 90vw; font-family: 'Quantico', monospace; - font-size: 1rem; + font-size: 0.8rem; padding: 8px 12px; border: 2px solid #dee2e6; border-radius: 4px; @@ -394,10 +394,9 @@ pre { padding-left: 2rem; padding-right: 2rem; } - /* Scale down input and button sizes for wide screens */ + /* Scale input width on wide screens */ .ip-input { width: 450px; - font-size: 0.95rem; } .btn { @@ -437,10 +436,9 @@ pre { .content { max-width: 1320px; } - /* Further scale down for very wide screens */ + /* Further scale input width for very wide screens */ .ip-input { width: 420px; - font-size: 0.9rem; } .btn { @@ -478,10 +476,9 @@ pre { .content { max-width: 1400px; } - /* Even more conservative for 4K and ultra-wide monitors */ + /* Scale input width for 4K and ultra-wide monitors */ .ip-input { width: 400px; - font-size: 0.85rem; } .card-container { @@ -1212,3 +1209,71 @@ html body div.card div.card-header .btn { } } +/* Pixel matrix styles for ASCII art replacement */ +.ascii-art-pixel-matrix { + display: inline-block; + margin: 0 auto; + padding: 10px 0; + --pixel-size: 3px; + --matrix-cols: 240; + --aspect-ratio: 44 / 240; +} + +.pixel-matrix__grid { + display: grid; + grid-template-columns: repeat(240, var(--pixel-size)); + grid-auto-rows: var(--pixel-size); + gap: 0; + background: white; + border: none; +} + +.pixel-matrix__pixel { + display: block; + width: 100%; + height: 100%; +} + +.pixel-matrix__pixel--empty { + background-color: #ffffff; +} + +.pixel-matrix__pixel--edge { + background-color: #cccccc; +} + +.pixel-matrix__pixel--mid { + background-color: #888888; +} + +.pixel-matrix__pixel--dark { + background-color: #333333; +} + +.pixel-matrix__pixel--core { + background-color: #000000; +} + +/* Responsive sizing for pixel matrix */ +@media (max-width: 768px) { + .ascii-art-pixel-matrix { + --pixel-size: 2px; + } + + .pixel-matrix__grid { + grid-template-columns: repeat(240, var(--pixel-size)); + grid-auto-rows: var(--pixel-size); + } +} + +@media (max-width: 480px) { + .ascii-art-pixel-matrix { + --pixel-size: 1px; + } + + .pixel-matrix__grid { + grid-template-columns: repeat(240, var(--pixel-size)); + grid-auto-rows: var(--pixel-size); + } +} + diff --git a/wwwroot/sitemap.xml b/wwwroot/sitemap.xml index 88049e0..debbbd6 100644 --- a/wwwroot/sitemap.xml +++ b/wwwroot/sitemap.xml @@ -7,37 +7,37 @@ https://albatross.devnomadic.com/ - 2025-10-28 + 2025-10-31 weekly 1.0 https://albatross.devnomadic.com/ip-manifests/AWS.json - 2025-10-28 + 2025-10-31 daily 0.8 https://albatross.devnomadic.com/ip-manifests/Azure.json - 2025-10-28 + 2025-10-31 daily 0.8 https://albatross.devnomadic.com/ip-manifests/GCP.json - 2025-10-28 + 2025-10-31 daily 0.8 https://albatross.devnomadic.com/ip-manifests/Oracle.json - 2025-10-28 + 2025-10-31 daily 0.8 https://albatross.devnomadic.com/robots.txt - 2025-10-28 + 2025-10-31 monthly 0.3 From 0533b02667eaadd17f9cfbf829bafdd67c62dfad Mon Sep 17 00:00:00 2001 From: Drew Kennedy Date: Fri, 31 Oct 2025 20:15:26 +1000 Subject: [PATCH 15/17] Make ASCII pixel-matrix responsive: replace pixel-size vars with grid + aspect-ratio, enforce square pixels, set container width/max-width and responsive widths; center IP input text/placeholder --- wwwroot/css/app.css | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/wwwroot/css/app.css b/wwwroot/css/app.css index 351a2c8..dc9791b 100644 --- a/wwwroot/css/app.css +++ b/wwwroot/css/app.css @@ -311,6 +311,7 @@ code { margin-right: 10px; margin-bottom: 20px; transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + text-align: center; } .ip-input:focus { @@ -322,6 +323,7 @@ code { .ip-input::placeholder { color: #6c757d; opacity: 1; + text-align: center; } /* Button spacing */ @@ -1214,15 +1216,16 @@ html body div.card div.card-header .btn { display: inline-block; margin: 0 auto; padding: 10px 0; - --pixel-size: 3px; - --matrix-cols: 240; - --aspect-ratio: 44 / 240; + width: 50%; + max-width: 1163px; } .pixel-matrix__grid { display: grid; - grid-template-columns: repeat(240, var(--pixel-size)); - grid-auto-rows: var(--pixel-size); + grid-template-columns: repeat(240, 1fr); + grid-auto-rows: auto; + aspect-ratio: 1163 / 216; + width: 100%; gap: 0; background: white; border: none; @@ -1232,6 +1235,7 @@ html body div.card div.card-header .btn { display: block; width: 100%; height: 100%; + aspect-ratio: 1 / 1; } .pixel-matrix__pixel--empty { @@ -1257,23 +1261,13 @@ html body div.card div.card-header .btn { /* Responsive sizing for pixel matrix */ @media (max-width: 768px) { .ascii-art-pixel-matrix { - --pixel-size: 2px; - } - - .pixel-matrix__grid { - grid-template-columns: repeat(240, var(--pixel-size)); - grid-auto-rows: var(--pixel-size); + width: 70%; } } @media (max-width: 480px) { .ascii-art-pixel-matrix { - --pixel-size: 1px; - } - - .pixel-matrix__grid { - grid-template-columns: repeat(240, var(--pixel-size)); - grid-auto-rows: var(--pixel-size); + width: 90%; } } From 65519543a903b73aca9d8d0bcf76ef9ff635bd3a Mon Sep 17 00:00:00 2001 From: Drew Kennedy Date: Fri, 31 Oct 2025 22:15:57 +1000 Subject: [PATCH 16/17] Fix ASCII pixel-matrix mapping and spacing: use core class for value 3, tighten container padding, and normalize img width attribute quoting --- Pages/Home.razor | 4 ++-- wwwroot/css/app.css | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Pages/Home.razor b/Pages/Home.razor index e216b6f..88bc4d6 100644 --- a/Pages/Home.razor +++ b/Pages/Home.razor @@ -108,7 +108,7 @@
    --> CloudIPSearch
    -ascii-text-art-albatross

    +ascii-text-art-albatross