Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 3 additions & 3 deletions config/kubernetes/production/deployments/webapp.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ spec:
image: docs-internal
resources:
requests:
cpu: 2000m
memory: 6Gi
limits:
cpu: 8000m
memory: 10Gi
limits:
cpu: 16000m
memory: 16Gi
ports:
- name: http
Expand Down
1 change: 1 addition & 0 deletions data/reusables/code-scanning/codeql-query-tables/java.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
| [Detect JHipster Generator Vulnerability CVE-2019-16303](https://codeql.github.com/codeql-query-help/java/java-jhipster-prng/) | 338 | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} |
| [Disabled Netty HTTP header validation](https://codeql.github.com/codeql-query-help/java/java-netty-http-request-or-response-splitting/) | 93, 113 | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} |
| [Disabled Spring CSRF protection](https://codeql.github.com/codeql-query-help/java/java-spring-disabled-csrf-protection/) | 352 | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} |
| [Exposed Spring Boot actuators](https://codeql.github.com/codeql-query-help/java/java-spring-boot-exposed-actuators/) | 200 | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} |
| [Expression language injection (JEXL)](https://codeql.github.com/codeql-query-help/java/java-jexl-expression-injection/) | 094 | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} |
| [Expression language injection (MVEL)](https://codeql.github.com/codeql-query-help/java/java-mvel-expression-injection/) | 094 | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} |
| [Expression language injection (Spring)](https://codeql.github.com/codeql-query-help/java/java-spel-expression-injection/) | 094 | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} |
Expand Down
16 changes: 14 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@
"html-entities": "^2.5.2",
"http-proxy-middleware": "3.0.3",
"imurmurhash": "^0.1.4",
"ipaddr.js": "^2.2.0",
"is-svg": "5.0.0",
"javascript-stringify": "^2.1.0",
"js-cookie": "^3.0.1",
Expand Down
5 changes: 4 additions & 1 deletion src/events/components/events.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* eslint-disable camelcase */
/* eslint-disable camelcase, no-unreachable */
import Cookies from 'src/frame/components/lib/cookies'
import { parseUserAgent } from './user-agent'
import { Router } from 'next/router'
Expand Down Expand Up @@ -76,6 +76,9 @@ export function sendEvent<T extends EventType>({
} & EventPropsByType[T]) {
if (isHeadless()) return

// Early return to disable event tracking
return

const body = {
type,

Expand Down
38 changes: 19 additions & 19 deletions src/shielding/lib/fastly-ips.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
// Logic to get and store the current list of public Fastly IPs from the Fastly API: https://www.fastly.com/documentation/reference/api/utils/public-ip-list/

import ipaddr, { IPv4, IPv6 } from 'ipaddr.js'

type IPRangeArr = [IPv4 | IPv6, number][]

// Default returned from ➜ curl "https://api.fastly.com/public-ip-list"
export const DEFAULT_FASTLY_IPS: string[] = [
export const DEFAULT_FASTLY_IPS: IPRangeArr = [
'23.235.32.0/20',
'43.249.72.0/22',
'103.244.50.0/24',
Expand All @@ -21,22 +25,21 @@ export const DEFAULT_FASTLY_IPS: string[] = [
'185.31.16.0/22',
'199.27.72.0/21',
'199.232.0.0/16',
]
].map((cidr) => ipaddr.parseCIDR(cidr))

let ipCache: string[] = []
let ipRangeCache: IPRangeArr = []

export async function getPublicFastlyIPs(): Promise<string[]> {
export async function getPublicFastlyIPs(): Promise<IPRangeArr> {
// Don't fetch the list in dev & testing, just use the defaults
if (process.env.NODE_ENV !== 'production') {
ipCache = DEFAULT_FASTLY_IPS
ipRangeCache = DEFAULT_FASTLY_IPS
}

if (ipCache.length) {
return ipCache
if (ipRangeCache.length) {
return ipRangeCache
}

const endpoint = 'https://api.fastly.com/public-ip-list'
let ips: string[] = []
let attempt = 0

while (attempt < 3) {
Expand All @@ -47,8 +50,8 @@ export async function getPublicFastlyIPs(): Promise<string[]> {
}
const data = await response.json()
if (data && Array.isArray(data.addresses)) {
ips = data.addresses
break
ipRangeCache = data.addresses.map((cidr: string) => ipaddr.parseCIDR(cidr))
return ipRangeCache
} else {
throw new Error('Invalid response structure')
}
Expand All @@ -57,25 +60,22 @@ export async function getPublicFastlyIPs(): Promise<string[]> {
`Failed to fetch Fastly IPs: ${error.message}. Retrying ${3 - attempt} more times`,
)
attempt++
if (attempt >= 3) {
ips = DEFAULT_FASTLY_IPS
}
}
}

ipCache = ips
return ips
ipRangeCache = DEFAULT_FASTLY_IPS
return ipRangeCache
}

// The IPs we check in the rate-limiter are in the form `X.X.X.X`
// But the IPs returned from the Fastly API are in the form `X.X.X.X/Y`
// For an IP in the rate-limiter, we want `X.X.X.*` to match `X.X.X.X/Y`
export async function isFastlyIP(ip: string): Promise<boolean> {
// If IPs aren't initialized, fetch them
if (!ipCache.length) {
if (!ipRangeCache.length) {
await getPublicFastlyIPs()
}
const parts = ip.split('.')
const prefix = parts.slice(0, 3).join('.')
return ipCache.some((fastlyIP) => fastlyIP.startsWith(prefix))
if (!ip) return false // localhost
const addr = ipaddr.parse(ip)
return ipRangeCache.some((range) => addr.match(range))
}
8 changes: 3 additions & 5 deletions src/shielding/tests/shielding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { describe, expect, test } from 'vitest'

import { SURROGATE_ENUMS } from '@/frame/middleware/set-fastly-surrogate-key.js'
import { get } from '@/tests/helpers/e2etest.js'
import { DEFAULT_FASTLY_IPS } from '@/shielding/lib/fastly-ips'

describe('honeypotting', () => {
test('any GET with survey-vote and survey-token query strings is 400', async () => {
Expand Down Expand Up @@ -105,7 +104,7 @@ describe('rate limiting', () => {
headers: {
// Rate limiting only happens in production, so we need to
// make the environment look like production.
'fastly-client-ip': 'abc',
'fastly-client-ip': '0.0.0.0',
},
})
expect(res.statusCode).toBe(200)
Expand All @@ -118,7 +117,7 @@ describe('rate limiting', () => {
{
const res = await get('/robots.txt?foo=buzz', {
headers: {
'fastly-client-ip': 'abc',
'fastly-client-ip': '0.0.0.0',
},
})
expect(res.statusCode).toBe(200)
Expand All @@ -142,8 +141,7 @@ describe('rate limiting', () => {
// Fastly IPs are in the form `X.X.X.X/Y`
// Rate limited IPs are in the form `X.X.X.X`
// Where the last X could be any 2-3 digit number
const mockFastlyIP =
DEFAULT_FASTLY_IPS[0].split('.').slice(0, 3).join('.') + `.${Math.floor(Math.random() * 100)}`
const mockFastlyIP = '23.235.32.0'
// Cookies only allows 1 request per minute
const res1 = await get('/api/cookies', {
headers: {
Expand Down
Loading