Skip to content

fix: reject policy fields containing a line break when saving - #1769

Closed
NUAAwqy wants to merge 1 commit into
apache:masterfrom
NUAAwqy:fix/policy-line-csv-line-break
Closed

NUAAwqy wants to merge 1 commit into
apache:masterfrom
NUAAwqy:fix/policy-line-csv-line-break

Conversation

@NUAAwqy

@NUAAwqy NUAAwqy commented Oct 30, 2026

Copy link
Copy Markdown

What

persist.PolicyLineToCsv can write a policy file that no longer loads.

Its docstring says quoted fields "round-trip through LoadPolicyLine without corruption". That is
true for commas, quotes and leading spaces. It is not true for a field containing a line break,
which a sub/obj/act value taken from user input, a claim, or a multi-line condition can
carry. CSV has no escape for a newline in this format: a policy file holds one rule per line, and
the loaders (persist/file-adapter) feed LoadPolicyLine one bufio.Scanner line at a time, so
the closing quote is never seen.

_, _ = e.AddPolicy("bob", "first\nsecond", "write")
_ = e.SavePolicy()                     // returns nil
// reopen the same model + policy file

Before - the saved file is:

p, alice, data1, read
p, bob, "first
second", write

and reopening it fails outright:

parse error on line 1, column 15: extraneous or missing " in quoted-field

Because SavePolicy rewrites the whole file in one shot, one bad field costs every rule that was
already in it.

After - the save is refused before anything is written:

invalid policy field "first\nsecond": a policy file holds one rule per line, so a field cannot contain a line break

Both file-adapter and string-adapter already propagate the error from this function, and the
file adapter builds the full text before touching storage, so the existing policy file is left
untouched and still loadable.

Fix

Reject a field containing \n while serializing, rather than emitting an unreadable line. Same
shape as 9e2e997 for this file: report a clear error instead of letting the CSV machinery fail
later. A lone carriage return is deliberately still accepted - it does not end a physical line and
the rule round-trips unchanged, which the test pins.

Test

persist/persist_test.go, next to the existing TestPolicyLineToCsv table:

  • TestPolicyLineToCsvLineBreaks: \n and \r\n fields, lone \r and an ordinary quoted field,
    so the check is that a line break is refused and everything else is not.

  • TestSavePolicyWithLineBreakRule: end to end in a temp dir - save must either error with the
    file left intact and loadable, or produce a file that reloads with both rules present. On
    master it fails on the second branch; here it takes the first.

  • go test -race ./...: 8 packages ok

  • gofmt -l .: empty

  • golangci-lint run (v2.15.0, as .golangci.yml requires): 0 issues

Notes

  • Adapters that do not serialize through PolicyLineToCsv (databases, Redis, ...) are unaffected.
  • I chose refusing over escaping. Escaping the break as \n text would need a matching unescape in
    LoadPolicyLine, and without it the reloaded value differs from the stored one, so policies stop
    matching. Happy to switch if maintainers prefer that.

PolicyLineToCsv quoted a field holding a line break the same way it quotes a
field holding a comma, and the docstring promised the line round-trips through
LoadPolicyLine. It cannot: a policy file holds one rule per line and every
loader hands LoadPolicyLine a single line at a time, so the closing quote of
such a field is never seen. SavePolicy then rewrote the whole policy file with
a line Go's CSV reader rejects:

  parse error on line 1, column 15: extraneous or missing " in quoted-field

The rules that were already in the file are lost with it, because the damaged
file replaces the previous one.

Return an error for such a field instead. Both file-adapter and
string-adapter build the full text before touching storage and propagate the
error, so the existing policy file is left intact and still loadable. A lone
carriage return stays accepted: it does not end the physical line and the rule
round-trips unchanged.
@hsluoyz

hsluoyz commented Oct 31, 2026

Copy link
Copy Markdown
Member

Thanks for the report and the clear reproduction. This has been fixed directly in aa1ef64: PolicyLineToCsv now returns an error for any field containing a line break, so SavePolicy fails before writing instead of producing an unloadable policy file. Closing this PR in favor of that commit.

@hsluoyz hsluoyz closed this Oct 31, 2026
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.

2 participants