Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions .github/scripts/filter-changed-line-findings.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/**
* Fail the security lint only on ERROR-severity findings that land on lines
* THIS change actually added or modified.
*
* This is the "changed-line" half of the gate. The other half — the
* committed `eslint-suppressions.json` baseline — has already removed the
* pre-existing backlog from the ESLint report before it reaches this script,
* so what remains is genuinely new/excess findings; this step additionally
* narrows them to the diff's own lines so a large legacy file (e.g.
* `src/commands/test.ts`) does not fail a release just for being touched.
*
* Input: argv[2] = path to an ESLint JSON report (already baseline-filtered)
* env RESOLVED_BASE = base commit to diff against ('' = no base)
* env HEAD_SHA = head commit (default 'HEAD')
* Exit: 1 if any error finding falls on an added/changed line, else 0.
*/
import { readFileSync } from 'node:fs';
import { execFileSync } from 'node:child_process';

const reportPath = process.argv[2];
if (!reportPath) {
console.error('usage: filter-changed-line-findings.mjs <eslint-report.json>');
process.exit(2);
}

const base = (process.env.RESOLVED_BASE || '').trim();
const head = (process.env.HEAD_SHA || 'HEAD').trim();
const root = process.cwd();
const report = JSON.parse(readFileSync(reportPath, 'utf8'));

/**
* The set of line numbers this diff added/modified in `relPath`, parsed from
* `git diff --unified=0` hunk headers (`@@ -a,b +c,d @@` → lines c..c+d-1).
* Returns null to mean "keep every finding" — used when there is no base to
* diff against (a genuine first commit / full-tree fallback) or the diff
* cannot be computed, so nothing new is ever silently hidden.
*/
function addedLines(relPath) {
if (!base) return null;
let out;
try {
out = execFileSync(
'git',
['diff', '--unified=0', '--diff-filter=ACMR', base, head, '--', relPath],
{ encoding: 'utf8' },
);
} catch {
return null;
}
const lines = new Set();
for (const line of out.split('\n')) {
const m = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/.exec(line);
if (!m) continue;
const start = Number(m[1]);
const count = m[2] === undefined ? 1 : Number(m[2]);
// count === 0 is a pure deletion at that position — no added line.
for (let i = 0; i < count; i++) lines.add(start + i);
}
return lines;
}

const offenders = [];
for (const result of report) {
const errors = (result.messages || []).filter(m => m.severity === 2);
if (errors.length === 0) continue;
const rel = result.filePath.startsWith(`${root}/`)
? result.filePath.slice(root.length + 1)
: 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.

offenders.push(`${rel}:${m.line}:${m.column} ${m.ruleId} ${m.message}`);
}
}
}

if (offenders.length > 0) {
console.error(
`Security lint: ${offenders.length} finding(s) on lines this change added/modified ` +
`(and not in eslint-suppressions.json):\n`,
);
for (const o of offenders) console.error(` ${o}`);
console.error(
'\nFix them, or if the call is genuinely safe, disable the specific rule at that line ' +
'with a justification comment. Do NOT regenerate the baseline to hide new findings.',
);
process.exit(1);
}

