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)
게스트(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=) 추가
잔여 이슈
/api/packages가 게스트(gradeLevel=9) 목록 조회를 차단하지 않는다. 의도한 것인지 확인 필요./api/activity는 인증 없이 접근 가능하다(server.js:1799, authSession 없음). 내부 이벤트 노출 여부 검토 필요.- 대형 파일 업로드(50MB 이상)는 현 구조에서 불가. 청크 업로드 또는 직접 packages/ 적재(
upload-pkg) 경로를 안내서에 명시 필요. - 게시판 개념 신설 시 계정 자유가입 여부 결정 필요. 현재는 슈퍼관리자만 계정 생성 가능하다(server.js:1095~1110).
관련 파일
| 파일 | 역할 |
|---|---|
/Users/ef/crowny-cowork/server.js | 메인 HTTP 서버 + 모든 라우트 (2109줄) |
/Users/ef/crowny-cowork/auth-tier.js | 6등급 인증·세팅번들·블록체인 |
/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 | 지원 티켓 |