Fixes config.json exposure

This commit is contained in:
2025-07-11 09:22:05 -04:00
parent 370c29a6fc
commit c20b9e98f8
4 changed files with 66 additions and 3 deletions

2
public/.gitkeep Normal file
View File

@@ -0,0 +1,2 @@
# This file ensures the public directory is kept in git
# Public directory is for static assets that should be served to browsers

View File

@@ -190,7 +190,7 @@ export const Dashboard: React.FC<DashboardProps> = () => {
<span className="text-sm text-ctp-text hidden sm:inline">Settings</span>
</button>
<button
onClick={fetchWorkflowRuns}
onClick={() => fetchWorkflowRuns()}
disabled={loading}
className="flex items-center space-x-2 px-3 md:px-4 py-2 bg-ctp-blue text-ctp-base rounded-lg hover:bg-ctp-lavender disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>

View File

@@ -99,7 +99,11 @@ describe('ApiService', () => {
name: 'test-repo',
full_name: 'test-owner/test-repo'
}
]
],
cache: {
timeoutSeconds: 300
},
repositoryCount: 1
};
mockedAxios.get.mockResolvedValueOnce({ data: mockConfig });
@@ -108,6 +112,9 @@ describe('ApiService', () => {
expect(mockedAxios.get).toHaveBeenCalledWith('/api/config');
expect(result).toEqual(mockConfig);
// Ensure sensitive data is not included
expect(result).not.toHaveProperty('github.token');
expect(result).not.toHaveProperty('server');
});
});

View File

@@ -1,8 +1,53 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// Security plugin to block sensitive files
const securityPlugin = () => {
return {
name: 'security-plugin',
configureServer(server: any) {
server.middlewares.use((req: any, res: any, next: any) => {
const url = req.url?.toLowerCase() || '';
// Only block the most critical sensitive files
const blockedFiles = [
'/config.json',
'/config.example.json',
'/.env'
];
// Block specific directory traversal attempts
const blockedPaths = [
'/server/',
'/.git/'
];
// Check for exact file matches
const isBlockedFile = blockedFiles.includes(url);
// Check for blocked directory access
const isBlockedPath = blockedPaths.some(path => url.startsWith(path));
if (isBlockedFile || isBlockedPath) {
console.warn(`🚫 Blocked access to sensitive file: ${req.url} from ${req.headers['x-forwarded-for'] || req.socket.remoteAddress}`);
res.statusCode = 403;
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify({
error: 'Access denied',
message: 'This resource is not available'
}));
return;
}
next();
});
}
};
};
export default defineConfig({
plugins: [react()],
plugins: [react(), securityPlugin()],
publicDir: 'public',
server: {
host: '0.0.0.0',
port: 3000,
@@ -11,4 +56,13 @@ export default defineConfig({
'/api': 'http://localhost:3001'
}
},
build: {
rollupOptions: {
// These files should not be bundled into the build
external: (id) => {
// Only externalize if it's exactly these files
return id === 'config.json' || id === '.env' || id === 'config.example.json'
}
}
}
})