Skip to content

fix: repoint config rules at property keys Spring actually reads - #42

Merged
vianbas merged 2 commits into
mainfrom
fix/validate-rule-property-keys
Aug 15, 2026
Merged

fix: repoint config rules at property keys Spring actually reads#42
vianbas merged 2 commits into
mainfrom
fix/validate-rule-property-keys

Conversation

@vianbas

@vianbas vianbas commented Aug 14, 2026

Copy link
Copy Markdown
Owner

What

Repoints three CONFIG rules at property keys Spring Boot actually reads, and adds a test that
makes the class of defect impossible to reintroduce.

Two commits, in order:

  1. test: adds Rule.configKeys(), a vendored index of every property name Spring Boot
    declares across Boot 2.0 through 3.5, and ConfigKeyMetadataTest. This commit is red on
    purpose, naming all three offending rules.
  2. fix: repoints the rules, rewrites the fixtures and rule docs, and turns it green.

Why

Closes #39
Closes #40

Partially addresses #41: the Actuator CORS namespace is now covered, so the HIGH case in that
report is handled. The MEDIUM tier proposed there (wildcard without credentials) is not in
this PR and #41 stays open for it.

spring.web.cors.*, spring.mvc.cors.* and spring.security.debug do not exist in any Spring
Boot release. Spring ignores unknown properties silently, so nothing at runtime ever said
otherwise, and each rule's fixtures encoded the same invented keys. The suite was green while
two of ten rules could not fire on a real project, and SPR-CONFIG-004's documented remediation
told users to write a property Spring throws away.

What changed per rule

Rule Before After
SPR-CONFIG-004 spring.web.cors.*, spring.mvc.cors.* management.endpoints.web.cors.* (Boot 2.0+), spring.graphql.cors.* (Boot 2.7+)
SPR-CONFIG-005 spring.security.debug logging.level.org.springframework.security and loggers below it, at DEBUG or TRACE
SPR-CONFIG-001 real key plus dead management.endpoints.exposure.include real key only; detection unchanged

While rewriting SPR-CONFIG-004 I found a second defect that is not in any issue. The old
firstPresent() logic searched for origins across the whole key list, then credentials across
the whole key list, without requiring them to come from the same namespace. With two real
namespaces that would pair an Actuator wildcard with a GraphQL allow-credentials and report a
finding that is not there. Pairing is now per namespace, and
fixtures/cors-config-cross-namespace fails if that regresses.

How was this verified?

Tests: 59 tests, 0 failures, spotless clean. mvn verify run twice and mvn clean verify
once, BUILD SUCCESS each time. Local JDK is 17; CI builds on 21, which I could not exercise
locally (maven.compiler.release is 17, so this should be uneventful).

The first commit is genuinely red. Checked out at 78e62b7 in a separate worktree, the test
fails naming exactly six keys and nothing else:

SPR-CONFIG-001 -> management.endpoints.exposure.include
SPR-CONFIG-004 -> spring.mvc.cors.allow-credentials
SPR-CONFIG-004 -> spring.mvc.cors.allowed-origins
SPR-CONFIG-004 -> spring.web.cors.allow-credentials
SPR-CONFIG-004 -> spring.web.cors.allowed-origins
SPR-CONFIG-005 -> spring.security.debug

Scanning the probe projects from the issue reports gives the opposite verdicts to before,
which is the point of the change. Both columns are measured, the before column with a jar built
from c26a0e7:

Probe Before After
spring.web.cors.* wildcard + credentials SPR-CONFIG-004 HIGH, exit 1 0 findings, exit 0
management.endpoints.web.cors.* wildcard + credentials 0 findings, exit 0 SPR-CONFIG-004 HIGH, exit 1
spring.security.debug: true SPR-CONFIG-005 LOW 0 findings
logging.level.org.springframework.security: DEBUG 0 findings SPR-CONFIG-005 LOW
logging.level.org.springframework.security.web: TRACE 0 findings SPR-CONFIG-005 LOW

Runtime behaviour behind the doc rewrite was measured, not assumed. SPR-CONFIG-004's doc now
states two different outcomes depending on the Spring Framework version, and both were observed
against running apps:

  • Boot 2.3.12 (spring-web 5.2.15): the attacker origin is reflected with
    Access-Control-Allow-Credentials: true and the endpoint body is readable cross-origin.
  • Boot 3.5.16 (spring-web 6.2.19): the context fails to build.
    BeanCreationException ... When allowCredentials is true, allowedOrigins cannot contain the special value "*". The application never starts.

