Skip to content

P0: isolate Sheets caches and recover after entitlement upgrades - #33

Merged
karlwaldman merged 2 commits into
mainfrom
codex/sheets-cache-scope-isolation
Aug 11, 2026
Merged

P0: isolate Sheets caches and recover after entitlement upgrades#33
karlwaldman merged 2 commits into
mainfrom
codex/sheets-cache-scope-isolation

Conversation

@karlwaldman

@karlwaldman karlwaldman commented Aug 11, 2026

Copy link
Copy Markdown
Member

Summary

  • select DocumentCache only for a confirmed document-scoped credential generation; otherwise isolate in UserCache
  • bind every user-cache namespace to an opaque active-spreadsheet hash, without API keys or raw spreadsheet IDs
  • rotate the active cache generation after a successful Test Connection so endpoint-wide 403 blocks recover after upgrade
  • fail closed when the active scope generation cannot be persisted and retire unsafe pre-v6 unscoped credential fallback
  • hold legacy monthly quota rendering until the canonical limit/window contract is deployed
  • bump the replacement candidate to 1.3.1 and record that immutable version 12 / runtime 1.3.0 was never published
  • replace the workflow secret scan that masked a missing rg binary with a dependency-free, tested Node scanner

Red / green proof

RED before runtime fix:

  • same-user/two-sheet response isolation returned 81.78 instead of 92.35
  • same-user/two-sheet entitlement isolation returned cached #UPGRADE_REQUIRED
  • document generation write failure incorrectly returned Test Connection success
  • an unscoped legacy key authorized another spreadsheet and attempted a request
  • legacy monthly fields rendered 50 as a windowless limit

GREEN after fix:

  • npm run validate: 88/88 tests pass
  • Apps Script structure/bindings valid
  • deploy package valid
  • Marketplace assets valid
  • five generated portfolio packages valid
  • dependency-free secret scan passes and has red/green fixture coverage
  • git diff --check passes

Release gate

The public Marketplace configuration remains pinned to immutable Apps Script version 11 (runtime 1.2.2). Immutable version 12 (runtime 1.3.0) was created but failed this cache-isolation review and was never published. Do not cut/select/publish the 1.3.1 replacement until this exact head is hosted-green, independently reviewed, and passes an installed-account disposable-spreadsheet formula/recovery smoke.

Summary by CodeRabbit

  • Bug Fixes

    • Improved cache isolation across spreadsheets and users.
    • Prevented legacy unscoped API keys from authorizing other spreadsheets.
    • Added safer cache refresh handling and clearer retry behavior.
    • Connection tests now refresh entitlement information reliably.
    • User information consistently reports quota fields, including unavailable values.
  • Security

    • Added automated scanning for accidentally committed credentials.
  • Documentation

    • Updated release, deployment, Marketplace, and upgrade guidance for version 1.3.1, including legacy API-key migration steps.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

Next review available in: 39 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

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 Plus

Run ID: c277def9-d09b-40f6-9caf-568868a424b8

📥 Commits

Reviewing files that changed from the base of the PR and between 91c9a46 and cbf85a3.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (9)
  • .github/workflows/apps-script-release.yml
  • .github/workflows/validate.yml
  • Code.gs
  • TEST_RESULTS.md
  • package.json
  • scripts/scan-secrets.js
  • test/public-claims.test.js
  • test/runtime.test.js
  • test/secret-scan.test.js
📝 Walkthrough

Walkthrough

Runtime 1.3.1 adds spreadsheet-isolated cache handling, removes legacy unscoped API-key fallback, expands quota responses, updates release records, and adds a Node.js secret scanner to test and validation workflows.

Changes

Runtime cache isolation and release

Layer / File(s) Summary
Cache context and API-key scope
Code.gs
Cache generations now track document and user scopes. API-key operations clear cached scope state, and spreadsheet lookups no longer use unscoped fallback keys.
Scoped cache storage and keys
Code.gs
Cache operations select document or user storage, use spreadsheet-isolated user keys, and handle unavailable document caches.
Connection invalidation and quota response
Code.gs, test/runtime.test.js
Successful connection tests invalidate entitlement caches. getUserInfo always returns quota fields, using null when values are unavailable.
Runtime 1.3.1 release state
DEPLOYMENT_GUIDE.md, MARKETPLACE_LISTING.md, OAUTH_VERIFICATION.md, README.md, docs/index.html, package.json, test/public-claims.test.js
Release records and assertions identify runtime 1.3.1 and immutable version 12, which was not published. Documentation describes resaving legacy keys per spreadsheet.

Repository secret scanning

Layer / File(s) Summary
Secret scanner implementation and integration
scripts/scan-secrets.js, scripts/scan-secrets.sh, package.json, test/validate_code.js
A Node.js scanner recursively checks text files, excludes configured paths, reports sorted findings, and runs through test and validation commands.
Secret scanner tests
test/secret-scan.test.js
Tests verify credential detection, output redaction, safe-file handling, and excluded paths.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ConnectionTest
  participant CacheContext
  participant CacheStore
  participant ExternalAPI
  ConnectionTest->>ExternalAPI: validate API key
  ExternalAPI-->>ConnectionTest: connection response
  ConnectionTest->>CacheContext: invalidate active generation
  CacheContext->>CacheStore: refresh scoped generation
  CacheStore-->>CacheContext: refreshed scope
  CacheContext-->>ConnectionTest: invalidation result
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary changes: isolating Sheets caches and improving recovery after entitlement upgrades.
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 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/sheets-cache-scope-isolation

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

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
Code.gs (1)

781-790: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Separate the two digests in the spreadsheet namespace.

