Skip to content

Bug: Label_H1_Paren parseLat regex mis-parses 5-digit latitudes accepted by the outer regex (silent, out-of-range output) #468

Description

@kevinelliott

Summary

lib/plugins/Label_H1_Paren.ts decodes H1 position-report messages of the form (POS-<flight> <lat><lon>/<time> F<alt>\r\nRMK/FUEL <fuel> M<mach>). The outer regex captures the latitude as (?<lat>-?\d{4,5}[NS])4 or 5 digits — but the parseLat helper only understands the 4-digit DDMM form and its inner regex is unanchored, so on a 5-digit input the regex engine slides the match one character to the right and silently returns an out-of-range latitude as the aircraft's position.

Location

lib/plugins/Label_H1_Paren.ts:26,55-62

const regex =
  /^\(POS-(?<flight>\w+)\s+(?<lat>-?\d{4,5}[NS])(?<lon>\d{5}[EW])\/(?<timestamp>\d{6})\s+F(?<alt>\d{3})\r?\nRMK\/FUEL\s+(?<fuel>\d{2,3}\.\d)\s+M(?<mach>\d\.\d{2})\)/;
...
function parseLat(latStr: string): number {
  const match = latStr.match(/(-?)(\d{2})(\d{2})([NS])/);   // <-- unanchored, 2+2 digits only
  ...
}

Reproduction

const decoder = new MessageDecoder();
const result = decoder.decode({
  label: 'H1',
  text: '(POS-KLM296  39117N07600W/234212 F250\r\nRMK/FUEL  37.0 M0.69)',
});
console.log(result.raw.position.latitude); // → 91.28333... (invalid latitude, > 90)

Trace inside parseLat('39117N'):

  • Inner regex /(-?)(\d{2})(\d{2})([NS])/ is unanchored.
  • At offset 0: consumes "", 39, 11; next char is 7 (not [NS]) → fails.
  • At offset 1: consumes "", 91, 17; next char is Nmatches.
  • Returns sign * (91 + 17/60) = 91.283.

Expected

Either (a) the outer regex should be tightened to \d{4} so the 5-digit form fails the outer match (and decoded: false is returned) — or (b) parseLat should be updated to correctly handle the 5-digit DDMMM (degrees + minutes + tenths-of-minute) form and return 39° 11.7' N ≈ 39.195.

Either way, the current behaviour of emitting 91.283 for a syntactically-accepted input is silent, undetectable-downstream corruption of aircraft position data.

Actual

Silent out-of-range latitude (~91°) reported as the aircraft's position. No error, no fallback — decoded: true and decodeLevel: 'partial' are set.

Recommendation

Anchor and widen the helper, and reject offsets that don't consume the whole capture:

function parseLat(latStr: string): number {
  const match = latStr.match(/^(-?)(\d{2})(\d{2}(?:\d)?)([NS])$/);
  if (!match) return NaN;
  const deg = parseInt(match[2], 10);
  const rawMin = match[3];
  const min = rawMin.length === 3
    ? parseInt(rawMin, 10) / 10   // DDMMM → decimal minutes with tenths
    : parseInt(rawMin, 10);
  const sign = match[4] === 'S' ? -1 : 1;
  return sign * (deg + min / 60);
}

The same anchoring smell exists in parseLon — its outer regex fixes the digit count so it's not exploitable today, but the unanchored inner pattern is fragile if the outer regex is ever loosened.

Notes for the reviewer

  • Verified that MessageDecoder.decode() returns whatever the plugin sets; there is no downstream sanity check on latitude ∈ [-90, 90] to catch this.
  • No existing test in Label_H1_Paren.test.ts covers a 5-digit latitude.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions