Skip to content

fix(security): redact credentials in Settings and aerospike debug logs#927

Merged
oskarszoon merged 10 commits into
bsv-blockchain:mainfrom
oskarszoon:fix/codeql-redact-log
May 27, 2026
Merged

fix(security): redact credentials in Settings and aerospike debug logs#927
oskarszoon merged 10 commits into
bsv-blockchain:mainfrom
oskarszoon:fix/codeql-redact-log

Conversation

@oskarszoon

@oskarszoon oskarszoon commented May 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes 2 CodeQL alerts identified by the 2026-05-21 vulnerability triage as fix_class=codeql-redact-log, plus 5 additional same-class log sites found during review:

Approach

  • Added settings.Redact(s *Settings) (*Settings, error) that deep-clones via JSON round-trip and uses reflection to replace any field tagged redact:"true" with a placeholder.
  • Tagged 10 known secret fields across 6 files in settings/: GRPCAdminAPIKey, Coinbase.{UserPwd, P2PPrivateKey, WalletPrivateKey, SlackToken}, P2P.PrivateKey, Alert.P2PPrivateKey, RPC.{RPCPass, RPCLimitPass}, BlockAssembly.MinerWalletPrivateKeys.
  • Rewired PrintSettings to call Redact before marshal.
  • Added util.redactURL and util.aerospikePolicySummary helpers; rewired the Debug log call to use them. URL password placeholder uses REDACTED (URL-safe; *** would be percent-encoded by url.UserPassword).
  • Fixed 5 additional Aerospike Infof log sites that leaked the same URL with userinfo (review catch — worse than the original Debug log, since Info is on by default): readPolicy, writePolicy, batchPolicy, queryPolicy, base/connection policy URLs all now wrapped with redactURL.
  • Consolidated two parallel sources of truth:
    • Single redacted placeholder: redactedPlaceholder removed, redactedValue (existing in settings/export.go) used by both consumers.
    • sensitiveKeys map in export.go is now derived from struct tags via extractSensitiveKeys() instead of being hardcoded — tags are the single source of truth.
  • Added regression tests:
    • TestSensitiveFieldsHaveRedactTag — name-pattern enforcement: any Settings field whose name matches /password|pwd|token|apikey|secret|privatekey/ must have redact:"true" (or be in sensitiveNameAllowlist with a documented rationale).
    • TestSensitiveKeysDerivedMatchesExpected — pins the derived map to the original hardcoded list; catches accidental tag removal.

5 commits, one per logical change.

Test plan

  • go build ./...
  • go test ./... — 8150+ tests pass; new tests in settings/redact_test.go (5 tests) and util/aerospike_redact_test.go (9 tests)
  • go test -race ./...
  • go vet ./... (4 pre-existing issues in test/utils/, unchanged)
  • golangci-lint run (1 pre-existing prealloc nit in crosscheck_test.go, none in new files)
  • staticcheck ./...
  • govulncheck ./... (18 pre-existing vulns; this PR does not touch deps)
  • gosec ./... (informational, 22 pre-existing in affected pkgs)

Frontend verification (npm) skipped — this PR touches zero UI files.

Out of scope

  • Eyeball the PrintSettings output for Settings.ChainCfgParams after merge — the JSON round-trip through reflection may not preserve *chaincfg.Params fields containing *big.Int, *chainhash.Hash, etc. Not a security concern (those are public protocol parameters), but downstream tooling parsing the SETTINGS JSON should be checked.
  • Other potential cleartext-logging sites in the codebase. Separate triage session.

CodeQL alerts #7 and #39 will close automatically on the next default-branch CodeQL scan after merge.

Source: docs/superpowers/specs/2026-05-21-vuln-triage/report.md and docs/superpowers/specs/2026-05-21-codeql-redact-log-design.md.

@github-actions

github-actions Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

🤖 Claude Code Review

Status: Complete

Summary

