Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,8 @@
**Vulnerability:** A custom buffer length check (`if (signatureBytes.length !== expectedSignatureBytes.length) return false`) before calling `crypto.timingSafeEqual()` leaked the length of the expected signature, enabling timing attacks.
**Learning:** Never use custom 'homebrew' buffer-padding logic to match lengths for `crypto.timingSafeEqual()`, as early returns leak the length of the secret.
**Prevention:** Ensure inputs are hashed to a uniform length (e.g., using `crypto.createHash('sha256')`) before comparison.

## 2025-08-01 - [ERD DDL Injection λ°©μ§€]
**Vulnerability:** ERDModel을 톡해 DDL(Data Definition Language)을 생성할 λ•Œ 컬럼 νƒ€μž… ν•„λ“œ(`column.type`)에 λŒ€ν•œ μœ νš¨μ„± 검사가 λˆ„λ½λ˜μ–΄ SQL Injection(예: νƒ€μž… μ •μ˜μ— μ„Έλ―Έμ½œλ‘ μ„ μ‚½μž…ν•˜μ—¬ 좔가적인 SQL ꡬ문 μ‹€ν–‰)이 λ°œμƒν•  수 μžˆλŠ” μœ„ν—˜μ΄ μžˆμ—ˆμŠ΅λ‹ˆλ‹€.
**Learning:** μ‚¬μš©μž μž…λ ₯ λ˜λŠ” μ™ΈλΆ€ μ†ŒμŠ€μ—μ„œ μ •μ˜λœ 데이터(νƒ€μž… 이름 포함)λ₯Ό 기반으둜 직접 쿼리(λ˜λŠ” DDL λ¬Έμžμ—΄)λ₯Ό μ‘°ν•©ν•  경우, νŒŒλΌλ―Έν„°ν™”λœ 쿼리λ₯Ό μ‚¬μš©ν•  수 μ—†μœΌλ―€λ‘œ μ—„κ²©ν•œ μž…λ ₯ 검증이 ν•„μˆ˜μ μž…λ‹ˆλ‹€. μ„Έλ―Έμ½œλ‘ (;)κ³Ό 같은 λ¬Έμž₯이 μ’…λ£Œλ˜λŠ” λ¬Έμžκ°€ ν¬ν•¨λ˜λŠ” 것은 μ‹¬κ°ν•œ λ³΄μ•ˆ 결함을 μΌμœΌν‚¬ 수 μžˆμŠ΅λ‹ˆλ‹€.
**Prevention:** `assertNoStatementTerminator`와 같은 λ°©μ–΄ ν•¨μˆ˜λ₯Ό λ§Œλ“€μ–΄ 생성기(μ—¬κΈ°μ„œλŠ” `addColumn`의 `column.type` λ“±)둜 μ „λ‹¬λ˜λŠ” μ‹λ³„μž 및 λ¬Έμžμ—΄μ— λŒ€ν•΄ μ„Έλ―Έμ½œλ‘  포함 μ—¬λΆ€λ₯Ό μ—„κ²©ν•˜κ²Œ ν™•μΈν•˜κ³  차단해야 ν•©λ‹ˆλ‹€.
16 changes: 14 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,20 @@
"overrides": {
"@babel/core": "7.29.7",
"esbuild": "0.28.1",
"hono": "4.12.25",
"js-yaml": "4.2.0"
"hono": "4.12.27",
"js-yaml": "4.3.0",
"fast-uri": "3.1.4",
"@hono/node-server": "1.19.17",
"brace-expansion": "2.1.4",
"body-parser": "2.3.0",
"next-auth": "5.0.0-beta.32",
"@auth/core": "0.41.3",
"next": "15.5.21",
"postcss": "8.5.18",
"sharp": "0.35.0",
"@emnapi/runtime": "1.11.3",
"semver": "7.8.5",
"@img/sharp-wasm32": "0.35.0"
}
}
}
7 changes: 7 additions & 0 deletions packages/web/src/lib/erd.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ describe('ERDModel', () => {
model.addColumn('users', { name: 'created__at', type: 'timestamp' })
).toThrowError("Column 'created__at' must be snake_case.")
})

it('should prevent SQL injection by rejecting statement terminators in column type', () => {
model.addTable('users')
expect(() =>
model.addColumn('users', { name: 'id', type: 'integer; DROP TABLE users;' })
).toThrowError('SQL injection prevention: Statement terminators (;) are not allowed.')
})
})

