Skip to content
Closed
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,13 @@
**Vulnerability:** The standard user authentication routes (login, register, and reset-password) did not have a maximum length constraint on passwords. This allows an attacker to supply extremely long strings, which `bcrypt` will try to hash, causing CPU exhaustion and creating a Denial of Service (DoS) vulnerability.
**Learning:** `bcrypt` (and `bcryptjs`) is intentionally slow. While `bcrypt` may internally truncate passwords to 72 bytes, depending on the implementation the input string processing itself or the full string parsing before truncation can be very costly. In this codebase, the admin authentication correctly checked for a max length, but user schemas did not.
**Prevention:** Always enforce a maximum string length limit (e.g. `.max(1024)`) on user inputs that will be passed into expensive algorithms like bcrypt hashing.

## 2025-02-18 - [ERD ๋ชจ๋ธ์˜ SQL ์ธ์ ์…˜ ๋ฐ ์ƒํƒœ ๋ณ€์ด ์šฐํšŒ ์ทจ์•ฝ์  ์ˆ˜์ •]
**Vulnerability:** `ERDModel` ํด๋ž˜์Šค์—์„œ ์ปฌ๋Ÿผ ํƒ€์ž… ๋ฐ ๊ธฐ๋ณธ๊ฐ’์— ๋Œ€ํ•œ ์ •๊ทœ์‹ ๊ฒ€์ฆ์ด ๋ˆ„๋ฝ๋˜์–ด ์•…์˜์ ์ธ SQL ๋ฌธ๋ฒ•์ด ์‚ฝ์ž…๋  ์ˆ˜ ์žˆ์—ˆ์œผ๋ฉฐ, ๋‚ด๋ถ€ ์ƒํƒœ ๊ฐ์ฒด(ํ…Œ์ด๋ธ”)๊ฐ€ ์ง์ ‘ ๋ฐ˜ํ™˜๋˜์–ด ๊ฒ€์ฆ ๋กœ์ง์„ ์šฐํšŒํ•˜์—ฌ ์ƒํƒœ ๋ณ€์ด๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
**Learning:** DDL ์ƒ์„ฑ ์‹œ ์ž์œ  ํ…์ŠคํŠธ๋‚˜ ํƒ€์ž… ์ž…๋ ฅ์ด ํฌํ•จ๋  ๋•Œ๋Š” ๋ฐ˜๋“œ์‹œ ํ—ˆ์šฉ ๋ชฉ๋ก(Allowlist) ๊ธฐ๋ฐ˜ ์ •๊ทœ์‹์„ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋ฉฐ, ๊ฐ์ฒด ์ƒํƒœ๋ฅผ ๋ฐ˜ํ™˜ํ•  ๋•Œ๋Š” ์บก์Аํ™”๋ฅผ ๋ณด์žฅํ•˜๊ธฐ ์œ„ํ•ด ๊นŠ์€ ๋ณต์‚ฌ(Deep Copy)๋ฅผ ์ˆ˜ํ–‰ํ•ด์•ผ ๊ฒ€์ฆ ์šฐํšŒ๋ฅผ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
**Prevention:** ์ •๊ทœ์‹(์˜ˆ: `SAFE_SQL_TYPE`, `SAFE_SQL_DEFAULT_VALUE`)์„ ๋„์ž…ํ•˜์—ฌ ํƒ€์ž…๊ณผ ๊ธฐ๋ณธ๊ฐ’์„ ๊ฒ€์ฆํ•˜๊ณ , ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•  ๋•Œ `JSON.parse(JSON.stringify(table))`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์›๋ณธ ์ฐธ์กฐ๋ฅผ ์ˆจ๊น๋‹ˆ๋‹ค.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

## 2026-07-02 - [Fix High Severity Vulnerability in js-yaml]
**Vulnerability:** `js-yaml` versions 4.0.0 through versions below 4.3.0 were affected by quadratic CPU consumption in YAML merge-key chains (CVE-2026-59869, GHSA-52cp-r559-cp3m), allowing crafted YAML input to cause denial of service. Version 4.3.0 is patched.
**Learning:** `pnpm` ํ™˜๊ฒฝ์—์„œ ์„œ๋“œํŒŒํ‹ฐ ํŒจํ‚ค์ง€์˜ ํ•˜์œ„ ์˜์กด์„ฑ์— ์กด์žฌํ•˜๋Š” ์ทจ์•ฝ์ ์„ ์ˆ˜์ •ํ•  ๋•Œ, ์ง์ ‘์ ์ธ `package.json` ์—…๋ฐ์ดํŠธ๋กœ ํ•ด๊ฒฐ๋˜์ง€ ์•Š๋Š”๋‹ค๋ฉด `pnpm.overrides`๋ฅผ ์ ๊ทน ํ™œ์šฉํ•˜์—ฌ ์ „์ฒด ํ”„๋กœ์ ํŠธ ์ˆ˜์ค€์—์„œ ํŠน์ • ์•ˆ์ „ํ•œ ๋ฒ„์ „์„ ๊ฐ•์ œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
**Prevention:** `pnpm audit`๊ณผ ๊ฐ™์€ ๋„๊ตฌ๋ฅผ ์ฃผ๊ธฐ์ ์œผ๋กœ ์‹คํ–‰ํ•˜์—ฌ ์ทจ์•ฝ์ ์„ ์ ๊ฒ€ํ•˜๊ณ , ๋ฃจํŠธ `package.json`์˜ `"pnpm": { "overrides": { ... } }` ๊ตฌ๋ฌธ์„ ์‚ฌ์šฉํ•ด ์ทจ์•ฝ์ ์ด ํŒจ์น˜๋œ ๋ฒ„์ „์„ ๊ณ ์ •(pin)ํ•ฉ๋‹ˆ๋‹ค.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### ๐Ÿ›ก๏ธ ๋ณด์•ˆ (Security)

