Is daily.dev | Where developers discover what's next safe?

Medium risk

daily.dev is medium risk. Starting with v3.43, granting daily.dev's optional embedded-browsing permission installs a rule removing X-Frame-Options and CSP headers from every iframe on your tab, letting daily.dev embed sites that configured servers to refuse framing.…

Daily Dev Ltd.v3.47.3Chrome Web Store
49Risk
Who publishes it

Daily Dev Ltd. - 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.

Publisher
Daily Dev Ltd.
Declared legal entity
Daily Dev Ltd.
Registered address
Derech HaTikva 9, Ganei Tikva 5591252, IL
Registered contact
Nimrod Kramer

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.

api.daily.dev
Also called by 2 other listings, including AI Personal Assistants - Get More Done

AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.

Publishers can request a review.

Findings

Extension Strips Framing Defenses to Embed Blocked Third-Party Sites

Starting with v3.43, granting daily.dev's optional embedded-browsing permission installs a rule removing X-Frame-Options and CSP headers from every iframe on your tab, letting daily.dev embed sites that configured servers to refuse framing.

Severity
Medium unwanted
Type
Unexpected
CWE
CWE-829
Source
Dynamic sandbox
What actually happens
You did this

You grant the embedded-browsing permission when prompted by the daily.dev interface.

The permission dialog requests declarativeNetRequestWithHostAccess and access to all URLs (*://*/*), which gives the extension the ability to modify network responses on any site.

The extension did this

The extension's background service worker installs a Chrome session rule that removes X-Frame-Options and Content-Security-Policy from every iframe response on your current tab.

The rule uses Chrome's declarativeNetRequest API and targets all sub-frame resource types, matching every HTTP and HTTPS URL on the affected tab for the remainder of the browser session.

The code that does this

Header array and DNR session rule, minified source vs. readable form

Readable version

Security header array

js/background.bundle.js
// Headers removed from every sub-frame response on the active tabvar STRIPPED_HEADERS = ["X-Frame-Options", "Content-Security-Policy"];

Session rule installation

js/background.bundle.js
// Called when background receives ENABLE_FRAME_EMBEDDING_FOR_TAB messagechrome.declarativeNetRequest.updateSessionRules({  removeRuleIds: [ruleId],       // remove any prior rule for this tab  addRules: [{    id: ruleId,                  // 1000000 + tabId — unique per tab    priority: 1,    condition: {      tabIds: [tabId],           // only the tab that triggered frame.html      resourceTypes: ["sub_frame"],  // all iframe responses on that tab      regexFilter: "^https?://"      // matches every HTTP/HTTPS URL    },    action: {      type: "modifyHeaders",      responseHeaders: STRIPPED_HEADERS.map(header => ({        header: header,        operation: "remove"  // strips the header before the browser evaluates it      }))    }  }]});
Security headers removed from sub-frame responses
  • X-Frame-Options
    X-Frame-Options: DENY

    Tells the browser not to embed this page in a frame elsewhere. DENY or SAMEORIGIN is a site's explicit instruction not to be framed.

  • CSP (frame-ancestors)
    Content-Security-Policy: frame-ancestors 'none'

    Specifies which origins may embed this page. Removing it bypasses the allowlist, including frame-ancestors 'none', prohibiting all framing.

Check if you're affected

Queries the daily.dev extension's active declarativeNetRequest session rules to determine whether the header-stripping frame-embedding rule is currently installed on any tab.

Requires
  • Google Chrome with daily.dev extension installed
  • Developer mode enabled in chrome://extensions
detect_frame_embedding_rule.js · js
// Paste this script into the daily.dev service worker DevTools console.// Access it via chrome://extensions → daily.dev → Service Worker → Inspect → Console.chrome.declarativeNetRequest.getSessionRules(rules => {  const frameRules = rules.filter(r =>    r.action.type === 'modifyHeaders' &&    Array.isArray(r.condition.resourceTypes) &&    r.condition.resourceTypes.includes('sub_frame') &&    Array.isArray(r.action.responseHeaders) &&    r.action.responseHeaders.some(h => h.operation === 'remove')  );  if (frameRules.length === 0) {    console.log('No frame-embedding header-strip rules are currently active.');    return;  }  console.log(`${frameRules.length} frame-embedding rule(s) active:`);  frameRules.forEach(rule => {    const tabIds = rule.condition.tabIds ?? ['any'];    const headers = rule.action.responseHeaders      .filter(h => h.operation === 'remove')      .map(h => h.header);    console.log(`  Tab ${tabIds.join(',')}: removes [${headers.join(', ')}]`);    console.log('  Full rule:', JSON.stringify(rule, null, 2));  });});
How to run it
  1. 1Open chrome://extensions, enable Developer mode.
  2. 2Find daily.dev, click 'Service Worker'.
  3. 3Select Console, paste the script.
  4. 4Grant frame-embedding on a daily.dev tab to see a rule appear (embedded browsing must trigger first).

Imported bookmark URLs go into daily.dev settings

Using daily.dev's bookmark import with the bookmarks permission, it reads bookmark-bar entries and writes URL/title into daily.dev's settings via UpdateUserSettings.

The signed-in import UI wasn't reached, so no body was captured.

Severity
Medium unwanted
Type
Unexpected
CWE
CWE-359
Source
Dynamic sandbox
What actually happens
You did this

You choose to import bookmarks and approve Chrome's permission prompt.

The flow starts from the daily.dev shortcuts import UI, not from normal browsing.

The extension did this

The extension reads bookmark-bar entries and prepares selected URLs and titles for daily.dev settings.

The shipped source maps bookmark entries into objects with `url` and `title`, then passes selected items into the shortcut import path.

Bookmark data preserved by the import path
  • Bookmark URL
    https://developer.chrome.com/docs/extensions/reference/api/bookmarks (illustrative)

    The saved web address you selected for import. It can reveal work tools, documents, dashboards, or private pages you keep in bookmarks.

  • Bookmark title
    Chrome Extensions Bookmarks API (illustrative)

    Shows the human-readable name saved with that bookmark. Titles can add context even when the URL is not self-explanatory.

  • Selected item count
    3 selected bookmarks (illustrative)

    Shows how many chosen bookmarks move into the shortcut settings update in one import action.

The code that does this

Shipped code path from permission to settings upload

Readable version

Bookmark permission notice and button

js/newtab~43.bundle.js
function tw(e) {  var t = e.onGrant,    n = e.onClose;  return (0, i.jsxs)(tg.aF, {    kind: tg.aF.Kind.FlexibleCenter,    size: tg.aF.Size.Medium,    isOpen: !0,    onRequestClose: n,    children: [(0, i.jsx)(tg.aF.Header, {}), (0, i.jsxs)(tg.aF.Body, {      children: [(0, i.jsx)(tg.aF.Title, {        className: "mb-4",        children: "Import your bookmarks bar"      }), (0, i.jsx)(tg.aF.Text, {        className: "text-center",        children: "To import your bookmarks bar, your browser will ask for permission to read bookmarks. We never sync your bookmarks to our servers."      })]    }), (0, i.jsx)(tg.aF.Footer, {      justify: ey.F1.Center,      children: (0, i.jsx)(eR.$n, {        type: "button",        onClick: t,        variant: eU.Ak.Primary,        children: "Import bookmarks"      })    })]  })}

Bookmark entries become URL/title objects

js/newtab~28.bundle.js
var y=["1","toolbar_____"],g=["Bookmarks bar","Bookmarks Toolbar"],x=function(e){return e?e.reduce(function(e,t){return e?e:y.includes(t.id)||t.title&&g.includes(t.title)?t:x(t.children)},null):null},k=function(e){var t,r=[],n=function(e,t){e.forEach(function(e){if(e.url)return void r.push({title:e.title||e.url,url:e.url});if(0===t){var o;(null==(o=e.children)?void 0:o.length)&&n(e.children,t+1)}})};return n(null!=(t=e.children)?t:[],0),r},w=function(){var e=v((0,n.useState)(),2),t=e[0],o=e[1],i=v((0,n.useState)(),2),u=i[0],l=i[1],c=v((0,n.useState)(!1),2),s=c[0],f=c[1],d=(0,n.useCallback)(function(){return h(function(){var e;return m(this,function(r){switch(r.label){case 0:if(!(null==t?void 0:t.bookmarks))return l(void 0),f(!0),[2];r.label=1;case 1:return r.trys.push([1,3,,4]),[4,t.bookmarks.getTree()];case 2:return(e=x(r.sent()))?l(k(e)):l([]),[3,4];case 3:return r.sent(),l(void 0),[3,4];case 4:return f(!0),[2]}})})()},[t]),p=(0,n.useCallback)(function(){return h(function(){var e;return m(this,function(r){switch(r.label){case 0:if(!t)return[2,!1];return[4,t.permissions.request({permissions:["bookmarks"]})];case 1:if(!(e=r.sent()))return[3,3];return[4,d()];case 2:r.sent(),r.label=3;case 3:return[2,e]}})})()},[t,d]),b=(0,n.useCallback)(function(){return h(function(){return m(this,function(e){switch(e.label){case 0:if(!t)return[2];return[4,t.permissions.remove({permissions:["bookmarks"]})];case 1:return e.sent(),l(void 0),[2]}})})()},[t]);return(0,n.useEffect)(function(){(0,a.oX)()&&(t?d():Promise.resolve().then(r.t.bind(r,83346,23)).then(function(e){return o(e.default)}))},[t,d]),(0,n.useMemo)(function(){return{bookmarks:u,hasCheckedPermission:s,askBookmarksPermission:p,revokeBookmarksPermission:b}},[u,s,p,b])}

Import stores names beside custom links

js/newtab~28.bundle.js
importFrom:(0,n.useCallback)(function(e,t){return b(function(){var r,n,o,a,i,s;return y(this,function(d){switch(d.label){case 0:if((r=c.BA-j.length)<=0)return[2,{imported:0,skipped:t.length}];if(n=new Set(A.keys()),o=[],a={},i=0,t.forEach(function(e){if(o.length>=r){i+=1;return}var t=(0,l.yJ)(e.url),u=(0,f.a)(t);if(!u||n.has(u)){i+=1;return}n.add(u),o.push(t),e.title&&(a[t]={name:e.title})}),!o.length)return null==k||k(null),[2,{imported:0,skipped:i}];return[4,D(v(j).concat(v(o)),h({},w,a))];case 1:return d.sent(),s="bookmarks"===e?u.$B.Bookmarks:u.$B.Browser,T(u.VB.ImportShortcuts,{source:s,count:o.length}),null==k||k(null),[2,{imported:o.length,skipped:i}]}})})()},[A,j,w,D,k,T])

Custom links are sent through UpdateUserSettings

js/newtab~25.bundle.js
function x() {  var e, t, r = (e = ["\n  mutation UpdateUserSettings($data: UpdateSettingsInput!) {\n    updateUserSettings(data: $data) {\n      updatedAt\n    }\n  }\n"], t || (t = e.slice(0)), Object.freeze(Object.defineProperties(e, {    raw: {      value: Object.freeze(t)    }  })));  return x = function() {    return r  }, r}var O = (0, E.gql)(x());var C = (0, i.n)({  mutationFn: function(t) {    return v.e.request(c.S2, {      data: t    })  },  onError: function(t, e) {    var r = Object.keys(e).reduce(function(t, e) {      return S(O({}, t), h({}, e, n[e]))    }, {});    o(O({}, n, r))  }}).mutateAsync;updateCustomLinks: function(t) {  return T(S(O({}, n), {    customLinks: t  }))}
Captured request
POSThttps://api.daily.dev/graphql

No request body was recorded during dynamic analysis; the POST endpoint and mutation are identified from shipped source.

Destination named by the settings mutation path
    • api.daily.dev

    daily.dev GraphQL API that receives settings updates, including the customLinks array populated by selected imported bookmarks.

Updated 30 September 2026jlmpjdjjbgclbocgajdjefcidcncaied