reaching for something that isn't there
Index Out of Bounds & Missing Keys
Arrays, dictionaries, and parsed payloads get reached into positionally, on faith. The slot is empty, the key absent, the array shorter than assumed, and the access fails far from the bad assumption.
An index or key is assumed present; the structure never promised it.
01in the wild
In the wild
The Missing Key
Reaching into a structure for something that isn't there — a key, an array slot, a JSON field.
example.py
ages = {'Jim': 30, 'Pam': 28, 'Kevin': 33}
# WRONG: assumes the key exists
ages['Michael'] # KeyError: 'Michael'
# RIGHT: ask for it safely, or validate the shape first
ages.get('Michael', None) # None
if 'Michael' in ages:
use(ages['Michael'])A literal lookup assumes a shape the data never promised. .get() turns a crash into a value you can handle.
// observed
ages['Michael'] -> KeyError
ages.get('Michael') -> Noneexample.js
// A real one: trusting the shape of an SNS event
const msg = JSON.parse(event.Records[0].Sns.Message);
// throws if Records is empty, or Sns is missing, or...
// RIGHT: optional chaining + validate before parse
const raw = event?.Records?.[0]?.Sns?.Message;
if (typeof raw !== "string") throw new Error("bad event shape");
const msg = JSON.parse(raw);Each dot and index is an assumption about a payload you didn't build. Optional chaining plus a type check makes the contract explicit.
// observed
unchecked: TypeError: Cannot read '0' of undefined checked: throws 'bad event shape' with context
example.rb
# Brittle parsing chained on positional assumptions
valid = href_attrs.length == 2 &&
href_attrs[0] == 'href' &&
href_attrs[1].start_with?('"') # NoMethodError if [1] is nil
# RIGHT: guard each assumption, or parse with a real library
valid = href_attrs.length == 2 &&
href_attrs[0] == 'href' &&
href_attrs[1].to_s.start_with?('"')Index 1 is assumed present and assumed a String. .to_s makes the type safe; a real HTML parser makes the whole thing safe.
// observed
raw: NoMethodError on nil[1] to_s: false (no crash)
The Empty-Collection Edge
first(), last(), and [0] all assume the collection has a first element.
example.py
def latest(events):
return events[-1] # IndexError on []
# RIGHT: handle empty, or use a safe accessor
def latest(events):
return events[-1] if events else NoneNegative indexing doesn't wrap to safety; [-1] on an empty list throws just like [0] does.
// observed
latest([]) -> IndexError guarded -> None
example.java
// WRONG: assumes the stream produced something
String first = names.stream().findFirst().get(); // NoSuchElementException
// RIGHT: Optional forces you to handle the empty case
String first = names.stream().findFirst().orElse("(none)");Optional.get() on an empty Optional throws. orElse / orElseThrow make the empty path a decision, not an accident.
// observed
get() on empty -> NoSuchElementException orElse fallback -> "(none)"
02cross-pollination
Where this compounds
Runtime Errors
- Off-by-One Index Crash × ++ / -- & Integer Overflow
- Integer Overflow Sizes a Buffer Too Small × ++ / -- & Integer Overflow
Data Corruption
- Buffer Overflow (Out-of-Bounds Write) × Pointer Mismanagement
03weakness catalog
Mapped weaknesses (CWE)
On its own, this defect is catalogued by MITRE as one or more of these weaknesses. The exploitable vulnerability usually appears only when it chains or combines with another.