Is YouTube Chapters - TimeSkip AI safe?

High risk

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.

TimeSkipv4.2.2Chrome Web Store
72Risk
Who publishes it

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.

Publisher
TimeSkip
Registered address
5025 Rue Jean-Talon Ouest, Montréal, QC H4P 0C3, CA
Registered contact
Nigel Yong

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
What actually happens
You did this

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 did this

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.

Credential fields placed in telemetry records
  • Timeskip access token
    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNzIwMDAwMDAwfQ.7zHSCA1IPWjh7GRiZtKzpwKzQwttruHytoel4mIXVCM (illustrative)

    This can authorize requests as your Timeskip account until it expires or is revoked.

  • Timeskip refresh token
    0ad4d7b8-0a19-4a18-9be6-b6e2dfc0a117 (illustrative)

    This can be used to keep a Timeskip session alive after the current access token expires.

  • Timeskip account email
    creator@example.com (illustrative)

    This ties the telemetry record back to your Timeskip account identity.

Captured request
POSThttps://api.axiom.co/v1/datasets/timeskip-ext/ingest
Headers
Content-Type
application/x-ndjson
Authorization
Bearer xaat-294d6c6d-927b-48b5-9fbc-ffd55b567701
The code that does this

The background service worker logs token values before Axiom ingest

Readable version

Readable access-token path

static/background/index.js
const 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.js
function 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.js
async 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);      }    });  });}
External services reached by the token path
    • 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.

Updated 30 September 2026pedofdgoinfflbafjdldggkjcjafpalb