Skip to content
Merged
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
18 changes: 13 additions & 5 deletions .github/CI_SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,19 @@ configured. Review new action source and transitive downloads as well as pins.
Do not dismiss alerts merely to reduce the count. Correct versions or graph
semantics, submit the new graph, and let GitHub close packages that are no longer
present.
Baseline-sensitive API, JSP-engine and build-plugin dependencies are excluded only
from the broad Maven **version-update group**, so their proposals receive individual
review. They remain eligible for updates; the security-update group is unchanged.
See [dependency decisions](DEPENDENCY_DECISIONS.md) for the current contracts,
PR dispositions and conditions for reconsideration.
Baseline-sensitive API, JSP-engine and build-plugin dependencies are excluded
from the broad Maven **version-update group**, so unsuppressed proposals receive
individual review. Reviewed incompatible minor and major proposal classes use
`ignore.update-types`; GitHub applies those rules only to version updates, so
security updates remain eligible and the security-update group is unchanged.
Dependabot classifies repeated Maven coordinates from their lowest occurrence;
where historical and current inputs share a coordinate, a current-line patch can
therefore fall inside a suppressed update class. Review those coordinates
manually during dependency/release maintenance and for every advisory. Revisiting
a suppressed version class requires a deliberate compatibility change and removal
or narrowing of its rule. See
[dependency decisions](DEPENDENCY_DECISIONS.md) for the exact scopes, current
contracts, PR dispositions and conditions for reconsideration.
The nonstandard XML files under `compatibility/dependencies` remain explicit
manual compatibility fixtures. In particular Felix 5.6.12 is an intentional
OSGi R6/Java 8 baseline, not a production dependency; the Maven ignore prevents
Expand Down
55 changes: 41 additions & 14 deletions .github/DEPENDENCY_DECISIONS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Dependency proposal decisions — 1.5.0

Initial review: 2026-09-26. Focused API follow-up: 2026-09-27.
Initial review: 2026-09-26. Focused API follow-up: 2026-09-27. Dependabot
proposal-policy follow-up: 2026-09-28.

