Skip to content

Commit 59e97b5

Browse files
committed
Document pentest boundary findings
1 parent a8b5d0e commit 59e97b5

7 files changed

Lines changed: 65 additions & 21 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
- Classified stream adapters and error types for the `v1.0` audit, preserving
1717
fail-closed decode, checked recovery, framed-reader, localized diagnostic,
1818
and opaque constant-time-oriented error boundaries.
19+
- Tightened security documentation for public ct success/failure and length
20+
boundaries, volatile best-effort cleanup limits, and const-array panic
21+
policy.
1922

2023
## 0.9.0 - 2026-05-17
2124

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,8 @@ assert_eq!(&URL_BYTES, b"-_8");
155155
Stable Rust cannot yet express the encoded length as the return array length
156156
directly, so `encode_array` uses the destination array type supplied by the
157157
caller. A wrong output length fails during const evaluation.
158+
Use `encode_array` for fixed-size static values, not for runtime data whose
159+
size is controlled by an attacker.
158160

159161
For untrusted length metadata, use checked length calculation:
160162

@@ -656,6 +658,9 @@ Security commitments:
656658
standard ASCII assumptions. Its malformed-input errors are intentionally
657659
non-localized, clear-tail variants clear caller-owned buffers on error, and
658660
it is not documented as a formally verified cryptographic constant-time API.
661+
Input length, padding length, decoded length, and final success/failure are
662+
public; callers that need protocol-level success/failure timing resistance
663+
should continue with fixed-shape dummy downstream work after decode failure.
659664
- Clear-tail encode/decode variants are available for callers that want
660665
best-effort cleanup of unused caller-owned buffers without adding a runtime
661666
dependency.

SECURITY.md

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,20 +64,30 @@ buffer. This reduces easy timing and retention pitfalls, but `base64-ng` does
6464
not currently claim a formally verified cryptographic constant-time encode or
6565
decode API.
6666

67+
If a deployment treats final success/failure timing or exact ciphertext length
68+
as sensitive, the caller must continue protocol processing in a fixed-shape
69+
way after decode failure, for example by substituting dummy output and running
70+
the same downstream validation or comparison steps. The `ct` module narrows
71+
the Base64 symbol-mapping timing target; it does not hide the public `Result`,
72+
input length, padding length, or decoded length from the surrounding protocol.
73+
6774
The clear-tail encode and decode APIs provide best-effort cleanup for
6875
caller-owned buffers by writing zero bytes over unused tail bytes on success and
69-
over the whole buffer on encode/decode error. Because the scalar crate forbids
70-
unsafe code and has no runtime dependencies, this cleanup uses ordinary Rust
71-
writes plus a compiler fence, not volatile writes or a formally verified
72-
zeroization primitive. Treat these APIs as buffer-retention reduction, not as a
73-
complete secret-erasure guarantee against compiler optimizations, core dumps,
74-
swap, hardware observation, or other process memory disclosure bugs.
76+
over the whole buffer on encode/decode error. The cleanup primitive uses
77+
volatile byte writes plus a `SeqCst` compiler fence so cleanup writes are not
78+
optimized away. Treat these APIs as buffer-retention reduction, not as a
79+
complete secret-erasure guarantee against historical stack-frame copies,
80+
compiler spills, CPU registers, allocator behavior, core dumps, swap, hardware
81+
observation, or other process memory disclosure bugs. Callers that require a
82+
platform-specific formal zeroization policy should apply that policy to their
83+
own buffers in addition to using crate cleanup APIs.
7584

7685
The `SecretBuffer` owned wrapper is available with the `alloc` feature for
7786
sensitive encoded or decoded bytes that should not be accidentally logged. It
7887
redacts `Debug` and `Display`, requires explicit reveal methods, and clears
79-
initialized bytes on drop with the same best-effort cleanup helper. It cannot
80-
clean historical copies outside the wrapper or allocator spare capacity.
88+
initialized bytes and vector spare capacity on drop with the same best-effort
89+
cleanup helper. It cannot clean historical copies outside the wrapper or make
90+
guarantees about allocator internals after ownership is exposed.
8191

8292
Streaming wrappers apply best-effort cleanup to their small internal staging
8393
buffers. Encoders clear pending plaintext bytes when those bytes are consumed
@@ -95,7 +105,9 @@ helpers before allocating or accepting framed payloads.
95105
Runtime scalar APIs are expected to return `Result` or `Option` for malformed
96106
input and size errors instead of unwinding. Compile-time array encoding is the
97107
exception: it intentionally fails const evaluation when the caller supplies an
98-
incorrect output array length.
108+
incorrect output array length. Do not use `Engine::encode_array` as a runtime
109+
API for untrusted size decisions; use `checked_encoded_len`, `encoded_len`, or
110+
caller-owned slice APIs instead.
99111
`scripts/validate-panic-policy.sh` release-gates new non-test panic-like sites
100112
and requires reviewed exceptions to remain documented in `docs/PANIC_POLICY.md`.
101113

