Is YouTube Chapters - TimeSkip AI safe?
YouTube Chapters - TimeSkip AI is high risk. YouTube Chapters - TimeSkip AI, used while signed in, logs Timeskip access and refresh tokens during session lookup. Those log records are sent to Axiom's api.axiom.co endpoint for the timeskip-ext dataset.
Who publishes itTimeSkip - no other listings under this identity
TimeSkip - no other listings under this identity
What this publisher told the store about itself, and the other listings that told it the same thing.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Timeskip tokens are sent to Axiom telemetry
YouTube Chapters - TimeSkip AI, used while signed in, logs Timeskip access and refresh tokens during session lookup.
Those log records are sent to Axiom's api.axiom.co endpoint for the timeskip-ext dataset.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-522
- Source
- Dynamic sandbox
You use the extension on YouTube while signed in to Timeskip.
The content script initializes and asks the background service worker for the saved session.
The extension records your Timeskip tokens in telemetry log entries.
The background code logs stored tokens, newly fetched token responses, and saved token values before returning the session.
- Timeskip access tokeneyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNzIwMDAwMDAwfQ.7zHSCA1IPWjh7GRiZtKzpwKzQwttruHytoel4mIXVCM (illustrative)
This can authorize requests as your Timeskip account until it expires or is revoked.
- Timeskip refresh token0ad4d7b8-0a19-4a18-9be6-b6e2dfc0a117 (illustrative)
This can be used to keep a Timeskip session alive after the current access token expires.
- Timeskip account emailcreator@example.com (illustrative)
This ties the telemetry record back to your Timeskip account identity.
- Content-Type
- application/x-ndjson
- Authorization
- Bearer xaat-294d6c6d-927b-48b5-9fbc-ffd55b567701
The background service worker logs token values before Axiom ingest
Readable access-token path
static/background/index.jsconst axiomAccessLogger = new Axiom({ token: "xaat-294d6c6d-927b-48b5-9fbc-ffd55b567701" });const extensionStorage = new Storage();const getAccessToken = () => { axiomAccessLogger.ingest("timeskip-ext", [{ message: "getAccessToken() called", level: "info", request: { path: "/ext/core/access-token" } }]); return new Promise(async (resolve, reject) => { try { const storedAccessToken = await extensionStorage.get("access_token"); if (storedAccessToken) { axiomAccessLogger.ingest("timeskip-ext", [{ message: `A. Access token fetched from storage: ${storedAccessToken}`, level: "info", request: { path: "/ext/core/access-token" }, data: { access_token: storedAccessToken } }]); resolve(storedAccessToken); } const response = await fetch("https://timeskip.io/api/auth/get-token", { credentials: "include", mode: "cors" }); const tokenResponse = await response.json(); axiomAccessLogger.ingest("timeskip-ext", [{ message: "Received access token response", level: "info", request: { path: "/ext/core/access-token" }, data: tokenResponse }]); if (response.ok && tokenResponse.access_token) { axiomAccessLogger.ingest("timeskip-ext", [{ message: "Access token fetched successfully", level: "info", request: { path: "/ext/core/access-token" }, data: tokenResponse }]); resolve(tokenResponse.access_token); } else { axiomAccessLogger.ingest("timeskip-ext", [{ message: `Error fetching token: ${tokenResponse.error}`, level: "error", request: { path: "/ext/core/access-token" }, error: tokenResponse.error }]); reject(null); } } catch (error) { axiomAccessLogger.ingest("timeskip-ext", [{ message: `Network error: ${error.message}`, level: "error", request: { path: "/ext/core/access-token" }, error: error.message }]); reject(null); } });};Readable session handler
static/background/index.jsfunction logToAxiom({ message, path, level = "info", userEmail = null, event = null }) { console.log(`[${level}][${path}] ${message}`); axiomSessionLogger.ingest("timeskip-ext", [{ message, level, request: { path }, userEmail, event }]);}const handleGetSupabaseSession = async () => { try { logToAxiom({ message: "1. handleGetSupabaseSession() called", path: "/ext/bg/get_supabase_session", level: "info" }); let accessToken = await storage.get("access_token"); let refreshToken = await storage.get("refresh_token"); logToAxiom({ message: `2. Stored access token: ${accessToken}, refresh token: ${refreshToken}`, path: "/ext/bg/get_supabase_session", level: "info" }); if (!accessToken || !refreshToken) { logToAxiom({ message: "3. No tokens found in storage, fetching new tokens", path: "/ext/bg/get_supabase_session", level: "info" }); const tokens = await fetchSupabaseToken(); accessToken = tokens.access_token; refreshToken = tokens.refresh_token; } let { data: sessionData, error } = await supabase.auth.setSession({ access_token: accessToken, refresh_token: refreshToken }); logToAxiom({ message: `4. Supabase session set with tokens ${sessionData}`, path: "/ext/bg/get_supabase_session", level: "info", userEmail: sessionData?.user?.email }); if (error) { const tokens = await fetchSupabaseToken(); accessToken = tokens.access_token; refreshToken = tokens.refresh_token; const retry = await supabase.auth.setSession({ access_token: accessToken, refresh_token: refreshToken }); if (retry.error) { await storage.remove("access_token"); await storage.remove("refresh_token"); return { success: false, error: retry.error.message }; } sessionData = retry.data; } await storage.set("access_token", accessToken); await storage.set("refresh_token", refreshToken); logToAxiom({ message: "10.Saved in storage: access token: " + accessToken + " refresh token: " + refreshToken, path: "/ext/bg/get_supabase_session", level: "info" }); return { success: true, session: sessionData, access_token: accessToken, refresh_token: refreshToken }; } catch (error) { const message = error?.message || "An unknown error occurred"; logToAxiom({ message: "12. Error in GET_SUPABASE_SESSION handler: " + message, path: "/ext/bg/get_supabase_session", level: "error" }); return { success: false, error: message }; }};Readable YouTube-page trigger
contents.7868605b.jsasync function initializeSystems() { try { const videoId = getCurrentVideoId(); if (!videoId) throw Error("No video ID found"); await getSupabaseSession(); await initializeCredits(); preloadPricingData(); systemsInitialized = true; logToAxiom({ message: "Systems initialization completed", path: "/ext/initialize_systems", userEmail }); } catch (error) { logToAxiom({ message: "Systems initialization failed: " + String(error), path: "/ext/initialize_systems", userEmail, level: "error" }); throw error; }}async function getSupabaseSession() { return new Promise((resolve) => { chrome.runtime.sendMessage({ type: "GET_SUPABASE_SESSION" }, (response) => { if (response?.success && response.session?.user) { userId = response.session.user.id; userEmail = response.session.user.email; logToAxiom({ message: "userEmail " + userEmail, path: "/ext/get_supabase_session", userEmail }); resolve(true); } else { logToAxiom({ message: "User is not logged in", path: "/ext/get_supabase_session", userEmail }); resolve(false); } }); });}- api.axiom.co
Receives the timeskip-ext telemetry ingest records created by the extension's bundled Axiom client.
- timeskip.io
Returns Timeskip token responses from /api/auth/get-token before the extension logs those responses.
- uztoihtloenrybhngijz.supabase.co
Supabase project used when the extension sets the Timeskip session from the access and refresh tokens.