diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7c5ebec..799f30f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,8 +29,8 @@ jobs: outputs: code: ${{ steps.filter.outputs.code }} steps: - - uses: actions/checkout@v4 - - uses: dorny/paths-filter@v3 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 id: filter with: filters: | @@ -51,12 +51,12 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: fetch-depth: 0 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: 24 cache: npm @@ -82,7 +82,7 @@ jobs: - name: Upload coverage if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: coverage-report path: coverage/ diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index 7d81309..9c89f95 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -7,18 +7,19 @@ on: - '.github/workflows/**' - '.github/actions/**' -permissions: - contents: write - pull-requests: write +permissions: read-all jobs: auto-merge: runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write if: github.actor == 'dependabot[bot]' steps: - name: Fetch Dependabot metadata id: metadata - uses: dependabot/fetch-metadata@v2 + uses: dependabot/fetch-metadata@21025c705c08248db411dc16f3619e6b5f9ea21a # v2.5.0 with: github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 6ebeaed..a2a6c13 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -25,9 +25,7 @@ concurrency: group: docker-publish cancel-in-progress: false -permissions: - contents: read - packages: write +permissions: read-all env: REGISTRY: ghcr.io @@ -37,6 +35,9 @@ jobs: build-and-push: runs-on: ubuntu-latest timeout-minutes: 20 + permissions: + contents: read + packages: write # For workflow_run: only run if the Release workflow succeeded AND a new # release was actually created (check for a tag matching the latest release). # For release/workflow_dispatch: always run. @@ -47,7 +48,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Determine version id: version @@ -83,15 +84,15 @@ jobs: - name: Set up QEMU if: steps.version.outputs.skip != 'true' - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0 - name: Set up Docker Buildx if: steps.version.outputs.skip != 'true' - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - name: Log in to GHCR if: steps.version.outputs.skip != 'true' - uses: docker/login-action@v3 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} @@ -100,7 +101,7 @@ jobs: - name: Extract metadata if: steps.version.outputs.skip != 'true' id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | @@ -109,7 +110,7 @@ jobs: - name: Build and push if: steps.version.outputs.skip != 'true' - uses: docker/build-push-action@v6 + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2 with: context: . platforms: linux/amd64,linux/arm64 diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 30fc7c3..f24af1e 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -8,10 +8,7 @@ on: - 'package.json' workflow_dispatch: -permissions: - contents: read - pages: write - id-token: write +permissions: read-all concurrency: group: pages @@ -21,9 +18,9 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: 24 cache: npm @@ -34,18 +31,21 @@ jobs: - run: npm run docs:build - - uses: actions/configure-pages@v5 + - uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0 - - uses: actions/upload-pages-artifact@v3 + - uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1 with: path: docs/.vitepress/dist deploy: needs: build runs-on: ubuntu-latest + permissions: + pages: write + id-token: write environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} steps: - id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5 diff --git a/.github/workflows/release-pr-checks.yml b/.github/workflows/release-pr-checks.yml index b8c1552..1e6d8d6 100644 --- a/.github/workflows/release-pr-checks.yml +++ b/.github/workflows/release-pr-checks.yml @@ -29,10 +29,7 @@ on: workflows: [Release] types: [completed] -permissions: - contents: read - statuses: write - security-events: write +permissions: read-all jobs: # Gate: only run for release-please PRs. Finds PR details from either trigger. @@ -73,12 +70,12 @@ jobs: timeout-minutes: 10 steps: - name: Checkout PR code - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: ref: ${{ needs.should-run.outputs.head_sha }} - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: 24 cache: npm @@ -105,6 +102,8 @@ jobs: runs-on: ubuntu-latest if: always() needs: [should-run, lint-and-check] + permissions: + statuses: write steps: - name: Check CI status run: | @@ -152,23 +151,24 @@ jobs: timeout-minutes: 15 permissions: security-events: write + contents: read steps: - name: Checkout PR code - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: ref: ${{ needs.should-run.outputs.head_sha }} - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 with: languages: javascript-typescript config-file: .github/codeql/codeql-config.yml queries: security-extended - name: Autobuild - uses: github/codeql-action/autobuild@v3 + uses: github/codeql-action/autobuild@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 - name: Perform CodeQL analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 with: category: '/language:javascript-typescript' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 63c40cb..75769ee 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,20 +10,21 @@ on: # That workflow posts commit statuses to the PR head SHA so branch protection # sees the results. Docker publishing uses docker-publish.yml (also workflow_run). -permissions: - contents: write - pull-requests: write +permissions: read-all jobs: release-please: runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write outputs: pr: ${{ steps.rp.outputs.pr }} release_created: ${{ steps.rp.outputs.release_created }} steps: - name: Run release-please id: rp - uses: googleapis/release-please-action@v4 + uses: googleapis/release-please-action@16a9c90856f42705d54a6fda1823352bdc62cf38 # v4.4.0 with: config-file: release-please-config.json manifest-file: .release-please-manifest.json diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index fc455d7..3659312 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -18,18 +18,18 @@ jobs: id-token: write steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: persist-credentials: false - name: Run Scorecard - uses: ossf/scorecard-action@v2.4.3 + uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 with: results_file: results.sarif results_format: sarif publish_results: true - name: Upload SARIF - uses: github/codeql-action/upload-sarif@v3 + uses: github/codeql-action/upload-sarif@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 with: sarif_file: results.sarif diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 56e8ae4..cdff3ce 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -39,8 +39,8 @@ jobs: outputs: security_relevant: ${{ steps.filter.outputs.security_relevant }} steps: - - uses: actions/checkout@v4 - - uses: dorny/paths-filter@v3 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 id: filter with: filters: | @@ -64,20 +64,20 @@ jobs: security-events: write steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 with: languages: javascript-typescript config-file: .github/codeql/codeql-config.yml queries: security-extended - name: Autobuild - uses: github/codeql-action/autobuild@v3 + uses: github/codeql-action/autobuild@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 - name: Perform CodeQL analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 with: category: '/language:javascript-typescript' @@ -90,7 +90,7 @@ jobs: contents: read steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: fetch-depth: 0 @@ -109,10 +109,10 @@ jobs: if: needs.changes.outputs.security_relevant == 'true' || github.event_name == 'schedule' steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: 24 cache: npm @@ -130,13 +130,13 @@ jobs: if: needs.changes.outputs.security_relevant == 'true' || github.event_name == 'schedule' steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - name: Build Docker image - uses: docker/build-push-action@v6 + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2 with: context: . push: false @@ -146,7 +146,7 @@ jobs: cache-to: type=gha,mode=max - name: Upload image artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: docker-image path: /tmp/scrolly-image.tar @@ -158,13 +158,13 @@ jobs: needs: [build-image] steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: sparse-checkout: .trivyignore sparse-checkout-cone-mode: false - name: Download image artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: docker-image path: /tmp @@ -193,7 +193,7 @@ jobs: image: semgrep/semgrep:1.112.0 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Run Semgrep run: semgrep scan --config=auto --sarif --output=semgrep-results.sarif . @@ -202,7 +202,7 @@ jobs: - name: Upload Semgrep SARIF if: always() - uses: github/codeql-action/upload-sarif@v3 + uses: github/codeql-action/upload-sarif@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 with: sarif_file: semgrep-results.sarif category: semgrep @@ -216,10 +216,10 @@ jobs: github.event.pull_request.head.repo.fork != true steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Download image artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: docker-image path: /tmp @@ -250,7 +250,7 @@ jobs: exit 1 - name: Run OWASP ZAP baseline scan - uses: zaproxy/action-baseline@v0.14.0 + uses: zaproxy/action-baseline@7c4deb10e6261301961c86d65d54a516394f9aed # v0.14.0 with: target: http://localhost:3000 rules_file_name: .zap/rules.tsv diff --git a/.gitignore b/.gitignore index defdae2..5480696 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,4 @@ docs/.vitepress/dist *.swo *~ .vscode/ +REFACTOR-TRACKER.md diff --git a/SECURITY.md b/SECURITY.md index 7d3715a..0b74bc5 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -10,9 +10,42 @@ **Please do not report security vulnerabilities through public GitHub issues.** -Instead, please report them via [GitHub Security Advisories](https://github.com/312-dev/scrolly/security/advisories/new). +Instead, please report them via [GitHub Security Advisories](https://github.com/312-dev/scrolly/security/advisories/new). This ensures that sensitive vulnerability information is handled privately and responsibly. -You should receive a response within 48 hours. If the issue is confirmed, a patch will be released as soon as possible. +### What to Include + +When reporting a vulnerability, please provide: + +- A description of the vulnerability and its potential impact +- Detailed steps to reproduce the issue +- Any proof-of-concept code or screenshots +- The affected version(s) and configuration +- Your suggested fix or mitigation, if any + +## Vulnerability Disclosure Policy + +We follow a coordinated vulnerability disclosure process: + +1. **Reporter submits** a vulnerability via [GitHub Security Advisories](https://github.com/312-dev/scrolly/security/advisories/new) +2. **We acknowledge** receipt within 48 hours +3. **We investigate** and validate the report within 7 days +4. **We develop and test** a fix for confirmed vulnerabilities +5. **We release** a patch and publish a security advisory +6. **Public disclosure** occurs after the fix is released + +We ask that reporters refrain from publicly disclosing the vulnerability until a fix has been released. + +## Response Timeline + +| Action | Timeframe | +|--------|-----------| +| Acknowledgement of report | Within 48 hours | +| Initial assessment and validation | Within 7 days | +| Status update to reporter | Every 7 days until resolved | +| Patch development and release | Within 30 days for critical/high severity | +| Public disclosure | After patch release | + +If a vulnerability is accepted, we will work with the reporter to coordinate disclosure timing. If a vulnerability is declined (e.g., out of scope or not reproducible), we will explain our reasoning. ## Security Practices @@ -29,16 +62,18 @@ You should receive a response within 48 hours. If the issue is confirmed, a patc ### Infrastructure - Multi-stage Docker builds with minimal runtime image - Health check endpoint at `/api/health` -- Non-root process execution recommended +- Non-root process execution in containers ### CI/CD Security - **CodeQL SAST** — Static analysis on every PR and push +- **Semgrep** — Additional static analysis with community rules - **Gitleaks** — Secret detection across full git history - **npm audit** — Dependency vulnerability scanning -- **Trivy** — Container image scanning (MEDIUM+ severity) -- **OWASP ZAP** — Dynamic application security testing +- **Trivy** — Container image scanning (HIGH/CRITICAL severity) +- **OWASP ZAP** — Dynamic application security testing (DAST) - **OpenSSF Scorecard** — Supply chain security assessment -- **Dependabot** — Automated dependency updates +- **Dependabot** — Automated dependency updates (npm and GitHub Actions) +- **GitHub Actions pinned by SHA** — All workflow actions pinned to commit hashes ### Self-Hosting Security Checklist - [ ] Generate a strong `SESSION_SECRET` (32+ random bytes) diff --git a/package-lock.json b/package-lock.json index 1e897d1..5871ebc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -218,6 +218,7 @@ "integrity": "sha512-Nt9hri7nbOo0RipAsGjIssHkpLMHHN/P7QqENywAq5TLsoYDzUyJGny8FEiD/9KJUxtGH8blGpMedilI6kK3rA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@algolia/client-common": "5.49.1", "@algolia/requester-browser-xhr": "5.49.1", @@ -2551,6 +2552,7 @@ "integrity": "sha512-iAIPEahFgDJJyvz8g0jP08KvqnM6JvdW8YfsygZ+pMeMvyM2zssWMltcsotETvjSZ82G3VlitgDtBIvpQSZrTA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", @@ -2593,6 +2595,7 @@ "integrity": "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "deepmerge": "^4.3.1", @@ -2632,6 +2635,7 @@ "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@types/node": "*" } @@ -3023,6 +3027,7 @@ "integrity": "sha512-RpV6r/ij22zRRdyBPcxDeKAzH43phWVKEjL2iksqo1Vz3CuBUrgmPpPhALKiRfU7OMCmeeO9vECBMsV0hMTG8Q==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.18.0" } @@ -3109,6 +3114,7 @@ "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/types": "8.56.1", @@ -3709,6 +3715,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3761,6 +3768,7 @@ "integrity": "sha512-X3Pp2aRQhg4xUC6PQtkubn5NpRKuUPQ9FPDQlx36SmpFwwH2N0/tw4c+NXV3nw3PsgeUs+BuWGP0gjz3TvENLQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@algolia/abtesting": "1.15.1", "@algolia/client-abtesting": "5.49.1", @@ -3961,6 +3969,7 @@ "integrity": "sha512-8VYKM3MjCa9WcaSAI3hzwhmyHVlH8tiGFwf0RlTsZPWJ1I5MkzjiudCo4KC4DxOaL/53A5B1sI/IbldNFDbsKA==", "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" @@ -4200,6 +4209,7 @@ "integrity": "sha512-opLQzEVriiH1uUQ4Kctsd49bRoFDXGGSC4GUqj7pGyxM3RehRhvTlZJc1FL/Flew2p5uwxa1tUDWKzI4wNM8pg==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@chevrotain/cst-dts-gen": "11.1.2", "@chevrotain/gast": "11.1.2", @@ -4518,13 +4528,17 @@ } }, "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/copy-anything": { @@ -4672,6 +4686,7 @@ "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.10" } @@ -5116,6 +5131,7 @@ "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "dev": true, "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -5795,6 +5811,7 @@ "integrity": "sha512-uYixubwmqJZH+KLVYIVKY1JQt7tysXhtj21WSvjcSmU5SVNzMus1bgLe+pAt816yQ8opKfheVVoPLqvVMGejYw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", @@ -5914,22 +5931,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint-plugin-sonarjs/node_modules/minimatch": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.1.tgz", - "integrity": "sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/eslint-plugin-svelte": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.15.0.tgz", @@ -6267,6 +6268,7 @@ "integrity": "sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "tabbable": "^6.4.0" } @@ -8211,6 +8213,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -8382,6 +8385,7 @@ "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -8691,6 +8695,7 @@ "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -9314,6 +9319,7 @@ "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.53.6.tgz", "integrity": "sha512-lP5DGF3oDDI9fhHcSpaBiJEkFLuS16h92DhM1L5K1lFm0WjOmUh1i2sNkBBk8rkxJRpob0dBE75jRfUzGZUOGA==", "license": "MIT", + "peer": true, "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", @@ -9654,6 +9660,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -9838,6 +9845,7 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -10866,6 +10874,7 @@ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -10926,6 +10935,7 @@ "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/expect": "4.0.18", "@vitest/mocker": "4.0.18", @@ -11059,6 +11069,7 @@ "integrity": "sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vue/compiler-dom": "3.5.29", "@vue/compiler-sfc": "3.5.29", diff --git a/package.json b/package.json index a70f1f8..2c1da0a 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,10 @@ "uuid": "^13.0.0", "web-push": "^3.6.7" }, + "overrides": { + "minimatch": ">=10.2.3", + "cookie": ">=0.7.0" + }, "devDependencies": { "@commitlint/cli": "^20.4.2", "@commitlint/config-conventional": "^20.4.2", diff --git a/src/app.html b/src/app.html index 0136c35..5986ff4 100644 --- a/src/app.html +++ b/src/app.html @@ -15,8 +15,8 @@ - - + + diff --git a/src/hooks.server.ts b/src/hooks.server.ts index c46ca98..3eadb8f 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -71,7 +71,7 @@ function setThemeCookies(event: RequestEvent, response: Response): void { if (event.locals.user?.themePreference && !cookies.includes('scrolly_theme=')) { response.headers.append( 'Set-Cookie', - `scrolly_theme=${event.locals.user.themePreference};Path=/;Max-Age=31536000;SameSite=Lax` + `scrolly_theme=${event.locals.user.themePreference};Path=/;Max-Age=31536000;SameSite=Lax;Secure` ); } @@ -80,7 +80,7 @@ function setThemeCookies(event: RequestEvent, response: Response): void { const accentValue = encodeURIComponent(JSON.stringify({ hex: accent.hex, dark: accent.dark })); response.headers.append( 'Set-Cookie', - `scrolly_accent=${accentValue};Path=/;Max-Age=31536000;SameSite=Lax` + `scrolly_accent=${accentValue};Path=/;Max-Age=31536000;SameSite=Lax;Secure` ); } } @@ -98,7 +98,14 @@ export const handle: Handle = async ({ event, resolve }) => { if (userId) { const data = await getUserWithGroup(userId); if (data && !data.user.removedAt) { - event.locals.user = data.user; + // Only expose authenticated user to routes if onboarding is complete. + // Users with empty username haven't finished onboarding and should not + // have access to group data via API routes. The /api/auth onboarding + // actions read the cookie directly via getUserIdFromCookies, so they + // still work without locals.user. + if (data.user.username) { + event.locals.user = data.user; + } event.locals.group = data.group; } } diff --git a/src/lib/commentsApi.ts b/src/lib/commentsApi.ts index 0a446cb..96f06a6 100644 --- a/src/lib/commentsApi.ts +++ b/src/lib/commentsApi.ts @@ -101,7 +101,7 @@ export async function toggleCommentHeart( } export function markCommentsRead(clipId: string): void { - for (const type of ['comment', 'reply']) { + for (const type of ['comment', 'reply', 'mention']) { fetch('/api/notifications/mark-read', { method: 'POST', headers: { 'Content-Type': 'application/json' }, diff --git a/src/lib/components/ActionSidebar.svelte b/src/lib/components/ActionSidebar.svelte index 262b8ec..953e5de 100644 --- a/src/lib/components/ActionSidebar.svelte +++ b/src/lib/components/ActionSidebar.svelte @@ -104,7 +104,14 @@ -