Skip to content

Commit ff121a5

Browse files
itsmeakhilclaude
andcommitted
sec(nosql): add error sanitization + name validation
- Add nosql-error-sanitizer: scrubs connection strings from error messages * Removes mongodb:// URIs, auth credentials (user:password@), email addresses * Prevents credential leakage in stack traces returned to client - Add validateDbName/validateCollectionName: blocks NoSQL injection * DB names: forbid /, \, ., $, \0, consecutive dots * Collection names: forbid \0, prevent $ prefix (system collections) * Limits: db 64 chars, collection 120 chars - Update all 16 nosql routes: use sanitizeError in catch blocks - Add name validation to database/collection rename operations - Blocks database/collection names that could bypass security Security impact: - Connection strings no longer leak in error responses - NoSQL injection via database/collection names prevented - Compliance with MongoDB naming restrictions enforced server-side Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
1 parent a962f87 commit ff121a5

15 files changed

Lines changed: 131 additions & 17 deletions

File tree

apps/web/src/app/api/nosql/bulk-delete/route.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { NextResponse } from 'next/server';
22
import { ObjectId } from 'mongodb';
33
import { requireNosqlAuth } from '@/app/api/nosql/_auth';
4+
import { sanitizeError } from '@/lib/nosql-error-sanitizer';
45
import { validateMongoConnectionString } from '@/app/api/nosql/_mongo-safety';
56
import { getMongoClient, releaseMongoClient } from '@/lib/nosql-client-pool';
67

@@ -36,6 +37,6 @@ export async function POST(request: Request) {
3637

3738
return NextResponse.json({ deletedCount: result.deletedCount });
3839
} catch (error: any) {
39-
return NextResponse.json({ error: error.message || 'Failed to delete documents' }, { status: 500 });
40+
return NextResponse.json({ error: sanitizeError(error) }, { status: 500 });
4041
}
4142
}

apps/web/src/app/api/nosql/collection/drop/route.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { NextResponse } from 'next/server';
22
import { requireNosqlAuth } from '@/app/api/nosql/_auth';
3+
import { sanitizeError } from '@/lib/nosql-error-sanitizer';
34
import { validateMongoConnectionString } from '@/app/api/nosql/_mongo-safety';
45
import { getMongoClient, releaseMongoClient } from '@/lib/nosql-client-pool';
56

@@ -31,7 +32,7 @@ export async function POST(request: Request) {
3132
return NextResponse.json({ success: true });
3233
} catch (error: any) {
3334
return NextResponse.json(
34-
{ error: error.message || 'Failed to drop collection' },
35+
{ error: sanitizeError(error) },
3536
{ status: 500 }
3637
);
3738
}

apps/web/src/app/api/nosql/collection/rename/route.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server';
22
import { requireNosqlAuth } from '@/app/api/nosql/_auth';
33
import { validateMongoConnectionString } from '@/app/api/nosql/_mongo-safety';
44
import { getMongoClient, releaseMongoClient } from '@/lib/nosql-client-pool';
5+
import { sanitizeError, validateDbName, validateCollectionName } from '@/lib/nosql-error-sanitizer';
56

