about summary refs log tree commit diff
path: root/bskylink/tests
diff options
context:
space:
mode:
authorhailey <hailey@blueskyweb.xyz>2025-09-02 13:36:20 -0700
committerGitHub <noreply@github.com>2025-09-02 13:36:20 -0700
commitacdc509630d5182f9f3d224b259e2a46000b1f27 (patch)
tree92d6b474bad9692e5b054ed8b693bca1cba816ac /bskylink/tests
parentb2258fb6cbdb5de79a7c7d848347f3f157059aa5 (diff)
downloadvoidsky-acdc509630d5182f9f3d224b259e2a46000b1f27.tar.zst
safelink (#8917)
Co-authored-by: hailey <me@haileyok.com>
Co-authored-by: Stanislas Signoud <signez@stanisoft.net>
Co-authored-by: will berry <wsb@wills-MBP.attlocal.net>
Co-authored-by: BlueSkiesAndGreenPastures <will@blueskyweb.xyz>
Co-authored-by: Chenyu Huang <itschenyu@gmail.com>
Diffstat (limited to 'bskylink/tests')
-rw-r--r--bskylink/tests/index.ts241
1 files changed, 239 insertions, 2 deletions
diff --git a/bskylink/tests/index.ts b/bskylink/tests/index.ts
index c5604c7a1..1b3d06ad1 100644
--- a/bskylink/tests/index.ts
+++ b/bskylink/tests/index.ts
@@ -1,7 +1,9 @@
 import assert from 'node:assert'
-import {AddressInfo} from 'node:net'
+import {type AddressInfo} from 'node:net'
 import {after, before, describe, it} from 'node:test'
 
+import {ToolsOzoneSafelinkDefs} from '@atproto/api'
+
 import {Database, envToCfg, LinkService, readEnv} from '../src/index.js'
 
 describe('link service', async () => {
@@ -15,6 +17,10 @@ describe('link service', async () => {
       appHostname: 'test.bsky.app',
       dbPostgresSchema: 'link_test',
       dbPostgresUrl: process.env.DB_POSTGRES_URL,
+      safelinkEnabled: true,
+      ozoneUrl: 'http://localhost:2583',
+      ozoneAgentHandle: 'mod-authority.test',
+      ozoneAgentPass: 'hunter2',
     })
     const migrateDb = Database.postgres({
       url: cfg.db.url,
@@ -26,8 +32,85 @@ describe('link service', async () => {
     await linkService.start()
     const {port} = linkService.server?.address() as AddressInfo
     baseUrl = `http://localhost:${port}`
-  })
 
+    // Ensure blocklist, whitelist, and safelink rules are set up
+    const now = new Date().toISOString()
+    linkService.ctx.cfg.eventCache.smartUpdate({
+      $type: 'tools.ozone.safelink.defs#event',
+      id: 1,
+      eventType: ToolsOzoneSafelinkDefs.ADDRULE,
+      url: 'https://en.wikipedia.org/wiki/Fight_Club',
+      pattern: ToolsOzoneSafelinkDefs.URL,
+      action: ToolsOzoneSafelinkDefs.WARN,
+      reason: ToolsOzoneSafelinkDefs.SPAM,
+      createdBy: 'did:example:admin',
+      createdAt: now,
+      comment: 'Do not talk about Fight Club',
+    })
+    linkService.ctx.cfg.eventCache.smartUpdate({
+      $type: 'tools.ozone.safelink.defs#event',
+      id: 2,
+      eventType: ToolsOzoneSafelinkDefs.ADDRULE,
+      url: 'https://gist.github.com/MattIPv4/045239bc27b16b2bcf7a3a9a4648c08a',
+      pattern: ToolsOzoneSafelinkDefs.URL,
+      action: ToolsOzoneSafelinkDefs.BLOCK,
+      reason: ToolsOzoneSafelinkDefs.SPAM,
+      createdBy: 'did:example:admin',
+      createdAt: now,
+      comment: 'All Bs',
+    })
+    linkService.ctx.cfg.eventCache.smartUpdate({
+      $type: 'tools.ozone.safelink.defs#event',
+      id: 3,
+      eventType: ToolsOzoneSafelinkDefs.ADDRULE,
+      url: 'https://en.wikipedia.org',
+      pattern: ToolsOzoneSafelinkDefs.DOMAIN,
+      action: ToolsOzoneSafelinkDefs.WHITELIST,
+      reason: ToolsOzoneSafelinkDefs.NONE,
+      createdBy: 'did:example:admin',
+      createdAt: now,
+      comment: 'Whitelisting the knowledge base of the internet',
+    })
+    linkService.ctx.cfg.eventCache.smartUpdate({
+      $type: 'tools.ozone.safelink.defs#event',
+      id: 4,
+      eventType: ToolsOzoneSafelinkDefs.ADDRULE,
+      url: 'https://www.instagram.com/teamseshbones/?hl=en',
+      pattern: ToolsOzoneSafelinkDefs.URL,
+      action: ToolsOzoneSafelinkDefs.BLOCK,
+      reason: ToolsOzoneSafelinkDefs.SPAM,
+      createdBy: 'did:example:admin',
+      createdAt: now,
+      comment: 'BONES has been erroneously blocked for the sake of this test',
+    })
+    const later = new Date(Date.now() + 1000).toISOString()
+    linkService.ctx.cfg.eventCache.smartUpdate({
+      $type: 'tools.ozone.safelink.defs#event',
+      id: 5,
+      eventType: ToolsOzoneSafelinkDefs.REMOVERULE,
+      url: 'https://www.instagram.com/teamseshbones/?hl=en',
+      pattern: ToolsOzoneSafelinkDefs.URL,
+      action: ToolsOzoneSafelinkDefs.REMOVERULE,
+      reason: ToolsOzoneSafelinkDefs.NONE,
+      createdBy: 'did:example:admin',
+      createdAt: later,
+      comment:
+        'BONES has been resurrected to bring good music to the world once again',
+    })
+    linkService.ctx.cfg.eventCache.smartUpdate({
+      $type: 'tools.ozone.safelink.defs#event',
+      id: 6,
+      eventType: ToolsOzoneSafelinkDefs.ADDRULE,
+      url: 'https://www.leagueoflegends.com/en-us/',
+      pattern: ToolsOzoneSafelinkDefs.URL,
+      action: ToolsOzoneSafelinkDefs.WARN,
+      reason: ToolsOzoneSafelinkDefs.SPAM,
+      createdBy: 'did:example:admin',
+      createdAt: now,
+      comment:
+        'Could be quite the mistake to get into this addicting game, but we will warn instead of block',
+    })
+  })
   after(async () => {
     await linkService?.destroy()
   })
@@ -76,6 +159,80 @@ describe('link service', async () => {
     assert.strictEqual(json.message, 'Link not found')
   })
 
+  it('League of Legends warned', async () => {
+    const urlToRedirect = 'https://www.leagueoflegends.com/en-us/'
+    const url = new URL(`${baseUrl}/redirect`)
+    url.searchParams.set('u', urlToRedirect)
+    const res = await fetch(url, {redirect: 'manual'})
+    assert.strictEqual(res.status, 200)
+    const html = await res.text()
+    assert.match(
+      html,
+      new RegExp(urlToRedirect.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')),
+    )
+    // League of Legends is set to WARN, not BLOCK, so expect a warning (blocked-site div present)
+    assert.match(
+      html,
+      /Warning: Malicious Link/,
+      'Expected warning not found in HTML',
+    )
+  })
+
+  it('Wikipedia whitelisted, url restricted. Redirect safely since wikipedia is whitelisted', async () => {
+    const urlToRedirect = 'https://en.wikipedia.org/wiki/Fight_Club'
+    const url = new URL(`${baseUrl}/redirect`)
+    url.searchParams.set('u', urlToRedirect)
+    const res = await fetch(url, {redirect: 'manual'})
+    assert.strictEqual(res.status, 200)
+    const html = await res.text()
+    assert.match(html, /meta http-equiv="refresh"/)
+    assert.match(
+      html,
+      new RegExp(urlToRedirect.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')),
+    )
+    // Wikipedia domain is whitelisted, so no blocked-site div should be present
+    assert.doesNotMatch(html, /"blocked-site"/)
+  })
+
+  it('Unsafe redirect with block rule, due to the content of webpage.', async () => {
+    const urlToRedirect =
+      'https://gist.github.com/MattIPv4/045239bc27b16b2bcf7a3a9a4648c08a'
+    const url = new URL(`${baseUrl}/redirect`)
+    url.searchParams.set('u', urlToRedirect)
+    const res = await fetch(url, {redirect: 'manual'})
+    assert.strictEqual(res.status, 200)
+    const html = await res.text()
+    assert.match(
+      html,
+      new RegExp(urlToRedirect.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')),
+    )
+    assert.match(
+      html,
+      /"blocked-site"/,
+      'Expected blocked-site div not found in HTML',
+    )
+  })
+
+  it('Rule adjustment, safe redirect, 200 response for Instagram Account of teamsesh Bones', async () => {
+    // Retrieve the latest event after all updates
+    const result = linkService.ctx.cfg.eventCache.smartGet(
+      'https://www.instagram.com/teamseshbones/?hl=en',
+    )
+    assert(result, 'Expected event not found in eventCache')
+    assert.strictEqual(result.eventType, ToolsOzoneSafelinkDefs.REMOVERULE)
+    const urlToRedirect = 'https://www.instagram.com/teamseshbones/?hl=en'
+    const url = new URL(`${baseUrl}/redirect`)
+    url.searchParams.set('u', urlToRedirect)
+    const res = await fetch(url, {redirect: 'manual'})
+    assert.strictEqual(res.status, 200)
+    const html = await res.text()
+    assert.match(html, /meta http-equiv="refresh"/)
+    assert.match(
+      html,
+      new RegExp(urlToRedirect.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')),
+    )
+  })
+
   async function getRedirect(link: string): Promise<[number, string]> {
     const url = new URL(link)
     const base = new URL(baseUrl)
@@ -121,3 +278,83 @@ describe('link service', async () => {
     return payload.url
   }
 })
+
+describe('link service no safelink', async () => {
+  let linkService: LinkService
+  let baseUrl: string
+  before(async () => {
+    const env = readEnv()
+    const cfg = envToCfg({
+      ...env,
+      hostnames: ['test.bsky.link'],
+      appHostname: 'test.bsky.app',
+      dbPostgresSchema: 'link_test',
+      dbPostgresUrl: process.env.DB_POSTGRES_URL,
+      safelinkEnabled: false,
+      ozoneUrl: 'http://localhost:2583',
+      ozoneAgentHandle: 'mod-authority.test',
+      ozoneAgentPass: 'hunter2',
+    })
+    const migrateDb = Database.postgres({
+      url: cfg.db.url,
+      schema: cfg.db.schema,
+    })
+    await migrateDb.migrateToLatestOrThrow()
+    await migrateDb.close()
+    linkService = await LinkService.create(cfg)
+    await linkService.start()
+    const {port} = linkService.server?.address() as AddressInfo
+    baseUrl = `http://localhost:${port}`
+  })
+  after(async () => {
+    await linkService?.destroy()
+  })
+  it('Wikipedia whitelisted, url restricted. Safelink is disabled, so redirect is always safe', async () => {
+    const urlToRedirect = 'https://en.wikipedia.org/wiki/Fight_Club'
+    const url = new URL(`${baseUrl}/redirect`)
+    url.searchParams.set('u', urlToRedirect)
+    const res = await fetch(url, {redirect: 'manual'})
+    assert.strictEqual(res.status, 200)
+    const html = await res.text()
+    assert.match(html, /meta http-equiv="refresh"/)
+    assert.match(
+      html,
+      new RegExp(urlToRedirect.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')),
+    )
+    // No blocked-site div, always safe
+    assert.doesNotMatch(html, /"blocked-site"/)
+  })
+
+  it('Unsafe redirect with block rule, but safelink is disabled so redirect is always safe', async () => {
+    const urlToRedirect =
+      'https://gist.github.com/MattIPv4/045239bc27b16b2bcf7a3a9a4648c08a'
+    const url = new URL(`${baseUrl}/redirect`)
+    url.searchParams.set('u', urlToRedirect)
+    const res = await fetch(url, {redirect: 'manual'})
+    assert.strictEqual(res.status, 200)
+    const html = await res.text()
+    assert.match(html, /meta http-equiv="refresh"/)
+    assert.match(
+      html,
+      new RegExp(urlToRedirect.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')),
+    )
+    // No blocked-site div, always safe
+    assert.doesNotMatch(html, /"blocked-site"/)
+  })
+
+  it('Rule adjustment, safe redirect, safelink is disabled so always safe', async () => {
+    const urlToRedirect = 'https://www.instagram.com/teamseshbones/?hl=en'
+    const url = new URL(`${baseUrl}/redirect`)
+    url.searchParams.set('u', urlToRedirect)
+    const res = await fetch(url, {redirect: 'manual'})
+    assert.strictEqual(res.status, 200)
+    const html = await res.text()
+    assert.match(html, /meta http-equiv="refresh"/)
+    assert.match(
+      html,
+      new RegExp(urlToRedirect.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')),
+    )
+    // No blocked-site div, always safe
+    assert.doesNotMatch(html, /"blocked-site"/)
+  })
+})