COPPA §312.2 and §312.5 include precise and coarse geolocation — as well as IP-address-derived location used persistently — within the definition of 'personal information' requiring parental consent before collection from children under 13. GDPR Article 9 adds a special-category protection for location data tied to children in the EU. Calling navigator.geolocation.getCurrentPosition() during a child session, or running an IP-to-country lookup and storing the result on the child's user record, is unconsented personal information collection unless the parental consent notice disclosed geolocation collection specifically.
Low because geolocation is collected incidentally rather than as the primary data point, and the harm requires downstream misuse — but the §312.5 collection violation occurs regardless of whether the data is later weaponized.
Gate all geolocation collection — both browser Geolocation API and IP-to-location lookups — on account type before any data is collected or stored.
// lib/location.ts
export async function requestLocation(
user: { accountType: string }
): Promise<GeolocationPosition | null> {
// Never collect location from child accounts
if (user.accountType === 'child') return null
return new Promise((resolve, reject) => {
navigator.geolocation.getCurrentPosition(resolve, reject)
})
}
// app/api/middleware/location.ts
export function getLocationForSession(req: Request, accountType: string) {
if (accountType === 'child') return null
return geoipLookup(req.headers.get('x-forwarded-for') ?? '')
}
If geolocation is essential for a feature you want to offer child users, add explicit disclosure of geolocation collection to the parental consent notice and obtain consent before the feature activates.
ID: coppa-compliance.child-data.no-geolocation-without-consent
Severity: low
What to look for: Count all relevant instances and enumerate each. Search for geolocation collection in the codebase: navigator.geolocation.getCurrentPosition, navigator.geolocation.watchPosition, IP-to-geolocation lookups (using libraries like geoip-lite, maxmind, or external APIs like ip-api.com or ipstack). Check whether any of these run in contexts accessible to child accounts. Also check for coarse location collection: browser locale, timezone (Intl.DateTimeFormat().resolvedOptions().timeZone), or country inferred from IP — these may also count as personal information under COPPA's broad definition. Verify whether the parental consent notice (if it exists) discloses geolocation collection.
Pass criteria: Geolocation (precise or coarse) is not collected from child user sessions without it being disclosed in the parental consent notice and without parental consent being obtained. If geolocation is never collected from any user, this check passes trivially.
Fail criteria: Precise geolocation is requested from users via the browser Geolocation API without checking whether the session belongs to a child account. IP-to-location lookups run on all requests including those from child sessions, and the result is stored. Location data is collected from child sessions without disclosure in the parental consent request.
Skip (N/A) when: The application hard-blocks all users under 13 and no child sessions are possible. Or the application collects no geolocation data of any kind from any user.
Detail on fail: Example: "navigator.geolocation.getCurrentPosition() called on all users including those with child accounts. Location data stored in user profile." or "IP-to-country lookup runs on every page request and result stored on user record, including child user records, without disclosure in the parental consent notice.".
Remediation: Gate all geolocation collection on account type:
// lib/location.ts
export async function requestLocation(user: { accountType: string }): Promise<GeolocationPosition | null> {
// Do not collect location from child accounts
if (user.accountType === 'child') return null
return new Promise((resolve, reject) => {
navigator.geolocation.getCurrentPosition(resolve, reject)
})
}
// If IP-based location lookup runs server-side, skip for child sessions:
// app/api/middleware/location.ts
export function getLocationForSession(req: Request, accountType: string) {
if (accountType === 'child') return null // Never geolocate child sessions
return geoipLookup(req.headers.get('x-forwarded-for') ?? '')
}