Skip to content

feat: harden supply chain and application security - #68

Merged
nazozokc merged 4 commits into
mainfrom
AI-agent
Jul 2, 2026
Merged

feat: harden supply chain and application security#68
nazozokc merged 4 commits into
mainfrom
AI-agent

Conversation

@nazozokc

@nazozokc nazozokc commented Jul 2, 2026

Copy link
Copy Markdown
Owner

概要

サプライチェーンセキュリティとアプリケーションセキュリティを包括的に診断・改善。あわせてドキュメントの不足を全補完。

変更内容

サプライチェーンセキュリティ

  • pnpm.onlyBuiltDependencies: 許可リスト制
  • Dependabot設定: npm + GitHub Actions の週次アップデート監視
  • リリース時署名タグ強制: 未署名タグのリリースをエラー終了に変更

アプリケーションセキュリティ

  • usage-import: ファイル50MB / stdin 10MB + 30秒タイムアウト制限
  • cursor/windsurf scanner: テーブル名許可リストバリデーション(デッドコード削除)
  • pricing data fetch: 10MB上限 + 最低エントリ数チェック + 構造バリデーション + reader.cancel()
  • 暗号鍵ファイル: SHA-256完全性チェック (.key.sha256 sidecar)
  • usage-import: TOCTOU競合修正(同一fdでstat+read)
  • usage-import: process.exit(1) → stdin.destroy() に変更
  • nextDateForCycle: 月次/年次請求日の比較を >>= に修正(同日請求のバグfix)

ドキュメント全補完

  • コマンドリファレンスに 13の未記載コマンド を追加(search, trial, bulk, forecast, history, timeline, optimize, compare, calendar, notify, profile, mcp, tui)
  • 新規 MCPページ 作成(17ツールリファレンス + Claude Desktop/Cursor設定例)
  • データページに SHA-256鍵完全性チェック 追記
  • ガイドページに 11の新規ガイド 追加
  • FAQに 11の新規Q&A 追加
  • サイドバーに MCP 追加
  • export形式に excel/ics 追記
  • usage import にサイズ制限を追記

テスト

  • pnpm test: 396 passed ✅(既存不具合も解消)
  • pnpm lint:types: OK ✅
  • pnpm build: OK ✅
  • docs build: OK ✅

- Add file size limits and stdin timeout to usage-import (DoS prevention)
- Sanitize SQLite table names in cursor/windsurf scanners (SQLi prevention)
- Add response size limits and structure validation to pricing data fetch
- Add SHA-256 integrity verification for encryption key file
- Restrict install scripts via pnpm.onlyBuiltDependencies
- Enforce signed tags in release workflow
- Add Dependabot config for npm and GitHub Actions
- Add readStreamWithLimit helper for bounded network reads
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@nazozokc, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 21 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4e206925-f53b-436e-86e5-8e0245fe5877

📥 Commits

Reviewing files that changed from the base of the PR and between 9feccfe and ad1ec6e.

📒 Files selected for processing (1)
  • subtrack/src/upcoming.ts
📝 Walkthrough

Walkthrough

This PR hardens key handling, pricing fetch validation, and usage import input limits in subtrack, adds scanner comments, and updates Dependabot, release tag verification messaging, and pnpm package metadata.

Changes

Subtrack input/data hardening

Layer / File(s) Summary
Encryption key integrity sidecar
subtrack/src/crypto.ts
Adds SHA-256 sidecar hash computation, writing, and verification for the key file; getOrCreateKey() validates key size and integrity before use, and creates the sidecar on new key generation.
Pricing JSON download hardening
subtrack/src/pricing.ts
Adds MAX_PRICING_JSON_BYTES and MIN_PRICING_KEYS constants; replaces res.text() parsing with streamed, size-capped reads and validates the parsed result is a non-array object with sufficient keys.
Usage import size/timeout limits
subtrack/src/usage-import.ts
Adds max file/stdin size and timeout constants; enforces stdin size/timeout guards and a pre-read size check for files, logging and aborting on violations.
Scanner table-name comments
subtrack/src/cursor-scanner.ts, subtrack/src/windsurf-scanner.ts
Adds comments stating the selected table name is guaranteed to come from the known table list, with no runtime behavior change.

