Haeminway haeminway
English
기술 노트로
2 분 분량

웹앱 배포 설정 하나가 보안 모델을 정한다: Execute as / 접근 권한

‘누구로 실행’ × ‘누가 접근’ 조합이 데이터 권한과 코드 공개 여부를 결정한다. 공개 앱은 앱 레벨 인증을 따로 설계해야 한다.

웹앱 배포의 두 선택: “누구 권한으로 실행”과 “누가 접근”이 보안 모델을 결정한다. 코드를 짜기 전에 정하고 문서에 남겨야 나중에 권한 사고가 안 난다.

Execute as의미주의
Me (배포자)항상 배포자 권한으로 실행사용자별 권한 분리가 필요한 앱엔 부적합
User accessing접속자 권한으로 실행사용자 OAuth 동의·scope 누락 처리 필요

왜 중요한가

Execute as me로 “Anyone”에 공개하면, 접속한 누구나 배포자 권한으로 시트·메일을 건드릴 수 있게 된다. 입력 검증과 인증을 따로 안 하면 공개 엔드포인트가 그대로 뚫린다. 비용은 데이터 유출이나 오남용이다.

정직한 함정: 소스 공개

“Anyone with Google account”로 배포하면 프로젝트 공유가 필요해 고객이 소스를 열람할 수 있다. 코드 비공개가 계약 조건이면 “Me + Anyone” + 자체 앱 레벨 인증(이메일 코드 + CacheService 만료 등)으로 가야 한다.

공개 doPost는 tier로 지킨다

function doPost(e) {
  try {
    const body = JSON.parse(e.postData.contents || "{}");
    if (!ALLOWED.has(body.action)) throw new Error("unknown action");
    // payload의 함수명을 직접 실행하지 않는다 (allowlist만)
    return json({ ok: true, result: handle(body) });
  } catch (err) {
    return json({ ok: false, error: String(err.message || err) });
  }
}
  • minimal: API key + handler allowlist + 입력 검증 (내부/저위험)
  • standard: + timestamp/nonce replay 방지 + AuditLog (운영 데이터 쓰기)
  • hardened: + HMAC 서명 + 함수별 ACL + rate limit (외부 공개·민감)

깊이: CORS는 simple request로

외부 브라우저에서 GAS를 호출할 땐 application/json 대신 text/plain;charset=utf-8을 기본값으로 둬 preflight를 피한다. 서버는 e.postData.contents를 직접 파싱하고, 오류도 HTTP status가 아니라 JSON body로 반환한다.

핵심 한 줄: 실행 주체·접근 범위·인증을 배포 전에 정하고, payload 함수명을 절대 직접 실행하지 마라.

자주 묻는 질문

Execute as Me로 Anyone에 공개하면 어떤 위험이 생기나요?
접속한 누구나 배포자 권한으로 시트와 메일을 건드릴 수 있게 됩니다. 입력 검증과 인증을 따로 구현하지 않으면 공개 엔드포인트가 그대로 뚫립니다.
Anyone with Google account 배포 시 소스 코드가 노출될 수 있나요?
네. 이 옵션은 프로젝트 공유가 필요하기 때문에 접속자가 소스를 열람할 수 있습니다. 코드 비공개가 계약 조건이라면 Me + Anyone 조합에 자체 앱 레벨 인증을 추가해야 합니다.
공개 doPost 엔드포인트를 최소한으로 보호하려면 무엇이 필요한가요?
API 키, 핸들러 allowlist, 입력 검증이 기본(minimal) 티어입니다. payload의 함수명을 직접 실행하지 않고 allowlist에 등록된 핸들러만 호출해야 합니다.