Skip to content

Commit b6709cc

Browse files
committed
Add panic policy validation
1 parent e0120de commit b6709cc

9 files changed

Lines changed: 123 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
redacted owned sensitive output with dependency-free best-effort cleanup.
2323
- Added `docs/TRUST.md`, `docs/SECURITY_CONTROLS.md`, and a README trust
2424
dashboard for adoption-focused security evidence and CWE mapping.
25+
- Added `docs/PANIC_POLICY.md` and `scripts/validate-panic-policy.sh` to keep
26+
runtime panic-like sites reviewed and release-gated.
2527

2628
## 0.5.0 - 2026-05-14
2729

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,8 @@ acceleration gate, see [docs/SIMD.md](docs/SIMD.md).
478478
For the trust dashboard and CWE/security-control mapping, see
479479
[docs/TRUST.md](docs/TRUST.md) and
480480
[docs/SECURITY_CONTROLS.md](docs/SECURITY_CONTROLS.md).
481+
For panic-free public API policy, see
482+
[docs/PANIC_POLICY.md](docs/PANIC_POLICY.md).
481483
For dependency admission rules, see [docs/DEPENDENCIES.md](docs/DEPENDENCIES.md).
482484
For adoption guidance from the established `base64` crate, see
483485
[docs/MIGRATION.md](docs/MIGRATION.md).

SECURITY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ Runtime scalar APIs are expected to return `Result` or `Option` for malformed
9696
input and size errors instead of unwinding. Compile-time array encoding is the
9797
exception: it intentionally fails const evaluation when the caller supplies an
9898
incorrect output array length.
99+
`scripts/validate-panic-policy.sh` release-gates new non-test panic-like sites
100+
and requires reviewed exceptions to remain documented in `docs/PANIC_POLICY.md`.
99101

100102
Bounded-memory users should prefer `checked_encoded_len`, `decoded_capacity`,
101103
`decode_slice`, and `decode_in_place` so allocation limits are chosen by the

docs/PANIC_POLICY.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Panic Policy
2+
3+
`base64-ng` treats runtime panics in public scalar APIs as denial-of-service
4+
risk. Public encode, decode, validation, length, in-place, streaming, and
5+
profile APIs should report malformed input and size failures with `Result` or
6+
`Option` rather than unwinding.
7+
8+
This policy is enforced by:
9+
10+
```sh
11+
scripts/validate-panic-policy.sh
12+
```
13+
14+
The validator scans non-test source before Kani/test-only modules and fails on
15+
new `panic!`, `unreachable!`, `.unwrap()`, or `.expect()` sites unless they
16+
match an allowlisted pattern documented here.
17+
18+
## Allowed Non-Test Sites
19+
20+
The current reviewed exceptions are:
21+
22+
- `Engine::encode_array` may panic during const evaluation when the caller
23+
supplies an output array length that does not match the compile-time encoded
24+
length, or when that const length calculation overflows. This is documented
25+
as a const-array API contract and is not used for runtime untrusted length
26+
metadata.
27+
- Internal remainder matches use `_ => unreachable!()` after matching
28+
`len % 3` or equivalent remainder values. The preceding arithmetic bounds
29+
make those arms unreachable.
30+
- `String::from_utf8` conversions after Base64 encoding use `unreachable!`
31+
because crate encoders produce ASCII bytes by construction.
32+
- Stream `get_ref`, `get_mut`, and `into_inner` internal helpers use
33+
`unreachable!` if a wrapper has already consumed its inner value. The public
34+
API consumes `self` for `into_inner`/`finish`, so this state is not reachable
35+
through safe public calls.
36+
37+
Test code, doctest examples, and Kani proof harnesses may use `unwrap`,
38+
`expect`, or panic-like macros when they are asserting expected outcomes.
39+
40+
## Caller Guidance
41+
42+
For untrusted input and untrusted length metadata, prefer:
43+
44+
- `checked_encoded_len`
45+
- `encoded_len`
46+
- `wrapped_encoded_len`
47+
- `decoded_capacity`
48+
- `decoded_len`
49+
- caller-owned `encode_slice` and `decode_slice`
50+
- in-place APIs that return `Result`
51+
52+
Compile-time array encoding is intentionally stricter: an incorrect destination
53+
array length fails const evaluation so the mistake cannot silently produce a
54+
truncated or oversized static value.

docs/PLAN.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,12 +178,13 @@ the zero-runtime-dependency stance.
178178
- Internal safe-Rust best-effort wipe helpers for initialized bytes and
179179
redacted `SecretBuffer` owned outputs when `alloc` is enabled.
180180
- README trust dashboard and CWE/security-control mapping documentation.
181+
- Panic policy documentation and release-gated panic-like-site validation for
182+
non-test source.
181183

182184
### Missing Secure Core Features
183185

184-
- Expand panic-free policy checks for non-test scalar code, replacing unchecked
185-
indexing and unwrap-like operations where practical or proving their bounds
186-
where replacement would harm clarity.
186+
- Continue replacing unchecked indexing where practical or documenting bounded
187+
internal indexing with proof, tests, or local invariants.
187188

188189
### Missing Performance Features
189190

docs/RELEASE_EVIDENCE.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ The release gate runs:
6262
- unsafe-boundary validation that requires inventory documentation for every
6363
SIMD-boundary unsafe function and a nearby `SAFETY:` explanation for every
6464
unsafe block
65+
- panic-policy validation that fails on unreviewed non-test `panic!`,
66+
`unreachable!`, `.unwrap()`, or `.expect()` sites
6567
- runtime backend report tests proving the public active backend remains scalar
6668
until an accelerated backend is explicitly admitted
6769
- runtime backend policy tests for scalar execution and no-SIMD deployment

scripts/checks.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ scripts/check_reserved_features.sh
1616
echo "checks: unsafe boundary"
1717
scripts/validate-unsafe-boundary.sh
1818

19+
echo "checks: panic policy"
20+
scripts/validate-panic-policy.sh
21+
1922
echo "checks: clippy default"
2023
cargo clippy --all-targets -- -D warnings
2124

scripts/validate-panic-policy.sh

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/usr/bin/env sh
2+
set -eu
3+
4+
check_file() {
5+
file="$1"
6+
awk '
7+
BEGIN {
8+
failed = 0
9+
}
10+
/^#\[cfg\((test|kani)\)\]/ {
11+
exit failed
12+
}
13+
/^[[:space:]]*\/\/[!\/]/ {
14+
next
15+
}
16+
/panic!\(|unreachable!\(|\.unwrap\(|\.expect\(/ {
17+
allowed = 0
18+
if ($0 ~ /unreachable!\("stream .* was already taken"\)/) {
19+
allowed = 1
20+
}
21+
if ($0 ~ /unreachable!\("base64 encoder produced non-UTF-8 output"\)/) {
22+
allowed = 1
23+
}
24+
if ($0 ~ /_ => unreachable!\(\),/) {
25+
allowed = 1
26+
}
27+
if ($0 ~ /panic!\("encoded base64 length overflows usize"\)/) {
28+
allowed = 1
29+
}
30+
if (!allowed) {
31+
printf "panic policy: unreviewed panic-like site in %s:%d: %s\n", FILENAME, FNR, $0 > "/dev/stderr"
32+
failed = 1
33+
}
34+
}
35+
END {
36+
exit failed
37+
}
38+
' "$file"
39+
}
40+
41+
test -s docs/PANIC_POLICY.md
42+
43+
check_file src/lib.rs
44+
check_file src/simd.rs
45+
46+
echo "panic policy: ok"

scripts/validate-release-metadata.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,13 @@ test -s docs/BENCHMARKS.md
6161
test -s docs/CONSTANT_TIME.md
6262
test -s docs/DEPENDENCIES.md
6363
test -s docs/MIGRATION.md
64+
test -s docs/PANIC_POLICY.md
6465
test -s docs/PLAN.md
6566
test -s docs/RELEASE.md
6667
test -s docs/RELEASE_EVIDENCE.md
68+
test -s docs/SECURITY_CONTROLS.md
6769
test -s docs/SIMD.md
70+
test -s docs/TRUST.md
6871
test -s docs/UNSAFE.md
6972

7073
for required_script in \
@@ -81,6 +84,7 @@ for required_script in \
8184
"scripts/reproducible_build_check.sh" \
8285
"scripts/stable_release_gate.sh" \
8386
"scripts/validate-dependencies.sh" \
87+
"scripts/validate-panic-policy.sh" \
8488
"scripts/validate-release-metadata.sh" \
8589
"scripts/validate-unsafe-boundary.sh"
8690
do
@@ -128,10 +132,13 @@ for required_package_file in \
128132
"docs/CONSTANT_TIME.md" \
129133
"docs/DEPENDENCIES.md" \
130134
"docs/MIGRATION.md" \
135+
"docs/PANIC_POLICY.md" \
131136
"docs/PLAN.md" \
132137
"docs/RELEASE.md" \
133138
"docs/RELEASE_EVIDENCE.md" \
139+
"docs/SECURITY_CONTROLS.md" \
134140
"docs/SIMD.md" \
141+
"docs/TRUST.md" \
135142
"docs/UNSAFE.md" \
136143
"scripts/check_backend_evidence.sh" \
137144
"scripts/check_fuzz.sh" \
@@ -146,6 +153,7 @@ for required_package_file in \
146153
"scripts/reproducible_build_check.sh" \
147154
"scripts/stable_release_gate.sh" \
148155
"scripts/validate-dependencies.sh" \
156+
"scripts/validate-panic-policy.sh" \
149157
"scripts/validate-release-metadata.sh" \
150158
"scripts/validate-unsafe-boundary.sh" \
151159
"src/lib.rs" \

0 commit comments

Comments
 (0)