console.log(
'Security lint: no new findings on changed lines. ' +
'(The pre-existing backlog is baselined in eslint-suppressions.json.)',
);
63 changes: 57 additions & 6 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,26 @@
# ~380 pre-existing findings (almost entirely
# `security/detect-non-literal-fs-filename` on ordinary local-path fs
# calls a config-file-reading CLI makes routinely — not a real
# vulnerability signal for this codebase shape). Blocking on that
# backlog on day one would make the job noise from the first run; this
# keeps every rule at its designed severity and holds new/changed code to
# it without silently exempting the existing tree from the rule.
# vulnerability signal for this codebase shape).
#
# ESLint lints whole FILES, though, so "changed files only" still dumps a
# legacy file's entire backlog the moment one line of it is touched — which
# is exactly what happened on the v0.6.0 release push (touching the ~11k-line
# `src/commands/test.ts` surfaced 128 pre-existing `detect-non-literal-fs-
# filename` errors and failed the job). Two layers fix that WITHOUT lowering
# any rule's severity or exempting the tree:
# (C) A committed `eslint-suppressions.json` baseline (ESLint's native bulk
# suppressions, auto-read from repo root) records today's backlog, so
# pre-existing findings are not re-reported; a genuinely NEW fs call
# pushes the per-file count past the baseline and surfaces.
# (A) `.github/scripts/filter-changed-line-findings.mjs` then keeps only the
# remaining error findings that fall on lines THIS diff added/modified,
# so touching a legacy file never fails on its untouched lines.
# Net: every rule stays at its designed severity, new/changed code is held to
# it, and the existing tree is neither re-linted from scratch nor exempted.
# To retire baseline entries as they get fixed, run ESLint with
# `--prune-suppressions` in a maintenance PR (never regenerate it to hide new
# findings).
# - Secret scanning: this repo's `ci.yml` already runs a gitleaks
# WORKING-TREE scan on every PR/push (added by atlas #274, 2026-07). This
# file adds the complementary FULL-HISTORY scan from #220 as its own job,
Expand Down Expand Up @@ -165,6 +181,11 @@ jobs:

echo "Base used: ${BASE_DESC}"

# Persist the resolved base for the lint step's changed-line filter
# (empty string on the full-tree fallback — the filter treats that as
# "keep every finding", so nothing new is hidden).
printf '%s' "${RESOLVED_BASE}" > "$RUNNER_TEMP/resolved-base.txt"

if [ -n "$RESOLVED_BASE" ]; then
git diff --name-only --diff-filter=ACMR -z "$RESOLVED_BASE" "$HEAD_SHA" -- ':(glob)src/**/*.ts' \
> "$RUNNER_TEMP/changed-ts-files.txt"
Expand Down Expand Up @@ -207,8 +228,38 @@ jobs:
fi
FILE_COUNT=$(tr -dc '\0' < "$RUNNER_TEMP/changed-ts-files.txt" | wc -c)
echo "Linting ${FILE_COUNT} file(s)."
xargs -a "$RUNNER_TEMP/changed-ts-files.txt" -0 \
npx eslint --config eslint.security.config.mjs --format stylish --

# Read the NUL-separated list into an array (NUL-safe, single ESLint
# invocation so `--format json` emits ONE array) and lint with:
# - the committed eslint-suppressions.json baseline (auto-read from
# repo root) so the pre-existing backlog is not re-reported; and
# - --pass-on-unpruned-suppressions so FIXING a baselined finding in
# a changed file (its count drops) doesn't fail the job over a now-
# stale suppression entry (prune it in a separate maintenance pass).
# The trailing `--` stops ESLint's option parser at the file boundary.
mapfile -d '' -t FILES < "$RUNNER_TEMP/changed-ts-files.txt"

# ESLint exits 1 when it reports findings and 2 on a fatal (config/crash)
# error; only 2 is a real failure here, because the changed-line filter
# below — not ESLint's own exit — decides pass/fail. Capture the report
# even on exit 1, but surface a genuine crash.
set +e
npx eslint --config eslint.security.config.mjs \
--pass-on-unpruned-suppressions --format json -- "${FILES[@]}" \
> "$RUNNER_TEMP/eslint-report.json"
Comment on lines +247 to +249

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.

ESLINT_EXIT=$?
set -e
if [ "$ESLINT_EXIT" -gt 1 ]; then
echo "::error title=Security lint crashed::ESLint exited ${ESLINT_EXIT} (fatal error, not a lint finding)."
cat "$RUNNER_TEMP/eslint-report.json" || true
exit "$ESLINT_EXIT"
fi

# Fail only on findings that land on lines this change added/modified
# (see the header note and .github/scripts/filter-changed-line-findings.mjs).
RESOLVED_BASE="$(cat "$RUNNER_TEMP/resolved-base.txt")" \
HEAD_SHA="${{ github.sha }}" \
node .github/scripts/filter-changed-line-findings.mjs "$RUNNER_TEMP/eslint-report.json"

