Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
b8d6ace
chore: set changesets baseBranch to develop
drbarzaga Mar 14, 2026
4190377
chore: update CI workflow permissions for GitHub Actions (#23)
drbarzaga Mar 14, 2026
1e90bd1
chore: release @route-auditor/cli@0.1.1 (#24)
drbarzaga Mar 14, 2026
991f619
feat: add GitHub Action (composite) for route-auditor (#26)
drbarzaga Mar 14, 2026
c92e964
docs: fix README table formatting (#27)
drbarzaga Mar 14, 2026
e39a33f
Fix/shared bundled (#29)
drbarzaga Mar 14, 2026
f5fbb30
Merge branch 'main' into develop
drbarzaga Mar 14, 2026
f6c4444
fix: replace createRequire(import.meta.url) with build-time constant …
drbarzaga Mar 14, 2026
f09e919
chore: merge main into develop, resolve version conflicts
drbarzaga Mar 14, 2026
fe7c09b
Merge branch 'main' of github.com:ayaxsoft/route-auditor into develop
drbarzaga Mar 14, 2026
1096b84
ci: auto-sync develop with main after every merge
drbarzaga Mar 14, 2026
642085d
Merge remote-tracking branch 'origin/main' into develop
github-actions[bot] Mar 14, 2026
968af12
Merge remote-tracking branch 'origin/main' into develop
github-actions[bot] Mar 14, 2026
4b32f02
Merge remote-tracking branch 'origin/main' into develop
github-actions[bot] Mar 14, 2026
6003ce3
docs: update readme with ci steps (#38)
drbarzaga Mar 14, 2026
f1087ce
Merge remote-tracking branch 'origin/main' into develop
github-actions[bot] Mar 14, 2026
9ff2bc7
docs: update variable naming examples in AGENTS.md (#40)
drbarzaga Mar 14, 2026
cf80901
docs: update repository URL in README.md (#41)
drbarzaga Mar 15, 2026
2099970
fix: Agent rules compilance (#42)
drbarzaga Mar 15, 2026
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
5 changes: 5 additions & 0 deletions .changeset/real-bugs-smash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@route-auditor/cli': patch
---

Fix code style violations per AGENTS.md rules
3 changes: 1 addition & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
- If the code is a hack (like a setTimeout or potentially confusing code), it must be prefixed with // HACK: reason for hack
- MUST: Use kebab-case for files
- MUST: Use descriptive names for variables (avoid shorthands, or 1-2 character names).
- Example: for .map(), you can use `innerX` instead of `x`
- Example: instead of `moved` use `didPositionChange`
- Example: for .map(), you can use `index` instead of `x`
- MUST: Frequently re-evaluate and refactor variable names to be more accurate and descriptive.
- MUST: Do not type cast ("as") unless absolutely necessary
- MUST: Remove unused code and don't repeat yourself.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ route-auditor audit . --output sarif --file results.sarif
Want to contribute? Check out the codebase and submit a PR.

```bash
git clone https://github.com/drbarzaga/route-auditor
git clone https://github.com/ayaxsoft/route-auditor
cd route-auditor
pnpm install
pnpm build
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/__tests__/utils/is-nextjs-project.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ describe('isNextjsProject', () => {
})

it('returns true when next.config.js exists', async () => {
mockAccess.mockResolvedValueOnce(undefined)
mockAccess.mockResolvedValue(undefined)
expect(await isNextjsProject('/project')).toBe(true)
})

Expand Down
13 changes: 2 additions & 11 deletions packages/cli/src/analyzers/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,7 @@ import type {
import { scanRoutes } from './scanner'
import { detectStack } from './detector'
import { ALL_RULES } from '../rules'

const SEVERITY_ORDER: Severity[] = ['critical', 'high', 'medium', 'low', 'info']

const SEVERITY_PENALTY: Record<Severity, number> = {
critical: 25,
high: 15,
medium: 8,
low: 3,
info: 1,
}
import { SEVERITY_ORDER, SEVERITY_PENALTY } from '@route-auditor/shared'

const deriveRouterType = (routerTypes: Set<RouterType>): RouterType => {
if (routerTypes.has('app') && routerTypes.has('pages')) return 'mixed'
Expand Down Expand Up @@ -96,7 +87,7 @@ export const runAudit = async (
const detectedStack = detectStack(projectRoot)

const routerTypes = new Set(routes.map((innerRoute) => innerRoute.routerType))
const routerType = deriveRouterType(routerTypes as Set<RouterType>)
const routerType = deriveRouterType(routerTypes)

const minimumSeverity = config.severity ?? 'info'

Expand Down
5 changes: 2 additions & 3 deletions packages/cli/src/commands/audit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import { ALL_RULES } from '../rules'
import { renderHeader, renderConsoleReport } from '../reporters/console'
import { renderJsonReport } from '../reporters/json'
import { renderSarifReport } from '../reporters/sarif'

const SEVERITY_ORDER: Severity[] = ['critical', 'high', 'medium', 'low', 'info']
import { SEVERITY_ORDER } from '@route-auditor/shared'

const meetsFailThreshold = (severity: Severity, failOn: Severity): boolean =>
SEVERITY_ORDER.indexOf(severity) <= SEVERITY_ORDER.indexOf(failOn)
Expand Down Expand Up @@ -98,7 +97,7 @@ export const auditCommand = new Command('audit')
process.exit(1)
}

const output = config.output ?? 'console'
const output = config.output

let rendered: string | null = null
if (output === 'json') {
Expand Down
1 change: 0 additions & 1 deletion packages/cli/src/rules/open-redirect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ const USER_INPUT_REDIRECT_PATTERNS = [
]

const SAFE_REDIRECT_PATTERNS = [
"startsWith('/')",
"startsWith('/')",
'new URL(',
'isValidUrl',
Expand Down
28 changes: 2 additions & 26 deletions packages/cli/src/rules/unprotected-api-route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,7 @@ import type {
DetectedStack,
Fix,
} from '../types'

const AUTH_SIGNATURES: Record<NonNullable<DetectedStack['auth']>, string[]> = {
'next-auth': ['getServerSession', 'getToken', 'next-auth'],
'auth-js': ['getServerSession', 'getToken', '@auth/'],
clerk: ['auth()', 'currentUser()', '@clerk/', 'clerkClient'],
lucia: ['validateRequest', 'lucia'],
'better-auth': ['auth.api', 'better-auth', 'fromNodeHeaders'],
supabase: ['supabase.auth', '@supabase/', 'createClient'],
custom: ['auth', 'session', 'token', 'user'],
}
import { detectsAuth } from '../utils/detect-auth'

const GENERIC_AUTH_SIGNATURES = [
'Authorization',
Expand Down Expand Up @@ -73,26 +64,11 @@ const GENERIC_FIX: Fix = {
effort: 'low',
}

const hasAuthSignature = (rawContent: string, signatures: string[]): boolean =>
signatures.some((innerSignature) => rawContent.includes(innerSignature))

const buildFix = (detectedAuth: DetectedStack['auth']): Fix => {
if (detectedAuth) return AUTH_FIX[detectedAuth]
return GENERIC_FIX
}

const detectsAuth = (route: RouteFile, context: AuditContext): boolean => {
const { detectedStack } = context
const { rawContent } = route

if (detectedStack.auth) {
const signatures = AUTH_SIGNATURES[detectedStack.auth]
if (hasAuthSignature(rawContent, signatures)) return true
}

return hasAuthSignature(rawContent, GENERIC_AUTH_SIGNATURES)
}

export const unprotectedApiRoute: AuditRule = {
id: 'RW-AUTH-001',
name: 'Unprotected API Route',
Expand All @@ -102,7 +78,7 @@ export const unprotectedApiRoute: AuditRule = {
enabled: true,
check(route: RouteFile, context: AuditContext): Vulnerability[] {
if (!route.isApiRoute) return []
if (detectsAuth(route, context)) return []
if (detectsAuth(route, context, GENERIC_AUTH_SIGNATURES)) return []

return [
{
Expand Down
27 changes: 2 additions & 25 deletions packages/cli/src/rules/unprotected-sensitive-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type {
DetectedStack,
Fix,
} from '../types'
import { detectsAuth } from '../utils/detect-auth'

const SENSITIVE_PATH_SEGMENTS = [
'admin',
Expand All @@ -26,16 +27,6 @@ const SENSITIVE_PATH_SEGMENTS = [
'secure',
]

const AUTH_SIGNATURES: Record<NonNullable<DetectedStack['auth']>, string[]> = {
'next-auth': ['getServerSession', 'getToken', 'next-auth'],
'auth-js': ['getServerSession', 'getToken', '@auth/'],
clerk: ['auth()', 'currentUser()', '@clerk/', 'clerkClient', 'protect()'],
lucia: ['validateRequest', 'lucia'],
'better-auth': ['auth.api', 'better-auth', 'fromNodeHeaders'],
supabase: ['supabase.auth', '@supabase/', 'createClient'],
custom: ['auth', 'session', 'token', 'user'],
}

const GENERIC_AUTH_SIGNATURES = [
'getServerSession',
'requireAuth',
Expand Down Expand Up @@ -96,20 +87,6 @@ const GENERIC_FIX: Fix = {
const isSensitivePage = (routePath: string): boolean =>
SENSITIVE_PATH_SEGMENTS.some((innerSegment) => routePath.toLowerCase().includes(innerSegment))

const hasAuthSignature = (rawContent: string, signatures: string[]): boolean =>
signatures.some((innerSignature) => rawContent.includes(innerSignature))

const detectsAuth = (route: RouteFile, context: AuditContext): boolean => {
const { detectedStack } = context

if (detectedStack.auth) {
const signatures = AUTH_SIGNATURES[detectedStack.auth]
if (hasAuthSignature(route.rawContent, signatures)) return true
}

return hasAuthSignature(route.rawContent, GENERIC_AUTH_SIGNATURES)
}

const buildFix = (detectedAuth: DetectedStack['auth']): Fix =>
detectedAuth ? AUTH_FIX[detectedAuth] : GENERIC_FIX

Expand All @@ -124,7 +101,7 @@ export const unprotectedSensitivePage: AuditRule = {
if (route.isApiRoute) return []
if (route.routerType !== 'app') return []
if (!isSensitivePage(route.routePath)) return []
if (detectsAuth(route, context)) return []
if (detectsAuth(route, context, GENERIC_AUTH_SIGNATURES)) return []

return [
{
Expand Down
31 changes: 31 additions & 0 deletions packages/cli/src/utils/detect-auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { AuditContext, DetectedStack, RouteFile } from '../types'

const AUTH_SIGNATURES: Record<NonNullable<DetectedStack['auth']>, string[]> = {
'next-auth': ['getServerSession', 'getToken', 'next-auth'],
'auth-js': ['getServerSession', 'getToken', '@auth/'],
clerk: ['auth()', 'currentUser()', '@clerk/', 'clerkClient'],
lucia: ['validateRequest', 'lucia'],
'better-auth': ['auth.api', 'better-auth', 'fromNodeHeaders'],
supabase: ['supabase.auth', '@supabase/', 'createClient'],
custom: ['auth', 'session', 'token', 'user'],
}

const hasAuthSignature = (rawContent: string, signatures: string[]): boolean => {
return signatures.some((innerSignature) => rawContent.includes(innerSignature))
}

export const detectsAuth = (
route: RouteFile,
context: AuditContext,
signatures: string[],
): boolean => {
const { detectedStack } = context
const { rawContent } = route

if (detectedStack.auth) {
const signatures = AUTH_SIGNATURES[detectedStack.auth]
if (hasAuthSignature(rawContent, signatures)) return true
}

return hasAuthSignature(rawContent, signatures)
}
5 changes: 2 additions & 3 deletions packages/cli/src/utils/dir-exists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ import { access } from 'fs/promises'

export const dirExists = async (path: string): Promise<boolean> => {
try {
return access(path)
.then(() => true)
.catch(() => false)
await access(path)
return true
} catch {
return false
}
Expand Down
11 changes: 11 additions & 0 deletions packages/shared/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { Severity } from './types'

export const SEVERITY_ORDER: Severity[] = ['critical', 'high', 'medium', 'low', 'info']

export const SEVERITY_PENALTY: Record<Severity, number> = {
critical: 25,
high: 15,
medium: 8,
low: 3,
info: 1,
}
1 change: 1 addition & 0 deletions packages/shared/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './types'
export * from './constants'
Loading