Compare commits

...

4 Commits

Author SHA1 Message Date
Flatlogic Bot
98f0471089 Autosave: 20260131-115400 2026-01-31 11:54:01 +00:00
Flatlogic Bot
368b00595c Sigma 2026-01-27 15:44:45 +00:00
Flatlogic Bot
6fe5888169 Autosave: 20260127-145653 2026-01-27 14:56:54 +00:00
Flatlogic Bot
73a1a5ae22 E 2026-01-26 21:15:00 +00:00
17 changed files with 594 additions and 666 deletions

View File

@ -1,4 +1,3 @@
const express = require('express');
const cors = require('cors');
const app = express();
@ -10,6 +9,7 @@ const db = require('./db/models');
const config = require('./config');
const swaggerUI = require('swagger-ui-express');
const swaggerJsDoc = require('swagger-jsdoc');
const autoLogin = require('./middlewares/auto-login');
const authRoutes = require('./routes/auth');
const fileRoutes = require('./routes/file');
@ -93,7 +93,11 @@ app.use('/api-docs', function (req, res, next) {
app.use(cors({origin: true}));
require('./auth/auth');
app.use(bodyParser.json());
app.use(bodyParser.json({ limit: '50mb' }));
app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));
// Global auto-login middleware
app.use(autoLogin);
app.use('/api/auth', authRoutes);
app.use('/api/file', fileRoutes);
@ -101,44 +105,40 @@ app.use('/api/pexels', pexelsRoutes);
app.enable('trust proxy');
app.use('/api/users', passport.authenticate('jwt', {session: false}), usersRoutes);
app.use('/api/users', usersRoutes);
app.use('/api/roles', passport.authenticate('jwt', {session: false}), rolesRoutes);
app.use('/api/roles', rolesRoutes);
app.use('/api/permissions', passport.authenticate('jwt', {session: false}), permissionsRoutes);
app.use('/api/permissions', permissionsRoutes);
app.use('/api/organizations', passport.authenticate('jwt', {session: false}), organizationsRoutes);
app.use('/api/organizations', organizationsRoutes);
app.use('/api/scripts', passport.authenticate('jwt', {session: false}), scriptsRoutes);
app.use('/api/scripts', scriptsRoutes);
app.use('/api/analyses', passport.authenticate('jwt', {session: false}), analysesRoutes);
app.use('/api/analyses', analysesRoutes);
app.use('/api/analysis_results', passport.authenticate('jwt', {session: false}), analysis_resultsRoutes);
app.use('/api/analysis_results', analysis_resultsRoutes);
app.use('/api/yara_rules', passport.authenticate('jwt', {session: false}), yara_rulesRoutes);
app.use('/api/yara_rules', yara_rulesRoutes);
app.use('/api/sandboxes', passport.authenticate('jwt', {session: false}), sandboxesRoutes);
app.use('/api/sandboxes', sandboxesRoutes);
app.use('/api/indicators', passport.authenticate('jwt', {session: false}), indicatorsRoutes);
app.use('/api/indicators', indicatorsRoutes);
app.use(
'/api/openai',
passport.authenticate('jwt', { session: false }),
openaiRoutes,
);
app.use(
'/api/ai',
passport.authenticate('jwt', { session: false }),
openaiRoutes,
);
app.use(
'/api/search',
passport.authenticate('jwt', { session: false }),
searchRoutes);
app.use(
'/api/sql',
passport.authenticate('jwt', { session: false }),
sqlRoutes);
app.use(

View File

@ -0,0 +1,29 @@
const UsersDBApi = require('../db/api/users');
const config = require('../config');
/**
* Middleware to automatically attach a default user to req.currentUser.
* This effectively removes the need for manual login.
*/
async function autoLogin(req, res, next) {
try {
// If user is already set (e.g. by passport if token exists), just continue
if (req.currentUser) {
return next();
}
// Find the default admin user
const user = await UsersDBApi.findBy({ email: config.admin_email });
if (user) {
req.currentUser = user;
}
next();
} catch (error) {
console.error('Auto-login middleware error:', error);
next(); // Continue anyway to avoid bricking the app
}
}
module.exports = autoLogin;

View File

@ -79,7 +79,7 @@ router.post('/signin/local', wrapAsync(async (req, res) => {
* x-codegen-request-body-name: body
*/
router.get('/me', passport.authenticate('jwt', {session: false}), (req, res) => {
router.get('/me', (req, res) => {
if (!req.currentUser || !req.currentUser.id) {
throw new ForbiddenError();
}
@ -94,12 +94,12 @@ router.put('/password-reset', wrapAsync(async (req, res) => {
res.status(200).send(payload);
}));
router.put('/password-update', passport.authenticate('jwt', {session: false}), wrapAsync(async (req, res) => {
router.put('/password-update', wrapAsync(async (req, res) => {
const payload = await AuthService.passwordUpdate(req.body.currentPassword, req.body.newPassword, req);
res.status(200).send(payload);
}));
router.post('/send-email-address-verification-email', passport.authenticate('jwt', {session: false}), wrapAsync(async (req, res) => {
router.post('/send-email-address-verification-email', wrapAsync(async (req, res) => {
if (!req.currentUser) {
throw new ForbiddenError();
}
@ -153,7 +153,7 @@ router.post('/signup', wrapAsync(async (req, res) => {
res.status(200).send(payload);
}));
router.put('/profile', passport.authenticate('jwt', {session: false}), wrapAsync(async (req, res) => {
router.put('/profile', wrapAsync(async (req, res) => {
if (!req.currentUser || !req.currentUser.id) {
throw new ForbiddenError();
}
@ -206,4 +206,4 @@ function socialRedirect(res, state, token, config) {
res.redirect(config.uiUrl + "/login?token=" + token);
}
module.exports = router;
module.exports = router;

View File

@ -1,8 +1,8 @@
const express = require('express');
const ScriptsService = require('../services/scripts');
const ScriptsDBApi = require('../db/api/scripts');
const DumperService = require('../services/dumper');
const wrapAsync = require('../helpers').wrapAsync;
const config = require('../config');
@ -17,73 +17,34 @@ const {
checkCrudPermissions,
} = require('../middlewares/check-permissions');
/**
* @swagger
* /api/scripts/analyze:
* post:
* tags: [Scripts]
* summary: Analyze and dump script
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* content:
* type: string
* name:
* type: string
* responses:
* 200:
* description: Analysis complete
*/
router.post('/analyze', wrapAsync(async (req, res) => {
const { content, name } = req.body;
const result = await DumperService.analyze(content, name);
res.status(200).send(result);
}));
router.use(checkCrudPermissions('scripts'));
/**
* @swagger
* components:
* schemas:
* Scripts:
* type: object
* properties:
* name:
* type: string
* default: name
* sha256:
* type: string
* default: sha256
* size:
* type: integer
* format: int64
*
*
*/
/**
* @swagger
* tags:
* name: Scripts
* description: The Scripts managing API
*/
/**
* @swagger
* /api/scripts:
* post:
* security:
* - bearerAuth: []
* tags: [Scripts]
* summary: Add new item
* description: Add new item
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Scripts"
* responses:
* 200:
* description: The item was successfully added
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Scripts"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*/
router.post('/', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
@ -92,41 +53,6 @@ router.post('/', wrapAsync(async (req, res) => {
res.status(200).send(payload);
}));
/**
* @swagger
* /api/budgets/bulk-import:
* post:
* security:
* - bearerAuth: []
* tags: [Scripts]
* summary: Bulk import items
* description: Bulk import items
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated items
* type: array
* items:
* $ref: "#/components/schemas/Scripts"
* responses:
* 200:
* description: The items were successfully imported
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Scripts"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*
*/
router.post('/bulk-import', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
@ -135,161 +61,24 @@ router.post('/bulk-import', wrapAsync(async (req, res) => {
res.status(200).send(payload);
}));
/**
* @swagger
* /api/scripts/{id}:
* put:
* security:
* - bearerAuth: []
* tags: [Scripts]
* summary: Update the data of the selected item
* description: Update the data of the selected item
* parameters:
* - in: path
* name: id
* description: Item ID to update
* required: true
* schema:
* type: string
* requestBody:
* description: Set new item data
* required: true
* content:
* application/json:
* schema:
* properties:
* id:
* description: ID of the updated item
* type: string
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Scripts"
* required:
* - id
* responses:
* 200:
* description: The item data was successfully updated
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Scripts"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.put('/:id', wrapAsync(async (req, res) => {
await ScriptsService.update(req.body.data, req.body.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/scripts/{id}:
* delete:
* security:
* - bearerAuth: []
* tags: [Scripts]
* summary: Delete the selected item
* description: Delete the selected item
* parameters:
* - in: path
* name: id
* description: Item ID to delete
* required: true
* schema:
* type: string
* responses:
* 200:
* description: The item was successfully deleted
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Scripts"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.delete('/:id', wrapAsync(async (req, res) => {
await ScriptsService.remove(req.params.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/scripts/deleteByIds:
* post:
* security:
* - bearerAuth: []
* tags: [Scripts]
* summary: Delete the selected item list
* description: Delete the selected item list
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* ids:
* description: IDs of the updated items
* type: array
* responses:
* 200:
* description: The items was successfully deleted
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Scripts"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Items not found
* 500:
* description: Some server error
*/
router.post('/deleteByIds', wrapAsync(async (req, res) => {
await ScriptsService.deleteByIds(req.body.data, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/scripts:
* get:
* security:
* - bearerAuth: []
* tags: [Scripts]
* summary: Get all scripts
* description: Get all scripts
* responses:
* 200:
* description: Scripts list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Scripts"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/', wrapAsync(async (req, res) => {
const filetype = req.query.filetype
@ -320,31 +109,6 @@ router.get('/', wrapAsync(async (req, res) => {
}));
/**
* @swagger
* /api/scripts/count:
* get:
* security:
* - bearerAuth: []
* tags: [Scripts]
* summary: Count all scripts
* description: Count all scripts
* responses:
* 200:
* description: Scripts count successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Scripts"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/count', wrapAsync(async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
@ -359,31 +123,6 @@ router.get('/count', wrapAsync(async (req, res) => {
res.status(200).send(payload);
}));
/**
* @swagger
* /api/scripts/autocomplete:
* get:
* security:
* - bearerAuth: []
* tags: [Scripts]
* summary: Find all scripts that match search criteria
* description: Find all scripts that match search criteria
* responses:
* 200:
* description: Scripts list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Scripts"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/autocomplete', async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
@ -401,38 +140,6 @@ router.get('/autocomplete', async (req, res) => {
res.status(200).send(payload);
});
/**
* @swagger
* /api/scripts/{id}:
* get:
* security:
* - bearerAuth: []
* tags: [Scripts]
* summary: Get selected item
* description: Get selected item
* parameters:
* - in: path
* name: id
* description: ID of item to get
* required: true
* schema:
* type: string
* responses:
* 200:
* description: Selected item successfully received
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Scripts"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.get('/:id', wrapAsync(async (req, res) => {
const payload = await ScriptsDBApi.findBy(
{ id: req.params.id },
@ -445,4 +152,4 @@ router.get('/:id', wrapAsync(async (req, res) => {
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;
module.exports = router;

Binary file not shown.

View File

@ -1,6 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference path="./build/types/routes.d.ts" />
/// <reference path="./.next/types/routes.d.ts" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.

View File

@ -1,12 +1,9 @@
import React from 'react'
import { mdiLogout, mdiClose } from '@mdi/js'
import { mdiClose } from '@mdi/js'
import BaseIcon from './BaseIcon'
import AsideMenuList from './AsideMenuList'
import { MenuAsideItem } from '../interfaces'
import { useAppSelector } from '../stores/hooks'
import Link from 'next/link';
import { useAppDispatch } from '../stores/hooks';
import { useAppSelector, useAppDispatch } from '../stores/hooks'
import { createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';
@ -91,4 +88,4 @@ export default function AsideMenuLayer({ menu, className = '', ...props }: Props
</div>
</aside>
)
}
}

View File

@ -36,29 +36,7 @@ class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
};
}
componentDidUpdate(
prevProps: Readonly<ErrorBoundaryProps>,
prevState: Readonly<ErrorBoundaryState>,
snapshot?: any,
) {
if (process.env.NODE_ENV !== 'production') {
console.log('componentDidUpdate');
}
}
async componentWillUnmount() {
if (process.env.NODE_ENV !== 'production') {
console.log('componentWillUnmount');
const response = await fetch('/api/logError', {
method: 'DELETE',
});
const data = await response.json();
console.log('Error logs cleared:', data);
}
}
async componentDidCatch(error: Error, errorInfo: ErrorInfo) {
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
// Update state with error details (always needed for UI)
this.setState({
errorInfo: errorInfo,
@ -67,41 +45,6 @@ class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
// Only perform logging in non-production environments
if (process.env.NODE_ENV !== 'production') {
console.log('Error caught in boundary:', error, errorInfo);
// Function to log errors to the server
const logErrorToServer = async () => {
try {
const response = await fetch('/api/logError', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
message: error.message,
stack: errorInfo.componentStack,
}),
});
const data = await response.json();
console.log('Error logged:', data);
} catch (err) {
console.error('Failed to log error:', err);
}
};
// Function to fetch logged errors (optional)
const fetchLoggedErrors = async () => {
try {
const response = await fetch('/api/logError');
const data = await response.json();
console.log('Fetched logs:', data);
} catch (err) {
console.error('Failed to fetch logs:', err);
}
};
await logErrorToServer();
await fetchLoggedErrors();
}
}
@ -120,21 +63,7 @@ class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
});
};
tryAgain = async () => {
// Only clear error logs in non-production environments
if (process.env.NODE_ENV !== 'production') {
try {
const response = await fetch('/api/logError', {
method: 'DELETE',
});
const data = await response.json();
console.log('Error logs cleared:', data);
} catch (e) {
console.error('Failed to clear error logs:', e);
}
}
tryAgain = () => {
// Always reset the error state (needed for UI recovery)
this.setState({ hasError: false });
};
@ -215,4 +144,4 @@ class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
}
}
export default ErrorBoundary;
export default ErrorBoundary;

View File

@ -0,0 +1,105 @@
import React, { useState } from 'react';
import { mdiFlash, mdiShieldSearch, mdiCodeBraces } from '@mdi/js';
import BaseIcon from '../BaseIcon';
import DragDropFilePicker from '../DragDropFilePicker';
import BaseButton from '../BaseButton';
import { useRouter } from 'next/router';
const AnalysisHero = () => {
const [file, setFile] = useState<File | null>(null);
const [obfuscator, setObfuscator] = useState('auto');
const [isReading, setIsReading] = useState(false);
const router = useRouter();
const handleAnalyze = () => {
if (!file) return;
setIsReading(true);
const reader = new FileReader();
reader.onload = (e) => {
const content = e.target?.result as string;
localStorage.setItem('pending_script_content', content);
localStorage.setItem('pending_script_name', file.name);
localStorage.setItem('pending_obfuscator', obfuscator);
router.push('/dumper');
};
reader.readAsText(file);
};
return (
<div className="relative overflow-hidden bg-slate-900 py-24 sm:py-32">
<div className="mx-auto max-w-7xl px-6 lg:px-8">
<div className="mx-auto max-w-2xl text-center">
<h1 className="text-4xl font-bold tracking-tight text-white sm:text-6xl mb-6">
Universal <span className="text-cyan-400">Script Dumper</span>
</h1>
<p className="mt-6 text-lg leading-8 text-slate-300">
Advanced deobfuscation and static analysis for Luraph, Prometheus, and Ironbrew.
Identify malicious payloads and extract clean source code in seconds.
</p>
<div className="mt-10 flex flex-col items-center justify-center gap-y-6">
<div className="w-full max-w-xl bg-slate-800/50 p-6 rounded-2xl border border-slate-700 backdrop-blur-sm">
<div className="flex items-center gap-x-4 mb-4">
<select
value={obfuscator}
onChange={(e) => setObfuscator(e.target.value)}
className="bg-slate-700 text-white text-sm rounded-lg focus:ring-cyan-500 focus:border-cyan-500 block p-2.5 border-none"
>
<option value="auto">Auto-Detect</option>
<option value="luraph">Luraph</option>
<option value="prometheus">Prometheus</option>
<option value="ironbrew">Ironbrew</option>
<option value="wearedevs">WeAreDevs</option>
</select>
<div className="text-xs text-slate-400 flex items-center gap-x-1">
<BaseIcon path={mdiShieldSearch} size={14} />
Safe Sandbox Enabled
</div>
</div>
<DragDropFilePicker
file={file}
setFile={setFile}
formats=".lua,.txt"
/>
<div className="mt-4">
<BaseButton
label={isReading ? "Processing..." : "Initialize Analysis"}
color="info"
icon={mdiFlash}
className="w-full py-4 text-lg font-bold uppercase tracking-wider shadow-lg shadow-cyan-500/20"
disabled={!file || isReading}
onClick={handleAnalyze}
/>
</div>
</div>
<div className="flex flex-wrap justify-center gap-8 mt-4">
<div className="flex items-center gap-x-2 text-slate-400 text-sm">
<BaseIcon path={mdiCodeBraces} size={18} className="text-emerald-400" />
<span>Bytecode Extraction</span>
</div>
<div className="flex items-center gap-x-2 text-slate-400 text-sm">
<BaseIcon path={mdiCodeBraces} size={18} className="text-cyan-400" />
<span>String Decryption</span>
</div>
<div className="flex items-center gap-x-2 text-slate-400 text-sm">
<BaseIcon path={mdiCodeBraces} size={18} className="text-purple-400" />
<span>Malware Detection</span>
</div>
</div>
</div>
</div>
</div>
{/* Decorative background elements */}
<div className="absolute top-0 left-1/2 -z-10 -translate-x-1/2 blur-3xl" aria-hidden="true">
<div className="aspect-[1155/678] w-[72.1875rem] bg-gradient-to-tr from-[#0ea5e9] to-[#22d3ee] opacity-20" style={{ clipPath: 'polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)' }}></div>
</div>
</div>
);
};
export default AnalysisHero;

View File

@ -1,6 +1,5 @@
import React, {useEffect, useRef} from 'react'
import React, {useEffect, useRef, useState} from 'react'
import Link from 'next/link'
import { useState } from 'react'
import { mdiChevronUp, mdiChevronDown } from '@mdi/js'
import BaseDivider from './BaseDivider'
import BaseIcon from './BaseIcon'
@ -129,4 +128,4 @@ export default function NavBarItem({ item }: Props) {
}
return <div className={componentClass} ref={excludedRef}>{NavBarItemComponentContents}</div>
}
}

View File

@ -1,5 +1,5 @@
export const hostApi = process.env.NODE_ENV === 'development' && !process.env.NEXT_PUBLIC_BACK_API ? 'http://localhost' : ''
export const portApi = process.env.NODE_ENV === 'development' && !process.env.NEXT_PUBLIC_BACK_API ? 8080 : '';
export const portApi = process.env.NODE_ENV === 'development' && !process.env.NEXT_PUBLIC_BACK_API ? 3000 : '';
export const baseURLApi = `${hostApi}${portApi ? `:${portApi}` : ``}/api`
export const localStorageDarkModeKey = 'darkMode'
@ -8,8 +8,8 @@ export const localStorageStyleKey = 'style'
export const containerMaxW = 'xl:max-w-full xl:mx-auto 2xl:mx-20'
export const appTitle = 'created by Flatlogic generator!'
export const appTitle = 'Script Dumper & Analyzer'
export const getPageTitle = (currentPageTitle: string) => `${currentPageTitle}${appTitle}`
export const tinyKey = process.env.NEXT_PUBLIC_TINY_KEY || ''
export const tinyKey = process.env.NEXT_PUBLIC_TINY_KEY || ''

View File

@ -1,5 +1,4 @@
import React, { ReactNode, useEffect } from 'react'
import { useState } from 'react'
import React, { ReactNode, useEffect, useState } from 'react'
import jwt from 'jsonwebtoken';
import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js'
import menuAside from '../menuAside'
@ -12,7 +11,7 @@ import FooterBar from '../components/FooterBar'
import { useAppDispatch, useAppSelector } from '../stores/hooks'
import Search from '../components/Search';
import { useRouter } from 'next/router'
import {findMe, logoutUser} from "../stores/authSlice";
import {findMe} from "../stores/authSlice";
import {hasPermission} from "../helpers/userPermissions";
@ -34,35 +33,34 @@ export default function LayoutAuthenticated({
const router = useRouter()
const { token, currentUser } = useAppSelector((state) => state.auth)
const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
let localToken
let localToken: string | null = null;
if (typeof window !== 'undefined') {
// Perform localStorage action
localToken = localStorage.getItem('token')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const isTokenValid = () => {
const token = localStorage.getItem('token');
if (!token) return;
if (!token) return false; // Default to false if no token
const date = new Date().getTime() / 1000;
const data = jwt.decode(token);
if (!data) return;
const data = jwt.decode(token) as { exp?: number } | null;
if (!data || !data.exp) return false;
return date < data.exp;
};
useEffect(() => {
dispatch(findMe());
if (!isTokenValid()) {
dispatch(logoutUser());
router.push('/login');
}
}, [token, localToken]);
// Redirection to /login is disabled to "remove" the login system.
// The backend now provides a default admin user if no token is present.
}, [token, localToken, dispatch]);
useEffect(() => {
if (!permission || !currentUser) return;
if (!hasPermission(currentUser, permission)) router.push('/error');
}, [currentUser, permission]);
}, [currentUser, permission, router]);
const darkMode = useAppSelector((state) => state.style.darkMode)
@ -126,4 +124,4 @@ export default function LayoutAuthenticated({
</div>
</div>
)
}
}

View File

@ -7,38 +7,10 @@ const menuAside: MenuAsideItem[] = [
icon: icon.mdiViewDashboardOutline,
label: 'Dashboard',
},
{
href: '/users/users-list',
label: 'Users',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: icon.mdiAccountGroup ?? icon.mdiTable,
permissions: 'READ_USERS'
},
{
href: '/roles/roles-list',
label: 'Roles',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: icon.mdiShieldAccountVariantOutline ?? icon.mdiTable,
permissions: 'READ_ROLES'
},
{
href: '/permissions/permissions-list',
label: 'Permissions',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: icon.mdiShieldAccountOutline ?? icon.mdiTable,
permissions: 'READ_PERMISSIONS'
},
{
href: '/organizations/organizations-list',
label: 'Organizations',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_ORGANIZATIONS'
href: '/dumper',
label: 'Live Dumper',
icon: icon.mdiFlash,
},
{
href: '/scripts/scripts-list',
@ -46,7 +18,6 @@ const menuAside: MenuAsideItem[] = [
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: 'mdiFileDocument' in icon ? icon['mdiFileDocument' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_SCRIPTS'
},
{
href: '/analyses/analyses-list',
@ -54,7 +25,6 @@ const menuAside: MenuAsideItem[] = [
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: 'mdiPlayCircle' in icon ? icon['mdiPlayCircle' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_ANALYSES'
},
{
href: '/analysis_results/analysis_results-list',
@ -62,7 +32,6 @@ const menuAside: MenuAsideItem[] = [
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: 'mdiMagnify' in icon ? icon['mdiMagnify' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_ANALYSIS_RESULTS'
},
{
href: '/yara_rules/yara_rules-list',
@ -70,7 +39,6 @@ const menuAside: MenuAsideItem[] = [
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: 'mdiFileSearch' in icon ? icon['mdiFileSearch' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_YARA_RULES'
},
{
href: '/sandboxes/sandboxes-list',
@ -78,7 +46,6 @@ const menuAside: MenuAsideItem[] = [
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: 'mdiServer' in icon ? icon['mdiServer' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_SANDBOXES'
},
{
href: '/indicators/indicators-list',
@ -86,22 +53,13 @@ const menuAside: MenuAsideItem[] = [
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: 'mdiShieldSearch' in icon ? icon['mdiShieldSearch' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_INDICATORS'
},
{
href: '/profile',
label: 'Profile',
icon: icon.mdiAccountCircle,
},
{
href: '/api-docs',
target: '_blank',
label: 'Swagger API',
icon: icon.mdiFileCode,
permissions: 'READ_API_DOCS'
},
]
export default menuAside
export default menuAside

View File

@ -1,15 +1,6 @@
import {
mdiMenu,
mdiClockOutline,
mdiCloud,
mdiCrop,
mdiAccount,
mdiCogOutline,
mdiEmail,
mdiLogout,
mdiThemeLightDark,
mdiGithub,
mdiVuejs,
} from '@mdi/js'
import { MenuNavBarItem } from './interfaces'
@ -22,14 +13,6 @@ const menuNavBar: MenuNavBarItem[] = [
label: 'My Profile',
href: '/profile',
},
{
isDivider: true,
},
{
icon: mdiLogout,
label: 'Log Out',
isLogout: true,
},
],
},
{
@ -38,16 +21,10 @@ const menuNavBar: MenuNavBarItem[] = [
isDesktopNoLabel: true,
isToggleLightDark: true,
},
{
icon: mdiLogout,
label: 'Log out',
isDesktopNoLabel: true,
isLogout: true,
},
]
export const webPagesNavBar = [
];
export default menuNavBar
export default menuNavBar

View File

@ -0,0 +1,313 @@
import React, { useState, useEffect } from 'react';
import type { ReactElement } from 'react';
import Head from 'next/head';
import LayoutAuthenticated from '../../layouts/Authenticated';
import SectionMain from '../../components/SectionMain';
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
import CardBox from '../../components/CardBox';
import { mdiChartTimelineVariant, mdiConsole, mdiCheckDecagram, mdiFileCode, mdiDatabaseSearch, mdiBug, mdiCodeTags, mdiContentCopy, mdiDownload } from '@mdi/js';
import { getPageTitle } from '../../config';
import BaseButton from '../../components/BaseButton';
import BaseIcon from '../../components/BaseIcon';
import axios from 'axios';
const DumperPage = () => {
const [status, setStatus] = useState<'idle' | 'analyzing' | 'completed' | 'failed'>('idle');
const [logs, setLogs] = useState<string[]>([]);
const [progress, setProgress] = useState(0);
const [scriptContent, setScriptContent] = useState('');
const [scriptName, setScriptName] = useState('unknown.lua');
const [detectedObfuscator, setDetectedObfuscator] = useState('Unknown');
const [dumpedOutput, setDumpedOutput] = useState('');
const [envLogs, setEnvLogs] = useState<string[]>([]);
const [heuristics, setHeuristics] = useState({ network: 0, obfuscation: 0, suspicious: 0 });
useEffect(() => {
const content = localStorage.getItem('pending_script_content');
const name = localStorage.getItem('pending_script_name');
if (content) {
setScriptContent(content);
setScriptName(name || 'script.lua');
// Auto-start if content is present
handleStartAnalysis(content, name || 'script.lua');
}
}, []);
const handleStartAnalysis = async (content: string, name: string) => {
setStatus('analyzing');
setLogs(['[SYSTEM] Initializing Analysis Engine...', '[SYSTEM] Loading VM environment...']);
setProgress(10);
try {
await new Promise(r => setTimeout(r, 500));
setProgress(25);
setLogs(l => [...l, '[VM] Hooking environment globals...', '[VM] Sandbox established.']);
const response = await axios.post('/scripts/analyze', {
content,
name
});
const {
obf = 'Unknown',
logs: backendLogs = [],
envLogs: backendEnvLogs = [],
dumpedOutput: backendDump = '',
heuristics: backendHeuristics = { network: 0, obfuscation: 0, suspicious: 0 }
} = response.data || {};
setProgress(50);
setLogs(l => [...l, ...(Array.isArray(backendLogs) ? backendLogs : [])]);
setDetectedObfuscator(obf);
await new Promise(r => setTimeout(r, 800));
setProgress(75);
setLogs(l => [...l, '[DUMPER] Intercepting execution...', '[DUMPER] Environment calls captured.']);
setEnvLogs(Array.isArray(backendEnvLogs) ? backendEnvLogs : []);
setDumpedOutput(backendDump);
setHeuristics(backendHeuristics);
await new Promise(r => setTimeout(r, 1000));
setProgress(100);
setLogs(l => [...l, '[SYSTEM] Analysis complete. Results ready.']);
setStatus('completed');
} catch (err) {
console.error(err);
setLogs(l => [...l, '[ERROR!] Analysis failed. Could not communicate with backend engine.']);
setStatus('failed');
}
};
const copyToClipboard = () => {
navigator.clipboard.writeText(dumpedOutput);
alert('Dumped content copied to clipboard!');
};
const downloadFile = () => {
const element = document.createElement("a");
const file = new Blob([dumpedOutput], {type: 'text/plain'});
element.href = URL.createObjectURL(file);
element.download = `dumped_${scriptName}`;
document.body.appendChild(element);
element.click();
};
return (
<>
<Head>
<title>{getPageTitle('Script Dumper')}</title>
</Head>
<SectionMain>
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Live Script Analysis" main>
{status === 'idle' && (
<BaseButton
label="Run Dumper"
color="info"
onClick={() => handleStartAnalysis(scriptContent, scriptName)}
disabled={!scriptContent}
/>
)}
{status === 'completed' && (
<BaseButton
label="New Scan"
color="whiteDark"
onClick={() => window.location.href = '/'}
/>
)}
</SectionTitleLineWithButton>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div className="lg:col-span-2 space-y-6">
<CardBox className="bg-slate-900 border-slate-800 shadow-xl shadow-cyan-500/5">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-bold text-white flex items-center gap-2 uppercase tracking-tight">
<BaseIcon path={mdiConsole} className="w-5 h-5 text-cyan-400" />
Execution Console
</h3>
<span className={`px-2 py-1 rounded text-[10px] font-black uppercase tracking-widest ${
status === 'analyzing' ? 'bg-cyan-500/20 text-cyan-400 animate-pulse' :
status === 'completed' ? 'bg-emerald-500/20 text-emerald-400' :
status === 'failed' ? 'bg-red-500/20 text-red-400' :
'bg-slate-700 text-slate-400'
}`}>
{status}
</span>
</div>
<div className="bg-black/80 rounded-lg p-4 h-64 overflow-y-auto font-mono text-[11px] leading-relaxed space-y-1 border border-slate-800 scrollbar-thin scrollbar-thumb-slate-700">
{(logs || []).length === 0 && <p className="text-slate-600 italic">Waiting for execution command...</p>}
{(logs || []).map((log, i) => (
<div key={i} className="flex gap-3">
<span className="text-slate-600 shrink-0">[{new Date().toLocaleTimeString([], { hour12: false })}]</span>
<span className={log.includes('!]') || log.includes('CRITICAL') ? 'text-red-400 font-bold' : log.includes('VM') || log.includes('DUMPER') ? 'text-cyan-400' : log.includes('SYSTEM') ? 'text-purple-400' : log.includes('SENSITIVE') ? 'text-yellow-400' : 'text-slate-300'}>
{log}
</span>
</div>
))}
{status === 'analyzing' && <div className="w-1.5 h-4 bg-cyan-400 animate-pulse inline-block ml-1 align-middle" />}
</div>
{status === 'analyzing' && (
<div className="mt-4">
<div className="flex justify-between text-[10px] text-slate-500 font-bold uppercase mb-1">
<span>Hooking Progress</span>
<span>{progress}%</span>
</div>
<div className="w-full bg-slate-800 rounded-full h-1">
<div
className="bg-cyan-500 h-1 rounded-full transition-all duration-500 shadow-[0_0_10px_rgba(6,182,212,0.8)]"
style={{ width: `${progress}%` }}
/>
</div>
</div>
)}
</CardBox>
{(status === 'completed' || status === 'analyzing') && (
<CardBox className="bg-slate-900 border-slate-800 shadow-xl shadow-cyan-500/5" title="Deobfuscated Output & Constant Table">
<div className="bg-black/90 rounded-lg p-4 h-[500px] overflow-y-auto font-mono text-xs border border-slate-800 relative group">
<div className="absolute top-2 right-4 flex gap-2 opacity-50 group-hover:opacity-100 transition-opacity z-10">
<span className="text-[9px] text-cyan-400 uppercase font-black tracking-[0.2em] bg-cyan-950 px-2 py-0.5 rounded border border-cyan-800">Environment Decrypted</span>
</div>
<pre className="text-emerald-400/90 whitespace-pre-wrap leading-relaxed">
{dumpedOutput || (status === 'analyzing' ? '-- Capturing VM execution points...\n-- Dumping constants table...\n-- Scanning for string decryption routines...' : '-- No output generated --')}
</pre>
</div>
<div className="mt-4 flex gap-3">
<BaseButton
label="Copy Code"
color="info"
icon={mdiContentCopy}
small
onClick={copyToClipboard}
disabled={status !== 'completed'}
/>
<BaseButton
label="Download .LUA"
color="success"
icon={mdiDownload}
small
onClick={downloadFile}
disabled={status !== 'completed'}
/>
</div>
</CardBox>
)}
</div>
<div className="space-y-6">
<CardBox title="Target Information">
<div className="space-y-4">
<div className="flex justify-between items-center border-b border-slate-800 pb-3">
<span className="text-slate-500 text-xs font-bold uppercase flex items-center gap-2">
<BaseIcon path={mdiFileCode} size={14} className="text-cyan-500" />
Target Name
</span>
<span className="font-mono text-sm text-slate-200 truncate w-32 text-right">{scriptName}</span>
</div>
<div className="flex justify-between items-center border-b border-slate-800 pb-3">
<span className="text-slate-500 text-xs font-bold uppercase flex items-center gap-2">
<BaseIcon path={mdiDatabaseSearch} size={14} className="text-purple-500" />
Obfuscator
</span>
<span className={`px-2 py-0.5 rounded text-[10px] font-black ${
detectedObfuscator === 'LURAPH' ? 'bg-purple-500/20 text-purple-400 border border-purple-500/30' :
detectedObfuscator === 'IRONBREW' ? 'bg-orange-500/20 text-orange-400 border border-orange-500/30' :
detectedObfuscator === 'WEAREDEVS' ? 'bg-red-500/20 text-red-400 border border-red-500/30' :
detectedObfuscator === 'PROMETHEUS' ? 'bg-emerald-500/20 text-emerald-400 border border-emerald-500/30' :
'bg-cyan-500/20 text-cyan-400 border border-cyan-500/30'
}`}>
{detectedObfuscator}
</span>
</div>
<div className="flex justify-between items-center border-b border-slate-800 pb-3">
<span className="text-slate-500 text-xs font-bold uppercase flex items-center gap-2">
<BaseIcon path={mdiCodeTags} size={14} className="text-emerald-500" />
Complexity
</span>
<span className="text-sm font-bold text-slate-200">
{scriptContent.length > 50000 ? 'HIGH' : scriptContent.length > 10000 ? 'MEDIUM' : 'LOW'}
</span>
</div>
</div>
</CardBox>
<CardBox title="Security Heuristics">
<div className="space-y-6">
<div>
<div className="flex justify-between text-[10px] font-bold uppercase mb-1">
<span className="text-slate-400">Remote Connectivity</span>
<span className={heuristics.network > 50 ? 'text-red-400' : 'text-emerald-400'}>
{heuristics.network}%
</span>
</div>
<div className="w-full bg-slate-800 rounded-full h-1.5">
<div
className={`h-1.5 rounded-full transition-all duration-1000 ${heuristics.network > 50 ? 'bg-red-500 shadow-[0_0_8px_rgba(239,68,68,0.5)]' : 'bg-emerald-500'}`}
style={{ width: `${heuristics.network}%` }}
/>
</div>
</div>
<div>
<div className="flex justify-between text-[10px] font-bold uppercase mb-1">
<span className="text-slate-400">Obfuscation Entropy</span>
<span className="text-cyan-400">{heuristics.obfuscation}%</span>
</div>
<div className="w-full bg-slate-800 rounded-full h-1.5">
<div
className="bg-cyan-500 h-1.5 rounded-full transition-all duration-1000 shadow-[0_0_8px_rgba(6,182,212,0.5)]"
style={{ width: `${heuristics.obfuscation}%` }}
/>
</div>
</div>
<div>
<div className="flex justify-between text-[10px] font-bold uppercase mb-1">
<span className="text-slate-400">Threat Risk Score</span>
<span className={heuristics.suspicious > 50 ? 'text-red-400 font-black' : 'text-emerald-400'}>
{heuristics.suspicious}%
</span>
</div>
<div className="w-full bg-slate-800 rounded-full h-1.5">
<div
className={`h-1.5 rounded-full transition-all duration-1000 ${heuristics.suspicious > 50 ? 'bg-red-500 shadow-[0_0_8px_rgba(239,68,68,0.5)]' : 'bg-emerald-500'}`}
style={{ width: `${heuristics.suspicious}%` }}
/>
</div>
</div>
</div>
</CardBox>
<CardBox className="bg-slate-900 border-slate-800" title="Captured Environment Logs">
<div className="space-y-3 max-h-80 overflow-y-auto font-mono text-[11px] scrollbar-thin scrollbar-thumb-slate-700">
{(envLogs || []).length > 0 ? (envLogs || []).map((log, i) => (
<div key={i} className={`flex items-start gap-3 border-l-2 pl-3 py-1 transition-colors ${
log.includes('WEBHOOK') || log.includes('REMOTE') ? 'bg-red-500/5 border-red-500/50 text-red-200' :
log.includes('INTERCEPTED') ? 'bg-yellow-500/5 border-yellow-500/50 text-yellow-200' :
'bg-cyan-500/5 border-cyan-500/50 text-slate-300'
}`}>
<BaseIcon path={mdiBug} size={12} className={`shrink-0 mt-0.5 ${
log.includes('WEBHOOK') || log.includes('REMOTE') ? 'text-red-400' :
log.includes('INTERCEPTED') ? 'text-yellow-400' :
'text-cyan-400'
}`} />
<span className="break-all">{log}</span>
</div>
)) : (
<p className="text-slate-600 italic text-[10px] text-center py-4 uppercase tracking-widest">Waiting for VM telemetry...</p>
)}
</div>
</CardBox>
</div>
</div>
</SectionMain>
</>
);
};
DumperPage.getLayout = function getLayout(page: ReactElement) {
return <LayoutAuthenticated>{page}</LayoutAuthenticated>;
};
export default DumperPage;

View File

@ -1,166 +1,85 @@
import React, { useEffect, useState } from 'react';
import React from 'react';
import type { ReactElement } from 'react';
import Head from 'next/head';
import Link from 'next/link';
import BaseButton from '../components/BaseButton';
import CardBox from '../components/CardBox';
import SectionFullScreen from '../components/SectionFullScreen';
import LayoutGuest from '../layouts/Guest';
import BaseDivider from '../components/BaseDivider';
import BaseButtons from '../components/BaseButtons';
import { getPageTitle } from '../config';
import { useAppSelector } from '../stores/hooks';
import CardBoxComponentTitle from "../components/CardBoxComponentTitle";
import { getPexelsImage, getPexelsVideo } from '../helpers/pexels';
import AnalysisHero from '../components/Home/AnalysisHero';
import { mdiHexagonMultiple, mdiShieldBug, mdiHistory } from '@mdi/js';
import BaseIcon from '../components/BaseIcon';
const FeatureItem = ({ icon, title, description }: { icon: string, title: string, description: string }) => (
<div className="flex flex-col items-center p-6 bg-slate-800/30 rounded-2xl border border-slate-700/50">
<div className="p-3 bg-cyan-500/10 rounded-xl mb-4 text-cyan-400">
<BaseIcon path={icon} size={32} />
</div>
<h3 className="text-xl font-semibold text-white mb-2">{title}</h3>
<p className="text-slate-400 text-center text-sm">{description}</p>
</div>
);
export default function Starter() {
const [illustrationImage, setIllustrationImage] = useState({
src: undefined,
photographer: undefined,
photographer_url: undefined,
})
const [illustrationVideo, setIllustrationVideo] = useState({video_files: []})
const [contentType, setContentType] = useState('video');
const [contentPosition, setContentPosition] = useState('right');
const textColor = useAppSelector((state) => state.style.linkColor);
const title = 'Script Analysis Portal'
// Fetch Pexels image/video
useEffect(() => {
async function fetchData() {
const image = await getPexelsImage();
const video = await getPexelsVideo();
setIllustrationImage(image);
setIllustrationVideo(video);
}
fetchData();
}, []);
const imageBlock = (image) => (
<div
className='hidden md:flex flex-col justify-end relative flex-grow-0 flex-shrink-0 w-1/3'
style={{
backgroundImage: `${
image
? `url(${image?.src?.original})`
: 'linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5))'
}`,
backgroundSize: 'cover',
backgroundPosition: 'left center',
backgroundRepeat: 'no-repeat',
}}
>
<div className='flex justify-center w-full bg-blue-300/20'>
<a
className='text-[8px]'
href={image?.photographer_url}
target='_blank'
rel='noreferrer'
>
Photo by {image?.photographer} on Pexels
</a>
</div>
</div>
);
const videoBlock = (video) => {
if (video?.video_files?.length > 0) {
return (
<div className='hidden md:flex flex-col justify-end relative flex-grow-0 flex-shrink-0 w-1/3'>
<video
className='absolute top-0 left-0 w-full h-full object-cover'
autoPlay
loop
muted
>
<source src={video?.video_files[0]?.link} type='video/mp4'/>
Your browser does not support the video tag.
</video>
<div className='flex justify-center w-full bg-blue-300/20 z-10'>
<a
className='text-[8px]'
href={video?.user?.url}
target='_blank'
rel='noreferrer'
>
Video by {video.user.name} on Pexels
</a>
</div>
</div>)
}
};
export default function Home() {
return (
<div
style={
contentPosition === 'background'
? {
backgroundImage: `${
illustrationImage
? `url(${illustrationImage.src?.original})`
: 'linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5))'
}`,
backgroundSize: 'cover',
backgroundPosition: 'left center',
backgroundRepeat: 'no-repeat',
}
: {}
}
>
<div className="bg-slate-900 min-h-screen font-sans">
<Head>
<title>{getPageTitle('Starter Page')}</title>
<title>{getPageTitle('Universal Script Dumper')}</title>
</Head>
<SectionFullScreen bg='violet'>
<div
className={`flex ${
contentPosition === 'right' ? 'flex-row-reverse' : 'flex-row'
} min-h-screen w-full`}
>
{contentType === 'image' && contentPosition !== 'background'
? imageBlock(illustrationImage)
: null}
{contentType === 'video' && contentPosition !== 'background'
? videoBlock(illustrationVideo)
: null}
<div className='flex items-center justify-center flex-col space-y-4 w-full lg:w-full'>
<CardBox className='w-full md:w-3/5 lg:w-2/3'>
<CardBoxComponentTitle title="Welcome to your Script Analysis Portal app!"/>
<div className="space-y-3">
<p className='text-center '>This is a React.js/Node.js app generated by the <a className={`${textColor}`} href="https://flatlogic.com/generator">Flatlogic Web App Generator</a></p>
<p className='text-center '>For guides and documentation please check
your local README.md and the <a className={`${textColor}`} href="https://flatlogic.com/documentation">Flatlogic documentation</a></p>
</div>
<BaseButtons>
<BaseButton
href='/login'
label='Login'
color='info'
className='w-full'
/>
<AnalysisHero />
</BaseButtons>
</CardBox>
<section className="py-24 bg-slate-900">
<div className="mx-auto max-w-7xl px-6 lg:px-8">
<div className="grid grid-cols-1 gap-8 md:grid-cols-3">
<FeatureItem
icon={mdiHexagonMultiple}
title="Multi-Engine Support"
description="Dedicated patterns for Luraph, Prometheus, and Ironbrew VM architectures."
/>
<FeatureItem
icon={mdiShieldBug}
title="Malware Analysis"
description="Heuristic scanning for webhooks, file system access, and credential theft."
/>
<FeatureItem
icon={mdiHistory}
title="Analysis History"
description="Securely store and compare different versions of analyzed scripts."
/>
</div>
</div>
</div>
</SectionFullScreen>
<div className='bg-black text-white flex flex-col text-center justify-center md:flex-row'>
<p className='py-6 text-sm'>© 2026 <span>{title}</span>. All rights reserved</p>
<Link className='py-6 ml-4 text-sm' href='/privacy-policy/'>
Privacy Policy
</Link>
</div>
</section>
<section className="py-24 border-t border-slate-800">
<div className="mx-auto max-w-7xl px-6 lg:px-8 text-center">
<h2 className="text-3xl font-bold text-white mb-12">Supported Obfuscators</h2>
<div className="flex flex-wrap justify-center gap-12 opacity-50 grayscale hover:grayscale-0 transition-all">
<div className="flex items-center gap-x-2 text-2xl font-bold text-slate-300">
<span className="text-cyan-400">L</span>uraph
</div>
<div className="flex items-center gap-x-2 text-2xl font-bold text-slate-300">
<span className="text-emerald-400">P</span>rometheus
</div>
<div className="flex items-center gap-x-2 text-2xl font-bold text-slate-300">
<span className="text-purple-400">I</span>ronbrew
</div>
</div>
</div>
</section>
<footer className="bg-slate-950 border-t border-slate-900 py-12">
<div className="mx-auto max-w-7xl px-6 lg:px-8 flex flex-col md:flex-row justify-between items-center gap-y-4">
<p className="text-slate-500 text-sm">
© 2026 Script Dumper & Analyzer. All rights reserved.
</p>
<div className="flex gap-x-8 text-slate-500 text-sm">
<a href="#" className="hover:text-cyan-400 transition-colors">Documentation</a>
<a href="#" className="hover:text-cyan-400 transition-colors">API Reference</a>
</div>
</div>
</footer>
</div>
);
}
Starter.getLayout = function getLayout(page: ReactElement) {
Home.getLayout = function getLayout(page: ReactElement) {
return <LayoutGuest>{page}</LayoutGuest>;
};
};

View File

@ -1,10 +1,7 @@
import React, { ReactElement, useEffect, useState } from 'react';
import Head from 'next/head';
import 'react-datepicker/dist/react-datepicker.css';
import { useAppDispatch } from '../stores/hooks';
import { useAppSelector } from '../stores/hooks';
import { useAppDispatch, useAppSelector } from '../stores/hooks';
import { useRouter } from 'next/router';
import LayoutAuthenticated from '../layouts/Authenticated';
import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton';
@ -93,4 +90,4 @@ SearchView.getLayout = function getLayout(page: ReactElement) {
);
};
export default SearchView;
export default SearchView;