For SPR-CONFIG-005, a Boot 3.5.16 app with spring-boot-starter-security emits 6 Spring Security
DEBUG lines at startup and 6 more per request under the new key, and none at all under the old
one, while still returning 401 to prove security was active either way.

Dogfood scan (the same command CI runs) reports one finding, the pre-existing
NoOpPasswordEncoderRule.java:91 self-match. No new findings, and the vendored index is not
picked up as a config file.

Goldens regenerated with -DupdateGoldens=true and reviewed as a diff: the same 11 findings
with the same rule ids, differing only in message, property path and line.

Notes for review

  • The index is 2920 property names, ~112KB, regenerated by
    tools/generate-spring-property-index.py. The generator aborts rather than writing a file if
    sentinel properties are missing, so a failed download cannot produce an index that passes
    vacuously. ConfigKeyMetadataTest carries the same guard plus a check that the matcher
    actually rejects invented keys.
  • Map-typed properties are marked in the index, which is what makes
    logging.level.<logger> resolve. Without that, the SPR-CONFIG-005 fix would fail its own test.
  • SPR-CONFIG-002 is exempt from the "must declare keys" check with a stated reason: it matches
    key shape, not a fixed list.
  • Neither rule doc recommends allowed-origin-patterns as a remedy. It is the documented way to
    bypass validateAllowCredentials(), so suggesting it to silence a startup failure would
    restore the exposure the check exists to prevent.

Checklist

  • Linked issue exists and is referenced above
  • Tests added or updated for the behavior change
  • mvn verify passes locally
  • Docs updated (README.md, docs/rules/*.md) if user-facing behavior changed
  • For a new/changed rule: false-positive rationale documented in docs/rules/

Nothing validated that a rule's property keys correspond to properties
Spring actually reads. Two rules shipped with keys that exist nowhere in
Spring Boot: SPR-CONFIG-004 matches spring.web.cors.* / spring.mvc.cors.*
and SPR-CONFIG-005 matches spring.security.debug. Spring ignores unknown
properties silently, so nothing at runtime says otherwise, and each rule's
fixtures encode the same invented keys, so the suite stayed green while
the rules could not fire on a real project.

Adds Rule.configKeys(), a vendored index of every property name Spring
Boot declares across Boot 2.0 through 3.5, and ConfigKeyMetadataTest,
which checks the two against each other. The index is regenerated by
tools/generate-spring-property-index.py; Map-typed properties are marked
so logging.level.<logger> resolves correctly.

The test fails as of this commit, naming the three offenders (including
SPR-CONFIG-001's dead secondary key management.endpoints.exposure.include).
The rule fixes follow; committing the check first so the fix is what turns
it green.

Refs #39, #40, #41
Turns ConfigKeyMetadataTest green.

SPR-CONFIG-004 matched spring.web.cors.* and spring.mvc.cors.*. Neither
has ever existed. It now matches the two surfaces Spring Boot really does
expose as configuration properties: management.endpoints.web.cors.* (Boot
2.0+) and spring.graphql.cors.* (Boot 2.7+). Origins and credentials are
now paired within a namespace rather than across the whole key list, so an
Actuator wildcard is never combined with a GraphQL allow-credentials;
cors-config-cross-namespace pins that down.

SPR-CONFIG-005 matched spring.security.debug, which Spring does not read.
It now matches logging.level.org.springframework.security, and loggers
nested under it, set to DEBUG or TRACE. That is how verbose security
logging is actually turned on, verified against a running Boot 3.5.16 app:
6 security DEBUG lines at startup and 6 per request, against none at all
for the old property.

SPR-CONFIG-001 keeps its real key and drops the dead secondary
management.endpoints.exposure.include. Detection is unchanged.

Scanning the probe projects from the issue reports now gives the opposite
verdicts to before, which is the point: the invented namespace is clean and
the real one is flagged.

Rule docs rewritten. SPR-CONFIG-004's remediation previously told users to
write a property Spring ignores; it now documents that below Framework 5.3
the wildcard leaks at runtime while from 5.3 onward it fails the context at
startup, both measured. Neither doc now recommends allowed-origin-patterns
as a fix, since that bypasses the validation rather than satisfying it.

Goldens regenerated: same 11 findings and the same rule ids, with updated
messages, property paths and lines.

Fixes #39
Fixes #40
Refs #41
@vianbas
vianbas merged commit 589ff01 into main Aug 15, 2026
5 checks passed
@vianbas
vianbas deleted the fix/validate-rule-property-keys branch August 15, 2026 11:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant