Skip to content

Latest commit

 

History

122 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CoolSites

Version License Sites Categories Status GitHub Pages

Buy me a coffee on Ko-fi

If CoolSites helps you find something useful, a coffee helps me keep the directory curated and current.

A curated directory of 586 free tools, open source projects, and hidden gems across 30 categories, built for sysadmins, devs, creators, homelabbers, and the endlessly curious.

Browse the directory

Pick a category, search, star what you want to keep, and the directory remembers your groups in the browser. Nothing is sent anywhere. There is no account, no backend and no tracking of any kind.

Features

  • 586 curated sites across 30 categories, every entry hand-picked and described
  • A browse workspace. Bookmarks stay in the fixed rail beside the category and metadata filters, leaving the rest of the screen for search and results.
  • Focused collections. Pick a starting point in the rail, scan every member, then move to another collection without leaving the page.
  • Search that narrows. Every word you type has to match. Names tolerate typos, longer fields need a real match, and hits are highlighted.
  • Filters that stack. Category, tags, open source only, no account needed, staff picks and the products a tool replaces all combine, with live counts on each. Every count is measured against the list as it already stands, so a chip that says 12 gives you 12, and a term that would leave you with nothing is never offered.
  • Sorting. A to Z, Z to A, recently added, shuffle, or the default with your bookmarks floated to the top.
  • Ten themes. Eight dark (OLED, Catppuccin, Dracula, Nord, Rosé Pine, GitHub Dark, Midnight, Solarized), one light, and a system option that follows the OS.
  • Bookmarks with groups. Star a site, sort it into a colour-coded group, drag to reorder. Removing a bookmark or deleting a group can be undone.
  • Export and import. Bookmarks download as versioned JSON and restore on any device. Imports are validated entry by entry and report what they skipped.
  • Grid and list views, remembered in the URL along with your search, category, sort, tags and filters, so a view can be shared as a link.
  • Keyboard first. / focuses search, arrows move between cards, Enter opens, ? lists every shortcut.
  • Copy as Markdown, so the current filtered list can be pasted into notes or an issue.
  • Random pick from whatever is on screen, and a Wayback Machine link on every card.
  • Compare up to five sites side by side.
  • Feeds. Atom and JSON Feed of recent entries, generated at build time.
  • Works offline after the first visit, and says so when it is showing you cached data.
  • No third-party requests. Fonts and favicons ship with the site. Nothing loads from a CDN, and nothing calls home.
  • No runtime dependencies. Static HTML, CSS, JavaScript and JSON. No framework, no build step to view it.
  • Accessible. Body text clears WCAG AA in every theme, with visible focus, live regions, a skip link, and reduced-motion and forced-colors support.
  • Self-hostable on GitHub Pages, any static web server, or the local server in this repo.

Categories