This PR implements credential redaction for Settings logging and Aerospike connection URLs to address CodeQL alerts #7 and #39. The implementation is well-structured with good test coverage.

Findings

No critical issues found. The implementation follows security best practices:

  • ✅ Reflection-based redaction correctly walks nested structs
  • ✅ Struct tags provide single source of truth for sensitive fields
  • ✅ URL redaction preserves original input (no mutation)
  • ✅ Comprehensive test coverage with regression guards
  • ✅ All 10 sensitive fields properly tagged
  • ✅ Both export.go and logging paths use same redacted placeholder

Design Observations

Reflection Trade-offs (Documented)

The PR description correctly notes that the JSON round-trip clone does not preserve non-JSON-marshalable fields (*chaincfg.Params internals, function pointers, channels). The comment at settings/redact.go:16-22 documents this clearly. This is acceptable since:

  • The redacted value is only for logging (not runtime use)
  • User-configurable settings survive JSON round-trip
  • The "Out of scope" section flags downstream tooling should be checked post-merge

Test Coverage

Strong regression protection:

  • TestSensitiveFieldsHaveRedactTag enforces naming convention
  • TestSensitiveKeysDerivedMatchesExpected prevents accidental tag removal
  • TestRedactPreservesNonSecretFields verifies non-secret survival

Implementation Quality

  • extractSensitiveKeys() centralizes tag-to-map derivation
  • redactURL() uses URL cloning to avoid side effects (verified by test)
  • aerospikePolicySummary() exposes useful debug fields while hiding Password
  • All 5 additional Aerospike Info log sites fixed (lines 118, 157, 195, 242, 273 in util/aerospike.go)

Code References

  • Primary redaction: settings/redact.go:23-41 (Redact function)
  • URL redaction: util/aerospike.go:403-426 (redactURL function)
  • Settings integration: cmd/settings/Settings.go:172-176 (PrintSettings)
  • Test enforcement: settings/redact_test.go:99-107 (TestSensitiveFieldsHaveRedactTag)

The PR is ready for merge.

@github-actions

github-actions Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

Benchmark Comparison Report

Baseline: main (unknown)

Current: PR-927 (340a591)

Summary

  • Regressions: 0
  • Improvements: 0
  • Unchanged: 144
  • Significance level: p < 0.05
All benchmark results (sec/op)
Benchmark Baseline Current Change p-value
_NewBlockFromBytes-4 1.751µ 1.735µ ~ 0.100
SplitSyncedParentMap_SetIfNotExists/256_buckets-4 61.74n 61.96n ~ 1.000
SplitSyncedParentMap_SetIfNotExists/16_buckets-4 61.68n 61.41n ~ 0.100
SplitSyncedParentMap_SetIfNotExists/1_bucket-4 61.74n 61.47n ~ 0.100
SplitSyncedParentMap_ConcurrentSetIfNotExists/256_buckets... 31.27n 30.08n ~ 0.700
SplitSyncedParentMap_ConcurrentSetIfNotExists/16_buckets_... 50.45n 50.61n ~ 1.000
SplitSyncedParentMap_ConcurrentSetIfNotExists/1_bucket_pa... 118.5n 109.1n ~ 0.100
MiningCandidate_Stringify_Short-4 271.1n 272.0n ~ 0.300
MiningCandidate_Stringify_Long-4 1.937µ 1.917µ ~ 0.100
MiningSolution_Stringify-4 982.6n 984.4n ~ 0.400
BlockInfo_MarshalJSON-4 1.819µ 1.830µ ~ 1.000
NewFromBytes-4 155.2n 154.6n ~ 1.000
AddTxBatchColumnar_Validation-4 2.580µ 2.619µ ~ 0.100
OffsetValidationLoop-4 607.7n 596.7n ~ 0.700
Mine_EasyDifficulty-4 52.07µ 52.04µ ~ 1.000
Mine_WithAddress-4 5.479µ 5.449µ ~ 0.400
DirectSubtreeAdd/4_per_subtree-4 61.13n 58.95n ~ 0.400
DirectSubtreeAdd/64_per_subtree-4 30.12n 31.83n ~ 0.100
DirectSubtreeAdd/256_per_subtree-4 29.05n 30.69n ~ 0.100
DirectSubtreeAdd/1024_per_subtree-4 28.08n 29.37n ~ 0.100
DirectSubtreeAdd/2048_per_subtree-4 27.79n 28.97n ~ 0.100
SubtreeProcessorAdd/4_per_subtree-4 296.6n 283.4n ~ 0.100
SubtreeProcessorAdd/64_per_subtree-4 292.0n 280.2n ~ 0.100
SubtreeProcessorAdd/256_per_subtree-4 297.2n 281.4n ~ 0.100
SubtreeProcessorAdd/1024_per_subtree-4 291.6n 272.0n ~ 0.100
SubtreeProcessorAdd/2048_per_subtree-4 285.1n 271.4n ~ 0.100
SubtreeProcessorRotate/4_per_subtree-4 290.3n 279.1n ~ 0.100
SubtreeProcessorRotate/64_per_subtree-4 283.6n 276.3n ~ 0.100
SubtreeProcessorRotate/256_per_subtree-4 285.2n 277.6n ~ 0.100
SubtreeProcessorRotate/1024_per_subtree-4 285.0n 276.2n ~ 0.100
SubtreeNodeAddOnly/4_per_subtree-4 55.32n 54.45n ~ 0.100
SubtreeNodeAddOnly/64_per_subtree-4 34.40n 34.35n ~ 0.400
SubtreeNodeAddOnly/256_per_subtree-4 33.54n 33.34n ~ 0.200
SubtreeNodeAddOnly/1024_per_subtree-4 32.57n 32.76n ~ 0.400
SubtreeCreationOnly/4_per_subtree-4 117.9n 116.4n ~ 0.100
SubtreeCreationOnly/64_per_subtree-4 409.9n 409.5n ~ 1.000
SubtreeCreationOnly/256_per_subtree-4 1.375µ 1.379µ ~ 0.400
SubtreeCreationOnly/1024_per_subtree-4 4.363µ 4.474µ ~ 0.200
SubtreeCreationOnly/2048_per_subtree-4 8.332µ 8.466µ ~ 0.200
SubtreeProcessorOverheadBreakdown/64_per_subtree-4 286.7n 279.4n ~ 0.100
SubtreeProcessorOverheadBreakdown/1024_per_subtree-4 285.3n 283.4n ~ 0.100
ParallelGetAndSetIfNotExists/1k_nodes-4 2.230m 2.286m ~ 0.100
ParallelGetAndSetIfNotExists/10k_nodes-4 5.494m 5.596m ~ 0.100
ParallelGetAndSetIfNotExists/50k_nodes-4 7.572m 7.851m ~ 0.200
ParallelGetAndSetIfNotExists/100k_nodes-4 10.48m 10.98m ~ 0.100
SequentialGetAndSetIfNotExists/1k_nodes-4 1.984m 1.966m ~ 0.400
SequentialGetAndSetIfNotExists/10k_nodes-4 4.924m 4.822m ~ 0.700
SequentialGetAndSetIfNotExists/50k_nodes-4 12.96m 13.28m ~ 0.700
SequentialGetAndSetIfNotExists/100k_nodes-4 24.27m 23.11m ~ 0.200
ProcessOwnBlockSubtreeNodesParallel/1k_nodes-4 2.285m 2.336m ~ 0.100
ProcessOwnBlockSubtreeNodesParallel/10k_nodes-4 8.436m 8.344m ~ 0.700
ProcessOwnBlockSubtreeNodesParallel/100k_nodes-4 13.87m 13.80m ~ 0.700
ProcessOwnBlockSubtreeNodesSequential/1k_nodes-4 2.015m 2.051m ~ 0.400
ProcessOwnBlockSubtreeNodesSequential/10k_nodes-4 8.718m 8.781m ~ 0.700
ProcessOwnBlockSubtreeNodesSequential/100k_nodes-4 39.85m 44.67m ~ 0.100
DiskTxMap_SetIfNotExists-4 3.497µ 3.469µ ~ 0.700
DiskTxMap_SetIfNotExists_Parallel-4 3.341µ 3.251µ ~ 0.700
DiskTxMap_ExistenceOnly-4 315.2n 307.7n ~ 0.200
Queue-4 185.6n 186.9n ~ 0.400
AtomicPointer-4 4.858n 4.444n ~ 0.100
ReorgOptimizations/DedupFilterPipeline/Old/10K-4 865.4µ 855.8µ ~ 0.100
ReorgOptimizations/DedupFilterPipeline/New/10K-4 789.6µ 786.7µ ~ 0.200
ReorgOptimizations/AllMarkFalse/Old/10K-4 104.7µ 102.0µ ~ 0.100
ReorgOptimizations/AllMarkFalse/New/10K-4 62.23µ 62.37µ ~ 0.200
ReorgOptimizations/HashSlicePool/Old/10K-4 54.41µ 57.86µ ~ 0.400
ReorgOptimizations/HashSlicePool/New/10K-4 12.11µ 11.85µ ~ 0.700
ReorgOptimizations/NodeFlags/Old/10K-4 4.992µ 4.672µ ~ 0.100
ReorgOptimizations/NodeFlags/New/10K-4 1.788µ 1.580µ ~ 0.100
ReorgOptimizations/DedupFilterPipeline/Old/100K-4 10.144m 9.539m ~ 0.100
ReorgOptimizations/DedupFilterPipeline/New/100K-4 9.947m 9.941m ~ 1.000
ReorgOptimizations/AllMarkFalse/Old/100K-4 1.077m 1.076m ~ 0.400
ReorgOptimizations/AllMarkFalse/New/100K-4 689.6µ 684.7µ ~ 0.100
ReorgOptimizations/HashSlicePool/Old/100K-4 708.2µ 648.4µ ~ 0.700
ReorgOptimizations/HashSlicePool/New/100K-4 281.6µ 286.7µ ~ 1.000
ReorgOptimizations/NodeFlags/Old/100K-4 50.08µ 48.22µ ~ 1.000
ReorgOptimizations/NodeFlags/New/100K-4 15.71µ 17.68µ ~ 0.100
TxMapSetIfNotExists-4 52.61n 52.56n ~ 1.000
TxMapSetIfNotExistsDuplicate-4 40.41n 39.83n ~ 0.100
ChannelSendReceive-4 578.0n 605.3n ~ 0.100
BlockAssembler_AddTx-4 0.02694n 0.02778n ~ 0.400
AddNode-4 10.69 10.73 ~ 0.400
AddNodeWithMap-4 10.79 11.52 ~ 0.100
CalcBlockWork-4 472.2n 507.9n ~ 0.700
CalculateWork-4 642.3n 640.4n ~ 0.400
BuildBlockLocatorString_Helpers/Size_10-4 1.344µ 1.358µ ~ 0.300
BuildBlockLocatorString_Helpers/Size_100-4 15.59µ 13.20µ ~ 0.700
BuildBlockLocatorString_Helpers/Size_1000-4 127.2µ 127.2µ ~ 1.000
CatchupWithHeaderCache-4 104.5m 104.5m ~ 1.000
_BufferPoolAllocation/16KB-4 4.023µ 3.970µ ~ 0.100
_BufferPoolAllocation/32KB-4 7.919µ 8.228µ ~ 0.700
_BufferPoolAllocation/64KB-4 16.00µ 17.02µ ~ 0.700
_BufferPoolAllocation/128KB-4 32.26µ 28.63µ ~ 0.200
_BufferPoolAllocation/512KB-4 112.2µ 121.0µ ~ 0.100
_BufferPoolConcurrent/32KB-4 19.11µ 19.45µ ~ 0.400
_BufferPoolConcurrent/64KB-4 30.78µ 30.50µ ~ 0.400
_BufferPoolConcurrent/512KB-4 157.5µ 145.4µ ~ 0.100
_SubtreeDeserializationWithBufferSizes/16KB-4 622.3µ 717.2µ ~ 0.100
_SubtreeDeserializationWithBufferSizes/32KB-4 615.9µ 711.4µ ~ 0.100
_SubtreeDeserializationWithBufferSizes/64KB-4 628.7µ 709.3µ ~ 0.100
_SubtreeDeserializationWithBufferSizes/128KB-4 621.1µ 717.5µ ~ 0.100
_SubtreeDeserializationWithBufferSizes/512KB-4 647.1µ 624.7µ ~ 0.100
_SubtreeDataDeserializationWithBufferSizes/16KB-4 37.05m 36.93m ~ 1.000
_SubtreeDataDeserializationWithBufferSizes/32KB-4 37.13m 36.86m ~ 0.700
_SubtreeDataDeserializationWithBufferSizes/64KB-4 37.00m 36.74m ~ 0.100
_SubtreeDataDeserializationWithBufferSizes/128KB-4 36.72m 36.93m ~ 0.400
_SubtreeDataDeserializationWithBufferSizes/512KB-4 36.95m 36.60m ~ 0.200
_PooledVsNonPooled/Pooled-4 839.1n 844.3n ~ 0.400
_PooledVsNonPooled/NonPooled-4 9.530µ 8.285µ ~ 0.100
_MemoryFootprint/Current_512KB_32concurrent-4 8.397µ 7.821µ ~ 0.100
_MemoryFootprint/Proposed_32KB_32concurrent-4 13.87µ 12.29µ ~ 0.100
_MemoryFootprint/Alternative_64KB_32concurrent-4 12.38µ 10.31µ ~ 0.100
SubtreeSizes/10k_tx_4_per_subtree-4 1.278m 1.309m ~ 0.700
SubtreeSizes/10k_tx_16_per_subtree-4 302.1µ 305.7µ ~ 0.200
SubtreeSizes/10k_tx_64_per_subtree-4 71.37µ 72.63µ ~ 0.700
SubtreeSizes/10k_tx_256_per_subtree-4 18.01µ 17.89µ ~ 0.700
SubtreeSizes/10k_tx_512_per_subtree-4 8.847µ 8.756µ ~ 0.200
SubtreeSizes/10k_tx_1024_per_subtree-4 4.391µ 4.346µ ~ 0.400
SubtreeSizes/10k_tx_2k_per_subtree-4 2.185µ 2.183µ ~ 1.000
BlockSizeScaling/10k_tx_64_per_subtree-4 70.75µ 69.67µ ~ 0.100
BlockSizeScaling/10k_tx_256_per_subtree-4 17.76µ 17.38µ ~ 0.100
BlockSizeScaling/10k_tx_1024_per_subtree-4 4.379µ 4.293µ ~ 0.100
BlockSizeScaling/50k_tx_64_per_subtree-4 377.1µ 364.8µ ~ 0.200
BlockSizeScaling/50k_tx_256_per_subtree-4 88.13µ 86.88µ ~ 0.200
BlockSizeScaling/50k_tx_1024_per_subtree-4 21.67µ 21.32µ ~ 0.200
SubtreeAllocations/small_subtrees_exists_check-4 147.8µ 147.8µ ~ 1.000
SubtreeAllocations/small_subtrees_data_fetch-4 156.4µ 157.5µ ~ 0.700
SubtreeAllocations/small_subtrees_full_validation-4 306.3µ 301.7µ ~ 0.700
SubtreeAllocations/medium_subtrees_exists_check-4 8.821µ 8.832µ ~ 0.500
SubtreeAllocations/medium_subtrees_data_fetch-4 9.248µ 9.247µ ~ 1.000
SubtreeAllocations/medium_subtrees_full_validation-4 17.59µ 17.32µ ~ 0.700
SubtreeAllocations/large_subtrees_exists_check-4 2.090µ 2.119µ ~ 0.100
SubtreeAllocations/large_subtrees_data_fetch-4 2.220µ 2.217µ ~ 1.000
SubtreeAllocations/large_subtrees_full_validation-4 4.329µ 4.334µ ~ 0.700
_prepareTxsPerLevel-4 403.5m 402.1m ~ 1.000
_prepareTxsPerLevelOrdered-4 4.746m 5.197m ~ 0.400
_prepareTxsPerLevel_Comparison/Original-4 396.0m 405.6m ~ 0.200
_prepareTxsPerLevel_Comparison/Optimized-4 4.445m 5.155m ~ 0.100
StoreBlock_Sequential/BelowCSVHeight-4 256.1µ 257.6µ ~ 1.000
StoreBlock_Sequential/AboveCSVHeight-4 257.0µ 259.3µ ~ 0.400
GetUtxoHashes-4 276.0n 272.3n ~ 0.400
GetUtxoHashes_ManyOutputs-4 45.57µ 45.39µ ~ 0.700
_NewMetaDataFromBytes-4 215.6n 216.3n ~ 0.400
_Bytes-4 399.4n 402.4n ~ 0.400
_MetaBytes-4 138.1n 139.8n ~ 0.100

