commit 52b200574bf7c2b00d143f58f1957113a7fcb54c Author: shafi54 <108669266+shafi-aviz@users.noreply.github.com> Date: Tue Aug 18 19:38:47 2026 +0530 first commit diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 0000000..855e008 --- /dev/null +++ b/DEPLOYMENT.md @@ -0,0 +1,135 @@ +# Deploying Cerebrus to Cloud Run + +Two independent Cloud Run services, built with **Cloud Build** → **Artifact +Registry**, region **us-central1**. Each app's `.env` is **baked into its image** +(no Secret Manager). + +- `cerebrus-backend` — Express/Bun API (`oven/bun` runtime, listens on `$PORT`). +- `cerebrus-frontend` — Vite SPA served by a small **Bun** server (built assets, `$PORT`). + +> Secrets are baked into the images, so **keep the Artifact Registry repo private** +> — anyone who can pull an image can read its `.env`. + +## Prerequisites + +- `gcloud` CLI authenticated (`gcloud auth login`) with Owner/Editor on the project. +- A GCP **project id** (billing enabled). +- Supabase `DATABASE_URL`, WorkOS app, and a GitHub OAuth App (see `apps/backend/README.md`). + +## One-time: create the env files + +```bash +cp apps/backend/.env.production.example apps/backend/.env.production +cp apps/frontend/.env.production.example apps/frontend/.env.production +``` + +Fill in the secrets now (WorkOS, Supabase, GitHub, `APP_ENCRYPTION_KEY`). Leave the +URL fields at their placeholder for the first deploy — you'll set them in step 2. +These files are gitignored; they only ever exist locally and inside the images. + +## Deploy (two-phase, because each service needs the other's URL) + +### Phase 1 — first deploy to learn the URLs +```bash +./deploy.sh +``` +This enables APIs, creates the `cerebrus` Artifact Registry repo, grants Cloud +Build deploy permission, builds + deploys both services, and prints their URLs, e.g.: + +``` +cerebrus-backend https://cerebrus-backend-abc123-uc.a.run.app +cerebrus-frontend https://cerebrus-frontend-def456-uc.a.run.app +``` + +> **One origin.** The browser only ever talks to the **frontend** URL. The Bun +> server in the frontend image reverse-proxies `/auth` and `/api` to the backend, +> so the session cookie stays first-party (cross-site `*.run.app` cookies are +> blocked by browsers). So every public URL you configure is the **frontend** URL — +> and the frontend needs the backend URL to proxy to (`BACKEND_URL`, set once below). + +`deploy.sh` builds and deploys the backend, frontend, and scanner job. It reads +`BACKEND_URL` from `apps/frontend/.env.production` and sets it on the frontend +Cloud Run service automatically. You manage the rest of the config via the +`.env.production` files (backend config is baked into its image). + +### Phase 2 — wire the URLs and redeploy +1. Edit **`apps/backend/.env.production`**: + - `NODE_ENV=production` + - `FRONTEND_URL=` + - `WORKOS_REDIRECT_URI=/auth/callback` + - `GITHUB_REDIRECT_URI=/api/github/callback` + - `SCAN_CALLBACK_URL=` (must be the backend, not the frontend, + because the scanner calls `/internal/*`) +2. **`apps/frontend/.env.production`**: + - `BACKEND_URL=` + - leave `VITE_API_URL` unset (same-origin) +3. Update the external dashboards (all on the **frontend** origin): + - **WorkOS**: add `/auth/callback` as a Redirect URI, and set the + sign-out redirect to ``. + - **GitHub OAuth App**: set the Authorization callback URL to + `/api/github/callback`. +4. Redeploy. `deploy.sh` now reads `BACKEND_URL` from `apps/frontend/.env.production` + and sets it on the frontend Cloud Run service automatically. It also builds and + deploys the scanner Cloud Run job used when `SCAN_RUNNER=cloudrun`: + ```bash + ./deploy.sh + ``` + To skip the scanner job, run with `DEPLOY_SCANNER=0 ./deploy.sh `. + +## Database + +Supabase Postgres is publicly reachable, so Cloud Run connects with no extra +networking. Apply migrations once (from your machine, against the same `DATABASE_URL`): + +```bash +cd apps/backend && bun run db:migrate +``` + +## Cloud Run scanner permissions (when `SCAN_RUNNER=cloudrun`) + +The backend Cloud Run service launches the scanner Cloud Run job. It authenticates +as the service account whose key is inlined in `GOOGLE_SERVICE_ACCOUNT_JSON`. + +1. In `apps/backend/.env.production`, remove any `GOOGLE_APPLICATION_CREDENTIALS` + line and add the full service-account key JSON on one line: + ```env + GOOGLE_SERVICE_ACCOUNT_JSON={"type":"service_account","project_id":"..."} + ``` +2. Grant that service account permission to run jobs: + ```bash + SA_EMAIL="cerebrus-job-exec@local-volt-486423-t2.iam.gserviceaccount.com" + PROJECT_ID=local-volt-486423-t2 + + gcloud projects add-iam-policy-binding "$PROJECT_ID" \ + --member="serviceAccount:${SA_EMAIL}" --role="roles/run.admin" --condition=None + + gcloud projects add-iam-policy-binding "$PROJECT_ID" \ + --member="serviceAccount:${SA_EMAIL}" --role="roles/iam.serviceAccountUser" --condition=None + ``` + +For local dev you can keep using `GOOGLE_APPLICATION_CREDENTIALS=/path/to/key.json` +instead of inlining the JSON. + +## How env is handled + +| | Where it's read | How it's configured | +| --- | --- | --- | +| Backend | runtime (`bun` auto-loads `/app/.env`) | `COPY apps/backend/.env.production ./.env` in the Dockerfile | +| Frontend build | **build time** (Vite inlines `VITE_*`) | `vite build` reads `apps/frontend/.env.production` | +| Frontend runtime | runtime (`process.env.BACKEND_URL`) | `deploy.sh` reads `BACKEND_URL` from `apps/frontend/.env.production` and sets it on the Cloud Run service | + +`PORT` is **not** set in `.env.production` — Cloud Run injects `8080`, and a real +env var always wins over the file. The session cookie stays **first-party** +because the browser only talks to the frontend origin (the Bun server proxies `/auth` + +`/api` to the backend) — no cross-site/third-party cookies involved. + +## Notes + +- `deploy.sh` now sets the frontend's `BACKEND_URL` Cloud Run env var automatically + from `apps/frontend/.env.production` on every deploy. +- Build context is the **repo root** (the bun workspace must resolve); the + Dockerfiles live in each app but are built with `-f apps//Dockerfile .`. +- `.gcloudignore` exists so Cloud Build keeps `.env.production` in the upload + (otherwise gcloud falls back to `.gitignore`, which excludes all `.env*`). +- Services are deployed `--allow-unauthenticated` (public web app). Remove that + flag in the cloudbuild configs to require IAM auth. diff --git a/apps/backend/.env.example b/apps/backend/.env.example new file mode 100644 index 0000000..7368127 --- /dev/null +++ b/apps/backend/.env.example @@ -0,0 +1,69 @@ +# Server +PORT=3001 +NODE_ENV=development + +# Frontend origin (CORS + post-login redirect target) +FRONTEND_URL=http://localhost:5173 + +# WorkOS AuthKit — https://dashboard.workos.com +# Configure WORKOS_REDIRECT_URI as a Redirect URI in the WorkOS dashboard. +WORKOS_API_KEY= +WORKOS_CLIENT_ID= +# Must be at least 32 characters. Generate: openssl rand -base64 32 +WORKOS_COOKIE_PASSWORD= +WORKOS_REDIRECT_URI=http://localhost:3001/auth/callback + +# Optional local-only identity used by Docker/E2E runs when WorkOS credentials +# are intentionally unavailable. Ignored when NODE_ENV=production. +LOCAL_DEV_AUTH_EMAIL= +LOCAL_DEV_AUTH_FIRST_NAME=Guest +LOCAL_DEV_AUTH_LAST_NAME= + +# Supabase Postgres connection string (BACKEND ONLY — never expose to the browser). +# Supabase dashboard → Project Settings → Database → Connection string (URI). +# Use the connection pooler URI for serverless / many short connections. +DATABASE_URL= + +# GitHub OAuth App — https://github.com/settings/developers (OAuth Apps). +# Set the app's "Authorization callback URL" to GITHUB_REDIRECT_URI. +# The 'repo' scope (used to list private repos) grants broad repo access. +GITHUB_CLIENT_ID= +GITHUB_CLIENT_SECRET= +GITHUB_REDIRECT_URI=http://localhost:3001/api/github/callback + +# Secret used to encrypt stored GitHub tokens at rest (any random string). +# Generate: openssl rand -base64 32 +APP_ENCRYPTION_KEY= + +# Scanner (apps/cli). SCAN_RUNNER: inline (in-process, default for dev — no Docker) | +# docker (run the container locally) | cloudrun (trigger the Cloud Run Job). +SCAN_RUNNER=inline +# Backend URL the scanner POSTs results to (docker/cloudrun modes). +# For docker, use http://host.docker.internal:3001 so the container can reach the host. +SCAN_CALLBACK_URL=http://localhost:3001 +# Shared secret for /internal/scan-result (required for docker/cloudrun). +SCAN_CALLBACK_SECRET= +# docker mode: the built scanner image (e.g. cerebrus-scanner). +SCANNER_IMAGE= +# cloudrun mode: the Cloud Run Job to execute. +GCP_PROJECT= +GCP_REGION=us-central1 +SCAN_JOB_NAME=cerebrus-scanner + +# DeepSeek — the LLM that performs the file-by-file vulnerability analysis + chaining. +# Required for a real scan (docker/cloudrun pass it to the container; inline reads it here). +DEEPSEEK_API_KEY= +DEEPSEEK_MODEL=deepseek-reasoner +DEEPSEEK_BASE_URL=https://api.deepseek.com +# Max files analyzed in parallel (deepseek-reasoner is slow — keep this modest). +DEEPSEEK_CONCURRENCY=4 + +# Per-scan logs are written to /scan-.log (host-readable). +SCAN_LOG_DIR=./scan-logs + +# Ecosystems for the optional bulk `cve:sync` (on-demand OSV querybatch needs no config). +OSV_ECOSYSTEMS=npm,PyPI,Go,crates.io,Maven,RubyGems + +# Auto-fix: after a scan finds vulnerabilities, open a PR on the user's repo applying +# the suggested fixes (uses the user's GitHub token). Set to "false" to disable. +SCAN_AUTOFIX=true diff --git a/apps/backend/.env.production.example b/apps/backend/.env.production.example new file mode 100644 index 0000000..a7c0861 --- /dev/null +++ b/apps/backend/.env.production.example @@ -0,0 +1,46 @@ +# Production env BAKED into the backend image (no Secret Manager). +# Copy to .env.production and fill in. Keep .env.production out of git (it holds secrets). +# PORT is intentionally omitted — Cloud Run injects it (8080) and that wins over this file. +# +# IMPORTANT: the browser only ever talks to ONE origin — the FRONTEND service URL. +# The frontend (nginx) reverse-proxies /auth and /api to the backend, so the +# session cookie stays first-party. Every public URL below is the FRONTEND URL. + +NODE_ENV=production + +# The deployed FRONTEND service URL (set after the first deploy). The single +# public origin. Exact origin, no trailing slash. +FRONTEND_URL=https://cerebrus-frontend-XXXXXXXX-uc.a.run.app + +# WorkOS — https://dashboard.workos.com +WORKOS_API_KEY= +WORKOS_CLIENT_ID= +WORKOS_COOKIE_PASSWORD= +# FRONTEND URL + /auth/callback (proxied to the backend). Register this exact URL +# as a Redirect URI in the WorkOS dashboard. +WORKOS_REDIRECT_URI=https://cerebrus-frontend-XXXXXXXX-uc.a.run.app/auth/callback + +# Supabase Postgres connection string (use the pooler URI). +DATABASE_URL= + +# GitHub OAuth App — https://github.com/settings/developers +GITHUB_CLIENT_ID= +GITHUB_CLIENT_SECRET= +# FRONTEND URL + /api/github/callback (proxied to the backend). Set this exact URL +# as the OAuth App's Authorization callback URL. +GITHUB_REDIRECT_URI=https://cerebrus-frontend-XXXXXXXX-uc.a.run.app/api/github/callback + +# Encrypts stored GitHub tokens at rest. openssl rand -base64 32 +APP_ENCRYPTION_KEY= + +# Scanner: run scans as Cloud Run Jobs in production. +SCAN_RUNNER=cloudrun +# The deployed BACKEND service URL the scanner POSTs results to. +SCAN_CALLBACK_URL=https://cerebrus-backend-XXXXXXXX-uc.a.run.app +# Shared secret the scanner sends as x-scan-secret. openssl rand -base64 32 +SCAN_CALLBACK_SECRET= +# Cloud Run Job to execute (deployed via apps/cli/cloudbuild.yaml). The backend's +# service account needs run.developer + run.jobsExecutorWithOverrides on the job. +GCP_PROJECT= +GCP_REGION=us-central1 +SCAN_JOB_NAME=cerebrus-scanner diff --git a/apps/backend/Dockerfile b/apps/backend/Dockerfile new file mode 100644 index 0000000..c36daf5 --- /dev/null +++ b/apps/backend/Dockerfile @@ -0,0 +1,31 @@ +# syntax=docker/dockerfile:1 +# Build context is the repo ROOT (so the bun workspace resolves). + +# ---- Build: install workspace deps and bundle the backend to one file ---- +FROM oven/bun:1.3.14 AS build +WORKDIR /repo + +# Manifests first for cached installs. +COPY package.json bun.lock turbo.json ./ +COPY packages/typescript-config/package.json ./packages/typescript-config/ +COPY packages/eslint-config/package.json ./packages/eslint-config/ +COPY apps/backend/package.json ./apps/backend/ +COPY apps/frontend/package.json ./apps/frontend/ +COPY apps/cli/package.json ./apps/cli/ +RUN bun install --frozen-lockfile + +# Sources, then bundle to a self-contained dist/index.js. +COPY . . +RUN cd apps/backend && bun build src/index.ts --target bun --outdir dist + +# ---- Runtime: minimal image with the bundle + baked env ---- +FROM oven/bun:1.3.14-slim AS runtime +WORKDIR /app +ENV NODE_ENV=production +COPY --from=build /repo/apps/backend/dist ./dist +# Bake the production env into the image (no Secret Manager). Cloud Run injects +# PORT at runtime, which takes precedence over any value in this file. +COPY apps/backend/.env.production ./.env +EXPOSE 8080 +USER bun +CMD ["bun", "dist/index.js"] diff --git a/apps/backend/README.md b/apps/backend/README.md new file mode 100644 index 0000000..856615d --- /dev/null +++ b/apps/backend/README.md @@ -0,0 +1,68 @@ +# @cerebrus/backend + +Express + TypeScript API (run by Bun) that owns **WorkOS AuthKit** authentication +and all database access via **Drizzle ORM** over **Supabase Postgres**. + +The browser never talks to Supabase directly — `DATABASE_URL` lives only here. + +## Setup + +```bash +cp .env.example .env # fill in the values below +bun install # from the repo root +``` + +### Environment + +| Var | Notes | +| --- | --- | +| `PORT` | Default `3001`. | +| `FRONTEND_URL` | Browser origin, used for CORS and the post-login redirect. | +| `WORKOS_API_KEY` / `WORKOS_CLIENT_ID` | From the [WorkOS dashboard](https://dashboard.workos.com). | +| `WORKOS_COOKIE_PASSWORD` | 32+ chars. `openssl rand -base64 32`. | +| `WORKOS_REDIRECT_URI` | Must also be registered as a Redirect URI in WorkOS (default `http://localhost:3001/auth/callback`). | +| `DATABASE_URL` | Supabase Postgres connection string (use the pooler URI). | +| `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET` | From a [GitHub OAuth App](https://github.com/settings/developers). | +| `GITHUB_REDIRECT_URI` | Must equal the OAuth App's Authorization callback URL (default `http://localhost:3001/api/github/callback`). | +| `APP_ENCRYPTION_KEY` | Encrypts stored GitHub tokens at rest. `openssl rand -base64 32`. | + +### GitHub OAuth App + +Create one at GitHub → Settings → Developer settings → **OAuth Apps** → New OAuth App. +Set **Authorization callback URL** to `GITHUB_REDIRECT_URI`. Copy the client id and a +generated client secret into `.env`. The connection requests the `repo` scope so we +can list **private** repositories — note this grants broad repo access (an OAuth App +has no read-only private scope; use a GitHub App if you need least privilege). + +Auth and DB are independent: the server boots with neither set and reports status +at `GET /health`. Each integration activates as soon as its vars are present. + +## Run + +```bash +bun run dev # watch mode (also runs via `bun run dev` at the repo root, alongside the frontend) +bun run start # one-off +``` + +## Database (Drizzle + Supabase) + +```bash +bun run db:generate # SQL migration from src/db/schema.ts -> src/db/migrations (offline) +bun run db:migrate # apply migrations to DATABASE_URL +bun run db:push # push schema directly (dev convenience) +``` + +## Routes + +- `GET /health` — liveness + `{ authConfigured, dbConfigured }`. +- `GET /auth/login` — redirect to WorkOS AuthKit. +- `GET /auth/callback` — exchange code, set sealed-session cookie, upsert user. +- `GET /auth/me` — `{ user | null, configured }`. +- `GET /auth/logout` — clear cookie, redirect through WorkOS logout. +- `GET /api/me` — **protected**; WorkOS user + their Supabase row. +- `GET /api/github/status` — **protected**; `{ configured, connected, login }`. +- `GET /api/github/connect` — **protected**; start GitHub OAuth. +- `GET /api/github/callback` — store the encrypted token, redirect to the app. +- `GET /api/github/repos` — **protected**; the user's repos (public + private), each flagged `connected`. +- `POST /api/github/repos/connect` · `POST /api/github/repos/disconnect` — **protected**; toggle a project. +- `GET /api/github/projects` — **protected**; the user's connected repositories. diff --git a/apps/backend/cloudbuild.yaml b/apps/backend/cloudbuild.yaml new file mode 100644 index 0000000..396f91e --- /dev/null +++ b/apps/backend/cloudbuild.yaml @@ -0,0 +1,26 @@ +# Build + push + deploy the backend. Run from the repo root: +# gcloud builds submit --config apps/backend/cloudbuild.yaml \ +# --substitutions=_REGION=us-central1,_IMAGE=us-central1-docker.pkg.dev/PROJECT/cerebrus/backend . +steps: + - name: gcr.io/cloud-builders/docker + args: ["build", "-f", "apps/backend/Dockerfile", "-t", "${_IMAGE}:${BUILD_ID}", "-t", "${_IMAGE}:latest", "."] + - name: gcr.io/cloud-builders/docker + args: ["push", "--all-tags", "${_IMAGE}"] + - name: gcr.io/google.com/cloudsdktool/cloud-sdk + entrypoint: gcloud + args: + - "run" + - "deploy" + - "cerebrus-backend" + - "--image=${_IMAGE}:${BUILD_ID}" + - "--region=${_REGION}" + - "--platform=managed" + - "--port=8080" + - "--allow-unauthenticated" +images: + - "${_IMAGE}" +substitutions: + _REGION: "us-central1" + _IMAGE: "us-central1-docker.pkg.dev/${PROJECT_ID}/cerebrus/backend" +options: + logging: CLOUD_LOGGING_ONLY diff --git a/apps/backend/drizzle.config.ts b/apps/backend/drizzle.config.ts new file mode 100644 index 0000000..66d4c8a --- /dev/null +++ b/apps/backend/drizzle.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from "drizzle-kit"; + +// drizzle-kit reads DATABASE_URL for `migrate`/`push`. `generate` works offline +// (no connection needed), so an empty fallback is fine for codegen. +export default defineConfig({ + dialect: "postgresql", + schema: "./src/db/schema.ts", + out: "./src/db/migrations", + dbCredentials: { + url: process.env.DATABASE_URL ?? "", + }, +}); diff --git a/apps/backend/eslint.config.js b/apps/backend/eslint.config.js new file mode 100644 index 0000000..a523e24 --- /dev/null +++ b/apps/backend/eslint.config.js @@ -0,0 +1,3 @@ +import base from "@cerebrus/eslint-config/base"; + +export default base; diff --git a/apps/backend/package.json b/apps/backend/package.json new file mode 100644 index 0000000..e6a9c94 --- /dev/null +++ b/apps/backend/package.json @@ -0,0 +1,42 @@ +{ + "name": "@cerebrus/backend", + "private": true, + "version": "0.0.0", + "type": "module", + "main": "src/index.ts", + "scripts": { + "dev": "bun --watch src/index.ts", + "start": "bun src/index.ts", + "build": "bun build src/index.ts --target bun --outdir dist", + "check-types": "tsc --noEmit", + "lint": "eslint .", + "test": "bun test src", + "db:generate": "drizzle-kit generate", + "db:migrate": "drizzle-kit migrate", + "db:push": "drizzle-kit push" + }, + "dependencies": { + "@cerebrus/cli": "*", + "@workos-inc/node": "^7.69.0", + "cookie-parser": "^1.4.7", + "cors": "^2.8.5", + "drizzle-orm": "^0.44.6", + "express": "^5.1.0", + "pino": "^9.6.0", + "pino-http": "^10.4.0", + "postgres": "^3.4.7", + "zod": "^4.1.12" + }, + "devDependencies": { + "@cerebrus/eslint-config": "*", + "@cerebrus/typescript-config": "*", + "@types/cookie-parser": "^1.4.9", + "@types/cors": "^2.8.19", + "@types/express": "^5.0.3", + "@types/node": "^24.13.2", + "drizzle-kit": "^0.31.5", + "eslint": "^10.5.0", + "pino-pretty": "^13.0.0", + "typescript": "~6.0.2" + } +} diff --git a/apps/backend/src/cve/match.ts b/apps/backend/src/cve/match.ts new file mode 100644 index 0000000..1e735cd --- /dev/null +++ b/apps/backend/src/cve/match.ts @@ -0,0 +1,98 @@ +import { logger } from "../lib/logger"; +import { advisoriesForPackages, upsertAdvisories } from "../db/advisories"; +import { canonicalEcosystem, cveIdFromAliases, fetchVulns, fixedVersionFor, queryBatch, toAdvisoryRow } from "./osv"; +import { firstFixedVersion, versionIsAffected, type OsvAffectedLike } from "./semver"; + +export interface DepInput { + ecosystem: string; + name: string; + version: string; +} + +export interface CveMatch { + ecosystem: string; + name: string; + version: string; + osvId: string; + cveId?: string | null; + severity?: string | null; + summary?: string | null; + fixedVersion?: string | null; +} + +// Given the dependencies parsed from a repo's manifests, returns the known CVEs +// affecting them. Primary path: OSV querybatch (authoritative, server-side version +// matching) + enrichment fetch, caching every advisory into Postgres so the local +// CVE DB grows over time. If OSV is unreachable, falls back to the cached advisories +// with the vendored version matcher. +export async function matchAdvisories(deps: DepInput[]): Promise { + if (deps.length === 0) return []; + try { + return await matchViaOsv(deps); + } catch (err) { + logger.warn({ err }, "OSV lookup failed — falling back to cached advisories"); + return matchViaCache(deps); + } +} + +async function matchViaOsv(deps: DepInput[]): Promise { + const idsPerDep = await queryBatch(deps); + const allIds = idsPerDep.flat(); + if (allIds.length === 0) return []; + + const records = await fetchVulns(allIds); + + // Cache/enrich every advisory we resolved, keyed to the dep that surfaced it. + const rows = []; + for (let i = 0; i < deps.length; i++) { + for (const id of idsPerDep[i] ?? []) { + const rec = records.get(id); + if (rec) rows.push(toAdvisoryRow(rec, deps[i].ecosystem, deps[i].name)); + } + } + await upsertAdvisories(rows).catch((err) => logger.warn({ err }, "advisory cache upsert failed")); + + const matches: CveMatch[] = []; + for (let i = 0; i < deps.length; i++) { + const dep = deps[i]; + for (const id of idsPerDep[i] ?? []) { + const rec = records.get(id); + matches.push({ + ecosystem: canonicalEcosystem(dep.ecosystem), + name: dep.name, + version: dep.version, + osvId: id, + cveId: rec ? cveIdFromAliases(rec) : null, + severity: rec ? toAdvisoryRow(rec, dep.ecosystem, dep.name).severity : null, + summary: rec?.summary ?? null, + fixedVersion: rec ? fixedVersionFor(rec, dep.ecosystem, dep.name) : null, + }); + } + } + return matches; +} + +async function matchViaCache(deps: DepInput[]): Promise { + const advisories = await advisoriesForPackages(deps.map((d) => ({ ecosystem: canonicalEcosystem(d.ecosystem), name: d.name }))); + const matches: CveMatch[] = []; + for (const dep of deps) { + const canon = canonicalEcosystem(dep.ecosystem).toLowerCase(); + for (const adv of advisories) { + if (adv.packageName !== dep.name || adv.ecosystem.toLowerCase() !== canon) continue; + const ranges = adv.rangesRaw as OsvAffectedLike; + if (!versionIsAffected(dep.version, ranges)) continue; + const aliases = (adv.aliases as string[] | null) ?? []; + matches.push({ + ecosystem: adv.ecosystem, + name: dep.name, + version: dep.version, + osvId: adv.osvId, + cveId: aliases.find((a) => a.startsWith("CVE-")) ?? adv.osvId, + severity: adv.severity, + summary: adv.summary, + fixedVersion: firstFixedVersion(ranges), + }); + } + } + return matches; +} diff --git a/apps/backend/src/cve/osv.ts b/apps/backend/src/cve/osv.ts new file mode 100644 index 0000000..eeb9a49 --- /dev/null +++ b/apps/backend/src/cve/osv.ts @@ -0,0 +1,146 @@ +import { logger } from "../lib/logger"; +import type { NewAdvisory } from "../db/schema"; +import { firstFixedVersion, type OsvAffectedLike } from "./semver"; + +// Thin client for OSV.dev (https://osv.dev). `querybatch` does authoritative, +// server-side version matching and returns the vuln ids affecting each queried +// {package, version}; we then fetch full records to enrich + cache them. +const OSV_BASE = "https://api.osv.dev/v1"; + +export interface OsvPackageQuery { + ecosystem: string; + name: string; + version: string; +} + +interface OsvSeverity { + type?: string; + score?: string; +} +export interface OsvAffected extends OsvAffectedLike { + package?: { ecosystem?: string; name?: string }; + database_specific?: { severity?: string }; +} +export interface OsvRecord { + id: string; + modified?: string; + summary?: string; + details?: string; + aliases?: string[]; + severity?: OsvSeverity[]; + affected?: OsvAffected[]; + database_specific?: { severity?: string }; +} + +// Maps OSV's ecosystem casing. Manifests give us lowercase-ish keys; OSV uses +// canonical names (npm, PyPI, Go, crates.io, Maven, RubyGems, Packagist, ...). +const ECOSYSTEM_CANON: Record = { + npm: "npm", + pypi: "PyPI", + go: "Go", + "crates.io": "crates.io", + cargo: "crates.io", + maven: "Maven", + rubygems: "RubyGems", + gem: "RubyGems", + packagist: "Packagist", + composer: "Packagist", +}; + +export function canonicalEcosystem(ecosystem: string): string { + return ECOSYSTEM_CANON[ecosystem.toLowerCase()] ?? ecosystem; +} + +// POST /querybatch — returns, per query (index-aligned), the vuln ids affecting it. +export async function queryBatch(queries: OsvPackageQuery[]): Promise { + if (queries.length === 0) return []; + const body = { + queries: queries.map((q) => ({ + package: { ecosystem: canonicalEcosystem(q.ecosystem), name: q.name }, + version: q.version, + })), + }; + const res = await fetch(`${OSV_BASE}/querybatch`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }); + if (!res.ok) throw new Error(`OSV querybatch failed (${res.status}): ${await res.text()}`); + const data = (await res.json()) as { results?: { vulns?: { id: string }[] }[] }; + return (data.results ?? []).map((r) => (r.vulns ?? []).map((v) => v.id)); +} + +// GET /vulns/{id} — full record for enrichment/caching. +export async function fetchVuln(id: string): Promise { + const res = await fetch(`${OSV_BASE}/vulns/${encodeURIComponent(id)}`); + if (!res.ok) throw new Error(`OSV vuln fetch failed for ${id} (${res.status})`); + return (await res.json()) as OsvRecord; +} + +// Best-effort severity label from an OSV record (GHSA-style database_specific first, +// else a coarse bucket from a CVSS base score if present). +function deriveSeverity(record: OsvRecord, affected?: OsvAffected): string | null { + const raw = affected?.database_specific?.severity ?? record.database_specific?.severity; + if (raw) return raw.toLowerCase(); + const cvss = record.severity?.find((s) => s.type?.startsWith("CVSS"))?.score; + const score = cvss ? Number.parseFloat(cvss) : NaN; + if (!Number.isNaN(score)) { + if (score >= 9) return "critical"; + if (score >= 7) return "high"; + if (score >= 4) return "medium"; + return "low"; + } + return null; +} + +function pickAffected(record: OsvRecord, ecosystem: string, name: string): OsvAffected | undefined { + const canon = canonicalEcosystem(ecosystem); + return record.affected?.find( + (a) => a.package?.name === name && (a.package?.ecosystem ?? "").toLowerCase() === canon.toLowerCase(), + ); +} + +export function cveIdFromAliases(record: OsvRecord): string | null { + return record.aliases?.find((a) => a.startsWith("CVE-")) ?? record.id; +} + +// Maps a full OSV record to an `advisories` row for a specific queried package. +export function toAdvisoryRow(record: OsvRecord, ecosystem: string, name: string): NewAdvisory { + const affected = pickAffected(record, ecosystem, name); + return { + osvId: record.id, + ecosystem: canonicalEcosystem(ecosystem), + packageName: name, + severity: deriveSeverity(record, affected), + summary: record.summary ?? null, + details: record.details ?? null, + aliases: record.aliases ?? [], + rangesRaw: { ranges: affected?.ranges ?? [], versions: affected?.versions ?? [] }, + modified: record.modified ? new Date(record.modified) : null, + }; +} + +export function fixedVersionFor(record: OsvRecord, ecosystem: string, name: string): string | null { + const affected = pickAffected(record, ecosystem, name); + return affected ? firstFixedVersion(affected) : null; +} + +// Fetches many vuln records with bounded concurrency; failures are logged and skipped. +export async function fetchVulns(ids: string[]): Promise> { + const out = new Map(); + const unique = [...new Set(ids)]; + const limit = 6; + for (let i = 0; i < unique.length; i += limit) { + const batch = unique.slice(i, i + limit); + const records = await Promise.all( + batch.map((id) => + fetchVuln(id).catch((err) => { + logger.warn({ err, id }, "OSV vuln fetch failed"); + return null; + }), + ), + ); + for (const rec of records) if (rec) out.set(rec.id, rec); + } + return out; +} diff --git a/apps/backend/src/cve/semver.ts b/apps/backend/src/cve/semver.ts new file mode 100644 index 0000000..4eb5bd0 --- /dev/null +++ b/apps/backend/src/cve/semver.ts @@ -0,0 +1,80 @@ +// A deliberately minimal version comparator + OSV-range matcher. OSV's querybatch +// API does authoritative version matching server-side; this is only the offline +// fallback used when OSV is unreachable but we have cached advisories. It handles +// dotted numeric versions and simple introduced/fixed/last_affected ranges. Complex +// pre-release / build-metadata semantics are best-effort (documented limitation). + +function normalize(version: string): string { + return version.trim().replace(/^[v=]+/, "").replace(/^\D*/, ""); +} + +// Splits into numeric-ish parts; non-numeric segments compare as 0 so we degrade +// gracefully rather than throw on odd version strings. +function parts(version: string): number[] { + return normalize(version) + .split(/[.+-]/) + .map((p) => { + const n = Number.parseInt(p, 10); + return Number.isNaN(n) ? 0 : n; + }); +} + +export function compareVersions(a: string, b: string): number { + const pa = parts(a); + const pb = parts(b); + const len = Math.max(pa.length, pb.length); + for (let i = 0; i < len; i++) { + const diff = (pa[i] ?? 0) - (pb[i] ?? 0); + if (diff !== 0) return diff < 0 ? -1 : 1; + } + return 0; +} + +interface OsvEvent { + introduced?: string; + fixed?: string; + last_affected?: string; +} +interface OsvRange { + type?: string; + events?: OsvEvent[]; +} +export interface OsvAffectedLike { + ranges?: OsvRange[]; + versions?: string[]; +} + +// Is `version` affected by this OSV affected-blob? Exact `versions[]` membership +// wins; otherwise we walk each range's introduced→fixed/last_affected windows. +export function versionIsAffected(version: string, affected: OsvAffectedLike): boolean { + const v = normalize(version); + if (affected.versions?.some((x) => normalize(x) === v)) return true; + + for (const range of affected.ranges ?? []) { + let affectedInWindow = false; + for (const ev of range.events ?? []) { + if (ev.introduced !== undefined) { + // "introduced" opens a window; assume affected until a later fixed/last_affected closes it. + if (compareVersions(v, ev.introduced) >= 0) affectedInWindow = true; + } + if (ev.fixed !== undefined && affectedInWindow) { + if (compareVersions(v, ev.fixed) >= 0) affectedInWindow = false; + } + if (ev.last_affected !== undefined && affectedInWindow) { + if (compareVersions(v, ev.last_affected) > 0) affectedInWindow = false; + } + } + if (affectedInWindow) return true; + } + return false; +} + +// First "fixed" version across the affected ranges, if any (used as the upgrade hint). +export function firstFixedVersion(affected: OsvAffectedLike): string | null { + for (const range of affected.ranges ?? []) { + for (const ev of range.events ?? []) { + if (ev.fixed) return ev.fixed; + } + } + return null; +} diff --git a/apps/backend/src/db/advisories.ts b/apps/backend/src/db/advisories.ts new file mode 100644 index 0000000..3b75eef --- /dev/null +++ b/apps/backend/src/db/advisories.ts @@ -0,0 +1,38 @@ +import { and, inArray, sql } from "drizzle-orm"; +import { getDb } from "./client"; +import { advisories, type Advisory, type NewAdvisory } from "./schema"; + +// Upserts advisories keyed by osvId. On conflict, refreshes the record only when +// the incoming `modified` is newer (or unknown), so re-syncing is cheap and safe. +export async function upsertAdvisories(rows: NewAdvisory[]): Promise { + if (rows.length === 0) return; + await getDb() + .insert(advisories) + .values(rows) + .onConflictDoUpdate({ + target: advisories.osvId, + set: { + ecosystem: sql`excluded.ecosystem`, + packageName: sql`excluded.package_name`, + severity: sql`excluded.severity`, + summary: sql`excluded.summary`, + details: sql`excluded.details`, + aliases: sql`excluded.aliases`, + rangesRaw: sql`excluded.ranges_raw`, + modified: sql`excluded.modified`, + updatedAt: sql`now()`, + }, + }); +} + +// All cached advisories for the given (ecosystem, package) pairs. Ecosystem match +// is case-insensitive since OSV and manifests disagree on casing (npm vs NPM). +export async function advisoriesForPackages(pairs: { ecosystem: string; name: string }[]): Promise { + if (pairs.length === 0) return []; + const names = [...new Set(pairs.map((p) => p.name))]; + const ecosystems = [...new Set(pairs.map((p) => p.ecosystem.toLowerCase()))]; + return getDb() + .select() + .from(advisories) + .where(and(inArray(advisories.packageName, names), inArray(sql`lower(${advisories.ecosystem})`, ecosystems))); +} diff --git a/apps/backend/src/db/client.ts b/apps/backend/src/db/client.ts new file mode 100644 index 0000000..7263cda --- /dev/null +++ b/apps/backend/src/db/client.ts @@ -0,0 +1,20 @@ +import { drizzle, type PostgresJsDatabase } from "drizzle-orm/postgres-js"; +import postgres from "postgres"; +import { env } from "../env"; +import * as schema from "./schema"; + +let dbInstance: PostgresJsDatabase | undefined; + +// Lazily creates the Drizzle client over Supabase Postgres. The connection is +// only opened on first use, so the server boots fine when DATABASE_URL is unset. +// `prepare: false` keeps it compatible with Supabase's transaction pooler. +export function getDb(): PostgresJsDatabase { + if (!env.dbConfigured) { + throw Object.assign(new Error("Database is not configured. Set DATABASE_URL."), { status: 503 }); + } + if (!dbInstance) { + const sql = postgres(env.DATABASE_URL as string, { prepare: false }); + dbInstance = drizzle(sql, { schema }); + } + return dbInstance; +} diff --git a/apps/backend/src/db/findings.ts b/apps/backend/src/db/findings.ts new file mode 100644 index 0000000..82e84d5 --- /dev/null +++ b/apps/backend/src/db/findings.ts @@ -0,0 +1,49 @@ +import { asc, eq, sql } from "drizzle-orm"; +import { getDb } from "./client"; +import { findings, findingChains, scans, type Finding, type FindingChain, type NewFinding, type NewFindingChain } from "./schema"; + +// Appends findings for a scan. Idempotent on id so retried streaming batches (the +// scanner supplies stable ids) don't duplicate rows. Bumps the scan's findingCount +// by however many rows were actually inserted. +export async function insertFindings(scanId: string, rows: NewFinding[]): Promise { + if (rows.length === 0) return; + const inserted = await getDb() + .insert(findings) + .values(rows.map((row) => ({ ...row, scanId }))) + .onConflictDoNothing({ target: findings.id }) + .returning({ id: findings.id }); + if (inserted.length > 0) { + await getDb() + .update(scans) + .set({ findingCount: sql`${scans.findingCount} + ${inserted.length}` }) + .where(eq(scans.id, scanId)); + } +} + +export async function insertChains(scanId: string, rows: NewFindingChain[]): Promise { + if (rows.length === 0) return; + await getDb() + .insert(findingChains) + .values(rows.map((row) => ({ ...row, scanId }))); +} + +// Patches progress fields on the scan row (files analyzed so far, stage). Also +// flips a queued scan to running the first time progress arrives. +export async function updateScanProgress( + scanId: string, + patch: { filesScanned?: number; fileCount?: number; stage?: string }, +): Promise { + const set: Record = { status: sql`case when ${scans.status} = 'queued' then 'running' else ${scans.status} end` }; + if (patch.filesScanned !== undefined) set.filesScanned = patch.filesScanned; + if (patch.fileCount !== undefined) set.fileCount = patch.fileCount; + if (patch.stage !== undefined) set.stage = patch.stage; + await getDb().update(scans).set(set).where(eq(scans.id, scanId)); +} + +export async function findingsForScan(scanId: string): Promise { + return getDb().select().from(findings).where(eq(findings.scanId, scanId)).orderBy(asc(findings.createdAt)); +} + +export async function chainsForScan(scanId: string): Promise { + return getDb().select().from(findingChains).where(eq(findingChains.scanId, scanId)).orderBy(asc(findingChains.createdAt)); +} diff --git a/apps/backend/src/db/fix.ts b/apps/backend/src/db/fix.ts new file mode 100644 index 0000000..1150826 --- /dev/null +++ b/apps/backend/src/db/fix.ts @@ -0,0 +1,42 @@ +import { eq } from "drizzle-orm"; +import { getDb } from "./client"; +import { connectedRepositories, githubConnections, scans } from "./schema"; + +export interface FixStatusPatch { + fixStatus: "creating" | "opened" | "no_fixes" | "failed"; + fixPrUrl?: string | null; + fixPrNumber?: number | null; + fixedCount?: number | null; + skippedCount?: number | null; + fixError?: string | null; +} + +export async function setFixStatus(scanId: string, patch: FixStatusPatch): Promise { + await getDb().update(scans).set(patch).where(eq(scans.id, scanId)); +} + +export interface ScanContext { + owner: string; + name: string; + defaultBranch: string | null; + accessTokenEncrypted: string; +} + +// Resolves everything the fix step needs from just a scanId: the target repo and the +// owning user's encrypted GitHub token. Returns null if the repo/scan is gone or the +// user has no GitHub connection (nothing to push with). +export async function loadScanContext(scanId: string): Promise { + const [row] = await getDb() + .select({ + owner: connectedRepositories.owner, + name: connectedRepositories.name, + defaultBranch: connectedRepositories.defaultBranch, + accessTokenEncrypted: githubConnections.accessTokenEncrypted, + }) + .from(scans) + .innerJoin(connectedRepositories, eq(connectedRepositories.id, scans.connectedRepositoryId)) + .innerJoin(githubConnections, eq(githubConnections.userId, connectedRepositories.userId)) + .where(eq(scans.id, scanId)) + .limit(1); + return row ?? null; +} diff --git a/apps/backend/src/db/images.ts b/apps/backend/src/db/images.ts new file mode 100644 index 0000000..d155700 --- /dev/null +++ b/apps/backend/src/db/images.ts @@ -0,0 +1,48 @@ +import { and, desc, eq } from "drizzle-orm"; +import { getDb } from "./client"; +import { connectedImages, type ConnectedImage, type NewConnectedImage } from "./schema"; + +// Persists an image the user picked to scan. Idempotent on the (user, registry, repo, +// tag) unique key — reconnecting returns the existing row. +export async function connectImage(row: NewConnectedImage): Promise { + const [inserted] = await getDb().insert(connectedImages).values(row).onConflictDoNothing().returning(); + if (inserted) return inserted; + const [existing] = await getDb() + .select() + .from(connectedImages) + .where( + and( + eq(connectedImages.userId, row.userId), + eq(connectedImages.registryConnectionId, row.registryConnectionId), + eq(connectedImages.repository, row.repository), + eq(connectedImages.tag, row.tag ?? "latest"), + ), + ) + .limit(1); + return existing; +} + +export async function listImages(userId: string): Promise { + return getDb() + .select() + .from(connectedImages) + .where(eq(connectedImages.userId, userId)) + .orderBy(desc(connectedImages.connectedAt)); +} + +export async function getImage(userId: string, id: string): Promise { + const [row] = await getDb() + .select() + .from(connectedImages) + .where(and(eq(connectedImages.id, id), eq(connectedImages.userId, userId))) + .limit(1); + return row ?? null; +} + +export async function deleteImage(userId: string, id: string): Promise { + await getDb().delete(connectedImages).where(and(eq(connectedImages.id, id), eq(connectedImages.userId, userId))); +} + +export async function updateImageDigest(id: string, digest: string): Promise { + await getDb().update(connectedImages).set({ lastDigest: digest }).where(eq(connectedImages.id, id)); +} diff --git a/apps/backend/src/db/migrations/0000_nappy_darkstar.sql b/apps/backend/src/db/migrations/0000_nappy_darkstar.sql new file mode 100644 index 0000000..8dc992d --- /dev/null +++ b/apps/backend/src/db/migrations/0000_nappy_darkstar.sql @@ -0,0 +1,10 @@ +CREATE TABLE "users" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "workos_user_id" text NOT NULL, + "email" text NOT NULL, + "first_name" text, + "last_name" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "users_workos_user_id_unique" UNIQUE("workos_user_id") +); diff --git a/apps/backend/src/db/migrations/0001_parched_mercury.sql b/apps/backend/src/db/migrations/0001_parched_mercury.sql new file mode 100644 index 0000000..37c61b1 --- /dev/null +++ b/apps/backend/src/db/migrations/0001_parched_mercury.sql @@ -0,0 +1,29 @@ +CREATE TABLE "connected_repositories" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "user_id" uuid NOT NULL, + "github_repo_id" text NOT NULL, + "full_name" text NOT NULL, + "name" text NOT NULL, + "owner" text NOT NULL, + "private" boolean DEFAULT false NOT NULL, + "default_branch" text, + "html_url" text, + "connected_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "connected_repositories_user_repo_unique" UNIQUE("user_id","github_repo_id") +); +--> statement-breakpoint +CREATE TABLE "github_connections" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "user_id" uuid NOT NULL, + "github_user_id" text NOT NULL, + "github_login" text NOT NULL, + "access_token_encrypted" text NOT NULL, + "scope" text, + "token_type" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "github_connections_user_id_unique" UNIQUE("user_id") +); +--> statement-breakpoint +ALTER TABLE "connected_repositories" ADD CONSTRAINT "connected_repositories_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "github_connections" ADD CONSTRAINT "github_connections_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; \ No newline at end of file diff --git a/apps/backend/src/db/migrations/0002_pink_alex_power.sql b/apps/backend/src/db/migrations/0002_pink_alex_power.sql new file mode 100644 index 0000000..6f455ff --- /dev/null +++ b/apps/backend/src/db/migrations/0002_pink_alex_power.sql @@ -0,0 +1,11 @@ +CREATE TABLE "scans" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "connected_repository_id" uuid NOT NULL, + "status" text DEFAULT 'queued' NOT NULL, + "file_count" integer, + "error" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "finished_at" timestamp with time zone +); +--> statement-breakpoint +ALTER TABLE "scans" ADD CONSTRAINT "scans_connected_repository_id_connected_repositories_id_fk" FOREIGN KEY ("connected_repository_id") REFERENCES "public"."connected_repositories"("id") ON DELETE cascade ON UPDATE no action; \ No newline at end of file diff --git a/apps/backend/src/db/migrations/0003_classy_roland_deschain.sql b/apps/backend/src/db/migrations/0003_classy_roland_deschain.sql new file mode 100644 index 0000000..3a05b9f --- /dev/null +++ b/apps/backend/src/db/migrations/0003_classy_roland_deschain.sql @@ -0,0 +1,48 @@ +CREATE TABLE "advisories" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "osv_id" text NOT NULL, + "ecosystem" text NOT NULL, + "package_name" text NOT NULL, + "severity" text, + "summary" text, + "details" text, + "aliases" jsonb, + "ranges_raw" jsonb NOT NULL, + "modified" timestamp with time zone, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "advisories_osv_id_unique" UNIQUE("osv_id") +); +--> statement-breakpoint +CREATE TABLE "finding_chains" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "scan_id" uuid NOT NULL, + "title" text NOT NULL, + "severity" text NOT NULL, + "description" text NOT NULL, + "steps" jsonb NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "findings" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "scan_id" uuid NOT NULL, + "file_path" text NOT NULL, + "severity" text NOT NULL, + "title" text NOT NULL, + "description" text NOT NULL, + "vulnerable_code" text NOT NULL, + "suggested_fix" text NOT NULL, + "start_line" integer, + "end_line" integer, + "category" text, + "cve_id" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +ALTER TABLE "scans" ADD COLUMN "files_scanned" integer;--> statement-breakpoint +ALTER TABLE "scans" ADD COLUMN "finding_count" integer DEFAULT 0 NOT NULL;--> statement-breakpoint +ALTER TABLE "scans" ADD COLUMN "stage" text;--> statement-breakpoint +ALTER TABLE "finding_chains" ADD CONSTRAINT "finding_chains_scan_id_scans_id_fk" FOREIGN KEY ("scan_id") REFERENCES "public"."scans"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "findings" ADD CONSTRAINT "findings_scan_id_scans_id_fk" FOREIGN KEY ("scan_id") REFERENCES "public"."scans"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "advisories_ecosystem_package_idx" ON "advisories" USING btree ("ecosystem","package_name"); \ No newline at end of file diff --git a/apps/backend/src/db/migrations/0004_peaceful_golden_guardian.sql b/apps/backend/src/db/migrations/0004_peaceful_golden_guardian.sql new file mode 100644 index 0000000..8bc779e --- /dev/null +++ b/apps/backend/src/db/migrations/0004_peaceful_golden_guardian.sql @@ -0,0 +1,6 @@ +ALTER TABLE "scans" ADD COLUMN "fix_status" text;--> statement-breakpoint +ALTER TABLE "scans" ADD COLUMN "fix_pr_url" text;--> statement-breakpoint +ALTER TABLE "scans" ADD COLUMN "fix_pr_number" integer;--> statement-breakpoint +ALTER TABLE "scans" ADD COLUMN "fixed_count" integer;--> statement-breakpoint +ALTER TABLE "scans" ADD COLUMN "skipped_count" integer;--> statement-breakpoint +ALTER TABLE "scans" ADD COLUMN "fix_error" text; \ No newline at end of file diff --git a/apps/backend/src/db/migrations/0005_elite_living_tribunal.sql b/apps/backend/src/db/migrations/0005_elite_living_tribunal.sql new file mode 100644 index 0000000..aa6491d --- /dev/null +++ b/apps/backend/src/db/migrations/0005_elite_living_tribunal.sql @@ -0,0 +1,33 @@ +CREATE TABLE "connected_images" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "user_id" uuid NOT NULL, + "registry_connection_id" uuid NOT NULL, + "repository" text NOT NULL, + "tag" text DEFAULT 'latest' NOT NULL, + "image_ref" text NOT NULL, + "name" text NOT NULL, + "last_digest" text, + "connected_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "connected_images_unique" UNIQUE("user_id","registry_connection_id","repository","tag") +); +--> statement-breakpoint +CREATE TABLE "registry_connections" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "user_id" uuid NOT NULL, + "type" text NOT NULL, + "host" text NOT NULL, + "username" text, + "secret_encrypted" text, + "extra" jsonb, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "registry_connections_user_type_host_unique" UNIQUE("user_id","type","host") +); +--> statement-breakpoint +ALTER TABLE "scans" ALTER COLUMN "connected_repository_id" DROP NOT NULL;--> statement-breakpoint +ALTER TABLE "scans" ADD COLUMN "target_type" text DEFAULT 'repo' NOT NULL;--> statement-breakpoint +ALTER TABLE "scans" ADD COLUMN "connected_image_id" uuid;--> statement-breakpoint +ALTER TABLE "connected_images" ADD CONSTRAINT "connected_images_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "connected_images" ADD CONSTRAINT "connected_images_registry_connection_id_registry_connections_id_fk" FOREIGN KEY ("registry_connection_id") REFERENCES "public"."registry_connections"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "registry_connections" ADD CONSTRAINT "registry_connections_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "scans" ADD CONSTRAINT "scans_connected_image_id_connected_images_id_fk" FOREIGN KEY ("connected_image_id") REFERENCES "public"."connected_images"("id") ON DELETE cascade ON UPDATE no action; \ No newline at end of file diff --git a/apps/backend/src/db/migrations/meta/0000_snapshot.json b/apps/backend/src/db/migrations/meta/0000_snapshot.json new file mode 100644 index 0000000..270ec4a --- /dev/null +++ b/apps/backend/src/db/migrations/meta/0000_snapshot.json @@ -0,0 +1,85 @@ +{ + "id": "477250b6-b61f-46dc-afc0-73156e3ddbca", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "workos_user_id": { + "name": "workos_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_workos_user_id_unique": { + "name": "users_workos_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "workos_user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/backend/src/db/migrations/meta/0001_snapshot.json b/apps/backend/src/db/migrations/meta/0001_snapshot.json new file mode 100644 index 0000000..ce923b9 --- /dev/null +++ b/apps/backend/src/db/migrations/meta/0001_snapshot.json @@ -0,0 +1,276 @@ +{ + "id": "c1bd980e-f301-4f57-b44d-f97abf9f84cd", + "prevId": "477250b6-b61f-46dc-afc0-73156e3ddbca", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.connected_repositories": { + "name": "connected_repositories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "github_repo_id": { + "name": "github_repo_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "full_name": { + "name": "full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private": { + "name": "private", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default_branch": { + "name": "default_branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "html_url": { + "name": "html_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "connected_at": { + "name": "connected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "connected_repositories_user_id_users_id_fk": { + "name": "connected_repositories_user_id_users_id_fk", + "tableFrom": "connected_repositories", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "connected_repositories_user_repo_unique": { + "name": "connected_repositories_user_repo_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id", + "github_repo_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.github_connections": { + "name": "github_connections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "github_user_id": { + "name": "github_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "github_login": { + "name": "github_login", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token_encrypted": { + "name": "access_token_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "github_connections_user_id_users_id_fk": { + "name": "github_connections_user_id_users_id_fk", + "tableFrom": "github_connections", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "github_connections_user_id_unique": { + "name": "github_connections_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "workos_user_id": { + "name": "workos_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_workos_user_id_unique": { + "name": "users_workos_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "workos_user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/backend/src/db/migrations/meta/0002_snapshot.json b/apps/backend/src/db/migrations/meta/0002_snapshot.json new file mode 100644 index 0000000..e6cc264 --- /dev/null +++ b/apps/backend/src/db/migrations/meta/0002_snapshot.json @@ -0,0 +1,348 @@ +{ + "id": "2b85b410-27ac-4e5a-9f13-dd8b76aa206e", + "prevId": "c1bd980e-f301-4f57-b44d-f97abf9f84cd", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.connected_repositories": { + "name": "connected_repositories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "github_repo_id": { + "name": "github_repo_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "full_name": { + "name": "full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private": { + "name": "private", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default_branch": { + "name": "default_branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "html_url": { + "name": "html_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "connected_at": { + "name": "connected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "connected_repositories_user_id_users_id_fk": { + "name": "connected_repositories_user_id_users_id_fk", + "tableFrom": "connected_repositories", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "connected_repositories_user_repo_unique": { + "name": "connected_repositories_user_repo_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id", + "github_repo_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.github_connections": { + "name": "github_connections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "github_user_id": { + "name": "github_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "github_login": { + "name": "github_login", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token_encrypted": { + "name": "access_token_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "github_connections_user_id_users_id_fk": { + "name": "github_connections_user_id_users_id_fk", + "tableFrom": "github_connections", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "github_connections_user_id_unique": { + "name": "github_connections_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scans": { + "name": "scans", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "connected_repository_id": { + "name": "connected_repository_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "file_count": { + "name": "file_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "scans_connected_repository_id_connected_repositories_id_fk": { + "name": "scans_connected_repository_id_connected_repositories_id_fk", + "tableFrom": "scans", + "tableTo": "connected_repositories", + "columnsFrom": [ + "connected_repository_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "workos_user_id": { + "name": "workos_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_workos_user_id_unique": { + "name": "users_workos_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "workos_user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/backend/src/db/migrations/meta/0003_snapshot.json b/apps/backend/src/db/migrations/meta/0003_snapshot.json new file mode 100644 index 0000000..32949c6 --- /dev/null +++ b/apps/backend/src/db/migrations/meta/0003_snapshot.json @@ -0,0 +1,663 @@ +{ + "id": "0559d6e1-6e5c-4b35-88db-01585aa239a5", + "prevId": "2b85b410-27ac-4e5a-9f13-dd8b76aa206e", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.advisories": { + "name": "advisories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "osv_id": { + "name": "osv_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ecosystem": { + "name": "ecosystem", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "package_name": { + "name": "package_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "aliases": { + "name": "aliases", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ranges_raw": { + "name": "ranges_raw", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "modified": { + "name": "modified", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "advisories_ecosystem_package_idx": { + "name": "advisories_ecosystem_package_idx", + "columns": [ + { + "expression": "ecosystem", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "package_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "advisories_osv_id_unique": { + "name": "advisories_osv_id_unique", + "nullsNotDistinct": false, + "columns": [ + "osv_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.connected_repositories": { + "name": "connected_repositories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "github_repo_id": { + "name": "github_repo_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "full_name": { + "name": "full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private": { + "name": "private", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default_branch": { + "name": "default_branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "html_url": { + "name": "html_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "connected_at": { + "name": "connected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "connected_repositories_user_id_users_id_fk": { + "name": "connected_repositories_user_id_users_id_fk", + "tableFrom": "connected_repositories", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "connected_repositories_user_repo_unique": { + "name": "connected_repositories_user_repo_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id", + "github_repo_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.finding_chains": { + "name": "finding_chains", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "scan_id": { + "name": "scan_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "steps": { + "name": "steps", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "finding_chains_scan_id_scans_id_fk": { + "name": "finding_chains_scan_id_scans_id_fk", + "tableFrom": "finding_chains", + "tableTo": "scans", + "columnsFrom": [ + "scan_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.findings": { + "name": "findings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "scan_id": { + "name": "scan_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "file_path": { + "name": "file_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vulnerable_code": { + "name": "vulnerable_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "suggested_fix": { + "name": "suggested_fix", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "start_line": { + "name": "start_line", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "end_line": { + "name": "end_line", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cve_id": { + "name": "cve_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "findings_scan_id_scans_id_fk": { + "name": "findings_scan_id_scans_id_fk", + "tableFrom": "findings", + "tableTo": "scans", + "columnsFrom": [ + "scan_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.github_connections": { + "name": "github_connections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "github_user_id": { + "name": "github_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "github_login": { + "name": "github_login", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token_encrypted": { + "name": "access_token_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "github_connections_user_id_users_id_fk": { + "name": "github_connections_user_id_users_id_fk", + "tableFrom": "github_connections", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "github_connections_user_id_unique": { + "name": "github_connections_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scans": { + "name": "scans", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "connected_repository_id": { + "name": "connected_repository_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "file_count": { + "name": "file_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "files_scanned": { + "name": "files_scanned", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "finding_count": { + "name": "finding_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "stage": { + "name": "stage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "scans_connected_repository_id_connected_repositories_id_fk": { + "name": "scans_connected_repository_id_connected_repositories_id_fk", + "tableFrom": "scans", + "tableTo": "connected_repositories", + "columnsFrom": [ + "connected_repository_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "workos_user_id": { + "name": "workos_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_workos_user_id_unique": { + "name": "users_workos_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "workos_user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/backend/src/db/migrations/meta/0004_snapshot.json b/apps/backend/src/db/migrations/meta/0004_snapshot.json new file mode 100644 index 0000000..7d42b5c --- /dev/null +++ b/apps/backend/src/db/migrations/meta/0004_snapshot.json @@ -0,0 +1,699 @@ +{ + "id": "9e1720f9-1a84-4e75-8725-8171c331bc98", + "prevId": "0559d6e1-6e5c-4b35-88db-01585aa239a5", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.advisories": { + "name": "advisories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "osv_id": { + "name": "osv_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ecosystem": { + "name": "ecosystem", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "package_name": { + "name": "package_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "aliases": { + "name": "aliases", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ranges_raw": { + "name": "ranges_raw", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "modified": { + "name": "modified", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "advisories_ecosystem_package_idx": { + "name": "advisories_ecosystem_package_idx", + "columns": [ + { + "expression": "ecosystem", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "package_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "advisories_osv_id_unique": { + "name": "advisories_osv_id_unique", + "nullsNotDistinct": false, + "columns": [ + "osv_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.connected_repositories": { + "name": "connected_repositories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "github_repo_id": { + "name": "github_repo_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "full_name": { + "name": "full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private": { + "name": "private", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default_branch": { + "name": "default_branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "html_url": { + "name": "html_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "connected_at": { + "name": "connected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "connected_repositories_user_id_users_id_fk": { + "name": "connected_repositories_user_id_users_id_fk", + "tableFrom": "connected_repositories", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "connected_repositories_user_repo_unique": { + "name": "connected_repositories_user_repo_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id", + "github_repo_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.finding_chains": { + "name": "finding_chains", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "scan_id": { + "name": "scan_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "steps": { + "name": "steps", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "finding_chains_scan_id_scans_id_fk": { + "name": "finding_chains_scan_id_scans_id_fk", + "tableFrom": "finding_chains", + "tableTo": "scans", + "columnsFrom": [ + "scan_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.findings": { + "name": "findings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "scan_id": { + "name": "scan_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "file_path": { + "name": "file_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vulnerable_code": { + "name": "vulnerable_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "suggested_fix": { + "name": "suggested_fix", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "start_line": { + "name": "start_line", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "end_line": { + "name": "end_line", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cve_id": { + "name": "cve_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "findings_scan_id_scans_id_fk": { + "name": "findings_scan_id_scans_id_fk", + "tableFrom": "findings", + "tableTo": "scans", + "columnsFrom": [ + "scan_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.github_connections": { + "name": "github_connections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "github_user_id": { + "name": "github_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "github_login": { + "name": "github_login", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token_encrypted": { + "name": "access_token_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "github_connections_user_id_users_id_fk": { + "name": "github_connections_user_id_users_id_fk", + "tableFrom": "github_connections", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "github_connections_user_id_unique": { + "name": "github_connections_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scans": { + "name": "scans", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "connected_repository_id": { + "name": "connected_repository_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "file_count": { + "name": "file_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "files_scanned": { + "name": "files_scanned", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "finding_count": { + "name": "finding_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "stage": { + "name": "stage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fix_status": { + "name": "fix_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fix_pr_url": { + "name": "fix_pr_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fix_pr_number": { + "name": "fix_pr_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "fixed_count": { + "name": "fixed_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "skipped_count": { + "name": "skipped_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "fix_error": { + "name": "fix_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "scans_connected_repository_id_connected_repositories_id_fk": { + "name": "scans_connected_repository_id_connected_repositories_id_fk", + "tableFrom": "scans", + "tableTo": "connected_repositories", + "columnsFrom": [ + "connected_repository_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "workos_user_id": { + "name": "workos_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_workos_user_id_unique": { + "name": "users_workos_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "workos_user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/backend/src/db/migrations/meta/0005_snapshot.json b/apps/backend/src/db/migrations/meta/0005_snapshot.json new file mode 100644 index 0000000..1344044 --- /dev/null +++ b/apps/backend/src/db/migrations/meta/0005_snapshot.json @@ -0,0 +1,927 @@ +{ + "id": "5827a037-8fb3-4e23-b75c-f5d690d6643e", + "prevId": "9e1720f9-1a84-4e75-8725-8171c331bc98", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.advisories": { + "name": "advisories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "osv_id": { + "name": "osv_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ecosystem": { + "name": "ecosystem", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "package_name": { + "name": "package_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "aliases": { + "name": "aliases", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ranges_raw": { + "name": "ranges_raw", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "modified": { + "name": "modified", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "advisories_ecosystem_package_idx": { + "name": "advisories_ecosystem_package_idx", + "columns": [ + { + "expression": "ecosystem", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "package_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "advisories_osv_id_unique": { + "name": "advisories_osv_id_unique", + "nullsNotDistinct": false, + "columns": [ + "osv_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.connected_images": { + "name": "connected_images", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "registry_connection_id": { + "name": "registry_connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "repository": { + "name": "repository", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag": { + "name": "tag", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'latest'" + }, + "image_ref": { + "name": "image_ref", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_digest": { + "name": "last_digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "connected_at": { + "name": "connected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "connected_images_user_id_users_id_fk": { + "name": "connected_images_user_id_users_id_fk", + "tableFrom": "connected_images", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connected_images_registry_connection_id_registry_connections_id_fk": { + "name": "connected_images_registry_connection_id_registry_connections_id_fk", + "tableFrom": "connected_images", + "tableTo": "registry_connections", + "columnsFrom": [ + "registry_connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "connected_images_unique": { + "name": "connected_images_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id", + "registry_connection_id", + "repository", + "tag" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.connected_repositories": { + "name": "connected_repositories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "github_repo_id": { + "name": "github_repo_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "full_name": { + "name": "full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private": { + "name": "private", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default_branch": { + "name": "default_branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "html_url": { + "name": "html_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "connected_at": { + "name": "connected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "connected_repositories_user_id_users_id_fk": { + "name": "connected_repositories_user_id_users_id_fk", + "tableFrom": "connected_repositories", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "connected_repositories_user_repo_unique": { + "name": "connected_repositories_user_repo_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id", + "github_repo_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.finding_chains": { + "name": "finding_chains", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "scan_id": { + "name": "scan_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "steps": { + "name": "steps", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "finding_chains_scan_id_scans_id_fk": { + "name": "finding_chains_scan_id_scans_id_fk", + "tableFrom": "finding_chains", + "tableTo": "scans", + "columnsFrom": [ + "scan_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.findings": { + "name": "findings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "scan_id": { + "name": "scan_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "file_path": { + "name": "file_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vulnerable_code": { + "name": "vulnerable_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "suggested_fix": { + "name": "suggested_fix", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "start_line": { + "name": "start_line", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "end_line": { + "name": "end_line", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cve_id": { + "name": "cve_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "findings_scan_id_scans_id_fk": { + "name": "findings_scan_id_scans_id_fk", + "tableFrom": "findings", + "tableTo": "scans", + "columnsFrom": [ + "scan_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.github_connections": { + "name": "github_connections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "github_user_id": { + "name": "github_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "github_login": { + "name": "github_login", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token_encrypted": { + "name": "access_token_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "github_connections_user_id_users_id_fk": { + "name": "github_connections_user_id_users_id_fk", + "tableFrom": "github_connections", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "github_connections_user_id_unique": { + "name": "github_connections_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.registry_connections": { + "name": "registry_connections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "host": { + "name": "host", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_encrypted": { + "name": "secret_encrypted", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "extra": { + "name": "extra", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "registry_connections_user_id_users_id_fk": { + "name": "registry_connections_user_id_users_id_fk", + "tableFrom": "registry_connections", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "registry_connections_user_type_host_unique": { + "name": "registry_connections_user_type_host_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id", + "type", + "host" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scans": { + "name": "scans", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'repo'" + }, + "connected_repository_id": { + "name": "connected_repository_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connected_image_id": { + "name": "connected_image_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "file_count": { + "name": "file_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "files_scanned": { + "name": "files_scanned", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "finding_count": { + "name": "finding_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "stage": { + "name": "stage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fix_status": { + "name": "fix_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fix_pr_url": { + "name": "fix_pr_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fix_pr_number": { + "name": "fix_pr_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "fixed_count": { + "name": "fixed_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "skipped_count": { + "name": "skipped_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "fix_error": { + "name": "fix_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "scans_connected_repository_id_connected_repositories_id_fk": { + "name": "scans_connected_repository_id_connected_repositories_id_fk", + "tableFrom": "scans", + "tableTo": "connected_repositories", + "columnsFrom": [ + "connected_repository_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "scans_connected_image_id_connected_images_id_fk": { + "name": "scans_connected_image_id_connected_images_id_fk", + "tableFrom": "scans", + "tableTo": "connected_images", + "columnsFrom": [ + "connected_image_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "workos_user_id": { + "name": "workos_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_workos_user_id_unique": { + "name": "users_workos_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "workos_user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/backend/src/db/migrations/meta/_journal.json b/apps/backend/src/db/migrations/meta/_journal.json new file mode 100644 index 0000000..d7690fc --- /dev/null +++ b/apps/backend/src/db/migrations/meta/_journal.json @@ -0,0 +1,48 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1782660286911, + "tag": "0000_nappy_darkstar", + "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1782679895454, + "tag": "0001_parched_mercury", + "breakpoints": true + }, + { + "idx": 2, + "version": "7", + "when": 1782801265021, + "tag": "0002_pink_alex_power", + "breakpoints": true + }, + { + "idx": 3, + "version": "7", + "when": 1783008905222, + "tag": "0003_classy_roland_deschain", + "breakpoints": true + }, + { + "idx": 4, + "version": "7", + "when": 1783016510687, + "tag": "0004_peaceful_golden_guardian", + "breakpoints": true + }, + { + "idx": 5, + "version": "7", + "when": 1783026891296, + "tag": "0005_elite_living_tribunal", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/apps/backend/src/db/registry.ts b/apps/backend/src/db/registry.ts new file mode 100644 index 0000000..9de2739 --- /dev/null +++ b/apps/backend/src/db/registry.ts @@ -0,0 +1,45 @@ +import { and, desc, eq } from "drizzle-orm"; +import { getDb } from "./client"; +import { registryConnections, type NewRegistryConnection, type RegistryConnection } from "./schema"; + +// Upserts a registry connection keyed on (userId, type, host) so reconnecting the +// same registry refreshes its credential instead of duplicating. +export async function upsertRegistryConnection(row: NewRegistryConnection): Promise { + const [saved] = await getDb() + .insert(registryConnections) + .values(row) + .onConflictDoUpdate({ + target: [registryConnections.userId, registryConnections.type, registryConnections.host], + set: { + username: row.username ?? null, + secretEncrypted: row.secretEncrypted ?? null, + extra: row.extra ?? null, + updatedAt: new Date(), + }, + }) + .returning(); + return saved; +} + +export async function listRegistryConnections(userId: string): Promise { + return getDb() + .select() + .from(registryConnections) + .where(eq(registryConnections.userId, userId)) + .orderBy(desc(registryConnections.createdAt)); +} + +export async function getRegistryConnection(userId: string, id: string): Promise { + const [row] = await getDb() + .select() + .from(registryConnections) + .where(and(eq(registryConnections.id, id), eq(registryConnections.userId, userId))) + .limit(1); + return row ?? null; +} + +export async function deleteRegistryConnection(userId: string, id: string): Promise { + await getDb() + .delete(registryConnections) + .where(and(eq(registryConnections.id, id), eq(registryConnections.userId, userId))); +} diff --git a/apps/backend/src/db/scans.ts b/apps/backend/src/db/scans.ts new file mode 100644 index 0000000..48418cc --- /dev/null +++ b/apps/backend/src/db/scans.ts @@ -0,0 +1,105 @@ +import { and, desc, eq, inArray, or, sql } from "drizzle-orm"; +import { getDb } from "./client"; +import { scans, type Scan } from "./schema"; + +export async function createScan(connectedRepositoryId: string): Promise { + const [row] = await getDb() + .insert(scans) + .values({ connectedRepositoryId, targetType: "repo", status: "queued" }) + .returning({ id: scans.id }); + return row.id; +} + +export async function createImageScan(connectedImageId: string): Promise { + const [row] = await getDb() + .insert(scans) + .values({ connectedImageId, targetType: "image", status: "queued" }) + .returning({ id: scans.id }); + return row.id; +} + +export async function getScan(scanId: string): Promise { + const [row] = await getDb().select().from(scans).where(eq(scans.id, scanId)).limit(1); + return row ?? null; +} + +export async function markScanRunning(scanId: string): Promise { + await getDb().update(scans).set({ status: "running" }).where(eq(scans.id, scanId)); +} + +export async function completeScan(scanId: string, fileCount: number): Promise { + await getDb() + .update(scans) + .set({ status: "completed", fileCount, filesScanned: fileCount, stage: "done", error: null, finishedAt: new Date() }) + .where(eq(scans.id, scanId)); +} + +export async function failScan(scanId: string, error: string): Promise { + await getDb() + .update(scans) + .set({ status: "failed", error, finishedAt: new Date() }) + .where(eq(scans.id, scanId)); +} + +// Mark any queued/running scans for a connected repository as failed. Used when the +// user retries a scan so the old one doesn't stay stuck in "Scanning…" forever. +export async function failScansForRepo(connectedRepositoryId: string, error: string): Promise { + await getDb() + .update(scans) + .set({ status: "failed", error, finishedAt: new Date() }) + .where( + and( + eq(scans.connectedRepositoryId, connectedRepositoryId), + or(eq(scans.status, "queued"), eq(scans.status, "running")), + ), + ); +} + +// Mark any queued/running scans older than `maxAgeMinutes` as failed. Run on startup +// to clean up scans that were abandoned by a crash, deploy, or failed callback. +export async function failStaleScans(maxAgeMinutes: number, error: string): Promise { + const cutoff = sql`now() - interval '${sql.raw(String(maxAgeMinutes))} minutes'`; + const result = await getDb() + .update(scans) + .set({ status: "failed", error, finishedAt: new Date() }) + .where( + and( + or(eq(scans.status, "queued"), eq(scans.status, "running")), + sql`${scans.createdAt} < ${cutoff}`, + ), + ) + .returning({ id: scans.id }); + return result.length; +} + +// Latest scan per connected repository, keyed by connectedRepositoryId. +export async function latestScansForRepos(repoIds: string[]): Promise> { + if (repoIds.length === 0) return new Map(); + const rows = await getDb() + .select() + .from(scans) + .where(inArray(scans.connectedRepositoryId, repoIds)) + .orderBy(desc(scans.createdAt)); + const latest = new Map(); + for (const row of rows) { + const key = row.connectedRepositoryId; + if (key && !latest.has(key)) latest.set(key, row); + } + return latest; +} + +// Latest scan per connected image, keyed by connectedImageId. +export async function latestScansForImages(imageIds: string[]): Promise> { + if (imageIds.length === 0) return new Map(); + const rows = await getDb() + .select() + .from(scans) + .where(inArray(scans.connectedImageId, imageIds)) + .orderBy(desc(scans.createdAt)); + const latest = new Map(); + for (const row of rows) { + const key = row.connectedImageId; + if (key && !latest.has(key)) latest.set(key, row); + } + return latest; +} diff --git a/apps/backend/src/db/schema.ts b/apps/backend/src/db/schema.ts new file mode 100644 index 0000000..3557e2a --- /dev/null +++ b/apps/backend/src/db/schema.ts @@ -0,0 +1,196 @@ +import { pgTable, uuid, text, boolean, integer, timestamp, jsonb, unique, index } from "drizzle-orm/pg-core"; + +// Application users, synced from WorkOS on each login (see db/users.ts). +export const users = pgTable("users", { + id: uuid("id").defaultRandom().primaryKey(), + workosUserId: text("workos_user_id").notNull().unique(), + email: text("email").notNull(), + firstName: text("first_name"), + lastName: text("last_name"), + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), + updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), +}); + +// A user's connected GitHub account. One per user; the OAuth access token is +// stored encrypted (AES-256-GCM, see lib/crypto.ts) and only decrypted to call +// the GitHub API from the backend. +export const githubConnections = pgTable("github_connections", { + id: uuid("id").defaultRandom().primaryKey(), + userId: uuid("user_id") + .notNull() + .unique() + .references(() => users.id, { onDelete: "cascade" }), + githubUserId: text("github_user_id").notNull(), + githubLogin: text("github_login").notNull(), + accessTokenEncrypted: text("access_token_encrypted").notNull(), + scope: text("scope"), + tokenType: text("token_type"), + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), + updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), +}); + +// Repositories a user has explicitly connected as projects. +export const connectedRepositories = pgTable( + "connected_repositories", + { + id: uuid("id").defaultRandom().primaryKey(), + userId: uuid("user_id") + .notNull() + .references(() => users.id, { onDelete: "cascade" }), + githubRepoId: text("github_repo_id").notNull(), + fullName: text("full_name").notNull(), + name: text("name").notNull(), + owner: text("owner").notNull(), + private: boolean("private").notNull().default(false), + defaultBranch: text("default_branch"), + htmlUrl: text("html_url"), + connectedAt: timestamp("connected_at", { withTimezone: true }).notNull().defaultNow(), + }, + (table) => [unique("connected_repositories_user_repo_unique").on(table.userId, table.githubRepoId)], +); + +// A container registry a user has connected (Docker Hub, GHCR, GAR, ECR, …). Multiple +// per user; the pull credential is stored encrypted (see lib/crypto.ts). +export const registryConnections = pgTable( + "registry_connections", + { + id: uuid("id").defaultRandom().primaryKey(), + userId: uuid("user_id") + .notNull() + .references(() => users.id, { onDelete: "cascade" }), + type: text("type").notNull(), // dockerhub | ghcr | gar | ecr | generic + host: text("host").notNull(), + username: text("username"), + secretEncrypted: text("secret_encrypted"), + extra: jsonb("extra"), // provider-specific (e.g. { region } for ECR, { project } for GAR) + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), + updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), + }, + (table) => [unique("registry_connections_user_type_host_unique").on(table.userId, table.type, table.host)], +); + +// A specific container image the user has picked to scan from a connected registry. +export const connectedImages = pgTable( + "connected_images", + { + id: uuid("id").defaultRandom().primaryKey(), + userId: uuid("user_id") + .notNull() + .references(() => users.id, { onDelete: "cascade" }), + registryConnectionId: uuid("registry_connection_id") + .notNull() + .references(() => registryConnections.id, { onDelete: "cascade" }), + repository: text("repository").notNull(), // e.g. library/nginx + tag: text("tag").notNull().default("latest"), + imageRef: text("image_ref").notNull(), // full ref, e.g. docker.io/library/nginx:latest + name: text("name").notNull(), + lastDigest: text("last_digest"), + connectedAt: timestamp("connected_at", { withTimezone: true }).notNull().defaultNow(), + }, + (table) => [ + unique("connected_images_unique").on(table.userId, table.registryConnectionId, table.repository, table.tag), + ], +); + +// Scans of a connected repository OR container image (targetType). The row tracks +// lifecycle (queued → running → completed/failed), progress, and result counts. The +// actual vulnerabilities live in `findings` / `findingChains`. +export const scans = pgTable("scans", { + id: uuid("id").defaultRandom().primaryKey(), + // Exactly one target FK is set, per targetType. + targetType: text("target_type").notNull().default("repo"), // repo | image + connectedRepositoryId: uuid("connected_repository_id").references(() => connectedRepositories.id, { + onDelete: "cascade", + }), + connectedImageId: uuid("connected_image_id").references(() => connectedImages.id, { onDelete: "cascade" }), + status: text("status").notNull().default("queued"), + fileCount: integer("file_count"), + // Progress: how many eligible files have been analyzed so far (of fileCount). + filesScanned: integer("files_scanned"), + findingCount: integer("finding_count").notNull().default(0), + // Coarse lifecycle stage for the UI: download | analyzing | chaining | done. + stage: text("stage"), + error: text("error"), + // Auto-fix PR lifecycle: creating | opened | no_fixes | failed (null = not attempted). + fixStatus: text("fix_status"), + fixPrUrl: text("fix_pr_url"), + fixPrNumber: integer("fix_pr_number"), + fixedCount: integer("fixed_count"), + skippedCount: integer("skipped_count"), + fixError: text("fix_error"), + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), + finishedAt: timestamp("finished_at", { withTimezone: true }), +}); + +// A single vulnerability found in one file. `vulnerableCode` is the exact snippet +// to replace (rendered RED in the UI); `suggestedFix` is the proposed replacement +// (rendered GREEN). The `id` may be supplied by the scanner so chains can reference +// it before it's persisted. +export const findings = pgTable("findings", { + id: uuid("id").defaultRandom().primaryKey(), + scanId: uuid("scan_id") + .notNull() + .references(() => scans.id, { onDelete: "cascade" }), + filePath: text("file_path").notNull(), + severity: text("severity").notNull(), // critical | high | medium | low + title: text("title").notNull(), + description: text("description").notNull(), + vulnerableCode: text("vulnerable_code").notNull(), + suggestedFix: text("suggested_fix").notNull(), + startLine: integer("start_line"), + endLine: integer("end_line"), + category: text("category"), // code | dependency + cveId: text("cve_id"), + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), +}); + +// An attack path chaining multiple findings, produced by the chaining pass. +export const findingChains = pgTable("finding_chains", { + id: uuid("id").defaultRandom().primaryKey(), + scanId: uuid("scan_id") + .notNull() + .references(() => scans.id, { onDelete: "cascade" }), + title: text("title").notNull(), + severity: text("severity").notNull(), + description: text("description").notNull(), + steps: jsonb("steps").notNull(), // [{ findingId?, filePath, note }] + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), +}); + +// Cache of CVE/security advisories, synced from OSV.dev. Grows on demand as scans +// query dependency versions; can also be bulk-loaded (see cve:sync). +export const advisories = pgTable( + "advisories", + { + id: uuid("id").defaultRandom().primaryKey(), + osvId: text("osv_id").notNull().unique(), + ecosystem: text("ecosystem").notNull(), + packageName: text("package_name").notNull(), + severity: text("severity"), + summary: text("summary"), + details: text("details"), + aliases: jsonb("aliases"), // string[] of CVE/GHSA ids + rangesRaw: jsonb("ranges_raw").notNull(), // OSV affected[].ranges + versions + modified: timestamp("modified", { withTimezone: true }), + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), + updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), + }, + (table) => [index("advisories_ecosystem_package_idx").on(table.ecosystem, table.packageName)], +); + +export type User = typeof users.$inferSelect; +export type NewUser = typeof users.$inferInsert; +export type GithubConnection = typeof githubConnections.$inferSelect; +export type ConnectedRepository = typeof connectedRepositories.$inferSelect; +export type NewConnectedRepository = typeof connectedRepositories.$inferInsert; +export type Scan = typeof scans.$inferSelect; +export type Finding = typeof findings.$inferSelect; +export type NewFinding = typeof findings.$inferInsert; +export type FindingChain = typeof findingChains.$inferSelect; +export type NewFindingChain = typeof findingChains.$inferInsert; +export type Advisory = typeof advisories.$inferSelect; +export type NewAdvisory = typeof advisories.$inferInsert; +export type RegistryConnection = typeof registryConnections.$inferSelect; +export type NewRegistryConnection = typeof registryConnections.$inferInsert; +export type ConnectedImage = typeof connectedImages.$inferSelect; +export type NewConnectedImage = typeof connectedImages.$inferInsert; diff --git a/apps/backend/src/db/users.ts b/apps/backend/src/db/users.ts new file mode 100644 index 0000000..bed5eae --- /dev/null +++ b/apps/backend/src/db/users.ts @@ -0,0 +1,37 @@ +import { eq } from "drizzle-orm"; +import { getDb } from "./client"; +import { users } from "./schema"; + +export interface WorkosUserInput { + id: string; + email: string; + firstName: string | null; + lastName: string | null; +} + +// Upsert the WorkOS user into Supabase and return the local users.id. +export async function upsertUser(user: WorkosUserInput): Promise { + const [row] = await getDb() + .insert(users) + .values({ + workosUserId: user.id, + email: user.email, + firstName: user.firstName, + lastName: user.lastName, + }) + .onConflictDoUpdate({ + target: users.workosUserId, + set: { email: user.email, firstName: user.firstName, lastName: user.lastName, updatedAt: new Date() }, + }) + .returning({ id: users.id }); + return row.id; +} + +export async function getUserId(workosUserId: string): Promise { + const [row] = await getDb() + .select({ id: users.id }) + .from(users) + .where(eq(users.workosUserId, workosUserId)) + .limit(1); + return row?.id ?? null; +} diff --git a/apps/backend/src/env.test.ts b/apps/backend/src/env.test.ts new file mode 100644 index 0000000..424f92c --- /dev/null +++ b/apps/backend/src/env.test.ts @@ -0,0 +1,14 @@ +import { describe, expect, test } from "bun:test"; +import { isLocalDevAuthEnabled } from "./env"; + +describe("local integration authentication", () => { + test("is available only when explicitly configured outside production", () => { + expect(isLocalDevAuthEnabled("development", "guest@cefense.local")).toBe(true); + expect(isLocalDevAuthEnabled("test", "guest@cefense.local")).toBe(true); + expect(isLocalDevAuthEnabled("development", undefined)).toBe(false); + }); + + test("cannot replace WorkOS in production", () => { + expect(isLocalDevAuthEnabled("production", "guest@cefense.local")).toBe(false); + }); +}); diff --git a/apps/backend/src/env.ts b/apps/backend/src/env.ts new file mode 100644 index 0000000..c356674 --- /dev/null +++ b/apps/backend/src/env.ts @@ -0,0 +1,95 @@ +import { z } from "zod"; + +// Treat empty-string env vars (common when copying .env.example) as unset, so +// optional integrations stay "not configured" instead of failing validation. +const cleaned = Object.fromEntries( + Object.entries(process.env).map(([key, value]) => [key, value === "" ? undefined : value]), +); + +const schema = z.object({ + PORT: z.coerce.number().default(3001), + NODE_ENV: z.enum(["development", "production", "test"]).default("development"), + LOG_LEVEL: z.enum(["fatal", "error", "warn", "info", "debug", "trace"]).optional(), + FRONTEND_URL: z.string().default("http://localhost:5173"), + + WORKOS_API_KEY: z.string().optional(), + WORKOS_CLIENT_ID: z.string().optional(), + WORKOS_COOKIE_PASSWORD: z.string().min(32, "WORKOS_COOKIE_PASSWORD must be at least 32 characters").optional(), + WORKOS_REDIRECT_URI: z.string().default("http://localhost:3001/auth/callback"), + + // Explicit local-only identity for Docker/E2E runs. It is ignored in + // production so WorkOS remains the sole production authentication backend. + LOCAL_DEV_AUTH_EMAIL: z.email().optional(), + LOCAL_DEV_AUTH_FIRST_NAME: z.string().default("Guest"), + LOCAL_DEV_AUTH_LAST_NAME: z.string().default(""), + + DATABASE_URL: z.string().optional(), + + GITHUB_CLIENT_ID: z.string().optional(), + GITHUB_CLIENT_SECRET: z.string().optional(), + GITHUB_REDIRECT_URI: z.string().default("http://localhost:3001/api/github/callback"), + APP_ENCRYPTION_KEY: z.string().optional(), + + // Scanner (apps/cli) orchestration. + SCAN_RUNNER: z.enum(["inline", "docker", "cloudrun"]).default("inline"), + SCAN_CALLBACK_URL: z.string().default("http://localhost:3001"), + SCAN_CALLBACK_SECRET: z.string().optional(), + SCANNER_IMAGE: z.string().optional(), + GCP_PROJECT: z.string().optional(), + GCP_REGION: z.string().default("us-central1"), + SCAN_JOB_NAME: z.string().default("cerebrus-scanner"), + // Service-account key JSON, inlined. Preferred for Cloud Run because the file is + // baked into the image with the rest of the env. For local dev you can instead set + // GOOGLE_APPLICATION_CREDENTIALS to a file path. + GOOGLE_SERVICE_ACCOUNT_JSON: z.string().optional(), + // Path to a service-account key JSON file. Useful for local dev; ignored if + // GOOGLE_SERVICE_ACCOUNT_JSON is set. + GOOGLE_APPLICATION_CREDENTIALS: z.string().optional(), + + // DeepSeek — the LLM that performs the actual file-by-file vulnerability analysis. + DEEPSEEK_API_KEY: z.string().optional(), + DEEPSEEK_MODEL: z.string().default("deepseek-reasoner"), + DEEPSEEK_BASE_URL: z.string().default("https://api.deepseek.com"), + DEEPSEEK_CONCURRENCY: z.coerce.number().int().positive().default(4), + + // Per-scan logs are written here (host-readable; docker mode redirects the + // container's stdout/stderr into /scan-.log). + SCAN_LOG_DIR: z.string().default("./scan-logs"), + + // Ecosystems to bulk-load for the optional `cve:sync` script (on-demand querybatch + // needs no config). + OSV_ECOSYSTEMS: z.string().default("npm,PyPI,Go,crates.io,Maven,RubyGems"), + + // Auto-fix: after a scan finds vulnerabilities, open a PR on the user's repo with the + // suggested fixes applied. Set to "false" to disable (findings still show, no PR). + SCAN_AUTOFIX: z.string().default("true"), +}); + +const parsed = schema.safeParse(cleaned); + +if (!parsed.success) { + console.error("Invalid environment configuration:\n", z.treeifyError(parsed.error)); + process.exit(1); +} + +const raw = parsed.data; + +export function isLocalDevAuthEnabled( + nodeEnv: "development" | "production" | "test", + email?: string, +): boolean { + return nodeEnv !== "production" && Boolean(email); +} + +export const env = { + ...raw, + isProd: raw.NODE_ENV === "production", + // Auth and DB are independently optional so the server boots un-configured and + // surfaces a clear status instead of crashing. + authConfigured: Boolean(raw.WORKOS_API_KEY && raw.WORKOS_CLIENT_ID && raw.WORKOS_COOKIE_PASSWORD), + localDevAuthEnabled: isLocalDevAuthEnabled(raw.NODE_ENV, raw.LOCAL_DEV_AUTH_EMAIL), + dbConfigured: Boolean(raw.DATABASE_URL), + githubConfigured: Boolean(raw.GITHUB_CLIENT_ID && raw.GITHUB_CLIENT_SECRET && raw.APP_ENCRYPTION_KEY), + scannerConfigured: Boolean(raw.DEEPSEEK_API_KEY), + autofixEnabled: raw.SCAN_AUTOFIX !== "false", +}; diff --git a/apps/backend/src/fix/autofix.ts b/apps/backend/src/fix/autofix.ts new file mode 100644 index 0000000..c0ed5ce --- /dev/null +++ b/apps/backend/src/fix/autofix.ts @@ -0,0 +1,168 @@ +import { env } from "../env"; +import { logger } from "../lib/logger"; +import { decrypt } from "../lib/crypto"; +import { loadScanContext, setFixStatus } from "../db/fix"; +import { getScan } from "../db/scans"; +import { findingsForScan } from "../db/findings"; +import type { Finding } from "../db/schema"; +import { + createCommit, + createPullRequest, + createRef, + createTree, + getCommitTree, + getFileContent, + getRef, + getRepoDefaultBranch, + type TreeFile, +} from "../lib/github"; + +interface Skipped { + finding: Finding; + reason: string; +} + +// After a scan completes, open a PR on the user's repo applying the suggested fixes. +// Fire-and-forget: callers `void createFixPr(scanId)` and never await it. Every exit +// path records a fixStatus so the dashboard can reflect what happened. +export async function createFixPr(scanId: string): Promise { + if (!env.autofixEnabled) return; + try { + // Auto-fix PRs only apply to repo scans. Image scans have no repo to open a PR + // against — bail before loadScanContext (whose repo join would return null and + // mislabel the scan as a failed fix). + const scan = await getScan(scanId); + if (!scan || scan.targetType !== "repo") return; + + const ctx = await loadScanContext(scanId); + if (!ctx) { + await setFixStatus(scanId, { fixStatus: "failed", fixError: "GitHub connection unavailable — reconnect GitHub." }); + return; + } + + let token: string; + try { + token = decrypt(ctx.accessTokenEncrypted); + } catch { + await setFixStatus(scanId, { fixStatus: "failed", fixError: "Stored GitHub token unreadable — reconnect GitHub." }); + return; + } + + const findings = await findingsForScan(scanId); + const codeFindings = findings.filter((f) => (f.category === "code" || f.category == null) && f.vulnerableCode && f.suggestedFix); + const depFindings = findings.filter((f) => f.category === "dependency"); + if (codeFindings.length === 0) { + await setFixStatus(scanId, { fixStatus: "no_fixes", fixedCount: 0, skippedCount: 0 }); + return; + } + + await setFixStatus(scanId, { fixStatus: "creating" }); + + const { owner, name } = ctx; + const base = ctx.defaultBranch ?? (await getRepoDefaultBranch(token, owner, name)); + + // Apply each fix to its file, but only when the vulnerable snippet is an exact, + // unique substring — otherwise skip it (never corrupt the file). + const byFile = new Map(); + for (const f of codeFindings) { + const list = byFile.get(f.filePath); + if (list) list.push(f); + else byFile.set(f.filePath, [f]); + } + + const changedFiles: TreeFile[] = []; + const fixed: Finding[] = []; + const skipped: Skipped[] = []; + + for (const [path, fileFindings] of byFile) { + let content: string; + try { + content = (await getFileContent(token, owner, name, path, base)).content; + } catch { + for (const f of fileFindings) skipped.push({ finding: f, reason: "file not found on the default branch" }); + continue; + } + let updated = content; + let fileChanged = false; + for (const f of fileFindings) { + const occurrences = updated.split(f.vulnerableCode).length - 1; + if (occurrences === 1) { + updated = updated.replace(f.vulnerableCode, f.suggestedFix); + fixed.push(f); + fileChanged = true; + } else { + skipped.push({ + finding: f, + reason: occurrences === 0 ? "snippet not found (file may have changed)" : "snippet appears multiple times (ambiguous)", + }); + } + } + if (fileChanged) changedFiles.push({ path, content: updated }); + } + + if (changedFiles.length === 0) { + await setFixStatus(scanId, { fixStatus: "no_fixes", fixedCount: 0, skippedCount: skipped.length }); + return; + } + + // Build one commit on a new branch via the Git Data API, then open the PR. + const baseSha = await getRef(token, owner, name, base); + const baseTree = await getCommitTree(token, owner, name, baseSha); + const newTree = await createTree(token, owner, name, baseTree, changedFiles); + const title = `Cerebrus: fix ${fixed.length} security finding${fixed.length === 1 ? "" : "s"}`; + const commitSha = await createCommit(token, owner, name, title, newTree, baseSha); + const branch = await createRef(token, owner, name, `cerebrus/fix-${scanId.slice(0, 8)}`, commitSha); + + const pr = await createPullRequest(token, owner, name, { + title, + head: branch, + base, + body: buildPrBody(fixed, skipped, depFindings, base), + }); + + await setFixStatus(scanId, { + fixStatus: "opened", + fixPrUrl: pr.url, + fixPrNumber: pr.number, + fixedCount: fixed.length, + skippedCount: skipped.length, + }); + logger.info({ scanId, pr: pr.number, fixed: fixed.length, skipped: skipped.length }, "opened fix PR"); + } catch (err) { + const message = scrubToken(err instanceof Error ? err.message : String(err)); + logger.error({ err, scanId }, "fix PR failed"); + await setFixStatus(scanId, { fixStatus: "failed", fixError: message }).catch(() => {}); + } +} + +function buildPrBody(fixed: Finding[], skipped: Skipped[], deps: Finding[], base: string): string { + const lines: string[] = []; + lines.push(`Automated security fixes generated by **Cerebrus** from a scan of \`${base}\`.`); + lines.push(""); + lines.push(`## Fixed (${fixed.length})`); + for (const f of fixed) lines.push(`- \`${f.severity}\` \`${f.filePath}\` — ${f.title}`); + + if (skipped.length > 0) { + lines.push(""); + lines.push(`## Skipped — needs manual review (${skipped.length})`); + for (const s of skipped) lines.push(`- \`${s.finding.filePath}\` — ${s.finding.title} _(${s.reason})_`); + } + + if (deps.length > 0) { + lines.push(""); + lines.push(`## Vulnerable dependencies (${deps.length})`); + for (const d of deps) lines.push(`- ${d.cveId ? `\`${d.cveId}\` ` : ""}${d.title} — ${d.suggestedFix}`); + } + + lines.push(""); + lines.push( + "> ⚠️ These fixes were generated by an LLM (DeepSeek). Review them carefully before merging — they are not guaranteed to be correct or complete.", + ); + return lines.join("\n"); +} + +// Defensive: strip anything that looks like a GitHub token from error text before it +// lands in the DB / logs. +function scrubToken(message: string): string { + return message.replace(/gh[opsu]_[A-Za-z0-9]{20,}/g, "***"); +} diff --git a/apps/backend/src/index.ts b/apps/backend/src/index.ts new file mode 100644 index 0000000..2deff81 --- /dev/null +++ b/apps/backend/src/index.ts @@ -0,0 +1,57 @@ +import express from "express"; +import cookieParser from "cookie-parser"; +import cors from "cors"; +import { env } from "./env"; +import { logger } from "./lib/logger"; +import { httpLogger } from "./middleware/logging"; +import { healthRouter } from "./routes/health"; +import { authRouter } from "./routes/auth"; +import { githubRouter } from "./routes/github"; +import { registryRouter } from "./routes/registry"; +import { internalRouter } from "./routes/internal"; +import { apiRouter } from "./routes/api"; +import { errorHandler } from "./middleware/error"; +import { failStaleScans } from "./db/scans"; + +const app = express(); + +// Behind the Cloud Run proxy in production: trust X-Forwarded-* for correct +// protocol/client IP (and secure-cookie handling). +if (env.isProd) app.set("trust proxy", true); + +app.use(httpLogger); +app.use(express.json()); +app.use(cookieParser()); +app.use(cors({ origin: env.FRONTEND_URL, credentials: true })); + +app.use(healthRouter); +app.use("/internal", internalRouter); +app.use("/auth", authRouter); +app.use("/api/github", githubRouter); +app.use("/api/registry", registryRouter); +app.use("/api", apiRouter); + +app.use(errorHandler); + +app.listen(env.PORT, async () => { + logger.info( + { + port: env.PORT, + authConfigured: env.authConfigured, + dbConfigured: env.dbConfigured, + githubConfigured: env.githubConfigured, + }, + `cerebrus-backend listening on port ${env.PORT}`, + ); + + // Mark scans that were left queued/running across a crash/deploy as failed so the + // UI offers a retry instead of staying stuck. + if (env.dbConfigured) { + try { + const count = await failStaleScans(30, "Scan timed out — no completion signal received"); + if (count > 0) logger.info({ count }, "Marked stale scans as failed"); + } catch (err) { + logger.error({ err }, "Failed to mark stale scans as failed"); + } + } +}); diff --git a/apps/backend/src/lib/crypto.ts b/apps/backend/src/lib/crypto.ts new file mode 100644 index 0000000..00c56f5 --- /dev/null +++ b/apps/backend/src/lib/crypto.ts @@ -0,0 +1,30 @@ +import { createCipheriv, createDecipheriv, createHash, randomBytes } from "node:crypto"; +import { env } from "../env"; + +const ALGORITHM = "aes-256-gcm"; +const IV_LENGTH = 12; +const TAG_LENGTH = 16; + +// Derive a fixed 32-byte key from APP_ENCRYPTION_KEY so any-length secret works. +function getKey(): Buffer { + return createHash("sha256").update(env.APP_ENCRYPTION_KEY ?? "").digest(); +} + +// AES-256-GCM. Output is base64 of iv | authTag | ciphertext. +export function encrypt(plaintext: string): string { + const iv = randomBytes(IV_LENGTH); + const cipher = createCipheriv(ALGORITHM, getKey(), iv); + const ciphertext = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]); + const tag = cipher.getAuthTag(); + return Buffer.concat([iv, tag, ciphertext]).toString("base64"); +} + +export function decrypt(payload: string): string { + const buf = Buffer.from(payload, "base64"); + const iv = buf.subarray(0, IV_LENGTH); + const tag = buf.subarray(IV_LENGTH, IV_LENGTH + TAG_LENGTH); + const ciphertext = buf.subarray(IV_LENGTH + TAG_LENGTH); + const decipher = createDecipheriv(ALGORITHM, getKey(), iv); + decipher.setAuthTag(tag); + return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8"); +} diff --git a/apps/backend/src/lib/gcp.ts b/apps/backend/src/lib/gcp.ts new file mode 100644 index 0000000..c7eae8e --- /dev/null +++ b/apps/backend/src/lib/gcp.ts @@ -0,0 +1,82 @@ +import { createSign } from "node:crypto"; +import { Buffer } from "node:buffer"; +import { readFileSync } from "node:fs"; +import { env } from "../env"; + +// Google Cloud auth for triggering the Cloud Run scanner job. The service-account +// key can be supplied two ways: +// • GOOGLE_SERVICE_ACCOUNT_JSON — the key JSON inlined in the env (used on Cloud +// Run so no extra file needs to be copied into the image). +// • GOOGLE_APPLICATION_CREDENTIALS — path to a key JSON file (useful for local dev). +// The JWT-bearer grant is used to exchange the key for an access token. + +interface SaKey { + client_email: string; + private_key: string; + token_uri: string; + project_id: string; +} + +let cachedKey: SaKey | null | undefined; + +function loadKey(): SaKey | null { + if (cachedKey !== undefined) return cachedKey; + + let raw: string | undefined; + if (env.GOOGLE_SERVICE_ACCOUNT_JSON) { + raw = env.GOOGLE_SERVICE_ACCOUNT_JSON; + } else if (env.GOOGLE_APPLICATION_CREDENTIALS) { + try { + raw = readFileSync(env.GOOGLE_APPLICATION_CREDENTIALS, "utf8"); + } catch (err) { + throw new Error(`Could not read GOOGLE_APPLICATION_CREDENTIALS (${env.GOOGLE_APPLICATION_CREDENTIALS})`, { cause: err }); + } + } + + if (!raw) { + cachedKey = null; + return null; + } + + if (!raw.trim()) throw new Error(`Service-account key JSON is empty — paste the key into GOOGLE_SERVICE_ACCOUNT_JSON or the credentials file.`); + const parsed = JSON.parse(raw) as SaKey; + cachedKey = parsed; // only cached on success + return parsed; +} + +// Project id: explicit GCP_PROJECT wins, else the key's project_id. +export function gcpProjectId(): string | undefined { + return env.GCP_PROJECT ?? loadKey()?.project_id ?? undefined; +} + +export async function gcpAccessToken(): Promise { + const key = loadKey(); + if (!key) throw new Error("No GCP service account configured — set GOOGLE_SERVICE_ACCOUNT_JSON or GOOGLE_APPLICATION_CREDENTIALS"); + return tokenFromKey(key); +} + +function base64url(input: object): string { + return Buffer.from(JSON.stringify(input)).toString("base64url"); +} + +async function tokenFromKey(key: SaKey): Promise { + const now = Math.floor(Date.now() / 1000); + const claims = { + iss: key.client_email, + scope: "https://www.googleapis.com/auth/cloud-platform", + aud: key.token_uri, + iat: now, + exp: now + 3600, + }; + const signingInput = `${base64url({ alg: "RS256", typ: "JWT" })}.${base64url(claims)}`; + const signature = createSign("RSA-SHA256").update(signingInput).sign(key.private_key, "base64url"); + const assertion = `${signingInput}.${signature}`; + + const res = await fetch(key.token_uri, { + method: "POST", + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + body: new URLSearchParams({ grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer", assertion }), + }); + if (!res.ok) throw new Error(`Service-account token exchange failed (${res.status}): ${await res.text()}`); + return ((await res.json()) as { access_token: string }).access_token; +} diff --git a/apps/backend/src/lib/github.ts b/apps/backend/src/lib/github.ts new file mode 100644 index 0000000..491e541 --- /dev/null +++ b/apps/backend/src/lib/github.ts @@ -0,0 +1,242 @@ +import { Buffer } from "node:buffer"; +import { env } from "../env"; +import { logger } from "./logger"; + +const GITHUB_AUTHORIZE_URL = "https://github.com/login/oauth/authorize"; +const GITHUB_TOKEN_URL = "https://github.com/login/oauth/access_token"; +const GITHUB_API = "https://api.github.com"; +const SCOPES = "repo read:user"; +const MAX_PAGES = 5; + +export interface GithubRepo { + githubRepoId: string; + name: string; + fullName: string; + owner: string; + private: boolean; + defaultBranch: string | null; + htmlUrl: string | null; + description: string | null; + updatedAt: string | null; +} + +export interface GithubViewer { + id: string; + login: string; +} + +// Thrown when GitHub rejects the stored token (401) — the user must reconnect. +export class GithubReconnectError extends Error { + constructor() { + super("GitHub token rejected — reconnect required."); + this.name = "GithubReconnectError"; + } +} + +export function getAuthorizeUrl(state: string): string { + const params = new URLSearchParams({ + client_id: env.GITHUB_CLIENT_ID ?? "", + redirect_uri: env.GITHUB_REDIRECT_URI, + scope: SCOPES, + state, + allow_signup: "true", + }); + return `${GITHUB_AUTHORIZE_URL}?${params.toString()}`; +} + +export async function exchangeCode(code: string): Promise<{ accessToken: string; scope: string; tokenType: string }> { + const res = await fetch(GITHUB_TOKEN_URL, { + method: "POST", + headers: { Accept: "application/json", "Content-Type": "application/json" }, + body: JSON.stringify({ + client_id: env.GITHUB_CLIENT_ID, + client_secret: env.GITHUB_CLIENT_SECRET, + code, + redirect_uri: env.GITHUB_REDIRECT_URI, + }), + }); + const data = (await res.json()) as { + access_token?: string; + scope?: string; + token_type?: string; + error?: string; + error_description?: string; + }; + if (!res.ok || !data.access_token) { + throw new Error(data.error_description || data.error || "GitHub token exchange failed."); + } + return { accessToken: data.access_token, scope: data.scope ?? "", tokenType: data.token_type ?? "bearer" }; +} + +export async function fetchViewer(token: string): Promise { + const res = await fetch(`${GITHUB_API}/user`, { headers: githubHeaders(token) }); + if (!res.ok) throw apiError(res.status); + const data = (await res.json()) as { id: number; login: string }; + return { id: String(data.id), login: data.login }; +} + +// All repos the user can access (public + private), newest first. +export async function listRepos(token: string): Promise { + const repos: GithubRepo[] = []; + for (let page = 1; page <= MAX_PAGES; page++) { + const params = new URLSearchParams({ + visibility: "all", + affiliation: "owner,collaborator,organization_member", + per_page: "100", + sort: "updated", + page: String(page), + }); + const res = await fetch(`${GITHUB_API}/user/repos?${params.toString()}`, { headers: githubHeaders(token) }); + if (!res.ok) throw apiError(res.status); + const batch = (await res.json()) as GithubApiRepo[]; + for (const repo of batch) repos.push(mapRepo(repo)); + if (batch.length < 100) return repos; + } + logger.warn(`GitHub repo listing capped at ${MAX_PAGES * 100} repositories.`); + return repos; +} + +interface GithubApiRepo { + id: number; + name: string; + full_name: string; + owner: { login: string }; + private: boolean; + default_branch: string | null; + html_url: string | null; + description: string | null; + updated_at: string | null; +} + +function mapRepo(repo: GithubApiRepo): GithubRepo { + return { + githubRepoId: String(repo.id), + name: repo.name, + fullName: repo.full_name, + owner: repo.owner.login, + private: repo.private, + defaultBranch: repo.default_branch ?? null, + htmlUrl: repo.html_url ?? null, + description: repo.description ?? null, + updatedAt: repo.updated_at ?? null, + }; +} + +function githubHeaders(token: string): Record { + return { + Authorization: `Bearer ${token}`, + Accept: "application/vnd.github+json", + "X-GitHub-Api-Version": "2022-11-28", + "User-Agent": "cerebrus-backend", + }; +} + +function apiError(status: number): Error { + if (status === 401) return new GithubReconnectError(); + return new Error(`GitHub API error (${status}).`); +} + +// ---- Write API (used by the auto-fix step to branch, commit, and open a PR) ---- + +// Carries the HTTP status so callers can special-case (e.g. 422 branch-exists). +export class GithubHttpError extends Error { + status: number; + constructor(status: number, message: string) { + super(message); + this.status = status; + this.name = "GithubHttpError"; + } +} + +async function ghRequest(token: string, method: string, path: string, body?: unknown): Promise { + const res = await fetch(`${GITHUB_API}${path}`, { + method, + headers: body ? { ...githubHeaders(token), "Content-Type": "application/json" } : githubHeaders(token), + body: body === undefined ? undefined : JSON.stringify(body), + }); + if (res.status === 401) throw new GithubReconnectError(); + if (!res.ok) throw new GithubHttpError(res.status, `GitHub ${method} ${path} → ${res.status}: ${await res.text()}`); + return (await res.json()) as T; +} + +function encodePath(path: string): string { + return path.split("/").map(encodeURIComponent).join("/"); +} + +export async function getRepoDefaultBranch(token: string, owner: string, repo: string): Promise { + const data = await ghRequest<{ default_branch: string }>(token, "GET", `/repos/${owner}/${repo}`); + return data.default_branch; +} + +export async function getRef(token: string, owner: string, repo: string, branch: string): Promise { + const data = await ghRequest<{ object: { sha: string } }>(token, "GET", `/repos/${owner}/${repo}/git/ref/heads/${encodePath(branch)}`); + return data.object.sha; +} + +export async function getCommitTree(token: string, owner: string, repo: string, sha: string): Promise { + const data = await ghRequest<{ tree: { sha: string } }>(token, "GET", `/repos/${owner}/${repo}/git/commits/${sha}`); + return data.tree.sha; +} + +export async function getFileContent( + token: string, + owner: string, + repo: string, + path: string, + ref: string, +): Promise<{ content: string; sha: string }> { + const data = await ghRequest<{ content: string; sha: string; encoding: string }>( + token, + "GET", + `/repos/${owner}/${repo}/contents/${encodePath(path)}?ref=${encodeURIComponent(ref)}`, + ); + return { content: Buffer.from(data.content, "base64").toString("utf8"), sha: data.sha }; +} + +export interface TreeFile { + path: string; + content: string; +} + +export async function createTree(token: string, owner: string, repo: string, baseTreeSha: string, files: TreeFile[]): Promise { + const data = await ghRequest<{ sha: string }>(token, "POST", `/repos/${owner}/${repo}/git/trees`, { + base_tree: baseTreeSha, + tree: files.map((f) => ({ path: f.path, mode: "100644", type: "blob", content: f.content })), + }); + return data.sha; +} + +export async function createCommit(token: string, owner: string, repo: string, message: string, treeSha: string, parentSha: string): Promise { + const data = await ghRequest<{ sha: string }>(token, "POST", `/repos/${owner}/${repo}/git/commits`, { + message, + tree: treeSha, + parents: [parentSha], + }); + return data.sha; +} + +// Creates refs/heads/ at sha. On a 422 (branch already exists), retries once +// with a "-2" suffix and returns the branch name that actually got created. +export async function createRef(token: string, owner: string, repo: string, branch: string, sha: string): Promise { + try { + await ghRequest(token, "POST", `/repos/${owner}/${repo}/git/refs`, { ref: `refs/heads/${branch}`, sha }); + return branch; + } catch (err) { + if (err instanceof GithubHttpError && err.status === 422) { + const alt = `${branch}-2`; + await ghRequest(token, "POST", `/repos/${owner}/${repo}/git/refs`, { ref: `refs/heads/${alt}`, sha }); + return alt; + } + throw err; + } +} + +export async function createPullRequest( + token: string, + owner: string, + repo: string, + pr: { title: string; head: string; base: string; body: string }, +): Promise<{ url: string; number: number }> { + const data = await ghRequest<{ html_url: string; number: number }>(token, "POST", `/repos/${owner}/${repo}/pulls`, pr); + return { url: data.html_url, number: data.number }; +} diff --git a/apps/backend/src/lib/logger.ts b/apps/backend/src/lib/logger.ts new file mode 100644 index 0000000..e162c29 --- /dev/null +++ b/apps/backend/src/lib/logger.ts @@ -0,0 +1,34 @@ +import { pino } from "pino"; +import { env } from "../env"; + +// Map pino levels onto Google Cloud Logging severities so Cloud Run renders the +// right level (and groups errors) instead of treating every line as default. +const SEVERITY_BY_LEVEL: Record = { + trace: "DEBUG", + debug: "DEBUG", + info: "INFO", + warn: "WARNING", + error: "ERROR", + fatal: "CRITICAL", +}; + +export const logger = pino( + env.isProd + ? { + // Structured JSON to stdout — Cloud Run/Cloud Logging parses it. + level: env.LOG_LEVEL ?? "info", + messageKey: "message", + timestamp: pino.stdTimeFunctions.isoTime, + formatters: { + level: (label) => ({ severity: SEVERITY_BY_LEVEL[label] ?? "DEFAULT" }), + }, + } + : { + // Human-friendly output in development. + level: env.LOG_LEVEL ?? "debug", + transport: { + target: "pino-pretty", + options: { colorize: true, translateTime: "HH:MM:ss.l", ignore: "pid,hostname" }, + }, + }, +); diff --git a/apps/backend/src/lib/workos.ts b/apps/backend/src/lib/workos.ts new file mode 100644 index 0000000..921b3ec --- /dev/null +++ b/apps/backend/src/lib/workos.ts @@ -0,0 +1,28 @@ +import { WorkOS } from "@workos-inc/node"; +import type { CookieOptions } from "express"; +import { env } from "../env"; + +let instance: WorkOS | undefined; + +// Lazily constructed so the server can boot without WorkOS credentials. Only +// called from paths guarded by env.authConfigured. +export function getWorkos(): WorkOS { + if (!instance) { + instance = new WorkOS(env.WORKOS_API_KEY, { clientId: env.WORKOS_CLIENT_ID }); + } + return instance; +} + +export const SESSION_COOKIE = "wos_session"; + +// Env-aware cookie options: cross-site (SameSite=None;Secure) in production, +// Lax over http in dev (localhost ports are same-site, so Lax is delivered). +export function sessionCookieOptions(): CookieOptions { + return { + httpOnly: true, + secure: env.isProd, + sameSite: env.isProd ? "none" : "lax", + path: "/", + maxAge: 1000 * 60 * 60 * 24 * 30, + }; +} diff --git a/apps/backend/src/middleware/auth.ts b/apps/backend/src/middleware/auth.ts new file mode 100644 index 0000000..dc4e4da --- /dev/null +++ b/apps/backend/src/middleware/auth.ts @@ -0,0 +1,96 @@ +import type { Request, RequestHandler } from "express"; +import type { User as WorkOSUser } from "@workos-inc/node"; +import { env } from "../env"; +import { getWorkos, SESSION_COOKIE, sessionCookieOptions } from "../lib/workos"; + +export interface AuthUser { + id: string; + email: string; + firstName: string | null; + lastName: string | null; +} + +// Express Request carrying the authenticated user (set by withAuth). +export interface AuthedRequest extends Request { + user: AuthUser; +} + +export function toAuthUser(user: WorkOSUser): AuthUser { + return { + id: user.id, + email: user.email, + firstName: user.firstName ?? null, + lastName: user.lastName ?? null, + }; +} + +export function localDevAuthUser(): AuthUser | null { + if (!env.localDevAuthEnabled || !env.LOCAL_DEV_AUTH_EMAIL) return null; + return { + id: `local-dev:${env.LOCAL_DEV_AUTH_EMAIL}`, + email: env.LOCAL_DEV_AUTH_EMAIL, + firstName: env.LOCAL_DEV_AUTH_FIRST_NAME || null, + lastName: env.LOCAL_DEV_AUTH_LAST_NAME || null, + }; +} + +// Authenticates the WorkOS sealed-session cookie, refreshing it transparently +// when the access token has expired. Attaches req.user or responds 401/503. +export const withAuth: RequestHandler = async (req, res, next) => { + const localUser = localDevAuthUser(); + if (localUser) { + (req as AuthedRequest).user = localUser; + next(); + return; + } + + if (!env.authConfigured) { + res.status(503).json({ error: "Authentication is not configured." }); + return; + } + + const sessionData = req.cookies?.[SESSION_COOKIE] as string | undefined; + if (!sessionData) { + res.status(401).json({ error: "Not authenticated" }); + return; + } + + const cookiePassword = env.WORKOS_COOKIE_PASSWORD as string; + const session = getWorkos().userManagement.loadSealedSession({ sessionData, cookiePassword }); + + try { + const result = await session.authenticate(); + if (result.authenticated) { + (req as AuthedRequest).user = toAuthUser(result.user); + next(); + return; + } + + if (result.reason === "no_session_cookie_provided") { + res.status(401).json({ error: "Not authenticated" }); + return; + } + + // Access token expired — refresh and re-seal the cookie. + const refreshed = await session.refresh(); + if (!refreshed.authenticated || !refreshed.sealedSession) { + res.clearCookie(SESSION_COOKIE, sessionCookieOptions()); + res.status(401).json({ error: "Session expired" }); + return; + } + + res.cookie(SESSION_COOKIE, refreshed.sealedSession, sessionCookieOptions()); + const reauth = await getWorkos() + .userManagement.loadSealedSession({ sessionData: refreshed.sealedSession, cookiePassword }) + .authenticate(); + if (!reauth.authenticated) { + res.status(401).json({ error: "Session expired" }); + return; + } + (req as AuthedRequest).user = toAuthUser(reauth.user); + next(); + } catch { + res.clearCookie(SESSION_COOKIE, sessionCookieOptions()); + res.status(401).json({ error: "Not authenticated" }); + } +}; diff --git a/apps/backend/src/middleware/error.ts b/apps/backend/src/middleware/error.ts new file mode 100644 index 0000000..a0f4bf2 --- /dev/null +++ b/apps/backend/src/middleware/error.ts @@ -0,0 +1,16 @@ +import type { ErrorRequestHandler } from "express"; +import { logger } from "../lib/logger"; + +interface HttpError extends Error { + status?: number; +} + +// Central error handler. Express identifies it by its 4-argument arity, so all +// four params must stay even when unused. +export const errorHandler: ErrorRequestHandler = (err: HttpError, req, res, _next) => { + const status = typeof err.status === "number" ? err.status : 500; + if (status >= 500) { + (req.log ?? logger).error({ err }, "Unhandled request error"); + } + res.status(status).json({ error: err.message || "Internal Server Error" }); +}; diff --git a/apps/backend/src/middleware/logging.ts b/apps/backend/src/middleware/logging.ts new file mode 100644 index 0000000..c3448a4 --- /dev/null +++ b/apps/backend/src/middleware/logging.ts @@ -0,0 +1,36 @@ +import { randomUUID } from "node:crypto"; +import { pinoHttp } from "pino-http"; +import { logger } from "../lib/logger"; + +// Per-request logging. Emits one line per completed request with method, path, +// status, and latency; assigns/propagates a request id; and maps status codes to +// log levels. Sensitive headers are never logged (we only serialize safe fields). +export const httpLogger = pinoHttp({ + logger, + genReqId: (req, res) => { + const incoming = req.headers["x-request-id"]; + const id = (Array.isArray(incoming) ? incoming[0] : incoming) ?? randomUUID(); + res.setHeader("x-request-id", id); + return id; + }, + customLogLevel: (_req, res, err) => { + if (err || res.statusCode >= 500) return "error"; + if (res.statusCode >= 400) return "warn"; + return "info"; + }, + customSuccessMessage: (req, res) => `${req.method} ${req.url} ${res.statusCode}`, + customErrorMessage: (req, res, err) => `${req.method} ${req.url} ${res.statusCode} — ${err.message}`, + // Correlate with Cloud Run request traces when available. + customProps: (req) => { + const trace = req.headers["x-cloud-trace-context"]; + return typeof trace === "string" ? { traceId: trace.split("/")[0] } : {}; + }, + serializers: { + req: (req) => ({ id: req.id, method: req.method, url: req.url }), + res: (res) => ({ statusCode: res.statusCode }), + }, + // Don't spam logs with Cloud Run health checks. + autoLogging: { + ignore: (req) => req.url === "/health", + }, +}); diff --git a/apps/backend/src/registry/endpoints.ts b/apps/backend/src/registry/endpoints.ts new file mode 100644 index 0000000..9bf2df4 --- /dev/null +++ b/apps/backend/src/registry/endpoints.ts @@ -0,0 +1,25 @@ +// Maps a stored registry connection to the concrete OCI pull endpoint + repository +// normalization the scanner needs. Scanning is uniform OCI; only these differ per type. + +export function pullEndpoint(type: string, host: string): string { + switch (type) { + case "dockerhub": + return "registry-1.docker.io"; + case "ghcr": + return "ghcr.io"; + default: + return host; // gar (*-docker.pkg.dev), ecr, generic — host is the endpoint + } +} + +// Docker Hub official images live under `library/`; a bare name needs that prefix. +export function normalizeRepository(type: string, repository: string): string { + if (type === "dockerhub" && !repository.includes("/")) return `library/${repository}`; + return repository; +} + +// Human-readable ref stored on the image row (e.g. docker.io/library/nginx:latest). +export function displayImageRef(type: string, host: string, repository: string, tag: string): string { + const displayHost = type === "dockerhub" ? "docker.io" : host; + return `${displayHost}/${repository}:${tag}`; +} diff --git a/apps/backend/src/registry/list.ts b/apps/backend/src/registry/list.ts new file mode 100644 index 0000000..c44da61 --- /dev/null +++ b/apps/backend/src/registry/list.ts @@ -0,0 +1,80 @@ +import { Buffer } from "node:buffer"; +import { logger } from "../lib/logger"; +import type { RegistryConnection } from "../db/schema"; + +export interface RegistryImage { + repository: string; // e.g. myuser/app or library/nginx + description?: string | null; +} + +// Lists image repositories available in a connected registry so the user can pick one. +// Docker Hub + GHCR are supported; other types return [] and rely on manual image-ref +// entry. Failures degrade to [] (the manual add path still works). +export async function listRegistryImages(conn: RegistryConnection, secret: string | null): Promise { + try { + if (conn.type === "dockerhub") return await listDockerHub(conn, secret); + if (conn.type === "ghcr") return await listGhcr(conn, secret); + return []; + } catch (err) { + logger.warn({ err, type: conn.type }, "registry image listing failed"); + return []; + } +} + +async function listDockerHub(conn: RegistryConnection, secret: string | null): Promise { + const namespace = conn.username; + if (!namespace) return []; + let jwt: string | null = null; + if (secret) { + const login = await fetch("https://hub.docker.com/v2/users/login", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ username: namespace, password: secret }), + }); + if (login.ok) jwt = ((await login.json()) as { token?: string }).token ?? null; + } + const res = await fetch(`https://hub.docker.com/v2/repositories/${encodeURIComponent(namespace)}/?page_size=100`, { + headers: jwt ? { Authorization: `JWT ${jwt}` } : {}, + }); + if (!res.ok) return []; + const data = (await res.json()) as { results?: { name: string; description?: string }[] }; + return (data.results ?? []).map((r) => ({ repository: `${namespace}/${r.name}`, description: r.description })); +} + +async function listGhcr(conn: RegistryConnection, secret: string | null): Promise { + const owner = conn.username; + if (!owner || !secret) return []; + const res = await fetch("https://api.github.com/user/packages?package_type=container&per_page=100", { + headers: { + Authorization: `Bearer ${secret}`, + Accept: "application/vnd.github+json", + "X-GitHub-Api-Version": "2022-11-28", + "User-Agent": "cerebrus-backend", + }, + }); + if (!res.ok) return []; + const data = (await res.json()) as { name: string }[]; + return data.map((p) => ({ repository: `${owner}/${p.name}` })); +} + +// Validates a registry credential by minting an OCI pull token (a bare /v2/ returns 401 +// even for valid creds). Returns true if the credential works (or if anonymous is fine). +export async function validateCredential(endpoint: string, username: string | null, secret: string | null): Promise { + try { + const ping = await fetch(`https://${endpoint}/v2/`, { headers: { "User-Agent": "cerebrus-backend" } }); + if (ping.status === 200) return true; + const challenge = ping.headers.get("www-authenticate"); + if (!challenge) return true; + const realm = /realm="([^"]+)"/.exec(challenge)?.[1]; + const service = /service="([^"]+)"/.exec(challenge)?.[1]; + if (!realm) return true; + const url = new URL(realm); + if (service) url.searchParams.set("service", service); + const headers: Record = { "User-Agent": "cerebrus-backend" }; + if (username && secret) headers.Authorization = `Basic ${Buffer.from(`${username}:${secret}`).toString("base64")}`; + const tok = await fetch(url, { headers }); + return tok.ok; + } catch { + return false; + } +} diff --git a/apps/backend/src/routes/api.ts b/apps/backend/src/routes/api.ts new file mode 100644 index 0000000..6527b39 --- /dev/null +++ b/apps/backend/src/routes/api.ts @@ -0,0 +1,27 @@ +import { Router } from "express"; +import { eq } from "drizzle-orm"; +import { env } from "../env"; +import { withAuth, type AuthedRequest } from "../middleware/auth"; +import { getDb } from "../db/client"; +import { users } from "../db/schema"; + +export const apiRouter: Router = Router(); + +// Protected: returns the authenticated WorkOS user plus their Supabase row, +// demonstrating the frontend → backend → Supabase access boundary. +apiRouter.get("/me", withAuth, async (req, res) => { + const { user } = req as AuthedRequest; + + if (!env.dbConfigured) { + res.json({ user, profile: null, dbConfigured: false }); + return; + } + + const [profile] = await getDb() + .select() + .from(users) + .where(eq(users.workosUserId, user.id)) + .limit(1); + + res.json({ user, profile: profile ?? null, dbConfigured: true }); +}); diff --git a/apps/backend/src/routes/auth.ts b/apps/backend/src/routes/auth.ts new file mode 100644 index 0000000..291792c --- /dev/null +++ b/apps/backend/src/routes/auth.ts @@ -0,0 +1,112 @@ +import { Router } from "express"; +import type { User as WorkOSUser } from "@workos-inc/node"; +import { env } from "../env"; +import { getWorkos, SESSION_COOKIE, sessionCookieOptions } from "../lib/workos"; +import { localDevAuthUser, toAuthUser, type AuthUser } from "../middleware/auth"; +import { upsertUser } from "../db/users"; +import { logger } from "../lib/logger"; + +export const authRouter: Router = Router(); + +// Kick off the WorkOS AuthKit hosted login. Falls back to the frontend when +// WorkOS isn't configured yet, so the UI stays usable with placeholder env. +authRouter.get("/login", (_req, res) => { + if (env.localDevAuthEnabled) { + res.redirect(`${env.FRONTEND_URL}/app`); + return; + } + if (!env.authConfigured) { + res.redirect(`${env.FRONTEND_URL}/login`); + return; + } + const authorizationUrl = getWorkos().userManagement.getAuthorizationUrl({ + provider: "authkit", + clientId: env.WORKOS_CLIENT_ID as string, + redirectUri: env.WORKOS_REDIRECT_URI, + }); + res.redirect(authorizationUrl); +}); + +// WorkOS redirects here with a one-time code. Exchange it for a sealed session, +// set the cookie, sync the user into Supabase, and return to the app. +authRouter.get("/callback", async (req, res) => { + const code = typeof req.query.code === "string" ? req.query.code : undefined; + if (!env.authConfigured || !code) { + res.redirect(`${env.FRONTEND_URL}/login?error=auth`); + return; + } + + const { user, sealedSession } = await getWorkos().userManagement.authenticateWithCode({ + clientId: env.WORKOS_CLIENT_ID as string, + code, + session: { sealSession: true, cookiePassword: env.WORKOS_COOKIE_PASSWORD as string }, + }); + + if (sealedSession) { + res.cookie(SESSION_COOKIE, sealedSession, sessionCookieOptions()); + } + await syncUser(user); + res.redirect(`${env.FRONTEND_URL}/app/feed`); +}); + +// Lightweight auth-state probe for the frontend. Always 200. +authRouter.get("/me", async (req, res) => { + const localUser = localDevAuthUser(); + if (localUser) { + await syncUser(localUser); + res.json({ user: localUser, configured: true }); + return; + } + if (!env.authConfigured) { + res.json({ user: null, configured: false }); + return; + } + const sessionData = req.cookies?.[SESSION_COOKIE] as string | undefined; + if (!sessionData) { + res.json({ user: null, configured: true }); + return; + } + try { + const result = await getWorkos() + .userManagement.loadSealedSession({ sessionData, cookiePassword: env.WORKOS_COOKIE_PASSWORD as string }) + .authenticate(); + res.json({ user: result.authenticated ? toAuthUser(result.user) : null, configured: true }); + } catch { + res.json({ user: null, configured: true }); + } +}); + +// Clear the local session and bounce through the WorkOS logout endpoint. +authRouter.get("/logout", async (req, res) => { + const sessionData = req.cookies?.[SESSION_COOKIE] as string | undefined; + res.clearCookie(SESSION_COOKIE, sessionCookieOptions()); + + if (env.authConfigured && sessionData) { + try { + const logoutUrl = await getWorkos() + .userManagement.loadSealedSession({ sessionData, cookiePassword: env.WORKOS_COOKIE_PASSWORD as string }) + .getLogoutUrl(); + res.redirect(logoutUrl); + return; + } catch { + // Fall through to the frontend if the session can't be unsealed. + } + } + res.redirect(env.FRONTEND_URL); +}); + +// Upsert the WorkOS user into Supabase via Drizzle. Login still succeeds even if +// the DB write fails, so errors are logged and swallowed. +async function syncUser(user: WorkOSUser | AuthUser): Promise { + if (!env.dbConfigured) return; + try { + await upsertUser({ + id: user.id, + email: user.email, + firstName: user.firstName ?? null, + lastName: user.lastName ?? null, + }); + } catch (err) { + logger.error({ err }, "Failed to sync user to database"); + } +} diff --git a/apps/backend/src/routes/github.ts b/apps/backend/src/routes/github.ts new file mode 100644 index 0000000..ea11432 --- /dev/null +++ b/apps/backend/src/routes/github.ts @@ -0,0 +1,332 @@ +import { Router } from "express"; +import type { CookieOptions } from "express"; +import { randomBytes } from "node:crypto"; +import { z } from "zod"; +import { and, desc, eq } from "drizzle-orm"; +import { env } from "../env"; +import { withAuth, type AuthedRequest } from "../middleware/auth"; +import { getDb } from "../db/client"; +import { upsertUser } from "../db/users"; +import { githubConnections, connectedRepositories, type ConnectedRepository } from "../db/schema"; +import { encrypt, decrypt } from "../lib/crypto"; +import { exchangeCode, fetchViewer, getAuthorizeUrl, listRepos, GithubReconnectError } from "../lib/github"; +import { createScan, failScansForRepo, latestScansForRepos } from "../db/scans"; +import { chainsForScan, findingsForScan } from "../db/findings"; +import { triggerScan } from "../scan/runner"; + +export const githubRouter: Router = Router(); + +// Queues a scan for a connected repo using the user's GitHub token. Returns the +// scan id, or null if the GitHub token is missing/unusable (skip silently). +async function startScan(userId: string, project: ConnectedRepository): Promise { + const connection = await loadConnection(userId); + if (!connection) return null; + let token: string; + try { + token = decrypt(connection.accessTokenEncrypted); + } catch { + return null; + } + // Abandon any previously queued/running scan for this repo so the UI doesn't stay + // stuck in "Scanning…" if a callback was lost. + await failScansForRepo(project.id, "Abandoned — retry started"); + const scanId = await createScan(project.id); + // Await so the cloudrun jobs:run call completes within this request (Cloud Run + // freezes CPU after the response; inline/docker resolve immediately). + await triggerScan({ scanId, owner: project.owner, repo: project.name, ref: project.defaultBranch ?? undefined, token }); + return scanId; +} + +const STATE_COOKIE = "gh_oauth_state"; + +function stateCookieOptions(): CookieOptions { + return { + httpOnly: true, + secure: env.isProd, + sameSite: env.isProd ? "none" : "lax", + path: "/", + maxAge: 1000 * 60 * 10, + }; +} + +// Ensures a local users row exists for the authenticated WorkOS user and returns +// its id (the FK used by the github tables). +function resolveUserId(req: AuthedRequest): Promise { + return upsertUser({ + id: req.user.id, + email: req.user.email, + firstName: req.user.firstName, + lastName: req.user.lastName, + }); +} + +async function loadConnection(userId: string) { + const [connection] = await getDb() + .select() + .from(githubConnections) + .where(eq(githubConnections.userId, userId)) + .limit(1); + return connection ?? null; +} + +// Whether the user has a GitHub account connected. +githubRouter.get("/status", withAuth, async (req, res) => { + if (!env.dbConfigured) { + res.json({ configured: env.githubConfigured, connected: false }); + return; + } + const userId = await resolveUserId(req as AuthedRequest); + const connection = await loadConnection(userId); + res.json({ configured: env.githubConfigured, connected: Boolean(connection), login: connection?.githubLogin ?? null }); +}); + +// Start the GitHub OAuth flow (top-level redirect; carries the WorkOS session). +githubRouter.get("/connect", withAuth, (_req, res) => { + if (!env.githubConfigured) { + res.redirect(`${env.FRONTEND_URL}/app/repositories?github=unconfigured`); + return; + } + const state = randomBytes(16).toString("hex"); + res.cookie(STATE_COOKIE, state, stateCookieOptions()); + res.redirect(getAuthorizeUrl(state)); +}); + +// OAuth callback: validate state, exchange code, store the encrypted token. +githubRouter.get("/callback", withAuth, async (req, res) => { + const code = typeof req.query.code === "string" ? req.query.code : undefined; + const state = typeof req.query.state === "string" ? req.query.state : undefined; + const expectedState = req.cookies?.[STATE_COOKIE] as string | undefined; + res.clearCookie(STATE_COOKIE, stateCookieOptions()); + + if (!env.githubConfigured || !env.dbConfigured || !code || !state || state !== expectedState) { + res.redirect(`${env.FRONTEND_URL}/app/repositories?github=error`); + return; + } + + const { accessToken, scope, tokenType } = await exchangeCode(code); + const viewer = await fetchViewer(accessToken); + const userId = await resolveUserId(req as AuthedRequest); + const accessTokenEncrypted = encrypt(accessToken); + + await getDb() + .insert(githubConnections) + .values({ userId, githubUserId: viewer.id, githubLogin: viewer.login, accessTokenEncrypted, scope, tokenType }) + .onConflictDoUpdate({ + target: githubConnections.userId, + set: { githubUserId: viewer.id, githubLogin: viewer.login, accessTokenEncrypted, scope, tokenType, updatedAt: new Date() }, + }); + + res.redirect(`${env.FRONTEND_URL}/app/repositories?github=connected`); +}); + +// List the user's GitHub repos (public + private), each flagged if connected. +githubRouter.get("/repos", withAuth, async (req, res) => { + if (!env.dbConfigured) { + res.json({ connected: false, repos: [] }); + return; + } + const userId = await resolveUserId(req as AuthedRequest); + const connection = await loadConnection(userId); + if (!connection) { + res.json({ connected: false, repos: [] }); + return; + } + + // Decrypt the stored token. A failure here means it was encrypted with a + // different APP_ENCRYPTION_KEY (or is corrupt) — the token is unusable, so ask + // the user to reconnect rather than 500. + let token: string; + try { + token = decrypt(connection.accessTokenEncrypted); + } catch { + res.json({ connected: true, needsReconnect: true, repos: [] }); + return; + } + + let repos; + try { + repos = await listRepos(token); + } catch (err) { + if (err instanceof GithubReconnectError) { + res.json({ connected: true, needsReconnect: true, repos: [] }); + return; + } + throw err; + } + + const connectedRows = await getDb() + .select({ githubRepoId: connectedRepositories.githubRepoId }) + .from(connectedRepositories) + .where(eq(connectedRepositories.userId, userId)); + const connectedIds = new Set(connectedRows.map((row) => row.githubRepoId)); + + res.json({ + connected: true, + login: connection.githubLogin, + repos: repos.map((repo) => ({ ...repo, connected: connectedIds.has(repo.githubRepoId) })), + }); +}); + +const connectSchema = z.object({ + githubRepoId: z.string(), + fullName: z.string(), + name: z.string(), + owner: z.string(), + private: z.boolean(), + defaultBranch: z.string().nullable().optional(), + htmlUrl: z.string().nullable().optional(), +}); + +// Connect a repo as a project — persisted to connected_repositories. +githubRouter.post("/repos/connect", withAuth, async (req, res) => { + if (!env.dbConfigured) { + res.status(503).json({ error: "Database is not configured." }); + return; + } + const parsed = connectSchema.safeParse(req.body); + if (!parsed.success) { + res.status(400).json({ error: "Invalid repository payload." }); + return; + } + const userId = await resolveUserId(req as AuthedRequest); + const repo = parsed.data; + + const [inserted] = await getDb() + .insert(connectedRepositories) + .values({ + userId, + githubRepoId: repo.githubRepoId, + fullName: repo.fullName, + name: repo.name, + owner: repo.owner, + private: repo.private, + defaultBranch: repo.defaultBranch ?? null, + htmlUrl: repo.htmlUrl ?? null, + }) + .onConflictDoNothing() + .returning(); + + const [project] = inserted + ? [inserted] + : await getDb() + .select() + .from(connectedRepositories) + .where(and(eq(connectedRepositories.userId, userId), eq(connectedRepositories.githubRepoId, repo.githubRepoId))) + .limit(1); + + // Kick off the first scan (non-blocking; failures here don't fail the connect). + await startScan(userId, project); + + res.json({ project }); +}); + +// Re-run a scan for an already-connected project. +githubRouter.post("/projects/:githubRepoId/scan", withAuth, async (req, res) => { + if (!env.dbConfigured) { + res.status(503).json({ error: "Database is not configured." }); + return; + } + const userId = await resolveUserId(req as AuthedRequest); + const githubRepoId = String(req.params.githubRepoId); + const [project] = await getDb() + .select() + .from(connectedRepositories) + .where(and(eq(connectedRepositories.userId, userId), eq(connectedRepositories.githubRepoId, githubRepoId))) + .limit(1); + if (!project) { + res.status(404).json({ error: "Project not found." }); + return; + } + const scanId = await startScan(userId, project); + if (!scanId) { + res.status(409).json({ error: "GitHub connection unavailable — reconnect GitHub." }); + return; + } + res.json({ scanId }); +}); + +const disconnectSchema = z.object({ githubRepoId: z.string() }); + +// Disconnect a previously connected repo. +githubRouter.post("/repos/disconnect", withAuth, async (req, res) => { + if (!env.dbConfigured) { + res.status(503).json({ error: "Database is not configured." }); + return; + } + const parsed = disconnectSchema.safeParse(req.body); + if (!parsed.success) { + res.status(400).json({ error: "Invalid payload." }); + return; + } + const userId = await resolveUserId(req as AuthedRequest); + await getDb() + .delete(connectedRepositories) + .where(and(eq(connectedRepositories.userId, userId), eq(connectedRepositories.githubRepoId, parsed.data.githubRepoId))); + res.json({ ok: true }); +}); + +// The user's connected projects. +githubRouter.get("/projects", withAuth, async (req, res) => { + if (!env.dbConfigured) { + res.json({ projects: [] }); + return; + } + const userId = await resolveUserId(req as AuthedRequest); + const projects = await getDb() + .select() + .from(connectedRepositories) + .where(eq(connectedRepositories.userId, userId)) + .orderBy(desc(connectedRepositories.connectedAt)); + + const scanByRepo = await latestScansForRepos(projects.map((project) => project.id)); + res.json({ + projects: projects.map((project) => { + const scan = scanByRepo.get(project.id); + return { + ...project, + scan: scan + ? { + status: scan.status, + fileCount: scan.fileCount, + filesScanned: scan.filesScanned, + findingCount: scan.findingCount, + stage: scan.stage, + finishedAt: scan.finishedAt, + fixStatus: scan.fixStatus, + fixPrUrl: scan.fixPrUrl, + fixPrNumber: scan.fixPrNumber, + fixedCount: scan.fixedCount, + skippedCount: scan.skippedCount, + } + : null, + }; + }), + }); +}); + +// Findings + attack chains for a connected project's most recent scan. +githubRouter.get("/projects/:githubRepoId/findings", withAuth, async (req, res) => { + if (!env.dbConfigured) { + res.json({ findings: [], chains: [] }); + return; + } + const userId = await resolveUserId(req as AuthedRequest); + const githubRepoId = String(req.params.githubRepoId); + const [project] = await getDb() + .select() + .from(connectedRepositories) + .where(and(eq(connectedRepositories.userId, userId), eq(connectedRepositories.githubRepoId, githubRepoId))) + .limit(1); + if (!project) { + res.status(404).json({ error: "Project not found." }); + return; + } + + const latestScan = (await latestScansForRepos([project.id])).get(project.id); + if (!latestScan) { + res.json({ findings: [], chains: [] }); + return; + } + const [findings, chains] = await Promise.all([findingsForScan(latestScan.id), chainsForScan(latestScan.id)]); + res.json({ findings, chains }); +}); diff --git a/apps/backend/src/routes/health.ts b/apps/backend/src/routes/health.ts new file mode 100644 index 0000000..8b76b74 --- /dev/null +++ b/apps/backend/src/routes/health.ts @@ -0,0 +1,16 @@ +import { Router } from "express"; +import { env } from "../env"; + +export const healthRouter: Router = Router(); + +// Liveness + integration-configuration probe. Always 200 so it works before any +// credentials are set. +healthRouter.get("/health", (_req, res) => { + res.json({ + ok: true, + service: "cerebrus-backend", + authConfigured: env.authConfigured || env.localDevAuthEnabled, + dbConfigured: env.dbConfigured, + githubConfigured: env.githubConfigured, + }); +}); diff --git a/apps/backend/src/routes/internal.ts b/apps/backend/src/routes/internal.ts new file mode 100644 index 0000000..81f0715 --- /dev/null +++ b/apps/backend/src/routes/internal.ts @@ -0,0 +1,159 @@ +import { Router, type NextFunction, type Request, type Response } from "express"; +import { z } from "zod"; +import { env } from "../env"; +import { completeScan, failScan, markScanRunning } from "../db/scans"; +import { printScanLogSummary } from "../scan/log"; +import { logger } from "../lib/logger"; +import { insertChains, insertFindings, updateScanProgress } from "../db/findings"; +import { matchAdvisories } from "../cve/match"; +import { createFixPr } from "../fix/autofix"; +import type { NewFinding, NewFindingChain } from "../db/schema"; + +export const internalRouter: Router = Router(); + +// Scanner callbacks are authenticated by a shared secret (not a user session), so +// this whole router is mounted outside the withAuth routers. Guard every route. +function requireScanSecret(req: Request, res: Response, next: NextFunction): void { + if (!env.SCAN_CALLBACK_SECRET || req.headers["x-scan-secret"] !== env.SCAN_CALLBACK_SECRET) { + res.status(401).json({ error: "Unauthorized" }); + return; + } + next(); +} +internalRouter.use(requireScanSecret); + +const resultSchema = z.object({ + scanId: z.string(), + status: z.enum(["running", "completed", "failed"]), + fileCount: z.number().int().nonnegative().optional(), + error: z.string().optional(), +}); + +// Terminal (and initial "running") lifecycle transitions. +internalRouter.post("/scan-result", async (req, res) => { + const parsed = resultSchema.safeParse(req.body); + if (!parsed.success) { + res.status(400).json({ error: "Invalid payload" }); + return; + } + const { scanId, status, fileCount, error } = parsed.data; + if (status === "running") { + await markScanRunning(scanId); + } else if (status === "completed") { + await completeScan(scanId, fileCount ?? 0); + // Await within the request: on Cloud Run background work after the response is + // frozen. createFixPr catches its own errors, so this never fails the callback. + await createFixPr(scanId); + } else { + await failScan(scanId, error ?? "Scan failed"); + } + // Emit a consolidated log block to stdout so scan output appears in Cloud Run logs + // regardless of whether the scanner ran inline, in Docker, or as a Cloud Run job. + logger.info({ scanId, status }, "Scanner callback received"); + printScanLogSummary(scanId); + res.json({ ok: true }); +}); + +const progressSchema = z.object({ + scanId: z.string(), + filesScanned: z.number().int().nonnegative().optional(), + fileCount: z.number().int().nonnegative().optional(), + stage: z.string().optional(), +}); + +// Progress heartbeat during a scan (drives the "n/total files" UI). Also flips a +// still-queued scan to running. +internalRouter.post("/scan/progress", async (req, res) => { + const parsed = progressSchema.safeParse(req.body); + if (!parsed.success) { + res.status(400).json({ error: "Invalid payload" }); + return; + } + const { scanId, ...patch } = parsed.data; + await updateScanProgress(scanId, patch); + res.json({ ok: true }); +}); + +const findingSchema = z.object({ + id: z.string(), + filePath: z.string(), + severity: z.enum(["critical", "high", "medium", "low"]), + title: z.string(), + description: z.string(), + vulnerableCode: z.string(), + suggestedFix: z.string(), + startLine: z.number().int().nullable().optional(), + endLine: z.number().int().nullable().optional(), + category: z.enum(["code", "dependency", "os-package", "secret", "misconfig"]).optional(), + cveId: z.string().nullable().optional(), +}); +const findingsSchema = z.object({ scanId: z.string(), findings: z.array(findingSchema) }); + +// Streaming append of findings as the scanner discovers them. +internalRouter.post("/scan/findings", async (req, res) => { + const parsed = findingsSchema.safeParse(req.body); + if (!parsed.success) { + res.status(400).json({ error: "Invalid payload" }); + return; + } + const { scanId, findings } = parsed.data; + const rows: NewFinding[] = findings.map((f) => ({ + id: f.id, + scanId, + filePath: f.filePath, + severity: f.severity, + title: f.title, + description: f.description, + vulnerableCode: f.vulnerableCode, + suggestedFix: f.suggestedFix, + startLine: f.startLine ?? null, + endLine: f.endLine ?? null, + category: f.category ?? "code", + cveId: f.cveId ?? null, + })); + await insertFindings(scanId, rows); + res.json({ ok: true }); +}); + +const chainSchema = z.object({ + title: z.string(), + severity: z.enum(["critical", "high", "medium", "low"]), + description: z.string(), + steps: z.array(z.object({ findingId: z.string().optional(), filePath: z.string(), note: z.string() })), +}); +const chainsSchema = z.object({ scanId: z.string(), chains: z.array(chainSchema) }); + +// Attack-chain results from the chaining pass. +internalRouter.post("/scan/chains", async (req, res) => { + const parsed = chainsSchema.safeParse(req.body); + if (!parsed.success) { + res.status(400).json({ error: "Invalid payload" }); + return; + } + const { scanId, chains } = parsed.data; + const rows: NewFindingChain[] = chains.map((c) => ({ + scanId, + title: c.title, + severity: c.severity, + description: c.description, + steps: c.steps, + })); + await insertChains(scanId, rows); + res.json({ ok: true }); +}); + +const cveCheckSchema = z.object({ + deps: z.array(z.object({ ecosystem: z.string(), name: z.string(), version: z.string() })), +}); + +// Dependency → CVE lookup (OSV querybatch + cached advisories). Called by the CLI +// before its chaining pass. Matching lives here so the DB/OSV logic stays backend-side. +internalRouter.post("/scan/cve-check", async (req, res) => { + const parsed = cveCheckSchema.safeParse(req.body); + if (!parsed.success) { + res.status(400).json({ error: "Invalid payload" }); + return; + } + const matches = await matchAdvisories(parsed.data.deps); + res.json({ matches }); +}); diff --git a/apps/backend/src/routes/registry.ts b/apps/backend/src/routes/registry.ts new file mode 100644 index 0000000..1ef02d6 --- /dev/null +++ b/apps/backend/src/routes/registry.ts @@ -0,0 +1,229 @@ +import { Router } from "express"; +import { z } from "zod"; +import { env } from "../env"; +import { withAuth, type AuthedRequest } from "../middleware/auth"; +import { upsertUser } from "../db/users"; +import { encrypt, decrypt } from "../lib/crypto"; +import { + deleteRegistryConnection, + getRegistryConnection, + listRegistryConnections, + upsertRegistryConnection, +} from "../db/registry"; +import { connectImage, getImage, listImages } from "../db/images"; +import { createImageScan, latestScansForImages } from "../db/scans"; +import { chainsForScan, findingsForScan } from "../db/findings"; +import { triggerImageScan } from "../scan/runner"; +import { displayImageRef, normalizeRepository, pullEndpoint } from "../registry/endpoints"; +import { listRegistryImages, validateCredential } from "../registry/list"; +import type { RegistryConnection } from "../db/schema"; + +export const registryRouter: Router = Router(); + +function resolveUserId(req: AuthedRequest): Promise { + return upsertUser({ id: req.user.id, email: req.user.email, firstName: req.user.firstName, lastName: req.user.lastName }); +} + +// Never leak the encrypted secret to the browser. +function sanitize(conn: RegistryConnection) { + return { + id: conn.id, + type: conn.type, + host: conn.host, + username: conn.username, + extra: conn.extra, + createdAt: conn.createdAt, + }; +} + +function requireDb(res: import("express").Response): boolean { + if (!env.dbConfigured) { + res.status(503).json({ error: "Database is not configured." }); + return false; + } + return true; +} + +registryRouter.get("/connections", withAuth, async (req, res) => { + if (!env.dbConfigured) { + res.json({ connections: [] }); + return; + } + const userId = await resolveUserId(req as AuthedRequest); + const connections = await listRegistryConnections(userId); + res.json({ connections: connections.map(sanitize) }); +}); + +const connectSchema = z.object({ + type: z.enum(["dockerhub", "ghcr", "gar", "ecr", "generic"]), + host: z.string().min(1), + username: z.string().optional(), + secret: z.string().optional(), + extra: z.record(z.string(), z.unknown()).optional(), +}); + +registryRouter.post("/connect", withAuth, async (req, res) => { + if (!requireDb(res)) return; + const parsed = connectSchema.safeParse(req.body); + if (!parsed.success) { + res.status(400).json({ error: "Invalid registry payload." }); + return; + } + const { type, host, username, secret, extra } = parsed.data; + const endpoint = pullEndpoint(type, host); + if (!(await validateCredential(endpoint, username ?? null, secret ?? null))) { + res.status(400).json({ error: "Could not authenticate to the registry with those credentials." }); + return; + } + const userId = await resolveUserId(req as AuthedRequest); + const conn = await upsertRegistryConnection({ + userId, + type, + host, + username: username ?? null, + secretEncrypted: secret ? encrypt(secret) : null, + extra: extra ?? null, + }); + res.json({ connection: sanitize(conn) }); +}); + +registryRouter.delete("/connections/:id", withAuth, async (req, res) => { + if (!requireDb(res)) return; + const userId = await resolveUserId(req as AuthedRequest); + await deleteRegistryConnection(userId, String(req.params.id)); + res.json({ ok: true }); +}); + +// Available image repositories in a connected registry (for the picker). +registryRouter.get("/connections/:id/images", withAuth, async (req, res) => { + if (!requireDb(res)) return; + const userId = await resolveUserId(req as AuthedRequest); + const conn = await getRegistryConnection(userId, String(req.params.id)); + if (!conn) { + res.status(404).json({ error: "Registry connection not found." }); + return; + } + const secret = conn.secretEncrypted ? safeDecrypt(conn.secretEncrypted) : null; + const images = await listRegistryImages(conn, secret); + res.json({ images }); +}); + +const connectImageSchema = z.object({ + registryConnectionId: z.string(), + repository: z.string().min(1), + tag: z.string().optional(), +}); + +registryRouter.post("/images/connect", withAuth, async (req, res) => { + if (!requireDb(res)) return; + const parsed = connectImageSchema.safeParse(req.body); + if (!parsed.success) { + res.status(400).json({ error: "Invalid image payload." }); + return; + } + const userId = await resolveUserId(req as AuthedRequest); + const conn = await getRegistryConnection(userId, parsed.data.registryConnectionId); + if (!conn) { + res.status(404).json({ error: "Registry connection not found." }); + return; + } + const tag = parsed.data.tag ?? "latest"; + const repository = normalizeRepository(conn.type, parsed.data.repository.trim()); + const image = await connectImage({ + userId, + registryConnectionId: conn.id, + repository, + tag, + imageRef: displayImageRef(conn.type, conn.host, repository, tag), + name: repository.split("/").pop() ?? repository, + }); + const scanId = await startImageScan(conn, repository, tag, image.id); + res.json({ image, scanId }); +}); + +registryRouter.get("/images", withAuth, async (req, res) => { + if (!env.dbConfigured) { + res.json({ images: [] }); + return; + } + const userId = await resolveUserId(req as AuthedRequest); + const images = await listImages(userId); + const scanByImage = await latestScansForImages(images.map((i) => i.id)); + res.json({ + images: images.map((image) => { + const scan = scanByImage.get(image.id); + return { + ...image, + scan: scan + ? { + status: scan.status, + findingCount: scan.findingCount, + stage: scan.stage, + finishedAt: scan.finishedAt, + } + : null, + }; + }), + }); +}); + +registryRouter.get("/images/:id/findings", withAuth, async (req, res) => { + if (!env.dbConfigured) { + res.json({ findings: [], chains: [] }); + return; + } + const userId = await resolveUserId(req as AuthedRequest); + const image = await getImage(userId, String(req.params.id)); + if (!image) { + res.status(404).json({ error: "Image not found." }); + return; + } + const scan = (await latestScansForImages([image.id])).get(image.id); + if (!scan) { + res.json({ findings: [], chains: [] }); + return; + } + const [findings, chains] = await Promise.all([findingsForScan(scan.id), chainsForScan(scan.id)]); + res.json({ findings, chains }); +}); + +registryRouter.post("/images/:id/scan", withAuth, async (req, res) => { + if (!requireDb(res)) return; + const userId = await resolveUserId(req as AuthedRequest); + const image = await getImage(userId, String(req.params.id)); + if (!image) { + res.status(404).json({ error: "Image not found." }); + return; + } + const conn = await getRegistryConnection(userId, image.registryConnectionId); + if (!conn) { + res.status(409).json({ error: "Registry connection unavailable — reconnect the registry." }); + return; + } + const scanId = await startImageScan(conn, image.repository, image.tag, image.id); + res.json({ scanId }); +}); + +// Creates the scan row and dispatches the image scan (awaited so the cloudrun jobs:run +// call completes within the request — see runner.ts). +async function startImageScan(conn: RegistryConnection, repository: string, tag: string, imageId: string): Promise { + const scanId = await createImageScan(imageId); + const secret = conn.secretEncrypted ? safeDecrypt(conn.secretEncrypted) : null; + await triggerImageScan({ + scanId, + imageRef: `${repository}:${tag}`, + registryHost: pullEndpoint(conn.type, conn.host), + registryType: conn.type, + registryUsername: conn.username ?? undefined, + registryToken: secret ?? undefined, + }); + return scanId; +} + +function safeDecrypt(payload: string): string | null { + try { + return decrypt(payload); + } catch { + return null; + } +} diff --git a/apps/backend/src/scan/dbReporter.ts b/apps/backend/src/scan/dbReporter.ts new file mode 100644 index 0000000..4c8b21a --- /dev/null +++ b/apps/backend/src/scan/dbReporter.ts @@ -0,0 +1,77 @@ +import type { ChainInput, CveMatch, DepInput, FindingInput, Reporter } from "@cerebrus/cli"; +import { completeScan, failScan, markScanRunning } from "../db/scans"; +import { insertChains, insertFindings, updateScanProgress } from "../db/findings"; +import { matchAdvisories } from "../cve/match"; +import type { NewFinding, NewFindingChain } from "../db/schema"; +import type { ScanLog } from "./log"; + +// Reporter used by inline mode: the scan runs in-process inside the backend, so it +// writes straight to Postgres (and the shared CVE matcher) instead of the HTTP +// callbacks the containerized HttpReporter uses. Same interface, different transport. +export class DbReporter implements Reporter { + private readonly scanId: string; + private readonly log?: ScanLog; + + constructor(scanId: string, log?: ScanLog) { + this.scanId = scanId; + this.log = log; + } + + async setRunning(): Promise { + this.log?.write("scan running"); + await markScanRunning(this.scanId); + } + + async progress(filesScanned: number, fileCount: number, stage?: string): Promise { + this.log?.write(`progress ${filesScanned}/${fileCount}${stage ? ` (${stage})` : ""}`); + await updateScanProgress(this.scanId, { filesScanned, fileCount, stage }); + } + + async addFindings(findings: FindingInput[]): Promise { + if (findings.length === 0) return; + this.log?.write(`+${findings.length} finding(s): ${findings.map((f) => `${f.severity} ${f.filePath}`).join(", ")}`); + const rows: NewFinding[] = findings.map((f) => ({ + id: f.id, + scanId: this.scanId, + filePath: f.filePath, + severity: f.severity, + title: f.title, + description: f.description, + vulnerableCode: f.vulnerableCode, + suggestedFix: f.suggestedFix, + startLine: f.startLine ?? null, + endLine: f.endLine ?? null, + category: f.category ?? "code", + cveId: f.cveId ?? null, + })); + await insertFindings(this.scanId, rows); + } + + async addChains(chains: ChainInput[]): Promise { + if (chains.length === 0) return; + this.log?.write(`+${chains.length} attack chain(s)`); + const rows: NewFindingChain[] = chains.map((c) => ({ + scanId: this.scanId, + title: c.title, + severity: c.severity, + description: c.description, + steps: c.steps, + })); + await insertChains(this.scanId, rows); + } + + matchDependencies(deps: DepInput[]): Promise { + this.log?.write(`cve-check ${deps.length} dependency(ies)`); + return matchAdvisories(deps); + } + + async complete(fileCount: number): Promise { + this.log?.write(`scan completed (${fileCount} files)`); + await completeScan(this.scanId, fileCount); + } + + async fail(error: string): Promise { + this.log?.write(`scan failed: ${error}`); + await failScan(this.scanId, error); + } +} diff --git a/apps/backend/src/scan/log.ts b/apps/backend/src/scan/log.ts new file mode 100644 index 0000000..7e37f12 --- /dev/null +++ b/apps/backend/src/scan/log.ts @@ -0,0 +1,58 @@ +import { appendFileSync, existsSync, mkdirSync, openSync, readFileSync } from "node:fs"; +import { join, resolve } from "node:path"; +import { env } from "../env"; + +// Per-scan logs live at /scan-.log so a user can `tail` a +// running scan and inspect failures after the fact. The dir is resolved relative to +// the backend's CWD. +function ensureLogDir(): string { + const dir = resolve(env.SCAN_LOG_DIR); + mkdirSync(dir, { recursive: true }); + return dir; +} + +export function scanLogPath(scanId: string): string { + return join(ensureLogDir(), `scan-${scanId}.log`); +} + +// Opens an append fd for a scan log — used by the docker runner to redirect the +// container's stdout/stderr straight into the host file. +export function openScanLogFd(scanId: string): number { + return openSync(scanLogPath(scanId), "a"); +} + +export interface ScanLog { + write(message: string): void; + close(): void; +} + +// A simple line-writer used by inline mode (the scan runs in-process, so we can't +// just redirect a child's stdio). Timestamps each line to mirror container logs. +// Uses synchronous writes so the file is always current and can be summarized +// immediately after a scan finishes. +export function openScanLog(scanId: string): ScanLog { + const path = scanLogPath(scanId); + return { + write(message: string) { + appendFileSync(path, `[${new Date().toISOString()}] ${message}\n`); + }, + close() { + // Sync writes flush immediately; nothing to close. + }, + }; +} + +// Prints the contents of a scan log to stdout as a single summarized block so it +// shows up in Cloud Run / gcloud logs. Safe to call even if the log does not exist. +export function printScanLogSummary(scanId: string): void { + const path = scanLogPath(scanId); + if (!existsSync(path)) return; + const content = readFileSync(path, "utf-8").trimEnd(); + if (!content) return; + + console.log(`===Logs Summary=== [scanId=${scanId}]`); + for (const line of content.split("\n")) { + console.log(line); + } + console.log(`===End Logs Summary=== [scanId=${scanId}]`); +} diff --git a/apps/backend/src/scan/runner.ts b/apps/backend/src/scan/runner.ts new file mode 100644 index 0000000..5a359f0 --- /dev/null +++ b/apps/backend/src/scan/runner.ts @@ -0,0 +1,200 @@ +import { spawn } from "node:child_process"; +import { closeSync } from "node:fs"; +import { scanImage, scanRepository } from "@cerebrus/cli"; +import { env } from "../env"; +import { logger } from "../lib/logger"; +import { failScan } from "../db/scans"; +import { DbReporter } from "./dbReporter"; +import { openScanLog, openScanLogFd, printScanLogSummary } from "./log"; +import { createFixPr } from "../fix/autofix"; +import { gcpAccessToken, gcpProjectId } from "../lib/gcp"; + +export interface ScanJob { + scanId: string; + owner: string; + repo: string; + ref?: string; + token: string; +} + +export interface ImageScanJob { + scanId: string; + imageRef: string; // "repository:tag" + registryHost: string; // pull endpoint + registryType: string; + registryUsername?: string; + registryToken?: string; +} + +// --- Repo scans ------------------------------------------------------------------- + +export function triggerScan(job: ScanJob): Promise { + const envArgs = scanEnvArgs(job); + switch (env.SCAN_RUNNER) { + case "inline": + runInlineRepo(job); + return Promise.resolve(); + case "docker": + launchDocker(job.scanId, envArgs); + return Promise.resolve(); + case "cloudrun": + return launchCloudRun(job.scanId, envArgs); + } + return Promise.resolve(); +} + +function runInlineRepo(job: ScanJob): void { + void (async () => { + const log = openScanLog(job.scanId); + const reporter = new DbReporter(job.scanId, log); + try { + await scanRepository({ owner: job.owner, repo: job.repo, ref: job.ref, token: job.token }, reporter); + await createFixPr(job.scanId); // repo-only; no-ops otherwise + } catch (err) { + await reporter.fail(err instanceof Error ? err.message : String(err)).catch(() => {}); + } finally { + printScanLogSummary(job.scanId); + log.close(); + } + })(); +} + +// --- Image scans ------------------------------------------------------------------ + +export function triggerImageScan(job: ImageScanJob): Promise { + const envArgs = imageScanEnvArgs(job); + switch (env.SCAN_RUNNER) { + case "inline": + runInlineImage(job); + return Promise.resolve(); + case "docker": + launchDocker(job.scanId, envArgs); + return Promise.resolve(); + case "cloudrun": + return launchCloudRun(job.scanId, envArgs); + } + return Promise.resolve(); +} + +function runInlineImage(job: ImageScanJob): void { + void (async () => { + const log = openScanLog(job.scanId); + const reporter = new DbReporter(job.scanId, log); + try { + // No createFixPr — image scans have no repo to open a PR against. + await scanImage( + { + imageRef: job.imageRef, + registryHost: job.registryHost, + username: job.registryUsername, + token: job.registryToken, + }, + reporter, + ); + } catch (err) { + await reporter.fail(err instanceof Error ? err.message : String(err)).catch(() => {}); + } finally { + printScanLogSummary(job.scanId); + log.close(); + } + })(); +} + +// --- Shared launchers ------------------------------------------------------------- + +function launchDocker(scanId: string, envArgs: string[]): void { + if (!env.SCANNER_IMAGE) { + void failScan(scanId, "SCANNER_IMAGE is not configured"); + return; + } + const args = [ + "run", + "--rm", + "--add-host=host.docker.internal:host-gateway", + ...envArgs.flatMap((e) => ["-e", e]), + env.SCANNER_IMAGE, + ]; + // Redirect container stdout/stderr into the per-scan host log file. + const logFd = openScanLogFd(scanId); + const proc = spawn("docker", args, { stdio: ["ignore", logFd, logFd], detached: true }); + closeSync(logFd); // child has its own dup + proc.on("error", (err) => { + logger.error({ err }, "Failed to launch scanner container"); + void failScan(scanId, `docker launch failed: ${err.message}`); + }); + proc.unref(); +} + +async function launchCloudRun(scanId: string, envArgs: string[]): Promise { + try { + const project = gcpProjectId(); + if (!project) throw new Error("GCP project not configured (set GCP_PROJECT or GOOGLE_SERVICE_ACCOUNT_JSON / GOOGLE_APPLICATION_CREDENTIALS)"); + logger.info({ scanId, jobName: env.SCAN_JOB_NAME, region: env.GCP_REGION }, "Launching Cloud Run scanner job"); + const token = await gcpAccessToken(); + const url = `https://${env.GCP_REGION}-run.googleapis.com/v2/projects/${project}/locations/${env.GCP_REGION}/jobs/${env.SCAN_JOB_NAME}:run`; + const body = { overrides: { containerOverrides: [{ env: envArgs.map((pair) => splitEnv(pair)) }] } }; + const res = await fetch(url, { + method: "POST", + headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" }, + body: JSON.stringify(body), + }); + if (!res.ok) throw new Error(`Cloud Run job run failed (${res.status}): ${await res.text()}`); + logger.info({ scanId, jobName: env.SCAN_JOB_NAME }, "Cloud Run scanner job launched"); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + logger.error({ scanId, err: message }, "Failed to launch Cloud Run scanner job"); + await failScan(scanId, message).catch(() => {}); + } +} + +// --- Env builders ----------------------------------------------------------------- + +// DeepSeek config passed to every scanner container (never DB creds). +function deepseekEnvArgs(): string[] { + return [ + `DEEPSEEK_API_KEY=${env.DEEPSEEK_API_KEY ?? ""}`, + `DEEPSEEK_MODEL=${env.DEEPSEEK_MODEL}`, + `DEEPSEEK_BASE_URL=${env.DEEPSEEK_BASE_URL}`, + `DEEPSEEK_CONCURRENCY=${env.DEEPSEEK_CONCURRENCY}`, + ]; +} + +function callbackEnvArgs(scanId: string): string[] { + return [ + `SCAN_ID=${scanId}`, + `SCAN_CALLBACK_URL=${env.SCAN_CALLBACK_URL}`, + `SCAN_CALLBACK_SECRET=${env.SCAN_CALLBACK_SECRET ?? ""}`, + ]; +} + +function scanEnvArgs(job: ScanJob): string[] { + const pairs = [ + ...callbackEnvArgs(job.scanId), + `SCAN_TARGET=repo`, + `REPO_OWNER=${job.owner}`, + `REPO_NAME=${job.repo}`, + `GITHUB_TOKEN=${job.token}`, + ...deepseekEnvArgs(), + ]; + if (job.ref) pairs.push(`REPO_REF=${job.ref}`); + return pairs; +} + +function imageScanEnvArgs(job: ImageScanJob): string[] { + const pairs = [ + ...callbackEnvArgs(job.scanId), + `SCAN_TARGET=image`, + `IMAGE_REF=${job.imageRef}`, + `REGISTRY_TYPE=${job.registryType}`, + `REGISTRY_HOST=${job.registryHost}`, + ...deepseekEnvArgs(), + ]; + if (job.registryUsername) pairs.push(`REGISTRY_USERNAME=${job.registryUsername}`); + if (job.registryToken) pairs.push(`REGISTRY_TOKEN=${job.registryToken}`); + return pairs; +} + +function splitEnv(pair: string): { name: string; value: string } { + const idx = pair.indexOf("="); + return { name: pair.slice(0, idx), value: pair.slice(idx + 1) }; +} diff --git a/apps/backend/tsconfig.json b/apps/backend/tsconfig.json new file mode 100644 index 0000000..aec8780 --- /dev/null +++ b/apps/backend/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@cerebrus/typescript-config/bun-app.json", + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo", + "types": ["node", "bun"] + }, + "include": ["src", "drizzle.config.ts"] +} diff --git a/apps/cli/Dockerfile b/apps/cli/Dockerfile new file mode 100644 index 0000000..8605973 --- /dev/null +++ b/apps/cli/Dockerfile @@ -0,0 +1,22 @@ +# syntax=docker/dockerfile:1 +# Build context is the repo ROOT (so the bun workspace resolves). + +# ---- Build: install workspace deps and bundle the scanner ---- +FROM oven/bun:1.3.14 AS build +WORKDIR /repo +COPY package.json bun.lock turbo.json ./ +COPY packages/typescript-config/package.json ./packages/typescript-config/ +COPY packages/eslint-config/package.json ./packages/eslint-config/ +COPY apps/backend/package.json ./apps/backend/ +COPY apps/frontend/package.json ./apps/frontend/ +COPY apps/cli/package.json ./apps/cli/ +RUN bun install --frozen-lockfile +COPY . . +RUN cd apps/cli && bun build src/index.ts --target bun --outdir dist + +# ---- Runtime: run-to-completion scanner (tar ships in the Debian-based image) ---- +FROM oven/bun:1.3.14-slim AS runtime +WORKDIR /app +ENV NODE_ENV=production +COPY --from=build /repo/apps/cli/dist ./dist +CMD ["bun", "dist/index.js"] diff --git a/apps/cli/cloudbuild.yaml b/apps/cli/cloudbuild.yaml new file mode 100644 index 0000000..86f9667 --- /dev/null +++ b/apps/cli/cloudbuild.yaml @@ -0,0 +1,27 @@ +# Build + push the scanner image and create/update the Cloud Run JOB. Run from the +# repo root (separate from deploy.sh, which deploys only the two services): +# gcloud builds submit --config apps/cli/cloudbuild.yaml \ +# --substitutions=_REGION=us-central1,_IMAGE=us-central1-docker.pkg.dev/PROJECT/cerebrus/scanner . +steps: + - name: gcr.io/cloud-builders/docker + args: ["build", "-f", "apps/cli/Dockerfile", "-t", "${_IMAGE}:${BUILD_ID}", "-t", "${_IMAGE}:latest", "."] + - name: gcr.io/cloud-builders/docker + args: ["push", "--all-tags", "${_IMAGE}"] + - name: gcr.io/google.com/cloudsdktool/cloud-sdk + entrypoint: gcloud + args: + - "run" + - "jobs" + - "deploy" + - "cerebrus-scanner" + - "--image=${_IMAGE}:${BUILD_ID}" + - "--region=${_REGION}" + - "--max-retries=1" + - "--task-timeout=600" +images: + - "${_IMAGE}" +substitutions: + _REGION: "us-central1" + _IMAGE: "us-central1-docker.pkg.dev/${PROJECT_ID}/cerebrus/scanner" +options: + logging: CLOUD_LOGGING_ONLY diff --git a/apps/cli/eslint.config.js b/apps/cli/eslint.config.js new file mode 100644 index 0000000..a523e24 --- /dev/null +++ b/apps/cli/eslint.config.js @@ -0,0 +1,3 @@ +import base from "@cerebrus/eslint-config/base"; + +export default base; diff --git a/apps/cli/package.json b/apps/cli/package.json new file mode 100644 index 0000000..08b7a11 --- /dev/null +++ b/apps/cli/package.json @@ -0,0 +1,23 @@ +{ + "name": "@cerebrus/cli", + "private": true, + "version": "0.0.0", + "type": "module", + "main": "src/scan.ts", + "exports": { + ".": "./src/scan.ts" + }, + "scripts": { + "start": "bun src/index.ts", + "build": "bun build src/index.ts --target bun --outdir dist", + "check-types": "tsc --noEmit", + "lint": "eslint ." + }, + "devDependencies": { + "@cerebrus/eslint-config": "*", + "@cerebrus/typescript-config": "*", + "@types/node": "^24.13.2", + "eslint": "^10.5.0", + "typescript": "~6.0.2" + } +} diff --git a/apps/cli/src/deepseek.ts b/apps/cli/src/deepseek.ts new file mode 100644 index 0000000..525fc73 --- /dev/null +++ b/apps/cli/src/deepseek.ts @@ -0,0 +1,115 @@ +// Minimal DeepSeek client (OpenAI-compatible chat completions) over global fetch — +// no SDK, keeping the CLI dependency-free. +// +// IMPORTANT: the default model is `deepseek-reasoner` (R1), which does NOT support +// `response_format: json_object` or tool-calling and returns its chain-of-thought in +// a separate `reasoning_content` field. So we never send response_format/tools, read +// only `message.content` (ignoring reasoning_content), and parse JSON out of it +// defensively with retries. + +export interface DeepSeekConfig { + apiKey: string; + model: string; + baseUrl: string; + concurrency: number; +} + +export interface ChatMessage { + role: "system" | "user" | "assistant"; + content: string; +} + +export function loadDeepSeekConfig(): DeepSeekConfig { + const apiKey = process.env.DEEPSEEK_API_KEY; + if (!apiKey) throw new Error("DEEPSEEK_API_KEY is not set — cannot run the scan."); + return { + apiKey, + model: process.env.DEEPSEEK_MODEL || "deepseek-reasoner", + baseUrl: process.env.DEEPSEEK_BASE_URL || "https://api.deepseek.com", + concurrency: Math.max(1, Number.parseInt(process.env.DEEPSEEK_CONCURRENCY || "4", 10) || 4), + }; +} + +export class JsonParseError extends Error {} + +export async function callDeepSeek(config: DeepSeekConfig, messages: ChatMessage[]): Promise { + const res = await fetch(`${config.baseUrl}/chat/completions`, { + method: "POST", + headers: { "Content-Type": "application/json", Authorization: `Bearer ${config.apiKey}` }, + body: JSON.stringify({ model: config.model, messages, stream: false }), + }); + if (!res.ok) throw new Error(`DeepSeek request failed (${res.status}): ${await res.text()}`); + const data = (await res.json()) as { choices?: { message?: { content?: string } }[] }; + const content = data.choices?.[0]?.message?.content; + if (!content) throw new Error("DeepSeek returned an empty response"); + return content; +} + +// Extracts a JSON value from a possibly-noisy LLM reply: tries a direct parse, then +// strips markdown fences, then scans for the first balanced {...} / [...] region +// (respecting string literals + escapes so braces inside strings don't fool it). +export function parseJsonLoose(raw: string): T { + const attempts: string[] = [raw.trim()]; + + const fence = raw.match(/```(?:json)?\s*([\s\S]*?)```/i); + if (fence) attempts.push(fence[1].trim()); + + const balanced = extractBalanced(raw); + if (balanced) attempts.push(balanced); + + for (const candidate of attempts) { + try { + return JSON.parse(candidate) as T; + } catch { + // try the next candidate + } + } + throw new JsonParseError("Could not parse JSON from model reply"); +} + +function extractBalanced(raw: string): string | null { + const start = raw.search(/[[{]/); + if (start === -1) return null; + const open = raw[start]; + const close = open === "{" ? "}" : "]"; + let depth = 0; + let inString = false; + let escaped = false; + for (let i = start; i < raw.length; i++) { + const ch = raw[i]; + if (inString) { + if (escaped) escaped = false; + else if (ch === "\\") escaped = true; + else if (ch === '"') inString = false; + continue; + } + if (ch === '"') inString = true; + else if (ch === open) depth++; + else if (ch === close) { + depth--; + if (depth === 0) return raw.slice(start, i + 1); + } + } + return null; +} + +// Calls the model and parses JSON, retrying with a corrective nudge when the reply +// isn't valid JSON. +export async function askForJson(config: DeepSeekConfig, messages: ChatMessage[], retries = 2): Promise { + const convo = [...messages]; + let lastErr: unknown; + for (let attempt = 0; attempt <= retries; attempt++) { + const reply = await callDeepSeek(config, convo); + try { + return parseJsonLoose(reply); + } catch (err) { + lastErr = err; + convo.push({ role: "assistant", content: reply }); + convo.push({ + role: "user", + content: "Your previous reply was not valid JSON. Reply with ONLY the JSON value — no prose, no markdown code fences.", + }); + } + } + throw lastErr instanceof Error ? lastErr : new JsonParseError(String(lastErr)); +} diff --git a/apps/cli/src/image.ts b/apps/cli/src/image.ts new file mode 100644 index 0000000..f5c3148 --- /dev/null +++ b/apps/cli/src/image.ts @@ -0,0 +1,193 @@ +import { randomUUID } from "node:crypto"; +import { mkdir, mkdtemp, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { OciClient, type ImageConfig, type RegistryAuth } from "./registry/oci"; +import { inventoryOsPackages } from "./osdb"; +import { scanSecrets } from "./secrets"; +import { askForJson, loadDeepSeekConfig } from "./deepseek"; +import type { RawFinding } from "./prompts"; +import type { ChatMessage } from "./deepseek"; +import type { CveMatch, FindingInput, Reporter, Severity } from "./reporter"; +import { error, info, warn } from "./logger"; + +export interface ImageScanOptions { + imageRef: string; // "repository:tag" or "repository@sha256:…" (host is separate) + registryHost: string; // pull endpoint, e.g. registry-1.docker.io + username?: string; + token?: string; +} + +export interface ImageScanResult { + packageCount: number; + findingCount: number; +} + +const SEVERITIES: Severity[] = ["critical", "high", "medium", "low"]; +const LAYER_BYTES_CAP = 800 * 1024 * 1024; // cumulative compressed layer budget + +// Pulls the image, inventories OS packages → OSV CVEs, scans for baked secrets, and +// runs a DeepSeek config/runtime analysis — streaming findings to the reporter. +export async function scanImage(opts: ImageScanOptions, reporter: Reporter): Promise { + const { repository, ref } = splitRef(opts.imageRef); + const auth: RegistryAuth = { host: opts.registryHost, username: opts.username, token: opts.token }; + const client = new OciClient(auth, repository); + const workDir = await mkdtemp(join(tmpdir(), "cerebrus-image-")); + const rootfs = join(workDir, "rootfs"); + let findingCount = 0; + + try { + await reporter.setRunning(); + await reporter.progress(0, 0, "pull"); + info(`Pulling ${opts.registryHost}/${repository}:${ref}`); + await client.authenticate(); + const { config, layers } = await client.resolve(ref); + + await mkdir(rootfs, { recursive: true }); + let budget = LAYER_BYTES_CAP; + for (const layer of layers) { + if (layer.size > budget) { + warn(`skipping layer ${layer.digest} (${layer.size} bytes, over remaining budget)`); + continue; + } + budget -= layer.size; + await client.extractLayer(layer, rootfs); + } + + // OS packages → OSV CVEs. + await reporter.progress(0, 0, "packages"); + const inv = await inventoryOsPackages(rootfs); + info(`Distro: ${inv.distro ?? "unknown"} (${inv.ecosystem ?? "no OSV ecosystem"}), ${inv.packages.length} packages`); + if (inv.packages.length > 0) { + await reporter.progress(0, inv.packages.length, "cve-check"); + const matches = await reporter.matchDependencies(inv.packages); + const cveFindings = matches.map((m) => toCveFinding(m)); + if (cveFindings.length > 0) { + await reporter.addFindings(cveFindings); + findingCount += cveFindings.length; + info(` ${cveFindings.length} package CVE finding(s)`); + } + } + + // Baked-in secrets. + await reporter.progress(0, 0, "secrets"); + const secretFindings = await scanSecrets(rootfs); + if (secretFindings.length > 0) { + await reporter.addFindings(secretFindings); + findingCount += secretFindings.length; + info(` ${secretFindings.length} secret finding(s)`); + } + + // Config / runtime misconfig via DeepSeek. + await reporter.progress(0, 0, "misconfig"); + const misconfigFindings = await analyzeConfig(config, inv.distro, inv.packages.length); + if (misconfigFindings.length > 0) { + await reporter.addFindings(misconfigFindings); + findingCount += misconfigFindings.length; + info(` ${misconfigFindings.length} misconfig finding(s)`); + } + + await reporter.complete(inv.packages.length); + return { packageCount: inv.packages.length, findingCount }; + } finally { + await rm(workDir, { recursive: true, force: true }); + } +} + +function toCveFinding(m: CveMatch): FindingInput { + return { + id: randomUUID(), + filePath: `pkg:${m.name}`, + severity: normalizeSeverity(m.severity ?? "high"), + title: `${m.name} ${m.version}: ${m.cveId ?? m.osvId}`, + description: m.summary ?? `Known vulnerability ${m.osvId} affects ${m.name} ${m.version} in this image.`, + vulnerableCode: `${m.name} ${m.version}`, + suggestedFix: m.fixedVersion + ? `Upgrade ${m.name} to ${m.fixedVersion} or later — rebuild the image on a patched base.` + : `Upgrade ${m.name} to a patched version / rebuild on an updated base image.`, + category: "os-package", + cveId: m.cveId ?? m.osvId, + }; +} + +async function analyzeConfig(config: ImageConfig, distro: string | null, packageCount: number): Promise { + const summary = { + user: config.User || "(root)", + exposedPorts: Object.keys(config.ExposedPorts ?? {}), + env: redactEnv(config.Env ?? []), + entrypoint: config.Entrypoint ?? config.Cmd ?? [], + workingDir: config.WorkingDir ?? null, + volumes: Object.keys(config.Volumes ?? {}), + labels: config.Labels ?? {}, + distro, + packageCount, + }; + try { + const { findings } = await askForJson<{ findings?: RawFinding[] }>(loadDeepSeekConfig(), configPrompt(summary)); + return (findings ?? []) + .filter((f) => f.title && f.description) + .map((f) => ({ + id: randomUUID(), + filePath: "image-config", + severity: normalizeSeverity(f.severity), + title: (f.title as string).slice(0, 300), + description: f.description as string, + vulnerableCode: f.vulnerable_code ?? "", + suggestedFix: f.suggested_fix ?? "", + category: "misconfig" as const, + })); + } catch (err) { + error(" misconfig analysis failed", { reason: err instanceof Error ? err.message : String(err) }); + return []; + } +} + +function configPrompt(summary: unknown): ChatMessage[] { + return [ + { + role: "system", + content: + "You are a container security auditor. Given a container image's runtime configuration and metadata, identify " + + "real cloud/runtime security risks: running as root, exposed sensitive ports (SSH/DB/daemon), secrets in env " + + "vars, missing non-root USER, dangerous entrypoints (curl|bash), writable/host volumes, or an end-of-life base " + + "image. Ignore cosmetic issues. Reply with a single JSON object only.", + }, + { + role: "user", + content: + `Image configuration:\n${JSON.stringify(summary, null, 2)}\n\n` + + `Return JSON exactly: {"findings":[{"severity":"critical|high|medium|low","title":"short title",` + + `"description":"the runtime/cloud impact","vulnerable_code":"the offending setting, e.g. USER root or EXPOSE 22",` + + `"suggested_fix":"how to remediate"}]}. If nothing is wrong, return {"findings":[]}.`, + }, + ]; +} + +// Masks env values that look like secrets so we never send/store credentials. +function redactEnv(env: string[]): string[] { + const secretKey = /(secret|token|key|pass|pwd|credential)/i; + return env.map((pair) => { + const idx = pair.indexOf("="); + if (idx === -1) return pair; + const key = pair.slice(0, idx); + const value = pair.slice(idx + 1); + if (secretKey.test(key) || /^[A-Za-z0-9_\-./+]{20,}$/.test(value)) return `${key}=`; + return pair; + }); +} + +function normalizeSeverity(value: string | undefined): Severity { + const v = (value ?? "").toLowerCase(); + if (v === "moderate") return "medium"; + return (SEVERITIES as string[]).includes(v) ? (v as Severity) : "medium"; +} + +function splitRef(ref: string): { repository: string; ref: string } { + if (ref.includes("@")) { + const [repository, digest] = ref.split("@"); + return { repository, ref: digest }; + } + const i = ref.lastIndexOf(":"); + if (i > 0) return { repository: ref.slice(0, i), ref: ref.slice(i + 1) }; + return { repository: ref, ref: "latest" }; +} diff --git a/apps/cli/src/index.ts b/apps/cli/src/index.ts new file mode 100644 index 0000000..e4023c3 --- /dev/null +++ b/apps/cli/src/index.ts @@ -0,0 +1,75 @@ +// Container entrypoint for the scanner. Reads config from env and runs either a repo +// scan (SCAN_TARGET unset/"repo") or a container-image scan (SCAN_TARGET="image"), +// streaming results back to the backend via the HTTP reporter. All logging goes to +// stdout/stderr as structured JSON so Cloud Run / Cloud Logging captures it. +import { scanRepository } from "./scan"; +import { scanImage } from "./image"; +import { HttpReporter } from "./reporter"; +import { error, info } from "./logger"; + +const SCAN_ID = process.env.SCAN_ID; +const SCAN_TARGET = process.env.SCAN_TARGET || "repo"; +const SCAN_CALLBACK_URL = process.env.SCAN_CALLBACK_URL; +const SCAN_CALLBACK_SECRET = process.env.SCAN_CALLBACK_SECRET; + +async function main(): Promise { + info("=== Scanner starting ===", { + scanTarget: SCAN_TARGET, + callbackUrl: SCAN_CALLBACK_URL, + hasCallbackSecret: Boolean(SCAN_CALLBACK_SECRET), + nodeEnv: process.env.NODE_ENV, + }); + + if (!SCAN_ID) { + error("Missing required env: SCAN_ID"); + process.exit(1); + } + if (!SCAN_CALLBACK_URL) { + error("Missing required env: SCAN_CALLBACK_URL"); + process.exit(1); + } + + const reporter = new HttpReporter(SCAN_ID, SCAN_CALLBACK_URL, SCAN_CALLBACK_SECRET ?? ""); + + try { + if (SCAN_TARGET === "image") { + const imageRef = process.env.IMAGE_REF; + const registryHost = process.env.REGISTRY_HOST; + if (!imageRef || !registryHost) { + error("Missing required env for image scan: IMAGE_REF, REGISTRY_HOST"); + process.exit(1); + } + info("Starting image scan", { imageRef, registryHost }); + const { packageCount, findingCount } = await scanImage( + { + imageRef, + registryHost, + username: process.env.REGISTRY_USERNAME || undefined, + token: process.env.REGISTRY_TOKEN || undefined, + }, + reporter, + ); + info("=== Scanner finished ===", { imageRef, packageCount, findingCount }); + } else { + const owner = process.env.REPO_OWNER; + const repo = process.env.REPO_NAME; + if (!owner || !repo) { + error("Missing required env for repo scan: REPO_OWNER, REPO_NAME"); + process.exit(1); + } + info("Starting repo scan", { owner, repo, ref: process.env.REPO_REF || "default" }); + const { fileCount, findingCount } = await scanRepository( + { owner, repo, ref: process.env.REPO_REF || undefined, token: process.env.GITHUB_TOKEN || undefined }, + reporter, + ); + info("=== Scanner finished ===", { owner, repo, fileCount, findingCount }); + } + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + error("=== Scanner failed ===", { error: message }); + await reporter.fail(message); + process.exit(1); + } +} + +await main(); diff --git a/apps/cli/src/logger.ts b/apps/cli/src/logger.ts new file mode 100644 index 0000000..4decaec --- /dev/null +++ b/apps/cli/src/logger.ts @@ -0,0 +1,24 @@ +// Tiny structured logger for the scanner. Emits one JSON line per log entry so +// Cloud Run / Cloud Logging captures it reliably (plain multi-line logs often get +// split or dropped when containers exit quickly). + +const SCAN_ID = process.env.SCAN_ID; +const SCAN_TARGET = process.env.SCAN_TARGET || "repo"; + +export function log(level: "info" | "error" | "warn", message: string, extra?: Record): void { + const entry = { + timestamp: new Date().toISOString(), + level, + scanId: SCAN_ID, + scanTarget: SCAN_TARGET, + message, + ...extra, + }; + const out = level === "error" ? console.error : console.log; + out(JSON.stringify(entry)); +} + +/** Convenience wrappers */ +export const info = (message: string, extra?: Record) => log("info", message, extra); +export const warn = (message: string, extra?: Record) => log("warn", message, extra); +export const error = (message: string, extra?: Record) => log("error", message, extra); diff --git a/apps/cli/src/manifests.ts b/apps/cli/src/manifests.ts new file mode 100644 index 0000000..f4d7438 --- /dev/null +++ b/apps/cli/src/manifests.ts @@ -0,0 +1,126 @@ +import { basename } from "node:path"; +import type { DepInput } from "./reporter"; + +// Pragmatic dependency-manifest parsers. Goal: extract {ecosystem, name, version} +// with a *concrete* version we can hand to OSV. Range specifiers are reduced to their +// lower-bound version; entries without a pinnable version (*, latest, workspace:, +// git/file refs) are skipped. Maven (pom.xml/gradle) is intentionally not parsed yet. + +// Pulls the first concrete version out of a range string ("^1.2.3" → "1.2.3"). +function cleanVersion(raw: string): string | null { + const trimmed = raw.trim(); + if (!trimmed || /^(?:\*|latest|workspace:|file:|link:|git[+:]|https?:|github:)/i.test(trimmed)) return null; + const m = trimmed.match(/\d+\.\d+(?:\.\d+)?(?:[-+][0-9A-Za-z.-]+)?/); + return m ? m[0] : null; +} + +function parsePackageJson(content: string): DepInput[] { + const deps: DepInput[] = []; + try { + const json = JSON.parse(content) as Record; + for (const key of ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"]) { + const block = json[key]; + if (!block || typeof block !== "object") continue; + for (const [name, spec] of Object.entries(block as Record)) { + const version = cleanVersion(String(spec)); + if (version) deps.push({ ecosystem: "npm", name, version }); + } + } + } catch { + // malformed package.json — skip + } + return deps; +} + +function parseRequirementsTxt(content: string): DepInput[] { + const deps: DepInput[] = []; + for (const line of content.split(/\r?\n/)) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("-")) continue; + const m = trimmed.match(/^([A-Za-z0-9._-]+)\s*==\s*([0-9][^\s;#]*)/); + if (m) deps.push({ ecosystem: "PyPI", name: m[1], version: m[2] }); + } + return deps; +} + +function parsePyproject(content: string): DepInput[] { + const deps: DepInput[] = []; + // PEP 621 / poetry: `name = "^1.2.3"` and `"name==1.2.3"` list entries. + for (const m of content.matchAll(/^([A-Za-z0-9._-]+)\s*=\s*["']([^"']+)["']/gm)) { + if (m[1] === "python") continue; + const version = cleanVersion(m[2]); + if (version) deps.push({ ecosystem: "PyPI", name: m[1], version }); + } + for (const m of content.matchAll(/["']([A-Za-z0-9._-]+)\s*==\s*([0-9][^"']*)["']/g)) { + deps.push({ ecosystem: "PyPI", name: m[1], version: m[2] }); + } + return deps; +} + +function parseGoMod(content: string): DepInput[] { + const deps: DepInput[] = []; + for (const m of content.matchAll(/^\s*([\w./-]+)\s+v(\d+\.\d+\.\d+[0-9A-Za-z.\-+]*)/gm)) { + if (m[1] === "go" || m[1] === "module" || m[1] === "toolchain") continue; + deps.push({ ecosystem: "Go", name: m[1], version: m[2] }); + } + return deps; +} + +function parseCargoToml(content: string): DepInput[] { + const deps: DepInput[] = []; + for (const m of content.matchAll(/^([A-Za-z0-9._-]+)\s*=\s*(?:["']([^"']+)["']|\{[^}]*version\s*=\s*["']([^"']+)["'])/gm)) { + const version = cleanVersion(m[2] ?? m[3] ?? ""); + if (version) deps.push({ ecosystem: "crates.io", name: m[1], version }); + } + return deps; +} + +function parseComposerJson(content: string): DepInput[] { + const deps: DepInput[] = []; + try { + const json = JSON.parse(content) as Record; + for (const key of ["require", "require-dev"]) { + const block = json[key]; + if (!block || typeof block !== "object") continue; + for (const [name, spec] of Object.entries(block as Record)) { + if (name === "php" || !name.includes("/")) continue; + const version = cleanVersion(String(spec)); + if (version) deps.push({ ecosystem: "Packagist", name, version }); + } + } + } catch { + // skip + } + return deps; +} + +function parseGemfile(content: string): DepInput[] { + const deps: DepInput[] = []; + for (const m of content.matchAll(/gem\s+["']([^"']+)["']\s*,\s*["']([^"']+)["']/g)) { + const version = cleanVersion(m[2]); + if (version) deps.push({ ecosystem: "RubyGems", name: m[1], version }); + } + return deps; +} + +export function parseManifest(relPath: string, content: string): DepInput[] { + switch (basename(relPath)) { + case "package.json": + return parsePackageJson(content); + case "requirements.txt": + return parseRequirementsTxt(content); + case "pyproject.toml": + case "Pipfile": + return parsePyproject(content); + case "go.mod": + return parseGoMod(content); + case "Cargo.toml": + return parseCargoToml(content); + case "composer.json": + return parseComposerJson(content); + case "Gemfile": + return parseGemfile(content); + default: + return []; + } +} diff --git a/apps/cli/src/osdb.ts b/apps/cli/src/osdb.ts new file mode 100644 index 0000000..d07fca2 --- /dev/null +++ b/apps/cli/src/osdb.ts @@ -0,0 +1,127 @@ +import { readdir, readFile } from "node:fs/promises"; +import { join } from "node:path"; +import type { DepInput } from "./reporter"; + +// Reads a container rootfs and returns its installed OS packages as OSV DepInputs. +// The ecosystem string MUST be release-qualified (Debian:12 / Ubuntu:22.04:LTS / +// Alpine:v3.19) and the name MUST be the SOURCE package, or OSV returns nothing. + +interface OsRelease { + id: string; + versionId: string; +} + +async function readText(path: string): Promise { + try { + return await readFile(path, "utf8"); + } catch { + return null; + } +} + +async function readOsRelease(rootfs: string): Promise { + const raw = (await readText(join(rootfs, "etc/os-release"))) ?? (await readText(join(rootfs, "usr/lib/os-release"))); + if (!raw) return null; + const fields: Record = {}; + for (const line of raw.split(/\r?\n/)) { + const m = line.match(/^([A-Z_]+)=(.*)$/); + if (m) fields[m[1]] = m[2].replace(/^"(.*)"$/, "$1"); + } + return { id: (fields.ID ?? "").toLowerCase(), versionId: fields.VERSION_ID ?? "" }; +} + +// Maps os-release → the OSV ecosystem string. Returns null for distros we can't map +// (the scan still runs; it just won't have OS-package CVEs). +export function osvEcosystem(os: OsRelease): string | null { + const { id, versionId } = os; + if (id === "debian") return versionId ? `Debian:${versionId}` : null; + if (id === "ubuntu") { + if (!versionId) return null; + const major = Number.parseInt(versionId, 10); + const isLts = /\.04$/.test(versionId) && major % 2 === 0; + return isLts ? `Ubuntu:${versionId}:LTS` : `Ubuntu:${versionId}`; + } + if (id === "alpine") { + const [maj, min] = versionId.split("."); + return maj && min ? `Alpine:v${maj}.${min}` : null; + } + return null; +} + +// Parses one dpkg status file (blank-line-separated RFC822 stanzas). Only counts +// installed packages; keys the name on Source (fallback Package). +function parseDpkgStatus(raw: string, ecosystem: string, out: DepInput[]): void { + for (const stanza of raw.split(/\n\n+/)) { + if (!stanza.trim()) continue; + const fields: Record = {}; + for (const line of stanza.split(/\r?\n/)) { + const m = line.match(/^([A-Za-z0-9-]+):\s?(.*)$/); + if (m) fields[m[1]] = m[2]; + } + if (fields.Status !== "install ok installed") continue; + const version = fields.Version; + if (!version) continue; + // "Source: openssl (1.1.1n)" → "openssl"; else the binary Package name. + const source = fields.Source ? fields.Source.replace(/\s*\(.*\)$/, "").trim() : undefined; + const name = source || fields.Package; + if (name) out.push({ ecosystem, name, version }); + } +} + +// Parses Alpine's /lib/apk/db/installed. Name keyed on origin (o:), fallback P:. +function parseApkInstalled(raw: string, ecosystem: string, out: DepInput[]): void { + for (const record of raw.split(/\n\n+/)) { + if (!record.trim()) continue; + const fields: Record = {}; + for (const line of record.split(/\r?\n/)) { + const m = line.match(/^([A-Za-z]):(.*)$/); + if (m) fields[m[1]] = m[2]; + } + const version = fields.V; + const name = fields.o || fields.P; + if (name && version) out.push({ ecosystem, name, version }); + } +} + +export interface Inventory { + distro: string | null; + ecosystem: string | null; + packages: DepInput[]; +} + +// Full OS-package inventory of an extracted rootfs. +export async function inventoryOsPackages(rootfs: string): Promise { + const os = await readOsRelease(rootfs); + const ecosystem = os ? osvEcosystem(os) : null; + const distro = os ? `${os.id} ${os.versionId}`.trim() : null; + const packages: DepInput[] = []; + if (!ecosystem) return { distro, ecosystem, packages }; + + if (ecosystem.startsWith("Alpine")) { + const apk = await readText(join(rootfs, "lib/apk/db/installed")); + if (apk) parseApkInstalled(apk, ecosystem, packages); + } else { + // Debian/Ubuntu: monolithic status, plus distroless per-package status.d/*. + const status = await readText(join(rootfs, "var/lib/dpkg/status")); + if (status) parseDpkgStatus(status, ecosystem, packages); + try { + const dir = join(rootfs, "var/lib/dpkg/status.d"); + for (const entry of await readdir(dir)) { + const raw = await readText(join(dir, entry)); + if (raw) parseDpkgStatus(raw, ecosystem, packages); + } + } catch { + // no status.d — normal for non-distroless + } + } + + // De-dupe by name+version (source packages repeat across binary packages). + const seen = new Set(); + const deduped = packages.filter((p) => { + const key = `${p.name}\n${p.version}`; + if (seen.has(key)) return false; + seen.add(key); + return true; + }); + return { distro, ecosystem, packages: deduped }; +} diff --git a/apps/cli/src/prompts.ts b/apps/cli/src/prompts.ts new file mode 100644 index 0000000..e27f866 --- /dev/null +++ b/apps/cli/src/prompts.ts @@ -0,0 +1,74 @@ +import type { ChatMessage } from "./deepseek"; +import type { CveMatch } from "./reporter"; + +// Model output shapes (snake_case — that's what we ask the model for). +export interface RawFinding { + severity?: string; + title?: string; + description?: string; + vulnerable_code?: string; + suggested_fix?: string; + start_line?: number | null; + end_line?: number | null; +} +export interface RawChain { + title?: string; + severity?: string; + description?: string; + steps?: { finding_id?: string; file_path?: string; note?: string }[]; +} + +const MAX_CONTENT_CHARS = 24000; + +export function analyzeFilePrompt(relPath: string, content: string): ChatMessage[] { + const truncated = content.length > MAX_CONTENT_CHARS ? `${content.slice(0, MAX_CONTENT_CHARS)}\n/* …truncated… */` : content; + return [ + { + role: "system", + content: + "You are a meticulous application security auditor. You ONLY identify real, exploitable security vulnerabilities " + + "(injection, broken authn/authz, hardcoded secrets, SSRF, path traversal, unsafe deserialization, XSS, crypto " + + "misuse, command execution, etc.). Ignore style, performance, and non-security nits. Do not invent issues. " + + "Respond with a single JSON object and nothing else.", + }, + { + role: "user", + content: + `File: ${relPath}\n\n\`\`\`\n${truncated}\n\`\`\`\n\n` + + `Return JSON exactly: {"findings":[{"severity":"critical|high|medium|low","title":"short title",` + + `"description":"why it is exploitable and the impact","vulnerable_code":"the exact snippet copied VERBATIM ` + + `from the file above","suggested_fix":"the replacement code that fixes it","start_line":number|null,` + + `"end_line":number|null}]}. "vulnerable_code" MUST be an exact substring of the file so it can be highlighted. ` + + `If there are no real vulnerabilities, return {"findings":[]}.`, + }, + ]; +} + +export interface ChainFindingRef { + id: string; + filePath: string; + severity: string; + title: string; + description: string; +} + +export function chainPrompt(findings: ChainFindingRef[], cves: CveMatch[]): ChatMessage[] { + return [ + { + role: "system", + content: + "You are a security analyst finding ATTACK CHAINS: sequences of individually-scoped findings that combine into a " + + "higher-impact exploit path (e.g. an SSRF that reaches an endpoint protected only by a hardcoded secret). Only " + + "report chains that are genuinely linked. Respond with a single JSON object and nothing else.", + }, + { + role: "user", + content: + `Findings:\n${JSON.stringify(findings)}\n\nKnown dependency CVEs:\n${JSON.stringify(cves)}\n\n` + + `Return JSON exactly: {"chains":[{"title":"short title","severity":"critical|high|medium|low",` + + `"description":"how the steps chain into an attack","steps":[{"finding_id":"id from the list above or null",` + + `"file_path":"path","note":"the role this step plays in the chain"}]}]}. If there are no meaningful chains, ` + + `return {"chains":[]}.`, + }, + ]; +} diff --git a/apps/cli/src/registry/oci.ts b/apps/cli/src/registry/oci.ts new file mode 100644 index 0000000..f1710aa --- /dev/null +++ b/apps/cli/src/registry/oci.ts @@ -0,0 +1,166 @@ +import { spawn } from "node:child_process"; +import { Buffer } from "node:buffer"; +import { Readable } from "node:stream"; + +// A minimal OCI Distribution API client over global fetch — auth token flow, manifest +// resolution (image index → linux/amd64), config blob, and streamed layer extraction. +// Enough to inventory an image; not a general-purpose registry client. + +export interface RegistryAuth { + host: string; // registry endpoint, e.g. registry-1.docker.io, ghcr.io + username?: string; + token?: string; // password / PAT / short-lived access token +} + +export interface ImageConfig { + User?: string; + ExposedPorts?: Record; + Env?: string[]; + Entrypoint?: string[]; + Cmd?: string[]; + WorkingDir?: string; + Volumes?: Record; + Labels?: Record; +} + +interface Descriptor { + mediaType: string; + digest: string; + size: number; + platform?: { os?: string; architecture?: string }; + annotations?: Record; +} +interface Manifest { + mediaType?: string; + manifests?: Descriptor[]; // index / manifest list + config?: Descriptor; // single manifest + layers?: Descriptor[]; +} + +const ACCEPT = [ + "application/vnd.oci.image.index.v1+json", + "application/vnd.oci.image.manifest.v1+json", + "application/vnd.docker.distribution.manifest.list.v2+json", + "application/vnd.docker.distribution.manifest.v2+json", +].join(", "); + +export class OciClient { + private readonly auth: RegistryAuth; + private readonly repository: string; + private token: string | null = null; + + constructor(auth: RegistryAuth, repository: string) { + this.auth = auth; + this.repository = repository; + } + + private authHeaders(extra?: Record): Record { + const headers: Record = { "User-Agent": "cerebrus-scanner", ...extra }; + if (this.token) headers.Authorization = `Bearer ${this.token}`; + return headers; + } + + // OCI token dance: hit an endpoint, read WWW-Authenticate, mint a pull-scoped token. + async authenticate(): Promise { + const res = await fetch(`https://${this.auth.host}/v2/`, { headers: { "User-Agent": "cerebrus-scanner" } }); + if (res.status === 200) return; // registry needs no auth + const challenge = res.headers.get("www-authenticate"); + if (!challenge || !/^Bearer/i.test(challenge)) return; + const params = parseChallenge(challenge); + if (!params.realm) return; + const url = new URL(params.realm); + if (params.service) url.searchParams.set("service", params.service); + url.searchParams.set("scope", `repository:${this.repository}:pull`); + const headers: Record = { "User-Agent": "cerebrus-scanner" }; + if (this.auth.username && this.auth.token) { + headers.Authorization = `Basic ${Buffer.from(`${this.auth.username}:${this.auth.token}`).toString("base64")}`; + } + const tokRes = await fetch(url, { headers }); + if (!tokRes.ok) throw new Error(`registry auth failed (${tokRes.status}): ${await tokRes.text()}`); + const data = (await tokRes.json()) as { token?: string; access_token?: string }; + this.token = data.token ?? data.access_token ?? null; + } + + // Resolves the image to a single-arch manifest (linux/amd64), returning the image + // config and layer descriptors plus the manifest digest. + async resolve(ref: string): Promise<{ config: ImageConfig; layers: Descriptor[]; digest: string | null }> { + let { manifest, digest } = await this.getManifest(ref); + + if (manifest.manifests && manifest.manifests.length > 0) { + const child = manifest.manifests.find( + (m) => + m.platform?.os === "linux" && + m.platform?.architecture === "amd64" && + m.annotations?.["vnd.docker.reference.type"] !== "attestation-manifest", + ); + if (!child) throw new Error("no linux/amd64 manifest in the image index"); + ({ manifest, digest } = await this.getManifest(child.digest)); + } + + if (!manifest.config || !manifest.layers) throw new Error("unexpected manifest shape (no config/layers)"); + const config = (await this.getBlobJson(manifest.config.digest)) as { config?: ImageConfig }; + return { config: config.config ?? {}, layers: manifest.layers, digest }; + } + + private async getManifest(ref: string): Promise<{ manifest: Manifest; digest: string | null }> { + const res = await fetch(`https://${this.auth.host}/v2/${this.repository}/manifests/${ref}`, { + headers: this.authHeaders({ Accept: ACCEPT }), + }); + if (!res.ok) throw new Error(`manifest fetch failed for ${this.repository}:${ref} (${res.status})`); + return { manifest: (await res.json()) as Manifest, digest: res.headers.get("docker-content-digest") }; + } + + // Blob GETs often 302/307 to a CDN that rejects the forwarded Authorization header — + // so on a redirect we re-fetch the Location WITHOUT auth. + private async getBlob(digest: string): Promise { + const res = await fetch(`https://${this.auth.host}/v2/${this.repository}/blobs/${digest}`, { + headers: this.authHeaders(), + redirect: "manual", + }); + if (res.status >= 300 && res.status < 400) { + const location = res.headers.get("location"); + if (!location) throw new Error(`blob ${digest} redirect had no Location`); + const redirected = await fetch(location, { headers: { "User-Agent": "cerebrus-scanner" } }); + if (!redirected.ok) throw new Error(`blob ${digest} fetch failed (${redirected.status})`); + return redirected; + } + if (!res.ok) throw new Error(`blob ${digest} fetch failed (${res.status})`); + return res; + } + + private async getBlobJson(digest: string): Promise { + return (await this.getBlob(digest)).json(); + } + + // Streams a gzipped layer blob straight into `tar` extracting under rootfs. Skips + // zstd layers (the slim runtime image has no zstd). Returns false if skipped. + async extractLayer(layer: Descriptor, rootfs: string): Promise { + if (layer.mediaType.includes("zstd")) { + console.warn(`skipping zstd layer ${layer.digest} (unsupported)`); + return false; + } + const res = await this.getBlob(layer.digest); + if (!res.body) throw new Error(`layer ${layer.digest} had no body`); + await new Promise((resolve, reject) => { + const proc = spawn("tar", ["-xz", "-C", rootfs, "--no-same-owner", "--no-same-permissions"], { + stdio: ["pipe", "ignore", "pipe"], + }); + let stderr = ""; + proc.stderr.on("data", (c) => (stderr += c)); + proc.on("error", reject); + proc.on("close", (code) => { + // tar exits 2 on benign issues (e.g. whiteout/device entries); tolerate it. + if (code === 0 || code === 2) resolve(); + else reject(new Error(`tar failed (exit ${code}): ${stderr.trim()}`)); + }); + Readable.fromWeb(res.body as import("node:stream/web").ReadableStream).pipe(proc.stdin); + }); + return true; + } +} + +function parseChallenge(header: string): { realm?: string; service?: string } { + const out: Record = {}; + for (const m of header.matchAll(/(\w+)="([^"]*)"/g)) out[m[1]] = m[2]; + return out; +} diff --git a/apps/cli/src/reporter.ts b/apps/cli/src/reporter.ts new file mode 100644 index 0000000..27b2b06 --- /dev/null +++ b/apps/cli/src/reporter.ts @@ -0,0 +1,144 @@ +// The reporting contract between the scan engine (scan.ts) and its transport. +// Two implementations exist: HttpReporter (below, used by the container entrypoint +// — POSTs to the backend's secret-authed /internal routes) and DbReporter (in the +// backend, used by inline mode — writes straight to Postgres). This lets scan.ts be +// transport-agnostic and keeps the CLI free of any DB dependency. + +export type Severity = "critical" | "high" | "medium" | "low"; + +export interface FindingInput { + /** Stable id generated by the scanner so chains can reference it pre-persist. */ + id: string; + filePath: string; + severity: Severity; + title: string; + description: string; + /** Exact snippet to replace (rendered RED). */ + vulnerableCode: string; + /** Proposed replacement (rendered GREEN). */ + suggestedFix: string; + startLine?: number | null; + endLine?: number | null; + category?: "code" | "dependency" | "os-package" | "secret" | "misconfig"; + cveId?: string | null; +} + +export interface ChainStep { + findingId?: string; + filePath: string; + note: string; +} + +export interface ChainInput { + title: string; + severity: Severity; + description: string; + steps: ChainStep[]; +} + +export interface DepInput { + ecosystem: string; + name: string; + version: string; +} + +export interface CveMatch { + ecosystem: string; + name: string; + version: string; + osvId: string; + cveId?: string | null; + severity?: string | null; + summary?: string | null; + fixedVersion?: string | null; +} + +export interface Reporter { + setRunning(): Promise; + progress(filesScanned: number, fileCount: number, stage?: string): Promise; + addFindings(findings: FindingInput[]): Promise; + addChains(chains: ChainInput[]): Promise; + matchDependencies(deps: DepInput[]): Promise; + complete(fileCount: number): Promise; + fail(error: string): Promise; +} + +import { error, warn } from "./logger"; + +// HTTP transport for containerized runners (docker / cloudrun). Every call posts to +// the backend's /internal/* routes with the shared secret. Non-fatal calls swallow +// network errors (a dropped progress ping shouldn't kill a scan); only `fail` and +// `matchDependencies` surface problems to the caller. +export class HttpReporter implements Reporter { + private readonly scanId: string; + private readonly baseUrl: string; + private readonly secret: string; + + constructor(scanId: string, baseUrl: string, secret: string) { + this.scanId = scanId; + this.baseUrl = baseUrl; + this.secret = secret; + if (!secret) { + warn("[reporter] SCAN_CALLBACK_SECRET is empty — the backend will reject every callback (findings will NOT stream)."); + } + } + + private post(path: string, body: unknown): Promise { + return fetch(`${this.baseUrl}${path}`, { + method: "POST", + headers: { "Content-Type": "application/json", "x-scan-secret": this.secret }, + body: JSON.stringify(body), + }); + } + + private async postSafe(path: string, body: unknown, label: string): Promise { + try { + const res = await this.post(path, body); + if (!res.ok) error(`[reporter] ${label} → ${res.status} ${await res.text()}`); + } catch (err) { + error(`[reporter] ${label} failed`, { reason: err instanceof Error ? err.message : String(err) }); + } + } + + setRunning(): Promise { + return this.postSafe("/internal/scan-result", { scanId: this.scanId, status: "running" }, "setRunning"); + } + + progress(filesScanned: number, fileCount: number, stage?: string): Promise { + return this.postSafe("/internal/scan/progress", { scanId: this.scanId, filesScanned, fileCount, stage }, "progress"); + } + + addFindings(findings: FindingInput[]): Promise { + if (findings.length === 0) return Promise.resolve(); + return this.postSafe("/internal/scan/findings", { scanId: this.scanId, findings }, "addFindings"); + } + + addChains(chains: ChainInput[]): Promise { + if (chains.length === 0) return Promise.resolve(); + return this.postSafe("/internal/scan/chains", { scanId: this.scanId, chains }, "addChains"); + } + + async matchDependencies(deps: DepInput[]): Promise { + if (deps.length === 0) return []; + try { + const res = await this.post("/internal/scan/cve-check", { deps }); + if (!res.ok) { + error(`[reporter] matchDependencies → ${res.status} ${await res.text()}`); + return []; + } + const data = (await res.json()) as { matches?: CveMatch[] }; + return data.matches ?? []; + } catch (err) { + error("[reporter] matchDependencies failed", { reason: err instanceof Error ? err.message : String(err) }); + return []; + } + } + + complete(fileCount: number): Promise { + return this.postSafe("/internal/scan-result", { scanId: this.scanId, status: "completed", fileCount }, "complete"); + } + + fail(error: string): Promise { + return this.postSafe("/internal/scan-result", { scanId: this.scanId, status: "failed", error }, "fail"); + } +} diff --git a/apps/cli/src/scan.ts b/apps/cli/src/scan.ts new file mode 100644 index 0000000..37bd5b0 --- /dev/null +++ b/apps/cli/src/scan.ts @@ -0,0 +1,248 @@ +import { spawn } from "node:child_process"; +import { Buffer } from "node:buffer"; +import { randomUUID } from "node:crypto"; +import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { askForJson, loadDeepSeekConfig, type DeepSeekConfig } from "./deepseek"; +import { collectFiles, isManifest, type ScanFile } from "./walk"; +import { parseManifest } from "./manifests"; +import { error, info } from "./logger"; +import { analyzeFilePrompt, chainPrompt, type ChainFindingRef, type RawChain, type RawFinding } from "./prompts"; +import type { ChainInput, CveMatch, DepInput, FindingInput, Reporter, Severity } from "./reporter"; + +export interface ScanOptions { + owner: string; + repo: string; + /** Git ref (branch/tag/sha). Defaults to the repo's default branch. */ + ref?: string; + /** GitHub token (required for private repos). */ + token?: string; +} + +export interface ScanResult { + fileCount: number; + findingCount: number; +} + +export type { + Reporter, + FindingInput, + ChainInput, + ChainStep, + DepInput, + CveMatch, + Severity, +} from "./reporter"; +export { HttpReporter } from "./reporter"; +export { scanImage } from "./image"; +export type { ImageScanOptions, ImageScanResult } from "./image"; + +const GITHUB_API = "https://api.github.com"; +const SEVERITIES: Severity[] = ["critical", "high", "medium", "low"]; + +// Downloads + extracts the repo, then runs a DeepSeek-powered, file-by-file +// vulnerability analysis, a dependency→CVE check, and a chaining pass — streaming +// results to the injected reporter as they are found. +export async function scanRepository(opts: ScanOptions, reporter: Reporter): Promise { + const config = loadDeepSeekConfig(); + const workDir = await mkdtemp(join(tmpdir(), "cerebrus-scan-")); + try { + await reporter.setRunning(); + await reporter.progress(0, 0, "download"); + info(`Downloading ${opts.owner}/${opts.repo}${opts.ref ? `@${opts.ref}` : ""}`); + + const tarballPath = join(workDir, "repo.tar.gz"); + await downloadTarball(opts, tarballPath); + const extractDir = join(workDir, "extract"); + await mkdir(extractDir); + await extractTarball(tarballPath, extractDir); + + const files = await collectFiles(extractDir); + const total = files.length; + info(`Analyzing ${total} files with ${config.model} (concurrency ${config.concurrency})`); + await reporter.progress(0, total, "analyzing"); + + const collected: ChainFindingRef[] = []; + await analyzeFiles(files, config, reporter, total, collected); + + // Dependency → CVE pass. + const cves = await runCveCheck(files, reporter, collected, total); + + // Chaining pass over everything we found. + await runChaining(collected, cves, config, reporter, total); + + await reporter.complete(total); + return { fileCount: total, findingCount: collected.length }; + } finally { + await rm(workDir, { recursive: true, force: true }); + } +} + +// Bounded-concurrency, per-file analysis. Each file is isolated: a failure (network, +// unparseable reply) is logged and skipped so one bad file never fails the scan. +async function analyzeFiles( + files: ScanFile[], + config: DeepSeekConfig, + reporter: Reporter, + total: number, + collected: ChainFindingRef[], +): Promise { + let done = 0; + let cursor = 0; + const worker = async (): Promise => { + while (cursor < files.length) { + const file = files[cursor++]; + try { + const content = await readFile(file.absPath, "utf8"); + const { findings } = await askForJson<{ findings?: RawFinding[] }>(config, analyzeFilePrompt(file.relPath, content)); + const mapped = (findings ?? []) + .filter((f) => f.vulnerable_code && f.title) + .map((f) => toFinding(file.relPath, f)); + if (mapped.length > 0) { + await reporter.addFindings(mapped); + for (const f of mapped) { + collected.push({ id: f.id, filePath: f.filePath, severity: f.severity, title: f.title, description: f.description }); + } + info(` ${file.relPath}: ${mapped.length} finding(s)`); + } + } catch (err) { + error(` ${file.relPath}: analysis failed`, { file: file.relPath, reason: err instanceof Error ? err.message : String(err) }); + } finally { + done++; + await reporter.progress(done, total, "analyzing"); + } + } + }; + await Promise.all(Array.from({ length: Math.min(config.concurrency, files.length) }, worker)); +} + +// Parses dependency manifests, asks the backend to match versions against OSV/CVEs, +// and emits a dependency finding per match. +async function runCveCheck(files: ScanFile[], reporter: Reporter, collected: ChainFindingRef[], total: number): Promise { + const pathByKey = new Map(); + const deps: DepInput[] = []; + for (const file of files.filter((f) => isManifest(f.relPath))) { + let content: string; + try { + content = await readFile(file.absPath, "utf8"); + } catch { + continue; + } + for (const dep of parseManifest(file.relPath, content)) { + deps.push(dep); + pathByKey.set(depKey(dep), file.relPath); + } + } + if (deps.length === 0) return []; + + info(`Checking ${deps.length} dependencies against the CVE database`); + await reporter.progress(total, total, "cve-check"); + const matches = await reporter.matchDependencies(deps); + if (matches.length === 0) return []; + + const findings: FindingInput[] = matches.map((m) => { + const filePath = pathByKey.get(depKey(m)) ?? `${m.name} (dependency)`; + return { + id: randomUUID(), + filePath, + severity: normalizeSeverity(m.severity ?? "high"), + title: `${m.name}@${m.version}: ${m.cveId ?? m.osvId}`, + description: m.summary ?? `Known vulnerability ${m.osvId} affects ${m.name} ${m.version}.`, + vulnerableCode: `${m.name}: ${m.version}`, + suggestedFix: m.fixedVersion ? `${m.name}: ${m.fixedVersion}` : `Upgrade ${m.name} to a patched version.`, + category: "dependency", + cveId: m.cveId ?? m.osvId, + }; + }); + await reporter.addFindings(findings); + for (const f of findings) { + collected.push({ id: f.id, filePath: f.filePath, severity: f.severity, title: f.title, description: f.description }); + } + info(` ${findings.length} vulnerable dependency(ies)`); + return matches; +} + +async function runChaining( + collected: ChainFindingRef[], + cves: CveMatch[], + config: DeepSeekConfig, + reporter: Reporter, + total: number, +): Promise { + if (collected.length < 2) return; // nothing to chain + info("Correlating findings into attack chains"); + await reporter.progress(total, total, "chaining"); + try { + const { chains } = await askForJson<{ chains?: RawChain[] }>(config, chainPrompt(collected, cves)); + const mapped: ChainInput[] = (chains ?? []) + .filter((c) => c.title && c.description) + .map((c) => ({ + title: c.title as string, + severity: normalizeSeverity(c.severity ?? "high"), + description: c.description as string, + steps: (c.steps ?? []).map((s) => ({ findingId: s.finding_id, filePath: s.file_path ?? "", note: s.note ?? "" })), + })); + if (mapped.length > 0) { + await reporter.addChains(mapped); + info(` ${mapped.length} attack chain(s)`); + } + } catch (err) { + error(" chaining failed", { reason: err instanceof Error ? err.message : String(err) }); + } +} + +function toFinding(relPath: string, raw: RawFinding): FindingInput { + return { + id: randomUUID(), + filePath: relPath, + severity: normalizeSeverity(raw.severity), + title: (raw.title ?? "Untitled finding").slice(0, 300), + description: raw.description ?? "", + vulnerableCode: raw.vulnerable_code ?? "", + suggestedFix: raw.suggested_fix ?? "", + startLine: raw.start_line ?? null, + endLine: raw.end_line ?? null, + category: "code", + }; +} + +function normalizeSeverity(value: string | undefined): Severity { + const v = (value ?? "").toLowerCase(); + return (SEVERITIES as string[]).includes(v) ? (v as Severity) : "medium"; +} + +function depKey(d: { ecosystem: string; name: string; version: string }): string { + return `${d.ecosystem.toLowerCase()}\n${d.name}\n${d.version}`; +} + +async function downloadTarball(opts: ScanOptions, dest: string): Promise { + const url = `${GITHUB_API}/repos/${opts.owner}/${opts.repo}/tarball/${opts.ref ?? ""}`; + const headers: Record = { + Accept: "application/vnd.github+json", + "User-Agent": "cerebrus-scanner", + "X-GitHub-Api-Version": "2022-11-28", + }; + if (opts.token) headers.Authorization = `Bearer ${opts.token}`; + + const res = await fetch(url, { headers, redirect: "follow" }); + if (!res.ok) { + throw new Error(`Tarball download failed for ${opts.owner}/${opts.repo} (${res.status})`); + } + await writeFile(dest, Buffer.from(await res.arrayBuffer())); +} + +function extractTarball(tarball: string, dest: string): Promise { + return new Promise((resolve, reject) => { + const proc = spawn("tar", ["-xzf", tarball, "-C", dest]); + let stderr = ""; + proc.stderr.on("data", (chunk) => { + stderr += chunk; + }); + proc.on("error", reject); + proc.on("close", (code) => { + if (code === 0) resolve(); + else reject(new Error(`tar extraction failed (exit ${code}): ${stderr.trim()}`)); + }); + }); +} diff --git a/apps/cli/src/secrets.ts b/apps/cli/src/secrets.ts new file mode 100644 index 0000000..18d0d93 --- /dev/null +++ b/apps/cli/src/secrets.ts @@ -0,0 +1,113 @@ +import { randomUUID } from "node:crypto"; +import { readdir, readFile, stat } from "node:fs/promises"; +import { extname, join, relative } from "node:path"; +import type { FindingInput } from "./reporter"; + +// Scans an extracted image rootfs for baked-in secrets. Values are ALWAYS redacted +// before they leave this function (into findings or, later, the DeepSeek prompt). + +const MAX_FILE_BYTES = 64 * 1024; +const MAX_FILES = 5000; +const MAX_FINDINGS = 50; + +const SKIP_DIRS = new Set(["proc", "sys", "dev", "run"]); +const SKIP_EXTS = new Set([ + ".png", ".jpg", ".jpeg", ".gif", ".webp", ".ico", ".svg", ".pdf", ".woff", ".woff2", ".ttf", + ".so", ".a", ".o", ".bin", ".exe", ".dll", ".class", ".pyc", ".wasm", ".zip", ".gz", ".xz", + ".mo", ".deb", ".apk", +]); + +interface Pattern { + name: string; + re: RegExp; + severity: "high" | "medium"; +} + +const PATTERNS: Pattern[] = [ + { name: "AWS access key id", re: /AKIA[0-9A-Z]{16}/g, severity: "high" }, + { name: "Private key", re: /-----BEGIN (?:RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY-----/g, severity: "high" }, + { name: "GitHub token", re: /gh[opsu]_[A-Za-z0-9]{30,}/g, severity: "high" }, + { name: "Slack token", re: /xox[baprs]-[A-Za-z0-9-]{10,}/g, severity: "high" }, + { + name: "Hardcoded credential", + re: /(?:api[_-]?key|secret|token|passwd|password)["']?\s*[:=]\s*["'][A-Za-z0-9_\-./+]{16,}["']/gi, + severity: "medium", + }, +]; + +function redact(secret: string): string { + const head = secret.slice(0, 4); + return `${head}${"*".repeat(Math.max(4, Math.min(12, secret.length - 4)))}`; +} + +async function isProbablyText(path: string): Promise { + try { + const fd = await readFile(path); + const sample = fd.subarray(0, 512); + return !sample.includes(0); // null byte → binary + } catch { + return false; + } +} + +export async function scanSecrets(rootfs: string): Promise { + const findings: FindingInput[] = []; + const seen = new Set(); + let filesScanned = 0; + const stack: string[] = [rootfs]; + + while (stack.length > 0 && filesScanned < MAX_FILES && findings.length < MAX_FINDINGS) { + const dir = stack.pop() as string; + let entries; + try { + entries = await readdir(dir, { withFileTypes: true }); + } catch { + continue; + } + for (const entry of entries) { + const full = join(dir, entry.name); + if (entry.isDirectory()) { + if (!SKIP_DIRS.has(entry.name)) stack.push(full); + continue; + } + if (!entry.isFile() || SKIP_EXTS.has(extname(entry.name).toLowerCase())) continue; + let size: number; + try { + size = (await stat(full)).size; + } catch { + continue; + } + if (size === 0 || size > MAX_FILE_BYTES) continue; + filesScanned++; + if (!(await isProbablyText(full))) continue; + + let content: string; + try { + content = await readFile(full, "utf8"); + } catch { + continue; + } + const relPath = relative(rootfs, full); + for (const pattern of PATTERNS) { + pattern.re.lastIndex = 0; + const match = pattern.re.exec(content); + if (!match) continue; + const key = `${pattern.name}\n${redact(match[0])}`; + if (seen.has(key)) continue; + seen.add(key); + findings.push({ + id: randomUUID(), + filePath: relPath, + severity: pattern.severity, + title: `${pattern.name} baked into image`, + description: `A ${pattern.name.toLowerCase()} appears in \`${relPath}\`. Secrets embedded in image layers persist in the registry history and are readable by anyone who can pull the image.`, + vulnerableCode: `${match[0].slice(0, 4)}… (${pattern.name}, redacted)`, + suggestedFix: "Remove the secret from the image and inject it at runtime (env var / secret store); rotate the exposed credential.", + category: "secret", + }); + if (findings.length >= MAX_FINDINGS) break; + } + } + } + return findings; +} diff --git a/apps/cli/src/walk.ts b/apps/cli/src/walk.ts new file mode 100644 index 0000000..249ead6 --- /dev/null +++ b/apps/cli/src/walk.ts @@ -0,0 +1,128 @@ +import { readdir, stat } from "node:fs/promises"; +import { basename, extname, join, relative } from "node:path"; + +export interface ScanFile { + /** Absolute path on disk. */ + absPath: string; + /** Path relative to the repo root (GitHub tarball wrapper stripped). */ + relPath: string; +} + +const MAX_FILE_BYTES = 256 * 1024; + +// Directories that are never worth analyzing (deps output, VCS, build artifacts). +const SKIP_DIRS = new Set([ + ".git", + "node_modules", + "dist", + "build", + "out", + ".next", + ".nuxt", + ".svelte-kit", + "coverage", + "vendor", + "target", + ".venv", + "venv", + "__pycache__", + ".turbo", + ".cache", +]); + +// Lockfiles carry no code to audit (huge + noisy). Dependency *manifests* are kept. +const LOCKFILES = new Set([ + "package-lock.json", + "yarn.lock", + "pnpm-lock.yaml", + "bun.lock", + "bun.lockb", + "Cargo.lock", + "poetry.lock", + "Gemfile.lock", + "composer.lock", + "go.sum", + "Pipfile.lock", +]); + +// Binary / media / non-source extensions we never send to the model. +const SKIP_EXTS = new Set([ + ".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp", ".ico", ".svg", ".pdf", + ".woff", ".woff2", ".ttf", ".otf", ".eot", + ".mp3", ".mp4", ".mov", ".avi", ".webm", ".wav", ".ogg", ".flac", + ".zip", ".gz", ".tar", ".tgz", ".rar", ".7z", ".bz2", + ".wasm", ".so", ".dylib", ".dll", ".exe", ".bin", ".class", ".o", ".a", + ".map", ".lock", ".snap", ".ipynb", +]); + +function isMinified(name: string): boolean { + return /\.min\.[^.]+$/.test(name) || name.endsWith(".min.js") || name.endsWith(".min.css"); +} + +function shouldSkipFile(name: string): boolean { + if (LOCKFILES.has(name)) return true; + if (isMinified(name)) return true; + if (SKIP_EXTS.has(extname(name).toLowerCase())) return true; + return false; +} + +// GitHub tarballs wrap everything in a single `owner-repo-sha/` folder; treat that as +// the repo root so file paths in findings read naturally. +async function repoRoot(extractDir: string): Promise { + const entries = await readdir(extractDir, { withFileTypes: true }); + const dirs = entries.filter((e) => e.isDirectory()); + if (dirs.length === 1 && entries.length === 1) return join(extractDir, dirs[0].name); + return extractDir; +} + +// Walks the extracted repo and returns the files eligible for analysis, applying the +// skip rules and size cap. Iterative DFS (no recursion depth limits). +export async function collectFiles(extractDir: string): Promise { + const root = await repoRoot(extractDir); + const files: ScanFile[] = []; + const stack: string[] = [root]; + while (stack.length > 0) { + const dir = stack.pop() as string; + let entries; + try { + entries = await readdir(dir, { withFileTypes: true }); + } catch { + continue; + } + for (const entry of entries) { + const full = join(dir, entry.name); + if (entry.isDirectory()) { + if (!SKIP_DIRS.has(entry.name)) stack.push(full); + } else if (entry.isFile()) { + if (shouldSkipFile(entry.name)) continue; + let size: number; + try { + size = (await stat(full)).size; + } catch { + continue; + } + if (size === 0 || size > MAX_FILE_BYTES) continue; + files.push({ absPath: full, relPath: relative(root, full) }); + } + } + } + return files; +} + +// Dependency manifest basenames we parse for CVE matching (see manifests.ts). +export const MANIFEST_NAMES = new Set([ + "package.json", + "requirements.txt", + "pyproject.toml", + "Pipfile", + "go.mod", + "Cargo.toml", + "Gemfile", + "composer.json", + "pom.xml", + "build.gradle", +]); + +export function isManifest(relPath: string): boolean { + return MANIFEST_NAMES.has(basename(relPath)); +} diff --git a/apps/cli/tsconfig.json b/apps/cli/tsconfig.json new file mode 100644 index 0000000..2681988 --- /dev/null +++ b/apps/cli/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "@cerebrus/typescript-config/bun-app.json", + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo" + }, + "include": ["src"] +} diff --git a/apps/frontend/.env.example b/apps/frontend/.env.example new file mode 100644 index 0000000..6f0c05a --- /dev/null +++ b/apps/frontend/.env.example @@ -0,0 +1,2 @@ +# Base URL of the @cerebrus/backend API (WorkOS auth + Supabase access). +VITE_API_URL=http://localhost:3001 diff --git a/apps/frontend/.env.production.example b/apps/frontend/.env.production.example new file mode 100644 index 0000000..d1211d7 --- /dev/null +++ b/apps/frontend/.env.production.example @@ -0,0 +1,7 @@ +# The frontend calls its OWN origin (/auth, /api), which the Bun server +# reverse-proxies to the backend (keeping the session cookie first-party). So no +# backend URL is baked into the bundle — leave VITE_API_URL unset. +# +# The backend the server proxies to is set via the BACKEND_URL env var on the +# frontend Cloud Run service (e.g. https://cerebrus-backend-xxxxx.run.app). +# VITE_API_URL= diff --git a/apps/frontend/Dockerfile b/apps/frontend/Dockerfile new file mode 100644 index 0000000..e19f7aa --- /dev/null +++ b/apps/frontend/Dockerfile @@ -0,0 +1,31 @@ +# syntax=docker/dockerfile:1 +# Build context is the repo ROOT (so the bun workspace resolves). + +# ---- Build: produce the static SPA. The app calls its OWN origin (/auth, /api), +# which the Bun server reverse-proxies to the backend, so no backend URL is baked. ---- +FROM oven/bun:1.3.14 AS build +WORKDIR /repo + +COPY package.json bun.lock turbo.json ./ +COPY packages/typescript-config/package.json ./packages/typescript-config/ +COPY packages/eslint-config/package.json ./packages/eslint-config/ +COPY apps/backend/package.json ./apps/backend/ +COPY apps/frontend/package.json ./apps/frontend/ +COPY apps/cli/package.json ./apps/cli/ +RUN bun install --frozen-lockfile + +COPY . . +# vite build runs in "production" mode and loads apps/frontend/.env.production. +RUN cd apps/frontend && bun run build + +# ---- Runtime: Bun serving the static build on $PORT and proxying backend routes ---- +FROM oven/bun:1.3.14-slim AS runtime +WORKDIR /app +ENV PORT=8080 +# BACKEND_URL is set at deploy time by cloudbuild.yaml from the value in +# apps/frontend/.env.production. Do not bake a default here. +COPY apps/frontend/server.ts ./server.ts +COPY --from=build /repo/apps/frontend/dist ./dist +EXPOSE 8080 +USER bun +CMD ["bun", "server.ts"] diff --git a/apps/frontend/README.md b/apps/frontend/README.md new file mode 100644 index 0000000..a00d0dd --- /dev/null +++ b/apps/frontend/README.md @@ -0,0 +1,77 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) + +## React Compiler + +The React Compiler is enabled on this template. See [this documentation](https://react.dev/learn/react-compiler) for more information. + +Note: This will impact Vite dev & build performances. + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: + +```js +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + + // Remove tseslint.configs.recommended and replace with this + tseslint.configs.recommendedTypeChecked, + // Alternatively, use this for stricter rules + tseslint.configs.strictTypeChecked, + // Optionally, add this for stylistic rules + tseslint.configs.stylisticTypeChecked, + + // Other configs... + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) + +``` + +You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: + +```js +// eslint.config.js +import reactX from 'eslint-plugin-react-x' +import reactDom from 'eslint-plugin-react-dom' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + // Enable lint rules for React + reactX.configs['recommended-typescript'], + // Enable lint rules for React DOM + reactDom.configs.recommended, + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) + +``` diff --git a/apps/frontend/cloudbuild.yaml b/apps/frontend/cloudbuild.yaml new file mode 100644 index 0000000..96fe15c --- /dev/null +++ b/apps/frontend/cloudbuild.yaml @@ -0,0 +1,28 @@ +# Build + push + deploy the frontend. Run from the repo root: +# gcloud builds submit --config apps/frontend/cloudbuild.yaml \ +# --substitutions=_REGION=us-central1,_IMAGE=us-central1-docker.pkg.dev/PROJECT/cerebrus/frontend . +steps: + - name: gcr.io/cloud-builders/docker + args: ["build", "-f", "apps/frontend/Dockerfile", "-t", "${_IMAGE}:${BUILD_ID}", "-t", "${_IMAGE}:latest", "."] + - name: gcr.io/cloud-builders/docker + args: ["push", "--all-tags", "${_IMAGE}"] + - name: gcr.io/google.com/cloudsdktool/cloud-sdk + entrypoint: gcloud + args: + - "run" + - "deploy" + - "cerebrus-frontend" + - "--image=${_IMAGE}:${BUILD_ID}" + - "--region=${_REGION}" + - "--platform=managed" + - "--port=8080" + - "--allow-unauthenticated" + - "--set-env-vars=BACKEND_URL=${_BACKEND_URL}" +images: + - "${_IMAGE}" +substitutions: + _REGION: "us-central1" + _IMAGE: "us-central1-docker.pkg.dev/${PROJECT_ID}/cerebrus/frontend" + _BACKEND_URL: "http://localhost:3001" +options: + logging: CLOUD_LOGGING_ONLY diff --git a/apps/frontend/eslint.config.js b/apps/frontend/eslint.config.js new file mode 100644 index 0000000..fbacd71 --- /dev/null +++ b/apps/frontend/eslint.config.js @@ -0,0 +1,3 @@ +import react from "@cerebrus/eslint-config/react"; + +export default react; diff --git a/apps/frontend/index.html b/apps/frontend/index.html new file mode 100644 index 0000000..21e47f8 --- /dev/null +++ b/apps/frontend/index.html @@ -0,0 +1,17 @@ + + + + + + + + + + + Cefense — From attack to proven fix. + + +
+ + + diff --git a/apps/frontend/package.json b/apps/frontend/package.json new file mode 100644 index 0000000..caf0ced --- /dev/null +++ b/apps/frontend/package.json @@ -0,0 +1,40 @@ +{ + "name": "@cerebrus/frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "check-types": "tsc -b", + "test": "bun test src/ported", + "preview": "vite preview", + "start": "bun server.ts" + }, + "dependencies": { + "@tanstack/react-query": "^5.90.21", + "react": "^19.2.7", + "react-dom": "^19.2.7", + "react-router-dom": "^7.18.0", + "zod": "^4.1.12", + "zustand": "^5.0.11" + }, + "devDependencies": { + "@babel/core": "^7.29.7", + "@cerebrus/eslint-config": "*", + "@cerebrus/typescript-config": "*", + "@tailwindcss/postcss": "4.2.1", + "@rolldown/plugin-babel": "^0.2.3", + "@types/babel__core": "^7.20.5", + "@types/node": "^24.13.2", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.2", + "babel-plugin-react-compiler": "^1.0.0", + "eslint": "^10.5.0", + "tailwindcss": "4.2.1", + "typescript": "~6.0.2", + "vite": "^8.1.0" + } +} diff --git a/apps/frontend/postcss.config.mjs b/apps/frontend/postcss.config.mjs new file mode 100644 index 0000000..c2ddf74 --- /dev/null +++ b/apps/frontend/postcss.config.mjs @@ -0,0 +1,5 @@ +export default { + plugins: { + "@tailwindcss/postcss": {}, + }, +}; diff --git a/apps/frontend/public/brand/sf-bay-haze.png b/apps/frontend/public/brand/sf-bay-haze.png new file mode 100644 index 0000000..b83dcc3 Binary files /dev/null and b/apps/frontend/public/brand/sf-bay-haze.png differ diff --git a/apps/frontend/public/brand/sf-skyline-source.png b/apps/frontend/public/brand/sf-skyline-source.png new file mode 100644 index 0000000..55b62b3 Binary files /dev/null and b/apps/frontend/public/brand/sf-skyline-source.png differ diff --git a/apps/frontend/public/brand/sf-skyline.png b/apps/frontend/public/brand/sf-skyline.png new file mode 100644 index 0000000..39b8634 Binary files /dev/null and b/apps/frontend/public/brand/sf-skyline.png differ diff --git a/apps/frontend/public/cerebrus-sky.webp b/apps/frontend/public/cerebrus-sky.webp new file mode 100644 index 0000000..091dc60 Binary files /dev/null and b/apps/frontend/public/cerebrus-sky.webp differ diff --git a/apps/frontend/public/favicon.svg b/apps/frontend/public/favicon.svg new file mode 100644 index 0000000..807c665 --- /dev/null +++ b/apps/frontend/public/favicon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/apps/frontend/public/icons.svg b/apps/frontend/public/icons.svg new file mode 100644 index 0000000..e952219 --- /dev/null +++ b/apps/frontend/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/frontend/public/immunity/threat-icon-atlas-64-source.png b/apps/frontend/public/immunity/threat-icon-atlas-64-source.png new file mode 100644 index 0000000..2efe215 Binary files /dev/null and b/apps/frontend/public/immunity/threat-icon-atlas-64-source.png differ diff --git a/apps/frontend/public/immunity/threat-icon-atlas-64.png b/apps/frontend/public/immunity/threat-icon-atlas-64.png new file mode 100644 index 0000000..329bcc1 Binary files /dev/null and b/apps/frontend/public/immunity/threat-icon-atlas-64.png differ diff --git a/apps/frontend/public/immunity/threat-icon-atlas-source.png b/apps/frontend/public/immunity/threat-icon-atlas-source.png new file mode 100644 index 0000000..27915f7 Binary files /dev/null and b/apps/frontend/public/immunity/threat-icon-atlas-source.png differ diff --git a/apps/frontend/public/immunity/threat-icon-atlas.png b/apps/frontend/public/immunity/threat-icon-atlas.png new file mode 100644 index 0000000..09205ab Binary files /dev/null and b/apps/frontend/public/immunity/threat-icon-atlas.png differ diff --git a/apps/frontend/public/immunity/threat-icons/threat-0000.svg b/apps/frontend/public/immunity/threat-icons/threat-0000.svg new file mode 100644 index 0000000..8431c69 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0000.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0001.svg b/apps/frontend/public/immunity/threat-icons/threat-0001.svg new file mode 100644 index 0000000..fd485e7 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0001.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0002.svg b/apps/frontend/public/immunity/threat-icons/threat-0002.svg new file mode 100644 index 0000000..d8c8107 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0002.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0003.svg b/apps/frontend/public/immunity/threat-icons/threat-0003.svg new file mode 100644 index 0000000..f96a64a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0003.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0004.svg b/apps/frontend/public/immunity/threat-icons/threat-0004.svg new file mode 100644 index 0000000..e1fec98 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0004.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0005.svg b/apps/frontend/public/immunity/threat-icons/threat-0005.svg new file mode 100644 index 0000000..7a496e7 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0005.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0006.svg b/apps/frontend/public/immunity/threat-icons/threat-0006.svg new file mode 100644 index 0000000..6d282bf --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0006.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0007.svg b/apps/frontend/public/immunity/threat-icons/threat-0007.svg new file mode 100644 index 0000000..605b45d --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0007.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0008.svg b/apps/frontend/public/immunity/threat-icons/threat-0008.svg new file mode 100644 index 0000000..3f3f1fb --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0008.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0009.svg b/apps/frontend/public/immunity/threat-icons/threat-0009.svg new file mode 100644 index 0000000..3350558 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0009.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0010.svg b/apps/frontend/public/immunity/threat-icons/threat-0010.svg new file mode 100644 index 0000000..be002c3 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0010.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0011.svg b/apps/frontend/public/immunity/threat-icons/threat-0011.svg new file mode 100644 index 0000000..c210ae5 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0011.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0012.svg b/apps/frontend/public/immunity/threat-icons/threat-0012.svg new file mode 100644 index 0000000..b1102b1 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0012.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0013.svg b/apps/frontend/public/immunity/threat-icons/threat-0013.svg new file mode 100644 index 0000000..28508bc --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0013.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0014.svg b/apps/frontend/public/immunity/threat-icons/threat-0014.svg new file mode 100644 index 0000000..b72d77e --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0014.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0015.svg b/apps/frontend/public/immunity/threat-icons/threat-0015.svg new file mode 100644 index 0000000..daee8cf --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0015.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0016.svg b/apps/frontend/public/immunity/threat-icons/threat-0016.svg new file mode 100644 index 0000000..7c7c60c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0016.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0017.svg b/apps/frontend/public/immunity/threat-icons/threat-0017.svg new file mode 100644 index 0000000..b266b5c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0017.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0018.svg b/apps/frontend/public/immunity/threat-icons/threat-0018.svg new file mode 100644 index 0000000..aec8bd8 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0018.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0019.svg b/apps/frontend/public/immunity/threat-icons/threat-0019.svg new file mode 100644 index 0000000..0c306d9 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0019.svg @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0020.svg b/apps/frontend/public/immunity/threat-icons/threat-0020.svg new file mode 100644 index 0000000..a9bf4a1 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0020.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0021.svg b/apps/frontend/public/immunity/threat-icons/threat-0021.svg new file mode 100644 index 0000000..e2eff3d --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0021.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0022.svg b/apps/frontend/public/immunity/threat-icons/threat-0022.svg new file mode 100644 index 0000000..9ce2b27 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0022.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0023.svg b/apps/frontend/public/immunity/threat-icons/threat-0023.svg new file mode 100644 index 0000000..342ca8a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0023.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0024.svg b/apps/frontend/public/immunity/threat-icons/threat-0024.svg new file mode 100644 index 0000000..b28335d --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0024.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0025.svg b/apps/frontend/public/immunity/threat-icons/threat-0025.svg new file mode 100644 index 0000000..a4c113d --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0025.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0026.svg b/apps/frontend/public/immunity/threat-icons/threat-0026.svg new file mode 100644 index 0000000..60e8969 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0026.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0027.svg b/apps/frontend/public/immunity/threat-icons/threat-0027.svg new file mode 100644 index 0000000..7de967a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0027.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0028.svg b/apps/frontend/public/immunity/threat-icons/threat-0028.svg new file mode 100644 index 0000000..36bcdbd --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0028.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0029.svg b/apps/frontend/public/immunity/threat-icons/threat-0029.svg new file mode 100644 index 0000000..2777e6f --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0029.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0030.svg b/apps/frontend/public/immunity/threat-icons/threat-0030.svg new file mode 100644 index 0000000..80d4479 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0030.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0031.svg b/apps/frontend/public/immunity/threat-icons/threat-0031.svg new file mode 100644 index 0000000..a359ef9 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0031.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0032.svg b/apps/frontend/public/immunity/threat-icons/threat-0032.svg new file mode 100644 index 0000000..8d9b703 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0032.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0033.svg b/apps/frontend/public/immunity/threat-icons/threat-0033.svg new file mode 100644 index 0000000..fd822a3 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0033.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0034.svg b/apps/frontend/public/immunity/threat-icons/threat-0034.svg new file mode 100644 index 0000000..dfeba08 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0034.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0035.svg b/apps/frontend/public/immunity/threat-icons/threat-0035.svg new file mode 100644 index 0000000..e63c196 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0035.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0036.svg b/apps/frontend/public/immunity/threat-icons/threat-0036.svg new file mode 100644 index 0000000..99f532c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0036.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0037.svg b/apps/frontend/public/immunity/threat-icons/threat-0037.svg new file mode 100644 index 0000000..a3c7c3a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0037.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0038.svg b/apps/frontend/public/immunity/threat-icons/threat-0038.svg new file mode 100644 index 0000000..443c3de --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0038.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0039.svg b/apps/frontend/public/immunity/threat-icons/threat-0039.svg new file mode 100644 index 0000000..5a91d90 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0039.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0040.svg b/apps/frontend/public/immunity/threat-icons/threat-0040.svg new file mode 100644 index 0000000..3300680 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0040.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0041.svg b/apps/frontend/public/immunity/threat-icons/threat-0041.svg new file mode 100644 index 0000000..516f8c3 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0041.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0042.svg b/apps/frontend/public/immunity/threat-icons/threat-0042.svg new file mode 100644 index 0000000..6c9479a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0042.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0043.svg b/apps/frontend/public/immunity/threat-icons/threat-0043.svg new file mode 100644 index 0000000..20973a3 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0043.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0044.svg b/apps/frontend/public/immunity/threat-icons/threat-0044.svg new file mode 100644 index 0000000..fa7344d --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0044.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0045.svg b/apps/frontend/public/immunity/threat-icons/threat-0045.svg new file mode 100644 index 0000000..bbbeab0 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0045.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0046.svg b/apps/frontend/public/immunity/threat-icons/threat-0046.svg new file mode 100644 index 0000000..a698346 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0046.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0047.svg b/apps/frontend/public/immunity/threat-icons/threat-0047.svg new file mode 100644 index 0000000..922cd98 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0047.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0048.svg b/apps/frontend/public/immunity/threat-icons/threat-0048.svg new file mode 100644 index 0000000..93e8225 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0048.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0049.svg b/apps/frontend/public/immunity/threat-icons/threat-0049.svg new file mode 100644 index 0000000..7849844 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0049.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0050.svg b/apps/frontend/public/immunity/threat-icons/threat-0050.svg new file mode 100644 index 0000000..fc164e6 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0050.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0051.svg b/apps/frontend/public/immunity/threat-icons/threat-0051.svg new file mode 100644 index 0000000..ff4b746 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0051.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0052.svg b/apps/frontend/public/immunity/threat-icons/threat-0052.svg new file mode 100644 index 0000000..5eae2c5 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0052.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0053.svg b/apps/frontend/public/immunity/threat-icons/threat-0053.svg new file mode 100644 index 0000000..74adfb5 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0053.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0054.svg b/apps/frontend/public/immunity/threat-icons/threat-0054.svg new file mode 100644 index 0000000..e3779cb --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0054.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0055.svg b/apps/frontend/public/immunity/threat-icons/threat-0055.svg new file mode 100644 index 0000000..293951d --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0055.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0056.svg b/apps/frontend/public/immunity/threat-icons/threat-0056.svg new file mode 100644 index 0000000..9f21eb3 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0056.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0057.svg b/apps/frontend/public/immunity/threat-icons/threat-0057.svg new file mode 100644 index 0000000..e7159dd --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0057.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0058.svg b/apps/frontend/public/immunity/threat-icons/threat-0058.svg new file mode 100644 index 0000000..9f4fc16 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0058.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0059.svg b/apps/frontend/public/immunity/threat-icons/threat-0059.svg new file mode 100644 index 0000000..94adfd2 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0059.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0060.svg b/apps/frontend/public/immunity/threat-icons/threat-0060.svg new file mode 100644 index 0000000..565fade --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0060.svg @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0061.svg b/apps/frontend/public/immunity/threat-icons/threat-0061.svg new file mode 100644 index 0000000..2701cbb --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0061.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0062.svg b/apps/frontend/public/immunity/threat-icons/threat-0062.svg new file mode 100644 index 0000000..5236ee6 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0062.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0063.svg b/apps/frontend/public/immunity/threat-icons/threat-0063.svg new file mode 100644 index 0000000..cc0cc61 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0063.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0064.svg b/apps/frontend/public/immunity/threat-icons/threat-0064.svg new file mode 100644 index 0000000..2b39478 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0064.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0065.svg b/apps/frontend/public/immunity/threat-icons/threat-0065.svg new file mode 100644 index 0000000..513bdc5 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0065.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0066.svg b/apps/frontend/public/immunity/threat-icons/threat-0066.svg new file mode 100644 index 0000000..77b1288 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0066.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0067.svg b/apps/frontend/public/immunity/threat-icons/threat-0067.svg new file mode 100644 index 0000000..fe44e6b --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0067.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0068.svg b/apps/frontend/public/immunity/threat-icons/threat-0068.svg new file mode 100644 index 0000000..5b626e1 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0068.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0069.svg b/apps/frontend/public/immunity/threat-icons/threat-0069.svg new file mode 100644 index 0000000..7129007 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0069.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0070.svg b/apps/frontend/public/immunity/threat-icons/threat-0070.svg new file mode 100644 index 0000000..cd5e200 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0070.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0071.svg b/apps/frontend/public/immunity/threat-icons/threat-0071.svg new file mode 100644 index 0000000..91b2eb8 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0071.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0072.svg b/apps/frontend/public/immunity/threat-icons/threat-0072.svg new file mode 100644 index 0000000..91d3f3b --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0072.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0073.svg b/apps/frontend/public/immunity/threat-icons/threat-0073.svg new file mode 100644 index 0000000..fb1635a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0073.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0074.svg b/apps/frontend/public/immunity/threat-icons/threat-0074.svg new file mode 100644 index 0000000..6b54635 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0074.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0075.svg b/apps/frontend/public/immunity/threat-icons/threat-0075.svg new file mode 100644 index 0000000..d0d9fe5 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0075.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0076.svg b/apps/frontend/public/immunity/threat-icons/threat-0076.svg new file mode 100644 index 0000000..6af40e2 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0076.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0077.svg b/apps/frontend/public/immunity/threat-icons/threat-0077.svg new file mode 100644 index 0000000..9459d77 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0077.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0078.svg b/apps/frontend/public/immunity/threat-icons/threat-0078.svg new file mode 100644 index 0000000..975cdbd --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0078.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0079.svg b/apps/frontend/public/immunity/threat-icons/threat-0079.svg new file mode 100644 index 0000000..006a66d --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0079.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0080.svg b/apps/frontend/public/immunity/threat-icons/threat-0080.svg new file mode 100644 index 0000000..8eb4e8d --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0080.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0081.svg b/apps/frontend/public/immunity/threat-icons/threat-0081.svg new file mode 100644 index 0000000..36b00cd --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0081.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0082.svg b/apps/frontend/public/immunity/threat-icons/threat-0082.svg new file mode 100644 index 0000000..a917437 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0082.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0083.svg b/apps/frontend/public/immunity/threat-icons/threat-0083.svg new file mode 100644 index 0000000..3bb1789 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0083.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0084.svg b/apps/frontend/public/immunity/threat-icons/threat-0084.svg new file mode 100644 index 0000000..ba738e1 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0084.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0085.svg b/apps/frontend/public/immunity/threat-icons/threat-0085.svg new file mode 100644 index 0000000..acc62ea --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0085.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0086.svg b/apps/frontend/public/immunity/threat-icons/threat-0086.svg new file mode 100644 index 0000000..62b50a9 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0086.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0087.svg b/apps/frontend/public/immunity/threat-icons/threat-0087.svg new file mode 100644 index 0000000..3aec7a4 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0087.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0088.svg b/apps/frontend/public/immunity/threat-icons/threat-0088.svg new file mode 100644 index 0000000..9d2513c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0088.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0089.svg b/apps/frontend/public/immunity/threat-icons/threat-0089.svg new file mode 100644 index 0000000..3d88f11 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0089.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0090.svg b/apps/frontend/public/immunity/threat-icons/threat-0090.svg new file mode 100644 index 0000000..055d93c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0090.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0091.svg b/apps/frontend/public/immunity/threat-icons/threat-0091.svg new file mode 100644 index 0000000..26e80f1 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0091.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0092.svg b/apps/frontend/public/immunity/threat-icons/threat-0092.svg new file mode 100644 index 0000000..74fb030 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0092.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0093.svg b/apps/frontend/public/immunity/threat-icons/threat-0093.svg new file mode 100644 index 0000000..e9280ac --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0093.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0094.svg b/apps/frontend/public/immunity/threat-icons/threat-0094.svg new file mode 100644 index 0000000..2c61067 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0094.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0095.svg b/apps/frontend/public/immunity/threat-icons/threat-0095.svg new file mode 100644 index 0000000..f5cb266 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0095.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0096.svg b/apps/frontend/public/immunity/threat-icons/threat-0096.svg new file mode 100644 index 0000000..529bab8 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0096.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0097.svg b/apps/frontend/public/immunity/threat-icons/threat-0097.svg new file mode 100644 index 0000000..7a68637 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0097.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0098.svg b/apps/frontend/public/immunity/threat-icons/threat-0098.svg new file mode 100644 index 0000000..3b18e7d --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0098.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0099.svg b/apps/frontend/public/immunity/threat-icons/threat-0099.svg new file mode 100644 index 0000000..a78b519 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0099.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0100.svg b/apps/frontend/public/immunity/threat-icons/threat-0100.svg new file mode 100644 index 0000000..113f37f --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0100.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0101.svg b/apps/frontend/public/immunity/threat-icons/threat-0101.svg new file mode 100644 index 0000000..bf48a04 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0101.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0102.svg b/apps/frontend/public/immunity/threat-icons/threat-0102.svg new file mode 100644 index 0000000..9235c1a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0102.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0103.svg b/apps/frontend/public/immunity/threat-icons/threat-0103.svg new file mode 100644 index 0000000..ef84a93 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0103.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0104.svg b/apps/frontend/public/immunity/threat-icons/threat-0104.svg new file mode 100644 index 0000000..98be5cf --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0104.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0105.svg b/apps/frontend/public/immunity/threat-icons/threat-0105.svg new file mode 100644 index 0000000..3db4325 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0105.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0106.svg b/apps/frontend/public/immunity/threat-icons/threat-0106.svg new file mode 100644 index 0000000..18be219 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0106.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0107.svg b/apps/frontend/public/immunity/threat-icons/threat-0107.svg new file mode 100644 index 0000000..57fb2ae --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0107.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0108.svg b/apps/frontend/public/immunity/threat-icons/threat-0108.svg new file mode 100644 index 0000000..cba77aa --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0108.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0109.svg b/apps/frontend/public/immunity/threat-icons/threat-0109.svg new file mode 100644 index 0000000..b9e40fd --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0109.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0110.svg b/apps/frontend/public/immunity/threat-icons/threat-0110.svg new file mode 100644 index 0000000..e14ccbd --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0110.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0111.svg b/apps/frontend/public/immunity/threat-icons/threat-0111.svg new file mode 100644 index 0000000..30e1519 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0111.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0112.svg b/apps/frontend/public/immunity/threat-icons/threat-0112.svg new file mode 100644 index 0000000..6193ec6 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0112.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0113.svg b/apps/frontend/public/immunity/threat-icons/threat-0113.svg new file mode 100644 index 0000000..60e581b --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0113.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0114.svg b/apps/frontend/public/immunity/threat-icons/threat-0114.svg new file mode 100644 index 0000000..5939135 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0114.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0115.svg b/apps/frontend/public/immunity/threat-icons/threat-0115.svg new file mode 100644 index 0000000..5deb16f --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0115.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0116.svg b/apps/frontend/public/immunity/threat-icons/threat-0116.svg new file mode 100644 index 0000000..300ffaf --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0116.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0117.svg b/apps/frontend/public/immunity/threat-icons/threat-0117.svg new file mode 100644 index 0000000..8d3f9fd --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0117.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0118.svg b/apps/frontend/public/immunity/threat-icons/threat-0118.svg new file mode 100644 index 0000000..735fb2f --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0118.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0119.svg b/apps/frontend/public/immunity/threat-icons/threat-0119.svg new file mode 100644 index 0000000..26bcae5 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0119.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0120.svg b/apps/frontend/public/immunity/threat-icons/threat-0120.svg new file mode 100644 index 0000000..18f8d0a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0120.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0121.svg b/apps/frontend/public/immunity/threat-icons/threat-0121.svg new file mode 100644 index 0000000..9a65729 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0121.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0122.svg b/apps/frontend/public/immunity/threat-icons/threat-0122.svg new file mode 100644 index 0000000..f78ae27 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0122.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0123.svg b/apps/frontend/public/immunity/threat-icons/threat-0123.svg new file mode 100644 index 0000000..70040aa --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0123.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0124.svg b/apps/frontend/public/immunity/threat-icons/threat-0124.svg new file mode 100644 index 0000000..e89238a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0124.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0125.svg b/apps/frontend/public/immunity/threat-icons/threat-0125.svg new file mode 100644 index 0000000..95c3181 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0125.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0126.svg b/apps/frontend/public/immunity/threat-icons/threat-0126.svg new file mode 100644 index 0000000..4acfff6 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0126.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0127.svg b/apps/frontend/public/immunity/threat-icons/threat-0127.svg new file mode 100644 index 0000000..d817021 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0127.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0128.svg b/apps/frontend/public/immunity/threat-icons/threat-0128.svg new file mode 100644 index 0000000..6ac5c17 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0128.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0129.svg b/apps/frontend/public/immunity/threat-icons/threat-0129.svg new file mode 100644 index 0000000..c50c92f --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0129.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0130.svg b/apps/frontend/public/immunity/threat-icons/threat-0130.svg new file mode 100644 index 0000000..2e3a040 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0130.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0131.svg b/apps/frontend/public/immunity/threat-icons/threat-0131.svg new file mode 100644 index 0000000..8ddcdb8 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0131.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0132.svg b/apps/frontend/public/immunity/threat-icons/threat-0132.svg new file mode 100644 index 0000000..96a59b6 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0132.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0133.svg b/apps/frontend/public/immunity/threat-icons/threat-0133.svg new file mode 100644 index 0000000..ba136b3 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0133.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0134.svg b/apps/frontend/public/immunity/threat-icons/threat-0134.svg new file mode 100644 index 0000000..679ab18 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0134.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0135.svg b/apps/frontend/public/immunity/threat-icons/threat-0135.svg new file mode 100644 index 0000000..04177eb --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0135.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0136.svg b/apps/frontend/public/immunity/threat-icons/threat-0136.svg new file mode 100644 index 0000000..116ba88 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0136.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0137.svg b/apps/frontend/public/immunity/threat-icons/threat-0137.svg new file mode 100644 index 0000000..fcaaaf5 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0137.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0138.svg b/apps/frontend/public/immunity/threat-icons/threat-0138.svg new file mode 100644 index 0000000..a5b4526 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0138.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0139.svg b/apps/frontend/public/immunity/threat-icons/threat-0139.svg new file mode 100644 index 0000000..0809962 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0139.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0140.svg b/apps/frontend/public/immunity/threat-icons/threat-0140.svg new file mode 100644 index 0000000..e0259ef --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0140.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0141.svg b/apps/frontend/public/immunity/threat-icons/threat-0141.svg new file mode 100644 index 0000000..70a8a7a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0141.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0142.svg b/apps/frontend/public/immunity/threat-icons/threat-0142.svg new file mode 100644 index 0000000..22ccc69 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0142.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0143.svg b/apps/frontend/public/immunity/threat-icons/threat-0143.svg new file mode 100644 index 0000000..141de74 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0143.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0144.svg b/apps/frontend/public/immunity/threat-icons/threat-0144.svg new file mode 100644 index 0000000..362263b --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0144.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0145.svg b/apps/frontend/public/immunity/threat-icons/threat-0145.svg new file mode 100644 index 0000000..a6768f9 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0145.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0146.svg b/apps/frontend/public/immunity/threat-icons/threat-0146.svg new file mode 100644 index 0000000..34fc3ed --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0146.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0147.svg b/apps/frontend/public/immunity/threat-icons/threat-0147.svg new file mode 100644 index 0000000..404c906 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0147.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0148.svg b/apps/frontend/public/immunity/threat-icons/threat-0148.svg new file mode 100644 index 0000000..1900dd6 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0148.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0149.svg b/apps/frontend/public/immunity/threat-icons/threat-0149.svg new file mode 100644 index 0000000..fe7cd14 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0149.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0150.svg b/apps/frontend/public/immunity/threat-icons/threat-0150.svg new file mode 100644 index 0000000..52bc0ad --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0150.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0151.svg b/apps/frontend/public/immunity/threat-icons/threat-0151.svg new file mode 100644 index 0000000..7e68a05 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0151.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0152.svg b/apps/frontend/public/immunity/threat-icons/threat-0152.svg new file mode 100644 index 0000000..7afba0e --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0152.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0153.svg b/apps/frontend/public/immunity/threat-icons/threat-0153.svg new file mode 100644 index 0000000..71b2fe5 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0153.svg @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0154.svg b/apps/frontend/public/immunity/threat-icons/threat-0154.svg new file mode 100644 index 0000000..0f3fdb7 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0154.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0155.svg b/apps/frontend/public/immunity/threat-icons/threat-0155.svg new file mode 100644 index 0000000..a910005 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0155.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0156.svg b/apps/frontend/public/immunity/threat-icons/threat-0156.svg new file mode 100644 index 0000000..3ccd2fb --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0156.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0157.svg b/apps/frontend/public/immunity/threat-icons/threat-0157.svg new file mode 100644 index 0000000..a5f38da --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0157.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0158.svg b/apps/frontend/public/immunity/threat-icons/threat-0158.svg new file mode 100644 index 0000000..443b805 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0158.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0159.svg b/apps/frontend/public/immunity/threat-icons/threat-0159.svg new file mode 100644 index 0000000..365c657 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0159.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0160.svg b/apps/frontend/public/immunity/threat-icons/threat-0160.svg new file mode 100644 index 0000000..07128a5 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0160.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0161.svg b/apps/frontend/public/immunity/threat-icons/threat-0161.svg new file mode 100644 index 0000000..890c516 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0161.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0162.svg b/apps/frontend/public/immunity/threat-icons/threat-0162.svg new file mode 100644 index 0000000..3964f39 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0162.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0163.svg b/apps/frontend/public/immunity/threat-icons/threat-0163.svg new file mode 100644 index 0000000..26d3b27 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0163.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0164.svg b/apps/frontend/public/immunity/threat-icons/threat-0164.svg new file mode 100644 index 0000000..1d88531 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0164.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0165.svg b/apps/frontend/public/immunity/threat-icons/threat-0165.svg new file mode 100644 index 0000000..052993a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0165.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0166.svg b/apps/frontend/public/immunity/threat-icons/threat-0166.svg new file mode 100644 index 0000000..b7b9026 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0166.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0167.svg b/apps/frontend/public/immunity/threat-icons/threat-0167.svg new file mode 100644 index 0000000..417d5f5 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0167.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0168.svg b/apps/frontend/public/immunity/threat-icons/threat-0168.svg new file mode 100644 index 0000000..fb98dc4 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0168.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0169.svg b/apps/frontend/public/immunity/threat-icons/threat-0169.svg new file mode 100644 index 0000000..4eb7cd7 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0169.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0170.svg b/apps/frontend/public/immunity/threat-icons/threat-0170.svg new file mode 100644 index 0000000..7072976 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0170.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0171.svg b/apps/frontend/public/immunity/threat-icons/threat-0171.svg new file mode 100644 index 0000000..7baed30 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0171.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0172.svg b/apps/frontend/public/immunity/threat-icons/threat-0172.svg new file mode 100644 index 0000000..e3f482c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0172.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0173.svg b/apps/frontend/public/immunity/threat-icons/threat-0173.svg new file mode 100644 index 0000000..a294f81 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0173.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0174.svg b/apps/frontend/public/immunity/threat-icons/threat-0174.svg new file mode 100644 index 0000000..929cb10 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0174.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0175.svg b/apps/frontend/public/immunity/threat-icons/threat-0175.svg new file mode 100644 index 0000000..81cc0ef --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0175.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0176.svg b/apps/frontend/public/immunity/threat-icons/threat-0176.svg new file mode 100644 index 0000000..e771aae --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0176.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0177.svg b/apps/frontend/public/immunity/threat-icons/threat-0177.svg new file mode 100644 index 0000000..63b5ace --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0177.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0178.svg b/apps/frontend/public/immunity/threat-icons/threat-0178.svg new file mode 100644 index 0000000..137b69b --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0178.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0179.svg b/apps/frontend/public/immunity/threat-icons/threat-0179.svg new file mode 100644 index 0000000..5ccba5e --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0179.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0180.svg b/apps/frontend/public/immunity/threat-icons/threat-0180.svg new file mode 100644 index 0000000..991b4d6 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0180.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0181.svg b/apps/frontend/public/immunity/threat-icons/threat-0181.svg new file mode 100644 index 0000000..0607912 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0181.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0182.svg b/apps/frontend/public/immunity/threat-icons/threat-0182.svg new file mode 100644 index 0000000..e44c397 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0182.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0183.svg b/apps/frontend/public/immunity/threat-icons/threat-0183.svg new file mode 100644 index 0000000..435f687 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0183.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0184.svg b/apps/frontend/public/immunity/threat-icons/threat-0184.svg new file mode 100644 index 0000000..aa6be59 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0184.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0185.svg b/apps/frontend/public/immunity/threat-icons/threat-0185.svg new file mode 100644 index 0000000..95da1c3 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0185.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0186.svg b/apps/frontend/public/immunity/threat-icons/threat-0186.svg new file mode 100644 index 0000000..bec7b88 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0186.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0187.svg b/apps/frontend/public/immunity/threat-icons/threat-0187.svg new file mode 100644 index 0000000..e1eb0f5 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0187.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0188.svg b/apps/frontend/public/immunity/threat-icons/threat-0188.svg new file mode 100644 index 0000000..f3af7b4 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0188.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0189.svg b/apps/frontend/public/immunity/threat-icons/threat-0189.svg new file mode 100644 index 0000000..39f3b13 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0189.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0190.svg b/apps/frontend/public/immunity/threat-icons/threat-0190.svg new file mode 100644 index 0000000..d766616 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0190.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0191.svg b/apps/frontend/public/immunity/threat-icons/threat-0191.svg new file mode 100644 index 0000000..d7a910e --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0191.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0192.svg b/apps/frontend/public/immunity/threat-icons/threat-0192.svg new file mode 100644 index 0000000..28e1393 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0192.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0193.svg b/apps/frontend/public/immunity/threat-icons/threat-0193.svg new file mode 100644 index 0000000..60eb4d1 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0193.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0194.svg b/apps/frontend/public/immunity/threat-icons/threat-0194.svg new file mode 100644 index 0000000..abc3f80 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0194.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0195.svg b/apps/frontend/public/immunity/threat-icons/threat-0195.svg new file mode 100644 index 0000000..e3ed58f --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0195.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0196.svg b/apps/frontend/public/immunity/threat-icons/threat-0196.svg new file mode 100644 index 0000000..c389ef6 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0196.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0197.svg b/apps/frontend/public/immunity/threat-icons/threat-0197.svg new file mode 100644 index 0000000..067ff6e --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0197.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0198.svg b/apps/frontend/public/immunity/threat-icons/threat-0198.svg new file mode 100644 index 0000000..494cec4 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0198.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0199.svg b/apps/frontend/public/immunity/threat-icons/threat-0199.svg new file mode 100644 index 0000000..56e30ff --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0199.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0200.svg b/apps/frontend/public/immunity/threat-icons/threat-0200.svg new file mode 100644 index 0000000..79bd3f7 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0200.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0201.svg b/apps/frontend/public/immunity/threat-icons/threat-0201.svg new file mode 100644 index 0000000..ee8b7d5 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0201.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0202.svg b/apps/frontend/public/immunity/threat-icons/threat-0202.svg new file mode 100644 index 0000000..58e9681 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0202.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0203.svg b/apps/frontend/public/immunity/threat-icons/threat-0203.svg new file mode 100644 index 0000000..882c99d --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0203.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0204.svg b/apps/frontend/public/immunity/threat-icons/threat-0204.svg new file mode 100644 index 0000000..d04230b --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0204.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0205.svg b/apps/frontend/public/immunity/threat-icons/threat-0205.svg new file mode 100644 index 0000000..2a965f5 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0205.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0206.svg b/apps/frontend/public/immunity/threat-icons/threat-0206.svg new file mode 100644 index 0000000..1663bb2 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0206.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0207.svg b/apps/frontend/public/immunity/threat-icons/threat-0207.svg new file mode 100644 index 0000000..007a27a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0207.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0208.svg b/apps/frontend/public/immunity/threat-icons/threat-0208.svg new file mode 100644 index 0000000..638cac1 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0208.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0209.svg b/apps/frontend/public/immunity/threat-icons/threat-0209.svg new file mode 100644 index 0000000..b06c6ae --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0209.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0210.svg b/apps/frontend/public/immunity/threat-icons/threat-0210.svg new file mode 100644 index 0000000..f139003 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0210.svg @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0211.svg b/apps/frontend/public/immunity/threat-icons/threat-0211.svg new file mode 100644 index 0000000..02a4eb7 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0211.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0212.svg b/apps/frontend/public/immunity/threat-icons/threat-0212.svg new file mode 100644 index 0000000..fe77e4c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0212.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0213.svg b/apps/frontend/public/immunity/threat-icons/threat-0213.svg new file mode 100644 index 0000000..048eac5 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0213.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0214.svg b/apps/frontend/public/immunity/threat-icons/threat-0214.svg new file mode 100644 index 0000000..5aa4d9b --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0214.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0215.svg b/apps/frontend/public/immunity/threat-icons/threat-0215.svg new file mode 100644 index 0000000..db00f73 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0215.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0216.svg b/apps/frontend/public/immunity/threat-icons/threat-0216.svg new file mode 100644 index 0000000..8f71ca6 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0216.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0217.svg b/apps/frontend/public/immunity/threat-icons/threat-0217.svg new file mode 100644 index 0000000..3c48377 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0217.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0218.svg b/apps/frontend/public/immunity/threat-icons/threat-0218.svg new file mode 100644 index 0000000..2cfbe89 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0218.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0219.svg b/apps/frontend/public/immunity/threat-icons/threat-0219.svg new file mode 100644 index 0000000..6fa5bb8 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0219.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0220.svg b/apps/frontend/public/immunity/threat-icons/threat-0220.svg new file mode 100644 index 0000000..d41dbad --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0220.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0221.svg b/apps/frontend/public/immunity/threat-icons/threat-0221.svg new file mode 100644 index 0000000..7778fcf --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0221.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0222.svg b/apps/frontend/public/immunity/threat-icons/threat-0222.svg new file mode 100644 index 0000000..d3007da --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0222.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0223.svg b/apps/frontend/public/immunity/threat-icons/threat-0223.svg new file mode 100644 index 0000000..8778907 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0223.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0224.svg b/apps/frontend/public/immunity/threat-icons/threat-0224.svg new file mode 100644 index 0000000..d625e5b --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0224.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0225.svg b/apps/frontend/public/immunity/threat-icons/threat-0225.svg new file mode 100644 index 0000000..c2ac261 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0225.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0226.svg b/apps/frontend/public/immunity/threat-icons/threat-0226.svg new file mode 100644 index 0000000..33f929f --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0226.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0227.svg b/apps/frontend/public/immunity/threat-icons/threat-0227.svg new file mode 100644 index 0000000..3ca718b --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0227.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0228.svg b/apps/frontend/public/immunity/threat-icons/threat-0228.svg new file mode 100644 index 0000000..2b2ad56 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0228.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0229.svg b/apps/frontend/public/immunity/threat-icons/threat-0229.svg new file mode 100644 index 0000000..0cedf82 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0229.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0230.svg b/apps/frontend/public/immunity/threat-icons/threat-0230.svg new file mode 100644 index 0000000..734adf5 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0230.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0231.svg b/apps/frontend/public/immunity/threat-icons/threat-0231.svg new file mode 100644 index 0000000..a7ff294 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0231.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0232.svg b/apps/frontend/public/immunity/threat-icons/threat-0232.svg new file mode 100644 index 0000000..a398e43 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0232.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0233.svg b/apps/frontend/public/immunity/threat-icons/threat-0233.svg new file mode 100644 index 0000000..deb37d4 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0233.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0234.svg b/apps/frontend/public/immunity/threat-icons/threat-0234.svg new file mode 100644 index 0000000..f62d1e2 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0234.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0235.svg b/apps/frontend/public/immunity/threat-icons/threat-0235.svg new file mode 100644 index 0000000..11cdc95 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0235.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0236.svg b/apps/frontend/public/immunity/threat-icons/threat-0236.svg new file mode 100644 index 0000000..add8611 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0236.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0237.svg b/apps/frontend/public/immunity/threat-icons/threat-0237.svg new file mode 100644 index 0000000..3368bcc --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0237.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0238.svg b/apps/frontend/public/immunity/threat-icons/threat-0238.svg new file mode 100644 index 0000000..2c9d343 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0238.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0239.svg b/apps/frontend/public/immunity/threat-icons/threat-0239.svg new file mode 100644 index 0000000..e88f929 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0239.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0240.svg b/apps/frontend/public/immunity/threat-icons/threat-0240.svg new file mode 100644 index 0000000..71c9c34 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0240.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0241.svg b/apps/frontend/public/immunity/threat-icons/threat-0241.svg new file mode 100644 index 0000000..271bdb1 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0241.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0242.svg b/apps/frontend/public/immunity/threat-icons/threat-0242.svg new file mode 100644 index 0000000..e8e0fcd --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0242.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0243.svg b/apps/frontend/public/immunity/threat-icons/threat-0243.svg new file mode 100644 index 0000000..75a6f15 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0243.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0244.svg b/apps/frontend/public/immunity/threat-icons/threat-0244.svg new file mode 100644 index 0000000..c3ff35a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0244.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0245.svg b/apps/frontend/public/immunity/threat-icons/threat-0245.svg new file mode 100644 index 0000000..9fa5396 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0245.svg @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0246.svg b/apps/frontend/public/immunity/threat-icons/threat-0246.svg new file mode 100644 index 0000000..c0fcda7 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0246.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0247.svg b/apps/frontend/public/immunity/threat-icons/threat-0247.svg new file mode 100644 index 0000000..a9b14fd --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0247.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0248.svg b/apps/frontend/public/immunity/threat-icons/threat-0248.svg new file mode 100644 index 0000000..4ff118c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0248.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0249.svg b/apps/frontend/public/immunity/threat-icons/threat-0249.svg new file mode 100644 index 0000000..a9321b3 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0249.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0250.svg b/apps/frontend/public/immunity/threat-icons/threat-0250.svg new file mode 100644 index 0000000..d980459 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0250.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0251.svg b/apps/frontend/public/immunity/threat-icons/threat-0251.svg new file mode 100644 index 0000000..7a107ca --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0251.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0252.svg b/apps/frontend/public/immunity/threat-icons/threat-0252.svg new file mode 100644 index 0000000..defa895 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0252.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0253.svg b/apps/frontend/public/immunity/threat-icons/threat-0253.svg new file mode 100644 index 0000000..f3d1aa5 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0253.svg @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0254.svg b/apps/frontend/public/immunity/threat-icons/threat-0254.svg new file mode 100644 index 0000000..72ce345 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0254.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0255.svg b/apps/frontend/public/immunity/threat-icons/threat-0255.svg new file mode 100644 index 0000000..c6395e0 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0255.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0256.svg b/apps/frontend/public/immunity/threat-icons/threat-0256.svg new file mode 100644 index 0000000..d522ada --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0256.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0257.svg b/apps/frontend/public/immunity/threat-icons/threat-0257.svg new file mode 100644 index 0000000..4b023c7 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0257.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0258.svg b/apps/frontend/public/immunity/threat-icons/threat-0258.svg new file mode 100644 index 0000000..20a675c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0258.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0259.svg b/apps/frontend/public/immunity/threat-icons/threat-0259.svg new file mode 100644 index 0000000..9c887b3 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0259.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0260.svg b/apps/frontend/public/immunity/threat-icons/threat-0260.svg new file mode 100644 index 0000000..e0a7943 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0260.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0261.svg b/apps/frontend/public/immunity/threat-icons/threat-0261.svg new file mode 100644 index 0000000..84829a6 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0261.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0262.svg b/apps/frontend/public/immunity/threat-icons/threat-0262.svg new file mode 100644 index 0000000..3b3c589 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0262.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0263.svg b/apps/frontend/public/immunity/threat-icons/threat-0263.svg new file mode 100644 index 0000000..064b647 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0263.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0264.svg b/apps/frontend/public/immunity/threat-icons/threat-0264.svg new file mode 100644 index 0000000..3335e3a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0264.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0265.svg b/apps/frontend/public/immunity/threat-icons/threat-0265.svg new file mode 100644 index 0000000..662fca0 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0265.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0266.svg b/apps/frontend/public/immunity/threat-icons/threat-0266.svg new file mode 100644 index 0000000..4cfbd84 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0266.svg @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0267.svg b/apps/frontend/public/immunity/threat-icons/threat-0267.svg new file mode 100644 index 0000000..c6dd16c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0267.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0268.svg b/apps/frontend/public/immunity/threat-icons/threat-0268.svg new file mode 100644 index 0000000..b3e2cf7 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0268.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0269.svg b/apps/frontend/public/immunity/threat-icons/threat-0269.svg new file mode 100644 index 0000000..10df8ef --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0269.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0270.svg b/apps/frontend/public/immunity/threat-icons/threat-0270.svg new file mode 100644 index 0000000..a964770 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0270.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0271.svg b/apps/frontend/public/immunity/threat-icons/threat-0271.svg new file mode 100644 index 0000000..3063a62 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0271.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0272.svg b/apps/frontend/public/immunity/threat-icons/threat-0272.svg new file mode 100644 index 0000000..03ab4b9 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0272.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0273.svg b/apps/frontend/public/immunity/threat-icons/threat-0273.svg new file mode 100644 index 0000000..b70d258 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0273.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0274.svg b/apps/frontend/public/immunity/threat-icons/threat-0274.svg new file mode 100644 index 0000000..d1fcf82 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0274.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0275.svg b/apps/frontend/public/immunity/threat-icons/threat-0275.svg new file mode 100644 index 0000000..2e6e070 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0275.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0276.svg b/apps/frontend/public/immunity/threat-icons/threat-0276.svg new file mode 100644 index 0000000..c1fdda1 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0276.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0277.svg b/apps/frontend/public/immunity/threat-icons/threat-0277.svg new file mode 100644 index 0000000..f34c8e1 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0277.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0278.svg b/apps/frontend/public/immunity/threat-icons/threat-0278.svg new file mode 100644 index 0000000..b34f326 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0278.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0279.svg b/apps/frontend/public/immunity/threat-icons/threat-0279.svg new file mode 100644 index 0000000..bf51c25 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0279.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0280.svg b/apps/frontend/public/immunity/threat-icons/threat-0280.svg new file mode 100644 index 0000000..c654dc6 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0280.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0281.svg b/apps/frontend/public/immunity/threat-icons/threat-0281.svg new file mode 100644 index 0000000..df0c140 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0281.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0282.svg b/apps/frontend/public/immunity/threat-icons/threat-0282.svg new file mode 100644 index 0000000..f1b9119 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0282.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0283.svg b/apps/frontend/public/immunity/threat-icons/threat-0283.svg new file mode 100644 index 0000000..82ca19a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0283.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0284.svg b/apps/frontend/public/immunity/threat-icons/threat-0284.svg new file mode 100644 index 0000000..07ed58c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0284.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0285.svg b/apps/frontend/public/immunity/threat-icons/threat-0285.svg new file mode 100644 index 0000000..3bf4007 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0285.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0286.svg b/apps/frontend/public/immunity/threat-icons/threat-0286.svg new file mode 100644 index 0000000..633ef6b --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0286.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0287.svg b/apps/frontend/public/immunity/threat-icons/threat-0287.svg new file mode 100644 index 0000000..06aab5e --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0287.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0288.svg b/apps/frontend/public/immunity/threat-icons/threat-0288.svg new file mode 100644 index 0000000..1f30271 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0288.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0289.svg b/apps/frontend/public/immunity/threat-icons/threat-0289.svg new file mode 100644 index 0000000..45abaad --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0289.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0290.svg b/apps/frontend/public/immunity/threat-icons/threat-0290.svg new file mode 100644 index 0000000..0372271 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0290.svg @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0291.svg b/apps/frontend/public/immunity/threat-icons/threat-0291.svg new file mode 100644 index 0000000..209ed84 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0291.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0292.svg b/apps/frontend/public/immunity/threat-icons/threat-0292.svg new file mode 100644 index 0000000..c39607d --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0292.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0293.svg b/apps/frontend/public/immunity/threat-icons/threat-0293.svg new file mode 100644 index 0000000..67acf56 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0293.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0294.svg b/apps/frontend/public/immunity/threat-icons/threat-0294.svg new file mode 100644 index 0000000..c006529 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0294.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0295.svg b/apps/frontend/public/immunity/threat-icons/threat-0295.svg new file mode 100644 index 0000000..f57dacb --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0295.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0296.svg b/apps/frontend/public/immunity/threat-icons/threat-0296.svg new file mode 100644 index 0000000..70f93ef --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0296.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0297.svg b/apps/frontend/public/immunity/threat-icons/threat-0297.svg new file mode 100644 index 0000000..b1fc9f1 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0297.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0298.svg b/apps/frontend/public/immunity/threat-icons/threat-0298.svg new file mode 100644 index 0000000..3f17aae --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0298.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0299.svg b/apps/frontend/public/immunity/threat-icons/threat-0299.svg new file mode 100644 index 0000000..209f8b7 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0299.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0300.svg b/apps/frontend/public/immunity/threat-icons/threat-0300.svg new file mode 100644 index 0000000..9df5fa7 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0300.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0301.svg b/apps/frontend/public/immunity/threat-icons/threat-0301.svg new file mode 100644 index 0000000..d5d6292 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0301.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0302.svg b/apps/frontend/public/immunity/threat-icons/threat-0302.svg new file mode 100644 index 0000000..85a3f74 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0302.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0303.svg b/apps/frontend/public/immunity/threat-icons/threat-0303.svg new file mode 100644 index 0000000..6a4a5a8 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0303.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0304.svg b/apps/frontend/public/immunity/threat-icons/threat-0304.svg new file mode 100644 index 0000000..eb0ee2b --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0304.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0305.svg b/apps/frontend/public/immunity/threat-icons/threat-0305.svg new file mode 100644 index 0000000..d3f6891 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0305.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0306.svg b/apps/frontend/public/immunity/threat-icons/threat-0306.svg new file mode 100644 index 0000000..ef36cb7 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0306.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0307.svg b/apps/frontend/public/immunity/threat-icons/threat-0307.svg new file mode 100644 index 0000000..1396d37 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0307.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0308.svg b/apps/frontend/public/immunity/threat-icons/threat-0308.svg new file mode 100644 index 0000000..650304b --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0308.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0309.svg b/apps/frontend/public/immunity/threat-icons/threat-0309.svg new file mode 100644 index 0000000..b7afcf7 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0309.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0310.svg b/apps/frontend/public/immunity/threat-icons/threat-0310.svg new file mode 100644 index 0000000..2897f1f --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0310.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0311.svg b/apps/frontend/public/immunity/threat-icons/threat-0311.svg new file mode 100644 index 0000000..5fbc6d9 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0311.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0312.svg b/apps/frontend/public/immunity/threat-icons/threat-0312.svg new file mode 100644 index 0000000..5bad41e --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0312.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0313.svg b/apps/frontend/public/immunity/threat-icons/threat-0313.svg new file mode 100644 index 0000000..a764f7e --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0313.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0314.svg b/apps/frontend/public/immunity/threat-icons/threat-0314.svg new file mode 100644 index 0000000..70816a0 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0314.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0315.svg b/apps/frontend/public/immunity/threat-icons/threat-0315.svg new file mode 100644 index 0000000..6129af9 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0315.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0316.svg b/apps/frontend/public/immunity/threat-icons/threat-0316.svg new file mode 100644 index 0000000..3863a3c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0316.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0317.svg b/apps/frontend/public/immunity/threat-icons/threat-0317.svg new file mode 100644 index 0000000..f6c4a76 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0317.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0318.svg b/apps/frontend/public/immunity/threat-icons/threat-0318.svg new file mode 100644 index 0000000..f7ffb63 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0318.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0319.svg b/apps/frontend/public/immunity/threat-icons/threat-0319.svg new file mode 100644 index 0000000..bc0bc42 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0319.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0320.svg b/apps/frontend/public/immunity/threat-icons/threat-0320.svg new file mode 100644 index 0000000..50160b2 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0320.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0321.svg b/apps/frontend/public/immunity/threat-icons/threat-0321.svg new file mode 100644 index 0000000..b74beb3 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0321.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0322.svg b/apps/frontend/public/immunity/threat-icons/threat-0322.svg new file mode 100644 index 0000000..d7774ed --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0322.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0323.svg b/apps/frontend/public/immunity/threat-icons/threat-0323.svg new file mode 100644 index 0000000..a7c172d --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0323.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0324.svg b/apps/frontend/public/immunity/threat-icons/threat-0324.svg new file mode 100644 index 0000000..3ed214a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0324.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0325.svg b/apps/frontend/public/immunity/threat-icons/threat-0325.svg new file mode 100644 index 0000000..3f101f6 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0325.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0326.svg b/apps/frontend/public/immunity/threat-icons/threat-0326.svg new file mode 100644 index 0000000..2796882 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0326.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0327.svg b/apps/frontend/public/immunity/threat-icons/threat-0327.svg new file mode 100644 index 0000000..3309dfc --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0327.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0328.svg b/apps/frontend/public/immunity/threat-icons/threat-0328.svg new file mode 100644 index 0000000..46291b6 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0328.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0329.svg b/apps/frontend/public/immunity/threat-icons/threat-0329.svg new file mode 100644 index 0000000..e6eb96d --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0329.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0330.svg b/apps/frontend/public/immunity/threat-icons/threat-0330.svg new file mode 100644 index 0000000..77acd9e --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0330.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0331.svg b/apps/frontend/public/immunity/threat-icons/threat-0331.svg new file mode 100644 index 0000000..a510f2c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0331.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0332.svg b/apps/frontend/public/immunity/threat-icons/threat-0332.svg new file mode 100644 index 0000000..cb8052d --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0332.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0333.svg b/apps/frontend/public/immunity/threat-icons/threat-0333.svg new file mode 100644 index 0000000..3907324 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0333.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0334.svg b/apps/frontend/public/immunity/threat-icons/threat-0334.svg new file mode 100644 index 0000000..4104e78 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0334.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0335.svg b/apps/frontend/public/immunity/threat-icons/threat-0335.svg new file mode 100644 index 0000000..540ceef --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0335.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0336.svg b/apps/frontend/public/immunity/threat-icons/threat-0336.svg new file mode 100644 index 0000000..de7b648 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0336.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0337.svg b/apps/frontend/public/immunity/threat-icons/threat-0337.svg new file mode 100644 index 0000000..07a8985 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0337.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0338.svg b/apps/frontend/public/immunity/threat-icons/threat-0338.svg new file mode 100644 index 0000000..94b3cd9 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0338.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0339.svg b/apps/frontend/public/immunity/threat-icons/threat-0339.svg new file mode 100644 index 0000000..cb33cf4 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0339.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0340.svg b/apps/frontend/public/immunity/threat-icons/threat-0340.svg new file mode 100644 index 0000000..dd2bb3a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0340.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0341.svg b/apps/frontend/public/immunity/threat-icons/threat-0341.svg new file mode 100644 index 0000000..0bb4931 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0341.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0342.svg b/apps/frontend/public/immunity/threat-icons/threat-0342.svg new file mode 100644 index 0000000..5563c87 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0342.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0343.svg b/apps/frontend/public/immunity/threat-icons/threat-0343.svg new file mode 100644 index 0000000..2bb89a4 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0343.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0344.svg b/apps/frontend/public/immunity/threat-icons/threat-0344.svg new file mode 100644 index 0000000..dc399ff --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0344.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0345.svg b/apps/frontend/public/immunity/threat-icons/threat-0345.svg new file mode 100644 index 0000000..0d33fd5 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0345.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0346.svg b/apps/frontend/public/immunity/threat-icons/threat-0346.svg new file mode 100644 index 0000000..dd375d1 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0346.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0347.svg b/apps/frontend/public/immunity/threat-icons/threat-0347.svg new file mode 100644 index 0000000..4ba7555 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0347.svg @@ -0,0 +1,18 @@ + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0348.svg b/apps/frontend/public/immunity/threat-icons/threat-0348.svg new file mode 100644 index 0000000..03922a1 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0348.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0349.svg b/apps/frontend/public/immunity/threat-icons/threat-0349.svg new file mode 100644 index 0000000..b41bcc2 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0349.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0350.svg b/apps/frontend/public/immunity/threat-icons/threat-0350.svg new file mode 100644 index 0000000..bcc4bf5 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0350.svg @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0351.svg b/apps/frontend/public/immunity/threat-icons/threat-0351.svg new file mode 100644 index 0000000..c67215c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0351.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0352.svg b/apps/frontend/public/immunity/threat-icons/threat-0352.svg new file mode 100644 index 0000000..eca993a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0352.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0353.svg b/apps/frontend/public/immunity/threat-icons/threat-0353.svg new file mode 100644 index 0000000..7cde316 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0353.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0354.svg b/apps/frontend/public/immunity/threat-icons/threat-0354.svg new file mode 100644 index 0000000..dc5e128 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0354.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0355.svg b/apps/frontend/public/immunity/threat-icons/threat-0355.svg new file mode 100644 index 0000000..127e1ca --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0355.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0356.svg b/apps/frontend/public/immunity/threat-icons/threat-0356.svg new file mode 100644 index 0000000..9429225 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0356.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0357.svg b/apps/frontend/public/immunity/threat-icons/threat-0357.svg new file mode 100644 index 0000000..830996e --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0357.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0358.svg b/apps/frontend/public/immunity/threat-icons/threat-0358.svg new file mode 100644 index 0000000..8081d4e --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0358.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0359.svg b/apps/frontend/public/immunity/threat-icons/threat-0359.svg new file mode 100644 index 0000000..f80655d --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0359.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0360.svg b/apps/frontend/public/immunity/threat-icons/threat-0360.svg new file mode 100644 index 0000000..8e8eb29 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0360.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0361.svg b/apps/frontend/public/immunity/threat-icons/threat-0361.svg new file mode 100644 index 0000000..8ce70b9 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0361.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0362.svg b/apps/frontend/public/immunity/threat-icons/threat-0362.svg new file mode 100644 index 0000000..b2ba7ff --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0362.svg @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0363.svg b/apps/frontend/public/immunity/threat-icons/threat-0363.svg new file mode 100644 index 0000000..ab04c70 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0363.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0364.svg b/apps/frontend/public/immunity/threat-icons/threat-0364.svg new file mode 100644 index 0000000..69a743f --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0364.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0365.svg b/apps/frontend/public/immunity/threat-icons/threat-0365.svg new file mode 100644 index 0000000..b166868 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0365.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0366.svg b/apps/frontend/public/immunity/threat-icons/threat-0366.svg new file mode 100644 index 0000000..060bc23 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0366.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0367.svg b/apps/frontend/public/immunity/threat-icons/threat-0367.svg new file mode 100644 index 0000000..c520bd4 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0367.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0368.svg b/apps/frontend/public/immunity/threat-icons/threat-0368.svg new file mode 100644 index 0000000..301a322 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0368.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0369.svg b/apps/frontend/public/immunity/threat-icons/threat-0369.svg new file mode 100644 index 0000000..e41891a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0369.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0370.svg b/apps/frontend/public/immunity/threat-icons/threat-0370.svg new file mode 100644 index 0000000..ef52b3a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0370.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0371.svg b/apps/frontend/public/immunity/threat-icons/threat-0371.svg new file mode 100644 index 0000000..6be63cc --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0371.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0372.svg b/apps/frontend/public/immunity/threat-icons/threat-0372.svg new file mode 100644 index 0000000..e9e3913 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0372.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0373.svg b/apps/frontend/public/immunity/threat-icons/threat-0373.svg new file mode 100644 index 0000000..98bbf14 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0373.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0374.svg b/apps/frontend/public/immunity/threat-icons/threat-0374.svg new file mode 100644 index 0000000..5967dc0 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0374.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0375.svg b/apps/frontend/public/immunity/threat-icons/threat-0375.svg new file mode 100644 index 0000000..845bbc9 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0375.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0376.svg b/apps/frontend/public/immunity/threat-icons/threat-0376.svg new file mode 100644 index 0000000..ddb3c94 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0376.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0377.svg b/apps/frontend/public/immunity/threat-icons/threat-0377.svg new file mode 100644 index 0000000..e890a32 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0377.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0378.svg b/apps/frontend/public/immunity/threat-icons/threat-0378.svg new file mode 100644 index 0000000..25a2877 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0378.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0379.svg b/apps/frontend/public/immunity/threat-icons/threat-0379.svg new file mode 100644 index 0000000..29a3746 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0379.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0380.svg b/apps/frontend/public/immunity/threat-icons/threat-0380.svg new file mode 100644 index 0000000..04fab14 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0380.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0381.svg b/apps/frontend/public/immunity/threat-icons/threat-0381.svg new file mode 100644 index 0000000..0ab87e9 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0381.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0382.svg b/apps/frontend/public/immunity/threat-icons/threat-0382.svg new file mode 100644 index 0000000..086f55b --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0382.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0383.svg b/apps/frontend/public/immunity/threat-icons/threat-0383.svg new file mode 100644 index 0000000..1d745c6 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0383.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0384.svg b/apps/frontend/public/immunity/threat-icons/threat-0384.svg new file mode 100644 index 0000000..ca931f0 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0384.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0385.svg b/apps/frontend/public/immunity/threat-icons/threat-0385.svg new file mode 100644 index 0000000..cc1b27b --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0385.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0386.svg b/apps/frontend/public/immunity/threat-icons/threat-0386.svg new file mode 100644 index 0000000..fbdb985 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0386.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0387.svg b/apps/frontend/public/immunity/threat-icons/threat-0387.svg new file mode 100644 index 0000000..bc1ab96 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0387.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0388.svg b/apps/frontend/public/immunity/threat-icons/threat-0388.svg new file mode 100644 index 0000000..5a6cebe --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0388.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0389.svg b/apps/frontend/public/immunity/threat-icons/threat-0389.svg new file mode 100644 index 0000000..bbc82e8 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0389.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0390.svg b/apps/frontend/public/immunity/threat-icons/threat-0390.svg new file mode 100644 index 0000000..8169c8f --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0390.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0391.svg b/apps/frontend/public/immunity/threat-icons/threat-0391.svg new file mode 100644 index 0000000..ba89c5d --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0391.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0392.svg b/apps/frontend/public/immunity/threat-icons/threat-0392.svg new file mode 100644 index 0000000..d2d5470 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0392.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0393.svg b/apps/frontend/public/immunity/threat-icons/threat-0393.svg new file mode 100644 index 0000000..c832fab --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0393.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0394.svg b/apps/frontend/public/immunity/threat-icons/threat-0394.svg new file mode 100644 index 0000000..35dd95e --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0394.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0395.svg b/apps/frontend/public/immunity/threat-icons/threat-0395.svg new file mode 100644 index 0000000..637d247 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0395.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0396.svg b/apps/frontend/public/immunity/threat-icons/threat-0396.svg new file mode 100644 index 0000000..57bed56 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0396.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0397.svg b/apps/frontend/public/immunity/threat-icons/threat-0397.svg new file mode 100644 index 0000000..4bf607b --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0397.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0398.svg b/apps/frontend/public/immunity/threat-icons/threat-0398.svg new file mode 100644 index 0000000..473b507 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0398.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0399.svg b/apps/frontend/public/immunity/threat-icons/threat-0399.svg new file mode 100644 index 0000000..b7fb6f5 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0399.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0400.svg b/apps/frontend/public/immunity/threat-icons/threat-0400.svg new file mode 100644 index 0000000..db412b9 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0400.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0401.svg b/apps/frontend/public/immunity/threat-icons/threat-0401.svg new file mode 100644 index 0000000..38dafd1 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0401.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0402.svg b/apps/frontend/public/immunity/threat-icons/threat-0402.svg new file mode 100644 index 0000000..763d391 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0402.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0403.svg b/apps/frontend/public/immunity/threat-icons/threat-0403.svg new file mode 100644 index 0000000..0c85617 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0403.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0404.svg b/apps/frontend/public/immunity/threat-icons/threat-0404.svg new file mode 100644 index 0000000..c7b691f --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0404.svg @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0405.svg b/apps/frontend/public/immunity/threat-icons/threat-0405.svg new file mode 100644 index 0000000..e567e3b --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0405.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0406.svg b/apps/frontend/public/immunity/threat-icons/threat-0406.svg new file mode 100644 index 0000000..2c97d4a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0406.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0407.svg b/apps/frontend/public/immunity/threat-icons/threat-0407.svg new file mode 100644 index 0000000..c4b7179 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0407.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0408.svg b/apps/frontend/public/immunity/threat-icons/threat-0408.svg new file mode 100644 index 0000000..c8c8512 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0408.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0409.svg b/apps/frontend/public/immunity/threat-icons/threat-0409.svg new file mode 100644 index 0000000..a289bb6 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0409.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0410.svg b/apps/frontend/public/immunity/threat-icons/threat-0410.svg new file mode 100644 index 0000000..04097a0 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0410.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0411.svg b/apps/frontend/public/immunity/threat-icons/threat-0411.svg new file mode 100644 index 0000000..b128f4a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0411.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0412.svg b/apps/frontend/public/immunity/threat-icons/threat-0412.svg new file mode 100644 index 0000000..348470e --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0412.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0413.svg b/apps/frontend/public/immunity/threat-icons/threat-0413.svg new file mode 100644 index 0000000..b129f81 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0413.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0414.svg b/apps/frontend/public/immunity/threat-icons/threat-0414.svg new file mode 100644 index 0000000..e3d9173 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0414.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0415.svg b/apps/frontend/public/immunity/threat-icons/threat-0415.svg new file mode 100644 index 0000000..3e5401d --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0415.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0416.svg b/apps/frontend/public/immunity/threat-icons/threat-0416.svg new file mode 100644 index 0000000..66a9146 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0416.svg @@ -0,0 +1,18 @@ + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0417.svg b/apps/frontend/public/immunity/threat-icons/threat-0417.svg new file mode 100644 index 0000000..6d48818 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0417.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0418.svg b/apps/frontend/public/immunity/threat-icons/threat-0418.svg new file mode 100644 index 0000000..f22344c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0418.svg @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0419.svg b/apps/frontend/public/immunity/threat-icons/threat-0419.svg new file mode 100644 index 0000000..a5cfca6 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0419.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0420.svg b/apps/frontend/public/immunity/threat-icons/threat-0420.svg new file mode 100644 index 0000000..30b3961 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0420.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0421.svg b/apps/frontend/public/immunity/threat-icons/threat-0421.svg new file mode 100644 index 0000000..603b894 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0421.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0422.svg b/apps/frontend/public/immunity/threat-icons/threat-0422.svg new file mode 100644 index 0000000..6f94554 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0422.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0423.svg b/apps/frontend/public/immunity/threat-icons/threat-0423.svg new file mode 100644 index 0000000..551befe --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0423.svg @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0424.svg b/apps/frontend/public/immunity/threat-icons/threat-0424.svg new file mode 100644 index 0000000..98a3fa8 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0424.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0425.svg b/apps/frontend/public/immunity/threat-icons/threat-0425.svg new file mode 100644 index 0000000..328672d --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0425.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0426.svg b/apps/frontend/public/immunity/threat-icons/threat-0426.svg new file mode 100644 index 0000000..24b5b52 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0426.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0427.svg b/apps/frontend/public/immunity/threat-icons/threat-0427.svg new file mode 100644 index 0000000..8ceeddf --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0427.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0428.svg b/apps/frontend/public/immunity/threat-icons/threat-0428.svg new file mode 100644 index 0000000..4bb7526 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0428.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0429.svg b/apps/frontend/public/immunity/threat-icons/threat-0429.svg new file mode 100644 index 0000000..733e586 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0429.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0430.svg b/apps/frontend/public/immunity/threat-icons/threat-0430.svg new file mode 100644 index 0000000..7240a62 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0430.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0431.svg b/apps/frontend/public/immunity/threat-icons/threat-0431.svg new file mode 100644 index 0000000..fa37e8e --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0431.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0432.svg b/apps/frontend/public/immunity/threat-icons/threat-0432.svg new file mode 100644 index 0000000..4f79ca0 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0432.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0433.svg b/apps/frontend/public/immunity/threat-icons/threat-0433.svg new file mode 100644 index 0000000..76ab6b0 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0433.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0434.svg b/apps/frontend/public/immunity/threat-icons/threat-0434.svg new file mode 100644 index 0000000..343c751 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0434.svg @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0435.svg b/apps/frontend/public/immunity/threat-icons/threat-0435.svg new file mode 100644 index 0000000..132ed8c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0435.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0436.svg b/apps/frontend/public/immunity/threat-icons/threat-0436.svg new file mode 100644 index 0000000..ccb3939 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0436.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0437.svg b/apps/frontend/public/immunity/threat-icons/threat-0437.svg new file mode 100644 index 0000000..d1854e9 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0437.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0438.svg b/apps/frontend/public/immunity/threat-icons/threat-0438.svg new file mode 100644 index 0000000..9f190df --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0438.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0439.svg b/apps/frontend/public/immunity/threat-icons/threat-0439.svg new file mode 100644 index 0000000..a744f1d --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0439.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0440.svg b/apps/frontend/public/immunity/threat-icons/threat-0440.svg new file mode 100644 index 0000000..1868b72 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0440.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0441.svg b/apps/frontend/public/immunity/threat-icons/threat-0441.svg new file mode 100644 index 0000000..c03e8aa --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0441.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0442.svg b/apps/frontend/public/immunity/threat-icons/threat-0442.svg new file mode 100644 index 0000000..671b0f3 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0442.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0443.svg b/apps/frontend/public/immunity/threat-icons/threat-0443.svg new file mode 100644 index 0000000..cfda944 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0443.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0444.svg b/apps/frontend/public/immunity/threat-icons/threat-0444.svg new file mode 100644 index 0000000..c6c82e0 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0444.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0445.svg b/apps/frontend/public/immunity/threat-icons/threat-0445.svg new file mode 100644 index 0000000..1baa61b --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0445.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0446.svg b/apps/frontend/public/immunity/threat-icons/threat-0446.svg new file mode 100644 index 0000000..445b28e --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0446.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0447.svg b/apps/frontend/public/immunity/threat-icons/threat-0447.svg new file mode 100644 index 0000000..08d3384 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0447.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0448.svg b/apps/frontend/public/immunity/threat-icons/threat-0448.svg new file mode 100644 index 0000000..73df984 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0448.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0449.svg b/apps/frontend/public/immunity/threat-icons/threat-0449.svg new file mode 100644 index 0000000..ce692ed --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0449.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0450.svg b/apps/frontend/public/immunity/threat-icons/threat-0450.svg new file mode 100644 index 0000000..5b62c29 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0450.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0451.svg b/apps/frontend/public/immunity/threat-icons/threat-0451.svg new file mode 100644 index 0000000..d1457ec --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0451.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0452.svg b/apps/frontend/public/immunity/threat-icons/threat-0452.svg new file mode 100644 index 0000000..93e0664 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0452.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0453.svg b/apps/frontend/public/immunity/threat-icons/threat-0453.svg new file mode 100644 index 0000000..3793d8d --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0453.svg @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0454.svg b/apps/frontend/public/immunity/threat-icons/threat-0454.svg new file mode 100644 index 0000000..ff6c3d4 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0454.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0455.svg b/apps/frontend/public/immunity/threat-icons/threat-0455.svg new file mode 100644 index 0000000..833168e --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0455.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0456.svg b/apps/frontend/public/immunity/threat-icons/threat-0456.svg new file mode 100644 index 0000000..82ba207 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0456.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0457.svg b/apps/frontend/public/immunity/threat-icons/threat-0457.svg new file mode 100644 index 0000000..424e5ca --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0457.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0458.svg b/apps/frontend/public/immunity/threat-icons/threat-0458.svg new file mode 100644 index 0000000..46b5c9e --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0458.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0459.svg b/apps/frontend/public/immunity/threat-icons/threat-0459.svg new file mode 100644 index 0000000..64c2d65 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0459.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0460.svg b/apps/frontend/public/immunity/threat-icons/threat-0460.svg new file mode 100644 index 0000000..d9ba2e5 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0460.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0461.svg b/apps/frontend/public/immunity/threat-icons/threat-0461.svg new file mode 100644 index 0000000..766ca92 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0461.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0462.svg b/apps/frontend/public/immunity/threat-icons/threat-0462.svg new file mode 100644 index 0000000..936ed08 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0462.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0463.svg b/apps/frontend/public/immunity/threat-icons/threat-0463.svg new file mode 100644 index 0000000..938b677 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0463.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0464.svg b/apps/frontend/public/immunity/threat-icons/threat-0464.svg new file mode 100644 index 0000000..aca1ddc --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0464.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0465.svg b/apps/frontend/public/immunity/threat-icons/threat-0465.svg new file mode 100644 index 0000000..be32390 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0465.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0466.svg b/apps/frontend/public/immunity/threat-icons/threat-0466.svg new file mode 100644 index 0000000..8508bd6 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0466.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0467.svg b/apps/frontend/public/immunity/threat-icons/threat-0467.svg new file mode 100644 index 0000000..45f1c40 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0467.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0468.svg b/apps/frontend/public/immunity/threat-icons/threat-0468.svg new file mode 100644 index 0000000..d60546b --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0468.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0469.svg b/apps/frontend/public/immunity/threat-icons/threat-0469.svg new file mode 100644 index 0000000..dcae4b5 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0469.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0470.svg b/apps/frontend/public/immunity/threat-icons/threat-0470.svg new file mode 100644 index 0000000..cf9b870 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0470.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0471.svg b/apps/frontend/public/immunity/threat-icons/threat-0471.svg new file mode 100644 index 0000000..cf66981 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0471.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0472.svg b/apps/frontend/public/immunity/threat-icons/threat-0472.svg new file mode 100644 index 0000000..8c9b864 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0472.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0473.svg b/apps/frontend/public/immunity/threat-icons/threat-0473.svg new file mode 100644 index 0000000..27b0025 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0473.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0474.svg b/apps/frontend/public/immunity/threat-icons/threat-0474.svg new file mode 100644 index 0000000..b432b88 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0474.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0475.svg b/apps/frontend/public/immunity/threat-icons/threat-0475.svg new file mode 100644 index 0000000..955be7d --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0475.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0476.svg b/apps/frontend/public/immunity/threat-icons/threat-0476.svg new file mode 100644 index 0000000..5f535a3 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0476.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0477.svg b/apps/frontend/public/immunity/threat-icons/threat-0477.svg new file mode 100644 index 0000000..7e16f53 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0477.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0478.svg b/apps/frontend/public/immunity/threat-icons/threat-0478.svg new file mode 100644 index 0000000..d997b3b --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0478.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0479.svg b/apps/frontend/public/immunity/threat-icons/threat-0479.svg new file mode 100644 index 0000000..f61d180 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0479.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0480.svg b/apps/frontend/public/immunity/threat-icons/threat-0480.svg new file mode 100644 index 0000000..11b55f3 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0480.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0481.svg b/apps/frontend/public/immunity/threat-icons/threat-0481.svg new file mode 100644 index 0000000..2dee012 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0481.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0482.svg b/apps/frontend/public/immunity/threat-icons/threat-0482.svg new file mode 100644 index 0000000..7f227b7 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0482.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0483.svg b/apps/frontend/public/immunity/threat-icons/threat-0483.svg new file mode 100644 index 0000000..36e90a4 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0483.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0484.svg b/apps/frontend/public/immunity/threat-icons/threat-0484.svg new file mode 100644 index 0000000..8905f59 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0484.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0485.svg b/apps/frontend/public/immunity/threat-icons/threat-0485.svg new file mode 100644 index 0000000..156348a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0485.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0486.svg b/apps/frontend/public/immunity/threat-icons/threat-0486.svg new file mode 100644 index 0000000..be014d0 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0486.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0487.svg b/apps/frontend/public/immunity/threat-icons/threat-0487.svg new file mode 100644 index 0000000..18619ea --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0487.svg @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0488.svg b/apps/frontend/public/immunity/threat-icons/threat-0488.svg new file mode 100644 index 0000000..4a91dce --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0488.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0489.svg b/apps/frontend/public/immunity/threat-icons/threat-0489.svg new file mode 100644 index 0000000..ba9ca33 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0489.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0490.svg b/apps/frontend/public/immunity/threat-icons/threat-0490.svg new file mode 100644 index 0000000..b8d2cff --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0490.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0491.svg b/apps/frontend/public/immunity/threat-icons/threat-0491.svg new file mode 100644 index 0000000..4fe801e --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0491.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0492.svg b/apps/frontend/public/immunity/threat-icons/threat-0492.svg new file mode 100644 index 0000000..4a61cfa --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0492.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0493.svg b/apps/frontend/public/immunity/threat-icons/threat-0493.svg new file mode 100644 index 0000000..2298243 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0493.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0494.svg b/apps/frontend/public/immunity/threat-icons/threat-0494.svg new file mode 100644 index 0000000..2aa3c68 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0494.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0495.svg b/apps/frontend/public/immunity/threat-icons/threat-0495.svg new file mode 100644 index 0000000..5549f28 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0495.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0496.svg b/apps/frontend/public/immunity/threat-icons/threat-0496.svg new file mode 100644 index 0000000..bfb320f --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0496.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0497.svg b/apps/frontend/public/immunity/threat-icons/threat-0497.svg new file mode 100644 index 0000000..96759ae --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0497.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0498.svg b/apps/frontend/public/immunity/threat-icons/threat-0498.svg new file mode 100644 index 0000000..98495b1 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0498.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0499.svg b/apps/frontend/public/immunity/threat-icons/threat-0499.svg new file mode 100644 index 0000000..b72c249 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0499.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0500.svg b/apps/frontend/public/immunity/threat-icons/threat-0500.svg new file mode 100644 index 0000000..6326857 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0500.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0501.svg b/apps/frontend/public/immunity/threat-icons/threat-0501.svg new file mode 100644 index 0000000..2d2c9ae --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0501.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0502.svg b/apps/frontend/public/immunity/threat-icons/threat-0502.svg new file mode 100644 index 0000000..4461ca9 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0502.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0503.svg b/apps/frontend/public/immunity/threat-icons/threat-0503.svg new file mode 100644 index 0000000..6a39e18 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0503.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0504.svg b/apps/frontend/public/immunity/threat-icons/threat-0504.svg new file mode 100644 index 0000000..c080cd2 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0504.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0505.svg b/apps/frontend/public/immunity/threat-icons/threat-0505.svg new file mode 100644 index 0000000..faa2061 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0505.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0506.svg b/apps/frontend/public/immunity/threat-icons/threat-0506.svg new file mode 100644 index 0000000..6dac2bf --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0506.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0507.svg b/apps/frontend/public/immunity/threat-icons/threat-0507.svg new file mode 100644 index 0000000..4c884ca --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0507.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0508.svg b/apps/frontend/public/immunity/threat-icons/threat-0508.svg new file mode 100644 index 0000000..a2ca4c2 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0508.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0509.svg b/apps/frontend/public/immunity/threat-icons/threat-0509.svg new file mode 100644 index 0000000..485ed20 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0509.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0510.svg b/apps/frontend/public/immunity/threat-icons/threat-0510.svg new file mode 100644 index 0000000..bf0a400 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0510.svg @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0511.svg b/apps/frontend/public/immunity/threat-icons/threat-0511.svg new file mode 100644 index 0000000..015da34 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0511.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0512.svg b/apps/frontend/public/immunity/threat-icons/threat-0512.svg new file mode 100644 index 0000000..95ff56d --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0512.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0513.svg b/apps/frontend/public/immunity/threat-icons/threat-0513.svg new file mode 100644 index 0000000..17d6cfc --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0513.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0514.svg b/apps/frontend/public/immunity/threat-icons/threat-0514.svg new file mode 100644 index 0000000..edd4e0f --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0514.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0515.svg b/apps/frontend/public/immunity/threat-icons/threat-0515.svg new file mode 100644 index 0000000..9af8f3e --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0515.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0516.svg b/apps/frontend/public/immunity/threat-icons/threat-0516.svg new file mode 100644 index 0000000..ff8b6e8 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0516.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0517.svg b/apps/frontend/public/immunity/threat-icons/threat-0517.svg new file mode 100644 index 0000000..87754c5 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0517.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0518.svg b/apps/frontend/public/immunity/threat-icons/threat-0518.svg new file mode 100644 index 0000000..93895f0 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0518.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0519.svg b/apps/frontend/public/immunity/threat-icons/threat-0519.svg new file mode 100644 index 0000000..ac2c78c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0519.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0520.svg b/apps/frontend/public/immunity/threat-icons/threat-0520.svg new file mode 100644 index 0000000..cf5024a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0520.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0521.svg b/apps/frontend/public/immunity/threat-icons/threat-0521.svg new file mode 100644 index 0000000..e71859f --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0521.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0522.svg b/apps/frontend/public/immunity/threat-icons/threat-0522.svg new file mode 100644 index 0000000..56d1b09 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0522.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0523.svg b/apps/frontend/public/immunity/threat-icons/threat-0523.svg new file mode 100644 index 0000000..ef22748 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0523.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0524.svg b/apps/frontend/public/immunity/threat-icons/threat-0524.svg new file mode 100644 index 0000000..8f3c4bc --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0524.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0525.svg b/apps/frontend/public/immunity/threat-icons/threat-0525.svg new file mode 100644 index 0000000..f244eab --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0525.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0526.svg b/apps/frontend/public/immunity/threat-icons/threat-0526.svg new file mode 100644 index 0000000..3946407 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0526.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0527.svg b/apps/frontend/public/immunity/threat-icons/threat-0527.svg new file mode 100644 index 0000000..522f216 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0527.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0528.svg b/apps/frontend/public/immunity/threat-icons/threat-0528.svg new file mode 100644 index 0000000..d6422e1 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0528.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0529.svg b/apps/frontend/public/immunity/threat-icons/threat-0529.svg new file mode 100644 index 0000000..aaedd07 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0529.svg @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0530.svg b/apps/frontend/public/immunity/threat-icons/threat-0530.svg new file mode 100644 index 0000000..b9048ec --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0530.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0531.svg b/apps/frontend/public/immunity/threat-icons/threat-0531.svg new file mode 100644 index 0000000..5d5619d --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0531.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0532.svg b/apps/frontend/public/immunity/threat-icons/threat-0532.svg new file mode 100644 index 0000000..5469bf4 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0532.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0533.svg b/apps/frontend/public/immunity/threat-icons/threat-0533.svg new file mode 100644 index 0000000..1a9bde4 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0533.svg @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0534.svg b/apps/frontend/public/immunity/threat-icons/threat-0534.svg new file mode 100644 index 0000000..d935b9d --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0534.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0535.svg b/apps/frontend/public/immunity/threat-icons/threat-0535.svg new file mode 100644 index 0000000..7641def --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0535.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0536.svg b/apps/frontend/public/immunity/threat-icons/threat-0536.svg new file mode 100644 index 0000000..17dfbc1 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0536.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0537.svg b/apps/frontend/public/immunity/threat-icons/threat-0537.svg new file mode 100644 index 0000000..b921ff8 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0537.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0538.svg b/apps/frontend/public/immunity/threat-icons/threat-0538.svg new file mode 100644 index 0000000..e9a32e2 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0538.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0539.svg b/apps/frontend/public/immunity/threat-icons/threat-0539.svg new file mode 100644 index 0000000..5621cb8 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0539.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0540.svg b/apps/frontend/public/immunity/threat-icons/threat-0540.svg new file mode 100644 index 0000000..76b474e --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0540.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0541.svg b/apps/frontend/public/immunity/threat-icons/threat-0541.svg new file mode 100644 index 0000000..70968b8 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0541.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0542.svg b/apps/frontend/public/immunity/threat-icons/threat-0542.svg new file mode 100644 index 0000000..b455a51 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0542.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0543.svg b/apps/frontend/public/immunity/threat-icons/threat-0543.svg new file mode 100644 index 0000000..478906d --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0543.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0544.svg b/apps/frontend/public/immunity/threat-icons/threat-0544.svg new file mode 100644 index 0000000..a5e8726 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0544.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0545.svg b/apps/frontend/public/immunity/threat-icons/threat-0545.svg new file mode 100644 index 0000000..d1218a8 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0545.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0546.svg b/apps/frontend/public/immunity/threat-icons/threat-0546.svg new file mode 100644 index 0000000..dffaded --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0546.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0547.svg b/apps/frontend/public/immunity/threat-icons/threat-0547.svg new file mode 100644 index 0000000..f49b31a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0547.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0548.svg b/apps/frontend/public/immunity/threat-icons/threat-0548.svg new file mode 100644 index 0000000..2019e49 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0548.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0549.svg b/apps/frontend/public/immunity/threat-icons/threat-0549.svg new file mode 100644 index 0000000..d9cdf6d --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0549.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0550.svg b/apps/frontend/public/immunity/threat-icons/threat-0550.svg new file mode 100644 index 0000000..5adf0bd --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0550.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0551.svg b/apps/frontend/public/immunity/threat-icons/threat-0551.svg new file mode 100644 index 0000000..14d3623 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0551.svg @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0552.svg b/apps/frontend/public/immunity/threat-icons/threat-0552.svg new file mode 100644 index 0000000..f5f391a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0552.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0553.svg b/apps/frontend/public/immunity/threat-icons/threat-0553.svg new file mode 100644 index 0000000..6890dd3 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0553.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0554.svg b/apps/frontend/public/immunity/threat-icons/threat-0554.svg new file mode 100644 index 0000000..956190e --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0554.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0555.svg b/apps/frontend/public/immunity/threat-icons/threat-0555.svg new file mode 100644 index 0000000..137729e --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0555.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0556.svg b/apps/frontend/public/immunity/threat-icons/threat-0556.svg new file mode 100644 index 0000000..e43260e --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0556.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0557.svg b/apps/frontend/public/immunity/threat-icons/threat-0557.svg new file mode 100644 index 0000000..d32d91a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0557.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0558.svg b/apps/frontend/public/immunity/threat-icons/threat-0558.svg new file mode 100644 index 0000000..a3338fc --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0558.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0559.svg b/apps/frontend/public/immunity/threat-icons/threat-0559.svg new file mode 100644 index 0000000..4ef74c0 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0559.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0560.svg b/apps/frontend/public/immunity/threat-icons/threat-0560.svg new file mode 100644 index 0000000..46e976b --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0560.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0561.svg b/apps/frontend/public/immunity/threat-icons/threat-0561.svg new file mode 100644 index 0000000..2463d27 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0561.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0562.svg b/apps/frontend/public/immunity/threat-icons/threat-0562.svg new file mode 100644 index 0000000..af698a9 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0562.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0563.svg b/apps/frontend/public/immunity/threat-icons/threat-0563.svg new file mode 100644 index 0000000..87db614 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0563.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0564.svg b/apps/frontend/public/immunity/threat-icons/threat-0564.svg new file mode 100644 index 0000000..7daa4fb --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0564.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0565.svg b/apps/frontend/public/immunity/threat-icons/threat-0565.svg new file mode 100644 index 0000000..83df98b --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0565.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0566.svg b/apps/frontend/public/immunity/threat-icons/threat-0566.svg new file mode 100644 index 0000000..8e0c2fd --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0566.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0567.svg b/apps/frontend/public/immunity/threat-icons/threat-0567.svg new file mode 100644 index 0000000..c85a659 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0567.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0568.svg b/apps/frontend/public/immunity/threat-icons/threat-0568.svg new file mode 100644 index 0000000..0feb835 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0568.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0569.svg b/apps/frontend/public/immunity/threat-icons/threat-0569.svg new file mode 100644 index 0000000..d829427 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0569.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0570.svg b/apps/frontend/public/immunity/threat-icons/threat-0570.svg new file mode 100644 index 0000000..f1ad7f3 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0570.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0571.svg b/apps/frontend/public/immunity/threat-icons/threat-0571.svg new file mode 100644 index 0000000..cd76c33 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0571.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0572.svg b/apps/frontend/public/immunity/threat-icons/threat-0572.svg new file mode 100644 index 0000000..c970066 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0572.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0573.svg b/apps/frontend/public/immunity/threat-icons/threat-0573.svg new file mode 100644 index 0000000..c5c7baf --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0573.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0574.svg b/apps/frontend/public/immunity/threat-icons/threat-0574.svg new file mode 100644 index 0000000..0fd9dbb --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0574.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0575.svg b/apps/frontend/public/immunity/threat-icons/threat-0575.svg new file mode 100644 index 0000000..7551d01 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0575.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0576.svg b/apps/frontend/public/immunity/threat-icons/threat-0576.svg new file mode 100644 index 0000000..3acdbaa --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0576.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0577.svg b/apps/frontend/public/immunity/threat-icons/threat-0577.svg new file mode 100644 index 0000000..79d4650 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0577.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0578.svg b/apps/frontend/public/immunity/threat-icons/threat-0578.svg new file mode 100644 index 0000000..fcc1d56 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0578.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0579.svg b/apps/frontend/public/immunity/threat-icons/threat-0579.svg new file mode 100644 index 0000000..b2a3223 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0579.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0580.svg b/apps/frontend/public/immunity/threat-icons/threat-0580.svg new file mode 100644 index 0000000..80ada2a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0580.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0581.svg b/apps/frontend/public/immunity/threat-icons/threat-0581.svg new file mode 100644 index 0000000..d76177a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0581.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0582.svg b/apps/frontend/public/immunity/threat-icons/threat-0582.svg new file mode 100644 index 0000000..fdd6a81 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0582.svg @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0583.svg b/apps/frontend/public/immunity/threat-icons/threat-0583.svg new file mode 100644 index 0000000..3f38445 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0583.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0584.svg b/apps/frontend/public/immunity/threat-icons/threat-0584.svg new file mode 100644 index 0000000..5de2f31 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0584.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0585.svg b/apps/frontend/public/immunity/threat-icons/threat-0585.svg new file mode 100644 index 0000000..85b6baf --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0585.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0586.svg b/apps/frontend/public/immunity/threat-icons/threat-0586.svg new file mode 100644 index 0000000..e6b5408 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0586.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0587.svg b/apps/frontend/public/immunity/threat-icons/threat-0587.svg new file mode 100644 index 0000000..04f8b44 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0587.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0588.svg b/apps/frontend/public/immunity/threat-icons/threat-0588.svg new file mode 100644 index 0000000..438b6cb --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0588.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0589.svg b/apps/frontend/public/immunity/threat-icons/threat-0589.svg new file mode 100644 index 0000000..d3c5d7e --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0589.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0590.svg b/apps/frontend/public/immunity/threat-icons/threat-0590.svg new file mode 100644 index 0000000..9e83a54 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0590.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0591.svg b/apps/frontend/public/immunity/threat-icons/threat-0591.svg new file mode 100644 index 0000000..913f58f --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0591.svg @@ -0,0 +1,20 @@ + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0592.svg b/apps/frontend/public/immunity/threat-icons/threat-0592.svg new file mode 100644 index 0000000..1b435c3 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0592.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0593.svg b/apps/frontend/public/immunity/threat-icons/threat-0593.svg new file mode 100644 index 0000000..f5a463b --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0593.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0594.svg b/apps/frontend/public/immunity/threat-icons/threat-0594.svg new file mode 100644 index 0000000..4e9ac88 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0594.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0595.svg b/apps/frontend/public/immunity/threat-icons/threat-0595.svg new file mode 100644 index 0000000..aba2036 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0595.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0596.svg b/apps/frontend/public/immunity/threat-icons/threat-0596.svg new file mode 100644 index 0000000..3e4d50a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0596.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0597.svg b/apps/frontend/public/immunity/threat-icons/threat-0597.svg new file mode 100644 index 0000000..837b859 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0597.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0598.svg b/apps/frontend/public/immunity/threat-icons/threat-0598.svg new file mode 100644 index 0000000..e1c5b92 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0598.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0599.svg b/apps/frontend/public/immunity/threat-icons/threat-0599.svg new file mode 100644 index 0000000..0773c7c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0599.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0600.svg b/apps/frontend/public/immunity/threat-icons/threat-0600.svg new file mode 100644 index 0000000..7341fae --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0600.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0601.svg b/apps/frontend/public/immunity/threat-icons/threat-0601.svg new file mode 100644 index 0000000..4147679 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0601.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0602.svg b/apps/frontend/public/immunity/threat-icons/threat-0602.svg new file mode 100644 index 0000000..3be3dea --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0602.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0603.svg b/apps/frontend/public/immunity/threat-icons/threat-0603.svg new file mode 100644 index 0000000..60846f6 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0603.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0604.svg b/apps/frontend/public/immunity/threat-icons/threat-0604.svg new file mode 100644 index 0000000..c69f46b --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0604.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0605.svg b/apps/frontend/public/immunity/threat-icons/threat-0605.svg new file mode 100644 index 0000000..71c4426 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0605.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0606.svg b/apps/frontend/public/immunity/threat-icons/threat-0606.svg new file mode 100644 index 0000000..6942726 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0606.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0607.svg b/apps/frontend/public/immunity/threat-icons/threat-0607.svg new file mode 100644 index 0000000..1f8e0ca --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0607.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0608.svg b/apps/frontend/public/immunity/threat-icons/threat-0608.svg new file mode 100644 index 0000000..a1d637c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0608.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0609.svg b/apps/frontend/public/immunity/threat-icons/threat-0609.svg new file mode 100644 index 0000000..d2bf599 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0609.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0610.svg b/apps/frontend/public/immunity/threat-icons/threat-0610.svg new file mode 100644 index 0000000..c0f5f6e --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0610.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0611.svg b/apps/frontend/public/immunity/threat-icons/threat-0611.svg new file mode 100644 index 0000000..8dbdc88 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0611.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0612.svg b/apps/frontend/public/immunity/threat-icons/threat-0612.svg new file mode 100644 index 0000000..dbee283 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0612.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0613.svg b/apps/frontend/public/immunity/threat-icons/threat-0613.svg new file mode 100644 index 0000000..4cd0821 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0613.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0614.svg b/apps/frontend/public/immunity/threat-icons/threat-0614.svg new file mode 100644 index 0000000..fbc39d7 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0614.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0615.svg b/apps/frontend/public/immunity/threat-icons/threat-0615.svg new file mode 100644 index 0000000..ff63e75 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0615.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0616.svg b/apps/frontend/public/immunity/threat-icons/threat-0616.svg new file mode 100644 index 0000000..86cbe4c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0616.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0617.svg b/apps/frontend/public/immunity/threat-icons/threat-0617.svg new file mode 100644 index 0000000..a683ac9 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0617.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0618.svg b/apps/frontend/public/immunity/threat-icons/threat-0618.svg new file mode 100644 index 0000000..d0b384e --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0618.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0619.svg b/apps/frontend/public/immunity/threat-icons/threat-0619.svg new file mode 100644 index 0000000..d62d9ce --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0619.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0620.svg b/apps/frontend/public/immunity/threat-icons/threat-0620.svg new file mode 100644 index 0000000..650cbb0 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0620.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0621.svg b/apps/frontend/public/immunity/threat-icons/threat-0621.svg new file mode 100644 index 0000000..59c6c55 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0621.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0622.svg b/apps/frontend/public/immunity/threat-icons/threat-0622.svg new file mode 100644 index 0000000..b1aadd4 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0622.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0623.svg b/apps/frontend/public/immunity/threat-icons/threat-0623.svg new file mode 100644 index 0000000..4514223 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0623.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0624.svg b/apps/frontend/public/immunity/threat-icons/threat-0624.svg new file mode 100644 index 0000000..a3f2036 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0624.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0625.svg b/apps/frontend/public/immunity/threat-icons/threat-0625.svg new file mode 100644 index 0000000..67c93b0 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0625.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0626.svg b/apps/frontend/public/immunity/threat-icons/threat-0626.svg new file mode 100644 index 0000000..c0a7552 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0626.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0627.svg b/apps/frontend/public/immunity/threat-icons/threat-0627.svg new file mode 100644 index 0000000..ec61130 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0627.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0628.svg b/apps/frontend/public/immunity/threat-icons/threat-0628.svg new file mode 100644 index 0000000..0cfad98 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0628.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0629.svg b/apps/frontend/public/immunity/threat-icons/threat-0629.svg new file mode 100644 index 0000000..0a57081 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0629.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0630.svg b/apps/frontend/public/immunity/threat-icons/threat-0630.svg new file mode 100644 index 0000000..3a5199c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0630.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0631.svg b/apps/frontend/public/immunity/threat-icons/threat-0631.svg new file mode 100644 index 0000000..a773b25 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0631.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0632.svg b/apps/frontend/public/immunity/threat-icons/threat-0632.svg new file mode 100644 index 0000000..a35879b --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0632.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0633.svg b/apps/frontend/public/immunity/threat-icons/threat-0633.svg new file mode 100644 index 0000000..f3b433a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0633.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0634.svg b/apps/frontend/public/immunity/threat-icons/threat-0634.svg new file mode 100644 index 0000000..d33ee02 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0634.svg @@ -0,0 +1,19 @@ + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0635.svg b/apps/frontend/public/immunity/threat-icons/threat-0635.svg new file mode 100644 index 0000000..c3fae2a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0635.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0636.svg b/apps/frontend/public/immunity/threat-icons/threat-0636.svg new file mode 100644 index 0000000..fd2c64f --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0636.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0637.svg b/apps/frontend/public/immunity/threat-icons/threat-0637.svg new file mode 100644 index 0000000..7488003 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0637.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0638.svg b/apps/frontend/public/immunity/threat-icons/threat-0638.svg new file mode 100644 index 0000000..a80246a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0638.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0639.svg b/apps/frontend/public/immunity/threat-icons/threat-0639.svg new file mode 100644 index 0000000..f8680e9 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0639.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0640.svg b/apps/frontend/public/immunity/threat-icons/threat-0640.svg new file mode 100644 index 0000000..6d2af6f --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0640.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0641.svg b/apps/frontend/public/immunity/threat-icons/threat-0641.svg new file mode 100644 index 0000000..44d28f8 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0641.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0642.svg b/apps/frontend/public/immunity/threat-icons/threat-0642.svg new file mode 100644 index 0000000..e104e4b --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0642.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0643.svg b/apps/frontend/public/immunity/threat-icons/threat-0643.svg new file mode 100644 index 0000000..1a83a4a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0643.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0644.svg b/apps/frontend/public/immunity/threat-icons/threat-0644.svg new file mode 100644 index 0000000..435345b --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0644.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0645.svg b/apps/frontend/public/immunity/threat-icons/threat-0645.svg new file mode 100644 index 0000000..0874444 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0645.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0646.svg b/apps/frontend/public/immunity/threat-icons/threat-0646.svg new file mode 100644 index 0000000..9079d39 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0646.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0647.svg b/apps/frontend/public/immunity/threat-icons/threat-0647.svg new file mode 100644 index 0000000..4117b2a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0647.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0648.svg b/apps/frontend/public/immunity/threat-icons/threat-0648.svg new file mode 100644 index 0000000..81d200d --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0648.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0649.svg b/apps/frontend/public/immunity/threat-icons/threat-0649.svg new file mode 100644 index 0000000..108299c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0649.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0650.svg b/apps/frontend/public/immunity/threat-icons/threat-0650.svg new file mode 100644 index 0000000..32c394a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0650.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0651.svg b/apps/frontend/public/immunity/threat-icons/threat-0651.svg new file mode 100644 index 0000000..7f87ad8 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0651.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0652.svg b/apps/frontend/public/immunity/threat-icons/threat-0652.svg new file mode 100644 index 0000000..8d5fb4d --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0652.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0653.svg b/apps/frontend/public/immunity/threat-icons/threat-0653.svg new file mode 100644 index 0000000..397cdff --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0653.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0654.svg b/apps/frontend/public/immunity/threat-icons/threat-0654.svg new file mode 100644 index 0000000..799927d --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0654.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0655.svg b/apps/frontend/public/immunity/threat-icons/threat-0655.svg new file mode 100644 index 0000000..ce90ba6 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0655.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0656.svg b/apps/frontend/public/immunity/threat-icons/threat-0656.svg new file mode 100644 index 0000000..c05ae00 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0656.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0657.svg b/apps/frontend/public/immunity/threat-icons/threat-0657.svg new file mode 100644 index 0000000..183f024 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0657.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0658.svg b/apps/frontend/public/immunity/threat-icons/threat-0658.svg new file mode 100644 index 0000000..7b17d18 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0658.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0659.svg b/apps/frontend/public/immunity/threat-icons/threat-0659.svg new file mode 100644 index 0000000..8e3c948 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0659.svg @@ -0,0 +1,19 @@ + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0660.svg b/apps/frontend/public/immunity/threat-icons/threat-0660.svg new file mode 100644 index 0000000..feaad05 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0660.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0661.svg b/apps/frontend/public/immunity/threat-icons/threat-0661.svg new file mode 100644 index 0000000..4ede2a8 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0661.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0662.svg b/apps/frontend/public/immunity/threat-icons/threat-0662.svg new file mode 100644 index 0000000..1759f86 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0662.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0663.svg b/apps/frontend/public/immunity/threat-icons/threat-0663.svg new file mode 100644 index 0000000..65bfcb1 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0663.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0664.svg b/apps/frontend/public/immunity/threat-icons/threat-0664.svg new file mode 100644 index 0000000..08718bc --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0664.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0665.svg b/apps/frontend/public/immunity/threat-icons/threat-0665.svg new file mode 100644 index 0000000..ffb8c45 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0665.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0666.svg b/apps/frontend/public/immunity/threat-icons/threat-0666.svg new file mode 100644 index 0000000..0ffa083 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0666.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0667.svg b/apps/frontend/public/immunity/threat-icons/threat-0667.svg new file mode 100644 index 0000000..ad59c1c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0667.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0668.svg b/apps/frontend/public/immunity/threat-icons/threat-0668.svg new file mode 100644 index 0000000..8c7f3b8 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0668.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0669.svg b/apps/frontend/public/immunity/threat-icons/threat-0669.svg new file mode 100644 index 0000000..36b1b8b --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0669.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0670.svg b/apps/frontend/public/immunity/threat-icons/threat-0670.svg new file mode 100644 index 0000000..7995252 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0670.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0671.svg b/apps/frontend/public/immunity/threat-icons/threat-0671.svg new file mode 100644 index 0000000..7777dca --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0671.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0672.svg b/apps/frontend/public/immunity/threat-icons/threat-0672.svg new file mode 100644 index 0000000..773e9a6 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0672.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0673.svg b/apps/frontend/public/immunity/threat-icons/threat-0673.svg new file mode 100644 index 0000000..9470b75 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0673.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0674.svg b/apps/frontend/public/immunity/threat-icons/threat-0674.svg new file mode 100644 index 0000000..19addb7 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0674.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0675.svg b/apps/frontend/public/immunity/threat-icons/threat-0675.svg new file mode 100644 index 0000000..4f8a527 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0675.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0676.svg b/apps/frontend/public/immunity/threat-icons/threat-0676.svg new file mode 100644 index 0000000..9c884d2 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0676.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0677.svg b/apps/frontend/public/immunity/threat-icons/threat-0677.svg new file mode 100644 index 0000000..d8e380b --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0677.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0678.svg b/apps/frontend/public/immunity/threat-icons/threat-0678.svg new file mode 100644 index 0000000..4a8889f --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0678.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0679.svg b/apps/frontend/public/immunity/threat-icons/threat-0679.svg new file mode 100644 index 0000000..eca3a7d --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0679.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0680.svg b/apps/frontend/public/immunity/threat-icons/threat-0680.svg new file mode 100644 index 0000000..bb3f445 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0680.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0681.svg b/apps/frontend/public/immunity/threat-icons/threat-0681.svg new file mode 100644 index 0000000..44c5d97 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0681.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0682.svg b/apps/frontend/public/immunity/threat-icons/threat-0682.svg new file mode 100644 index 0000000..e866007 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0682.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0683.svg b/apps/frontend/public/immunity/threat-icons/threat-0683.svg new file mode 100644 index 0000000..213e12c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0683.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0684.svg b/apps/frontend/public/immunity/threat-icons/threat-0684.svg new file mode 100644 index 0000000..280336c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0684.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0685.svg b/apps/frontend/public/immunity/threat-icons/threat-0685.svg new file mode 100644 index 0000000..b1388db --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0685.svg @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0686.svg b/apps/frontend/public/immunity/threat-icons/threat-0686.svg new file mode 100644 index 0000000..053b36b --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0686.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0687.svg b/apps/frontend/public/immunity/threat-icons/threat-0687.svg new file mode 100644 index 0000000..1f36831 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0687.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0688.svg b/apps/frontend/public/immunity/threat-icons/threat-0688.svg new file mode 100644 index 0000000..d20e4c4 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0688.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0689.svg b/apps/frontend/public/immunity/threat-icons/threat-0689.svg new file mode 100644 index 0000000..5d0ccc5 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0689.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0690.svg b/apps/frontend/public/immunity/threat-icons/threat-0690.svg new file mode 100644 index 0000000..1dce037 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0690.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0691.svg b/apps/frontend/public/immunity/threat-icons/threat-0691.svg new file mode 100644 index 0000000..db5c21d --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0691.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0692.svg b/apps/frontend/public/immunity/threat-icons/threat-0692.svg new file mode 100644 index 0000000..2297bab --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0692.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0693.svg b/apps/frontend/public/immunity/threat-icons/threat-0693.svg new file mode 100644 index 0000000..43de46a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0693.svg @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0694.svg b/apps/frontend/public/immunity/threat-icons/threat-0694.svg new file mode 100644 index 0000000..146279c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0694.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0695.svg b/apps/frontend/public/immunity/threat-icons/threat-0695.svg new file mode 100644 index 0000000..2701f15 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0695.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0696.svg b/apps/frontend/public/immunity/threat-icons/threat-0696.svg new file mode 100644 index 0000000..03ab560 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0696.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0697.svg b/apps/frontend/public/immunity/threat-icons/threat-0697.svg new file mode 100644 index 0000000..d95a590 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0697.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0698.svg b/apps/frontend/public/immunity/threat-icons/threat-0698.svg new file mode 100644 index 0000000..be26d3f --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0698.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0699.svg b/apps/frontend/public/immunity/threat-icons/threat-0699.svg new file mode 100644 index 0000000..808602c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0699.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0700.svg b/apps/frontend/public/immunity/threat-icons/threat-0700.svg new file mode 100644 index 0000000..4fc7f09 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0700.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0701.svg b/apps/frontend/public/immunity/threat-icons/threat-0701.svg new file mode 100644 index 0000000..90e246f --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0701.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0702.svg b/apps/frontend/public/immunity/threat-icons/threat-0702.svg new file mode 100644 index 0000000..4a3fafd --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0702.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0703.svg b/apps/frontend/public/immunity/threat-icons/threat-0703.svg new file mode 100644 index 0000000..7f37d65 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0703.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0704.svg b/apps/frontend/public/immunity/threat-icons/threat-0704.svg new file mode 100644 index 0000000..38c2d29 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0704.svg @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0705.svg b/apps/frontend/public/immunity/threat-icons/threat-0705.svg new file mode 100644 index 0000000..240f35e --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0705.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0706.svg b/apps/frontend/public/immunity/threat-icons/threat-0706.svg new file mode 100644 index 0000000..694b44b --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0706.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0707.svg b/apps/frontend/public/immunity/threat-icons/threat-0707.svg new file mode 100644 index 0000000..6d578be --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0707.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0708.svg b/apps/frontend/public/immunity/threat-icons/threat-0708.svg new file mode 100644 index 0000000..2f44a00 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0708.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0709.svg b/apps/frontend/public/immunity/threat-icons/threat-0709.svg new file mode 100644 index 0000000..3615b28 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0709.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0710.svg b/apps/frontend/public/immunity/threat-icons/threat-0710.svg new file mode 100644 index 0000000..fdc3ecc --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0710.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0711.svg b/apps/frontend/public/immunity/threat-icons/threat-0711.svg new file mode 100644 index 0000000..7add348 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0711.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0712.svg b/apps/frontend/public/immunity/threat-icons/threat-0712.svg new file mode 100644 index 0000000..e66a8ff --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0712.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0713.svg b/apps/frontend/public/immunity/threat-icons/threat-0713.svg new file mode 100644 index 0000000..797dfe6 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0713.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0714.svg b/apps/frontend/public/immunity/threat-icons/threat-0714.svg new file mode 100644 index 0000000..c09e50c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0714.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0715.svg b/apps/frontend/public/immunity/threat-icons/threat-0715.svg new file mode 100644 index 0000000..81f7fd2 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0715.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0716.svg b/apps/frontend/public/immunity/threat-icons/threat-0716.svg new file mode 100644 index 0000000..edae2d2 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0716.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0717.svg b/apps/frontend/public/immunity/threat-icons/threat-0717.svg new file mode 100644 index 0000000..d127886 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0717.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0718.svg b/apps/frontend/public/immunity/threat-icons/threat-0718.svg new file mode 100644 index 0000000..23f1377 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0718.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0719.svg b/apps/frontend/public/immunity/threat-icons/threat-0719.svg new file mode 100644 index 0000000..71a01ce --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0719.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0720.svg b/apps/frontend/public/immunity/threat-icons/threat-0720.svg new file mode 100644 index 0000000..d0d781c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0720.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0721.svg b/apps/frontend/public/immunity/threat-icons/threat-0721.svg new file mode 100644 index 0000000..0cfbef4 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0721.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0722.svg b/apps/frontend/public/immunity/threat-icons/threat-0722.svg new file mode 100644 index 0000000..a8dedb3 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0722.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0723.svg b/apps/frontend/public/immunity/threat-icons/threat-0723.svg new file mode 100644 index 0000000..cfe4361 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0723.svg @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0724.svg b/apps/frontend/public/immunity/threat-icons/threat-0724.svg new file mode 100644 index 0000000..806235a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0724.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0725.svg b/apps/frontend/public/immunity/threat-icons/threat-0725.svg new file mode 100644 index 0000000..88d6e34 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0725.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0726.svg b/apps/frontend/public/immunity/threat-icons/threat-0726.svg new file mode 100644 index 0000000..f9e553c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0726.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0727.svg b/apps/frontend/public/immunity/threat-icons/threat-0727.svg new file mode 100644 index 0000000..2a0264d --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0727.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0728.svg b/apps/frontend/public/immunity/threat-icons/threat-0728.svg new file mode 100644 index 0000000..e45717e --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0728.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0729.svg b/apps/frontend/public/immunity/threat-icons/threat-0729.svg new file mode 100644 index 0000000..e5533d8 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0729.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0730.svg b/apps/frontend/public/immunity/threat-icons/threat-0730.svg new file mode 100644 index 0000000..f310710 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0730.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0731.svg b/apps/frontend/public/immunity/threat-icons/threat-0731.svg new file mode 100644 index 0000000..c40abf8 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0731.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0732.svg b/apps/frontend/public/immunity/threat-icons/threat-0732.svg new file mode 100644 index 0000000..fb94d27 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0732.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0733.svg b/apps/frontend/public/immunity/threat-icons/threat-0733.svg new file mode 100644 index 0000000..9f7d8a3 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0733.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0734.svg b/apps/frontend/public/immunity/threat-icons/threat-0734.svg new file mode 100644 index 0000000..dfd77b8 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0734.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0735.svg b/apps/frontend/public/immunity/threat-icons/threat-0735.svg new file mode 100644 index 0000000..97fda46 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0735.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0736.svg b/apps/frontend/public/immunity/threat-icons/threat-0736.svg new file mode 100644 index 0000000..e36f806 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0736.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0737.svg b/apps/frontend/public/immunity/threat-icons/threat-0737.svg new file mode 100644 index 0000000..8b4ac8a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0737.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0738.svg b/apps/frontend/public/immunity/threat-icons/threat-0738.svg new file mode 100644 index 0000000..147bbf7 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0738.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0739.svg b/apps/frontend/public/immunity/threat-icons/threat-0739.svg new file mode 100644 index 0000000..f4ccc8f --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0739.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0740.svg b/apps/frontend/public/immunity/threat-icons/threat-0740.svg new file mode 100644 index 0000000..80fadfe --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0740.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0741.svg b/apps/frontend/public/immunity/threat-icons/threat-0741.svg new file mode 100644 index 0000000..5bd1cf8 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0741.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0742.svg b/apps/frontend/public/immunity/threat-icons/threat-0742.svg new file mode 100644 index 0000000..5185807 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0742.svg @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0743.svg b/apps/frontend/public/immunity/threat-icons/threat-0743.svg new file mode 100644 index 0000000..702fa5c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0743.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0744.svg b/apps/frontend/public/immunity/threat-icons/threat-0744.svg new file mode 100644 index 0000000..8ac6958 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0744.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0745.svg b/apps/frontend/public/immunity/threat-icons/threat-0745.svg new file mode 100644 index 0000000..15e1e0c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0745.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0746.svg b/apps/frontend/public/immunity/threat-icons/threat-0746.svg new file mode 100644 index 0000000..1339179 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0746.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0747.svg b/apps/frontend/public/immunity/threat-icons/threat-0747.svg new file mode 100644 index 0000000..b7aa62d --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0747.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0748.svg b/apps/frontend/public/immunity/threat-icons/threat-0748.svg new file mode 100644 index 0000000..df82dfc --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0748.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0749.svg b/apps/frontend/public/immunity/threat-icons/threat-0749.svg new file mode 100644 index 0000000..7022cc9 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0749.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0750.svg b/apps/frontend/public/immunity/threat-icons/threat-0750.svg new file mode 100644 index 0000000..a4f0fae --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0750.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0751.svg b/apps/frontend/public/immunity/threat-icons/threat-0751.svg new file mode 100644 index 0000000..7c8b1c3 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0751.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0752.svg b/apps/frontend/public/immunity/threat-icons/threat-0752.svg new file mode 100644 index 0000000..015d787 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0752.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0753.svg b/apps/frontend/public/immunity/threat-icons/threat-0753.svg new file mode 100644 index 0000000..d0c7518 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0753.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0754.svg b/apps/frontend/public/immunity/threat-icons/threat-0754.svg new file mode 100644 index 0000000..52880a1 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0754.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0755.svg b/apps/frontend/public/immunity/threat-icons/threat-0755.svg new file mode 100644 index 0000000..ca390bc --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0755.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0756.svg b/apps/frontend/public/immunity/threat-icons/threat-0756.svg new file mode 100644 index 0000000..44173bb --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0756.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0757.svg b/apps/frontend/public/immunity/threat-icons/threat-0757.svg new file mode 100644 index 0000000..2bedf5b --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0757.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0758.svg b/apps/frontend/public/immunity/threat-icons/threat-0758.svg new file mode 100644 index 0000000..9332aad --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0758.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0759.svg b/apps/frontend/public/immunity/threat-icons/threat-0759.svg new file mode 100644 index 0000000..0e69444 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0759.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0760.svg b/apps/frontend/public/immunity/threat-icons/threat-0760.svg new file mode 100644 index 0000000..b85f6e4 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0760.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0761.svg b/apps/frontend/public/immunity/threat-icons/threat-0761.svg new file mode 100644 index 0000000..4e4a0e6 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0761.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0762.svg b/apps/frontend/public/immunity/threat-icons/threat-0762.svg new file mode 100644 index 0000000..5048ff2 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0762.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0763.svg b/apps/frontend/public/immunity/threat-icons/threat-0763.svg new file mode 100644 index 0000000..c2608de --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0763.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0764.svg b/apps/frontend/public/immunity/threat-icons/threat-0764.svg new file mode 100644 index 0000000..4f9b882 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0764.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0765.svg b/apps/frontend/public/immunity/threat-icons/threat-0765.svg new file mode 100644 index 0000000..b0f5955 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0765.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0766.svg b/apps/frontend/public/immunity/threat-icons/threat-0766.svg new file mode 100644 index 0000000..ad345f3 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0766.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0767.svg b/apps/frontend/public/immunity/threat-icons/threat-0767.svg new file mode 100644 index 0000000..56b0dcc --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0767.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0768.svg b/apps/frontend/public/immunity/threat-icons/threat-0768.svg new file mode 100644 index 0000000..fc5fc14 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0768.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0769.svg b/apps/frontend/public/immunity/threat-icons/threat-0769.svg new file mode 100644 index 0000000..8b16bcb --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0769.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0770.svg b/apps/frontend/public/immunity/threat-icons/threat-0770.svg new file mode 100644 index 0000000..dec5df8 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0770.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0771.svg b/apps/frontend/public/immunity/threat-icons/threat-0771.svg new file mode 100644 index 0000000..c84b74e --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0771.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0772.svg b/apps/frontend/public/immunity/threat-icons/threat-0772.svg new file mode 100644 index 0000000..4691f5e --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0772.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0773.svg b/apps/frontend/public/immunity/threat-icons/threat-0773.svg new file mode 100644 index 0000000..0a8bb81 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0773.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0774.svg b/apps/frontend/public/immunity/threat-icons/threat-0774.svg new file mode 100644 index 0000000..05d7760 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0774.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0775.svg b/apps/frontend/public/immunity/threat-icons/threat-0775.svg new file mode 100644 index 0000000..9635759 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0775.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0776.svg b/apps/frontend/public/immunity/threat-icons/threat-0776.svg new file mode 100644 index 0000000..0ae3369 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0776.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0777.svg b/apps/frontend/public/immunity/threat-icons/threat-0777.svg new file mode 100644 index 0000000..3fcf010 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0777.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0778.svg b/apps/frontend/public/immunity/threat-icons/threat-0778.svg new file mode 100644 index 0000000..df09dd2 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0778.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0779.svg b/apps/frontend/public/immunity/threat-icons/threat-0779.svg new file mode 100644 index 0000000..c060cdf --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0779.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0780.svg b/apps/frontend/public/immunity/threat-icons/threat-0780.svg new file mode 100644 index 0000000..6458a8f --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0780.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0781.svg b/apps/frontend/public/immunity/threat-icons/threat-0781.svg new file mode 100644 index 0000000..4a94c8a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0781.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0782.svg b/apps/frontend/public/immunity/threat-icons/threat-0782.svg new file mode 100644 index 0000000..d306833 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0782.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0783.svg b/apps/frontend/public/immunity/threat-icons/threat-0783.svg new file mode 100644 index 0000000..1a6bbec --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0783.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0784.svg b/apps/frontend/public/immunity/threat-icons/threat-0784.svg new file mode 100644 index 0000000..373f7cc --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0784.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0785.svg b/apps/frontend/public/immunity/threat-icons/threat-0785.svg new file mode 100644 index 0000000..de591fe --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0785.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0786.svg b/apps/frontend/public/immunity/threat-icons/threat-0786.svg new file mode 100644 index 0000000..e4f633c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0786.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0787.svg b/apps/frontend/public/immunity/threat-icons/threat-0787.svg new file mode 100644 index 0000000..69445b7 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0787.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0788.svg b/apps/frontend/public/immunity/threat-icons/threat-0788.svg new file mode 100644 index 0000000..38b17c5 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0788.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0789.svg b/apps/frontend/public/immunity/threat-icons/threat-0789.svg new file mode 100644 index 0000000..65ff906 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0789.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0790.svg b/apps/frontend/public/immunity/threat-icons/threat-0790.svg new file mode 100644 index 0000000..186a453 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0790.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0791.svg b/apps/frontend/public/immunity/threat-icons/threat-0791.svg new file mode 100644 index 0000000..c2e9730 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0791.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0792.svg b/apps/frontend/public/immunity/threat-icons/threat-0792.svg new file mode 100644 index 0000000..34e020a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0792.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0793.svg b/apps/frontend/public/immunity/threat-icons/threat-0793.svg new file mode 100644 index 0000000..3957219 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0793.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0794.svg b/apps/frontend/public/immunity/threat-icons/threat-0794.svg new file mode 100644 index 0000000..2944cee --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0794.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0795.svg b/apps/frontend/public/immunity/threat-icons/threat-0795.svg new file mode 100644 index 0000000..e579ab3 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0795.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0796.svg b/apps/frontend/public/immunity/threat-icons/threat-0796.svg new file mode 100644 index 0000000..ada8d3b --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0796.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0797.svg b/apps/frontend/public/immunity/threat-icons/threat-0797.svg new file mode 100644 index 0000000..e2c1161 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0797.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0798.svg b/apps/frontend/public/immunity/threat-icons/threat-0798.svg new file mode 100644 index 0000000..0f2a7d9 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0798.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0799.svg b/apps/frontend/public/immunity/threat-icons/threat-0799.svg new file mode 100644 index 0000000..1834d9a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0799.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0800.svg b/apps/frontend/public/immunity/threat-icons/threat-0800.svg new file mode 100644 index 0000000..b5f7515 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0800.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0801.svg b/apps/frontend/public/immunity/threat-icons/threat-0801.svg new file mode 100644 index 0000000..3f6b75d --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0801.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0802.svg b/apps/frontend/public/immunity/threat-icons/threat-0802.svg new file mode 100644 index 0000000..d29fbea --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0802.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0803.svg b/apps/frontend/public/immunity/threat-icons/threat-0803.svg new file mode 100644 index 0000000..14b4bf3 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0803.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0804.svg b/apps/frontend/public/immunity/threat-icons/threat-0804.svg new file mode 100644 index 0000000..e5d60ae --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0804.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0805.svg b/apps/frontend/public/immunity/threat-icons/threat-0805.svg new file mode 100644 index 0000000..121679c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0805.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0806.svg b/apps/frontend/public/immunity/threat-icons/threat-0806.svg new file mode 100644 index 0000000..97ec6c0 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0806.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0807.svg b/apps/frontend/public/immunity/threat-icons/threat-0807.svg new file mode 100644 index 0000000..9f111fb --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0807.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0808.svg b/apps/frontend/public/immunity/threat-icons/threat-0808.svg new file mode 100644 index 0000000..e395eaf --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0808.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0809.svg b/apps/frontend/public/immunity/threat-icons/threat-0809.svg new file mode 100644 index 0000000..33174bf --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0809.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0810.svg b/apps/frontend/public/immunity/threat-icons/threat-0810.svg new file mode 100644 index 0000000..3d7ae76 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0810.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0811.svg b/apps/frontend/public/immunity/threat-icons/threat-0811.svg new file mode 100644 index 0000000..ae29645 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0811.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0812.svg b/apps/frontend/public/immunity/threat-icons/threat-0812.svg new file mode 100644 index 0000000..bc50772 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0812.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0813.svg b/apps/frontend/public/immunity/threat-icons/threat-0813.svg new file mode 100644 index 0000000..c1b440a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0813.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0814.svg b/apps/frontend/public/immunity/threat-icons/threat-0814.svg new file mode 100644 index 0000000..648bb97 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0814.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0815.svg b/apps/frontend/public/immunity/threat-icons/threat-0815.svg new file mode 100644 index 0000000..56ba7f3 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0815.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0816.svg b/apps/frontend/public/immunity/threat-icons/threat-0816.svg new file mode 100644 index 0000000..bbbfc65 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0816.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0817.svg b/apps/frontend/public/immunity/threat-icons/threat-0817.svg new file mode 100644 index 0000000..21e4222 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0817.svg @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0818.svg b/apps/frontend/public/immunity/threat-icons/threat-0818.svg new file mode 100644 index 0000000..abce198 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0818.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0819.svg b/apps/frontend/public/immunity/threat-icons/threat-0819.svg new file mode 100644 index 0000000..8052d7c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0819.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0820.svg b/apps/frontend/public/immunity/threat-icons/threat-0820.svg new file mode 100644 index 0000000..6df1b36 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0820.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0821.svg b/apps/frontend/public/immunity/threat-icons/threat-0821.svg new file mode 100644 index 0000000..0b01b9d --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0821.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0822.svg b/apps/frontend/public/immunity/threat-icons/threat-0822.svg new file mode 100644 index 0000000..658a0a6 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0822.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0823.svg b/apps/frontend/public/immunity/threat-icons/threat-0823.svg new file mode 100644 index 0000000..19f1d6a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0823.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0824.svg b/apps/frontend/public/immunity/threat-icons/threat-0824.svg new file mode 100644 index 0000000..300f793 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0824.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0825.svg b/apps/frontend/public/immunity/threat-icons/threat-0825.svg new file mode 100644 index 0000000..f0f9be4 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0825.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0826.svg b/apps/frontend/public/immunity/threat-icons/threat-0826.svg new file mode 100644 index 0000000..5962b4b --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0826.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0827.svg b/apps/frontend/public/immunity/threat-icons/threat-0827.svg new file mode 100644 index 0000000..9efe9cd --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0827.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0828.svg b/apps/frontend/public/immunity/threat-icons/threat-0828.svg new file mode 100644 index 0000000..1842823 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0828.svg @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0829.svg b/apps/frontend/public/immunity/threat-icons/threat-0829.svg new file mode 100644 index 0000000..45e3d77 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0829.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0830.svg b/apps/frontend/public/immunity/threat-icons/threat-0830.svg new file mode 100644 index 0000000..991e7f0 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0830.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0831.svg b/apps/frontend/public/immunity/threat-icons/threat-0831.svg new file mode 100644 index 0000000..e4612af --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0831.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0832.svg b/apps/frontend/public/immunity/threat-icons/threat-0832.svg new file mode 100644 index 0000000..b620609 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0832.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0833.svg b/apps/frontend/public/immunity/threat-icons/threat-0833.svg new file mode 100644 index 0000000..c920ca4 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0833.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0834.svg b/apps/frontend/public/immunity/threat-icons/threat-0834.svg new file mode 100644 index 0000000..ac8919b --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0834.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0835.svg b/apps/frontend/public/immunity/threat-icons/threat-0835.svg new file mode 100644 index 0000000..a8ee3f4 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0835.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0836.svg b/apps/frontend/public/immunity/threat-icons/threat-0836.svg new file mode 100644 index 0000000..422bf21 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0836.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0837.svg b/apps/frontend/public/immunity/threat-icons/threat-0837.svg new file mode 100644 index 0000000..d398031 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0837.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0838.svg b/apps/frontend/public/immunity/threat-icons/threat-0838.svg new file mode 100644 index 0000000..d037df8 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0838.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0839.svg b/apps/frontend/public/immunity/threat-icons/threat-0839.svg new file mode 100644 index 0000000..5254af6 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0839.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0840.svg b/apps/frontend/public/immunity/threat-icons/threat-0840.svg new file mode 100644 index 0000000..6c16495 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0840.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0841.svg b/apps/frontend/public/immunity/threat-icons/threat-0841.svg new file mode 100644 index 0000000..6018603 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0841.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0842.svg b/apps/frontend/public/immunity/threat-icons/threat-0842.svg new file mode 100644 index 0000000..0dd30c4 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0842.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0843.svg b/apps/frontend/public/immunity/threat-icons/threat-0843.svg new file mode 100644 index 0000000..1d0e8f4 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0843.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0844.svg b/apps/frontend/public/immunity/threat-icons/threat-0844.svg new file mode 100644 index 0000000..0ea3c64 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0844.svg @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0845.svg b/apps/frontend/public/immunity/threat-icons/threat-0845.svg new file mode 100644 index 0000000..d92278f --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0845.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0846.svg b/apps/frontend/public/immunity/threat-icons/threat-0846.svg new file mode 100644 index 0000000..19efc37 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0846.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0847.svg b/apps/frontend/public/immunity/threat-icons/threat-0847.svg new file mode 100644 index 0000000..1908244 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0847.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0848.svg b/apps/frontend/public/immunity/threat-icons/threat-0848.svg new file mode 100644 index 0000000..5dfbc98 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0848.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0849.svg b/apps/frontend/public/immunity/threat-icons/threat-0849.svg new file mode 100644 index 0000000..f774cb2 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0849.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0850.svg b/apps/frontend/public/immunity/threat-icons/threat-0850.svg new file mode 100644 index 0000000..7f1514f --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0850.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0851.svg b/apps/frontend/public/immunity/threat-icons/threat-0851.svg new file mode 100644 index 0000000..9b3daaa --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0851.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0852.svg b/apps/frontend/public/immunity/threat-icons/threat-0852.svg new file mode 100644 index 0000000..c67576d --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0852.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0853.svg b/apps/frontend/public/immunity/threat-icons/threat-0853.svg new file mode 100644 index 0000000..cb1b665 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0853.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0854.svg b/apps/frontend/public/immunity/threat-icons/threat-0854.svg new file mode 100644 index 0000000..f231c7c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0854.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0855.svg b/apps/frontend/public/immunity/threat-icons/threat-0855.svg new file mode 100644 index 0000000..24c2159 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0855.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0856.svg b/apps/frontend/public/immunity/threat-icons/threat-0856.svg new file mode 100644 index 0000000..9a3cc4d --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0856.svg @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0857.svg b/apps/frontend/public/immunity/threat-icons/threat-0857.svg new file mode 100644 index 0000000..a625e15 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0857.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0858.svg b/apps/frontend/public/immunity/threat-icons/threat-0858.svg new file mode 100644 index 0000000..ec38572 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0858.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0859.svg b/apps/frontend/public/immunity/threat-icons/threat-0859.svg new file mode 100644 index 0000000..cb8c7f0 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0859.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0860.svg b/apps/frontend/public/immunity/threat-icons/threat-0860.svg new file mode 100644 index 0000000..b20e7ad --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0860.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0861.svg b/apps/frontend/public/immunity/threat-icons/threat-0861.svg new file mode 100644 index 0000000..c9751fe --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0861.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0862.svg b/apps/frontend/public/immunity/threat-icons/threat-0862.svg new file mode 100644 index 0000000..a700d83 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0862.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0863.svg b/apps/frontend/public/immunity/threat-icons/threat-0863.svg new file mode 100644 index 0000000..da51b9f --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0863.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0864.svg b/apps/frontend/public/immunity/threat-icons/threat-0864.svg new file mode 100644 index 0000000..3cc459d --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0864.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0865.svg b/apps/frontend/public/immunity/threat-icons/threat-0865.svg new file mode 100644 index 0000000..6e47db7 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0865.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0866.svg b/apps/frontend/public/immunity/threat-icons/threat-0866.svg new file mode 100644 index 0000000..0e6fb68 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0866.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0867.svg b/apps/frontend/public/immunity/threat-icons/threat-0867.svg new file mode 100644 index 0000000..399c855 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0867.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0868.svg b/apps/frontend/public/immunity/threat-icons/threat-0868.svg new file mode 100644 index 0000000..5fada42 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0868.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0869.svg b/apps/frontend/public/immunity/threat-icons/threat-0869.svg new file mode 100644 index 0000000..9a73ee1 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0869.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0870.svg b/apps/frontend/public/immunity/threat-icons/threat-0870.svg new file mode 100644 index 0000000..2dde7fc --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0870.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0871.svg b/apps/frontend/public/immunity/threat-icons/threat-0871.svg new file mode 100644 index 0000000..8f04b04 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0871.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0872.svg b/apps/frontend/public/immunity/threat-icons/threat-0872.svg new file mode 100644 index 0000000..53bab97 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0872.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0873.svg b/apps/frontend/public/immunity/threat-icons/threat-0873.svg new file mode 100644 index 0000000..58bf358 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0873.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0874.svg b/apps/frontend/public/immunity/threat-icons/threat-0874.svg new file mode 100644 index 0000000..b6a77ce --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0874.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0875.svg b/apps/frontend/public/immunity/threat-icons/threat-0875.svg new file mode 100644 index 0000000..2bac1c5 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0875.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0876.svg b/apps/frontend/public/immunity/threat-icons/threat-0876.svg new file mode 100644 index 0000000..82b2028 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0876.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0877.svg b/apps/frontend/public/immunity/threat-icons/threat-0877.svg new file mode 100644 index 0000000..d90e3f2 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0877.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0878.svg b/apps/frontend/public/immunity/threat-icons/threat-0878.svg new file mode 100644 index 0000000..c34b191 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0878.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0879.svg b/apps/frontend/public/immunity/threat-icons/threat-0879.svg new file mode 100644 index 0000000..89cd175 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0879.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0880.svg b/apps/frontend/public/immunity/threat-icons/threat-0880.svg new file mode 100644 index 0000000..8ab69f5 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0880.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0881.svg b/apps/frontend/public/immunity/threat-icons/threat-0881.svg new file mode 100644 index 0000000..3690771 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0881.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0882.svg b/apps/frontend/public/immunity/threat-icons/threat-0882.svg new file mode 100644 index 0000000..622d467 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0882.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0883.svg b/apps/frontend/public/immunity/threat-icons/threat-0883.svg new file mode 100644 index 0000000..2c72414 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0883.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0884.svg b/apps/frontend/public/immunity/threat-icons/threat-0884.svg new file mode 100644 index 0000000..bb644e7 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0884.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0885.svg b/apps/frontend/public/immunity/threat-icons/threat-0885.svg new file mode 100644 index 0000000..58a3320 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0885.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0886.svg b/apps/frontend/public/immunity/threat-icons/threat-0886.svg new file mode 100644 index 0000000..2d23516 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0886.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0887.svg b/apps/frontend/public/immunity/threat-icons/threat-0887.svg new file mode 100644 index 0000000..21e4d26 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0887.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0888.svg b/apps/frontend/public/immunity/threat-icons/threat-0888.svg new file mode 100644 index 0000000..20d1172 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0888.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0889.svg b/apps/frontend/public/immunity/threat-icons/threat-0889.svg new file mode 100644 index 0000000..25fff86 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0889.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0890.svg b/apps/frontend/public/immunity/threat-icons/threat-0890.svg new file mode 100644 index 0000000..cd44178 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0890.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0891.svg b/apps/frontend/public/immunity/threat-icons/threat-0891.svg new file mode 100644 index 0000000..2939681 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0891.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0892.svg b/apps/frontend/public/immunity/threat-icons/threat-0892.svg new file mode 100644 index 0000000..86c7df8 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0892.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0893.svg b/apps/frontend/public/immunity/threat-icons/threat-0893.svg new file mode 100644 index 0000000..11117c7 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0893.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0894.svg b/apps/frontend/public/immunity/threat-icons/threat-0894.svg new file mode 100644 index 0000000..a6bf540 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0894.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0895.svg b/apps/frontend/public/immunity/threat-icons/threat-0895.svg new file mode 100644 index 0000000..6e4a777 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0895.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0896.svg b/apps/frontend/public/immunity/threat-icons/threat-0896.svg new file mode 100644 index 0000000..7ea30ff --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0896.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0897.svg b/apps/frontend/public/immunity/threat-icons/threat-0897.svg new file mode 100644 index 0000000..87f9e9e --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0897.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0898.svg b/apps/frontend/public/immunity/threat-icons/threat-0898.svg new file mode 100644 index 0000000..493e7ae --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0898.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0899.svg b/apps/frontend/public/immunity/threat-icons/threat-0899.svg new file mode 100644 index 0000000..4e94df5 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0899.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0900.svg b/apps/frontend/public/immunity/threat-icons/threat-0900.svg new file mode 100644 index 0000000..a66ecb2 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0900.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0901.svg b/apps/frontend/public/immunity/threat-icons/threat-0901.svg new file mode 100644 index 0000000..a0b99d5 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0901.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0902.svg b/apps/frontend/public/immunity/threat-icons/threat-0902.svg new file mode 100644 index 0000000..c9c7f99 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0902.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0903.svg b/apps/frontend/public/immunity/threat-icons/threat-0903.svg new file mode 100644 index 0000000..14b97ae --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0903.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0904.svg b/apps/frontend/public/immunity/threat-icons/threat-0904.svg new file mode 100644 index 0000000..c3110d1 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0904.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0905.svg b/apps/frontend/public/immunity/threat-icons/threat-0905.svg new file mode 100644 index 0000000..b4e622f --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0905.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0906.svg b/apps/frontend/public/immunity/threat-icons/threat-0906.svg new file mode 100644 index 0000000..3e1d620 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0906.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0907.svg b/apps/frontend/public/immunity/threat-icons/threat-0907.svg new file mode 100644 index 0000000..7bb2e3c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0907.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0908.svg b/apps/frontend/public/immunity/threat-icons/threat-0908.svg new file mode 100644 index 0000000..0410bf5 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0908.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0909.svg b/apps/frontend/public/immunity/threat-icons/threat-0909.svg new file mode 100644 index 0000000..3fe8804 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0909.svg @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0910.svg b/apps/frontend/public/immunity/threat-icons/threat-0910.svg new file mode 100644 index 0000000..f6486a5 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0910.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0911.svg b/apps/frontend/public/immunity/threat-icons/threat-0911.svg new file mode 100644 index 0000000..80f7eff --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0911.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0912.svg b/apps/frontend/public/immunity/threat-icons/threat-0912.svg new file mode 100644 index 0000000..6529f9b --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0912.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0913.svg b/apps/frontend/public/immunity/threat-icons/threat-0913.svg new file mode 100644 index 0000000..135d7e0 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0913.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0914.svg b/apps/frontend/public/immunity/threat-icons/threat-0914.svg new file mode 100644 index 0000000..5bcc468 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0914.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0915.svg b/apps/frontend/public/immunity/threat-icons/threat-0915.svg new file mode 100644 index 0000000..421def4 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0915.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0916.svg b/apps/frontend/public/immunity/threat-icons/threat-0916.svg new file mode 100644 index 0000000..c8a7744 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0916.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0917.svg b/apps/frontend/public/immunity/threat-icons/threat-0917.svg new file mode 100644 index 0000000..e51fbb6 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0917.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0918.svg b/apps/frontend/public/immunity/threat-icons/threat-0918.svg new file mode 100644 index 0000000..6e16c6c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0918.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0919.svg b/apps/frontend/public/immunity/threat-icons/threat-0919.svg new file mode 100644 index 0000000..2a0a858 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0919.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0920.svg b/apps/frontend/public/immunity/threat-icons/threat-0920.svg new file mode 100644 index 0000000..f1cfb89 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0920.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0921.svg b/apps/frontend/public/immunity/threat-icons/threat-0921.svg new file mode 100644 index 0000000..9d6eed8 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0921.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0922.svg b/apps/frontend/public/immunity/threat-icons/threat-0922.svg new file mode 100644 index 0000000..fcf01e3 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0922.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0923.svg b/apps/frontend/public/immunity/threat-icons/threat-0923.svg new file mode 100644 index 0000000..cc28ec3 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0923.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0924.svg b/apps/frontend/public/immunity/threat-icons/threat-0924.svg new file mode 100644 index 0000000..1fd6bed --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0924.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0925.svg b/apps/frontend/public/immunity/threat-icons/threat-0925.svg new file mode 100644 index 0000000..0741d19 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0925.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0926.svg b/apps/frontend/public/immunity/threat-icons/threat-0926.svg new file mode 100644 index 0000000..b2c9660 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0926.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0927.svg b/apps/frontend/public/immunity/threat-icons/threat-0927.svg new file mode 100644 index 0000000..530a7ef --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0927.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0928.svg b/apps/frontend/public/immunity/threat-icons/threat-0928.svg new file mode 100644 index 0000000..e9e8716 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0928.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0929.svg b/apps/frontend/public/immunity/threat-icons/threat-0929.svg new file mode 100644 index 0000000..f140d5f --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0929.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0930.svg b/apps/frontend/public/immunity/threat-icons/threat-0930.svg new file mode 100644 index 0000000..3b3d23c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0930.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0931.svg b/apps/frontend/public/immunity/threat-icons/threat-0931.svg new file mode 100644 index 0000000..03fcdfb --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0931.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0932.svg b/apps/frontend/public/immunity/threat-icons/threat-0932.svg new file mode 100644 index 0000000..938fa94 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0932.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0933.svg b/apps/frontend/public/immunity/threat-icons/threat-0933.svg new file mode 100644 index 0000000..fff99b1 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0933.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0934.svg b/apps/frontend/public/immunity/threat-icons/threat-0934.svg new file mode 100644 index 0000000..d8cd919 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0934.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0935.svg b/apps/frontend/public/immunity/threat-icons/threat-0935.svg new file mode 100644 index 0000000..3618ed5 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0935.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0936.svg b/apps/frontend/public/immunity/threat-icons/threat-0936.svg new file mode 100644 index 0000000..0340394 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0936.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0937.svg b/apps/frontend/public/immunity/threat-icons/threat-0937.svg new file mode 100644 index 0000000..905df5e --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0937.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0938.svg b/apps/frontend/public/immunity/threat-icons/threat-0938.svg new file mode 100644 index 0000000..e2bf94f --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0938.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0939.svg b/apps/frontend/public/immunity/threat-icons/threat-0939.svg new file mode 100644 index 0000000..816b0d2 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0939.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0940.svg b/apps/frontend/public/immunity/threat-icons/threat-0940.svg new file mode 100644 index 0000000..0e6d8eb --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0940.svg @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0941.svg b/apps/frontend/public/immunity/threat-icons/threat-0941.svg new file mode 100644 index 0000000..24ec58c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0941.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0942.svg b/apps/frontend/public/immunity/threat-icons/threat-0942.svg new file mode 100644 index 0000000..e7ebfcf --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0942.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0943.svg b/apps/frontend/public/immunity/threat-icons/threat-0943.svg new file mode 100644 index 0000000..caa21a2 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0943.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0944.svg b/apps/frontend/public/immunity/threat-icons/threat-0944.svg new file mode 100644 index 0000000..f59cb87 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0944.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0945.svg b/apps/frontend/public/immunity/threat-icons/threat-0945.svg new file mode 100644 index 0000000..aa88f4f --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0945.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0946.svg b/apps/frontend/public/immunity/threat-icons/threat-0946.svg new file mode 100644 index 0000000..631c0dd --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0946.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0947.svg b/apps/frontend/public/immunity/threat-icons/threat-0947.svg new file mode 100644 index 0000000..d99f76d --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0947.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0948.svg b/apps/frontend/public/immunity/threat-icons/threat-0948.svg new file mode 100644 index 0000000..d132845 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0948.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0949.svg b/apps/frontend/public/immunity/threat-icons/threat-0949.svg new file mode 100644 index 0000000..bda9765 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0949.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0950.svg b/apps/frontend/public/immunity/threat-icons/threat-0950.svg new file mode 100644 index 0000000..8882c7c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0950.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0951.svg b/apps/frontend/public/immunity/threat-icons/threat-0951.svg new file mode 100644 index 0000000..79d92a8 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0951.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0952.svg b/apps/frontend/public/immunity/threat-icons/threat-0952.svg new file mode 100644 index 0000000..8bb9d32 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0952.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0953.svg b/apps/frontend/public/immunity/threat-icons/threat-0953.svg new file mode 100644 index 0000000..603ea8a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0953.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0954.svg b/apps/frontend/public/immunity/threat-icons/threat-0954.svg new file mode 100644 index 0000000..0562139 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0954.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0955.svg b/apps/frontend/public/immunity/threat-icons/threat-0955.svg new file mode 100644 index 0000000..a100856 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0955.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0956.svg b/apps/frontend/public/immunity/threat-icons/threat-0956.svg new file mode 100644 index 0000000..0447776 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0956.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0957.svg b/apps/frontend/public/immunity/threat-icons/threat-0957.svg new file mode 100644 index 0000000..57e78b9 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0957.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0958.svg b/apps/frontend/public/immunity/threat-icons/threat-0958.svg new file mode 100644 index 0000000..50a5af4 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0958.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0959.svg b/apps/frontend/public/immunity/threat-icons/threat-0959.svg new file mode 100644 index 0000000..4bab7fa --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0959.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0960.svg b/apps/frontend/public/immunity/threat-icons/threat-0960.svg new file mode 100644 index 0000000..8db2135 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0960.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0961.svg b/apps/frontend/public/immunity/threat-icons/threat-0961.svg new file mode 100644 index 0000000..26e79f7 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0961.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0962.svg b/apps/frontend/public/immunity/threat-icons/threat-0962.svg new file mode 100644 index 0000000..d2d3d16 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0962.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0963.svg b/apps/frontend/public/immunity/threat-icons/threat-0963.svg new file mode 100644 index 0000000..1d51fcd --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0963.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0964.svg b/apps/frontend/public/immunity/threat-icons/threat-0964.svg new file mode 100644 index 0000000..491f87c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0964.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0965.svg b/apps/frontend/public/immunity/threat-icons/threat-0965.svg new file mode 100644 index 0000000..6e8246e --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0965.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0966.svg b/apps/frontend/public/immunity/threat-icons/threat-0966.svg new file mode 100644 index 0000000..6e3d3d8 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0966.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0967.svg b/apps/frontend/public/immunity/threat-icons/threat-0967.svg new file mode 100644 index 0000000..5d6ae56 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0967.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0968.svg b/apps/frontend/public/immunity/threat-icons/threat-0968.svg new file mode 100644 index 0000000..375c46c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0968.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0969.svg b/apps/frontend/public/immunity/threat-icons/threat-0969.svg new file mode 100644 index 0000000..a0780b5 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0969.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0970.svg b/apps/frontend/public/immunity/threat-icons/threat-0970.svg new file mode 100644 index 0000000..8a122bf --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0970.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0971.svg b/apps/frontend/public/immunity/threat-icons/threat-0971.svg new file mode 100644 index 0000000..78e1d36 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0971.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0972.svg b/apps/frontend/public/immunity/threat-icons/threat-0972.svg new file mode 100644 index 0000000..a031506 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0972.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0973.svg b/apps/frontend/public/immunity/threat-icons/threat-0973.svg new file mode 100644 index 0000000..35542ad --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0973.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0974.svg b/apps/frontend/public/immunity/threat-icons/threat-0974.svg new file mode 100644 index 0000000..b489f18 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0974.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0975.svg b/apps/frontend/public/immunity/threat-icons/threat-0975.svg new file mode 100644 index 0000000..5929e78 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0975.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0976.svg b/apps/frontend/public/immunity/threat-icons/threat-0976.svg new file mode 100644 index 0000000..42297e6 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0976.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0977.svg b/apps/frontend/public/immunity/threat-icons/threat-0977.svg new file mode 100644 index 0000000..8ad943c --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0977.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0978.svg b/apps/frontend/public/immunity/threat-icons/threat-0978.svg new file mode 100644 index 0000000..bcbabb5 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0978.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0979.svg b/apps/frontend/public/immunity/threat-icons/threat-0979.svg new file mode 100644 index 0000000..d22ecb3 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0979.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0980.svg b/apps/frontend/public/immunity/threat-icons/threat-0980.svg new file mode 100644 index 0000000..1c82ad3 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0980.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0981.svg b/apps/frontend/public/immunity/threat-icons/threat-0981.svg new file mode 100644 index 0000000..d517ddf --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0981.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0982.svg b/apps/frontend/public/immunity/threat-icons/threat-0982.svg new file mode 100644 index 0000000..89e0f15 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0982.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0983.svg b/apps/frontend/public/immunity/threat-icons/threat-0983.svg new file mode 100644 index 0000000..563e07f --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0983.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0984.svg b/apps/frontend/public/immunity/threat-icons/threat-0984.svg new file mode 100644 index 0000000..ddccfd9 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0984.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0985.svg b/apps/frontend/public/immunity/threat-icons/threat-0985.svg new file mode 100644 index 0000000..a7e9a56 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0985.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0986.svg b/apps/frontend/public/immunity/threat-icons/threat-0986.svg new file mode 100644 index 0000000..47a8c5a --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0986.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0987.svg b/apps/frontend/public/immunity/threat-icons/threat-0987.svg new file mode 100644 index 0000000..64d1d34 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0987.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0988.svg b/apps/frontend/public/immunity/threat-icons/threat-0988.svg new file mode 100644 index 0000000..254becc --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0988.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0989.svg b/apps/frontend/public/immunity/threat-icons/threat-0989.svg new file mode 100644 index 0000000..806bcdf --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0989.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0990.svg b/apps/frontend/public/immunity/threat-icons/threat-0990.svg new file mode 100644 index 0000000..357169b --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0990.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0991.svg b/apps/frontend/public/immunity/threat-icons/threat-0991.svg new file mode 100644 index 0000000..cf31525 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0991.svg @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0992.svg b/apps/frontend/public/immunity/threat-icons/threat-0992.svg new file mode 100644 index 0000000..17eb7f4 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0992.svg @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0993.svg b/apps/frontend/public/immunity/threat-icons/threat-0993.svg new file mode 100644 index 0000000..5f1f177 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0993.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0994.svg b/apps/frontend/public/immunity/threat-icons/threat-0994.svg new file mode 100644 index 0000000..a910230 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0994.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0995.svg b/apps/frontend/public/immunity/threat-icons/threat-0995.svg new file mode 100644 index 0000000..d2d3bcc --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0995.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0996.svg b/apps/frontend/public/immunity/threat-icons/threat-0996.svg new file mode 100644 index 0000000..0275665 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0996.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0997.svg b/apps/frontend/public/immunity/threat-icons/threat-0997.svg new file mode 100644 index 0000000..cba5db5 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0997.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0998.svg b/apps/frontend/public/immunity/threat-icons/threat-0998.svg new file mode 100644 index 0000000..5b0e6e3 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0998.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/immunity/threat-icons/threat-0999.svg b/apps/frontend/public/immunity/threat-icons/threat-0999.svg new file mode 100644 index 0000000..b6bbda1 --- /dev/null +++ b/apps/frontend/public/immunity/threat-icons/threat-0999.svg @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/frontend/public/og-v4.png b/apps/frontend/public/og-v4.png new file mode 100644 index 0000000..2c6bf6f Binary files /dev/null and b/apps/frontend/public/og-v4.png differ diff --git a/apps/frontend/server.ts b/apps/frontend/server.ts new file mode 100644 index 0000000..ab55d7e --- /dev/null +++ b/apps/frontend/server.ts @@ -0,0 +1,56 @@ +// Production static-file server for the built SPA, run by Bun (replaces nginx). +// Serves apps/frontend/dist and reverse-proxies backend routes so the +// session cookie stays first-party. Dev still uses Vite (see vite.config.ts). + +const PORT = Number(process.env.PORT ?? 8080); +const BACKEND_URL = process.env.BACKEND_URL ?? "http://localhost:3001"; +const DIST = "./dist"; + +function isProxied(pathname: string): boolean { + return ( + pathname === "/health" || + pathname.startsWith("/auth") || + pathname.startsWith("/api") || + pathname.startsWith("/internal") + ); +} + +// Forward the request to the backend, preserving method/headers/body/cookies. +async function proxy(req: Request, url: URL): Promise { + const target = new URL(url.pathname + url.search, BACKEND_URL); + const headers = new Headers(req.headers); + // Let fetch set Host from the target URL (Cloud Run routes by Host). + headers.delete("host"); + headers.set("x-forwarded-host", url.host); + headers.set("x-forwarded-proto", url.protocol.replace(":", "")); + + const init: RequestInit = { method: req.method, headers, redirect: "manual" }; + if (req.method !== "GET" && req.method !== "HEAD") { + init.body = await req.arrayBuffer(); + } + return fetch(target, init); +} + +async function serveStatic(url: URL): Promise { + const pathname = url.pathname === "/" ? "/index.html" : url.pathname; + const file = Bun.file(`${DIST}${pathname}`); + if (await file.exists()) { + const headers: Record = + pathname.startsWith("/assets/") + ? { "Cache-Control": "public, max-age=31536000, immutable" } + : { "Cache-Control": "no-cache" }; + return new Response(file, { headers }); + } + // SPA fallback: serve index.html for client-side routes. + return new Response(Bun.file(`${DIST}/index.html`), { headers: { "Cache-Control": "no-cache" } }); +} + +Bun.serve({ + port: PORT, + fetch(req) { + const url = new URL(req.url); + return isProxied(url.pathname) ? proxy(req, url) : serveStatic(url); + }, +}); + +console.log(`cerebrus-frontend serving on :${PORT} (proxy backend routes -> ${BACKEND_URL})`); diff --git a/apps/frontend/src/App.tsx b/apps/frontend/src/App.tsx new file mode 100644 index 0000000..0b52fdc --- /dev/null +++ b/apps/frontend/src/App.tsx @@ -0,0 +1,48 @@ +import { Link, Navigate, Route, Routes } from "react-router-dom"; +import { ClarityConceptPage, ClarityIndexPage, ClarityWorkspacePage } from "./ported/clarity/ClarityPages"; +import CompanyPage from "./ported/pages/CompanyPage"; +import DocsPage from "./ported/pages/DocsPage"; +import HomePage from "./ported/pages/HomePage"; +import LoginPage from "./ported/pages/LoginPage"; +import PricingPage from "./ported/pages/PricingPage"; +import ProductPage from "./ported/pages/ProductPage"; +import ThreatsPage from "./ported/pages/ThreatsPage"; +import { WorkspacePage } from "./ported/workspace/WorkspacePage"; + +function NotFoundPage() { + return ( +
+
+
+

404

+

This path is not open.

+

The page moved or the address is incomplete.

+ Return home +
+
+
+ ); +} + +export default function App() { + return ( + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + ); +} diff --git a/apps/frontend/src/components/AppHeader.tsx b/apps/frontend/src/components/AppHeader.tsx new file mode 100644 index 0000000..1f542fd --- /dev/null +++ b/apps/frontend/src/components/AppHeader.tsx @@ -0,0 +1,43 @@ +import type { ViewMeta } from "../types"; +import { useAuth } from "../lib/auth"; + +interface AppHeaderProps { + meta: ViewMeta; + onAction: (actionId: string) => void; +} + +// Page header with title, subtitle, the per-view primary action, and — when +// signed in via WorkOS — the user's email and a Sign out control. +export function AppHeader({ meta, onAction }: AppHeaderProps) { + const { user, logout } = useAuth(); + const greeting = user?.firstName ?? user?.email ?? "Arnav"; + + return ( +
+
+

+ Hello, {greeting} +

+

{meta.title}

+

{meta.subtitle}

+
+
+ + + + {user && ( + + )} + +
+
+ ); +} diff --git a/apps/frontend/src/components/Badges.tsx b/apps/frontend/src/components/Badges.tsx new file mode 100644 index 0000000..2d1f944 --- /dev/null +++ b/apps/frontend/src/components/Badges.tsx @@ -0,0 +1,14 @@ +// Small inline badge primitives used across tables and cards. + +export function SeverityBadge({ value }: { value: string }) { + return {value}; +} + +export function StatusBadge({ value }: { value: string }) { + const normalized = value.toLowerCase().replace(/\s+/g, "-"); + return {value}; +} + +export function TypeBadge({ value }: { value: string }) { + return {value.toUpperCase()}; +} diff --git a/apps/frontend/src/components/Brand.tsx b/apps/frontend/src/components/Brand.tsx new file mode 100644 index 0000000..3e7c518 --- /dev/null +++ b/apps/frontend/src/components/Brand.tsx @@ -0,0 +1,11 @@ +import { Link } from "react-router-dom"; + +// Cerebrus wordmark + logo, links home. Mirrors the original `.brand` anchor. +export function Brand() { + return ( + +