Is daily.dev | Where developers discover what's next safe?
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.…
Who publishes itDaily Dev Ltd. - no other listings under this identity, 1 shared hostname
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.
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
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
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'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.
Header array and DNR session rule, minified source vs. readable form
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 })) } }]});- X-Frame-OptionsX-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.
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.
- Google Chrome with daily.dev extension installed
- Developer mode enabled in chrome://extensions
// 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)); });});- 1Open chrome://extensions, enable Developer mode.
- 2Find daily.dev, click 'Service Worker'.
- 3Select Console, paste the script.
- 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
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 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 URLhttps://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 titleChrome 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 count3 selected bookmarks (illustrative)
Shows how many chosen bookmarks move into the shortcut settings update in one import action.
Shipped code path from permission to settings upload
Bookmark permission notice and button
js/newtab~43.bundle.jsfunction 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.jsvar 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.jsimportFrom:(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.jsfunction 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 }))}No request body was recorded during dynamic analysis; the POST endpoint and mutation are identified from shipped source.
- api.daily.dev
daily.dev GraphQL API that receives settings updates, including the customLinks array populated by selected imported bookmarks.