Threshold: >10% with p < 0.05 | Generated: 2026-05-26 13:47 UTC

@oskarszoon oskarszoon requested review from icellan and ordishs May 21, 2026 20:54

@ordishs ordishs left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve. Solid security fix — tag-driven design, good regression tests, clean commit history. A few suggestions:

Please fix before merge

1. Godoc misattribution in util/aerospike.go

The two new helpers are inserted between GetAerospikeClient's doc comment and its func line:

// GetAerospikeClient creates or retrieves a cached Aerospike client for the given URL.
// It configures connection policies, authentication, and connection pooling based on settings.
// Returns a thread-safe client instance that can be shared across goroutines.
// redactURL returns the URL string with the userinfo password component
// masked. It does not mutate the input. A nil input returns ""<nil>"".
func redactURL(u *url.URL) string { ... }
...
func GetAerospikeClient(...) { ... }   // ← now undocumented

Go treats contiguous // lines as one comment block, so the doc rebinds to redactURL and the exported GetAerospikeClient loses its godoc (lint will flag). Move redactURL / aerospikePolicySummary below the existing functions, or above the GetAerospikeClient doc with a blank line separator.

Please track as follow-up

2. *url.URL fields with user:pass@ still leak via PrintSettings

Redact() only handles tagged scalars. Several settings hold *url.URL whose documented values include credentials:

  • Coinbase.Storepostgres://user:pass@host:port/db
  • BlockChain.StoreURLpostgres://user:pass@localhost:5432/blockchain
  • Alert.StoreURL, BlockPersister.Store, etc.

