← 목록
기타 2026-06-27 9KB 읽기 10분

cowork 백엔드 감사 보고서 (2026-06-27)

감사 대상: /Users/ef/crowny-cowork/ 초점: 파일업로드·패키지다운로드·로그/보고/공유·게시판 재기획


1. 파일 업로드 — 다른 컴퓨터에서 실제로 되는가?

결론: 된다. 단 multipart 미지원, 50MB 이하, base64 필수

1-1. 인증 흐름

/api/upload (server.js:1536) 는 두 경로를 모두 허용한다.

  • 구 워커 토큰: X-Cowork-Token 헤더 → authWorker() (server.js:1538)
  • 신 세션 토큰: Authorization: Bearer <token>authSession() (server.js:1542)
2026-06-27 rev15에서 두 방식이 OR 관계로 통합됐다. 이전에는 워커 토큰만 허용해 세션 로그인 사용자가 업로드 불가였다.

게스트(gradeLevel=9)는 명시 차단한다 (server.js:1543).

1-2. 본문 크기 한계 — 50MB 하드컷

js// server.js:974
req.on('data', c => { body += c; if (body.length > 50 * 1024 * 1024) { body = ''; req.destroy(); } });

JSON 문자열 누적 방식이라 50MB 초과 시 연결을 즉시 파괴한다. 파일은 base64로 인코딩해 보내야 하므로 실제 전송 가능 파일 크기는 약 37.5MB 이하다.

1-3. multipart 미지원 — JSON base64만 가능

서버 전체에 multipart/form-data 파싱 코드가 없다. 브라우저 <input type="file"> 직접 전송 불가.

전송 방식은 반드시 아래 형태여야 한다.

bash# cowork-cli.sh:723~731
CONTENT=$(base64 < "$FILE")
curl -X POST /api/upload \
  -H "Authorization: Bearer $TOK" \
  -H "Content-Type: application/json" \
  -d "{\"filename\":\"파일명\",\"content\":\"$CONTENT\",\"description\":\"설명\"}"

서버는 Buffer.from(body.content, 'base64') (server.js:1555)로 디코드해 쓰므로 바이너리(zip, exe 등)도 손상 없이 저장된다.

