saas-session-recon

Validate whether a specific SaaS platform's APIs accept browser session cookies for programmatic access. This determines if a Chrome extension can act as an invisible API proxy using the user's existing authenticated session.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "saas-session-recon" with this command: npx skills add inkeep/team-skills/inkeep-team-skills-saas-session-recon

SaaS Session Recon

Validate whether a specific SaaS platform's APIs accept browser session cookies for programmatic access. This determines if a Chrome extension can act as an invisible API proxy using the user's existing authenticated session.

Why this matters: Enterprise SaaS platforms gate OAuth/app installation behind IT admin approval. If the platform's APIs accept session cookies, a Chrome extension service worker can make fetch() calls with credentials: 'include' and the browser attaches cookies automatically — no admin approval needed.

Requirements

Requirement Type If unavailable

Chrome running + user logged into target platform Hard Stop. Ask user to open Chrome and log into the platform.

use-browser skill available (Claude in Chrome extension) Hard Fallback: use eng:browser with local browser mode (Playwright MCP Bridge).

Platform is one of the known platforms Adaptable Proceed with generic discovery workflow (skip platform-specific priors).

Browser Tool Surface

This skill uses use-browser tools. Key tools for recon:

Tool Use for

tabs_context_mcp

Get tab IDs (ALWAYS call first)

navigate(url, tabId)

Go to platform pages

read_page(tabId)

Verify login state, find UI elements

javascript_tool(text, tabId)

Cookie enumeration, fetch() API tests, localStorage/sessionStorage reads

read_network_requests(tabId, urlPattern)

Capture API calls the web app makes

read_console_messages(tabId)

Check for auth errors

computer(action="screenshot")

Visual evidence capture

Critical constraint: All JavaScript passed to javascript_tool MUST use ES5 syntax:

  • Use var not const /let

  • Use function(){} not arrow functions =>

  • Use string concatenation not template literals

  • Use String.fromCharCode(10) not "\n" for newlines

  • For async: store results in window._result , retrieve in a follow-up call

Workflow

Phase 0: Setup and Platform Identification

Identify the target platform from the user's argument. Normalize aliases:

  • "gmail", "google mail", "gcal", "google calendar", "gdrive", "google docs" → Google Workspace

  • "outlook", "teams", "onedrive", "o365", "office 365" → Microsoft 365

  • "jira", "confluence", "atlassian" → Jira/Atlassian

  • Use the canonical name for all others (Slack, GitHub, Linear, Notion, Salesforce, HubSpot, Zendesk)

Load: references/platform-intelligence.md — read the section for the target platform. This contains known cookie names, API endpoints, auth patterns, and CSRF mechanisms from prior research. Treat as "hypotheses to validate," not ground truth.

Get browser context:

  • Call tabs_context_mcp to get current tab IDs

  • Call navigate(platformUrl, tabId) to go to the platform's web app

Verify login state:

  • Call read_page(tabId) — look for authenticated UI elements (profile avatar, username, dashboard)

  • If not logged in, tell the user: "Please log into [platform] in Chrome, then tell me when you're ready."

  • Take a screenshot for evidence: computer(action="screenshot")

Create the output directory:

mkdir -p reports/saas-session-recon/<platform-name>

Phase 1: Cookie Reconnaissance

Goal: Enumerate all cookies for the platform's domain(s) and identify which are auth-relevant.

  • Get JS-visible cookies via javascript_tool :

// ES5 — returns non-HttpOnly cookies (function() { var cookies = document.cookie.split("; "); var result = cookies.map(function(c) { var parts = c.split("="); var name = parts[0]; var value = parts.slice(1).join("="); return { name: name, valuePrefix: value.substring(0, 10) + "...", length: value.length, httpOnly: false }; }); return { count: result.length, cookies: result }; })()

  • Get localStorage and sessionStorage tokens:

// ES5 — check storage for auth tokens (function() { var ls = {}; var ss = {}; var i; for (i = 0; i < window.localStorage.length; i++) { var lk = window.localStorage.key(i); var lv = window.localStorage.getItem(lk); ls[lk] = { prefix: lv.substring(0, 30), length: lv.length }; } for (i = 0; i < window.sessionStorage.length; i++) { var sk = window.sessionStorage.key(i); var sv = window.sessionStorage.getItem(sk); ss[sk] = { prefix: sv.substring(0, 30), length: sv.length }; } return { localStorage: ls, sessionStorage: ss }; })()

Discover HttpOnly cookies AND extract full cookie attributes — document.cookie only returns name=value pairs. It does NOT expose HttpOnly, Domain, Path, SameSite, Secure, or Expires/Max-Age. You MUST use read_network_requests to get these.