CI and dependency configuration

Layer / File(s) Summary
Dependabot config restructuring
.github/dependabot.yml
Consolidates npm updates into a single /subtrack entry with versioning strategy, higher PR limit, commit-message prefixes, and grouped prod/dev dependency rules; updates github-actions labels and commit-message prefix.
Release tag verification messaging
.github/workflows/release.yml
Changes error and ambiguous verification messages in the Verify signed tag step, limiting ambiguous output to five lines.
pnpm config addition
subtrack/package.json
Adds a pnpm block with an empty onlyBuiltDependencies array.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the PR's main security hardening changes across supply chain and application paths.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch AI-agent

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
subtrack/src/windsurf-scanner.ts (1)

96-111: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Same dead-code guard as in cursor-scanner.ts.

tableName at Line 100 comes from knownTables.find((t) => tableNames.includes(t)), so it is always drawn from the fixed knownTables list, never from the untrusted tableNames array. The added !knownTables.includes(tableName) check at Line 107 can therefore never be true and doesn't provide real protection — see the corresponding comment in cursor-scanner.ts for the same pattern.

🧹 Proposed cleanup
-    // Sanitize table name: must match exactly a known table name (prevents SQL injection from malicious DB)
-    if (!knownTables.includes(tableName)) {
-      consola.warn(`Windsurf DB has suspicious table name "${tableName}" — skipping`)
-      return { source: "windsurf", entries: [] }
-    }
+    // tableName is guaranteed to be one of `knownTables` by construction
+    // (see the `.find` above), so no further identifier validation is needed here.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@subtrack/src/windsurf-scanner.ts` around lines 96 - 111, Remove the dead-code
sanitization guard in windsurf-scanner.ts: tableName is already constrained by
knownTables.find(...) to a fixed allowlist, so the subsequent
!knownTables.includes(tableName) branch in the scan flow is unreachable. Keep
the existing knownTables lookup and the no-table-found early return, and delete
the redundant suspicious-table warning check to match the cleanup done in
cursor-scanner.ts.
subtrack/src/cursor-scanner.ts (1)

104-119: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Added "sanitize" check is unreachable — tableName can never fail it.

tableName is computed as knownTables.find((t) => tableNames.includes(t)) — it iterates over the fixed knownTables array, so any non-undefined result is always one of "cursorDiskKV"/"ItemTable" by construction, never an attacker-controlled value from tableNames. Consequently !knownTables.includes(tableName) at Line 115 can never be true; the new guard is dead code and doesn't add protection against a malicious DB (the existing derivation was already safe).

Consider removing the redundant check (or replacing it with a comment clarifying the existing derivation is already safe), so the code doesn't imply a runtime validation that isn't actually happening.

🧹 Proposed cleanup
-    // Sanitize table name: must match exactly a known table name (prevents SQL injection from malicious DB)
-    if (!knownTables.includes(tableName)) {
-      consola.warn(`Cursor DB has suspicious table name "${tableName}" — skipping`)
-      return { source: "cursor", entries: [] }
-    }
+    // tableName is guaranteed to be one of `knownTables` by construction
+    // (see the `.find` above), so no further identifier validation is needed here.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@subtrack/src/cursor-scanner.ts` around lines 104 - 119, The new sanitize
guard in cursor-scanner’s table lookup is unreachable because
cursorTableName/tableName is derived only from the fixed knownTables list, so
the extra includes check can never fail. Remove the redundant runtime validation
(or replace it with a short comment) in cursor-scanner’s table-name selection
logic so the code accurately reflects that the safe derivation already prevents
untrusted table names from being used.
subtrack/src/usage-import.ts (1)

172-190: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Avoid reopening the file between the size check and the read