After this PR, JSON-marshaling these still emits credentials in cleartext from PrintSettings. The Aerospike URL is fixed; PostgreSQL URLs in Settings are not. Tagging them redact:""true"" would not help — zeroSecret would clobber the whole URL, losing host/scheme/path. A typed branch in redactValue for *url.URL that calls redactURL-equivalent (keep host/scheme, mask password) would close this. Acknowledged as out-of-scope in the PR body — please file an issue so it doesn't get lost between triage sessions.

Nits (optional)

3. Redact() JSON-roundtrip silently drops chaincfg.Params function fields (Subsidy, deployment activators). Worth a sentence in the godoc warning that ""func/chan fields are not preserved"" so future readers aren't surprised by missing fields in PrintSettings output.

4. walkSensitiveTags doesn't recurse into slices/maps/interfaces. Correct today (no such patterns with secrets) but a one-line comment documenting the ""structs and pointer-to-struct only"" contract would prevent future surprise.

5. aerospikePolicySummary only prints 3 of many ClientPolicy fields vs the original %#v. Lost debug visibility for MinConnectionsPerNode, LoginTimeout, TlsConfig, etc. Consider whitelisting more non-secret fields — Password is the only one that needs hiding.

6. TestRedactPreservesNonSecretFields test name overpromises — the body only asserts the placeholder appears. Either rename to TestRedactProducesPlaceholder or add an assertion that a known non-secret value survives the roundtrip.

