Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
673f63e
fix(web): share report limits and surface submission errors
DysektAI Oct 15, 2026
aca3814
fix(web): honor provider waits and classify parser errors
DysektAI Oct 15, 2026
a9efcc3
fix(web): include exhausting attempts in retry guidance
DysektAI Oct 15, 2026
8777a5c
test(web): match fixture tag boundaries and disclose attempt limit
DysektAI Oct 15, 2026
388c94e
fix(web): combine all reservation waits and preserve uncertain delivery
DysektAI Oct 15, 2026
61cbf16
test: detect duplicate attempt charges on reservation denial
DysektAI Oct 15, 2026
d07c73a
#5: remove check published data reports button on data report page
Nivmizz7 Oct 23, 2026
771a852
fix #5 commit
Nivmizz7 Oct 23, 2026
95a2cbd
fix(deps): pin follow-redirects credential-stripping patch
DysektAI Oct 23, 2026
373fafe
fix(deps): pin ip-address zone-validation patch
DysektAI Oct 23, 2026
374da82
fix(web): route data reports publicly and close readiness gaps
DysektAI Oct 23, 2026
3087874
Merge branch 'fix/deps-follow-redirects-1-16-1' into fix/report-limit…
DysektAI Oct 23, 2026
ceb9144
Merge branch 'fix/deps-ip-address-10-7-4' into fix/report-limits-errors
DysektAI Oct 23, 2026
4a4fdf1
fix(web): enforce provider backoff and preserve limiter safety
DysektAI Oct 23, 2026
e38b895
test(deps): resolve redirects from the Axios installation
DysektAI Oct 23, 2026
ed75493
test(deps): resolve IPv6 parsing from the limiter installation
DysektAI Oct 23, 2026
68d55d2
Merge dependency test resolution fix from PR #39
DysektAI Oct 23, 2026
e82883f
Merge dependency test resolution fix from PR #40
DysektAI Oct 23, 2026
464835d
fix(build): keep the service launcher in production installs
DysektAI Oct 23, 2026
a5bc9f5
fix(deps): pin undici content-encoding and EventSource patch
DysektAI Oct 24, 2026
57399ab
fix(deps): pin body-parser compressed-body DoS patch
DysektAI Oct 24, 2026
cd07547
Merge remote-tracking branch 'origin/fix/deps-undici-6-29-1' into fix…
DysektAI Oct 24, 2026
26a572c
Merge dependency advisory fix from PR #43
DysektAI Oct 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
# Discord bot
DISCORD_TOKEN=YOUR_DISCORD_TOKEN

# GitHub issue routing (webserver)
# GitHub issue routing (webserver): both data and website reports use REPO_DEV.
# The production token must have issue-write access to this public repository.
GITHUB_TOKEN=YOUR_GITHUB_TOKEN
REPO_DEV=orga/repo
REPO_DATA_REPORT=orga/repo
REPO_DEV=tarkovtracker-org/TarkovTracker
# Legacy REPO_DATA_REPORT settings are ignored while the overlay is private.

# Web server
PORT=3000
# Comma-separated list of allowed origins for the report forms.
# Leave blank to disable origin checking (not recommended for production).
ALLOWED_ORIGINS=https://tarkovtracker.org,https://www.tarkovtracker.org
ALLOWED_ORIGINS=https://tarkovtracker.org,https://www.tarkovtracker.org,https://trackerbot.nivmizz7.dev,https://www.trackerbot.nivmizz7.dev

# Discord channel IDs for automated posts
WELCOME_CHANNEL_ID=WELCOME_CHANNEL_ID
Expand Down
19 changes: 4 additions & 15 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,11 @@ jobs:
- name: Install dependencies
run: npm ci

- name: Audit production dependencies
run: npm run audit:prod

- name: Syntax check all JS files
run: |
set -e
fail=0
while IFS= read -r f; do
if ! node --check "$f" 2>/dev/null; then
echo "::error file=$f::Syntax error in $f"
node --check "$f" || true
fail=1
fi
done < <(find . -name "*.js" -not -path "./node_modules/*")
if [ "$fail" -ne 0 ]; then
echo "::error::One or more JS files failed syntax check"
exit 1
fi
echo "All JS files passed syntax check."
run: npm run build

- name: Run tests
run: npm test
121 changes: 118 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,92 @@ TrackerBot runs two services side by side:
| **Discord bot** | `bot.js` | Slash commands, reaction roles, welcome automation, and ticket creation. |
| **Bug intake web server** | `webserver.js` | Serves the public issue forms and forwards submissions to GitHub. |