Category Sites Highlights
Desktop Software 50 DevToys, PowerToys, Files, Everything, ShareX, VLC, Ventoy, HWiNFO
Homelab 38 Uptime Kuma, Proxmox, Tailscale, Pi-hole, Jellyfin, Immich, Paperless-ngx
Dev Tools 35 Regex101, Hoppscotch, CodePen, StackBlitz, Zed, Cursor, Playwright
AI & ML 32 Ollama, Open WebUI, LM Studio, ComfyUI, Hugging Face, Groq, Dify
Creative 30 Blender, Figma, Photopea, Excalidraw, Penpot, Coolors, fffuel.co
Sysadmin 27 Grafana, Netdata, Prometheus, Terraform, Wazuh, Cockpit, btop
Fun & Culture 26 Neal.fun, Radio Garden, Shadertoy, Geoguessr, xkcd, Noclip, Wiby
Privacy 25 Mullvad, Tor, Signal, ProtonMail, SearXNG, uBlock Origin, Tails
Security 25 CyberChef, Have I Been Pwned, VirusTotal, OWASP ZAP, Qualys SSL Labs
Productivity 24 Obsidian, Notion, Linear, Cal.com, Stirling PDF, NocoDB, Typst
Education 21 freeCodeCamp, Khan Academy, MIT OCW, CS50, LeetCode, Nand2Tetris
CLI Tools 20 fzf, ripgrep, bat, eza, lazygit, lazydocker, neovim, starship
News & Reference 19 Hacker News, ArchWiki, Stack Overflow, AlternativeTo, free-for.dev
OSINT 19 Shodan, Censys, Wayback Machine, SpiderFoot, TinEye, BuiltWith
Torrenting 18 qBittorrent, Transmission, Deluge, Jackett, Prowlarr, Stremio
Gaming 16 RetroArch, itch.io, Lichess, Board Game Arena, OpenTTD, RPCS3
AI Creative 15 Midjourney, Suno, Runway, ElevenLabs, Remove.bg, Recraft, Pika
File Tools 15 Cobalt, yt-dlp, FFmpeg, HandBrake, Pandoc, Croc, PDF24
IP & Network 15 BGP.Tools, IPinfo.io, ping.pe, Robtex, HackerTarget, ViewDNS
Media Downloads 13 yt-dlp, Cobalt, JDownloader, gallery-dl, Stremio
Networking 13 Wireshark, nmap, PuTTY, mRemoteNG, NetBox, Netbird, WinSCP
Data & Viz 12 Metabase, Apache Superset, Datawrapper, D3.js, Mermaid Live, Kepler.gl
Reading 11 Calibre, Readarr, Kavita, Libby, Standard Ebooks
Arr Stack 10 Sonarr, Radarr, Lidarr, Prowlarr, Bazarr, Overseerr
Automation 10 n8n, Node-RED, Huginn, Apache Airflow, Pipedream, Make, Temporal
DNS Tools 10 whatsmydns.net, DNSViz, MXToolbox, DNS Leak Test, Zonemaster
Music & Audio 10 Ardour, LMMS, Vital, Mixxx, MuseScore, Bandlab, Freesound
Speed Tests 10 Cloudflare, Fast.com, Waveform Bufferbloat, LibreSpeed, M-Lab
Containers 9 Docker, Podman, k3s, Lens, Helm, ArgoCD, Harbor, Rancher
Indexes & Wikis 8 ArchWiki, Gentoo Wiki, FMHY, Awesome Privacy

Quick Start

CoolSites is a static site with no runtime dependencies. It loads its data from companion JSON files, so it needs to be served over HTTP rather than opened from disk.

GitHub Pages (recommended)

  1. Fork this repository
  2. Go to Settings > Pages
  3. Set source to main branch, root folder
  4. Your directory is live at https://yourusername.github.io/CoolSites/

Local

git clone https://github.com/SysAdminDoc/CoolSites.git
cd CoolSites
npm run serve

Open the printed http://127.0.0.1:4173/ address. Opening index.html straight from disk will not work, because browsers block file:// fetches; the page says so and tells you to run the server if you try.

Any other web server

Copy the repository contents into the document root. It works with nginx, Apache, Caddy, or python3 -m http.server. There is no client-side routing, so don't add a catch-all rewrite to index.html. It would turn every typo into a page that looks like the homepage but isn't.

Two things are worth excluding from the document root if you can: the .git directory and anything starting with a dot. The bundled npm run serve already refuses them.

The content security policy travels in a <meta> tag, so it applies wherever you host the page, including GitHub Pages.

Security headers

Three things a meta tag cannot do, so they need a real server. GitHub Pages sends none of them, which is worth knowing rather than assuming: the hosted copy can be framed by anyone and browsers are free to sniff content types.

Content-Security-Policy: <the policy from index.html>; frame-ancestors 'none'
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(), microphone=(), camera=()

The policy is not written out here because it is not a constant. script-src and style-src each list a SHA-256 for every inline block, and npm run generate recomputes those as its last step, so copying the string anywhere means copying something that goes stale the next time a script or a stylesheet changes. Read it out of the <meta> tag in index.html, which is what npm run serve does. The only thing worth adding on top is frame-ancestors, which browsers ignore in a meta tag, so the copy hosted on GitHub Pages genuinely does not have it.

npm run serve sends all four, so local development matches a properly configured host.

On nginx, remember that add_header does not inherit into a location block that sets any header of its own, so these have to be repeated in each one rather than declared once at the server level.

How It Works

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│   sites.json     │────>│   Filter Engine  │────>│   Card Renderer  │
│                  │     │                  │     │                  │
│  One JSON entry  │     │  Category match  │     │  Grid / List     │
│  Name, URL,      │     │  + Fuzzy search  │     │  + View Trans.   │
│  Desc, Tags,     │     │  + Sort modes    │     │  + Lazy favicons │
│  Metadata        │     │  + Metadata      │     │                  │
└─────────────────┘     └─────────────────┘     └─────────────────┘

Data and generated feeds live beside index.html:

