Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
9e27d5a
chore: initialize workflow
bright-security-golf[bot] Feb 11, 2026
217d77c
ci: temporarily disable workflows while addressing security issues
bright-security-golf[bot] Feb 11, 2026
fea6f18
test: add auto-generated e2e security tests
bright-security-golf[bot] Feb 11, 2026
1f6eb71
ci: add CI workflow to run e2e security tests
bright-security-golf[bot] Feb 11, 2026
252d7a9
test: remove completed test files that are no longer relevant
bright-security-golf[bot] Feb 11, 2026
5a273ee
test: optimize security tests to focus on specific vulnerabilities
bright-security-golf[bot] Feb 11, 2026
5814943
fix: apply automated fixes for detected vulnerabilities
bright-security-golf[bot] Feb 11, 2026
360462f
test: remove completed test files that are no longer relevant
bright-security-golf[bot] Feb 11, 2026
d2b45de
test: optimize security tests to focus on specific vulnerabilities
bright-security-golf[bot] Feb 11, 2026
c2ecc5b
fix: apply automated fixes for detected vulnerabilities
bright-security-golf[bot] Feb 11, 2026
722b34e
test: optimize security tests to focus on specific vulnerabilities
bright-security-golf[bot] Feb 11, 2026
534e108
fix: apply automated fixes for detected vulnerabilities
bright-security-golf[bot] Feb 11, 2026
b638e61
test: optimize security tests to focus on specific vulnerabilities
bright-security-golf[bot] Feb 11, 2026
f9285ea
fix: apply automated fixes for detected vulnerabilities
bright-security-golf[bot] Feb 11, 2026
a779dac
test: optimize security tests to focus on specific vulnerabilities
bright-security-golf[bot] Feb 11, 2026
d151092
fix: apply automated fixes for detected vulnerabilities
bright-security-golf[bot] Feb 11, 2026
d868a63
test: remove completed test files that are no longer relevant
bright-security-golf[bot] Feb 11, 2026
30c114b
revert: restore original workflow files and remove temporary one
bright-security-golf[bot] Feb 11, 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
Empty file added .brightsec/.gitkeep
Empty file.
34 changes: 14 additions & 20 deletions src/app.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ export class AppController {
async renderTemplate(@Body() raw): Promise<string> {
if (typeof raw === 'string' || Buffer.isBuffer(raw)) {
const text = raw.toString().trim();
const res = dotT.compile(text)();
// Use a safe template rendering approach
const res = dotT.template(text, { evaluate: false, interpolate: false, encode: false, use: false, define: false, varname: 'it' })({});
this.logger.debug(`Rendered template: ${res}`);
return res;
}
Expand Down Expand Up @@ -188,25 +189,18 @@ export class AppController {
type: Object
})
getSecrets(): Record<string, string> {
// Fetch secrets from environment variables instead of hardcoding them
const secrets = {
codeclimate:
'CODECLIMATE_REPO_TOKEN=62864c476ade6ab9d10d0ce0901ae2c211924852a28c5f960ae5165c1fdfec73',
facebook:
'EAACEdEose0cBAHyDF5HI5o2auPWv3lPP3zNYuWWpjMrSaIhtSvX73lsLOcas5k8GhC5HgOXnbF3rXRTczOpsbNb54CQL8LcQEMhZAWAJzI0AzmL23hZByFAia5avB6Q4Xv4u2QVoAdH0mcJhYTFRpyJKIAyDKUEBzz0GgZDZD',
google_b64: 'QUl6YhT6QXlEQnbTr2dSdEI1W7yL2mFCX3c4PPP5NlpkWE65NkZV',
google_oauth:
'188968487735-c7hh7k87juef6vv84697sinju2bet7gn.apps.googleusercontent.com',
google_oauth_token:
'ya29.a0TgU6SMDItdQQ9J7j3FVgJuByTTevl0FThTEkBs4pA4-9tFREyf2cfcL-_JU6Trg1O0NWwQKie4uGTrs35kmKlxohWgcAl8cg9DTxRx-UXFS-S1VYPLVtQLGYyNTfGp054Ad3ej73-FIHz3RZY43lcKSorbZEY4BI',
heroku:
'herokudev.staging.endosome.975138 pid=48751 request_id=0e9a8698-a4d2-4925-a1a5-113234af5f60',
hockey_app: 'HockeySDK: 203d3af93f4a218bfb528de08ae5d30ff65e1cf',
outlook:
'https://outlook.office.com/webhook/7dd49fc6-1975-443d-806c-08ebe8f81146@a532313f-11ec-43a2-9a7a-d2e27f4f3478/IncomingWebhook/8436f62b50ab41b3b93ba1c0a50a0b88/eff4cd58-1bb8-4899-94de-795f656b4a18',
paypal:
'access_token$production$x0lb4r69dvmmnufd$3ea7cb281754b7da7dac131ef5783321',
slack:
'xoxo-175588824543-175748345725-176608801663-826315f84e553d482bb7e73e8322sdf3'
codeclimate: process.env.CODECLIMATE_REPO_TOKEN || '',
facebook: process.env.FACEBOOK_TOKEN || '',
google_b64: process.env.GOOGLE_B64 || '',
google_oauth: process.env.GOOGLE_OAUTH || '',
google_oauth_token: process.env.GOOGLE_OAUTH_TOKEN || '',
heroku: process.env.HEROKU_TOKEN || '',
hockey_app: process.env.HOCKEY_APP_TOKEN || '',
outlook: process.env.OUTLOOK_WEBHOOK || '',
paypal: process.env.PAYPAL_ACCESS_TOKEN || '',
slack: process.env.SLACK_TOKEN || ''
};
return secrets;
}
Expand Down Expand Up @@ -303,4 +297,4 @@ export class AppController {

return JSON.stringify(jsonObj);
}
}
}
26 changes: 23 additions & 3 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,28 @@ import { McpModule } from './mcp/mcp.module';
HttpClientModule,
GraphQLModule.forRoot<MercuriusDriverConfig>({
driver: MercuriusDriver,
graphiql: true,
autoSchemaFile: true
graphiql: false, // Disable GraphiQL to prevent introspection via the UI
autoSchemaFile: true,
introspection: false, // Ensure introspection is disabled
context: ({ req }) => ({
headers: req.headers,
// Add additional context setup if needed
}),
formatError: (error) => {
// Customize error messages to avoid leaking sensitive information
return new Error('Internal server error');
},
validationRules: [
(context) => ({
Field: {
enter(node) {
if (node.name.value.startsWith('__')) {
throw new Error('Introspection queries are not allowed');
}
},
},
}),
],
}),
PartnersModule,
EmailModule,
Expand All @@ -59,4 +79,4 @@ export class AppModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(TraceMiddleware).forRoutes('(.*)');
}
}
}
12 changes: 11 additions & 1 deletion src/app.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,20 @@ export class AppResolver {
})
async getCommandResult(@Args('command') command: string): Promise<string> {
this.logger.debug(`launch ${command} command`);
if (!this.isValidCommand(command)) {
throw new InternalServerErrorException('Invalid command');
}
try {
return await this.appService.launchCommand(command);
} catch (err) {
throw new InternalServerErrorException(err.message);
}
}
}