### Reporting limits and errors

Both report forms currently create issues in `REPO_DEV`, configured as
`tarkovtracker-org/TarkovTracker`. The data overlay repository is private, so
`REPO_DATA_REPORT` is no longer required and any existing value is ignored.
Data reports retain their category-prefixed title, category, reference and details.
Both forms link to the public TarkovTracker issue list for delivery checks and
explicitly warn that Discord handles, reference links and report details are public.

`POST /data` and `POST /issue` share one reporting policy for each client IP
(with the existing IPv6 `/56` grouping). The allowance is **20 delivered reports
in a rolling 12 hours**, with **at least 10 minutes between deliveries**. A
delivery exactly 12 hours old leaves the allowance; a new report is admitted
exactly at the 10-minute boundary. IPv4-mapped addresses use the same key as IPv4,
including dotted and hexadecimal IPv6 representations. Switching forms does not
reset either limit. Users sharing an IP or IPv6 prefix share these limits.

The server reserves a slot before awaiting GitHub, so concurrent requests cannot
both pass. Confirmed GitHub issue creation (`201`) consumes the allowance and
starts the cooldown at delivery completion. Validation, origin/parser errors,
honeypot submissions and proven delivery rejections do not count as delivered
reports. A separate shared **five-attempt rolling minute** budget runs before
body parsing and includes rejected/invalid requests. Denials return the longest
active attempt/window/cooldown wait rather than an earlier partial reset.
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Resolved

Timeouts, connection resets, unexpected provider responses and GitHub `5xx`
responses are **unconfirmed delivery**, not proof that no issue was created.
They conservatively reserve allowance and cooldown. A hash of the same report is
held against automatic repetition for 12 hours, bounded by the same retention
window; this is not a durable idempotency guarantee. Check the published issue
list or contact support before repeating it. Different reports may be sent after
the ordinary cooldown if allowance remains. No automatic GitHub retry occurs.

Both forms preserve their entered fields on failure, disable double submission,
and display safe typed server errors. Unknown network/proxy/success responses
keep the draft and stop automatic repetition in that page. The displayed wait is
advisory; every new request is checked by the server. Length limits are visible
and enforced without silently truncating evidence: title 200, Discord handle 64,
category 64, reference 500, details 8,000 characters; request bodies are capped at
32 KiB. Successful submission resets the form. Drafts remain in the open page,
not persistent browser storage; copy them before reloading.

