summaryrefslogtreecommitdiff
path: root/apps/web/lib/validate-webhook-url.test.ts
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-02-10 01:59:01 -0800
committerFuwn <[email protected]>2026-02-10 01:59:01 -0800
commit871985bc9eb42c6a088563e7c34db181f603f407 (patch)
tree31299597a9f246d332b3bf6d5e2bed177648b577 /apps/web/lib/validate-webhook-url.test.ts
parentfeat: reorder feature grid by attention-grabbing impact (diff)
downloadasa.news-871985bc9eb42c6a088563e7c34db181f603f407.tar.xz
asa.news-871985bc9eb42c6a088563e7c34db181f603f407.zip
fix: harden CI and close remaining test/security gaps
- Make webhook URL tests deterministic with injectable DNS resolver - Wire tier parity checker into CI and root scripts - Add rate_limits cleanup cron job (hourly, >1hr retention) - Change rate limiter to fail closed on RPC error - Add Go worker tests: parser, SSRF protection, error classification, authentication, and worker pool (48 test functions)
Diffstat (limited to 'apps/web/lib/validate-webhook-url.test.ts')
-rw-r--r--apps/web/lib/validate-webhook-url.test.ts34
1 files changed, 31 insertions, 3 deletions
diff --git a/apps/web/lib/validate-webhook-url.test.ts b/apps/web/lib/validate-webhook-url.test.ts
index 3375133..78f5b62 100644
--- a/apps/web/lib/validate-webhook-url.test.ts
+++ b/apps/web/lib/validate-webhook-url.test.ts
@@ -1,5 +1,20 @@
import { describe, it, expect } from "vitest"
-import { validateWebhookUrl } from "./validate-webhook-url"
+import { validateWebhookUrl, type DnsResolver } from "./validate-webhook-url"
+
+const publicResolver: DnsResolver = {
+ resolve4: async () => ["93.184.216.34"],
+ resolve6: async () => [],
+}
+
+const privateResolver: DnsResolver = {
+ resolve4: async () => ["192.168.1.1"],
+ resolve6: async () => [],
+}
+
+const failingResolver: DnsResolver = {
+ resolve4: async () => { throw new Error("ENOTFOUND") },
+ resolve6: async () => { throw new Error("ENOTFOUND") },
+}
describe("validateWebhookUrl", () => {
it("rejects empty urls", async () => {
@@ -44,13 +59,26 @@ describe("validateWebhookUrl", () => {
}
})
+ it("rejects hostnames that resolve to private addresses", async () => {
+ const result = await validateWebhookUrl("https://internal.example.com/webhook", privateResolver)
+ expect(result.valid).toBe(false)
+ })
+
+ it("rejects hostnames that fail to resolve", async () => {
+ const result = await validateWebhookUrl("https://nonexistent.example.com/webhook", failingResolver)
+ expect(result.valid).toBe(false)
+ if (!result.valid) {
+ expect(result.error).toContain("could not be resolved")
+ }
+ })
+
it("accepts valid public https urls", async () => {
- const result = await validateWebhookUrl("https://example.com/webhook")
+ const result = await validateWebhookUrl("https://example.com/webhook", publicResolver)
expect(result.valid).toBe(true)
})
it("trims whitespace from urls", async () => {
- const result = await validateWebhookUrl(" https://example.com/webhook ")
+ const result = await validateWebhookUrl(" https://example.com/webhook ", publicResolver)
expect(result.valid).toBe(true)
if (result.valid) {
expect(result.url).toBe("https://example.com/webhook")