From cacac12767260b1784fdfae8cddd7d7cf3b7a6cd Mon Sep 17 00:00:00 2001 From: Chris Wilton-Magras Date: Thu, 28 Mar 2024 11:00:16 +0000 Subject: [PATCH] Docker and backend changes to support prod-lite mode (#877) * Docker and backend changes to support prod-lite mode * Bump app version to 1.0.0 across all package files --- .dockerignore | 12 + .gitignore | 118 +--- CONTRIBUTING.md | 32 +- README.md | 87 ++- backend/.env.example | 13 +- backend/Dockerfile | 5 +- backend/README.md | 41 +- backend/docker-compose.yml | 12 - backend/package-lock.json | 460 +++++++++++-- backend/package.json | 14 +- backend/src/app.ts | 18 - backend/src/nonSessionRoutes.ts | 19 - backend/src/server/app.ts | 41 ++ backend/src/{ => server}/importMetaUtils.ts | 0 backend/src/{server.ts => server/index.ts} | 10 +- backend/src/server/nonSessionRoutes.ts | 18 + backend/src/{ => server}/sessionRoutes.ts | 71 +- backend/src/server/uiRoutes.ts | 14 + backend/test/api/start.test.ts | 4 +- backend/test/tsconfig.json | 4 + compose.yaml | 15 + frontend/.env.example | 2 +- frontend/README.md | 34 +- frontend/package.json | 4 +- frontend/src/service/backendService.ts | 3 +- frontend/src/service/startService.ts | 2 +- k6/package.json | 2 +- package-lock.json | 698 ++++++++++---------- package.json | 33 +- prodlite.Dockerfile | 27 + 30 files changed, 1128 insertions(+), 685 deletions(-) create mode 100644 .dockerignore delete mode 100644 backend/docker-compose.yml delete mode 100644 backend/src/app.ts delete mode 100644 backend/src/nonSessionRoutes.ts create mode 100644 backend/src/server/app.ts rename backend/src/{ => server}/importMetaUtils.ts (100%) rename backend/src/{server.ts => server/index.ts} (82%) create mode 100644 backend/src/server/nonSessionRoutes.ts rename backend/src/{ => server}/sessionRoutes.ts (55%) create mode 100644 backend/src/server/uiRoutes.ts create mode 100644 compose.yaml create mode 100644 prodlite.Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..ae90c664e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,12 @@ +**/node_modules +**/build/ +**/dist/ +**/test/ +**/jest* +**/tsconfig.test.json +**/vitest* +**/.env* +**/.eslint* +**/.prettier* +**/.stylelint* +**/*.md diff --git a/.gitignore b/.gitignore index bb7d928c0..fa983e6ed 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,9 @@ -# Created by https://www.toptal.com/developers/gitignore/api/react,node -# Edit at https://www.toptal.com/developers/gitignore?templates=react,node - -### Node ### # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* -lerna-debug.log* -.pnpm-debug.log* # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json @@ -22,111 +16,39 @@ pids # Directory for instrumented libs generated by jscoverage/JSCover lib-cov - -# Coverage directory used by tools like istanbul coverage *.lcov -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - # node-waf configuration .lock-wscript -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/ - # Dependency directories node_modules/ -jspm_packages/ -# Snowpack dependency directory (https://snowpack.dev/) -web_modules/ +# Compiled stuff +build/ +dist/ -# TypeScript cache +# Caches *.tsbuildinfo - -# Optional npm cache directory .npm - -# Optional eslint cache .eslintcache - -# Optional stylelint cache .stylelintcache -# Microbundle cache -.rpt2_cache/ -.rts2_cache_cjs/ -.rts2_cache_es/ -.rts2_cache_umd/ - # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz -# Yarn Integrity file -.yarn-integrity - # dotenv environment variable files -.env -.env.development.local -.env.test.local -.env.production.local -.env.local - -# parcel-bundler cache (https://parceljs.org/) -.cache -.parcel-cache - -# Next.js build output -.next -out - -# Nuxt.js build / generate output -.nuxt -dist - -# Gatsby files -.cache/ -# Comment in the public line in if your project uses Gatsby and not Next.js -# https://nextjs.org/blog/next-9-1#public-directory-support -# public - -# vuepress build output -.vuepress/dist +.env* +!**/.env.example -# vuepress v2.x temp and cache directory -.temp - -# Docusaurus cache and generated files -.docusaurus - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ - -# TernJS port file -.tern-port - -# Stores VSCode versions used for testing VSCode extensions +# IDEs .vscode-test - -# JetBrains .idea +*.sublime* # yarn v2 .yarn/cache @@ -134,27 +56,3 @@ dist .yarn/build-state.yml .yarn/install-state.gz .pnp.* - -### Node Patch ### -# Serverless Webpack directories -.webpack/ - -# Optional stylelint cache - -# SvelteKit build / generate output -.svelte-kit - -### react ### -.DS_* -**/*.backup.* -**/*.back.* - -node_modules - -*.sublime* - -psd -thumb -sketch - -# End of https://www.toptal.com/developers/gitignore/api/react,node \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 32d5188b1..b63a88a68 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,8 @@ Thank you for considering contributing to this open source project! -This guide will ensure that you have all the necessary information needed to be an effective contributor to the prompt-injection project. +This guide will ensure that you have all the necessary information needed to be an effective contributor to the +prompt-injection project. There are many ways you can contribute. Feel free to raise issues for bug reports or new feature requests. @@ -12,8 +13,8 @@ Please do read through our [code of conduct](CODE_OF_CONDUCT.md) before contribu ## Before you create an issue -- Does the issue already exist? _Remember to check the **open** and **closed** issues._ -- Is this a security issue? _Do **NOT** open an issue. Email gsproston@scottlogic.com instead._ +- Does the issue already exist? _Remember to check **open** and **closed** issues._ +- Is this a security issue? _Do **NOT** open an issue. Email cwilton@scottlogic.com instead._ ## What to include in the issue @@ -37,27 +38,34 @@ Please do read through our [code of conduct](CODE_OF_CONDUCT.md) before contribu - Fork the repo. - Have you contributed before? _If not, look for issues with the 'good first issue' label._ - Is it assigned to anyone else? _If so, post a message to see if the assignee is still working on it._ -- Is it assigned to you? _If not, post a message stating your intent so that the maintainers and other contributors know what is being developed._ -- Do you understand the issue fully? _If not, ask on in the issue. We are all here to help you contribute._ +- Is it assigned to you? _If not, post a message stating your intent so that the maintainers and other contributors know + what is being developed._ +- Do you understand the issue fully? _If not, ask a question on the issue. We are all here to help you contribute._ - Check the issue labels. Some of them indicate that you shouldn't start developing yet: - - `triage` means that a contributor has created the issue, and a maintainer hasn't looked at it yet. _This means that we might not accept work done for this issue._ - - `question` means that we need to agree on some points before implementing the issue. _Look in the comments and feel free to get involved in discussion!_ - - `blocked` means the issue cannot be resolved until a different piece of work is finished. _Look in the comments to see what is blocking it, and make sure it is resolved before starting the current issue._ + - `triage` means that a contributor has created the issue, and a maintainer hasn't looked at it yet. _This means that + we might not accept work done for this issue._ + - `question` means that we need to agree on some points before implementing the issue. _Look in the comments and feel + free to get involved in discussion!_ + - `blocked` means the issue cannot be resolved until a different piece of work is finished. _Look in the comments to + see what is blocking it, and make sure it is resolved before starting the current issue._ - `ui` means that we are waiting for input from the designer on the maintainer team before implementing the issue. -## When you submit a PR +## When you submit a Pull Request (PR) - [Link the issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) that the PR addresses. - Link any related PRs. - Have you added tests? - Are all workflow steps passing? -- Is it ready for review? _If not, create a draft._ +- Is it ready for review? If not, mark the PR as a draft. - Include the ticket number in the PR name. ### Adding tests -Our project has unit tests and integration tests. We expect tests to be written / altered when a PR adds a new feature or modifes existing behaviour. It is generally up to you to judge what depth of testing to go into. +Our project has unit tests and integration tests. We expect tests to be written / altered when a PR adds a new feature +or modifes existing behaviour. It is generally up to you to judge what depth of testing to go into. ### Checking the workflow steps -When a PR is submitted or updated, GitHub automatically runs a [workflow](https://docs.github.com/en/actions/using-workflows) for each of the frontend and backend. To see if those are passing, you must scroll to the bottom of the PR, just above where you can post a new comment. +When a PR is submitted or updated, GitHub automatically runs checks on affected code via +[workflow actions](https://docs.github.com/en/actions/using-workflows). +A PR cannot be merged until all workflow steps have passed. diff --git a/README.md b/README.md index 17b657418..1cab57d37 100644 --- a/README.md +++ b/README.md @@ -1,62 +1,99 @@ -# prompt-injection +# ![SpyLogic Avatar](./frontend/src/assets/images/BotAvatarDefault.svg) Welcome to SpyLogic ## Introduction -Welcome to the Scott Logic prompt injection open source project! -As generative AI and LLMs become more prevalent, it becomes more important to learn about the dangers of prompt injection. -This project aims to teach people about prompt injection attacks that can be used on generative AI, and how to defend against such attacks. +Welcome to the open source Scott Logic prompt injection playground! -This project is presented in two modes: +As generative AI and LLMs are becoming more prevalent, it is important to learn about the weaknesses inherent to +generative AI models. We have built an application called **SpyLogic** to teach people in a fun way about prompt +injection attacks, and how to defend against them. + +SpyLogic is presented in two modes: ### Story mode -Go undercover and use prompt injection attacks on ScottBrewBot, a clever but flawed generative AI bot. Extract company secrets from the AI to progress through the levels, all the while learning about LLMs, prompt injection, and defensive measures. +Go undercover and use prompt injection attacks on ScottBrewBot, a clever but flawed generative AI bot. Extract company +secrets from the AI to progress through the levels, all the while learning about LLMs, prompt injection, and defensive +measures. ### Sandbox mode -Activate and configure a number of different prompt injection defence measures to create your own security system. Then talk to the AI and try to crack it! +Activate and configure a number of different prompt injection defence measures to create your own security system. Then +talk to the AI and try to crack it! ## Installation -Ensure you have Node v18+ installed. Clone this repo and run + +Minimum requirement: Node v18 ```bash npm ci ``` -## Setup +## Setup Local Environment + +To run locally, a few environment variables must be defined. We are using [dotenv](https://github.com/motdotla/dotenv) +to load local `.env` files. -### Environment variables +_Note: these files are deliberately gitignored, as they will contain secrets! Never commit them._ -#### Backend +### API -1. Copy the example environment file [.env.example](backend/.env.example) in the backend directory and rename it to `.env`. -1. Replace the `OPENAI_API_KEY` value in the `.env` file with your [OpenAI API key](https://platform.openai.com/account/api-keys). -1. Replace the `SESSION_SECRET` value with a [random UUID](https://www.uuidgenerator.net/). +1. In the backend directory, copy file [.env.example](backend/.env.example) and name the copy `.env`, then open for + editing +2. Set value of `OPENAI_API_KEY` to your [OpenAI API key](https://platform.openai.com/account/api-keys) +3. Set value of `SESSION_SECRET` to a [random UUID](https://www.uuidgenerator.net/) -#### Frontend +### UI -1. Copy the example environment file [.env.example](frontend/.env.example) in the frontend directory and rename it to `.env`. -1. If you've changed the default port for running the API, adjust the `VITE_BACKEND_URL` value accordingly. +1. In the frontend directory, copy file [.env.example](frontend/.env.example) and name the copy `.env` +2. If you've changed the default port for running the server, modify the value of `VITE_BACKEND_URL` accordingly ## Run -### Backend +### Local + +It is easiest to host both API and UI through the server. From project root: ```bash -npm run start:api +npm run build +npm start ``` -### Frontend +Alternatively, to run in Docker we have provided a [Compose file](./compose.yaml) and npm scripts for convenience: ```bash -npm run start:ui +# Run container - image will be built first time this is run, so be patient +npm run docker:start + +# Tail the server logs +npm run docker:logs + +# Stop the container +npm run docker:stop ``` -Note that this project also includes a VS Code launch file, to allow running API and UI directly from the IDE. +In either case you will need the `backend/.env` file, as mentioned above. + +### Remote + +For those wishing to host the application in their own infrastructure, we have provided two Dockerfiles: + +1. [Dockerfile](./backend/Dockerfile) in the backend directory will generate an image for running just the API. If you + intend to deploy to the cloud, this is the one to use. +2. [prodlite.Dockerfile](./prodlite.Dockerfile) in the root directory will generate an image hosting UI and API from the + same server, for convenience. This will get you up and running as quickly as possible. + +In either case, you will need to provide environment vars `OPENAI_API_KEY` and `SESSION_SECRET` (as described in +[Setup Environment](#setup-local-environment) above). + +Please note server-side session storage is currently in-memory, so if you wish to scale the API you will either need to +enable sticky load-balancing, or, modify the code to use a shared storage solution - refer to +[Express-Session](https://www.npmjs.com/package/express-session#compatible-session-stores) for the various options. -## Development +## Develop -For development instructions, see the [frontend](frontend/README.md) and [backend](backend/README.md) READMEs. +For all the hot reloading and React DevTools comforts, you'll want to run UI and API separately in dev mode. +See the [frontend](frontend/README.md) and [backend](backend/README.md) READMEs for instructions. ## Contributing @@ -64,7 +101,7 @@ For development instructions, see the [frontend](frontend/README.md) and [backen Thank you for considering contributing to this open source project! -Please read the our [contributing guide](CONTRIBUTING.md) and our [code of conduct](CODE_OF_CONDUCT.md) before contributing. +Please read the our [contributing guide](CONTRIBUTING.md) and our [code of conduct](CODE_OF_CONDUCT.md) first. ## License diff --git a/backend/.env.example b/backend/.env.example index 905ffccfb..1f6776ca1 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,3 +1,12 @@ +# https://platform.openai.com/docs/quickstart?context=node OPENAI_API_KEY=YOUR_API_KEY -SESSION_SECRET=YOUR_SESSION_SECRET -CORS_ALLOW_ORIGIN=http://localhost:5173 +SESSION_SECRET=CHANGE_ME_FOR_EXAMPLE_GENERATE_A_UUID + +COOKIE_NAME=SpyLogicLocal.sid +SESSION_EXPIRY_HOURS=8 + +# If running UI and API separately, this value must match your UI URL. +# Default in development mode is http://localhost:5173, so you don't +# need to uncomment this unless you change the UI port. +# In a production deployment that needs CORS, you must provide a value. +#CORS_ALLOW_ORIGIN=http://localhost:5173 diff --git a/backend/Dockerfile b/backend/Dockerfile index ffe2f31a3..813dad0b4 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,8 +1,11 @@ FROM node:lts-alpine +ENV NODE_ENV=production + WORKDIR /usr/app COPY package*.json ./ RUN npm ci + COPY . . EXPOSE 3001 RUN npm run build -CMD ["node", "--import=tsx", "./src/server.ts"] +CMD ["node", "--import=tsx", "./src/server/index.ts"] diff --git a/backend/README.md b/backend/README.md index d4d31e361..404130ca1 100644 --- a/backend/README.md +++ b/backend/README.md @@ -1,6 +1,10 @@ # prompt-injection-api -This is the API module of prompt-injection. +This is the backend module of the SpyLogic app. We are using [Express](https://expressjs.com/) to serve the API. + +In `production` (containerised) and `development` modes we only serve the API. However, in `prodlite` mode we also host +the UI through the Express server; the advantages of this are not needing to worry about CORS, and Cookies do not need +to be _secure_, as API and UI are [same origin and same site](https://web.dev/articles/same-site-same-origin). ## Install @@ -10,36 +14,35 @@ npm ci ## Setup Environment -1. Copy the example environment file [.env.example](.env.example) and rename it to `.env`. -1. Replace the `OPENAI_API_KEY` value in `.env` with your - [OpenAI API key](https://platform.openai.com/account/api-keys). -1. Replace the `SESSION_SECRET` value with a [random UUID](https://www.uuidgenerator.net/) or other long phrase. +See the main [README](../README.md) for setting up environment variables. ## Run -Start the server: - -```bash -npm start -``` +### development -Or for development, to hot-reload on changes: +Start the API and hot-reload on changes: ```bash npm run dev ``` -You can also run using Docker if you prefer: +### prodlite + +Start the API _and_ host the UI: ```bash -# Start the server -npm run docker:start -# View the logs -npm run docker:logs -# Stop the server -npm run docker:stop +# If you've not yet built the frontend, do this first: +cd ../frontend && npm run build +cd ../backend + +# Run the app: +npm start ``` +### production + +There is a [Dockerfile](./Dockerfile) for running the API in a container. + ## Linting and Formatting To manually lint and format: @@ -51,6 +54,8 @@ npm run format ## Test +We use [Jest](https://jestjs.io/) for the backend tests. + ```bash npm test ``` diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml deleted file mode 100644 index cd26aedb5..000000000 --- a/backend/docker-compose.yml +++ /dev/null @@ -1,12 +0,0 @@ -services: - prompt-injection-api: - environment: - NODE_ENV: ${NODE_ENV:-development} - OPENAI_API_KEY: ${OPENAI_API_KEY} - SESSION_SECRET: ${SESSION_SECRET} - CORS_ALLOW_ORIGIN: '${CORS_ALLOW_ORIGIN:-*}' - PORT: 3001 - build: . - image: 'scottlogic/prompt-injection-api' - ports: - - '3001:3001' diff --git a/backend/package-lock.json b/backend/package-lock.json index c98a35edd..7a291fcbb 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -25,10 +25,11 @@ "@types/cors": "^2.8.16", "@types/express": "^4.17.21", "@types/express-session": "^1.17.10", - "@types/node": "^20.9.0", + "@types/node": "^20.11.30", "@types/supertest": "^2.0.16", "@typescript-eslint/eslint-plugin": "^6.10.0", "@typescript-eslint/parser": "^6.10.0", + "cross-env": "^7.0.3", "eslint": "^8.53.0", "eslint-import-resolver-alias": "^1.1.2", "eslint-plugin-import": "^2.29.0", @@ -36,7 +37,7 @@ "prettier": "^2.8.8", "supertest": "^6.3.3", "ts-jest": "^29.1.1", - "tsx": "^4.6.2", + "tsx": "^4.7.1", "typescript": "^5.2.2" } }, @@ -761,10 +762,362 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@esbuild/win32-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", - "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", "cpu": [ "x64" ], @@ -1567,9 +1920,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.0.tgz", - "integrity": "sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==", + "version": "20.11.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz", + "integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==", "dependencies": { "undici-types": "~5.26.4" } @@ -2741,6 +3094,24 @@ "optional": true, "peer": true }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -3121,9 +3492,9 @@ } }, "node_modules/esbuild": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", - "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", "dev": true, "hasInstallScript": true, "bin": { @@ -3133,28 +3504,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/android-arm": "0.18.20", - "@esbuild/android-arm64": "0.18.20", - "@esbuild/android-x64": "0.18.20", - "@esbuild/darwin-arm64": "0.18.20", - "@esbuild/darwin-x64": "0.18.20", - "@esbuild/freebsd-arm64": "0.18.20", - "@esbuild/freebsd-x64": "0.18.20", - "@esbuild/linux-arm": "0.18.20", - "@esbuild/linux-arm64": "0.18.20", - "@esbuild/linux-ia32": "0.18.20", - "@esbuild/linux-loong64": "0.18.20", - "@esbuild/linux-mips64el": "0.18.20", - "@esbuild/linux-ppc64": "0.18.20", - "@esbuild/linux-riscv64": "0.18.20", - "@esbuild/linux-s390x": "0.18.20", - "@esbuild/linux-x64": "0.18.20", - "@esbuild/netbsd-x64": "0.18.20", - "@esbuild/openbsd-x64": "0.18.20", - "@esbuild/sunos-x64": "0.18.20", - "@esbuild/win32-arm64": "0.18.20", - "@esbuild/win32-ia32": "0.18.20", - "@esbuild/win32-x64": "0.18.20" + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" } }, "node_modules/escalade": { @@ -4036,6 +4408,20 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -7757,12 +8143,12 @@ } }, "node_modules/tsx": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.6.2.tgz", - "integrity": "sha512-QPpBdJo+ZDtqZgAnq86iY/PD2KYCUPSUGIunHdGwyII99GKH+f3z3FZ8XNFLSGQIA4I365ui8wnQpl8OKLqcsg==", + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.7.1.tgz", + "integrity": "sha512-8d6VuibXHtlN5E3zFkgY8u4DX7Y3Z27zvvPKVmLon/D4AjuKzarkUBTLDBgj9iTQ0hg5xM7c/mYiRVM+HETf0g==", "dev": true, "dependencies": { - "esbuild": "~0.18.20", + "esbuild": "~0.19.10", "get-tsconfig": "^4.7.2" }, "bin": { diff --git a/backend/package.json b/backend/package.json index c56b25471..287f2762f 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,14 +1,11 @@ { "name": "prompt-injection-api", - "version": "0.1.0", + "version": "1.0.0", "type": "module", "scripts": { "build": "tsc --noEmit", - "dev": "tsx watch -r dotenv/config src/server.ts", - "start": "tsx -r dotenv/config src/server.ts", - "docker:start": "docker compose up -d", - "docker:logs": "docker compose logs -f", - "docker:stop": "docker compose down", + "dev": "cross-env NODE_ENV=development tsx watch -r dotenv/config src/server/index.ts", + "start": "cross-env NODE_ENV=prodlite tsx -r dotenv/config src/server/index.ts", "format": "prettier --write .", "lint": "eslint . --fix", "test": "jest" @@ -31,10 +28,11 @@ "@types/cors": "^2.8.16", "@types/express": "^4.17.21", "@types/express-session": "^1.17.10", - "@types/node": "^20.9.0", + "@types/node": "^20.11.30", "@types/supertest": "^2.0.16", "@typescript-eslint/eslint-plugin": "^6.10.0", "@typescript-eslint/parser": "^6.10.0", + "cross-env": "^7.0.3", "eslint": "^8.53.0", "eslint-import-resolver-alias": "^1.1.2", "eslint-plugin-import": "^2.29.0", @@ -42,7 +40,7 @@ "prettier": "^2.8.8", "supertest": "^6.3.3", "ts-jest": "^29.1.1", - "tsx": "^4.6.2", + "tsx": "^4.7.1", "typescript": "^5.2.2" } } diff --git a/backend/src/app.ts b/backend/src/app.ts deleted file mode 100644 index 7bf9a1c21..000000000 --- a/backend/src/app.ts +++ /dev/null @@ -1,18 +0,0 @@ -import cors from 'cors'; -import express from 'express'; -import queryTypes from 'query-types'; - -import nonSessionRoutes from './nonSessionRoutes'; -import sessionRoutes from './sessionRoutes'; - -export default express() - .use(express.json()) - .use(queryTypes.middleware()) - .use( - cors({ - origin: process.env.CORS_ALLOW_ORIGIN, - credentials: true, - }) - ) - .use('/', nonSessionRoutes) - .use('/', sessionRoutes); diff --git a/backend/src/nonSessionRoutes.ts b/backend/src/nonSessionRoutes.ts deleted file mode 100644 index 2497bfa47..000000000 --- a/backend/src/nonSessionRoutes.ts +++ /dev/null @@ -1,19 +0,0 @@ -import express from 'express'; -import { fileURLToPath } from 'node:url'; - -import { handleGetDocuments } from './controller/documentController'; -import { handleHealthCheck } from './controller/healthController'; -import { importMetaUrl } from './importMetaUtils'; - -const router = express.Router(); - -router.use( - '/documents', - express.static( - fileURLToPath(new URL('../resources/documents', importMetaUrl())) - ) -); -router.get('/documents', handleGetDocuments); -router.get('/health', handleHealthCheck); - -export default router; diff --git a/backend/src/server/app.ts b/backend/src/server/app.ts new file mode 100644 index 000000000..4a45be2a8 --- /dev/null +++ b/backend/src/server/app.ts @@ -0,0 +1,41 @@ +import cors from 'cors'; +import express from 'express'; +import { existsSync } from 'node:fs'; +import { env, exit } from 'node:process'; +import { fileURLToPath } from 'node:url'; +import queryTypes from 'query-types'; + +import { importMetaUrl } from './importMetaUtils'; +import nonSessionRoutes from './nonSessionRoutes'; +import sessionRoutes from './sessionRoutes'; +import uiRoutes from './uiRoutes'; + +const app = express().use(express.json()).use(queryTypes.middleware()); + +const isDevelopment = env.NODE_ENV === 'development'; +const isServingUI = env.NODE_ENV === 'prodlite'; + +// Enable CORS if set in env, and always when running in dev mode. +const origin = + env.CORS_ALLOW_ORIGIN ?? (isDevelopment ? 'http://localhost:5173' : null); +if (origin) { + app.use(cors({ origin, credentials: true })); +} + +// API routes +app.use('/api', nonSessionRoutes); +app.use('/api', sessionRoutes); + +// Host the UI? +if (isServingUI) { + const uiBuildPath = fileURLToPath( + new URL('../../../frontend/dist/index.html', importMetaUrl()) + ); + if (!existsSync(uiBuildPath)) { + console.error('UI build not found, cannot serve it!'); + exit(1); + } + app.use('/', uiRoutes); +} + +export default app; diff --git a/backend/src/importMetaUtils.ts b/backend/src/server/importMetaUtils.ts similarity index 100% rename from backend/src/importMetaUtils.ts rename to backend/src/server/importMetaUtils.ts diff --git a/backend/src/server.ts b/backend/src/server/index.ts similarity index 82% rename from backend/src/server.ts rename to backend/src/server/index.ts index 19017bf4c..2713d6945 100644 --- a/backend/src/server.ts +++ b/backend/src/server/index.ts @@ -1,10 +1,12 @@ import { env, exit } from 'node:process'; +import { initDocumentVectors } from '@src/document'; +import { getValidModelsFromOpenAI } from '@src/openai'; + import app from './app'; -import { initDocumentVectors } from './document'; -import { getValidModelsFromOpenAI } from './openai'; -// by default runs on port 3001 -const port = env.PORT ?? String(3001); + +// by default runs on port 3000 +const port = env.PORT ?? String(3000); app.listen(port, () => { // Set API key from environment variable diff --git a/backend/src/server/nonSessionRoutes.ts b/backend/src/server/nonSessionRoutes.ts new file mode 100644 index 000000000..cef55daa6 --- /dev/null +++ b/backend/src/server/nonSessionRoutes.ts @@ -0,0 +1,18 @@ +import express from 'express'; +import { fileURLToPath } from 'node:url'; + +import { handleGetDocuments } from '@src/controller/documentController'; +import { handleHealthCheck } from '@src/controller/healthController'; + +import { importMetaUrl } from './importMetaUtils'; + +export default express + .Router() + .use( + '/documents', + express.static( + fileURLToPath(new URL('../../resources/documents', importMetaUrl())) + ) + ) + .get('/documents', handleGetDocuments) + .get('/health', handleHealthCheck); diff --git a/backend/src/sessionRoutes.ts b/backend/src/server/sessionRoutes.ts similarity index 55% rename from backend/src/sessionRoutes.ts rename to backend/src/server/sessionRoutes.ts index 9f07414d4..3e2edb4d3 100644 --- a/backend/src/sessionRoutes.ts +++ b/backend/src/server/sessionRoutes.ts @@ -1,4 +1,3 @@ -import 'dotenv/config'; import express from 'express'; import session from 'express-session'; import memoryStoreFactory from 'memorystore'; @@ -6,26 +5,26 @@ import memoryStoreFactory from 'memorystore'; import { handleChatToGPT, handleAddInfoToChatHistory, -} from './controller/chatController'; +} from '@src/controller/chatController'; import { handleConfigureDefence, handleDefenceActivation, handleDefenceDeactivation, handleResetDefenceConfigItem, -} from './controller/defenceController'; -import { handleLoadLevel } from './controller/levelController'; +} from '@src/controller/defenceController'; +import { handleLoadLevel } from '@src/controller/levelController'; import { handleConfigureModel, handleSetModel, -} from './controller/modelController'; +} from '@src/controller/modelController'; import { handleResetLevel, handleResetProgress, -} from './controller/resetController'; -import { handleStart } from './controller/startController'; -import { handleTest } from './controller/testController'; -import { defaultChatModel } from './models/chat'; -import { getInitialLevelStates } from './models/level'; +} from '@src/controller/resetController'; +import { handleStart } from '@src/controller/startController'; +import { handleTest } from '@src/controller/testController'; +import { defaultChatModel } from '@src/models/chat'; +import { getInitialLevelStates } from '@src/models/level'; const sessionSigningSecret = process.env.SESSION_SECRET; if (!sessionSigningSecret) { @@ -35,40 +34,45 @@ if (!sessionSigningSecret) { process.exit(1); } -const router = express.Router(); - -const stage = process.env.NODE_ENV; +/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */ +const stage = process.env.NODE_ENV || 'development'; console.log(`env=${stage}`); -const isProd = stage === 'production'; -const cookieStaleHours = isProd ? 2 : 8; +const cookieName = process.env.COOKIE_NAME || 'SpyLogic.sid'; +/* eslint-enable @typescript-eslint/prefer-nullish-coalescing */ + +const isProduction = stage === 'production'; +const cookieStaleHours = Number(process.env.SESSION_EXPIRY_HOURS); +const defaultSessionTTL = 2; const oneHourInMillis = 60 * 60 * 1000; -const maxAge = oneHourInMillis * cookieStaleHours; +const maxAge = + oneHourInMillis * + (Number.isFinite(cookieStaleHours) ? cookieStaleHours : defaultSessionTTL); + +const router = express.Router(); router.use( session({ - name: 'prompt-injection.sid', + name: cookieName, resave: false, saveUninitialized: true, secret: sessionSigningSecret, - // Session storage: currently in-memory but could use Redis in AWS + // In-memory session storage means single instance or sticky sessions! + // There are scalable alternatives: + // https://www.npmjs.com/package/express-session#compatible-session-stores store: new (memoryStoreFactory(session))({ checkPeriod: oneHourInMillis, }), - proxy: isProd, + // Trust proxy servers in production, else secure cookies won't work + proxy: isProduction, cookie: { maxAge, /* https://developer.mozilla.org/en-US/blog/goodbye-third-party-cookies/ - Now that browsers have begun clamping down on non-secure Cookies, we - need to set secure=true in prod, until we can put Route53 in front of both - UI and API and get rid of APIGateway entirely. The showstopper is that - APIGateway is not adding Forwarded headers correctly, so the (secure) - session Cookie is no longer working in Prod. - See - https://repost.aws/questions/QUtBHMaz7IQ6aM4RCBMnJvgw/why-does-apigw-http-api-use-forwarded-header-while-other-services-still-use-x-forwarded-headers + Now browsers have begun clamping down on non-secure Cookies, we must + set secure=true in production, meaning prod deployments must use https */ - sameSite: isProd ? 'none' : 'strict', - secure: isProd, + sameSite: isProduction ? 'none' : 'strict', + secure: isProduction, }, }) ); @@ -108,15 +112,4 @@ router.post('/reset/:level', handleResetLevel); // Testing endpoints router.post('/test/load', handleTest); -// Debugging: log headers in prod for primary routes -if (isProd) { - router.use('/openai', (req, res, next) => { - console.log('Request:', req.path, `secure=${req.secure}`, req.headers); - res.on('finish', () => { - console.log('Response:', req.path, res.getHeaders()); - }); - next(); - }); -} - export default router; diff --git a/backend/src/server/uiRoutes.ts b/backend/src/server/uiRoutes.ts new file mode 100644 index 000000000..6d712301a --- /dev/null +++ b/backend/src/server/uiRoutes.ts @@ -0,0 +1,14 @@ +import express from 'express'; +import { fileURLToPath } from 'node:url'; + +import { importMetaUrl } from './importMetaUtils'; + +export default express.Router().use( + express.static( + fileURLToPath(new URL('../../../frontend/dist', importMetaUrl())), + { + maxAge: '86400000', + immutable: true, + } + ) +); diff --git a/backend/test/api/start.test.ts b/backend/test/api/start.test.ts index bef928f59..eb631b242 100644 --- a/backend/test/api/start.test.ts +++ b/backend/test/api/start.test.ts @@ -2,13 +2,13 @@ import { beforeAll, describe, expect, it, jest } from '@jest/globals'; import { OpenAI } from 'openai'; import request from 'supertest'; -import app from '@src/app'; import { StartResponse } from '@src/models/api/StartGetRequest'; import { LEVEL_NAMES } from '@src/models/level'; +import app from '@src/server/app'; jest.mock('openai'); -const PATH = '/start'; +const PATH = '/api/start'; describe('/start endpoints', () => { const mockListFn = jest.fn(); diff --git a/backend/test/tsconfig.json b/backend/test/tsconfig.json index 634d839bf..0fdc5b3dc 100644 --- a/backend/test/tsconfig.json +++ b/backend/test/tsconfig.json @@ -1,6 +1,10 @@ { "extends": "../tsconfig.json", "compilerOptions": { + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "node", + "isolatedModules": true, "noEmit": true }, "include": ["./**/*.ts", "../typings", "../jest.config.ts"] diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 000000000..a1316baf2 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,15 @@ +services: + spylogic: + environment: + COOKIE_NAME: SpyLogicLite.sid + SESSION_EXPIRY_HOURS: 2 + PORT: 5000 + build: + context: . + dockerfile: prodlite.Dockerfile + image: 'scottlogic/spylogic-playground' + ports: + - '5000:5000' + # When running locally, need .env file for API Key etc + volumes: + - ./backend/.env:/usr/app/backend/.env:ro diff --git a/frontend/.env.example b/frontend/.env.example index 0226d93f3..8bef33d4b 100644 --- a/frontend/.env.example +++ b/frontend/.env.example @@ -1 +1 @@ -VITE_BACKEND_URL=http://localhost:3001/ +VITE_BACKEND_URL=http://localhost:3000/api diff --git a/frontend/README.md b/frontend/README.md index 1ac8be763..00583ff3c 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,6 +1,10 @@ # prompt-injection-ui -This is the UI module of prompt-injection. +This is the UI module of the SpyLogic app. It is a React [SPA](https://en.wikipedia.org/wiki/Single-page_application) without routing, and built to be as +accessible as possible. If you find a problem with keyboard navigation, screen reader usage, colour contrast or any +other accessibility concern, please [open an issue](../CONTRIBUTING.md). + +We are working on making the app useable up to 200% zoom, so please be patient with us! ## Install @@ -10,25 +14,39 @@ npm ci ## Setup Environment -1. Copy the example environment file [.env.example](.env.example) and rename it to `.env`. +1. If you've not done so already, copy file [.env.example](.env.example) and name the copy `.env`. 1. If you've changed the default port for running the API, adjust the `VITE_BACKEND_URL` value accordingly. ## Run We are using [Vite](https://vitejs.dev/) to bootstrap, build and run our UI locally. -To build and run: +### development + +Run the dev server which will hot-reload on changes: ```bash -npm run build -npm run preview -# type 'o' to open your browser at http://localhost:4173/ +npm run dev ``` -Or for development, run the dev server which will hot-reload on changes: +### production + +Note that for simplicity, we recommend hosting the UI through the backend when running locally. Follow instructions in +the main [README](../README.md). + +To build and run the UI separately: ```bash -npm run dev +# Build if you've not done so yet +npm run build + +# Any server can then host the resulting dist/ folder ... + +# Locally you can use Vite Preview +npm run preview # then type 'o' to open browser + +# Or the "serve" package +npx serve dist -l 5000 # then navigate to localhost:5000 in browser ``` ## Linting and Formatting diff --git a/frontend/package.json b/frontend/package.json index 1ca865ead..b141906e8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,13 +1,13 @@ { "name": "prompt-injection-ui", - "version": "0.1.0", + "version": "1.0.0", "type": "module", "scripts": { "dev": "vite --open", "build": "tsc && vite build", + "preview": "vite preview --strictPort 5173", "format": "prettier --write .", "lint": "concurrently \"eslint . --fix\" \"stylelint **/*.css --fix\"", - "preview": "vite preview", "pretest": "tsc --project tsconfig.test.json", "test": "vitest" }, diff --git a/frontend/src/service/backendService.ts b/frontend/src/service/backendService.ts index f60a49252..1e080ad58 100644 --- a/frontend/src/service/backendService.ts +++ b/frontend/src/service/backendService.ts @@ -1,7 +1,8 @@ function getBackendUrl(): string { const url = import.meta.env.VITE_BACKEND_URL; if (!url) throw new Error('VITE_BACKEND_URL env variable not set'); - return url; + + return url.endsWith('/') ? url : `${url}/`; } function makeUrl(path: string): URL { diff --git a/frontend/src/service/startService.ts b/frontend/src/service/startService.ts index f6e61a420..985e963cd 100644 --- a/frontend/src/service/startService.ts +++ b/frontend/src/service/startService.ts @@ -4,7 +4,7 @@ import { sendRequest } from './backendService'; import { getChatMessagesFromDTOResponse } from './chatService'; import { getDefencesFromDTOs } from './defenceService'; -const PATH = 'start/'; +const PATH = 'start'; async function start(level: number) { const response = await sendRequest(`${PATH}?level=${level}`, { diff --git a/k6/package.json b/k6/package.json index e27325218..8ebbdcfed 100644 --- a/k6/package.json +++ b/k6/package.json @@ -1,6 +1,6 @@ { "name": "prompt-injection-k6", - "version": "0.1.0", + "version": "1.0.0", "type": "module", "scripts": { "format": "prettier --write .", diff --git a/package-lock.json b/package-lock.json index b0be91107..6c3517975 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,351 +1,351 @@ { - "name": "prompt-injection", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "prompt-injection", - "version": "0.1.0", - "hasInstallScript": true, - "devDependencies": { - "concurrently": "^8.2.2" - } - }, - "node_modules/@babel/runtime": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz", - "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==", - "dev": true, - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chalk/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/concurrently": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz", - "integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.2", - "date-fns": "^2.30.0", - "lodash": "^4.17.21", - "rxjs": "^7.8.1", - "shell-quote": "^1.8.1", - "spawn-command": "0.0.2", - "supports-color": "^8.1.1", - "tree-kill": "^1.2.2", - "yargs": "^17.7.2" - }, - "bin": { - "conc": "dist/bin/concurrently.js", - "concurrently": "dist/bin/concurrently.js" - }, - "engines": { - "node": "^14.13.0 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" - } - }, - "node_modules/date-fns": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", - "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.21.0" - }, - "engines": { - "node": ">=0.11" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/regenerator-runtime": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", - "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==", - "dev": true - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/shell-quote": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", - "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/spawn-command": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", - "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", - "dev": true - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true, - "bin": { - "tree-kill": "cli.js" - } - }, - "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "engines": { - "node": ">=12" - } - } - } + "name": "prompt-injection", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "prompt-injection", + "version": "1.0.0", + "hasInstallScript": true, + "devDependencies": { + "concurrently": "^8.2.2" + } + }, + "node_modules/@babel/runtime": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz", + "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concurrently": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz", + "integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.2", + "date-fns": "^2.30.0", + "lodash": "^4.17.21", + "rxjs": "^7.8.1", + "shell-quote": "^1.8.1", + "spawn-command": "0.0.2", + "supports-color": "^8.1.1", + "tree-kill": "^1.2.2", + "yargs": "^17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": "^14.13.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/regenerator-runtime": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==", + "dev": true + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/spawn-command": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", + "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", + "dev": true + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + } + } } diff --git a/package.json b/package.json index d7998f54c..b54f734ba 100644 --- a/package.json +++ b/package.json @@ -1,17 +1,20 @@ { - "name": "prompt-injection", - "version": "0.1.0", - "description": "Game to promote awareness of prompt injection attacks on LLM AI models, and teach defensive measures", - "keywords": ["ai", "openai", "llm", "langchain", "chatgpt", "bot", "prompt", "injection", "scottlogic"], - "bugs": { - "url": "https://github.com/ScottLogic/prompt-injection/issues" - }, - "scripts": { - "postinstall": "concurrently \"cd backend && npm ci\" \"cd frontend && npm ci\"", - "start:api": "cd backend && npm start", - "start:ui": "cd frontend && npm run build && npm run preview" - }, - "devDependencies": { - "concurrently": "^8.2.2" - } + "name": "prompt-injection", + "version": "1.0.0", + "description": "Game to promote awareness of prompt injection attacks on LLM AI models, and teach defensive measures", + "keywords": ["ai", "openai", "llm", "langchain", "chatgpt", "bot", "prompt", "injection", "scottlogic", "spylogic"], + "bugs": { + "url": "https://github.com/ScottLogic/prompt-injection/issues" + }, + "scripts": { + "postinstall": "concurrently \"cd backend && npm ci --no-audit\" \"cd frontend && npm ci --no-audit\"", + "build": "concurrently \"cd backend && npm run build\" \"cd frontend && npm run build\"", + "start": "cd backend && npm start", + "docker:start": "docker compose up -d", + "docker:logs": "docker compose logs -f", + "docker:stop": "docker compose down" + }, + "devDependencies": { + "concurrently": "^8.2.2" + } } diff --git a/prodlite.Dockerfile b/prodlite.Dockerfile new file mode 100644 index 000000000..59cab0f36 --- /dev/null +++ b/prodlite.Dockerfile @@ -0,0 +1,27 @@ +FROM node:lts-alpine +ARG BASEDIR=/usr/app +ENV NODE_ENV=prodlite PORT=${PORT:-5000} +ENV VITE_BACKEND_URL=http://localhost:${PORT}/api +RUN echo "Server will run on $VITE_BACKEND_URL" + +# Install deps in separate layer, to utilise docker cache +WORKDIR $BASEDIR +COPY package*.json ./ +COPY frontend/package*.json ./frontend/ +COPY backend/package*.json ./backend/ +RUN npm ci --no-audit + +# Copy frontend sources and build UI in separate layer, to utilise docker cache +WORKDIR $BASEDIR/frontend +COPY frontend ./ +RUN npm run build + +# Copy backend sources +WORKDIR $BASEDIR/backend +COPY backend ./ + +EXPOSE $PORT + +# Preload dotenv module to run locally - env vars via compose file volume +# In remote, mandatory env vars must be provided to the container! +CMD ["node", "--import=tsx", "-r", "dotenv/config", "./src/server/index.ts"]