Skip to content

security(dbml): enforce identifier-to-DDL trust boundary - #833

Open
seonghobae wants to merge 6 commits into
codex/sql-control-validationfrom
codex/dbml-identifier-boundary
Open

security(dbml): enforce identifier-to-DDL trust boundary#833
seonghobae wants to merge 6 commits into
codex/sql-control-validationfrom
codex/dbml-identifier-boundary

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator

Security outcome

  • parse PostgreSQL-compatible doubled quotes in DBML identifiers without losing Unicode, reserved words, whitespace, semicolons, or comment markers
  • fail closed on NUL, empty/unterminated identifiers, more than 63 UTF-8 bytes, empty path segments, over-deep table paths, and malformed references; the HTTP route returns 422 without a partial snapshot or DDL
  • centralize identifier validation and double-quote rendering for DDL export/migrations, index recommendations, Snowflake introspection, and DBML PK/FK constraint definitions
  • make statement splitting quote-aware so semicolons inside identifier or string tokens cannot be mistaken for appended SQL
  • add deterministic property fuzzing plus focused parser→snapshot→DDL/FK/API regressions
  • document authority, failure/recovery, limitations, and APA 7 primary sources

Stack

This PR is based on #832. The branch was synchronized without history rewriting by merge commit c3435f71ed4819769dedd1197c91afbfd6c1ca24, whose second parent is exact live base 5bf6cc56ca4e80d89fed852362a6ae4f8717be98. It must not merge before #832. Review the DBML identifier-boundary commits relative to that live base.

Verification

Combined exact-tree evidence after synchronizing #832:

  • focused schema-validation and DBML suites: 220 passed
  • complete backend: 591 passed, 1 skipped
  • mypy: Success: no issues found in 69 source files
  • git diff --check: clean
  • merge tree matched the locally validated tree exactly: b04a35f402c9b65f8a3f269cec2620bb6fa85dde

The earlier DBML-only owned production statement/branch coverage was 100.00% (414 statements, 64 branches). Exact-head CI, Security Scan, Semgrep, zero unresolved valid findings, and qualifying independent approval remain mandatory before merge.

No dependency, lockfile, database migration, frontend, or workflow changes.

Closes #747

Summary by CodeRabbit

  • 새로운 기능

    • DBML을 DDL로 변환할 때 식별자와 참조 경로를 엄격히 검증합니다.
    • 특수문자, 예약어, 유니코드 및 이스케이프된 따옴표를 안전하게 처리합니다.
    • 따옴표 안의 세미콜론과 주석 표식을 데이터로 보존합니다.
  • 버그 수정

    • 잘못되거나 모호한 입력을 HTTP 422 오류로 일관되게 반환합니다.
    • PostgreSQL 식별자 길이 제한과 인용 규칙을 준수합니다.
  • 문서

    • DBML 식별자 검증 및 안전한 DDL 변환 기준을 문서화했습니다.

@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@seonghobae, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 34 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 40af6ecc-86e8-4f7a-9410-cb9ad8b5cd06

📥 Commits

Reviewing files that changed from the base of the PR and between c3435f7 and 575b07f.

📒 Files selected for processing (5)
  • backend/app/spec/dbml_import.py
  • backend/app/spec/index_design.py
  • backend/tests/test_dbml_import.py
  • backend/tests/test_index_design.py
  • docs/security/dbml-identifier-boundary.md
📝 Walkthrough

Walkthrough

DBML 식별자 검증과 PostgreSQL 인용 처리를 공통화했습니다. 잘못된 식별자와 경로는 파싱 단계에서 거부합니다. DBML API는 파싱 오류를 HTTP 422로 반환합니다. 인용 내부의 세미콜론과 주석 표식은 보존합니다.

Changes

DBML 식별자 경계