Navigate to a page and immediately call read_network_requests(tabId, urlPattern) . Inspect:

  • Request Cookie headers — compare with JS-visible cookies to identify HttpOnly-only cookies

  • Response Set-Cookie headers — these contain the FULL cookie attributes: Set-Cookie: sid=abc123; Domain=.example.com; Path=/; Secure; HttpOnly; SameSite=None; Max-Age=7776000

For each auth cookie, extract from Set-Cookie headers:

  • Domain — which (sub)domains it's scoped to

  • Path — which URL paths it's sent to (usually / but check)

  • Secure — HTTPS-only flag

  • HttpOnly — not accessible from JS

  • SameSite — None/Lax/Strict (critical for extension compatibility)

  • Expires or Max-Age — when the cookie expires (session cookies have neither)

If you can't find Set-Cookie headers (already-set cookies won't appear in new responses), trigger a fresh set by: clearing cookies for one auth cookie via javascript_tool (document.cookie = "name=; expires=Thu, 01 Jan 1970" ), then refreshing the page to force the platform to re-issue it — capture the Set-Cookie in the response. Only do this for non-HttpOnly cookies you can delete from JS.

Classify each cookie:

Classification Indicators

Auth/session Names containing: session, sid, token, auth, jwt, csrf, canary, xoxc, sapisid, estsauth

Tracking/analytics Names containing: _ga, _gid, _fbp, _gcl, amplitude, mixpanel, segment

Functional Names containing: locale, timezone, theme, preferences, consent

Unknown Everything else — investigate if it appears in API request headers

Cross-reference discovered cookies against the platform intelligence priors. Note any unexpected auth cookies not in the prior intelligence.

Multi-domain cookie scoping — Many platforms use multiple subdomains (e.g., Google: mail.google.com , calendar.google.com , drive.google.com ). For each auth cookie, check its Domain attribute:

  • Domain = .google.com → works across all subdomains

  • Domain = mail.google.com → only works for that subdomain

  • Navigate to 2-3 different subdomains of the platform and compare which cookies are sent to each (use read_network_requests after each navigation)

SameSite attribute check — For each auth cookie, note its SameSite value. This matters for extension service worker behavior:

  • SameSite=None → sent on cross-origin requests (extension-compatible)

  • SameSite=Lax → sent on top-level navigations only (may work for GET from extension, not POST)

  • SameSite=Strict → never sent cross-origin (will NOT work from extension service worker)

  • Missing → browser defaults to Lax since Chrome 80

SameSite values are NOT visible from document.cookie . Infer from read_network_requests response Set-Cookie headers, or test behaviorally in Phase 3.

Multi-account detection — Check if the user has multiple accounts active:

// ES5 — detect multi-account indicators (function() { var cookies = document.cookie; var multiAccountSignals = []; // Google: u/0, u/1 path patterns if (window.location.href.indexOf("/u/0") !== -1 || window.location.href.indexOf("/u/1") !== -1) { multiAccountSignals.push("Google multi-account (u/N path)"); } // Microsoft: multiple ESTSAUTH cookies // Slack: multiple workspace cookies // Look for account selector UI var accountSwitchers = document.querySelectorAll("[aria-label*='account'], [aria-label*='Account'], [data-testid*='account-switch']"); if (accountSwitchers.length > 0) { multiAccountSignals.push("Account switcher UI detected"); } return { signals: multiAccountSignals, currentUrl: window.location.href }; })()

If multi-account is detected, document: which account's cookies are used for API calls? Does the API response correspond to the expected account?

Output: Write cookie inventory to reports/saas-session-recon/<platform-name>/cookies.md .

Phase 2: Network Traffic Analysis

Goal: Discover which API endpoints the web app calls, what auth each uses, and whether they're official or internal.

Clear network tracking then navigate through core features:

read_network_requests(tabId, clear=True)

Navigate to each key area of the platform:

  • Main dashboard / inbox / home

  • List view of primary resources (messages, issues, contacts, documents)

  • Detail view of a single resource

  • Settings or profile page

  • Search (if available)

After each navigation, capture with:

read_network_requests(tabId, urlPattern="/api/", limit=50)

Vary the urlPattern to catch different API paths:

  • /api/ — standard REST paths

  • graphql — GraphQL endpoints

  • /v1/ , /v2/ , /v3/ — versioned APIs

  • gateway — API gateways

  • The platform's known API domain from platform-intelligence.md

Also inject a fetch/XHR interceptor for calls read_network_requests might miss:

// ES5 — inject BEFORE navigating to capture all calls (function() { window.__apiCalls = []; var origFetch = window.fetch; window.fetch = function() { var url = typeof arguments[0] === "string" ? arguments[0] : (arguments[0] && arguments[0].url); var opts = arguments[1] || {}; var headers = {}; if (opts.headers) { if (typeof opts.headers.entries === "function") { var iter = opts.headers.entries(); var entry; while (!(entry = iter.next()).done) { headers[entry.value[0]] = entry.value[1]; } } else { for (var k in opts.headers) { if (opts.headers.hasOwnProperty(k)) headers[k] = opts.headers[k]; } } } window.__apiCalls.push({ type: "fetch", url: url, method: opts.method || "GET", headers: headers, credentials: opts.credentials, ts: Date.now() }); return origFetch.apply(this, arguments); }; var origOpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function(method, url) { window.__apiCalls.push({ type: "xhr", url: url, method: method, ts: Date.now() }); return origOpen.apply(this, arguments); }; return "interceptor installed"; })()

After navigating, harvest:

(function() { var calls = window.__apiCalls.splice(0); return { count: calls.length, calls: calls }; })()

For each discovered API endpoint, record:

  • URL pattern (collapse IDs to {id} placeholders)

  • HTTP method

  • Whether it matches the platform's official documented API base URL

  • Any custom headers (especially auth-related: Authorization, X-CSRF-*, X-OWA-CANARY, etc.)

Identify the CSRF mechanism — check for:

// ES5 — look for CSRF meta tags in page HTML (function() { var metas = document.querySelectorAll("meta"); var csrfMetas = []; for (var i = 0; i < metas.length; i++) { var name = metas[i].getAttribute("name") || ""; var content = metas[i].getAttribute("content") || ""; if (name.toLowerCase().indexOf("csrf") !== -1 || name.toLowerCase().indexOf("token") !== -1) { csrfMetas.push({ name: name, contentPrefix: content.substring(0, 20) + "..." }); } } // Also check for CSRF in cookies var csrfCookies = document.cookie.split("; ").filter(function(c) { return c.toLowerCase().indexOf("csrf") !== -1; }); return { metaTags: csrfMetas, csrfCookies: csrfCookies }; })()

  • Check Content-Security-Policy — CSP connect-src directives can block fetch() from injected JS to certain API domains. If a test fails from injected JS but works from Bun, CSP may be the cause, not auth.

// ES5 — check CSP meta tags and capture CSP header (function() { var cspMetas = []; var metas = document.querySelectorAll("meta[http-equiv='Content-Security-Policy']"); for (var i = 0; i < metas.length; i++) { cspMetas.push(metas[i].getAttribute("content")); } return { cspMetaTags: cspMetas, note: "Also check CSP response header via read_network_requests" }; })()

Also check read_network_requests response headers for Content-Security-Policy and Content-Security-Policy-Report-Only . Look for connect-src — if it doesn't include * or the API's domain, injected JS fetch will fail.

Key distinction: CSP blocks injected JS but does NOT affect extension service worker fetch() (service workers aren't subject to the page's CSP). So a CSP block in browser testing is NOT a problem for the actual extension — document this.

  • Check for request signing / nonce patterns — look in captured request headers for:

// ES5 — detect signing patterns in captured API calls (function() { var calls = window.__apiCalls || []; var signingPatterns = []; calls.forEach(function(call) { var headers = call.headers || {}; for (var key in headers) { var lower = key.toLowerCase(); if (lower.indexOf("signature") !== -1 || lower.indexOf("nonce") !== -1 || lower.indexOf("timestamp") !== -1 || lower.indexOf("hash") !== -1 || lower.indexOf("sapisidhash") !== -1 || lower.indexOf("x-goog-authuser") !== -1) { signingPatterns.push({ url: call.url, header: key, valuePrefix: headers[key].substring(0, 30) }); } } }); return { count: signingPatterns.length, patterns: signingPatterns }; })()

If signing headers are found, the extension/agent must compute these dynamically — simple cookie replay won't work. Document the formula if known (check platform-intelligence.md) or flag for reverse-engineering.

Output: Write API endpoint map to reports/saas-session-recon/<platform-name>/api-endpoints.md .

Phase 2.5: Build the API Test Target List

Goal: Consolidate everything into a concrete list of specific API endpoints to test. Phase 3 tests each of these — there should be no <API_URL> placeholders left.

Build the list from THREE sources:

Source 1: Platform intelligence priors — from references/platform-intelligence.md , extract:

Source 2: Network traffic discovery — from Phase 2 captured API calls:

  • Group by base URL pattern (collapse resource IDs to {id} )

  • Identify which are official API calls vs internal/undocumented

  • Pick 1-2 representative endpoints per base URL for testing

Source 3: Official API documentation lookup — for the platform, find the official API docs:

Look up official API base URL from docs

bun -e " const resp = await fetch('https://www.google.com/search?q=&#x3C;PLATFORM>+REST+API+documentation+site:docs', { headers: { 'User-Agent': 'Mozilla/5.0' } }); // Or just use known URLs from platform intelligence console.log('Check platform docs for API base URL and a simple GET endpoint'); "