stableCacheDigest_ returns base36 of a 32-bit value, so its length varies from 1 to 7 characters. Concatenating two variable-length digests is ambiguous. Two different spreadsheet IDs can produce digest pairs such as ("ab","cde") and ("abc","de"), and both yield abcde. A collision makes two spreadsheets of the same user share cached responses and entitlement blocks, which defeats the isolation this change adds.

Add a separator so the namespace keeps the full entropy of both digests.

♻️ Proposed fix
     const spreadsheetHash =
-      `${stableCacheDigest_(spreadsheetId)}${stableCacheDigest_(`sheet:${spreadsheetId}`)}`;
+      `${stableCacheDigest_(spreadsheetId)}x${stableCacheDigest_(`sheet:${spreadsheetId}`)}`;
🤖 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 `@Code.gs` around lines 781 - 790, Update namespacedCacheKey_ so the two
stableCacheDigest_ values in the user-scoped spreadsheet hash are joined with an
unambiguous separator rather than concatenated directly. Preserve the existing
digest inputs and cache-key structure while ensuring different digest pairs
cannot produce the same combined namespace.
scripts/scan-secrets.js (1)

6-12: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Bare 64-hex-char pattern lacks context, unlike the other patterns.

PATTERNS[0] at Line 7 matches any 64-character hex string with no surrounding keyword or prefix requirement. Every other pattern in this list (Lines 8-11) requires a recognizable prefix (sk-, AIza, gh[pousr]_) or keyword (api_key, access_token, secret). A bare hex-64 match will also fire on unrelated content such as SHA-256 checksums, git blob hashes, or other generated hash identifiers, none of which are secrets.

The PR objectives introduce an "opaque active-spreadsheet hash" for cache namespacing. If that hash format is ever documented with a literal example value in a scanned text file, this pattern will flag it as a false positive and break the validate pipeline.

Add a keyword/context requirement to this pattern (similar to Line 11), or move it into a lower-confidence secondary check that only fires alongside a nearby keyword.

Run the following to confirm no tracked file already contains an unrelated bare 64-hex-char string that would trip this pattern:

#!/bin/bash
rg -nP '\b[0-9a-fA-F]{64}\b' --hidden \
  -g '!node_modules' -g '!.git' -g '!package-lock.json' \
  -g '!scripts/scan-secrets.js' -g '!scripts/scan-secrets.sh' -g '!test/secret-scan.test.js' .
🤖 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 `@scripts/scan-secrets.js` around lines 6 - 12, The first entry in PATTERNS
matches arbitrary 64-character hexadecimal strings, causing hash-like
identifiers to be treated as secrets. Replace that bare pattern with a
context-aware expression requiring a nearby secret-related keyword, or move it
into a secondary check that only reports matches when such context is present;
preserve the existing detection behavior for the prefixed and keyword-based
patterns.
🤖 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 `@test/public-claims.test.js`:
- Around line 63-75: Update the assertions in the public claims test to validate
each release relationship within its source document: assert deployment ties
Apps Script version 12 to release 1.3.0 and its non-publication status, and
assert listing ties version 11 to release 1.2.2. Remove the cross-document
records check and keep the existing current-version assertions.

---

Nitpick comments:
In `@Code.gs`:
- Around line 781-790: Update namespacedCacheKey_ so the two stableCacheDigest_
values in the user-scoped spreadsheet hash are joined with an unambiguous
separator rather than concatenated directly. Preserve the existing digest inputs
and cache-key structure while ensuring different digest pairs cannot produce the
same combined namespace.

In `@scripts/scan-secrets.js`:
- Around line 6-12: The first entry in PATTERNS matches arbitrary 64-character
hexadecimal strings, causing hash-like identifiers to be treated as secrets.
Replace that bare pattern with a context-aware expression requiring a nearby
secret-related keyword, or move it into a secondary check that only reports
matches when such context is present; preserve the existing detection behavior
for the prefixed and keyword-based patterns.
🪄 Autofix

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 Plus

Run ID: 73850c2a-372e-4c91-b03b-b5a87e3d34b0

📥 Commits

Reviewing files that changed from the base of the PR and between 6b31e14 and 91c9a46.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (13)
  • Code.gs
  • DEPLOYMENT_GUIDE.md
  • MARKETPLACE_LISTING.md
  • OAUTH_VERIFICATION.md
  • README.md
  • docs/index.html
  • package.json
  • scripts/scan-secrets.js
  • scripts/scan-secrets.sh
  • test/public-claims.test.js
  • test/runtime.test.js
  • test/secret-scan.test.js
  • test/validate_code.js

Comment thread test/public-claims.test.js Outdated
@karlwaldman

Copy link
Copy Markdown
Member Author

Follow-up cbf85a3 addresses the final review notes and release dependency gate:

  • user-cache digest components now use an explicit _ boundary, with a regression proving ab+cde cannot collide with abc+de;
  • release records are asserted per file and preserve the 12/1.3.0 unpublished vs 11/1.2.2 public pairings;
  • the bare 64-hex detector is intentionally retained because production OilPriceAPI keys use exactly that format; a regression proves detection without secret disclosure, while known checksum-bearing paths remain explicitly allowlisted;
  • both validation and production release workflows now block on npm audit --audit-level=moderate;
  • exact clean install is audit-zero, clasp remains 3.3.0, and full validation is 91/91 plus deployment/assets/portfolio/secret verification.

@karlwaldman
karlwaldman merged commit 8a52373 into main Aug 11, 2026
2 checks passed
@karlwaldman
karlwaldman deleted the codex/sheets-cache-scope-isolation branch August 11, 2026 12:51
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