Disallowed Tag Library
ID |
html.disallowed_taglibs |
Severity |
critical |
Remediation Complexity |
medium |
Remediation Risk |
low |
Remediation Effort |
medium |
Resource |
Code Smell |
Language |
Html |
Tags |
best-practice, code-style |
Description
Reports a page that pulls in a tag library the project has decided its views must not use.
Both spellings of a library declaration are read, because the two view technologies that share the HTML file disagree about it:
-
JSP writes a directive —
<%@ taglib uri="…" prefix="…" %>. -
Facelets writes an XML namespace attribute on any element —
xmlns:sql="…".
A JSP comment declares nothing, so <%-- taglib uri="…" --%> is not reported, and neither is a
page or include directive, nor an ordinary attribute that merely happens to hold a URI.
disallowedUris ships denying the JSTL sql library under every spelling it has shipped with —
jakarta.tags.sql, http://java.sun.com/jsp/jstl/sql, http://xmlns.jcp.org/jsp/jstl/sql,
http://java.sun.com/jstl/sql and http://java.sun.com/jstl/sql_rt — because querying the database
from the markup is a defect in every project rather than a matter of local policy.
Which further libraries are unacceptable is a project decision: add them to disallowedUris, or
set the list empty to turn the URI check off. disallowedPrefixes stays empty by default, since an
alias is a local naming convention no default can know.
Rationale
A tag library is a dependency of the view, and it is a dependency declared where dependencies are hardest to see — inside the markup, rather than in the build file the rest of the project’s dependencies live in. Some libraries carry capabilities a presentation layer has no business holding.
The standing example is the JSTL sql library. It lets a page open a connection and run a query
from inside the markup:
<%@ taglib uri="jakarta.tags.sql" prefix="sql" %>
<sql:query var="rows" dataSource="jdbc/reports">
SELECT * FROM orders WHERE customer = '${param.customer}'
</sql:query>
Every problem with that is structural rather than stylistic. Data access has moved into the view, so it is outside the transaction, connection-pool and audit boundaries the application maintains everywhere else. The query is assembled by string substitution from a request parameter, which is SQL injection with no layer left in which to parameterise it. And the query is invisible to anyone reading the service layer, so the page’s data dependencies do not appear in any inventory of them.
The same reasoning covers a project’s own libraries. Once a shared component library is superseded, a new page must not reach for the old one; a library reserved for the admin console must not be declared by a customer-facing page; a library whose tags emit unescaped markup must not be available to a view that renders user content. In each case the decision is already made and written down somewhere, and this rule is what makes it hold at the point of use.
The Facelets spelling matters just as much as the JSP one, and is the one a modern codebase actually contains:
<h:panelGroup xmlns:sql="jakarta.tags.sql">
Remediation
Remove the declaration and move the work it enabled into the layer that owns it. For the sql
example, the query belongs in a repository or service, and the page receives the finished rows
through the model:
<h:dataTable value="#{reportBean.orders}" var="order">
<h:column>#{order.reference}</h:column>
</h:dataTable>
For a superseded component library, migrate the page to the replacement and drop the old prefix. For a library that is legitimate in one part of the application and not another, the deny-list is per-configuration, so the restriction can be scoped to the views it applies to rather than applied project-wide.
Configuration
| Property | Default | Description |
|---|---|---|
|
the JSTL |
URIs of the tag libraries the project’s views must not declare. Matched in full and
case-insensitively, against a JSP |
|
|
Local prefixes the project’s views must not bind, for a project that has standardised its aliases. Matched in full and case-insensitively. A prefix is only a local alias, so a deny-list built on prefixes is evaded by renaming; use it as a convenience, not as the primary list. |
With both lists empty the rule reports nothing.