Common official API patterns:

  • api.<platform>.com/v1/ or /v2/

  • <platform>.com/api/v1/

  • graph.microsoft.com/v1.0/

  • www.googleapis.com/

Find ONE simple "identity" endpoint to test first (tells you if auth works without needing to know resource IDs):

  • /users/me , /me , /api/v2/users/me.json , /v1/me

  • These require no arguments — just auth

Build the consolidated test target table:

Endpoint URL Type Source Test priority Identity endpoint?

1 https://api.example.com/v2/users/me.json

Official Docs + priors P0 Yes

2 https://api.example.com/v2/tickets.json

Official Priors P0 No

3 https://app.example.com/api/internal/search

Internal Network capture P1 No

4 https://app.example.com/graphql

Internal Network capture P1 No

... ... ... ... ... ...

Test priority rules:

  • P0: Identity/me endpoints (simplest test — no resource IDs needed), official API base URL

  • P0: Any endpoint the web app uses heavily (high-frequency in network capture)

  • P1: Other discovered endpoints, less-used API paths

  • P2: Edge cases (admin endpoints, settings, file operations)

For unknown platforms (not in platform-intelligence.md):

  • Search for <platform name> API documentation and <platform name> REST API

  • Look for a developer portal or API reference

  • Find the API base URL and a /me or /users/me endpoint

  • Fall back entirely on network traffic discovery from Phase 2

Output: This table IS the input to Phase 3. Every row gets tested through the gradient. Start with P0 endpoints.

Phase 3: Gradient API Testing (Bun → Injected JS → Extension Implications)

Goal: Test each endpoint from the Phase 2.5 target list across three execution contexts, from least to most constrained. This gradient reveals exactly what works, what breaks, and why.

Why inverted order? If Bun (full header control) can't make the API work with extracted cookies, nothing else will either. Start with the most permissive context to establish the baseline, then test more constrained contexts to understand what additional restrictions apply.

Input: The API test target table from Phase 2.5. Test all P0 endpoints first, then P1 if time allows.

Constraint gradient:

Context Origin control Cookie handling CORS SameSite

Bun/Node (test first) Full control Manual (extracted values) N/A N/A

Injected JS (test second) Browser-controlled document.cookie

  • credentials: 'include'

Same-origin free, cross-origin restricted Browser enforces

Extension service worker (infer from delta) chrome-extension:// (can't override) credentials: 'include' attaches all (incl. HttpOnly) Subject to CORS SameSite=Strict blocks

Step 3A: Bun/Node Testing (Least Constrained)

Extract cookies from Phase 1 (both JS-visible and HttpOnly cookies from network headers), then test each endpoint from the Phase 2.5 target list starting with the P0 identity endpoint. This tells you: does cookie auth work at all, independent of browser restrictions?

First, build a cookie string from Phase 1 findings. For HttpOnly cookies you can't read from document.cookie , get the values from read_network_requests captured headers.

Start with the identity endpoint (e.g., /users/me ) — it requires no arguments, just auth:

Test identity endpoint first — simplest possible auth check

bun -e " const cookies = '<ALL_AUTH_COOKIES_FROM_PHASE_1>'; const resp = await fetch('<IDENTITY_ENDPOINT_FROM_TARGET_LIST>', { headers: { 'Cookie': cookies, 'Origin': 'https://<PLATFORM_DOMAIN>', 'Accept': 'application/json', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36' } }); const body = await resp.text(); console.log(JSON.stringify({ status: resp.status, statusText: resp.statusText, ok: resp.ok, contentType: resp.headers.get('content-type'), bodyPreview: body.substring(0, 500), wwwAuthenticate: resp.headers.get('www-authenticate') }, null, 2)); "

Bun test matrix — run for EACH discovered API endpoint (official and internal):

Test Headers What it tells you

GET + cookies + correct Origin Origin: https://<platform>

Baseline: does cookie auth work?

GET + cookies + wrong Origin Origin: https://evil.com

Does the API validate Origin?

GET + cookies + no Origin Omit Origin header Does the API require Origin?

GET + cookies + extension Origin Origin: chrome-extension://abcd1234

Would extension service worker work?

GET + cookies + CSRF header Add CSRF from Phase 2 Does the API require CSRF for reads?

POST + cookies + CSRF + correct Origin Full auth Does cookie auth work for writes?

GET without cookies Omit Cookie header Confirm it fails — proves cookies are doing the auth

Important: For POST/PUT/DELETE tests, use safe, non-destructive operations (search endpoints, draft/preview, validation). Ask the user before testing writes that create real data.

