IpDetailPanel had grown to ~750 lines holding three independent regions.
It is now the shell (metadata grid, inline Map / Country-traffic panels,
dialogs) with unchanged props, emits and multi-root template, rendering
IpAdvancedBlock (with ScoreDetailsPanel beside ScenarioFrictionPanel) and
IpAsnBlock, which relay view-usage / open-tool through it.
The inline /api/asn-history fetch becomes loadAsnHistoryInto in
utils/ip/asn-history.js, matching the other ASN loaders, now with tests.
Markup, copy, analytics and per-IP reset behaviour are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The advanced block gains two signed-in fields under the existing four:
Abuse activity (the reputation level) and Scenario friction, ten usage
scenarios each rated smooth / extra checks / may be restricted. The card
shows one tone dot per scenario instead of a score, so it can't be read
as a second quality score; the details list follows the score details'
layout and closes with a Persona Check invitation.
Green and yellow only (a new light-yellow ok-mild tone for extra checks),
no reasons shown, copy in every full locale.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Drop SAT (times out from outside Mexico) and Rapido (CORP same-origin
blocks the probe), plus the India members that are corporate or B2B
sites rather than ones people open day to day.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Links to the repository's GitHub /contribute page, which lists the open
good-first-issues above the contributing guide.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Closes the "Import a List" grid with a muted line linking to the open
country-list issue, so a visitor who finds their country missing sees
where to add it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Multi-line release notes in the labels input split into one label per
line and an empty line aborted buildx. Labels now come from
docker/metadata-action with the version pinned to RELEASE_VERSION, and
version inputs reach the shell through env instead of interpolation.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reposition the tool from AI services to well-known internet products:
the Note and the tool card copy change in every full locale, with a
v8.0.0 changelog entry. Perplexity and Docker read the incident.io
structure; Canva is a plain Statuspage row.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A `components.rename: { <id>: '<label>' }` selector relabels upstream rows
by component id after selection, so include / exclude keep matching the
upstream names. npm's "www.npmjs.com website" row now reads "Official
Website".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Express 5 exposes req.query through a getter that re-parses the URL on
every read, so the guards' in-place normalization was silently dropped:
?type=caa reached dns-resolver lowercase, ?asn=AS13335 reached the ASN
handlers unparsed, ?q=as13335 missed whois's AS<n> branch. Guards now pin
the parsed query as an own property before rewriting it, and a regression
block exercises them on a real Express request.
Fixes#521
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Four more ptest workers are live, so the Rule Test grid grows to
twelve cards and the README rule snippets cover them. The note above
the grid used to spell out the count in every locale; it now takes
the number from the card list, so the next change in count touches
one line. The CrossingTheWall rule keeps its eight-IP threshold.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Supabase's card showed one region and Cloudflare's read "major
outage" on every visit. Supabase and five other providers are
incident.io pages, whose Statuspage-compatible summary flattens
groups away and, for Supabase, returns one component of 189; those
providers now also read the native /proxy/<host> structure and list
its top-level groups, each rolled up to its worst visible child.
Cloudflare's top level is seven continents that go red whenever one
PoP is re-routed, so its card lists a fixed set of products instead
and takes its light from them, not from the page indicator.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
GA saw only category switches and resets, so a month of visits could
not say whether anyone read an item's details or ticked one. Four
labels on the existing SecurityChecklist event — ItemInfo and
CategoryIntro when a details panel or intro opens, Check and Ignore
when an item is ticked or set aside — read as a funnel after
ChangeList. Only the positive direction fires; closing and undoing
stay silent.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The checklist is content to read and work through, not a test that
runs, yet it sat among the network diagnostics where a card promises a
result. A fourth group, 'reference', now closes the Advanced Tools grid
and holds it alone; the group's title lands in every full locale, and
the self-hosted grouping test expects the group to survive the gates.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Tool opens labelled NavClick with the slug's first letter capitalized
('Pingtest'), while keyboard shortcuts sent a second, hand-written label
('PingTest', 'AsnProfile') on top of the one openTool already sent. GA
labels are case-sensitive, so one tool split into two rows and every
shortcut open counted twice. The label is now the slug as-is, the same
id as /tools/<slug>, and shortcuts rely on openTool's event alone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Radar files some events against a cloud region (originDetails) rather
than a location or ASN. The feed ignored that subject, so an ongoing
ORIGIN anomaly rendered as a blank first row and escaped dedupe against
its matching outage. Origins are now mapped, shown, and matched in
dedupe; stub annotations with no subject and no text are dropped. The
new PLATFORM outage level gets a translation in every full locale.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Raycast / curl / Lite badges sat in an unlabelled image row of their
own under the Advanced Tools. The footer now has four columns aligned
with that card grid — brand, Project, Docs & Support, Clients — so the
product entries get a heading and show on every route; the curl
commands keep their dialog. Docs links share one locale-aware helper.
The footer never repeats on-page content, and the fixed footer height
in style.css goes, as the columns size themselves.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Tablets report a desktop UA, so between 768px and ~1366px the flat
link row rendered and wrapped inside its links; no single breakpoint
fits six locales. use-nav-overflow.js measures the row against the
space beside the brand and actions and collapses to the hamburger
when it doesn't fit. The row stays rendered (invisible, inert) so it
remains measurable. DocsSearch switches to its icon below xl by CSS so
the action group's width never depends on the collapse state.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A probe is a plain fetch from this page, so a filter rule aimed only at
one request type (e.g. `$script`) can let it through while still
blocking the real script on a website; a line under the list version now
says so. The empty "not blocked only" / "blocked only" views claimed
everything was (or nothing was) blocked even when results had timed out
or were still pending; they now say only that no request was confirmed
in that state.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A blocker setup is part of the visitor's own environment, like the
browser info and proxy-rule sections, and is worth handing to whoever
helps debug it. The section stays deliberately small: request counts per
category (blocked / not blocked / no answer / total), no per-address or
per-vendor rows, so a report stays readable and the list can change
without breaking old links. Group ids are capped strings rather than an
enum for the same reason.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
People running uBlock Origin, Privacy Badger, a blocking browser, or a
DNS / VPN / router filter had no way here to see what their setup lets
through. The new tool probes a static list of widely used ad, analytics,
social, error-monitoring and device-maker endpoints plus popular tracking
scripts (fetched with no-cors and no cookies or referrer, never run), and
places bait elements for cosmetic filters. Nothing runs until the visitor
starts it; every group starts selected.
A timeout reads as "no answer" and stays out of the score, and every
listed host was checked to resolve, serve valid HTTPS and not redirect to
http:// or in a loop, since any of those would fail like a block. Element
hiding is left out of the score (DNS-level filters can never pass it) and
instead tells browser-level from network-level blocking. The page copy
says outright that blocking everything is not automatically better.
Ships with the x shortcut, locale copy for every full locale and a
changelog entry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Notices across the tools are hand-rolled boxes; Alert gives new ones the
primitive instead. In this JavaScript setup the CLI imports `cn` from a
bare "cn" module and installs an unrelated npm package of that name, so
the dependency is dropped and the components import from @/lib/utils;
frontend/AGENTS.md records the trap for the next `shadcn-vue add`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A card only shows its map once location data has landed, so the bundled
placeholder maps were never meant to be seen, and their labels sat in one
language among many. An empty map URL now renders a same-sized muted box,
which also covers a payload with a country but no coordinates.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Any non-empty language reached Static Maps, so every distinct value was an
uncached upstream call whose answer the edge kept for a year. A tag-shape
check still passes region-tagged UI locales (zh-TW, pt-BR) without an
allow-list, and the handler block now covers both sides.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
runChain's optional originalSite / accept move into an options object, so a
chain that needs only one no longer passes an `undefined` placeholder.
The per-version validators become isValidIPv4 / isValidIPv6 in
common/valid-ip.js, next to isValidIP: the "Valid" prefix keeps them apart
from the non-validating isIPv6 discriminator.
Tests now run the four single-version entry points against a stubbed fetch,
so dropping or swapping a chain's validator fails the suite.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Proxy-Seller joins the paid sponsors in all six READMEs and the footer
acknowledgement list. Each sponsor's bold name in the description line now
links to the same destination as its logo, so the copy is clickable too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The README listed every sponsor as an equal-size logo at the bottom, with
the only ad copy hidden in a hover-only title attribute. Paid sponsors now
get their own group with a visible one-line description under each logo;
free-plan supporters share one row of smaller logos. V.PS links carry the
README's locale in utm_content.
SwiftProxy joins the paid group and the footer acknowledgement list.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Score details is one of the most-opened panels, but the score is not a
live reading. A line under it links to Blocklist Check with the IP
carried as `?q=`, which the tool now prefills without running: the
visitor may be signed out or out of quota.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Home and ToolPage are both kept alive with a Footer each, so once a tool
page had been visited, a footer link on Home opened both sheets and the
cached one, stuck on its own tab, covered the right one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The layout refactor moved the two re-export bridges into utils/ip/ and
utils/features/; their header and test comments still named the old
paths.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A new In-depth Checks tool shows which blocklists an IP is on: the
real-time DNSBLs and the threat intelligence lists the private API's
/iphitlist checks. Listings are split by kind, so a VPN or Tor exit
reads as an anonymity mark rather than a bad record; hits stay open with
their reasons and removal links, the remaining lists fold away, and a
legend explains every mark.
/api/ipblocklist proxies the upstream with the caller's headers and a
30s timeout (a cold DNSBL pass outlasts the default 8s), passing sign-in
and quota rejections through. Signed-in users only, original site only,
150 checks a month (ip_hitlist in the usage dialog and the pre-flight
quota gate). Shift+B opens it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fifteen cards in one flat grid mixed network diagnostics, lookups and the
in-depth checks that only run on IPCheck.ing. Each registry entry now
carries a category, and Advanced.vue renders the cards under three
headed groups: Network Diagnostics, Lookup & Calculation and In-depth
Checks. A group whose tools all fail their gates drops out with its
heading, so a self-hosted deployment simply shows no In-depth Checks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
common/ had become the backend's library: 30 of its 41 modules were
imported by nothing in the frontend, so the name no longer said what
belonged there. It now holds only what the frontend or the build imports
too; backend-only code moved to server/, flat except for datasets/ (the
dataset engine and its readers). The *-db/ dataset directories stay in
common/: self-hosting docs and Docker volumes mount those paths.
frontend/utils/ keeps its app-wide modules at the root and gains ip/
(IP / ASN shaping and validation), report/ (the report pipeline) and
features/ (the pure layer of a single feature). Paths only — no logic
changes. The Dockerfile copies server/, and the AGENTS.md files say where
a new module goes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Returning early on empty input left the previous result and error on
screen. It now clears them first, and the button stays disabled for a
whitespace-only input.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Skipping a row while this process published it also dropped a change
someone else made in that window: no file moved afterwards, so no later
check came. The watcher now looks again once the publish has recorded its
files, and reloads if they are no longer its own.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The watcher told its own publish apart by the fingerprint reloadRow
records, but that lands only after the reload finishes. A settle tick in
between — the state write and a slow reload outlast the settle window, as
on a loaded CI runner — saw new files under an old fingerprint and reloaded
them a second time as a "file change". Rows being published by this
process are now skipped by the watcher until that reload has recorded them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The shared parser rejects an empty string, so Enter in the empty field
showed "Invalid physical address". onSubmit now returns early on empty
input, as IP Calculator does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
isRand marked every locally administered unicast address, CID ranges
included. A CID is assigned local space (IEEE 802c ELI), so an address a
registered CID covers is no longer reported as random. For the rest the
U/L bit stays the signal: it is the only mark randomization leaves on an
address, and what the previous provider reported.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The success test read the checkout's downloaded registries and fell back
to asserting a 503 without them, so CI never exercised a 200. The handler
now comes from createMacChecker(lookup); the tests pass a fixture lookup
for the 200 and a null one for the 503.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three review findings in the IEEE registry reader:
- A country code after the postal code now wins over a state code before
it ("Dover DE 19901 US" → US), except after NL / IT, whose postcodes are
followed by letters (Dutch letter pairs, Italian provinces). Over the
live registries this corrects two addresses and changes no other.
- A registry file that stops mid-row, or inside a quoted field, is refused
as truncated; IEEE ends every row, the last included, with CRLF.
- A multicast query shows its block's multicast range (01:00:5E:…), which
holds the address asked about, instead of the unicast registration.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The form validates with the shared common/mac-input.js parser, so vendor
prefixes go through. A collapsed "See examples" fold under the input, as in
IP Calculator, holds one pill per kind of answer: full address, vendor
prefix, MA-M block, private registration, multicast and randomized. Browsers
expose no MAC address, so there is no "my address" pill. Changelog entry in
every full locale.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MAC Lookup no longer calls maclookup.app. The five public IEEE registries
(MA-L, MA-M, MA-S, IAB, CID) are a new `oui` dataset row — no key needed,
published as downloaded, refused below per-registry row floors — and
common/oui-db.js answers the longest assignment covering a query, the group
bit masked so a multicast address resolves to the block it derives from.
The country is read off the end of the registered address.
?mac= now takes a vendor prefix as well as a full address (6–12 hex digits,
colon / hyphen / dotted / bare), parsed by common/mac-input.js, which the
form shares. An address no block covers is still answered, with its flags.
The route is gated by requireOfflineData while the registries download at
boot. MAC_LOOKUP_API_KEY is gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A scheduled or catch-up run that found every dataset current logged
nothing — only a publish wrote a line — so a run that fired looked like one
that never did. updateDatasets now logs the run's start and, per row, that
it was up to date or skipped for another process's lock.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CauPFca6bYL9qpuYEmsU9H
updateDatasets applies one timeout to every row, so the 50 ms that stops
the stalled row also bounded the healthy one — a lock, a temp dir and two
fsync'd state writes — and a loaded CI runner timed it out too. 1 s keeps
the stall test meaningful with room for the healthy row.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CauPFca6bYL9qpuYEmsU9H
- A run that died or lost its lock between renames left every file present
(possibly a mixed MaxMind City / ASN pair), and the boot download skipped
the row because the files existed. The in-flux state now carries
`publishing: true`, cleared by the final record, and boot re-fetches a row
whose last publish never finished.
- The boot skip asked whether a snapshot file existed, so an empty or
corrupt one — which the reader refuses — kept the row unrepaired. Rows now
supply isLoaded(), the reader's own readiness (all four), and boot
downloads whenever the reader holds nothing usable; files under the
published names are re-fetched even when the state records the remote's
version (updateDataset's new `force`).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CauPFca6bYL9qpuYEmsU9H
publish took the combined signal (caller timeout + lock loss) and treated
any abort as a lost lock, so a scheduled or boot timeout firing between
renames skipped the rollback and cleanup while this process still held the
lock, leaving a mixed set and .next / .bak files. The combined signal still
cancels, but only the lock's own signal now stops rollback and cleanup.
Once every rename is done, only a lost lock keeps the version from being
recorded, so a timeout landing after a complete publish no longer forces a
re-download.
The lock-loss test now trips the engine's real onCompromised handler; a
new test covers the timeout path.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CauPFca6bYL9qpuYEmsU9H
- The watcher started only after every boot step, so a dataset published
by another process (or placed by hand) while another step was running
became the watcher's baseline and was never loaded. It now starts before
the boot downloads.
- The CAIDA readers serve the newest *.txt under any name, but the boot
download checked only the published names, so an upgrade with a
hand-named snapshot re-downloaded it. A row can now supply hasSnapshot();
as2org / as-rel use their reader's own rule.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rows update one after another, each with up to 30 minutes, so four datasets
can legitimately take two hours while the monitor failed a run after one.
maxRuntime is now every scheduled row's timeout plus a 10-minute margin.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rolling back after a lost lock could overwrite what the lock's new holder
had just published. Once the lock is lost, publish now touches nothing more
(no rollback, no cleanup). To keep a half-done set from passing for
current, the state names no version from the start of publishing until the
final write, so whoever runs next — after a lost lock, a crash or a kill —
re-fetches and publishes a whole set. A rename failure with the lock still
held is rolled back as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With connectivity now an error when no as-rel snapshot is loaded, the
network-down handler test has no working source on a data-less checkout
(CI), so the handler rightly answers 502. The test asserts that there and
keeps its 200 expectations where a snapshot exists.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The lock-loss signal was checked only before publishing began, so a lock
compromised mid-copy or mid-rename let publication continue. It is now
checked before every copy and rename, an abort taking the same rollback,
and again before the state is written.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- PeeringDB is 'disabled' only without a Cloudflare key; configured but not
loaded (still downloading, or the download failed) is an 'error', so the
ASN Profile isn't cached for a week without it.
- Connectivity without an as-rel snapshot is an 'error' too, not an empty
graph; a missing as2org still only blocks while it downloads, as RIPEstat
names the nodes.
- A CAIDA as2org / as-rel file that parses to nothing (corrupt) no longer
counts as loaded.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A compromised lock (failed heartbeat; another process may take it over)
aborts the update holding it, download included, before it publishes,
instead of only being logged.
- A failed publish removes files it had just added when the dataset had none
before, as well as restoring replaced ones from backup: never half a set.
- The watcher reloads hand-placed or externally published files only once
they have held still for the settle interval and passed the row's
validator, so a file still being copied never replaces healthy data.
- Back to write-file-atomic 8, with engines aligned to its range
(^24.15.0 || >=26.0.0).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Scheduled and catch-up runs give each dataset 30 minutes, download
included, so a stalled upstream can't hold up the rest or, under croner's
overrun protection, every later run.
- Downloads go through fetchUpstream (project User-Agent, a timeout on the
wait for a response); the body stays bounded by the caller's signal.
- Catch-up asks whether a scheduled run fell between the last check and now,
instead of a 25-hour age that missed a check skipped just after one.
- A remote that names no version (no Last-Modified) is always fetched, not
taken for unchanged; maxmindIdentifier is null when an edition omits it.
- The watcher records a dataset's files as loaded only after the reload
succeeds, so a failed load is retried on their next change.
- write-file-atomic 7, which supports the project's Node >=24 (8 needs 24.15).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>