diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 737132f8..f3e19972 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -5,6 +5,10 @@ on: branches: - all-blocks +permissions: + contents: write + pages: write + jobs: build-and-deploy: runs-on: ubuntu-latest diff --git a/.github/workflows/emoji-helper.yml b/.github/workflows/emoji-helper.yml index 8965a1a2..da62d3e7 100644 --- a/.github/workflows/emoji-helper.yml +++ b/.github/workflows/emoji-helper.yml @@ -4,6 +4,9 @@ on: release: types: [published] +permissions: + contents: write + jobs: emoji: runs-on: ubuntu-latest diff --git a/.github/workflows/issue-labeled.yml b/.github/workflows/issue-labeled.yml index a3e8b5fc..e4a7fa6d 100644 --- a/.github/workflows/issue-labeled.yml +++ b/.github/workflows/issue-labeled.yml @@ -4,6 +4,9 @@ on: issues: types: [labeled] +permissions: + issues: write + jobs: reply-helper: runs-on: ubuntu-latest diff --git a/.github/workflows/issue-open-check.yml b/.github/workflows/issue-open-check.yml index 36442132..5e7bea25 100644 --- a/.github/workflows/issue-open-check.yml +++ b/.github/workflows/issue-open-check.yml @@ -4,6 +4,9 @@ on: issues: types: [opened, edited] +permissions: + issues: write + jobs: check-issue: runs-on: ubuntu-latest diff --git a/.github/workflows/preview-deploy.yml b/.github/workflows/preview-deploy.yml index 21a07cc1..16e35d52 100644 --- a/.github/workflows/preview-deploy.yml +++ b/.github/workflows/preview-deploy.yml @@ -26,7 +26,14 @@ jobs: - name: save PR id id: pr - run: echo "::set-output name=id::$(> "$GITHUB_OUTPUT" - name: download dist artifact uses: dawidd6/action-download-artifact@v6 @@ -84,7 +91,14 @@ jobs: - name: save PR id id: pr - run: echo "::set-output name=id::$(> "$GITHUB_OUTPUT" - name: The job failed uses: actions-cool/maintain-one-comment@v1.2.1 diff --git a/.gitignore b/.gitignore index e54692b2..b9b3f6a2 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,6 @@ screenshot .firebase build + +# worktrees +.worktrees diff --git a/mock/listTableList.ts b/mock/listTableList.ts index ddb857c2..17a990d2 100644 --- a/mock/listTableList.ts +++ b/mock/listTableList.ts @@ -106,11 +106,6 @@ function getRule(req: Request, res: Response, u: string) { } function postRule(req: Request, res: Response, u: string, b: Request) { - let realUrl = u; - if (!realUrl || Object.prototype.toString.call(realUrl) !== '[object String]') { - realUrl = req.url; - } - const body = b?.body || req.body; const { method, name, desc, key } = body; diff --git a/src/pages/user/login/index.tsx b/src/pages/user/login/index.tsx index c27e4bcd..ac9a5df3 100644 --- a/src/pages/user/login/index.tsx +++ b/src/pages/user/login/index.tsx @@ -118,6 +118,27 @@ const Login: React.FC = () => { const { message } = App.useApp(); const intl = useIntl(); + /** + * Validate redirect URL to prevent open redirect attacks + * Only allow same-origin relative paths starting with '/' + */ + const getSafeRedirectUrl = (redirect: string | null): string => { + if (!redirect || !redirect.startsWith('/')) return '/'; + + // Block protocol-relative URLs (//example.com) + if (redirect.startsWith('//')) return '/'; + + try { + const parsed = new URL(redirect, window.location.origin); + // Only allow same-origin URLs + if (parsed.origin !== window.location.origin) return '/'; + // Return the path with query and hash preserved + return `${parsed.pathname}${parsed.search}${parsed.hash}`; + } catch { + return '/'; + } + }; + const fetchUserInfo = async () => { const userInfo = await initialState?.fetchUserInfo?.(); if (userInfo) { @@ -142,7 +163,8 @@ const Login: React.FC = () => { message.success(defaultLoginSuccessMessage); await fetchUserInfo(); const urlParams = new URL(window.location.href).searchParams; - window.location.href = urlParams.get('redirect') || '/'; + const redirectUrl = getSafeRedirectUrl(urlParams.get('redirect')); + window.location.href = redirectUrl; return; } console.log(msg); diff --git a/src/service-worker.js b/src/service-worker.js index b76e4049..a38a2f3e 100644 --- a/src/service-worker.js +++ b/src/service-worker.js @@ -45,6 +45,12 @@ workbox.routing.registerRoute( /** Response to client after skipping waiting with MessageChannel */ addEventListener('message', (event) => { + // Security: Verify origin to prevent cross-origin attacks + // Use self.location.origin for dynamic origin validation (works across all deployment domains) + if (event.origin !== self.location.origin) { + return; + } + const replyPort = event.ports[0]; const message = event.data; if (replyPort && message && message.type === 'skip-waiting') {