Skip to content

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

Draft
JavierCarnelli-ConductorOne wants to merge 2 commits into
mainfrom
fix/cxh-2458
Draft

JavierCarnelli-ConductorOne wants to merge 2 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
if atValueStart {
if end, ok := pairs[i]; ok {
if bareFieldPattern.MatchString(dsn[i+1 : end]) {
return nil, fmt.Errorf("%w: %q", ErrAmbiguousDSN, dsn[i:end+1])

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.

🔴 Security: this error embeds the brace-quoted value verbatim, and the value that triggers this heuristic is almost always the credential field — PWD={oops;DATABASE=TESTDB} produces ambiguous DB2 DSN...: "{oops;DATABASE=TESTDB}". The error propagates out of ParseNativeDSNnativeDB2DSN/convertToDB2DSNdatabase.Connect, where the SDK logs it, so a plaintext password (including one expanded from ${DB_PASSWORD} into a braced value) ends up in connector logs. Report the offending keyword name and/or byte offset instead of the value, e.g. fmt.Errorf("%w (keyword %q)", ErrAmbiguousDSN, keyword).

Comment thread pkg/database/db2/dsn.go

// bareFieldPattern matches ";KEYWORD=" inside a brace-quoted span: a sign that the span has
// swallowed a separate KEYWORD=value field rather than deliberately quoting one value.
var bareFieldPattern = regexp.MustCompile(`;\s*[A-Za-z][A-Za-z0-9 _]*=`)

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: bareFieldPattern cannot distinguish "unterminated brace swallowed a later field" from "a value legitimately contains ;word=", so it rejects DSNs that are valid ODBC and worked before this PR. HOSTNAME=h;DATABASE=db;UID=u;PWD={pa;ss=word} (password pa;ss=word, brace-quoted exactly as quoteDB2Value would) now hard-fails with ErrAmbiguousDSN and the connector cannot connect at all. The keyword class also allows spaces ([A-Za-z0-9 _]*), widening it further. Consider only firing when there is an earlier unmatched { in the DSN (the actual swallow signal), and document the rejection plus the db2:// workaround in docs/db2.md.

Comment thread pkg/database/db2/dsn.go
Comment on lines +77 to +93
func matchBraces(s string) map[int]int {
pairs := make(map[int]int)
var stack []int
for i := 0; i < len(s); i++ {
switch s[i] {
case '{':
stack = append(stack, i)
case '}':
if n := len(stack); n > 0 {
open := stack[n-1]
stack = stack[:n-1]
pairs[open] = i
}
}
}
return pairs
}

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: LIFO matching treats { inside a brace-quoted value as a nested opener, but ODBC has no nesting — a { inside braces is literal and the first } closes the value (what the old scan did). So HOSTNAME=h;DATABASE={a{b;c};UID=u now pairs the inner { with the }, leaves DATABASE's { unmatched, splits on the inner ;, and DSNDatabase returns {a{b instead of a{b;c. Note quoteDB2Value deliberately brace-quotes values containing {, so this shape is one the code itself considers legal. Skipping openers that occur while already inside a matched span (or pairing each value-start { with the next }) would preserve the old semantics.

Comment thread pkg/database/db2/dsn.go
Comment on lines 62 to 65
func IsNativeDSN(dsn string) bool {
_, native := ParseNativeDSN(dsn)
return native
_, native, err := ParseNativeDSN(dsn)
return err == nil && native
}

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: the doc comment says callers "fail loudly downstream", but the one production caller — resolveConnectScheme in pkg/bsql/offline_validate.go:112 — falls through to url.Parse on false and reports connect: scheme missing from dsn for an ambiguous native DSN. The failure is loud but points the user at the wrong problem; consider having that caller use ParseNativeDSN directly so the ambiguity error surfaces during offline validation.

@github-actions

Copy link
Copy Markdown
Contributor

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

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

Review Summary

Full PR diff scanned for security and correctness: the DB2 native-DSN brace matcher (pkg/database/db2/dsn.go), the propagated error signature through pkg/database/database.go, and the new table-driven tests. The stack-based matchBraces correctly fixes the case where an earlier unterminated opening brace steals a later value's closing brace, and threading the error through ParseNativeDSN is a good call. One blocking issue: the new ErrAmbiguousDSN message echoes the brace-quoted value verbatim, and that value is in practice the PWD= credential, into an error that reaches the logs. No dependency changes (go.mod and go.sum untouched).

Security Issues

  • pkg/database/db2/dsn.go:122ErrAmbiguousDSN embeds the brace-quoted value verbatim; the triggering value is typically the password, and the error propagates to database.Connect and into the logs.

Correctness Issues

None found.

Suggestions

  • pkg/database/db2/dsn.go:97bareFieldPattern rejects valid ODBC DSNs whose brace-quoted value legitimately contains a semicolon followed by word= (for example PWD={pa;ss=word}), which worked before this PR.
  • pkg/database/db2/dsn.go:77-93 — LIFO pairing treats an opening brace inside an already-open quoted value as a nested opener, but ODBC values do not nest, so a brace-quoted value containing a literal opening brace is now mis-split and truncated.
  • pkg/database/db2/dsn.go:62-65IsNativeDSN returning false on ambiguity makes pkg/bsql/offline_validate.go:112 report the misleading connect: scheme missing from dsn.
  • docs/db2.md:113-118 — the docs still say the native form is accepted as-is; the new rejection class and the db2:// workaround are undocumented.
  • pkg/database/db2/dsn_test.go:131-136 — the hostname-marker-inside-a-braced-value case now passes because it errors, not because the split keeps it as one part; its comment is stale and the original split path lost coverage.
Prompt for AI agents
Verify each finding against the current code and only fix it if needed.

## Security Issues

In `pkg/database/db2/dsn.go`:
- Around line 122: the ErrAmbiguousDSN error interpolates the brace-quoted DSN span
  (dsn sliced from the opening brace through the closing brace) into its message. The values
  that trigger this heuristic are the ones containing a semicolon, which in practice means the
  PWD credential, including a password expanded from an environment placeholder into a braced
  value. The error flows through ParseNativeDSN, then nativeDB2DSN or convertToDB2DSN, then
  database.Connect, and is logged by the SDK, leaking the password in plaintext. Replace the
  value with a non-sensitive locator: report the keyword name that owns the span (parse it out
  of the DSN text preceding the opening brace) and/or the byte offset, and do not include any
  part of the value. Update TestParseNativeDSN_Ambiguous and
  pkg/database/native_db2_dsn_test.go accordingly, and add an assertion that the error text
  does not contain the secret substring.

## Suggestions

In `pkg/database/db2/dsn.go`:
- Around line 97: bareFieldPattern cannot distinguish an unterminated brace that swallowed a
  later field from a value that legitimately contains a semicolon followed by word=. As a
  result the DSN `HOSTNAME=h;DATABASE=db;UID=u;PWD={pa;ss=word}` -- a valid ODBC DSN with
  password pa;ss=word, brace-quoted exactly the way quoteDB2Value would quote it -- now
  hard-fails with ErrAmbiguousDSN and the connector cannot connect at all, where it worked
  before this PR. Narrow the trigger so it only fires when the DSN also contains an earlier
  unmatched opening brace (the actual swallow signal, already computed by matchBraces), rather
  than on any brace span whose contents merely look like a field. Also consider tightening the
  keyword character class by dropping the space from it. Add a test asserting that the DSN
  above parses with DATABASE=db and no error.
- Around line 77-93: matchBraces pushes every opening brace, including ones that occur inside
  an already-open brace span, but ODBC values do not nest -- an opening brace inside a
  brace-quoted value is literal and the first closing brace ends the value, which is what the
  pre-PR scan did. Consequence: for a DSN like HOSTNAME=h, then DATABASE= a brace-quoted value
  whose contents are the four characters a, opening-brace, b, semicolon, c, then UID=u, the
  inner opening brace pairs with the closing brace, DATABASE's own opening brace is left
  unmatched, the split happens on the inner semicolon, and DSNDatabase returns a truncated
  value instead of the full contents. Note quoteDB2Value intentionally brace-quotes values
  that contain an opening brace, so this shape is legal in this codebase. Fix by not pushing
  openers while a span is already open (track depth and pair only outermost openers), or by
  pairing each value-start opener with the next closing brace after it, and add a DSNDatabase
  test for that DSN. See the inline review comment on this function for the literal example
  string.
- Around line 62-65: the new IsNativeDSN doc comment claims callers fail loudly downstream,
  but the only production caller, resolveConnectScheme in pkg/bsql/offline_validate.go:112,
  treats false as not-DB2 and falls through to url.Parse, which reports "connect: scheme
  missing from dsn" for an ambiguous native DSN. Change that caller to use ParseNativeDSN
  directly and surface the ambiguity error wrapped as "connect: invalid dsn", so offline
  validation reports the real cause.

In `docs/db2.md`:
- Around line 113-118: the DSN Format section still says DB2's native form is accepted as-is.
  Document the new rejection: a brace-quoted value that appears to contain a later
  KEYWORD=value field is rejected as ambiguous, and users whose credentials legitimately
  contain a semicolon followed by word= should use the db2:// URL form, which brace-quotes
  automatically, instead.

In `pkg/database/db2/dsn_test.go`:
- Around line 131-136: the hostname-marker-only-inside-braced-value case
  `UID=u;PWD={x;HOSTNAME=y}` now returns false because ParseNativeDSN errors with
  ErrAmbiguousDSN, not because the brace-aware split keeps it as one PWD part as the comment
  states. Update the comment, and add a case that still exercises the non-erroring split path
  for a braced value containing a native marker without a semicolon-KEYWORD shape, so that
  path keeps coverage.

@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.

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