Skip to content

Commit 980467a

Browse files
LeadGoEngineerPaperclip-Paperclip
andcommitted
feat(phase3a): wire-capture tooling + redacted Cronometer fixtures
QUA-37 phase 3a: build the recording http.RoundTripper and capture-driver binary, run them against a real Cronometer account, and commit the redacted wire fixtures + WIRE_SHAPES.md / CAPTURE.md so the clean-room client (phase 3b) has a fixture-backed implementation target. What changed: - tools/wirecapture/ — separate Go module so importing GPL-2.0 gocronometer here does not contaminate the main module's license claim. Recording transport + capture binary are fresh-authored under MIT; no gocronometer source consulted (only its public API surface via go doc, plus this repo's own MIT call sites). - internal/cronoclient/testdata/cronometer/ — 14 redacted exchange metadata files (one per HTTP call in a full session: login + 5 generateAuthorizationToken calls + 5 /export GETs + logout). Cookie values, anti-CSRF tokens, per-session auth tokens, the password POST body, the username, and all real account data are stripped. Only wire metadata (URL, headers, status, body length) survives. - internal/cronoclient/testdata/cronometer/WIRE_SHAPES.md — synthesized wire shapes for every endpoint, with placeholders for sensitive fields and explicit TODOs for behaviours not yet captured (long windows, auth failure shapes, 2FA). - internal/cronoclient/testdata/cronometer/CAPTURE.md — playbook for re-capturing when Cronometer rotates the GWT permutation hash or we need a previously-uncaptured failure mode. - .gitignore — block cronometer-capture/ and tools/wirecapture/captures/ so unredacted dumps never get committed. Phase 3b (the clean-room cronoapi package + go.mod swap) lands as a follow-up PR on this base. Refs: docs/cronometer-protocol.md (phase 1 spec) Co-Authored-By: Paperclip <[email protected]>
1 parent 5e09220 commit 980467a

23 files changed

Lines changed: 1650 additions & 0 deletions