7. Placeholder inconsistency (""REDACTED"" for URL passwords vs ""********"" elsewhere) is fine given URL-encoding constraints, but a sentence-comment near redactURL explaining why it differs would help.

Highlights

  • TestSensitiveFieldsHaveRedactTag with name-pattern enforcement + allowlist-with-rationale is the right design — exactly the kind of guardrail that prevents the next leak.
  • TestSensitiveKeysDerivedMatchesExpected pins the derived set to the old hardcoded list, catching accidental tag removal.
  • TestRedactURLDoesNotMutateInput is thoughtful given the shallow clone := *u could easily have aliased *Userinfo.
  • 5 logical commits, one per concern — easy to review.

LGTM modulo #1.

@oskarszoon oskarszoon self-assigned this May 22, 2026
Adds a deep-clone redactor that walks the Settings struct via reflection
and replaces any field tagged redact:"true" with a placeholder. Tags 10
known secret fields: GRPCAdminAPIKey, Coinbase.{UserPwd, P2PPrivateKey,
WalletPrivateKey, SlackToken}, P2P.PrivateKey, Alert.P2PPrivateKey,
RPC.{RPCPass, RPCLimitPass}, BlockAssembly.MinerWalletPrivateKeys.

Prepares for CodeQL alert bsv-blockchain#7 fix.
Closes CodeQL alert bsv-blockchain#7. PrintSettings was marshaling the full Settings
struct to Info logs, exposing GRPCAdminAPIKey and 9 other tagged secret
fields. PrintSettings now calls settings.Redact before marshal so the
log dump shows only non-secret configuration.
Closes CodeQL alert bsv-blockchain#39. The Aerospike connection URL (which may contain
user:password in the userinfo) and the ClientPolicy struct (which has a
Password field) were being logged at Debug level. Adds redactURL and
aerospikePolicySummary helpers and rewires the log line to use them.