Errors use `{ error: { code, message }, retryAfterSeconds?, resetAt? }`.
Admission errors use HTTP `429` (`report_cooldown`, `report_window_limit`,
`attempt_limit`, `report_in_progress`), capacity uses `503`, and uncertain repeat
delivery uses `409`. Wait errors include numeric `Retry-After` seconds and an ISO
`resetAt`. Validation/parser/origin errors use `400`/`413`/`415`/`403`. Proven provider
rejections/connectivity failures use `502`/`503`; unconfirmed delivery uses `504`.
Provider throttling honors full `Retry-After` and exhausted-primary
`x-ratelimit-reset` waits, taking the longer when both apply. A shared process-local
provider hold prevents every client on both routes, including staged uploads, from
calling GitHub again before that reset. Local denials include any longer provider
wait, while known uncertain-report denials keep their check-before-repeating warning.
Secondary throttles without timing headers use GitHub's minimum one-minute guidance.
Neither server nor browser truncates a valid provider wait. Known client parser failures,
including parameter overflow, excessive nesting and unsupported formats, return
retryable typed `4xx` errors; they are not uncertain GitHub delivery.
See [GitHub rate limits](https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api#exceeding-the-rate-limit)
and [body-parser errors](https://expressjs.com/en/resources/middleware/body-parser/#errors).
Provider bodies, tokens, IPs
and report descriptions are never included in error output or delivery logs.

**Rollout limitation: these controls are process-local memory, not durable or
shared across workers/hosts.** A restart clears allowances, cooldowns,
uncertain-report holds and the shared provider throttle. There is no existing
persistent limiter store or hosting manifest in this repository. State is capped
at 10,000 clients and stale entries are removed; at capacity, expiry cleanup runs
at most once per second. New clients
receive a safe busy response when the remaining entries still hold active limits;
active attempt budgets are not evicted to make room. Otherwise rotating keys could
bypass the five-attempt allowance. Sustained traffic from 10,000 distinct active
client keys can still exhaust this bounded table; operator-side abuse protection
and the trusted-proxy boundary remain necessary. Strict enforcement across restarts
or multiple processes needs an operator-approved persistent/shared store after
hosting is established.
This change adds no infrastructure or production settings and is not evidence
that the deployed topology is ready for strict enforcement.

`trust proxy = 1` remains unchanged. Before rollout, operators must verify that
the origin is reachable only through the intended trusted proxy and that it
overwrites client forwarding headers, and confirm the actual process topology.
Otherwise clients may spoof the limiter key or unrelated users may share one.
Do not fix that by trusting arbitrary forwarded headers. The deployed form origin
must also match the existing `ALLOWED_ORIGINS` setting. Credential, proxy-trust and
production configuration changes require separate approval.

Requires **Node.js 18.17+**.

## Features
Expand All @@ -39,20 +125,49 @@ Requires **Node.js 18.17+**.
### 1. Install dependencies

```bash
npm install
npm ci
npm run build
```

This project has no bundle or generated build output. `npm run build` syntax-checks
all JavaScript files without starting either service. CI runs the same command,
the tests, and `npm run audit:prod`; production dependency advisories fail CI.

### 2. Configure environment variables

Copy `.env.example` to `.env` and fill in every value:

- **Discord bot basics:** `DISCORD_TOKEN`, `ADMIN_ROLE_ID` (comma-separated Discord role IDs granting admin-only commands such as `/wipe`)
- **GitHub issue routing:** `GITHUB_TOKEN`, `REPO_DEV`, `REPO_DATA_REPORT`
- **GitHub issue routing:** `GITHUB_TOKEN`, `REPO_DEV=tarkovtracker-org/TarkovTracker` for both forms. Legacy `REPO_DATA_REPORT` is ignored.
- **Channel IDs for automated posts:** `WELCOME_CHANNEL_ID`; optional `HONEYPOT_CHANNEL_ID` (a hidden channel — anyone who posts in it is banned on sight, with a kick fallback; messages older than 16h are swept hourly)
- **Role IDs:** `PANEL_ADMIN_ROLE_ID`, `AUTO_ROLE_ID_1`–`AUTO_ROLE_ID_4` (auto-roles for new members); optional `ALLOWED_COMMAND_ROLE_IDS` (comma-separated role IDs allowed to use member commands, falls back to a built-in default)
- **Web server:** `PORT`, `ALLOWED_ORIGINS` (comma-separated origins, exact match)

> `GITHUB_TOKEN` needs `repo` scope (or equivalent granular access) to create issues in the configured repositories. For production, prefer a fine-grained PAT scoped to `issues:write` on the two target repos only.
> The deployed `GITHUB_TOKEN` needs issue-write access to `tarkovtracker-org/TarkovTracker`. Prefer a fine-grained PAT with **Issues: write** on that repository only. Redirecting data reports removes the private-overlay dependency, but does not grant permissions to a token that cannot create website issues.

### Production rollout checklist

- Use `npm ci`, `npm run build`, and `npm start` in the hosting configuration.
`npm start` launches one bot and one web process per invocation; its launcher is
a runtime dependency, so `npm ci --omit=dev` is also supported. Verify the actual
replica count and deployment overlap rather than inferring it from the command.
- Set `REPO_DEV=tarkovtracker-org/TarkovTracker`. Verify the deployed token's
issue-write access without sharing credentials. Both report routes use that destination.
- Correct the operator's origin-list typos (`https//` is not `https://`). A valid
list for the reported domains is:
`https://tarkovtracker.org,https://www.tarkovtracker.org,https://trackerbot.nivmizz7.dev,https://www.trackerbot.nivmizz7.dev`.
A preview hostname must be added separately if its forms are tested. Do not
disable origin checking just to make a preview pass.
- Verify that Node is not directly Internet-accessible, that the trusted proxy
overwrites forwarding headers, and that distinct visitors retain distinct limiter keys.
Leave `trust proxy = 1` unchanged until the real proxy path is established.
- Confirm a single serving web process, including during deployments, and explicitly
accept that restarts clear limits, uncertain-delivery holds and provider throttles.
If strict limits across replicas or restarts are required, approve a shared/persistent
store before rollout.
- A successful `/health` response only proves the HTTP process is running, not
GitHub delivery or correct proxy isolation. Coolify configuration and production
credentials cannot be verified from repository tests.

### 3. Run the services locally

Expand Down
Loading
Loading