.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,12 @@ crono-export
3939
crono-export-linux-amd64
4040
*.zip
4141
*.tar.gz
42+
43+
# Wire-capture dumps from tools/wirecapture/ — contain raw cookies,
44+
# anti-CSRF tokens, password POST bodies, and real account data. Never
45+
# commit. Redacted fixtures live in
46+
# internal/cronoclient/testdata/cronometer/ and are committed.
47+
cronometer-capture/
48+
tools/wirecapture/captures/
49+
tools/wirecapture/wirecapture
50+
*.unredacted.json
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"seq": 1,
3+
"label": "login_get",
4+
"reqMethod": "GET",
5+
"reqUrl": "https://cronometer.com/login/",
6+
"reqHeader": {},
7+
"reqBodySummary": null,
8+
"respStatus": 200,
9+
"respHeader": {
10+
"Content-Type": [
11+
"text/html;charset=UTF-8"
12+
],
13+
"Date": [
14+
"Tue, 12 May 2026 02:18:14 GMT"
15+
],
16+
"Referrer-Policy": [
17+
"strict-origin-when-cross-origin"
18+
],
19+
"Set-Cookie": [
20+
"<REDACTED-COOKIE-NAME>=<REDACTED-VALUE>; Expires=Tue, 19 May 2026 02:18:14 GMT; Path=/",
21+
"<REDACTED-COOKIE-NAME>=<REDACTED-VALUE>; Expires=Tue, 19 May 2026 02:18:14 GMT; Path=/; SameSite=None; Secure",
22+
"<REDACTED-COOKIE-NAME>=<REDACTED-VALUE>; Path=/; Secure; HttpOnly",
23+
"<REDACTED-COOKIE-NAME>=<REDACTED-VALUE>; Max-Age=172800; Expires=Thu, 14 May 2026 02:18:14 GMT; Path=/; Secure; HttpOnly"
24+
],
25+
"Strict-Transport-Security": [
26+
"max-age=63072000; includeSubDomains; preload"
27+
],
28+
"Vary": [
29+
"accept-encoding"
30+
],
31+
"X-Content-Type-Options": [
32+
"nosniff"
33+
],
34+
"X-Frame-Options": [
35+
"sameorigin"
36+
],
37+
"X-Xss-Protection": [
38+
"1; mode=block"
39+
]
40+
},
41+
"respBodySummary": {
42+
"kind": "text",
43+
"len": 683935,
44+
"note": "Body elided (contains real account data + tokens). See WIRE_SHAPES.md for the synthesized response shape for this endpoint."
45+
},
46+
"durationMsApprox": 105
47+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
{
2+
"seq": 2,
3+
"label": "login_post",
4+
"reqMethod": "POST",
5+
"reqUrl": "https://cronometer.com/login",
6+
"reqHeader": {
7+
"Content-Type": [
8+
"application/x-www-form-urlencoded"
9+
],
10+
"Cookie": [
11+
"<REDACTED-COOKIE-VALUES>"
12+
]
13+
},
14+
"reqBodySummary": {
15+
"kind": "text",
16+
"len": 104,
17+
"note": "Body elided. See WIRE_SHAPES.md for the synthesized request shape for this endpoint."
18+
},
19+
"respStatus": 200,
20+
"respHeader": {
21+
"Content-Type": [
22+
"application/json;charset=UTF-8"
23+
],
24+
"Date": [
25+
"Tue, 12 May 2026 02:18:14 GMT"
26+
],
27+
"Referrer-Policy": [
28+
"strict-origin-when-cross-origin"
29+
],
30+
"Set-Cookie": [
31+
"<REDACTED-COOKIE-NAME>=<REDACTED-VALUE>; Expires=Tue, 19 May 2026 02:18:14 GMT; Path=/",
32+
"<REDACTED-COOKIE-NAME>=<REDACTED-VALUE>; Expires=Tue, 19 May 2026 02:18:14 GMT; Path=/; SameSite=None; Secure",
33+
"<REDACTED-COOKIE-NAME>=<REDACTED-VALUE>; Max-Age=1209600; Expires=Tue, 26 May 2026 02:18:14 GMT; Path=/; Secure; HttpOnly; SameSite=Lax",
34+
"<REDACTED-COOKIE-NAME>=<REDACTED-VALUE>; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:10 GMT; Path=/; Secure; HttpOnly"
35+
],
36+
"Strict-Transport-Security": [
37+
"max-age=63072000; includeSubDomains; preload"
38+
],
39+
"Vary": [
40+
"accept-encoding"
41+
],
42+
"X-Content-Type-Options": [
43+
"nosniff"
44+
],
45+
"X-Frame-Options": [
46+
"sameorigin"
47+
],
48+
"X-Xss-Protection": [
49+
"1; mode=block"
50+
]
51+
},
52+
"respBodySummary": {
53+
"kind": "text",
54+
"len": 39,
55+
"note": "Body elided (contains real account data + tokens). See WIRE_SHAPES.md for the synthesized response shape for this endpoint."
56+
},
57+
"durationMsApprox": 138
58+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
{
2+
"seq": 3,
3+
"label": "gwt_authenticate",
4+
"reqMethod": "POST",
5+
"reqUrl": "https://cronometer.com/cronometer/app",
6+
"reqHeader": {
7+
"Content-Type": [
8+
"text/x-gwt-rpc; charset=UTF-8"
9+
],
10+
"Cookie": [
11+
"<REDACTED-COOKIE-VALUES>"
12+
],
13+
"X-Gwt-Module-Base": [
14+
"https://cronometer.com/cronometer/"
15+
],
16+
"X-Gwt-Permutation": [
17+
"7B121DC5483BF272B1BC1916DA9FA963"
18+
]
19+
},
20+
"reqBodySummary": {
21+
"kind": "text",
22+
"len": 179,
23+
"note": "Body elided. See WIRE_SHAPES.md for the synthesized request shape for this endpoint."
24+
},
25+
"respStatus": 200,
26+
"respHeader": {
27+
"Content-Disposition": [
28+
"attachment"
29+
],
30+
"Content-Type": [
31+
"application/json;charset=utf-8"
32+
],
33+
"Date": [
34+
"Tue, 12 May 2026 02:18:14 GMT"
35+
],
36+
"Referrer-Policy": [
37+
"strict-origin-when-cross-origin"
38+
],
39+
"Set-Cookie": [
40+
"<REDACTED-COOKIE-NAME>=<REDACTED-VALUE>; Expires=Tue, 19 May 2026 02:18:14 GMT; Path=/",
41+
"<REDACTED-COOKIE-NAME>=<REDACTED-VALUE>; Expires=Tue, 19 May 2026 02:18:14 GMT; Path=/; SameSite=None; Secure",
42+
"<REDACTED-COOKIE-NAME>=<REDACTED-VALUE>; Max-Age=1209600; Expires=Tue, 26 May 2026 02:18:14 GMT; Path=/; Secure; HttpOnly; SameSite=Lax"
43+
],
44+
"Strict-Transport-Security": [
45+
"max-age=63072000; includeSubDomains; preload"
46+
],
47+
"X-Content-Type-Options": [
48+
"nosniff"
49+
],
50+
"X-Frame-Options": [
51+
"sameorigin"
52+
],
53+
"X-Xss-Protection": [
54+
"1; mode=block"
55+
]
56+
},
57+
"respBodySummary": {
58+
"kind": "text",
59+
"len": 8294,
60+
"note": "Body elided (contains real account data + tokens). See WIRE_SHAPES.md for the synthesized response shape for this endpoint."
61+
},
62+
"durationMsApprox": 54
63+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{
2+
"seq": 4,
3+
"label": "gwt_generate_auth_token_servings",
4+
"reqMethod": "POST",
5+
"reqUrl": "https://cronometer.com/cronometer/app",
6+
"reqHeader": {
7+
"Content-Type": [
8+
"text/x-gwt-rpc; charset=UTF-8"
9+
],
10+
"Cookie": [
11+
"<REDACTED-COOKIE-VALUES>"
12+
],
13+
"X-Gwt-Module-Base": [
14+
"https://cronometer.com/cronometer/"
15+
],
16+
"X-Gwt-Permutation": [
17+
"7B121DC5483BF272B1BC1916DA9FA963"
18+
]
19+
},
20+
"reqBodySummary": {
21+
"kind": "text",
22+
"len": 294,
23+
"note": "Body elided. See WIRE_SHAPES.md for the synthesized request shape for this endpoint."
24+
},
25+
"respStatus": 200,
26+
"respHeader": {
27+
"Content-Disposition": [
28+
"attachment"
29+
],
30+
"Content-Length": [
31+
"48"
32+
],
33+
"Content-Type": [
34+
"application/json;charset=utf-8"
35+
],
36+
"Date": [
37+
"Tue, 12 May 2026 02:18:14 GMT"
38+
],
39+
"Referrer-Policy": [
40+
"strict-origin-when-cross-origin"
41+
],
42+
"Set-Cookie": [
43+
"<REDACTED-COOKIE-NAME>=<REDACTED-VALUE>; Expires=Tue, 19 May 2026 02:18:14 GMT; Path=/",
44+
"<REDACTED-COOKIE-NAME>=<REDACTED-VALUE>; Expires=Tue, 19 May 2026 02:18:14 GMT; Path=/; SameSite=None; Secure"
45+
],
46+
"Strict-Transport-Security": [
47+
"max-age=63072000; includeSubDomains; preload"
48+
],
49+
"X-Content-Type-Options": [
50+
"nosniff"
51+
],
52+
"X-Frame-Options": [
53+
"sameorigin"
54+
],
55+
"X-Xss-Protection": [
56+
"1; mode=block"
57+
]
58+
},
59+
"respBodySummary": {
60+
"kind": "text",
61+
"len": 48,
62+
"note": "Body elided (contains real account data + tokens). See WIRE_SHAPES.md for the synthesized response shape for this endpoint."
63+
},
64+
"durationMsApprox": 25
65+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
{
2+
"seq": 5,
3+
"label": "export_servings",
4+
"reqMethod": "GET",
5+
"reqUrl": "https://cronometer.com/export?end=2026-05-11&generate=servings&nonce=%3CREDACTED%3E&start=2026-05-04",
6+
"reqHeader": {
7+
"Cookie": [
8+
"<REDACTED-COOKIE-VALUES>"
9+
],
10+
"Sec-Fetch-Dest": [
11+
"document"
12+
],
13+
"Sec-Fetch-Mode": [
14+
"navigate"
15+
],
16+
"Sec-Fetch-Site": [
17+
"same-origin"
18+
]
19+
},
20+
"reqBodySummary": null,
21+
"respStatus": 200,
22+
"respHeader": {
23+
"Content-Disposition": [
24+
"attachment; filename=\"servings.csv\""
25+
],
26+
"Content-Length": [
27+
"6507"
28+
],
29+
"Content-Type": [
30+
"text/csv"
31+
],
32+
"Date": [
33+
"Tue, 12 May 2026 02:18:14 GMT"
34+
],
35+
"Referrer-Policy": [
36+
"strict-origin-when-cross-origin"
37+
],
38+
"Set-Cookie": [
39+
"<REDACTED-COOKIE-NAME>=<REDACTED-VALUE>; Expires=Tue, 19 May 2026 02:18:14 GMT; Path=/",
40+
"<REDACTED-COOKIE-NAME>=<REDACTED-VALUE>; Expires=Tue, 19 May 2026 02:18:14 GMT; Path=/; SameSite=None; Secure"
41+
],
42+
"Strict-Transport-Security": [
43+
"max-age=63072000; includeSubDomains; preload"
44+
],
45+
"X-Content-Type-Options": [
46+
"nosniff"
47+
],
48+
"X-Frame-Options": [
49+
"sameorigin"
50+
],
51+
"X-Xss-Protection": [
52+
"1; mode=block"
53+
]
54+
},
55+
"respBodySummary": {
56+
"kind": "text",
57+
"len": 6500,
58+
"note": "Body elided (contains real account data + tokens). See WIRE_SHAPES.md for the synthesized response shape for this endpoint."
59+
},
60+
"durationMsApprox": 59
61+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{
2+
"seq": 6,
3+
"label": "gwt_generate_auth_token_exercises",
4+
"reqMethod": "POST",
5+
"reqUrl": "https://cronometer.com/cronometer/app",
6+
"reqHeader": {
7+
"Content-Type": [
8+
"text/x-gwt-rpc; charset=UTF-8"
9+
],
10+
"Cookie": [
11+
"<REDACTED-COOKIE-VALUES>"
12+
],
13+
"X-Gwt-Module-Base": [
14+
"https://cronometer.com/cronometer/"
15+
],
16+
"X-Gwt-Permutation": [
17+
"7B121DC5483BF272B1BC1916DA9FA963"
18+
]
19+
},
20+
"reqBodySummary": {
21+
"kind": "text",
22+
"len": 294,
23+
"note": "Body elided. See WIRE_SHAPES.md for the synthesized request shape for this endpoint."
24+
},
25+
"respStatus": 200,
26+
"respHeader": {
27+
"Content-Disposition": [
28+
"attachment"
29+
],
30+
"Content-Length": [
31+
"48"
32+
],
33+
"Content-Type": [
34+
"application/json;charset=utf-8"
35+
],
36+
"Date": [
37+
"Tue, 12 May 2026 02:18:14 GMT"
38+
],
39+
"Referrer-Policy": [
40+
"strict-origin-when-cross-origin"
41+
],
42+
"Set-Cookie": [
43+
"<REDACTED-COOKIE-NAME>=<REDACTED-VALUE>; Expires=Tue, 19 May 2026 02:18:14 GMT; Path=/",
44+
"<REDACTED-COOKIE-NAME>=<REDACTED-VALUE>; Expires=Tue, 19 May 2026 02:18:14 GMT; Path=/; SameSite=None; Secure"
45+
],
46+
"Strict-Transport-Security": [
47+
"max-age=63072000; includeSubDomains; preload"
48+
],
49+
"X-Content-Type-Options": [
50+
"nosniff"
51+
],
52+
"X-Frame-Options": [
53+
"sameorigin"
54+
],
55+
"X-Xss-Protection": [
56+
"1; mode=block"
57+
]
58+
},
59+
"respBodySummary": {
60+
"kind": "text",
61+
"len": 48,
62+
"note": "Body elided (contains real account data + tokens). See WIRE_SHAPES.md for the synthesized response shape for this endpoint."
63+
},
64+
"durationMsApprox": 26
65+
}

0 commit comments

Comments
 (0)