api-request-trust.spec.ts 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
  1. /** Behavior of the /api browser-trust fence (rebinding + cross-site defense). */
  2. import { describe, expect, it } from 'vitest'
  3. import { isTrustedApiRequest } from '../src/api-request-trust.ts'
  4. function request(headers: Record<string, string | undefined>): { headers: Record<string, string | undefined> } {
  5. return { headers }
  6. }
  7. describe('isTrustedApiRequest', () => {
  8. it('accepts loopback Hosts in every spelling, with and without ports', () => {
  9. for (const host of ['localhost', 'localhost:3080', '127.0.0.1', '127.0.0.1:3080', '127.8.9.10:80', '[::1]', '[::1]:3080', 'LOCALHOST:3080']) {
  10. expect(isTrustedApiRequest(request({ host }), [])).toBe(true)
  11. }
  12. })
  13. it('accepts non-browser requests (no Origin, no sec-fetch-site) — curl, tests, native clients', () => {
  14. expect(isTrustedApiRequest(request({ host: '127.0.0.1:3080' }), [])).toBe(true)
  15. })
  16. it('refuses a rebound Host: the attacker domain names the socket it did not expect', () => {
  17. expect(isTrustedApiRequest(request({
  18. host: 'evil.example:3080',
  19. origin: 'http://evil.example:3080',
  20. 'sec-fetch-site': 'same-origin',
  21. }), [])).toBe(false)
  22. })
  23. it('accepts a declared public authority only on exact host[:port] match', () => {
  24. const headers = { host: 'harness.internal:3080', origin: 'http://harness.internal:3080' }
  25. expect(isTrustedApiRequest(request(headers), ['harness.internal:3080'])).toBe(true)
  26. expect(isTrustedApiRequest(request(headers), ['harness.internal'])).toBe(false)
  27. expect(isTrustedApiRequest(request(headers), [])).toBe(false)
  28. })
  29. it('refuses cross-origin browser markers even on a loopback Host', () => {
  30. // Origin present and different → cross-site request that survived preflight rules.
  31. expect(isTrustedApiRequest(request({ host: '127.0.0.1:3080', origin: 'http://evil.example' }), [])).toBe(false)
  32. // Explicit cross-site label → refused regardless of Origin.
  33. expect(isTrustedApiRequest(request({ host: '127.0.0.1:3080', 'sec-fetch-site': 'cross-site' }), [])).toBe(false)
  34. // Opaque origin (sandboxed iframe, file: page) parses to no authority.
  35. expect(isTrustedApiRequest(request({ host: '127.0.0.1:3080', origin: 'null' }), [])).toBe(false)
  36. })
  37. it('accepts a same-origin browser request', () => {
  38. expect(isTrustedApiRequest(request({
  39. host: 'localhost:3080',
  40. origin: 'http://localhost:3080',
  41. 'sec-fetch-site': 'same-origin',
  42. }), [])).toBe(true)
  43. })
  44. it('refuses malformed authorities', () => {
  45. expect(isTrustedApiRequest(request({}), [])).toBe(false)
  46. expect(isTrustedApiRequest(request({ host: '' }), [])).toBe(false)
  47. expect(isTrustedApiRequest(request({ host: 'bad host' }), [])).toBe(false)
  48. expect(isTrustedApiRequest(request({ host: '127.0.0.999' }), [])).toBe(false)
  49. expect(isTrustedApiRequest(request({ host: '128.0.0.1' }), [])).toBe(false)
  50. })
  51. })