Reject more include directives and invalid journal encodings

This commit is contained in:
2026-09-27 16:08:39 +02:00
parent 1c02e62b2a
commit 3dea524dd9
3 changed files with 172 additions and 1 deletions
+3 -1
View File
@@ -6,7 +6,7 @@ module Hledger
# control.
class Journal
FENCE = /^[ \t]*`{3,}hledger[ \t]*\r?\n(?<source>.*?)^[ \t]*`{3,}[ \t]*$/m
INCLUDE = /^[ \t]*!?include\b/i
INCLUDE = /^[ \t]*!?[ \t]*include\b/i
MAX_LINES = 5_000
Result =
@@ -29,6 +29,8 @@ module Hledger
end
def extract
return Result.new(source: nil, error: :invalid_encoding) unless @raw.valid_encoding?
matches = @raw.scan(FENCE).flatten
return Result.new(source: nil, error: :none) if matches.empty?
return Result.new(source: nil, error: :ambiguous) if matches.length > 1
+43
View File
@@ -39,6 +39,32 @@ RSpec.describe Hledger::Journal do
expect(result.error).to eq(:include_not_allowed)
end
[
"include /etc/passwd",
"include ../foo",
"include ~/foo",
"include *.journal",
"include **/*",
" include foo",
"\tinclude foo",
"INCLUDE foo",
"!include foo",
"! include foo",
"! include foo",
].each do |directive|
it "rejects the include directive #{directive.inspect}" do
result = described_class.extract(raw_with(directive))
expect(result.error).to eq(:include_not_allowed)
end
end
it "allows the word include inside a transaction" do
result = described_class.extract(raw_with("2024-01-01 include the foo\n a 1\n b -1"))
expect(result).to be_ok
end
it "rejects oversized journals" do
SiteSetting.hledger_max_journal_bytes = 10
@@ -48,4 +74,21 @@ RSpec.describe Hledger::Journal do
ensure
SiteSetting.hledger_max_journal_bytes = 262_144
end
it "extracts a fence with trailing whitespace and a longer fence" do
raw = "Intro\n\n````hledger \n2024-01-01 x\n a 1\n b -1\n````\n\nOutro\n"
result = described_class.extract(raw)
expect(result).to be_ok
expect(result.source).to include("2024-01-01 x")
end
it "rejects invalid UTF-8" do
raw = "```hledger\n2024-01-01 \xFF\n```\n"
result = described_class.extract(raw)
expect(result.error).to eq(:invalid_encoding)
end
end
+126
View File
@@ -129,6 +129,15 @@ RSpec.describe Hledger::ReportsController do
expect(response.status).to eq(422)
end
it "rejects journals with include directives" do
post.update!(raw: "```hledger\ninclude /etc/passwd\n```")
get_report("accounts")
expect(response.status).to eq(422)
expect(response.body).not_to include("/etc/passwd")
end
it "returns service unavailable when hledger is missing" do
allow(Hledger::Runner).to receive(:new).and_return(StubHledgerRunner.new(version: nil))
@@ -168,6 +177,123 @@ RSpec.describe Hledger::ReportsController do
expect(response.status).to eq(404)
end
describe "authorization" do
def journal_raw
<<~RAW
```hledger
2024-01-01 Opening balances
assets:bank:checking 1000.00 EUR
equity:alice -600.00 EUR
equity:bob -400.00 EUR
```
RAW
end
def report_for(topic)
get "/hledger/topics/#{topic.id}/reports/accounts.json"
end
it "allows anonymous access to a public topic" do
report_for(post.topic)
expect(response.status).to eq(200)
end
it "allows access to a locked and archived topic the user can see" do
post.topic.update!(closed: true, archived: true)
report_for(post.topic)
expect(response.status).to eq(200)
end
it "returns not found for a deleted topic" do
topic = Fabricate(:post, raw: journal_raw).topic
topic.trash!
report_for(topic)
expect(response.status).to eq(404)
expect(response.body).not_to include("assets:bank:checking")
end
it "returns not found when the first post is deleted" do
topic = Fabricate(:post, raw: journal_raw).topic
topic.first_post.trash!
report_for(topic)
expect(response.status).to eq(404)
end
context "with a group-restricted category" do
fab!(:group)
fab!(:restricted_category) { Fabricate(:private_category, group: group) }
fab!(:restricted_topic) do
Fabricate(
:post,
raw: journal_raw,
topic: Fabricate(:topic, category: restricted_category),
).topic
end
it "hides it from anonymous users" do
report_for(restricted_topic)
expect(response.status).to eq(404)
expect(response.body).not_to include("assets:bank:checking")
end
it "hides it from logged in non-members" do
sign_in(Fabricate(:user))
report_for(restricted_topic)
expect(response.status).to eq(404)
end
it "allows members" do
user = Fabricate(:user)
group.add(user)
sign_in(user)
report_for(restricted_topic)
expect(response.status).to eq(200)
end
end
context "with a private message" do
fab!(:member, :user)
fab!(:private_message_post) do
Fabricate(:private_message_post, recipient: member, raw: journal_raw)
end
it "hides it from anonymous users" do
report_for(private_message_post.topic)
expect(response.status).to eq(404)
end
it "hides it from non-members" do
sign_in(Fabricate(:user))
report_for(private_message_post.topic)
expect(response.status).to eq(404)
expect(response.body).not_to include("assets:bank:checking")
end
it "allows members" do
sign_in(member)
report_for(private_message_post.topic)
expect(response.status).to eq(200)
end
end
end
it "is not found when the plugin is disabled" do
SiteSetting.hledger_enabled = false