Skip to content

CXH-2458 Support brace matching on config string - #153

Merged
JavierCarnelli-ConductorOne merged 4 commits into
mainfrom
fix/cxh-2458
Sep 18, 2026
Merged

JavierCarnelli-ConductorOne merged 4 commits into
mainfrom
fix/cxh-2458

Conversation

@JavierCarnelli-ConductorOne

Copy link
Copy Markdown
Contributor

Description

  • Bug fix
  • New feature

Useful links:

…losing brace

splitDB2DSN decided whether a '{' opened a quoted ODBC value by checking
whether any '}' existed anywhere later in the string, with no concept of
brace pairing. An earlier, unterminated '{' could steal the closing '}'
of a later, legitimately-braced value, swallowing the key in between and
causing DSNDatabase to silently return an empty database name.

Replace the lookahead with LIFO stack-based brace matching (matchBraces)
so each '{' pairs with the '}' that actually closes it; an unmatched
brace stays literal instead of consuming unrelated content.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
matchBraces fixed the case of an unterminated '{' stealing a later
value's own closing '}', but a matched brace pair can still be wrong
when the "closing" '}' isn't part of any real quoted value: an earlier
unterminated '{' can pair with a stray '}' at the end of a later, plain
KEYWORD=value field (e.g. "PWD={oops;DATABASE=TESTDB}"), swallowing that
field and causing DSNDatabase to silently return "".

Detect this by checking whether a matched brace span's interior looks
like it contains a later "KEYWORD=" field. When it does, reject the DSN
with ErrAmbiguousDSN instead of guessing: DSNs carry credentials, so
fail loud rather than silently drop a field. ParseNativeDSN now returns
an error; callers in database.go propagate it instead of discarding it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@linear-code

linear-code Bot commented Sep 15, 2026

Copy link
Copy Markdown

CXH-2458

Comment thread pkg/database/db2/dsn.go Outdated
Comment thread pkg/database/db2/dsn.go Outdated
Comment thread pkg/database/db2/dsn.go
Comment thread pkg/database/db2/dsn.go
@github-actions

github-actions Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Connector PR Review: CXH-2458 Support brace matching on config string

Blocking Issues: 0 | Suggestions: 1 | Threads Resolved: 0
Criteria: Criteria status: loaded .claude/skills/ci-review.md from trusted base c98f2f07891b.
Review mode: incremental since e36d7dac
View review run

Review Summary

The new commit gates matchBraces pushes on the preceding word being a reserved DSN keyword, which addresses the previously reported DATABASE={db;x={y};UID=u truncation (now covered by a test), and the earlier findings about the error message echoing credential values and bareFieldPattern rejecting legitimate PWD={pa;ss=word} values are also addressed in the current code. I re-scanned the full PR diff (pkg/database/database.go, pkg/database/db2/dsn.go, and both test files; no dependency manifest changes, and the incremental artifact reports no dropped paths or truncation) for security and correctness. One residual gap remains from the same nested-brace family: the fix covers only non-reserved inner keywords.

Security Issues

None found. The ambiguity error now reports only keyword names (owner is cut at =, swallowed is a matched keyword), and the new test asserts no DSN value text leaks.

Correctness Issues

None found.

Suggestions

  • pkg/database/db2/dsn.go:80 — a nested ={ whose word is itself a reserved keyword is still pushed and still steals the outer value's }: HOSTNAME=h;DATABASE={db;UID={u};PORT=1 yields {db instead of db;UID={u, and because the outer brace never pairs, the ErrAmbiguousDSN guard never fires, so the mis-parse is silent.
Prompt for AI agents
Verify each finding against the current code and only fix it if needed.

## Suggestions

In `pkg/database/db2/dsn.go`:
- Around line 72-104 (`matchBraces`): the reserved-keyword gate added in the latest
  commit only fixes the case where the nested `KEYWORD=` inside an already-open braced
  value is NOT a reserved DSN keyword. When the inner word IS reserved, its `{` is still
  pushed onto the stack and the next `}` pops it (LIFO), leaving the outer value's `{`
  unpaired. Repro: `HOSTNAME=h;DATABASE={db;UID={u};PORT=1` — `UID`'s `{` pairs with the
  `}`, `DATABASE`'s `{` is unmatched, so `splitDB2DSN` splits on the inner `;` and
  `DSNDatabase` returns `{db` instead of `db;UID={u` (which is both what main returned and
  what the ODBC first-`}` rule gives the driver). Because the outer brace never gets a
  pair entry, `splitDB2DSN` never calls `swallowedReservedField` on that span, so the
  DSN is silently mis-parsed instead of being rejected with `ErrAmbiguousDSN`, which
  contradicts the "reject rather than guess" intent documented on `ErrAmbiguousDSN`.
  Fix: make the opener check also require that no span is currently open — e.g. skip the
  push when `len(stack) > 0`, or track the index of the `}` that closes the outermost
  open opener and ignore every `{`/`=` until that index — so only a true field-level
  `{` is a candidate. Add a table test for `HOSTNAME=h;DATABASE={db;UID={u};PORT=1`
  (expect `db;UID={u`, or an `ErrAmbiguousDSN` if you decide rejection is preferable)
  alongside the existing `DATABASE={db;x={y};UID=u` case.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Blocking issues found — see review comments.

- Never interpolate DSN values into ErrAmbiguousDSN; report only the
  owning and swallowed keyword names, since values may be credentials.
- Only treat a brace-quoted span as ambiguous when the field name it
  appears to swallow is a reserved DSN keyword, so a password merely
  containing ";word=" text is no longer rejected.
- Make matchBraces value-start aware so a literal '{' inside an
  already-open value isn't mistaken for a new opener; ODBC values
  don't nest, and this was truncating values like "a{b;c" to "{a{b".
- Add regression tests for all three, and restore coverage for the
  non-erroring brace-aware split path lost in an earlier fix.
- Trim comments across dsn.go and dsn_test.go to one or two sentences.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Comment thread pkg/database/db2/dsn.go

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No blocking issues found.

matchBraces set atValueStart on every '=', including one appearing
inside an already-open braced value. A non-keyword field there (e.g.
"x=" in "DATABASE={db;x={y}") could push a second candidate opener
that then stole the real closing brace, truncating the value.

Only push a '{' when the identifier preceding its '=' is a reserved
DSN keyword, so an '=' inside an open value can no longer be mistaken
for a new field's opener. The original ticket's fix (two reserved
keywords racing for one close) is unaffected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Comment thread pkg/database/db2/dsn.go

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No blocking issues found.

@mateoHernandez123 mateoHernandez123 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approving — the original CXH-2458 swallow (an earlier unterminated { stealing a later value's }), the credential leak in ErrAmbiguousDSN, and the non-reserved nested { case are all covered, and CI is green.

One leftover, not blocking this merge: a nested ={ whose word is a reserved keyword still pushes a second opener and silently truncates. I left a suggestion on the table test that pins the non-reserved sibling. Happy to take that as a follow-up.

{name: "literal brace inside a braced value does not truncate it", dsn: "HOSTNAME=h;DATABASE={a{b;c};UID=u", want: "a{b;c"},
// A non-reserved keyword's '=' inside an already-open value must not open a new
// candidate brace, or it steals the real closing brace and truncates the value.
{name: "non-reserved keyword inside a braced value does not steal its closing brace", dsn: "HOSTNAME=h;DATABASE={db;x={y};UID=u", want: "db;x={y"},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggestion: this pins the non-reserved half (x={y}) of the nested-= fix, which is the case Javier addressed. The reserved sibling is still open on HEAD 632856d: HOSTNAME=h;DATABASE={db;UID={u};PORT=1 returns database="{db" with err=nil instead of db;UID={u} (ODBC first-} / pre-PR) or ErrAmbiguousDSN.

matchBraces still pushes when the word before ={ is in reservedDSNKeywords, even with a span already on the stack, so UID's { steals DATABASE's } and the inner ; splits. Because the outer { never pairs, swallowedReservedField never runs either — silent mis-parse, including via nativeDB2DSN.

Worth a sibling row here, e.g. DATABASE={db;UID={u};UID=u (expect db;UID={u, or ErrAmbiguousDSN if rejection is the call). The matching parser change is skip the push while len(stack) > 0, which is also what the comment on matchBraces already claims ("ODBC values don't nest"). Not blocking the ticket; the original steal and the x={y} half are fixed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Great! We can handle this as a follow up item.
Thank you so much Tute!

@JavierCarnelli-ConductorOne
JavierCarnelli-ConductorOne merged commit e02f52a into main Sep 18, 2026
9 checks passed
@JavierCarnelli-ConductorOne
JavierCarnelli-ConductorOne deleted the fix/cxh-2458 branch September 18, 2026 17:06
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.

3 participants