-
Notifications
You must be signed in to change notification settings - Fork 69
Expand file tree
/
Copy pathspotbugs-exclude.xml
More file actions
1140 lines (1043 loc) · 55.2 KB
/
Copy pathspotbugs-exclude.xml
File metadata and controls
1140 lines (1043 loc) · 55.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="UTF-8"?>
<!--
SPDX-FileCopyrightText: 2017-2026 Bernard Ladenthin <bernard.ladenthin@gmail.com>
SPDX-License-Identifier: Apache-2.0
-->
<FindBugsFilter
xmlns="https://github.com/spotbugs/filter/3.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://github.com/spotbugs/filter/3.0.0 https://raw.githubusercontent.com/spotbugs/spotbugs/4.8.6/spotbugs/etc/findbugsfilter.xsd">
<!--
PREDICTABLE_RANDOM and DMI_RANDOM_USED_ONLY_ONCE are by design:
KeyProducerJavaRandom.java:54,57 (PREDICTABLE_RANDOM) — the
class is a configurable PRNG selector with intentionally weak
modes (RANDOM_CURRENT_TIME_MILLIS_SEED and RANDOM_CUSTOM_SEED).
These exist to exploit CWE-338 for the security-research use
case: scan for keys derived from wallets that used a weak PRNG.
The "EXPLOIT for: CWE-338" comments at the call sites document
this; the constructor already carries
@SuppressWarnings("squid:S2245") for the equivalent SonarQube
rule.
KeyProducerJavaRandom.java:68 (DMI_RANDOM_USED_ONLY_ONCE) —
the SHA1_PRNG branch is intentionally constructed as
SecureRandom.getInstance("SHA1PRNG") followed by an optional
setSeed(). The comment "To simulate bug: do NOT set a seed at
all" pairs with the EXPLOIT-for-CWE-338 pattern: this enum
value exposes the unseeded-SHA1PRNG vulnerability (CVE-2013-7372
in early Android, plus other historical incidents) so that BAF
can scan for keys generated by buggy software. The seeded
variant cannot be re-expressed without setSeed because
SecureRandom has no getInstance(algorithm, seed) overload.
Spotbugs DMI flags the create-then-setSeed pattern without
tracing the subsequent field assignment to RandomSecretSupplier
(which actually uses the SecureRandom across many calls), so
the "used only once" claim is itself a false positive.
BIP39KeyProducer.java:44 (PREDICTABLE_RANDOM) — class extends
java.util.Random to wrap deterministic BIP39/BIP44 key
derivation in the Random interface. Deterministic reproduction
from a mnemonic phrase is the BIP39 contract; "predictable" is
the feature. BIP39KeyProducer has no DMI_RANDOM_USED_ONLY_ONCE
site (the pattern matches nothing there); covering it in the
same Match block is harmless and keeps the two related
suppressions in one place.
-->
<Match>
<Or>
<Class name="net.ladenthin.bitcoinaddressfinder.keyproducer.BIP39KeyProducer"/>
<Class name="net.ladenthin.bitcoinaddressfinder.keyproducer.KeyProducerJavaRandom"/>
</Or>
<Or>
<Bug pattern="PREDICTABLE_RANDOM"/>
<Bug pattern="DMI_RANDOM_USED_ONLY_ONCE"/>
</Or>
</Match>
<!--
PublicKeyBytes.MAX_PRIVATE_KEY_HEX is the secp256k1 group order
n, a public mathematical constant published in SEC 2 v2 section
2.4.1 (Recommended Parameters secp256k1). It is the same value
in every secp256k1 implementation worldwide (bitcoinj,
libsecp256k1, openssl, ...). The constant defines the upper
bound of the valid private-key range for BigInteger.compareTo
validation; it is never used as a key, signing material, or
secret. See also the field's Javadoc which links to SEC 2 v2.
findsecbugs HARD_CODE_KEY flags long hex literals as "possible
hardcoded cryptographic key" syntactically; it cannot
distinguish a curve parameter from a credential.
-->
<Match>
<Class name="net.ladenthin.bitcoinaddressfinder.model.PublicKeyBytes"/>
<Bug pattern="HARD_CODE_KEY"/>
</Match>
<!--
PublicKeyBytes.runtimePublicKeyCalculationCheck is a developer-
facing self-consistency probe: it recomputes the public-key
hashes via bitcoinj from the same secret key and compares them
against the hashes produced by the GPU/CPU pipeline. Both sides
of the Arrays.equals comparison are derived locally from the
SAME secret key, which the method itself logs at ERROR level
when a mismatch occurs. There is no attacker boundary: a
process able to measure timing here is already running inside
the JVM with full access to the secret key being checked.
findsecbugs UNSAFE_HASH_EQUALS targets attacker-vs-secret MAC
or token comparisons where Arrays.equals leaks the secret byte
by byte. That threat model does not apply here: the "hash" is
a Bitcoin address (public derivation), and the comparison
exists only to validate the GPU-derivation pipeline against
bitcoinj.
-->
<Match>
<Class name="net.ladenthin.bitcoinaddressfinder.model.PublicKeyBytes"/>
<Method name="runtimePublicKeyCalculationCheck"/>
<Bug pattern="UNSAFE_HASH_EQUALS"/>
</Match>
<!--
KeyProducerJavaSocket is the project's TCP plumbing for the
socket-based key producer (CKeyProducerJavaSocket): the
SERVER mode listens for clients pushing 32-byte candidate
private keys, the CLIENT mode pulls them from a configured
host.
The "private keys" exchanged are candidate keys being checked
against the local LMDB known-address database, not real wallet
credentials. The producer is a security-research orchestration
primitive: operators run it localhost-to-localhost or on a
trusted LAN as part of their batch-scan setup. There is no
production-facing TLS endpoint to harden.
findsecbugs UNENCRYPTED_SOCKET / UNENCRYPTED_SERVER_SOCKET
assume a general-purpose-app threat model where any socket
carrying any "private key" bytes must be TLS-protected.
MDM_PROMISCUOUS_SERVERSOCKET assumes the corresponding bind-
time threat model: that a ServerSocket bound to 0.0.0.0
accepting from any network interface is a security risk.
Neither model applies here; the producer is intentionally
bound to all interfaces because operators may run it on a
host whose primary IP is not the loopback (e.g. a private LAN
with a single physical NIC). Adding TLS plumbing (keystore,
certs, client-cert validation) or restricting the bind would
be substantial architectural surface for zero security gain
in the documented use case. Same design-intent rationale as
the PREDICTABLE_RANDOM suppression above.
-->
<Match>
<Class name="net.ladenthin.bitcoinaddressfinder.keyproducer.KeyProducerJavaSocket"/>
<Or>
<Bug pattern="UNENCRYPTED_SOCKET"/>
<Bug pattern="UNENCRYPTED_SERVER_SOCKET"/>
<Bug pattern="MDM_PROMISCUOUS_SERVERSOCKET"/>
</Or>
</Match>
<!--
CLI_CONSTANT_LIST_INDEX is a fb-contrib style check that flags
`arr[N]` constant indices as a possible typo for a loop
variable. The detector ALREADY suppresses the finding when the
array comes from String.split() (see fb-contrib
ConstantListIndex.java): column-based access into a tokenized
line is a legitimate idiom, not a bug pattern.
SeparatorFormat.split() is functionally a String.split:
internally it calls String.split for each separator and
concatenates the parts (recursive multi-separator tokenizer).
The fb-contrib special-case cannot trace through the wrapper,
so the finding fires on AddressTxtLine.getCoinIfPossible which
reads the optional amount column. The named constants
COLUMN_ADDRESS / COLUMN_AMOUNT document the column layout, the
read is bounds-checked by length comparison, and the access
pattern matches exactly the idiom the detector author already
carved out for String.split callers.
-->
<Match>
<Class name="net.ladenthin.bitcoinaddressfinder.io.AddressTxtLine"/>
<Method name="getCoinIfPossible"/>
<Bug pattern="CLI_CONSTANT_LIST_INDEX"/>
</Match>
<!--
UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD: every C-prefixed POJO
in the configuration package is populated by Jackson via
reflection when JSON is deserialized (see
examples/config_*.json). SpotBugs only tracks direct bytecode
writes and cannot see reflection writes, so every Jackson-only
field shows up as unwritten. Each flagged field has a matching
JSON key in the example configs and is read by production
code.
-->
<Match>
<Class name="~net\.ladenthin\.bitcoinaddressfinder\.configuration\..*"/>
<Bug pattern="UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD"/>
</Match>
<!--
RFI_SET_ACCESSIBLE flags reflective access into
private/protected members. All 5 sites here reach into
third-party libraries that expose no public alternative for
what we need:
- Bech32Helper: bitcoinj 0.17's Bech32.convertBits is
package-private, and
Bech32.Bech32Bytes.witnessProgram / witnessVersion are
protected; both are needed for SegWit address decoding
paths that bitcoinj does not expose publicly.
- OpenCLBuilder.isOpenClNativeLibraryLoaded: JOCL exposes
no public probe for whether its native lib loaded.
Needed for graceful CPU-only fallback on hosts without
GPU drivers.
- LMDBPersistence.getApproximateSizeBytes: Guava's
BloomFilter does not expose its in-memory size. We read
the private backing array length for diagnostics.
The :add-opens JVM flags in .mvn/jvm.config and pom.xml
explicitly permit this access at runtime.
-->
<Match>
<Or>
<And>
<Class name="net.ladenthin.bitcoinaddressfinder.util.Bech32Helper"/>
<Or>
<Method name="invokeConvertBitsStatic"/>
<Method name="invokeProtectedMethod"/>
</Or>
</And>
<And>
<Class name="net.ladenthin.bitcoinaddressfinder.opencl.OpenCLBuilder"/>
<Method name="isOpenClNativeLibraryLoaded"/>
</And>
<And>
<Class name="net.ladenthin.bitcoinaddressfinder.persistence.lmdb.LMDBPersistence"/>
<Method name="getApproximateSizeBytes"/>
</And>
</Or>
<Bug pattern="RFI_SET_ACCESSIBLE"/>
</Match>
<!--
EXS_EXCEPTION_SOFTENING_NO_CONSTRAINTS on
LMDBPersistence.getApproximateSizeBytes: the reflective
access suppressed in the RFI block above can throw checked
exceptions (NoSuchFieldException, IllegalAccessException).
They are wrapped in RuntimeException because this is a
best-effort diagnostic-size estimate; callers log the
result and continue regardless. Adding throws to the static
helper would force every caller to handle exceptions for a
non-critical metric.
-->
<Match>
<Class name="net.ladenthin.bitcoinaddressfinder.persistence.lmdb.LMDBPersistence"/>
<Method name="getApproximateSizeBytes"/>
<Bug pattern="EXS_EXCEPTION_SOFTENING_NO_CONSTRAINTS"/>
</Match>
<!--
EXS_EXCEPTION_SOFTENING_NO_CONSTRAINTS on
KeyProducerJavaRandom constructor (3 sites). Same
"this cannot happen if invariants hold" idiom we suppress
elsewhere:
- NoSuchAlgorithmException from
SecureRandom.getInstanceStrong() and
SecureRandom.getInstance("SHA1PRNG"). Both algorithm
names are required by the standard JDK security
provider list; the catch+wrap exists only to satisfy
the compiler.
- switch default for an unknown
CKeyProducerJavaRandom.keyProducerJavaRandomInstance
enum value, reachable only if a new enum constant is
added without updating the switch.
Widening the constructor to declare throws is not an
option: KeyProducerJavaRandom is instantiated from a
Function<T, K> lambda in Finder.startKeyProducer; the
functional interface cannot propagate checked exceptions
without a hierarchy-wide redesign.
-->
<Match>
<Class name="net.ladenthin.bitcoinaddressfinder.keyproducer.KeyProducerJavaRandom"/>
<Method name="<init>"/>
<Bug pattern="EXS_EXCEPTION_SOFTENING_NO_CONSTRAINTS"/>
</Match>
<!--
EXS_EXCEPTION_SOFTENING_NO_CONSTRAINTS on
AddressTxtLine.fromLine: the inner try block uses a
type-filtering catch pattern - DecoderException (Bouncy
Castle unchecked, signalling invalid Bech32 chars) is
caught and rethrown unchanged so callers see the original
type, while a broader
catch (RuntimeException | ReflectiveOperationException)
wraps every other failure in the domain-specific
AddressFormatNotAcceptedException. fb-contrib's bytecode
analysis sees a checked-to-unchecked conversion in the
catch block but cannot reconstruct the "filter and rethrow
same type" idiom. The behaviour is intentional.
-->
<Match>
<Class name="net.ladenthin.bitcoinaddressfinder.io.AddressTxtLine"/>
<Method name="fromLine"/>
<Bug pattern="EXS_EXCEPTION_SOFTENING_NO_CONSTRAINTS"/>
</Match>
<!--
EXS_EXCEPTION_SOFTENING_NO_CONSTRAINTS on
AbstractPlaintextFile.readFile: same type-filtering catch
idiom as AddressTxtLine.fromLine above. LmdbException
(subclass e.g. MapFullException is the trigger for the
map-auto-grow recovery path elsewhere) must propagate
unchanged; everything else is logged and the read
continues to the next line. The in-source comment at the
catch makes the intent explicit.
-->
<Match>
<Class name="net.ladenthin.bitcoinaddressfinder.io.AbstractPlaintextFile"/>
<Method name="readFile"/>
<Bug pattern="EXS_EXCEPTION_SOFTENING_NO_CONSTRAINTS"/>
</Match>
<!--
EXS_EXCEPTION_SOFTENING_NO_CONSTRAINTS on
SortedArrayAddressPresence.populateFrom: the throw on bucket
overflow is a "cannot happen unless the input violates the
Integer.MAX_VALUE-per-array contract" invariant assertion.
IllegalStateException is the idiomatic choice here; the
bucket-prefix would need to be widened by the caller (e.g.
to 2 bytes) before this path could ever fire. Same idiom as
the AbstractProducer.produceKeys assertions suppressed
above.
-->
<!--
EXS_EXCEPTION_SOFTENING_NO_CONSTRAINTS on
TruncatedLong64SortedArrayPresence.populateFrom: the throw
fires only if a single bucket would exceed Integer.MAX_VALUE
entries; a finer bucket prefix would be required for the
caller to ever observe this.
-->
<Match>
<Class name="net.ladenthin.bitcoinaddressfinder.persistence.inmemory.TruncatedLong64SortedArrayPresence"/>
<Method name="populateFrom"/>
<Bug pattern="EXS_EXCEPTION_SOFTENING_NO_CONSTRAINTS"/>
</Match>
<!--
EXS_EXCEPTION_SOFTENING_NO_CHECKED: 3 sites convert a
checked exception to a RuntimeException by design, in two
different intentional patterns:
- ConsumerJava.interrupt: fail-loud during shutdown. An
InterruptedException while awaiting consumer
termination, or an Exception from persistence.close(),
means the shutdown could not complete cleanly. The
throw makes the failure visible to the operator
instead of silently logging and losing it. The
InterruptedException site restores the interrupt flag
before rethrowing per the Java idiom. Adding throws to
interrupt() is not an option because the Interruptable
interface declares no throws.
- AbstractKeyProducerQueueBuffered.createSecrets: catches
InterruptedException, restores the interrupt flag
(Thread.currentThread().interrupt()), then throws the
domain-specific NoMoreSecretsAvailableException.
fb-contrib flags this only because
NoMoreSecretsAvailableException extends
RuntimeException; the pattern is the idiomatic
translation of "blocking queue interrupted during
shutdown" into the producer's domain language.
-->
<Match>
<Or>
<And>
<Class name="net.ladenthin.bitcoinaddressfinder.consumer.ConsumerJava"/>
<Method name="interrupt"/>
</And>
<And>
<Class name="net.ladenthin.bitcoinaddressfinder.keyproducer.AbstractKeyProducerQueueBuffered"/>
<Method name="createSecrets"/>
</And>
</Or>
<Bug pattern="EXS_EXCEPTION_SOFTENING_NO_CHECKED"/>
</Match>
<!--
PATH_TRAVERSAL_IN flags new File(userInput) where userInput
could be attacker-controlled (HTTP, RPC, etc). Every flagged
site here reads a path from the JSON configuration file that
the operator hand-picks at CLI launch:
- FileHelper.stringsToFiles (and its two callers
AddressFilesToLMDB / ProducerJavaSecretsFiles): converts
the operator-specified addressesFiles / secretsFile
arrays.
- LMDBToAddressFile: opens the operator-specified output
directory.
- LMDBPersistence (read-only and write constructors):
opens the operator-specified lmdbDirectory.
The configuration file is part of the CLI invocation
(java -jar ... config.json); there is no untrusted-input
boundary that an attacker could traverse from.
-->
<Match>
<Or>
<Class name="net.ladenthin.bitcoinaddressfinder.io.FileHelper"/>
<Class name="net.ladenthin.bitcoinaddressfinder.command.AddressFilesToLMDB"/>
<Class name="net.ladenthin.bitcoinaddressfinder.producer.ProducerJavaSecretsFiles"/>
<Class name="net.ladenthin.bitcoinaddressfinder.command.LMDBToAddressFile"/>
<Class name="net.ladenthin.bitcoinaddressfinder.persistence.lmdb.LMDBPersistence"/>
</Or>
<Bug pattern="PATH_TRAVERSAL_IN"/>
</Match>
<!--
EI_EXPOSE_REP / EI_EXPOSE_REP2 findings on this codebase fall
into four design-intent categories; defensive copies are
inappropriate in every case:
1. Hot-path byte[] / ByteBuffer ownership transfer.
PublicKeyBytes, AddressToCoin.hash160,
OpenClTask$CLByteBufferPointerArgument.byteBuffer,
OpenCLGridResult.result, and the cached read-only
PersistenceUtils.zeroByteBuffer are passed across the
GPU/CPU key-derivation pipeline at millions of operations
per second. Allocating a copy at every getter or
constructor would destroy throughput. The contract is
"ownership transfers at boundary";
PersistenceUtils.zeroByteBuffer is additionally pinned as
asReadOnlyBuffer() to defeat mutation.
2. JOCL native handles. cl_context, cl_mem, cl_device_id,
cl_context_properties, and Pointer are opaque native
resource references. They cannot be cloned; the Java owner
must retain the reference to release the resource via
clRelease*.
3. Effectively-immutable bitcoinj Coin. AddressToCoin.coin
holds a Coin instance which is a value-class style
immutable type. SpotBugs lacks the annotation to recognise
it.
4. Deliberate dependency injection.
RandomSecretSupplier.random must share state across calls
(PRNG progression / seeded reproducibility);
LMDBPersistence.persistenceUtils is a stateless helper
owned by the caller. The shared RuntimeStatistics metrics
sink is intentionally injected into AbstractProducer (every
producer increments its per-producer batch counter) and into
ConsumerJava (which renders it); it MUST be the same instance
across all of them, so a defensive copy would defeat its
purpose. Copying any of these would break the contract.
-->
<Match>
<Or>
<Class name="net.ladenthin.bitcoinaddressfinder.producer.AbstractProducer"/>
<Class name="net.ladenthin.bitcoinaddressfinder.consumer.ConsumerJava"/>
<Class name="net.ladenthin.bitcoinaddressfinder.model.PublicKeyBytes"/>
<Class name="net.ladenthin.bitcoinaddressfinder.model.AddressToCoin"/>
<Class name="net.ladenthin.bitcoinaddressfinder.opencl.OpenClTask"/>
<Class name="net.ladenthin.bitcoinaddressfinder.opencl.OpenClTask$CLByteBufferPointerArgument"/>
<Class name="net.ladenthin.bitcoinaddressfinder.opencl.OpenCLGridResult"/>
<Class name="net.ladenthin.bitcoinaddressfinder.persistence.PersistenceUtils"/>
<Class name="net.ladenthin.bitcoinaddressfinder.opencl.OpenCLDevice"/>
<Class name="net.ladenthin.bitcoinaddressfinder.opencl.OpenCLDeviceSelection"/>
<Class name="net.ladenthin.bitcoinaddressfinder.opencl.OpenCLPlatform"/>
<Class name="net.ladenthin.bitcoinaddressfinder.secret.RandomSecretSupplier"/>
<Class name="net.ladenthin.bitcoinaddressfinder.persistence.lmdb.LMDBPersistence"/>
<Class name="net.ladenthin.bitcoinaddressfinder.persistence.bloom.BloomFilterAccelerator"/>
<!--
5. BinaryFuse8GpuFilterData is a Java record that carries the
fingerprints byte[] for GPU VRAM upload. The class Javadoc
documents the array as "(reference, treated as read-only)";
the array is consumed by the OpenCL upload path in a single
call to clCreateBuffer + CL_MEM_COPY_HOST_PTR. Making a
defensive copy at the accessor or in the compact constructor
would double the allocation on the hot GPU-upload path with
no safety gain — the record itself is immutable (only the
fingerprints component is mutable-by-reference) and is not
exposed beyond the upload call.
-->
<Class name="net.ladenthin.bitcoinaddressfinder.persistence.inmemory.BinaryFuse8GpuFilterData"/>
</Or>
<Or>
<Bug pattern="EI_EXPOSE_REP"/>
<Bug pattern="EI_EXPOSE_REP2"/>
</Or>
</Match>
<!--
EXS_EXCEPTION_SOFTENING_HAS_CHECKED on three internal
sanity-check sites. These are "this can never happen if the
contract holds" assertions where RuntimeException /
IllegalArgumentException is the idiomatic Java choice;
promoting them to checked exceptions would force callers to
wrap statically unreachable code:
- AbstractProducer.produceKeys lines 122 + 126:
verifies that KeyProducer.createSecrets returned an
array of the requested length. A mismatch is a bug in
the KeyProducer implementation; the same method catches
the RuntimeException right after (line 133) for
logging.
- LMDBPersistence.writeAllAmountsToAddressFile line 333:
exhaustive switch default for CAddressFileOutputFormat.
Reachable only if a new enum constant is added without
updating this switch.
-->
<Match>
<Or>
<And>
<Class name="net.ladenthin.bitcoinaddressfinder.producer.AbstractProducer"/>
<Method name="produceKeys"/>
</And>
<And>
<Class name="net.ladenthin.bitcoinaddressfinder.persistence.lmdb.LMDBPersistence"/>
<Method name="writeAllAmountsToAddressFile"/>
</And>
</Or>
<Bug pattern="EXS_EXCEPTION_SOFTENING_HAS_CHECKED"/>
</Match>
<!--
IMC_IMMATURE_CLASS_NO_EQUALS on three exception types.
AddressFormatNotAcceptedException, KeyProducerIdIsNotUniqueException,
and KeyProducerIdUnknownException are domain exceptions that follow
the standard JDK / bitcoinj / junit / jackson / slf4j convention:
exceptions are events with identity semantics, not value objects.
Adding Lombok-generated value-equality on an exception class would:
- Surprise readers familiar with the JDK convention (Throwable
never overrides equals).
- Change the semantics of Set<Exception> / Map<Exception, ?>
instances at every call site (a single parse failure with
"reason X" would now hash-collide with every other failure
sharing the same reason).
- Force callers that rely on catch-block reference equality
(e.g. "is THIS exception the one we just rethrew?") to migrate
to identity comparison even though identity is what they meant.
Generating an equals that does NOT compare any field (callSuper=true
on a Throwable subclass) is a no-op: super.equals is identity, so
the generated method always returns false on different instances,
which is precisely what NOT overriding equals already gives us.
Per-class @SuppressFBWarnings annotations would require importing
edu.umd.cs.findbugs.annotations.SuppressFBWarnings into 3 production
classes for zero functional gain over this central suppression.
-->
<Match>
<Or>
<Class name="net.ladenthin.bitcoinaddressfinder.io.AddressFormatNotAcceptedException"/>
<Class name="net.ladenthin.bitcoinaddressfinder.keyproducer.KeyProducerIdIsNotUniqueException"/>
<Class name="net.ladenthin.bitcoinaddressfinder.keyproducer.KeyProducerIdUnknownException"/>
</Or>
<Bug pattern="IMC_IMMATURE_CLASS_NO_EQUALS"/>
</Match>
<!--
IMC_IMMATURE_CLASS_NO_EQUALS on RuntimeStatistics.
RuntimeStatistics is a shared, mutable runtime-metrics sink (a
ConcurrentMap of per-producer batch counters plus a live gauge supplier),
not a value object. It has a toString() for logging/debugging, which trips
the IMC "immature class" heuristic into also demanding equals()/hashCode().
Value equality is meaningless for a mutable, identity-shared metrics holder
(two distinct instances are never "equal", and it is used by identity as a
single injected instance), so equals()/hashCode() are intentionally omitted.
-->
<Match>
<Class name="net.ladenthin.bitcoinaddressfinder.statistics.RuntimeStatistics"/>
<Bug pattern="IMC_IMMATURE_CLASS_NO_EQUALS"/>
</Match>
<!--
USBR_UNNECESSARY_STORE_BEFORE_RETURN on Lombok-generated equals / hashCode /
canEqual / toString.
Lombok's @EqualsAndHashCode and @ToString annotation processors inject the
textbook polynomial-hash bytecode pattern (and lombok.config already emits
@lombok.Generated on every synthetic member via
lombok.addLombokGeneratedAnnotation = true):
int result = 1;
result = result * 59 + ($field == null ? 43 : $field.hashCode());
...
return result; // USBR fires here, on the istore_N / iload_N / ireturn triplet
SpotBugs core honours @lombok.Generated and skips its own detectors on those
members, but the fb-contrib plugin's USBR detector does NOT — fb-contrib is
a separate plugin family with its own filter pipeline. Suppressing USBR on
equals / hashCode / canEqual / toString matches every method name Lombok can
emit. The collateral cost is small: any handwritten member of those four
names that genuinely stores-then-immediately-returns is either a
debugger-friendly local-variable pattern or a micro-optimisation, both of
which are considered intentional in this codebase.
-->
<Match>
<Or>
<Method name="equals"/>
<Method name="hashCode"/>
<Method name="canEqual"/>
<Method name="toString"/>
</Or>
<Bug pattern="USBR_UNNECESSARY_STORE_BEFORE_RETURN"/>
</Match>
<!--
CRLF_INJECTION_LOGS — project-wide suppression.
Mitigation lives in the Logback PatternLayout, not at each call site.
The bundled src/main/resources/logback.xml uses
%replace(%msg){'[\r\n]+', ' | '}
on every appender. Any CR/LF in the rendered message body is
normalised to " | " before the appender flushes, so untrusted bytes
that reach a LOGGER.* argument cannot forge new log lines. This
covers every LOGGER.* call in the project — including transitive
dependency logs and any sites added by future contributors —
without per-site wrapping. Throwables render via %xThrowable / %ex
(a separate pattern token), so multi-line stack traces are
preserved. The same pattern is documented in
examples/logbackConfiguration.xml for operators who supply their
own logback.xml override.
Rationale for choosing native Logback %replace over alternatives:
- org.owasp:security-logging-logback (%crlf converter): the
"textbook OWASP answer" is effectively unmaintained — latest
Maven Central release 1.1.7 published 2021-12-15, no activity
since. Equivalent function is ~20 lines of regex wrapped as a
CompositeConverter; pulling in three jars for that is not
justified when Logback ships the same control natively.
- Logback JsonEncoder / logstash-logback-encoder: a stronger
answer that escapes CRLF as a side effect of JSON encoding,
but requires the operator's log pipeline to consume JSON;
loses cat/tail/grep usability for plain-file deployments.
- Per-site value.replaceAll("[\r\n]", ""): 68 mechanical edits
with no guard against future call sites forgetting the wrap.
The control disappears the moment a new contributor adds a
LOGGER.info(rawInput) call without re-running the audit.
Threat-model note: of the 68 sites fb-contrib flagged, most log
operator-controlled state (paths and config values from the POM /
JSON file the operator wrote; an attacker who can edit those
already has full RCE) or locally-computed deterministic state
(hex-encoded bytes the code itself just built — CRLF is
structurally impossible). The real attack surface is network input
flowing into Exception.getMessage() in KeyProducerJavaSocket /
WebSocket / Zmq; the layout-layer %replace covers those sites the
same way it covers everything else. The fb-contrib detector reads
bytecode and cannot inspect logback.xml, so the static finding
persists; this Match silences it project-wide with the layout
pattern as the documented control.
Authoritative references:
- OWASP Logging Cheat Sheet
https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html
- OWASP Injection Prevention in Java Cheat Sheet
https://cheatsheetseries.owasp.org/cheatsheets/Injection_Prevention_in_Java_Cheat_Sheet.html
- SEI CERT IDS03-J. Do not log unsanitized user input
https://wiki.sei.cmu.edu/confluence/display/java/IDS03-J.+Do+not+log+unsanitized+user+input
- find-sec-bugs #425 (false-positive discussion)
https://github.com/find-sec-bugs/find-sec-bugs/issues/425
-->
<Match>
<Bug pattern="CRLF_INJECTION_LOGS"/>
</Match>
<!--
THROWS_METHOD_THROWS_RUNTIMEEXCEPTION — narrow suppression on two
catch+cleanup+rethrow sites.
Both methods follow the pattern
try { ... } catch (RuntimeException e) { cleanup(); throw e; }
The exception the caller actually sees at runtime is whatever the
inner code threw (IllegalStateException, NoMoreSecretsAvailableException,
NPE, etc.), NOT a new RuntimeException constructed at the catch site.
fb-contrib reads the rethrow as "method throws RuntimeException" from
the compile-time catch-variable type only — a known false positive
acknowledged upstream:
- Issue: https://github.com/spotbugs/spotbugs/issues/3918
"THROWS_METHOD_THROWS_RUNTIMEEXCEPTION is reported when a
RuntimeException is caught and rethrown within the same catch
block. This shouldn't trigger the warning, because we are just
rethrowing an exception that would have been thrown anyway if
we hadn't caught it."
- Fix in flight: https://github.com/spotbugs/spotbugs/pull/4087
"Matches exception-handler ASTORE registers against the local
variable being thrown, and suppresses the warning when the
caught type is RuntimeException (or a subtype)."
Once SpotBugs ships with #4087 merged, drop this suppression — the
TODO entry in BAF's TODO.md tracks the lifecycle.
Alternatives considered and rejected:
1. Wrap+rethrow via a new "UncheckedRuntimeException" /
"CaughtRuntimeException" class. Rejected because:
(a) Changes the runtime type the caller sees, breaking any
caller that catches a specific subtype (e.g.
NoMoreSecretsAvailableException).
(b) Adds a "caused by:" frame to every stack trace that goes
through one of these methods.
(c) Ironically violates the very SEI CERT ERR07-J rule the
detector enforces — ERR07-J exists so callers can
distinguish typed exceptions for recovery; wrapping hides
the type. The SpotBugs maintainers articulate this
directly in #3918 ("rethrowing an exception that would
have been thrown anyway" is fine; constructing one is
not).
2. Narrow the catch to specific subtypes. Drops coverage of
unforeseen RuntimeException subtypes (NPE, CCE, …) that the
cleanup is meant to handle uniformly.
3. try-finally that always runs cleanup. Doesn't fit either site:
AbstractProducer.produceKeys logs only on the exception path
(not on success); LMDBPersistence.addresses must keep the txn
open on the success path so the returned Stream.onClose
handler can close it after the caller drains the stream.
-->
<Match>
<Or>
<And>
<Class name="net.ladenthin.bitcoinaddressfinder.producer.AbstractProducer"/>
<Method name="produceKeys"/>
</And>
<And>
<Class name="net.ladenthin.bitcoinaddressfinder.persistence.lmdb.LMDBPersistence"/>
<Method name="addresses"/>
</And>
<And>
<!--
OpenCLContext.allocateFilterBuffers lines 370-374:
catch (RuntimeException e) {
clReleaseMemObject(localFpMem);
throw e;
}
Frees the already-allocated fingerprint CL buffer before
propagating, preventing a VRAM leak when the second
clCreateBuffer (metadata) fails. Same false-positive
catch+cleanup+rethrow pattern as the two existing sites.
-->
<Class name="net.ladenthin.bitcoinaddressfinder.opencl.OpenCLContext"/>
<Method name="allocateFilterBuffers"/>
</And>
</Or>
<Bug pattern="THROWS_METHOD_THROWS_RUNTIMEEXCEPTION"/>
</Match>
<!--
fb-contrib MDM_THREAD_YIELD ("Method attempts to manually schedule
threads") flags every direct call to Thread.sleep(long) and
Thread.yield(). The rule's intent is to nudge towards higher-level
primitives (BlockingQueue.poll(timeout), CountDownLatch.await,
Semaphore.acquire, ScheduledExecutorService).
After the three structural refactors landed (AbstractProducer →
CountDownLatch, ConsumerJava → poll(timeout), ProducerOpenCL →
Semaphore), only two Thread.sleep sites remain in production:
1. AbstractKeyProducerQueueBuffered.sleep(int) — the
interrupt-flag-restoring sleep utility. Used by ONE production
caller, KeyProducerJavaSocket, for backoff inside its
bootstrap retry loop. The loop is hard-capped by
connectionRetryCount (default 5); after the cap is hit the
producer permanently gives up with IllegalStateException.
Exponential backoff is the right tool for reconnect-FOREVER
loops where polite back-off avoids hammering a struggling
peer; here, with a hard cap and final give-up, linear delay
is correct. Cancellation cooperates with shouldStop +
readerFuture.cancel(true), which delivers InterruptedException
to Thread.sleep and the catch clause restores the flag.
Flagging the sleep utility's definition as "manually scheduling
threads" is a structural false positive: the helper IS the
sleep primitive.
2. cli.Main.printAllStackTracesWithDelay — a developer-debug
diagnostic helper. Its sole production caller is gated by
`if (false) { ... }` (Main.java around line 282) — an
"uncomment for local debugging" hook. Sleep-then-sample-all-
thread-stacks is the textbook pattern; there is no
higher-level primitive that improves it. Kept rather than
deleted so the debug hook survives for future use.
Both suppressions are narrowly scoped to the specific method,
not pattern-wide.
-->
<Match>
<Bug pattern="MDM_THREAD_YIELD"/>
<Or>
<And>
<Class name="net.ladenthin.bitcoinaddressfinder.keyproducer.AbstractKeyProducerQueueBuffered"/>
<Method name="sleep"/>
</And>
<And>
<Class name="net.ladenthin.bitcoinaddressfinder.cli.Main"/>
<Method name="printAllStackTracesWithDelay"/>
</And>
</Or>
</Match>
<!--
fb-contrib OPM_OVERLY_PERMISSIVE_METHOD ("Method is declared more
permissively than is used in the code base") suppressed PROJECT-WIDE.
Rationale:
- The current BAF package structure groups most production code in a
single root package (net.ladenthin.bitcoinaddressfinder), so any
method called only from sibling files in the same package is
flagged as "could be package-private". That answer is correct
today but unstable: once the planned package-architecture refactor
(see workspace/policies/code-quality-todos.md + crossrepostatus.md
for the cross-repo audit) splits the root package into orchestration
/ domain / persistence / opencl / keyproducer layers, methods that
today are correctly package-private will need to become public to
cross the new package boundary. Tightening every site now would
produce a large mechanical visibility churn that is highly likely
to be reverted by the refactor — noise without lasting value.
- 33 sites at the time of suppression (Max+Low). Categories: Main CLI
internal helpers (~8), test-only public surface (~5), abstract-class
constructors (~4), concrete-class constructors needing per-class audit
(~5), internal helpers (~9), and one enum.valueOf false positive
(BIP39Wordlist). The per-category breakdown lives in the
crossrepostatus tracker under the "OPM scope-tightening — after
package refactor" row.
TODO: re-enable this rule (delete this Match block) once the package
refactor has settled — at that point genuine "method exposed beyond
its actual call site" findings become stable signals worth fixing.
-->
<Match>
<Bug pattern="OPM_OVERLY_PERMISSIVE_METHOD"/>
</Match>
<!--
BC_UNCONFIRMED_CAST on the keyproducer package — pure generic-erasure
false positive.
Every keyproducer class extends a generic base:
KeyProducerJava<T extends CKeyProducerJava>
holds protected final T cKeyProducerJava
Subclasses parameterise T with a specific CKeyProducerJava subtype
(CKeyProducerJavaBip39, …Random, …Socket, etc.). At source level
every cKeyProducerJava.specificField access is type-checked by the
generic bound; at bytecode level erasure forces the JVM to emit a
CHECKCAST from CKeyProducerJava to the bound's runtime type. fb-contrib
reads the bytecode and flags every such cast as "unconfirmed".
These nine sites are all the same shape: a subclass accessing a field
on its own typed configuration via the erased base field. The cast is
introduced by javac, not by the source. Removing it would require
deleting the shared cKeyProducerJava field and giving every subclass
its own typed copy — a structural change for no behavioural gain.
-->
<Match>
<Package name="~net\.ladenthin\.bitcoinaddressfinder\.keyproducer.*"/>
<Bug pattern="BC_UNCONFIRMED_CAST"/>
</Match>
<!--
DLS_DEAD_LOCAL_STORE on four deliberate "ignored value" patterns.
Three are the canonical Error-Prone-compliant "fire-and-forget Future"
idiom: when a submit/scheduleAtFixedRate return is intentionally
unused, the project assigns it to `Object unused` and applies
@FireAndForget("…rationale…") + @SuppressWarnings("FutureReturnValueIgnored")
to silence Error Prone's FutureReturnValueIgnored. fb-contrib's DLS
reads the same dead store and flags it; honouring DLS by dropping the
local would immediately re-trigger FutureReturnValueIgnored, so the
two rules are at odds and we accept the DLS finding as the documented
tradeoff. Sites: ConsumerJava.startStatisticsTimer (scheduler),
Finder.startProducer (producer submit), KeyProducerJavaWebSocket.start
(server submit).
The fourth (LMDBPersistence.count) is the standard "count an Iterable
by iterating, discarding each item" pattern:
for (KeyVal ignored : iterable) { count++; }
The loop variable IS the count's side effect; LMDB's CursorIterable
offers no Stream / size() shortcut so the iteration is mandatory.
-->
<Match>
<Bug pattern="DLS_DEAD_LOCAL_STORE"/>
<Or>
<And>
<Class name="net.ladenthin.bitcoinaddressfinder.consumer.ConsumerJava"/>
<Method name="startStatisticsTimer"/>
</And>
<And>
<Class name="net.ladenthin.bitcoinaddressfinder.engine.Finder"/>
<Method name="startProducer"/>
</And>
<And>
<Class name="net.ladenthin.bitcoinaddressfinder.keyproducer.KeyProducerJavaWebSocket"/>
<Method name="start"/>
</And>
<And>
<Class name="net.ladenthin.bitcoinaddressfinder.persistence.lmdb.LMDBPersistence"/>
<Method name="count"/>
</And>
</Or>
</Match>
<!--
THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION on the Producer interface's
initProducer/produceKeys and on ConsumerJava.initLMDB.
Producer is the abstract contract every producer impl honours. Concrete
impls throw a heterogeneous mix (LmdbException, JOCL CLException,
IOException from socket binds, NoMoreSecretsAvailableException, etc.)
with no shared checked-exception supertype. Narrowing the throws clause
would require either (a) wrapping every impl's exception into a single
domain type at every site — adds a "caused by:" frame to every
diagnostic and forces every impl to re-catch its own native exception,
or (b) listing every concrete impl's exception family on the interface,
coupling the abstract API to every backend's transitive dependency.
Same reasoning for ConsumerJava.initLMDB: it composes
LmdbException (runtime), AddressLookupBackend chain failures, and the
IO-level snapshot population — there is no minimal checked supertype.
Both interface methods are the "fail fatally on init" boundary; their
callers (Main.run / Finder.initProducer) already handle
Exception broadly and trigger Shutdown.
-->
<Match>
<Bug pattern="THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION"/>
<Or>
<And>
<Class name="net.ladenthin.bitcoinaddressfinder.producer.Producer"/>
<Or>
<Method name="initProducer"/>
<Method name="produceKeys"/>
</Or>
</And>
<And>
<Class name="net.ladenthin.bitcoinaddressfinder.consumer.ConsumerJava"/>
<Method name="initLMDB"/>
</And>
</Or>
</Match>
<!--
PRMC_POSSIBLY_REDUNDANT_METHOD_CALLS on ConsumerJava.consumeKeys.
The flagged pattern is the standard non-blocking BlockingQueue drain:
PublicKeyBytes[] batch = keysQueue.poll(); // loop init
while (batch != null) {
processBatch(batch, buf);
batch = keysQueue.poll(); // loop step
}
Both poll() calls are necessary (drain-until-empty); they are not
redundant. fb-contrib pattern-matches the two identical calls
regardless of the loop structure.
-->
<Match>
<Class name="net.ladenthin.bitcoinaddressfinder.consumer.ConsumerJava"/>
<Method name="consumeKeys"/>
<Bug pattern="PRMC_POSSIBLY_REDUNDANT_METHOD_CALLS"/>
</Match>
<!--
MDM_RANDOM_SEED on KeyProducerJavaRandom and BIP39KeyProducer — both
deliberate, matched and documented in BitcoinAddressFinderArchitectureTest
("noNewRandom" ArchUnit rule) with the same two-class exemption list:
- KeyProducerJavaRandom constructs weak `new Random(seed)` instances
for documented CWE-338 demonstrations (the project's research focus
includes scanning addresses derived from weak-RNG wallets).
- BIP39KeyProducer extends java.util.Random as a façade pattern; the
implicit super() hits the Random() constructor. Random is used as
a sequential-iterator surface for deterministic HD-wallet
derivation, not as a source of randomness.
-->
<Match>
<Bug pattern="MDM_RANDOM_SEED"/>
<Or>
<Class name="net.ladenthin.bitcoinaddressfinder.keyproducer.KeyProducerJavaRandom"/>
<Class name="net.ladenthin.bitcoinaddressfinder.keyproducer.BIP39KeyProducer"/>
</Or>
</Match>
<!--
HARD_CODE_KEY on Secp256k1Constants — false positive on a public curve
parameter. The flagged constant is the secp256k1 generator point G (and
the associated curve order n / field prime p), which are PUBLIC
cryptographic parameters defined by SEC 2 / BIP 340 and identical
across every secp256k1 implementation. They are not credentials; they
are the parameters anyone scanning Bitcoin addresses must use. Static
analysis pattern-matches the hex blob as "looks like a key".
-->
<Match>
<Class name="net.ladenthin.bitcoinaddressfinder.constants.Secp256k1Constants"/>
<Bug pattern="HARD_CODE_KEY"/>
</Match>
<!--
NP_LOAD_OF_KNOWN_NULL_VALUE on OpenClTask.executeKernel — false
positive on the JOCL API contract. clEnqueueNDRangeKernel takes
explicit null arguments for the global_work_offset, event_wait_list,
and event return-pointer when the caller has nothing to provide; the
nulls are not a defensive omission, they are the JOCL spec.
-->
<Match>
<Class name="net.ladenthin.bitcoinaddressfinder.opencl.OpenClTask"/>
<Method name="executeKernel"/>
<Bug pattern="NP_LOAD_OF_KNOWN_NULL_VALUE"/>
</Match>
<!--
UPM_UNCALLED_PRIVATE_METHOD and URF_UNREAD_FIELD on a documented "preserved
for future revival" idiom.
Each affected site already carries @Deprecated + @SuppressWarnings on
the Error Prone counterpart (UnusedMethod / UnusedVariable), with a
comment explaining what the helper would be revived for. fb-contrib's
UPM/URF are the duplicate analysis path; they read bytecode and have