1-4. 저장 경로

  • 일반 업로드: data/uploads/<uploaderId>/TIMESTAMP_파일명 (server.js:169)
  • dest:'package' 지정 시: packages/<basename> 직접 적재 → /api/download 즉시 배포 (server.js:1563)
  • 단, dest:'package'는 로그인 세션(Bearer)만 가능. 워커 토큰으로는 불가 (server.js:1560).
  • 1-5. 막히는 지점 요약

    지점내용파일:라인
    multipart 미지원브라우저 폼 직접 전송 불가. JSON+base64 필수server.js:971~979
    50MB 컷base64 오버헤드 감안 실제 ~37MB 이하만 가능server.js:974
    게스트 차단gradeLevel=9 계정은 업로드 불가server.js:1543
    dest:package 워커 불가X-Cowork-Token(워커) 기반으로는 packages/ 직접 적재 불가server.js:1560

    2. 패키지 다운로드 — 티어 게이팅 정확성

    결론: 인증 게이팅은 완전하나 티어별 차등 없음

    2-1. /api/packages (패키지 목록)

    server.js:1829~1861

    jsconst s = tier.authSession(req);
    if (!s) return json(res, { error: '로그인 필요' }, 401);
    

    미인증이면 401. 그러나 게스트(gradeLevel=9) 차단 코드가 없다. 게스트도 목록을 볼 수 있다.

    2-2. /api/download/<파일> (패키지 다운로드)

    server.js:1864~1884

    jslet s = tier.authSession(req);
    if (!s) { const qt = url.searchParams.get('token'); if (qt) s = tier.authSession({...}); }
    if (!s) return json(res, { error: '로그인 필요' }, 401);
    

    미인증이면 401. 경로 탈출은 path.basename()으로 차단한다 (server.js:1871).

    그러나 canAccessTier() 호출이 없다. 등급과 무관하게 로그인만 되면 모든 패키지를 내려받을 수 있다. 티어1~4, 슈퍼관리자, 게스트 모두 /api/packages+/api/download 접근이 동일하다.

    CLAUDE.md에 "패키지 다운로드 전면 인증게이팅 완료"라고 기록됐지만, 실제로는 로그인 여부만 보고 등급별 차등은 없다.

    2-3. 세팅 번들(/api/setting-bundle)과의 차이

    세팅 번들은 canAccessTier() + 게스트 명시 차단 (server.js:1128~1134)이 모두 있다. 패키지 다운로드만 등급 게이팅이 빠져 있다.


    3. 로그·보고·공유 — 어디까지 되고 무엇이 빠졌나

    결론: 로그는 있다, 보고·공유 API는 없다

    3-1. 현재 되는 것 — activity.json

    js// server.js:224~231
    function logActivity(workerId, action, detail) {
      log.push({ time, worker: workerId, action, detail });
      if (log.length > 10000) log = log.slice(-5000);
      fs.writeFileSync(ACTIVITY_FILE, ...);
    }
    

    모든 주요 이벤트(로그인·업로드·다운로드·커밋·학습·지원티켓 등)가 data/activity.json에 쌓인다. 현재 12169줄. GET /api/activity?limit=N으로 조회 가능 (server.js:1799).

    단, /api/activity는 인증 없이 접근 가능하다 (라우트에 authSession 없음).

    3-2. 현재 되는 것 — 업로드→docs 자동 기록

    handleUpload() 내부의 syncToDocsServer() (server.js:188~219)가 업로드 이벤트를 CrownyDoc에 .md 파일로 기록한다. 단, 업로드 이벤트에만 적용된다.

    3-3. 현재 되는 것 — 오너 통합 피드

    GET /api/feed (server.js:1270)는 지원 티켓 댓글 + 태스크 + 협업 작업 + 오너 메시지를 시간순으로 통합 조회한다 (owner.js:36~52). 단, 슈퍼관리자(gradeLevel=0)만 볼 수 있다.

    3-4. 빠진 것 — 보고·공유 API

    기능상태비고
    활동 보고서 생성없음activity.json 원본 노출만, 기간/계정 필터링 집계 없음
    로그 공유(내보내기)없음타 계정·외부 시스템에 전달하는 경로 없음
    알림 구독(webhook)없음지원 pending LaunchAgent 알림만, 일반 이벤트 push 없음
    activity → docs 자동화부분적upload 이벤트만 docs에 기록, 나머지는 수동
    공개 피드없음activity가 공개이지만 /api/feed는 슈퍼만

    4. 게시판(board) 재기획 기반 — 현재 자원 재활용 가능성

    결론: support.js + collab.js 구조를 확장하면 게시판 가능

    4-1. 재활용 가능한 자원

    Q&A 게시판 → support.js 확장

    support-tickets.json은 이미 {id, from, type, title, body, comments[], status, platform}을 갖추고 있다 (support.js:53~68). type 값에 'board'를 추가하면 일반 게시물로 전환 가능하다.

    댓글 스레드, 증분 폴링(messagesSince), 재오픈 로직도 그대로 쓸 수 있다.

    공지/작업 게시판 → collab.js 확장

    works.json{id, account, title, message, files, status, comments[], archive} 구조다 (collab.js:33~44). 현재 오너 승인 후 docs 보관까지 자동화돼 있다. 공개 조회 권한만 추가하면 결과물 공유 게시판으로 쓸 수 있다.

    통합 피드 → owner.js의 feed() 확장

    feed() (owner.js:36~52)는 지원·태스크·작업·오너 메시지를 하나로 합산한다. 현재 슈퍼만 볼 수 있는데, 등급별 필터로 공개 범위를 조절하면 전체 구성원 피드가 된다.

    4-2. 게시판 신설 시 필요한 것

    항목현황필요 작업
    계정 자유가입없음 (슈퍼만 생성 가능, server.js:1095)/api/register/account 공개 라우트 추가
    게시물 공개 읽기works는 본인+슈퍼만 (server.js:1373)GET /api/board 공개 필터 추가
    게시물 검색없음/api/board/search?q= 라우트 신설
    파일 첨부 (브라우저)JSON base64만 가능multipart 파서 추가 또는 포털 base64 변환
    좋아요/반응없음works/support 구조에 reactions[] 필드 추가

    4-3. 데이터 재활용 권장 방안

    • type:'board' 게시물: support.createTicket()을 그대로 호출 (type='board' 추가)
    • 결과물 공유: collab.submitWork() → want.share=true → 오너 미승인 상태도 본인+티어별 공개 허용
    • 활동 피드: GET /api/activity에 인증 + 필터(action=, worker=, since=) 추가

    잔여 이슈

    1. /api/packages가 게스트(gradeLevel=9) 목록 조회를 차단하지 않는다. 의도한 것인지 확인 필요.
    2. /api/activity는 인증 없이 접근 가능하다(server.js:1799, authSession 없음). 내부 이벤트 노출 여부 검토 필요.
    3. 대형 파일 업로드(50MB 이상)는 현 구조에서 불가. 청크 업로드 또는 직접 packages/ 적재(upload-pkg) 경로를 안내서에 명시 필요.
    4. 게시판 개념 신설 시 계정 자유가입 여부 결정 필요. 현재는 슈퍼관리자만 계정 생성 가능하다(server.js:1095~1110).

    관련 파일

    파일역할
    /Users/ef/crowny-cowork/server.js메인 HTTP 서버 + 모든 라우트 (2109줄)
    /Users/ef/crowny-cowork/auth-tier.js6등급 인증·세팅번들·블록체인
    /Users/ef/crowny-cowork/collab.js협업 허브 (works.json, 오너 승인)
    /Users/ef/crowny-cowork/support.js지원 티켓 + 실시간 댓글
    /Users/ef/crowny-cowork/tasks.js디바이스 간 태스크 교환
    /Users/ef/crowny-cowork/owner.js오너 피드 + 오너→세션 채널
    /Users/ef/crowny-cowork/data/activity.json전체 이벤트 로그 (12169줄)
    /Users/ef/crowny-cowork/data/works.json협업 작업 목록
    /Users/ef/crowny-cowork/data/support-tickets.json지원 티켓