Skip to content

crimes 0.31.0: JSP, LESS and CSS, and scans that lead with what matters

JSON schema remains 0.8.0. Fingerprints of existing findings are unchanged. 0.31 reads server-rendered views and stylesheets — JSP pages, fragments, tag files and tag library descriptors, LESS and plain CSS — and follows what one template or stylesheet hides: the pages that reach a tag through a prefix declared somewhere else, and the partials that use a variables file they never import. It also reorders full scans on JVM and mobile code, and adds public Java, Kotlin, Swift, JSP and LESS repositories to the eval corpus.

Nothing you have recorded stops matching. What you may notice:

  • New files are discovered by default: .jsp, .jspf, .tag, .tld, .less and .css. They appear in coverage.files_by_language as jsp, less and css, and coverage.packs_loaded gains language-jsp and language-less. Finding.pack can carry either value; consumers that switch on it should tolerate them, as for earlier packs.
  • Two new finding types: dangling_template_reference (detector dangling_template_reference.jsp) and overridden_variable (detector overridden_variable.less).
  • Templates and stylesheets carry no test gap, as documentation does, and context gives no test-gap clue for them. Their blast radius is scored.
  • A new large_file threshold, thresholds.largeFile.stylesheet (default 600 lines), for .less and .css.
  • Blast radius changed on cyclic graphs (0.30.2): reach no longer walks round a file’s own import cycle. On Kotlin, where most files share one cycle, scores spread out instead of all sitting near 0.77.
  • context names the relation for template and stylesheet edges. related_files[].reason says “uses @brand, defined here”, “calls this tag as <x:card>” or “includes this file” where the edge is not an import.
  • Test-quality findings on test files (weak_test_signal, mock_saturation) now take the blast radius of the code under test, and say which file in their evidence.

JSP. A lexical scanner, not a grammar: directives, actions, custom tags, EL and scriptlets are read as facts, never guessed. Pages link to what they include (<%@ include %>, <jsp:include>, <c:import>, and web.xml jsp-property-group preludes and codas), to the tag files their tags resolve to — taglib prefixes bind across included fragments, as in a JSP translation unit — to tag library descriptors, and to the Java classes they name in imports, beans, attribute types and scriptlets. TLDs link to their tag, function and listener classes. So a Java class used only from templates gets a real blast radius, and crimes context on a tag file lists the pages that call it, which a search for the file name does not find.

dangling_template_reference.jsp reports what JSP checks only on the first request: an include, tag file or .tld that does not exist while its directory does, and a tag call that omits a required attribute or passes one the tag does not declare. A missing directory is read as a resource a library supplies and never reported.

LESS and CSS. @import edges, and edges from a partial to the files defining the variables and mixins it uses: a partial compiled inside a bundle uses names the bundle’s root imported, never importing them itself. Plain CSS is read as the LESS subset it is, and JavaScript imports of stylesheets resolve (without counting as code fan-out).

overridden_variable.less simulates each bundle’s import order. LESS evaluates variables lazily and the last definition wins everywhere, so a definition a later import redefines does nothing; it is reported when that holds in every bundle compiling it. A base value one bundle overrides and another uses — the theme pattern — is not.

Stylesheet calibration. commented_out_code does not read stylesheets (commented-out declarations are inert). large_file gives them a 600-line budget and skips a stylesheet library copied in under its /*! name vX.Y.Z banner.

Scan ranking on JVM and mobile code (0.30.2, 0.31.0).

  • Blast radius walks the condensation of the import graph: members of a file’s own cycle count only when they import it directly.
  • commented_out_code ramps to its cap over 16 lines rather than 6.
  • Each further finding of the same claim in one file sorts at 0.75ⁿ of its rank score, so one file’s repeats give way to other files’.
  • Test-quality findings are scored by the code under test.

Precision on public Java, Kotlin and Swift (0.30.1). Snapshot test baselines and .appiconset contents are exempt from the image detectors; Kotlin generic, screenshot and Robolectric assertions count; Swift tests on a project’s own XCTestCase subclass are recognised; catch (… ignored2) reads as deliberate.

Security. The scanners are linear on hostile input. Reviews of this work found and fixed two super-linear paths in the JSP scanner and descriptor readers (an EL expression left open in an attribute value scanned quadratically; descriptor patterns could backtrack) and an exponential path in the LESS bundle simulation ((multiple) imports). Hostile-input tests pin each one.

On the private estate behind 0.30 (a Java web application with 1,089 templates and 948 stylesheets), against a 0.30.2 build: every tagdir tag call (3,828) resolves; no reference is falsely reported dangling; 258 existing findings gain blast radius, mostly Java classes used from templates; 9 of the top 10 stay in the top 10. Findings 1,590 → 1,682, every one of the 92 additions on a template or stylesheet. overridden_variable.less reports four definitions there, all real: two redundant copies and two overrides that a later import undoes (an asset path and a feature flag).

On public repositories: Apache Roller 444 → 451 findings (one false orphaned_destination on a script a JSP includes removed); spring-framework-petclinic unchanged; Bootstrap 3.4.1 37 → 41. At 0.30.1 the precision fixes took Now in Android from 57 to 33 findings and Kickstarter’s iOS app from 713 to 366.

Evals. A full live round at 0.31.0 completed 134/134 cells (67 scenarios, Claude and Codex). On the 60 scenarios shared with the last round (0.29.9, re-scored with the same scorer) Claude holds at 0.86 and Codex rises from 0.72 to 0.75; on the seven new Java, Kotlin, Swift, JSP and LESS scenarios both score 0.96. Two scorer faults found on the way were corrected and re-scored: file paths with the new packs’ extensions were not recognised, and paths an agent linked absolutely into its disposable workspace did not count (12 Codex cells). Ranking on the labelled scenarios: mean_ndcg_deep 0.4041, against 0.4059 at 0.30.2 on a deep set that has since grown from 37 to 38 scenarios.

Performance. On the estate’s web application the scan now parses about 2,000 more files: CPU time rose from 12.8–14.3 s to 15.0–15.7 s across two runs each, and peak memory was unchanged at 410–465 MB. The JSP and LESS packs are pure TypeScript and add no WebAssembly.

  • Clock reads lead the Java backends’ top tens. Discounting recorded readings in files that also compare would move them down, and was measured and rejected: it lowers the labelled metric on exactly the case it should keep, and the readings sit in each backend’s most central files, deciding expiry and age checks.
  • A large file used everywhere ranks low. On Bootstrap, large_file on variables.less (53 users) ranks 34th of 40, because structural types are scaled down. Whether wide use should lift a structural finding is undecided.
  • TLD-declared tag contracts are checked but no measured repository exercised them.
  • Large repositories: a full n8n scan still takes about 27 minutes.
  • Java 21 type patterns still do not parse with the current tree-sitter-java.

Upgrade by updating the npm package normally. See language packs and calibration follow-ups.