describe('Foreign Key Management', () => {
Expand Down
7 changes: 7 additions & 0 deletions packages/web/src/lib/erd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ function assertSnakeCaseIdentifier(kind: string, name: string): void {
}
}

function assertNoStatementTerminator(value: string): void {
if (value.includes(';')) {
throw new Error(`SQL injection prevention: Statement terminators (;) are not allowed.`)
}
}

Comment on lines +28 to +33

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ”’ Security & Privacy | 🟠 Major | πŸ—οΈ Heavy lift

μ„Έλ―Έμ½œλ‘  κ²€μ‚¬λ§ŒμœΌλ‘œ column.type의 DDL μ‚½μž…μ„ 막을 수 μ—†μŠ΅λ‹ˆλ‹€.

generateDDL()은 col.type을 SQL에 직접 μ‚½μž…ν•©λ‹ˆλ‹€. λ”°λΌμ„œ integer, is_admin boolean처럼 μ„Έλ―Έμ½œλ‘ μ΄ μ—†λŠ” 값도 μΆ”κ°€ μ»¬λŸΌμ„ μ‚½μž…ν•  수 μžˆμŠ΅λ‹ˆλ‹€. column.type을 ν—ˆμš©λœ νƒ€μž… λ¬Έλ²•μ˜ allowlist λ˜λŠ” 문법 기반 κ²€μ¦μœΌλ‘œ μ œν•œν•˜μ‹­μ‹œμ˜€. μ„Έλ―Έμ½œλ‘ μ΄ μ—†λŠ” μ‚½μž… 값에 λŒ€ν•œ νšŒκ·€ ν…ŒμŠ€νŠΈλ„ μΆ”κ°€ν•˜μ‹­μ‹œμ˜€.

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/web/src/lib/erd.ts` around lines 28 - 33, Update generateDDL’s
validation of column.type so it uses an allowlist or grammar-based check for
valid type syntax, rather than relying only on assertNoStatementTerminator.
Reject semicolon-free injection values such as types that append another column
definition, while preserving support for legitimate SQL types. Add a regression
test covering this injection case.

export class ERDModel {
private tables: Map<string, Table> = new Map()

Expand All @@ -49,6 +55,7 @@ export class ERDModel {
addColumn(tableName: string, column: Column): void {
assertSnakeCaseIdentifier('Table', tableName)
assertSnakeCaseIdentifier('Column', column.name)
assertNoStatementTerminator(column.type)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ”’ Security & Privacy | 🟠 Major | ⚑ Quick win

DDL 생성 κ²½κ³„μ—μ„œλ„ column.type을 κ²€μ¦ν•˜μ‹­μ‹œμ˜€.

addColumn()은 검증 ν›„ λ™μΌν•œ column μ°Έμ‘°λ₯Ό μ €μž₯ν•©λ‹ˆλ‹€. ν˜ΈμΆœμžλŠ” 이후 column.type을 λ³€κ²½ν•  수 μžˆμŠ΅λ‹ˆλ‹€. getTable()κ³Ό getTables()도 κ°€λ³€ Table μ°Έμ‘°λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€. 그러면 μ„Έλ―Έμ½œλ‘ μ΄ ν¬ν•¨λœ 값이 generateDDL()에 λ„λ‹¬ν•©λ‹ˆλ‹€. SQL μ‚½μž… 직전에 λ‹€μ‹œ κ²€μ¦ν•˜κ³ , κ°€λŠ₯ν•˜λ©΄ κ°€λ³€ μ™ΈλΆ€ μ°Έμ‘°λ₯Ό μ €μž₯ν•˜μ§€ μ•Šλ„λ‘ λ³€κ²½ν•˜μ‹­μ‹œμ˜€.

ꢌμž₯ μ΅œμ†Œ λ°©μ–΄
       const columnDefs = table.columns.map((col) => {
+        assertNoStatementTerminator(col.type)
         let def = `  ${col.name} ${col.type}`
πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/web/src/lib/erd.ts` at line 58, Validate column.type immediately
before interpolating it into generated DDL, including the generation paths used
by getTable() and getTables(), so mutations after addColumn() cannot introduce
statement terminators. In addColumn() and table accessors, avoid retaining or
returning mutable external Table/column references where feasible, while
preserving the existing validated behavior.

const table = this.tables.get(tableName)
if (!table) {
throw new Error(`Table '${tableName}' does not exist.`)
Expand Down
Loading
Loading