Repository navigation
Conversation
The mini-ini parser in config/config.go and util.RemoveComments both treat the first '#' or ';' as the start of a comment, so a comment marker inside a quoted value silently truncates the value. A matcher such as m = r.sub == p.sub && r.act == "#write" then reaches the expression engine as r.act == " and enforcement fails with "unclosed string literal". Skip terminated string literals when looking for a comment marker in both places. An unterminated literal is left untouched so that the missing quote, rather than a silently shortened matcher, is what gets reported. Co-authored-by: AI assistant (analysis and code drafted with an AI agent)
|
Thanks for the report and the clear write-up, the bug is real. It's fixed in 6a018f2 with a slightly different approach, so I'm closing this PR. The difference is how a backslash inside a quoted string is treated. In matchers it is not an escape character: |
What
A
#or;inside a quoted value of a model.conffile is treated as the start of a comment, so the value is silently truncated and enforcement fails.The value
#writecontains a comment marker, so the matcher is read as:and
Enforcereturnsunclosed string literalinstead of a decision. A matcher literal that legitimately contains#or;(a URL fragment, a tag, a role name) cannot be written in a model file at all today.Root cause
Two places cut the line at the first
#/;without knowing about quoted literals:config/config.go—parseBuffer()scans the raw line forDEFAULT_COMMENT/DEFAULT_COMMENT_SEMand writes only the prefix beforewrite()ever splitsoption = value, so the truncation happens while the file is being read.util/util.go—RemoveComments()usedstrings.Index(s, "#"). It is applied to the matcher inmodel/model.go,enforcer.goandmanagement_api.go, so even a value that survives parsing is truncated again on the way to the expression engine.EscapeAssertion()already skips quoted literals when rewritingr./p.prefixes (added in 9a5c253), so the two comment scanners were the remaining inconsistent case.Fix
Skip a terminated string literal (with
\escaping) when looking for the comment marker, in both scanners:config/config.go: the inline byte scan is replaced byoptionValueEnd(), which returns the first#/;outside a quoted literal, or the end of the line.util/util.go:RemoveComments()walks the string the same way. The literal scanner thatEscapeAssertion()already needed is factored out intostringLiteralEnd()and reused;EscapeAssertion()behavior is unchanged.An unterminated literal is deliberately left alone:
m = r.act == "#writestill becomesr.act == ", so the expression parser reports the missing quote rather than the config parser hiding it behind a shortened value.Tests
Added regression tests, all of which fail on current
masterand pass with the fix:TestCommentMarkerInsideQuotedValue(config/config_test.go):#, single-quoted#, escaped-quote#, and the "value + real comment" combinations through the actual file/text parser.TestRemoveComments(util/util_test.go): the same cases at the util level, plus# it's a comment(an apostrophe in a trailing comment must not swallow the rest of the line) and the unterminated-literal case.TestMatcherWithStringLiteralContainingHash(enforcer_test.go): end-to-endNewModelFromString+Enforce, asserting#writeis allowed andreadis denied.Verification performed locally against
master(d3b2659):r.act == ", andEnforce Error: unclosed string literal).go test ./...passes for the whole module, including the existingTestGet/TestEscapeAssertion/TestRemoveCommentscases, andgofmt -lis clean.Notes
The
confighelper is a[]bytevariant duplicated from theutilone rather than shared:configcurrently depends only on the standard library, and reaching intoutil(which pulls inrbac) to reuse a 12-line scanner over a different type seemed like the worse trade. I'm happy to switch to a shared exported helper if you'd prefer that.This PR is deliberately limited to the comment/quoted-value interaction; no other parser behavior changes.
Disclosure: I used an AI-assisted agent to help locate the two truncation sites, draft the fix, and write the tests; the reproduction and the verification results above are my own, and the code is what I would submit and maintain.