pending-dns 1.2.4 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/.github/codeql/codeql-config.yml +11 -0
  2. package/.github/workflows/codeql.yml +52 -0
  3. package/.github/workflows/deploy.yml +16 -3
  4. package/.github/workflows/release.yaml +43 -0
  5. package/.github/workflows/test.yml +75 -0
  6. package/.release-please-manifest.json +3 -0
  7. package/CHANGELOG.md +8 -0
  8. package/CLAUDE.md +97 -0
  9. package/README.md +28 -5
  10. package/SECURITY.md +88 -0
  11. package/SECURITY.txt +27 -0
  12. package/bin/pending-dns.js +1 -1
  13. package/config/default.toml +13 -0
  14. package/config/test.toml +25 -0
  15. package/eslint.config.js +38 -0
  16. package/lib/api-server.js +13 -6
  17. package/lib/cached-resolver.js +5 -3
  18. package/lib/certs.js +11 -4
  19. package/lib/dns-handler.js +46 -14
  20. package/lib/dns-server.js +30 -18
  21. package/lib/dns-tcp-server.js +1 -1
  22. package/lib/dns-udp-server.js +1 -1
  23. package/lib/logger.js +3 -0
  24. package/lib/public-server.js +20 -2
  25. package/lib/sentry.js +72 -0
  26. package/lib/tools.js +1 -1
  27. package/lib/zone-store.js +4 -4
  28. package/package.json +43 -33
  29. package/release-please-config.json +13 -0
  30. package/server.js +5 -24
  31. package/systemd/pending-dns.service +4 -4
  32. package/test/api.test.js +139 -0
  33. package/test/cached-resolver.test.js +57 -0
  34. package/test/certs.test.js +34 -0
  35. package/test/dns-handler.test.js +140 -0
  36. package/test/dns-server.test.js +69 -0
  37. package/test/helpers.js +25 -0
  38. package/test/sentry.test.js +21 -0
  39. package/test/tools.test.js +48 -0
  40. package/test/zone-store.test.js +209 -0
  41. package/workers/api.js +3 -1
  42. package/workers/dns.js +2 -24
  43. package/workers/health.js +3 -26
  44. package/workers/public.js +3 -25
  45. package/.eslintrc +0 -14
  46. package/Gruntfile.js +0 -16
@@ -0,0 +1,11 @@
1
+ name: "PendingDNS CodeQL config"
2
+
3
+ # Exclude code that is not part of the production runtime from analysis.
4
+ # These paths generate only false-positive noise:
5
+ # - the test suite intentionally disables TLS verification and resolves
6
+ # throwaway domains
7
+ # - the example scripts are developer helpers, not shipped code
8
+ paths-ignore:
9
+ - test
10
+ - '**/test/**'
11
+ - example
@@ -0,0 +1,52 @@
1
+ name: "CodeQL Advanced"
2
+
3
+ on:
4
+ push:
5
+ branches: [ "master" ]
6
+ pull_request:
7
+ branches: [ "master" ]
8
+ schedule:
9
+ - cron: '40 17 * * 6'
10
+
11
+ jobs:
12
+ analyze:
13
+ name: Analyze (${{ matrix.language }})
14
+ # Skip release-please's auto-generated release PR: it only changes CHANGELOG/version,
15
+ # so there is no new code to scan. (Matches the guard in test.yml.)
16
+ if: ${{ !startsWith(github.ref_name, 'release-please--') && !startsWith(github.head_ref, 'release-please--') }}
17
+ runs-on: ubuntu-latest
18
+ permissions:
19
+ # required for all workflows
20
+ security-events: write
21
+
22
+ # required to fetch internal or private CodeQL packs
23
+ packages: read
24
+
25
+ # only required for workflows in private repositories
26
+ actions: read
27
+ contents: read
28
+
29
+ strategy:
30
+ fail-fast: false
31
+ matrix:
32
+ include:
33
+ - language: actions
34
+ build-mode: none
35
+ - language: javascript-typescript
36
+ build-mode: none
37
+
38
+ steps:
39
+ - name: Checkout repository
40
+ uses: actions/checkout@v4
41
+
42
+ - name: Initialize CodeQL
43
+ uses: github/codeql-action/init@v4
44
+ with:
45
+ languages: ${{ matrix.language }}
46
+ build-mode: ${{ matrix.build-mode }}
47
+ config-file: ./.github/codeql/codeql-config.yml
48
+
49
+ - name: Perform CodeQL Analysis
50
+ uses: github/codeql-action/analyze@v4
51
+ with:
52
+ category: "/language:${{matrix.language}}"
@@ -5,14 +5,27 @@ on:
5
5
 
