Skip to content

fix(security): baseline the fs-lint backlog + scope the gate to changed lines - #308

Closed
fyZhang66 wants to merge 1 commit into
mainfrom
fix/security-lint-baseline-changed-line
Closed

fix(security): baseline the fs-lint backlog + scope the gate to changed lines#308
fyZhang66 wants to merge 1 commit into
mainfrom
fix/security-lint-baseline-changed-line

Conversation

@fyZhang66

@fyZhang66 fyZhang66 commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Problem

The Security workflow's ESLint Security (changed files) job failed on the v0.6.0 release push with 141 problems (128 errors) — all security/detect-non-literal-fs-filename in src/commands/test.ts (+ test.test.ts).

None of these were introduced by v0.6.0. The job lints changed FILES, and ESLint lints a file whole — so touching the ~11k-line test.ts surfaced its entire pre-existing backlog of fs.X(<variable path>) calls, on lines the release never touched. As the workflow header already notes, a full-tree run yields ~380 such findings, almost all ordinary local-path fs calls a CLI makes routinely — not a real vulnerability signal for this codebase shape. So "changed files only" doesn't actually bound the noise once a large legacy file is touched.

Fix — two layers, no rule severity lowered, no part of the tree exempted

  • (C) Baseline — commit eslint-suppressions.json (ESLint's native bulk suppressions, auto-read from repo root) recording today's backlog. Pre-existing findings are no longer re-reported; a genuinely new fs call still surfaces because the per-file count exceeds the baseline.
  • (A) Changed-line scope.github/scripts/filter-changed-line-findings.mjs keeps only error findings on lines this diff actually added or modified, so touching a legacy file never fails on its untouched lines. The lint step now emits --format json and runs with --pass-on-unpruned-suppressions, so fixing a baselined finding in a changed file doesn't fail the job on a now-stale suppression entry.

Net: every security/* rule keeps its designed severity, new/changed code is still held to it, and the existing tree is neither re-linted from scratch nor exempted. Retire baseline entries as they get fixed with eslint --prune-suppressions in a maintenance pass (never regenerate it to hide new findings).

Verified locally (against the real commits)

  • v0.6.0 release push (b35dbaebaa517e7c, 16 changed src TS files): ESLint exit 0, filter reports no new findings → the release would go green.
  • A genuinely new writeFileSync(<var>): surfaces past the baseline and the changed-line filter fails the job, pointing at the exact file:line.
  • Fatal ESLint errors (config/crash, exit ≥ 2) still hard-fail — only lint findings are deferred to the changed-line decision.
  • eslint-suppressions.json records only the detect-non-literal-fs-filename error rule (warnings are unaffected and still print). Added to .prettierignore since it is tool-generated.

Note

This PR touches only .github/** + config, no src/**/*.ts, so its own security job sees zero changed source files and passes cleanly.

Summary by CodeRabbit

  • Security
    • Security checks now focus on newly added or modified code, making actionable issues easier to identify.
    • Existing, reviewed findings are tracked through suppression baselines while new high-severity issues continue to fail validation.
  • Chores
    • Improved linting reliability and handling of files with special characters.
    • Added documentation for managing security suppressions and regenerating the baseline.

…gate to changed lines

The Security workflow's ESLint job lints whole changed FILES, so touching a
large legacy file surfaces its entire pre-existing detect-non-literal-fs-filename
backlog. That failed the v0.6.0 release push — 128 errors in src/commands/test.ts,
none introduced by the release, on lines the release never touched.

Two layers, with no rule severity lowered and no part of the tree exempted:

- (C) Commit eslint-suppressions.json — ESLint's native bulk suppressions,
  auto-read from the repo root — recording today's backlog, so pre-existing
  findings are not re-reported. A genuinely new fs call still surfaces because
  the per-file count exceeds the baseline. Retire entries with
  `eslint --prune-suppressions` in a maintenance pass; never regenerate it to
  hide new findings.

- (A) .github/scripts/filter-changed-line-findings.mjs then keeps only error
  findings on lines this diff actually added or modified, so touching a legacy
  file never fails on its untouched lines. The lint step now emits --format json
  and runs with --pass-on-unpruned-suppressions so fixing a baselined finding in
  a changed file doesn't fail the job over a now-stale suppression entry.
@github-actions

Copy link
Copy Markdown

Thanks for the PR, @fyZhang66! A quick note on our workflow: for features and behavior changes we require contributors to open an issue first, claim it by commenting /assign on the issue, then submit a PR that links it (e.g. Closes #123). This PR isn't linked to any issue yet, so it is not review-ready. After fixing it, edit the PR description or push a commit to re-run this check. See CONTRIBUTING → Contribution model.

@github-actions github-actions Bot added the needs-issue PR not linked to an issue yet — please open one first and claim it (see CONTRIBUTING) label Aug 13, 2026
@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The pull request adds an ESLint suppression manifest, a changed-line finding filter, and security workflow integration. ESLint now runs once with JSON output and suppressions. The filter reports severity-2 findings on changed lines and supports full-tree fallback behavior.

Changes

ESLint security filtering

Layer / File(s) Summary
ESLint suppression baseline
eslint-suppressions.json, .prettierignore
Adds suppressions for non-literal filesystem filename findings and excludes the generated manifest from Prettier.
Changed-line finding filter
.github/scripts/filter-changed-line-findings.mjs
Reads ESLint JSON output, extracts changed lines from Git diff hunks, reports severity-2 findings, and returns failure status when findings remain.
Security workflow integration
.github/workflows/security.yml
Persists the Git base, runs ESLint with NUL-safe arguments and suppression handling, and delegates finding filtering to the new script.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Mergeability Score: 🟡 Moderate · up to 55ce6

The updated security gate can miss newly introduced filesystem findings when baseline counts remain unchanged or when a diagnostic spans multiple lines, weakening protection for changed code. These bounded correctness issues should be fixed before merging.

Suggested reviewers: ruili-testsprite

Sequence Diagram(s)

sequenceDiagram
  participant SecurityWorkflow
  participant ESLint
  participant Git
  participant ChangedLineFilter
  SecurityWorkflow->>ESLint: Run with suppressions and JSON output
  SecurityWorkflow->>Git: Resolve changed-line base
  Git-->>SecurityWorkflow: Return base SHA
  SecurityWorkflow->>ChangedLineFilter: Pass report, base, and head
  ChangedLineFilter->>Git: Read zero-context diff
  Git-->>ChangedLineFilter: Return changed line numbers
  ChangedLineFilter-->>SecurityWorkflow: Return lint status
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: baselining existing filesystem lint findings and limiting security failures to changed lines.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/security-lint-baseline-changed-line

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

Test Coverage Report

Metric Coverage
Lines 88.36%
Statements 88.36%
Functions 83.84%
Branches 86.28%

@github-actions

Copy link
Copy Markdown

Thanks, @fyZhang66! CI is red on this PR — here's what failed and how to reproduce it locally:

  • Lint & Format — run npm run lint:fix && npm run format, then commit (logs)

Everything runs on Node 22 after npm ci. Push a fix and this comment flips green automatically once all checks pass.

@fyZhang66

Copy link
Copy Markdown
Contributor Author

Wrong target — this belongs in the source repo, not this mirror. Moving the fix there.

@fyZhang66 fyZhang66 closed this Aug 13, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/scripts/filter-changed-line-findings.mjs:
- Line 71: Update the finding filter condition in the changed-line matching
logic to retain a diagnostic when any line from m.line through m.endLine ??
m.line exists in scope, rather than checking only m.line. Preserve the existing
behavior when scope is null.

In @.github/workflows/security.yml:
- Around line 247-249: Replace the count-only suppression approach in
.github/workflows/security.yml lines 247-249 with a comparison of unsuppressed
base and head findings or a location-aware baseline, so replaced findings on
changed lines are detected. Update the related documentation in
eslint-suppressions.json lines 1-147, the workflow header at
.github/workflows/security.yml lines 31-39, and the header in
.github/scripts/filter-changed-line-findings.mjs lines 5-10 to describe the
corrected baseline behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 103e00ed-640f-4ad2-8ffe-dfa7bd20c9e7

📥 Commits

Reviewing files that changed from the base of the PR and between aa517e7 and 55ce6fc.

📒 Files selected for processing (4)
  • .github/scripts/filter-changed-line-findings.mjs
  • .github/workflows/security.yml
  • .prettierignore
  • eslint-suppressions.json

: result.filePath;
const scope = addedLines(rel);
for (const m of errors) {
if (scope === null || scope.has(m.line)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

For ESLint 10.7.0 JSON formatter output, do lint messages include endLine for multi-line diagnostics, and is the range intended to identify the full reported node?

💡 Result:

Yes, the ESLint JSON formatter output includes endLine for diagnostic messages [1][2]. When a linting rule identifies a range (such as for multi-line diagnostics), the endLine and endColumn properties are populated to define the end point of the reported issue [3][4][5]. If a diagnostic message does not represent a range, these properties are omitted [3][4][5]. Regarding whether the range is intended to identify the full reported node, the endLine and endColumn are used to specify the exact location range on which the error or warning occurred [4][5]. While this often corresponds to the extent of the node involved in the violation—provided the rule author correctly specifies the node's location (loc) during the report—it is not strictly guaranteed for every rule [5]. Rule authors are responsible for defining the reported location, which may be a specific node, a subset of a node, or a more precise range depending on the rule's logic [3][4][5]. Consequently, while the range is intended to accurately represent the problem area, its relationship to the "full node" depends entirely on how the individual rule is implemented [5].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu
file=".github/scripts/filter-changed-line-findings.mjs"
ast-grep outline "$file" --lang javascript
printf '\n--- file ---\n'
cat -n "$file"
printf '\n--- related references ---\n'
rg -n --glob '!node_modules' 'filter-changed-line-findings|endLine|addedLines|scope\.has\(m\.line\)' .github package.json 2>/dev/null || true

Repository: TestSprite/testsprite-cli

Length of output: 5410


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- security workflow context ---'
sed -n '220,275p' .github/workflows/security.yml
printf '%s\n' '--- lint configuration and dependencies ---'
rg -n --glob 'package.json' --glob '*.json' --glob '*.mjs' --glob '*.js' \
  'detect-non-literal|eslint-plugin-security|eslint|suppressions|endLine|messages' .
printf '%s\n' '--- candidate rule references ---'
rg -n --hidden --glob '!node_modules' \
  'detect-non-literal-(fs|require)|non-literal.*filename|security/detect' .

Repository: TestSprite/testsprite-cli

Length of output: 21716


🌐 Web query:

eslint-plugin-security 4.0.1 detect-non-literal-fs-filename rule implementation report loc endLine multiline

💡 Result:

The detect-non-literal-fs-filename rule in eslint-plugin-security is designed to identify when variable, non-literal arguments are used in fs calls, which may indicate a path traversal vulnerability [1][2]. In version 4.0.1, the rule relies on internal utility logic to determine whether an expression is static or dynamic [3]. Regarding the specific details of the rule's reporting and location data: Rule Reporting Behavior The rule flags any filename argument that contains a dynamic (non-literal) component [3]. If an expression includes runtime values—such as a timestamp generated via new Date.toISOString—the rule will report it as non-literal because it cannot distinguish between safe dynamic values and potentially attacker-influenced input [3]. Location Data (loc, endLine, multiline) As an ESLint rule, detect-non-literal-fs-filename produces standard ESLint report objects. When a violation is found, the rule emits a diagnostic that includes location information defined by the ESLint API: 1. loc: This object provides the start and end coordinates of the reported node. It includes: - start: { line, column } - end: { line, column } 2. endLine: This is part of the loc object and identifies the final line number of the expression being flagged. 3. Multiline Support: The rule is capable of reporting on multiline expressions. Because it utilizes ESLint's Abstract Syntax Tree (AST) traversal, it can pinpoint the exact range of a node, even if that node spans multiple lines (e.g., a multi-line path resolution function call) [4]. Technical Implementation The rule defers to an internal helper utility, often found in utils/is-static-expression.js, to evaluate the argument [3]. Recent updates have improved this logic to recognize static properties like import.meta.dirname and import.meta.filename as constants, preventing them from being flagged as non-literal [3]. If a piece of code is intentionally dynamic but safe, users are advised to use eslint-disable comments to suppress the warning for that specific location [3].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path

source = Path(".github/scripts/filter-changed-line-findings.mjs").read_text()
assert "scope.has(m.line)" in source

def current(scope, message):
    return message["line"] in scope

def proposed(scope, message):
    end = message.get("endLine") or message["line"]
    return any(line in scope for line in range(message["line"], end + 1))

cases = [
    ({3}, {"line": 2, "endLine": 3}),
    ({2}, {"line": 2, "endLine": 3}),
    ({4}, {"line": 2, "endLine": 3}),
    ({3}, {"line": 2}),
]
for scope, message in cases:
    print(scope, message, "current=", current(scope, message), "range=", proposed(scope, message))
assert current({3}, {"line": 2, "endLine": 3}) is False
assert proposed({3}, {"line": 2, "endLine": 3}) is True
assert proposed({3}, {"line": 2}) is False
print("range-intersection invariant: PASS")
PY

Repository: TestSprite/testsprite-cli

Length of output: 413


🌐 Web query:

site:github.com/eslint-community/eslint-plugin-security/blob/v4.0.1 detect-non-literal-fs-filename.js context.report node

💡 Result:

The requested GitHub path appears to be invalid: v4.0.1 does not contain that file at the repository root. The rule is likely under:

lib/rules/detect-non-literal-fs-filename.js

Try:

https://github.com/eslint-community/eslint-plugin-security/blob/v4.0.1/lib/rules/detect-non-literal-fs-filename.js

The rule detects variable filename arguments passed to fs calls. [1][2]


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path

report = [
    {"line": 10, "column": 3, "endLine": 12, "endColumn": 4, "severity": 2},
    {"line": 20, "column": 1, "severity": 2},
]
changed = {11}

def retained(message):
    end = message.get("endLine", message["line"])
    return any(line in changed for line in range(message["line"], end + 1))

assert retained(report[0])
assert not retained(report[1])
print("ESLint-shaped range filtering example: PASS")
PY

Repository: TestSprite/testsprite-cli

Length of output: 207


🏁 Script executed:

#!/bin/bash
set -eu
url="https://raw.githubusercontent.com/eslint-community/eslint-plugin-security/v4.0.1/lib/rules/detect-non-literal-fs-filename.js"
curl --fail --silent --show-error "$url" | nl -ba | sed -n '1,240p'

Repository: TestSprite/testsprite-cli

Length of output: 248


🏁 Script executed:

#!/bin/bash
set -eu
curl --fail --silent --show-error \
  https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-4.0.1.tgz |
  tar -tzf - | grep -E 'detect-non-literal-fs-filename|package/lib/rules|package/dist'

Repository: TestSprite/testsprite-cli

Length of output: 317


🏁 Script executed:

#!/bin/bash
set -eu
base="https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-4.0.1.tgz"
printf '%s\n' '--- rule ---'
curl --fail --silent --show-error "$base" |
  tar -xzOf - package/rules/detect-non-literal-fs-filename.js | cat -n
printf '%s\n' '--- tests with report cases ---'
curl --fail --silent --show-error "$base" |
  tar -xzOf - package/test/rules/detect-non-literal-fs-filename.js | grep -n -A8 -B4 'errors\|code:' | head -180

Repository: TestSprite/testsprite-cli

Length of output: 11566


Match the complete diagnostic range.

security/detect-non-literal-fs-filename reports the full CallExpression, which can span multiple lines. If the call starts on an unchanged line and reaches a changed line, scope.has(m.line) drops the finding. Retain the finding when any line from m.line through m.endLine ?? m.line is in scope.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/filter-changed-line-findings.mjs at line 71, Update the
finding filter condition in the changed-line matching logic to retain a
diagnostic when any line from m.line through m.endLine ?? m.line exists in
scope, rather than checking only m.line. Preserve the existing behavior when
scope is null.

Comment on lines +247 to +249
npx eslint --config eslint.security.config.mjs \
--pass-on-unpruned-suppressions --format json -- "${FILES[@]}" \
> "$RUNNER_TEMP/eslint-report.json"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🌐 Web query:

In ESLint 10.7.0 bulk suppressions, does a suppression entry with only {"count": N} retain diagnostic locations? What happens when one existing finding is removed and one new finding for the same rule and file is added?

💡 Result:

In ESLint, a bulk suppression entry with only a {"count": N} property does not retain specific diagnostic locations [1][2]. The eslint-suppressions.json file uses a file-centric format that maps file paths and rule IDs to an integer count, representing the total number of violations of a specific rule in that file, rather than recording the specific line numbers or column information of those violations [1][2][3]. When you perform a linting operation or use the --prune-suppressions flag, ESLint compares the actual number of findings against the count stored in the suppressions file: 1. If you remove one existing finding and add one new finding for the same rule and same file, the net count remains the same. Because ESLint tracks only the total count (N), it will consider the suppression entry still valid for the new, different violation [1][4]. 2. As long as the total number of violations does not exceed the suppressed count, ESLint will continue to suppress all findings for that rule in that file [1]. 3. If the actual count of violations decreases below the suppressed count N, the suppression is considered "unused" and can be removed (pruned) [4][5]. 4. If the actual count of violations exceeds the suppressed count N, ESLint will report all of the findings, as the suppression threshold has been exceeded [1]. Because specific diagnostic locations are not stored in eslint-suppressions.json, ESLint effectively "forgets" exactly which lines were originally suppressed once they are added to the suppressions file [1][6]. The suppression simply acts as a blanket rule to ignore the first N occurrences of that rule in the specified file [1].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- workflow and script references ---'
sed -n '1,70p' .github/workflows/security.yml
sed -n '220,275p' .github/workflows/security.yml
cat -n .github/scripts/filter-changed-line-findings.mjs | sed -n '1,180p'

printf '%s\n' '--- package and suppression configuration ---'
rg -n --hidden -S 'eslint|suppressions|prune-suppressions|pass-on-unpruned' \
  package.json package-lock.json npm-shrinkwrap.json pnpm-lock.yaml yarn.lock \
  eslint.security.config.mjs .github 2>/dev/null || true

printf '%s\n' '--- tracked suppression shape ---'
python3 - <<'PY'
import json
from pathlib import Path

p = Path("eslint-suppressions.json")
data = json.loads(p.read_text())
print("files:", len(data))
for path, rules in list(data.items())[:3]:
    print(path, rules)
PY

Repository: TestSprite/testsprite-cli

Length of output: 27797


🏁 Script executed:

#!/bin/bash
set -eu

tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT

curl -fsSL 'https://api.github.com/repos/eslint/eslint/git/trees/v10.7.0?recursive=1' \
  | jq -r '.tree[] | select(.path|test("suppress|bulk|lint-result"; "i")) | .path' \
  | tee "$tmp/paths.txt"

while IFS= read -r path; do
  [ -n "$path" ] || continue
  url="https://raw.githubusercontent.com/eslint/eslint/v10.7.0/$path"
  echo "--- $path ---"
  curl -fsSL "$url" | rg -n -C 4 'count|suppression|prune|unused|messages|location' || true
done < "$tmp/paths.txt"

Repository: TestSprite/testsprite-cli

Length of output: 204


🏁 Script executed:

#!/bin/bash
set -eu

tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT

curl -fsSL 'https://api.github.com/repos/eslint/eslint/git/trees/v10.7.0?recursive=1' \
  | jq -r '.tree[] | select(.path|test("suppress|bulk|lint-result"; "i")) | .path' \
  > "$tmp/paths.txt"

cat "$tmp/paths.txt"

while IFS= read -r path; do
  [ -n "$path" ] || continue
  echo "--- $path ---"
  curl -fsSL "https://raw.githubusercontent.com/eslint/eslint/v10.7.0/$path" \
    | rg -n -C 4 'count|suppression|prune|unused|messages|location' || true
done < "$tmp/paths.txt"

Repository: TestSprite/testsprite-cli

Length of output: 47293


🏁 Script executed:

#!/bin/bash
set -eu

node - <<'JS'
const fs = require('node:fs');

const suppressions = JSON.parse(fs.readFileSync('eslint-suppressions.json', 'utf8'));
const entries = Object.entries(suppressions);
if (entries.length === 0) throw new Error('suppression manifest is empty');

for (const [file, rules] of entries) {
  for (const [rule, value] of Object.entries(rules)) {
    if (Object.keys(value).join(',') !== 'count' || !Number.isInteger(value.count)) {
      throw new Error(`non-count suppression at ${file} ${rule}`);
    }
  }
}

// Reproduce ESLint 10.7.0 SuppressionsService.applySuppressions():
// a file/rule suppression removes every message for that rule when
// currentCount <= suppressionCount.
const oldFinding = { ruleId: 'security/detect-non-literal-fs-filename', line: 10 };
const newFinding = { ruleId: oldFinding.ruleId, line: 200 };
const suppressionCount = 1;
const currentFindings = [newFinding];

const currentCount = currentFindings.filter(
  message => message.ruleId === oldFinding.ruleId,
).length;

const reported = currentCount > suppressionCount ? currentFindings : [];
const suppressed = currentCount <= suppressionCount ? currentFindings : [];

if (reported.length !== 0 || suppressed.length !== 1) {
  throw new Error('count-preserving replacement was not suppressed');
}

console.log(JSON.stringify({
  manifestFiles: entries.length,
  sampleRule: oldFinding.ruleId,
  baselineCount: suppressionCount,
  currentCount,
  reportedMessages: reported.length,
  suppressedMessages: suppressed.length,
  conclusion: 'a replacement finding at a new location is suppressed',
}, null, 2));
JS

Repository: TestSprite/testsprite-cli

Length of output: 416


Do not derive the changed-line gate from count-only suppressions.

ESLint suppresses all findings for a file and rule when the current count is less than or equal to the stored count. A removed finding can therefore be replaced by a new finding without increasing the count, so the changed-line filter cannot detect it.

Compare unsuppressed base and head findings, or use a location-aware baseline. Update the related documentation and script header.

📍 Affects 3 files
  • .github/workflows/security.yml#L247-L249 (this comment)
  • eslint-suppressions.json#L1-L147
  • .github/workflows/security.yml#L31-L39
  • .github/scripts/filter-changed-line-findings.mjs#L5-L10
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/security.yml around lines 247 - 249, Replace the
count-only suppression approach in .github/workflows/security.yml lines 247-249
with a comparison of unsuppressed base and head findings or a location-aware
baseline, so replaced findings on changed lines are detected. Update the related
documentation in eslint-suppressions.json lines 1-147, the workflow header at
.github/workflows/security.yml lines 31-39, and the header in
.github/scripts/filter-changed-line-findings.mjs lines 5-10 to describe the
corrected baseline behavior.

@fyZhang66
fyZhang66 deleted the fix/security-lint-baseline-changed-line branch August 13, 2026 01:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-issue PR not linked to an issue yet — please open one first and claim it (see CONTRIBUTING)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant