You don’t need a custom scanner to apply the Error Principle to your codebase. You can measure information debt with a pen, a printout, and 30 minutes per file.
Here’s the step-by-step methodology I use — the same one that found 53 information debt points in Android AOSP system services.
Step 1: Identify Trust Boundaries
Draw a line through your architecture diagram. Everything on the left is “untrusted.” Everything on the right is “trusted.” The IPC / API layer in between is where information debt lives.
In a typical Android system service, the boundary is between an unprivileged app and the system_server process. In a web application, it’s between the client browser and the server. In a kernel module, it’s between userspace and kernel space.
Write down every function that processes requests crossing this boundary. These are your audit targets.
Step 2: Enumerate Error Handlers
For each function, find every try-catch block. Ignore library-internal catches (they handle resource cleanup, not security enforcement). Focus on catches in the security enforcement path.
Categorize each catch by pattern:
- Pattern A (Broad):
catch (Exception e)orcatch (Throwable t)— catches everything - Pattern B (Silent return):
return false/return nullon exception — indistinguishable from “denied” - Pattern C (TOCTOU): Permission check after the resource is already consumed
- Pattern D (Decorative):
checkPermission()called but result never used - Pattern E (Inconsistent state): Operation partially completes, then error skips remaining enforcement
Step 3: Score D_e
For each finding, calculate D_e = 1 − H_e, where H_e = α + β + γ + δ:
| Factor | Weight | Ask Yourself |
|---|---|---|
| Detection (α) | 0–0.4 | Does the catch target the correct exception type? Not too broad, not too narrow? |
| Handling (β) | 0–0.3 | Does the handler restore security invariants? Re-throws, recovers, or re-checks? |
| Audit (γ) | 0–0.2 | Is the failure logged? Is a metric pushed? Can a SOC team detect this? |
| State (δ) | 0–0.1 | Is the system left in a safe, consistent state after handling? |
Scoring guide:
- Empty catch block → α=0, β=0, γ=0, δ=0 → H_e=0.0, D_e=1.0 (Max Debt)
- Log + continue → α=0.1, β=0, γ=0.05, δ=0 → H_e=0.15, D_e=0.85
- Log + re-throw SecurityException → α=0.3, β=0.25, γ=0.1, δ=0.05 → H_e=0.70, D_e=0.30
- Log + re-throw + push metric → α=0.35, β=0.3, γ=0.2, δ=0.1 → H_e=0.95, D_e=0.05
Step 4: Classify by Regime
| D_e Range | Regime | Action |
|---|---|---|
| > 0.8 | Maximum Debt | Treat as security defect. File immediately. |
| 0.5–0.8 | Information Debt | Needs hardening. Add to sprint backlog. |
| 0.2–0.5 | Partial Debt | Low priority. Review next quarter. |
| < 0.2 | Minimal Debt | Acceptable. No action needed. |
Step 5: Build the Debt Lattice
Take all findings with D_e > 0.5. Arrange them in order of execution flow. If finding F₁ can affect the state seen by finding F₂, draw an edge from F₁ to F₂.
Two findings are chainable if the output of F₁ (a skipped permission check, an inconsistent state) becomes the input condition for F₂ (now the permission check can be triggered from a different code path).
The critical findings are the ones with the most outgoing and incoming edges. These are your bottlenecks — fix them and the lattice collapses.
Step 6: Apply the Three Rules
For every finding with D_e > 0.5:
- No broad catches: Replace
catch (Exception e)with specific exception types. If you don’t know what exceptions can be thrown, that’s a design problem — not an excuse for a blanket catch. - Default to DENY: Every exception in a permission check must re-throw as SecurityException.
return falseis a silent bypass. - Audit by default: Every catch block must push a metric.
SecurityExceptionfrom a permission check is a security event — treat it like one.
Real Example: My Scoring in Action
Here’s how I scored the worst finding from the Android audit:
| Factor | Score | Why |
|---|---|---|
| α (detection) | 0.0 | Catches BadParcelableException specifically but does nothing — zero detection value |
| β (handling) | 0.0 | No remediation. Permission check skipped. Execution continues. |
| γ (audit) | 0.05 | Logs a warning with Slog.w(). No StatsLog metric. |
| δ (state) | 0.0 | System continues without URI permission enforcement |
H_e = 0.05. D_e = 0.95. Regime: Maximum Debt. Verdict: file as security defect immediately.
And this is from Google — one of the most security-mature organizations in the world, on a codebase that has been reviewed by hundreds of engineers and dozens of external researchers. If their code has D_e = 0.95, yours probably does too.
Bonus: Command-Line Scanner
I built an open-source Error Debt scanner that automates steps 2-4 for Java, C++, Python, and PHP. It computes H_e and D_e from source code and classifies findings by regime.
Clone it from my Codeberg and run:
python3 error_debt/debt_scanner.py scan path/to/source
python3 error_debt/debt_scanner.py classify path/to/source
python3 error_debt/debt_scanner.py report findings.json
The scanner outputs a formal report with D_e scores, regime classification, and VMF curvature modifiers. It’s the same methodology that found 53 findings in Android system services, applied automatically.
The Bottom Line
Information debt is the most systematically overlooked class of security defect in the industry. It’s not in the OWASP Top 10. It’s not in most SAST tool rulebooks. It’s not taught in security engineering courses.
But it’s everywhere. And it’s measurable.
30 minutes per file. Five patterns. Four factors. One score. You now have everything you need to find information debt in your own codebase.
Want me to run this analysis on your code? Send me a source file and I’ll return a D_e-scored report within 24 hours. Free for the first file.
Part of the Geometric Vulnerability Analysis series