diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 000000000..fefeede03 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,12 @@ +FROM mcr.microsoft.com/devcontainers/javascript-node:4-24-trixie + +# [Optional] Uncomment this section to install additional OS packages. +# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ +# && apt-get -y install --no-install-recommends + +# [Optional] Uncomment if you want to install an additional version of node using nvm +# ARG EXTRA_NODE_VERSION=10 +# RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}" + +# [Optional] Uncomment if you want to install more global node modules +RUN su node -c "npm install -g @devcontainers/cli" diff --git a/.devcontainer/devcontainer-lock.json b/.devcontainer/devcontainer-lock.json new file mode 100644 index 000000000..3d40882e9 --- /dev/null +++ b/.devcontainer/devcontainer-lock.json @@ -0,0 +1,22 @@ +{ + "features": { + "ghcr.io/devcontainers-community/npm-features/prettier:1": { + "version": "1.1.0", + "resolved": "ghcr.io/devcontainers-community/npm-features/prettier@sha256:41aca434d43373d78b276d3cd89ecdce960e0b618ff0ee59f2ecee0810dbabe9", + "integrity": "sha256:41aca434d43373d78b276d3cd89ecdce960e0b618ff0ee59f2ecee0810dbabe9", + "dependsOn": [ + "ghcr.io/devcontainers/features/node" + ] + }, + "ghcr.io/devcontainers/features/docker-outside-of-docker:1": { + "version": "1.10.0", + "resolved": "ghcr.io/devcontainers/features/docker-outside-of-docker@sha256:c2c2cf829505ead8e4892c88c31b6594ae94a2bbb209e16e1fac456c1a3a624e", + "integrity": "sha256:c2c2cf829505ead8e4892c88c31b6594ae94a2bbb209e16e1fac456c1a3a624e" + }, + "ghcr.io/devcontainers/features/node": { + "version": "2.1.0", + "resolved": "ghcr.io/devcontainers/features/node@sha256:586c9a6f7dd40bd3ba2cd41e7f2f88dcc31fbe5d1442afcbf07ffbc66b686857", + "integrity": "sha256:586c9a6f7dd40bd3ba2cd41e7f2f88dcc31fbe5d1442afcbf07ffbc66b686857" + } + } +} \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000..1b3f444ce --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,46 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/javascript-node-postgres +{ + "name": "Node.js & PostgreSQL", + "dockerComposeFile": "docker-compose.yml", + "service": "app", + "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", + "features": { + "ghcr.io/devcontainers-community/npm-features/prettier:1": {}, + "ghcr.io/devcontainers/features/docker-outside-of-docker:1": { + "moby": false + } + }, + "mounts": [ + "source=${localEnv:HOME}/.gitconfig,target=/home/node/.gitconfig,type=bind,consistency=cached" + ], + + "containerEnv": { + "DATABASE_URL": "postgres://postgres:password@db:5432/visualization_tool" + }, + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // This can be used to network with other containers or with the host. + // "forwardPorts": [3000, 5432], + + // Use 'postCreateCommand' to run commands after the container is created. + "postCreateCommand": "yarn install", + + // Configure tool-specific properties. + "customizations": { + "vscode": { + "extensions": [ + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "ms-azuretools.vscode-docker", + "codeandstuff.package-json-upgrade" + ] + } + } + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml new file mode 100644 index 000000000..ca7f587de --- /dev/null +++ b/.devcontainer/docker-compose.yml @@ -0,0 +1,55 @@ +volumes: + postgres-data: + yarn-cache: + +services: + app: + build: + context: . + dockerfile: Dockerfile + + volumes: + - ../..:/workspaces:cached + - yarn-cache:/home/node/.yarn-cache + + environment: + YARN_CACHE_FOLDER: /home/node/.cache/yarn + + # Overrides default command so things don't shut down after the process ends. + command: sleep infinity + + # Use proper Docker networking instead of network_mode: service:db + # to ensure reliable DNS resolution in all environments + depends_on: + db: + condition: service_healthy + networks: + - app-network + + # Use "forwardPorts" in **devcontainer.json** to forward an app port locally. + # (Adding the "ports" property to this file will not forward from a Codespace.) + + db: + image: postgres:18-alpine + restart: unless-stopped + networks: + - app-network + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + volumes: + - postgres-data:/var/lib/postgresql + environment: + POSTGRES_PASSWORD: password + POSTGRES_USER: postgres + POSTGRES_DB: visualization_tool + + # Add "forwardPorts": ["5432"] to **devcontainer.json** to forward PostgreSQL locally. + # (Adding the "ports" property to this file will not forward from a Codespace.) + +networks: + app-network: + driver: bridge diff --git a/CHANGELOG.md b/CHANGELOG.md index ad618f37c..38a09b19b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,11 @@ You can also check the ## Unreleased +- Fixes + - Harden markdown renderer sanitization +- Maintenance + - Add dev-container support + ## 6.5.0 – 2026-06-01 - Features diff --git a/app/components/markdown.tsx b/app/components/markdown.tsx index c99a39682..7dca5c1fe 100644 --- a/app/components/markdown.tsx +++ b/app/components/markdown.tsx @@ -1,13 +1,44 @@ import clsx from "clsx"; import { ComponentProps } from "react"; import ReactMarkdown from "react-markdown"; -import rehypeRaw from "rehype-raw"; -import rehypeSanitize from "rehype-sanitize"; +import rehypeSanitize, { defaultSchema } from "rehype-sanitize"; import remarkGfm from "remark-gfm"; import classes from "@/components/markdown.module.css"; import { palette } from "@/themes/palette"; +const sanitizeSchema = { + ...defaultSchema, + tagNames: [ + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "p", + "strong", + "em", + "ins", + "del", + "s", + "ul", + "ol", + "li", + "blockquote", + "code", + "pre", + "br", + "hr", + "a", + ], + attributes: { + ...defaultSchema.attributes, + a: ["href", "title"], + "*": defaultSchema.attributes?.["*"] ?? [], + }, +}; + const components: ComponentProps["components"] = { h1: ({ children, className, ...props }) => (

@@ -46,10 +77,11 @@ const components: ComponentProps["components"] = { ), a: ({ children, className, style, ...props }) => ( {children} @@ -63,7 +95,7 @@ export const Markdown = ( ); @@ -81,7 +113,11 @@ export const InlineMarkdown = ({ p: ({ children }) => <>{children}, strong: ({ children }) => {children}, em: ({ children }) => {children}, - a: ({ children, href }) => {children}, + a: ({ children, href }) => { + const safeHref = + href && /^(https?|mailto|ircs?):/i.test(href) ? href : undefined; + return {children}; + }, h1: () => null, h2: () => null, h3: () => null, @@ -163,10 +199,11 @@ const componentsInheritFonts: ComponentProps< ), a: ({ children, className, style, ...props }) => ( {children} @@ -180,7 +217,7 @@ export const MarkdownInheritFonts = ( ); diff --git a/app/package.json b/app/package.json index 2aa8f0394..cf046c6d1 100644 --- a/app/package.json +++ b/app/package.json @@ -153,7 +153,6 @@ "react-transition-group": "^4.4.5", "react-virtualized-auto-sizer": "^1.0.25", "react-window": "^1.8.10", - "rehype-raw": "^7.0.0", "rehype-sanitize": "^6.0.0", "remark-gfm": "^4.0.0", "simple-statistics": "^7.6.0", diff --git a/package.json b/package.json index a8633a338..e5fb0ce82 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "dev:ssl": "preconstruct dev && NODE_OPTIONS=\"--inspect --openssl-legacy-provider \" next dev --experimental-https ./app", "dev:mitmproxy": "preconstruct dev && HTTP_PROXY=http://localhost:8080 NODE_EXTRA_CA_CERTS=~/.mitmproxy/mitmproxy-ca-cert.pem GLOBAL_AGENT_ENVIRONMENT_VARIABLE_NAMESPACE='' NO_PROXY='localhost,127.0.0.1' next ./app", "dev:rollup": "rollup -c rollup.config.js --watch", - "db:migrate:dev": "DATABASE_URL=postgres://postgres:password@localhost:5432/visualization_tool prisma db push", + "db:migrate:dev": "DATABASE_URL=${DATABASE_URL:-postgres://postgres:password@localhost:5432/visualization_tool} prisma db push", "db:migrate": "[ -z \"$DATABASE_URL\" ] && echo 'Bypassing database migration, DATABASE_URL not set' || prisma db push", "build": "yarn graphql:codegen && lingui compile && rollup -c rollup.config.js && yarn storybook:build && yarn generate-json-schema && next build ./app", "build:npm": "yarn graphql:codegen && lingui compile && BABEL_ENV=NPM_PACKAGE preconstruct build", diff --git a/readme/dev.md b/readme/dev.md index 0c0228d2d..04aed7425 100644 --- a/readme/dev.md +++ b/readme/dev.md @@ -114,3 +114,20 @@ yarn db:migrate:dev > scoping the new environment variables to the preview branch you are working > on. After merging the branch, you can delete the environment variables scoped > to the branch. + +# Dev-containers + +You can use dev-containers for development if you want to have a consistent +environment. + +Codeium based IDE do not support some convenience functionality like "Rebuild +container" after changes to the devcontainer configuration. The devcontainer-cli +is shipped with the devcontainer image and can be used to rebuild the +devcontainer: + +```bash +devcontainer up --workspace-folder /workspaces/visualization-tool --remove-existing-container --build-no-cache +``` + +⚠️ Supporting/ Using dev-containers is currently under development and not fully +tested, please report if you run into issues.