A Windows service creates a virtual directory tree of convincing-looking credential files (SSH keys, AWS creds, .env files, etc.) using the Windows Projected File System (ProjFS). When any file is opened, two alerts fire simultaneously:
- DNS canary — a lookup to your canarytokens.com token domain, recorded in the Thinkst dashboard.
- GitHub repo dispatch — a webhook POST to your repository that triggers
file-access-alert.yml, which opens a GitHub Issue and (optionally) posts to Slack.
Attacker opens decoy file
│
▼
ProjFS-Service (Windows)
│
├─► DNS lookup → canarytokens.com (passive, no auth needed)
│
└─► POST /repos/{owner}/{repo}/dispatches
│
▼
file-access-alert.yml
│
├─► Creates GitHub Issue 🚨
└─► Posts to Slack (optional)
Place these files at the root of your repository before activating the workflows:
your-repo/
├── ProjFS-Service.cs # Windows service source
├── ProjFS-Service.exe.config # Virtual filesystem layout + runtime config
└── .github/
└── workflows/
├── deploy-honeypot-service.yml # Builds & installs the service
└── file-access-alert.yml # Receives alerts, opens Issues
| Requirement | Details |
|---|---|
| Windows runner | A self-hosted Windows runner registered to this repo. For quick testing only, the ephemeral windows-latest GitHub-hosted runner also works. |
| .NET Framework 4.8 | Pre-installed on Windows Server 2019+. Required to compile ProjFS-Service.cs. |
| Admin rights on runner | Needed for ProjFS feature enablement and Windows Service installation. |
| Windows 10 1809 / Server 2019+ | Minimum OS for the ProjFS kernel feature. |
-
Go to canarytokens.com/generate.
-
Select token type DNS.
-
Enter a reminder (e.g.
Windows honeypot — prod server). -
Click Create my Canarytoken.
-
Copy the token hostname — it looks like:
abc1def2ghi3.canarytokens.comThis is the value you will store as
ALERT_DOMAIN.
When the service runs on the target host, every file access fires a DNS lookup to a subdomain of this hostname. The Thinkst dashboard shows the timestamp, source IP, and (encoded in the subdomain) the filename and process name.
The PAT is used by the Windows service to POST the canary-file-access repository dispatch event. The file-access-alert.yml workflow then picks it up and opens an Issue.
- Go to GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic).
- Click Generate new token (classic).
- Set an expiry appropriate for your deployment window.
- Under Select scopes, tick
repo(the entire top-level scope). This grants thecontents,issues, andmetadataaccess needed forrepository_dispatchand Issue creation. - Click Generate token and copy it immediately — you will not see it again.
The value you store in ALERT_WEBHOOK_AUTH must include the Bearer prefix:
Bearer ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
- Go to GitHub → Settings → Developer settings → Personal access tokens → Fine-grained tokens.
- Click Generate new token.
- Set Resource owner to the account or org that owns this repository.
- Under Repository access, select Only select repositories and choose this repo.
- Under Permissions → Repository permissions, grant:
- Contents — Read-only
- Issues — Read and write
- Metadata — Read-only (mandatory, auto-selected)
- Click Generate token, copy the value.
Store as:
Bearer github_pat_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Security note: Store this PAT only in GitHub Secrets (see Step 3). Never commit it to the repository.
Go to your repository → Settings → Secrets and variables → Actions → New repository secret and create each secret below.
| Secret name | Value | Where it comes from |
|---|---|---|
ALERT_DOMAIN |
abc1def2ghi3.canarytokens.com |
Canarytoken hostname from Step 1 |
ALERT_WEBHOOK_URL |
https://api.github.com/repos/{owner}/{repo}/dispatches |
Replace {owner} and {repo} with your GitHub username/org and repository name |
ALERT_WEBHOOK_AUTH |
Bearer ghp_xxx… or Bearer github_pat_xxx… |
PAT created in Step 2, with the Bearer prefix |
| Secret name | Value | Purpose |
|---|---|---|
SLACK_WEBHOOK_URL |
https://hooks.slack.com/services/… |
Slack incoming webhook URL. If absent the Slack step is silently skipped (continue-on-error: true). |
deploy-honeypot-service.yml patches these three values directly into ProjFS-Service.exe.config on the runner before installing the service:
AppSettings key ← GitHub Secret
─────────────────────────────────
RootPath ← workflow input (not a secret)
AlertDomain ← ALERT_DOMAIN
WebhookUrl ← ALERT_WEBHOOK_URL
WebhookAuth ← ALERT_WEBHOOK_AUTH
The service reads them from the config at startup. They are never written to disk in plaintext outside of that patched config file on the runner itself.
Both .yml files must live in .github/workflows/ at the root of the repository. The directory must be created if it does not already exist.
# From the root of your local clone
mkdir -p .github/workflows
cp deploy-honeypot-service.yml .github/workflows/
cp file-access-alert.yml .github/workflows/Then commit and push:
git add .github/workflows/
git commit -m "Add honeypot deploy and alert workflows"
git pushGitHub Actions picks up any .yml file inside .github/workflows/ automatically — no further registration is needed.
file-access-alert.yml applies three labels when it opens an Issue. Create them in your repository before the first alert fires so the Issue creation does not fail.
Go to Issues → Labels → New label and create:
| Label | Suggested colour |
|---|---|
security |
#d93f0b (red-orange) |
honeypot |
#e4e669 (yellow) |
high-priority |
#b60205 (dark red) |
For a persistent honeypot (the whole point of this setup) you need a self-hosted runner on the target Windows machine. The GitHub-hosted windows-latest runner is ephemeral and resets after each job.
- Go to your repository → Settings → Actions → Runners → New self-hosted runner.
- Select Windows and follow the on-screen instructions to download and configure the runner agent.
- Run the agent as a service with Administrator privileges so it can install ProjFS and register the Windows service.
- Once registered, update the
runs-online indeploy-honeypot-service.yml:
# Before (ephemeral, testing only)
runs-on: windows-latest
# After (persistent, production)
runs-on: [self-hosted, windows, x64]-
Go to Actions → Deploy ProjFS Fake FileSystem Service.
-
Click Run workflow.
-
Fill in the inputs:
Input Description Default runner_typegithub-hostedfor a quick test,self-hostedfor productiongithub-hostedactiondeployinstalls and starts the service;removeuninstalls itdeployroot_pathWindows path where the decoy directory will appear C:\DevRepo -
Click the green Run workflow button.
The workflow will:
- Enable the
Client-ProjFSWindows feature if not already active. - Compile
ProjFS-Service.cswith the .NET 4.8csc.execompiler. - Deploy and patch
ProjFS-Service.exe.configwith your secrets. - Register the
WindowsFakeFileSystemWindows Event Log source. - Install and start the service via
InstallUtil.exe. - Run a smoke test — opens the first projected file and waits 30 seconds for the webhook round-trip.
A successful run ends with:
Smoke test passed. Check canarytokens dashboard and GitHub Issues for the alert.
- The
WindowsFakeFileSystemservice fires the webhook POST to the GitHub API. file-access-alert.ymlwakes up on thecanary-file-accessrepository dispatch event.- A GitHub Issue is opened automatically titled:
The issue body contains a table of the accessed file path, triggering process, hostname, and UTC timestamp, plus a checklist of immediate response actions.
🚨 Honeypot Alert: \AWS\credentials accessed on DESKTOP-TARGET - If
SLACK_WEBHOOK_URLis configured, a formatted Slack message is also sent. - The Canarytoken dashboard at canarytokens.com simultaneously records the DNS hit with the source IP.
file-access-alert.yml supports workflow_dispatch so you can fire a test alert from the GitHub UI at any time without touching the Windows service.
- Go to Actions → File Access Alert → Run workflow.
- Fill in the test fields (file path, process name, hostname, timestamp).
- Click Run workflow.
A real Issue will be created and Slack will be notified (if configured) — identical to a live alert. Use this to verify your secrets and label setup before deploying to a production host.
Edit the <fileList> section in ProjFS-Service.exe.config. Each line is:
\Path\To\Entry,isDirectory,fileSize,unixTimestamp
isDirectory—trueorfalsefileSize— size in bytes (displayed todir/Explorer; content is zero-filled)unixTimestamp— file modification time as a Unix epoch integer
Directories must be declared before any files they contain. Commit the updated config and re-run the deploy workflow to push the new layout to the runner.
- Go to Actions → Deploy ProjFS Fake FileSystem Service → Run workflow.
- Set
actiontoremove. - Run.
This stops and uninstalls the WindowsFakeFileSystem Windows service via InstallUtil /u.