Repository navigation
OAUTH-001A2K — Bound Strava OAuth token JSON responses before parsing #614
Description
Outcome
The authorization-exchange and token-refresh Functions must fully consume successful Strava token replies through one bounded native response-body reader before UTF-8 decoding, JSON parsing, projection, or persistence. Missing or dishonest response headers must not bypass the actual streamed-byte ceiling.
Parent: #88
Split rationale
A pre-implementation runtime review split the original four-response proposal. OAuth token replies have tightly bounded accepted fields and can use one reviewable 64 KiB application ceiling. Activities may contain large provider summary fields with no published response-size guarantee, and activities/statistics also need explicit sibling-body cleanup because their fetches start together. That separate adoption will be named and reviewed after this child; it is not silently included here.
Problem
Exact main 644d9f1fe3e6c06427412fd3ae37973dbbfeca20 calls Response.json() without a body limit after confirmed HTTP success at the authorization exchange and refresh paths in functions/strava.js. A provider, intermediary, decompression expansion, or response drift can therefore make the Function buffer and parse an unbounded token reply before the existing strict field projection rejects it.
Invariant and allowed transitions
- Use one named
65,536-byte (64 KiB) application ceiling for the content-decoded native Node 20Response.bodystream at authorization exchange and token refresh. - Require the exact admitted native Response, captured native headers/body getters, and a genuine readable body. Do not retain a production
.json(),.text(),.arrayBuffer(),.bytes(), clone, tee, or plain-test-object fallback. - A missing
Content-Lengthis allowed. If present, it must be normalized ASCII decimal digits representing a safe integer no greater than the ceiling. A malformed or declared-over-limit value fails before body reads. A lower or dishonest value never admits a body; actual streamed bytes remain authoritative. - Count every genuine
Uint8Arraychunk byte before copying. Exact limit succeeds. At byte65,537, stop without copying the overflowing chunk and begin best-effort cancellation with no raw reason. - Bound reads to at most 4,096 chunks, including zero-byte chunks, so an immediately resolving zero-byte stream cannot loop forever.
- Cancellation and lock release are guarded, never logged, never awaited indefinitely, and cannot replace the fixed endpoint result.
- Decode only completely collected bounded bytes with fatal UTF-8 handling, then parse with captured JSON parsing. Empty/null/locked/already-used bodies, malformed chunks/read records, stream failure, malformed UTF-8/JSON, exceptional native operations, chunk overflow, and byte overflow use the existing endpoint-specific fixed
unavailableresult without provider details. - A valid bounded JSON value still passes through the current strict projector. A bounded but invalid authorization/refresh schema keeps the current endpoint-specific
internalresult. - HTTP non-success keeps its existing classification and no-body-read behavior.
- Authorization state remains one-use before provider exchange. A token-body failure writes no secret or connection. A refresh-body failure preserves the previously stored secret, performs no CAS write, and starts no activity/statistics bearer request.
The ceiling covers content-decoded chunks consumed by this code. It does not bound headers, compressed wire bytes, Undici internal buffering, the allocation of one provider-supplied chunk before inspection, parsed-object heap amplification, CPU, or time.
Scope
functions/strava.js: one dependency-free bounded native JSON reader; authorization exchange and token refresh adoption only; a non-deployed testable projector seam only as required to preserve hostile-value tests.functions/strava.test.js: genuine nativeResponse/ReadableStreamfixtures, trustworthy RED, boundary/integration matrix, compatibility updates, and direct preservation of the existing accessor/Proxy/custom-prototype projector cases.SECURITY.md: one separately named OAUTH-001A2K current-source/residual row.
No activities/statistics response adoption, sibling-response cancellation, browser/client, callback, disconnect/revoke, Firestore schema or Rules, workflow, package, provider configuration, Firebase deployment, production data, timeout/AbortSignal policy, IAM/encryption, scope/account policy, reconciliation, or live change is included.
Trustworthy RED and required coverage
On exact base 644d9f1fe3e6c06427412fd3ae37973dbbfeca20, create a genuine successful native authorization Response with no Content-Length. Its ReadableStream must emit a valid exchange JSON document padded with trailing JSON whitespace: first 65,536 bytes, then byte 65,537. Require fixed authorization unavailable, consumed one-use state, zero secret/connection writes, no logs, and one best-effort cancellation. The old source must instead parse the complete valid document, persist both records, return success, and never cancel.
Cover:
- one byte below, exact limit, and one byte over;
- one-chunk and multi-chunk overflow without copying the offending chunk;
- absent, truthful, over-limit, underreported, and exact-but-dishonest
Content-Length, plus malformed/negative/unsafe values; - split multibyte UTF-8, BOM parity, byte-not-character counting, and malformed UTF-8;
- empty, null, locked, and already-used body;
- non-
Uint8Arraychunks, malformed reader results, stream read failure, cancellation throw/rejection/pending promise, lock-release failure, and the 4,097th zero-byte chunk; - a pending final chunk with no early parse, persistence, or result;
- malformed JSON without provider bytes/errors/lengths/headers/URLs in error, logs, or test artifacts;
- an overridden native
.json()and a plain{ ok: true, json }mock cannot bypass the stream boundary; - non-success no-body-read compatibility;
- bounded invalid-schema
internalcompatibility; - exact-bound and ordinary bounded authorization persistence/return;
- refresh overflow preserves the old secret/CAS state and starts no activity/statistics request;
- ordinary bounded refresh CAS persistence;
- frozen projection/output and fixed error compatibility.
Do not delete or weaken existing accessor, Proxy, custom-prototype, field-bound, logging, state-consumption, lifecycle, CAS, or response-status coverage. Genuine JSON bytes cannot construct every hostile JavaScript value, so preserve those projector defenses through a small reviewable direct seam that functions/index.js does not deploy.
Dependencies and residuals
- OAUTH-001A2J — Safely admit Strava HTTP success markers #604 / OAUTH-001A2J merged exact HTTP-success admission.
- OAUTH-001C1E — Validate Strava authorization-exchange success before writes #281 merged exchange projection/persistence.
- OAUTH-001A1H — Refuse stale Strava refresh persistence #606 merged refresh CAS persistence.
- OAUTH-001C1J — Require exact Strava exchange client confirmation #612 is merged, closed, explicitly claim-released, and its documentation hunk is excluded.
A later separately named #88 child owns activities/statistics limits, their evidence-based caps, and targeted cleanup of an unread successful statistics body when activity admission fails. Request/connect/read timeouts, AbortSignal policy, compressed-wire limits, provider guarantees, lost-acknowledgement and rotated-token reconciliation, scopes/account binding, revoke/audit, IAM/encryption, deployment, configuration, and live proof also remain open under #88. Never retry a failed token response blindly: the provider may already have issued or rotated credentials.
Acceptance criteria
- The exact-base RED is reproducible with a test-only binary diff hash and fails for the intended unbounded-parse reason.
- Neither current successful token-response path invokes
.json(),.text(),.arrayBuffer(),.bytes(), clone, or tee. - Exact native streamed bytes enforce the limit even with absent or false-low headers.
- Every failure is fixed, redacted, side-effect-bounded, and endpoint-compatible.
- Existing impossible-JSON projector defenses remain directly tested rather than silently deleted.
- Focused checks, Functions lint/full tests, and relevant repository suites pass.
- Dependency audits are recorded without forced upgrades.
- Exact-head security, availability/resource-lifecycle, privacy/logging, test-quality, and backup-officer reviews find no STOP.
Officer impact: None — this server-only source guard preserves the existing member-visible success and fixed failure states while bounding OAuth token-response memory before parsing.
Officer documentation: None — no officer task, page structure, data route, permission, account ownership, deployment procedure, or visible wording changes. Existing source-only Strava guidance remains current.
Deployment evidence: At claim time, no source change, test result, merge, Firebase deployment, website publication, runmprc.com verification, Strava/provider action, production-data action, or live-behavior verification is claimed. Final evidence must record every surface separately.
Activity
[-]OAUTH-001A2K — Bound Strava provider JSON responses before parsing[/-][+]OAUTH-001A2K — Bound Strava OAuth token JSON responses before parsing[/+]daliu commentedon Aug 2, 2026
CLAIMED — exact scope after pre-implementation split
Claimant: daliu / Codex
Exact base:
644d9f1fe3e6c06427412fd3ae37973dbbfeca20Parent: #88
Owned paths/outcome:
functions/strava.js: one bounded native JSON reader plus authorization exchange and refresh adoption only.functions/strava.test.js: native byte-stream RED/matrix, endpoint integration, and preserved hostile projector tests through a non-deployed test seam.SECURITY.md: one separately named OAUTH-001A2K row only.The earlier all-four concept was split before implementation after independent runtime review found different activity-size compatibility and sibling-body lifecycle concerns. Activities/statistics limits and sibling cancellation are not owned by this claim. Also excluded: client/callback/disconnect, officer docs, packages/workflows, Rules/schema/migration, timeouts/AbortSignal, Firebase/provider/config/data/live action.
Required RED: exact-base native authorization Response emits valid JSON padded to 65,537 bytes without Content-Length; old source succeeds/writes and never cancels, while the fix must return fixed unavailable after the 65,536-byte ceiling, consume state once, write nothing, log nothing, and best-effort cancel.
No source/test/deployment/live completion is claimed yet.
daliu commentedon Aug 2, 2026
Trustworthy exact-base RED
644d9f1fe3e6c06427412fd3ae37973dbbfeca20.9e9d94b7586b04e73882f763c206e97bbe5df3821b2850c793488880256a3e72.v20.20.2.npm --prefix functions run test:run -- --runInBand strava.test.js -t OAUTH-001A2K.The genuine native successful
Responsehas noContent-Lengthand streams one valid exchange JSON document padded with JSON whitespace to exactly 65,537 bytes in chunks of 65,536 plus 1. Its own test.json()override delegates to nativeResponse.prototype.textandJSON.parse, so the old production call must consume the real body; the corrected reader must ignore this override entirely.Old-source witness:
{ ok: true, athleteId: 123456 };The required result is the existing fixed authorization
unavailable, zero token/connection writes, consumed state, no logs, and exactly one best-effort cancellation at byte 65,537. No runtime or documentation change was present for this RED.daliu commentedon Aug 2, 2026
RED CORRECTION / STRENGTHENED EXACT-BASE PROOF
Exact base:
644d9f1fe3e6c06427412fd3ae37973dbbfeca20.A fresh detached worktree received only one minimal integration-test patch. Binary test-patch SHA-256:
94dc7e995384a8398d918e688fb2690920d20a5d541a55a52c8ff399eec6f813.Node 20 focused result: 1 failed, 721 skipped, 722 total. The genuine native
Responsehad noContent-Length; its overriddenjsondelegated to nativeResponse.prototype.textso the base implementation consumed streamed chunks of 65,536 and 1 bytes.Observed on the exact base:
chunkReadCount: 2cancelCount: 0{ ok: true, athleteId: 123456 }Required by this child:
chunkReadCount: 2cancelCount: 1unavailableauthorization resultThis supersedes the earlier RED witness by explicitly proving that both chunks were consumed. The detached worktree was removed after the run. No provider, Firebase project, production data, or live surface was contacted.
daliu commentedon Aug 2, 2026
COMPLETE / CLOSED / CLAIM RELEASED
Outcome: authorization-exchange and token-refresh replies are now bounded to 65,536 content-decoded bytes and 4,096 chunks before UTF-8 decoding, JSON parsing, strict projection, or persistence. Actual streamed bytes remain authoritative when
Content-Lengthis absent or false-low. Failures retain fixed endpoint outcomes and the documented state/write/CAS boundaries.Release evidence:
165934dee32ecc1daaafb596bab16160ff52e6b0; tree3683a9ccb982b6a2bb1c67a8e0fd27a86f38bcc9; binary base-to-head diff SHA-2560ca738c94f25e8ba7507d28e40b5fc3410f32774288afba12d0721540c1c2433.644d9f1fe3e6c06427412fd3ae37973dbbfeca20, minimal test-only patch SHA-25694dc7e995384a8398d918e688fb2690920d20a5d541a55a52c8ff399eec6f813; old source read both 65,536 + 1-byte chunks, returned success, made two writes, and did not cancel.30740379852: all five GitHub jobs passed. Exact-head Netlify preview6a6f038b9701b00008066000is ready and unpublished; page-change check was neutral because no site file changed.2026-08-02T08:48:24Zas exact mainb94622bd42775d84704d5fc05ee87da81f5a76e0; merge tree exactly matches the reviewed head tree.30740495924: all five jobs passed.Officer impact: None — the existing member-visible success and fixed failure states are preserved.
Officer documentation: None — no officer task, page, visible wording, data route, permission, account ownership, provider configuration, or deployment procedure changed.
SECURITY.mdrecords the source/residual boundary.Deployment evidence:
b94622bd42775d84704d5fc05ee87da81f5a76e0.runmprc.comverified: no; no website revision or public behavior was tested.[].Residuals remain exactly those named in the issue and
SECURITY.md: headers/wire/Undici/one allocated chunk/heap/CPU/timeouts, exchange or rotation lost acknowledgement, activities/statistics limits and sibling cleanup, scope/account policy, reconciliation, audit, IAM/encryption, deployment, provider configuration, production data, and live proof.Issue #614 is complete and closed. The exact claimed paths (
functions/strava.js,functions/strava.test.js, and the namedSECURITY.mdrow) are released. The later activities/statistics child remains unclaimed and must be coordinated separately.COMPLETE / CLOSED / CLAIM RELEASED at
b94622bd42775d84704d5fc05ee87da81f5a76e0.