├── .husky └── pre-commit ├── packages ├── lessons │ ├── json-injection │ │ ├── .env │ │ ├── prisma │ │ │ ├── studio.js │ │ │ ├── migrations │ │ │ │ ├── migration_lock.toml │ │ │ │ └── 20230303175750_init │ │ │ │ │ └── migration.sql │ │ │ ├── schema.prisma │ │ │ └── seed.js │ │ ├── package.json │ │ ├── src │ │ │ ├── index.js │ │ │ └── schema.js │ │ ├── exploit │ │ │ └── index.js │ │ └── README.md │ ├── authors.json │ ├── disable-debug-mode │ │ ├── .env │ │ ├── package.json │ │ ├── src │ │ │ └── index.js │ │ └── README.md │ ├── list-pagination │ │ └── README.md │ ├── unsafe-consumption-of-apis │ │ └── README.md │ ├── improper-inventory-management │ │ └── README.md │ ├── limit-batching │ │ └── README.md │ ├── abort-expensive-queries │ │ └── README.md │ ├── server-side-request-forgery │ │ └── README.md │ ├── lack-of-protection-from-automated-threats │ │ └── README.md │ ├── cors │ │ ├── evil │ │ │ ├── evil.js │ │ │ └── evil.html │ │ ├── package.json │ │ ├── src │ │ │ ├── index.js │ │ │ ├── server.js │ │ │ └── index.html │ │ └── README.md │ ├── broken-object-property-level-authorization │ │ ├── src │ │ │ ├── resolvers.js │ │ │ ├── database.js │ │ │ ├── context.js │ │ │ └── index.js │ │ ├── package.json │ │ └── README.md │ ├── broken-object-level-authorization │ │ ├── package.json │ │ ├── src │ │ │ ├── context.js │ │ │ ├── resolvers.js │ │ │ ├── database.js │ │ │ └── index.js │ │ └── README.md │ ├── unrestricted-resource-consumption │ │ ├── package.json │ │ ├── src │ │ │ └── index.js │ │ └── README.md │ ├── broken-function-level-authorization │ │ ├── package.json │ │ ├── src │ │ │ ├── context.js │ │ │ ├── resolvers.js │ │ │ ├── database.js │ │ │ └── index.js │ │ └── README.md │ ├── broken-authentication │ │ ├── package.json │ │ ├── src │ │ │ └── index.js │ │ └── README.md │ ├── sql-injection │ │ ├── package.json │ │ ├── src │ │ │ ├── index.js │ │ │ ├── database.js │ │ │ └── resolvers.js │ │ └── README.md │ ├── package.json │ └── build.ts └── app │ ├── src │ ├── lib │ │ ├── chalk.ts │ │ ├── Task.svelte │ │ ├── RunCommand.svelte │ │ ├── editor │ │ │ ├── panes │ │ │ │ ├── Markdown.svelte │ │ │ │ ├── Browser.svelte │ │ │ │ ├── index.ts │ │ │ │ ├── Xterm.svelte │ │ │ │ └── File.svelte │ │ │ ├── icons │ │ │ │ ├── Icon.svelte │ │ │ │ ├── categories │ │ │ │ │ ├── Http.svelte │ │ │ │ │ ├── Injection.svelte │ │ │ │ │ ├── Dos.svelte │ │ │ │ │ ├── AccessControl.svelte │ │ │ │ │ ├── InformationDiclosure.svelte │ │ │ │ │ └── Complexity.svelte │ │ │ │ └── FileIcon.svelte │ │ │ ├── shell.ts │ │ │ ├── Split.svelte │ │ │ ├── Explorer.svelte │ │ │ └── Editor.svelte │ │ ├── categories.ts │ │ ├── layout │ │ │ └── nav │ │ │ │ ├── Burger.svelte │ │ │ │ └── ListItems.svelte │ │ └── progress.ts │ ├── lessons │ │ ├── .gitignore │ │ └── index.ts │ ├── routes │ │ ├── +layout.ts │ │ ├── github-image │ │ │ └── [user] │ │ │ │ └── +server.ts │ │ ├── +page.ts │ │ ├── [lesson] │ │ │ ├── +page.ts │ │ │ ├── +page.svelte │ │ │ └── Readme.svelte │ │ ├── +layout.svelte │ │ ├── hello-world │ │ │ └── +page.svelte │ │ ├── Social.svelte │ │ ├── boot.ts │ │ ├── Filters.svelte │ │ ├── Newsletter.svelte │ │ ├── Header.svelte │ │ ├── Progress.svelte │ │ └── +page.svelte │ ├── hooks.server.ts │ ├── app.d.ts │ ├── assets │ │ ├── markdown-content.scss │ │ ├── prism.scss │ │ ├── cup.svg │ │ ├── logo.svg │ │ ├── escape-logo.svg │ │ └── byEscape.svg │ ├── app.html │ └── app.scss │ ├── static │ ├── favicon.ico │ ├── favicon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── Escape_academy_logo.jpg │ ├── certification_badge.png │ └── certification_badge_for_linkedin.jpg │ ├── tsconfig.json │ ├── vite.config.js │ ├── svelte.config.js │ └── package.json ├── .yarnrc.yml ├── .lintignore ├── .lintstagedrc ├── .gitattributes ├── .editorconfig ├── .gitignore ├── .prettierrc ├── .dockerignore ├── Dockerfile ├── .vscode └── settings.json ├── .stylelintrc ├── .github └── workflows │ ├── build.yaml │ └── docker.yaml ├── eslint.config.mjs ├── .eslintrc.cjs ├── package.json └── README.md /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | yarn lint-staged 2 | -------------------------------------------------------------------------------- /packages/lessons/json-injection/.env: -------------------------------------------------------------------------------- 1 | DATABASE_URL="file:./dev.db" 2 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | yarnPath: .yarn/releases/yarn-4.5.0.cjs 4 | -------------------------------------------------------------------------------- /packages/lessons/authors.json: -------------------------------------------------------------------------------- 1 | { 2 | "escape": { "name": "Escape", "github": "Escape-Technologies" } 3 | } 4 | -------------------------------------------------------------------------------- /packages/lessons/disable-debug-mode/.env: -------------------------------------------------------------------------------- 1 | # All variables in this file will be loaded into the environment 2 | -------------------------------------------------------------------------------- /packages/app/src/lib/chalk.ts: -------------------------------------------------------------------------------- 1 | import { Chalk } from 'chalk'; 2 | 3 | export const chalk = new Chalk({ level: 3 }); 4 | -------------------------------------------------------------------------------- /.lintignore: -------------------------------------------------------------------------------- 1 | # Ignore list used by linting tools 2 | .svelte-kit/ 3 | .yarn/ 4 | build/ 5 | node_modules/ 6 | /packages/app/src/lessons/* 7 | -------------------------------------------------------------------------------- /packages/app/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Escape-Technologies/graphql-security-academy/HEAD/packages/app/static/favicon.ico -------------------------------------------------------------------------------- /packages/app/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Escape-Technologies/graphql-security-academy/HEAD/packages/app/static/favicon.png -------------------------------------------------------------------------------- /packages/app/static/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Escape-Technologies/graphql-security-academy/HEAD/packages/app/static/favicon-16x16.png -------------------------------------------------------------------------------- /packages/app/static/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Escape-Technologies/graphql-security-academy/HEAD/packages/app/static/favicon-32x32.png -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "*.{js,ts,svelte}": "eslint --fix", 3 | "*.{css,scss,svelte}": "stylelint --fix", 4 | "*": "prettier --write --ignore-unknown" 5 | } 6 | -------------------------------------------------------------------------------- /packages/app/src/lessons/.gitignore: -------------------------------------------------------------------------------- 1 | # This directory contains artifacts generated by running `yarn build` in `packages/lessons` 2 | * 3 | !.gitignore 4 | !index.js 5 | -------------------------------------------------------------------------------- /packages/app/static/Escape_academy_logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Escape-Technologies/graphql-security-academy/HEAD/packages/app/static/Escape_academy_logo.jpg -------------------------------------------------------------------------------- /packages/app/static/certification_badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Escape-Technologies/graphql-security-academy/HEAD/packages/app/static/certification_badge.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | /.yarn/** linguist-vendored 2 | /.yarn/releases/* binary 3 | /.yarn/plugins/**/* binary 4 | /.pnp.* binary linguist-generated 5 | -------------------------------------------------------------------------------- /packages/app/src/routes/+layout.ts: -------------------------------------------------------------------------------- 1 | import { browser } from '$app/environment'; 2 | 3 | export const load = async () => { 4 | if (browser) await import('./boot.js'); 5 | }; 6 | -------------------------------------------------------------------------------- /packages/app/static/certification_badge_for_linkedin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Escape-Technologies/graphql-security-academy/HEAD/packages/app/static/certification_badge_for_linkedin.jpg -------------------------------------------------------------------------------- /packages/lessons/json-injection/prisma/studio.js: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from '@prisma/client'; 2 | 3 | const prisma = new PrismaClient(); 4 | console.table(await prisma.user.findMany()); 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | 7 | [*.{js,json,yml}] 8 | charset = utf-8 9 | indent_style = space 10 | indent_size = 2 11 | -------------------------------------------------------------------------------- /packages/lessons/json-injection/prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "sqlite" -------------------------------------------------------------------------------- /packages/app/src/lib/Task.svelte: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .yarn/* 2 | !.yarn/patches 3 | !.yarn/plugins 4 | !.yarn/releases 5 | !.yarn/sdks 6 | !.yarn/versions 7 | 8 | .svelte-kit/ 9 | build/ 10 | node_modules/ 11 | 12 | # Specific to lessons 13 | *.db 14 | *.db-journal 15 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "quoteProps": "consistent", 4 | "plugins": [ 5 | "prettier-plugin-jsdoc", 6 | "prettier-plugin-organize-imports", 7 | "prettier-plugin-pkg", 8 | "prettier-plugin-svelte" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .yarn/* 2 | !.yarn/patches 3 | !.yarn/plugins 4 | !.yarn/releases 5 | !.yarn/sdks 6 | !.yarn/versions 7 | 8 | .svelte-kit/ 9 | build/ 10 | node_modules/ 11 | 12 | # Specific to lessons 13 | *.db 14 | *.db-journal 15 | 16 | # Specific to docker 17 | Dockerfile 18 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:23-bullseye-slim AS build 2 | ENV USER=node 3 | ENV HOME=/home/node 4 | USER node 5 | WORKDIR $HOME/academy 6 | 7 | ADD --chown=$USER:$USER . . 8 | 9 | RUN YARN_CHECKSUM_BEHAVIOR=ignore yarn workspaces focus app && yarn build 10 | CMD [ "sh", "-c", "node packages/app/build/index.js" ] 11 | -------------------------------------------------------------------------------- /packages/lessons/list-pagination/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Implement List Pagination ' 3 | description: 'Discover how implementing pagination for list results can lead to optimized database performance and resource usage.' 4 | category: 'Complexity' 5 | difficulty: 'Hard' 6 | owasp: 'API4:2023' 7 | todo: true 8 | --- 9 | -------------------------------------------------------------------------------- /packages/lessons/unsafe-consumption-of-apis/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Secure Third-Party API Interactions' 3 | description: 'Learn to safeguard against injection risks when interacting with APIs that return user-provided data.' 4 | category: 'Injection' 5 | difficulty: 'Hard' 6 | owasp: 'API10:2023' 7 | todo: true 8 | --- 9 | -------------------------------------------------------------------------------- /packages/app/src/lib/RunCommand.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /packages/lessons/improper-inventory-management/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Configure a Secure API Gateway' 3 | description: 'Understand the importance of a well-configured gateway to prevent unauthorized access to your underlying API.' 4 | category: 'Access Control' 5 | difficulty: 'Hard' 6 | owasp: 'API9:2023' 7 | todo: true 8 | --- 9 | -------------------------------------------------------------------------------- /packages/lessons/limit-batching/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Limit Query Batching to Safeguard Resources' 3 | description: 'Explore strategies to cap query batching, thereby minimizing the risk of resource depletion and enhancing API stability.' 4 | category: 'Complexity' 5 | difficulty: 'Hard' 6 | owasp: 'API4:2023' 7 | todo: true 8 | --- 9 | -------------------------------------------------------------------------------- /packages/lessons/abort-expensive-queries/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Abort Expensive Queries for Database Protection' 3 | description: 'Discover how to identify and terminate resource-intensive queries, thereby safeguarding your database from undue stress.' 4 | category: 'DoS' 5 | difficulty: 'Hard' 6 | owasp: 'API4:2023' 7 | todo: true 8 | --- 9 | -------------------------------------------------------------------------------- /packages/lessons/server-side-request-forgery/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Mitigate Server Side Request Forgery' 3 | description: 'Grasp the fundamentals of Server Side Request Forgery (SSRF) and the measures to effectively prevent this vulnerability.' 4 | category: 'Injection' 5 | difficulty: 'Easy' 6 | owasp: 'API6:2023' 7 | todo: true 8 | --- 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cssvar.files": ["packages/app/src/app.scss"], 3 | "cssvar.postcssSyntax": { 4 | "postcss-html": ["svelte"] 5 | }, 6 | "javascript.preferences.importModuleSpecifierEnding": "js", 7 | "js/ts.implicitProjectConfig.checkJs": true, 8 | "typescript.preferences.importModuleSpecifierEnding": "js" 9 | } 10 | -------------------------------------------------------------------------------- /packages/lessons/lack-of-protection-from-automated-threats/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Implement Rate-Limiting for Bot Deterrence' 3 | description: 'Uncover the importance of rate-limiting as a mechanism to deter automated threats and bots from abusing your API.' 4 | category: 'DoS' 5 | difficulty: 'Medium' 6 | owasp: 'API8:2023' 7 | todo: true 8 | --- 9 | -------------------------------------------------------------------------------- /packages/lessons/cors/evil/evil.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { fileURLToPath } from 'node:url'; 3 | 4 | const app = express(); 5 | app.get('/', (req, res) => { 6 | res.sendFile(fileURLToPath(new URL('evil.html', import.meta.url))); 7 | }); 8 | 9 | app.listen(5000, () => { 10 | console.info('Evil server listening on port 5000'); 11 | }); 12 | -------------------------------------------------------------------------------- /packages/app/src/hooks.server.ts: -------------------------------------------------------------------------------- 1 | export const handle = async ({ event, resolve }) => { 2 | const response = await resolve(event); 3 | response.headers.set('Cross-Origin-Embedder-Policy', 'require-corp'); 4 | response.headers.set('Cross-Origin-Opener-Policy', 'same-origin'); 5 | response.headers.set('X-Content-Type-Options', 'nosniff'); 6 | return response; 7 | }; 8 | -------------------------------------------------------------------------------- /packages/app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "outDir": "build", 9 | "resolveJsonModule": true, 10 | "skipLibCheck": true, 11 | "sourceMap": true, 12 | "strict": true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/lessons/broken-object-property-level-authorization/src/resolvers.js: -------------------------------------------------------------------------------- 1 | import { getUsers } from './database.js'; 2 | 3 | export const Query = { 4 | users: getUsers, 5 | me: (_, args, context) => context.user, 6 | }; 7 | 8 | export const User = { 9 | id: (user) => user.id, 10 | name: (user) => user.name, 11 | location: (user) => { 12 | return user.location; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /packages/lessons/json-injection/prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | generator client { 2 | provider = "prisma-client-js" 3 | } 4 | 5 | datasource db { 6 | provider = "sqlite" 7 | url = env("DATABASE_URL") 8 | } 9 | 10 | model User { 11 | id Int @id @default(autoincrement()) 12 | firstName String 13 | lastName String 14 | email String @unique 15 | apiKey String @unique 16 | } 17 | -------------------------------------------------------------------------------- /packages/app/src/routes/github-image/[user]/+server.ts: -------------------------------------------------------------------------------- 1 | import { redirect } from '@sveltejs/kit'; 2 | 3 | /** 4 | * Redirects to a GitHub user image. It is not possible to do it client side 5 | * because of `Cross-Origin-Embedder-Policy`. 6 | */ 7 | export const GET = async ({ params, fetch }) => { 8 | const response = await fetch(`https://github.com/${params.user}.png`); 9 | redirect(302, response.url); 10 | }; 11 | -------------------------------------------------------------------------------- /packages/lessons/cors/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cors-lesson", 3 | "type": "module", 4 | "private": true, 5 | "scripts": { 6 | "evil": "node evil/evil.js", 7 | "start": "node --watch src/index.js" 8 | }, 9 | "dependencies": { 10 | "@apollo/server": "^4.11.0", 11 | "body-parser": "^1.20.3", 12 | "cors": "^2.8.5", 13 | "express": "^4.21.1", 14 | "graphql": "^16.9.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/app/src/lib/editor/panes/Markdown.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 | 11 |
12 | 13 | 21 | -------------------------------------------------------------------------------- /packages/lessons/broken-object-level-authorization/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "broken-object-level-authorization-lesson", 3 | "type": "module", 4 | "private": true, 5 | "scripts": { 6 | "start": "node --watch src/index.js" 7 | }, 8 | "dependencies": { 9 | "@apollo/server": "^4.11.0", 10 | "@graphql-yoga/render-graphiql": "^5.7.0", 11 | "body-parser": "^1.20.3", 12 | "express": "^4.21.1", 13 | "graphql": "^16.9.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/lessons/unrestricted-resource-consumption/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "unrestricted-resource-consumption-lesson", 3 | "type": "module", 4 | "private": true, 5 | "scripts": { 6 | "start": "node --watch src/index.js" 7 | }, 8 | "dependencies": { 9 | "@apollo/server": "^4.11.0", 10 | "@graphql-yoga/render-graphiql": "^5.7.0", 11 | "body-parser": "^1.20.3", 12 | "express": "^4.21.1", 13 | "graphql": "^16.9.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/lessons/broken-function-level-authorization/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "broken-function-level-authorization-lesson", 3 | "type": "module", 4 | "private": true, 5 | "scripts": { 6 | "start": "node --watch src/index.js" 7 | }, 8 | "dependencies": { 9 | "@apollo/server": "^4.11.0", 10 | "@graphql-yoga/render-graphiql": "^5.7.0", 11 | "body-parser": "^1.20.3", 12 | "express": "^4.21.1", 13 | "graphql": "^16.9.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/lessons/disable-debug-mode/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "disable-debug-mode-lesson", 3 | "type": "module", 4 | "private": true, 5 | "scripts": { 6 | "start": "dotenv node src/index.js" 7 | }, 8 | "dependencies": { 9 | "@apollo/server": "^4.11.0", 10 | "@graphql-yoga/render-graphiql": "^5.7.0", 11 | "body-parser": "^1.20.3", 12 | "dotenv-cli": "^7.4.2", 13 | "express": "^4.21.1", 14 | "graphql": "^16.9.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/lessons/broken-authentication/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "broken-authentication-lesson", 3 | "type": "module", 4 | "private": true, 5 | "scripts": { 6 | "start": "node --watch src/index.js" 7 | }, 8 | "dependencies": { 9 | "@apollo/server": "^4.11.0", 10 | "@graphql-yoga/render-graphiql": "^5.7.0", 11 | "argon2-browser": "^1.18.0", 12 | "body-parser": "^1.20.3", 13 | "express": "^4.21.1", 14 | "graphql": "^16.9.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/lessons/broken-object-property-level-authorization/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "broken-object-property-level-authorization-lesson", 3 | "type": "module", 4 | "private": true, 5 | "scripts": { 6 | "start": "node --watch src/index.js" 7 | }, 8 | "dependencies": { 9 | "@apollo/server": "^4.11.0", 10 | "@graphql-yoga/render-graphiql": "^5.7.0", 11 | "body-parser": "^1.20.3", 12 | "express": "^4.21.1", 13 | "graphql": "^16.9.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "stylelint-config-standard-scss", 4 | "stylelint-config-recess-order" 5 | ], 6 | "overrides": [ 7 | { 8 | "files": ["*.svelte"], 9 | "customSyntax": "postcss-html", 10 | "rules": { 11 | "selector-pseudo-class-no-unknown": [ 12 | true, 13 | { "ignorePseudoClasses": ["global"] } 14 | ] 15 | } 16 | } 17 | ], 18 | "ignoreFiles": ["**/*", "!**/*.{css,scss,svelte}"] 19 | } 20 | -------------------------------------------------------------------------------- /packages/lessons/json-injection/prisma/migrations/20230303175750_init/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "User" ( 3 | "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, 4 | "firstName" TEXT NOT NULL, 5 | "lastName" TEXT NOT NULL, 6 | "email" TEXT NOT NULL, 7 | "apiKey" TEXT NOT NULL 8 | ); 9 | 10 | -- CreateIndex 11 | CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); 12 | 13 | -- CreateIndex 14 | CREATE UNIQUE INDEX "User_apiKey_key" ON "User"("apiKey"); 15 | -------------------------------------------------------------------------------- /packages/lessons/sql-injection/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sql-injection-lesson", 3 | "type": "module", 4 | "private": true, 5 | "scripts": { 6 | "start": "node --watch src/index.js" 7 | }, 8 | "dependencies": { 9 | "@apollo/server": "^4.11.0", 10 | "@graphql-yoga/render-graphiql": "^5.7.0", 11 | "argon2-browser": "^1.18.0", 12 | "body-parser": "^1.20.3", 13 | "express": "^4.21.1", 14 | "graphql": "^16.9.0", 15 | "sqlite3": "^5.1.7" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/lessons/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lessons", 3 | "type": "module", 4 | "private": true, 5 | "workspaces": [ 6 | "*" 7 | ], 8 | "scripts": { 9 | "build": "tsx build.ts", 10 | "dev": "yarn build && chokidar '**' -c 'yarn build'" 11 | }, 12 | "devDependencies": { 13 | "@types/node": "^22.7.6", 14 | "@webcontainer/api": "^1.4.0", 15 | "chokidar-cli": "^3.0.0", 16 | "tsx": "^4.19.1" 17 | }, 18 | "volta": { 19 | "extends": "../../package.json" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/lessons/broken-object-property-level-authorization/src/database.js: -------------------------------------------------------------------------------- 1 | /** The `users` table. */ 2 | const users = new Map([ 3 | ['1', { name: 'Alice', location: 'Tokyo, Japan' }], 4 | ['2', { name: 'Bob', location: 'San Francisco, US' }], 5 | ['3', { name: 'Eve', location: 'Toulouse, France' }], 6 | ]); 7 | 8 | export const getUser = (id) => { 9 | const user = users.get(id); 10 | if (user) return { id, ...user }; 11 | }; 12 | export const getUsers = () => [...users].map(([id, user]) => ({ id, ...user })); 13 | -------------------------------------------------------------------------------- /packages/app/src/app.d.ts: -------------------------------------------------------------------------------- 1 | import type { WebContainer } from '@webcontainer/api'; 2 | import 'unplugin-icons/types/svelte'; 3 | 4 | // See https://kit.svelte.dev/docs/types#app 5 | // for information about these interfaces 6 | declare global { 7 | namespace App { 8 | // interface Error {} 9 | // interface Locals {} 10 | interface PageData { 11 | title?: string; 12 | } 13 | // interface Platform {} 14 | } 15 | 16 | interface Window { 17 | webcontainer: Promise; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/lessons/broken-function-level-authorization/src/context.js: -------------------------------------------------------------------------------- 1 | import { getUser } from './database.js'; 2 | 3 | /** 4 | * Produces a context object for the GraphQL server. 5 | * 6 | * @param {import('express').Request} req 7 | */ 8 | export const getContext = (req) => { 9 | if (req.headers.authorization) { 10 | const [type, credentials] = req.headers.authorization.split(' '); 11 | if (type === 'Bearer') { 12 | // Simple authorization: the user is identified their id 13 | return { user: getUser(credentials) }; 14 | } 15 | } 16 | return {}; 17 | }; 18 | -------------------------------------------------------------------------------- /packages/lessons/broken-object-level-authorization/src/context.js: -------------------------------------------------------------------------------- 1 | import { getUser } from './database.js'; 2 | 3 | /** 4 | * Produces a context object for the GraphQL server. 5 | * 6 | * @param {import('express').Request} req 7 | */ 8 | export const getContext = (req) => { 9 | if (req.headers.authorization) { 10 | const [type, credentials] = req.headers.authorization.split(' '); 11 | if (type === 'Bearer') { 12 | // Simple authorization: the user is identified their id 13 | return { user: getUser(credentials) }; 14 | } 15 | } 16 | return {}; 17 | }; 18 | -------------------------------------------------------------------------------- /packages/lessons/broken-object-property-level-authorization/src/context.js: -------------------------------------------------------------------------------- 1 | import { getUser } from './database.js'; 2 | 3 | /** 4 | * Produces a context object for the GraphQL server. 5 | * 6 | * @param {import('express').Request} req 7 | */ 8 | export const getContext = (req) => { 9 | if (req.headers.authorization) { 10 | const [type, credentials] = req.headers.authorization.split(' '); 11 | if (type === 'Bearer') { 12 | // Simple authorization: the user is identified their id 13 | return { user: getUser(credentials) }; 14 | } 15 | } 16 | return {}; 17 | }; 18 | -------------------------------------------------------------------------------- /packages/app/src/routes/+page.ts: -------------------------------------------------------------------------------- 1 | import { lessons } from '$lessons'; 2 | 3 | export const trailingSlash = 'always'; 4 | 5 | export const load = async () => ({ 6 | lessons: await Promise.all( 7 | [...lessons].map(async ([path, load]) => { 8 | const { metadata } = await load(); 9 | return { path, ...metadata }; 10 | }), 11 | ), 12 | title: 'API Security Academy – Master GraphQL API vulnerabilities', 13 | description: 14 | 'The API Security Academy is a set of free and interactive online modules that will teach you how to secure your GraphQL applications.', 15 | }); 16 | -------------------------------------------------------------------------------- /packages/app/vite.config.js: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import icons from 'unplugin-icons/vite'; 3 | import { defineConfig } from 'vite'; 4 | 5 | export default defineConfig({ 6 | plugins: [ 7 | icons({ compiler: 'svelte', defaultClass: 'icon', scale: 1.5 }), 8 | sveltekit(), 9 | ], 10 | server: { 11 | headers: { 12 | 'Cross-Origin-Embedder-Policy': 'require-corp', 13 | 'Cross-Origin-Opener-Policy': 'same-origin', 14 | }, 15 | }, 16 | css: { 17 | preprocessorOptions: { 18 | scss: { api: 'modern' }, 19 | }, 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: ['main'] 6 | pull_request: 7 | branches: ['main'] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: volta-cli/action@v3 15 | - uses: actions/cache@v3 16 | with: 17 | path: | 18 | .yarn 19 | node_modules 20 | key: yarn-${{ runner.os }}-${{ hashFiles('yarn.lock') }} 21 | - run: YARN_CHECKSUM_BEHAVIOR=ignore yarn install --immutable 22 | - run: yarn build 23 | - run: yarn lint 24 | -------------------------------------------------------------------------------- /packages/lessons/cors/src/index.js: -------------------------------------------------------------------------------- 1 | import { expressMiddleware } from '@apollo/server/express4'; 2 | import bodyParser from 'body-parser'; 3 | import cors from 'cors'; 4 | import express from 'express'; 5 | import { fileURLToPath } from 'node:url'; 6 | import { server } from './server.js'; 7 | 8 | await server.start(); 9 | 10 | const app = express(); 11 | 12 | app.get('/', (req, res) => { 13 | res.sendFile(fileURLToPath(new URL('index.html', import.meta.url))); 14 | }); 15 | app.post('/graphql', cors(), bodyParser.json(), expressMiddleware(server)); 16 | 17 | app.listen(4000, () => { 18 | console.info('Server ready on port 4000'); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/app/src/lib/editor/icons/Icon.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | {#if type === 'file'} 13 | 14 | {:else if type === 'browser'} 15 | 16 | {:else if type === 'markdown'} 17 | 18 | {:else if type === 'terminal'} 19 | 20 | {/if} 21 | -------------------------------------------------------------------------------- /packages/lessons/json-injection/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "json-injection-lesson", 3 | "type": "module", 4 | "private": true, 5 | "scripts": { 6 | "exploit": "node exploit/index.js", 7 | "start": "node --watch src/index.js", 8 | "studio": "node prisma/studio.js" 9 | }, 10 | "dependencies": { 11 | "@graphql-yoga/render-graphiql": "^5.7.0", 12 | "@prisma/client": "^5.21.1", 13 | "@whatwg-node/fetch": "^0.9.21", 14 | "graphql": "^16.9.0", 15 | "graphql-scalars": "^1.23.0", 16 | "graphql-yoga": "^5.7.0", 17 | "prisma": "^5.21.1" 18 | }, 19 | "prisma": { 20 | "seed": "node prisma/seed.js" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/app/src/assets/markdown-content.scss: -------------------------------------------------------------------------------- 1 | :where(.markdown-content) { 2 | padding: 0 0.5em; 3 | 4 | > :where(*) { 5 | width: 50rem; 6 | max-width: 100%; 7 | margin: 1rem auto; 8 | } 9 | 10 | table { 11 | min-width: 100%; 12 | 13 | tr { 14 | height: 1px; 15 | } 16 | 17 | tr + tr, 18 | tbody > tr { 19 | border-top: 1px solid gray; 20 | } 21 | 22 | td, 23 | th { 24 | height: inherit; 25 | padding: 0.25rem; 26 | vertical-align: top; 27 | } 28 | } 29 | 30 | h1, 31 | h2, 32 | h3, 33 | h4, 34 | h5, 35 | h6 { 36 | margin: 2rem auto 1rem; 37 | 38 | > a { 39 | margin-right: 0.5rem; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/lessons/json-injection/src/index.js: -------------------------------------------------------------------------------- 1 | import { renderGraphiQL } from '@graphql-yoga/render-graphiql'; 2 | import { createYoga } from 'graphql-yoga'; 3 | import { createServer } from 'node:http'; 4 | import { schema } from './schema.js'; 5 | 6 | const yoga = createYoga({ 7 | schema, 8 | renderGraphiQL, 9 | graphiql: { 10 | defaultQuery: /* GraphQL */ ` 11 | query { 12 | findUsers(where: { email: { endsWith: "@example.com" } }) { 13 | firstName 14 | lastName 15 | email 16 | } 17 | } 18 | `, 19 | }, 20 | }); 21 | 22 | const server = createServer(yoga); 23 | 24 | server.listen(4000, () => { 25 | console.info('Server ready on port 4000'); 26 | }); 27 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import eslint from '@eslint/js'; 2 | import svelte from 'eslint-plugin-svelte'; 3 | import globals from 'globals'; 4 | import tseslint from 'typescript-eslint'; 5 | 6 | export default tseslint.config( 7 | eslint.configs.recommended, 8 | ...tseslint.configs.recommended, 9 | ...svelte.configs['flat/recommended'], 10 | { 11 | languageOptions: { 12 | globals: { 13 | ...globals.browser, 14 | ...globals.node, 15 | }, 16 | }, 17 | }, 18 | { 19 | files: ['**/*.svelte'], 20 | languageOptions: { 21 | parserOptions: { 22 | parser: tseslint.parser, 23 | }, 24 | }, 25 | }, 26 | { 27 | ignores: ['**/build/', '**/.svelte-kit/', '**/dist/'], 28 | }, 29 | ); 30 | -------------------------------------------------------------------------------- /packages/app/src/routes/[lesson]/+page.ts: -------------------------------------------------------------------------------- 1 | import { lessons } from '$lessons'; 2 | import { error } from '@sveltejs/kit'; 3 | import type { FileSystemTree } from '@webcontainer/api'; 4 | 5 | export const load = async ({ params }) => { 6 | const load = lessons.get(params.lesson); 7 | if (!load) error(404, `Lesson ${params.lesson} found`); 8 | // https://github.com/sveltejs/kit/issues/9296 9 | const [files, readme] = await Promise.all([ 10 | import(`../../lessons/${params.lesson}/files.json`).then( 11 | (module) => module.default as FileSystemTree, 12 | ), 13 | load(), 14 | ]); 15 | return { 16 | readme, 17 | files, 18 | title: readme.metadata.title, 19 | description: readme.metadata.description, 20 | }; 21 | }; 22 | -------------------------------------------------------------------------------- /packages/app/src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | {$page.data.title} 8 | 9 | 14 | 15 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import('eslint').Linter.Config} */ 2 | module.exports = { 3 | root: true, 4 | parser: '@typescript-eslint/parser', 5 | extends: [ 6 | 'eslint:recommended', 7 | 'plugin:@typescript-eslint/recommended', 8 | 'plugin:@typescript-eslint/strict', 9 | 'plugin:svelte/recommended', 10 | 'prettier', 11 | ], 12 | plugins: ['@typescript-eslint'], 13 | overrides: [ 14 | { 15 | files: ['*.svelte'], 16 | parser: 'svelte-eslint-parser', 17 | parserOptions: { parser: '@typescript-eslint/parser' }, 18 | rules: { 19 | 'svelte/valid-compile': 'warn', 20 | }, 21 | }, 22 | ], 23 | parserOptions: { 24 | sourceType: 'module', 25 | ecmaVersion: 2020, 26 | }, 27 | env: { browser: true, es2017: true, node: true }, 28 | }; 29 | -------------------------------------------------------------------------------- /packages/app/src/routes/hello-world/+page.svelte: -------------------------------------------------------------------------------- 1 |
2 | 👋 3 |

Hello World!

4 |
5 | 6 | 42 | -------------------------------------------------------------------------------- /packages/app/src/lib/editor/panes/Browser.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
9 |
{ 11 | context.url = value; 12 | }} 13 | > 14 | 15 | 16 |
17 |