Layer / File(s) Summary
공통 식별자 검증 및 인용
backend/app/ddl/identifiers.py, backend/app/ddl/export.py, backend/app/snowflake_introspect/introspect.py, backend/app/spec/index_design.py, docs/security/dbml-identifier-boundary.md
PostgreSQL 식별자의 빈 값, NUL, UTF-8 63바이트 초과를 거부합니다. 내부 큰따옴표를 이중화합니다. DDL 출력 경로는 quote_identifier를 사용합니다.
DBML 식별자 및 경로 파싱
backend/app/spec/dbml_import.py, backend/tests/test_dbml_import.py, docs/security/dbml-identifier-boundary.md, CHANGELOG.md
인용 식별자와 이스케이프된 따옴표를 디코딩합니다. 테이블·컬럼·참조 경로를 검증합니다. 잘못된 입력과 과도한 경로를 DbmlParseError로 처리합니다. 제약조건 이름은 PostgreSQL 길이 제한에 맞게 축약합니다. round-trip 및 퍼징 테스트를 추가했습니다.
API 오류 및 SQL 문장 경계
backend/app/api/dbml.py, backend/app/pg_introspect/forward_ddl.py, backend/tests/test_dbml_import.py, docs/security/dbml-identifier-boundary.md
DBML 파싱 오류를 HTTP 422 응답으로 변환합니다. SQL 문장 분할기는 따옴표 내부의 세미콜론을 구분자로 처리하지 않습니다. malformed 입력의 부분 결과와 인용된 외래 키 출력을 검증합니다.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant DBML_API
  participant parse_dbml
  participant quote_identifier
  participant split_statements
  DBML_API->>parse_dbml: DBML 입력 전달
  parse_dbml->>quote_identifier: 검증된 식별자 인용 요청
  quote_identifier-->>parse_dbml: PostgreSQL 식별자 토큰 반환
  parse_dbml-->>DBML_API: DDL 반환
  DBML_API->>split_statements: SQL 문장 분할 요청
  split_statements-->>DBML_API: 인용 영역을 보존한 문장 목록 반환
Loading
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 43.48% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Linked Issues check ❓ Inconclusive 핵심 보안 요구사항은 충족하지만 100% 커버리지와 공개 docstring 완성 여부는 제공된 정보만으로 확인할 수 없습니다. 커버리지 결과와 공개 API docstring 검증 결과를 추가로 제시하십시오.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 DBML 식별자에서 DDL로 이어지는 보안 경계 강화를 정확하고 간결하게 설명합니다.
Out of Scope Changes check ✅ Passed 변경 사항은 식별자 검증·인용, DBML 파싱, DDL 생성, 테스트 및 보안 문서 범위에 있으며 관련 없는 변경은 확인되지 않습니다.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/dbml-identifier-boundary

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copy link
Copy Markdown
Collaborator Author

Exact-current-head stacked review request for c3435f71ed4819769dedd1197c91afbfd6c1ca24 on live exact base 5bf6cc56ca4e80d89fed852362a6ae4f8717be98.

@opencode-agent @cwl-noema-review Please review this unchanged exact head and provide a qualifying non-author formal verdict. Current-head CI 31480642120 is terminal-success; the combined tree passed 591 backend tests (1 skipped), mypy across 69 source files, and git diff --check; zero review threads are unresolved. Security Scan/Semgrep do not dispatch for this non-main stack target and must run after #832 integrates and this PR is retargeted to protected main.

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

Please perform a full review of exact current head c3435f71ed4819769dedd1197c91afbfd6c1ca24 against its live stacked base. Treat prior skipped/status-only evidence as non-passing. Focus on PostgreSQL identifier parsing/canonicalization/rendering, single-statement integrity, Unicode/quoted identifier semantics, and the stack boundary. Do not mutate or merge.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

@seonghobae I will perform a full review of the exact current head against the live stacked base. I will treat skipped and status-only evidence as non-passing. I will not mutate or merge the branch.

✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
backend/app/spec/dbml_import.py (1)

230-235: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

4,096자 초과 DBML 줄에서 fail-closed 처리를 적용하십시오.

Line 233은 길이 초과 줄을 continue로 건너뜁니다. 유효한 테이블 뒤에 길이 초과의 잘못된 Ref 또는 Table 줄을 넣으면 parse_dbml은 부분 snapshot을 반환합니다. convert_dbml은 이 결과로 응답과 선택적 DDL을 생성합니다.