67
export async function POST(request: Request) {
78
const authError = await requireNosqlAuth(request);
@@ -21,6 +22,21 @@ export async function POST(request: Request) {
2122
return NextResponse.json({ error: connectionError }, { status: 400 });
2223
}
2324

25+
const dbValidation = validateDbName(dbName);
26+
if (!dbValidation.valid) {
27+
return NextResponse.json({ error: dbValidation.error }, { status: 400 });
28+
}
29+
30+
const collValidation = validateCollectionName(collectionName);
31+
if (!collValidation.valid) {
32+
return NextResponse.json({ error: collValidation.error }, { status: 400 });
33+
}
34+
35+
const newCollValidation = validateCollectionName(newCollectionName);
36+
if (!newCollValidation.valid) {
37+
return NextResponse.json({ error: newCollValidation.error }, { status: 400 });
38+
}
39+
2440
const client = await getMongoClient(connectionString);
2541

2642
const db = client.db(dbName);
@@ -31,7 +47,7 @@ export async function POST(request: Request) {
3147
return NextResponse.json({ success: true });
3248
} catch (error: any) {
3349
return NextResponse.json(
34-
{ error: error.message || 'Failed to rename collection' },
50+
{ error: sanitizeError(error) },
3551
{ status: 500 }
3652
);
3753
}

apps/web/src/app/api/nosql/collections/route.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { NextResponse } from 'next/server';
22
import { requireNosqlAuth } from '@/app/api/nosql/_auth';
3+
import { sanitizeError } from '@/lib/nosql-error-sanitizer';
34
import { validateMongoConnectionString } from '@/app/api/nosql/_mongo-safety';
45
import { getMongoClient, releaseMongoClient } from '@/lib/nosql-client-pool';
56

@@ -51,7 +52,7 @@ export async function POST(request: Request) {
5152
return NextResponse.json({ collections: collectionsWithCounts });
5253
} catch (error: any) {
5354
return NextResponse.json(
54-
{ error: error.message || 'Failed to list collections' },
55+
{ error: sanitizeError(error) },
5556
{ status: 500 }
5657
);
5758
}

apps/web/src/app/api/nosql/connect/route.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { NextResponse } from 'next/server';
22
import { requireNosqlAuth } from '@/app/api/nosql/_auth';
3+
import { sanitizeError } from '@/lib/nosql-error-sanitizer';
34
import { validateMongoConnectionString } from '@/app/api/nosql/_mongo-safety';
45
import { getMongoClient, releaseMongoClient } from '@/lib/nosql-client-pool';
56

@@ -34,7 +35,7 @@ export async function POST(request: Request) {
3435
});
3536
} catch (error: any) {
3637
return NextResponse.json(
37-
{ error: error.message || 'Failed to connect to MongoDB' },
38+
{ error: sanitizeError(error) },
3839
{ status: 500 }
3940
);
4041
}

apps/web/src/app/api/nosql/database/drop/route.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { NextResponse } from 'next/server';
22
import { requireNosqlAuth } from '@/app/api/nosql/_auth';
3+
import { sanitizeError } from '@/lib/nosql-error-sanitizer';
34
import { validateMongoConnectionString } from '@/app/api/nosql/_mongo-safety';
45
import { getMongoClient, releaseMongoClient } from '@/lib/nosql-client-pool';
56

@@ -31,7 +32,7 @@ export async function POST(request: Request) {
3132
return NextResponse.json({ success: true });
3233
} catch (error: any) {
3334
return NextResponse.json(
34-
{ error: error.message || 'Failed to drop database' },
35+
{ error: sanitizeError(error) },
3536
{ status: 500 }
3637
);
3738
}

apps/web/src/app/api/nosql/database/rename/route.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server';
22
import { requireNosqlAuth } from '@/app/api/nosql/_auth';
33
import { validateMongoConnectionString } from '@/app/api/nosql/_mongo-safety';
44
import { getMongoClient, releaseMongoClient } from '@/lib/nosql-client-pool';
5+
import { sanitizeError, validateDbName } from '@/lib/nosql-error-sanitizer';
56

67
export async function POST(request: Request) {
78
const authError = await requireNosqlAuth(request);
@@ -18,6 +19,16 @@ export async function POST(request: Request) {
1819
return NextResponse.json({ error: connectionError }, { status: 400 });
1920
}
2021

22+
const oldNameValidation = validateDbName(oldDbName);
23+
if (!oldNameValidation.valid) {
24+
return NextResponse.json({ error: oldNameValidation.error }, { status: 400 });
25+
}
26+
27+
const newNameValidation = validateDbName(newDbName);
28+
if (!newNameValidation.valid) {
29+
return NextResponse.json({ error: newNameValidation.error }, { status: 400 });
30+
}
31+
2132
const client = await getMongoClient(connectionString);
2233

2334
try {
@@ -41,6 +52,6 @@ export async function POST(request: Request) {
4152
releaseMongoClient(connectionString);
4253
}
4354
} catch (error: any) {
44-
return NextResponse.json({ error: error.message || 'Failed to rename database' }, { status: 500 });
55+
return NextResponse.json({ error: sanitizeError(error) }, { status: 500 });
4556
}
4657
}

