Skip to content

Commit 82fe822

Browse files
udsy19kz-tob
andauthored
static-analysis: Scan files over 1 MB, and report the ones still skipped (#343)
semgrep skips any file larger than --max-target-bytes and reports nothing about it: no paths.skipped entry, no error, and the file simply absent from paths.scanned. run-scans.sh never set the flag, so semgrep's 1,000,000-byte default applied and a scan that never opened a file recorded `findings: 0` next to a plausible filesScanned count. Reproduced on semgrep 1.178.0 against a 1,050,060-byte bundle carrying a document.write(location.hash) sink, beside a 36-byte sibling so the scan opened something and coveredNothing stayed empty: before findings 0, filesScanned 1, coveredNothing [] after findings 1, filesScanned 2, maxTargetBytes 20000000 The limit is now explicit at 20,000,000 bytes and settable with --max-target-bytes N, where 0 means no limit as it does in semgrep. Bytes only: the oversized report below compares the limit against a file size, and semgrep's '1.5MB' spelling would have to be parsed a second way that could disagree. A higher default moves the cliff rather than removing it, so the files still over it are named as `oversized` in scans.json, scoped to the --include globs a scan actually passed. The report phase of the workflow and the report template carry it for the reason they carry coveredNothing: a gap only stderr knows about is a gap the report calls coverage. Cross-language rulesets take no --include and semgrep decides for itself what it can parse, so their share is not enumerable here; references/scan-modes.md says so and maxTargetBytes is in the report. Eleven assertions added to tests/run_scan_tests.sh (104 in total), and one to the workflow harness with the mutation that proves it load-bearing. Refs #298 Signed-off-by: Udaya Tejas <[email protected]> Co-authored-by: kz-tob <[email protected]>
1 parent ed01a6b commit 82fe822

9 files changed

Lines changed: 210 additions & 6 deletions

File tree

‎.claude-plugin/marketplace.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@
188188
},
189189
{
190190
"name": "static-analysis",
191-
"version": "1.4.5",
191+
"version": "1.5.0",
192192
"description": "Static analysis toolkit with CodeQL, Semgrep, and SARIF parsing for security vulnerability detection",
193193
"author": {
194194
"name": "Axel Mierczuk & Paweł Płatek"

‎plugins/static-analysis/.claude-plugin/plugin.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "static-analysis",
3-
"version": "1.4.5",
3+
"version": "1.5.0",
44
"description": "Static analysis toolkit with CodeQL, Semgrep, and SARIF parsing for security vulnerability detection",
55
"author": {
66
"name": "Axel Mierczuk & Paweł Płatek"

‎plugins/static-analysis/skills/semgrep/SKILL.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,8 @@ read from the processes and the JSON they wrote.
264264
- [ ] Approved plan written to `$OUTPUT_DIR/rulesets.json` at the Step 3 gate, and passed to
265265
the scanner unchanged
266266
- [ ] `coveredNothing` from `scans.json` is empty, or listed in the report
267+
- [ ] `oversized` from `scans.json` is empty, or listed in the report — those files exceed
268+
`--max-target-bytes` and no ruleset opened them
267269
- [ ] Raw per-scan outputs stored in `$OUTPUT_DIR/raw/`
268270
- [ ] `results.sarif` exists in `$OUTPUT_DIR/results/` and is valid JSON
269271
- [ ] Important-only mode: post-filter applied before merge, merge run with `--important`, unfiltered results preserved in `raw/`

‎plugins/static-analysis/skills/semgrep/references/scan-modes.md‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,33 @@ set would drop real findings from the primary deliverable with nothing downstrea
140140

141141
- **Run all**: no severity flags
142142
- **Important only**: `--severity WARNING --severity ERROR`
143+
- **Both**: `--max-target-bytes 20000000` (see below)
143144

144145
That pre-filter is applied by semgrep at scan time, before the metadata post-filter above. A
145146
rule shipping with CLI severity INFO is dropped by the flag and never reaches the filter that
146147
would have kept it.
148+
149+
### File Size Limit
150+
151+
semgrep skips any file larger than `--max-target-bytes` and says nothing about it in `--json`
152+
output: no `paths.skipped` entry, no error, and the file is absent from `paths.scanned`. A 1 MB
153+
bundle therefore reports `findings: 0` exactly as a file that was opened and found clean does.
154+
The reason is only reachable through `--verbose`, which the scanner does not use.
155+
156+
`run-scans.sh` sets the limit explicitly at 20,000,000 bytes rather than leaning on semgrep's
157+
1,000,000-byte default, which generated sources, single-file bundles and legacy monoliths pass
158+
routinely. Override it with `--max-target-bytes N` when a target holds larger sources, or with
159+
`--max-target-bytes 0` to lift the limit entirely, which is what semgrep does with a zero. The
160+
effective value is recorded as `maxTargetBytes` in `scans.json`. Bytes only: semgrep also accepts
161+
`1.5MB`, and the scanner refuses that form rather than parsing the size a second way to build the
162+
report below.
163+
164+
Files an `--include` glob selected that are still over the limit are listed as `oversized` in
165+
`scans.json`, and they belong in the report: a scan that did not open a file has not cleared it.
166+
Report them as a coverage gap, not as a clean result. Raising the limit re-scans them at the cost
167+
of memory and time, so a deliberate decision to leave one out is fine as long as it is written
168+
down.
169+
170+
Cross-language rulesets (`p/security-audit`, `p/secrets`, third-party repos) take no `--include`,
171+
and semgrep alone decides which files it can parse, so `oversized` cannot enumerate their share
172+
of the gap. `maxTargetBytes` is in the report so that part can be checked by hand.

‎plugins/static-analysis/skills/semgrep/scripts/run-scans.sh‎

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@ readonly METRICS_OFF="--metrics=off"
1111
# references/scan-modes.md, not here.
1212
SEVERITY_FLAGS=(--severity WARNING --severity ERROR)
1313
readonly DEFAULT_JOBS=4
14+
# semgrep drops any file larger than --max-target-bytes and reports NOTHING about it: no
15+
# paths.skipped entry, no error, and the file is simply absent from .paths.scanned. A scan that
16+
# never opened a 1 MB bundle therefore records `findings: 0` next to a plausible filesScanned
17+
# count, which is indistinguishable from a file that was opened and found clean. semgrep's own
18+
# default is 1,000,000 bytes, which generated sources, single-file bundles, vendored blobs and
19+
# legacy monoliths pass routinely, so the limit is set explicitly here, recorded in scans.json,
20+
# and the files it still drops are named — see the oversized report below.
21+
readonly DEFAULT_MAX_TARGET_BYTES=20000000
1422

1523
# A semgrep join rule carries `mode: join` and the `join:` block that mode needs, and requiring
1624
# both is what keeps the prune below off a rule that merely mentions the words. Each is anchored
@@ -32,12 +40,18 @@ Usage: run-scans.sh --target DIR --output-dir DIR --mode MODE --rulesets FILE [o
3240
--rulesets FILE JSON: {"baseline":[...], "<language>":[...], "third_party":["https://..."]}
3341
--pro add --pro to every command (Semgrep Pro engine)
3442
--jobs N concurrent semgrep processes (default 4)
43+
--max-target-bytes N
44+
skip files larger than N bytes; 0 means no limit, as it does in
45+
semgrep (default 20000000). semgrep's own default is 1000000 and
46+
it drops the file with no trace in the JSON
3547
--dry-run print the commands that would run, then exit; clones nothing
3648
3749
Writes OUTPUT_DIR/scans.json:
3850
{scans:[{lang,ruleset,json,sarif,findings,filesScanned,partial,exitCode}],
39-
failed:[...], skipped:[...], unscoped:[lang], alsoShared:["lang/ruleset"]}
51+
failed:[...], skipped:[...], unscoped:[lang], alsoShared:["lang/ruleset"],
52+
maxTargetBytes:N, oversized:[path]}
4053
partial is a scan that wrote complete output while some of its rules failed to compile.
54+
oversized names the files an --include glob selected that are too large for semgrep to open.
4155
USAGE
4256
}
4357

@@ -145,6 +159,7 @@ count_lines() {
145159

146160
TARGET="" OUTPUT_DIR="" MODE="" RULESETS_FILE="" PRO="" DRY_RUN=""
147161
JOBS=$DEFAULT_JOBS
162+
MAX_TARGET_BYTES=$DEFAULT_MAX_TARGET_BYTES
148163
while [ $# -gt 0 ]; do
149164
case "$1" in
150165
--target)
@@ -167,6 +182,10 @@ while [ $# -gt 0 ]; do
167182
JOBS=${2:-}
168183
shift 2
169184
;;
185+
--max-target-bytes)
186+
MAX_TARGET_BYTES=${2:-}
187+
shift 2
188+
;;
170189
--pro)
171190
PRO=1
172191
shift
@@ -198,6 +217,12 @@ fi
198217
jq -e . "$RULESETS_FILE" >/dev/null 2>&1 || die "--rulesets is not valid JSON: $RULESETS_FILE"
199218
case "$JOBS" in '' | *[!0-9]*) die "--jobs must be a positive integer, got '${JOBS}'" ;; esac
200219
[ "$JOBS" -ge 1 ] || die "--jobs must be at least 1"
220+
# Bytes only. semgrep also takes '1.5MB', but the oversized report below has to compare the
221+
# limit against a file size, so a suffixed value would have to be parsed twice and the two
222+
# readings could disagree. 0 is accepted and means no limit, which is what semgrep does with it.
223+
case "$MAX_TARGET_BYTES" in
224+
'' | *[!0-9]*) die "--max-target-bytes must be a whole number of bytes, got '${MAX_TARGET_BYTES}'" ;;
225+
esac
201226

