Compare commits

..

2 Commits

Author SHA1 Message Date
LeChristopher Blackwell
5884573a69 fix: validate X-Refreshed-Token before storing it as the auth token (#971)
* fix: validate X-Refreshed-Token before storing it as the auth token

authenticatedFetch stored any X-Refreshed-Token response header value
straight into localStorage. A malformed or injected header value would
silently overwrite the stored auth token, breaking every subsequent
authenticated request. Only accept values that match the JWT shape the
server actually issues (three base64url segments).

* fix: apply refreshed-token validation to file upload path

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GYpHw7VYW1d2HwfL9EZKx4

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 15:38:23 +03:00
Simos Mikelatos
123d244a51 fix: harden docker cloudcli install 2026-07-08 09:22:10 +00:00
5 changed files with 43 additions and 9 deletions

View File

@@ -14,18 +14,22 @@ jobs:
strategy:
fail-fast: false
matrix:
template: [claude-code, codex, gemini]
template: [claude-code, codex]
steps:
- uses: actions/checkout@v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@v4
with:
platforms: arm64
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
with:
version: latest
- name: Log in to Docker Hub
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -40,7 +44,7 @@ jobs:
echo "tags=$TAGS" >> "$GITHUB_OUTPUT"
- name: Build and push
uses: docker/build-push-action@v6
uses: docker/build-push-action@v7
with:
context: ./docker
file: ./docker/${{ matrix.template }}/Dockerfile

View File

@@ -5,7 +5,16 @@ COPY shared/install-cloudcli.sh /tmp/install-cloudcli.sh
RUN chmod +x /tmp/install-cloudcli.sh && /tmp/install-cloudcli.sh
USER agent
RUN npm install -g @cloudcli-ai/cloudcli && cloudcli --version
RUN --mount=type=cache,target=/tmp/npm-cache,sharing=locked,mode=0777 \
npm install -g @cloudcli-ai/cloudcli \
--cache=/tmp/npm-cache \
--fetch-retries=5 \
--fetch-retry-mintimeout=20000 \
--fetch-retry-maxtimeout=120000 \
--fetch-timeout=600000 \
--no-audit \
--no-fund && \
cloudcli --version
COPY --chown=agent:agent shared/start-cloudcli.sh /home/agent/.cloudcli-start.sh
RUN echo '. ~/.cloudcli-start.sh' >> /home/agent/.bashrc

View File

@@ -5,7 +5,16 @@ COPY shared/install-cloudcli.sh /tmp/install-cloudcli.sh
RUN chmod +x /tmp/install-cloudcli.sh && /tmp/install-cloudcli.sh
USER agent
RUN npm install -g @cloudcli-ai/cloudcli && cloudcli --version
RUN --mount=type=cache,target=/tmp/npm-cache,sharing=locked,mode=0777 \
npm install -g @cloudcli-ai/cloudcli \
--cache=/tmp/npm-cache \
--fetch-retries=5 \
--fetch-retry-mintimeout=20000 \
--fetch-retry-maxtimeout=120000 \
--fetch-timeout=600000 \
--no-audit \
--no-fund && \
cloudcli --version
COPY --chown=agent:agent shared/start-cloudcli.sh /home/agent/.cloudcli-start.sh
RUN echo '. ~/.cloudcli-start.sh' >> /home/agent/.bashrc

View File

@@ -3,6 +3,7 @@ import type { DragEvent } from 'react';
import { IS_PLATFORM } from '../../../constants/config';
import type { Project } from '../../../types/app';
import { isValidRefreshedToken } from '../../../utils/api';
import {
MAX_FILE_UPLOAD_COUNT,
MAX_FILE_UPLOAD_SIZE_BYTES,
@@ -131,7 +132,7 @@ const uploadFormDataWithProgress = (
xhr.onload = () => {
const refreshedToken = xhr.getResponseHeader('X-Refreshed-Token');
if (refreshedToken) {
if (isValidRefreshedToken(refreshedToken)) {
localStorage.setItem('auth-token', refreshedToken);
}

View File

@@ -1,5 +1,16 @@
import { IS_PLATFORM } from "../constants/config";
// Only accept a refreshed token that has this app's issued JWT shape
// (three base64url segments). An attacker-injected/malformed header value
// must never overwrite the stored auth token.
/**
* @param {unknown} token
* @returns {token is string}
*/
export const isValidRefreshedToken = (token) =>
typeof token === 'string' &&
/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(token);
// Utility function for authenticated API calls
export const authenticatedFetch = (url, options = {}) => {
const token = localStorage.getItem('auth-token');
@@ -23,7 +34,7 @@ export const authenticatedFetch = (url, options = {}) => {
},
}).then((response) => {
const refreshedToken = response.headers.get('X-Refreshed-Token');
if (refreshedToken) {
if (isValidRefreshedToken(refreshedToken)) {
localStorage.setItem('auth-token', refreshedToken);
}
return response;