Skip to content
Closed
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
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ VERCEL_ORG_ID=
VERCEL_PROJECT_ID=

# ── Sentry (Error Tracking) ───────────────────────────────────────────────────
NEXT_PUBLIC_SENTRY_DSN=
SENTRY_DSN=
SENTRY_AUTH_TOKEN=
SENTRY_ORG=
Expand Down
56 changes: 56 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
time: "09:00"
timezone: "Africa/Lagos"
open-pull-requests-limit: 10
labels:
- "dependencies"
- "security"
assignees:
- "kellymusk"
commit-message:
prefix: "fix"
prefix-development: "chore"
include: "scope"
groups:
stellar:
patterns:
- "@stellar/*"
update-types:
- "minor"
- "patch"
react:
patterns:
- "react"
- "react-dom"
- "@types/react"
- "@types/react-dom"
update-types:
- "minor"
- "patch"
radix:
patterns:
- "@radix-ui/*"
update-types:
- "minor"
- "patch"
ignore:
- dependency-name: "@nuxt/kit"
- dependency-name: "next"
update-types: ["version-update:semver-major"]

- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
time: "09:00"
timezone: "Africa/Lagos"
labels:
- "docker"
- "security"
33 changes: 33 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,39 @@ jobs:
- name: TypeScript Check
run: npm run type-check

security:
name: Security Audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: npm audit
run: npm audit --audit-level=high
continue-on-error: true

- name: Snyk Security Scan
uses: snyk/actions/node@master
continue-on-error: true
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

- name: Run OWASP scan
run: bash scripts/owasp-scan.sh
continue-on-error: true

- name: Run wallet deps audit
run: bash scripts/audit-wallet-deps.sh
continue-on-error: true

test:
name: Tests & Coverage
runs-on: ubuntu-latest
Expand Down
13 changes: 13 additions & 0 deletions .snyk
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Snyk policy file
# Exclude specific paths from scanning
exclude:
global:
- "**/__tests__/**"
- "**/*.test.*"
- "**/*.spec.*"
- "coverage/**"
- ".next/**"
- "node_modules/**"

# Ignore specific vulnerabilities (with expiry)
patch: {}
16 changes: 14 additions & 2 deletions app/error.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
'use client'

import * as Sentry from '@sentry/nextjs'
import { useEffect } from 'react'
import ErrorLayout from '@/components/error/ErrorLayout'

export default function GlobalError({ reset }: { reset: () => void }) {
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
useEffect(() => {
Sentry.captureException(error)
}, [error])

return (
<ErrorLayout
status={500}
title="Something went wrong"
message="A server error occurred. Please try again."
message="A server error occurred. Our team has been notified."
actions={[
{ label: 'Retry', onClick: reset },
{ label: 'Home', href: '/' },
Expand Down
9 changes: 6 additions & 3 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { Metadata, Viewport } from 'next'
import { Analytics } from '@vercel/analytics/next'
import { ThemeProvider } from '@/components/theme-provider'
import { KycProvider } from '@/contexts/kyc-context'
import SentryErrorBoundary from '@/components/error/SentryErrorBoundary'
import './globals.css'

export const metadata: Metadata = {
Expand Down Expand Up @@ -50,9 +51,11 @@ export default function RootLayout({
<html lang="en" suppressHydrationWarning>
<body className="font-sans antialiased" suppressHydrationWarning>
<ThemeProvider attribute="class" defaultTheme="dark" enableSystem disableTransitionOnChange>
<KycProvider>
{children}
</KycProvider>
<SentryErrorBoundary>
<KycProvider>
{children}
</KycProvider>
</SentryErrorBoundary>
</ThemeProvider>
<Analytics />
</body>
Expand Down
36 changes: 36 additions & 0 deletions components/error/SentryErrorBoundary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
'use client'

import * as Sentry from '@sentry/nextjs'
import ErrorLayout from './ErrorLayout'

function Fallback({
error,
resetError,
}: {
error: unknown
componentStack: string
eventId: string
resetError(): void
}) {
Sentry.captureException(error)

return (
<ErrorLayout
status={500}
title="Something went wrong"
message="An unexpected error occurred. Our team has been notified."
actions={[
{ label: 'Try again', onClick: resetError },
{ label: 'Home', href: '/' },
]}
/>
)
}

type Props = {
children: React.ReactNode
}

export default function SentryErrorBoundary({ children }: Props) {
return <Sentry.ErrorBoundary fallback={Fallback}>{children}</Sentry.ErrorBoundary>
}
13 changes: 13 additions & 0 deletions instrumentation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as Sentry from '@sentry/nextjs'

export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
await import('./sentry.server.config')
}

if (process.env.NEXT_RUNTIME === 'edge') {
await import('./sentry.edge.config')
}
}

export const onRequestError = Sentry.captureRequestError
6 changes: 2 additions & 4 deletions next.config.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import withPWAInit from 'next-pwa'
import { withSentryConfig } from '@sentry/nextjs'

const withPWA = withPWAInit({
dest: 'public',
Expand All @@ -18,14 +19,11 @@ const nextConfig = {
ignoreBuildErrors: true,
},
images: {
// Allow optimized images for Lighthouse Best Practices score
unoptimized: false,
formats: ['image/avif', 'image/webp'],
minimumCacheTTL: 60,
},
// Enable standalone output for Docker deployments
output: 'standalone',
// Security headers — improves Best Practices score
async headers() {
return [
{
Expand All @@ -42,4 +40,4 @@ const nextConfig = {
},
}

export default withPWA(nextConfig)
export default withSentryConfig(withPWA(nextConfig))
Loading