202227
# Both paths resolve the same way before being compared. Resolving only one makes the equality
203228
# and inside-the-target checks miss on any symlinked path (every /var path on macOS), and the
@@ -294,11 +319,13 @@ UNSCOPED="$WORK/unscoped.txt"
294319
# exactly like p/gosec would have. semgrep counts what it opened in .paths.scanned, so the two
295320
# can be told apart and the ones that covered nothing named.
296321
COVERED_NOTHING="$WORK/covered-nothing.txt"
322+
OVERSIZED="$WORK/oversized.txt"
297323
: >"$SCAN_LIST"
298324
: >"$SKIPPED"
299325
: >"$ALSO_SHARED"
300326
: >"$UNSCOPED"
301327
: >"$COVERED_NOTHING"
328+
: >"$OVERSIZED"
302329

303330
# Stems are the filename half of every output path and the key results are matched on, so a
304331
# collision would let one scan's result be read as another's.
@@ -522,6 +549,7 @@ build_argv() {
522549
ARGV=(semgrep)
523550
[ -z "$PRO" ] || ARGV+=(--pro)
524551
ARGV+=("$METRICS_OFF")
552+
ARGV+=(--max-target-bytes "$MAX_TARGET_BYTES")
525553
[ "$MODE" != "important-only" ] || ARGV+=("${SEVERITY_FLAGS[@]}")
526554
# Unquoted on purpose: includes is a space-separated glob list and must word-split here.
527555
# shellcheck disable=SC2086
@@ -554,6 +582,59 @@ if [ -n "$DRY_RUN" ]; then
554582
exit 0
555583
fi
556584

585+
# ------------------------------------------------------------- oversized files
586+
# What semgrep refused to open, as against what it opened and found clean. filesScanned counts
587+
# the second and nothing in the JSON reports the first, so a file over the limit reads as a file
588+
# that is clean. Named here for the reason coveredNothing and excludePattern are: a gap only
589+
# stderr knows about is a gap the report calls coverage.
590+
#
591+
# Scoped to the --include globs this run actually passes, because those are the files a scan
592+
# claimed to cover. The cross-language rulesets take no --include and semgrep decides for itself
593+
# which files it can parse, so their share of the gap is not enumerable from here;
594+
# references/scan-modes.md says so, and maxTargetBytes is in the report for a reader to check by
595+
# hand. includes_for only ever emits basename patterns, which is what makes -name enough.
596+
scoped_globs() {
597+
cut -f5 "$SCAN_LIST" | tr ' ' '\n' | grep -v '^$' | sort -u | tr '\n' ' '
598+
}
599+
600+
# The globs are read into an array rather than left to word-split: unquoted expansion is
601+
# pathname expansion too, and these patterns are meant to match under the target, not the CWD.
602+
find_oversized() { # <limit> <globs> -> paths relative to TARGET, one per line
603+
local limit=$1 globs=$2 g f
604+
local gl nameargs prune
605+
# 0 disables semgrep's own filter, so nothing is dropped for its size and there is no gap to
606+
# report. Guarded rather than left to `find -size +0c`, which matches every non-empty file.
607+
[ "$limit" -gt 0 ] || return 0
608+
gl=()
609+
nameargs=()
610+
prune=()
611+
[ -n "$(printf '%s' "$globs" | tr -d ' ')" ] || return 0
612+
IFS=' ' read -r -a gl <<<"$globs"
613+
for g in ${gl[@]+"${gl[@]}"}; do
614+
[ -n "$g" ] || continue
615+
if [ ${#nameargs[@]} -eq 0 ]; then
616+
nameargs=(-name "$g")
617+
else
618+
nameargs+=(-o -name "$g")
619+
fi
620+
done
621+
[ ${#nameargs[@]} -gt 0 ] || return 0
622+
# The output directory is already --exclude'd from every scan, so a file inside it is not a
623+
# coverage gap; listing it would put this run's own raw JSON in the report.
624+
[ -z "$EXCLUDE_PATTERN" ] || prune=(-name "$EXCLUDE_PATTERN" -prune -o)
625+
find "$TARGET" ${prune[@]+"${prune[@]}"} -type f -size +"${limit}"c \
626+
\( "${nameargs[@]}" \) -print 2>/dev/null | while IFS= read -r f; do
627+
printf '%s\n' "${f#"$TARGET"/}"
628+
done | sort
629+
}
630+
631+
OVERSIZED_LIST=$(find_oversized "$MAX_TARGET_BYTES" "$(scoped_globs)")
632+
printf '%s\n' "$OVERSIZED_LIST" >"$OVERSIZED"
633+
if [ -n "$OVERSIZED_LIST" ]; then
634+
echo "note: $(count_lines "$OVERSIZED_LIST") file(s) exceed --max-target-bytes" >&2
635+
echo " ($MAX_TARGET_BYTES) and no scan will open them; see .oversized in scans.json." >&2
636+
fi
637+
557638
# ------------------------------------------------------------------- run the scans
558639
# Batched rather than a rolling slot count, because `wait -n` needs bash 4.3 and this has to run
559640
# on the bash 3.2 that ships with macOS. semgrep holds the rules and the scanned ASTs in memory,
@@ -680,15 +761,18 @@ jq -n \
680761
--rawfile alsoSharedRaw "$ALSO_SHARED" \
681762
--rawfile unscopedRaw "$UNSCOPED" \
682763
--rawfile coveredNothingRaw "$COVERED_NOTHING" \
764+
--rawfile oversizedRaw "$OVERSIZED" \
765+
--argjson maxTargetBytes "$MAX_TARGET_BYTES" \
683766
'{
684767
outputDir: $outputDir, rawDir: $rawDir, reposPath: $reposPath, mode: $mode, pro: $pro,
685-
excludePattern: $excludePattern,
768+
excludePattern: $excludePattern, maxTargetBytes: $maxTargetBytes,
686769
scans: $scans, failed: $failed,
687770
skipped: ($skippedRaw | split("\n") | map(select(length > 0)) | map(split("\t"))
688771
| map({ruleset: .[0], reason: (.[1] // "clone failed")})),
689772
alsoShared: ($alsoSharedRaw | split("\n") | map(select(length > 0)) | unique),
690773
unscoped: ($unscopedRaw | split("\n") | map(select(length > 0)) | unique),
691-
coveredNothing: ($coveredNothingRaw | split("\n") | map(select(length > 0)) | unique)
774+
coveredNothing: ($coveredNothingRaw | split("\n") | map(select(length > 0)) | unique),
775+
oversized: ($oversizedRaw | split("\n") | map(select(length > 0)) | unique)
692776
}' >"$OUTPUT_ROOT/scans.json"
693777

694778
n_ok=$(jq '.scans | length' "$OUTPUT_ROOT/scans.json")

‎plugins/static-analysis/skills/semgrep/workflows/scan-workflow.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,8 @@ and the severity flags are all its job, not yours. It writes `$OUTPUT_DIR/scans.
323323
| `unscoped` | Languages with no `--include` globs, which ran against every file |
324324
| `alsoShared` | Rulesets dropped from a language because the same ruleset is already running unscoped over the whole target. Coverage is unaffected; report them so a per-ruleset accounting adds up |
325325
| `excludePattern` | Set when the output directory sits inside the target: the pattern passed as `--exclude` to every scan, or `""`. semgrep matches it anywhere in the tree, so `out` also drops `src/out/`. **Must be shown when non-empty.** |
326+
| `maxTargetBytes` | The `--max-target-bytes` every scan ran with. semgrep opens no file larger than this |
327+
| `oversized` | Files an `--include` glob selected that are larger than `maxTargetBytes`. semgrep skipped them with no `paths.skipped` entry and no error, so they report 0 findings exactly like a file that was opened and found clean. A scan that did not open a file has not cleared it. **Must be shown.** |
326328
| `reposPath` | The clone directory Step 5 deletes |
327329

328330
**A non-zero exit means no scan succeeded.** The script exits 1 when `scans` is empty, so a run
@@ -439,6 +441,12 @@ one finding flagged by two rulesets is one row in the merge and two in that sum]
439441
looked at anything. Check the plan against the languages Step 1 detected: this is what a
440442
ruleset for a language the target does not contain looks like
441443
444+
### Too Large To Scan:
445+
[omit when oversized is empty]
446+
- <path> — larger than maxTargetBytes, so semgrep never opened it and no ruleset covered it.
447+
Not a clean result: re-run with a higher --max-target-bytes, or say in the report that the
448+
file was left unscanned
449+
442450
### Missing From The Merge:
443451
[omit when the merge printed no "unparseable:" line]
444452
- <file> — the scan succeeded and is counted in scans.json, but its SARIF could not be parsed,

‎plugins/static-analysis/tests/run_scan_tests.sh‎

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ command -v uv >/dev/null 2>&1 || {
1616

1717
PLUGIN_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
1818
SCRIPT="$PLUGIN_ROOT/skills/semgrep/scripts/run-scans.sh"
19-
readonly EXPECTED_ASSERTIONS=93
19+
readonly EXPECTED_ASSERTIONS=104
2020

2121
command -v jq >/dev/null 2>&1 || {
2222
echo "run_scan_tests.sh: jq not found — required" >&2
@@ -574,6 +574,81 @@ ok "$([ -e "$WORK/filter/results.sarif" ] && echo 1 || echo 0)" \
574574
contains "$MERGE_ERR" "python-broken-important.json" \
575575
"the merge error must name the scan that has no filtered JSON"
576576

577+
# ---------------------------------------------------------------------- the file size limit
578+
# semgrep skips any file over --max-target-bytes and writes nothing about it: no paths.skipped
579+
# entry, no error, and the file absent from paths.scanned. `findings: 0` for a file that was
580+
# never opened reads exactly like a file that was opened and found clean.
581+
echo "→ the file size limit"
582+
583+
unset STUB_PATHS
584+
export STUB_RC=0 STUB_RESULTS=''
585+
586+
out=$(dry "$BASIC")
587+
n=$(printf '%s\n' "$out" | grep -c -- '--max-target-bytes 20000000')
588+
eq "$n" "2" "an explicit limit must be on every command rather than semgrep's 1 MB default"
589+
590+
out=$(dry "$BASIC" --max-target-bytes 12345)
591+
n=$(printf '%s\n' "$out" | grep -c -- '--max-target-bytes 12345')
592+
eq "$n" "2" "an overridden limit must reach every command"
593+
594+
# semgrep takes '1.5MB' as well as a byte count, but the oversized report has to compare the
595+
# limit against a file size, so a suffixed value is refused rather than parsed a second way.
596+
msg=$(fails bash "$SCRIPT" --target "$TARGET" --output-dir "$WORK/out" --mode run-all \
597+
--rulesets "$BASIC" --max-target-bytes 5MB --dry-run)
598+
ok $? "a suffixed --max-target-bytes must be rejected rather than reinterpreted"
599+
contains "$msg" "whole number of bytes" "the message must say what form the value takes"
600+
601+
# semgrep reads 0 as no limit at all, so it is passed through rather than refused.
602+
out=$(dry "$BASIC" --max-target-bytes 0)
603+
n=$(printf '%s\n' "$out" | grep -c -- '--max-target-bytes 0')
604+
eq "$n" "2" "zero must reach every command, since semgrep reads it as no limit"
605+
606+
# The files over the limit are named, because filesScanned counts what semgrep opened and can
607+
# say nothing about what it refused. A small limit over small fixtures: the behaviour does not
608+
# depend on the size, and a suite that wrote a 20 MB file to prove it would earn nothing.
609+
BIGT="$WORK/bigproj"
610+
mkdir -p "$BIGT/src" "$BIGT/out"
611+
pad='padding that exists only to push this fixture past the limit
612+
'
613+
big="$pad$pad$pad$pad"
614+
big="$big$big"
615+
printf 'x = 1\n' >"$BIGT/src/small.py"
616+
printf '%s' "$big" >"$BIGT/src/big.py"
617+
printf '%s' "$big" >"$BIGT/src/big.txt"
618+
printf '%s' "$big" >"$BIGT/out/vendored.py"
619+
620+
PATH="$WORK/bin:$PATH" bash "$SCRIPT" --target "$BIGT" --output-dir "$BIGT/out" \
621+
--mode run-all --rulesets "$ONE" --jobs 2 --max-target-bytes 100 >/dev/null 2>&1
622+
BIGJSON="$BIGT/out/scans.json"
623+
ok "$([ -f "$BIGJSON" ] && echo 0 || echo 1)" "a target holding oversized files must still write scans.json"
624+
eq "$(jq -r '.maxTargetBytes' "$BIGJSON" 2>/dev/null || echo NOFILE)" "100" \
625+
"scans.json must record the limit the scans actually ran with"
626+
# One path exactly: small.py is under the limit, big.txt matches no --include glob, and
627+
# out/vendored.py sits in the excluded output directory, so none of the three is a coverage gap.
628+
eq "$(jq -r '.oversized | join(",")' "$BIGJSON" 2>/dev/null || echo NOFILE)" "src/big.py" \
629+
"a file over the limit that an --include glob selected must be named in oversized"
630+
631+
PATH="$WORK/bin:$PATH" bash "$SCRIPT" --target "$BIGT" --output-dir "$WORK/big2" \
632+
--mode run-all --rulesets "$ONE" --jobs 2 --max-target-bytes 100000 >/dev/null 2>&1
633+
eq "$(jq '.oversized | length' "$WORK/big2/scans.json" 2>/dev/null || echo NOFILE)" "0" \
634+
"a limit nothing exceeds must leave oversized empty"
635+
636+
# A cross-language ruleset takes no --include and semgrep alone decides which files it can
637+
# parse, so its share of the gap is not enumerable here. Pinned so the limitation stays a
638+
# decision rather than becoming a surprise.
639+
# No limit means nothing was dropped for its size, so the report must stay empty rather than
640+
# listing every non-empty file the globs matched — which is what `find -size +0c` would give.
641+
PATH="$WORK/bin:$PATH" bash "$SCRIPT" --target "$BIGT" --output-dir "$WORK/big4" \
642+
--mode run-all --rulesets "$ONE" --jobs 2 --max-target-bytes 0 >/dev/null 2>&1
643+
eq "$(jq '.oversized | length' "$WORK/big4/scans.json" 2>/dev/null || echo NOFILE)" "0" \
644+
"a disabled limit must leave oversized empty, not list every file"
645+
646+
BASEONLY=$(plan baseonly '{"baseline":["p/security-audit"],"third_party":[]}')
647+
PATH="$WORK/bin:$PATH" bash "$SCRIPT" --target "$BIGT" --output-dir "$WORK/big3" \
648+
--mode run-all --rulesets "$BASEONLY" --jobs 2 --max-target-bytes 100 >/dev/null 2>&1
649+
eq "$(jq '.oversized | length' "$WORK/big3/scans.json" 2>/dev/null || echo NOFILE)" "0" \
650+
"a plan with no --include globs must leave oversized empty by design"
651+
577652
TOTAL=$((PASS + FAIL))
578653
echo
579654
echo "$PASS passed, $FAIL failed, $TOTAL run"

0 commit comments

Comments
 (0)