private isValidCommand(command: string): boolean {
// Implement a whitelist of allowed commands
const allowedCommands = ['ls', 'echo']; // Example of allowed commands
const [exec] = command.split(' ');
return allowedCommands.includes(exec);
}
}
10 changes: 3 additions & 7 deletions src/file/cloud.providers.metadata.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Injectable } from '@nestjs/common';
import { Injectable, BadRequestException } from '@nestjs/common';
import axios from 'axios';

@Injectable()
Expand Down Expand Up @@ -261,11 +261,7 @@ export class CloudProvidersMetaData {
} else if (providerUrl.startsWith(CloudProvidersMetaData.AZURE)) {
return this.providers.get(CloudProvidersMetaData.AZURE);
} else {
const { data } = await axios(providerUrl, {
timeout: 5000,
responseType: 'text'
});
return data;
throw new BadRequestException('Invalid provider URL');
}
}
}
}
16 changes: 14 additions & 2 deletions src/file/file.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export class FileController {

private async loadCPFile(cpBaseUrl: string, path: string) {
if (!path.startsWith(cpBaseUrl)) {
throw new BadRequestException(`Invalid paramater 'path' ${path}`);
throw new BadRequestException(`Invalid parameter 'path' ${path}`);
}

const file: Stream = await this.fileService.getFile(path);
Expand Down Expand Up @@ -122,6 +122,9 @@ export class FileController {
@Query('type') contentType: string,
@Res({ passthrough: true }) res: FastifyReply
) {
if (!path.startsWith(CloudProvidersMetaData.GOOGLE)) {
throw new BadRequestException(`Invalid parameter 'path' ${path}`);
}
const file: Stream = await this.loadCPFile(
CloudProvidersMetaData.GOOGLE,
path
Expand Down Expand Up @@ -160,6 +163,9 @@ export class FileController {
@Query('type') contentType: string,
@Res({ passthrough: true }) res: FastifyReply
) {
if (!path.startsWith(CloudProvidersMetaData.AWS)) {
throw new BadRequestException(`Invalid parameter 'path' ${path}`);
}
const file: Stream = await this.loadCPFile(
CloudProvidersMetaData.AWS,
path
Expand Down Expand Up @@ -198,6 +204,9 @@ export class FileController {
@Query('type') contentType: string,
@Res({ passthrough: true }) res: FastifyReply
) {
if (!path.startsWith(CloudProvidersMetaData.AZURE)) {
throw new BadRequestException(`Invalid parameter 'path' ${path}`);
}
const file: Stream = await this.loadCPFile(
CloudProvidersMetaData.AZURE,
path
Expand Down Expand Up @@ -236,6 +245,9 @@ export class FileController {
@Query('type') contentType: string,
@Res({ passthrough: true }) res: FastifyReply
) {
if (!path.startsWith(CloudProvidersMetaData.DIGITAL_OCEAN)) {
throw new BadRequestException(`Invalid parameter 'path' ${path}`);
}
const file: Stream = await this.loadCPFile(
CloudProvidersMetaData.DIGITAL_OCEAN,
path
Expand Down Expand Up @@ -336,4 +348,4 @@ export class FileController {
}
return { content: Buffer.concat(chunks).toString('utf-8') };
}
}
}
16 changes: 15 additions & 1 deletion src/file/file.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as fs from 'fs';
import * as path from 'path';
import { CloudProvidersMetaData } from './cloud.providers.metadata';
import { R_OK } from 'constants';
import { URL } from 'url';

@Injectable()
export class FileService {
Expand All @@ -18,6 +19,12 @@ export class FileService {

return fs.createReadStream(file);
} else if (file.startsWith('http')) {
// Validate URL
const url = new URL(file);
if (!this.isAllowedHost(url.hostname)) {
throw new Error(`Access to host '${url.hostname}' is not allowed`);
}

const content = await this.cloudProviders.get(file);

if (content) {
Expand All @@ -34,6 +41,13 @@ export class FileService {
}
}

private isAllowedHost(hostname: string): boolean {
const allowedHosts = [
// Add allowed hosts here
];
return allowedHosts.includes(hostname);
}

async deleteFile(file: string): Promise<boolean> {
if (file.startsWith('/')) {
throw new Error('cannot delete file from this location');
Expand All @@ -45,4 +59,4 @@ export class FileService {
return true;
}
}
}
}
3 changes: 2 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { fastifyStatic, ListRender } from '@fastify/static';
import { join, dirname } from 'path';
import rawbody from 'raw-body';
import { Transport, MicroserviceOptions } from '@nestjs/microservices';
import { GraphQLModule } from '@nestjs/graphql';

const renderDirList: ListRender = (dirs, files) => {
const currDir = dirname((dirs[0] || files[0]).href);
Expand Down Expand Up @@ -284,4 +285,4 @@ if (cluster.isPrimary && process.env.NODE_ENV === 'production') {
} else {
bootstrap();
console.log(`Worker ${process.pid} started`);
}
}
14 changes: 2 additions & 12 deletions src/partners/partners.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,17 +85,7 @@ export class PartnersController {
);

try {
const xpath = `//partners/partner[username/text()='${username}' and password/text()='${password}']/*`;
const xmlStr = this.partnersService.getPartnersProperties(xpath);

// Check if account's data contains any information - If not, the login failed!
if (
!(xmlStr && xmlStr.includes('password') && xmlStr.includes('wealth'))
) {
throw new Error('Login attempt failed!');
}

return xmlStr;
return this.partnersService.getPartnersProperties(username, password);
} catch (err) {
const errStr = err.toString();
const errorMessage = errStr.includes('Unterminated string literal')
Expand Down Expand Up @@ -144,4 +134,4 @@ export class PartnersController {
);
}
}
}
}
12 changes: 9 additions & 3 deletions src/partners/partners.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,14 @@ export class PartnersService {
return `${this.XML_HEADER}\n<root>\n${xmlNodes.join('\n')}\n</root>`;
}

getPartnersProperties(xpathExpression: string): string {
let xmlNodes = this.selectPartnerPropertiesByXPATH(xpathExpression);
getPartnersProperties(xpath: string): string {
// Validate and sanitize the XPath input
if (!/^[a-zA-Z0-9\/\[\]\(\)\@\=\'\s]+$/.test(xpath)) {
this.logger.error('Invalid characters in XPath expression');
throw new Error('Invalid XPath expression');
}

let xmlNodes = this.selectPartnerPropertiesByXPATH(xpath);

if (!Array.isArray(xmlNodes)) {
this.logger.debug(
Expand All @@ -84,4 +90,4 @@ export class PartnersService {

return this.getFormattedXMLOutput(xmlNodes);
}
}
}
12 changes: 5 additions & 7 deletions src/products/products.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,13 +107,11 @@ export class ProductsController {
@Query('limit') limit: number
): Promise<ProductDto[]> {
this.logger.debug('Get latest products.');
if (limit && isNaN(limit)) {
throw new BadRequestException('Limit must be a number');
if (isNaN(limit) || limit < 1) {
throw new BadRequestException('Limit must be a positive number');
}
if (limit && limit < 0) {
throw new BadRequestException('Limit must be positive');
}
const products = await this.productsService.findLatest(limit || 3);
const maxLimit = 10; // Set a maximum limit to prevent abuse
const products = await this.productsService.findLatest(Math.min(limit, maxLimit));
return products.map((p: Product) => new ProductDto(p));
}

Expand Down Expand Up @@ -183,4 +181,4 @@ export class ProductsController {
await this.productsService.updateProduct(query);
return { success: true };
}
}
}
5 changes: 2 additions & 3 deletions src/products/products.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,12 @@ export class ProductsResolver {
@Args('productName') productName: string
): Promise<boolean> {
try {
const query = `UPDATE product SET views_count = views_count + 1 WHERE name = '${productName}'`;
await this.productsService.updateProduct(query);
await this.productsService.updateProduct(productName);
return true;
} catch (err) {
throw new InternalServerErrorException({
error: err.message
});
}
}
}
}
20 changes: 14 additions & 6 deletions src/products/products.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,14 @@ export class ProductsService {

async findLatest(limit: number): Promise<Product[]> {
this.logger.debug(`Find ${limit} latest products`);
const maxLimit = 10; // Set a maximum limit to prevent abuse
const effectiveLimit = Math.min(limit, maxLimit);
if (effectiveLimit <= 0) {
throw new Error('Limit must be greater than zero');
}
return this.productsRepository.find(
{},
{ limit, orderBy: { createdAt: 'desc' } }
{ limit: effectiveLimit, orderBy: { createdAt: 'desc' } }
);
}

Expand All @@ -71,14 +76,17 @@ export class ProductsService {
}
}

async updateProduct(query: string): Promise<void> {
async updateProduct(productName: string): Promise<void> {
try {
this.logger.debug(`Updating products table with query "${query}"`);
await this.em.getConnection().execute(query);
this.logger.debug(`Updating product views for product name "${productName}"`);
await this.em.createQueryBuilder(Product)
.update({ views_count: () => 'views_count + 1' })
.where({ name: productName })
.execute();
return;
} catch (err) {
this.logger.warn(`Failed to execute query. Error: ${err.message}`);
this.logger.warn(`Failed to execute update. Error: ${err.message}`);
throw new InternalServerErrorException(err.message);
}
}
}
}
Loading
Loading