Skip to content

harden(serve): strict JIRA_BOARD_ID parse + share one digits-only int primitive#79

Merged
abpai merged 1 commit into
mainfrom
harden-jira-board-id
Jun 23, 2026
Merged

harden(serve): strict JIRA_BOARD_ID parse + share one digits-only int primitive#79
abpai merged 1 commit into
mainfrom
harden-jira-board-id

Conversation

@abpai

@abpai abpai commented Jun 23, 2026

Copy link
Copy Markdown
Owner

Follow-up to #77, addressing the two review findings that were deferred there (F3, F2).

F3 — strict JIRA_BOARD_ID (the headline change)

trackerConfigFromEnv parsed JIRA_BOARD_ID with Number.parseInt(raw, 10) + Number.isFinite, which silently coerced malformed values into a plausible-but-wrong board id:

  • '12abc'12, '1e3'1, '-5'-5, '0x10'0 — all pinned a wrong board
  • 'notanumber' → dropped silently

Now resolved through a strict loader:

  • unset / blank → undefined (no board pinned) — unchanged
  • valid positive integer → used
  • set-but-invalid → throws INVALID_CONFIG (JIRA_BOARD_ID must be a positive integer)

⚠️ Behavior change

A non-empty malformed JIRA_BOARD_ID now fails config loading instead of being silently ignored. This matches how resolvePollingSyncIntervalMs already treats a malformed optional numeric env var (the codebase's own precedent — it throws INVALID_CONFIG rather than falling back), and avoids silently pinning a wrong board. The wiring test that asserted silent fallback was updated to assert the throw.

F2 — one shared digits-only primitive

Extracted parseDecimalDigits(value): number | null as the single pure digits-only / safe-integer parser. parseBoundedInt (CLI/HTTP, INVALID_ARGUMENT), resolvePollingSyncIntervalMs (env, INVALID_CONFIG), and the new JIRA_BOARD_ID loader all build on it — so "what counts as an integer" has one definition and can't drift. Each caller keeps its own error code/message (no error-code threading, per the review's guidance). Removes the previously hand-rolled duplicate parse body.

Verification

  • bun run check clean; suite 499 pass / 27 skip / 0 fail (+4 new: parseDecimalDigits unit coverage; JIRA_BOARD_ID accept/omit/reject).
  • parseBoundedInt and resolvePollingSyncIntervalMs behavior byte-identical (the refactor is a pure dedup); no import cycle.
  • Independent Codex review: APPROVE (agreed with the fail-fast decision).

https://claude.ai/code/session_01LQjR5poSV2M3nxt8h5hNad

…mitive

Follow-up to #77 (review findings F3 + F2).

F3 — JIRA_BOARD_ID used Number.parseInt, which silently coerced trailing
garbage and signs into a plausible-but-wrong board id ('12abc'→12, '-5'→-5,
'1e3'→1) that was then pinned via Number.isFinite. Now resolved through a
strict loader: unset/blank → undefined (no board), set-but-invalid → throw
INVALID_CONFIG. This matches how resolvePollingSyncIntervalMs already treats a
malformed optional numeric env var (the codebase's own precedent), instead of
silently dropping or misinterpreting it.

BEHAVIOR CHANGE: a non-empty malformed JIRA_BOARD_ID now fails config loading
with INVALID_CONFIG rather than being silently ignored. Updated the wiring test
that previously asserted silent fallback.

F2 — extracted parseDecimalDigits (transport-input.ts) as the single pure
digits-only/safe-integer primitive; parseBoundedInt, resolvePollingSyncIntervalMs,
and the new JIRA_BOARD_ID loader all build on it, so "what counts as an integer"
has one definition and can't drift (no third hand-rolled copy). Error
codes/messages unchanged (INVALID_ARGUMENT vs INVALID_CONFIG preserved per path).

+4 tests (parseDecimalDigits unit coverage; JIRA_BOARD_ID accept/omit/reject).
Suite 499 pass / 0 fail, bun run check clean.

Claude-Session: https://claude.ai/code/session_01LQjR5poSV2M3nxt8h5hNad
@abpai abpai merged commit da1f4cd into main Jun 23, 2026
1 check passed
@abpai abpai deleted the harden-jira-board-id branch June 23, 2026 17:11
abpai added a commit that referenced this pull request Jun 24, 2026
The serve-hardening and CLI work since v0.7.0 (PRs #76, #77, #78, #79) merged
without changesets, so the Release workflow had nothing to consume and the
package is still published at 0.7.0. Add the missing changesets so the next
release captures this work.

- serve-api-webhook-hardening (patch): #76 - envelope webhook-route errors plus
  tunnel / Postgres-receipt / broadcast / base-path fixes (10 defects).
- webhook-secret-fail-open (patch): #77 - single WEBHOOK_SECRET_ENV source of
  truth for the tunnel gate + stricter KANBAN_SYNC_INTERVAL_MS parsing.
- honor-default-task-column (patch): #78 - SQLite createTask honors the
  configured default task column, with first-column fallback parity with Postgres.
- strict-jira-board-id (minor): #79 - a malformed JIRA_BOARD_ID now throws
  INVALID_CONFIG instead of being silently misparsed (behavior change).

Net bump: minor -> 0.8.0. #80 (pure internal refactor) and the README link fix
are intentionally omitted as they have no user-facing change.

Claude-Session: https://claude.ai/code/session_01X9j5Rs6kXK8BuHguhy3x33
@github-actions github-actions Bot mentioned this pull request Jun 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant