feat: implement full CyberPot Attack Map redesign - #4
Conversation
Complete redesign of visual foundation, map enhancements, dashboard, interactive features, and real-time animations with comprehensive optimizations. Co-authored-by: KhulnaSoft bot <43526132+khulnasoft-bot@users.noreply.github.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review. WalkthroughThe redesign adds map, dashboard, interaction, and real-time animation modules. It also adds design documentation, CSS themes and animations, deferred script loading, and project configuration. ChangesProject setup and design documentation
Frontend integration and presentation
Client-side enhancement modules
Estimated code review effort: 4 (Complex) | ~45 minutes Merge Risk: 🔵 Low · up to The redesign may throw a runtime error when anomaly detection receives missing statistics data. The change is localized and mergeable with owner follow-up, but normalizing the input before reading its fields is recommended. Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 14
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
static/index.css (1)
1790-1834:⚠️ Potential issue | 🟠 MajorHiding all scrollbars globally is an accessibility concern.
Setting
scrollbar-width: noneon*and hiding WebKit scrollbars removes a critical visual affordance. Users who rely on visible scrollbars (e.g., those with motor impairments or those unfamiliar with trackpad gestures) will have no indication that content is scrollable. Consider restricting this to specific containers where it's a deliberate design choice, or use thin scrollbars (scrollbar-width: thin) as a compromise.static/index.html (1)
345-363:⚠️ Potential issue | 🔴 CriticalHTML tab
data-tabvalues don't matchDashboardTabManager.tabskeys — tab switching will silently fail.The HTML defines
data-tab="countries"(line 353) anddata-tab="overview"(line 357), butDashboardTabManager(indashboard-enhancements.js) expects'top-countries'and'stats'. Similarly, thedata-paneattributes on.tab-paneelements use IDs likeoverview-tabandcountries-tab, which don't follow adata-pane="..."attribute pattern at all —switchTabqueries[data-pane="${tabName}"]which won't find these elements.Either update the HTML attributes to match the manager's keys, or update the manager's keys to match the HTML. Here's one direction:
🐛 Proposed HTML fix (align with manager)
<button class="tab-btn" data-tab="top-ips"> <i class="fas fa-bullseye"></i> Top IPs </button> - <button class="tab-btn" data-tab="countries"> + <button class="tab-btn" data-tab="top-countries"> <i class="fas fa-globe"></i> Top Countries </button> - <button class="tab-btn" data-tab="overview"> + <button class="tab-btn" data-tab="stats"> <i class="fas fa-chart-bar"></i> Dashboard </button>And add
data-paneattributes to.tab-paneelements:- <div class="tab-pane" id="overview-tab"> + <div class="tab-pane" id="overview-tab" data-pane="stats"> ... - <div class="tab-pane" id="top-ips-tab"> + <div class="tab-pane" id="top-ips-tab" data-pane="top-ips"> ... - <div class="tab-pane" id="countries-tab"> + <div class="tab-pane" id="countries-tab" data-pane="top-countries"> ... - <div class="tab-pane" id="live-feed-tab"> + <div class="tab-pane" id="live-feed-tab" data-pane="live-feed">
🤖 Fix all issues with AI agents
In @.vscode/settings.json:
- Around line 1-5: Remove the workspace setting that makes every file read-only
in .vscode/settings.json by deleting the "\"**/*\": true" entry (or remove the
entire .vscode/settings.json file) so developers can edit files after cloning;
ensure no other read-only-wide globs remain in that file and commit the cleaned
settings (or remove the file) instead.
In `@node_modules`:
- Line 1: Remove the committed artifact named "node_modules" from the repo
(delete that file entry from the index/commit) and add "node_modules" to
.gitignore to prevent future commits; ensure you run git rm --cached (or
equivalent) to untrack the file and create a new commit that removes it, then
verify the real dependency directory created by npm/yarn is not shadowed by any
lingering repo entry named "node_modules".
In `@package.json`:
- Around line 1-71: The added package.json is incorrect for this repo: it
declares a Next.js app (scripts "dev"/"build"/"start"), dozens of unused
React/Radix/etc. dependencies, and mismatched Tailwind plugins (devDependency
"@tailwindcss/postcss" vs "tailwindcss"), which don't match the static
HTML/JS/CSS + Python backend; remove the package.json file entirely from the PR
(delete package.json) so the repository remains unchanged and CI/dependency
configs are not polluted.
In `@static/dashboard-enhancements.js`:
- Around line 36-58: The tabs defined in DashboardTabManager.tabs do not match
the HTML data-tab values, causing switchTab(tabName) to reject valid clicks;
update the tabs mapping in DashboardTabManager.tabs (or the HTML data-tab
attributes) so keys exactly match the data-tab values used in HTML (e.g.,
provide entries for "countries" and "overview" or rename HTML to "top-countries"
and "stats"), ensure switchTab, this.currentTab assignment, and
this.onTabActivated(tabName) continue to use the same canonical keys, and verify
any consumers that reference DashboardTabManager.tabs keys are updated to the
chosen naming.
- Around line 97-121: The template in createCard and the updateCardValue
function inject untrusted strings (title, value, icon, color, unit, trend) via
innerHTML/template literals creating XSS/CSS-injection risks; fix by
constructing the card with DOM methods or by escaping all interpolated values:
set text nodes via element.textContent for title/value/unit/trend percent and
never insert raw HTML for icon names or numeric values, instead apply icon
classes using element.classList.add(icon) on the created <i> element, and
validate/sanitize color before applying it to style (prefer a whitelist of
allowed colors or map semantic color names to CSS classes rather than writing
raw color into style). Also replace updateCardValue's element.innerHTML =
newValue with element.textContent = String(newValue) after
validation/sanitization.
- Around line 227-252: The createFeedItemElement function is vulnerable to XSS
because untrusted fields (event.src_ip, event.dst_ip, event.protocol,
event.country, event.honeypot, and event.timestamp) are interpolated into a
template string and later inserted via insertAdjacentHTML; fix it by
HTML-escaping those fields (or building DOM nodes and using
textContent/createTextNode) before interpolation or by using a trusted sanitizer
like DOMPurify, replacing the raw interpolations in createFeedItemElement and
any caller that uses insertAdjacentHTML so only escaped/sanitized strings are
inserted into the DOM.
- Around line 348-367: recordTimeSeries currently buckets by now.getMinutes()
which collides across hours; change the bucket key to a unique minute identifier
(e.g., compute epochMinute = Math.floor(now.getTime()/60000) or format a HH:mm
string) and use that as the comparison key instead of minute so entries for the
same minute-of-hour but different hours won't merge; update references in
recordTimeSeries to compare this new bucket key against the last entry in
this.stats.timeSeriesData (e.g., last.bucket or last.epochMinute), preserve the
display time (time: now.toLocaleTimeString()) and the rolling trim (keep length
<= 60) logic.
In `@static/index.css`:
- Around line 2961-2970: There are duplicate/conflicting `@keyframes`
definitions—@keyframes slideIn, `@keyframes` countUp, and `@keyframes`
fadeOut—causing later definitions to override earlier ones; locate all
definitions of these keyframe names (search for "@keyframes slideIn",
"@keyframes countUp", "@keyframes fadeOut") and consolidate or rename them so
each animation name has a single canonical definition (or give distinct names
like slideIn-up/slideIn-down, countUp-scale/countUp-slide, fadeOut-x/fadeOut-y)
and then update any CSS classes or HTML that reference the old names (animation
or animation-name) to use the chosen canonical/renamed keyframe to preserve
intended behavior.
- Around line 4190-4198: The CSS custom property --text-primary is declared
directly inside the `@media` (prefers-color-scheme: dark) block without a
selector, which is invalid; move the variable declaration into a valid selector
(e.g., :root or the `@media` block's body selector) so browsers apply it correctly
— update the `@media` (prefers-color-scheme: dark) block to contain a selector
(such as :root or body) and place --text-primary: `#E8EFF7`; there, leaving the
existing body { background-attachment: fixed; } rule intact.
In `@static/index.html`:
- Around line 553-556: Add SHA-384 SRI integrity attributes to the four new
script tags so they match the rest of the file's CSP/SRI setup: compute each
file's base64 SHA-384 using the provided command (openssl dgst -sha384 -binary
your.file.name | openssl base64 -A) and add integrity="sha384-<BASE64_HASH>" and
crossorigin="anonymous" to the <script> tags for static/map-enhancements.js,
static/dashboard-enhancements.js, static/interactive-features.js, and
static/realtime-animations.js; ensure the integrity value is the exact output
and that crossorigin="anonymous" is present to mirror the existing script tag
pattern.
In `@static/interactive-features.js`:
- Around line 228-256: The formatEventContent method is vulnerable to XSS
because it interpolates event.src_ip, event.dst_ip, event.protocol,
event.country, and event.dst_port directly into an HTML template; fix it by
creating and using an HTML-escape helper (e.g., escapeHtml) and apply it to each
dynamic value before interpolation in formatEventContent (e.g.,
escapeHtml(event.src_ip) etc.), and ensure this.escapeHtml is used for the value
passed into this.formatTime if that returns user-controllable text; update
references in formatEventContent to call the sanitizer for protocol, src_ip,
dst_ip, country, dst_port (and any other interpolated event fields).
In `@static/map-enhancements.js`:
- Around line 16-39: attackDensityGrid currently grows unbounded because
updateAttackDensity only increments entries; add a bounded eviction/reset
mechanism by implementing a clearDensityGrid() function (and optionally a
touch/timestamp map) and call it periodically or when visualization mode
switches; modify updateAttackDensity to record last-seen timestamps (or
increment a rolling counter) so you can prune stale keys when grid size exceeds
a cap, and ensure getGridCellKey and GRID_SIZE remain the aggregation basis
while you add the periodic setInterval or mode-switch hook to clear or prune
attackDensityGrid.
In `@static/realtime-animations.js`:
- Around line 197-241: The detectAnomalies function currently calls
Object.entries on currentStats.protocolStats and currentStats.countryStats
without guarding for null/undefined; update detectAnomalies to first normalize
currentStats (e.g., const cs = currentStats || {}) and derive safe locals like
const protocolStats = cs.protocolStats || {} and const countryStats =
cs.countryStats || {} and use a safe attacksPerMinute (e.g., const
attacksPerMinute = cs.attacksPerMinute || 0) so the Object.entries and
comparisons never receive null/undefined; modify all usages in detectAnomalies
(references to currentStats, currentStats.protocolStats,
currentStats.countryStats, and currentStats.attacksPerMinute) to use these safe
locals.
- Around line 287-303: The showAnomalyAlert method currently sets
alertDiv.innerHTML with unsanitized anomaly.message and anomaly.value causing an
XSS risk; change it to build the alert DOM using createElement and textContent
(or setAttribute for non-text) so that anomaly.message and anomaly.value are
never injected as HTML—locate showAnomalyAlert and replace the innerHTML
template with explicit DOM nodes for the icon, title, details and close button,
using textContent for the title/details and className or appendChild to compose
the structure.
🟡 Minor comments (7)
static/map-enhancements.js-200-215 (1)
200-215:⚠️ Potential issue | 🟡 Minor
lineGraph.node()can benull— unguardedgetTotalLength()call will throw.If the SVG element isn't ready or the append fails,
lineGraph.node()returnsnull, and calling.getTotalLength()on it will throw aTypeError. Add a null check.🛡️ Defensive fix
// Line animation with dash effect const length = lineGraph.node().getTotalLength(); + if (!lineGraph.node()) return; + const length = lineGraph.node().getTotalLength();static/map-enhancements.js-240-268 (1)
240-268:⚠️ Potential issue | 🟡 MinorRipple
setTimeoutcallbacks append to SVG but are not cleaned up on map zoom.Per project conventions, D3 elements should be cleared on zoom to prevent coordinate desynchronization. The
setTimeout-based ripple circles persist across zoom events and will render at stale coordinates. Based on learnings, D3 elements should be cleared on zoom to prevent coordinate desynchronization between visual elements and data.REDESIGN_IMPLEMENTATION_GUIDE.md-159-159 (1)
159-159:⚠️ Potential issue | 🟡 MinorMinor grammar: compound adjectives before nouns need hyphens.
- Line 159: "Full featured interface" → "Full-featured interface"
- Line 387: "Production Ready" → "Production-ready"
Also applies to: 387-387
REDESIGN_IMPLEMENTATION_GUIDE.md-154-170 (1)
154-170:⚠️ Potential issue | 🟡 MinorWCAG 2.1 Level AA compliance claim is premature given current implementation gaps.
The guide claims WCAG 2.1 Level AA compliance (line 165), but the reviewed JS modules generate interactive HTML (tooltips, context menus, feed items, stat cards) without ARIA roles, labels, or keyboard interaction support. The
ContextMenuManagercreates menu items with no keyboard navigation, no focus management, and norole="menu"/role="menuitem"attributes. Consider softening the claim to an aspiration or adding the missing accessibility attributes.static/interactive-features.js-413-415 (1)
413-415:⚠️ Potential issue | 🟡 MinorGlobal
contextmenulistener registered with no-op handler and nopreventDefault.
ContextMenuManager.init()attaches a globalcontextmenulistener, buthandleContextMenuis empty — it neither callse.preventDefault()nor creates any menu. This means a new listener is added on every instantiation with no effect, and the native context menu is never suppressed. Either implement the handler or defer registration untilcreateContextMenuis actually wired up.REDESIGN_IMPLEMENTATION_GUIDE.md-96-98 (1)
96-98:⚠️ Potential issue | 🟡 MinorDocumentation lists "Critical" severity but the code only implements "high", "medium", and "low".
Line 97 lists four severity levels:
Low/Medium/High/Critical. However,FilterSystemManager.checkSeverity(ininteractive-features.js, lines 78-92) only handles'high','medium', and'low'— there is no'critical'branch. Either add the missing severity level in the code or correct the documentation.,
static/interactive-features.js-187-212 (1)
187-212:⚠️ Potential issue | 🟡 MinorRemove the
closeButtonoption from the tooltip configuration at line 199 — it is not supported by Leaflet'sL.tooltip()and will be silently ignored.The
closeButtonoption is only valid forL.popup(), notL.tooltip(). Either useL.popupif you need a dismissible overlay with a close button, or remove this option from the tooltip configuration.
🧹 Nitpick comments (10)
static/index.css (2)
1199-1208: Redundant theme-specific overrides — both are identical to the base rule.Both
[data-theme="light"] .protocol-otherand[data-theme="dark"] .protocol-otherdeclare the exact same values (background:#78909C; color: white;) as the generic.protocol-otherat Line 1194. These blocks can be removed.
4142-4144: Permanentwill-change: transformon.map-containerwastes GPU memory.
will-changeshould be applied just before an animation and removed after. Keeping it permanently on a large container promotes it to its own compositing layer, consuming GPU memory for the lifetime of the page. If the map container isn't frequently animated, remove this or scope it to an active animation class.static/design-tokens.css (1)
1-200: This file is pure documentation (100% comments) — consider moving it out of the stylesheet pipeline.Loading this as a
<link rel="stylesheet">adds an HTTP request that delivers zero CSS rules. The documentation is valuable but would be better served as:
- Inline comments in
index.css(where the tokens are already defined), or- A Markdown file (e.g.,
DESIGN_TOKENS.md)static/realtime-animations.js (2)
359-369:durationparameter is accepted but never used.
createPulse(element, color, duration)accepts adurationparameter but doesn't apply it. The animation duration is controlled entirely by the.glow-elementCSS class (hardcoded at 2s inindex.css). Either use the parameter to setanimation-durationon the element's style, or remove it from the signature to avoid misleading callers.
87-115: Serial transition queue blocks all queued transitions behind each other.
SmoothTransitionManagerprocesses one transition at a time. If multiple independent elements are queued (e.g., several feed items appearing simultaneously), they'll animate sequentially rather than in parallel. For a real-time dashboard with high event throughput, this can cause a growing backlog.Consider allowing parallel transitions for distinct elements and only serializing transitions on the same element.
static/interactive-features.js (4)
10-22:this.filtersis initialized but never read — onlythis.activeFiltersis used.The
this.filtersobject (lines 12-19) with its predefined keys is set in the constructor but never referenced anywhere; all filter logic operates onthis.activeFilters. This creates confusion about which is the source of truth. Either removethis.filtersor use it as a schema to validatefilterTypeinapplyFilter.♻️ Option: use `filters` as a validation schema
applyFilter(filterType, filterValue) { + if (!(filterType in this.filters)) { + console.warn(`[FILTER-SYSTEM] Unknown filter type: ${filterType}`); + return; + } if (filterValue === null || filterValue === '') {
326-333:Math.random()as fallback ID risks collisions.Using
Math.random()for the search indexid(line 329) can produce duplicates across items in the same batch. Considercrypto.randomUUID()(widely supported in modern browsers) or a simple incrementing counter for uniqueness.
370-385:advancedSearchregex allows injection of arbitrary data keys.The regex
/(\w+):([^\s]+)/gparses user input into key-value pairs and performs a strict equality check againstitem.data[key]. While there's no direct security exploit here, a query like__proto__:fooorconstructor:barwould access prototype properties. Consider validatingkeyagainst a whitelist of known fields.♻️ Proposed safeguard
advancedSearch(query) { + const allowedFields = ['src_ip', 'dst_ip', 'protocol', 'country', 'honeypot', 'src_ip_rep', 'region', 'dst_port']; const params = {}; const regex = /(\w+):([^\s]+)/g; let match; while ((match = regex.exec(query)) !== null) { - params[match[1]] = match[2]; + if (allowedFields.includes(match[1])) { + params[match[1]] = match[2]; + } } return this.searchIndex.filter(item => { - for (const [key, value] of Object.entries(params)) { - if (item.data[key] !== value) return false; + for (const [key, value] of Object.entries(params)) { + if (!Object.prototype.hasOwnProperty.call(item.data, key) || item.data[key] !== value) return false; } return true; }).map(item => item.data); }
468-473: Exporting classes rather than instances means each consumer must instantiate separately.
window.interactiveFeaturesexposes the raw classes, whilewindow.dashboardEnhancements(in the other file) also exports classes. This is consistent, but note that multiple instantiations ofContextMenuManagerwill register duplicate globalcontextmenulisteners. Consider documenting that each manager should be instantiated at most once, or export singleton instances instead.static/dashboard-enhancements.js (1)
306-316:uniqueAttackersSet grows unboundedly for long-running sessions.In a cybersecurity dashboard that may run for hours or days, every distinct
src_ipis permanently retained in the Set (line 310, 326). There is no eviction strategy. Consider capping it or resetting periodically (e.g., per time window), or tracking only a count/HyperLogLog approximation.
| { | ||
| "files.readonlyInclude": { | ||
| "**/*": true | ||
| } | ||
| } No newline at end of file |
There was a problem hiding this comment.
Do not commit workspace settings that mark all files as read-only.
This setting will prevent any developer from editing files in VSCode after cloning the repo. It appears to be a leftover from the code-generation/preview environment (v0.app). Remove this file or at least remove the "**/*": true glob.
🤖 Prompt for AI Agents
In @.vscode/settings.json around lines 1 - 5, Remove the workspace setting that
makes every file read-only in .vscode/settings.json by deleting the "\"**/*\":
true" entry (or remove the entire .vscode/settings.json file) so developers can
edit files after cloning; ensure no other read-only-wide globs remain in that
file and commit the cleaned settings (or remove the file) instead.
| @@ -0,0 +1 @@ | |||
| /vercel/share/v0-next-shadcn/node_modules No newline at end of file | |||
There was a problem hiding this comment.
Remove this file — node_modules must never be committed.
This is a platform artifact from the v0.app/Vercel generation environment (/vercel/share/v0-next-shadcn/node_modules). Committing a file named node_modules will shadow the real dependency directory after npm install and break the build. Add node_modules to .gitignore if not already present.
🤖 Prompt for AI Agents
In `@node_modules` at line 1, Remove the committed artifact named "node_modules"
from the repo (delete that file entry from the index/commit) and add
"node_modules" to .gitignore to prevent future commits; ensure you run git rm
--cached (or equivalent) to untrack the file and create a new commit that
removes it, then verify the real dependency directory created by npm/yarn is not
shadowed by any lingering repo entry named "node_modules".
| { | ||
| "name": "my-project", | ||
| "version": "0.1.0", | ||
| "private": true, | ||
| "scripts": { | ||
| "dev": "next dev", | ||
| "build": "next build", | ||
| "start": "next start", | ||
| "lint": "eslint ." | ||
| }, | ||
| "dependencies": { | ||
| "@hookform/resolvers": "^3.9.1", | ||
| "@radix-ui/react-accordion": "1.2.2", | ||
| "@radix-ui/react-alert-dialog": "1.1.4", | ||
| "@radix-ui/react-aspect-ratio": "1.1.1", | ||
| "@radix-ui/react-avatar": "1.1.2", | ||
| "@radix-ui/react-checkbox": "1.1.3", | ||
| "@radix-ui/react-collapsible": "1.1.2", | ||
| "@radix-ui/react-context-menu": "2.2.4", | ||
| "@radix-ui/react-dialog": "1.1.4", | ||
| "@radix-ui/react-dropdown-menu": "2.1.4", | ||
| "@radix-ui/react-hover-card": "1.1.4", | ||
| "@radix-ui/react-label": "2.1.1", | ||
| "@radix-ui/react-menubar": "1.1.4", | ||
| "@radix-ui/react-navigation-menu": "1.2.3", | ||
| "@radix-ui/react-popover": "1.1.4", | ||
| "@radix-ui/react-progress": "1.1.1", | ||
| "@radix-ui/react-radio-group": "1.2.2", | ||
| "@radix-ui/react-scroll-area": "1.2.2", | ||
| "@radix-ui/react-select": "2.1.4", | ||
| "@radix-ui/react-separator": "1.1.1", | ||
| "@radix-ui/react-slider": "1.2.2", | ||
| "@radix-ui/react-slot": "1.1.1", | ||
| "@radix-ui/react-switch": "1.1.2", | ||
| "@radix-ui/react-tabs": "1.1.2", | ||
| "@radix-ui/react-toast": "1.2.4", | ||
| "@radix-ui/react-toggle": "1.1.1", | ||
| "@radix-ui/react-toggle-group": "1.1.1", | ||
| "@radix-ui/react-tooltip": "1.1.6", | ||
| "autoprefixer": "^10.4.20", | ||
| "class-variance-authority": "^0.7.1", | ||
| "clsx": "^2.1.1", | ||
| "cmdk": "1.1.1", | ||
| "date-fns": "4.1.0", | ||
| "embla-carousel-react": "8.5.1", | ||
| "input-otp": "1.4.1", | ||
| "lucide-react": "^0.544.0", | ||
| "next": "16.1.6", | ||
| "next-themes": "^0.4.6", | ||
| "react": "^19", | ||
| "react-day-picker": "8.10.1", | ||
| "react-dom": "^19", | ||
| "react-hook-form": "^7.54.1", | ||
| "react-resizable-panels": "^2.1.7", | ||
| "recharts": "2.15.0", | ||
| "sonner": "^1.7.1", | ||
| "tailwind-merge": "^2.5.5", | ||
| "tailwindcss-animate": "^1.0.7", | ||
| "vaul": "^1.1.2", | ||
| "zod": "^3.24.1" | ||
| }, | ||
| "devDependencies": { | ||
| "@tailwindcss/postcss": "^4.1.13", | ||
| "@types/node": "^22", | ||
| "@types/react": "^19", | ||
| "@types/react-dom": "^19", | ||
| "postcss": "^8.5", | ||
| "tailwindcss": "^3.4.17", | ||
| "typescript": "5.7.3" | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
latest Next.js version 2026 release
💡 Result:
As of February 6, 2026, the latest stable Next.js release is Next.js 16.1.6, released January 27, 2026. [1][2]
If you also count pre-release (canary) builds, the newest tag is 16.2.0-canary.29 from February 5, 2026. [3]
Sources:
[1] GitHub tags (vercel/next.js) – v16.1.6 release date
[2] endoflife.date – Next.js latest versions table
[3] GitHub tags (vercel/next.js) – latest canary tag/date
🏁 Script executed:
# Check the overall project structure to understand what we're dealing with
git ls-files | head -20Repository: khulnasoft/cyberpot-attack-map
Length of output: 455
🏁 Script executed:
# Look for the actual files mentioned in the review
find . -name "map-enhancements.js" -o -name "realtime-animations.js" 2>/dev/null | head -10Repository: khulnasoft/cyberpot-attack-map
Length of output: 134
🏁 Script executed:
# Check if there's an app directory structure (Next.js 13+ uses this)
ls -la | grep -E "^d.*\.(next|app|pages)" || echo "No .next, app, or pages directories found"
fd -t d "app|pages" -d 2 2>/dev/null || echo "No app or pages directories"Repository: khulnasoft/cyberpot-attack-map
Length of output: 115
🏁 Script executed:
# Check content of the new JS files to see if they're plain browser scripts
cat -n static/map-enhancements.js | head -50Repository: khulnasoft/cyberpot-attack-map
Length of output: 1897
🏁 Script executed:
# Check if package.json was part of the original repo or added in this PR
git log --oneline package.json 2>/dev/null | head -5 || echo "No git history for package.json"Repository: khulnasoft/cyberpot-attack-map
Length of output: 131
🏁 Script executed:
# Check git status to see what files are in this PR/change
git diff --name-only HEAD~1 2>/dev/null || git status --short 2>/dev/null | head -20 || echo "Cannot determine PR files"Repository: khulnasoft/cyberpot-attack-map
Length of output: 159
🏁 Script executed:
# Look at the entire package.json to understand its scope
cat package.jsonRepository: khulnasoft/cyberpot-attack-map
Length of output: 2274
🏁 Script executed:
# Check if there's any Next.js config or build setup
ls -la next.config.* tsconfig.json postcss.config.* 2>/dev/null || echo "No Next.js or PostCSS config found"Repository: khulnasoft/cyberpot-attack-map
Length of output: 108
This package.json is entirely disconnected from the actual project and should not be merged.
The existing project is a static HTML/JS/CSS application with a Python backend (AttackMapServer.py, DataServer.py). All new JS files (map-enhancements.js, realtime-animations.js, etc.) are plain browser scripts—they don't use React, Next.js, Radix UI, or any dependency listed here. The repo has no Next.js structure (no app/, pages/, next.config.js, or tsconfig.json), yet this file declares Next.js scripts and 48+ unused dependencies, creating confusion for contributors and CI.
Key issues:
- Tailwind version conflict —
@tailwindcss/postcss: ^4.1.13(Tailwind v4 plugin) is incompatible withtailwindcss: ^3.4.17(Tailwind v3). - ~48 unused dependencies — None of the Radix UI, Recharts, react-hook-form, zod, etc. packages are referenced anywhere in the added code.
- No Next.js application — Scripts reference
next devandnext build, but the repo is a static project with no app or pages directory.
Remove this file.
🤖 Prompt for AI Agents
In `@package.json` around lines 1 - 71, The added package.json is incorrect for
this repo: it declares a Next.js app (scripts "dev"/"build"/"start"), dozens of
unused React/Radix/etc. dependencies, and mismatched Tailwind plugins
(devDependency "@tailwindcss/postcss" vs "tailwindcss"), which don't match the
static HTML/JS/CSS + Python backend; remove the package.json file entirely from
the PR (delete package.json) so the repository remains unchanged and
CI/dependency configs are not polluted.
| switchTab(tabName) { | ||
| if (!this.tabs[tabName]) { | ||
| console.warn('[DASHBOARD] Invalid tab:', tabName); | ||
| return; | ||
| } | ||
|
|
||
| // Update active states | ||
| document.querySelectorAll('.tab-btn').forEach(btn => { | ||
| btn.classList.remove('active'); | ||
| }); | ||
| document.querySelector(`[data-tab="${tabName}"]`)?.classList.add('active'); | ||
|
|
||
| // Update active pane | ||
| document.querySelectorAll('.tab-pane').forEach(pane => { | ||
| pane.classList.remove('active'); | ||
| }); | ||
| document.querySelector(`[data-pane="${tabName}"]`)?.classList.add('active'); | ||
|
|
||
| this.currentTab = tabName; | ||
|
|
||
| // Trigger tab-specific initialization if needed | ||
| this.onTabActivated(tabName); | ||
| } |
There was a problem hiding this comment.
Tab name mismatch: DashboardTabManager.tabs keys don't match the HTML data-tab values.
The manager defines tabs 'live-feed', 'top-ips', 'top-countries', 'stats' (lines 14-18), but the HTML in index.html uses data-tab="countries" (line 353) and data-tab="overview" (line 357). switchTab validates against this.tabs (line 37) so clicking "Top Countries" or "Dashboard" in the HTML will be rejected as invalid.
🤖 Prompt for AI Agents
In `@static/dashboard-enhancements.js` around lines 36 - 58, The tabs defined in
DashboardTabManager.tabs do not match the HTML data-tab values, causing
switchTab(tabName) to reject valid clicks; update the tabs mapping in
DashboardTabManager.tabs (or the HTML data-tab attributes) so keys exactly match
the data-tab values used in HTML (e.g., provide entries for "countries" and
"overview" or rename HTML to "top-countries" and "stats"), ensure switchTab,
this.currentTab assignment, and this.onTabActivated(tabName) continue to use the
same canonical keys, and verify any consumers that reference
DashboardTabManager.tabs keys are updated to the chosen naming.
| const cardHtml = ` | ||
| <div class="stat-card stat-card-${id}" data-card-id="${id}"> | ||
| <div class="stat-card-header"> | ||
| <div class="stat-card-title-section"> | ||
| <i class="fas ${icon}" style="color: ${color};"></i> | ||
| <h4 class="stat-card-title">${title}</h4> | ||
| </div> | ||
| ${trend ? ` | ||
| <div class="stat-card-trend ${trend > 0 ? 'positive' : 'negative'}"> | ||
| <i class="fas ${trend > 0 ? 'fa-arrow-up' : 'fa-arrow-down'}"></i> | ||
| <span>${Math.abs(trend)}%</span> | ||
| </div> | ||
| ` : ''} | ||
| </div> | ||
| <div class="stat-card-content"> | ||
| <div class="stat-card-value" data-value="${value}"> | ||
| ${value} | ||
| </div> | ||
| ${unit ? `<div class="stat-card-unit">${unit}</div>` : ''} | ||
| </div> | ||
| <div class="stat-card-footer"> | ||
| <div class="stat-card-bar"></div> | ||
| </div> | ||
| </div> | ||
| `; |
There was a problem hiding this comment.
createCard and updateCardValue also use raw HTML interpolation.
createCard builds HTML with title, value, icon, and color (lines 97-121) via template literals. updateCardValue sets element.innerHTML = newValue (line 153). If any of these values originate from attack data, they are XSS vectors. Additionally, injecting color directly into a style attribute (line 101) enables CSS injection.
Use textContent instead of innerHTML in updateCardValue, and escape values in createCard.
🔒 Partial fix for updateCardValue
- element.innerHTML = newValue;
+ element.textContent = newValue;Also applies to: 142-161
🤖 Prompt for AI Agents
In `@static/dashboard-enhancements.js` around lines 97 - 121, The template in
createCard and the updateCardValue function inject untrusted strings (title,
value, icon, color, unit, trend) via innerHTML/template literals creating
XSS/CSS-injection risks; fix by constructing the card with DOM methods or by
escaping all interpolated values: set text nodes via element.textContent for
title/value/unit/trend percent and never insert raw HTML for icon names or
numeric values, instead apply icon classes using element.classList.add(icon) on
the created <i> element, and validate/sanitize color before applying it to style
(prefer a whitelist of allowed colors or map semantic color names to CSS classes
rather than writing raw color into style). Also replace updateCardValue's
element.innerHTML = newValue with element.textContent = String(newValue) after
validation/sanitization.
| <script src="static/map-enhancements.js" defer></script> | ||
| <script src="static/dashboard-enhancements.js" defer></script> | ||
| <script src="static/interactive-features.js" defer></script> | ||
| <script src="static/realtime-animations.js" defer></script> |
There was a problem hiding this comment.
Missing Subresource Integrity (SRI) hashes on all four new script tags.
Every other <script> in this file includes an integrity attribute with an SRI hash. The four new enhancement scripts omit it entirely. This breaks the security posture established by the existing CSP and SRI setup — if any of these files are tampered with (CDN compromise, MITM, accidental corruption), the browser will still load them without verification.
Generate and add integrity attributes matching the pattern used elsewhere (SHA-384):
- <script src="static/map-enhancements.js" defer></script>
- <script src="static/dashboard-enhancements.js" defer></script>
- <script src="static/interactive-features.js" defer></script>
- <script src="static/realtime-animations.js" defer></script>
+ <script src="static/map-enhancements.js" defer integrity="sha384-HASH_HERE"></script>
+ <script src="static/dashboard-enhancements.js" defer integrity="sha384-HASH_HERE"></script>
+ <script src="static/interactive-features.js" defer integrity="sha384-HASH_HERE"></script>
+ <script src="static/realtime-animations.js" defer integrity="sha384-HASH_HERE"></script>The comment on line 11 even provides the command: openssl dgst -sha384 -binary your.file.name | openssl base64 -A
🤖 Prompt for AI Agents
In `@static/index.html` around lines 553 - 556, Add SHA-384 SRI integrity
attributes to the four new script tags so they match the rest of the file's
CSP/SRI setup: compute each file's base64 SHA-384 using the provided command
(openssl dgst -sha384 -binary your.file.name | openssl base64 -A) and add
integrity="sha384-<BASE64_HASH>" and crossorigin="anonymous" to the <script>
tags for static/map-enhancements.js, static/dashboard-enhancements.js,
static/interactive-features.js, and static/realtime-animations.js; ensure the
integrity value is the exact output and that crossorigin="anonymous" is present
to mirror the existing script tag pattern.
| formatEventContent(event) { | ||
| return ` | ||
| <div class="attack-tooltip"> | ||
| <div class="attack-tooltip-header"> | ||
| ${event.protocol || 'UNKNOWN'} Attack | ||
| </div> | ||
| <div class="attack-tooltip-row"> | ||
| <span class="attack-tooltip-label">From:</span> | ||
| <span class="attack-tooltip-value">${event.src_ip || 'Unknown'}</span> | ||
| </div> | ||
| <div class="attack-tooltip-row"> | ||
| <span class="attack-tooltip-label">To:</span> | ||
| <span class="attack-tooltip-value">${event.dst_ip || 'Local'}</span> | ||
| </div> | ||
| <div class="attack-tooltip-row"> | ||
| <span class="attack-tooltip-label">Country:</span> | ||
| <span class="attack-tooltip-value">${event.country || 'Unknown'}</span> | ||
| </div> | ||
| <div class="attack-tooltip-row"> | ||
| <span class="attack-tooltip-label">Port:</span> | ||
| <span class="attack-tooltip-value">${event.dst_port || 'N/A'}</span> | ||
| </div> | ||
| <div class="attack-tooltip-row"> | ||
| <span class="attack-tooltip-label">Time:</span> | ||
| <span class="attack-tooltip-value">${this.formatTime(event.timestamp)}</span> | ||
| </div> | ||
| </div> | ||
| `; | ||
| } |
There was a problem hiding this comment.
XSS vulnerability: event data is interpolated directly into HTML without sanitization.
formatEventContent injects event.src_ip, event.dst_ip, event.protocol, event.country, and event.dst_port directly into an HTML template string via ${}. If any of these fields contain malicious HTML or JavaScript (e.g., a crafted hostname or IP field from a WebSocket event), it will be rendered as live markup when the tooltip is added to the DOM.
Sanitize or escape all dynamic values before interpolation. A minimal helper:
🔒 Proposed fix
+ /**
+ * Escape HTML special characters
+ */
+ escapeHtml(str) {
+ if (!str) return '';
+ return String(str)
+ .replace(/&/g, '&')
+ .replace(/</g, '<')
+ .replace(/>/g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''');
+ }
+
formatEventContent(event) {
return `
<div class="attack-tooltip">
<div class="attack-tooltip-header">
- ${event.protocol || 'UNKNOWN'} Attack
+ ${this.escapeHtml(event.protocol) || 'UNKNOWN'} Attack
</div>
<div class="attack-tooltip-row">
<span class="attack-tooltip-label">From:</span>
- <span class="attack-tooltip-value">${event.src_ip || 'Unknown'}</span>
+ <span class="attack-tooltip-value">${this.escapeHtml(event.src_ip) || 'Unknown'}</span>
</div>
<div class="attack-tooltip-row">
<span class="attack-tooltip-label">To:</span>
- <span class="attack-tooltip-value">${event.dst_ip || 'Local'}</span>
+ <span class="attack-tooltip-value">${this.escapeHtml(event.dst_ip) || 'Local'}</span>
</div>
<div class="attack-tooltip-row">
<span class="attack-tooltip-label">Country:</span>
- <span class="attack-tooltip-value">${event.country || 'Unknown'}</span>
+ <span class="attack-tooltip-value">${this.escapeHtml(event.country) || 'Unknown'}</span>
</div>
<div class="attack-tooltip-row">
<span class="attack-tooltip-label">Port:</span>
- <span class="attack-tooltip-value">${event.dst_port || 'N/A'}</span>
+ <span class="attack-tooltip-value">${this.escapeHtml(event.dst_port) || 'N/A'}</span>
</div>
<div class="attack-tooltip-row">
<span class="attack-tooltip-label">Time:</span>
- <span class="attack-tooltip-value">${this.formatTime(event.timestamp)}</span>
+ <span class="attack-tooltip-value">${this.escapeHtml(this.formatTime(event.timestamp))}</span>
</div>
</div>
`;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| formatEventContent(event) { | |
| return ` | |
| <div class="attack-tooltip"> | |
| <div class="attack-tooltip-header"> | |
| ${event.protocol || 'UNKNOWN'} Attack | |
| </div> | |
| <div class="attack-tooltip-row"> | |
| <span class="attack-tooltip-label">From:</span> | |
| <span class="attack-tooltip-value">${event.src_ip || 'Unknown'}</span> | |
| </div> | |
| <div class="attack-tooltip-row"> | |
| <span class="attack-tooltip-label">To:</span> | |
| <span class="attack-tooltip-value">${event.dst_ip || 'Local'}</span> | |
| </div> | |
| <div class="attack-tooltip-row"> | |
| <span class="attack-tooltip-label">Country:</span> | |
| <span class="attack-tooltip-value">${event.country || 'Unknown'}</span> | |
| </div> | |
| <div class="attack-tooltip-row"> | |
| <span class="attack-tooltip-label">Port:</span> | |
| <span class="attack-tooltip-value">${event.dst_port || 'N/A'}</span> | |
| </div> | |
| <div class="attack-tooltip-row"> | |
| <span class="attack-tooltip-label">Time:</span> | |
| <span class="attack-tooltip-value">${this.formatTime(event.timestamp)}</span> | |
| </div> | |
| </div> | |
| `; | |
| } | |
| /** | |
| * Escape HTML special characters | |
| */ | |
| escapeHtml(str) { | |
| if (!str) return ''; | |
| return String(str) | |
| .replace(/&/g, '&') | |
| .replace(/</g, '<') | |
| .replace(/>/g, '>') | |
| .replace(/"/g, '"') | |
| .replace(/'/g, '''); | |
| } | |
| formatEventContent(event) { | |
| return ` | |
| <div class="attack-tooltip"> | |
| <div class="attack-tooltip-header"> | |
| ${this.escapeHtml(event.protocol) || 'UNKNOWN'} Attack | |
| </div> | |
| <div class="attack-tooltip-row"> | |
| <span class="attack-tooltip-label">From:</span> | |
| <span class="attack-tooltip-value">${this.escapeHtml(event.src_ip) || 'Unknown'}</span> | |
| </div> | |
| <div class="attack-tooltip-row"> | |
| <span class="attack-tooltip-label">To:</span> | |
| <span class="attack-tooltip-value">${this.escapeHtml(event.dst_ip) || 'Local'}</span> | |
| </div> | |
| <div class="attack-tooltip-row"> | |
| <span class="attack-tooltip-label">Country:</span> | |
| <span class="attack-tooltip-value">${this.escapeHtml(event.country) || 'Unknown'}</span> | |
| </div> | |
| <div class="attack-tooltip-row"> | |
| <span class="attack-tooltip-label">Port:</span> | |
| <span class="attack-tooltip-value">${this.escapeHtml(event.dst_port) || 'N/A'}</span> | |
| </div> | |
| <div class="attack-tooltip-row"> | |
| <span class="attack-tooltip-label">Time:</span> | |
| <span class="attack-tooltip-value">${this.escapeHtml(this.formatTime(event.timestamp))}</span> | |
| </div> | |
| </div> | |
| `; | |
| } |
🤖 Prompt for AI Agents
In `@static/interactive-features.js` around lines 228 - 256, The
formatEventContent method is vulnerable to XSS because it interpolates
event.src_ip, event.dst_ip, event.protocol, event.country, and event.dst_port
directly into an HTML template; fix it by creating and using an HTML-escape
helper (e.g., escapeHtml) and apply it to each dynamic value before
interpolation in formatEventContent (e.g., escapeHtml(event.src_ip) etc.), and
ensure this.escapeHtml is used for the value passed into this.formatTime if that
returns user-controllable text; update references in formatEventContent to call
the sanitizer for protocol, src_ip, dst_ip, country, dst_port (and any other
interpolated event fields).
| // Attack density tracking for heatmap mode | ||
| const attackDensityGrid = {}; | ||
| const GRID_SIZE = 50; // Size of grid cells for density calculation | ||
|
|
||
| /** | ||
| * Get grid cell key for a given latitude/longitude | ||
| * Used to aggregate attacks into heatmap cells | ||
| */ | ||
| function getGridCellKey(lat, lng) { | ||
| const cellLat = Math.floor(lat / GRID_SIZE) * GRID_SIZE; | ||
| const cellLng = Math.floor(lng / GRID_SIZE) * GRID_SIZE; | ||
| return `${cellLat},${cellLng}`; | ||
| } | ||
|
|
||
| /** | ||
| * Update attack density for heatmap visualization | ||
| */ | ||
| function updateAttackDensity(lat, lng, intensity = 1) { | ||
| const cellKey = getGridCellKey(lat, lng); | ||
| if (!attackDensityGrid[cellKey]) { | ||
| attackDensityGrid[cellKey] = 0; | ||
| } | ||
| attackDensityGrid[cellKey] += intensity; | ||
| } |
There was a problem hiding this comment.
attackDensityGrid grows without bound — potential memory leak.
updateAttackDensity only ever adds to the grid; there's no eviction, TTL, or reset mechanism. On a long-running dashboard receiving continuous attack data, this object will grow indefinitely. Consider adding a clearDensityGrid() function and calling it periodically or on visualization mode switch.
♻️ Suggested addition
+/**
+ * Clear the density grid to free memory
+ */
+function clearDensityGrid() {
+ Object.keys(attackDensityGrid).forEach(key => delete attackDensityGrid[key]);
+}
+
window.mapEnhancements = {
...
+ clearDensityGrid,
};🤖 Prompt for AI Agents
In `@static/map-enhancements.js` around lines 16 - 39, attackDensityGrid currently
grows unbounded because updateAttackDensity only increments entries; add a
bounded eviction/reset mechanism by implementing a clearDensityGrid() function
(and optionally a touch/timestamp map) and call it periodically or when
visualization mode switches; modify updateAttackDensity to record last-seen
timestamps (or increment a rolling counter) so you can prune stale keys when
grid size exceeds a cap, and ensure getGridCellKey and GRID_SIZE remain the
aggregation basis while you add the periodic setInterval or mode-switch hook to
clear or prune attackDensityGrid.
| showAnomalyAlert(anomaly) { | ||
| const alertDiv = document.createElement('div'); | ||
| alertDiv.className = `anomaly-alert anomaly-alert-${anomaly.severity}`; | ||
| alertDiv.innerHTML = ` | ||
| <div class="anomaly-alert-icon"> | ||
| <i class="fas fa-exclamation-triangle"></i> | ||
| </div> | ||
| <div class="anomaly-alert-content"> | ||
| <div class="anomaly-alert-title">${anomaly.message}</div> | ||
| <div class="anomaly-alert-details"> | ||
| Value: <strong>${anomaly.value}</strong> | ||
| </div> | ||
| </div> | ||
| <button class="anomaly-alert-close"> | ||
| <i class="fas fa-times"></i> | ||
| </button> | ||
| `; |
There was a problem hiding this comment.
XSS vulnerability: innerHTML with unsanitized data.
anomaly.message and anomaly.value are interpolated directly into innerHTML. The message field is constructed from protocol and country names derived from attack traffic data (Lines 224, 233), which could contain attacker-controlled strings. A crafted protocol or country name like <img src=x onerror=alert(1)> would execute arbitrary JavaScript.
Use textContent for user-facing text or sanitize before interpolation.
🐛 Proposed fix using DOM API
showAnomalyAlert(anomaly) {
const alertDiv = document.createElement('div');
alertDiv.className = `anomaly-alert anomaly-alert-${anomaly.severity}`;
- alertDiv.innerHTML = `
- <div class="anomaly-alert-icon">
- <i class="fas fa-exclamation-triangle"></i>
- </div>
- <div class="anomaly-alert-content">
- <div class="anomaly-alert-title">${anomaly.message}</div>
- <div class="anomaly-alert-details">
- Value: <strong>${anomaly.value}</strong>
- </div>
- </div>
- <button class="anomaly-alert-close">
- <i class="fas fa-times"></i>
- </button>
- `;
+
+ const icon = document.createElement('div');
+ icon.className = 'anomaly-alert-icon';
+ icon.innerHTML = '<i class="fas fa-exclamation-triangle"></i>';
+
+ const content = document.createElement('div');
+ content.className = 'anomaly-alert-content';
+
+ const title = document.createElement('div');
+ title.className = 'anomaly-alert-title';
+ title.textContent = anomaly.message;
+
+ const details = document.createElement('div');
+ details.className = 'anomaly-alert-details';
+ details.textContent = `Value: ${anomaly.value}`;
+
+ content.appendChild(title);
+ content.appendChild(details);
+
+ const closeBtn = document.createElement('button');
+ closeBtn.className = 'anomaly-alert-close';
+ closeBtn.innerHTML = '<i class="fas fa-times"></i>';
+
+ alertDiv.appendChild(icon);
+ alertDiv.appendChild(content);
+ alertDiv.appendChild(closeBtn);🤖 Prompt for AI Agents
In `@static/realtime-animations.js` around lines 287 - 303, The showAnomalyAlert
method currently sets alertDiv.innerHTML with unsanitized anomaly.message and
anomaly.value causing an XSS risk; change it to build the alert DOM using
createElement and textContent (or setAttribute for non-text) so that
anomaly.message and anomaly.value are never injected as HTML—locate
showAnomalyAlert and replace the innerHTML template with explicit DOM nodes for
the icon, title, details and close button, using textContent for the
title/details and className or appendChild to compose the structure.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: KhulnaSoft bot <43526132+khulnasoft-bot@users.noreply.github.com>
This PR introduces a complete redesign of the CyberPot Attack Map, enhancing its visual foundation and interactive capabilities.
Problem/Issue/Goal:
Fix/Solution:
Chat link: https://v0.app/chat/EQkhcBRpF6c
Summary by Sourcery
Redesign the CyberPot Attack Map as an interactive, responsive cybersecurity dashboard with richer visualizations, analytics, and real-time feedback.
New Features:
Enhancements:
Build:
Documentation:
Chores:
Summary by CodeRabbit
Release Notes
New Features
Styling
Documentation