From 8a19c686cbb3f52b816b23669ac9b65d929f915f Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Sat, 23 May 2026 02:20:10 +0800 Subject: [PATCH] ci: address code scanning scorecard alerts (#4275) --- .github/workflows/docker-image.yml | 25 +++++++++++-------- .github/workflows/vercel-merge.yml | 3 +-- apps/readest-app/package.json | 1 + .../src/__tests__/utils/misc.test.ts | 22 ++++++++++++++++ apps/readest-app/src/utils/misc.ts | 4 +-- pnpm-lock.yaml | 16 ++++++++++++ 6 files changed, 57 insertions(+), 14 deletions(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 10c1ac49..ea678714 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -15,10 +15,12 @@ concurrency: permissions: contents: read - packages: write jobs: build: + permissions: + contents: read + packages: write runs-on: ${{ matrix.runner }} strategy: fail-fast: false @@ -42,10 +44,10 @@ jobs: submodules: recursive - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 - name: Log in to GHCR - uses: docker/login-action@v3 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 with: registry: ghcr.io username: ${{ github.actor }} @@ -53,13 +55,13 @@ jobs: - name: Docker meta (for labels) id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5 with: images: ghcr.io/${{ github.repository_owner }}/readest - name: Build and push by digest id: build - uses: docker/build-push-action@v6 + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 with: context: . file: ./Dockerfile @@ -87,6 +89,9 @@ jobs: merge: needs: build + permissions: + contents: read + packages: write runs-on: ubuntu-latest steps: - name: Download digests @@ -97,10 +102,10 @@ jobs: merge-multiple: true - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 - name: Log in to GHCR - uses: docker/login-action@v3 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 with: registry: ghcr.io username: ${{ github.actor }} @@ -117,7 +122,7 @@ jobs: - name: Log in to Docker Hub if: steps.dockerhub.outputs.enabled == 'true' - uses: docker/login-action@v3 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -125,7 +130,7 @@ jobs: - name: Extract image metadata (GHCR only) id: meta-ghcr if: steps.dockerhub.outputs.enabled != 'true' - uses: docker/metadata-action@v5 + uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5 with: images: ghcr.io/${{ github.repository_owner }}/readest tags: | @@ -137,7 +142,7 @@ jobs: - name: Extract image metadata (GHCR + Docker Hub) id: meta-all if: steps.dockerhub.outputs.enabled == 'true' - uses: docker/metadata-action@v5 + uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5 with: images: | ghcr.io/${{ github.repository_owner }}/readest diff --git a/.github/workflows/vercel-merge.yml b/.github/workflows/vercel-merge.yml index ef2ab59d..99f38f06 100644 --- a/.github/workflows/vercel-merge.yml +++ b/.github/workflows/vercel-merge.yml @@ -10,7 +10,6 @@ jobs: runs-on: ubuntu-latest permissions: contents: read - deployments: write steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: @@ -18,7 +17,7 @@ jobs: - uses: amondnet/vercel-action@de09aeac2ace6599ec9b11ef87558759a496bac4 # v42 with: vercel-token: ${{ secrets.VERCEL_TOKEN }} - github-token: ${{ secrets.GITHUB_TOKEN }} + github-comment: false vercel-args: '--prod' vercel-org-id: ${{ secrets.ORG_ID}} vercel-project-id: ${{ secrets.PROJECT_ID}} diff --git a/apps/readest-app/package.json b/apps/readest-app/package.json index 7eb961aa..749bb22f 100644 --- a/apps/readest-app/package.json +++ b/apps/readest-app/package.json @@ -235,6 +235,7 @@ "cpx2": "^8.0.0", "daisyui": "^4.12.24", "dotenv-cli": "^7.4.4", + "fast-check": "^4.8.0", "i18next-scanner": "^4.6.0", "jsdom": "^28.1.0", "mkdirp": "^3.0.1", diff --git a/apps/readest-app/src/__tests__/utils/misc.test.ts b/apps/readest-app/src/__tests__/utils/misc.test.ts index f6e92c15..57abcbf0 100644 --- a/apps/readest-app/src/__tests__/utils/misc.test.ts +++ b/apps/readest-app/src/__tests__/utils/misc.test.ts @@ -1,3 +1,4 @@ +import fc from 'fast-check'; import { describe, it, expect } from 'vitest'; import { makeSafeFilename } from '../../utils/misc'; @@ -53,6 +54,11 @@ describe('makeSafeFilename', () => { expect(makeSafeFilename('LPT9')).toBe('LPT9_'); }); + it('should handle reserved names after trimming whitespace', () => { + expect(makeSafeFilename(' CON ')).toBe('CON_'); + expect(makeSafeFilename(' prn ')).toBe('prn_'); + }); + it('should not affect reserved names with extensions', () => { // Reserved names only apply to the base name without extension const result = makeSafeFilename('CON.txt'); @@ -375,4 +381,20 @@ describe('makeSafeFilename', () => { expect(result).toBe('https___example.com_book.pdf'); }); }); + + describe('Property checks', () => { + it('should keep sanitized filenames bounded, safe, trimmed, and stable', () => { + fc.assert( + fc.property(fc.string({ maxLength: 1000 }), (filename) => { + const result = makeSafeFilename(filename); + + expect(new TextEncoder().encode(result).length).toBeLessThanOrEqual(250); + expect(result).not.toMatch(/[<>:%#"\/\\|?*\x00-\x1F]/); + expect(result).toBe(result.trim()); + expect(makeSafeFilename(result)).toBe(result); + }), + { numRuns: 200 }, + ); + }); + }); }); diff --git a/apps/readest-app/src/utils/misc.ts b/apps/readest-app/src/utils/misc.ts index e375e667..a421e831 100644 --- a/apps/readest-app/src/utils/misc.ts +++ b/apps/readest-app/src/utils/misc.ts @@ -15,7 +15,7 @@ export const makeSafeFilename = (filename: string, replacement = '_') => { // Unsafe to use filename including file extensions over 255 bytes on Android const maxFilenameBytes = 250; - let safeName = filename.replace(unsafeCharacters, replacement); + let safeName = filename.replace(unsafeCharacters, replacement).trim(); if (reservedFilenames.test(safeName)) { safeName = `${safeName}${replacement}`; @@ -29,7 +29,7 @@ export const makeSafeFilename = (filename: string, replacement = '_') => { utf8Bytes = encoder.encode(safeName); } - return safeName.trim(); + return safeName; }; export const getLocale = () => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 330a8afc..6a32195e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -508,6 +508,9 @@ importers: dotenv-cli: specifier: ^7.4.4 version: 7.4.4 + fast-check: + specifier: ^4.8.0 + version: 4.8.0 i18next-scanner: specifier: ^4.6.0 version: 4.6.0(typescript@5.9.3) @@ -5542,6 +5545,10 @@ packages: engines: {node: '>= 10.17.0'} hasBin: true + fast-check@4.8.0: + resolution: {integrity: sha512-GOJ158CUMnN6cSahsv4+ExARvIDuzzinFjkp0E9WtiBa5zcVeLozVkWaE4IzFcc+Y48Wp1EDlUZsXRyAztQcSg==} + engines: {node: '>=12.17.0'} + fast-deep-equal@2.0.1: resolution: {integrity: sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==} @@ -7300,6 +7307,9 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + pure-rand@8.4.0: + resolution: {integrity: sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A==} + qs@6.15.1: resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==} engines: {node: '>=0.6'} @@ -14300,6 +14310,10 @@ snapshots: transitivePeerDependencies: - supports-color + fast-check@4.8.0: + dependencies: + pure-rand: 8.4.0 + fast-deep-equal@2.0.1: {} fast-deep-equal@3.1.3: {} @@ -16422,6 +16436,8 @@ snapshots: punycode@2.3.1: {} + pure-rand@8.4.0: {} + qs@6.15.1: dependencies: side-channel: 1.1.0