# ── 4. Full-history secret scan (push only) ────────────────────────────────
# Complements ci.yml's working-tree gitleaks job (every PR/push) with the
Expand Down
4 changes: 4 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ coverage/
node_modules/
*.tgz
package-lock.json
# ESLint-generated (bulk suppressions baseline) — written/rewritten by
# `eslint --suppress-all` / `--prune-suppressions`, so leave it in ESLint's own
# format rather than fighting it with Prettier on every regeneration.
eslint-suppressions.json
.claude/worktrees/

# Internal design docs (dropped wholesale from the public snapshot) — not Prettier-governed
147 changes: 147 additions & 0 deletions eslint-suppressions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
{
"src/commands/agent.test.ts": {
"security/detect-non-literal-fs-filename": {
"count": 7
}
},
"src/commands/agent.ts": {
"security/detect-non-literal-fs-filename": {
"count": 4
}
},
"src/commands/project.test.ts": {
"security/detect-non-literal-fs-filename": {
"count": 3
}
},
"src/commands/project.ts": {
"security/detect-non-literal-fs-filename": {
"count": 6
}
},
"src/commands/test.artifact.spec.ts": {
"security/detect-non-literal-fs-filename": {
"count": 12
}
},
"src/commands/test.cancel.spec.ts": {
"security/detect-non-literal-fs-filename": {
"count": 2
}
},
"src/commands/test.create-batch-run.spec.ts": {
"security/detect-non-literal-fs-filename": {
"count": 3
}
},
"src/commands/test.flaky.spec.ts": {
"security/detect-non-literal-fs-filename": {
"count": 2
}
},
"src/commands/test.quickwins.spec.ts": {
"security/detect-non-literal-fs-filename": {
"count": 20
}
},
"src/commands/test.rerun.closure-fanout.spec.ts": {
"security/detect-non-literal-fs-filename": {
"count": 2
}
},
"src/commands/test.rerun.spec.ts": {
"security/detect-non-literal-fs-filename": {
"count": 3
}
},
"src/commands/test.result.history.spec.ts": {
"security/detect-non-literal-fs-filename": {
"count": 2
}
},
"src/commands/test.run.spec.ts": {
"security/detect-non-literal-fs-filename": {
"count": 4
}
},
"src/commands/test.test.ts": {
"security/detect-non-literal-fs-filename": {
"count": 92
}
},
"src/commands/test.ts": {
"security/detect-non-literal-fs-filename": {
"count": 29
}
},
"src/commands/test.wait.spec.ts": {
"security/detect-non-literal-fs-filename": {
"count": 2
}
},
"src/lib/agent-targets.ts": {
"security/detect-non-literal-fs-filename": {
"count": 1
}
},
"src/lib/bundle.commit.test.ts": {
"security/detect-non-literal-fs-filename": {
"count": 19
}
},
"src/lib/bundle.test.ts": {
"security/detect-non-literal-fs-filename": {
"count": 18
}
},
"src/lib/bundle.ts": {
"security/detect-non-literal-fs-filename": {
"count": 23
}
},
"src/lib/credentials.test.ts": {
"security/detect-non-literal-fs-filename": {
"count": 17
}
},
"src/lib/credentials.ts": {
"security/detect-non-literal-fs-filename": {
"count": 14
}
},
"src/lib/junit-report.test.ts": {
"security/detect-non-literal-fs-filename": {
"count": 4
}
},
"src/lib/junit-report.ts": {
"security/detect-non-literal-fs-filename": {
"count": 6
}
},
"src/lib/plan-schema.spec.ts": {
"security/detect-non-literal-fs-filename": {
"count": 1
}
},
"src/lib/skill-nudge.ts": {
"security/detect-non-literal-fs-filename": {
"count": 1
}
},
"src/lib/telemetry.spec.ts": {
"security/detect-non-literal-fs-filename": {
"count": 2
}
},
"src/lib/update-check.test.ts": {
"security/detect-non-literal-fs-filename": {
"count": 1
}
},
"src/lib/update-check.ts": {
"security/detect-non-literal-fs-filename": {
"count": 3
}
}
}
Loading