"아직 사용자도 없는데 보안이 왜 필요해?"
이 질문을 스스로에게도 했습니다. 사용자 0명인 서비스에 OWASP 보안 감사라니. 과하지 않나? 하지만 한 번 터지면 끝이라는 걸 알고 있었습니다. 특히 채용 데이터를 다루는 서비스에서 개인정보 유출이 발생하면, 사업 자체가 끝납니다.
그래서 사용자가 오기 전에 먼저 보안을 잡기로 했습니다. AI에게 맡겨서.
• • •
1인 SaaS도 해킹당합니다
"우리 같은 작은 서비스를 누가 해킹하겠어?"라는 생각은 위험합니다. 실제로 자동화된 봇이 인터넷을 스캔하면서 취약한 서비스를 찾습니다. 서비스 크기와 상관없이요.
클라우드에 배포한 첫 주에 서버 로그를 보고 놀랐습니다. 아무에게도 알리지 않은 URL인데, 하루 수십 건의 비정상 요청이 들어오고 있었습니다. /admin, /wp-login.php, /.env 같은 경로를 자동으로 탐색하는 봇들이었습니다.
실제 로그에서 발견된 공격 시도들
GET /admin — 관리자 페이지 탐색
GET /.env — 환경 변수(API 키, DB 비밀번호) 탈취 시도
GET /wp-login.php — WordPress 기본 로그인 페이지 탐색
POST /api/login (대량) — 브루트포스 로그인 시도
GET /api/users?id=1 OR 1=1 — SQL 인젝션 시도
이걸 보고 보안이 선택이 아니라 필수라는 걸 체감했습니다. 사용자가 0명이어도, 서비스가 인터넷에 올라간 순간부터 공격 대상이 됩니다.
• • •
OWASP Top 10이란
OWASP(Open Web Application Security Project)는 웹 애플리케이션 보안에 대한 오픈 프로젝트입니다. "Top 10"은 가장 흔하고 위험한 10가지 보안 취약점 목록인데, 업계 표준으로 통합니다.
OWASP Top 10 (2021) — Convince-X 대응 현황
A01 Broken Access ControlDONE
A02 Cryptographic FailuresDONE
A03 Injection (XSS, NoSQL)DONE
A04 Insecure DesignDONE
A05 Security MisconfigurationDONE
A06 Vulnerable ComponentsDONE
A07 Auth FailuresDONE
A08 Software & Data IntegrityDONE
A09 Logging & MonitoringDONE
A10 SSRFDONE
10개 항목 전체를 대응한다는 건, 1인 개발자 입장에서 상당한 작업량입니다. 하지만 AI가 각 항목에 대한 구체적인 대응 방안을 제안하고, 코드를 작성하고, 테스트를 만들어줬기 때문에 가능했습니다.
• • •
XSS: 보이지 않는 위협
XSS(Cross-Site Scripting)는 공격자가 웹사이트에 악성 스크립트를 주입하는 공격입니다. 예를 들어, JD 입력 필드에 <script>alert('hacked')</script>를 넣으면, 다른 사용자의 브라우저에서 그 스크립트가 실행될 수 있습니다.
채용 서비스에서 XSS가 특히 위험한 이유가 있습니다. JD나 이력서 데이터에 악성 코드를 심으면, 그 데이터를 열람하는 다른 리크루터나 관리자의 세션을 탈취할 수 있습니다.
대응 방법은 세 겹입니다.
1층: 입력 살균
모든 사용자 입력에서 HTML 태그와 스크립트를 제거합니다. DOMPurify 라이브러리로 서버 사이드 살균.
2층: 출력 인코딩
데이터를 화면에 렌더링할 때 HTML 엔티티로 인코딩. <가 <로 변환되어 실행 불가.
3층: CSP 헤더
Content Security Policy 헤더로 인라인 스크립트 실행 자체를 차단. 브라우저 레벨 방어.
보안 미들웨어가 CSP 헤더를 포함한 여러 보안 헤더를 한 번에 설정해줍니다. 미들웨어 한 줄로 X-Content-Type-Options, X-Frame-Options, Strict-Transport-Security 등 10여 개의 보안 헤더가 적용됩니다.
• • •
인증과 세션 보안
Convince-X는 OAuth + 토큰 기반 보안 인증 방식으로 인증을 처리합니다. 사용자가 소셜 계정으로 로그인하면, 서버가 인증 토큰을 발급하고, 이후 모든 API 요청에 이 토큰이 포함됩니다.
보안의 핵심 포인트들.
| 보안 항목 |
위험 |
대응 |
| 토큰 저장 위치 |
클라이언트 저장소에 저장 시 XSS로 탈취 가능 |
보안 쿠키에 저장 — JS로 접근 불가 |
| 토큰 만료 |
토큰 탈취 시 영구 사용 가능 |
다중 보안 레이어 (토큰 만료 + 갱신 회전) |
| 요청 위변조 |
쿠키 자동 전송으로 위조 요청 |
요청 위변조 방지 + SameSite 쿠키 |
| NoSQL 인젝션 |
DB 쿼리 조작 |
입력값 검증 + DB 쿼리 보호 |
보안 쿠키 설정은 단순하지만 강력한 방어입니다. JavaScript로 쿠키에 접근할 수 없으니, XSS가 성공하더라도 토큰 탈취가 불가능합니다. 이 한 가지 설정만으로 공격 표면이 크게 줄어듭니다.
• • •
Rate Limiting과 브루트포스
Rate Limiting은 "일정 시간 내 요청 횟수를 제한하는 것"입니다. 왜 필요한가? 브루트포스 공격 때문입니다.
브루트포스는 비밀번호나 토큰을 무차별 대입하는 공격입니다. Google OAuth를 쓰니 비밀번호 브루트포스는 없지만, API 엔드포인트에 대한 무차별 요청은 여전히 위험합니다. 서버 자원을 소진시켜 서비스를 마비시킬 수 있습니다.
Rate Limiting 전략
일반 API: 표준 요청 제한 적용
인증 관련: 엄격한 요청 제한 (브루트포스 방지)
AI 분석: 사용자별 시간당 제한
파일 업로드: 사용자별 시간당 제한
→ 엔드포인트별로 다르게 설정하는 게 핵심
모든 엔드포인트에 같은 제한을 걸면 안 됩니다. 로그인은 엄격하게, 검색은 약간 느슨하게. 사용자 경험과 보안 사이의 균형입니다.
• • •
414건의 보안 테스트
보안 대응을 코드로 구현하는 것과, 그 구현이 실제로 작동하는지 검증하는 것은 별개의 문제입니다. 그래서 414건의 보안 테스트를 만들었습니다.
테스트는 단순히 "방어 코드가 있는지" 확인하는 게 아니라, "실제 공격 패턴을 시뮬레이션"합니다. 예를 들어 XSS 테스트는 실제 공격자가 사용하는 수십 가지 인코딩 우회 패턴을 입력으로 넣어봅니다.
재미있는 건, 테스트를 만드는 과정에서 실제 취약점이 발견됐다는 겁니다. 입력값 살균이 빠진 엔드포인트 3곳, 요청 위변조 방지 미검증 라우트 2곳. 테스트를 "만들었더니" 버그가 "잡혔습니다". 보안 테스트는 확인 도구인 동시에 발견 도구입니다.
보안 테스트가 발견한 취약점: 5건. 테스트가 없었다면 이 5건은 프로덕션에 그대로 올라갔을 겁니다.
• • •
보안은 기능이 아니라 기반입니다
보안을 "기능"으로 생각하면 후순위로 밀립니다. "먼저 기능을 만들고 나중에 보안을 추가하자"는 접근은 위험합니다. 나중에 추가하려면 기존 코드를 전부 뜯어고쳐야 하거든요.
Convince-X에서 보안은 처음부터 기반에 깔았습니다. 보안 미들웨어, 입력 살균, 보안 쿠키 — 이것들은 첫 번째 기능을 만들기 전에 설정했습니다.
보안을 나중에 하면
• 기존 코드 전체 리팩토링 필요
• "어디가 취약한지" 파악부터 난관
• 기능과 보안이 충돌 → 타협
• 사고 나서야 대응 (사후 약방문)
보안을 처음부터 하면
• 기반 설정 후 기능 추가 → 자연스러움
• 새 기능마다 자동으로 보안 적용
• 414건 테스트가 지속적 검증
• 프로덕션 보안 인시던트 0건
이 글을 쓰는 시점에 Convince-X의 프로덕션 보안 인시던트는 0건입니다. 물론 사용자가 적어서일 수도 있습니다. 하지만 414건의 보안 테스트가 매 배포마다 전부 통과하는 한, 최소한의 안전망은 갖춰져 있다고 말할 수 있습니다.
1인 SaaS라서 보안을 대충 해도 된다? 아닙니다. 1인이라서 더 철저해야 합니다. 대기업은 보안 사고가 나도 팀이 대응합니다. 1인은 사고 = 사업 종료입니다. AI가 보안 감사를 대신해주는 시대에, 비용 핑계는 더 이상 안 통합니다.
보안은 기능이 아니라 기반입니다. 건물의 철근 같은 것입니다. 보이지 않지만, 없으면 무너집니다.
"Why do you need security when you have no users?"
I asked myself this too. An OWASP security audit for a service with zero users? Seems excessive. But I knew that one breach means game over. Especially for a service handling recruitment data -- a personal information leak would end the business entirely.
So I decided to lock down security before users arrived. By handing the audit to AI.
• • •
Even Solo SaaS Gets Hacked
"Who would bother hacking a small service like ours?" is a dangerous assumption. Automated bots constantly scan the internet for vulnerable services, regardless of size.
In the first week after deploying to the cloud, I checked the server logs and was stunned. Nobody had been told about the URL, yet dozens of anomalous requests per day were hitting the server. Bots automatically probing paths like /admin, /wp-login.php, and /.env.
Actual Attack Attempts Found in Logs
GET /admin -- Admin page discovery
GET /.env -- Environment variable theft (API keys, DB passwords)
GET /wp-login.php -- WordPress default login probe
POST /api/login (high volume) -- Brute-force login attempts
GET /api/users?id=1 OR 1=1 -- SQL injection attempt
This made it viscerally clear: security is not optional. Even with zero users, the moment a service goes live on the internet, it becomes a target.
• • •
What Is OWASP Top 10
OWASP (Open Web Application Security Project) is an open project for web application security. The "Top 10" is a list of the most common and critical security vulnerabilities, widely accepted as the industry standard.
OWASP Top 10 (2021) -- Convince-X Compliance Status
A01 Broken Access ControlDONE
A02 Cryptographic FailuresDONE
A03 Injection (XSS, NoSQL)DONE
A04 Insecure DesignDONE
A05 Security MisconfigurationDONE
A06 Vulnerable ComponentsDONE
A07 Auth FailuresDONE
A08 Software & Data IntegrityDONE
A09 Logging & MonitoringDONE
A10 SSRFDONE
Addressing all 10 items is significant work for a solo developer. But it was achievable because AI proposed specific remediation strategies for each item, wrote the code, and created the tests.
• • •
XSS: The Invisible Threat
XSS (Cross-Site Scripting) is an attack where malicious scripts are injected into a website. For example, inserting <script>alert('hacked')</script> into a JD input field could execute that script in another user's browser.
XSS is particularly dangerous in recruitment services. Embedding malicious code in JD or resume data can hijack the sessions of other recruiters or admins who view that data.
The defense is three layers deep:
Layer 1: Input Sanitization
Strip HTML tags and scripts from all user input. Server-side sanitization via DOMPurify.
Layer 2: Output Encoding
Encode data as HTML entities when rendering. < becomes <, preventing script execution.
Layer 3: CSP Headers
Content Security Policy headers block inline script execution entirely. Browser-level defense.
Security middleware sets CSP headers alongside many other security headers in one go. A single middleware line applies X-Content-Type-Options, X-Frame-Options, Strict-Transport-Security, and 10+ other security headers.
• • •
Authentication & Session Security
Convince-X handles authentication via OAuth + token-based security. When a user logs in with their social account, the server issues an auth token included in all subsequent API requests.
Key security considerations:
| Security Item |
Risk |
Mitigation |
| Token Storage |
Client-side storage vulnerable to XSS theft |
HttpOnly cookies -- inaccessible via JS |
| Token Expiry |
Stolen tokens usable indefinitely |
Multi-layer security (token expiry + refresh rotation) |
| Request Forgery |
Cookie auto-send enables forged requests |
CSRF protection + SameSite cookies |
| NoSQL Injection |
DB query manipulation |
Input validation + query parameterization |
The HttpOnly cookie setting is simple but powerful. JavaScript can't access the cookie, so even a successful XSS attack can't steal the token. This single configuration dramatically reduces the attack surface.
• • •
Rate Limiting & Brute Force
Rate limiting means restricting the number of requests within a given time period. Why? Because of brute-force attacks.
Brute-force attacks involve systematically trying passwords or tokens. Since we use Google OAuth, password brute-forcing isn't a concern. But mass requests against API endpoints remain dangerous -- they can exhaust server resources and crash the service.
Rate Limiting Strategy
General API: Standard request limits
Authentication: Strict limits (brute-force prevention)
AI Analysis: Per-user hourly caps
File Upload: Per-user hourly caps
→ The key is different limits for different endpoints
You can't apply the same limits everywhere. Login gets strict limits; search gets looser ones. It's the balance between user experience and security.
• • •
414 Security Tests
Implementing security in code and verifying that it actually works are two separate problems. That's why we created 414 security tests.
These tests don't just check "does defense code exist?" -- they simulate real attack patterns. The XSS tests, for example, inject dozens of encoding bypass patterns actually used by attackers.
The interesting part: the process of writing tests uncovered real vulnerabilities. Three endpoints missing input sanitization, two routes without CSRF verification. "Writing" the tests "caught" the bugs. Security tests serve as both verification and discovery tools.
Vulnerabilities found by security tests: 5. Without those tests, all 5 would have shipped to production.
• • •
Security Is Infrastructure, Not a Feature
If you think of security as a "feature," it gets deprioritized. "Build features first, add security later" is dangerous. By the time you try to retrofit it, you need to rewrite existing code entirely.
At Convince-X, security was baked into the foundation from day one. Security middleware, input sanitization, HttpOnly cookies -- all configured before the first feature was built.
Security Later
• Full codebase refactoring required
• Identifying vulnerabilities is the first challenge
• Features vs. security conflicts → compromises
• Incident response only (reactive, not proactive)
Security First
• Foundation set, then features layered on naturally
• Every new feature inherits security automatically
• 414 tests provide continuous verification
• Zero production security incidents
As of this writing, Convince-X has zero production security incidents. That might be because we have few users. But as long as 414 security tests pass with every deployment, we can confidently say the safety net is in place.
Security can be skipped because it's a solo SaaS? No. Being solo means you need to be more thorough. Large companies have teams to respond to breaches. For a solo founder, breach = business over. In an era when AI can run security audits for you, cost is no longer an excuse.
Security isn't a feature -- it's infrastructure. Like rebar in a building. You can't see it, but without it, everything collapses.