PRs [#176](https://github.com/OWASP/owasp-java-encoder/pull/176) and
[#188](https://github.com/OWASP/owasp-java-encoder/pull/188) mixed ordinary build
Expand Down Expand Up @@ -46,7 +47,7 @@ old Servlet 6.0.0 and EL 4.0.0 support JARs remain explicit japicmp inputs for t
| Plexus Utils 3.6.2 → 4.1.0 in GPG/Central plugin dependencies | Keep the reviewed 3.6.2 mitigation. The [upstream 4.x migration](https://github.com/codehaus-plexus/plexus-utils) moves XML utilities to a separate artifact; 4.1 also changes DirectoryScanner default exclusions. A newer major is not a drop-in plugin-realm security fix. Reconsider with actual plugin linkage, isolated signing/bundle/rehearsal evidence, transitive-advisory review and repeatable payloads. |
| javax Servlet 3.0.1 → 4.0.1; EL 2.2.5 → 3.0.0 | Keep the test-only minimum API fixtures. These are not bundled production container implementations. Modern engine coverage is separate; replacing the minimum tests would remove evidence for existing consumers. |
| Jakarta Pages 3.0.0 → 4.0.0 | Keep the published provided Pages 3 API and existing `[3.0,4)` package ranges. Reconsider only with a reviewed minimum-runtime/API migration, public POM implications and compatibility evidence. |
| Jasper/annotations 9.0.122 or 10.1.60 → 11.0.26 in the isolated tag fixtures | Keep coherent Tomcat 9 (`javax`) and 10.1 (`jakarta`) engines. PR #188 fails the javax engine with missing `javax.servlet.jsp.tagext.SimpleTagSupport` after the Tomcat 11 switch. The optional Boot/browser WAR already exercises Tomcat 11. Patch updates within each intended engine line remain reviewable; cross-line migration needs a separate coverage decision. |
| Jasper/annotations 9.0.122 or 10.1.60 → 11.0.26 in the isolated tag fixtures | Keep coherent Tomcat 9 (`javax`) and 10.1 (`jakarta`) engines. PR #188 fails the javax engine with missing `javax.servlet.jsp.tagext.SimpleTagSupport` after the Tomcat 11 switch. The optional Boot/browser WAR already exercises Tomcat 11. Patch updates within each intended engine line remain candidates for manual review; Dependabot's lowest-version classification cannot automate both lines independently. Cross-line migration needs a separate coverage decision. |

A dependency's test/build scope does not dismiss an advisory. Check each finding's
actual affected versions, executed path and proposed remedy; use a supported fix
Expand All @@ -70,18 +71,44 @@ record; a grouped PR closure is not proof that every proposed upgrade was applie
## Future Dependabot proposals

The [configuration](dependabot.yml) excludes the eleven baseline-sensitive
coordinates above from the broad Maven **version-update group**, not from update
eligibility. They therefore receive individual proposals and compatibility review;
ordinary Maven changes can proceed separately. This follows GitHub's
[group matching rules](https://docs.github.com/en/code-security/reference/supply-chain-security/dependabot-options-reference#groups).
coordinates above from the broad Maven **version-update group**. For the ten
coordinates with a rejected proposal in #218–#227, it also ignores only the
SemVer minor or major version-update classes covered by the decisions above.
This prevents the weekly job from recreating proposals that merely replace
historical comparators, minimum-consumer fixtures, coherent servlet-engine
lines, or reviewed tool majors. The accepted Felix Maven Bundle Plugin remains
individually updateable.

Every new rule uses `update-types`, not a version range or an unqualified
coordinate ignore. GitHub documents that `update-types` affects version updates,
not security updates, so the Maven security-update group and security alerts
remain eligible:
<https://docs.github.com/en/code-security/reference/supply-chain-security/dependabot-options-reference#update-types-ignore>.
Future minor or major upgrades covered by these rules require a deliberate
compatibility change and manual proposal; remove or narrow the matching rule as
part of that reviewed change. Ordinary Maven changes continue separately.

Dependabot combines repeated Maven coordinates and classifies an update from the
lowest version it found. Consequently, when one coordinate has both a historical
input and a newer current input, a current-line patch may be classified as a
minor or major change from the old floor and suppressed from routine version
PRs. This affects, for example, the split JSP/Jakarta API comparators and the
mixed Plexus Utils plugin realms. It is an explicit noise-versus-automation
tradeoff: manually review those coordinates during dependency and release
maintenance, and always when an advisory appears. The behavior is pinned in
Dependabot's [`DependencySet`](https://github.com/dependabot/dependabot-core/blob/a6e095f540c6542facc06ba47f66418da50d669c/common/lib/dependabot/file_parsers/base/dependency_set.rb#L158-L170)
and [`IgnoreCondition`](https://github.com/dependabot/dependabot-core/blob/a6e095f540c6542facc06ba47f66418da50d669c/common/lib/dependabot/config/ignore_condition.rb#L50-L65)
implementations.

The root directory monitors the complete Maven reactor once; listing each reactor
module again produced duplicate pull requests. The standalone
`compatibility/dependencies` Maven project remains a separate monitored directory.
The Maven security-update group is unchanged, and no new `ignore` rules,
security-alert dismissals or automatic merges are introduced. The pre-existing
Felix framework fixture exception remains scoped and documented in
[CI/security operations](CI_SECURITY.md).

When a new proposal repeats a deferred baseline change, compare it with this dated
record and any new advisory or upstream evidence. Do not automatically close a
security proposal or infer permanent rejection from an older version decision.
No security-alert dismissals or automatic merges are introduced. The pre-existing
unqualified Felix framework fixture exception remains scoped and documented in
[CI/security operations](CI_SECURITY.md); it is the only ignore rule without an
explicit version-update class.

When a new security proposal or manually raised compatibility change revisits a
deferred baseline, compare it with this dated record and current upstream evidence.
Do not automatically close a security proposal or infer permanent rejection from
an older version decision.
48 changes: 46 additions & 2 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,54 @@ updates:
maven-security:
applies-to: security-updates
patterns: ['*']
# The Felix 5.6.12 fixture is the intentional OSGi R6/Java 8 baseline.
# Review alerts by execution scope; never dismiss or upgrade it blindly.
# Suppress only the routine SemVer update classes already rejected by the
# documented compatibility review. GitHub applies update-types only to
# version updates, so security updates remain eligible. Dependabot combines
# repeated Maven coordinates from their lowest version; for mixed historical
# and current inputs, routine current-line updates therefore require manual
# review while these rules are active.
#
# The unqualified Felix rule predates these scoped rules: 5.6.12 is the
# intentional OSGi R6/Java 8 execution fixture. Review any alert against its
# actual test scope; never dismiss or upgrade it blindly.
ignore:
- dependency-name: org.apache.felix:org.apache.felix.framework
- dependency-name: com.puppycrawl.tools:checkstyle
update-types:
- version-update:semver-major
- dependency-name: org.codehaus.plexus:plexus-utils
update-types:
- version-update:semver-major
- dependency-name: javax.servlet.jsp:javax.servlet.jsp-api
update-types:
- version-update:semver-minor
- version-update:semver-major
- dependency-name: javax.servlet:javax.servlet-api
update-types:
- version-update:semver-minor
- version-update:semver-major
- dependency-name: javax.el:javax.el-api
update-types:
- version-update:semver-minor
- version-update:semver-major
- dependency-name: jakarta.servlet.jsp:jakarta.servlet.jsp-api
update-types:
- version-update:semver-minor
- version-update:semver-major
- dependency-name: jakarta.servlet:jakarta.servlet-api
update-types:
- version-update:semver-minor
- version-update:semver-major
- dependency-name: jakarta.el:jakarta.el-api
update-types:
- version-update:semver-minor
- version-update:semver-major
- dependency-name: org.apache.tomcat.embed:tomcat-embed-jasper
update-types:
- version-update:semver-major
- dependency-name: org.apache.tomcat:tomcat-annotations-api
update-types:
- version-update:semver-major
- package-ecosystem: github-actions
directory: /
schedule:
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ unchanged. [1.4.1 is also available from Central](releases/1.4.1-central-publica

Development builds use `1.5.0-SNAPSHOT`; this is not a published release.

* build: stop Dependabot from recreating already-reviewed incompatible API,
servlet-engine and build-tool version proposals. The ignores are limited to
routine version updates in the rejected SemVer classes; security updates remain
eligible. Mixed historical/current coordinates require manual version review
because Dependabot classifies them from their lowest occurrence.
* removed: retire the optional `encoder-esapi` adapter. Version 1.4.1 is its final published release and is no longer supported; no `encoder-esapi:1.5.0` artifact will be published. Consumers must remove the adapter and [migrate Java Encoder-backed calls to the direct context APIs](docs/encoder-esapi-retirement.md). Historical Maven artifacts remain immutable.
* build: remove advisory-affected dependencies from active Maven plugin realms, including the separately invoked compatibility-fixture downloader; invoke the same japicmp engine without its obsolete reporting wrapper; and submit only actually invoked build plugins to GitHub's dependency graph. Shared inherited tooling is recorded once, and no Dependabot alert is dismissed or suppressed.
* build/compatibility: update the published JSP provided API to 2.3.3 and the Jakarta test classpath to Servlet 6.1.0 and EL 6.0.1, while retaining independent JSP 2.2.1 and Java 8-compatible Jakarta minimum-consumer fixtures. Japicmp now resolves distinct old/new support classpaths so the 1.4.1 comparison remains complete. Dependabot scans the root Maven reactor once, rather than opening duplicate module proposals, and continues to scan the standalone compatibility-fixture project separately.
Expand Down
119 changes: 119 additions & 0 deletions scripts/tests/test_ci_policy.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Negative tests for the CI release/version and aggregate-result boundaries."""
import importlib.util
from pathlib import Path
import re
import shutil
import tempfile
import unittest
Expand All @@ -20,6 +21,66 @@ def load(name):
gate = load('check-ci-gate')


def parse_dependabot_ignore(block):
"""Parse the deliberately narrow ignore-rule subset and reject drift."""
rules = {}
seen_keys = {}
current = None
active = None

def require_nonempty_update_types(number):
if (current is not None
and 'update-types' in seen_keys[current]
and not rules[current]):
raise ValueError('empty update-types before line %d' % number)

for number, line in enumerate(block.splitlines(), start=1):
if not line.strip() or line.lstrip().startswith('#'):
continue

dependency = re.fullmatch(
r' {6}- dependency-name\s*:\s*(\S+)\s*', line)
if dependency:
require_nonempty_update_types(number)
name = dependency.group(1)
if name in rules:
raise ValueError('duplicate dependency-name on line %d' % number)
rules[name] = []
seen_keys[name] = {'dependency-name'}
current = name
active = None
continue

key_value = re.fullmatch(
r' {8}([A-Za-z][A-Za-z0-9_-]*)\s*:\s*(.*?)\s*', line)
if key_value:
if current is None:
raise ValueError('ignore key before dependency on line %d' % number)
key, value = key_value.groups()
if key in seen_keys[current]:
raise ValueError('duplicate %s key on line %d' % (key, number))
if key != 'update-types' or value:
raise ValueError('unsupported ignore key on line %d' % number)
seen_keys[current].add(key)
active = key
continue

item = re.fullmatch(r' {10}-\s*(\S+)\s*', line)
if item:
if current is None or active != 'update-types':
raise ValueError('orphan ignore value on line %d' % number)
value = item.group(1)
if value in rules[current]:
raise ValueError('duplicate ignore value on line %d' % number)
rules[current].append(value)
continue

raise ValueError('unexpected ignore syntax on line %d' % number)

require_nonempty_update_types(len(block.splitlines()) + 1)
return rules


class VersionPolicy(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
Expand Down Expand Up @@ -119,6 +180,64 @@ def test_dependabot_scans_reactor_once(self):
self.assertTrue(reactor_modules)
self.assertEqual(set(), reactor_modules.intersection(directories))

def test_dependabot_scopes_reviewed_ignores_to_version_updates(self):
dependabot = (ROOT / '.github/dependabot.yml').read_text()
maven = dependabot.split('- package-ecosystem: maven', 1)[1]
maven = maven.split('- package-ecosystem:', 1)[0]
self.assertIn(
"maven-security:\n"
" applies-to: security-updates\n"
" patterns: ['*']",
maven)

ignore = maven.split(' ignore:\n', 1)[1]
rules = parse_dependabot_ignore(ignore)

minor_and_major = [
'version-update:semver-minor',
'version-update:semver-major',
]
expected = {
'org.apache.felix:org.apache.felix.framework': [],
'com.puppycrawl.tools:checkstyle': [
'version-update:semver-major'],
'org.codehaus.plexus:plexus-utils': [
'version-update:semver-major'],
'javax.servlet.jsp:javax.servlet.jsp-api': minor_and_major,
'javax.servlet:javax.servlet-api': minor_and_major,
'javax.el:javax.el-api': minor_and_major,
'jakarta.servlet.jsp:jakarta.servlet.jsp-api': minor_and_major,
'jakarta.servlet:jakarta.servlet-api': minor_and_major,
'jakarta.el:jakarta.el-api': minor_and_major,
'org.apache.tomcat.embed:tomcat-embed-jasper': [
'version-update:semver-major'],
'org.apache.tomcat:tomcat-annotations-api': [
'version-update:semver-major'],
}
self.assertEqual(expected, rules)
self.assertEqual(
{'org.apache.felix:org.apache.felix.framework'},
{name for name, update_types in rules.items() if not update_types})

mutations = {
'spaced versions key': ignore.replace(
' update-types:\n', ' versions : ["[0,)"]\n', 1),
'duplicate dependency': ignore + (
'\n - dependency-name: com.puppycrawl.tools:checkstyle\n'),
'duplicate key': ignore.replace(
' update-types:\n',
' update-types:\n update-types:\n', 1),
'unknown key': ignore.replace(
' update-types:\n', ' directory: /\n', 1),
'empty update-types': ignore.replace(
' - dependency-name: org.apache.felix:org.apache.felix.framework\n',
' - dependency-name: org.apache.felix:org.apache.felix.framework\n'
' update-types:\n', 1),
}
for name, mutation in mutations.items():
with self.subTest(mutation=name), self.assertRaises(ValueError):
parse_dependabot_ignore(mutation)

def test_only_executed_plugins_are_submitted(self):
workflow = (ROOT / '.github/workflows/dependency-submission.yaml').read_text()
self.assertIn('-DincludeArtifactIds=', workflow)
Expand Down
Loading