You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
AppSec audit — nap-java, v0.8.0: findings and remediation order #33
Full application-security review of nap-java at v0.8.0: all six modules (nap-core, nap-server, nap-jdbc, nap-client, nap-spring, nap-it), the protocol core, the Spring adapter and its filters, the JDBC stores, dependencies, and CI. Reviewed against the OWASP Top 10 and CWE Top 25, with the NAP v2 RFC as the specification and the TypeScript implementation as the interop reference.
Three of these are cross-implementation divergences rather than bugs in isolation, which is what makes them worth treating as a group. nap-java must agree with nap on the wire, and #27 and #28 are places where it does not.
No injection. Every JdbcSessionStore / JdbcChallengeStore query uses PreparedStatement with bound parameters. findBy(column, value) interpolates a column name, but only from three private call sites passing compile-time constants — not attacker-reachable. Worth an enum for defence in depth, not a finding.
The NIP-98 validator is correct, and better than it had to be.verifySignature() recomputes the event id from the canonical serialization before verifying, with a comment explaining precisely why trusting the supplied id would let any note the victim ever published be re-dressed as a completion. That is the subtle bug in this protocol and it is handled.
Constant-time comparison throughout.MessageDigest.isEqual for refresh tokens and step-up tokens, including across length differences.
Timing side channels handled.padAuthResponse() in a finally so a store outage answers on the same schedule as a refusal; 429s deliberately unpadded.
ClientIpResolver is exemplary. The javadoc states the problem (a proxy collapsing every caller into one rate-limit bucket), why the naive fix is worse (client-settable X-Forwarded-For removes the limit entirely), and the right-to-left walk is implemented correctly.
NapSessionFilter's ACL cache is keyed by principal (not session), bounded, and caches only grants — the reasoning for not caching denials is correct.
No secrets committed.
The protocol implementation is careful and the comments explain the reasoning rather than the mechanics, which is what made this productive to audit. Most findings are about defaults and configuration surface rather than the core: the code does the right thing when wired correctly, and does not always insist on being wired correctly.
Scope
Full application-security review of
nap-javaat v0.8.0: all six modules (nap-core,nap-server,nap-jdbc,nap-client,nap-spring,nap-it), the protocol core, the Spring adapter and its filters, the JDBC stores, dependencies, and CI. Reviewed against the OWASP Top 10 and CWE Top 25, with the NAP v2 RFC as the specification and the TypeScript implementation as the interop reference.Findings
proofbody-field fallback for the NIP-98 proofprotected-path-prefixesdoes not fail closedAllowAllAclResolver, in-memory stores)Three of these are cross-implementation divergences rather than bugs in isolation, which is what makes them worth treating as a group.
nap-javamust agree withnapon the wire, and #27 and #28 are places where it does not.Suggested order
CHANGELOGentry, and they address the same theme of failing open.proofbody field, outside the signed payload hash #28 — deleting the fallback is small; the only real question is whether any client depends on it.What the review did not find
JdbcSessionStore/JdbcChallengeStorequery usesPreparedStatementwith bound parameters.findBy(column, value)interpolates a column name, but only from three private call sites passing compile-time constants — not attacker-reachable. Worth an enum for defence in depth, not a finding.verifySignature()recomputes the event id from the canonical serialization before verifying, with a comment explaining precisely why trusting the supplied id would let any note the victim ever published be re-dressed as a completion. That is the subtle bug in this protocol and it is handled.MessageDigest.isEqualfor refresh tokens and step-up tokens, including across length differences.padAuthResponse()in afinallyso a store outage answers on the same schedule as a refusal; 429s deliberately unpadded.ClientIpResolveris exemplary. The javadoc states the problem (a proxy collapsing every caller into one rate-limit bucket), why the naive fix is worse (client-settableX-Forwarded-Forremoves the limit entirely), and the right-to-left walk is implemented correctly.EventReplayGuardis bounded, sized to the clock-skew allowance, sweeps without owning a thread, and the unbounded variant is deprecated with an accurate explanation. In-memory challenge and session stores never evict: unbounded growth driven by unauthenticated /auth/init #31 is the same treatment not yet applied to the stores.NapSessionFilter's ACL cache is keyed by principal (not session), bounded, and caches only grants — the reasoning for not caching denials is correct.The protocol implementation is careful and the comments explain the reasoning rather than the mechanics, which is what made this productive to audit. Most findings are about defaults and configuration surface rather than the core: the code does the right thing when wired correctly, and does not always insist on being wired correctly.