statSync(safeFile) and readFileSync(safeFile, "utf-8") use the path twice, so a writable file under home/tmp can be replaced after the size check and still be read. Open it once and use the same file descriptor for both operations.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@subtrack/src/usage-import.ts` around lines 172 - 190, Avoid reopening the
target file in usage-import.ts: the current statSync(safeFile) followed by
readFileSync(safeFile, "utf-8") can be raced if the file is replaced between
calls. Update the file-reading flow in the usage-import logic to open the file
once and perform both the size check and content read through the same file
descriptor, keeping the existing MAX_FILE_SIZE, safeFile, and related error
handling paths intact.
🧹 Nitpick comments (2)
subtrack/package.json (1)

37-39: 🔒 Security & Privacy | 🔵 Trivial

Keep this empty allowlist intentional. Add any future dependency with install/postinstall scripts to onlyBuiltDependencies, and consider a short note here so the policy is obvious.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@subtrack/package.json` around lines 37 - 39, The pnpm onlyBuiltDependencies
allowlist is currently empty and should be made intentionally documented. Update
the package.json pnpm configuration around onlyBuiltDependencies to keep the
empty list explicit, and add a short note/comment nearby explaining that any
future dependency with install/postinstall scripts must be added there.
subtrack/src/pricing.ts (1)

69-103: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Cancel the stream reader when the size limit is exceeded.

When totalBytes > MAX_PRICING_JSON_BYTES (Lines 79-81), the code throws without calling reader.cancel(), so the underlying HTTP response stream isn't explicitly released — it relies on the 15s FETCH_TIMEOUT_MS/GC to eventually clean up the connection rather than terminating it immediately.

🔧 Proposed fix
           if (totalBytes > MAX_PRICING_JSON_BYTES) {
+            reader.cancel().catch(() => {})
             throw new Error(`Pricing data too large (exceeded ${MAX_PRICING_JSON_BYTES / 1024 / 1024} MB)`)
           }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@subtrack/src/pricing.ts` around lines 69 - 103, In the pricing JSON
fetch/parsing flow, the size-limit failure path throws before the response
stream is explicitly released. Update the reader loop in the logic that uses
MAX_PRICING_JSON_BYTES so that when totalBytes exceeds the limit, the code first
cancels the reader (and then raises the same error) before exiting. Keep the
cleanup consistent with the existing timeout handling and finally block around
the fetch/read path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/dependabot.yml:
- Around line 1-33: The npm Dependabot entry in the dependabot config still
overlaps with Renovate for the same subtrack dependencies, which can create
duplicate patch/minor PRs. Update the existing npm update rule in the dependabot
configuration so it is scoped away from the dependencies Renovate already
manages, using the relevant updates entry for the /subtrack package-ecosystem
block and its grouping settings.

In `@subtrack/src/usage-import.ts`:
- Around line 141-162: The stdin timeout handling in usage-import.ts is
terminating the whole process directly from the setTimeout callback, which makes
the read path abrupt and hard to test. Update the stdin read logic around the
timeout timer and the for await loop over process.stdin so the callback stops
the stream instead of calling process.exit(1), allowing the loop to throw and be
handled by the existing try/finally (and any caller-level catch). Keep the
behavior localized to the stdin-reading block and preserve the clear timeout
error message via consola.error.

---

Outside diff comments:
In `@subtrack/src/cursor-scanner.ts`:
- Around line 104-119: The new sanitize guard in cursor-scanner’s table lookup
is unreachable because cursorTableName/tableName is derived only from the fixed
knownTables list, so the extra includes check can never fail. Remove the
redundant runtime validation (or replace it with a short comment) in
cursor-scanner’s table-name selection logic so the code accurately reflects that
the safe derivation already prevents untrusted table names from being used.

In `@subtrack/src/usage-import.ts`:
- Around line 172-190: Avoid reopening the target file in usage-import.ts: the
current statSync(safeFile) followed by readFileSync(safeFile, "utf-8") can be
raced if the file is replaced between calls. Update the file-reading flow in the
usage-import logic to open the file once and perform both the size check and
content read through the same file descriptor, keeping the existing
MAX_FILE_SIZE, safeFile, and related error handling paths intact.

In `@subtrack/src/windsurf-scanner.ts`:
- Around line 96-111: Remove the dead-code sanitization guard in
windsurf-scanner.ts: tableName is already constrained by knownTables.find(...)
to a fixed allowlist, so the subsequent !knownTables.includes(tableName) branch
in the scan flow is unreachable. Keep the existing knownTables lookup and the
no-table-found early return, and delete the redundant suspicious-table warning
check to match the cleanup done in cursor-scanner.ts.

---

Nitpick comments:
In `@subtrack/package.json`:
- Around line 37-39: The pnpm onlyBuiltDependencies allowlist is currently empty
and should be made intentionally documented. Update the package.json pnpm
configuration around onlyBuiltDependencies to keep the empty list explicit, and
add a short note/comment nearby explaining that any future dependency with
install/postinstall scripts must be added there.

In `@subtrack/src/pricing.ts`:
- Around line 69-103: In the pricing JSON fetch/parsing flow, the size-limit
failure path throws before the response stream is explicitly released. Update
the reader loop in the logic that uses MAX_PRICING_JSON_BYTES so that when
totalBytes exceeds the limit, the code first cancels the reader (and then raises
the same error) before exiting. Keep the cleanup consistent with the existing
timeout handling and finally block around the fetch/read path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7d8f3590-ac7e-440d-b35d-c572bfcd3bff

📥 Commits

Reviewing files that changed from the base of the PR and between b6a21ea and 51ac707.

📒 Files selected for processing (8)
  • .github/dependabot.yml
  • .github/workflows/release.yml
  • subtrack/package.json
  • subtrack/src/crypto.ts
  • subtrack/src/cursor-scanner.ts
  • subtrack/src/pricing.ts
  • subtrack/src/usage-import.ts
  • subtrack/src/windsurf-scanner.ts

Comment thread .github/dependabot.yml
Comment on lines +1 to 33
# Dependabot configuration for automated dependency updates.
# Also managed by Renovate (GitHub App). Dependabot provides
# additional security advisories and PR labeling.

version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
time: "09:00"
timezone: "Asia/Tokyo"
open-pull-requests-limit: 5
labels:
- "dependencies"
- "security"
# Only security updates — regular version bumps are handled by Renovate
allow:
- dependency-type: "all"
reviewers:
- "nazozokc"
assignees:
- "nazozokc"

- package-ecosystem: "npm"
directory: "/subtrack"
schedule:
interval: "weekly"
day: "monday"
time: "09:00"
timezone: "Asia/Tokyo"
open-pull-requests-limit: 5
versioning-strategy: increase
labels:
- "dependencies"
- "security"
allow:
- dependency-type: "all"
reviewers:
- "nazozokc"
assignees:
- "nazozokc"
- "npm"
open-pull-requests-limit: 10
commit-message:
prefix: "chore"
prefix-development: "chore"
groups:
production-dependencies:
dependency-type: "production"
update-types:
- "patch"
- "minor"
dev-dependencies:
dependency-type: "development"
update-types:
- "patch"
- "minor"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if a root package.json exists with dependencies
fd -HI '^package\.json$' -d 1 . && cat package.json 2>/dev/null | jq '{dependencies, devDependencies}'

Repository: nazozokc/subtrack

Length of output: 224


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== dependabot =="
cat -n .github/dependabot.yml

echo
echo "== renovate files =="
fd -HI 'renovate.*(\.json|\.json5|\.js|\.mjs|\.cjs)?$' .github . 2>/dev/null || true

echo
echo "== renovate config snippets =="
for f in $(fd -HI 'renovate.*(\.json|\.json5|\.js|\.mjs|\.cjs)?$' .github . 2>/dev/null); do
  echo "--- $f ---"
  cat -n "$f"
done

Repository: nazozokc/subtrack

Length of output: 15728


Avoid overlapping npm updates
The root package.json is empty, so dropping directory: "/" is fine. The /subtrack npm entry still overlaps with Renovate, so both bots will open patch/minor PRs for the same dependencies unless one side is scoped out.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/dependabot.yml around lines 1 - 33, The npm Dependabot entry in the
dependabot config still overlaps with Renovate for the same subtrack
dependencies, which can create duplicate patch/minor PRs. Update the existing
npm update rule in the dependabot configuration so it is scoped away from the
dependencies Renovate already manages, using the relevant updates entry for the
/subtrack package-ecosystem block and its grouping settings.

Comment thread subtrack/src/usage-import.ts
nazozokc added 2 commits July 2, 2026 19:05
- cursor-scanner.ts: remove dead-code table-name guard (already safe by construction)
- windsurf-scanner.ts: same cleanup
- usage-import.ts: fix TOCTOU race (open file once for stat+read via fd)
- usage-import.ts: replace process.exit(1) with stdin.destroy() in timeout
- pricing.ts: add reader.cancel() before throw on size limit

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@subtrack/src/usage-import.ts`:
- Around line 189-191: The file-reading logic in usage-import should not use
readSync plus Buffer.alloc because a short read can leave zero-filled bytes in
content and corrupt parsing. Update the read path in usage-import.ts to read the
file descriptor directly with readFileSync(fd, "utf-8") and assign that result
to content, keeping the surrounding logic in place.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0ee916d2-5dbc-45d8-bfe1-ed5663d589f8

📥 Commits

Reviewing files that changed from the base of the PR and between 51ac707 and 9feccfe.

📒 Files selected for processing (4)
  • subtrack/src/cursor-scanner.ts
  • subtrack/src/pricing.ts
  • subtrack/src/usage-import.ts
  • subtrack/src/windsurf-scanner.ts
✅ Files skipped from review due to trivial changes (2)
  • subtrack/src/cursor-scanner.ts
  • subtrack/src/windsurf-scanner.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • subtrack/src/pricing.ts

Comment on lines +189 to +191
const buffer = Buffer.alloc(st.size)
readSync(fd, buffer, 0, st.size, 0)
content = buffer.toString("utf-8")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Node.js fs.readSync partial read regular file may return fewer bytes than requested

💡 Result:

Yes, the Node.js fs.readSync function (and its asynchronous counterpart fs.read) can return fewer bytes than the length requested [1]. This behavior is expected and is not a bug [1]. ### Explanation When you call fs.readSync, Node.js acts as a wrapper around the underlying operating system's read system call (e.g., read() or pread() on POSIX/Linux) [2][1]. These system-level operations do not guarantee that they will fill the entire requested buffer in a single call [2][1]. Several scenarios can cause a partial read where bytesRead < length: * End of File (EOF): If the number of bytes remaining in the file is less than the requested length, the function will read only until the end of the file and return the number of bytes actually read [3]. * System-Level Limitations: Factors such as reading from slow network filesystems, interrupted system calls, or pipe/device-specific behaviors can cause the kernel to return fewer bytes than requested [2][1]. ### How to Handle Partial Reads Because fs.readSync does not automatically loop to fill the buffer, your application code must handle the possibility of partial reads if your logic strictly requires a specific amount of data [1]. If you need to ensure a buffer is completely filled (or until EOF is reached), you should wrap fs.readSync in a loop that continues to read until the desired number of bytes has been reached or the end of the file is signaled (indicated by bytesRead === 0) [2][1]. Example of correct loop implementation: javascript const fs = require('fs'); const fd = fs.openSync(path, 'r'); const buffer = Buffer.alloc(BUFFER_SIZE); let totalBytesRead = 0; while (totalBytesRead < BUFFER_SIZE) { const bytesRead = fs.readSync(fd, buffer, totalBytesRead, BUFFER_SIZE - totalBytesRead); if (bytesRead === 0) break; // EOF reached totalBytesRead += bytesRead; } fs.closeSync(fd);

Citations:


Use readFileSync(fd, "utf-8") here. readSync can return fewer bytes than requested, so decoding the zero-filled tail can corrupt content and break parsing. Reading directly from the fd avoids the partial-read hazard.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@subtrack/src/usage-import.ts` around lines 189 - 191, The file-reading logic
in usage-import should not use readSync plus Buffer.alloc because a short read
can leave zero-filled bytes in content and corrupt parsing. Update the read path
in usage-import.ts to read the file descriptor directly with readFileSync(fd,
"utf-8") and assign that result to content, keeping the surrounding logic in
place.

The monthly and yearly date comparison used strict greater-than (>),
so when today is exactly the billing day, the next date was incorrectly
pushed to the following month/year. Use >= to correctly return today
when it matches the billing day.

Fixes the pre-existing test failure: 'handleNotify dry-run shows upcoming bills'
@nazozokc
nazozokc merged commit 4a778fb into main Jul 2, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant