knaxly.dev / sextant

Privacy policy

Last updated: 2026-08-19

← Sextant

Sextant is a browser extension that adds analytics and navigation tools to GitHub pages: a lines-of-code panel, a repo health card, dependency vulnerability badges, TODO counts, a file-tree sidebar, a pull-request analyzer, a notification inbox, a permission auditor, an organisation dashboard, a command palette, and an optional AI assistant. This policy describes what data the extension stores, what leaves your browser, where it goes, and what the companion backend at sextant.octago.nl does with it. Sextant is published by Knaxly Ltd; the extension, this policy and the terms live at knaxly.dev/sextant.

Summary

What's stored on your machine

chrome.storage.local: stays in this browser profile

Written only by the extension, readable only by the extension, never synced by us:

chrome.storage.sync: replicated by your browser

The ⌘K / Ctrl+K command palette records where you have been so it can offer "recent" results. Every time you load a GitHub repository, pull request or issue page, the extension writes an entry to recentVisits in chrome.storage.sync holding the owner, repository name, item type, pull-request or issue number, the page URL, and a timestamp. The 60 most recent entries are kept. The organisation dashboard reads the same list to pre-fill the organisation field.

This is not local-only storage. When browser profile sync is enabled, Chrome and Edge (the only browsers Sextant supports) replicate chrome.storage.sync to your Google or Microsoft account and to your other signed-in devices. Private repository, pull request and issue names and URLs are included; recording happens automatically on page load, with no prompt. Sextant does not send this list to its own backend or to any third party; the replication is performed by your browser. To keep it out of sync entirely, turn off extension sync in your browser settings, or use "Clear all" in the Options page to delete it.

"Clear all" in the Options page erases both chrome.storage.local and chrome.storage.sync. Uninstalling the extension removes its browser storage.

What's transmitted off your machine

1. GitHub → api.github.com (directly from your browser)

Most Sextant features read GitHub through GitHub's own REST and GraphQL APIs, from your browser, with your stored token in the Authorization: Bearer … header. No Sextant server is involved in the calls listed below, and what comes back stays in your browser, except on the AI path, where the last two items below are put into a prompt and sent onward as described in section 6. Separately from this list, the backend makes its own calls to api.github.com with your token when you use the features in section 3; those are described there. The endpoints your browser calls are, by feature:

2. Public repositories → codetabs.com (third-party service)

When the Sextant backend is not used or fails, the extension may call api.codetabs.com directly from your browser for a line-of-code count. The only data sent is the repository owner and name taken from the page URL: no GitHub token, no Sextant identifiers, no other metadata. Codetabs is a third-party service we do not operate or control; see codetabs.com for their policy.

Order of attempts on a repository page: if you have a token configured the backend is tried first; codetabs is tried next; the GitHub byte estimate is the last resort.

The codetabs call is made only when Sextant has positively determined that the repository is public. The determination is made from the page you are already looking at: GitHub's own repository metadata in the page markup and the Public / Private / Internal label in the repository header, each cross-checked against the owner and repository named in the URL you are on. No extra network request is made to work it out, so establishing visibility cannot itself disclose anything.

The check has three possible answers, and only "public" allows the call. If the page says the repository is private or internal, or if Sextant cannot establish the visibility at all (because the page has not finished loading, because GitHub changed the markup the check reads, or for any other reason), the repository is treated as not public: codetabs is skipped and the extension falls back to the GitHub byte estimate, which uses your own token. If you have no token configured, the panel simply shows no count. "Could not tell" is never treated as "public".

Codetabs is also skipped, whatever the visibility, when the Sextant backend has refused the repository as too large (see Backend refusals): that refusal is about the size of the repository, and codetabs would be counting the same repository.

No token and no repository content is ever sent to codetabs.

3. Repository analysis → sextant.octago.nl

When you have a GitHub token configured, the extension calls POST https://sextant.octago.nl/analyze (and the deprecated POST /sloc, kept for older extension versions) with:

These calls happen automatically when you open a repository page or the file sidebar, no click required. The analyses are:

The backend does not persist your token. Tokens are held in memory for the lifetime of a single request, used to authenticate the GitHub API calls and the clone, and discarded. Cloned repositories are deleted as soon as the analysis finishes. Clone errors are scrubbed before being returned so a token cannot leak in an error string.

For the two analyses that clone (sloc and todos), the backend makes one additional call to GitHub's GET /user endpoint with your token, to read your numeric GitHub account id. The id is used as a fairness key: a single account may occupy at most two of the backend's four concurrent clone slots, so that one caller cannot exhaust the service for everyone else. Only the numeric id is read from GitHub's reply: your login, name, email and the rest of your profile are not decoded, not logged and not stored. The id is kept in the server's memory for 10 minutes, in a table bounded to 2,048 entries and filed under a truncated SHA-256 hash of your token, so that a busy session does not repeat the call; it is never written to disk and never written to a log. If the lookup fails, that failure alone is remembered for 30 seconds under the same hash (no identifier is stored in that case), so a GitHub outage does not produce one failing call per request. The call is made only when a clone is actually about to be attempted: it comes after the repository has been resolved with your token and after the size, cool-off and disk checks have passed, so a request the backend refuses for those reasons does not trigger it.

4. Dependency vulnerabilities → api.osv.dev (third-party service)

The deps analysis parses your dependency manifests (for example package.json, go.mod, requirements.txt) and sends the package names, ecosystems and versions in a batch query to https://api.osv.dev/v1/querybatch, operated by the Open Source Vulnerabilities project. No source code, no file contents, no repository name and no token are sent. Note that for a private repository the package names themselves can reveal internal module paths. This request is made by the Sextant backend, not by your browser.

5. Byte-estimate fallback → api.github.com

If the backend fails and codetabs was skipped or failed, Sextant calls GitHub's /repos/{owner}/{repo}/languages endpoint directly from your browser with your stored token, divides bytes-per-language by an embedded constants table, and shows the result prefixed with . The backend is not involved.

6. AI features → api.anthropic.com (third-party service)

The AI features are off by default. They activate only after you tick "Enable AI" and save your own Anthropic API key (sk-ant-…) in the Options page. They also require a connected GitHub account: an OAuth login or a Personal Access Token. An Anthropic key on its own is not enough, because both features read the repository from api.github.com with your own credential before they build a prompt, and without one that read is anonymous and returns nothing at all from a private repository. The extension refuses up front rather than sending a prompt with no context. With the AI features enabled, the following data is sent to Anthropic, a third-party AI provider, directly from your browser:

The request is made by the extension's own background service worker to https://api.anthropic.com/v1/messages. No Sextant server is involved: neither the prompt nor your Anthropic key reaches our infrastructure at any point on this path, and there is nothing for us to parse, cache, store or log because we never receive it. Your GitHub token is not sent to Anthropic and is not sent to us here either; it is used only for the api.github.com reads listed in section 1 that assemble the prompt. The key is held in the service worker rather than in the scripts Sextant runs on GitHub pages, so page-level JavaScript cannot read it.

What this does not change is the transfer itself. Anthropic still receives the same file contents, diffs and issue text it received before, on your instruction and billed to your own account. The route is shorter by one hop; the disclosure to a third party is the same disclosure.

One consequence worth stating, because it is visible to you: Anthropic blocks calls made from a browser for organisations that have configured custom data-retention settings. If your Anthropic organisation is one of those, the request fails and Sextant reports that it could not reach api.anthropic.com. There is no fallback route through our infrastructure.

Once your data reaches Anthropic it is governed by Anthropic's terms and privacy policy and by the settings on your own Anthropic account, including whatever data-retention configuration that account has. Sextant has no control over it. See anthropic.com/legal/privacy. If you do not want repository content leaving your browser, leave the AI features switched off; every other Sextant feature works without them.

The assistant's replies are stored in your browser in aiChatSessions (see above) and are not sent to the Sextant backend for storage.

7. Sign-in → github.com (OAuth device flow)

Clicking "Connect GitHub" runs GitHub's OAuth device flow, in the extension's own background service worker and against github.com only. It asks https://github.com/login/device/code for a short user code, shows you that code and sends you to GitHub's device-authorisation page to approve it, then polls https://github.com/login/oauth/access_token until GitHub returns an access token. The token, and your login name read once from GET /user, are stored in your browser.

No Sextant server takes any part in signing you in. Nothing about the sign-in is sent to sextant.octago.nl, and there is no code exchange, no redirect and no client secret on our side: the device flow uses none. The only value the extension needs is the OAuth application's client_id, which is a public identifier and is compiled into the extension.

OAuth scope

"Connect GitHub" requests the scopes repo, read:user and read:org.

If you want least privilege instead, skip OAuth and paste a fine-grained Personal Access Token scoped to only the repositories and permissions you choose. Read-only Contents access is enough for the SLOC, TODO and dependency features.

What we do not do

Logs

The backend writes operational logs to Azure Container Apps log analytics. What goes in: one line per request with the HTTP method, the request path (truncated to 256 characters), the status code, the duration and a small set of diagnostic flags about how the backend identified the caller for rate-limiting purposes: how many entries the X-Forwarded-For header carried, whether the last one could be parsed and whether it carried a port, whether the backend fell back to the connection's peer address, and whether the address was IPv4, IPv6 or unparseable. These describe the shape of the address, not the address: no part of it, in any form, is included. They exist so that a misconfigured proxy (which would silently collapse rate limiting into a single shared bucket) is visible to the operator. Also logged: error traces, and, for GitHub webhook deliveries, the event name, action, plan, delivery id, and a one-way hash of the sending and purchasing account logins (SHA-256, truncated to 12 hex characters) so the operator can correlate repeat events without retaining the plaintext username. A webhook delivery that fails its signature check also logs the sender's IP address.

What does not go in: request bodies, access tokens, Anthropic API keys, prompts, AI responses, source code, repository contents, or any plaintext per-user GitHub identifier. Request paths for the endpoints the extension uses contain no repository names: owner and repo travel in the JSON body, which is never logged.

Data retention

The backend caches analysis results so repeated visits do not re-clone. The cache is tiered by sensitivity:

Cloned repositories are deleted immediately after each analysis, on every exit path: success, error, timeout and crash of the request. The clone is written into a scratch directory created owner-readable only. The one case a deletion cannot cover is the whole server process being killed mid-analysis, which leaves a partial checkout on the container's own temporary filesystem; a janitor sweeps that scratch area at startup and every 5 minutes and removes any directory that has been idle for 15 minutes, and that filesystem is discarded when the container is replaced. Clone scratch is not the shared cache volume described above: it is ephemeral, per-instance and never a persistent volume, which is why a repository's files never outlive the analysis that fetched them. Backend logs are retained for up to 30 days for operational diagnostics.

Rate limiting and backend refusals

To keep the backend available, it limits how many requests one caller may make. Two keys are used: your IP address as the hosting platform reports it, and a truncated SHA-256 hash of your token. A third, your numeric GitHub account id, is used only to share the clone slots for the sloc and todos analyses (see section 3 above); it is used instead of the token hash there because one person can create any number of tokens, and counting tokens would let a single account take every slot. All three live only in the running process's memory, in bounded tables that discard the least recently seen entries, and all three disappear when the backend restarts. None of them is written to disk, and none is written to the logs. The one place an IP address is logged is a GitHub webhook delivery that fails its signature check, as described under Logs above. A caller over the limit gets an HTTP 429 with a Retry-After header.

A repository analysis can also be refused for reasons that are about the service's resources or the repository itself rather than about your request rate:

The 503 and 507 carry a Retry-After; the 413 does not, because waiting will not make the repository smaller. In every case the extension falls back to its other line-count sources, subject to the codetabs gate described in section 2, and after a 413 skipping codetabs entirely.

Revoking access and deleting your data

Data protection and how to complain

Knaxly Ltd is a UK company and is the data controller for the processing described on this page. That processing is governed by the UK GDPR and the Data Protection Act 2018.

Where the data sits shapes what a request to us can achieve. Your GitHub token, your Anthropic key, your chat history and the synced visit list are held in your own browser and not on our servers: "Clear all" in the Options page deletes them without us being involved, and we cannot read them in the first place. What the backend holds is set out under Logs and Data retention above, and listed again under Your data protection rights: operational logs, analysis results keyed by commit SHA, and the short-lived in-memory tables described in section 3 and under Rate limiting. There is no Sextant account, no profile and no mailing list.

To exercise a data-protection right, or if you think your data has been mishandled, contact us first through the feedback page. You also have the right to complain to the UK supervisory authority, the Information Commissioner's Office, at ico.org.uk/make-a-complaint.

Lawful basis for processing

The UK GDPR requires a lawful basis for each purpose personal data is processed for. The bases below are matched to the processing described above. Where an activity is not ours at all it is listed at the end rather than given a basis it does not need.

Providing the analyses you asked for: contract

Article 6(1)(b): processing necessary to perform a contract with you, or to take steps at your request before entering one. Installing the extension and sending requests to the backend accepts the terms of service, and the following exists only to deliver what those terms describe:

None of this is optional enrichment. Without your token the repository cannot be resolved or cloned and there is no analysis to return. Removing the token in the Options page, or uninstalling the extension, stops every one of these requests.

Pull-request analysis, which names other people: legitimate interests

Article 6(1)(f). The pr analysis returns the pull request's author and a list of suggested reviewers derived from recent commit history, so it processes personal data about people other than the one asking. For you that processing sits under the contract basis above; for the people named, the basis is the legitimate interest in the analyser producing a useful review suggestion at all, which it cannot do without naming anyone.

Why that does not override their rights: every name is derived from data your own credential can already read on GitHub (the request fails outright if it cannot read the repository), it is recomputed on each request from that repository's own commit history, returned only to you, and never cached (see Data retention). It is not logged, not combined with anything else, not retained once the response is written, and not used to build a profile of anybody.

Keeping the service available and unblocked: legitimate interests

Article 6(1)(f). Stated plainly, the interest is keeping a free service usable by everyone who has installed it, and protecting the egress IP address the backend calls GitHub from against being throttled or blocked because of what one caller does. Both are concrete rather than theoretical: a handful of concurrent requests from a single account can occupy every clone slot in the service, and a stream of invented tokens becomes a stream of rejected credentials presented to api.github.com from one address, which is answered by blocking that address for every user of the service. What is processed to prevent it:

Why this does not override your rights: all of it lives in the running process's memory, in tables with a fixed maximum size, and is discarded when the backend restarts. None of it is written to disk and none of it is written to a log; the single exception is the IP address of a GitHub webhook delivery that fails its signature check, described under Logs. The values are the smallest that do the job: a hash and an opaque integer rather than your name, your login or a stored address. They decide one thing, whether this request proceeds, and are never used to profile you, to target you, or to recognise you anywhere else. The alternative to them is not less processing; it is a service one caller can take away from everybody else.

These controls apply to every request and cannot be switched off for one caller without giving up the protection they exist to provide. If you object to them, the way to stop the processing is to stop sending requests: remove your token, or uninstall the extension. See Your data protection rights.

Operational logs: legitimate interests

Article 6(1)(f). The interest is being able to tell whether the service is working, diagnose faults, notice a misconfigured proxy that would silently collapse rate limiting into one shared bucket, and see abuse for what it is. What goes into a log line, and what is deliberately kept out of it, is set out under Logs above; the records are retained for up to 30 days and contain no request bodies, no credentials, no repository contents and no plaintext per-user identifier: webhook logins are one-way hashed for precisely this reason. Against the interest, that is close to the least an operator can run a service on.

Caching results: legitimate interests

Article 6(1)(f). The interest is not re-cloning and re-scanning the same commit for every visitor: it is the difference between answering in a second and spending minutes of CPU, and it also spares GitHub the repeated fetch. The safeguard is the tiering under Data retention: the persistent disk holds only aggregate counts for public repositories keyed by commit SHA, and an entry records nothing about who asked for it; private-repository aggregates stay in memory for 15 minutes, partitioned by the token hash; and anything that names people or reveals paths in private code is not cached at all.

Webhook deliveries from GitHub: legitimate interests

Article 6(1)(f). Deliveries to the Marketplace webhook are signature-checked and logged as described under Logs: the event name, action, plan, delivery id, and a one-way hash of the sending and purchasing account logins. The interest is operating that listing and being able to correlate repeat deliveries; hashing is what makes that possible without retaining the plaintext usernames.

The AI features: consent

Article 6(1)(a). Everything on the AI path runs on your consent and on nothing else. The features are off by default: the extension sends nothing to api.anthropic.com until you have both ticked "Enable AI" in the Options page and saved your own Anthropic API key, and it re-checks that switch and that key before every AI request. Ticking the box is a specific, informed opt-in to one identified flow of data (the file contents you attach, the pull-request diff when you click "Summarize with AI", the page context attached automatically to a chat message, and your questions) going from your browser to Anthropic under your own Anthropic account. AI features describes that flow in full, including what is attached without being shown to you.

The basis is stated here even though the transfer no longer passes through our infrastructure, because it is still the extension we publish that assembles the prompt and makes the call, and because the disclosure to a third party is the part that matters to you. What consent buys you has not narrowed: it is the same content, going to the same recipient, and it is still yours to withhold.

You can withdraw consent at any time, as easily as you gave it: untick "Enable AI", or delete the key, in the Options page, or use "Clear all", or uninstall the extension. The next AI request is then not sent at all, because the extension checks both before each one. Withdrawal is not retroactive over data already delivered to Anthropic under your key: that is governed by your Anthropic account and its retention settings, which we do not control. Refusing or withdrawing costs you nothing else: every other Sextant feature works with the AI features off, which is what makes this a real choice rather than a condition of using the product.

One thing this consent is not: it is not a consent anyone can give on someone else's behalf. Content you send may contain other people's personal data, such as the logins of people who commented on an issue you are asking about. That content goes from your browser to Anthropic and never to us, so we neither store, cache nor log it; section 3 of the terms of service puts the responsibility for having the right to disclose it on you.

Processing that is not ours

Some of what this policy describes never reaches us, and giving it a lawful basis of ours would misdescribe who is doing it:

Your data protection rights

Under the UK GDPR you have the rights set out below. How far each one reaches depends on what we actually hold, so each says what it means here rather than in the abstract.

What we hold that a request could reach

The rights

How to exercise a right, and why we may not be able to identify you

Make the request through the feedback page, or to the contact address under Company details below. The feedback tracker is public, so do not put a token, an API key or anything else you would not publish into it. We aim to answer within one month, as the UK GDPR requires.

We usually cannot tell which records, if any, are yours. There is no identifier for you anywhere in the design: no account, no user id, no stored login, no stored address. What reaches the backend is a credential we use and discard, and hashes we cannot reverse. That is a real consequence of building it this way and it cuts both ways: very little about you exists to be disclosed or misused, and equally we cannot pick your rows out of a log that does not name you. The UK GDPR does not require us to collect extra information about you purely so that we can answer such a request, and we will not: asking for identity documents in order to search records that contain no names would create more personal data than it could ever return. If you can point at a specific record (a webhook delivery id, or a time window together with the address a failing delivery came from), we will look for it and act on it. Otherwise the honest answer to an access or erasure request is that we hold nothing we can attribute to you.

EU representative (Article 27)

Knaxly Ltd is established in the United Kingdom, which is outside the European Union. Where a controller established outside the EU offers goods or services to people in the EU, or monitors their behaviour in the EU, Article 27 of the EU GDPR may require it to designate a representative established in the EU in writing, and to publish that representative's identity and contact details.

Our assessment is that it does not, because the EU GDPR does not reach Sextant in the first place. To be clear about what that does and does not mean: Knaxly Ltd is subject to the UK GDPR, which applies to everything described in this policy no matter where you are. The narrower question here is whether the EU GDPR also applies, and with it the Article 27 duty.

Being reachable from the EU is not enough on its own: the test is whether the service is directed at people there. Our reasons for concluding it is not, as at the date at the top of this page:

This is a reasoned position rather than a permanent fact, and it is the kind of position that can expire quietly. We will revisit it, and appoint a representative if the answer changes, if Sextant gains a paid plan or any pricing in euros, is offered in an EU language, is marketed in a member state, begins profiling users, or if Knaxly Ltd establishes an entity in the Union.

None of this changes how your data is handled. If you are in the EU and use Sextant, everything in this policy applies to you in the same way, including the retention limits and the rights set out below; those do not depend on this assessment. If you want to raise a concern, the feedback page reaches us directly, and nothing here is intended to discourage you from also approaching your own national data-protection authority or the UK Information Commissioner's Office.

Visiting these pages

Everything above is about the extension and the backend. This section is about the five pages at knaxly.dev/sextant themselves, which are what you are reading now.

They are static files with no server of ours behind them. They set no cookies, run no analytics and no tracking script, and have no form: the feedback page routes you to GitHub rather than collecting anything here. We receive nothing when you read them, and we keep no record that you did.

Two parties necessarily see the request, because something has to serve the file: GitHub, which hosts the pages, and Cloudflare, which fronts the domain. Serving a page means receiving the IP address and user agent it was requested with, and both operate their own logs on their own terms, as processors for the hosting we buy from them. Neither is asked for anything beyond delivering the file.

Three of the five pages (this policy's overview, the beta page and the feedback page) load webfonts from Google Fonts, so Google receives the IP address and user agent of those font requests. Each of those pages says so in its own footer. This page and the terms load nothing external at all, which is why the note at the bottom of both says so.

Children

Sextant is a developer tool and is not directed at children under 13. We do not knowingly collect data from them.

Changes to this policy

The "Last updated" date at the top of this page reflects the most recent revision. Material changes to what is collected or where it is sent will be announced on the Sextant page at knaxly.dev/sextant.

Contact

Operator and data controller: Knaxly Ltd.

Company details

Sextant is operated by Knaxly Ltd, a privately owned company registered in the United Kingdom.