- ERD DDL ์ƒ์„ฑ ๊ฒฝ๊ณ„๋ฅผ ๊ตฌ์กฐ์  allow-list ๋ฐฉ์‹์œผ๋กœ ๊ฐ•ํ™”ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ง€์›ํ•˜๋Š” PostgreSQL ํƒ€์ž… ๋ฌธ๋ฒ•๊ณผ ์ œํ•œ๋œ side-effect-free ๊ธฐ๋ณธ๊ฐ’๋งŒ ํ—ˆ์šฉํ•˜๊ณ , ์ปฌ๋Ÿผ ์ œ์•ฝยท์ž„์˜ ํ•จ์ˆ˜ ํ˜ธ์ถœยท์ž˜๋ชป๋œ ๋Ÿฐํƒ€์ž„ ํƒ€์ž…์„ ๊ฑฐ๋ถ€ํ•ฉ๋‹ˆ๋‹ค. ๊ฒ€์ฆ๋œ ์ปฌ๋Ÿผยท์™ธ๋ž˜ ํ‚ค๋Š” ๋ชจ๋ธ ์†Œ์œ  ๋ณต์‚ฌ๋ณธ์œผ๋กœ ์ €์žฅํ•˜๋ฉฐ, ๋ชจ๋“  ์‹๋ณ„์ž๋ฅผ quoted identifier๋กœ ์ถœ๋ ฅํ•ด ์˜ˆ์•ฝ์–ด ์ถฉ๋Œ๊ณผ ๊ฒ€์ฆ ํ›„ ๊ฐ์ฒด ๋ณ€์ด ์šฐํšŒ๋ฅผ ์ฐจ๋‹จํ•ฉ๋‹ˆ๋‹ค. ๊ด€๋ จ ํšŒ๊ท€ ํ…Œ์ŠคํŠธ์™€ `docs/doctoring/erd-ddl-injection-boundary.md`์— ์œ„ํ˜‘ ๋ชจ๋ธยท๋กค๋ฐฑยทAPA 7 ๊ทผ๊ฑฐ๋ฅผ ๊ธฐ๋กํ–ˆ์Šต๋‹ˆ๋‹ค.
- ๋กœ๊ทธ์ธ, ํšŒ์›๊ฐ€์ž…, ๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ •์ด ํ•˜๋‚˜์˜ ๊ณต์œ  ๋น„๋ฐ€๋ฒˆํ˜ธ ๊ณ„์•ฝ์„ ์‚ฌ์šฉํ•˜๋„๋ก ํ†ตํ•ฉํ–ˆ์Šต๋‹ˆ๋‹ค. ์ž…๋ ฅ ์ฒ˜๋ฆฌ๋Ÿ‰์„ 1,024์ž๋กœ ๋จผ์ € ์ œํ•œํ•˜๊ณ , ํ˜„์žฌ `bcryptjs`๊ฐ€ ์™„์ „ํ•˜๊ฒŒ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ๋Š” 72 UTF-8 ๋ฐ”์ดํŠธ๋ฅผ ์ดˆ๊ณผํ•˜๋Š” ๊ฐ’์€ ์กฐ์šฉํžˆ ์ž˜๋ผ๋‚ด์ง€ ์•Š๊ณ  ๊ฑฐ๋ถ€ํ•ฉ๋‹ˆ๋‹ค. ASCII์™€ ๋‹ค์ค‘ ๋ฐ”์ดํŠธ Unicode ๊ฒฝ๊ณ„ ํšŒ๊ท€ ํ…Œ์ŠคํŠธ ๋ฐ ์šด์˜ยทํ‘œ์ค€ ๊ทผ๊ฑฐ ๋ฌธ์„œ๋ฅผ ํ•จ๊ป˜ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.

### โšก ์„ฑ๋Šฅ (Performance)
Expand Down
76 changes: 76 additions & 0 deletions docs/doctoring/erd-ddl-injection-boundary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# ERD DDL injection boundary

## Scope

`packages/web/src/lib/erd.ts` generates PostgreSQL `CREATE TABLE` text from the in-memory ERD model. This is a code-generation boundary rather than a normal parameterized DML query: SQL identifiers, data types, and selected default expressions occupy grammar positions where bind parameters are not generally available. The safe contract is therefore **positive allow-list validation plus explicit identifier quoting**, not deny-listing individual punctuation characters.

This document governs the `ERDModel` surface only. Generated DDL is still executable administrative input and must be reviewed and executed under least privilege in downstream migration tooling.

## Threat model

The relevant attacker controls or influences table/column metadata before `ERDModel.generateDDL()` is called. The security goals are:

1. A value supplied as a data type cannot smuggle a column constraint, reference, check expression, second statement, comment, operator, or unknown type grammar into the generated DDL.
2. A default value cannot invoke an arbitrary database function or inject another SQL statement.
3. A caller cannot mutate a previously validated `Column` or `ForeignKey` object after insertion and thereby change model-owned state without validation.
4. Accepted snake_case names remain valid even if they coincide with PostgreSQL keywords, because every generated identifier is double-quoted.
5. Malformed runtime values that bypass TypeScript's compile-time types fail closed before they reach grammar validation or model storage.

## Accepted grammar

### Identifiers

Database object identifiers use lower-case snake_case and are always emitted as PostgreSQL quoted identifiers. This keeps the storage naming policy deterministic and prevents accepted words such as `select` or `from` from becoming ambiguous SQL grammar.

### Data types

The model accepts only the reviewed type families encoded in `SIMPLE_SQL_TYPES`, `MULTI_WORD_SQL_TYPES`, and `PARAMETERIZED_SQL_TYPE`. Supported parameterized types are bounded to integer arity for `CHAR`, `VARCHAR`, `CHARACTER VARYING`, `NUMERIC`, and `DECIMAL`.

Column constraints such as `PRIMARY KEY`, `NOT NULL`, `UNIQUE`, `REFERENCES`, and `CHECK` are intentionally not part of `Column.type`. PostgreSQL documents a column definition as a data type followed by separate column-constraint grammar. Keeping those grammar slots distinct prevents a caller from bypassing the model's dedicated constraint fields.

### Default values

Defaults accept only:

- SQL single-quoted scalar strings, including standard doubled-quote escaping;
- signed integer or decimal literals;
- `TRUE`, `FALSE`, and `NULL`;
- reviewed current-time keywords (`CURRENT_DATE`, `CURRENT_TIME`, `CURRENT_TIMESTAMP`, `LOCALTIME`, `LOCALTIMESTAMP`); and
- `now()` as the sole reviewed function-form default.

Arbitrary function calls, malformed strings, statement terminators, and other expressions are rejected. If future product requirements need additional PostgreSQL expressions, extend the allow-list test-first rather than broadening it to a generic SQL-token regex.

## Validated-state ownership

`addColumn()` and `addForeignKey()` validate primitive runtime types and SQL/name contracts, then copy accepted fields into model-owned objects. `addTable()`, `getTable()`, and `getTables()` return independent plain-data snapshots. This makes validation stable across the lifetime of a model: mutating a caller-owned input or returned snapshot cannot alter the internal model.

## Verification contract

The ERD regression suite must continue to cover at least:

- legal simple, multi-word, and parameterized PostgreSQL type forms;
- grammar-smuggling attempts such as `INTEGER PRIMARY KEY`, `TEXT NOT NULL`, `INTEGER UNIQUE`, `INTEGER REFERENCES ...`, and `CHECK` expressions;
- unknown type names;
- scalar and reviewed built-in defaults;
- arbitrary function defaults and malformed quoted strings;
- malformed runtime field types despite TypeScript declarations;
- mutation of caller-owned column and foreign-key objects after insertion;
- independent getter snapshots;
- quoted DDL for normal and reserved-word identifiers; and
- multi-table foreign-key DDL.

Repository CI, security scanning, supply-chain checks, exact-head automated review, and independent approval remain separate merge gates. A predecessor-head pass does not prove a later head.

## Failure and rollback

This boundary is fail-closed. A newly requested type or default expression that is not explicitly supported is rejected instead of passed through. If compatibility pressure reveals a legitimate missing PostgreSQL form, add a focused failing regression, extend the smallest relevant allow-list, and rerun the complete exact-head suite.

Rollback of this hardening must not restore free-form type/default concatenation or semicolon-only filtering. If the ERD DDL generator must temporarily lose a capability, prefer disabling unsupported DDL export over accepting unvalidated grammar.

## References (APA 7th)

Open Worldwide Application Security Project. (n.d.). *Input validation cheat sheet*. OWASP Cheat Sheet Series. Retrieved August 7, 2026, from https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html

Open Worldwide Application Security Project. (n.d.). *SQL injection prevention cheat sheet*. OWASP Cheat Sheet Series. Retrieved August 7, 2026, from https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html

PostgreSQL Global Development Group. (2026). *CREATE TABLE*. In *PostgreSQL 18 documentation*. https://www.postgresql.org/docs/18/sql-createtable.html
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"@babel/core": "7.29.7",
"esbuild": "0.28.1",
"hono": "^4.12.34",
"js-yaml": "^4.3.0",
"js-yaml": "4.3.1",
"@auth/core": "^0.41.3",
"sharp": "^0.35.3",
"postcss": "^8.5.18",
Expand Down
Loading
Loading