| id | 81 |
|---|---|
| title | OOM mitigation: configurable file-size limit |
| status | ✅ |
| summary | Guard every file-read path against OOM by enforcing a configurable byte-size cap (default 2 MB). |
Prevent out-of-memory crashes from large Markdown files. Add a configurable byte-size limit enforced before any file content is loaded into memory.
os.ReadFile and io.ReadAll load the entire input
into a byte slice with no size guard. A multi-GB .md
file (or stdin pipe) will OOM the process. The
max-file-length rule (MDS022) only emits a diagnostic
after the file is fully loaded — it does not prevent
the allocation.
Every top-5 Markdown linter (Prettier, markdownlint, Vale, remark-lint, textlint) has the same gap. remark's docs advise callers to "cap input at 500 KB" but nothing is enforced.
New file internal/lint/limits.go:
const DefaultMaxInputBytes int64 = 2 * 1024 * 1024
func ReadFileLimited(path string, max int64) ([]byte, error)
func ReadFSFileLimited(fsys fs.FS, name string, max int64) ([]byte, error)Both use Open + io.LimitReader(f, max+1) +
io.ReadAll + post-read len(data) > max check.
The +1 sentinel distinguishes "exactly at limit"
from "truncated". When max <= 0, no limit is applied
(unlimited mode).
Top-level key in .mdsmith.yml (not a rule setting —
this is infrastructure, not a lint rule):
max-input-size: 2MBNew MaxInputSize field on config.Config:
MaxInputSize string `yaml:"max-input-size"`New file internal/config/size.go:
func ParseSize(s string) (int64, error)Accepted formats: 2MB, 500KB, 1GB, bare integer
(bytes), 0 (unlimited). Case-insensitive. Uses
binary units (1 MB = 1,048,576 bytes).
--max-input-size <size>
Added to both check and fix flag sets. CLI value
overrides the config value. Default: "2MB".
Add MaxInputBytes int64 field to engine.Runner and
fix.Fixer. Set from the parsed config + CLI override
in cmd/mdsmith/main.go.
Primary entry points (replace os.ReadFile with
lint.ReadFileLimited):
internal/engine/runner.go:52internal/fix/fix.go:84cmd/mdsmith/main.go:577(stdin viaio.LimitReader)
Secondary read sites (replace with
lint.ReadFileLimited or lint.ReadFSFileLimited):
internal/rules/include/rule.go:194internal/rules/catalog/rule.go:395,468internal/rules/crossfilereferenceintegrity/rule.go:236,253internal/rules/requiredstructure/rule.go:82,496internal/metrics/rank.go:21cmd/mdsmith/mergedriver.go:96,135internal/config/load.go:16
Rules that read files need the limit threaded via
lint.File or a new field on the rule struct (set
during ApplySettings or via the runner).
check/fix: Emit error, skip file, continue. Exit code 2.stdin: Print to stderr, exit 2.- Message format:
reading "huge.md": file too large (15728640 bytes, max 2097152)
- Add
internal/lint/limits.gowithReadFileLimitedandReadFSFileLimited - Add
internal/lint/limits_test.gowith tests for normal, at-limit, over-limit, zero (unlimited), and empty-file cases - Add
internal/config/size.gowithParseSize - Add
internal/config/size_test.gowith tests for2MB,500KB,0, bare integer, invalid input - Add
MaxInputSizefield toconfig.Config - Add
MaxInputBytesfield toengine.Runnerandfix.Fixer(andlint.Filefor rule threading) - Add
--max-input-sizeflag tocheckandfixsubcommands - Replace
os.ReadFilewithReadFileLimitedat all primary read sites (runner, fixer, stdin) - Replace
os.ReadFile/fs.ReadFileat all secondary read sites (include, catalog, cross-file-ref, required-structure, metrics, merge driver, config) - Thread
MaxInputBytesto rules that read files (vialint.Filefield) - Add integration test: file exceeding limit produces error diagnostic and exit code 2
- Document
max-input-sizeindocs/reference/cli.md
-
ReadFileLimitedreturns error for files exceeding the configured limit -
ReadFileLimitedsucceeds for files at or below the limit (no off-by-one) -
ReadFileLimitedwithmax <= 0applies no limit (unlimited mode) -
ParseSizehandles2MB,500KB,1GB, bare integers, and0 -
.mdsmith.ymlmax-input-sizekey is respected -
--max-input-sizeCLI flag overrides config -
--max-input-size 0disables the limit - All ~15 read sites use the limited helper
- Error message includes actual size and limit
- All tests pass:
go test ./... -
go tool golangci-lint runreports no issues