File What it holds
sites.json Every entry: name, URL, description, category, tags, open source and account flags
categories.json Category names, colours and blurbs
collections.json Curated sub-lists rendered by collections.html
favicons.json Icons inlined as data URIs so no icon is fetched at runtime
stars.json Cached GitHub star counts, refreshed locally, never fetched by the browser
cache-manifest.json When each cache above was refreshed, from where, and its hash
feeds/ Atom and JSON Feed of recent entries, generated by npm run generate
schemas/ JSON Schema for the site, category and collection shapes

No tracking, no cookies, no analytics, and no third-party requests. Bookmarks live in localStorage and never leave the browser.

Commands

npm run serve      # local HTTP server on 127.0.0.1:4173
npm run lint       # validate the data, feeds, and every version and count string
npm run generate   # regenerate feeds and sync versions and counts
npm run build      # lint sources, generate, then re-validate
npm test           # drive the app in headless Chrome over CDP
npm run package    # write dist/ with a minified single-page build
npm run check:links      # check every URL in sites.json and write a report
npm run update:stars     # refresh stars.json (set GITHUB_TOKEN to avoid the 60/hour cap)
npm run update:favicons  # refresh favicons.json and re-encode oversized icons
npm run sync:repo        # set the GitHub description and topics from the same data

npm run lint guarantees no count can drift inside the repository. Nothing covered anything outside it, so the description on GitHub sat at "588 free tools" while sites.json held 586, and the first place most people read about this project was the one place the guarantee did not reach. npm run sync:repo fixes that from the same source the README badges use, and npm run sync:repo -- --check reports a difference without changing anything. It needs the gh CLI. It is deliberately not part of npm run build: a build that reaches out and mutates something on github.com would be a surprising build.

There is no lockfile because there is nothing to lock. No runtime dependencies, no dev dependencies, no framework, no build step to view the page. That is a deliberate choice rather than an oversight, and npm run lint enforces it: the day a dependency appears in package.json without a package-lock.json beside it, the build fails.

Discoverability

npm run build writes sitemap.xml. Its lastmod is the date of the freshest entry in the directory rather than the moment the build ran, because a timestamp that moves whenever someone regenerates a feed tells a crawler nothing and teaches it to stop believing the field. robots.txt points at the sitemap and disallows only work/, which is where the local link-check report lands.

social-card.png is what appears when the site is pasted into Slack, Discord or Mastodon. Without it every share is a bare blue link, and sharing is how a directory gets found. node scripts/generate-social-card.js renders it in headless Chrome from an HTML card with the repository's own fonts inlined, so it needs no network and no image library. It is generated by hand rather than by the build, and it deliberately carries no site count: putting one on it would mean rewriting a 114KB binary on every commit that adds an entry, and git would keep every version of it forever.

Taking the directory elsewhere

npm run build writes feeds/directory.opml, the whole directory grouped by category. OPML is what the webring and blogroll world reads, and it is the one format that lets somebody take this list somewhere else rather than only look at it here. Kagi's small-web list and the XXIIVV webring both publish one.

No outline carries an xmlUrl. OPML's usual job is carrying feed subscriptions, so a reader that sees one will try to poll it, and most of these are sites with no feed at all. Claiming a feed where there is none breaks the reader that trusts you.

Favicon weight

favicons.json ships in full on every visit, so it is the largest single thing a reader waits for. The generator asks Google for a 32px icon, but Google has no icon for a long tail of domains, and the fallbacks hand back whatever the site happens to have: a multi-resolution .ico carrying every size from 16 to 256px, or an apple-touch-icon at 180. Four of those were 21% of the file on their own, the largest at 58KB.

npm run update:favicons now decodes anything over 2KB in the headless Chrome that already runs the tests, redraws it at 32px, and keeps whichever of WebP or PNG comes out smaller. It only keeps a result that actually shrank, so an icon that was already the right size is left alone, and SVG is skipped entirely because rasterising a vector trades away the thing worth keeping. That took the cache from 827KB to 593KB across 525 domains. Pass --no-reencode to skip it when Chrome is unavailable and you only need the fetch.

Two ceilings stop it creeping back: 8KB for any one raster icon, and 700KB for the file. Both fail the lint rather than warn.

An entry in favicons.json is one of three things. A data URI means an icon was found. false means all four sources were tried and there is nothing to find: putty.org serves no <link> tags at all, retroarch.com has seven and none of them is an icon. Absent means nobody has looked yet. Without that middle state every refresh re-attempted the same thirteen domains and spent four requests each learning what the last run already knew. --retry-missing asks again, for when one of them starts publishing an icon.

npm run lint is the gate that matters. It validates every data file against the schemas, rejects duplicate and colliding URLs, checks the generated feeds match the data, and fails when any version or count string in index.html, manifest.json, sw.js or this README has drifted from package.json and sites.json.

Cached data

stars.json and favicons.json are copies of somebody else's data, and a copy with no date on it is indistinguishable from a fresh one. cache-manifest.json records, for each: the command that wrote it, the sources it read, when the run finished, how many records it holds, how many fetches failed, and a SHA-256 of the file. The hash is the part that matters. A manifest carrying only a date would still read "fetched today" after somebody hand-edited the file, so npm run lint compares the hash and the record count and fails when either has moved without a refresh.

npm run update:stars also records whether it ran with a GITHUB_TOKEN. Without one the API allows 60 requests an hour, the run stops at the first 403, and what lands is a partial refresh wearing today's date. With that flag in the manifest the two can be told apart.

The page reads the manifest and hides star counts once the cache is more than 90 days old, and the diagnostics panel says why under Data. A star count is exactly the kind of number that looks current whatever its age. A cache with no manifest record is treated the same way: nobody can date it, so nothing repeats it. Icons have no such limit, because an icon does not go wrong the way a count does.

Entry dates

Two fields, and they mean different things. updatedAt is when the entry's own text last changed. lastReviewedAt is when a person last opened the site and confirmed the entry still describes it.

Most of the directory carries 2026-06-25 and no lastReviewedAt. That is the date of one bulk import, not a review, and pretending otherwise is what made the "recent" sort and the feeds alphabetical order with a timestamp attached. So the feeds now publish only entries with a real lastReviewedAt, which is a short list and an honest one.

After you have actually checked an entry:

npm run review -- https://example.com              # reviewed today, text unchanged
npm run review -- https://example.com --changed    # the entry's text changed too
npm run review -- --list                           # how much is still unreviewed

npm run lint enforces the rest: no future dates, no review dated before the change it reviewed, a lastReviewedAt on anything not left over from the import, and a ceiling on how many entries may carry the import date, so nothing new can be filed under it to dodge the rule.

Diagnostics

Add ?debug=1 to the URL and a panel opens with the app version, where the data came from, how old it is, the service worker state, which caches exist and how much they hold, and every error since the page loaded. Two recovery actions sit at the bottom: copy the report, or clear the caches, unregister the worker and reload.

It reads this browser and shows you what it found. Nothing is sent anywhere, there is no analytics of any kind in this project, and the panel never opens without the parameter. It is most useful on a self-hosted copy that is serving stale data, which is almost always an old cache the worker is still holding.

Checking links

npm run check:links walks every URL in sites.json and writes a JSON report to work/link-check.json. The whole directory takes about a minute. By default it writes nothing back, because whether a dead entry should be repaired or removed is an editorial call.

Pass --write and it records what it saw: linkStatus and lastCheckedAt on each entry it actually reached. That's an observation, not a decision, and it's the difference between a directory that says "checked last Tuesday" and one that asks you to take its word. Nothing else in the entry moves. lastCheckedAt is not lastReviewedAt: a check says the URL answered, a review says a person opened the page and confirmed the entry still describes it. If the result would fail the lint, the write is rolled back rather than left for whoever pulls next.

Cards show the check date. Anything that didn't answer normally says what happened and is reachable through the Link issues filter, which is a shareable URL like any other filter.

The point of it is telling apart things that look identical from a script:

Status Meaning
ok reachable at the URL on file
moved permanently redirected somewhere genuinely different, so the entry is stale
redirect temporary redirect, nothing to do
blocked a bot wall: it refused this client but serves a browser fine
dead 404 or 410 for everyone, including a browser
tls certificate or handshake failure
dns the hostname no longer resolves
timeout no answer inside the timeout, twice
error anything else: a dropped connection, a CDN edge failure, a 500, a redirect that goes nowhere

dead, tls and dns set a non-zero exit code. Everything else is information.

Only a 404 or a 410 counts as dead. A 500 or a 503 is a broken server, not a removed page, and failing the run on one would tell you to delete a live entry.

Before calling anything dead it asks once more with a browser user agent and downgrades to blocked if that works, because bot walls answer scripts with 404s and 503s just as readily as with 403s. One of them handed back a 404 to the checker while serving a real page to Chrome. The retry also checks the browser landed on the same URL, so a parked domain that redirects every path to its homepage does not get read as proof of life.

Some hosts refuse every automated request however politely it asks. Set checkDisabled on the entry to a sentence saying why, and the checker skips it and prints the reason instead of reporting the same false alarm every run. --recheck includes them anyway.

Useful flags: --write to record the result, --filter <text> to check one site, --limit <n> for a quick sample, --concurrency <n> (default 8), --timeout <ms> (default 15000), --recheck to include skipped entries, and --out <path> to put the report somewhere else.

npm run check:links -- --filter github.com
npm run check:links -- --write
npm run check:links -- --concurrency 12 --timeout 20000

Embedding

widget.js drops a short list of entries onto another page. One script tag, no dependencies, and exactly one request, to sites.json beside it. It renders in a shadow root so nothing leaks either direction.

<script src="https://your-host/CoolSites/widget.js"
        data-category="Homelab"
        data-limit="8"></script>
Attribute Meaning
data-category Show only this category, spelled exactly as the directory does. An unknown name says so rather than rendering an empty box.
data-limit How many entries, 1 to 50. Anything else falls back to 8.
data-target CSS selector for where to mount. Defaults to the script tag's parent.
data-theme auto (default), light or dark.

It follows the reader's system theme unless you pin it, and you can repaint it from the host page:

coolsites-widget {
  --coolsites-bg: #fff;
  --coolsites-text: #111;
  --coolsites-muted: #666;
  --coolsites-border: rgba(0, 0, 0, .12);
  --coolsites-accent: #2563eb;
  --coolsites-radius: 8px;
}

The element carries its contract version in data-version. Offline, a timeout and a failed load each say which one happened.

Badge

If your project is listed here, badge.svg is yours to use. It is one static file, it loads no font and makes no request of its own, and it reads on a light or a dark page.

<a href="https://sysadmindoc.github.io/CoolSites/"><img
   src="https://sysadmindoc.github.io/CoolSites/badge.svg"
   alt="listed on CoolSites" width="128" height="20"></a>
[![listed on CoolSites](https://sysadmindoc.github.io/CoolSites/badge.svg)](https://sysadmindoc.github.io/CoolSites/)

Adding Sites

The easiest way is to open a submission issue. Fill in the form and it gets reviewed from there.

To contribute directly, add an entry to sites.json and run npm run build:

{
  "name": "Tool Name",
  "url": "https://example.com",
  "description": "What it does and why it is worth knowing about.",
  "category": "Dev Tools",
  "openSource": true,
  "requiresAuth": false,
  "updatedAt": "2026-08-25",
  "tags": ["self-hosted", "docker"],
  "keywords": ["reverse proxy", "acme"],
  "editorsPick": true,
  "alternativeTo": ["Some Paid Thing"]
}

keywords, editorsPick, alternativeTo and repository are optional; everything else is required and enforced by schemas/site.schema.json. category has to be one of the names in categories.json, and npm run build updates the per-category counts, the feeds, and the counts in this README for you.

Tags and keywords are different things. tags is a shared vocabulary and every value has to already exist in tags.json, which only admits a term three or more entries have in common. That floor is what keeps it a browsing structure instead of a second description. Everything else an entry is about goes in keywords: search reads both at the same weight, so nothing becomes harder to find, but a term only one site uses never becomes a filter that returns one result. If a keyword grows to three entries, promote it by adding it to tags.json and moving it. Lint fails if a tag is missing from the vocabulary, if a vocabulary tag has fallen below the floor, if the same word appears in both fields, or if two spellings of one term both get in.

repository is where the source lives when that is not the main link. A project gets one entry, and lint treats url and repository as one address space, so listing a project twice fails the build.

Tech Stack

Component Technology
Structure Semantic HTML5 with ARIA
Theme menu Popover plus CSS anchor positioning, with a scripted fallback for browsers that have neither
Styling Vanilla CSS (custom properties, color-mix, content-visibility, forced-colors)
Logic Vanilla JavaScript, no framework
Fonts Outfit and JetBrains Mono, self-hosted as woff2
Icons Inline SVG, plus favicons inlined as data URIs at build time
Hosting GitHub Pages or any static web server
Local tooling Node.js 22.17 or newer for the lint, build, package, test and server scripts

Search

Every word you type has to match, so adding words narrows the list. Names tolerate a typo, longer fields need a real match, and if nothing matches literally the search relaxes and says so.

Type What it does
docker compose Both words have to match somewhere
"command line" Matches the phrase exactly, in that order
#dns Only entries carrying that tag or keyword, matched exactly, so #dns never means dnssec
#"open source" A tag with a space in it
-windows Leaves out anything mentioning the word
-#torrent Leaves out every entry carrying that tag

Anything else is read as words. A stray dash, a lone # or a quote you have not closed yet narrows nothing rather than emptying the page, which matters because the results update while you type.

Keyboard Shortcuts

Key Action
/ Focus search
Escape Clear the search box and close any open dropdown
Arrow keys Move between cards
Enter Open the focused card
Shift+Enter Open the focused card in a new tab
? Show the shortcut list

Contributing

Contributions are welcome. To add a site:

  1. Fork the repository
  2. Add your entry to sites.json
  3. Run npm run build, which validates the entry and updates the counts and feeds
  4. Open a pull request with the generated files included

What gets in

These are the rules submissions are actually judged against. They're written down because the first two we turned down were judged against reasons invented in the reply, which isn't fair to anyone who spends time on a submission.

Free means free. The site has to be free, or have a free tier that stands on its own. A tier that exists to show you what you're missing doesn't count. The submission we declined offered the last two days of results plus three full reports a week, with everything else behind a $49.99 a year subscription. Compare that against what's already listed: Ollama and Hugging Face are free to use for the thing they're for. That's the bar.

Six months old, and still moving. A project needs six months of public history before it's eligible. Plenty of good tools launch and vanish inside a year, and a directory that lists them is a directory of dead links. If a project hasn't shipped anything or answered an issue in twelve months, it becomes a candidate for removal. Some tools are simply finished, so this is a prompt to go look rather than an automatic delete. npm run update:stars records whether each repository is archived and when it was last pushed, and prints the ones that have crossed that line. A card says "Archived" when the author has said so themselves, which is worth more than any threshold guessed from dates.

Say if it's yours. Submitting your own project is fine and several good entries got here that way. Say so in the submission. What gets declined is the pattern where the same link lands in thirty directories the same week, because that's someone buying backlinks, not telling us about a tool. Every outbound link here carries rel="nofollow ugc", so there's nothing to buy.

If it's yours, bring two that aren't. A self-submission should also propose two entries you have no stake in. It costs you the same twenty minutes it costs whoever reviews it, and it's the difference between contributing to a directory and using one.

No affiliate links, no sponsored entries, no ads. There is no paid placement and there never will be.

What counts as open source. For software, an OSI-approved licence. For a curated list or a dataset, where the OSI has approved nothing and never will, an open content licence: CC0, CC BY or CC BY-SA. Source-available but restricted is not open whichever way it is spelled, so FSL, BUSL and SSPL entries read openSource: false and carry a note saying which one and why. Writing this down matters because the two halves are judged by different standards and it would otherwise look like an inconsistency rather than a rule.

Check removed.json first. Anything taken out of the directory leaves a record there saying when and why. Deleting a row on its own records nothing you can read before opening a pull request, and the predictable result is that somebody proposes the same site again in good faith and gets turned down for a reason nobody wrote down. Reversing a decision means deleting the record.

Descriptions say what it does and why it's worth knowing about. One or two sentences. Prefer open source when there's a reasonable alternative.

A worked example of a good self-submission: PR #8 disclosed the affiliation, set openSource: false correctly for a source-available licence rather than claiming otherwise, and ran the lint before opening.

Reporting a security problem

Email matt_parker@outlook.com, or open an issue if the problem is already public. Expect a reply within a week. There is no bounty and no formal disclosure process; this is one person maintaining a static site.

In scope: anything that gets script running in a reader's browser, anything that makes the page fetch from a third party, and anything in scripts/ that would let a contributed entry run code on a maintainer's machine at build time. The cached favicons are the interesting surface there: they are 522 base64 blobs fetched from third-party hosts, and the fetcher sniffs magic bytes rather than trusting the declared content type, because a server once claimed image/x-icon for a macOS alias file.

Out of scope: anything about the sites the directory links to. They are other people's, and a listing is not an endorsement of their security. Report a dangerous or compromised entry as an ordinary issue and it will be removed.

There is no SECURITY.md because this repository tracks exactly one Markdown file by design.

License

MIT License. See LICENSE for details.


Browse the full directory with all 586 sites.

About

A curated directory of 586 free tools, open source projects, and hidden gems across 30 categories, built for sysadmins, devs, creators, homelabbers, and the endlessly curious.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

13 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages