From 3055f30143fa0591eb2602b6057ceb7132d82013 Mon Sep 17 00:00:00 2001 From: STetsing <41009393+STetsing@users.noreply.github.com> Date: Wed, 6 Nov 2024 09:58:20 +0100 Subject: [PATCH] minor --- apps/remix-ide/src/app/files/fileManager.ts | 2 +- .../src/app/plugins/remixAIPlugin.tsx | 42 ++-- .../src/app/tabs/locales/en/editor.json | 2 +- .../remix-ai-core/src/agents/securityAgent.ts | 183 ++++++++++++++++-- libs/remix-ai-core/src/index.ts | 3 +- .../src/inferencers/remote/remoteInference.ts | 2 +- .../lib/providers/inlineCompletionProvider.ts | 9 +- .../editor/src/lib/remix-ui-editor.tsx | 2 +- 8 files changed, 191 insertions(+), 54 deletions(-) diff --git a/apps/remix-ide/src/app/files/fileManager.ts b/apps/remix-ide/src/app/files/fileManager.ts index a821ced648..707c14bb30 100644 --- a/apps/remix-ide/src/app/files/fileManager.ts +++ b/apps/remix-ide/src/app/files/fileManager.ts @@ -26,7 +26,7 @@ const profile = { 'readFile', 'copyFile', 'copyDir', 'rename', 'mkdir', 'readdir', 'dirList', 'fileList', 'remove', 'getCurrentFile', 'getFile', 'getFolder', 'setFile', 'switchFile', 'refresh', 'getProviderOf', 'getProviderByName', 'getPathFromUrl', 'getUrlFromPath', 'saveCurrentFile', 'setBatchFiles', 'isGitRepo', 'isFile', 'isDirectory', 'hasGitSubmodule', 'copyFolderToJson', 'diff', - 'hasGitSubmodules' + 'hasGitSubmodules', 'getOpenedFiles' ], kind: 'file-system' } diff --git a/apps/remix-ide/src/app/plugins/remixAIPlugin.tsx b/apps/remix-ide/src/app/plugins/remixAIPlugin.tsx index 73cd4e489e..ffef2d5294 100644 --- a/apps/remix-ide/src/app/plugins/remixAIPlugin.tsx +++ b/apps/remix-ide/src/app/plugins/remixAIPlugin.tsx @@ -3,7 +3,7 @@ import { ViewPlugin } from '@remixproject/engine-web' import { Plugin } from '@remixproject/engine'; import { RemixAITab, ChatApi } from '@remix-ui/remix-ai' import React, { useCallback } from 'react'; -import { ICompletions, IModel, RemoteInferencer, IRemoteModel, IParams, GenerationParams, CodeExplainAgent } from '@remix/remix-ai-core'; +import { ICompletions, IModel, RemoteInferencer, IRemoteModel, IParams, GenerationParams, CodeExplainAgent, SecurityAgent} from '@remix/remix-ai-core'; import { CustomRemixApi } from '@remix-api' type chatRequestBufferT = { @@ -35,13 +35,14 @@ export class RemixAIPlugin extends ViewPlugin { remoteInferencer:RemoteInferencer = null isInferencing: boolean = false chatRequestBuffer: chatRequestBufferT = null - agent: CodeExplainAgent + codeExpAgent: CodeExplainAgent + securityAgent: SecurityAgent useRemoteInferencer:boolean = false constructor(inDesktop:boolean) { super(profile) this.isOnDesktop = inDesktop - this.agent = new CodeExplainAgent(this) + this.codeExpAgent = new CodeExplainAgent(this) // user machine dont use ressource for remote inferencing } @@ -57,6 +58,8 @@ export class RemixAIPlugin extends ViewPlugin { this.useRemoteInferencer = true this.initialize() } + + this.securityAgent = new SecurityAgent(this) } async initialize(model1?:IModel, model2?:IModel, remoteModel?:IRemoteModel, useRemote?:boolean){ @@ -92,11 +95,6 @@ export class RemixAIPlugin extends ViewPlugin { } async code_generation(prompt: string): Promise { - if (this.isInferencing) { - this.call('terminal', 'log', { type: 'aitypewriterwarning', value: "RemixAI is already busy!" }) - return - } - if (this.isOnDesktop && !this.useRemoteInferencer) { return await this.call(this.remixDesktopPluginName, 'code_generation', prompt) } else { @@ -113,12 +111,7 @@ export class RemixAIPlugin extends ViewPlugin { } async solidity_answer(prompt: string, params: IParams=GenerationParams): Promise { - if (this.isInferencing) { - this.call('terminal', 'log', { type: 'aitypewriterwarning', value: "RemixAI is already busy!" }) - return - } - - const newPrompt = await this.agent.chatCommand(prompt) + const newPrompt = await this.codeExpAgent.chatCommand(prompt) let result if (this.isOnDesktop && !this.useRemoteInferencer) { result = await this.call(this.remixDesktopPluginName, 'solidity_answer', newPrompt) @@ -130,11 +123,6 @@ export class RemixAIPlugin extends ViewPlugin { } async code_explaining(prompt: string, context: string, params: IParams=GenerationParams): Promise { - if (this.isInferencing) { - this.call('terminal', 'log', { type: 'aitypewriterwarning', value: "RemixAI is already busy!" }) - return - } - let result if (this.isOnDesktop && !this.useRemoteInferencer) { result = await this.call(this.remixDesktopPluginName, 'code_explaining', prompt, context, params) @@ -147,11 +135,6 @@ export class RemixAIPlugin extends ViewPlugin { } async error_explaining(prompt: string, context: string="", params: IParams=GenerationParams): Promise { - if (this.isInferencing) { - this.call('terminal', 'log', { type: 'aitypewriterwarning', value: "RemixAI is already busy!" }) - return - } - let result if (this.isOnDesktop && !this.useRemoteInferencer) { result = await this.call(this.remixDesktopPluginName, 'error_explaining', prompt) @@ -163,22 +146,21 @@ export class RemixAIPlugin extends ViewPlugin { } async vulnerability_check(prompt: string, params: IParams=GenerationParams): Promise { - if (this.isInferencing) { - this.call('terminal', 'log', { type: 'aitypewriterwarning', value: "RemixAI is already busy!" }) - return - } - let result if (this.isOnDesktop && !this.useRemoteInferencer) { result = await this.call(this.remixDesktopPluginName, 'vulnerability_check', prompt) } else { - result = await this.remoteInferencer.vulnerability_check(prompt) + result = await this.remoteInferencer.vulnerability_check(prompt, params) } if (result && params.terminal_output) this.call('terminal', 'log', { type: 'aitypewriterwarning', value: result }) return result } + getVulnerabilityReport(file: string): any { + return this.securityAgent.getReport(file) + } + async code_insertion(msg_pfx: string, msg_sfx: string): Promise { if (this.isOnDesktop && !this.useRemoteInferencer) { return await this.call(this.remixDesktopPluginName, 'code_insertion', msg_pfx, msg_sfx) diff --git a/apps/remix-ide/src/app/tabs/locales/en/editor.json b/apps/remix-ide/src/app/tabs/locales/en/editor.json index 45805618c4..81d9f8c7c4 100644 --- a/apps/remix-ide/src/app/tabs/locales/en/editor.json +++ b/apps/remix-ide/src/app/tabs/locales/en/editor.json @@ -28,7 +28,7 @@ "editor.explainFunctionByAI": "```\n{content}\n```\nExplain the function {currentFunction}", "editor.explainFunctionByAISol": "```\n{content}\n```\nExplain the function {currentFunction}", "editor.ExplainPipeMessage": "```\n {content}\n```\nExplain the snipped above", - "editor.PastedCodeSafety": "```\n {content}\n```\n\nReply in a short maner: Does this code contain major security vulenerabilities leading to a scam or loss of funds?", + "editor.PastedCodeSafety": "```\n {content}\n```\n\nReply in a short manner: Does this code contain major security vulenerabilities leading to a scam or loss of funds?", "editor.executeFreeFunction": "Run a free function", "editor.executeFreeFunction2": "Run the free function \"{name}\"", "editor.toastText1": "This can only execute free function", diff --git a/libs/remix-ai-core/src/agents/securityAgent.ts b/libs/remix-ai-core/src/agents/securityAgent.ts index 32387b1209..04b0e786e3 100644 --- a/libs/remix-ai-core/src/agents/securityAgent.ts +++ b/libs/remix-ai-core/src/agents/securityAgent.ts @@ -1,24 +1,185 @@ // security checks import * as fs from 'fs'; -class SecurityAgent { - private codebase: string[]; // list of code base file +interface SecurityReport { + compiled: boolean; + vulnerabilities: string[]; + isNotSafe: string; + fileName: string; + reportTimestamp: string; + recommendations: string[]; + fileModifiedSinceLastReport: boolean; + hasPastedCode: boolean; +} + +class WorkspaceWatcher { + private intervalId: NodeJS.Timeout | null = null; + public interval: number; + private task: () => void; + + constructor(task: () => void, interval: number) { + this.task = task; + this.interval = interval; + } + + start(): void { + if (this.intervalId === null) { + this.intervalId = setInterval(() => { + this.task(); + }, this.interval); + } + } + + stop(): void { + if (this.intervalId !== null) { + clearInterval(this.intervalId); + this.intervalId = null; + } + } + + isRunning(): boolean { + return this.intervalId !== null; + } +} + +export class SecurityAgent { public currentFile: string; + public openedFiles: any; + private basePlugin: any; + private watcher: WorkspaceWatcher; + public reports: SecurityReport[] = []; + + constructor(plugin) { + this.basePlugin = plugin; + + this.basePlugin.on('fileManager', 'fileAdded', (path) => { }); + this.basePlugin.on('fileManager', 'fileChanged', (path) => { //this.modifiedFile(path) + }); + + this.basePlugin.on('fileManager', 'fileRemoved', (path) => { this.removeFileFromReport(path) }); + this.basePlugin.on('fileManager', 'fileRenamed', (oldName, newName) => { + this.removeFileFromReport(oldName); + this.addFileToReport(newName); + }); + + this.basePlugin.on('solidity', 'compilationFinished', async (fileName, source, languageVersion, data) => { this.onCompilationFinished(fileName) }); + this.basePlugin.on('vyper', 'compilationFinished', async (fileName, source, languageVersion, data) => { this.onCompilationFinished(fileName) }); + this.basePlugin.on('hardhat', 'compilationFinished', async (fileName, source, languageVersion, data) => { this.onCompilationFinished(fileName) }); + this.basePlugin.on('foundry', 'compilationFinished', async (fileName, source, languageVersion, data) => { this.onCompilationFinished(fileName) }); + + this.watcher = new WorkspaceWatcher(async () => { + try { + this.currentFile = await this.basePlugin.call('fileManager', 'getCurrentFile'); + this.openedFiles = await this.basePlugin.call('fileManager', 'getOpenedFiles'); + + Object.keys(this.openedFiles).forEach(key => { + this.addFileToReport(this.openedFiles[key]); + }); + } catch (error) { + // no file selected or opened currently + } + }, 10000); + this.watcher.start(); + } + + addFileToReport(file: string): void { + const report = this.reports.find((r) => r.fileName === file); + if (report) { + // nothing to do + } else { + this.reports.push({ + compiled: false, + isNotSafe: 'No', + vulnerabilities: [], + fileName: file, + reportTimestamp: null, + recommendations: [], + fileModifiedSinceLastReport: false, + hasPastedCode: false + }); + } - constructor(codebasePath: string) { - // git or fs - this.codebase = this.loadCodebase(codebasePath); } - private loadCodebase(path: string): string[] { - const files = fs.readdirSync(path); - return files - .filter(file => file.endsWith('.ts')) - .flatMap(file => fs.readFileSync(`${path}/${file}`, 'utf-8').split('\n')); + async onCompilationFinished(file: string) { + let report = this.reports.find((r) => r.fileName === file); + if (report) { + report.compiled = true; + report.fileModifiedSinceLastReport = false; + } else { + report = { + compiled: true, + isNotSafe: 'No', + vulnerabilities: [], + fileName: file, + reportTimestamp: null, + recommendations: [], + fileModifiedSinceLastReport: false, + hasPastedCode: false + } + this.reports.push(report); + } + + try { + this.processFile(file); + console.log('Checking for vulnerabilities after compilation', this.reports); + } catch (error) { + console.error('Error checking for vulnerabilities after compilation: ', error); + } + + // check for security vulnerabilities } - public update(currentFile, lineNumber){ + removeFileFromReport(file: string): void { + const index = this.reports.findIndex((r) => r.fileName === file); + if (index !== -1) { + this.reports.splice(index, 1); + } + } + + modifiedFile(file: string): void { + const report = this.reports.find((r) => r.fileName === file); + if (report) { + report.fileModifiedSinceLastReport = true; + } + } + + async processFile(file: string) { + try { + const report = this.reports.find((r) => r.fileName === file); + if (report) { } + else { + this.reports.push({ + compiled: false, + isNotSafe: 'No', + vulnerabilities: [], + fileName: file, + reportTimestamp: null, + recommendations: [], + fileModifiedSinceLastReport: false, + hasPastedCode: false + }); + } + + if (!report.reportTimestamp || report.fileModifiedSinceLastReport) { + const content = await this.basePlugin.call('fileManager', 'getFile', file); + const prompt = "```\n" + content + "\n```\n\nReply in a short manner: Does this code contain major security vulenerabilities leading to a scam or loss of funds?" + + let result = await this.basePlugin.call('remixAI', 'vulnerability_check', prompt) + result = JSON.parse(result); + report.vulnerabilities = result.Reason; + report.recommendations = result.Suggestion; + report.isNotSafe = result.Answer; + report.reportTimestamp = new Date().toISOString(); + } + + } catch (error) { + console.error('Error processing file: ', error); + } + } + getReport(file: string): SecurityReport { + return this.reports.find((r) => r.fileName === file); } public getRecommendations(currentLine: string, numSuggestions: number = 3): string[] { diff --git a/libs/remix-ai-core/src/index.ts b/libs/remix-ai-core/src/index.ts index fe54a57f2f..a53c630f32 100644 --- a/libs/remix-ai-core/src/index.ts +++ b/libs/remix-ai-core/src/index.ts @@ -21,4 +21,5 @@ export { export * from './types/types' export * from './helpers/streamHandler' -export * from './agents/codeExplainAgent' \ No newline at end of file +export * from './agents/codeExplainAgent' +export * from './agents/securityAgent' \ No newline at end of file diff --git a/libs/remix-ai-core/src/inferencers/remote/remoteInference.ts b/libs/remix-ai-core/src/inferencers/remote/remoteInference.ts index 4975f985f9..c6ad381618 100644 --- a/libs/remix-ai-core/src/inferencers/remote/remoteInference.ts +++ b/libs/remix-ai-core/src/inferencers/remote/remoteInference.ts @@ -12,7 +12,7 @@ export class RemoteInferencer implements ICompletions { max_history = 7 model_op = RemoteBackendOPModel.CODELLAMA // default model operation change this to llama if necessary event: EventEmitter - test_env=true + test_env=false test_url="http://solcodertest.org" constructor(apiUrl?:string, completionUrl?:string) { diff --git a/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts b/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts index 674d069f1c..5f15022b10 100644 --- a/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts +++ b/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts @@ -9,7 +9,7 @@ export class RemixInLineCompletionProvider implements monacoTypes.languages.Inli props: EditorUIProps monaco: any completionEnabled: boolean - task: string + task: string = 'code_completion' currentCompletion: any private lastRequestTime: number = 0; private readonly minRequestInterval: number = 200; @@ -60,13 +60,6 @@ export class RemixInLineCompletionProvider implements monacoTypes.languages.Inli endColumn: getTextAtLine(model.getLineCount()).length + 1, }); - if (!word.endsWith(' ') && - !word.endsWith('.') && - !word.endsWith('"') && - !word.endsWith('(')) { - return; - } - try { const split = word.split('\n') if (split.length < 2) return diff --git a/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx b/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx index 56e9bc273e..5947c47447 100644 --- a/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx +++ b/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx @@ -697,7 +697,7 @@ export const EditorUI = (props: EditorUIProps) => { ), } - + // get the file name const pastedCode = editor.getModel().getValueInRange(e.range) const pastedCodePrompt = intl.formatMessage({ id: 'editor.PastedCodeSafety' }, { content:pastedCode }) props.plugin.call('remixAI', 'chatPipe', 'vulnerability_check', pastedCodePrompt)