docs/CONSTANT_TIME.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ The guarantee should explicitly exclude:
4747
- public input length
4848
- selected engine/alphabet
4949
- final success or failure result
50+
- total protocol work performed after the public `Result` is returned
5051
- invalid length and output-buffer capacity errors
5152
- output length
5253
- allocator behavior
@@ -222,6 +223,12 @@ those values are visible through the returned `Result` and decoded length. Any
222223
future cryptographic profile must document memory cleanup separately from timing
223224
behavior.
224225

226+
Applications that must hide success/failure timing at the protocol level should
227+
continue with fixed-shape downstream work after decode failure. A common pattern
228+
is to decode into caller-owned storage, substitute a same-length dummy buffer on
229+
failure, and perform the same comparison, authentication, accounting, and
230+
cleanup steps before returning a protocol decision.
231+
225232
## Buffer Comparisons
226233

227234
`SecretBuffer::constant_time_eq`, `EncodedBuffer::constant_time_eq`, and

docs/PANIC_POLICY.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ The current reviewed exceptions are:
2323
supplies an output array length that does not match the compile-time encoded
2424
length, or when that const length calculation overflows. This is documented
2525
as a const-array API contract and is not used for runtime untrusted length
26-
metadata.
26+
metadata. Calling it at runtime with a mismatched const output length can
27+
also unwind; do not route attacker-controlled sizing decisions through this
28+
API.
2729
- Internal remainder matches use `_ => unreachable!()` after matching
2830
`len % 3` or equivalent remainder values. The preceding arithmetic bounds
2931
make those arms unreachable.
@@ -53,3 +55,8 @@ For untrusted input and untrusted length metadata, prefer:
5355
Compile-time array encoding is intentionally stricter: an incorrect destination
5456
array length fails const evaluation so the mistake cannot silently produce a
5557
truncated or oversized static value.
58+
59+
Use `Engine::encode_array` for fixed-size static data and compile-time checked
60+
arrays. For runtime data, especially data sized from packet headers, file
61+
metadata, network frames, or other untrusted sources, use checked length helpers
62+
and caller-owned slice APIs instead.

docs/SECURITY_CONTROLS.md

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,15 @@ profiles are strict about the configured line ending and non-final line width.
6161
### Memory Retention Reduction
6262

6363
Cleanup APIs are best-effort initialized-byte cleanup. They are implemented
64-
without runtime dependencies and without unsafe code in scalar paths. They do
65-
not claim formal zeroization against compiler behavior, historical stack-frame
66-
copies, allocator internals, copies made outside the wrapper, core dumps, swap,
67-
or arbitrary process memory disclosure vulnerabilities. For high-assurance
68-
secret handling, use the clear-tail APIs promptly and pair them with operating
69-
system and deployment controls that reduce crash dumps, swap, and broad memory
70-
disclosure exposure.
64+
without runtime dependencies using the audited volatile wipe helpers inventoried
65+
in [UNSAFE.md](UNSAFE.md). They do not claim formal zeroization against
66+
compiler behavior, historical stack-frame copies, allocator internals, copies
67+
made outside the wrapper, core dumps, swap, or arbitrary process memory
68+
disclosure vulnerabilities. For high-assurance secret handling, use the
69+
clear-tail APIs promptly and pair them with operating system and deployment
70+
controls that reduce crash dumps, swap, and broad memory disclosure exposure.
71+
If a platform requires a formal zeroization policy, apply that policy to
72+
caller-owned buffers in addition to the crate's dependency-free cleanup APIs.
7173

7274
### Side-Channel Posture
7375

@@ -76,6 +78,9 @@ built-in alphabets, and the decoder uses branch-minimized ASCII arithmetic.
7678
The `ct` module narrows the timing target further for scalar decode and uses a
7779
fixed scan over the selected alphabet for generic symbol mapping. It still does
7880
not carry a formal cryptographic constant-time guarantee.
81+
Input length, padding length, decoded length, and final success/failure remain
82+
public protocol facts; callers that must hide those facts need fixed-shape
83+
protocol-level processing after decode failure.
7984

8085
### Supply Chain
8186

docs/UNSAFE.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,12 @@ Safety argument:
5454
Limitations:
5555

5656
- This is best-effort data-retention reduction, not a formal zeroization
57-
guarantee. It cannot clear historical copies, allocator spare capacity, swap,
58-
core dumps, CPU registers, or buffers outside the slice provided to the API.
57+
guarantee. It cannot clear historical copies, compiler spill slots,
58+
allocator spare capacity, swap, core dumps, CPU registers, or buffers outside
59+
the slice provided to the API.
60+
- Callers with platform-specific formal zeroization requirements should apply
61+
their own zeroization policy to caller-owned buffers in addition to using the
62+
crate cleanup APIs.
5963

6064
### `wipe_vec_spare_capacity`
6165

@@ -95,8 +99,9 @@ Safety argument:
9599
Limitations:
96100

97101
- This is best-effort data-retention reduction, not a formal zeroization
98-
guarantee. It cannot make claims about allocator internals, historical copies,
99-
swap, core dumps, CPU registers, or buffers outside the vector allocation.
102+
guarantee. It cannot make claims about allocator internals, historical
103+
copies, compiler spill slots, swap, core dumps, CPU registers, or buffers
104+
outside the vector allocation.
100105

101106
### `encode_48_bytes_avx512`
102107

0 commit comments

Comments
 (0)