6
6
  name: Deploy instance
7
7
 
8
+ permissions:
9
+ contents: read
10
+
11
+ concurrency:
12
+ group: ${{ github.workflow }}-${{ github.ref }}
13
+ cancel-in-progress: true
14
+
8
15
  jobs:
9
16
  deploy:
10
17
  name: Deploy
11
- runs-on: ubuntu-latest
18
+ runs-on: ubuntu-24.04
19
+ timeout-minutes: 15
12
20
 
13
21
  steps:
14
22
  - name: Checkout
15
- uses: actions/checkout@v2
23
+ uses: actions/checkout@v6
24
+
25
+ - name: Use Node.js 24
26
+ uses: actions/setup-node@v6
27
+ with:
28
+ node-version: 24
16
29
 
17
30
  - name: Install SSH key
18
31
  uses: shimataro/ssh-key-action@v2
@@ -29,7 +42,7 @@ jobs:
29
42
  id: deploy
30
43
  run: |
31
44
  echo $GITHUB_SHA > commit.txt
32
- npm install --production
45
+ npm install --omit=dev
33
46
  tar czf /tmp/${SERVICE_NAME}.tar.gz --exclude .git .
34
47
  scp /tmp/${SERVICE_NAME}.tar.gz deploy@${TARGET_HOST_01}:
35
48
  scp /tmp/${SERVICE_NAME}.tar.gz deploy@${TARGET_HOST_02}:
@@ -0,0 +1,43 @@
1
+ on:
2
+ push:
3
+ branches:
4
+ - master
5
+
6
+ permissions:
7
+ contents: write
8
+ pull-requests: write
9
+ id-token: write
10
+
11
+ name: Manage Release
12
+ jobs:
13
+ release_please:
14
+ name: Create or prepare release
15
+ runs-on: ubuntu-24.04
16
+ outputs:
17
+ major: ${{ steps.release.outputs.major }}
18
+ minor: ${{ steps.release.outputs.minor }}
19
+ patch: ${{ steps.release.outputs.patch }}
20
+ release_created: ${{ steps.release.outputs.release_created }}
21
+ steps:
22
+ # Uses release-please-config.json and .release-please-manifest.json
23
+ # from the repository root (manifest mode).
24
+ - uses: googleapis/release-please-action@v4
25
+ id: release
26
+
27
+ publish:
28
+ name: Publish NPM package
29
+ runs-on: ubuntu-24.04
30
+ needs: release_please
31
+ if: ${{ needs.release_please.outputs.release_created }}
32
+ permissions:
33
+ contents: read
34
+ # Required for npm provenance / OIDC trusted publishing.
35
+ id-token: write
36
+ steps:
37
+ - uses: actions/checkout@v6
38
+ - uses: actions/setup-node@v6
39
+ with:
40
+ node-version: 24
41
+ registry-url: "https://registry.npmjs.org"
42
+ - run: npm ci
43
+ - run: npm publish --provenance --access public
@@ -0,0 +1,75 @@
1
+ name: Run Tests
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ concurrency:
11
+ group: ${{ github.workflow }}-${{ github.ref }}
12
+ cancel-in-progress: true
13
+
14
+ jobs:
15
+ # Skip CI for release-please's auto-generated release PR / branch: it only bumps the
16
+ # version + CHANGELOG (no code change), so re-running the suite is wasted work.
17
+ license_check:
18
+ name: License Compliance Check
19
+ if: ${{ !startsWith(github.ref_name, 'release-please--') && !startsWith(github.head_ref, 'release-please--') }}
20
+ runs-on: ubuntu-24.04
21
+ timeout-minutes: 10
22
+ steps:
23
+ - uses: actions/checkout@v6
24
+ - name: Use Node.js 24
25
+ uses: actions/setup-node@v6
26
+ with:
27
+ node-version: 24
28
+ cache: npm
29
+ - run: npm install
30
+ - name: Run License Checks
31
+ run: npm run licenses
32
+
33
+ lint:
34
+ name: Lint
35
+ if: ${{ !startsWith(github.ref_name, 'release-please--') && !startsWith(github.head_ref, 'release-please--') }}
36
+ runs-on: ubuntu-24.04
37
+ timeout-minutes: 10
38
+ steps:
39
+ - uses: actions/checkout@v6
40
+ - name: Use Node.js 24
41
+ uses: actions/setup-node@v6
42
+ with:
43
+ node-version: 24
44
+ cache: npm
45
+ - run: npm install
46
+ - name: Run ESLint
47
+ run: npm run lint
48
+
49
+ unit:
50
+ name: Unit Tests
51
+ if: ${{ !startsWith(github.ref_name, 'release-please--') && !startsWith(github.head_ref, 'release-please--') }}
52
+ runs-on: ubuntu-24.04
53
+ timeout-minutes: 10
54
+ services:
55
+ redis:
56
+ image: redis
57
+ options: >-
58
+ --health-cmd "redis-cli ping"
59
+ --health-interval 10s
60
+ --health-timeout 5s
61
+ --health-retries 5
62
+ ports:
63
+ - 6379:6379
64
+ steps:
65
+ - uses: actions/checkout@v6
66
+ - name: Use Node.js 24
67
+ uses: actions/setup-node@v6
68
+ with:
69
+ node-version: 24
70
+ cache: npm
71
+ - run: npm install
72
+ - name: Run tests
73
+ run: npm test
74
+ env:
75
+ NODE_ENV: test
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "1.3.0"
3
+ }
package/CHANGELOG.md ADDED
@@ -0,0 +1,8 @@
1
+ # Changelog
2
+
3
+ ## [1.3.0](https://github.com/postalsys/pending-dns/compare/pending-dns-v1.2.5...pending-dns-v1.3.0) (2026-06-18)
4
+
5
+
6
+ ### Features
7
+
8
+ * replace Bugsnag with self-hosted Sentry error reporting ([3b374c7](https://github.com/postalsys/pending-dns/commit/3b374c737ff0a22509f79976bee18ab9d425f8bd))
package/CLAUDE.md ADDED
@@ -0,0 +1,97 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ PendingDNS is a lightweight, API-driven **authoritative** DNS server (UDP + TCP), bundled with a REST API for managing zone records, a public HTTP/HTTPS redirect/proxy server, automatic Let's Encrypt certificate generation, and periodic health checks. All state lives in Redis.
6
+
7
+ ## Commands
8
+
9
+ ```bash
10
+ npm start # run the whole app (node server.js)
11
+ npm test # run the test suite (requires a local Redis on 127.0.0.1:6379)
12
+ npm run lint # ESLint 9 (flat config: eslint.config.js)
13
+ npm run licenses # regenerate licenses.txt (license-report)
14
+ ```
15
+
16
+ Running the app or tests requires a reachable Redis and a config with a valid `acme.email` (the bootstrap in `server.js` exits otherwise). For local runs, either set it in `config/development.toml` or pass `--acme.email=you@example.com`.
17
+
18
+ ### Tests
19
+
20
+ - Built on the Node.js built-in test runner (`node:test`). `npm test` runs `NODE_ENV=test node --test --test-force-exit --test-concurrency=1 test/*.test.js`.
21
+ - `NODE_ENV=test` loads `config/test.toml`, which points Redis at a **dedicated database (db 15)**. `test/helpers.js#flushTestDb` wipes db 15 between cases and **refuses to run unless the Redis URL ends in `/15`** — so tests never touch dev (db 2) or prod data. Always run tests with `NODE_ENV=test`.
22
+ - `--test-concurrency=1` is required because all test files share db 15. `--test-force-exit` is required because requiring `lib/db` opens persistent ioredis connections; every test file must also call `closeDb()` in an `after()` hook or the process hangs.
23
+ - Some `cached-resolver`/`dns-server` tests do real DNS lookups (e.g. `one.one.one.one`); they are written to skip or tolerate missing network.
24
+
25
+ Run a single file or filter by test name:
26
+
27
+ ```bash
28
+ NODE_ENV=test node --test --test-force-exit test/zone-store.test.js
29
+ NODE_ENV=test node --test --test-force-exit --test-name-pattern="wildcard" test/*.test.js
30
+ ```
31
+
32
+ ### Building standalone executables
33
+
34
+ Executables are built with `@yao-pkg/pkg` (the maintained `pkg` fork) via the `pkg` block in `package.json`, targeting node24. The org-wide `../emailengine-api/upload.sh <repo>` script installs `@yao-pkg/pkg` globally, then runs `npm run build-source` followed by `npm run build-dist` (or `build-dist-fast` for an unsigned test build) and signs/notarizes/uploads the artifacts. Output binaries are named `pending-dns-<target>` under `ee-dist/`.
35
+
36
+ Because workers are loaded through **dynamic `require()` / `worker_threads`** (see below), `pkg.scripts` must list `lib/**/*.js` and `workers/**/*.js` or the worker code is missing from the snapshot. Runtime files read via `__dirname` (`lib/lua/health.lua`, `config/*.pem`) must be in `pkg.assets`. Do **not** add `pkg.options` like `max_old_space_size`: the workers forward `argv`, and an injected V8 flag leaks in as a bogus module path and crashes every worker.
37
+
38
+ ## Architecture
39
+
40
+ ### Process model (the most important thing to understand)
41
+
42
+ `server.js` is the supervisor. For each enabled subsystem (`api`, `dns`, `public`, `health`) it spawns a **worker thread** running `workers/<type>.js`, and restarts it on exit. Each `workers/<type>.js` is itself a small bootstrap that:
43
+
44
+ 1. installs crash handlers + optional Sentry error reporting via `lib/sentry.js#initSentry` (only when `SENTRY_DSN` / `[sentry] dsn` is set; uses Sentry's uncaught-exception / unhandled-rejection integrations so crashes are reported, then `closeProcess`'s `if (!logger.errorReportingEnabled)` lets Sentry flush+exit),
45
+ 2. uses Node `cluster` to fork `config[type].workers` processes (or runs in-thread when `workers === 1` and no user/group drop is configured),
46
+ 3. dynamically `require`s the implementation: ``require(`../lib/${type}-server.js`)`` (or `-worker.js` for `health`) and calls it,
47
+ 4. drops privileges via `config.process.user`/`group` after ports are bound.
48
+
49
+ So `lib/<type>-server.js` are entry points reached **only through dynamic requires** — static analysis (and `pkg`) won't find them. The implementations are: `lib/api-server.js`, `lib/dns-server.js`, `lib/public-server.js`, `lib/health-worker.js`.
50
+
51
+ ### Configuration (`wild-config`)
52
+
53
+ Config is loaded by `wild-config` from `config/default.toml` merged with `config/<NODE_ENV>.toml`, then overridden by CLI args (`--dns.port=53`) and `appconf_*` env vars. `NODE_CONFIG_PATH` points to an external file in production (see `systemd/pending-dns.service`). Values are coerced to the type of the default (a string env var for a numeric default becomes a number). Config is read from `process.cwd()/config`, **not** from `__dirname` — relevant for packaged binaries, which need a `config/` directory next to them.
54
+
55
+ ### Redis data model (`lib/zone-store.js`)
56
+
57
+ This is the heart of the system and is non-obvious:
58
+
59
+ - **Domain names are stored label-reversed**: `www.example.com` → `com.example.www` (`domainToName`/`nameToDomain`). This lets `resolveZone` walk *up* from a name to its registered zone by progressively dropping the most-specific label. A "zone" is any name with a `d:<name>:z` set; the shortest possible zone is the 2-label boundary (e.g. `example.com`).
60
+ - Keys: `d:<name>:z` is a **set of record keys** belonging to that zone; `d:<name>:r:<TYPE>` is a **hash** of `hid → JSON.stringify(valueArray)`. Each record's value is a positional array whose meaning depends on type (e.g. A = `[address, healthCheckUri]`, MX = `[exchange, priority]`, CAA = `[value, tag, flags]`, URL = `[url, code, proxy]`).
61
+ - A record's public **ID is `base64url(name \x01 TYPE \x01 hid)`** (`getFullId`/`parseFullId`); `hid` is a `nanoid()`. IDs are opaque and stable only while domain+type are unchanged (an `update` that changes either deletes and re-adds, producing a new ID).
62
+ - **Wildcards** are single-label: a record stored under subdomain `*.foo` matches `anything.foo.<zone>` only (`resolve` retries with the last label replaced by `*`).
63
+ - **Read/write split**: reads go to `db.redisRead`, writes to `db.redisWrite` (configurable as separate master/replica URLs). The health-check Lua script `lib/lua/health.lua` is registered as a custom command `nextHealth` on the write client.
64
+
65
+ ### DNS request handling (`lib/dns-handler.js` + custom servers)
66
+
67
+ `lib/dns-udp-server.js` and `lib/dns-tcp-server.js` are hand-rolled servers that parse/serialize with `dns2`'s `Packet` (the project does **not** use dns2's built-in server). `lib/dns-server.js` wires them to `dnsHandler` and returns the bound server handles.
68
+
69
+ `processQuestion` does more than a lookup: for an A/AAAA query it also pulls `CNAME`, `ANAME`, and `URL` records; it follows CNAME chains recursively (depth-limited); A/AAAA results are health-filtered and shuffled; MX is priority-sorted. When nothing is stored it synthesizes fallbacks — `NS` from `config.ns`, a `SOA` from `config.soa`, default Let's Encrypt `CAA` records, and `version.bind`-style CHAOS TXT answers.
70
+
71
+ Two pseudo-record types: **ANAME** (apex alias) is resolved to real A/AAAA at query time via `lib/cached-resolver.js` (a Redis-cached wrapper over Node's `dns.Resolver`, with soft/hard TTLs for both hits and errors). **URL** records answer A/AAAA with the redirect server IPs from `config.public.hosts`; the actual redirect/proxy happens in `lib/public-server.js`.
72
+
73
+ ### Certificates & the public server
74
+
75
+ `lib/certs.js` issues Let's Encrypt certs via the **dns-01** challenge, using the zone store itself as the ACME DNS provider (it writes/reads the `_acme-challenge` TXT records). Concurrent issuance is guarded with an `ioredfour` Redis lock; results and a per-domain renewal lock are cached in Redis. `lib/public-server.js` uses an SNI callback to load the right cert per hostname (falling back to a bundled self-signed cert in `config/`), then serves URL-record redirects or reverse-proxies (`proxy=true`), with TLS session tickets stored in Redis.
76
+
77
+ ### Testability seams
78
+
79
+ Production code exposes hooks used only by tests: `lib/api-server.js` exports `createServer()` (build the Hapi server without `start()`, for `server.inject()`); `lib/dns-server.js`'s `init()` awaits binding and returns `{ udpServer, tcpServer }`; `lib/dns-handler.js` and `lib/certs.js` attach a `.testables` object to their exported function.
80
+
81
+ ## CI / release
82
+
83
+ GitHub Actions mirror the `postalsys/emailengine` conventions and run on Node 24: `test.yml` (license check + lint + tests with a Redis service), `codeql.yml`, and `release.yaml` (release-please in manifest mode — see `release-please-config.json` + `.release-please-manifest.json`; publishes to npm via OIDC trusted publishing, which must be configured registry-side). `deploy.yml` tarballs the repo and ships it to the two name servers. Security policy lives in `SECURITY.md` + a GPG-signed `SECURITY.txt`.
84
+
85
+ ## Commit conventions
86
+
87
+ - Use **Conventional Commits** (`feat:`, `fix:`, `chore:`, `docs:`, `refactor:`, `ci:`, etc.). release-please derives the version bump and `CHANGELOG.md` from these prefixes, so `feat:`/`fix:` commits landing on `master` are what open a release PR.
88
+ - **Do not** add Claude (or any AI assistant) as a co-author / co-contributor in commit messages.
89
+ - For commits that do not change runtime behaviour (docs, comments, CI/workflow tweaks, formatting), append `[skip ci]` to the commit message to avoid triggering the workflows. **Exception:** never add `[skip ci]` to a `feat:`/`fix:` commit — those must run so the release workflow fires.
90
+ - Run `npm run lint` and `npm test` before committing (the test run needs a local Redis; see the Tests section).
91
+ - After pushing, check the workflow runs (`gh run list --branch <branch>`) and report their status. If a run fails for an unrelated infrastructure reason (auth errors, HTTP 403, "account suspended"), check <https://www.githubstatus.com/> for an active incident before assuming the change is at fault.
92
+
93
+ ## Code style
94
+
95
+ - No emojis in code or documentation — printable ASCII only.
96
+ - Use a single hyphen-minus (`-`) as a dash in user-facing strings and docs; never em dashes, en dashes, or double hyphens (`--`).
97
+ - Never swallow errors at the global `uncaughtException` / `unhandledRejection` handlers to keep a worker alive — those handlers are the last line of defence and the worker is expected to exit. Fix the error at its source (add the missing `try/catch`, `.catch()`, or `error` listener at the call site) instead.
package/README.md CHANGED
@@ -25,13 +25,13 @@ Lightweight API driven Authoritative DNS server.
25
25
 
26
26
  ## Requirements
27
27
 
28
- - **Node.js**, preferrably v12+
28
+ - **Node.js**, v18 or newer
29
29
  - **Redis**, any version should do as only basic commands are used
30
30
 
31
31
  ## Usage
32
32
 
33
33
  ```
34
- $ npm install --production
34
+ $ npm install --omit=dev
35
35
  $ npm start
36
36
  ```
37
37
 
@@ -45,9 +45,9 @@ As root run the following commands to set up PendingDNS:
45
45
 
46
46
  ```
47
47
  $ cd /opt
48
- $ git clone git://github.com/postalsys/pending-dns.git
48
+ $ git clone https://github.com/postalsys/pending-dns.git
49
49
  $ cd pending-dns
50
- $ npm install --production
50
+ $ npm install --omit=dev
51
51
  $ cp systemd/pending-dns.service /etc/systemd/system
52
52
  $ cp config/default.toml /etc/pending-dns.toml
53
53
  ```
@@ -110,6 +110,28 @@ Without proper setup domain registrars do not allow your name server domain name
110
110
 
111
111
  ![](https://cldup.com/l0U6jc5pfM.png)
112
112
 
113
+ ## Development
114
+
115
+ Install all dependencies (including dev dependencies):
116
+
117
+ ```
118
+ $ npm install
119
+ ```
120
+
121
+ ### Tests
122
+
123
+ The test suite runs with the built-in Node.js test runner and needs a **local Redis** instance listening on `127.0.0.1:6379`. Tests use a dedicated database (`db 15`) which is flushed between runs, so it does not touch development or production data.
124
+
125
+ ```
126
+ $ npm test
127
+ ```
128
+
129
+ ### Linting
130
+
131
+ ```
132
+ $ npm run lint
133
+ ```
134
+
113
135
  ## API
114
136
 
115
137
  You can see the entire API docs from the swagger page at http://127.0.0.1:5080/docs
@@ -203,7 +225,8 @@ All record types have the following properties
203
225
  **CAA**
204
226
 
205
227
  - **value** is the domain name of the provider, eg. `letsencrypt.org`
206
- - **tag** is the CAA tag, eg. `issue` or `issuewild`
228
+ - **tag** is the CAA tag, one of `issue`, `issuewild` or `iodef`
229
+ - **flags** (Number, default is `0`) is the CAA flags octet (0-255)
207
230
 
208
231
  **URL**
209
232
 
package/SECURITY.md ADDED
@@ -0,0 +1,88 @@
1
+ # Security Policy
2
+
3
+ PendingDNS is a lightweight, API-driven authoritative DNS server. It also runs a
4
+ public HTTP/HTTPS redirect/proxy server and generates Let's Encrypt certificates
5
+ over an ACME flow, and it stores all zone data in Redis. Because it is exposed to
6
+ untrusted DNS and HTTP traffic and handles certificate private keys, we take
7
+ security reports seriously and aim to respond quickly.
8
+
9
+ ## Supported Versions
10
+
11
+ Security fixes are released only against the latest version. We do not backport
12
+ patches to older releases - upgrading to the current release line is the
13
+ supported way to receive security updates.
14
+
15
+ | Version | Supported |
16
+ | ------- | ------------------ |
17
+ | 1.x | :white_check_mark: |
18
+ | < 1.0 | :x: |
19
+
20
+ If you are on an older version, please upgrade. See the release notes at
21
+ <https://github.com/postalsys/pending-dns/releases> before updating.
22
+
23
+ ## Reporting a Vulnerability
24
+
25
+ **Please do not report security vulnerabilities through public GitHub issues,
26
+ pull requests, or discussions.**
27
+
28
+ Report privately through one of the following channels:
29
+
30
+ 1. **GitHub Security Advisories (preferred).** Open a private report at
31
+ <https://github.com/postalsys/pending-dns/security/advisories/new>. This keeps
32
+ the discussion private until a fix is published and lets us credit you.
33
+ 2. **Email.** Send details to **andris@postalsys.com** (the contact listed in
34
+ [`SECURITY.txt`](SECURITY.txt)). Encrypt sensitive details if possible using
35
+ the key referenced there.
36
+
37
+ When reporting, please include as much of the following as you can:
38
+
39
+ - The affected version(s) and environment (PendingDNS version, Node.js version,
40
+ OS, deployment method - npm or prebuilt binary).
41
+ - The component involved (e.g. the UDP/TCP DNS server, the REST API, the public
42
+ HTTP/HTTPS redirect and proxy server, ACME certificate generation, health
43
+ checks, or the Redis-backed zone store).
44
+ - A clear description of the issue and its impact (e.g. cache poisoning, DNS
45
+ amplification, request smuggling, SSRF via the URL/proxy records or health
46
+ checks, certificate mis-issuance, credential or private-key disclosure,
47
+ injection, information disclosure, denial of service).
48
+ - A minimal proof of concept or reproduction steps.
49
+ - Any suggested remediation, if you have one.
50
+
51
+ We are a small team, so there is no guaranteed response time - sometimes reports
52
+ are handled within hours, sometimes they take longer. Accepted issues are fixed
53
+ in a new release and coordinated through a GitHub Security Advisory, and
54
+ reporters who wish to be named are credited.
55
+
56
+ ## CVEs
57
+
58
+ We track and disclose vulnerabilities through GitHub Security Advisories. We do
59
+ not request or manage CVE identifiers ourselves. If you need a CVE assigned for a
60
+ reported issue, please request one yourself - for example, through GitHub's own
61
+ CVE request flow on the published advisory, or another CNA.
62
+
63
+ ## Scope
64
+
65
+ In scope: the PendingDNS application source in this repository - the
66
+ authoritative DNS server (UDP and TCP), the REST API for managing zone records,
67
+ the public HTTP/HTTPS redirect and proxy server, ACME/Let's Encrypt certificate
68
+ generation and storage, the health-check subsystem, and the Redis-backed zone
69
+ store.
70
+
71
+ Out of scope:
72
+
73
+ - Vulnerabilities in your own application code or automation that integrates
74
+ with the PendingDNS API.
75
+ - Misconfiguration of your deployment - for example, exposing the management API
76
+ to untrusted networks, an unauthenticated or publicly reachable Redis instance,
77
+ binding the DNS server in a way that enables open-resolver-style abuse, or
78
+ missing TLS on the API.
79
+ - The inherent properties of plain DNS over UDP/TCP without DNSSEC (PendingDNS
80
+ does not implement DNSSEC, DoH, or DoT by design).
81
+ - Issues that require an already-compromised host or pre-existing administrator
82
+ access.
83
+ - Vulnerabilities in third-party services PendingDNS connects to (Let's Encrypt,
84
+ upstream resolvers, health-check targets).
85
+ - Social-engineering reports and missing security headers without a
86
+ demonstrated, concrete impact.
87
+
88
+ Thank you for helping keep PendingDNS and its users safe.
package/SECURITY.txt ADDED
@@ -0,0 +1,27 @@
1
+ -----BEGIN PGP SIGNED MESSAGE-----
2
+ Hash: SHA256
3
+
4
+ Contact: https://github.com/postalsys/pending-dns/security/advisories/new
5
+ Contact: mailto:andris@postalsys.com
6
+ Expires: 2027-06-01T00:00:00.000Z
7
+ Encryption: https://keys.openpgp.org/vks/v1/by-fingerprint/5D952A46E1D8C931F6364E01DC6C83F4D584D364
8
+ Preferred-Languages: en, et
9
+ Canonical: https://github.com/postalsys/pending-dns/blob/master/SECURITY.txt
10
+ Policy: https://github.com/postalsys/pending-dns/blob/master/SECURITY.md
11
+ -----BEGIN PGP SIGNATURE-----
12
+
13
+ iQJPBAEBCAA5FiEEXZUqRuHYyTH2Nk4B3GyD9NWE02QFAmozqqUbFIAAAAAABAAO
14
+ bWFudTIsMi41KzEuMTIsMCwzAAoJENxsg/TVhNNkUlUQAL8mZHOiH2ZVgsrJanW6
15
+ 58YQ5DTNfLL0CO5Qwo1j9XijmvF7dMwDsNTvQ2hDBV+Okz7mSfDBUaofgT6kIBQz
16
+ Qk0yMyOp1Um+l4rF7/J2d/9ABBnsu4Le59Mu87qIgziLCu3YarheThiPCQvFzRfH
17
+ A/vmGOu3/+gcHdF2GrlEk8xfFNdIqwdhuW8oTiR18WqUARe3S+wQdfumDX41gVRL
18
+ y0Q8eGXb3j8jLXp9E21ePlPdxtIQC4fZSexd64IEKv7kuVp9vDpai0jdi5SQSmCA
19
+ gOPp5f9jR0B6GCbRfCRYUHsrQlwOZPtCq46IGKCnrCd2wI7RHTeCUwtzOQsqod7n
20
+ u+GE/YAZZuck9OI6oDZ3klGXUNAi5RO16ix80rybFfVA2MmNdCDHDwTmlysHli8E
21
+ 9FjakLcnF/5eH5NlH579IhQIx1exmE9Q+ZyCwaNQ2uGIujxy6bay3cxvXXeecrJM
22
+ c0MMNgyh7CCfmHShoXfQ2JnFTlVycgqZetLySBxGzkgj5mczLKHviAjaoM3Hw2K6
23
+ znct7z4aE0yY+tCItdIHTdPV5NxR319HZRE980h2yxt92aWmhZn/dW/4fAEwqzFU
24
+ C6Ghu37qs9JwvyBZImH92fc9+27Xgx0Ahfb9RoXM/+TulXAdrQxcK+th9ye40GfJ
25
+ otvIQGxzLN1kyCWhQCqusEJ4
26
+ =QK6n
27
+ -----END PGP SIGNATURE-----
@@ -36,7 +36,7 @@ function run() {
36
36
 
37
37
  case 'version':
38
38
  // Show version
39
- console.log(`EmailEngine v${packageData.version} (${packageData.license})`);
39
+ console.log(`PendingDNS v${packageData.version} (${packageData.license})`);
40
40
  return process.exit();
41
41
 
42
42
  default:
@@ -2,6 +2,11 @@
2
2
  [log]
3
3
  level = "trace"
4
4
 
5
+ [sentry]
6
+ # Error reporting DSN for the self-hosted Sentry server. Leave empty to disable.
7
+ # The production value is set via the SENTRY_DSN environment variable.
8
+ dsn = ""
9
+
5
10
  [dbs]
6
11
 
7
12
  # By default all redis commands are sent against the same instance
@@ -66,6 +71,14 @@ retry = 600
66
71
  expiration = 604800
67
72
  minimum = 60
68
73
 
74
+ # Text to return for chaos requests
75
+ # Disabled by default
76
+ [chaos]
77
+ #"version.bind" = "PendingDNS"
78
+ #"hostname.bind" = "forbidden.lan"
79
+ #"id.server" = "forbidden.lan"
80
+ #"authors.bind" = ["Andris Reinman"]
81
+
69
82
  # Resolver for external DNS queries, set ns=false to use system default
70
83
  # Mostly used for ANAME resolving
71
84
  [resolver]
@@ -0,0 +1,25 @@
1
+ # Configuration used by the automated test suite (NODE_ENV=test).
2
+ # It is never loaded in production/development and keeps tests isolated
3
+ # from real data by pointing Redis at a dedicated database that the test
4
+ # setup is free to flush.
5
+
6
+ [log]
7
+ level = "silent"
8
+
9
+ [dbs]
10
+ # Dedicated Redis database for tests. The test bootstrap flushes this DB,
11
+ # so it must not be shared with development (db 2) or production data.
12
+ redis = "redis://127.0.0.1:6379/15"
13
+
14
+ [acme]
15
+ # A syntactically valid address is required for the server bootstrap check.
16
+ email = "test@example.com"
17
+
18
+ [dns]
19
+ # Avoid clashing with a locally running instance if the DNS server is started.
20
+ port = 15353
21
+ host = "127.0.0.1"
22
+
23
+ [api]
24
+ port = 15080
25
+ host = "127.0.0.1"
@@ -0,0 +1,38 @@
1
+ 'use strict';
2
+
3
+ const { FlatCompat } = require('@eslint/eslintrc');
4
+ const js = require('@eslint/js');
5
+ const prettier = require('eslint-config-prettier');
6
+
7
+ const compat = new FlatCompat({
8
+ baseDirectory: __dirname,
9
+ recommendedConfig: js.configs.recommended,
10
+ allConfig: js.configs.all
11
+ });
12
+
13
+ module.exports = [
14
+ {
15
+ ignores: ['node_modules/', 'ee-dist/', 'coverage/', 'views/', '.prettierrc.js']
16
+ },
17
+
18
+ // Shared Nodemailer ESLint rules (eslintrc format, wrapped for flat config)
19
+ ...compat.extends('eslint-config-nodemailer'),
20
+
21
+ // Disable stylistic rules that conflict with Prettier
22
+ prettier,
23
+
24
+ {
25
+ languageOptions: {
26
+ ecmaVersion: 2023,
27
+ sourceType: 'commonjs'
28
+ },
29
+ rules: {
30
+ indent: 'off',
31
+ 'no-await-in-loop': 'off',
32
+ 'require-atomic-updates': 'off',
33
+ // Preserve the long-standing project convention of `catch (err) { /* ignore */ }`.
34
+ // ESLint 9 changed the no-unused-vars `caughtErrors` default to 'all'.
35
+ 'no-unused-vars': ['error', { caughtErrors: 'none' }]
36
+ }
37
+ }
38
+ ];