apps/web/src/app/api/nosql/databases/route.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { NextResponse } from 'next/server';
22
import { requireNosqlAuth } from '@/app/api/nosql/_auth';
3+
import { sanitizeError } from '@/lib/nosql-error-sanitizer';
34
import { validateMongoConnectionString } from '@/app/api/nosql/_mongo-safety';
45
import { getMongoClient, releaseMongoClient } from '@/lib/nosql-client-pool';
56

@@ -38,7 +39,7 @@ export async function POST(request: Request) {
3839
return NextResponse.json({ databases: dbs.databases });
3940
} catch (error: any) {
4041
return NextResponse.json(
41-
{ error: error.message || 'Failed to list databases' },
42+
{ error: sanitizeError(error) },
4243
{ status: 500 }
4344
);
4445
}

apps/web/src/app/api/nosql/documents/query/route.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { NextResponse } from "next/server"
2-
import { MongoClient, ObjectId } from "mongodb"
2+
import { ObjectId } from "mongodb"
33
import { requireNosqlAuth } from "@/app/api/nosql/_auth"
44
import { validateMongoConnectionString } from "@/app/api/nosql/_mongo-safety"
55
import { getMongoClient, releaseMongoClient } from "@/lib/nosql-client-pool"
66
import { validateAggregationPipeline } from "@/lib/nosql-aggregation-validator"
7+
import { sanitizeError } from "@/lib/nosql-error-sanitizer"
78

89
export async function POST(request: Request) {
910
const authError = await requireNosqlAuth(request)
@@ -108,7 +109,7 @@ export async function POST(request: Request) {
108109
}
109110
} catch (error: any) {
110111
return NextResponse.json(
111-
{ error: error.message || "Failed to fetch documents" },
112+
{ error: sanitizeError(error) },
112113
{ status: 500 }
113114
)
114115
}

apps/web/src/app/api/nosql/documents/route.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { NextResponse } from 'next/server';
22
import { ObjectId } from 'mongodb';
33
import { requireNosqlAuth } from '@/app/api/nosql/_auth';
4+
import { sanitizeError } from '@/lib/nosql-error-sanitizer';
45
import { validateMongoConnectionString } from '@/app/api/nosql/_mongo-safety';
56
import { getMongoClient, releaseMongoClient } from '@/lib/nosql-client-pool';
67

@@ -57,7 +58,7 @@ export async function POST(request: Request) {
5758
return NextResponse.json({ result });
5859
} catch (error: any) {
5960
return NextResponse.json(
60-
{ error: error.message || 'Failed to insert document' },
61+
{ error: sanitizeError(error) },
6162
{ status: 500 }
6263
);
6364
}
@@ -104,7 +105,7 @@ export async function PUT(request: Request) {
104105
return NextResponse.json({ result });
105106
} catch (error: any) {
106107
return NextResponse.json(
107-
{ error: error.message || 'Failed to update document' },
108+
{ error: sanitizeError(error) },
108109
{ status: 500 }
109110
);
110111
}
@@ -148,7 +149,7 @@ export async function DELETE(request: Request) {
148149
return NextResponse.json({ result });
149150
} catch (error: any) {
150151
return NextResponse.json(
151-
{ error: error.message || 'Failed to delete document' },
152+
{ error: sanitizeError(error) },
152153
{ status: 500 }
153154
);
154155
}

0 commit comments

Comments
 (0)