If Bun test fails with correct Origin + all cookies: check for session binding. The platform may bind sessions to:

  • IP address — unlikely to differ if Bun runs on the same machine, but check if VPN/proxy is involved

  • User-Agent — try matching the exact browser User-Agent string from read_network_requests

  • TLS fingerprint — Bun's TLS stack differs from Chrome's; some platforms (Cloudflare, Akamai) fingerprint TLS

  • Device-bound session credentials (DBSC) — Chrome's DBSC ties cookies to a specific browser's TPM-bound key

Test each by varying one header at a time:

Test with exact browser User-Agent (copy from read_network_requests)

bun -e " const resp = await fetch('<API_URL>', { headers: { 'Cookie': '<COOKIES>', 'Origin': 'https://<PLATFORM>', 'User-Agent': '<EXACT_BROWSER_UA_STRING>', 'Accept': 'application/json', 'Accept-Language': 'en-US,en;q=0.9', 'Sec-Fetch-Site': 'same-origin', 'Sec-Fetch-Mode': 'cors', 'Sec-Fetch-Dest': 'empty' } }); console.log(resp.status, resp.statusText); "

If it still fails after matching all headers, the session is truly browser-bound. Document this — the extension must use credentials: 'include' (service worker direct) and CANNOT extract cookies for agent-process use.

If Bun test succeeds: you've confirmed cookie auth works outside the browser. Now isolate the minimum viable cookie set:

Test with EACH auth cookie individually to find which are required

Start with the most likely session cookie alone

bun -e " const resp = await fetch('<API_URL>', { headers: { 'Cookie': '<SINGLE_COOKIE_NAME>=<VALUE>', 'Origin': 'https://<PLATFORM>', 'Accept': 'application/json' } }); console.log('Single cookie:', resp.status); "

Binary-search the cookie set: start with all cookies (works), remove half, test. Repeat until you find the minimum set. This is critical for the credential extraction recipe — extracting 2 cookies vs 15 cookies is a different engineering problem.

Error response fingerprinting — For EVERY failed request (non-2xx), record the exact error shape:

Capture full error details for failed requests

bun -e " const resp = await fetch('<API_URL>', { headers: { 'Accept': 'application/json' } // NO cookies }); const body = await resp.text(); console.log(JSON.stringify({ status: resp.status, statusText: resp.statusText, contentType: resp.headers.get('content-type'), wwwAuthenticate: resp.headers.get('www-authenticate'), xError: resp.headers.get('x-error') || resp.headers.get('x-error-code'), bodyPreview: body.substring(0, 1000) }, null, 2)); "

Document the error fingerprint per platform — the agent needs to distinguish "auth failed" from "rate limited" from "not found" from "forbidden." Each platform returns different error shapes.

Step 3B: Injected JS Testing (Browser Context)

Now test from inside the page via javascript_tool . Compare results with Bun to identify browser-specific constraints.

// ES5 — test API from browser context (function() { var url = "<API_URL>"; fetch(url, { method: "GET", credentials: "include", headers: { "Accept": "application/json" } }) .then(function(resp) { return resp.text().then(function(body) { window._browserTest = { status: resp.status, statusText: resp.statusText, ok: resp.ok, bodyPreview: body.substring(0, 500) }; }); }) .catch(function(e) { window._browserTest = { error: e.message }; }); return "fetching..."; })()

Then retrieve: window._browserTest

Browser-specific tests:

Same-origin vs cross-origin — test APIs on the same domain (no Origin sent) and different domains (browser sends real Origin). Compare with Bun results.

SameSite behavioral test — if you're on mail.google.com , try fetching from clients6.google.com (different subdomain). If Bun succeeded but browser fails here, SameSite is blocking.

CORS preflight check — POST with Content-Type: application/json triggers preflight:

// ES5 — test POST (triggers CORS preflight for cross-origin) (function() { fetch("<CROSS_ORIGIN_API_URL>", { method: "POST", credentials: "include", headers: { "Content-Type": "application/json", "Accept": "application/json" }, body: JSON.stringify({}) }) .then(function(r) { window._corsTest = { status: r.status, ok: r.ok }; }) .catch(function(e) { window._corsTest = { error: e.message, note: "CORS preflight likely blocked" }; }); return "testing CORS..."; })()

  • Response format check — flag non-JSON responses:

// ES5 — check content types across key endpoints (function() { var urls = [/* 3-5 key API endpoints from Phase 2 */]; var results = []; var done = 0; urls.forEach(function(url) { fetch(url, { credentials: "include" }) .then(function(r) { results.push({ url: url, contentType: r.headers.get("content-type"), isJson: (r.headers.get("content-type") || "").indexOf("json") !== -1 }); done++; if (done === urls.length) window._formatCheck = results; }); }); return "checking formats..."; })()

Step 3C: Delta Analysis (Bun vs Browser → Extension Implications)

Compare Bun and browser results to build the extension compatibility matrix:

Endpoint Bun result Browser result Delta Extension implication

Official API GET ? ? ? ?

