Showing preview only (892K chars total). Download the full file or copy to clipboard to get everything.
Repository: Lissy93/web-check
Branch: master
Commit: aac6121e81dd
Files: 214
Total size: 840.1 KB
Directory structure:
gitextract_epf_u9tl/
├── .github/
│ ├── FUNDING.yml
│ ├── README.md
│ ├── screenshots/
│ │ └── README.md
│ └── workflows/
│ ├── credits.yml
│ ├── deploy-aws.yml
│ ├── docker.yml
│ └── mirror.yml
├── .gitignore
├── Dockerfile
├── LICENSE
├── api/
│ ├── _common/
│ │ ├── aws-webpack.config.js
│ │ └── middleware.js
│ ├── archives.js
│ ├── block-lists.js
│ ├── carbon.js
│ ├── cookies.js
│ ├── dns-server.js
│ ├── dns.js
│ ├── dnssec.js
│ ├── features.js
│ ├── firewall.js
│ ├── get-ip.js
│ ├── headers.js
│ ├── hsts.js
│ ├── http-security.js
│ ├── legacy-rank.js
│ ├── linked-pages.js
│ ├── mail-config.js
│ ├── ports.js
│ ├── quality.js
│ ├── rank.js
│ ├── redirects.js
│ ├── robots-txt.js
│ ├── screenshot.js
│ ├── security-txt.js
│ ├── sitemap.js
│ ├── social-tags.js
│ ├── ssl.js
│ ├── status.js
│ ├── tech-stack.js
│ ├── threats.js
│ ├── tls.js
│ ├── trace-route.js
│ ├── txt-records.js
│ └── whois.js
├── astro.config.mjs
├── docker-compose.yml
├── fly.toml
├── netlify.toml
├── package.json
├── public/
│ ├── error.html
│ ├── fonts/
│ │ └── Hubot-Sans/
│ │ ├── LICENSE
│ │ └── OTF/
│ │ ├── HubotSans-Black.otf
│ │ ├── HubotSans-BlackItalic.otf
│ │ ├── HubotSans-Bold.otf
│ │ ├── HubotSans-BoldItalic.otf
│ │ ├── HubotSans-ExtraBold.otf
│ │ ├── HubotSans-ExtraBoldItalic.otf
│ │ ├── HubotSans-ExtraLight.otf
│ │ ├── HubotSans-ExtraLightItalic.otf
│ │ ├── HubotSans-Italic.otf
│ │ ├── HubotSans-Light.otf
│ │ ├── HubotSans-LightItalic.otf
│ │ ├── HubotSans-Medium.otf
│ │ ├── HubotSans-MediumItalic.otf
│ │ ├── HubotSans-Regular.otf
│ │ ├── HubotSans-SemiBold.otf
│ │ ├── HubotSans-SemiBoldItalic.otf
│ │ ├── HubotSansCondensed-Black.otf
│ │ ├── HubotSansCondensed-BlackItalic.otf
│ │ ├── HubotSansCondensed-Bold.otf
│ │ ├── HubotSansCondensed-BoldItalic.otf
│ │ ├── HubotSansCondensed-ExtraBold.otf
│ │ ├── HubotSansCondensed-ExtraBoldItalic.otf
│ │ ├── HubotSansCondensed-ExtraLight.otf
│ │ ├── HubotSansCondensed-ExtraLightItalic.otf
│ │ ├── HubotSansCondensed-Italic.otf
│ │ ├── HubotSansCondensed-Light.otf
│ │ ├── HubotSansCondensed-LightItalic.otf
│ │ ├── HubotSansCondensed-Medium.otf
│ │ ├── HubotSansCondensed-MediumItalic.otf
│ │ ├── HubotSansCondensed-Regular.otf
│ │ ├── HubotSansCondensed-SemiBold.otf
│ │ ├── HubotSansCondensed-SemiBoldItalic.otf
│ │ ├── HubotSansExpanded-Black.otf
│ │ ├── HubotSansExpanded-BlackItalic.otf
│ │ ├── HubotSansExpanded-Bold.otf
│ │ ├── HubotSansExpanded-BoldItalic.otf
│ │ ├── HubotSansExpanded-ExtraBold.otf
│ │ ├── HubotSansExpanded-ExtraBoldItalic.otf
│ │ ├── HubotSansExpanded-ExtraLight.otf
│ │ ├── HubotSansExpanded-ExtraLightItalic.otf
│ │ ├── HubotSansExpanded-Italic.otf
│ │ ├── HubotSansExpanded-Light.otf
│ │ ├── HubotSansExpanded-LightItalic.otf
│ │ ├── HubotSansExpanded-Medium.otf
│ │ ├── HubotSansExpanded-MediumItalic.otf
│ │ ├── HubotSansExpanded-Regular.otf
│ │ ├── HubotSansExpanded-SemiBold.otf
│ │ └── HubotSansExpanded-SemiBoldItalic.otf
│ ├── index.html
│ ├── manifest.json
│ ├── placeholder.html
│ ├── resources/
│ │ └── openapi-spec.yml
│ ├── robots.txt
│ └── security.txt
├── server.js
├── src/
│ ├── components/
│ │ ├── homepage/
│ │ │ ├── AboutSection.astro
│ │ │ ├── AnimatedButton.astro
│ │ │ ├── AnimatedInput.astro
│ │ │ ├── ButtonGroup.astro
│ │ │ ├── Features.astro
│ │ │ ├── HeroForm.astro
│ │ │ ├── HomeBackground.tsx
│ │ │ ├── Screenshots.astro
│ │ │ ├── SponsorSegment.astro
│ │ │ └── TempDisabled.astro
│ │ ├── molecules/
│ │ │ └── Icon.svelte
│ │ └── scafold/
│ │ ├── Footer.astro
│ │ └── Nav.astro
│ ├── env.d.ts
│ ├── layouts/
│ │ ├── Base.astro
│ │ └── MetaTags.astro
│ ├── pages/
│ │ ├── account/
│ │ │ └── index.astro
│ │ ├── check/
│ │ │ └── [...target].astro
│ │ ├── index.astro
│ │ ├── self-hosted-setup.astro
│ │ └── web-check-api/
│ │ ├── index.astro
│ │ └── spec.astro
│ ├── styles/
│ │ ├── colors.scss
│ │ ├── global.scss
│ │ ├── media-queries.scss
│ │ └── typography.scss
│ └── web-check-live/
│ ├── App.tsx
│ ├── assets/
│ │ └── data/
│ │ └── map-features.json
│ ├── components/
│ │ ├── Form/
│ │ │ ├── Button.tsx
│ │ │ ├── Card.tsx
│ │ │ ├── Heading.tsx
│ │ │ ├── Input.tsx
│ │ │ ├── Modal.tsx
│ │ │ ├── Nav.tsx
│ │ │ └── Row.tsx
│ │ ├── Results/
│ │ │ ├── Archives.tsx
│ │ │ ├── BlockLists.tsx
│ │ │ ├── BuiltWith.tsx
│ │ │ ├── CarbonFootprint.tsx
│ │ │ ├── ContentLinks.tsx
│ │ │ ├── Cookies.tsx
│ │ │ ├── DnsRecords.tsx
│ │ │ ├── DnsSec.tsx
│ │ │ ├── DnsServer.tsx
│ │ │ ├── DomainLookup.tsx
│ │ │ ├── Firewall.tsx
│ │ │ ├── Headers.tsx
│ │ │ ├── HostNames.tsx
│ │ │ ├── Hsts.tsx
│ │ │ ├── HttpSecurity.tsx
│ │ │ ├── Lighthouse.tsx
│ │ │ ├── MailConfig.tsx
│ │ │ ├── OpenPorts.tsx
│ │ │ ├── Rank.tsx
│ │ │ ├── Redirects.tsx
│ │ │ ├── RobotsTxt.tsx
│ │ │ ├── Screenshot.tsx
│ │ │ ├── SecurityTxt.tsx
│ │ │ ├── ServerInfo.tsx
│ │ │ ├── ServerLocation.tsx
│ │ │ ├── ServerStatus.tsx
│ │ │ ├── SiteFeatures.tsx
│ │ │ ├── Sitemap.tsx
│ │ │ ├── SocialTags.tsx
│ │ │ ├── SslCert.tsx
│ │ │ ├── TechStack.tsx
│ │ │ ├── Threats.tsx
│ │ │ ├── TlsCipherSuites.tsx
│ │ │ ├── TlsClientSupport.tsx
│ │ │ ├── TlsIssueAnalysis.tsx
│ │ │ ├── TraceRoute.tsx
│ │ │ ├── TxtRecords.tsx
│ │ │ └── WhoIs.tsx
│ │ ├── boundaries/
│ │ │ └── PageError.tsx
│ │ └── misc/
│ │ ├── ActionButtons.tsx
│ │ ├── AdditionalResources.tsx
│ │ ├── DocContent.tsx
│ │ ├── ErrorBoundary.tsx
│ │ ├── FancyBackground.tsx
│ │ ├── Flag.tsx
│ │ ├── Footer.tsx
│ │ ├── Loader.tsx
│ │ ├── LocationMap.tsx
│ │ ├── ProgressBar.tsx
│ │ ├── SelfScanMsg.tsx
│ │ └── ViewRaw.tsx
│ ├── hooks/
│ │ └── motherOfAllHooks.ts
│ ├── main.tsx
│ ├── styles/
│ │ ├── colors.ts
│ │ ├── dimensions.ts
│ │ ├── globals.tsx
│ │ ├── index.css
│ │ └── typography.ts
│ ├── typings/
│ │ ├── file-types.d.ts
│ │ └── react-simple-maps.d.ts
│ ├── utils/
│ │ ├── address-type-checker.ts
│ │ ├── docs.ts
│ │ ├── get-keys.ts
│ │ └── result-processor.ts
│ └── views/
│ ├── About.tsx
│ ├── Home.tsx
│ ├── NotFound.tsx
│ └── Results.tsx
├── svelte.config.js
├── tsconfig.json
├── vercel.json
└── vite.config.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
github: [lissy93]
================================================
FILE: .github/README.md
================================================
<h1 align="center">Web-Check</h1>
<p align="center">
<img src="https://i.ibb.co/q1gZN2p/web-check-logo.png" width="96" /><br />
<b><i>Comprehensive, on-demand open source intelligence for any website</i></b>
<br />
<b>🌐 <a href="https://web-check.xyz/">web-check.xyz</a></b><br />
</p>
---
<p align="center">
<sup>Kindly supported by:</sup><br>
<a href="https://terminaltrove.com/?utm_campaign=github&utm_medium=referral&utm_content=web-check&utm_source=wcgh">
<img src="https://i.ibb.co/8jrrcZ0/IMG-7210.jpg" width="300" alt="Terminal Trove">
<br>
<strong>The $HOME of all things in the terminal.</strong>
</a>
<br>
<a href="https://terminaltrove.com/newsletter?utm_campaign=github&utm_medium=referral&utm_content=web-check&utm_source=wcgh">
<sub>Find your next CLI / TUI tool and more at Terminal Trove,</sub>
<br>
<sup>Get updates on new tools on our newsletter.</sup>
</a>
</p>
<p align="center">
<sup>Kindly supported by:</sup><br>
<a href="https://go.warp.dev/web-check"><b>Warp</b>, built for coding with multiple AI agents</a><br><br>
<a href="https://go.warp.dev/web-check"><img width="640" src="https://github.com/warpdotdev/brand-assets/blob/main/Github/Sponsor/Warp-Github-LG-02.png?raw=true" /></a>
</p>
---
#### Contents
- **[About](#about)**
- [Screenshot](#screenshot)
- [Live Demo](#live-demo)
- [Mirror](#mirror)
- [Features](#features)
- **[Usage](#usage)**
- [Deployment](#deployment)
- [Option#1: Netlify](#deploying---option-1-netlify)
- [Option#2: Vercel](#deploying---option-2-vercel)
- [Option#3: Docker](#deploying---option-3-docker)
- [Option#4: Source](#deploying---option-4-from-source)
- [Configuration Options](#configuring)
- [Developer Setup](#developing)
- **[Community](#community)**
- [Contributing](#contributing)
- [Bugs](#reporting-bugs)
- [Support](#supporting)
- **[License](#license)**
---
## About
Get an insight into the inner-workings of a given website: uncover potential attack vectors, analyse server architecture, view security configurations, and learn what technologies a site is using.
Currently the dashboard will show: IP info, SSL chain, DNS records, cookies, headers, domain info, search crawl rules, page map, server location, redirect ledger, open ports, traceroute, DNS security extensions, site performance, trackers, associated hostnames, carbon footprint. Stay tuned, as I'll add more soon!
The aim is to help you easily understand, optimize and secure your website.
### Screenshot
<details>
<summary>Expand Screenshot</summary>
[](https://web-check.as93.net/)
</details>
[](https://github.com/Lissy93/web-check/tree/master/.github/screenshots)
### Live Demo
A hosted version can be accessed at: **[web-check.as93.net](https://web-check.as93.net)**
### Mirror
The source for this repo is mirrored to CodeBerg, available at: **[codeberg.org/alicia/web-check](https://codeberg.org/alicia/web-check)**
### Status
Build & Deploys: [](https://app.netlify.com/sites/web-check/deploys)
[](https://vercel.com/as93/web-check/)
[](https://github.com/Lissy93/web-check/actions/workflows/docker.yml)
[](https://github.com/Lissy93/web-check/actions/workflows/deploy-aws.yml)
<br />
Repo Management & Miscellaneous: [](https://github.com/Lissy93/web-check/actions/workflows/mirror.yml)
[](https://github.com/Lissy93/web-check/actions/workflows/credits.yml)
### Features
<details open>
<summary><b>Click to expand / collapse section</b></summary>
<sup>**Note** _this list needs updating, many more jobs have been added since..._</sup>
The following section outlines the core features, and briefly explains why this data might be useful for you to know, as well as linking to further resources for learning more.
<details>
<summary><b>IP Info</b></summary>
###### Description
An IP address (Internet Protocol address) is a numerical label assigned to each device connected to a network / the internet. The IP associated with a given domain can be found by querying the Domain Name System (DNS) for the domain's A (address) record.
###### Use Cases
Finding the IP of a given server is the first step to conducting further investigations, as it allows us to probe the server for additional info. Including creating a detailed map of a target's network infrastructure, pinpointing the physical location of a server, identifying the hosting service, and even discovering other domains that are hosted on the same IP address.
###### Useful Links
- [Understanding IP Addresses](https://www.digitalocean.com/community/tutorials/understanding-ip-addresses-subnets-and-cidr-notation-for-networking)
- [IP Addresses - Wiki](https://en.wikipedia.org/wiki/IP_address)
- [RFC-791 Internet Protocol](https://tools.ietf.org/html/rfc791)
- [whatismyipaddress.com](https://whatismyipaddress.com/)
</details>
<details>
<summary><b>SSL Chain</b></summary>
<img width="300" src="https://i.ibb.co/kB7LsV1/wc-ssl.png" align="right" />
###### Description
SSL certificates are digital certificates that authenticate the identity of a website or server, enable secure encrypted communication (HTTPS), and establish trust between clients and servers. A valid SSL certificate is required for a website to be able to use the HTTPS protocol, and encrypt user + site data in transit. SSL certificates are issued by Certificate Authorities (CAs), which are trusted third parties that verify the identity and legitimacy of the certificate holder.
###### Use Cases
SSL certificates not only provide the assurance that data transmission to and from the website is secure, but they also provide valuable OSINT data. Information from an SSL certificate can include the issuing authority, the domain name, its validity period, and sometimes even organization details. This can be useful for verifying the authenticity of a website, understanding its security setup, or even for discovering associated subdomains or other services.
###### Useful Links
- [TLS - Wiki](https://en.wikipedia.org/wiki/Transport_Layer_Security)
- [What is SSL (via Cloudflare learning)](https://www.cloudflare.com/learning/ssl/what-is-ssl/)
- [RFC-8446 - TLS](https://tools.ietf.org/html/rfc8446)
- [SSL Checker](https://www.sslshopper.com/ssl-checker.html)
</details>
<details>
<summary><b>DNS Records</b></summary>
<img width="300" src="https://i.ibb.co/7Q1kMwM/wc-dns.png" align="right" />
###### Description
This task involves looking up the DNS records associated with a specific domain. DNS is a system that translates human-readable domain names into IP addresses that computers use to communicate. Various types of DNS records exist, including A (address), MX (mail exchange), NS (name server), CNAME (canonical name), and TXT (text), among others.
###### Use Cases
Extracting DNS records can provide a wealth of information in an OSINT investigation. For example, A and AAAA records can disclose IP addresses associated with a domain, potentially revealing the location of servers. MX records can give clues about a domain's email provider. TXT records are often used for various administrative purposes and can sometimes inadvertently leak internal information. Understanding a domain's DNS setup can also be useful in understanding how its online infrastructure is built and managed.
###### Useful Links
- [What are DNS records? (via Cloudflare learning)](https://www.cloudflare.com/learning/dns/dns-records/)
- [DNS Record Types](https://en.wikipedia.org/wiki/List_of_DNS_record_types)
- [RFC-1035 - DNS](https://tools.ietf.org/html/rfc1035)
- [DNS Lookup (via MxToolbox)](https://mxtoolbox.com/DNSLookup.aspx)
</details>
<details>
<summary><b>Cookies</b></summary>
<img width="300" src="https://i.ibb.co/TTQ6DtP/wc-cookies.png" align="right" />
###### Description
The Cookies task involves examining the HTTP cookies set by the target website. Cookies are small pieces of data stored on the user's computer by the web browser while browsing a website. They hold a modest amount of data specific to a particular client and website, such as site preferences, the state of the user's session, or tracking information.
###### Use Cases
Cookies can disclose information about how the website tracks and interacts with its users. For instance, session cookies can reveal how user sessions are managed, and tracking cookies can hint at what kind of tracking or analytics frameworks are being used. Additionally, examining cookie policies and practices can offer insights into the site's security settings and compliance with privacy regulations.
###### Useful Links
- [HTTP Cookie Docs (Mozilla)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies)
- [What are Cookies (via Cloudflare Learning)](https://www.cloudflare.com/learning/privacy/what-are-cookies/)
- [Testing for Cookie Attributes (OWASP)](https://owasp.org/www-project-web-security-testing-guide/v42/4-Web_Application_Security_Testing/06-Session_Management_Testing/02-Testing_for_Cookies_Attributes)
- [RFC-6265 - Coolies](https://tools.ietf.org/html/rfc6265)
</details>
<details>
<summary><b>Crawl Rules</b></summary>
<img width="300" src="https://i.ibb.co/KwQCjPf/wc-robots.png" align="right" />
###### Description
Robots.txt is a file found (usually) at the root of a domain, and is used to implement the Robots Exclusion Protocol (REP) to indicate which pages should be ignored by which crawlers and bots. It's good practice to avoid search engine crawlers from over-loading your site, but should not be used to keep pages out of search results (use the noindex meta tag or header instead).
###### Use Cases
It's often useful to check the robots.txt file during an investigation, as it can sometimes disclose the directories and pages that the site owner doesn't want to be indexed, potentially because they contain sensitive information, or reveal the existence of otherwise hidden or unlinked directories. Additionally, understanding crawl rules may offer insights into a website's SEO strategies.
###### Useful Links
- [Google Search Docs - Robots.txt](https://developers.google.com/search/docs/advanced/robots/intro)
- [Learn about robots.txt (via Moz.com)](https://moz.com/learn/seo/robotstxt)
- [RFC-9309 - Robots Exclusion Protocol](https://datatracker.ietf.org/doc/rfc9309/)
- [Robots.txt - wiki](https://en.wikipedia.org/wiki/Robots_exclusion_standard)
</details>
<details>
<summary><b>Headers</b></summary>
<img width="300" src="https://i.ibb.co/t3xcwP1/wc-headers.png" align="right" />
###### Description
The Headers task involves extracting and interpreting the HTTP headers sent by the target website during the request-response cycle. HTTP headers are key-value pairs sent at the start of an HTTP response, or before the actual data. Headers contain important directives for how to handle the data being transferred, including cache policies, content types, encoding, server information, security policies, and more.
###### Use Cases
Analyzing HTTP headers can provide significant insights in an OSINT investigation. Headers can reveal specific server configurations, chosen technologies, caching directives, and various security settings. This information can help to determine a website's underlying technology stack, server-side security measures, potential vulnerabilities, and general operational practices.
###### Useful Links
- [HTTP Headers - Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers)
- [RFC-7231 Section 7 - Headers](https://datatracker.ietf.org/doc/html/rfc7231#section-7)
- [List of header response fields](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields)
- [OWASP Secure Headers Project](https://owasp.org/www-project-secure-headers/)
</details>
<details>
<summary><b>Quality Metrics</b></summary>
<img width="300" src="https://i.ibb.co/Kqg8rx7/wc-quality.png" align="right" />
###### Description
Using Lighthouse, the Quality Metrics task measures the performance, accessibility, best practices, and SEO of the target website. This returns a simple checklist of 100 core metrics, along with a score for each category, to gauge the overall quality of a given site.
###### Use Cases
Useful for assessing a site's technical health, SEO issues, identify vulnerabilities, and ensure compliance with standards.
###### Useful Links
- [Lighthouse Docs](https://developer.chrome.com/docs/lighthouse/)
- [Google Page Speed Tools](https://developers.google.com/speed)
- [W3 Accessibility Tools](https://www.w3.org/WAI/test-evaluate/)
- [Google Search Console](https://search.google.com/search-console)
- [SEO Checker](https://www.seobility.net/en/seocheck/)
- [PWA Builder](https://www.pwabuilder.com/)
</details>
<details>
<summary><b>Server Location</b></summary>
<img width="300" src="https://i.ibb.co/cXH2hfR/wc-location.png" align="right" />
###### Description
The Server Location task determines the physical location of the server hosting a given website based on its IP address. This is done by looking up the IP in a location database, which maps the IP to a lat + long of known data centers and ISPs. From the latitude and longitude, it's then possible to show additional contextual info, like a pin on the map, along with address, flag, time zone, currency, etc.
###### Use Cases
Knowing the server location is a good first step in better understanding a website. For site owners this aids in optimizing content delivery, ensuring compliance with data residency requirements, and identifying potential latency issues that may impact user experience in specific geographical regions. And for security researcher, assess the risk posed by specific regions or jurisdictions regarding cyber threats and regulations.
###### Useful Links
- [IP Locator](https://geobytes.com/iplocator/)
- [Internet Geolocation - Wiki](https://en.wikipedia.org/wiki/Internet_geolocation)
</details>
<details>
<summary><b>Associated Hosts</b></summary>
<img width="300" src="https://i.ibb.co/25j1sT7/wc-hosts.png" align="right" />
###### Description
This task involves identifying and listing all domains and subdomains (hostnames) that are associated with the website's primary domain. This process often involves DNS enumeration to discover any linked domains and hostnames, as well as looking at known DNS records.
###### Use Cases
During an investigation, understanding the full scope of a target's web presence is critical. Associated domains could lead to uncovering related projects, backup sites, development/test sites, or services linked to the main site. These can sometimes provide additional information or potential security vulnerabilities. A comprehensive list of associated domains and hostnames can also give an overview of the organization's structure and online footprint.
###### Useful Links
- [DNS Enumeration - Wiki](https://en.wikipedia.org/wiki/DNS_enumeration)
- [OWASP - Enumerate Applications on Webserver](https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/01-Information_Gathering/04-Enumerate_Applications_on_Webserver)
- [DNS Enumeration - DNS Dumpster](https://dnsdumpster.com/)
- [Subdomain Finder](https://subdomainfinder.c99.nl/)
</details>
<details>
<summary><b>Redirect Chain</b></summary>
<img width="300" src="https://i.ibb.co/hVVrmwh/wc-redirects.png" align="right" />
###### Description
This task traces the sequence of HTTP redirects that occur from the original URL to the final destination URL. An HTTP redirect is a response with a status code that advises the client to go to another URL. Redirects can occur for several reasons, such as URL normalization (directing to the www version of the site), enforcing HTTPS, URL shorteners, or forwarding users to a new site location.
###### Use Cases
Understanding the redirect chain can be useful for several reasons. From a security perspective, long or complicated redirect chains can be a sign of potential security risks, such as unencrypted redirects in the chain. Additionally, redirects can impact website performance and SEO, as each redirect introduces additional round-trip-time (RTT). For OSINT, understanding the redirect chain can help identify relationships between different domains or reveal the use of certain technologies or hosting providers.
###### Useful Links
- [HTTP Redirects - MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections)
- [URL Redirection - Wiki](https://en.wikipedia.org/wiki/URL_redirection)
- [301 Redirects explained](https://ahrefs.com/blog/301-redirects/)
</details>
<details>
<summary><b>TXT Records</b></summary>
<img width="300" src="https://i.ibb.co/wyt21QN/wc-txt-records.png" align="right" />
###### Description
TXT records are a type of DNS record that provides text information to sources outside your domain. They can be used for a variety of purposes, such as verifying domain ownership, ensuring email security, and even preventing unauthorized changes to your website.
###### Use Cases
The TXT records often reveal which external services and technologies are being used with a given domain. They may reveal details about the domain's email configuration, the use of specific services like Google Workspace or Microsoft 365, or security measures in place such as SPF and DKIM. Understanding these details can give an insight into the technologies used by the organization, their email security practices, and potential vulnerabilities.
###### Useful Links
- [TXT Records (via Cloudflare Learning)](https://www.cloudflare.com/learning/dns/dns-records/dns-txt-record/)
- [TXT Records - Wiki](https://en.wikipedia.org/wiki/TXT_record)
- [RFC-1464 - TXT Records](https://datatracker.ietf.org/doc/html/rfc1464)
- [TXT Record Lookup (via MxToolbox)](https://mxtoolbox.com/TXTLookup.aspx)
</details>
<details>
<summary><b>Server Status</b></summary>
<img width="300" src="https://i.ibb.co/V9CNLBK/wc-status.png" align="right" />
###### Description
Checks if a server is online and responding to requests.
###### Use Cases
###### Useful Links
</details>
<details>
<summary><b>Open Ports</b></summary>
<img width="300" src="https://i.ibb.co/F8D1hmf/wc-ports.png" align="right" />
###### Description
Open ports on a server are endpoints of communication which are available for establishing connections with clients. Each port corresponds to a specific service or protocol, such as HTTP (port 80), HTTPS (port 443), FTP (port 21), etc. The open ports on a server can be determined using techniques such as port scanning.
###### Use Cases
Knowing which ports are open on a server can provide information about the services running on that server, useful for understanding the potential vulnerabilities of the system, or for understanding the nature of the services the server is providing.
###### Useful Links
- [List of TCP & UDP Port Numbers](https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers)
- [NMAP - Port Scanning Basics](https://nmap.org/book/man-port-scanning-basics.html)
</details>
<details>
<summary><b>Traceroute</b></summary>
<img width="300" src="https://i.ibb.co/M59qgxP/wc-trace-route.png" align="right" />
###### Description
Traceroute is a network diagnostic tool used to track in real-time the pathway taken by a packet of information from one system to another. It records each hop along the route, providing details about the IPs of routers and the delay at each point.
###### Use Cases
In OSINT investigations, traceroute can provide insights about the routing paths and geography of the network infrastructure supporting a website or service. This can help to identify network bottlenecks, potential censorship or manipulation of network traffic, and give an overall sense of the network's structure and efficiency. Additionally, the IP addresses collected during the traceroute may provide additional points of inquiry for further OSINT investigation.
###### Useful Links
- [undefined](function link() { [native code] })
- [undefined](function link() { [native code] })
- [undefined](function link() { [native code] })
- [undefined](function link() { [native code] })
</details>
<details>
<summary><b>Carbon Footprint</b></summary>
<img width="300" src="https://i.ibb.co/5v6fSyw/Screenshot-from-2023-07-29-19-07-50.png" align="right" />
###### Description
This task calculates the estimated carbon footprint of a website. It's based on the amount of data being transferred and processed, and the energy usage of the servers that host and deliver the website. The larger the website and the more complex its features, the higher its carbon footprint is likely to be.
###### Use Cases
From an OSINT perspective, understanding a website's carbon footprint doesn't directly provide insights into its internal workings or the organization behind it. However, it can still be valuable data in broader analyses, especially in contexts where environmental impact is a consideration. For example, it can be useful for activists, researchers, or ethical hackers who are interested in the sustainability of digital infrastructure, and who want to hold organizations accountable for their environmental impact.
###### Useful Links
- [WebsiteCarbon - Carbon Calculator](https://www.websitecarbon.com/)
- [The Green Web Foundation](https://www.thegreenwebfoundation.org/)
- [The Eco Friendly Web Alliance](https://ecofriendlyweb.org/)
- [Reset.org](https://en.reset.org/)
- [Your website is killing the planet - via Wired](https://www.wired.co.uk/article/internet-carbon-footprint)
</details>
<details>
<summary><b>Server Info</b></summary>
<img width="300" src="https://i.ibb.co/Mk1jx32/wc-server.png" align="right" />
###### Description
This task retrieves various pieces of information about the server hosting the target website. This can include the server type (e.g., Apache, Nginx), the hosting provider, the Autonomous System Number (ASN), and more. The information is usually obtained through a combination of IP address lookups and analysis of HTTP response headers.
###### Use Cases
In an OSINT context, server information can provide valuable clues about the organization behind a website. For instance, the choice of hosting provider could suggest the geographical region in which the organization operates, while the server type could hint at the technologies used by the organization. The ASN could also be used to find other domains hosted by the same organization.
###### Useful Links
- [undefined](function link() { [native code] })
- [undefined](function link() { [native code] })
- [undefined](function link() { [native code] })
- [undefined](function link() { [native code] })
</details>
<details>
<summary><b>Whois Lookup</b></summary>
<img width="300" src="https://i.ibb.co/89WLp14/wc-domain.png" align="right" />
###### Description
This task retrieves Whois records for the target domain. Whois records are a rich source of information, including the name and contact information of the domain registrant, the domain's creation and expiration dates, the domain's nameservers, and more. The information is usually obtained through a query to a Whois database server.
###### Use Cases
In an OSINT context, Whois records can provide valuable clues about the entity behind a website. They can show when the domain was first registered and when it's set to expire, which could provide insights into the operational timeline of the entity. The contact information, though often redacted or anonymized, can sometimes lead to additional avenues of investigation. The nameservers could also be used to link together multiple domains owned by the same entity.
###### Useful Links
- [undefined](function link() { [native code] })
- [undefined](function link() { [native code] })
- [undefined](function link() { [native code] })
</details>
<details>
<summary><b>Domain Info</b></summary>
<img width="300" src="https://i.ibb.co/89WLp14/wc-domain.png" align="right" />
###### Description
This task retrieves Whois records for the target domain. Whois records are a rich source of information, including the name and contact information of the domain registrant, the domain's creation and expiration dates, the domain's nameservers, and more. The information is usually obtained through a query to a Whois database server.
###### Use Cases
In an OSINT context, Whois records can provide valuable clues about the entity behind a website. They can show when the domain was first registered and when it's set to expire, which could provide insights into the operational timeline of the entity. The contact information, though often redacted or anonymized, can sometimes lead to additional avenues of investigation. The nameservers could also be used to link together multiple domains owned by the same entity.
###### Useful Links
- [undefined](function link() { [native code] })
- [undefined](function link() { [native code] })
- [undefined](function link() { [native code] })
</details>
<details>
<summary><b>DNS Security Extensions</b></summary>
<img width="300" src="https://i.ibb.co/J54zVmQ/wc-dnssec.png" align="right" />
###### Description
Without DNSSEC, it's possible for MITM attackers to spoof records and lead users to phishing sites. This is because the DNS system includes no built-in methods to verify that the response to the request was not forged, or that any other part of the process wasn’t interrupted by an attacker. The DNS Security Extensions (DNSSEC) secures DNS lookups by signing your DNS records using public keys, so browsers can detect if the response has been tampered with. Another solution to this issue is DoH (DNS over HTTPS) and DoT (DNS over TLD).
###### Use Cases
DNSSEC information provides insight into an organization's level of cybersecurity maturity and potential vulnerabilities, particularly around DNS spoofing and cache poisoning. If no DNS secururity (DNSSEC, DoH, DoT, etc) is implemented, this may provide an entry point for an attacker.
###### Useful Links
- [undefined](function link() { [native code] })
- [undefined](function link() { [native code] })
- [undefined](function link() { [native code] })
- [undefined](function link() { [native code] })
- [undefined](function link() { [native code] })
- [undefined](function link() { [native code] })
</details>
<details>
<summary><b>Site Features</b></summary>
<img width="300" src="https://i.ibb.co/gP4P6kp/wc-features.png" align="right" />
###### Description
Checks which core features are present on a site. If a feature as marked as dead, that means it's not being actively used at load time
###### Use Cases
This is useful to understand what a site is capable of, and what technologies to look for
###### Useful Links
</details>
<details>
<summary><b>HTTP Strict Transport Security</b></summary>
<img width="300" src="https://i.ibb.co/k253fq4/Screenshot-from-2023-07-17-20-10-52.png" align="right" />
###### Description
HTTP Strict Transport Security (HSTS) is a web security policy mechanism that helps protect websites against protocol downgrade attacks and cookie hijacking. A website can be included in the HSTS preload list by conforming to a set of requirements and then submitting itself to the list.
###### Use Cases
There are several reasons why it's important for a site to be HSTS enabled:
1. User bookmarks or manually types http://example.com and is subject to a man-in-the-middle attacker
HSTS automatically redirects HTTP requests to HTTPS for the target domain
2. Web application that is intended to be purely HTTPS inadvertently contains HTTP links or serves content over HTTP
HSTS automatically redirects HTTP requests to HTTPS for the target domain
3. A man-in-the-middle attacker attempts to intercept traffic from a victim user using an invalid certificate and hopes the user will accept the bad certificate
HSTS does not allow a user to override the invalid certificate message
###### Useful Links
- [undefined](function link() { [native code] })
- [undefined](function link() { [native code] })
- [undefined](function link() { [native code] })
</details>
<details>
<summary><b>DNS Server</b></summary>
<img width="300" src="https://i.ibb.co/tKpL8F9/Screenshot-from-2023-08-12-15-43-12.png" align="right" />
###### Description
This check determines the DNS server(s) that the requested URL / IP resolves to. Also fires off a rudimentary check to see if the DNS server supports DoH, and weather it's vulnerable to DNS cache poisoning.
###### Use Cases
###### Useful Links
</details>
<details>
<summary><b>Tech Stack</b></summary>
<img width="300" src="https://i.ibb.co/bBQSQNz/Screenshot-from-2023-08-12-15-43-46.png" align="right" />
###### Description
Checks what technologies a site is built with. This is done by fetching and parsing the site, then comparing it against a bit list of RegEx maintained by Wappalyzer to identify the unique fingerprints that different technologies leave.
###### Use Cases
Identifying a website's tech stack aids in evaluating its security by exposing potential vulnerabilities, informs competitive analyses and development decisions, and can guide tailored marketing strategies. Ethical application of this knowledge is crucial to avoid harmful activities like data theft or unauthorized intrusion.
###### Useful Links
- [Wappalyzer fingerprints](https://github.com/wappalyzer/wappalyzer/tree/master/src/technologies)
- [BuiltWith - Check what tech a site is using](https://builtwith.com/)
</details>
<details>
<summary><b>Listed Pages</b></summary>
<img width="300" src="https://i.ibb.co/GtrCQYq/Screenshot-from-2023-07-21-12-28-38.png" align="right" />
###### Description
This job finds and parses a site's listed sitemap. This file lists public sub-pages on the site, which the author wishes to be crawled by search engines. Sitemaps help with SEO, but are also useful for seeing all a sites public content at a glance.
###### Use Cases
Understand the structure of a site's public-facing content, and for site-owners, check that you're site's sitemap is accessible, parsable and contains everything you wish it to.
###### Useful Links
- [Learn about Sitemaps](https://developers.google.com/search/docs/crawling-indexing/sitemaps/overview)
- [Sitemap XML spec](https://www.sitemaps.org/protocol.html)
- [Sitemap tutorial](https://www.conductor.com/academy/xml-sitemap/)
</details>
<details>
<summary><b>Security.txt</b></summary>
<img width="300" src="https://i.ibb.co/tq1FT5r/Screenshot-from-2023-07-24-20-31-21.png" align="right" />
###### Description
The security.txt file tells researchers how they can responsibly disclose any security issues found on your site. The standard was proposed in RFC 9116, and specifies that this file should include a point of contact (email address), as well as optionally other info, like a link to the security disclosure policy, PGP key, proffered language, policy expiry and more. The file should be located at the root of your domain, either at /security.txt or /.well-known/security.txt.
###### Use Cases
This is important, as without a defined point of contact a security researcher may be unable to report a critical security issue, or may use insecure or possibly public channels to do so. From an OSINT perspective, you may also glean info about a site including their posture on security, their CSAF provider, and meta data from the PGP public key.
###### Useful Links
- [securitytxt.org](https://securitytxt.org/)
- [RFC-9116 Proposal](https://datatracker.ietf.org/doc/html/rfc9116)
- [RFC-9116 History](https://datatracker.ietf.org/doc/rfc9116/)
- [Security.txt (Wikipedia)](https://en.wikipedia.org/wiki/Security.txt)
- [Example security.txt (Cloudflare)](https://www.cloudflare.com/.well-known/security.txt)
- [Tutorial for creating security.txt (Pieter Bakker)](https://pieterbakker.com/implementing-security-txt/)
</details>
<details>
<summary><b>Linked Pages</b></summary>
<img width="300" src="https://i.ibb.co/LtK14XR/Screenshot-from-2023-07-29-11-16-44.png" align="right" />
###### Description
Displays all internal and external links found on a site, identified by the href attributes attached to anchor elements.
###### Use Cases
For site owners, this is useful for diagnosing SEO issues, improving the site structure, understanding how content is inter-connected. External links can show partnerships, dependencies, and potential reputation risks. From a security standpoint, the outbound links can help identify any potential malicious or compromised sites the website is unknowingly linking to. Analyzing internal links can aid in understanding the site's structure and potentially uncover hidden or vulnerable pages which are not intended to be public. And for an OSINT investigator, it can aid in building a comprehensive understanding of the target, uncovering related entities, resources, or even potential hidden parts of the site.
###### Useful Links
- [W3C Link Checker](https://validator.w3.org/checklink)
</details>
<details>
<summary><b>Social Tags</b></summary>
<img width="300" src="https://i.ibb.co/4srTT1w/Screenshot-from-2023-07-29-11-15-27.png" align="right" />
###### Description
Websites can include certain meta tags, that tell search engines and social media platforms what info to display. This usually includes a title, description, thumbnail, keywords, author, social accounts, etc.
###### Use Cases
Adding this data to your site will boost SEO, and as an OSINT researcher it can be useful to understand how a given web app describes itself
###### Useful Links
- [SocialSharePreview.com](https://socialsharepreview.com/)
- [The guide to social meta tags](https://css-tricks.com/essential-meta-tags-social-media/)
- [Web.dev metadata tags](https://web.dev/learn/html/metadata/)
- [Open Graph Protocol](https://ogp.me/)
- [Twitter Cards](https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/abouts-cards)
- [Facebook Open Graph](https://developers.facebook.com/docs/sharing/webmasters)
</details>
<details>
<summary><b>Email Configuration</b></summary>
<img width="300" src="https://i.ibb.co/yqhwx5G/Screenshot-from-2023-07-29-18-22-20.png" align="right" />
###### Description
DMARC (Domain-based Message Authentication, Reporting & Conformance): DMARC is an email authentication protocol that works with SPF and DKIM to prevent email spoofing and phishing. It allows domain owners to specify how to handle unauthenticated mail via a published policy in DNS, and provides a way for receiving mail servers to send feedback about emails' compliance to the sender. BIMI (Brand Indicators for Message Identification): BIMI is an emerging email standard that enables organizations to display a logo in their customers' email clients automatically. BIMI ties the logo to the domain's DMARC record, providing another level of visual assurance to recipients that the email is legitimate. DKIM (DomainKeys Identified Mail): DKIM is an email security standard designed to make sure that messages were not altered in transit between the sending and recipient servers. It uses digital signatures linked to the domain of the sender to verify the sender and ensure message integrity. SPF (Sender Policy Framework): SPF is an email authentication method designed to prevent email spoofing. It specifies which mail servers are authorized to send email on behalf of a domain by creating a DNS record. This helps protect against spam by providing a way for receiving mail servers to check that incoming mail from a domain comes from a host authorized by that domain's administrators.
###### Use Cases
This information is helpful for researchers as it helps assess a domain's email security posture, uncover potential vulnerabilities, and verify the legitimacy of emails for phishing detection. These details can also provide insight into the hosting environment, potential service providers, and the configuration patterns of a target organization, assisting in investigative efforts.
###### Useful Links
- [Intro to DMARC, DKIM, and SPF (via Cloudflare)](https://www.cloudflare.com/learning/email-security/dmarc-dkim-spf/)
- [EasyDMARC Domain Scanner](https://easydmarc.com/tools/domain-scanner)
- [MX Toolbox](https://mxtoolbox.com/)
- [RFC-7208 - SPF](https://datatracker.ietf.org/doc/html/rfc7208)
- [RFC-6376 - DKIM](https://datatracker.ietf.org/doc/html/rfc6376)
- [RFC-7489 - DMARC](https://datatracker.ietf.org/doc/html/rfc7489)
- [BIMI Group](https://bimigroup.org/)
</details>
<details>
<summary><b>Firewall Detection</b></summary>
<img width="300" src="https://i.ibb.co/MfcxQt2/Screenshot-from-2023-08-12-15-40-52.png" align="right" />
###### Description
A WAF or web application firewall helps protect web applications by filtering and monitoring HTTP traffic between a web application and the Internet. It typically protects web applications from attacks such as cross-site forgery, cross-site-scripting (XSS), file inclusion, and SQL injection, among others.
###### Use Cases
It's useful to understand if a site is using a WAF, and which firewall software / service it is using, as this provides an insight into the sites protection against several attack vectors, but also may reveal vulnerabilities in the firewall itself.
###### Useful Links
- [What is a WAF (via Cloudflare Learning)](https://www.cloudflare.com/learning/ddos/glossary/web-application-firewall-waf/)
- [OWASP - Web Application Firewalls](https://owasp.org/www-community/Web_Application_Firewall)
- [Web Application Firewall Best Practices](https://owasp.org/www-pdf-archive/Best_Practices_Guide_WAF_v104.en.pdf)
- [WAF - Wiki](https://en.wikipedia.org/wiki/Web_application_firewall)
</details>
<details>
<summary><b>HTTP Security Features</b></summary>
<img width="300" src="https://i.ibb.co/LP05HMV/Screenshot-from-2023-08-12-15-40-28.png" align="right" />
###### Description
Correctly configured security HTTP headers adds a layer of protection against common attacks to your site. The main headers to be aware of are: HTTP Strict Transport Security (HSTS): Enforces the use of HTTPS, mitigating man-in-the-middle attacks and protocol downgrade attempts. Content Security Policy (CSP): Constrains web page resources to prevent cross-site scripting and data injection attacks. X-Content-Type-Options: Prevents browsers from MIME-sniffing a response away from the declared content type, curbing MIME-type confusion attacks. X-Frame-Options: Protects users from clickjacking attacks by controlling whether a browser should render the page in a `<frame>`, `<iframe>`, `<embed>`, or `<object>`.
###### Use Cases
Reviewing security headers is important, as it offers insights into a site's defensive posture and potential vulnerabilities, enabling proactive mitigation and ensuring compliance with security best practices.
###### Useful Links
- [OWASP Secure Headers Project](https://owasp.org/www-project-secure-headers/)
- [HTTP Header Cheatsheet](https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html)
- [content-security-policy.com](https://content-security-policy.com/)
- [resourcepolicy.fyi](https://resourcepolicy.fyi/)
- [HTTP Security Headers](https://securityheaders.com/)
- [Mozilla Observatory](https://observatory.mozilla.org/)
- [CSP Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP)
- [HSTS Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security)
- [X-Content-Type-Options Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options)
- [X-Frame-Options Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options)
- [X-XSS-Protection Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection)
</details>
<details>
<summary><b>Archive History</b></summary>
<img width="300" src="https://i.ibb.co/nB9szT1/Screenshot-from-2023-08-14-22-31-16.png" align="right" />
###### Description
Fetches full history of archives from the Wayback machine
###### Use Cases
This is useful for understanding the history of a site, and how it has changed over time. It can also be useful for finding old versions of a site, or for finding content that has been removed.
###### Useful Links
- [Wayback Machine](https://archive.org/web/)
</details>
<details>
<summary><b>Global Ranking</b></summary>
<img width="300" src="https://i.ibb.co/nkbczgb/Screenshot-from-2023-08-14-22-02-40.png" align="right" />
###### Description
This check shows the global rank of the requested site. This is only accurate for websites which are in the top 100 million list. We're using data from the Tranco project (see below), which collates the top sites on the web from Umbrella, Majestic, Quantcast, the Chrome User Experience Report and Cloudflare Radar.
###### Use Cases
Knowing a websites overall global rank can be useful for understanding the scale of the site, and for comparing it to other sites. It can also be useful for understanding the relative popularity of a site, and for identifying potential trends.
###### Useful Links
- [Tranco List](https://tranco-list.eu/)
- [Tranco Research Paper](https://tranco-list.eu/assets/tranco-ndss19.pdf)
</details>
<details>
<summary><b>Block Detection</b></summary>
<img width="300" src="https://i.ibb.co/M5JSXbW/Screenshot-from-2023-08-26-12-12-43.png" align="right" />
###### Description
Checks access to the URL using 10+ of the most popular privacy, malware and parental control blocking DNS servers.
###### Use Cases
###### Useful Links
- [ThreatJammer Lists](https://threatjammer.com/osint-lists)
</details>
<details>
<summary><b>Malware & Phishing Detection</b></summary>
<img width="300" src="https://i.ibb.co/hYgy621/Screenshot-from-2023-08-26-12-07-47.png" align="right" />
###### Description
Checks if a site appears in several common malware and phishing lists, to determine it's threat level.
###### Use Cases
Knowing if a site is listed as a threat by any of these services can be useful for understanding the reputation of a site, and for identifying potential trends.
###### Useful Links
- [URLHaus](https://urlhaus-api.abuse.ch/)
- [PhishTank](https://www.phishtank.com/)
</details>
<details>
<summary><b>TLS Cipher Suites</b></summary>
<img width="300" src="https://i.ibb.co/6ydtH5R/Screenshot-from-2023-08-26-12-09-58.png" align="right" />
###### Description
These are combinations of cryptographic algorithms used by the server to establish a secure connection. It includes the key exchange algorithm, bulk encryption algorithm, MAC algorithm, and PRF (pseudorandom function).
###### Use Cases
This is important info to test for from a security perspective. Because a cipher suite is only as secure as the algorithms that it contains. If the version of encryption or authentication algorithm in a cipher suite have known vulnerabilities the cipher suite and TLS connection may then vulnerable to a downgrade or other attack
###### Useful Links
- [sslscan2 CLI](https://github.com/rbsec/sslscan)
- [ssl-enum-ciphers (NPMAP script)](https://nmap.org/nsedoc/scripts/ssl-enum-ciphers.html)
</details>
<details>
<summary><b>TLS Security Config</b></summary>
<img width="300" src="https://i.ibb.co/FmksZJt/Screenshot-from-2023-08-26-12-12-09.png" align="right" />
###### Description
This uses guidelines from Mozilla's TLS Observatory to check the security of the TLS configuration. It checks for bad configurations, which may leave the site vulnerable to attack, as well as giving advice on how to fix. It will also give suggestions around outdated and modern TLS configs
###### Use Cases
Understanding issues with a site's TLS configuration will help you address potential vulnerabilities, and ensure the site is using the latest and most secure TLS configuration.
###### Useful Links
</details>
<details>
<summary><b>TLS Handshake Simulation</b></summary>
<img width="300" src="https://i.ibb.co/F7qRZkh/Screenshot-from-2023-08-26-12-11-28.png" align="right" />
###### Description
This simulates how different clients (browsers, operating systems) would perform a TLS handshake with the server. It helps identify compatibility issues and insecure configurations.
###### Use Cases
###### Useful Links
- [TLS Handshakes (via Cloudflare Learning)](https://www.cloudflare.com/learning/ssl/what-happens-in-a-tls-handshake/)
- [SSL Test (via SSL Labs)](https://www.ssllabs.com/ssltest/)
</details>
<details>
<summary><b>Screenshot</b></summary>
<img width="300" src="https://i.ibb.co/2F0x8kP/Screenshot-from-2023-07-29-18-34-48.png" align="right" />
###### Description
This check takes a screenshot of webpage that the requested URL / IP resolves to, and displays it.
###### Use Cases
This may be useful to see what a given website looks like, free of the constraints of your browser, IP, or location.
</details>
</details>
Read more here: **[web-check.xyz/about](https://web-check.xyz/about)**
---
## Usage
### Deployment
### Deploying - Option #1: Netlify
Click the button below, to deploy to Netlify 👇
[](https://app.netlify.com/start/deploy?repository=https://github.com/lissy93/web-check)
### Deploying - Option #2: Vercel
Click the button below, to deploy to Vercel 👇
[](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Flissy93%2Fweb-check&project-name=web-check&repository-name=web-check-fork&demo-title=Web-Check%20Demo&demo-description=Check%20out%20web-check.xyz%20to%20see%20a%20live%20demo%20of%20this%20application%20running.&demo-url=https%3A%2F%2Fweb-check.xyz&demo-image=https%3A%2F%2Fraw.githubusercontent.com%2FLissy93%2Fweb-check%2Fmaster%2F.github%2Fscreenshots%2Fweb-check-screenshot10.png)
### Deploying - Option #3: Docker
Run `docker run -p 3000:3000 lissy93/web-check`, then open [`localhost:3000`](http://localhost:3000)
<details>
<summary>Docker Options</summary>
You can get the Docker image from:
- DockerHub: [`lissy93/web-check`](https://hub.docker.com/r/lissy93/web-check)
- GHCR: [`ghcr.io/lissy93/web-check`](https://github.com/Lissy93/web-check/pkgs/container/web-check)
- Or build the image yourself by cloning the repo and running `docker build -t web-check .`
</details>
### Deploying - Option #4: From Source
Install the prerequisites listed in the [Developing](#developing) section, then run:
```bash
git clone https://github.com/Lissy93/web-check.git # Download the code from GitHub
cd web-check # Navigate into the project dir
yarn install # Install the NPM dependencies
yarn build # Build the app for production
yarn serve # Start the app (API and GUI)
```
---
### Configuring
By default, no configuration is needed.
But there are some optional environmental variables that you can set to give you access to some additional checks, or to increase rate-limits for some checks that use external APIs.
**API Keys & Credentials**:
Key | Value
---|---
`GOOGLE_CLOUD_API_KEY` | A Google API key ([get here](https://cloud.google.com/api-gateway/docs/authenticate-api-keys)). This can be used to return quality metrics for a site
`REACT_APP_SHODAN_API_KEY` | A Shodan API key ([get here](https://account.shodan.io/)). This will show associated host names for a given domain
`REACT_APP_WHO_API_KEY` | A WhoAPI key ([get here](https://whoapi.com/)). This will show more comprehensive WhoIs records than the default job
<details>
<summary><small>Full / Upcoming Vals</small></summary>
- `GOOGLE_CLOUD_API_KEY` - A Google API key ([get here](https://cloud.google.com/api-gateway/docs/authenticate-api-keys)). This can be used to return quality metrics for a site
- `REACT_APP_SHODAN_API_KEY` - A Shodan API key ([get here](https://account.shodan.io/)). This will show associated host names for a given domain
- `REACT_APP_WHO_API_KEY` - A WhoAPI key ([get here](https://whoapi.com/)). This will show more comprehensive WhoIs records than the default job
- `SECURITY_TRAILS_API_KEY` - A Security Trails API key ([get here](https://securitytrails.com/corp/api)). This will show org info associated with the IP
- `CLOUDMERSIVE_API_KEY` - API key for Cloudmersive ([get here](https://account.cloudmersive.com/)). This will show known threats associated with the IP
- `TRANCO_USERNAME` - A Tranco email ([get here](https://tranco-list.eu/)). This will show the rank of a site, based on traffic
- `TRANCO_API_KEY` - A Tranco API key ([get here](https://tranco-list.eu/)). This will show the rank of a site, based on traffic
- `URL_SCAN_API_KEY` - A URLScan API key ([get here](https://urlscan.io/)). This will fetch miscalanious info about a site
- `BUILT_WITH_API_KEY` - A BuiltWith API key ([get here](https://api.builtwith.com/)). This will show the main features of a site
- `TORRENT_IP_API_KEY` - A torrent API key ([get here](https://iknowwhatyoudownload.com/en/api/)). This will show torrents downloaded by an IP
</details>
**Configuration Settings**:
Key | Value
---|---
`PORT` | Port to serve the API, when running server.js (e.g. `3000`)
`API_ENABLE_RATE_LIMIT` | Enable rate-limiting for the /api endpoints (e.g. `true`)
`API_TIMEOUT_LIMIT` | The timeout limit for API requests, in milliseconds (e.g. `10000`)
`API_CORS_ORIGIN` | Enable CORS, by setting your allowed hostname(s) here (e.g. `example.com`)
`CHROME_PATH` | The path the Chromium executable (e.g. `/usr/bin/chromium`)
`DISABLE_GUI` | Disable the GUI, and only serve the API (e.g. `false`)
`REACT_APP_API_ENDPOINT` | The endpoint for the API, either local or remote (e.g. `/api`)
All values are optional.
You can add these as environmental variables. Either put them directly into an `.env` file in the projects root, or via the Netlify / Vercel UI, or by passing to the Docker container with the --env flag, or using your own environmental variable management system
Note that keys that are prefixed with `REACT_APP_` are used client-side, and as such they must be scoped correctly with minimum privileges, since may be made visible when intercepting browser <-> server network requests
---
### Developing
1. Clone the repo, `git clone git@github.com:Lissy93/web-check.git`
2. Cd into it, `cd web-check`
3. Install dependencies: `yarn`
4. Start the dev server, with `yarn dev`
You'll need [Node.js](https://nodejs.org/en) (V 18.16.1 or later) installed, plus [yarn](https://yarnpkg.com/getting-started/install) as well as [git](https://git-scm.com/).
Some checks also require `chromium`, `traceroute` and `dns` to be installed within your environment. These jobs will just be skipped if those packages aren't present.
---
## Community
### Contributing
Contributions of any kind are very welcome, and would be much appreciated.
For Code of Conduct, see [Contributor Convent](https://www.contributor-covenant.org/version/2/1/code_of_conduct/).
To get started, fork the repo, make your changes, add, commit and push the code, then come back here to open a pull request. If you're new to GitHub or open source, [this guide](https://www.freecodecamp.org/news/how-to-make-your-first-pull-request-on-github-3#let-s-make-our-first-pull-request-) or the [git docs](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request) may help you get started, but feel free to reach out if you need any support.
[](https://github.com/Lissy93/web-check/compare)
### Reporting Bugs
If you've found something that doesn't work as it should, or would like to suggest a new feature, then go ahead and raise a ticket on GitHub.
For bugs, please outline the steps needed to reproduce, and include relevant info like system info and resulting logs.
[](https://github.com/Lissy93/web-check/issues/new/choose)
### Supporting
The app will remain 100% free and open source.
But due to the amount of traffic that the hosted instance gets, the lambda function usage is costing about $25/month.
Any help with covering the costs via GitHub Sponsorship would be much appreciated.
It's thanks to the support of the community that this project is able to be freely available for everyone :)
[](https://github.com/sponsors/Lissy93)
### Contributors
Credit to the following users for contributing to Web-Check
<!-- readme: contributors -start -->
<table>
<tbody>
<tr>
<td align="center">
<a href="https://github.com/Lissy93">
<img src="https://avatars.githubusercontent.com/u/1862727?v=4" width="80;" alt="Lissy93"/>
<br />
<sub><b>Alicia Sykes</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/liss-bot">
<img src="https://avatars.githubusercontent.com/u/87835202?v=4" width="80;" alt="liss-bot"/>
<br />
<sub><b>Alicia Bot</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/n0a">
<img src="https://avatars.githubusercontent.com/u/14150948?v=4" width="80;" alt="n0a"/>
<br />
<sub><b>Denis Simonov</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/muni106">
<img src="https://avatars.githubusercontent.com/u/65845442?v=4" width="80;" alt="muni106"/>
<br />
<sub><b>Mounir Samite</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/ChrisCarini">
<img src="https://avatars.githubusercontent.com/u/6374067?v=4" width="80;" alt="ChrisCarini"/>
<br />
<sub><b>Chris Carini</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/bolens">
<img src="https://avatars.githubusercontent.com/u/1218380?v=4" width="80;" alt="bolens"/>
<br />
<sub><b>Michael Bolens</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/HeroGamers">
<img src="https://avatars.githubusercontent.com/u/15278940?v=4" width="80;" alt="HeroGamers"/>
<br />
<sub><b>Marcus Sand</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/jinnabaalu">
<img src="https://avatars.githubusercontent.com/u/11784253?v=4" width="80;" alt="jinnabaalu"/>
<br />
<sub><b>Jinna Baalu</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/GreyXor">
<img src="https://avatars.githubusercontent.com/u/79602273?v=4" width="80;" alt="GreyXor"/>
<br />
<sub><b>GreyXor</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/brianteeman">
<img src="https://avatars.githubusercontent.com/u/1296369?v=4" width="80;" alt="brianteeman"/>
<br />
<sub><b>Brian Teeman</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/vitalykarasik">
<img src="https://avatars.githubusercontent.com/u/7628795?v=4" width="80;" alt="vitalykarasik"/>
<br />
<sub><b>Vitaly Karasik</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Its-Just-Nans">
<img src="https://avatars.githubusercontent.com/u/56606507?v=4" width="80;" alt="Its-Just-Nans"/>
<br />
<sub><b>n4n5</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/robinson">
<img src="https://avatars.githubusercontent.com/u/237874?v=4" width="80;" alt="robinson"/>
<br />
<sub><b>Lth</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/abhishekMuge">
<img src="https://avatars.githubusercontent.com/u/49590582?v=4" width="80;" alt="abhishekMuge"/>
<br />
<sub><b>Abhishek Muge</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/UlisesGascon">
<img src="https://avatars.githubusercontent.com/u/5110813?v=4" width="80;" alt="UlisesGascon"/>
<br />
<sub><b>Ulises Gascón</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/PhiRequiem">
<img src="https://avatars.githubusercontent.com/u/1323576?v=4" width="80;" alt="PhiRequiem"/>
<br />
<sub><b>PhiRequiem</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/ntaiko">
<img src="https://avatars.githubusercontent.com/u/108784453?v=4" width="80;" alt="ntaiko"/>
<br />
<sub><b>Nikolaos G. Ntaiko</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Myzel394">
<img src="https://avatars.githubusercontent.com/u/50424412?v=4" width="80;" alt="Myzel394"/>
<br />
<sub><b>Myzel394</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/murrple-1">
<img src="https://avatars.githubusercontent.com/u/5559656?v=4" width="80;" alt="murrple-1"/>
<br />
<sub><b>Murray Christopherson</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/t3chn0m4g3">
<img src="https://avatars.githubusercontent.com/u/4318452?v=4" width="80;" alt="t3chn0m4g3"/>
<br />
<sub><b>Marco Ochse</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/treatmesubj">
<img src="https://avatars.githubusercontent.com/u/39680353?v=4" width="80;" alt="treatmesubj"/>
<br />
<sub><b>John Hupperts</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/eltociear">
<img src="https://avatars.githubusercontent.com/u/22633385?v=4" width="80;" alt="eltociear"/>
<br />
<sub><b>Ikko Eltociear Ashimine</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Gertje823">
<img src="https://avatars.githubusercontent.com/u/36937387?v=4" width="80;" alt="Gertje823"/>
<br />
<sub><b>Gertje823</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/epreston">
<img src="https://avatars.githubusercontent.com/u/347224?v=4" width="80;" alt="epreston"/>
<br />
<sub><b>Ed Preston</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/dimitri-kandassamy">
<img src="https://avatars.githubusercontent.com/u/21193806?v=4" width="80;" alt="dimitri-kandassamy"/>
<br />
<sub><b>Dimitri Kandassamy</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/0xflotus">
<img src="https://avatars.githubusercontent.com/u/26602940?v=4" width="80;" alt="0xflotus"/>
<br />
<sub><b>0xflotus</b></sub>
</a>
</td>
</tr>
<tbody>
</table>
<!-- readme: contributors -end -->
### Sponsors
Huge thanks to these wonderful people, who sponsor me on GitHub, their support helps cover the costs required to keep Web-Check and my other projects free for everyone. Consider joining them, by [sponsoring me on GitHub](https://github.com/sponsors/Lissy93) if you're able.
<!-- readme: sponsors -start -->
<table>
<tbody>
<tr>
<td align="center">
<a href="https://github.com/vincentkoc">
<img src="https://avatars.githubusercontent.com/u/25068?v=4" width="80;" alt="vincentkoc"/>
<br />
<sub><b>Vincent Koc</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/AnandChowdhary">
<img src="https://avatars.githubusercontent.com/u/2841780?u=747e554b3a7f12eb20b7910e1c87d817844f714f&v=4" width="80;" alt="AnandChowdhary"/>
<br />
<sub><b>Anand Chowdhary</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/shrippen">
<img src="https://avatars.githubusercontent.com/u/2873570?v=4" width="80;" alt="shrippen"/>
<br />
<sub><b>Shrippen</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/bile0026">
<img src="https://avatars.githubusercontent.com/u/5022496?u=aec96ad173c0ea9baaba93807efa8a848af6595c&v=4" width="80;" alt="bile0026"/>
<br />
<sub><b>Zach Biles</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/UlisesGascon">
<img src="https://avatars.githubusercontent.com/u/5110813?u=3c41facd8aa26154b9451de237c34b0f78d672a5&v=4" width="80;" alt="UlisesGascon"/>
<br />
<sub><b>Ulises Gascón</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/digitalarche">
<img src="https://avatars.githubusercontent.com/u/6546135?u=564756d7f44ab2206819eb3148f6d822673f5066&v=4" width="80;" alt="digitalarche"/>
<br />
<sub><b>Digital Archeology</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/InDieTasten">
<img src="https://avatars.githubusercontent.com/u/7047377?u=8d8f8017628b38bc46dcbf3620e194b01d3fb2d1&v=4" width="80;" alt="InDieTasten"/>
<br />
<sub><b>InDieTasten</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/araguaci">
<img src="https://avatars.githubusercontent.com/u/7318668?v=4" width="80;" alt="araguaci"/>
<br />
<sub><b>Araguaci</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/bmcgonag">
<img src="https://avatars.githubusercontent.com/u/7346620?u=2a0f9284f3e12ac1cc15288c254d1ec68a5081e8&v=4" width="80;" alt="bmcgonag"/>
<br />
<sub><b>Brian McGonagill</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/arcestia">
<img src="https://avatars.githubusercontent.com/u/7936962?v=4" width="80;" alt="arcestia"/>
<br />
<sub><b>Laurensius Jeffrey</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/vlad-tim">
<img src="https://avatars.githubusercontent.com/u/11474041?u=eee43705b54d2ec9f51fc4fcce5ad18dd17c87e4&v=4" width="80;" alt="vlad-tim"/>
<br />
<sub><b>Vlad</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/helixzz">
<img src="https://avatars.githubusercontent.com/u/12218889?u=d06d0c103dfbdb99450623064f7da3c5a3675fb6&v=4" width="80;" alt="helixzz"/>
<br />
<sub><b>HeliXZz</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/mryesiller">
<img src="https://avatars.githubusercontent.com/u/24632172?u=0d20f2d615158f87cd60a3398d3efb026c32f291&v=4" width="80;" alt="mryesiller"/>
<br />
<sub><b>Göksel Yeşiller</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/sushibait">
<img src="https://avatars.githubusercontent.com/u/26634535?v=4" width="80;" alt="sushibait"/>
<br />
<sub><b>Shiverme Timbers</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/getumbrel">
<img src="https://avatars.githubusercontent.com/u/59408891?v=4" width="80;" alt="getumbrel"/>
<br />
<sub><b>Umbrel</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/OlliVHH">
<img src="https://avatars.githubusercontent.com/u/84959562?v=4" width="80;" alt="OlliVHH"/>
<br />
<sub><b>HamburgerJung</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/frankdez93">
<img src="https://avatars.githubusercontent.com/u/87549420?v=4" width="80;" alt="frankdez93"/>
<br />
<sub><b>Frankdez93</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/terminaltrove">
<img src="https://avatars.githubusercontent.com/u/121595180?v=4" width="80;" alt="terminaltrove"/>
<br />
<sub><b>Terminal Trove</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/st617">
<img src="https://avatars.githubusercontent.com/u/128325650?v=4" width="80;" alt="st617"/>
<br />
<sub><b>st617</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/hudsonrock-partnerships">
<img src="https://avatars.githubusercontent.com/u/163282900?u=5f2667f7fe5d284ac7a2da6b0800ea8970b0fcbf&v=4" width="80;" alt="hudsonrock-partnerships"/>
<br />
<sub><b>hudsonrock-partnerships</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/CarterPerez-dev">
<img src="https://avatars.githubusercontent.com/u/188120068?v=4" width="80;" alt="CarterPerez-dev"/>
<br />
<sub><b>Carter Perez</b></sub>
</a>
</td>
</tr>
<tbody>
</table>
<!-- readme: sponsors -end -->
---
## License
> _**[Lissy93/Web-Check](https://github.com/Lissy93/web-check)** is licensed under [MIT](https://github.com/Lissy93/web-check/blob/HEAD/LICENSE) © [Alicia Sykes](https://aliciasykes.com) 2023._<br>
> <sup align="right">For information, see <a href="https://tldrlegal.com/license/mit-license">TLDR Legal > MIT</a></sup>
<details>
<summary>Expand License</summary>
```
The MIT License (MIT)
Copyright (c) Alicia Sykes <alicia@omg.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sub-license, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included install
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANT ABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
```
[](https://app.fossa.com/projects/git%2Bgithub.com%2FLissy93%2Fweb-check?ref=badge_large&issueType=license)
</details>
<!-- License + Copyright -->
<p align="center">
<i>© <a href="https://aliciasykes.com">Alicia Sykes</a> 2023</i><br>
<i>Licensed under <a href="https://gist.github.com/Lissy93/143d2ee01ccc5c052a17">MIT</a></i><br>
<a href="https://github.com/lissy93"><img src="https://i.ibb.co/4KtpYxb/octocat-clean-mini.png" /></a><br>
<sup>Thanks for visiting :)</sup>
</p>
<!-- Dinosaurs are Awesome -->
<!--
. - ~ ~ ~ - .
.. _ .-~ ~-.
//| \ `..~ `.
|| | } } / \ \
(\ \\ \~^..' | } \
\`.-~ o / } | / \
(__ | / | / `.
`- - ~ ~ -._| /_ - ~ ~ ^| /- _ `.
| / | / ~-. ~- _
|_____| |_____| ~ - . _ _~_-_
-->
================================================
FILE: .github/screenshots/README.md
================================================

================================================
FILE: .github/workflows/credits.yml
================================================
# Inserts list of community members into ./README.md
name: 💓 Inserts Contributors & Sponsors
on:
workflow_dispatch: # Manual dispatch
schedule:
- cron: '45 1 * * 0' # At 01:45 on Sunday.
jobs:
# Job #1 - Fetches sponsors and inserts table into readme
insert-sponsors:
runs-on: ubuntu-latest
name: Inserts Sponsors 💓
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Updates readme with sponsors
uses: JamesIves/github-sponsors-readme-action@v1
with:
token: ${{ secrets.BOT_TOKEN || secrets.GITHUB_TOKEN }}
file: .github/README.md
# Job #2 - Fetches contributors and inserts table into readme
insert-contributors:
runs-on: ubuntu-latest
name: Inserts Contributors 💓
steps:
- name: Updates readme with contributors
uses: akhilmhdh/contributors-readme-action@v2.3.10
env:
GITHUB_TOKEN: ${{ secrets.BOT_TOKEN || secrets.GITHUB_TOKEN }}
with:
image_size: 80
readme_path: .github/README.md
columns_per_row: 6
commit_message: 'docs: Updates contributors list'
committer_username: liss-bot
committer_email: liss-bot@d0h.co
================================================
FILE: .github/workflows/deploy-aws.yml
================================================
name: 🚀 Deploy to AWS
on:
workflow_dispatch:
push:
branches:
- master
tags:
- '*'
paths:
- api/**
- serverless.yml
- package.json
- .github/workflows/deploy-aws.yml
jobs:
deploy-api:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 16
- name: Cache node_modules
uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Create GitHub deployment for API
uses: chrnorm/deployment-action@releases/v2
id: deployment_api
with:
token: ${{ secrets.BOT_TOKEN || secrets.GITHUB_TOKEN }}
environment: AWS (Backend API)
ref: ${{ github.ref }}
- name: Install Serverless CLI and dependencies
run: |
npm i -g serverless
yarn
- name: Deploy to AWS
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }}
run: serverless deploy
- name: Update GitHub deployment status (API)
if: always()
uses: chrnorm/deployment-status@v2
with:
token: ${{ secrets.BOT_TOKEN || secrets.GITHUB_TOKEN }}
state: "${{ job.status }}"
deployment_id: ${{ steps.deployment_api.outputs.deployment_id }}
ref: ${{ github.ref }}
deploy-frontend:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 16
- name: Cache node_modules
uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Create GitHub deployment for Frontend
uses: chrnorm/deployment-action@v2
id: deployment_frontend
with:
token: ${{ secrets.BOT_TOKEN || secrets.GITHUB_TOKEN }}
environment: AWS (Frontend Web UI)
ref: ${{ github.ref }}
- name: Install dependencies and build
run: |
yarn install
yarn build
- name: Setup AWS
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Upload to S3
env:
AWS_S3_BUCKET: 'web-check-frontend'
run: aws s3 sync ./build/ s3://$AWS_S3_BUCKET/ --delete
- name: Invalidate CloudFront cache
uses: chetan/invalidate-cloudfront-action@v2
env:
DISTRIBUTION: E30XKAM2TG9FD8
PATHS: '/*'
AWS_REGION: 'us-east-1'
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Update GitHub deployment status (Frontend)
if: always()
uses: chrnorm/deployment-status@v2
with:
token: ${{ secrets.BOT_TOKEN || secrets.GITHUB_TOKEN }}
state: "${{ job.status }}"
deployment_id: ${{ steps.deployment_frontend.outputs.deployment_id }}
ref: ${{ github.ref }}
================================================
FILE: .github/workflows/docker.yml
================================================
name: 🐳 Build + Publish Docker Image
on:
workflow_dispatch:
push:
branches:
- master
tags:
- '*'
paths:
- src/**
- api/**
- public/**
- Dockerfile
env:
IMAGE_NAME: web-check
DOCKER_USER: lissy93
GHCR_REGISTRY: ghcr.io
DOCKERHUB_REGISTRY: docker.io
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout 🛎️
uses: actions/checkout@v2
- name: Extract tag name 🏷️
shell: bash
run: echo "GIT_TAG=$(echo ${GITHUB_REF#refs/tags/} | sed 's/\//_/g')" >> $GITHUB_ENV
- name: Compute tags 🔖
id: compute-tags
run: |
if [[ "${{ github.ref }}" == "refs/heads/master" ]]; then
echo "GHCR_TAG=${GHCR_REGISTRY}/${DOCKER_USER}/${IMAGE_NAME}:latest" >> $GITHUB_ENV
echo "DOCKERHUB_TAG=${DOCKERHUB_REGISTRY}/${DOCKER_USER}/${IMAGE_NAME}:latest" >> $GITHUB_ENV
else
echo "GHCR_TAG=${GHCR_REGISTRY}/${DOCKER_USER}/${IMAGE_NAME}:${GIT_TAG}" >> $GITHUB_ENV
echo "DOCKERHUB_TAG=${DOCKERHUB_REGISTRY}/${DOCKER_USER}/${IMAGE_NAME}:${GIT_TAG}" >> $GITHUB_ENV
fi
- name: Set up QEMU 🐧
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx 🐳
uses: docker/setup-buildx-action@v1
- name: Login to GitHub Container Registry 🔑
uses: docker/login-action@v1
with:
registry: ${{ env.GHCR_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to DockerHub 🔑
uses: docker/login-action@v1
with:
registry: ${{ env.DOCKERHUB_REGISTRY }}
username: ${{ env.DOCKER_USER }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Build and push Docker images 🛠️
uses: docker/build-push-action@v2
with:
context: .
file: ./Dockerfile
push: true
platforms: linux/amd64,linux/arm64/v8
tags: |
${{ env.GHCR_TAG }}
${{ env.DOCKERHUB_TAG }}
================================================
FILE: .github/workflows/mirror.yml
================================================
# Pushes the contents of the repo to the Codeberg mirror
name: 🪞 Mirror to Codeberg
on:
workflow_dispatch:
schedule:
- cron: '30 0 * * 0'
jobs:
codeberg:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- uses: pixta-dev/repository-mirroring-action@v1
with:
target_repo_url: git@codeberg.org:alicia/web-check.git
ssh_private_key: ${{ secrets.CODEBERG_SSH }}
================================================
FILE: .gitignore
================================================
# ------------------------
# ENVIRONMENT SETTINGS
# ------------------------
.env
# ------------------------
# PRODUCTION
# ------------------------
/build/
# ------------------------
# BUILT FILES
# ------------------------
dist/
.vercel/
.netlify/
.webpack/
.serverless/
.astro/
# ------------------------
# DEPENDENCIES
# ------------------------
node_modules/
.yarn/cache/
.yarn/unplugged/
.yarn/build-state.yml
.yarn/install-state.gz
.pnpm/
.pnp.*
# ------------------------
# LOGS
# ------------------------
logs/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# ------------------------
# TESTING
# ------------------------
coverage/
.nyc_output/
# ------------------------
# OS SPECIFIC
# ------------------------
.DS_Store
Thumbs.db
# ------------------------
# EDITORS
# ------------------------
.idea/
.vscode/
*.swp
*.swo
================================================
FILE: Dockerfile
================================================
# Specify the Node.js version to use
ARG NODE_VERSION=21
# Specify the Debian version to use, the default is "bullseye"
ARG DEBIAN_VERSION=bullseye
# Use Node.js Docker image as the base image, with specific Node and Debian versions
FROM node:${NODE_VERSION}-${DEBIAN_VERSION} AS build
# Set the container's default shell to Bash and enable some options
SHELL ["/bin/bash", "-euo", "pipefail", "-c"]
# Install Chromium browser and Download and verify Google Chrome’s signing key
RUN apt-get update -qq --fix-missing && \
apt-get -qqy install --allow-unauthenticated gnupg wget && \
wget --quiet --output-document=- https://dl-ssl.google.com/linux/linux_signing_key.pub | gpg --dearmor > /etc/apt/trusted.gpg.d/google-archive.gpg && \
echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google.list && \
apt-get update -qq && \
apt-get -qqy --no-install-recommends install chromium traceroute python make g++ && \
rm -rf /var/lib/apt/lists/*
# Run the Chromium browser's version command and redirect its output to the /etc/chromium-version file
RUN /usr/bin/chromium --no-sandbox --version > /etc/chromium-version
# Set the working directory to /app
WORKDIR /app
# Copy package.json and yarn.lock to the working directory
COPY package.json yarn.lock ./
# Run yarn install to install dependencies and clear yarn cache
RUN apt-get update && \
yarn install --frozen-lockfile --network-timeout 100000 && \
rm -rf /app/node_modules/.cache
# Copy all files to working directory
COPY . .
# Run yarn build to build the application
RUN yarn build --production
# Final stage
FROM node:${NODE_VERSION}-${DEBIAN_VERSION} AS final
WORKDIR /app
COPY package.json yarn.lock ./
COPY --from=build /app .
RUN apt-get update && \
apt-get install -y --no-install-recommends chromium traceroute && \
chmod 755 /usr/bin/chromium && \
rm -rf /var/lib/apt/lists/* /app/node_modules/.cache
# Exposed container port, the default is 3000, which can be modified through the environment variable PORT
EXPOSE ${PORT:-3000}
# Set the environment variable CHROME_PATH to specify the path to the Chromium binaries
ENV CHROME_PATH='/usr/bin/chromium'
# Define the command executed when the container starts and start the server.js of the Node.js application
CMD ["yarn", "start"]
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2023 Alicia Sykes <https://github.com/lissy93>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: api/_common/aws-webpack.config.js
================================================
const path = require('path');
const nodeExternals = require('webpack-node-externals');
module.exports = {
target: 'node',
mode: 'production',
entry: {
'carbon': './api/carbon.js',
'cookies': './api/cookies.js',
'dns-server': './api/dns-server.js',
'dns': './api/dns.js',
'dnssec': './api/dnssec.js',
'features': './api/features.js',
'get-ip': './api/get-ip.js',
'headers': './api/headers.js',
'hsts': './api/hsts.js',
'linked-pages': './api/linked-pages.js',
'mail-config': './api/mail-config.js',
'ports': './api/ports.js',
'quality': './api/quality.js',
'redirects': './api/redirects.js',
'robots-txt': './api/robots-txt.js',
'screenshot': './api/screenshot.js',
'security-txt': './api/security-txt.js',
'sitemap': './api/sitemap.js',
'social-tags': './api/social-tags.js',
'ssl': './api/ssl.js',
'status': './api/status.js',
'tech-stack': './api/tech-stack.js',
'trace-route': './api/trace-route.js',
'txt-records': './api/txt-records.js',
'whois': './api/whois.js',
},
externals: [nodeExternals()],
output: {
filename: '[name].js',
path: path.resolve(__dirname, '.webpack'),
libraryTarget: 'commonjs2'
},
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader'
},
exclude: /node_modules/,
}
]
}
};
================================================
FILE: api/_common/middleware.js
================================================
const normalizeUrl = (url) => {
return url.startsWith('http') ? url : `https://${url}`;
};
// If present, set a shorter timeout for API requests
const TIMEOUT = process.env.API_TIMEOUT_LIMIT ? parseInt(process.env.API_TIMEOUT_LIMIT, 10) : 60000;
// If present, set CORS allowed origins for responses
const ALLOWED_ORIGINS = process.env.API_CORS_ORIGIN || '*';
// Disable everything :( Setting this env var will turn off the instance, and show message
const DISABLE_EVERYTHING = !!process.env.VITE_DISABLE_EVERYTHING;
// Set the platform currently being used
let PLATFORM = 'NETLIFY';
if (process.env.PLATFORM) { PLATFORM = process.env.PLATFORM.toUpperCase(); }
else if (process.env.VERCEL) { PLATFORM = 'VERCEL'; }
else if (process.env.WC_SERVER) { PLATFORM = 'NODE'; }
// Define the headers to be returned with each response
const headers = {
'Access-Control-Allow-Origin': ALLOWED_ORIGINS,
'Access-Control-Allow-Credentials': true,
'Content-Type': 'application/json;charset=UTF-8',
};
const timeoutErrorMsg = 'You can re-trigger this request, by clicking "Retry"\n'
+ 'If you\'re running your own instance of Web Check, then you can '
+ 'resolve this issue, by increasing the timeout limit in the '
+ '`API_TIMEOUT_LIMIT` environmental variable to a higher value (in milliseconds), '
+ 'or if you\'re hosting on Vercel increase the maxDuration in vercel.json.\n\n'
+ `The public instance currently has a lower timeout of ${TIMEOUT}ms `
+ 'in order to keep running costs affordable, so that Web Check can '
+ 'remain freely available for everyone.';
const disabledErrorMsg = 'Error - WebCheck Temporarily Disabled.\n\n'
+ 'We\'re sorry, but due to the increased cost of running Web Check '
+ 'we\'ve had to temporatily disable the public instand. '
+ 'We\'re activley looking for affordable ways to keep Web Check running, '
+ 'while free to use for everybody.\n'
+ 'In the meantime, since we\'ve made our code free and open source, '
+ 'you can get Web Check running on your own system, by following the instructions in our GitHub repo';
// A middleware function used by all API routes on all platforms
const commonMiddleware = (handler) => {
// Create a timeout promise, to throw an error if a request takes too long
const createTimeoutPromise = (timeoutMs) => {
return new Promise((_, reject) => {
setTimeout(() => {
reject(new Error(`Request timed-out after ${timeoutMs} ms`));
}, timeoutMs);
});
};
// Vercel
const vercelHandler = async (request, response) => {
if (DISABLE_EVERYTHING) {
response.status(503).json({ error: disabledErrorMsg });
}
const queryParams = request.query || {};
const rawUrl = queryParams.url;
if (!rawUrl) {
return response.status(500).json({ error: 'No URL specified' });
}
const url = normalizeUrl(rawUrl);
try {
// Race the handler against the timeout
const handlerResponse = await Promise.race([
handler(url, request),
createTimeoutPromise(TIMEOUT)
]);
if (handlerResponse.body && handlerResponse.statusCode) {
response.status(handlerResponse.statusCode).json(handlerResponse.body);
} else {
response.status(200).json(
typeof handlerResponse === 'object' ? handlerResponse : JSON.parse(handlerResponse)
);
}
} catch (error) {
let errorCode = 500;
if (error.message.includes('timed-out') || response.statusCode === 504) {
errorCode = 408;
error.message = `${error.message}\n\n${timeoutErrorMsg}`;
}
response.status(errorCode).json({ error: error.message });
}
};
// Netlify
const netlifyHandler = async (event, context, callback) => {
const queryParams = event.queryStringParameters || event.query || {};
const rawUrl = queryParams.url;
if (DISABLE_EVERYTHING) {
callback(null, {
statusCode: 503,
body: JSON.stringify({ error: 'Web-Check is temporarily disabled. Please try again later.' }),
headers,
});
return;
}
if (!rawUrl) {
callback(null, {
statusCode: 500,
body: JSON.stringify({ error: 'No URL specified' }),
headers,
});
return;
}
const url = normalizeUrl(rawUrl);
try {
// Race the handler against the timeout
const handlerResponse = await Promise.race([
handler(url, event, context),
createTimeoutPromise(TIMEOUT)
]);
if (handlerResponse.body && handlerResponse.statusCode) {
callback(null, handlerResponse);
} else {
callback(null, {
statusCode: 200,
body: typeof handlerResponse === 'object' ? JSON.stringify(handlerResponse) : handlerResponse,
headers,
});
}
} catch (error) {
callback(null, {
statusCode: 500,
body: JSON.stringify({ error: error.message }),
headers,
});
}
};
// The format of the handlers varies between platforms
const nativeMode = (['VERCEL', 'NODE'].includes(PLATFORM));
return nativeMode ? vercelHandler : netlifyHandler;
};
if (PLATFORM === 'NETLIFY') {
module.exports = commonMiddleware;
}
export default commonMiddleware;
================================================
FILE: api/archives.js
================================================
import axios from 'axios';
import middleware from './_common/middleware.js';
const convertTimestampToDate = (timestamp) => {
const [year, month, day, hour, minute, second] = [
timestamp.slice(0, 4),
timestamp.slice(4, 6) - 1,
timestamp.slice(6, 8),
timestamp.slice(8, 10),
timestamp.slice(10, 12),
timestamp.slice(12, 14),
].map(num => parseInt(num, 10));
return new Date(year, month, day, hour, minute, second);
}
const countPageChanges = (results) => {
let prevDigest = null;
return results.reduce((acc, curr) => {
if (curr[2] !== prevDigest) {
prevDigest = curr[2];
return acc + 1;
}
return acc;
}, -1);
}
const getAveragePageSize = (scans) => {
const totalSize = scans.map(scan => parseInt(scan[3], 10)).reduce((sum, size) => sum + size, 0);
return Math.round(totalSize / scans.length);
};
const getScanFrequency = (firstScan, lastScan, totalScans, changeCount) => {
const formatToTwoDecimal = num => parseFloat(num.toFixed(2));
const dayFactor = (lastScan - firstScan) / (1000 * 60 * 60 * 24);
const daysBetweenScans = formatToTwoDecimal(dayFactor / totalScans);
const daysBetweenChanges = formatToTwoDecimal(dayFactor / changeCount);
const scansPerDay = formatToTwoDecimal((totalScans - 1) / dayFactor);
const changesPerDay = formatToTwoDecimal(changeCount / dayFactor);
return {
daysBetweenScans,
daysBetweenChanges,
scansPerDay,
changesPerDay,
};
};
const wayBackHandler = async (url) => {
const cdxUrl = `https://web.archive.org/cdx/search/cdx?url=${url}&output=json&fl=timestamp,statuscode,digest,length,offset`;
try {
const { data } = await axios.get(cdxUrl);
// Check there's data
if (!data || !Array.isArray(data) || data.length <= 1) {
return { skipped: 'Site has never before been archived via the Wayback Machine' };
}
// Remove the header row
data.shift();
// Process and return the results
const firstScan = convertTimestampToDate(data[0][0]);
const lastScan = convertTimestampToDate(data[data.length - 1][0]);
const totalScans = data.length;
const changeCount = countPageChanges(data);
return {
firstScan,
lastScan,
totalScans,
changeCount,
averagePageSize: getAveragePageSize(data),
scanFrequency: getScanFrequency(firstScan, lastScan, totalScans, changeCount),
scans: data,
scanUrl: url,
};
} catch (err) {
return { error: `Error fetching Wayback data: ${err.message}` };
}
};
export const handler = middleware(wayBackHandler);
export default handler;
================================================
FILE: api/block-lists.js
================================================
import dns from 'dns';
import { URL } from 'url';
import middleware from './_common/middleware.js';
const DNS_SERVERS = [
{ name: 'AdGuard', ip: '176.103.130.130' },
{ name: 'AdGuard Family', ip: '176.103.130.132' },
{ name: 'CleanBrowsing Adult', ip: '185.228.168.10' },
{ name: 'CleanBrowsing Family', ip: '185.228.168.168' },
{ name: 'CleanBrowsing Security', ip: '185.228.168.9' },
{ name: 'CloudFlare', ip: '1.1.1.1' },
{ name: 'CloudFlare Family', ip: '1.1.1.3' },
{ name: 'Comodo Secure', ip: '8.26.56.26' },
{ name: 'Google DNS', ip: '8.8.8.8' },
{ name: 'Neustar Family', ip: '156.154.70.3' },
{ name: 'Neustar Protection', ip: '156.154.70.2' },
{ name: 'Norton Family', ip: '199.85.126.20' },
{ name: 'OpenDNS', ip: '208.67.222.222' },
{ name: 'OpenDNS Family', ip: '208.67.222.123' },
{ name: 'Quad9', ip: '9.9.9.9' },
{ name: 'Yandex Family', ip: '77.88.8.7' },
{ name: 'Yandex Safe', ip: '77.88.8.88' },
];
const knownBlockIPs = [
'146.112.61.106', // OpenDNS
'185.228.168.10', // CleanBrowsing
'8.26.56.26', // Comodo
'9.9.9.9', // Quad9
'208.69.38.170', // Some OpenDNS IPs
'208.69.39.170', // Some OpenDNS IPs
'208.67.222.222', // OpenDNS
'208.67.222.123', // OpenDNS FamilyShield
'199.85.126.10', // Norton
'199.85.126.20', // Norton Family
'156.154.70.22', // Neustar
'77.88.8.7', // Yandex
'77.88.8.8', // Yandex
'::1', // Localhost IPv6
'2a02:6b8::feed:0ff', // Yandex DNS
'2a02:6b8::feed:bad', // Yandex Safe
'2a02:6b8::feed:a11', // Yandex Family
'2620:119:35::35', // OpenDNS
'2620:119:53::53', // OpenDNS FamilyShield
'2606:4700:4700::1111', // Cloudflare
'2606:4700:4700::1001', // Cloudflare
'2001:4860:4860::8888', // Google DNS
'2a0d:2a00:1::', // AdGuard
'2a0d:2a00:2::' // AdGuard Family
];
const isDomainBlocked = async (domain, serverIP) => {
return new Promise((resolve) => {
dns.resolve4(domain, { server: serverIP }, (err, addresses) => {
if (!err) {
if (addresses.some(addr => knownBlockIPs.includes(addr))) {
resolve(true);
return;
}
resolve(false);
return;
}
dns.resolve6(domain, { server: serverIP }, (err6, addresses6) => {
if (!err6) {
if (addresses6.some(addr => knownBlockIPs.includes(addr))) {
resolve(true);
return;
}
resolve(false);
return;
}
if (err6.code === 'ENOTFOUND' || err6.code === 'SERVFAIL') {
resolve(true);
} else {
resolve(false);
}
});
});
});
};
const checkDomainAgainstDnsServers = async (domain) => {
let results = [];
for (let server of DNS_SERVERS) {
const isBlocked = await isDomainBlocked(domain, server.ip);
results.push({
server: server.name,
serverIp: server.ip,
isBlocked,
});
}
return results;
};
export const blockListHandler = async (url) => {
const domain = new URL(url).hostname;
const results = await checkDomainAgainstDnsServers(domain);
return { blocklists: results };
};
export const handler = middleware(blockListHandler);
export default handler;
================================================
FILE: api/carbon.js
================================================
import https from 'https';
import middleware from './_common/middleware.js';
const carbonHandler = async (url) => {
// First, get the size of the website's HTML
const getHtmlSize = (url) => new Promise((resolve, reject) => {
https.get(url, res => {
let data = '';
res.on('data', chunk => {
data += chunk;
});
res.on('end', () => {
const sizeInBytes = Buffer.byteLength(data, 'utf8');
resolve(sizeInBytes);
});
}).on('error', reject);
});
try {
const sizeInBytes = await getHtmlSize(url);
const apiUrl = `https://api.websitecarbon.com/data?bytes=${sizeInBytes}&green=0`;
// Then use that size to get the carbon data
const carbonData = await new Promise((resolve, reject) => {
https.get(apiUrl, res => {
let data = '';
res.on('data', chunk => {
data += chunk;
});
res.on('end', () => {
// Check if response looks like HTML (e.g., Cloudflare challenge page)
const trimmedData = data.trim();
if (trimmedData.startsWith('<!DOCTYPE') || trimmedData.startsWith('<html') || trimmedData.startsWith('<')) {
reject(new Error('WebsiteCarbon API returned HTML instead of JSON. This may be due to Cloudflare protection when running from a datacenter IP.'));
return;
}
try {
resolve(JSON.parse(data));
} catch (parseError) {
reject(new Error(`Failed to parse WebsiteCarbon API response as JSON: ${parseError.message}`));
}
});
}).on('error', reject);
});
if (!carbonData.statistics || (carbonData.statistics.adjustedBytes === 0 && carbonData.statistics.energy === 0)) {
return {
statusCode: 200,
body: JSON.stringify({ skipped: 'Not enough info to get carbon data' }),
};
}
carbonData.scanUrl = url;
return carbonData;
} catch (error) {
throw new Error(`Error: ${error.message}`);
}
};
export const handler = middleware(carbonHandler);
export default handler;
================================================
FILE: api/cookies.js
================================================
import axios from 'axios';
import puppeteer from 'puppeteer';
import middleware from './_common/middleware.js';
const getPuppeteerCookies = async (url) => {
const browser = await puppeteer.launch({
headless: 'new',
args: ['--no-sandbox', '--disable-setuid-sandbox'],
});
try {
const page = await browser.newPage();
const navigationPromise = page.goto(url, { waitUntil: 'networkidle2' });
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Puppeteer took too long!')), 3000)
);
await Promise.race([navigationPromise, timeoutPromise]);
return await page.cookies();
} finally {
await browser.close();
}
};
const cookieHandler = async (url) => {
let headerCookies = null;
let clientCookies = null;
try {
const response = await axios.get(url, {
withCredentials: true,
maxRedirects: 5,
});
headerCookies = response.headers['set-cookie'];
} catch (error) {
if (error.response) {
return { error: `Request failed with status ${error.response.status}: ${error.message}` };
} else if (error.request) {
return { error: `No response received: ${error.message}` };
} else {
return { error: `Error setting up request: ${error.message}` };
}
}
try {
clientCookies = await getPuppeteerCookies(url);
} catch (_) {
clientCookies = null;
}
if (!headerCookies && (!clientCookies || clientCookies.length === 0)) {
return { skipped: 'No cookies' };
}
return { headerCookies, clientCookies };
};
export const handler = middleware(cookieHandler);
export default handler;
================================================
FILE: api/dns-server.js
================================================
import { promises as dnsPromises, lookup } from 'dns';
import axios from 'axios';
import middleware from './_common/middleware.js';
const dnsHandler = async (url) => {
try {
const domain = url.replace(/^(?:https?:\/\/)?/i, "");
const addresses = await dnsPromises.resolve4(domain);
const results = await Promise.all(addresses.map(async (address) => {
const hostname = await dnsPromises.reverse(address).catch(() => null);
let dohDirectSupports = false;
try {
await axios.get(`https://${address}/dns-query`);
dohDirectSupports = true;
} catch (error) {
dohDirectSupports = false;
}
return {
address,
hostname,
dohDirectSupports,
};
}));
// let dohMozillaSupport = false;
// try {
// const mozillaList = await axios.get('https://firefox.settings.services.mozilla.com/v1/buckets/security-state/collections/onecrl/records');
// dohMozillaSupport = results.some(({ hostname }) => mozillaList.data.data.some(({ id }) => id.includes(hostname)));
// } catch (error) {
// console.error(error);
// }
return {
domain,
dns: results,
// dohMozillaSupport,
};
} catch (error) {
throw new Error(`An error occurred while resolving DNS. ${error.message}`); // This will be caught and handled by the commonMiddleware
}
};
export const handler = middleware(dnsHandler);
export default handler;
================================================
FILE: api/dns.js
================================================
import dns from 'dns';
import util from 'util';
import middleware from './_common/middleware.js';
const dnsHandler = async (url) => {
let hostname = url;
// Handle URLs by extracting hostname
if (hostname.startsWith('http://') || hostname.startsWith('https://')) {
hostname = new URL(hostname).hostname;
}
try {
const lookupPromise = util.promisify(dns.lookup);
const resolve4Promise = util.promisify(dns.resolve4);
const resolve6Promise = util.promisify(dns.resolve6);
const resolveMxPromise = util.promisify(dns.resolveMx);
const resolveTxtPromise = util.promisify(dns.resolveTxt);
const resolveNsPromise = util.promisify(dns.resolveNs);
const resolveCnamePromise = util.promisify(dns.resolveCname);
const resolveSoaPromise = util.promisify(dns.resolveSoa);
const resolveSrvPromise = util.promisify(dns.resolveSrv);
const resolvePtrPromise = util.promisify(dns.resolvePtr);
const [a, aaaa, mx, txt, ns, cname, soa, srv, ptr] = await Promise.all([
lookupPromise(hostname),
resolve4Promise(hostname).catch(() => []), // A record
resolve6Promise(hostname).catch(() => []), // AAAA record
resolveMxPromise(hostname).catch(() => []), // MX record
resolveTxtPromise(hostname).catch(() => []), // TXT record
resolveNsPromise(hostname).catch(() => []), // NS record
resolveCnamePromise(hostname).catch(() => []), // CNAME record
resolveSoaPromise(hostname).catch(() => []), // SOA record
resolveSrvPromise(hostname).catch(() => []), // SRV record
resolvePtrPromise(hostname).catch(() => []) // PTR record
]);
return {
A: a,
AAAA: aaaa,
MX: mx,
TXT: txt,
NS: ns,
CNAME: cname,
SOA: soa,
SRV: srv,
PTR: ptr
};
} catch (error) {
throw new Error(error.message);
}
};
export const handler = middleware(dnsHandler);
export default handler;
================================================
FILE: api/dnssec.js
================================================
import https from 'https';
import middleware from './_common/middleware.js';
const dnsSecHandler = async (domain) => {
const dnsTypes = ['DNSKEY', 'DS', 'RRSIG'];
const records = {};
for (const type of dnsTypes) {
const options = {
hostname: 'dns.google',
path: `/resolve?name=${encodeURIComponent(domain)}&type=${type}`,
method: 'GET',
headers: {
'Accept': 'application/dns-json'
}
};
try {
const dnsResponse = await new Promise((resolve, reject) => {
const req = https.request(options, res => {
let data = '';
res.on('data', chunk => {
data += chunk;
});
res.on('end', () => {
try {
resolve(JSON.parse(data));
} catch (error) {
reject(new Error('Invalid JSON response'));
}
});
res.on('error', error => {
reject(error);
});
});
req.end();
});
if (dnsResponse.Answer) {
records[type] = { isFound: true, answer: dnsResponse.Answer, response: dnsResponse.Answer };
} else {
records[type] = { isFound: false, answer: null, response: dnsResponse };
}
} catch (error) {
throw new Error(`Error fetching ${type} record: ${error.message}`); // This will be caught and handled by the commonMiddleware
}
}
return records;
};
export const handler = middleware(dnsSecHandler);
export default handler;
================================================
FILE: api/features.js
================================================
import https from 'https';
import middleware from './_common/middleware.js';
const featuresHandler = async (url) => {
const apiKey = process.env.BUILT_WITH_API_KEY;
if (!url) {
throw new Error('URL query parameter is required');
}
if (!apiKey) {
throw new Error('Missing BuiltWith API key in environment variables');
}
const apiUrl = `https://api.builtwith.com/free1/api.json?KEY=${apiKey}&LOOKUP=${encodeURIComponent(url)}`;
try {
const response = await new Promise((resolve, reject) => {
const req = https.get(apiUrl, res => {
let data = '';
res.on('data', chunk => {
data += chunk;
});
res.on('end', () => {
if (res.statusCode >= 200 && res.statusCode <= 299) {
resolve(data);
} else {
reject(new Error(`Request failed with status code: ${res.statusCode}`));
}
});
});
req.on('error', error => {
reject(error);
});
req.end();
});
return response;
} catch (error) {
throw new Error(`Error making request: ${error.message}`);
}
};
export const handler = middleware(featuresHandler);
export default handler;
================================================
FILE: api/firewall.js
================================================
import axios from 'axios';
import middleware from './_common/middleware.js';
const hasWaf = (waf) => {
return {
hasWaf: true, waf,
}
};
const firewallHandler = async (url) => {
const fullUrl = url.startsWith('http') ? url : `http://${url}`;
try {
const response = await axios.get(fullUrl);
const headers = response.headers;
if (headers['server'] && headers['server'].includes('cloudflare')) {
return hasWaf('Cloudflare');
}
if (headers['x-powered-by'] && headers['x-powered-by'].includes('AWS Lambda')) {
return hasWaf('AWS WAF');
}
if (headers['server'] && headers['server'].includes('AkamaiGHost')) {
return hasWaf('Akamai');
}
if (headers['server'] && headers['server'].includes('Sucuri')) {
return hasWaf('Sucuri');
}
if (headers['server'] && headers['server'].includes('BarracudaWAF')) {
return hasWaf('Barracuda WAF');
}
if (headers['server'] && (headers['server'].includes('F5 BIG-IP') || headers['server'].includes('BIG-IP'))) {
return hasWaf('F5 BIG-IP');
}
if (headers['x-sucuri-id'] || headers['x-sucuri-cache']) {
return hasWaf('Sucuri CloudProxy WAF');
}
if (headers['server'] && headers['server'].includes('FortiWeb')) {
return hasWaf('Fortinet FortiWeb WAF');
}
if (headers['server'] && headers['server'].includes('Imperva')) {
return hasWaf('Imperva SecureSphere WAF');
}
if (headers['x-protected-by'] && headers['x-protected-by'].includes('Sqreen')) {
return hasWaf('Sqreen');
}
if (headers['x-waf-event-info']) {
return hasWaf('Reblaze WAF');
}
if (headers['set-cookie'] && headers['set-cookie'].includes('_citrix_ns_id')) {
return hasWaf('Citrix NetScaler');
}
if (headers['x-denied-reason'] || headers['x-wzws-requested-method']) {
return hasWaf('WangZhanBao WAF');
}
if (headers['x-webcoment']) {
return hasWaf('Webcoment Firewall');
}
if (headers['server'] && headers['server'].includes('Yundun')) {
return hasWaf('Yundun WAF');
}
if (headers['x-yd-waf-info'] || headers['x-yd-info']) {
return hasWaf('Yundun WAF');
}
if (headers['server'] && headers['server'].includes('Safe3WAF')) {
return hasWaf('Safe3 Web Application Firewall');
}
if (headers['server'] && headers['server'].includes('NAXSI')) {
return hasWaf('NAXSI WAF');
}
if (headers['x-datapower-transactionid']) {
return hasWaf('IBM WebSphere DataPower');
}
if (headers['server'] && headers['server'].includes('QRATOR')) {
return hasWaf('QRATOR WAF');
}
if (headers['server'] && headers['server'].includes('ddos-guard')) {
return hasWaf('DDoS-Guard WAF');
}
return {
hasWaf: false,
}
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({ error: error.message }),
};
}
};
export const handler = middleware(firewallHandler);
export default handler;
================================================
FILE: api/get-ip.js
================================================
import dns from 'dns';
import middleware from './_common/middleware.js';
const lookupAsync = (address) => {
return new Promise((resolve, reject) => {
dns.lookup(address, (err, ip, family) => {
if (err) {
reject(err);
} else {
resolve({ ip, family });
}
});
});
};
const ipHandler = async (url) => {
const address = url.replaceAll('https://', '').replaceAll('http://', '');
return await lookupAsync(address);
};
export const handler = middleware(ipHandler);
export default handler;
================================================
FILE: api/headers.js
================================================
import axios from 'axios';
import middleware from './_common/middleware.js';
const headersHandler = async (url, event, context) => {
try {
const response = await axios.get(url, {
validateStatus: function (status) {
return status >= 200 && status < 600; // Resolve only if the status code is less than 600
},
});
return response.headers;
} catch (error) {
throw new Error(error.message);
}
};
export const handler = middleware(headersHandler);
export default handler;
================================================
FILE: api/hsts.js
================================================
import https from 'https';
import middleware from './_common/middleware.js';
const hstsHandler = async (url, event, context) => {
const errorResponse = (message, statusCode = 500) => {
return {
statusCode: statusCode,
body: JSON.stringify({ error: message }),
};
};
const hstsIncompatible = (message, compatible = false, hstsHeader = null ) => {
return { message, compatible, hstsHeader };
};
return new Promise((resolve, reject) => {
const req = https.request(url, res => {
const headers = res.headers;
const hstsHeader = headers['strict-transport-security'];
if (!hstsHeader) {
resolve(hstsIncompatible(`Site does not serve any HSTS headers.`));
} else {
const maxAgeMatch = hstsHeader.match(/max-age=(\d+)/);
const includesSubDomains = hstsHeader.includes('includeSubDomains');
const preload = hstsHeader.includes('preload');
if (!maxAgeMatch || parseInt(maxAgeMatch[1]) < 10886400) {
resolve(hstsIncompatible(`HSTS max-age is less than 10886400.`));
} else if (!includesSubDomains) {
resolve(hstsIncompatible(`HSTS header does not include all subdomains.`));
} else if (!preload) {
resolve(hstsIncompatible(`HSTS header does not contain the preload directive.`));
} else {
resolve(hstsIncompatible(`Site is compatible with the HSTS preload list!`, true, hstsHeader));
}
}
});
req.on('error', (error) => {
resolve(errorResponse(`Error making request: ${error.message}`));
});
req.end();
});
};
export const handler = middleware(hstsHandler);
export default handler;
================================================
FILE: api/http-security.js
================================================
import axios from 'axios';
import middleware from './_common/middleware.js';
const httpsSecHandler = async (url) => {
const fullUrl = url.startsWith('http') ? url : `http://${url}`;
try {
const response = await axios.get(fullUrl);
const headers = response.headers;
return {
strictTransportPolicy: headers['strict-transport-security'] ? true : false,
xFrameOptions: headers['x-frame-options'] ? true : false,
xContentTypeOptions: headers['x-content-type-options'] ? true : false,
xXSSProtection: headers['x-xss-protection'] ? true : false,
contentSecurityPolicy: headers['content-security-policy'] ? true : false,
}
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({ error: error.message }),
};
}
};
export const handler = middleware(httpsSecHandler);
export default handler;
================================================
FILE: api/legacy-rank.js
================================================
import axios from 'axios';
import unzipper from 'unzipper';
import csv from 'csv-parser';
import fs from 'fs';
import middleware from './_common/middleware.js';
// Should also work with the following sources:
// https://www.domcop.com/files/top/top10milliondomains.csv.zip
// https://tranco-list.eu/top-1m.csv.zip
// https://www.domcop.com/files/top/top10milliondomains.csv.zip
// https://radar.cloudflare.com/charts/LargerTopDomainsTable/attachment?id=525&top=1000000
// https://statvoo.com/dl/top-1million-sites.csv.zip
const FILE_URL = 'https://s3-us-west-1.amazonaws.com/umbrella-static/top-1m.csv.zip';
const TEMP_FILE_PATH = '/tmp/top-1m.csv';
const rankHandler = async (url) => {
let domain = null;
try {
domain = new URL(url).hostname;
} catch (e) {
throw new Error('Invalid URL');
}
// Download and unzip the file if not in cache
if (!fs.existsSync(TEMP_FILE_PATH)) {
const response = await axios({
method: 'GET',
url: FILE_URL,
responseType: 'stream'
});
await new Promise((resolve, reject) => {
response.data
.pipe(unzipper.Extract({ path: '/tmp' }))
.on('close', resolve)
.on('error', reject);
});
}
// Parse the CSV and find the rank
return new Promise((resolve, reject) => {
const csvStream = fs.createReadStream(TEMP_FILE_PATH)
.pipe(csv({
headers: ['rank', 'domain'],
}))
.on('data', (row) => {
if (row.domain === domain) {
csvStream.destroy();
resolve({
domain: domain,
rank: row.rank,
isFound: true,
});
}
})
.on('end', () => {
resolve({
skipped: `Skipping, as ${domain} is not present in the Umbrella top 1M list.`,
domain: domain,
isFound: false,
});
})
.on('error', reject);
});
};
export const handler = middleware(rankHandler);
export default handler;
================================================
FILE: api/linked-pages.js
================================================
import axios from 'axios';
import cheerio from 'cheerio';
import urlLib from 'url';
import middleware from './_common/middleware.js';
const linkedPagesHandler = async (url) => {
const response = await axios.get(url);
const html = response.data;
const $ = cheerio.load(html);
const internalLinksMap = new Map();
const externalLinksMap = new Map();
// Get all links on the page
$('a[href]').each((i, link) => {
const href = $(link).attr('href');
const absoluteUrl = urlLib.resolve(url, href);
// Check if absolute / relative, append to appropriate map or increment occurrence count
if (absoluteUrl.startsWith(url)) {
const count = internalLinksMap.get(absoluteUrl) || 0;
internalLinksMap.set(absoluteUrl, count + 1);
} else if (href.startsWith('http://') || href.startsWith('https://')) {
const count = externalLinksMap.get(absoluteUrl) || 0;
externalLinksMap.set(absoluteUrl, count + 1);
}
});
// Sort by most occurrences, remove supplicates, and convert to array
const internalLinks = [...internalLinksMap.entries()].sort((a, b) => b[1] - a[1]).map(entry => entry[0]);
const externalLinks = [...externalLinksMap.entries()].sort((a, b) => b[1] - a[1]).map(entry => entry[0]);
// If there were no links, then mark as skipped and show reasons
if (internalLinks.length === 0 && externalLinks.length === 0) {
return {
statusCode: 400,
body: {
skipped: 'No internal or external links found. '
+ 'This may be due to the website being dynamically rendered, using a client-side framework (like React), and without SSR enabled. '
+ 'That would mean that the static HTML returned from the HTTP request doesn\'t contain any meaningful content for Web-Check to analyze. '
+ 'You can rectify this by using a headless browser to render the page instead.',
},
};
}
return { internal: internalLinks, external: externalLinks };
};
export const handler = middleware(linkedPagesHandler);
export default handler;
================================================
FILE: api/mail-config.js
================================================
import dns from 'dns';
import URL from 'url-parse';
import middleware from './_common/middleware.js';
// TODO: Fix.
const mailConfigHandler = async (url, event, context) => {
try {
const domain = new URL(url).hostname || new URL(url).pathname;
// Get MX records
const mxRecords = await dns.resolveMx(domain);
// Get TXT records
const txtRecords = await dns.resolveTxt(domain);
// Filter for only email related TXT records (SPF, DKIM, DMARC, and certain provider verifications)
const emailTxtRecords = txtRecords.filter(record => {
const recordString = record.join('');
return (
recordString.startsWith('v=spf1') ||
recordString.startsWith('v=DKIM1') ||
recordString.startsWith('v=DMARC1') ||
recordString.startsWith('protonmail-verification=') ||
recordString.startsWith('google-site-verification=') || // Google Workspace
recordString.startsWith('MS=') || // Microsoft 365
recordString.startsWith('zoho-verification=') || // Zoho
recordString.startsWith('titan-verification=') || // Titan
recordString.includes('bluehost.com') // BlueHost
);
});
// Identify specific mail services
const mailServices = emailTxtRecords.map(record => {
const recordString = record.join('');
if (recordString.startsWith('protonmail-verification=')) {
return { provider: 'ProtonMail', value: recordString.split('=')[1] };
} else if (recordString.startsWith('google-site-verification=')) {
return { provider: 'Google Workspace', value: recordString.split('=')[1] };
} else if (recordString.startsWith('MS=')) {
return { provider: 'Microsoft 365', value: recordString.split('=')[1] };
} else if (recordString.startsWith('zoho-verification=')) {
return { provider: 'Zoho', value: recordString.split('=')[1] };
} else if (recordString.startsWith('titan-verification=')) {
return { provider: 'Titan', value: recordString.split('=')[1] };
} else if (recordString.includes('bluehost.com')) {
return { provider: 'BlueHost', value: recordString };
} else {
return null;
}
}).filter(record => record !== null);
// Check MX records for Yahoo
const yahooMx = mxRecords.filter(record => record.exchange.includes('yahoodns.net'));
if (yahooMx.length > 0) {
mailServices.push({ provider: 'Yahoo', value: yahooMx[0].exchange });
}
// Check MX records for Mimecast
const mimecastMx = mxRecords.filter(record => record.exchange.includes('mimecast.com'));
if (mimecastMx.length > 0) {
mailServices.push({ provider: 'Mimecast', value: mimecastMx[0].exchange });
}
return {
mxRecords,
txtRecords: emailTxtRecords,
mailServices,
};
} catch (error) {
if (error.code === 'ENOTFOUND' || error.code === 'ENODATA') {
return { skipped: 'No mail server in use on this domain' };
} else {
return {
statusCode: 500,
body: { error: error.message },
};
}
}
};
export const handler = middleware(mailConfigHandler);
export default handler;
================================================
FILE: api/ports.js
================================================
import net from 'net';
import middleware from './_common/middleware.js';
// A list of commonly used ports.
const DEFAULT_PORTS_TO_CHECK = [
20, 21, 22, 23, 25, 53, 80, 67, 68, 69,
110, 119, 123, 143, 156, 161, 162, 179, 194,
389, 443, 587, 993, 995,
3000, 3306, 3389, 5060, 5900, 8000, 8080, 8888
];
/*
* Checks if the env PORTS_TO_CHECK is set, if so the string is split via "," to get an array of ports to check.
* If the env is not set, return the default commonly used ports.
*/
const PORTS = process.env.PORTS_TO_CHECK ? process.env.PORTS_TO_CHECK.split(",") : DEFAULT_PORTS_TO_CHECK
async function checkPort(port, domain) {
return new Promise((resolve, reject) => {
const socket = new net.Socket();
socket.setTimeout(1500);
socket.once('connect', () => {
socket.destroy();
resolve(port);
});
socket.once('timeout', () => {
socket.destroy();
reject(new Error(`Timeout at port: ${port}`));
});
socket.once('error', (e) => {
socket.destroy();
reject(e);
});
socket.connect(port, domain);
});
}
const portsHandler = async (url, event, context) => {
const domain = url.replace(/(^\w+:|^)\/\//, '');
const delay = ms => new Promise(res => setTimeout(res, ms));
const timeout = delay(9000);
const openPorts = [];
const failedPorts = [];
const promises = PORTS.map(port => checkPort(port, domain)
.then(() => {
openPorts.push(port);
return { status: 'fulfilled', port };
})
.catch(() => {
failedPorts.push(port);
return { status: 'rejected', port };
}));
let timeoutReached = false;
for (const promise of promises) {
const result = await Promise.race([promise, timeout.then(() => ({ status: 'timeout', timeout: true }))]);
if (result.status === 'timeout') {
timeoutReached = true;
if (result.timeout) {
// Add the ports not checked yet to the failedPorts array
const checkedPorts = [...openPorts, ...failedPorts];
const portsNotChecked = PORTS.filter(port => !checkedPorts.includes(port));
failedPorts.push(...portsNotChecked);
}
break;
}
}
if(timeoutReached){
return errorResponse('The function timed out before completing.');
}
// Sort openPorts and failedPorts before returning
openPorts.sort((a, b) => a - b);
failedPorts.sort((a, b) => a - b);
return { openPorts, failedPorts };
};
const errorResponse = (message, statusCode = 444) => {
return { error: message };
};
export const handler = middleware(portsHandler);
export default handler;
================================================
FILE: api/quality.js
================================================
import axios from 'axios';
import middleware from './_common/middleware.js';
const qualityHandler = async (url, event, context) => {
const apiKey = process.env.GOOGLE_CLOUD_API_KEY;
if (!apiKey) {
throw new Error(
'Missing Google API. You need to set the `GOOGLE_CLOUD_API_KEY` environment variable'
);
}
const endpoint = `https://www.googleapis.com/pagespeedonline/v5/runPagespeed?`
+ `url=${encodeURIComponent(url)}&category=PERFORMANCE&category=ACCESSIBILITY`
+ `&category=BEST_PRACTICES&category=SEO&category=PWA&strategy=mobile`
+ `&key=${apiKey}`;
return (await axios.get(endpoint)).data;
};
export const handler = middleware(qualityHandler);
export default handler;
================================================
FILE: api/rank.js
================================================
import axios from 'axios';
import middleware from './_common/middleware.js';
const rankHandler = async (url) => {
const domain = url ? new URL(url).hostname : null;
if (!domain) throw new Error('Invalid URL');
try {
const auth = process.env.TRANCO_API_KEY ? // Auth is optional.
{ auth: { username: process.env.TRANCO_USERNAME, password: process.env.TRANCO_API_KEY } }
: {};
const response = await axios.get(
`https://tranco-list.eu/api/ranks/domain/${domain}`, { timeout: 5000 }, auth,
);
if (!response.data || !response.data.ranks || response.data.ranks.length === 0) {
return { skipped: `Skipping, as ${domain} isn't ranked in the top 100 million sites yet.`};
}
return response.data;
} catch (error) {
return { error: `Unable to fetch rank, ${error.message}` };
}
};
export const handler = middleware(rankHandler);
export default handler;
================================================
FILE: api/redirects.js
================================================
import got from 'got';
import middleware from './_common/middleware.js';
const redirectsHandler = async (url) => {
const redirects = [url];
try {
await got(url, {
followRedirect: true,
maxRedirects: 12,
hooks: {
beforeRedirect: [
(options, response) => {
redirects.push(response.headers.location);
},
],
},
});
return {
redirects: redirects,
};
} catch (error) {
throw new Error(`Error: ${error.message}`);
}
};
export const handler = middleware(redirectsHandler);
export default handler;
================================================
FILE: api/robots-txt.js
================================================
import axios from 'axios';
import middleware from './_common/middleware.js';
const parseRobotsTxt = (content) => {
const lines = content.split('\n');
const rules = [];
lines.forEach(line => {
line = line.trim(); // This removes trailing and leading whitespaces
let match = line.match(/^(Allow|Disallow):\s*(\S*)$/i);
if (match) {
const rule = {
lbl: match[1],
val: match[2],
};
rules.push(rule);
} else {
match = line.match(/^(User-agent):\s*(\S*)$/i);
if (match) {
const rule = {
lbl: match[1],
val: match[2],
};
rules.push(rule);
}
}
});
return { robots: rules };
}
const robotsHandler = async function(url) {
let parsedURL;
try {
parsedURL = new URL(url);
} catch (error) {
return {
statusCode: 400,
body: JSON.stringify({ error: 'Invalid url query parameter' }),
};
}
const robotsURL = `${parsedURL.protocol}//${parsedURL.hostname}/robots.txt`;
try {
const response = await axios.get(robotsURL);
if (response.status === 200) {
const parsedData = parseRobotsTxt(response.data);
if (!parsedData.robots || parsedData.robots.length === 0) {
return { skipped: 'No robots.txt file present, unable to continue' };
}
return parsedData;
} else {
return {
statusCode: response.status,
body: JSON.stringify({ error: 'Failed to fetch robots.txt', statusCode: response.status }),
};
}
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({ error: `Error fetching robots.txt: ${error.message}` }),
};
}
};
export const handler = middleware(robotsHandler);
export default handler;
================================================
FILE: api/screenshot.js
================================================
import puppeteer from 'puppeteer-core';
import chromium from 'chrome-aws-lambda';
import middleware from './_common/middleware.js';
import { execFile } from 'child_process';
import { promises as fs } from 'fs';
import path from 'path';
import pkg from 'uuid';
const { v4: uuidv4 } = pkg;
// Helper function for direct chromium screenshot as fallback
const directChromiumScreenshot = async (url) => {
console.log(`[DIRECT-SCREENSHOT] Starting direct screenshot process for URL: ${url}`);
// Create a tmp filename
const tmpDir = '/tmp';
const uuid = uuidv4();
const screenshotPath = path.join(tmpDir, `screenshot-${uuid}.png`);
console.log(`[DIRECT-SCREENSHOT] Will save screenshot to: ${screenshotPath}`);
return new Promise((resolve, reject) => {
const chromePath = process.env.CHROME_PATH || '/usr/bin/chromium';
const args = [
'--headless',
'--disable-gpu',
'--no-sandbox',
`--screenshot=${screenshotPath}`,
url
];
console.log(`[DIRECT-SCREENSHOT] Executing: ${chromePath} ${args.join(' ')}`);
execFile(chromePath, args, async (error, stdout, stderr) => {
if (error) {
console.error(`[DIRECT-SCREENSHOT] Chromium error: ${error.message}`);
return reject(error);
}
try {
// Read the screenshot file
const screenshotData = await fs.readFile(screenshotPath);
console.log(`[DIRECT-SCREENSHOT] Screenshot read successfully`);
// Convert to base64
const base64Data = screenshotData.toString('base64');
await fs.unlink(screenshotPath).catch(err =>
console.warn(`[DIRECT-SCREENSHOT] Failed to delete temp file: ${err.message}`)
);
resolve(base64Data);
} catch (readError) {
console.error(`[DIRECT-SCREENSHOT] Failed reading screenshot: ${readError.message}`);
reject(readError);
}
});
});
};
const screenshotHandler = async (targetUrl) => {
console.log(`[SCREENSHOT] Request received for URL: ${targetUrl}`);
if (!targetUrl) {
console.error('[SCREENSHOT] URL is missing from queryStringParameters');
throw new Error('URL is missing from queryStringParameters');
}
if (!targetUrl.startsWith('http://') && !targetUrl.startsWith('https://')) {
targetUrl = 'http://' + targetUrl;
}
try {
new URL(targetUrl);
} catch (error) {
console.error(`[SCREENSHOT] URL provided is invalid: ${targetUrl}`);
throw new Error('URL provided is invalid');
}
// First try direct Chromium
try {
console.log(`[SCREENSHOT] Using direct Chromium method for URL: ${targetUrl}`);
const base64Screenshot = await directChromiumScreenshot(targetUrl);
console.log(`[SCREENSHOT] Direct screenshot successful`);
return { image: base64Screenshot };
} catch (directError) {
console.error(`[SCREENSHOT] Direct screenshot method failed: ${directError.message}`);
console.log(`[SCREENSHOT] Falling back to puppeteer method...`);
}
// fall back puppeteer
let browser = null;
try {
console.log(`[SCREENSHOT] Launching puppeteer browser`);
browser = await puppeteer.launch({
args: [...chromium.args, '--no-sandbox'], // Add --no-sandbox flag
defaultViewport: { width: 800, height: 600 },
executablePath: process.env.CHROME_PATH || '/usr/bin/chromium',
headless: true,
ignoreHTTPSErrors: true,
ignoreDefaultArgs: ['--disable-extensions'],
});
console.log(`[SCREENSHOT] Creating new page`);
let page = await browser.newPage();
console.log(`[SCREENSHOT] Setting page preferences`);
await page.emulateMediaFeatures([{ name: 'prefers-color-scheme', value: 'dark' }]);
page.setDefaultNavigationTimeout(8000);
console.log(`[SCREENSHOT] Navigating to URL: ${targetUrl}`);
await page.goto(targetUrl, { waitUntil: 'domcontentloaded' });
console.log(`[SCREENSHOT] Checking if body element exists`);
await page.evaluate(() => {
const selector = 'body';
return new Promise((resolve, reject) => {
const element = document.querySelector(selector);
if (!element) {
reject(new Error(`Error: No element found with selector: ${selector}`));
}
resolve();
});
});
console.log(`[SCREENSHOT] Taking screenshot`);
const screenshotBuffer = await page.screenshot();
console.log(`[SCREENSHOT] Converting screenshot to base64`);
const base64Screenshot = screenshotBuffer.toString('base64');
console.log(`[SCREENSHOT] Screenshot complete, returning image`);
return { image: base64Screenshot };
} catch (error) {
console.error(`[SCREENSHOT] Puppeteer screenshot failed: ${error.message}`);
throw error;
} finally {
if (browser !== null) {
console.log(`[SCREENSHOT] Closing browser`);
await browser.close();
}
}
};
export const handler = middleware(screenshotHandler);
export default handler;
================================================
FILE: api/security-txt.js
================================================
import { URL } from 'url';
import followRedirects from 'follow-redirects';
import middleware from './_common/middleware.js';
const { https } = followRedirects;
const SECURITY_TXT_PATHS = [
'/security.txt',
'/.well-known/security.txt',
];
const parseResult = (result) => {
let output = {};
let counts = {};
const lines = result.split('\n');
const regex = /^([^:]+):\s*(.+)$/;
for (const line of lines) {
if (!line.startsWith("#") && !line.startsWith("-----") && line.trim() !== '') {
const match = line.match(regex);
if (match && match.length > 2) {
let key = match[1].trim();
const value = match[2].trim();
if (output.hasOwnProperty(key)) {
counts[key] = counts[key] ? counts[key] + 1 : 1;
key += counts[key];
}
output[key] = value;
}
}
}
return output;
};
const isPgpSigned = (result) => {
if (result.includes('-----BEGIN PGP SIGNED MESSAGE-----')) {
return true;
}
return false;
};
const securityTxtHandler = async (urlParam) => {
let url;
try {
url = new URL(urlParam.includes('://') ? urlParam : 'https://' + urlParam);
} catch (error) {
throw new Error('Invalid URL format');
}
url.pathname = '';
for (let path of SECURITY_TXT_PATHS) {
try {
const result = await fetchSecurityTxt(url, path);
if (result && result.includes('<html')) return { isPresent: false };
if (result) {
return {
isPresent: true,
foundIn: path,
content: result,
isPgpSigned: isPgpSigned(result),
fields: parseResult(result),
};
}
} catch (error) {
throw new Error(error.message);
}
}
return { isPresent: false };
};
async function fetchSecurityTxt(baseURL, path) {
return new Promise((resolve, reject) => {
const url = new URL(path, baseURL);
https.get(url.toString(), (res) => {
if (res.statusCode === 200) {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
resolve(data);
});
} else {
resolve(null);
}
}).on('error', (err) => {
reject(err);
});
});
}
export const handler = middleware(securityTxtHandler);
export default handler;
================================================
FILE: api/sitemap.js
================================================
import axios from 'axios';
import xml2js from 'xml2js';
import middleware from './_common/middleware.js';
const sitemapHandler = async (url) => {
let sitemapUrl = `${url}/sitemap.xml`;
const hardTimeOut = 5000;
try {
// Try to fetch sitemap directly
let sitemapRes;
try {
sitemapRes = await axios.get(sitemapUrl, { timeout: hardTimeOut });
} catch (error) {
if (error.response && error.response.status === 404) {
// If sitemap not found, try to fetch it from robots.txt
const robotsRes = await axios.get(`${url}/robots.txt`, { timeout: hardTimeOut });
const robotsTxt = robotsRes.data.split('\n');
for (let line of robotsTxt) {
if (line.toLowerCase().startsWith('sitemap:')) {
sitemapUrl = line.split(' ')[1].trim();
break;
}
}
if (!sitemapUrl) {
return { skipped: 'No sitemap found' };
}
sitemapRes = await axios.get(sitemapUrl, { timeout: hardTimeOut });
} else {
throw error; // If other error, throw it
}
}
const parser = new xml2js.Parser();
const sitemap = await parser.parseStringPromise(sitemapRes.data);
return sitemap;
} catch (error) {
if (error.code === 'ECONNABORTED') {
return { error: `Request timed-out after ${hardTimeOut}ms` };
} else {
return { error: error.message };
}
}
};
export const handler = middleware(sitemapHandler);
export default handler;
================================================
FILE: api/social-tags.js
================================================
import axios from 'axios';
import cheerio from 'cheerio';
import middleware from './_common/middleware.js';
const socialTagsHandler = async (url) => {
// Check if url includes protocol
if (!url.startsWith('http://') && !url.startsWith('https://')) {
url = 'http://' + url;
}
try {
const response = await axios.get(url);
const html = response.data;
const $ = cheerio.load(html);
const metadata = {
// Basic meta tags
title: $('head title').text(),
description: $('meta[name="description"]').attr('content'),
keywords: $('meta[name="keywords"]').attr('content'),
canonicalUrl: $('link[rel="canonical"]').attr('href'),
// OpenGraph Protocol
ogTitle: $('meta[property="og:title"]').attr('content'),
ogType: $('meta[property="og:type"]').attr('content'),
ogImage: $('meta[property="og:image"]').attr('content'),
ogUrl: $('meta[property="og:url"]').attr('content'),
ogDescription: $('meta[property="og:description"]').attr('content'),
ogSiteName: $('meta[property="og:site_name"]').attr('content'),
// Twitter Cards
twitterCard: $('meta[name="twitter:card"]').attr('content'),
twitterSite: $('meta[name="twitter:site"]').attr('content'),
twitterCreator: $('meta[name="twitter:creator"]').attr('content'),
twitterTitle: $('meta[name="twitter:title"]').attr('content'),
twitterDescription: $('meta[name="twitter:description"]').attr('content'),
twitterImage: $('meta[name="twitter:image"]').attr('content'),
// Misc
themeColor: $('meta[name="theme-color"]').attr('content'),
robots: $('meta[name="robots"]').attr('content'),
googlebot: $('meta[name="googlebot"]').attr('content'),
generator: $('meta[name="generator"]').attr('content'),
viewport: $('meta[name="viewport"]').attr('content'),
author: $('meta[name="author"]').attr('content'),
publisher: $('link[rel="publisher"]').attr('href'),
favicon: $('link[rel="icon"]').attr('href')
};
if (Object.keys(metadata).length === 0) {
return { skipped: 'No metadata found' };
}
return metadata;
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({ error: 'Failed fetching data' }),
};
}
};
export const handler = middleware(socialTagsHandler);
export default handler;
================================================
FILE: api/ssl.js
================================================
import tls from 'tls';
import middleware from './_common/middleware.js';
const sslHandler = async (urlString) => {
try {
const parsedUrl = new URL(urlString);
const options = {
host: parsedUrl.hostname,
port: parsedUrl.port || 443,
servername: parsedUrl.hostname,
rejectUnauthorized: false,
};
return new Promise((resolve, reject) => {
const socket = tls.connect(options, () => {
if (!socket.authorized) {
return reject(new Error(`SSL handshake not authorized. Reason: ${socket.authorizationError}`));
}
const cert = socket.getPeerCertificate();
if (!cert || Object.keys(cert).length === 0) {
return reject(new Error(`
No certificate presented by the server.\n
The server is possibly not using SNI (Server Name Indication) to identify itself, and you are connecting to a hostname-aliased IP address.
Or it may be due to an invalid SSL certificate, or an incomplete SSL handshake at the time the cert is being read.`));
}
const { raw, issuerCertificate, ...certWithoutRaw } = cert;
resolve(certWithoutRaw);
socket.end();
});
socket.on('error', (error) => {
reject(new Error(`Error fetching site certificate: ${error.message}`));
});
});
} catch (error) {
throw new Error(error.message);
}
};
export const handler = middleware(sslHandler);
export default handler;
================================================
FILE: api/status.js
================================================
import https from 'https';
import { performance, PerformanceObserver } from 'perf_hooks';
import middleware from './_common/middleware.js';
const statusHandler = async (url) => {
if (!url) {
throw new Error('You must provide a URL query parameter!');
}
let dnsLookupTime;
let responseCode;
let startTime;
const obs = new PerformanceObserver((items) => {
dnsLookupTime = items.getEntries()[0].duration;
performance.clearMarks();
});
obs.observe({ entryTypes: ['measure'] });
performance.mark('A');
try {
startTime = performance.now();
const response = await new Promise((resolve, reject) => {
const req = https.get(url, res => {
let data = '';
responseCode = res.statusCode;
res.on('data', chunk => {
data += chunk;
});
res.on('end', () => {
resolve(res);
});
});
req.on('error', reject);
req.end();
});
if (responseCode < 200 || responseCode >= 400) {
throw new Error(`Received non-success response code: ${responseCode}`);
}
performance.mark('B');
performance.measure('A to B', 'A', 'B');
let responseTime = performance.now() - startTime;
obs.disconnect();
return { isUp: true, dnsLookupTime, responseTime, responseCode };
} catch (error) {
obs.disconnect();
throw error;
}
};
export const handler = middleware(statusHandler);
export default handler;
================================================
FILE: api/tech-stack.js
================================================
import Wappalyzer from 'wappalyzer';
import middleware from './_common/middleware.js';
const techStackHandler = async (url) => {
const options = {};
const wappalyzer = new Wappalyzer(options);
try {
await wappalyzer.init();
const headers = {};
const storage = {
local: {},
session: {},
};
const site = await wappalyzer.open(url, headers, storage);
const results = await site.analyze();
if (!results.technologies || results.technologies.length === 0) {
throw new Error('Unable to find any technologies for site');
}
return results;
} catch (error) {
throw new Error(error.message);
} finally {
await wappalyzer.destroy();
}
};
export const handler = middleware(techStackHandler);
export default handler;
================================================
FILE: api/threats.js
================================================
import axios from 'axios';
import xml2js from 'xml2js';
import middleware from './_common/middleware.js';
const getGoogleSafeBrowsingResult = async (url) => {
try {
const apiKey = process.env.GOOGLE_CLOUD_API_KEY;
if (!apiKey) {
return { error: 'GOOGLE_CLOUD_API_KEY is required for the Google Safe Browsing check' };
}
const apiEndpoint = `https://safebrowsing.googleapis.com/v4/threatMatches:find?key=${apiKey}`;
const requestBody = {
threatInfo: {
threatTypes: [
'MALWARE', 'SOCIAL_ENGINEERING', 'UNWANTED_SOFTWARE', 'POTENTIALLY_HARMFUL_APPLICATION', 'API_ABUSE'
],
platformTypes: ["ANY_PLATFORM"],
threatEntryTypes: ["URL"],
threatEntries: [{ url }]
}
};
const response = await axios.post(apiEndpoint, requestBody);
if (response.data && response.data.matches) {
return {
unsafe: true,
details: response.data.matches
};
} else {
return { unsafe: false };
}
} catch (error) {
return { error: `Request failed: ${error.message}` };
}
};
const getUrlHausResult = async (url) => {
let domain = new URL(url).hostname;
return await axios({
method: 'post',
url: 'https://urlhaus-api.abuse.ch/v1/host/',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: `host=${domain}`
})
.then((x) => x.data)
.catch((e) => ({ error: `Request to URLHaus failed, ${e.message}`}));
};
const getPhishTankResult = async (url) => {
try {
const encodedUrl = Buffer.from(url).toString('base64');
const endpoint = `https://checkurl.phishtank.com/checkurl/?url=${encodedUrl}`;
const headers = {
'User-Agent': 'phishtank/web-check',
};
const response = await axios.post(endpoint, null, { headers, timeout: 3000 });
const parsed = await xml2js.parseStringPromise(response.data, { explicitArray: false });
return parsed.response.results;
} catch (error) {
return { error: `Request to PhishTank failed: ${error.message}` };
}
}
const getCloudmersiveResult = async (url) => {
const apiKey = process.env.CLOUDMERSIVE_API_KEY;
if (!apiKey) {
return { error: 'CLOUDMERSIVE_API_KEY is required for the Cloudmersive check' };
}
try {
const endpoint = 'https://api.cloudmersive.com/virus/scan/website';
const headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Apikey': apiKey,
};
const data = `Url=${encodeURIComponent(url)}`;
const response = await axios.post(endpoint, data, { headers });
return response.data;
} catch (error) {
return { error: `Request to Cloudmersive failed: ${error.message}` };
}
};
const threatsHandler = async (url) => {
try {
const urlHaus = await getUrlHausResult(url);
const phishTank = await getPhishTankResult(url);
const cloudmersive = await getCloudmersiveResult(url);
const safeBrowsing = await getGoogleSafeBrowsingResult(url);
if (urlHaus.error && phishTank.error && cloudmersive.error && safeBrowsing.error) {
throw new Error(`All requests failed - ${urlHaus.error} ${phishTank.error} ${cloudmersive.error} ${safeBrowsing.error}`);
}
return JSON.stringify({ urlHaus, phishTank, cloudmersive, safeBrowsing });
} catch (error) {
throw new Error(error.message);
}
};
export const handler = middleware(threatsHandler);
export default handler;
================================================
FILE: api/tls.js
================================================
import axios from 'axios';
import middleware from './_common/middleware.js';
const MOZILLA_TLS_OBSERVATORY_API = 'https://tls-observatory.services.mozilla.com/api/v1';
const tlsHandler = async (url) => {
try {
const domain = new URL(url).hostname;
const scanResponse = await axios.post(`${MOZILLA_TLS_OBSERVATORY_API}/scan?target=${domain}`);
const scanId = scanResponse.data.scan_id;
if (typeof scanId !== 'number') {
return {
statusCode: 500,
body: { error: 'Failed to get scan_id from TLS Observatory' },
};
}
const resultResponse = await axios.get(`${MOZILLA_TLS_OBSERVATORY_API}/results?id=${scanId}`);
return {
statusCode: 200,
body: resultResponse.data,
};
} catch (error) {
return { error: error.message };
}
};
export const handler = middleware(tlsHandler);
export default handler;
================================================
FILE: api/trace-route.js
================================================
import url from 'url';
import traceroute from 'traceroute';
import middleware from './_common/middleware.js';
const traceRouteHandler = async (urlString, context) => {
// Parse the URL and get the hostname
const urlObject = url.parse(urlString);
const host = urlObject.hostname;
if (!host) {
throw new Error('Invalid URL provided');
}
// Traceroute with callback
const result = await new Promise((resolve, reject) => {
traceroute.trace(host, (err, hops) => {
if (err || !hops) {
reject(err || new Error('No hops found'));
} else {
resolve(hops);
}
});
});
return {
message: "Traceroute completed!",
result,
};
};
export const handler = middleware(traceRouteHandler);
export default handler;
================================================
FILE: api/txt-records.js
================================================
import dns from 'dns/promises';
import middleware from './_common/middleware.js';
const txtRecordHandler = async (url, event, context) => {
try {
const parsedUrl = new URL(url);
const txtRecords = await dns.resolveTxt(parsedUrl.hostname);
// Parsing and formatting TXT records into a single object
const readableTxtRecords = txtRecords.reduce((acc, recordArray) => {
const recordObject = recordArray.reduce((recordAcc, recordString) => {
const splitRecord = recordString.split('=');
const key = splitRecord[0];
const value = splitRecord.slice(1).join('=');
return { ...recordAcc, [key]: value };
}, {});
return { ...acc, ...recordObject };
}, {});
return readableTxtRecords;
} catch (error) {
if (error.code === 'ERR_INVALID_URL') {
throw new Error(`Invalid URL ${error}`);
} else {
throw error;
}
}
};
export const handler = middleware(txtRecordHandler);
export default handler;
================================================
FILE: api/whois.js
================================================
import net from 'net';
import psl from 'psl';
import axios from 'axios';
import middleware from './_common/middleware.js';
const getBaseDomain = (url) => {
let protocol = '';
if (url.startsWith('http://')) {
protocol = 'http://';
} else if (url.startsWith('https://')) {
protocol = 'https://';
}
let noProtocolUrl = url.replace(protocol, '');
const parsed = psl.parse(noProtocolUrl);
return protocol + parsed.domain;
};
const parseWhoisData = (data) => {
if (data.includes('No match for')) {
return { error: 'No matches found for domain in internic database'};
}
const lines = data.split('\r\n');
const parsedData = {};
let lastKey = '';
for (const line of lines) {
const index = line.indexOf(':');
if (index === -1) {
if (lastKey !== '') {
parsedData[lastKey] += ' ' + line.trim();
}
continue;
}
let key = line.slice(0, index).trim();
const value = line.slice(index + 1).trim();
if (value.length === 0) continue;
key = key.replace(/\W+/g, '_');
lastKey = key;
parsedData[key] = value;
}
return parsedData;
};
const fetchFromInternic = async (hostname) => {
return new Promise((resolve, reject) => {
const client = net.createConnection({ port: 43, host: 'whois.internic.net' }, () => {
client.write(hostname + '\r\n');
});
let data = '';
client.on('data', (chunk) => {
data += chunk;
});
client.on('end', () => {
try {
const parsedData = parseWhoisData(data);
resolve(parsedData);
} catch (error) {
reject(error);
}
});
client.on('error', (err) => {
reject(err);
});
});
};
const fetchFromMyAPI = async (hostname) => {
try {
const response = await axios.post('https://whois-api-zeta.vercel.app/', {
domain: hostname
});
return response.data;
} catch (error) {
console.error('Error fetching data from your API:', error.message);
return null;
}
};
const whoisHandler = async (url) => {
if (!url.startsWith('http://') && !url.startsWith('https://')) {
url = 'http://' + url;
}
let hostname;
try {
hostname = getBaseDomain(new URL(url).hostname);
} catch (error) {
throw new Error(`Unable to parse URL: ${error}`);
}
const [internicData, whoisData] = await Promise.all([
fetchFromInternic(hostname),
fetchFromMyAPI(hostname)
]);
return {
internicData,
whoisData
};
};
export const handler = middleware(whoisHandler);
export default handler;
================================================
FILE: astro.config.mjs
================================================
import { defineConfig } from 'astro/config';
// Integrations
import svelte from '@astrojs/svelte';
import react from "@astrojs/react";
import partytown from '@astrojs/partytown';
import sitemap from '@astrojs/sitemap';
// Adapters
import vercelAdapter from '@astrojs/vercel/serverless';
import netlifyAdapter from '@astrojs/netlify';
import nodeAdapter from '@astrojs/node';
import cloudflareAdapter from '@astrojs/cloudflare';
// Helper function to unwrap both Vite and Node environment variables
const unwrapEnvVar = (varName, fallbackValue) => {
const classicEnvVar = process?.env && process.env[varName];
const viteEnvVar = import.meta.env[varName];
return classicEnvVar || viteEnvVar || fallbackValue;
}
// Determine the deploy target (vercel, netlify, cloudflare, node)
const deployTarget = unwrapEnvVar('PLATFORM', 'node').toLowerCase();
// Determine the output mode (server, hybrid or static)
const output = unwrapEnvVar('OUTPUT', 'hybrid');
// The FQDN of where the site is hosted (used for sitemaps & canonical URLs)
const site = unwrapEnvVar('SITE_URL', 'https://web-check.xyz');
// The base URL of the site (if serving from a subdirectory)
const base = unwrapEnvVar('BASE_URL', '/');
// Should run the app in boss-mode (requires extra configuration)
const isBossServer = unwrapEnvVar('BOSS_SERVER', false);
// Initialize Astro integrations
const integrations = [svelte(), react(), partytown(), sitemap()];
// Set the appropriate adapter, based on the deploy target
function getAdapter(target) {
switch(target) {
case 'vercel':
return vercelAdapter();
case 'netlify':
return netlifyAdapter();
case 'cloudflare':
return cloudflareAdapter();
case 'node':
return nodeAdapter({ mode: 'middleware' });
default:
throw new Error(`Unsupported deploy target: ${target}`);
}
}
const adapter = getAdapter(deployTarget);
// Print build information to console
console.log(
`\n\x1b[1m\x1b[35m Preparing to start build of Web Check.... \x1b[0m\n`,
`\x1b[35m\x1b[2mCompiling for "${deployTarget}" using "${output}" mode, `
+ `to deploy to "${site}" at "${base}"\x1b[0m\n`,
`\x1b[2m\x1b[36m🛟 For documentation and support, visit the GitHub repo: ` +
`https://github.com/lissy93/web-check \n`,
`💖 Found Web-Check useful? Consider sponsoring us on GitHub ` +
`to help fund maintenance & development.\x1b[0m\n`,
);
const redirects = {
'/about': '/check/about',
};
// Skip the marketing homepage for self-hosted users
if (!isBossServer && isBossServer !== true) {
redirects['/'] = '/check';
}
// Export Astro configuration
export default defineConfig({ output, base, integrations, site, adapter, redirects });
================================================
FILE: docker-compose.yml
================================================
version: '3.9'
services:
web-check:
container_name: Web-Check
image: lissy93/web-check
ports:
- 3000:3000
restart: unless-stopped
================================================
FILE: fly.toml
================================================
app = 'web-check'
primary_region = 'lhr'
[build]
[http_service]
internal_port = 3000
force_https = true
auto_stop_machines = true
auto_start_machines = true
min_machines_running = 0
processes = ['app']
[[vm]]
memory = '1gb'
cpu_kind = 'shared'
cpus = 1
================================================
FILE: netlify.toml
================================================
# Build settings and site core config
[build]
base = "/"
command = "yarn build"
publish = "dist"
functions = "api"
# Environmental variables and optional secrets
[build.environment]
PLATFORM = "netlify"
PUBLIC_API_ENDPOINT = "/.netlify/functions"
# Build configuration env vars (uncomment if you want to conigure these)
# CI="false" # Set CI to false, to prevent warnings from exiting the build
# CHROME_PATH='/usr/bin/chromium' # Path to Chromium binary
# NODE_VERSION = "16.16.0" # Set the version of Node.js to use
# Optional secrets and API keys (uncomment if you want to add these)
# GOOGLE_CLOUD_API_KEY='' # Google Cloud API key, for running Lighthouse scans
# BUILT_WITH_API_KEY='' # BuiltWith API key, for detecting site features
# REACT_APP_SHODAN_API_KEY='' # Shodan API key, for using Shodan scan API
# REACT_APP_WHO_API_KEY='' # WhoAPI key, for iniiating client-side whois lookup
# Redirect the /api/* path to the lambda functions
[[redirects]]
from = "/api/*"
to = "/.netlify/functions/:splat"
status = 301
force = true
# Plugins
[[plugins]]
package = "netlify-plugin-chromium"
[plugins.inputs]
packageManager = "yarn"
# Set any security headers here
[[headers]]
for = "/*"
[headers.values]
# Uncomment to enable Netlify user control. Requires premium plan.
# Basic-Auth = "someuser:somepassword anotheruser:anotherpassword"
================================================
FILE: package.json
================================================
{
"name": "web-check",
"type": "module",
"version": "2.0.2",
"homepage": "https://web-check.xyz",
"scripts": {
"start": "node server",
"start-pm": "pm2 start server.js -i max",
"build": "astro check && astro build",
"dev:vercel": "PLATFORM='vercel' npx vercel dev",
"dev:netlify": "PLATFORM='netlify' npx netlify dev",
"dev:api": "DISABLE_GUI='true' PORT='3001' nodemon server",
"dev:astro": "PUBLIC_API_ENDPOINT=http://localhost:3001/api astro dev",
"dev": "concurrently -c magenta,cyan -n backend,frontend 'yarn dev:api' 'yarn dev:astro'"
},
"dependencies": {
"@astrojs/check": "^0.5.10",
"@astrojs/react": "^3.3.2",
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5",
"@fortawesome/fontawesome-svg-core": "^6.5.2",
"@fortawesome/free-brands-svg-icons": "^6.5.2",
"@fortawesome/free-regular-svg-icons": "^6.5.2",
"@fortawesome/free-solid-svg-icons": "^6.5.2",
"@fortawesome/svelte-fontawesome": "^0.2.2",
"@types/react": "^18.3.1",
"@types/react-dom": "^18.3.0",
"astro": "^4.7.1",
"axios": "^1.4.8",
"cheerio": "^1.0.0-rc.12",
"chrome-aws-lambda": "^10.1.0",
"chromium": "^3.0.3",
"connect-history-api-fallback": "^2.0.0",
"cors": "^2.8.5",
"csv-parser": "^3.0.0",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"express-rate-limit": "^7.2.0",
"framer-motion": "^11.2.6",
"got": "^14.2.1",
"pm2": "^5.3.1",
"psl": "^1.9.0",
"puppeteer": "^22.8.0",
"puppeteer-core": "^22.8.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-masonry-css": "^1.0.16",
"react-router-dom": "^6.23.0",
"react-simple-maps": "^3.0.0",
"react-toastify": "^10.0.5",
"recharts": "^2.12.6",
"svelte": "^4.2.17",
"traceroute": "^1.0.0",
"typescript": "^5.4.5",
"unzipper": "^0.11.5",
"url-parse": "^1.5.10",
"wappalyzer": "^6.10.65",
"xml2js": "^0.6.2"
},
"devDependencies": {
"@astrojs/cloudflare": "^10.2.5",
"@astrojs/netlify": "^5.2.0",
"@astrojs/node": "^8.2.5",
"@astrojs/partytown": "^2.1.0",
"@astrojs/sitemap": "^3.1.4",
"@astrojs/svelte": "^5.4.0",
"@astrojs/ts-plugin": "^1.6.1",
"@astrojs/vercel": "^7.5.4",
"concurrently": "^8.2.2",
"nodemon": "^3.1.0",
"sass": "^1.77.1"
}
}
================================================
FILE: public/error.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Web-Check</title>
</head>
<body>
<section>
<a class="t" href="/"><h1><img src="https://i.ibb.co/q1gZN2p/web-check-logo.png" width="48" />Web-Check</h1></a>
<p class="moji">😪</p>
<p>There was an error finding this route.</p>
<span>Docs and Source: <a href="https://github.com/lissy93/web-check">github.com/lissy93/web-check</a></span>
</section>
<style>
body { background: #141d2b; color: #fff; }
h1 {
color: #9fef00; text-shadow: #0f1620 2px 2px 0px;
font-size: 3rem; margin: 1rem auto; flex-wrap: wrap;
display: flex; align-items: flex-start; gap: 1rem;
}
section {
display: flex; flex-direction: column; align-items: center; margin: 1rem; gap: 0.5rem;
background: #1a2332; box-shadow: #0f1620 4px 4px 0px; border-radius: 8px; padding: 1rem;
max-width: 800px; margin: 1rem auto;
}
p { font-size: 1.2rem; }
a { color: #9fef00; font-family: monospace; }
a.t { text-decoration: none; margin: 0;}
span { opacity: 0.8; font-size: 0.85rem; }
h1 {
font-family: PTMono, Ubuntu, Fira Sans, Helvetica, sans-serif;
}
p, span, a, section, div { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; }
code { color: #9fef00cc;}
.moji { font-size: 8rem; margin: 0; }
</style>
</body>
</html>
================================================
FILE: public/fonts/Hubot-Sans/LICENSE
================================================
Copyright (c) 2022, GitHub https://github.com/github/hubot-sans
with Reserved Font Name "Hubot Sans"
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting — in part or in whole — any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
================================================
FILE: public/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#9fef00" />
<meta
name="description"
content="All-in-one OSINT tool, for quickly checking a websites data"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>Web Check</title>
<script defer data-domain="web-check.as93.net" src="https://no-track.as93.net/js/script.js"></script>
<!-- OpenGraph Social Tags -->
<meta property="og:title" content="Web Check">
<meta property="og:description" content="All-in-one Website OSINT Scanner">
<meta property="og:image" content="/banner.png">
<meta property="og:url" content="https://web-check.xyz">
<meta name="twitter:card" content="summary_large_image">
<!-- DarkReader -->
<meta name="darkreader-lock">
</head>
<body>
<noscript>
<b>Welcome to Web-Check, the free and open source tool for viewing all available information about a website.</b><br />
Get started by entering a URL, and clicking the "Scan" button, or view the code and docs
on <a href="https://github.com/lissy93/web-check">GitHub</a>.<br />
<small>Licensed under MIT, ©️ <a href="https://aliciasykes.com">Alicia Sykes</a> 2023.</small>
<br /><br />
JavaScript is required to continue, please enable it in your browser.
</noscript>
<div id="root"></div>
</body>
</html>
================================================
FILE: public/manifest.json
================================================
{
"short_name": "Web Check",
"name": "Lissy93/Web-Check",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "apple-touch-icon.png",
"type": "image/png",
"sizes": "180x180"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#9fef00",
"background_color": "#141d2b"
}
================================================
FILE: public/placeholder.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Web-Check</title>
</head>
<body>
<section>
<a class="t" href="/"><h1><img src="https://i.ibb.co/q1gZN2p/web-check-logo.png" width="48" />Web-Check</h1></a>
<p><!-- CONTENT --></p>
<span>Docs and Source: <a href="https://github.com/lissy93/web-check">github.com/lissy93/web-check</a></span>
</section>
<style>
body { background: #141d2b; color: #fff; }
h1 {
color: #9fef00; text-shadow: #0f1620 2px 2px 0px;
font-size: 3rem; margin: 1rem auto; flex-wrap: wrap;
display: flex; align-items: flex-start; gap: 1rem;
}
section {
display: flex; flex-direction: column; align-items: center; margin: 1rem; gap: 0.5rem;
background: #1a2332; box-shadow: #0f1620 4px 4px 0px; border-radius: 8px; padding: 1rem;
max-width: 800px; margin: 1rem auto;
}
p { font-size: 1.2rem; }
a { color: #9fef00; font-family: monospace; }
a.t { text-decoration: none; margin: 0;}
span { opacity: 0.8; font-size: 0.85rem; }
h1 {
font-family: PTMono, Ubuntu, Fira Sans, Helvetica, sans-serif;
}
p, span, a, section, div { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; }
code { color: #9fef00cc;}
.logs { background: #141d2b; padding: 0.5rem;}
</style>
</body>
</html>
================================================
FILE: public/resources/openapi-spec.yml
================================================
openapi: 3.0.0
info:
title: Web Check 🕵
description: >
**API documentation for the [Web Check](https://github.com/lissy93/web-check) backend endpoints.**<br>
_Web Check gives you x-ray vision, revealing the configration and inner workings of any website._
<br><br>
[](https://web-check.xyz/)
[](https://hub.docker.com/r/lissy93/web-check)
[](https://github.com/lissy93/web-check)
[](https://github.com/sponsors/Lissy93)
version: 1.0.0
license:
name: 'License: MIT'
url: https://github.com/Lissy93/web-check/blob/master/LICENSE
termsOfService: https://web-check.xyz/about#terms-info
externalDocs:
description: 'Source: GitHub'
url: https://github.com/Lissy93/web-check
servers:
- url: http://localhost:3001/api
description: Local (Development)
- url: http://localhost:3000/api
description: Local (Production)
- url: https://web-check.xyz/api
description: Public Demo (Vercel)
- url: https://web-check.as93.net/api
description: Public Demo (Netlify)
tags:
- name: Quality & Info
description: Endpoints providing quality metrics, and general website information.
- name: Security
description: Endpoints related to website and server security configurations.
- name: Server Info
description: Endpoints providing information about the server hosting the website.
- name: Client-Side Information
description: Endpoints providing metrics about the website's client-side content.
components:
responses:
Error:
description: Internal Server Error - An error occurred while processing the request.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
Skipped:
description: No Content - The request was successful, but no content is returned.
content:
application/json:
schema:
$ref: '#/components/schemas/SkippedResponse'
MissingParam:
description: Bad Request - Missing or incorrect input parameters.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
Unauthorized:
description: Unauthorized - Authentication credentials were missing or incorrect.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
Forbidden:
description: Forbidden - The credentials provided do not grant the necessary permissions.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
TooManyRequests:
description: Too Many Requests - Rate limit exceeded.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
schemas:
ErrorResponse:
type: object
properties:
error:
type: string
description: A description of the error
SkippedResponse:
type: object
properties:
skipped:
type: string
description: A description of why the check was skipped
paths:
/archives:
get:
summary: Retrieve archive data
tags:
- Quality & Info
parameters:
- name: url
in: query
required: true
description: The URL to fetch results about
schema:
type: string
responses:
'200':
description: Successful response
content:
application/json:
schema:
type: object
properties:
firstScan:
type: string
format: date-time
description: The timestamp of the first scan
example: "1996-12-21T17:11:14.000Z"
lastScan:
type: string
format: date-time
description: The timestamp of the last scan
example: "2024-05-14T13:45:47.000Z"
totalScans:
type: integer
description: The total number of scans
example: 3393
changeCount:
type: integer
description: The total number of changes
example: 1946
averagePageSize:
type: integer
description: The average page size in KB
example: 527
scanFrequency:
type: object
properties:
daysBetweenScans:
type: number
format: float
description: Average days between scans
example: 2.95
daysBetweenChanges:
type: number
format: float
description: Average days between changes
example: 5.14
scansPerDay:
type: number
format: float
description: Number of scans per day
example: 0.34
changesPerDay:
type: number
format: float
description: Number of changes per day
example: 0.19
scans:
type: array
items:
type: array
items:
type: string
description: List of scan details
example:
- ["19961221171114", "200", "RX44GNWXPO4HX6ERA2LHBORWD4BJ2HUJ", "971", null]
- ["19970209085620", "200", "6PUZMQAGXXV3RCEQ65WWUCMIJVSG4OQI", "1025", null]
- ["19970209085620", "200", "RX44GNWXPO4HX6ERA2LHBORWD4BJ2HUJ", "975", null]
- ["19970412142046", "200", "RX44GNWXPO4HX6ERA2LHBORWD4BJ2HUJ", "1022", null]
- ["19980206180754", "200", "B6ACYSFKSPWKXRPASZYOK3IBIBY7HB3I", "1102", null]
scanUrl:
type: string
format: uri
description: The URL to the scan
example: "https://duck.com"
'204':
$ref: '#/components/responses/Skipped'
'500':
$ref: '#/components/responses/Error'
'400':
$ref: '#/components/responses/MissingParam'
'401':
$ref: '#/components/responses/Unauthorized'
'429':
$ref: '#/components/responses/TooManyRequests'
/block-lists:
get:
summary: Retrieve block lists data
tags:
- Security
parameters:
- name: url
in: query
required: true
description: The URL to fetch results about
schema:
type: string
responses:
'200':
description: Successful response
content:
application/json:
schema:
type: object
properties:
blocklists:
type: array
items:
type: object
properties:
server:
type: string
description: The name of the blocklist server
example: "AdGuard"
serverIp:
type: string
description: The IP address of the blocklist server
example: "176.103.130.130"
isBlocked:
type: boolean
description: Whether the URL is blocked by the server
example: false
'204':
$ref: '#/components/responses/Skipped'
'500':
$ref: '#/components/responses/Error'
'400':
$ref: '#/components/responses/MissingParam'
'401':
$ref: '#/components/responses/Unauthorized'
'429':
$ref: '#/components/responses/TooManyRequests'
/carbon:
get:
summary: Retrieve carbon data
tags:
- Quality & Info
parameters:
- name: url
in: query
required: true
description: The URL to fetch results about
schema:
type: string
responses:
'200':
description: Successful response
content:
application/json:
schema:
type: object
properties:
statistics:
type: object
properties:
adjustedBytes:
type: number
format: float
description: Adjusted bytes transferred
example: 104.19
energy:
type: number
format: float
description: Energy consumption in kWh
example: 7.859794422984123e-8
co2:
type: object
properties:
grid:
type: object
properties:
grams:
type: number
format: float
description: CO2 emissions in grams from grid energy
example: 0.00003474029134958983
litres:
type: number
format: float
description: CO2 emissions in litres from grid energy
example: 0.00001932255004864186
renewable:
type: object
properties:
grams:
type: number
format: float
description: CO2 emissions in grams from renewable energy
example: 0.000030118732228875164
litres:
type: number
format: float
description: CO2 emissions in litres from renewable energy
example: 0.000016752038865700363
cleanerThan:
type: integer
description: Percentage of websites that are less clean than the queried site
example: 1
rating:
type: string
description: Environmental rating
example: "A+"
green:
type: boolean
description: Whether the site is green
example: false
scanUrl:
type: string
format: uri
description: The URL to the scan
example: "https://duck.com"
'204':
$ref: '#/components/responses/Skipped'
'500':
$ref: '#/components/responses/Error'
'400':
$ref: '#/components/responses/MissingParam'
'401':
$ref: '#/components/responses/Unauthorized'
'429':
$ref: '#/components/responses/TooManyRequests'
/cookies:
get:
summary: Retrieve cookies data
tags:
- Server Info
parameters:
- name: url
in: query
required: true
description: The URL to fetch results about
schema:
type: string
responses:
'200':
description: Successful response
content:
application/json:
schema:
type: object
properties:
headerCookies:
type: array
items:
type: string
description: List of cookies from the HTTP headers
example:
- "SOCS=CAAaBgiAmZWyBg; expires=Sun, 15-Jun-2025 12:33:07 GMT; path=/; domain=.google.com; Secure; SameSite=lax"
- "AEC=AQTF6HyLiMgX13QRJxvRyBXFos8vw-et4igVlyhgeZaeLlfDgvkgkhQmSg; expires=Tue, 12-Nov-2024 12:33:07 GMT; path=/; domain=.google.com; Secure; HttpOnly; SameSite=lax"
- "__Secure-ENID=19.SE=YMEDMLYol9GgV7AzLehnn5lFsrHNqD3emYqfTaVxHQPMmAHiIqeKLyqdjfwED8gPGFN5Gb7OqkGiGTki34F94cir_SXxYvVJXTFuUU9hPAMaxoluY4JeeWKP3ma4RlsBZDKDUrBBVIA-PoPwMMBRyy8Fu0m_nOPTGjlz8WMhm_jMuHdWEF8; expires=Mon, 16-Jun-2025 04:51:25 GMT; path=/; domain=.google.com; Secure; HttpOnly; SameSite=lax"
clientCookies:
type: array
items:
type: object
properties:
name:
type: string
description: The name of the cookie
example: "__Secure-ENID"
value:
type: string
description: The value of the cookie
example: "19.SE=LZ6Zj5jPrWer7Kk1PqTOWpwREJDJlMaxrCes3es4eXfLZ3-9e-HQ0tnRU-RNA_ezSw_CSyFbUWAd4A3FnTJGGYRsjK2FcqJfIum0UEcSy-oFXM49VreKYjxFyUNEshqsY_VQnZw1lFuRqRyH2JA2V90uHmaL_AVOlL_Myv1_PXwcPYgOCqsBQTkxYPeDVpS6QyHO9g"
domain:
type: string
description: The domain of the cookie
example: ".google.com"
path:
type: string
description: The path of the cookie
example: "/"
expires:
type: number
format: float
description: The expiration time of the cookie in Unix time
example: 1750049486.285637
size:
type: integer
description: The size of the cookie
example: 217
httpOnly:
type: boolean
description: Whether the cookie is HttpOnly
example: true
secure:
type: boolean
description: Whether the cookie is Secure
example: true
session:
type: boolean
description: Whether the cookie is a session cookie
example: false
sameSite:
type: string
description: The SameSite attribute of the cookie
example: "Lax"
priority:
type: string
description: The priority of the cookie
example: "Medium"
sameParty:
type: boolean
description: Whether the cookie is SameParty
example: false
sourceScheme:
type: string
description: The source scheme of the cookie
example: "Secure"
'204':
$ref: '#/components/responses/Skipped'
'500':
$ref: '#/components/responses/Error'
'400':
$ref: '#/components/responses/MissingParam'
'401':
$ref: '#/components/responses/Unauthorized'
'429':
$ref: '#/components/responses/TooManyRequests'
/dns-server:
get:
summary: Retrieve DNS server data
tags:
- Server Info
parameters:
- name: url
in: query
required: true
description: The URL to fetch results about
schema:
type: string
responses:
'200':
description: Successful response
content:
application/json:
schema:
type: object
properties:
domain:
type: string
description: The domain name queried
example: "duck.com"
dns:
type: array
items:
type: object
properties:
address:
type: string
description: The IP address of the DNS server
example: "52.142.124.215"
hostname:
type: array
items:
type: string
description: Hostnames associated with the DNS server
nullable: true
example:
- "lhr48s30-in-f14.1e100.net"
dohDirectSupports:
type: boolean
description: Whether the server supports DoH (DNS over HTTPS) directly
example: false
'204':
$ref: '#/components/responses/Skipped'
'500':
$ref: '#/components/responses/Error'
'400':
$ref: '#/components/responses/MissingParam'
'401':
$ref: '#/components/responses/Unauthorized'
'429':
$ref: '#/components/responses/TooManyRequests'
/dns:
get:
summary: Retrieve DNS data
tags:
- Server Info
parameters:
- name: url
in: query
required: true
description: The URL to fetch results about
schema:
type: string
responses:
'200':
description: Successful response
content:
application/json:
schema:
type: object
properties:
A:
type: object
properties:
address:
type: string
description: IPv4 address
example: "52.142.124.215"
family:
type: integer
description: IP family
example: 4
AAAA:
type: array
items:
type: string
description: List of IPv6 addresses
example: ["52.142.124.215"]
MX:
type: array
items:
type: string
description: List of mail exchange servers
example: []
TXT:
type: array
items:
type: object
properties:
exchange:
type: string
description: Exchange server
example: "smtp-inbound1.duck.com"
priority:
type: integer
description: Priority of the exchange server
example: 5
description: List of TXT records
NS:
type: array
items:
type: array
items:
type: string
description: List of name servers
example:
- ["v=spf1 ip4:20.13.235.192/26 ip4:20.67.221.0/24 ip4:20.67.222.0/24 ip4:20.67.223.0/24 -all"]
- ["google-site-verification=xWLxaaNt2iGObwJ_jeX1E3Wn-xro--W75DBKWs5uufc"]
CNAME:
type: array
items:
type: string
description: List of canonical names
example:
- "dns1.p03.nsone.net"
- "dns2.p03.nsone.net"
- "dns3.p03.nsone.net"
- "dns4.p03.nsone.net"
- "ns01.quack-dns.com"
- "ns02.quack-dns.com"
- "ns03.quack-dns.com"
- "ns04.quack-dns.com"
SOA:
type: array
items:
type: string
description: Start of Authority records
example: []
SRV:
type: object
properties:
nsname:
type: string
description: Name server
example: "dns1.p03.nsone.net"
hostmaster:
type: string
description: Hostmaster email
example: "hostmaster.nsone.net"
serial:
type: integer
description: Serial number
example: 1654974460
refresh:
type: integer
description: Refresh interval
example: 7200
retry:
type: integer
description: Retry interval
example: 7200
expire:
type: integer
description: Expiration time
example: 1209600
minttl:
type: integer
description: Minimum TTL
example: 14400
description: Service records
PTR:
type: array
items:
type: string
description: Pointer records
example: []
'204':
$ref: '#/components/responses/Skipped'
'500':
$ref: '#/components/responses/Error'
'400':
$ref: '#/components/responses/MissingParam'
'401':
$ref: '#/components/responses/Unauthorized'
'429':
$ref: '#/components/responses/TooManyRequests'
/dnssec:
get:
summary: Retrieve DNSSEC data
tags:
- Security
parameters:
- name: url
in: query
required: true
description: The URL to fetch results about
schema:
type: string
responses:
'200':
description: Successful response
content:
application/json:
schema:
type: object
properties:
DNSKEY:
type: object
properties:
isFound:
type: boolean
description: Whether the DNSKEY record is found
example: false
answer:
type: string
nullable: true
description: The DNSKEY answer (if any)
example: null
response:
type: object
properties:
Status:
type: integer
description: The status code of the response
example: 3
TC:
type: boolean
description: Truncated response flag
example: false
RD:
type: boolean
description: Recursion desired flag
example: true
RA:
type: boolean
description: Recursion available flag
example: true
AD:
type: boolean
description: Authentic data flag
example: false
CD:
type: boolean
description: Checking disabled flag
example: false
Question:
type: array
items:
gitextract_epf_u9tl/ ├── .github/ │ ├── FUNDING.yml │ ├── README.md │ ├── screenshots/ │ │ └── README.md │ └── workflows/ │ ├── credits.yml │ ├── deploy-aws.yml │ ├── docker.yml │ └── mirror.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── api/ │ ├── _common/ │ │ ├── aws-webpack.config.js │ │ └── middleware.js │ ├── archives.js │ ├── block-lists.js │ ├── carbon.js │ ├── cookies.js │ ├── dns-server.js │ ├── dns.js │ ├── dnssec.js │ ├── features.js │ ├── firewall.js │ ├── get-ip.js │ ├── headers.js │ ├── hsts.js │ ├── http-security.js │ ├── legacy-rank.js │ ├── linked-pages.js │ ├── mail-config.js │ ├── ports.js │ ├── quality.js │ ├── rank.js │ ├── redirects.js │ ├── robots-txt.js │ ├── screenshot.js │ ├── security-txt.js │ ├── sitemap.js │ ├── social-tags.js │ ├── ssl.js │ ├── status.js │ ├── tech-stack.js │ ├── threats.js │ ├── tls.js │ ├── trace-route.js │ ├── txt-records.js │ └── whois.js ├── astro.config.mjs ├── docker-compose.yml ├── fly.toml ├── netlify.toml ├── package.json ├── public/ │ ├── error.html │ ├── fonts/ │ │ └── Hubot-Sans/ │ │ ├── LICENSE │ │ └── OTF/ │ │ ├── HubotSans-Black.otf │ │ ├── HubotSans-BlackItalic.otf │ │ ├── HubotSans-Bold.otf │ │ ├── HubotSans-BoldItalic.otf │ │ ├── HubotSans-ExtraBold.otf │ │ ├── HubotSans-ExtraBoldItalic.otf │ │ ├── HubotSans-ExtraLight.otf │ │ ├── HubotSans-ExtraLightItalic.otf │ │ ├── HubotSans-Italic.otf │ │ ├── HubotSans-Light.otf │ │ ├── HubotSans-LightItalic.otf │ │ ├── HubotSans-Medium.otf │ │ ├── HubotSans-MediumItalic.otf │ │ ├── HubotSans-Regular.otf │ │ ├── HubotSans-SemiBold.otf │ │ ├── HubotSans-SemiBoldItalic.otf │ │ ├── HubotSansCondensed-Black.otf │ │ ├── HubotSansCondensed-BlackItalic.otf │ │ ├── HubotSansCondensed-Bold.otf │ │ ├── HubotSansCondensed-BoldItalic.otf │ │ ├── HubotSansCondensed-ExtraBold.otf │ │ ├── HubotSansCondensed-ExtraBoldItalic.otf │ │ ├── HubotSansCondensed-ExtraLight.otf │ │ ├── HubotSansCondensed-ExtraLightItalic.otf │ │ ├── HubotSansCondensed-Italic.otf │ │ ├── HubotSansCondensed-Light.otf │ │ ├── HubotSansCondensed-LightItalic.otf │ │ ├── HubotSansCondensed-Medium.otf │ │ ├── HubotSansCondensed-MediumItalic.otf │ │ ├── HubotSansCondensed-Regular.otf │ │ ├── HubotSansCondensed-SemiBold.otf │ │ ├── HubotSansCondensed-SemiBoldItalic.otf │ │ ├── HubotSansExpanded-Black.otf │ │ ├── HubotSansExpanded-BlackItalic.otf │ │ ├── HubotSansExpanded-Bold.otf │ │ ├── HubotSansExpanded-BoldItalic.otf │ │ ├── HubotSansExpanded-ExtraBold.otf │ │ ├── HubotSansExpanded-ExtraBoldItalic.otf │ │ ├── HubotSansExpanded-ExtraLight.otf │ │ ├── HubotSansExpanded-ExtraLightItalic.otf │ │ ├── HubotSansExpanded-Italic.otf │ │ ├── HubotSansExpanded-Light.otf │ │ ├── HubotSansExpanded-LightItalic.otf │ │ ├── HubotSansExpanded-Medium.otf │ │ ├── HubotSansExpanded-MediumItalic.otf │ │ ├── HubotSansExpanded-Regular.otf │ │ ├── HubotSansExpanded-SemiBold.otf │ │ └── HubotSansExpanded-SemiBoldItalic.otf │ ├── index.html │ ├── manifest.json │ ├── placeholder.html │ ├── resources/ │ │ └── openapi-spec.yml │ ├── robots.txt │ └── security.txt ├── server.js ├── src/ │ ├── components/ │ │ ├── homepage/ │ │ │ ├── AboutSection.astro │ │ │ ├── AnimatedButton.astro │ │ │ ├── AnimatedInput.astro │ │ │ ├── ButtonGroup.astro │ │ │ ├── Features.astro │ │ │ ├── HeroForm.astro │ │ │ ├── HomeBackground.tsx │ │ │ ├── Screenshots.astro │ │ │ ├── SponsorSegment.astro │ │ │ └── TempDisabled.astro │ │ ├── molecules/ │ │ │ └── Icon.svelte │ │ └── scafold/ │ │ ├── Footer.astro │ │ └── Nav.astro │ ├── env.d.ts │ ├── layouts/ │ │ ├── Base.astro │ │ └── MetaTags.astro │ ├── pages/ │ │ ├── account/ │ │ │ └── index.astro │ │ ├── check/ │ │ │ └── [...target].astro │ │ ├── index.astro │ │ ├── self-hosted-setup.astro │ │ └── web-check-api/ │ │ ├── index.astro │ │ └── spec.astro │ ├── styles/ │ │ ├── colors.scss │ │ ├── global.scss │ │ ├── media-queries.scss │ │ └── typography.scss │ └── web-check-live/ │ ├── App.tsx │ ├── assets/ │ │ └── data/ │ │ └── map-features.json │ ├── components/ │ │ ├── Form/ │ │ │ ├── Button.tsx │ │ │ ├── Card.tsx │ │ │ ├── Heading.tsx │ │ │ ├── Input.tsx │ │ │ ├── Modal.tsx │ │ │ ├── Nav.tsx │ │ │ └── Row.tsx │ │ ├── Results/ │ │ │ ├── Archives.tsx │ │ │ ├── BlockLists.tsx │ │ │ ├── BuiltWith.tsx │ │ │ ├── CarbonFootprint.tsx │ │ │ ├── ContentLinks.tsx │ │ │ ├── Cookies.tsx │ │ │ ├── DnsRecords.tsx │ │ │ ├── DnsSec.tsx │ │ │ ├── DnsServer.tsx │ │ │ ├── DomainLookup.tsx │ │ │ ├── Firewall.tsx │ │ │ ├── Headers.tsx │ │ │ ├── HostNames.tsx │ │ │ ├── Hsts.tsx │ │ │ ├── HttpSecurity.tsx │ │ │ ├── Lighthouse.tsx │ │ │ ├── MailConfig.tsx │ │ │ ├── OpenPorts.tsx │ │ │ ├── Rank.tsx │ │ │ ├── Redirects.tsx │ │ │ ├── RobotsTxt.tsx │ │ │ ├── Screenshot.tsx │ │ │ ├── SecurityTxt.tsx │ │ │ ├── ServerInfo.tsx │ │ │ ├── ServerLocation.tsx │ │ │ ├── ServerStatus.tsx │ │ │ ├── SiteFeatures.tsx │ │ │ ├── Sitemap.tsx │ │ │ ├── SocialTags.tsx │ │ │ ├── SslCert.tsx │ │ │ ├── TechStack.tsx │ │ │ ├── Threats.tsx │ │ │ ├── TlsCipherSuites.tsx │ │ │ ├── TlsClientSupport.tsx │ │ │ ├── TlsIssueAnalysis.tsx │ │ │ ├── TraceRoute.tsx │ │ │ ├── TxtRecords.tsx │ │ │ └── WhoIs.tsx │ │ ├── boundaries/ │ │ │ └── PageError.tsx │ │ └── misc/ │ │ ├── ActionButtons.tsx │ │ ├── AdditionalResources.tsx │ │ ├── DocContent.tsx │ │ ├── ErrorBoundary.tsx │ │ ├── FancyBackground.tsx │ │ ├── Flag.tsx │ │ ├── Footer.tsx │ │ ├── Loader.tsx │ │ ├── LocationMap.tsx │ │ ├── ProgressBar.tsx │ │ ├── SelfScanMsg.tsx │ │ └── ViewRaw.tsx │ ├── hooks/ │ │ └── motherOfAllHooks.ts │ ├── main.tsx │ ├── styles/ │ │ ├── colors.ts │ │ ├── dimensions.ts │ │ ├── globals.tsx │ │ ├── index.css │ │ └── typography.ts │ ├── typings/ │ │ ├── file-types.d.ts │ │ └── react-simple-maps.d.ts │ ├── utils/ │ │ ├── address-type-checker.ts │ │ ├── docs.ts │ │ ├── get-keys.ts │ │ └── result-processor.ts │ └── views/ │ ├── About.tsx │ ├── Home.tsx │ ├── NotFound.tsx │ └── Results.tsx ├── svelte.config.js ├── tsconfig.json ├── vercel.json └── vite.config.js
SYMBOL INDEX (62 symbols across 29 files)
FILE: api/_common/middleware.js
constant TIMEOUT (line 6) | const TIMEOUT = process.env.API_TIMEOUT_LIMIT ? parseInt(process.env.API...
constant ALLOWED_ORIGINS (line 9) | const ALLOWED_ORIGINS = process.env.API_CORS_ORIGIN || '*';
constant DISABLE_EVERYTHING (line 12) | const DISABLE_EVERYTHING = !!process.env.VITE_DISABLE_EVERYTHING;
constant PLATFORM (line 15) | let PLATFORM = 'NETLIFY';
FILE: api/block-lists.js
constant DNS_SERVERS (line 5) | const DNS_SERVERS = [
FILE: api/legacy-rank.js
constant FILE_URL (line 14) | const FILE_URL = 'https://s3-us-west-1.amazonaws.com/umbrella-static/top...
constant TEMP_FILE_PATH (line 15) | const TEMP_FILE_PATH = '/tmp/top-1m.csv';
FILE: api/ports.js
constant DEFAULT_PORTS_TO_CHECK (line 5) | const DEFAULT_PORTS_TO_CHECK = [
constant PORTS (line 15) | const PORTS = process.env.PORTS_TO_CHECK ? process.env.PORTS_TO_CHECK.sp...
function checkPort (line 17) | async function checkPort(port, domain) {
FILE: api/security-txt.js
constant SECURITY_TXT_PATHS (line 7) | const SECURITY_TXT_PATHS = [
function fetchSecurityTxt (line 74) | async function fetchSecurityTxt(baseURL, path) {
FILE: api/tls.js
constant MOZILLA_TLS_OBSERVATORY_API (line 4) | const MOZILLA_TLS_OBSERVATORY_API = 'https://tls-observatory.services.mo...
FILE: astro.config.mjs
function getAdapter (line 41) | function getAdapter(target) {
FILE: server.js
constant API_DIR (line 20) | const API_DIR = '/api';
FILE: src/web-check-live/App.tsx
function App (line 20) | function App() {
FILE: src/web-check-live/components/Form/Button.tsx
type LoadState (line 8) | type LoadState = 'loading' | 'success' | 'error';
type ButtonProps (line 10) | interface ButtonProps {
FILE: src/web-check-live/components/Form/Card.tsx
type CardProps (line 21) | interface CardProps {
FILE: src/web-check-live/components/Form/Heading.tsx
type HeadingProps (line 6) | interface HeadingProps {
FILE: src/web-check-live/components/Form/Input.tsx
type Orientation (line 6) | type Orientation = 'horizontal' | 'vertical';
type Props (line 8) | interface Props {
type SupportedElements (line 21) | type SupportedElements = HTMLInputElement | HTMLLabelElement | HTMLDivEl...
type StyledInputTypes (line 22) | interface StyledInputTypes extends InputHTMLAttributes<SupportedElements> {
FILE: src/web-check-live/components/Form/Modal.tsx
type ModalProps (line 8) | interface ModalProps {
FILE: src/web-check-live/components/Form/Row.tsx
type RowProps (line 6) | interface RowProps {
FILE: src/web-check-live/components/Results/Lighthouse.tsx
type Audit (line 8) | interface Audit {
FILE: src/web-check-live/components/Results/Rank.tsx
function Chart (line 42) | function Chart(chartData: { date: string; uv: number; }[], data: any) {
FILE: src/web-check-live/components/Results/SslCert.tsx
function getExtendedKeyUsage (line 42) | function getExtendedKeyUsage(oids: string[]) {
FILE: src/web-check-live/components/boundaries/PageError.tsx
type ErrorBoundaryState (line 12) | interface ErrorBoundaryState {
type ErrorBoundaryProps (line 18) | interface ErrorBoundaryProps {
class ErrorBoundary (line 64) | class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBou...
method constructor (line 65) | constructor(props: ErrorBoundaryProps) {
method getDerivedStateFromError (line 70) | static getDerivedStateFromError(err: Error): ErrorBoundaryState {
method componentDidCatch (line 75) | componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
method render (line 92) | render() {
FILE: src/web-check-live/components/misc/ActionButtons.tsx
type Action (line 15) | interface Action {
FILE: src/web-check-live/components/misc/ErrorBoundary.tsx
type Props (line 7) | interface Props {
type State (line 13) | interface State {
class ErrorBoundary (line 22) | class ErrorBoundary extends Component<Props, State> {
method getDerivedStateFromError (line 29) | public static getDerivedStateFromError(error: Error): State {
method componentDidCatch (line 34) | public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
method render (line 38) | public render() {
FILE: src/web-check-live/components/misc/Flag.tsx
type Props (line 1) | interface Props {
FILE: src/web-check-live/components/misc/LocationMap.tsx
type Props (line 11) | interface Props {
FILE: src/web-check-live/components/misc/ProgressBar.tsx
type LoadingState (line 176) | type LoadingState = 'success' | 'loading' | 'skipped' | 'error' | 'timed...
type LoadingJob (line 178) | interface LoadingJob {
type JobListItemProps (line 227) | interface JobListItemProps {
FILE: src/web-check-live/hooks/motherOfAllHooks.ts
type UseIpAddressProps (line 9) | interface UseIpAddressProps<ResultType = any> {
type ResultType (line 26) | type ResultType = any;
type ReturnType (line 28) | type ReturnType = [ResultType | undefined, (data?: any) => void];
FILE: src/web-check-live/styles/dimensions.ts
type InputSize (line 1) | type InputSize = 'small' | 'medium' | 'large';
FILE: src/web-check-live/utils/address-type-checker.ts
type AddressType (line 6) | type AddressType = 'ipV4' | 'ipV6' | 'url' | 'err' | 'empt';
FILE: src/web-check-live/utils/docs.ts
type Doc (line 1) | interface Doc {
FILE: src/web-check-live/utils/result-processor.ts
type ServerLocation (line 3) | interface ServerLocation {
type Whois (line 24) | interface Whois {
type ServerInfo (line 55) | interface ServerInfo {
type HostNames (line 79) | interface HostNames {
type ShodanResults (line 96) | interface ShodanResults {
type Technology (line 108) | interface Technology {
type TechnologyGroup (line 120) | interface TechnologyGroup {
type Cookie (line 137) | type Cookie = {
Condensed preview — 214 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (907K chars).
[
{
"path": ".github/FUNDING.yml",
"chars": 18,
"preview": "github: [lissy93]\n"
},
{
"path": ".github/README.md",
"chars": 72516,
"preview": "<h1 align=\"center\">Web-Check</h1>\n\n\n<p align=\"center\">\n<img src=\"https://i.ibb.co/q1gZN2p/web-check-logo.png\" width=\"96\""
},
{
"path": ".github/screenshots/README.md",
"chars": 118,
"preview": "\n"
},
{
"path": ".github/workflows/credits.yml",
"chars": 1221,
"preview": "# Inserts list of community members into ./README.md\nname: 💓 Inserts Contributors & Sponsors\non:\n workflow_dispatch: # "
},
{
"path": ".github/workflows/deploy-aws.yml",
"chars": 3499,
"preview": "name: 🚀 Deploy to AWS\n\non:\n workflow_dispatch:\n push:\n branches:\n - master\n tags:\n - '*'\n paths:\n "
},
{
"path": ".github/workflows/docker.yml",
"chars": 2099,
"preview": "name: 🐳 Build + Publish Docker Image\n\non:\n workflow_dispatch:\n push:\n branches:\n - master\n tags:\n - '*"
},
{
"path": ".github/workflows/mirror.yml",
"chars": 458,
"preview": "# Pushes the contents of the repo to the Codeberg mirror\nname: 🪞 Mirror to Codeberg\non:\n workflow_dispatch:\n schedule:"
},
{
"path": ".gitignore",
"chars": 940,
"preview": "# ------------------------\n# ENVIRONMENT SETTINGS\n# ------------------------\n.env\n\n# ------------------------\n# "
},
{
"path": "Dockerfile",
"chars": 2357,
"preview": "# Specify the Node.js version to use\nARG NODE_VERSION=21\n\n# Specify the Debian version to use, the default is \"bullseye\""
},
{
"path": "LICENSE",
"chars": 1098,
"preview": "MIT License\n\nCopyright (c) 2023 Alicia Sykes <https://github.com/lissy93>\n\nPermission is hereby granted, free of charge,"
},
{
"path": "api/_common/aws-webpack.config.js",
"chars": 1597,
"preview": "const path = require('path');\nconst nodeExternals = require('webpack-node-externals');\n\nmodule.exports = {\n target: '"
},
{
"path": "api/_common/middleware.js",
"chars": 5227,
"preview": "const normalizeUrl = (url) => {\n return url.startsWith('http') ? url : `https://${url}`;\n};\n\n// If present, set a short"
},
{
"path": "api/archives.js",
"chars": 2609,
"preview": "import axios from 'axios';\nimport middleware from './_common/middleware.js';\n\nconst convertTimestampToDate = (timestamp)"
},
{
"path": "api/block-lists.js",
"chars": 3235,
"preview": "import dns from 'dns';\nimport { URL } from 'url';\nimport middleware from './_common/middleware.js';\n\nconst DNS_SERVERS ="
},
{
"path": "api/carbon.js",
"chars": 2074,
"preview": "import https from 'https';\nimport middleware from './_common/middleware.js';\n\nconst carbonHandler = async (url) => {\n\n "
},
{
"path": "api/cookies.js",
"chars": 1637,
"preview": "import axios from 'axios';\nimport puppeteer from 'puppeteer';\nimport middleware from './_common/middleware.js';\n\nconst g"
},
{
"path": "api/dns-server.js",
"chars": 1457,
"preview": "import { promises as dnsPromises, lookup } from 'dns';\nimport axios from 'axios';\nimport middleware from './_common/midd"
},
{
"path": "api/dns.js",
"chars": 1927,
"preview": "import dns from 'dns';\nimport util from 'util';\nimport middleware from './_common/middleware.js';\n\nconst dnsHandler = as"
},
{
"path": "api/dnssec.js",
"chars": 1512,
"preview": "import https from 'https';\nimport middleware from './_common/middleware.js';\n\nconst dnsSecHandler = async (domain) => {\n"
},
{
"path": "api/features.js",
"chars": 1203,
"preview": "import https from 'https';\nimport middleware from './_common/middleware.js';\n\nconst featuresHandler = async (url) => {\n "
},
{
"path": "api/firewall.js",
"chars": 3023,
"preview": "import axios from 'axios';\nimport middleware from './_common/middleware.js';\n\nconst hasWaf = (waf) => {\n return {\n h"
},
{
"path": "api/get-ip.js",
"chars": 535,
"preview": "import dns from 'dns';\nimport middleware from './_common/middleware.js';\n\nconst lookupAsync = (address) => {\n return ne"
},
{
"path": "api/headers.js",
"chars": 512,
"preview": "import axios from 'axios';\nimport middleware from './_common/middleware.js';\n\nconst headersHandler = async (url, event, "
},
{
"path": "api/hsts.js",
"chars": 1681,
"preview": "import https from 'https';\nimport middleware from './_common/middleware.js';\n\nconst hstsHandler = async (url, event, con"
},
{
"path": "api/http-security.js",
"chars": 870,
"preview": "import axios from 'axios';\nimport middleware from './_common/middleware.js';\n\nconst httpsSecHandler = async (url) => {\n "
},
{
"path": "api/legacy-rank.js",
"chars": 1879,
"preview": "import axios from 'axios';\nimport unzipper from 'unzipper';\nimport csv from 'csv-parser';\nimport fs from 'fs';\nimport mi"
},
{
"path": "api/linked-pages.js",
"chars": 2047,
"preview": "import axios from 'axios';\nimport cheerio from 'cheerio';\nimport urlLib from 'url';\nimport middleware from './_common/mi"
},
{
"path": "api/mail-config.js",
"chars": 3168,
"preview": "import dns from 'dns';\nimport URL from 'url-parse';\nimport middleware from './_common/middleware.js';\n\n// TODO: Fix.\n\nco"
},
{
"path": "api/ports.js",
"chars": 2678,
"preview": "import net from 'net';\nimport middleware from './_common/middleware.js';\n\n// A list of commonly used ports.\nconst DEFAUL"
},
{
"path": "api/quality.js",
"chars": 708,
"preview": "import axios from 'axios';\nimport middleware from './_common/middleware.js';\n\nconst qualityHandler = async (url, event, "
},
{
"path": "api/rank.js",
"chars": 912,
"preview": "import axios from 'axios';\nimport middleware from './_common/middleware.js';\n\nconst rankHandler = async (url) => { \n co"
},
{
"path": "api/redirects.js",
"chars": 598,
"preview": "import got from 'got';\nimport middleware from './_common/middleware.js';\n\nconst redirectsHandler = async (url) => {\n co"
},
{
"path": "api/robots-txt.js",
"chars": 1769,
"preview": "import axios from 'axios';\nimport middleware from './_common/middleware.js';\n\nconst parseRobotsTxt = (content) => {\n co"
},
{
"path": "api/screenshot.js",
"chars": 4989,
"preview": "import puppeteer from 'puppeteer-core';\nimport chromium from 'chrome-aws-lambda';\nimport middleware from './_common/midd"
},
{
"path": "api/security-txt.js",
"chars": 2321,
"preview": "import { URL } from 'url';\nimport followRedirects from 'follow-redirects';\nimport middleware from './_common/middleware."
},
{
"path": "api/sitemap.js",
"chars": 1496,
"preview": "import axios from 'axios';\nimport xml2js from 'xml2js';\nimport middleware from './_common/middleware.js';\n\nconst sitemap"
},
{
"path": "api/social-tags.js",
"chars": 2386,
"preview": "import axios from 'axios';\nimport cheerio from 'cheerio';\nimport middleware from './_common/middleware.js';\n\nconst socia"
},
{
"path": "api/ssl.js",
"chars": 1469,
"preview": "import tls from 'tls';\nimport middleware from './_common/middleware.js';\n\nconst sslHandler = async (urlString) => {\n tr"
},
{
"path": "api/status.js",
"chars": 1446,
"preview": "import https from 'https';\nimport { performance, PerformanceObserver } from 'perf_hooks';\nimport middleware from './_com"
},
{
"path": "api/tech-stack.js",
"chars": 782,
"preview": "import Wappalyzer from 'wappalyzer';\nimport middleware from './_common/middleware.js';\n\nconst techStackHandler = async ("
},
{
"path": "api/threats.js",
"chars": 3413,
"preview": "import axios from 'axios';\nimport xml2js from 'xml2js';\nimport middleware from './_common/middleware.js';\n\nconst getGoog"
},
{
"path": "api/tls.js",
"chars": 877,
"preview": "import axios from 'axios';\nimport middleware from './_common/middleware.js';\n\nconst MOZILLA_TLS_OBSERVATORY_API = 'https"
},
{
"path": "api/trace-route.js",
"chars": 770,
"preview": "import url from 'url';\nimport traceroute from 'traceroute';\nimport middleware from './_common/middleware.js';\n\nconst tra"
},
{
"path": "api/txt-records.js",
"chars": 994,
"preview": "import dns from 'dns/promises';\nimport middleware from './_common/middleware.js';\n\nconst txtRecordHandler = async (url, "
},
{
"path": "api/whois.js",
"chars": 2545,
"preview": "import net from 'net';\nimport psl from 'psl';\nimport axios from 'axios';\nimport middleware from './_common/middleware.js"
},
{
"path": "astro.config.mjs",
"chars": 2693,
"preview": "import { defineConfig } from 'astro/config';\n\n// Integrations\nimport svelte from '@astrojs/svelte';\nimport react from \"@"
},
{
"path": "docker-compose.yml",
"chars": 154,
"preview": "version: '3.9'\nservices:\n web-check:\n container_name: Web-Check\n image: lissy93/web-check\n ports:\n - 3000"
},
{
"path": "fly.toml",
"chars": 274,
"preview": "app = 'web-check'\nprimary_region = 'lhr'\n\n[build]\n\n[http_service]\n internal_port = 3000\n force_https = true\n auto_sto"
},
{
"path": "netlify.toml",
"chars": 1408,
"preview": "# Build settings and site core config\n[build]\n base = \"/\"\n command = \"yarn build\"\n publish = \"dist\"\n functions = \"ap"
},
{
"path": "package.json",
"chars": 2336,
"preview": "{\n \"name\": \"web-check\",\n \"type\": \"module\",\n \"version\": \"2.0.2\",\n \"homepage\": \"https://web-check.xyz\",\n \"scripts\": {"
},
{
"path": "public/error.html",
"chars": 1551,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-w"
},
{
"path": "public/fonts/Hubot-Sans/LICENSE",
"chars": 4400,
"preview": "Copyright (c) 2022, GitHub https://github.com/github/hubot-sans\nwith Reserved Font Name \"Hubot Sans\"\n\nThis Font Software"
},
{
"path": "public/index.html",
"chars": 1621,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <link rel=\"icon\" href=\"%PUBLIC_URL%/favicon.i"
},
{
"path": "public/manifest.json",
"chars": 402,
"preview": "{\n \"short_name\": \"Web Check\",\n \"name\": \"Lissy93/Web-Check\",\n \"icons\": [\n {\n \"src\": \"favicon.ico\",\n \"size"
},
{
"path": "public/placeholder.html",
"chars": 1510,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-w"
},
{
"path": "public/resources/openapi-spec.yml",
"chars": 118971,
"preview": "openapi: 3.0.0\ninfo:\n title: Web Check 🕵\n description: >\n **API documentation for the [Web Check](https://github.co"
},
{
"path": "public/robots.txt",
"chars": 67,
"preview": "# https://www.robotstxt.org/robotstxt.html\nUser-agent: *\nDisallow:\n"
},
{
"path": "public/security.txt",
"chars": 257,
"preview": "Contact: mailto:security@as93.net\nContact: mailto:alicia@omg.lol\nExpires: 2024-12-31T23:59:00.000Z\nEncryption: https://k"
},
{
"path": "server.js",
"chars": 7342,
"preview": "\nimport fs from 'fs';\nimport path from 'path';\nimport cors from 'cors';\nimport dotenv from 'dotenv';\nimport express from"
},
{
"path": "src/components/homepage/AboutSection.astro",
"chars": 2170,
"preview": "---\n\nimport ButtonGroup from '@components/homepage/ButtonGroup.astro';\nimport Features from '@components/homepage/Featur"
},
{
"path": "src/components/homepage/AnimatedButton.astro",
"chars": 2477,
"preview": "---\nconst buttonText = 'Analyze URL';\nconst buttonType = 'submit';\n---\n\n<button class=\"button\" type={buttonType}>{button"
},
{
"path": "src/components/homepage/AnimatedInput.astro",
"chars": 4420,
"preview": "---\nconst placeholders = [\n 'duck.com',\n 'github.com',\n 'google.com',\n 'x.com',\n 'bbc.co.uk',\n 'wikipedia.org',\n "
},
{
"path": "src/components/homepage/ButtonGroup.astro",
"chars": 1345,
"preview": "---\nimport Icon from '@components/molecules/Icon.svelte';\n\ninterface Props {\n links: {\n title: string;\n url: stri"
},
{
"path": "src/components/homepage/Features.astro",
"chars": 638,
"preview": "---\n\nimport Icon from '@components/molecules/Icon.svelte';\n\ninterface Props {\n supportedChecks: string[];\n}\n\nconst { su"
},
{
"path": "src/components/homepage/HeroForm.astro",
"chars": 3498,
"preview": "---\nimport AnimatedButton from \"./AnimatedButton.astro\"\nimport AnimatedInput from \"./AnimatedInput.astro\"\nimport Screens"
},
{
"path": "src/components/homepage/HomeBackground.tsx",
"chars": 5755,
"preview": "import { useState, useEffect } from 'react';\nimport { motion } from 'framer-motion';\nimport styled from '@emotion/styled"
},
{
"path": "src/components/homepage/Screenshots.astro",
"chars": 3483,
"preview": "---\nconst screenshots = [\n \"https://i.ibb.co/kB7LsV1/wc-ssl.png\",\n \"https://i.ibb.co/7Q1kMwM/wc-dns.png\",\n \"https://i"
},
{
"path": "src/components/homepage/SponsorSegment.astro",
"chars": 1633,
"preview": "---\nconst sponsorName = 'Terminal Trove';\nconst sponsorTagline = 'The $HOME of all things terminal.';\nconst sponsorLink "
},
{
"path": "src/components/homepage/TempDisabled.astro",
"chars": 560,
"preview": "<div class=\"banner\">\n <p>\n ⚠️ Web Check is temporarily disabled due to excess demand and associated costs. \n We a"
},
{
"path": "src/components/molecules/Icon.svelte",
"chars": 983,
"preview": "<script lang=\"ts\">\n import { FontAwesomeIcon } from '@fortawesome/svelte-fontawesome';\n import * as brands from '@fort"
},
{
"path": "src/components/scafold/Footer.astro",
"chars": 1131,
"preview": "---\nconst repo = 'github.com/lissy93/web-check';\nconst github = `https://${repo}`;\n\nconst licenseText = 'MIT';\nconst lic"
},
{
"path": "src/components/scafold/Nav.astro",
"chars": 2535,
"preview": "---\n\n---\n\n<nav class=\"navbar\">\n <div class=\"nav-left\">\n <a href=\"/\" class=\"logo-link\" aria-label=\"Home\">\n <img "
},
{
"path": "src/env.d.ts",
"chars": 85,
"preview": "/// <reference path=\"../.astro/types.d.ts\" />\n/// <reference types=\"astro/client\" />\n"
},
{
"path": "src/layouts/Base.astro",
"chars": 725,
"preview": "---\nimport { ViewTransitions } from 'astro:transitions'\nimport MetaTags from '@layouts/MetaTags.astro';\n\nimport '@styles"
},
{
"path": "src/layouts/MetaTags.astro",
"chars": 3332,
"preview": "---\n\ninterface Props {\n\ttitle?: string;\n\tdescription?: string;\n keywords?: string;\n\tcustomSchemaJson?: any;\n\tbreadcrumb"
},
{
"path": "src/pages/account/index.astro",
"chars": 1869,
"preview": "---\nimport BaseLayout from '@layouts/Base.astro';\nimport NavBar from '@components/scafold/Nav.astro';\nimport Footer from"
},
{
"path": "src/pages/check/[...target].astro",
"chars": 1270,
"preview": "---\nimport BaseLayout from '@layouts/Base.astro';\nimport Main from '../../web-check-live/main.tsx';\nimport '../../web-ch"
},
{
"path": "src/pages/index.astro",
"chars": 1777,
"preview": "---\nimport BaseLayout from '@layouts/Base.astro';\nimport HeroForm from '@components/homepage/HeroForm.astro';\nimport Tem"
},
{
"path": "src/pages/self-hosted-setup.astro",
"chars": 6867,
"preview": "---\nimport BaseLayout from '@layouts/Base.astro';\nimport NavBar from '@components/scafold/Nav.astro';\nimport Footer from"
},
{
"path": "src/pages/web-check-api/index.astro",
"chars": 6815,
"preview": "---\nimport BaseLayout from '@layouts/Base.astro';\nimport NavBar from '@components/scafold/Nav.astro';\nimport Footer from"
},
{
"path": "src/pages/web-check-api/spec.astro",
"chars": 1642,
"preview": "---\nimport BaseLayout from '@layouts/Base.astro';\nimport NavBar from '@components/scafold/Nav.astro';\nimport Footer from"
},
{
"path": "src/styles/colors.scss",
"chars": 885,
"preview": ":root {\n --primary: #d6fb41;\n --primary-lighter: #cff97a;\n --text-color: #ffffff;\n --text-color-secondary: #ffffffb6"
},
{
"path": "src/styles/global.scss",
"chars": 2114,
"preview": "/* Global Stylesheet */\n@import './colors.scss';\n@import './media-queries.scss';\n@import './typography.scss';\n\n/* CSS Re"
},
{
"path": "src/styles/media-queries.scss",
"chars": 1522,
"preview": "// Breakpoints\n$breakpoint-xs: 599px; // Max width for mobile only\n$breakpoint-sm: 600px; // Min width for tablet port"
},
{
"path": "src/styles/typography.scss",
"chars": 4223,
"preview": "@font-face {\n font-family: 'Inter';\n src: url('/fonts/Inter-Thin.ttf') format('truetype');\n font-weight: 100;\n font-"
},
{
"path": "src/web-check-live/App.tsx",
"chars": 946,
"preview": "import { Routes, Route, Outlet } from 'react-router-dom';\n\nimport Home from 'web-check-live/views/Home.tsx';\nimport Resu"
},
{
"path": "src/web-check-live/assets/data/map-features.json",
"chars": 235884,
"preview": "{\n \"type\": \"Topology\",\n \"objects\": {\n \"world\": {\n \"type\": \"GeometryCollection\",\n \"geometries\": [\n "
},
{
"path": "src/web-check-live/components/Form/Button.tsx",
"chars": 2412,
"preview": "import { type ReactNode, type MouseEventHandler } from 'react';\n\nimport styled from '@emotion/styled';\nimport { keyframe"
},
{
"path": "src/web-check-live/components/Form/Card.tsx",
"chars": 1191,
"preview": "import styled from '@emotion/styled';\n\nimport { type ReactNode } from 'react';\nimport ErrorBoundary from 'web-check-live"
},
{
"path": "src/web-check-live/components/Form/Heading.tsx",
"chars": 2094,
"preview": "import styled from '@emotion/styled';\nimport colors from 'web-check-live/styles/colors';\nimport { TextSizes } from 'web-"
},
{
"path": "src/web-check-live/components/Form/Input.tsx",
"chars": 2066,
"preview": "import { type InputHTMLAttributes } from 'react';\nimport styled from '@emotion/styled';\nimport colors from 'web-check-li"
},
{
"path": "src/web-check-live/components/Form/Modal.tsx",
"chars": 2068,
"preview": "import React from 'react';\nimport type { ReactNode } from 'react';\nimport ReactDOM from 'react-dom';\nimport styled from "
},
{
"path": "src/web-check-live/components/Form/Nav.tsx",
"chars": 864,
"preview": "import styled from '@emotion/styled';\nimport type { ReactNode } from 'react';\nimport { Link } from 'react-router-dom';\n\n"
},
{
"path": "src/web-check-live/components/Form/Row.tsx",
"chars": 5662,
"preview": "import type { ReactNode } from 'react';\nimport styled from '@emotion/styled';\nimport colors from 'web-check-live/styles/"
},
{
"path": "src/web-check-live/components/Results/Archives.tsx",
"chars": 1298,
"preview": "import styled from '@emotion/styled';\nimport colors from 'web-check-live/styles/colors';\nimport { Card } from 'web-check"
},
{
"path": "src/web-check-live/components/Results/BlockLists.tsx",
"chars": 690,
"preview": "\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Row from 'web-check-live/components/Form/Row';\n\ncons"
},
{
"path": "src/web-check-live/components/Results/BuiltWith.tsx",
"chars": 1849,
"preview": "\nimport styled from '@emotion/styled';\nimport type { TechnologyGroup, Technology } from 'web-check-live/utils/result-pro"
},
{
"path": "src/web-check-live/components/Results/CarbonFootprint.tsx",
"chars": 1899,
"preview": "import { useEffect, useState } from 'react';\nimport styled from '@emotion/styled';\nimport { Card } from 'web-check-live/"
},
{
"path": "src/web-check-live/components/Results/ContentLinks.tsx",
"chars": 2248,
"preview": "import { Card } from 'web-check-live/components/Form/Card';\nimport Row from 'web-check-live/components/Form/Row';\nimport"
},
{
"path": "src/web-check-live/components/Results/Cookies.tsx",
"chars": 1991,
"preview": "import { Card } from 'web-check-live/components/Form/Card';\nimport { ExpandableRow } from 'web-check-live/components/For"
},
{
"path": "src/web-check-live/components/Results/DnsRecords.tsx",
"chars": 1168,
"preview": "import { Card } from 'web-check-live/components/Form/Card';\nimport Row, { ListRow } from 'web-check-live/components/For"
},
{
"path": "src/web-check-live/components/Results/DnsSec.tsx",
"chars": 6553,
"preview": "import { Card } from 'web-check-live/components/Form/Card';\nimport Row, { ExpandableRow, type RowProps } from 'web-check"
},
{
"path": "src/web-check-live/components/Results/DnsServer.tsx",
"chars": 1565,
"preview": "\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Heading from 'web-check-live/components/Form/Heading"
},
{
"path": "src/web-check-live/components/Results/DomainLookup.tsx",
"chars": 1485,
"preview": "\nimport colors from 'web-check-live/styles/colors';\nimport { Card } from 'web-check-live/components/Form/Card';\nimport R"
},
{
"path": "src/web-check-live/components/Results/Firewall.tsx",
"chars": 776,
"preview": "import styled from '@emotion/styled';\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Row from 'web-c"
},
{
"path": "src/web-check-live/components/Results/Headers.tsx",
"chars": 655,
"preview": "import { Card } from 'web-check-live/components/Form/Card';\nimport Row from 'web-check-live/components/Form/Row';\nimport"
},
{
"path": "src/web-check-live/components/Results/HostNames.tsx",
"chars": 1461,
"preview": "\nimport styled from '@emotion/styled';\nimport type { HostNames } from 'web-check-live/utils/result-processor';\nimport co"
},
{
"path": "src/web-check-live/components/Results/Hsts.tsx",
"chars": 1383,
"preview": "\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Row, { type RowProps } from 'web-check-live/componen"
},
{
"path": "src/web-check-live/components/Results/HttpSecurity.tsx",
"chars": 828,
"preview": "import { Card } from 'web-check-live/components/Form/Card';\nimport Row from 'web-check-live/components/Form/Row';\n\nconst"
},
{
"path": "src/web-check-live/components/Results/Lighthouse.tsx",
"chars": 1554,
"preview": "import { Card } from 'web-check-live/components/Form/Card';\nimport { ExpandableRow } from 'web-check-live/components/For"
},
{
"path": "src/web-check-live/components/Results/MailConfig.tsx",
"chars": 2004,
"preview": "\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Row from 'web-check-live/components/Form/Row';\nimpor"
},
{
"path": "src/web-check-live/components/Results/OpenPorts.tsx",
"chars": 753,
"preview": "import { Card } from 'web-check-live/components/Form/Card';\nimport Row from 'web-check-live/components/Form/Row';\n\nconst"
},
{
"path": "src/web-check-live/components/Results/Rank.tsx",
"chars": 2667,
"preview": "\nimport { AreaChart, Area, Tooltip, CartesianGrid, ResponsiveContainer } from 'recharts';\nimport colors from 'web-check-"
},
{
"path": "src/web-check-live/components/Results/Redirects.tsx",
"chars": 1228,
"preview": "import colors from 'web-check-live/styles/colors';\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Ro"
},
{
"path": "src/web-check-live/components/Results/RobotsTxt.tsx",
"chars": 883,
"preview": "\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Row, { type RowProps } from 'web-check-live/compone"
},
{
"path": "src/web-check-live/components/Results/Screenshot.tsx",
"chars": 759,
"preview": "import { Card } from 'web-check-live/components/Form/Card';\n\nconst cardStyles = `\n overflow: auto;\n max-height: 50rem;"
},
{
"path": "src/web-check-live/components/Results/SecurityTxt.tsx",
"chars": 2084,
"preview": "\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Row, { Details } from 'web-check-live/components/For"
},
{
"path": "src/web-check-live/components/Results/ServerInfo.tsx",
"chars": 938,
"preview": "import type { ServerInfo } from 'web-check-live/utils/result-processor';\nimport { Card } from 'web-check-live/components"
},
{
"path": "src/web-check-live/components/Results/ServerLocation.tsx",
"chars": 1823,
"preview": "\nimport styled from '@emotion/styled';\nimport type { ServerLocation } from 'web-check-live/utils/result-processor';\nimpo"
},
{
"path": "src/web-check-live/components/Results/ServerStatus.tsx",
"chars": 963,
"preview": "\nimport colors from 'web-check-live/styles/colors';\nimport { Card } from 'web-check-live/components/Form/Card';\nimport R"
},
{
"path": "src/web-check-live/components/Results/SiteFeatures.tsx",
"chars": 1944,
"preview": "import { Card } from 'web-check-live/components/Form/Card';\nimport colors from 'web-check-live/styles/colors';\nimport Ro"
},
{
"path": "src/web-check-live/components/Results/Sitemap.tsx",
"chars": 1909,
"preview": "\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Row, { ExpandableRow } from 'web-check-live/componen"
},
{
"path": "src/web-check-live/components/Results/SocialTags.tsx",
"chars": 1963,
"preview": "\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Row from 'web-check-live/components/Form/Row';\nimpor"
},
{
"path": "src/web-check-live/components/Results/SslCert.tsx",
"chars": 3528,
"preview": "\nimport styled from '@emotion/styled';\nimport colors from 'web-check-live/styles/colors';\nimport { Card } from 'web-chec"
},
{
"path": "src/web-check-live/components/Results/TechStack.tsx",
"chars": 2871,
"preview": "\nimport styled from '@emotion/styled';\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Heading from '"
},
{
"path": "src/web-check-live/components/Results/Threats.tsx",
"chars": 3506,
"preview": "\nimport styled from '@emotion/styled';\nimport colors from 'web-check-live/styles/colors';\nimport { Card } from 'web-chec"
},
{
"path": "src/web-check-live/components/Results/TlsCipherSuites.tsx",
"chars": 2635,
"preview": "\nimport { useState, useEffect } from 'react';\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Button "
},
{
"path": "src/web-check-live/components/Results/TlsClientSupport.tsx",
"chars": 2908,
"preview": "\nimport { useState, useEffect } from 'react';\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Button "
},
{
"path": "src/web-check-live/components/Results/TlsIssueAnalysis.tsx",
"chars": 5509,
"preview": "\nimport { useState, useEffect } from 'react';\nimport styled from '@emotion/styled';\nimport colors from 'web-check-live/s"
},
{
"path": "src/web-check-live/components/Results/TraceRoute.tsx",
"chars": 1715,
"preview": "import styled from '@emotion/styled';\nimport colors from 'web-check-live/styles/colors';\nimport { Card } from 'web-check"
},
{
"path": "src/web-check-live/components/Results/TxtRecords.tsx",
"chars": 765,
"preview": "\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Row from 'web-check-live/components/Form/Row';\n\ncons"
},
{
"path": "src/web-check-live/components/Results/WhoIs.tsx",
"chars": 2058,
"preview": "\nimport styled from '@emotion/styled';\nimport type { Whois } from 'web-check-live/utils/result-processor';\nimport colors"
},
{
"path": "src/web-check-live/components/boundaries/PageError.tsx",
"chars": 4026,
"preview": "import React from 'react';\nimport styled from '@emotion/styled';\n\nimport colors from 'web-check-live/styles/colors';\nimp"
},
{
"path": "src/web-check-live/components/misc/ActionButtons.tsx",
"chars": 1320,
"preview": "import styled from '@emotion/styled';\nimport Button from 'web-check-live/components/Form/Button';\nimport colors from 'we"
},
{
"path": "src/web-check-live/components/misc/AdditionalResources.tsx",
"chars": 10063,
"preview": "import styled from '@emotion/styled';\nimport colors from 'web-check-live/styles/colors';\nimport { Card } from 'web-check"
},
{
"path": "src/web-check-live/components/misc/DocContent.tsx",
"chars": 1807,
"preview": "import styled from '@emotion/styled';\nimport docs, { type Doc } from 'web-check-live/utils/docs';\nimport colors from 'we"
},
{
"path": "src/web-check-live/components/misc/ErrorBoundary.tsx",
"chars": 1749,
"preview": "import React, { Component, type ErrorInfo, type ReactNode } from \"react\";\nimport styled from '@emotion/styled';\nimport C"
},
{
"path": "src/web-check-live/components/misc/FancyBackground.tsx",
"chars": 9805,
"preview": "import { useEffect, useMemo } from \"react\";\n\n\nconst FancyBackground = (): JSX.Element => {\n \n const makeAbsolute = (el"
},
{
"path": "src/web-check-live/components/misc/Flag.tsx",
"chars": 537,
"preview": "interface Props {\n countryCode: string,\n width: number,\n};\n\nconst Flag = ({ countryCode, width }: Props): JSX.Element "
},
{
"path": "src/web-check-live/components/misc/Footer.tsx",
"chars": 1557,
"preview": "import styled from '@emotion/styled';\nimport { Link } from 'react-router-dom';\nimport colors from 'web-check-live/styles"
},
{
"path": "src/web-check-live/components/misc/Loader.tsx",
"chars": 2914,
"preview": "import styled from '@emotion/styled';\n\nimport { StyledCard } from 'web-check-live/components/Form/Card';\nimport Heading "
},
{
"path": "src/web-check-live/components/misc/LocationMap.tsx",
"chars": 1322,
"preview": "import {\n ComposableMap,\n Geographies,\n Geography,\n Annotation,\n} from 'react-simple-maps';\n\nimport colors from 'web"
},
{
"path": "src/web-check-live/components/misc/ProgressBar.tsx",
"chars": 13047,
"preview": "import styled from '@emotion/styled';\nimport colors from 'web-check-live/styles/colors';\nimport Card from 'web-check-liv"
},
{
"path": "src/web-check-live/components/misc/SelfScanMsg.tsx",
"chars": 1844,
"preview": "\nimport styled from '@emotion/styled';\nimport colors from 'web-check-live/styles/colors';\nimport { StyledCard } from 'we"
},
{
"path": "src/web-check-live/components/misc/ViewRaw.tsx",
"chars": 3039,
"preview": "import React, { useState } from 'react';\nimport styled from '@emotion/styled';\nimport colors from 'web-check-live/styles"
},
{
"path": "src/web-check-live/hooks/motherOfAllHooks.ts",
"chars": 4555,
"preview": "import { useState, useEffect } from 'react';\nimport { toast } from 'react-toastify';\nimport 'react-toastify/dist/ReactTo"
},
{
"path": "src/web-check-live/main.tsx",
"chars": 333,
"preview": "import { BrowserRouter } from \"react-router-dom\";\nimport { StaticRouter } from \"react-router-dom/server\";\nimport App fro"
},
{
"path": "src/web-check-live/styles/colors.ts",
"chars": 478,
"preview": "\nconst colors = {\n primary: '#c1fb41',\n primaryLighter: '#cff97a',\n textColor: '#ffffff',\n textColorSecondary: '#a4b"
},
{
"path": "src/web-check-live/styles/dimensions.ts",
"chars": 752,
"preview": "export type InputSize = 'small' | 'medium' | 'large';\n\nexport const applySize = (inputSize?: InputSize) => {\n const siz"
},
{
"path": "src/web-check-live/styles/globals.tsx",
"chars": 490,
"preview": "import { Global, css } from '@emotion/react';\n\nconst GlobalStyles = () => (\n <Global\n styles={css`\n @font-face {\n"
},
{
"path": "src/web-check-live/styles/index.css",
"chars": 1099,
"preview": "@font-face {\n font-family: 'PTMono';\n src: url('/fonts/PTMono-Regular.ttf') format('truetype');\n font-weight: normal;"
},
{
"path": "src/web-check-live/styles/typography.ts",
"chars": 479,
"preview": "\nexport const TextSizes = {\n xSmall: '0.75rem',\n small: '1rem',\n medium: '1.5rem',\n large: '2rem',\n xLarge: '3rem',"
},
{
"path": "src/web-check-live/typings/file-types.d.ts",
"chars": 23,
"preview": "declare module \"*.ttf\"\n"
},
{
"path": "src/web-check-live/typings/react-simple-maps.d.ts",
"chars": 36,
"preview": "declare module 'react-simple-maps';\n"
},
{
"path": "src/web-check-live/utils/address-type-checker.ts",
"chars": 2513,
"preview": "/** \n * Helper functions to determine if a string is a valid web address,\n * and what type of address it is: URL, IPv4, "
},
{
"path": "src/web-check-live/utils/docs.ts",
"chars": 47121,
"preview": "export interface Doc {\n id: string;\n title: string;\n description: string;\n use: string;\n resources: string[] | { ti"
},
{
"path": "src/web-check-live/utils/get-keys.ts",
"chars": 599,
"preview": "\nconst keys = {\n shodan: import.meta.env.REACT_APP_SHODAN_API_KEY || \"default_value_if_not_set\",\n whoApi: import.meta."
},
{
"path": "src/web-check-live/utils/result-processor.ts",
"chars": 4347,
"preview": "import type { RowProps } from 'web-check-live/components/Form/Row';\n\nexport interface ServerLocation {\n city: string,\n"
},
{
"path": "src/web-check-live/views/About.tsx",
"chars": 13286,
"preview": "import styled from '@emotion/styled';\nimport { useEffect } from 'react';\nimport { useLocation } from 'react-router-dom';"
},
{
"path": "src/web-check-live/views/Home.tsx",
"chars": 9309,
"preview": "import styled from '@emotion/styled';\nimport { type ChangeEvent, type FormEvent, useState, useEffect } from 'react';\nimp"
},
{
"path": "src/web-check-live/views/NotFound.tsx",
"chars": 1607,
"preview": "\nimport styled from '@emotion/styled';\n\nimport colors from 'web-check-live/styles/colors';\nimport Heading from 'web-chec"
},
{
"path": "src/web-check-live/views/Results.tsx",
"chars": 34997,
"preview": "import { useState, useEffect, useCallback, type ReactNode } from 'react';\nimport { useParams, useLocation } from 'react-"
},
{
"path": "svelte.config.js",
"chars": 101,
"preview": "import { vitePreprocess } from '@astrojs/svelte';\n\nexport default {\n\tpreprocess: vitePreprocess(),\n}\n"
},
{
"path": "tsconfig.json",
"chars": 925,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"ES2020\",\n \"module\": \"ES2020\",\n \"moduleResolution\": \"node\",\n \"allowImpor"
},
{
"path": "vercel.json",
"chars": 496,
"preview": "{\n \"version\": 2,\n \"routes\": [\n {\n \"src\": \"/api/(.*)\",\n \"dest\": \"/api/$1.js\"\n }\n ],\n \"functions\": {\n "
},
{
"path": "vite.config.js",
"chars": 288,
"preview": "// vite.config.js\nimport { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\n\nexport default defineC"
}
]
// ... and 48 more files (download for full content)
About this extraction
This page contains the full source code of the Lissy93/web-check GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 214 files (840.1 KB), approximately 264.7k tokens, and a symbol index with 62 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.