URL password placeholder uses 'REDACTED' (no special chars) to avoid
percent-encoding by url.UserPassword.
Follow-up to the previous commit. Five additional Infof log sites in
getAerospikeClient log the URL (which may contain user:password
userinfo) without redaction. Info logs are on by default, so the
exposure here was worse than the Debug log already fixed.

Sites covered: readPolicy, writePolicy, batchPolicy, queryPolicy, and
base/connection policy. Each is wrapped with redactURL.
…rom tags

Two parallel sources of truth (redact tags vs the hardcoded sensitiveKeys
map in export.go) drift over time. Consolidate:

- Drop redactedPlaceholder constant in redact.go; redact and export both
  now use export.go's redactedValue ("********"). One placeholder string.
- Replace the hardcoded sensitiveKeys map literal with a single derived
  value: extractSensitiveKeys() walks Settings via reflection, collects
  every field tagged redact:"true" alongside its key:"X" tag.

Also adds TestSensitiveFieldsHaveRedactTag — a regression guard that fails
if a new Settings field whose name matches /password|pwd|token|apikey|
secret|privatekey/ lacks the redact:"true" tag. Documented false-matches
(SecretMiningThreshold, GenesisKeys, chaincfg.Params version bytes) are
in sensitiveNameAllowlist.

And TestSensitiveKeysDerivedMatchesExpected pins the derived map to the
former hardcoded list to catch accidental tag removal.
@oskarszoon oskarszoon force-pushed the fix/codeql-redact-log branch from 8e6576c to b39c42c Compare May 22, 2026 13:23
- Fix godoc misattribution in util/aerospike.go: redactURL and
  aerospikePolicySummary were inserted between GetAerospikeClient's doc
  comment and its func line, rebinding the doc to redactURL. Move the
  helpers below getAerospikeClient (and add their own godoc) so
  GetAerospikeClient regains its documentation.
- Expand aerospikePolicySummary to whitelist 15 non-secret ClientPolicy
  fields (AuthMode, ClusterName, IdleTimeout, LoginTimeout,
  MinConnectionsPerNode, MaxErrorRate, ErrorRateWindow,
  LimitConnectionsToQueueSize, FailIfNotConnected, TendInterval,
  UseServicesAlternate, RackAware, TLS presence). Recovers debug
  visibility that the original Password-only redaction had stripped.
- Add Redact godoc warning about JSON-roundtrip dropping non-marshalable
  fields (func/chan/unexported state/big.Int internals).
- Add walkSensitiveTags contract comment documenting "struct + pointer-
  to-struct only" descent rule.
- Add comment near redactURL explaining the REDACTED vs *** placeholder
  choice (URL-safety vs encoding by url.UserPassword).
- Strengthen TestRedactPreservesNonSecretFields: now asserts non-secret
  sentinel values survive the redact pipeline (LogLevel, ProfilerAddr,
  Coinbase.ArbitraryText) and the secret sentinel does not.

Note on *url.URL credentials leak (PR bsv-blockchain#927 review item 2): the JSON
round-trip in Redact() is already safe because url.Userinfo has only
unexported fields and marshals as {} — credentials never reach the
SETTINGS JSON dump. Direct %s/%v formatting on *url.URL elsewhere is a
separate audit; not addressed in this PR.
@oskarszoon

Copy link
Copy Markdown
Contributor Author

Pushed 1a94690bd + merge of upstream/main (1a7505e12).

#1 godoc misattribution — fixed. Moved redactURL and aerospikePolicySummary below getAerospikeClient with their own godoc blocks. GetAerospikeClient has its doc back.

#2 *url.URL leak via PrintSettings — investigated and not changing code here. url.URL.User is *Userinfo whose fields are all unexported, so json.Marshal emits User:{} and the credentials never reach the SETTINGS JSON dump — matches your own "Verified safe" note at the bottom of the review. The remaining concern is direct %s/%v on *url.URL at log sites elsewhere in the codebase; that needs a grep + audit and is out of scope here. Filed an issue? Not yet — happy to if you want it tracked separately; let me know.

#3 chaincfg.Params drop — added a godoc warning on Redact enumerating what JSON round-trip drops (func pointers, channels, unexported state, big.Int internals, *chaincfg.Params methods).

#4 walker contract — added comment on walkSensitiveTags documenting struct + pointer-to-struct only descent.

#5 policy summary — expanded to 15 non-secret fields: AuthMode, ClusterName, Idle/Login Timeouts, MinConnectionsPerNode, MaxErrorRate, ErrorRateWindow, LimitConnectionsToQueueSize, FailIfNotConnected, TendInterval, UseServicesAlternate, RackAware, plus TLS presence flag. Recovers debug visibility the original Password-only redaction had stripped.

#6 test nameTestRedactPreservesNonSecretFields now sets sentinel non-secret values (LogLevel, ProfilerAddr, Coinbase.ArbitraryText) and asserts they survive the redact pipeline; secret sentinel must not.

#7 placeholder rationale — added comment on redactURL explaining the URL-safe REDACTED vs ******** distinction (url.UserPassword percent-encodes * as %2A).

1453 race tests pass post-rebase + new commit.

Fixes CI gci linter failure on redact_test.go:170: the const block in
TestRedactPreservesNonSecretFields had inconsistent = alignment that
gofmt/gci flagged. Cosmetic only.
@sonarqubecloud

Copy link
Copy Markdown

@oskarszoon oskarszoon merged commit e269265 into bsv-blockchain:main May 27, 2026
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants