Skip to content

Better legal research using Raycast

Setting up Quicklinks for faster, more productive legal research

Hey folks, I put together another quick legal tech tutorial using macOS software—this time the incredibly powerful and useful Raycast. Raycast is a supercharged launcher and productivity tool that can dramatically streamline legal research workflows through its extensible commands, snippets, and AI capabilities.

As always, I've narrated a screenshare that shows how I use it in my legal practice. The screenshare covers a lot of ground, so let's break it down.

Most of my use cases for legal research involve custom web searches, called Quicklinks in Raycast. I explain how to set them up toward the end of the screenshare. Here are written instructions for convenience:

  1. Create a new Quicklink

Open Raycast Settings (i.e., by searching Raycast Settings within Raycast), switch to the Extensions tab, and click the + button, then Create Quicklink:

You'll now see the window for configuring a new platform for quick searches.

  1. Name the Quicklink

Under name, enter the name of the service you're configuring. You can enter an abbreviation here, e.g. fr for the Federal Register, but note you can separately assign an abbreviation as an alias later—so my approach is to spell out the name of the service in the name field.

  1. Determine the URL scheme

More important is the link field. To determine the correct entry, you should visit the site in question, use the built-in search feature or visit a page on the site you want accessible via Raycast, and examine its URL. Find the relevant variable(s). For instance, URLs for the CFR database Cornell Law School's Legal Information Institute look like this:

https://www.law.cornell.edu/cfr/text/50/402.02

That happens to be the link to the definitions section of the implementing regs for the Endangered Species Act, 50 C.F.R. § 402.02. You'll notice that the URL follows this pattern:

https://www.law.cornell.edu/cfr/text/{Title}/{Part}

And guess what? That's exactly what you enter in the link field in Raycast. Just make sure to wrap the field names in curly brackets, e.g., { and }.

That's it. Easy, right? Easy, and incredibly powerful.

Here are some of the URL schemes for sites I commonly use in my legal research, ready for your use in Raycast:

Federal Register - https://www.federalregister.gov/documents/search?conditions%5Bterm%5D={Query}

Westlaw - https://1.next.westlaw.com/Search/Results.html?query={Query}

U.S. Code (Cornell LII) - https://www.law.cornell.edu/uscode/text/{Title}/{Part}

C.F.R. (Cornell LII) - https://www.law.cornell.edu/cfr/text/{Title}/{Part}

eCFR - https://www.ecfr.gov/search?search%5Bdate%5D=current&search%5Bquery%5D={Query}

FRCP (Cornell LII)- https://www.law.cornell.edu/rules/frcp/rule_{Rule}

FRAP (Cornell LII) - https://www.law.cornell.edu/rules/frap/rule_{Rule}

RECAP (CourtListener) - https://www.courtlistener.com/?q=&type=r&order_by=score%20desc&docket_number={Docket}&court={Court}

I hope that's a helpful starting point for folks checking out Raycast for better legal research on macOS. And if you're a fellow Obsidian user and want to try out the Ultra Note or Reminder extensions I wrote for Raycast, have a look at the code here.

Cheers,

Better document review in DEVONthink with Bates deeplinks and AppleScript.

Hey folks, in this post I'm sharing and explaining a custom script I wrote for DEVONthink that greatly assists me when I'm reviewing large batches of Bates-stamped documents (e.g., administrative records or discovery dumps).

Specifically, this script will:

  1. copy selected text in a PDF to the clipboard;
  2. determine the Bates number of the page the selected text is on;
  3. generate a Markdown-formatted Bates cite for that page linking back to the document, page, and specific passage of text that was selected; and
  4. append this Bates 'deeplink' to the clipboard.

ⓘ About DEVONthink

DEVONthink is comprehensive document management and productivity software exclusive to macOS that has seen continuous updates, improvements, and new features added for over two decades. The developer calls it "your paperless office." That certainly rings true for me—it's been my go-to work and study app since I first started using it in law school 7 years ago.

Example usage.

Suppose I'm reviewing the file FWS 065382–065397.pdf near the end of a 75,000-page administrative record (true story).

Suppose I find something really incriminating on the sixth page of that document—a candid email exchange where a staff biologist wrote: "If we do this, Franklin's Bumble Bee will go extinct." (fictitious example).

If I select that sentence in the PDF and use the keyboard shortcut I've assigned to my script, ⇧ + ⌥ + C, the following is placed in my system clipboard:

"If we do this, Franklin's Bumble Bee will go extinct." [FWS 65387](x-devonthink-item://883C7BE0-A328-4818-A4B5-3AF7E5504135?page=6&start=534&length=53&search=If%20we%20do%20this%2C%20Franklin%27s%20Bumble%20Bee%20will%20go%20extinct.). 

With Markdown rendering, that becomes, "If we do this, Franklin's Bumble Bee will go extinct." FWS 65387.

Now, if I open my Markdown editor of choice—Ulysses, Obsidian, or DEVONthink itself depending on the task at hand (more on that in a future post)—and paste (i.e., ⌘ v), the text I selected, plus the correct Bates cite with a deeplink back to the source sentence in the PDF, is inserted.

I can then at any time simply click the Bates cite and get right back to the exact point in that specific document where the incriminating statement is found.

Deeplinks are neat, right? Let's set it up.

Pre-requisites.

  1. This tutorial assumes you have a macOS computer with Perl and DEVONthink installed. The standard version of DEVONthink will work but I do recommend buying the Pro version, among other reasons, for easier OCR. The agencies I sue often transmit non-OCR'd documents and DEVONthink Pro is a godsend when that occurs.
  2. The documents you're looking at should be Bates-numbered (though my script has a fall-back mode for non-Bates numbered documents—more on that below).
  3. The documents should be named according to their Bates starting number or their Bates range. The script I wrote can handle any of these filename conventions:

FWS 000533.pdf
FWS-000533.pdf
FWS_000533.pdf
NMFS 002561-002985.pdf
BLM 45.pdf
AR_45-62.pdf

As you can see:

  • the agency prefix doesn't matter;
  • the separator between the agency prefix and the pages number(s) can be (space), -, or _; and
  • the filename can include either the Bates number of the document's first page, or the Bates range of the complete document separated by -.

This makes the script flexible enough to cover the file naming conventions I see most often in my legal practice. But I have seen others that my script couldn't feasibly be made to accomodate, for example:

20170912 1813 Redacted.pdf
20170922 0000 CSERC Scoping comment letter transmittal email.pdf
20200828 County of Tuolumne transmittal submission.pdf
Butler and Wooster 2003.pdf
California Resources 2020.pdf
Cayan et al 2008.pdf
Crozier et al 2006.pdf

If you're working with documents that are Bates-stamped but use a different file naming convention, like the example above, the script won't work until you rename the files to a supported naming convention. But when you're wrangling a 2,215-document AR comprising over 75,000 pages (true story), it's infeasible manually rename them. That's why I wrote—

A nifty helper script to handle Bates documents with nonconforming filenames.

This helper script, bates, will batch-rename folders full of documents while preserving their original filenames as metadata. The Python-language helper script is quite complex in its own right— it can handle PDFs with multiple text layers, non-OCR'd PDFs, DRM-protected PDFs, PDFs where Bates stamps are inserted as annotations, and various different Bates stamp formatting conventions.

Find it here →

I've written complete documentation on installing and using bates here, but the basic usage goes like this:

bates "~/Cases/My Big Case/Adminstrative Record" \
    --prefix "BLM AR " \
    --digits 6 \
    --name-prefix "BLM " \
    --log INFO

In this example, the script will take every PDF file in the folder ~/Cases/My Big Case/Administrative Record, and search the first and last page for Bates stamps formatted like BLM AR ######. Once it finds them, it will stash the original file name in the file's Finder comment metadata field, then rename the file BLM {first page Bates number}-{last page Bates number}.

With this naming convention the DEVONthink script will work.

👍
I recently applied this helper script to the 2215-document / 75,000-page AR I already mentioned, and it successfully named every single document—even ones where Bates stamps were illegible because of overlapping text or dark backgrounds. It's quite robust if I may say so myself.
⚠️
This helper script stores the original file name in the Finder comment metadata field, to preserve that information and have it still readily accessible within DEVONthink, but you should nevertheless always work on copies of files as the script may result in destructive changes in edge cases.

Alright, with the prerequisites in place and our documents abiding a compatible filename convention, the actual script this post is about can work. I've written detailed documentation for it here, but it's actually quite simple to set up:

  1. Copy the script from here
  2. Open /Applications/Script Editor.app
  3. Paste in the script and save it (e.g. on your Desktop) as Bates Source Link.scpt
  4. Open DEVONthink 3.app and click the script icon in the menu bar (it looks sorta like §) → Open Scripts Folder, or alternatively open Finder and click Go in the menu bar → Go to folder... and enter ~/Library/Application Scripts/com.devon-technologies.think3
  5. Move the Bates Source Link.scpt to the Menu subfolder you should now see in the Finder window

That's it.

You can use the script now by clicking the script icon (§) in the menu bar and then Bates Source Link.

Assign a keyboard shortcut.

... but clicking the script menu and finding the right script each time becomes a bit cumbersome, right? I thought so, too, so let's configure a keyboard shortcut for it:

  1. In your menu bar, click  → System Settings → Keyboard → Keyboard Shortcuts → App Shortcuts
  2. Select DEVONthink 3.app and click +
  3. Enter exact script name as it appears in the script menu in DEVONthink, i.e., Bates Source Link
  4. Assign your desired shortcut. A good option that doesn't conflict with default shortcuts is ⇧ ⌥ b (Option + Shift + b).
💡
I use the free utility Hyperkey.app to expand my available keyboard shortcut. Using Hyperkey I've assigned ⇪ b (Caps Lock + b) to this script.

Handling non-Bates documents.

Not all documents I work with are Bates stamped, so I made the script handle other documents too.

When a document doesn't follow one of the Bates naming convention detailed above, the script will still work. But instead of determining the Bates number for the active page and formatting a deeplinked Bates cite, it will instead format a deeplinked generic cite like this:

"If we do this, Franklin's Bumble Bee will go extinct." [FWS email thread at 6](x-devonthink-item://883C7BE0-A328-4818-A4B5-3AF7E5504135?page=6&start=534&length=53&search=If%20we%20do%20this%2C%20Franklin%27s%20Bumble%20Bee%20will%20go%20extinct.).

With Markdown rendering, that becomes, "If we do this, Franklin's Bumble Bee will go extinct." FWS email thread at 6.

Conclusion

That's it for this tip, folks. Let me know in the comments if you use DEVONthink and decide to give my script(s) a whirl. And as always, feel free to expand their functionality on my repo at sij.ai. I'd be particularly interested if anyone knows how to handle rich text links in AppleScript, to make this compatible in other apps besides Markdown editors.

Cheers!

ENV.ESQ

Encrypted messaging for Earthlings and their Advocates

Folks, I'm thrilled to announce my recent launch of ENV.ESQ: a free, decentralized, end-to-end encrypted messaging and chat platform. Anyone can register, but ENV.ESQ is particularly intended for use by lawyers, journalists, activists, scientists, educators, and others working in environmental law, conservation, climate justice, and adjacent spaces who demand the strongest security and privacy.

For the technically-inclined, ENV.ESQ is both a Matrix.org homeserver and a Cinny webUI.

For the less-technically inclined, you have three simple options:

  1. Use your existing Matrix.org account via ENV.ESQ
  2. Create a new Matrix account on ENV.ESQ (for example, @sij:env.esq) and access it from other Matrix.org clients (like Element for macOS and iOS)
  3. Do both—register at ENV.ESQ, login, and start sending encrypted messages to anyone else on any Matrix.org–powered platform

And if you're unaware, the Matrix.org platform/protocol has been thoroughly vetted, audited, and approved for use by government intelligence agencies. Think of it as a more secure and decentralized alternative to Signal, WhatsApp, Slack, or Teams.

Like Earth.Law and Lone.Earth, ENV.ESQ is part of the Fediverse and runs on a performant dedicated server at a green datacenter in the privacy-friendly jurisdiction of Finland. And while ENV.ESQ was initially intended to add end-to-end encrypted chat functionality to Earth.Law, it doesn’t require membership there.

So go ahead and check it out, even if half of this was Greek to you. And let me know how you're using it in the comments—or better yet, don't, because it's designed with your privacy in mind.

Cheers,

Lone.Earth

Lone Earth is a Fediverse platform for nature documentaries, environmental videos, and eco-inspired art.

Hey folks, here's the first of a few quick updates— I recently launched Lone.Earth, a PeerTube instance for nature documentaries, environmental videos, and eco-inspired art.

Specifically, Lone.Earth welcomes:

  • 🎥 Documentaries, podcasts, & vlogs
  • 🌄 Time-lapse, slow-motion, & aerial/drone cinematography
  • 🦌 Wildlife photography & videography
  • 🌿 Environmental & ecological advocacy
  • 🎶 Music & other audiovisual art inspired by nature & the sentient condition

Lone.Earth joins Earth.Law on the Fediverse, hosted alongside it on a powerful dedicated server at a green datacenter in Finland.

So if you're into this sort of thing, check it out, sign up, and share what you're working on.

Cheers,

AdGuard Home & Tailscale for maximal DNS privacy

DNS is a privacy minefield. Here's my best shot at charting a safe course through.

Regardless who you are, what devices you use, or how you connect to the internet, you use a domain name server (DNS) resolver. You may not know who your DNS resolver is, in which case your DNS and Internet Service Provider (ISP) are likely one-and-the-same, or you may at some point have set your device to use a third-party alternative, like 1.1.1.1 (Cloudflare) or 8.8.8.8 (Google). Regardless, if you're reading this, there's a strong likelihood that your DNS resolver knows who you are. Worse, your DNS resolver likely knows what you do—i.e., every site you visit. And it probably does stuff with that knowledge.

The good news? You can (and should) choose your DNS resolver(s). Heck, you can even be your own DNS resolver. Read on to learn why you might not want to go quite that far, and how I, for one, navigated the DNS privacy minefield.

Why start here?

When I launched this blog two months ago, I had a couple dozen ideas for topics to post about, and while this was one of them, it didn't top my list. But after two months running through the options and the reasons each might be the best starting point, I realized the obvious: it's more important to just start somewhere. And, at least as far as privacy is concerned, the subject of DNS may actually be a great place to start. After all, explaining my decision to build my own Linux server, or rely on Tailscale for connections between all my devices—both subjects of future posts—could ring a bit hollow without a shared understanding of DNS and the possibilities and vulnerabilities associated with it.

So... what is DNS?

DNS, in a nutshell, is the mechanism that ties each human-comprehensible domain (like sij.law) to a numeric IP address. Numeric IP addresses, in turn, are used to send and request content between your browser and a web server — like when you loaded this post, for instance.

It's helpful to think of DNS resolution as a rather tedious Q&A. When you try to load a page, like https://sij.law/dns, your browser asks your DNS resolver, "What's the IP address for sij.law?" Assuming no one else recently asked that same question (then it answers from memory), your DNS resolver starts a process something like this:

  1. Your DNS resolver queries one of the 13 root DNS servers—operated by various universities, U.S. and international agencies, and both nonprofit and for-profit corporations—"Who knows about .law domains?"
  2. The root server responds, "Ask the .law TLD servers at a.nic.law through z.nic.law." (TLD stands for Top-Level Domain)
  3. Your DNS resolver then asks a .law TLD server, "Who knows about sij.law specifically?"
  4. The .law TLD server responds, "Ask Cloudflare's authoritative nameservers for sij.law at anuj.ns.cloudflare.com or ingrid.ns.cloudflare.com."
  5. Finally, your DNS resolver asks one of Cloudflare's authoritative nameservers, "What are the IP addresses for sij.law?"
  6. Cloudflare's authoritative nameserver responds with "172.67.165.115 and 104.21.57.184" (which incidentally also belong to Cloudflare, because my site uses Cloudflare proxies for security).

Your DNS resolver then passes these IP addresses back to your browser.

... and? So what?

THE PROBLEM

DNS resolution works by you telling a resolver every time you want to load a site. Once your DNS resolver has done its job—translated a domain into an IP address that your browser can use to load content—you may just as soon be on your merry way to another page, forgetting all about how you got there. But your DNS resolver most certainly has not forgotten. Your DNS resolver probably keeps info about your browsing habits. And you really have no idea what it does with that information.

Are you... okay with that!?

I'm not. So I researched and trialed a number of options in my quest for genuine DNS privacy. Here are the options I considered, and why I didn't accept them as good enough.

Privacy-oriented DNS resolvers

I began my quest for the ultimate DNS privacy solution in an obvious place: privacy-oriented DNS resolvers. This category includes Quad99.9.9.9 and 149.112.112.112 (traditional DNS servers usually come in sets of two IP addresses for redundancy in case the first fails) — and Mullvad -- 194.242.2.2 and 2a07:e340::2 (the funky-looking second one is an IPv6 address, which is beyond the scope of this post, or—if we're being honest—any likely future post), among numerous others.

What sets these DNS resolvers apart from, say, Google, are generally good privacy policies (e.g. not keeping logs), backed by strong respective reputations in privacy and information security ("infosec") spaces. The reason I decided to look further, beyond simply punching in Quad9's or Mullvad's IPs in my systems' DNS settings, was two-fold:

First, DNS queries are typically made in unencrypted, plain-text form over port 53 to the DNS resolver. This means my ISP, and anyone else in a position to monitor my web traffic, can readily identify the sites I visit. For me, this was unacceptable.

Second, I adhere as faithfully as I can to a 'zero trust' approach to infosec, which can be summarized as "never trust, always verify." In practice this means configuring and exclusively using systems that don't depend on the trustworthiness of anyone besides myself and my closest / most trusted associates. Obviously, zero trust is often more aspirational than actually possible in absolute terms. But even if using the internet necessarily involves placing some degree of trust in others, I have deep qualms about trusting profit-driven companies to abide their privacy policies—especially when it comes to information as deeply personal as my complete browsing history. Quad9 and Mullvad both profess not to keep any logs of DNS queries, but users ultimately have to take them at their word. Nothing actually prevents them from keeping logs tying specific domains to specific users. For that matter, nothing inherently prevents resolvers like Quad9 and Mullvad from complying with requests from government actors to surveil users, independent of whether they themselves "keep" logs. For me, this too was unacceptable.

DNS-over-HTTPS, DNS-over-TLS

The obvious answer to the problem of all DNS queries being exposed to one's ISP (or eavesdroppers) is to use encrypted DNS, i.e., configuring one's devices to use DNS-over-HTTPS (DoH) or DNS-over-TLS (DoT).

Most operating systems—at least those with which I am most familiar (macOS, iOS, Debian Linux)—do not natively support DoH or DoT, but it's rather straightforward using third-party software I'll mention at the end.

While this approach prevents an ISP directly monitoring which sites you visit, it does not solve the distinct problem of having to trust your DNS resolver. They'll still know (a) who you are, and (b) what sites you visit.

Pi-hole & Unbound for self-hosted recursive DNS resolution?

Once I got this far into the murky depths of DNS, I encountered repeat mentions of a "pie hole". Turns out they meant Pi-hole. It's free open-source ("FOSS" henceforth) DNS software that's actively developed by a large community of developers. Turns out Pi-hole is pretty great at a lot of things, like network-wide ad- or content-blocking with user-specific rulesets, and—more relevant here—acting as one's own recursive caching DNS resolver by pairing it with Unbound (also FOSS).

"What the ƒ#@% is a recursive caching DNS resolver!?" I hear you ask?

Recall the tedious Q&A we outlined. A recursive caching DNS resolver, like Pi-hole with Unbound, performs this entire Q&A process itself, starting from the root and working its way down. It then remembers the answer for a while, so it can respond more quickly to future questions about sij.law without having to ask everyone all over again.

This approach has significant pros & cons:

  • Pros: you're not relying on any third-party DNS resolver. You're querying the authoritative sources directly and caching the results yourself. This eliminates the privacy concerns associated with trusting one DNS resolver with all your queries. You're also positioned to implement industrial strength ad-blocking or web-filtering rules for your entire network.
  • Cons: authoritative DNS servers do not accept encrypted DNS queries. Let me repeat that, because too often this caveat isn't acknowledged in DNS privacy discourse that seems to favor Pi-hole + Unbound. Authoritative DNS servers. DO. NOT. ACCEPT. encrypted DNS queries. All DNS queries to authoritative DNS servers are made in UNENCRYPTED, PLAIN-TEXT form. This puts us back to square one with our ISP (and anyone else listening in) able to easily monitor every site we visit.

The astute reader will now ask: if all DNS queries are ultimately decrypted and sent over the open internet to authoritative DNS servers in plain text anyway, what's the point of encrypted DNS anyway? What's the point of any of this?

Well, the problem with being your own recursive caching DNS resolver is that all DNS queries you make are presumptively your own. Your ISP knows, the authoritative DNS servers knows, and anyone else listening in also knows—they know it's you.

By contrast, when you use a DNS resolver like Mullvad or Quad9 over encrypted DNS, your ISP doesn't know what you're doing, and while the DNS resolver's ISP and the authoritative DNS can see you what you're doing, they don't know who you are. All they see is a firehose of undifferentiated DNS queries going through a major DNS resolver, each presumptively from anyone anywhere in the world.

Think of it like digital black bloc—anonymity in a faceless crowd.

But as we already discussed, that approach entails a critical vulnerability: placing trust in a single DNS resolver (to strain the metaphor a tad: the black bloc clothing shop), which does know both (1) who you are, and (2) what you are doing.

A SOLUTION

By now, it became evident to me that there's no such thing as absolute DNS privacy and anonymity. The best you can do is:

  1. Conceal what you are doing from anyone that knows who you are,
  2. Conceal who you are from anyone that knows what you are doing, and
  3. Conceal the fact that your DNS queries all come from one source, even if that source is anonymous per #2.

I knew all three measures are independently possible, so the challenge became to achieve them together. Cue—

AdGuard Home

AdGuard Home, like Pi-hole, is FOSS you can install on a small at-home server (think Raspberry Pi, old MacBook, or Intel NUC), or a cheap Virtual Private Server (VPS), which handles DNS for your entire network. Like Pi-hole, it offers network-wide ad-blocking and custom filtering rules. But AdGuard Home comes with key DNS privacy functionality that Pi-hole doesn't:

  1. Built-in support for DNSCrypt: AdGuard Home natively supports DNSCrypt between itself and the upstream DNS server(s). This ensures your DNS queries are encrypted all the way to your upstream servers, concealing what you're doing from your AdGuard Home's ISP (which will typically know who you are) while also ensuring your upstream servers are who they purport to be—not imposters.
  2. Multiple upstream resolvers: AdGuard Home allows you to enter a list of upstream DNS resolvers and will rotate through them all, dividing your queries between multiple servers in multiple jurisdictions. This prevents any single resolver/authority building a complete profile of what you do, which could be used to infer with substantial certainty who you are.
  3. Built-in support for DoH and DoT: AdGuard Home natively supports DNS-over-HTTPS (DoH) and DNS-over-TLS (DoT) between your devices and AdGuard Home. These encryption protocols conceal what you are doing from your devices' ISPs and any other eavesdroppers between your devices that already know who you are.

Of course, without more, individual upstream DNS servers you select still know who you are. That brings us to—

Tailscale

Tailscale is an incredibly useful tool that essentially creates a virtual private network (VPN) between your devices, allowing you to access services on any one from any other, anywhere in the world.

👍
I'm not sure if I'll do a post specifically about Tailscale here, because it's typically most relevant in 'as-applied' contexts... but it applies to nearly everything I do in infosec and networking contexts, so I'm quite sure I'll discuss it a whole lot here.

Tailscale is free for personal use on up to 100 devices (more than most households will ever need), and offers extremely competitive pricing for business use.

Tailscale offers three key functionalities relevant here:

  1. Tailscale secures all traffic between your devices using the ironclad wireguard end-to-end-encryption (E2EE) protocol. This is a privacy boon for any number of use cases, but most relevant here, it ensures DNS queries from any devices that can't natively configure DoH or DoT are nevertheless encrypted from that device to your AdGuard Home server.
  2. Tailscale allows you to force your devices to all use the DNS service you specify. While some router's firmware lets you direct all DNS queries to a particular server over DoH or DoT, (a) not all do, and (b) you likely access the internet from a mobile device from time-to-time over other networks where your router isn't relevant. When you put your AdGuard Home server on the same Tailscale network ('tailnet') as your devices, you allow those devices to rely on AdGuard Home for DNS queries from anywhere in the world.
  3. Tailscale has a partnership with Mullvad to add privacy VPN functionality to any tailnet. You've likely heard of privacy VPNs—other noteworthy options include ProtonVPN, NordVPN, and WindScribe. What these all have in common is they will route your internet traffic over an encrypted tunnel to an 'exit node' somewhere in the world, concealing who you are from the sites you visit—and anyone else who can see what you are doing. Privacy VPNs aren't typically free, and the Mullvad extension for Tailscale is no exception. But at $5/mo for up to 5 devices, it's very competitively priced.

SETTING IT UP

  1. Choose a server for AdGuard Home

You can run AdGuard Home on macOS, Windows, Linux, or more specialized OSs like Raspberry Pi or TrueNAS SCALE. It doesn't matter much which device or operating system you choose, except that it should be 'high availability'—online and accessible 24/7—if you intend to use it for DNS resolution on other devices. You could install it on your laptop and use it effectively on that laptop, but your smartphone couldn't use it when your laptop is out of juice or asleep in a bag.

In future posts I'll discuss my home servers and the dedicated Debian server I rent at a Finnish datacenter. Without going into the specifics of selecting or building one just yet, I do recommend using a server for AdGuard Home. Servers are typically higher availability than end user computers, making them more suitable for multi-device setups.

The green datacenter in Helsinki where my AdGuard Home installation—and this site—are hosted.

Finally, if you intend to enable DoH, I recommend running AdGuard Home on the same server as Caddy so it can share access to the HTTPS certificates that Caddy automatically renews.

  1. Install and configure Tailscale on the server (and the device you're using to access it, if separate).

Tailscale is straightforward to setup, but first you'll need an account. A free one will work, but please make sure you're in compliance with Tailscale's policies for free vs. business accounts.

Once you've created a Tailscale account, install Tailscale on the same server that will run AdGuard Home, and if it's not the computer you're using to access it, install Tailscale on the local device too.

You can snag Tailscale on the macOS and iOS app stores. For Linux servers including Debian, Tailscale has wizard for creating an installation script. Windows and Android users are on their own (fair warning: my readers will see this a lot), but I suspect it's similarly straightforward to install there too.

Take note of your server's Tailscale IP address—it'll begin with 100.

💡
You might want to change your Tailscale IP address to something more intentional than the randomly assigned defaults. For instance, all my devices' IPs begin with 100.64.64, with routers occupying the .1–9 range, servers occupying the .10–.19 range, my own devices occupying the .20–.49 range, and family members' devices occupying the .50–.99 range. This makes it easier to remember the IPs of specific devices and to know what type of device an IP belongs to even if I don't immediately recall which specific device it is.
  1. Install AdGuard Home

The steps for installing AdGuard Home will depend on the device you're using, so please see the official documentation for other device-specific instructions. To install AdGuard Home on Debian:

curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sudo sh -s -- -v

sudo /opt/AdGuardHome/AdGuardHome -s start

Standard method for installing and starting AdGuard Home on Linux

  1. Initial AdGuard Home configuration

Open a new browser tab, navigate to http://Your-Tailscale-IP:3000, and begin the initial 5-step configuration.

On the second step, I recommend setting the listen interface for both the Admin Web Interface and the DNS Server to your Tailscale IP address, to ensure only devices you've added and authorized on Tailscale can access it directly. You'll also need to select a different port than the default of 80 if you intend to run Caddy on the same server (as I recommend if you're looking to configure DoH), or if you're running another reverse proxy or web server. I've adopted port 8052 for this purpose.

Be sure to create a very secure password on the third step.

Once you've completed the initial configuration, visit http://Your-Tailscale-IP:Admin-Web-Interface-Port. For me, for example, that is http://100.64.64.11:8052. Login with the credentials you just created.

  1. Add upstream DNS resolvers to AdGuard Home

You should now be greeted by the AdGuard Home admin panel. Click Settings > DNS settings, and you should see a field for entering a list of Upstream DNS servers. I recommend finding upstream DNS resolvers to use on DNSCrypt.info, or directly from this GitHub file.

I won't share my upstream resolvers because that would somewhat defeat my own DNS privacy, but I can share some tips for choosing your upstream resolvers:

  • Choose servers that support DNSCrypt and DNSSEC to ensure your DNS queries aren't tampered with.
  • Choose servers that profess not to keep logs.
  • Avoid servers that apply ad-blocking or filtering rules. If you want this functionality it's best to set it up yourself within AdGuard Home rather than relying on rules set on an upstream server outside your control.
  • Choose a healthy mix of servers around the world, but especially in your region to ensure fast DNS responses.
  • Choose a few servers from established privacy or infosec companies like Mullvad, Njalla, and Quad9. These will typically have the infrastructure to fulfill your DNS requests quickly and strong privacy policies, and are less likely to become defunct (which many servers listed on DNSCrypt.info seem to).
  • Rely on the sdns:// DNS stamp method of adding servers. These "stamps" encode all the information needed to connect to a DNS server, much like QR codes, streamlining the process.
  • Add a comment above each server in your list so you remember what/where it is.
  • Aim for around a dozen servers. The more you have, the smaller the proportion of your total DNS queries any one server will have, boosting your DNS privacy. On the other hand, the more you have, the more likely you are to select one that becomes defunct.
  • Scroll down and click Test upstreams after adding each server, so you immediately know if it's defunct. Replace the last server you added if the test ever fails.
  1. Configure the remaining DNS settings on AdGuard Home

Once you've added a dozen or so upstream DNS resolvers that test successfully, there are a few more settings on the same page (DNS Settings):

  • Select Load-balancing.
  • Add one or more Fallback DNS servers. You can get these from the same list as the primary upstream DNS servers discussed above, and use the same syntax (including the recommended sdns:// DNS stamp method). I recommend using established DNS privacy providers like Mullvad, Njalla, or Quad9 here.
  • Add one or more Bootstrap DNS servers. Here you need to enter traditional IP addresses. I use Quad9 and Mullvad for this, so I've entered 9.9.9.9, 149.112.112.112, and 149.112.112.10.
  • Again Test upstreams to ensure everything here is copacetic.
  • Scroll further down and Enable EDNS client subnet.
  • For privacy reasons (which is why we're here after all), you should also Use custom IP for EDNS. The IP you enter here may affect how quickly your DNS queries are answered, so it's a good idea to select the IP of an institution near you—e.g. a university, library, or hospital. For example, if you live in Michigan, you could run dig umich.edu and enter the resulting IP address.
  • Enable DNSSEC.
  • Set the Cache size to a higher value, e.g., 67108864 bytes (64 megabytes).
  • Enable Optimistic caching. This will result in fewer DNS queries reaching your upstream servers, enhancing your privacy and speeding up DNS resolution—possibly dramatically. The drawback—the occasional invalid DNS response the first time after a site's DNS has changed (rare)—is well worth the benefit.
  • Under Allowed clients, enter 127.0.0.1, and the narrowest CIDR range that encompasses all your Tailscale IPs. For me, that's 100.64.64.0/24.
  1. Configure Tailscale to enforce AdGuard Home as the only DNS for your tailnet

Return to the Tailscale admin console and navigate to the DNS tab. Scroll down to Nameservers and remove any that may be listed under Global nameservers.

Here, add the Tailscale IP address of your AdGuard Home server, and enable Override local DNS.

  1. Semi-Optional: Enable Mullvad on your AdGuard Home server

Still on the Tailscale admin console, navigate to the Settings tab, find the section for Mullvad VPN, and click Configure. Purchase the add-on for $5/month, then add the AdGuard Home server to the list of devices authorized to use Mullvad exit nodes.

On your AdGuard Home server, set Tailscale to connect to a Mullvad exit node. On Debian: tailscale exit-node list; find one in a privacy-friendly jurisdiction like Switzerland, Sweden, or Finland; then tailscale set --exit-node-allow-lan-access --accept-dns=true --exit-node=Hostname-or-IP-address-of-Mullvad-exit-node.

🤔
The reason I indicated this as "semi-optional" is that you can get pretty darn good DNS privacy without it. By splitting your DNS queries between multiple DNS resolvers in multiple jurisdictions, all of which profess not to keep logs, you've already gone a long way toward preventing anyone knowing much at all about your browsing habits. This step just takes you a bit further and helps prevent anyone linking even a fraction of your browsing habits to you. If you're concerned about privacy you probably want a privacy VPN regardless, and Mullvad is a great deal for $5/mo if you aren't already committed to another provider, so in my view this is a no brainer. But individual setups and financial pictures certainly vary.
👨‍💻
I will soon share a script I use to automatically rotate between different Mullvad exit nodes in privacy-friendly jurisdictions, so watch for that post in the near future!
  1. Optional: Set up blocklists for ads, malware, etc.

If you've gotten this far, AdGuard Home is uniquely well positioned to block ads across your entire network—before they ever reach your browsers' own ad-blocker. It's why this powerful multi-purpose software is called 'AdGuard', after all. If you want to take advantage of this core functionality—which you absolutely should—navigate to Filters -> DNS Blocklists -> Add blocklist.

Here you can either choose from a great assortment of blocklists that come with AdGuard Home by default, or add your own. I've not tested the options extensively, but have had good results enabling the following:

  • AdGuard DNS Filter
  • AdAway Default Blocklist
  • Malicious URL Blocklist (URLHaus)
  • Dan Pollock's List
  • 1Hosts (Lite)
  • Dandelion Sprout's Anti-Malware List
  • Steven Black's List
  • HaGeZi's Pro Blocklist
  • OISD Blocklist Small
  • Peter Lowe's Blocklist
⚠️
While it might be tempting, avoid adding all available blocklists, and be careful adding any especially large blocklists. The risk here is of breaking some legitimate sites. If this occurs, begin by disabling the largest blocklists (in my case that's Steven Black's and HaGeZi's Pro).
  1. Optional: Set up a DNS rewrite rule to easily tell if you're using AdGuard Home

DNS hijacking is a thing, and while the methods detailed here (in particular Tailscale and DNSSEC) should prevent that occurring, you can easily set up a simple DNS rewrite rule that will prove you're using AdGuard Home and not an imposter, for your continued peace of mind.

On the AdGuard Home admin panel, navigate to Filters -> DNS Rewrites -> Add DNS Rewrite, enter a made-up domain like adguard.dns.rewrite, and point it to your Caddy IP address or to another domain. Then, when you visit http://adguard.dns.rewrite, you'll know you're using AdGuard Home if your browser takes you to the location you've configured.

👨‍💻
In the near future I'll share a script that relies on this functionality to show an indicator in your macOS menu bar if you're using AdGuard Home for DNS, along with a flag emoji for the country of your current Mullvad exit node.
  1. Optional: Set up DoH and configure Caddy
🚧
This step is only necessary if you want to use your AdGuard Home without Tailscale but still ensure your DNS queries are encrypted between your devices and AdGuard Home. If you always use Tailscale, as I do, you don't need to do this—Tailscale ensures your DNS queries are encrypted with Wireguard and that no one outside your Tailnet is intercepting your DNS queries.

The instructions I'll provide here may not be compatible with Mullvad exit nodes depending on your setup. It works for me, but this configuration is stretching the limits of my know-how (I know just enough to know things could get squirrelly here depending on firewall rules, Tailscale ACLs, etc). If a reader can provide a more confident explanation of this setup, that would be lovely.

This step has some prerequisites that I might cover in a future post but for now are beyond the scope:

  • You need to have installed Caddy on the same server as AdGuard Home and configure it to use Cloudflare for HTTPS challenges.
  • The server with Caddy and AdGuard home on it needs to have a permanent IP address. You need to own a subdomain (e.g. dns.sij.law) and assign it to this IP address.

Edit your Caddyfile — typically it's at /etc/caddy/Caddyfile —

sudo nano /etc/caddy/Caddyfile

You will want to make sure your email address is included in it, for HTTPS certificate acquisition. Then add an entry for AdGuard Home.

{
	log {
		output stdout
		format console
		level DEBUG
	}

	admin 127.0.0.1:2019

	servers {
		metrics
	}

	# replace with your email address
	email [email protected]

	acme_ca https://acme.zerossl.com/v2/DV90
}

# replace this with a subdomain you've pointed to your server's public IP address
dns.sij.law { 
        tls {
                dns cloudflare {env.CLOUDFLARE_API_TOKEN}
        }

        @doh {
                path /dns-query
                header Accept application/dns-message
        }

        handle @doh {
                reverse_proxy https://localhost:8053 {
                        transport http {
                                tls_insecure_skip_verify
                        }
                }
        }

        handle {
                reverse_proxy http://localhost:8052
        }

        log {
                output file /var/log/caddy/dns.sij.law.log
                format json
        }
}

Your Caddyfile might look something like this.

Save it (Ctrl+O), exit Nano (Ctrl+X), then restart Caddy:

sudo systemctl restart caddy

Navigate back to the AdGuard Home admin panel, then Settings -> Encryption settings:

  • Enable Encryption (HTTPS, DNS-over-HTTPS, and DNS-over-TLS.
  • Leave Enable plain DNS on for standard DNS over Tailscale.
  • Set Server name to the subdomain you added to Caddy.
  • Do not enable Redirect to HTTPS automatically.
  • Set the HTTPS port to 8053 and the DNS-over-TLS port to 8054.
  • Under Certificates, Set a certificates file path and Set a private key file. You'll need to find where Caddy saved these files on your local filesystem. For me, these file paths are:
    • /var/lib/caddy/.local/share/caddy/certificates/acme.zerossl.com-v2-dv90/dns.sij.law/dns.sij.law.crt
    • /var/lib/caddy/.local/share/caddy/certificates/acme.zerossl.com-v2-dv90/dns.sij.law/dns.sij.law.key
Note that the Certificate and Private key paths point to files obtained and managed by Caddy. You may need to adjust file permissions accordingly.
  • Once you've entered your certificate and private key file paths, ensure the certificate chain and the private key are recognized as valid.
  • Save the settings.

AdGuard Home should now answer DoH requests at the subdomain you configured for it.

Depending on your device's operating system there are various apps and tools you can use to enforce DoH locally. These include:Little Snitch 6 (macOS), DNS Override (iOS and iPadOS).


Alright folks, that'll do it for now. Let me know in the comments your approach to DNS privacy, if you're going to try (or already use) something along these lines, and if you'd like to see more privacy-oriented deep dives on this blog.

Cheers,

Find me on the Fediverse

Introducing Earth.law

Hey folks, I have a lot of posts in the hopper finally, but I wanted to give a quick mention for the Mastodon instance I recently launched at Earth.law.

For those who may be less familiar, Mastodon is a decentralized alternative to X (formerly known as Twitter). Through the wonders of federation, you can join Earth.law and view and interact fully with profiles and posts on other Mastodon instances, or for that matter other ActivityPub platforms like Pixelfed (think Instagram) or PeerTube (think YouTube). Click through to join—

—I hope to see you there!

Hello, world!

Welcome to my blog!

You likely already know me if you’re reading this, but in case these blogs reach new readers, please forgive a brief introduction. I’m Sangye Ince-Johannsen. I'm a staff attorney at the Western Environmental Law Center, where for five years I've litigated environmental cases in federal court on behalf of various local, regional, and national, and international conservation-oriented nonprofit organizations. My docket largely focuses on defending spotted owls, anadromous fish, grizzlies, wolves, and their respective habitats from federal and federally-licensed activities in cases brought under the Endangered Species Act, National Environmental Policy Act, Administrative Procedure Act, and Clean Water Act.

Before pursuing my legal career, I worked as a documentary filmmaker and videographer for four years in southern Oregon. During that time I had the privilege of working on documentaries including One Billion Rising (2013) by Eve Ensler and Robert Redford, When Giants Fall (2015) by Leslie Griffith, narrative films including Redwood Highway (2013) and Wild (2014), outreach videos for the Neighborhood Food Project among other regional nonprofits, and numerous music and event videos.

Going back even further, before my undergraduate studies in Anthropology, Videography, and International Relations at Southern Oregon University, my first interest—my first love, even—was computer science and programming. After a long hiatus, my passion for programming, especially AI/ML research and development, has been rekindled. Recent advancements in AI have me particularly excited about leveraging large language models and automatic speech recognition to become a more productive and effective advocate.

Vision for this blog

That’s what I hope to do with these blogs: share AI/ML scripts, workflows, and apps, along with key caveats and considerations, with other litigators and knowledge workers. I also intend to share some legal analysis and other fun stuff—AI image and speech generation, drone videography, self-hosting tools and tricks—along the way.

So, what’s with the two URLs, you may be wondering? For most of my posts, especially at the outset, you’ll get the same content whether you visit sij.law or sij.ai. However, if you’re a litigator and not necessarily an AI/ML enthusiast, there will likely be posts that aren’t interesting to you in the slightest, which I’ll save for sij.ai. If you’re passionate about AI/ML productivity hacks but not necessarily a litigator or interested in deep legal analysis, you’ll likely have a better time over on sij.ai. Either way, thanks for dropping by, feel free to explore some of my current projects in the site navigation, and I can’t wait to engage with you in the comments or on social media.

Cheers!

Edit on 9/25/24: I've decided to devote sij.ai to a dev and code hub rather than a parallel topical blog. That means all posts, whether related to law or ai/ml, will be consolidated here, and you can use the tags law and ai/ml to isolate one topic or the other.