Skip to content

Commit 11bd2b8

Browse files
committed
Reject stale issue reproductions
* Verify reread bytes against the checked source hash before embedding a failed block. * Refuse source-bearing drafts when the file changes or cannot be reread.
1 parent 2033a37 commit 11bd2b8

2 files changed

Lines changed: 46 additions & 9 deletions

File tree

‎bibtexparser/cli.py‎

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import json
55
import sys
66
from collections.abc import Sequence
7+
from hashlib import sha256
78
from pathlib import Path
89

910
from . import __version__
@@ -124,18 +125,26 @@ def _run_check(args: argparse.Namespace) -> int:
124125
issue_url = build_issue_url(report)
125126
if args.include_source_in_issue_link:
126127
try:
127-
source = args.file.read_bytes().decode(args.encoding)
128-
reproduction = extract_reproduction_snippet(source)
129-
if reproduction is None:
128+
source_bytes = args.file.read_bytes()
129+
if sha256(source_bytes).hexdigest() != report.source_sha256:
130130
reproduction_notice = (
131-
"no exact bounded failed-block reproduction could be extracted; "
132-
"add a reviewed minimal example manually"
131+
"the file changed after the compatibility check; rerun the command"
133132
)
134133
else:
135-
reproduction_issue_url = build_issue_url(
136-
report,
137-
reproduction=reproduction,
138-
)
134+
source = source_bytes.decode(args.encoding)
135+
reproduction = extract_reproduction_snippet(source)
136+
if reproduction is None:
137+
reproduction_notice = (
138+
"no exact bounded failed-block reproduction could be "
139+
"extracted; add a reviewed minimal example manually"
140+
)
141+
else:
142+
reproduction_issue_url = build_issue_url(
143+
report,
144+
reproduction=reproduction,
145+
)
146+
except OSError:
147+
reproduction_notice = "the file could not be reread for a source-bearing draft"
139148
except (LookupError, UnicodeDecodeError):
140149
reproduction_notice = "the selected encoding could not decode the source"
141150
except ValueError as error:

‎tests/test_cli.py‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,34 @@ def test_explicit_source_link_contains_exact_failed_block(self, capsys):
9898
assert "WARNING" in output
9999
assert "terminal logs and browser history" in output
100100

101+
def test_changed_file_cannot_be_attached_to_an_outdated_report(self, capsys, monkeypatch):
102+
"""The reproduction and privacy-safe report must describe identical bytes."""
103+
original_source = b"@article{private-record, title={Original}"
104+
changed_source = b"@article{private-record, title={Changed}"
105+
path = self._source_file(original_source.decode())
106+
original_read_bytes = Path.read_bytes
107+
reads = 0
108+
109+
def changing_read_bytes(candidate):
110+
nonlocal reads
111+
if candidate == path:
112+
reads += 1
113+
return original_source if reads == 1 else changed_source
114+
return original_read_bytes(candidate)
115+
116+
monkeypatch.setattr(Path, "read_bytes", changing_read_bytes)
117+
try:
118+
status = main(["check", str(path), "--include-source-in-issue-link"])
119+
output = capsys.readouterr().out
120+
finally:
121+
os.unlink(path)
122+
123+
assert status == 1
124+
assert "file changed after the compatibility check" in output
125+
assert "Issue draft with reproduction:" not in output
126+
assert original_source.decode() not in output
127+
assert changed_source.decode() not in output
128+
101129
def test_json_report_can_omit_issue_link(self, capsys):
102130
"""Applications receive stable structured output and can suppress the URL."""
103131
path = self._source_file("@article{private-record, title={Secret title}")

0 commit comments

Comments
 (0)