this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Refactoring Privacy & Security, prep for 1.4.0

+19 -153
+5 -1
CHANGELOG.md
··· 9 9 10 10 ## [Unreleased] 11 11 12 - - Input validation for domains and DIDs (security enhancement) 12 + - Migrate/refactor everything from `content.js` into a self-contained `background.js` 13 + - Swap `activeTab` permission for `tabs` so that we can drop the `<all_urls>` permission 13 14 - Remove permissions for "management" as it is not needed 15 + - Migrate Firefox to Manifest v3 (but this means the minimum FF version is now 109) 16 + - Input validation for domains and DIDs (security enhancement) 17 + - Replace `staging.bsky.app` with `bsky.app` 14 18 15 19 ## [1.3.0] 16 20
+4 -4
README.md
··· 15 15 16 16 --- 17 17 18 - As of April 30, 2023, Bluesky is still invite-only and the web app is at https://staging.bsky.app. 18 + As of May 17, 2023, Bluesky is still invite-only and the web app is at https://bsky.app. 19 19 20 - You can find me there at [@adhdjesse.com](https://staging.bsky.app/profile/adhdjesse.com) 20 + You can find me there at [@adhdjesse.com](https://bsky.app/profile/adhdjesse.com) 21 21 22 22 **Contributors:** 23 23 24 - - [@danielhuckmann.com](https://staging.bsky.app/profile/danielhuckmann.com) - Firefox Support & Privacy Consent 25 - - [@aliceisjustplaying](https://staging.bsky.app/profile/alice.bsky.sh) - HTTPS Method of DID Detection 24 + - [@danielhuckmann.com](https://bsky.app/profile/danielhuckmann.com) - Firefox Support, Privacy & Security Enhancements 25 + - [@aliceisjustplaying](https://bsky.app/profile/alice.bsky.sh) - HTTPS Method of DID Detection
+1 -5
background.js
··· 4 4 const tabs = typeof browser !== "undefined" ? browser.tabs : chrome.tabs 5 5 const storage = 6 6 typeof browser !== "undefined" ? browser.storage.local : chrome.storage.local 7 - const action = 8 - typeof browser !== "undefined" ? browser.browserAction : chrome.action 9 - 10 - // The rest of the original background.js code... 7 + const action = typeof browser !== "undefined" ? browser.action : chrome.action 11 8 12 - // Content.js code migrated to background.js 13 9 // Make sure that we don't DoS the regex if someone supplies too large of a DID 14 10 const MAX_DID_LENGTH = 255 15 11
-130
content.js
··· 1 - // Set up cross-browser compatibility 2 - const runtime = 3 - typeof browser !== "undefined" ? browser.runtime : chrome.runtime 4 - const storage = 5 - typeof browser !== "undefined" ? browser.storage.local : chrome.storage.local 6 - 7 - // Make sure that we don't DoS the regex if someone supplies too large of a DID 8 - const MAX_DID_LENGTH = 255 9 - 10 - // Regular expression to validate the DID format 11 - // https://w3c.github.io/did-core/#did-syntax 12 - const didRegex = 13 - /^did:plc:([a-zA-Z0-9._-]+(:[a-zA-Z0-9._-]+)*|((%[0-9A-Fa-f]{2})|[a-zA-Z0-9._-])+(:((%[0-9A-Fa-f]{2})|[a-zA-Z0-9._-])+)*$)/ 14 - 15 - // Function to validate the DID string 16 - function isValidDID(didString) { 17 - return didString.length <= MAX_DID_LENGTH && didRegex.test(didString) 18 - } 19 - 20 - // Function to get the domain name from the current hostname 21 - function getDomainName() { 22 - const hostname = window.location.hostname 23 - return hostname.replace(/^www\./, "") 24 - } 25 - 26 - // Function to validate the domain name 27 - function isValidDomain(domain) { 28 - const MAX_DOMAIN_LENGTH = 255 29 - 30 - if (domain.length > MAX_DOMAIN_LENGTH) { 31 - return false 32 - } 33 - 34 - try { 35 - // Use the build in URL constructor to validate the URL, if doesn't throw an error, the domain is valid 36 - // This is a better choice than a regex since it should properly support punycode/international domains 37 - new URL(`https://${domain}`) 38 - return true 39 - } catch (error) { 40 - // The URL constructor threw an error, so the domain is not valid 41 - return false 42 - } 43 - } 44 - 45 - // Function to check for a DID in the domain's TXT records 46 - async function checkForDIDDNS(domain) { 47 - try { 48 - const response = await fetch( 49 - `https://dns.google/resolve?name=_atproto.${domain}&type=TXT` 50 - ) 51 - const data = await response.json() 52 - 53 - // We use the TXT record type to avoid CORS issues 54 - const records = data?.Answer?.filter((record) => record.type === 16) || [] 55 - 56 - // We filter out all records that are not TXT records 57 - const didRecord = records.find((record) => 58 - record.data.includes("did=did:plc:") 59 - ) 60 - 61 - // We return the DID if we found one and it's valid 62 - return didRecord && isValidDID(didRecord.data.replace("did=", "")) 63 - ? didRecord.data.replace("did=", "") 64 - : null 65 - } catch (error) { 66 - return null 67 - } 68 - } 69 - 70 - // Function to check for a DID in the well-known (not .well-known) location 71 - async function checkForDIDHTTPS(domain) { 72 - try { 73 - const response = await fetch( 74 - `https://${domain}/xrpc/com.atproto.identity.resolveHandle` 75 - ) 76 - 77 - if (!response.headers.get("Content-Type")?.includes("application/json")) { 78 - throw new Error("Invalid Content-Type") 79 - } 80 - 81 - const data = await response.json() 82 - return data.did && isValidDID(data.did) ? data.did : null 83 - } catch (error) { 84 - return null 85 - } 86 - } 87 - 88 - // Main function to perform actions, but only if the privacy consent has been accepted 89 - function performAction(privacyConsentAccepted) { 90 - // If the user has accepted the privacy consent 91 - if (privacyConsentAccepted) { 92 - // We check for a DID on the current domain 93 - ;(async function () { 94 - const domain = getDomainName() 95 - if (isValidDomain(domain)) { 96 - const domainDID = await checkForDIDDNS(domain) 97 - const httpsDID = await checkForDIDHTTPS(domain) 98 - 99 - if (domainDID) { 100 - runtime.sendMessage({ type: "DID_FOUND", did: domainDID }) 101 - } else if (httpsDID) { 102 - runtime.sendMessage({ type: "DID_FOUND", did: httpsDID }) 103 - } else { 104 - runtime.sendMessage({ type: "DID_NOT_FOUND" }) 105 - } 106 - } 107 - })() 108 - 109 - // We listen for messages from the background script 110 - runtime.onMessage.addListener((message, sender, sendResponse) => { 111 - if (message.type === "GET_DID") { 112 - const domain = getDomainName() 113 - if (isValidDomain(domain)) { 114 - checkForDIDDNS(domain) 115 - .then((did) => sendResponse({ did })) 116 - .catch(() => sendResponse({ did: null })) 117 - return true // Indicate that the response will be sent asynchronously. 118 - } 119 - } 120 - }) 121 - } else { 122 - // Do nothing since the consent form has not been accepted. 123 - return 124 - } 125 - } 126 - 127 - // Get the user's privacy consent from the storage and perform actions accordingly 128 - storage.get("privacyConsentAccepted", ({ privacyConsentAccepted }) => { 129 - performAction(privacyConsentAccepted) 130 - })
+8 -12
manifest-firefox.json
··· 1 1 { 2 - "manifest_version": 2, 2 + "manifest_version": 3, 3 3 "name": "SkyLink - Bluesky DID Detector", 4 4 "short_name": "SkyLink", 5 - "version": "1.3.0", 5 + "version": "1.4.0", 6 6 "author": "jesse@adhdjesse.com", 7 - "browser_action": { 7 + "action": { 8 8 "default_icon": { 9 9 "48": "logo48_gray.png", 10 10 "128": "logo128_gray.png" ··· 15 15 "128": "logo128.png" 16 16 }, 17 17 "description": "Detects Decentralized Identifiers (DIDs) in a domain's TXT records and links to the associated Bluesky profile.", 18 - "permissions": ["activeTab", "storage", "<all_urls>"], 19 - "content_security_policy": "script-src 'self'; object-src 'self'", 18 + "permissions": ["tabs", "storage"], 19 + "content_security_policy": { 20 + "extension_pages": "script-src 'self'; object-src 'self'" 21 + }, 20 22 "background": { 21 23 "scripts": ["background.js"] 22 24 }, 23 - "content_scripts": [ 24 - { 25 - "matches": ["<all_urls>"], 26 - "js": ["content.js"] 27 - } 28 - ], 29 25 "browser_specific_settings": { 30 26 "gecko": { 31 27 "id": "jesse@adhdjesse.com", 32 - "strict_min_version": "79.0" 28 + "strict_min_version": "109.0" 33 29 } 34 30 } 35 31 }
+1 -1
manifest.json
··· 2 2 "manifest_version": 3, 3 3 "name": "SkyLink - Bluesky DID Detector", 4 4 "short_name": "SkyLink", 5 - "version": "1.3.0", 5 + "version": "1.4.0", 6 6 "author": "jesse@adhdjesse.com", 7 7 "action": { 8 8 "default_icon": { "48": "logo48_gray.png", "128": "logo128_gray.png" }