Official API POST ? ? ? ?

Internal API GET ? ? ? ?

Internal API POST ? ? ? ?

Interpret the delta:

Bun Browser What it means Extension approach

Works Works Cookie auth works everywhere Service worker fetch() with credentials: 'include'

Works CORS blocked API doesn't set CORS headers for browser Service worker may bypass (SW has relaxed CORS); OR extract cookies → agent process via Bun

Works SameSite blocked Cookies not sent cross-origin in browser Service worker with host_permissions overrides SameSite; test needed

Works Origin rejected API validates Origin and browser sends wrong one Extract cookies → agent process (Bun) via native messaging

Fails Works Browser-binding (DBSC?) or missing HttpOnly cookies Service worker direct only (can't extract)

Fails Fails Cookie auth doesn't work for this API Use official OAuth/API tokens instead

Also test from Bun with extension-like Origin:

Simulate extension service worker Origin

bun -e " const resp = await fetch('<API_URL>', { headers: { 'Cookie': '<COOKIES>', 'Origin': 'chrome-extension://abcdefghijklmnop', 'Accept': 'application/json' } }); console.log('Status:', resp.status, resp.statusText); "

CORS preflight for extension origin:

Simulate CORS preflight from extension

curl -s -D - -X OPTIONS
-H "Origin: chrome-extension://fake-extension-id"
-H "Access-Control-Request-Method: POST"
-H "Access-Control-Request-Headers: Content-Type"
"<API_URL>" 2>&1 | head -30

Check for Access-Control-Allow-Origin , Access-Control-Allow-Credentials: true , Access-Control-Allow-Methods .

Document for each API endpoint:

  • Which execution context works (Bun / browser / both / neither)

  • What restrictions the browser adds (CORS, SameSite, Origin)

  • Recommended architecture for extension (service worker direct / cookie extraction to agent process / hybrid)

Phase 4: CRUD Coverage Testing

Goal: Using the working execution context(s) identified in Phase 3, test what CRUD operations are available.

Use whichever context worked in Phase 3 (prefer Bun for simplicity if it works).

Compare official vs internal API access. If the official API rejects cookies but the internal API accepts them, this is a key finding — the extension must use the internal API.

Test CRUD coverage:

  • Read: List/get operations (always test first)

  • Create: Only with user confirmation

  • Update: Only with user confirmation on a test resource

  • Delete: Only with explicit user confirmation, and only resources YOU created

Document the minimum credential set for each endpoint:

  • Cookies only? Which specific cookies?

  • Cookies + specific header? Which header, where does its value come from?

  • Any other requirements (Content-Type, Accept, Origin)?

Phase 5: Credential Extraction Recipe

Goal: Document exactly what a Chrome extension needs to extract and how the agent process should use it.

Based on findings from Phases 1-4, write the recipe:

What to extract: List specific cookies/tokens by name:

  • Is it HttpOnly? (If yes, chrome.cookies API can read it but document.cookie cannot)

  • Is it needed as a cookie in the request, or as a header value?

  • Does it need to be combined with anything else? (e.g., SAPISIDHASH computation)

How to make the API call: Specify exactly:

  • API URL pattern

  • Required headers (Authorization, CSRF, Content-Type, Origin, etc.)

  • Whether to use credentials: 'include' (send cookies) or extract-and-inject

  • Request body format (JSON, form-encoded, etc.)

Extension vs agent process — classify the approach:

  • Service worker direct — extension fetch() with credentials: 'include' works (most platforms)

  • Cookie extraction + agent process — extension extracts cookie, passes to agent via native messaging, agent makes API call with Origin set freely (needed when Origin validation exists, e.g., Google)

  • Token extraction — extract token value from cookie/localStorage, use as Bearer token (e.g., Salesforce sid )

Phase 6: Session Durability & Token Rotation

Goal: Determine how long cookie-based access lasts, what triggers re-auth, and whether tokens rotate mid-session.

Check cookie expirations from Phase 1 Set-Cookie data (step 3):

  • No Expires /Max-Age → session cookie, dies when browser closes

  • Max-Age=7776000 → 90 days

  • Max-Age=300 → 5 minutes (short-lived, needs refresh — e.g., HubSpot)

  • Expires=<date> → check if absolute or sliding

Build an expiration table:

Cookie Expires/Max-Age Effective lifetime Session or persistent?

... ... ... ...

Trace the client-side refresh flow — this is critical. Look in Phase 2 network captures for requests that RETURN new cookies. These are the session refresh mechanism.

Filter read_network_requests for responses containing Set-Cookie headers on auth cookie names:

read_network_requests(tabId, urlPattern="token") read_network_requests(tabId, urlPattern="refresh") read_network_requests(tabId, urlPattern="auth") read_network_requests(tabId, urlPattern="session")

For each refresh endpoint found, document:

  • URL and HTTP method

  • What triggers it (timer? page navigation? API 401 response?)

  • What it sends (old cookies? refresh token? nothing?)

  • What it returns (new cookie values via Set-Cookie )

  • Interval (how often does the web app call it?)

Why this matters: If the platform rotates session cookies every 5 minutes via a refresh endpoint, the extension must replicate this call. If it doesn't, extracted cookies expire after 5 minutes even though the user's browser session appears "active."

Also check for heartbeat/keepalive from Phase 2 data — look for periodic requests to URLs containing: heartbeat, keepalive, ping, alive, extend. These may keep sessions alive without rotating cookies.

Token rotation check — some platforms rotate cookie values mid-session (e.g., HubSpot's 5-min hubspotapi cookie). This breaks extracted-credential approaches where the agent process holds a stale token.

// ES5 — snapshot cookie values for rotation detection (function() { var snapshot = {}; document.cookie.split("; ").forEach(function(c) { var parts = c.split("="); snapshot[parts[0]] = parts.slice(1).join("=").substring(0, 20); }); window._cookieSnapshot1 = { ts: Date.now(), cookies: snapshot }; return "snapshot 1 taken at " + new Date().toISOString(); })()

Wait 2-5 minutes, then take a second snapshot and compare:

// ES5 — compare snapshots for rotation (function() { var snapshot = {}; document.cookie.split("; ").forEach(function(c) { var parts = c.split("="); snapshot[parts[0]] = parts.slice(1).join("=").substring(0, 20); }); var s1 = window._cookieSnapshot1; var rotated = []; for (var name in snapshot) { if (s1.cookies[name] && s1.cookies[name] !== snapshot[name]) { rotated.push(name); } } return { elapsed: Date.now() - s1.ts, rotatedCookies: rotated, newCookies: Object.keys(snapshot).filter(function(k) { return !s1.cookies[k]; }), removedCookies: Object.keys(s1.cookies).filter(function(k) { return !snapshot[k]; }) }; })()

If rotation detected: Document which cookies rotate, approximate rotation interval, and implication for credential extraction (must re-extract on every call? or can cache for N minutes?).

Test session refresh (if time permits):

  • Make an API call, wait a few minutes, make another

  • Check if cookie values changed (compare Phase 1 cookies before/after)

Document what the user will experience: How often must they be "actively logged in" to the platform in Chrome for the extension proxy to work?

Phase 7: Detection & Rate Limiting

Goal: Assess whether cookie-authenticated API calls look different from normal browser activity.

  • Test rate limiting via javascript_tool :

// ES5 — 10 rapid GET requests (function() { var url = "<TEST_API_URL>"; var results = []; var count = 0; function doFetch() { var start = Date.now(); fetch(url, { credentials: "include", headers: { "Accept": "application/json" } }) .then(function(r) { var headers = {}; r.headers.forEach(function(v, k) { headers[k] = v; }); results.push({ i: count, status: r.status, elapsed: Date.now() - start, headers: headers }); count++; if (count < 10) { doFetch(); } else { window._rateTest = results; } }) .catch(function(e) { results.push({ i: count, error: e.message }); window._rateTest = results; }); } doFetch(); return "testing rate limits..."; })()

Then retrieve: window._rateTest

Check rate limit headers in the results:

  • X-RateLimit-Limit , X-RateLimit-Remaining , X-RateLimit-Reset

  • Retry-After

  • Platform-specific headers (Slack: X-Slack-Req-Id , GitHub: X-RateLimit-* )

Compare request fingerprint — use read_network_requests to compare headers sent by the web app's own API calls vs your fetch() calls. Look for missing Sec-Fetch-* , Referer , or X-Requested-With headers.

Document: Are cookie-authenticated requests treated differently for rate limiting? Any fingerprinting signals?

Phase 8: Capability Mapping

Goal: Map what CRUD operations are available via cookie-authenticated APIs.

List the platform's core resources (based on what the web app interacts with):

Resource Read Create Update Delete API Used

Messages/emails ? ? ? ?

Contacts/users ? ? ? ?

Documents/files ? ? ? ?

Issues/tickets ? ? ? ?

Comments ? ? ? ?

Settings ? ? ? ?

Test read operations for each resource type via the working API path (official or internal).

Compare with official API documentation: Are there operations available via cookie auth that require higher OAuth scopes? Are there operations the official API exposes that the internal API doesn't?

Note admin-only operations: Which operations fail with a regular user's session? These indicate admin-gated permissions regardless of auth method.

Phase 9: Write Findings Document

Compile all results into a structured findings document.

Output directory: reports/saas-session-recon/<platform-name>/

Create the directory and write the findings:

mkdir -p reports/saas-session-recon/<platform-name>/evidence

Files to produce:

  • reports/saas-session-recon/<platform-name>/FINDINGS.md — the structured report below

  • reports/saas-session-recon/<platform-name>/evidence/ — screenshots and raw evidence captured during the recon

Use this template for FINDINGS.md:

Session Recon: <Platform Name>

Date: YYYY-MM-DD Platform URL: <url tested> User role: <user's role/permissions level in the platform>


Executive Summary

<2-3 sentences: Can this platform's APIs be accessed via session cookies? What's the recommended architecture (service worker direct / hybrid / agent-process only)? Key caveats.>

Cookie Inventory

CookieDomainHttpOnlySecureSameSiteExpiresPurposeAuth-relevant?
........................

Minimum auth cookie set: <list the specific cookies needed for API access>

localStorage/sessionStorage tokens: <any auth-relevant tokens found>

API Endpoint Map

Official API

EndpointMethodCookie Auth?CSRF Required?Notes
...............

Internal API

EndpointMethodCookie Auth?CSRF Required?Notes
...............

Gradient Test Results (Bun → Browser → Extension)

EndpointBun (cookies + correct Origin)Bun (no Origin)Bun (extension Origin)Browser (injected JS)Extension implication
Official API GET?????
Official API POST?????
Internal API GET?????
Internal API POST?????

Device-bound session check: <Bun works with extracted cookies: Yes/No. If No, sessions are browser-bound (DBSC).>

Extension Context Compatibility

FactorStatusImpact
SameSite cookie restrictions<None/Lax blocks POST/Strict blocks all><what breaks>
CORS preflight for extension origin<Passes/Fails/N/A><what breaks>
Non-JSON response formats<None/protobuf/HTML/other><parsing needed>
Multi-account behavior<Single/Multi detected><which account used>

Auth Error Fingerprints

Platform ResponseMeaningStatusBody Pattern
Auth missing/expired<what it looks like><status code><body snippet>
CSRF missing/invalid<what it looks like><status code><body snippet>
Rate limited<what it looks like><status code><body snippet>
Forbidden (permission)<what it looks like><status code><body snippet>

Credential Extraction Recipe

For Chrome Extension Service Worker (if Origin not validated):

  1. <step-by-step: what to extract, how to make the API call>

For Agent Process via Native Messaging (if Origin validated):

  1. <step-by-step: what to extract, how to pass to agent, how agent makes the call>

Minimum Viable Cookie Set

CookieRequired?How tested
...Yes/NoRemoved and API call succeeded/failed

Minimum set: <list of only the cookies actually needed>

Session Durability

CookieExpires/Max-AgeEffective lifetimeRotates?Refresh endpoint
.........Yes (interval)/NoURL or N/A
  • Session binding: <None / IP / User-Agent / TLS fingerprint / DBSC>
  • Refresh mechanism: <endpoint URL, interval, what triggers it>
  • Re-auth frequency: <how often user needs to be logged in>

Cookie Scoping

CookieDomain AttributeCross-subdomain?SameSiteExtension-compatible?
...............

Detection Risk

  • Rate limits: <observed limits, header names>
  • Fingerprinting: <any detected>
  • Differences from normal browser: <any observed>

Capability Matrix

ResourceReadCreateUpdateDeleteAPINotes
.....................

Architecture Recommendation

<Service worker only / Hybrid (extension + agent process) / Agent process only>

Confidence: <CONFIRMED / INFERRED / UNCERTAIN>

Comparison with Prior Intelligence

<What matched our prior research? What was different? What was new?>

Open Questions

  • <Things that couldn't be validated in this session>

Safety Rules

  • Read before write. Always test GET/read operations before attempting any create/update/delete.

  • Never delete real user data. Only delete resources you created during this recon session, and only with user confirmation.

  • Ask before writes. Before any POST/PUT/DELETE that creates or modifies data, describe what you'll do and get user confirmation.

  • Don't exfiltrate credentials. Cookie values and tokens appear in findings only as prefixes (first 10 chars + ... ). Never log full credential values.

  • One platform at a time. Don't navigate to other platforms during the recon.

Completing the Recon

  • Create the output directory: mkdir -p reports/saas-session-recon/<platform-name>/evidence

  • Save all screenshots captured during the recon to reports/saas-session-recon/<platform-name>/evidence/

  • Write reports/saas-session-recon/<platform-name>/FINDINGS.md using the template above

  • Present the executive summary and architecture recommendation to the user

  • Offer to run follow-up tests on specific endpoints or edge cases

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Automation

write-agent

No summary provided by upstream source.

Repository SourceNeeds Review
General

ship

No summary provided by upstream source.

Repository SourceNeeds Review
General

spec

No summary provided by upstream source.

Repository SourceNeeds Review
General

implement

No summary provided by upstream source.

Repository SourceNeeds Review