길이 초과 시 DbmlParseError를 발생시키십시오. test_pathological_long_line_is_skipped_fast도 예외 및 HTTP 422 계약을 검증하도록 변경하십시오.

🤖 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 `@backend/app/spec/dbml_import.py` around lines 230 - 235, Update parse_dbml’s
overlong-line guard to raise DbmlParseError instead of continuing, ensuring
oversized DBML input fails closed rather than producing a partial snapshot.
Update test_pathological_long_line_is_skipped_fast to assert the parser
exception and the convert_dbml HTTP 422 response contract.
backend/app/spec/index_design.py (1)

171-182: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

인덱스 이름을 PostgreSQL의 바이트 제한에 맞게 생성하십시오.

_index_name()은 63개 문자로 자르지만 quote_identifier()는 UTF-8 바이트 수를 검사합니다. 유효한 Unicode 이름에서는 결과가 63바이트를 초과하여 SqlIdentifierError가 발생합니다. 단순 절단은 서로 다른 입력을 같은 인덱스 이름으로 만들 수도 있습니다. UTF-8 경계를 보존하는 안정적 해시 접미사 방식으로 축약하고, 최대 길이 Unicode 입력 및 충돌 회피 테스트를 추가하십시오.

🤖 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 `@backend/app/spec/index_design.py` around lines 171 - 182, Update
_index_name() to enforce PostgreSQL’s 63-byte UTF-8 identifier limit rather than
a 63-character limit, preserving complete Unicode code points. When truncation
is required, append a deterministic hash suffix derived from the full input so
distinct long inputs remain distinguishable; add tests covering maximum-length
Unicode names and collision avoidance.
🤖 Prompt for all review comments with 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.

Inline comments:
In `@backend/app/spec/dbml_import.py`:
- Around line 46-51: Update the parsing logic using _REF_RE and _INLINE_REF_RE
so reference paths are validated completely: use full-string matching for
standalone Ref values, and require inline ref: matches to end at an allowed
settings delimiter or the string boundary. Add regression tests covering
trailing invalid tokens for both standalone and inline references, ensuring no
foreign key is created.

In `@docs/security/dbml-identifier-boundary.md`:
- Around line 62-69: Update the References section of
dbml-identifier-boundary.md to add relevant academic literature supporting the
DBML identifier-boundary security feature. Include complete citations,
accessible links, and a brief summary for each source, while retaining the
existing OWASP and PostgreSQL references.

---

Outside diff comments:
In `@backend/app/spec/dbml_import.py`:
- Around line 230-235: Update parse_dbml’s overlong-line guard to raise
DbmlParseError instead of continuing, ensuring oversized DBML input fails closed
rather than producing a partial snapshot. Update
test_pathological_long_line_is_skipped_fast to assert the parser exception and
the convert_dbml HTTP 422 response contract.

In `@backend/app/spec/index_design.py`:
- Around line 171-182: Update _index_name() to enforce PostgreSQL’s 63-byte
UTF-8 identifier limit rather than a 63-character limit, preserving complete
Unicode code points. When truncation is required, append a deterministic hash
suffix derived from the full input so distinct long inputs remain
distinguishable; add tests covering maximum-length Unicode names and collision
avoidance.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b5887249-a111-4a3e-b1c4-830e562ad6b8

📥 Commits

Reviewing files that changed from the base of the PR and between 5bf6cc5 and c3435f7.

📒 Files selected for processing (10)
  • CHANGELOG.md
  • backend/app/api/dbml.py
  • backend/app/ddl/export.py
  • backend/app/ddl/identifiers.py
  • backend/app/pg_introspect/forward_ddl.py
  • backend/app/snowflake_introspect/introspect.py
  • backend/app/spec/dbml_import.py
  • backend/app/spec/index_design.py
  • backend/tests/test_dbml_import.py
  • docs/security/dbml-identifier-boundary.md

Comment thread backend/app/spec/dbml_import.py Outdated
Comment thread docs/security/dbml-identifier-boundary.md

Copy link
Copy Markdown
Collaborator Author

Addressed all four CodeRabbit findings at exact head 8eeaa36bc45ddf0d0dc0ef0e0ae9f037c51bf879:

  • standalone references now require a full match; inline reference paths require a comma delimiter or settings-string boundary;
  • the security record retains OWASP/PostgreSQL guidance and adds complete Su–Wassermann (2006) and Ray–Ligatti (2012) citations, links, and bounded relevance summaries;
  • lines over 4,096 characters now fail closed with DbmlParseError, and the HTTP route preserves the 422/no-partial-output contract;
  • generated index names enforce PostgreSQL’s 63-byte UTF-8 limit, preserve complete code points, and use a deterministic SHA-256 suffix when shortened.

Test-first evidence: the new boundary tests first failed as expected (2 reference failures, then 3 oversized-line/index-name failures). After the fixes, the focused suites passed (34 passed) and the full backend suite passed (595 passed, 1 skipped). git diff --check is clean. The first full-suite attempt exposed only the runner’s SOCKS proxy without socksio; rerunning with proxy variables removed passed and did not change production code.

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

Please re-review exact current head 8eeaa36bc45ddf0d0dc0ef0e0ae9f037c51bf879 after the four test-first remediations. Treat predecessor review/status evidence as stale. Verify the complete reference boundary, oversized-input fail-closed behavior, UTF-8 byte-bounded deterministic index names, research citations, and regression coverage. Do not mutate or merge.

Copy link
Copy Markdown
Collaborator Author

Exact-head independent review request for 8eeaa36bc45ddf0d0dc0ef0e0ae9f037c51bf879 on live stacked base 5bf6cc56ca4e80d89fed852362a6ae4f8717be98.

@opencode-agent @cwl-noema-review Please review this unchanged exact head and provide a formal non-author verdict. Local evidence is 595 passed, 1 skipped, focused 34 passed, and clean git diff --check; exact-head GitHub CI remains in progress and is not treated as passing. Do not mutate or merge.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

requested_head="8eeaa36bc45ddf0d0dc0ef0e0ae9f037c51bf879"
requested_base="5bf6cc56ca4e80d89fed852362a6ae4f8717be98"

echo "== Revision verification =="
git rev-parse HEAD
git cat-file -t "$requested_head"
git cat-file -t "$requested_base"
echo "merge-base:"
git merge-base "$requested_head" "$requested_base"

echo
echo "== Changed files: base...head =="
git diff --name-status "$requested_base...$requested_head"

echo
echo "== Changed files: predecessor...requested head =="
git diff --name-status "c3435f71ed4819769dedd1197c91afbfd6c1ca24...$requested_head"

echo
echo "== Outline: DBML parser =="
ast-grep outline backend/app/spec/dbml_import.py --items all --view compact

echo
echo "== Outline: DBML API route and identifier helpers =="
ast-grep outline backend/app/api/dbml.py backend/app/ddl/identifiers.py --items all --view compact

echo
echo "== Focused definitions and regression references =="
rg -n -C 4 -P '(^\s*(def|class)\s+.*(reference|identifier|line|index)|MAX_.*(LINE|IDENTIFIER)|DbmlParseError|SqlIdentifierError|sha256|4096|4097|Su.?Wassermann|Ray.?Ligatti)' \
  backend/app/spec/dbml_import.py backend/app/api/dbml.py backend/app/ddl/identifiers.py \
  backend/tests/test_dbml_import.py docs/security/dbml-identifier-boundary.md || true

echo
echo "== Exact remediation diff =="
git diff --find-renames --find-copies --unified=80 \
  "c3435f71ed4819769dedd1197c91afbfd6c1ca24...$requested_head" -- \
  backend/app/spec/dbml_import.py \
  backend/app/api/dbml.py \
  backend/tests/test_dbml_import.py \
  docs/security/dbml-identifier-boundary.md \
  CHANGELOG.md

Length of output: 2891


🏁 Script executed:

#!/bin/bash
set -euo pipefail

head="8eeaa36bc45ddf0d0dc0ef0e0ae9f037c51bf879"
previous="c3435f71ed4819769dedd1197c91afbfd6c1ca24"

echo "== AST outlines =="
ast-grep outline backend/app/spec/dbml_import.py --items all --view signatures
ast-grep outline backend/app/spec/index_design.py --items all --view signatures
ast-grep outline backend/app/ddl/identifiers.py --items all --view signatures
ast-grep outline backend/app/api/dbml.py --items all --view signatures

echo
echo "== Remediation delta =="
git diff --find-renames --find-copies --unified=100 "$previous...$head" -- \
  backend/app/spec/dbml_import.py \
  backend/app/spec/index_design.py \
  backend/tests/test_dbml_import.py \
  backend/tests/test_index_design.py \
  docs/security/dbml-identifier-boundary.md

echo
echo "== DBML parser: reference, line-boundary, and index-name logic =="
rg -n -C 10 -P '(DbmlParseError|4096|len\(.*line|splitlines|reference|Ref:|_parse.*ref|constraint|index.*name|sha256|utf.?8|encode\()' \
  backend/app/spec/dbml_import.py

echo
echo "== DBML parser source, selected range =="
nl -ba backend/app/spec/dbml_import.py | sed -n '1,460p'

echo
echo "== Index design and shared SQL identifier renderer =="
nl -ba backend/app/spec/index_design.py | sed -n '1,360p'
nl -ba backend/app/ddl/identifiers.py | sed -n '1,220p'

echo
echo "== Tests: full matching test definitions and test fixtures =="
rg -n -C 12 -P '^\s*(async\s+)?def test_.*(reference|line|index|identifier|quote|unicode|oversiz|malformed)' \
  backend/tests/test_dbml_import.py backend/tests/test_index_design.py

echo
echo "== Documentation: citations and identifier/reference contract =="
nl -ba docs/security/dbml-identifier-boundary.md | sed -n '1,260p'

echo
echo "== SQL identifier quote/render call sites =="
rg -n -C 4 -P '\b(quote_identifier|validate_identifier|_q)\s*\(' backend/app

Length of output: 59412


🏁 Script executed:

#!/bin/bash
set -euo pipefail

base="5bf6cc56ca4e80d89fed852362a6ae4f8717be98"
head="8eeaa36bc45ddf0d0dc0ef0e0ae9f037c51bf879"

echo "== Current DBML parser =="
sed -n '1,470p' backend/app/spec/dbml_import.py

echo
echo "== Current HTTP route and DDL renderer/splitter =="
sed -n '1,180p' backend/app/api/dbml.py
sed -n '1,360p' backend/app/ddl/export.py
sed -n '1,240p' backend/app/ddl/identifiers.py
sed -n '1,240p' backend/app/pg_introspect/forward_ddl.py

echo
echo "== Current regression tests =="
sed -n '1,380p' backend/tests/test_dbml_import.py
sed -n '1,180p' backend/tests/test_index_design.py

echo
echo "== All changes in stack boundary =="
git diff --check "$base...$head"
git diff --stat "$base...$head"
git diff --unified=35 "$base...$head" -- \
  backend/app/api/dbml.py \
  backend/app/ddl/export.py \
  backend/app/ddl/identifiers.py \
  backend/app/pg_introspect/forward_ddl.py \
  backend/app/snowflake_introspect/introspect.py \
  backend/app/spec/dbml_import.py \
  backend/app/spec/index_design.py \
  backend/tests/test_dbml_import.py \
  backend/tests/test_index_design.py

echo
echo "== Identifier interpolation and quote renderer use sites =="
rg -n -C 5 -P '(CREATE\s+(TABLE|INDEX)|ALTER\s+TABLE|REFERENCES|CONSTRAINT|DROP\s+TABLE|quote_identifier\(|def _q\()' \
  backend/app/ddl backend/app/spec backend/app/snowflake_introspect backend/app/pg_introspect

echo
echo "== Static probes for reference-pattern acceptance =="
python3 - <<'PY'
import re
q = r'"(?:""|[^"])+"'
path = rf'(?:{q}|\w+)(?:\.(?:{q}|\w+))*'
standalone = re.compile(
    r"ref\s*(?:\w+\s*)?:?\s*"
    rf"(?P<from>{path})\s*(?P<op>[<>-])\s*(?P<to>{path})",
    re.IGNORECASE,
)
inline = re.compile(
    rf"ref:\s*(?P<op>[<>-])\s*(?P<to>{path})(?=\s*(?:,|$))",
    re.IGNORECASE,
)
for text in (
    "Ref: posts.user_id > users.id trailing",
    "Ref: posts.user_id > users.id",
    "ref: > users.id trailing",
    "ref: > users.id, not null",
    "ref: > users.id]",
    "ref: > users.id , note: 'x'",
):
    print(repr(text), "standalone_fullmatch=", bool(standalone.fullmatch(text)),
          "inline_search=", bool(inline.search(text)))
PY

Length of output: 137010


@seonghobae I found one regression in the updated reference boundary.

  • High — Preserve the documented named block-reference syntax. backend/app/spec/dbml_import.py documents Ref name { a.col > b.col } as supported. Line 279 now uses _REF_RE.fullmatch(line), but _REF_RE does not accept the { and } delimiters. Therefore this valid DBML form now raises DbmlParseError. The predecessor’s .search() accepted the contained path. Add a fully anchored grammar branch for the named block form, and add a regression test for it. Do not restore an unbounded substring search.

The exact head and stacked base match the requested commits. I verified the four remediations by static inspection:

  • Standalone references reject trailing tokens.
  • Inline references require a comma or end-of-settings boundary.
  • Lines longer than 4,096 characters raise DbmlParseError, and the route maps that error to HTTP 422.
  • Generated index names use UTF-8 byte measurement and a deterministic SHA-256 suffix.
  • The security record includes the requested Su–Wassermann and Ray–Ligatti citations.
  • git diff --check passed.

I could not independently execute the test suite in this sandbox. The reported test results remain unverified here.

You are interacting with an AI system.

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

Exact head 575b07fde5d88ba0ddba1ca3728fe8aa67c90f76 preserves documented one-line named blocks with an anchored conditional-brace grammar; it does not restore substring search. The new named-block regression test failed before the fix and passed afterward. Focused DBML/index suites: 35 passed; full backend: 596 passed, 1 skipped; git diff --check: clean. Please re-review this exact head and treat all predecessor verdict/status evidence as stale. Do not mutate or merge.

Copy link
Copy Markdown
Collaborator Author

Exact-head independent review request for 575b07fde5d88ba0ddba1ca3728fe8aa67c90f76 on live stacked base 5bf6cc56ca4e80d89fed852362a6ae4f8717be98.

@opencode-agent @cwl-noema-review Please provide a formal non-author verdict for this unchanged head. Local evidence is 596 passed, 1 skipped, focused 35 passed, and clean git diff --check; exact-head CI is queued and not passing evidence. Do not mutate or merge.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

head="575b07fde5d88ba0ddba1ca3728fe8aa67c90f76"
base="5bf6cc56ca4e80d89fed852362a6ae4f8717be98"
previous="8eeaa36bc45ddf0d0dc0ef0e0ae9f037c51bf879"

echo "== Revision verification =="
printf 'checked-out HEAD: '; git rev-parse HEAD
printf 'requested head type: '; git cat-file -t "$head"
printf 'requested base type: '; git cat-file -t "$base"
printf 'merge-base: '; git merge-base "$head" "$base"
printf 'previous-to-head ancestry: '; git merge-base --is-ancestor "$previous" "$head" && echo yes || echo no

echo
echo "== Exact remediation delta =="
git diff --check "$previous...$head"
git diff --unified=100 "$previous...$head" -- \
  backend/app/spec/dbml_import.py \
  backend/tests/test_dbml_import.py \
  docs/security/dbml-identifier-boundary.md

echo
echo "== Parser and API outlines =="
ast-grep outline backend/app/spec/dbml_import.py --items all --view signatures
ast-grep outline backend/app/api/dbml.py --items all --view signatures
ast-grep outline backend/app/ddl/identifiers.py --items all --view signatures

echo
echo "== Reference parsing, identifier validation, and DDL rendering sites =="
rg -n -C 12 -P '(_REF|reference|Ref:|DbmlParseError|quote_identifier|validate_identifier|_split_statements|4096|sha256)' \
  backend/app/spec/dbml_import.py \
  backend/app/api/dbml.py \
  backend/app/ddl/identifiers.py \
  backend/app/ddl/export.py \
  backend/app/pg_introspect/forward_ddl.py \
  backend/tests/test_dbml_import.py \
  docs/security/dbml-identifier-boundary.md

echo
echo "== Selected parser source =="
nl -ba backend/app/spec/dbml_import.py | sed -n '1,460p'

echo
echo "== Test definitions for references and identifier boundary =="
rg -n -C 15 -P '^\s*(async\s+)?def test_.*(ref|reference|identifier|quote|unicode|line|oversiz|statement)' \
  backend/tests/test_dbml_import.py

Length of output: 74992


🏁 Script executed:

#!/bin/bash
set -euo pipefail

head="575b07fde5d88ba0ddba1ca3728fe8aa67c90f76"
base="5bf6cc56ca4e80d89fed852362a6ae4f8717be98"
previous="8eeaa36bc45ddf0d0dc0ef0e0ae9f037c51bf879"

echo "== Changed files in the full stacked range =="
git diff --name-status "$base...$head"
git diff --check "$base...$head"

echo
echo "== Current reference parser and constraint adapter =="
sed -n '38,80p' backend/app/spec/dbml_import.py
sed -n '174,440p' backend/app/spec/dbml_import.py

echo
echo "== Quote-aware statement splitter =="
sed -n '174,245p' backend/app/pg_introspect/forward_ddl.py

echo
echo "== Shared renderer and DDL call paths =="
sed -n '1,120p' backend/app/ddl/identifiers.py
rg -n -C 3 -P '\bquote_identifier\s*\(|\b_q\s*\(' \
  backend/app/ddl backend/app/spec backend/app/snowflake_introspect

echo
echo "== Reference and statement regression coverage =="
sed -n '45,115p' backend/tests/test_dbml_import.py
sed -n '145,310p' backend/tests/test_dbml_import.py

echo
echo "== Exact named-block remediation diff =="
git diff --unified=50 "$previous...$head" -- \
  backend/app/spec/dbml_import.py backend/tests/test_dbml_import.py

echo
echo "== Static grammar probes through grep-visible test inputs =="
rg -n -P 'Ref\s+\w+\s*\{|Ref:.*trailing|malformed reference identifier|_REF_RE\.fullmatch' \
  backend/app/spec/dbml_import.py backend/tests/test_dbml_import.py

Length of output: 48433


@seonghobae Static re-review completed for exact head 575b07fde5d88ba0ddba1ca3728fe8aa67c90f76 against stacked base 5bf6cc56ca4e80d89fed852362a6ae4f8717be98.

I found no new issues.

The named one-line block reference is now accepted by the documented grammar:

  • _REF_RE captures block_name only when it also requires {.
  • The conditional regex branch requires the matching closing }.
  • parse_dbml() uses _REF_RE.fullmatch(line).
  • Trailing tokens remain rejected. The change does not restore substring matching.
  • The new regression test covers Ref user_posts { posts.user_id > users.id }.

The existing identifier boundary remains consistent:

  • validate_identifier() rejects empty values, NUL, and identifiers over PostgreSQL’s 63-byte UTF-8 limit.
  • quote_identifier() doubles embedded quotes and emits one quoted identifier token.
  • DDL export, DBML constraints, index design, and Snowflake introspection use the shared renderer.
  • _split_statements() preserves doubled quote escapes and does not split on semicolons inside single- or double-quoted tokens.

I verified that the checked-out HEAD, requested base, and predecessor ancestry match the requested review scope. git diff --check is clean.

I could not execute the focused or full test suites in this sandbox. The reported 35 passed and 596 passed, 1 skipped results remain unverified here.

⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant