Is Crystal safe?
Crystal Knows is medium risk. Visiting a LinkedIn profile with Crystal installed fetches DOM selectors from an S3 config and api.crystalknows.com/v3/selectors, both returning HTTP 200 in testing. Selectors are remote, so Crystal can change them without an update.
Who publishes itCrystal Project Inc - no other listings under this identity, 1 shared hostname
Crystal Project Inc - no other listings under this identity, 1 shared hostname
What this publisher told the store about itself, and the other listings that told it the same thing.
Shared hosts - 1 hostname
Hostnames hardcoded in this extension that few other listings call. That can mean one operator behind both, and it can equally mean a small shared vendor, so it is context rather than a conclusion. Hosts that many listings call are left out: they are services, not connections.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Remote-controlled CSS selectors determine what Crystal reads from LinkedIn
Visiting a LinkedIn profile with Crystal installed fetches DOM selectors from an S3 config and api.crystalknows.com/v3/selectors, both returning HTTP 200 in testing.
Selectors are remote, so Crystal can change them without an update.
You navigate to any page on linkedin.com with Crystal installed.
Crystal fetches its CSS selector configuration from Crystal-controlled servers before reading your LinkedIn page.
The extension asks two remote endpoints which DOM elements to target, meaning the server can change what the extension reads from LinkedIn pages without pushing an extension update.
| Origin | chrome-extension://nmaonghoefpmlfgaknnboiekjhfpmajh |
| Field | Value | Why it matters | |
|---|---|---|---|
linkedInInlineSelector | .msg-convo-wrapper.msg-thread.msg-thread--pillar.relative.display-flex | CSS selectors that identify the LinkedIn inline messaging thread, used to attach Crystal's email coach to messages you write. | |
linkedInEditableDivSelector | .msg-form__contenteditable.t-14.t-black--light.t-normal.flex-grow-1.full-height.notranslate | CSS selector for the editable compose field in LinkedIn messages, where Crystal intercepts your typed text. | |
linkedInPopupSelector | .msg-convo-wrapper.msg-overlay-conversation-bubble.msg-overlay-conversation-bubble--default-inactive.ml4 | CSS selector for floating LinkedIn message bubbles, lets Crystal attach its email coach to pop-out conversations. | |
inlineUrlContainer | .msg-thread__link-preview-container a | Selector for a URL-bearing element in LinkedIn's inline view, used to detect the current profile context. |
Extension source showing how remote selectors are fetched and applied
// Built into the extension — applied only when the remote fetch throws
const FALLBACK_WA_SELECTORS = {
linkedInInlineSelector: [".msg-convo-wrapper.msg-thread..."],
linkedInEditableDivSelector: [".msg-form__contenteditable..."],
linkedInPopupSelector: [".msg-convo-wrapper.msg-overlay-conversation-bubble..."]
};
this.waSelectors = FALLBACK_WA_SELECTORS; // overwritten on initialize()async fetchWASelectors() {
try {
const resp = await fetch(
`https://crystal-cdn.s3.amazonaws.com/linkedin_wa/config.json?v=${Date.now()}`
);
// Returns { elements: { linkedInInlineSelector: [...], linkedInEditableDivSelector: [...], ... } }
return (await resp.json()).elements;
} catch {
return false; // falls back to hardcoded JF selectors
}
}// Content script sends a message; background SW makes the authenticated API call
const fetchProfileSelectors = () =>
new Promise(async resolve => {
chrome.runtime.sendMessage({ type: 'GET_SELECTORS', appName: ..., selectorClass: ... }, result => {
resolve(result);
});
});// Handles the GET_SELECTORS message from the content script
case 'GET_SELECTORS':
return getAuthTokens().then(tokens => {
buildApiClient(tokens).get('/selectors')
.then(r => r.json())
.then(data => respond(data))
.catch(() => respond(null));
}), true;this.scrape = async callback => {
const profileElements = await this.getProfileElements();
if (!this.getProfileQuery(profileElements)) return false;
let scraper;
if (isSalesNavigatorProfile()) {
// Fetches linkedin/config.json from S3 for Sales Navigator selectors
this.linkedinScrapeConfig = await this.fetchLinkedinSelectors();
scraper = new SalesNavigatorScraper(this.linkedinScrapeConfig.salesNavigator);
} else if (this.isRecruiterProfile()) {
scraper = new RecruiterScraper();
} else if (isTalentProfile()) {
scraper = new TalentScraper();
} else {
// Uses this.selectors — the remotely-fetched set from /v3/selectors
scraper = new StandardScraper(this.selectors);
}
const scraped = await scraper.scrape();
// Sends result to crystalknows.com API for personality prediction
chrome.runtime.sendMessage(buildProfileQuery(scraped));
};- crystal-cdn.s3.amazonaws.com
AWS S3 bucket operated by Crystal. Serves linkedin/config.json (Sales Navigator selectors) and linkedin_wa/config.json (messaging UI selectors) as unauthenticated JSON files.
- api.crystalknows.com
Crystal's authenticated REST API. Serves /v3/selectors (profile-scraping set) and /manage/selectors (per-app set) to the background worker using the user's session token.