From 64ed20bc886b4f31bc3ce77060956db236be2218 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 26 Mar 2026 22:50:17 +0000 Subject: [PATCH 1/3] chore: sync all versions to 2.0.0 + fix sign script Set Chrome, Firefox, and package.json all to version 2.0.0 (major bump reflecting encrypted storage, document scanning, org/team features, sync improvements). Sign script rewritten: - Tries current version first instead of always bumping - Only bumps on "version already exists" errors - Handles rate limiting by parsing throttle duration from error and waiting the exact time (not blindly retrying) - Only updates source files on successful signing (not before) - Reduced max attempts to 5 (with proper backoff, shouldn't need more) - Commits version bump only after successful sign https://claude.ai/code/session_01SWSwDfMVij53bCTNSCLMwn --- manifest.firefox.json | 2 +- manifest.json | 2 +- package.json | 2 +- sign-firefox.sh | 96 +++++++++++++++++++++++++--------------- src/options/options.html | 2 +- 5 files changed, 64 insertions(+), 40 deletions(-) diff --git a/manifest.firefox.json b/manifest.firefox.json index 2bafc34..4242f80 100644 --- a/manifest.firefox.json +++ b/manifest.firefox.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Silent Send", - "version": "0.3.1", + "version": "2.0.0", "description": "Intercepts personal info and substitutes it with user-defined replacements before sending to AI services.", "browser_specific_settings": { "gecko": { diff --git a/manifest.json b/manifest.json index 5f069a4..efe7a11 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Silent Send", - "version": "0.3.1", + "version": "2.0.0", "description": "Intercepts personal info and substitutes it with user-defined replacements before sending to AI services.", "permissions": [ "storage", diff --git a/package.json b/package.json index 4ded9a7..74b78c5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "silent-send", - "version": "0.3.1", + "version": "2.0.0", "private": true, "license": "BSL-1.1", "description": "Browser extension that substitutes personal data before sending to AI services", diff --git a/sign-firefox.sh b/sign-firefox.sh index f86ce84..27b7efe 100755 --- a/sign-firefox.sh +++ b/sign-firefox.sh @@ -1,7 +1,9 @@ #!/bin/bash # # Sign the Firefox extension using Mozilla's API. -# Auto-bumps the patch version to avoid "version already exists" conflicts. +# Tries the current version first. Only bumps if that version +# already exists at Mozilla. Handles rate limiting with backoff. +# # Reads credentials from .env file. # @@ -20,25 +22,12 @@ if [ ! -f "$ENV_FILE" ]; then exit 1 fi -# --- Auto-bump version — always unique, no metadata --- -# Reads current version, increments patch. If already signed, -# keeps incrementing until it works. +# --- Read current version (don't bump yet — try current first) --- CURRENT_VERSION=$(grep -o '"version": "[^"]*"' "$MANIFEST" | head -1 | grep -o '[0-9.]*') IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION" -PATCH=$((PATCH + 1)) -NEW_VERSION="$MAJOR.$MINOR.$PATCH" +NEW_VERSION="$CURRENT_VERSION" -echo "Version: $CURRENT_VERSION → $NEW_VERSION" - -# Update all version references (only top-level "version", not "manifest_version") -sed -i "s/^ \"version\": \"$CURRENT_VERSION\"/ \"version\": \"$NEW_VERSION\"/" "$MANIFEST" -sed -i "s/^ \"version\": \"$CURRENT_VERSION\"/ \"version\": \"$NEW_VERSION\"/" "$MANIFEST_CHROME" -sed -i "s/^ \"version\": \"$CURRENT_VERSION\"/ \"version\": \"$NEW_VERSION\"/" "$PACKAGE_JSON" - -# Commit the version bump -cd "$SCRIPT_DIR" -git add manifest.json manifest.firefox.json package.json 2>/dev/null -git commit -m "chore: auto-bump version to $NEW_VERSION for Firefox signing" --allow-empty 2>/dev/null || true +echo "Current version: $CURRENT_VERSION" # --- Parse .env --- API_KEY="" @@ -88,38 +77,73 @@ echo "" echo "Building Firefox extension..." "$SCRIPT_DIR/build.sh" firefox -# Sign — retry with incremented patch if version conflict -MAX_ATTEMPTS=10 -for attempt in $(seq 1 $MAX_ATTEMPTS); do - echo "Signing v$NEW_VERSION with Mozilla (attempt $attempt)..." +# --- Sign with retry --- +MAX_ATTEMPTS=5 +WAIT_TIME=10 - if npx web-ext sign \ +for attempt in $(seq 1 $MAX_ATTEMPTS); do + echo "" + echo "=== Attempt $attempt: signing v$NEW_VERSION ===" + + # Capture output to check for specific errors + OUTPUT=$(npx web-ext sign \ --no-config-discovery \ --source-dir "$SCRIPT_DIR/dist/firefox" \ --artifacts-dir "$SCRIPT_DIR/dist/firefox-signed" \ --channel unlisted \ --api-key "$API_KEY" \ - --api-secret "$API_SECRET" 2>&1; then - + --api-secret "$API_SECRET" 2>&1) && { + echo "$OUTPUT" echo "" - echo "Done! v$NEW_VERSION signed." - echo "Install the .xpi file from dist/firefox-signed/" - echo "Drag it into Firefox or use File → Open File." + echo "Success! v$NEW_VERSION signed." + echo "Install: dist/firefox-signed/" + + # Update source files to match the signed version + sed -i "s/^ \"version\": \"[^\"]*\"/ \"version\": \"$NEW_VERSION\"/" "$MANIFEST" + sed -i "s/^ \"version\": \"[^\"]*\"/ \"version\": \"$NEW_VERSION\"/" "$MANIFEST_CHROME" + sed -i "s/^ \"version\": \"[^\"]*\"/ \"version\": \"$NEW_VERSION\"/" "$PACKAGE_JSON" + + cd "$SCRIPT_DIR" + git add manifest.json manifest.firefox.json package.json 2>/dev/null + git commit -m "chore: release v$NEW_VERSION (Firefox signed)" --allow-empty 2>/dev/null || true + exit 0 + } + + echo "$OUTPUT" + + # Check if rate limited + if echo "$OUTPUT" | grep -q "throttled"; then + # Extract wait time from error message + THROTTLE_SECS=$(echo "$OUTPUT" | grep -oP 'available in \K\d+' || echo "60") + echo "" + echo "Rate limited by Mozilla. Waiting ${THROTTLE_SECS}s..." + sleep "$THROTTLE_SECS" + # Don't bump version — retry the same version after cooldown + continue fi - # If it failed due to version conflict, bump and rebuild - echo "Version $NEW_VERSION already exists, trying next..." - PATCH=$((PATCH + 1)) - NEW_VERSION="$MAJOR.$MINOR.$PATCH" + # Check if version already exists + if echo "$OUTPUT" | grep -qi "already exists\|version.*conflict\|could not be uploaded"; then + PATCH=$((PATCH + 1)) + NEW_VERSION="$MAJOR.$MINOR.$PATCH" + echo "" + echo "Version conflict. Bumping to $NEW_VERSION..." - # Only replace the top-level "version" field, not "manifest_version" - sed -i "s/^ \"version\": \"[^\"]*\"/ \"version\": \"$NEW_VERSION\"/" "$SCRIPT_DIR/dist/firefox/manifest.json" + # Update only the built manifest (not source — we'll update source on success) + sed -i "s/^ \"version\": \"[^\"]*\"/ \"version\": \"$NEW_VERSION\"/" "$SCRIPT_DIR/dist/firefox/manifest.json" - # Wait before retrying to avoid Mozilla rate limiting - echo "Waiting 8 seconds before retry..." - sleep 8 + sleep "$WAIT_TIME" + continue + fi + + # Unknown error — wait and retry + echo "" + echo "Unknown error. Waiting ${WAIT_TIME}s before retry..." + sleep "$WAIT_TIME" done +echo "" echo "Error: Failed after $MAX_ATTEMPTS attempts." +echo "If rate limited, wait a few minutes and try again." exit 1 diff --git a/src/options/options.html b/src/options/options.html index c4dc127..ad41502 100644 --- a/src/options/options.html +++ b/src/options/options.html @@ -589,7 +589,7 @@ From f34e3b8eeecc384a654f3165b2683aefefac9a96 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 26 Mar 2026 23:39:06 +0000 Subject: [PATCH 2/3] docs: add privacy policy for AMO listing Required for Firefox Add-ons (AMO) public listing submission. Documents that all processing is 100% local, no data collection, no servers, no analytics. Explains each permission and all optional network requests (sync, org policy). https://claude.ai/code/session_01SWSwDfMVij53bCTNSCLMwn --- PRIVACY.md | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 PRIVACY.md diff --git a/PRIVACY.md b/PRIVACY.md new file mode 100644 index 0000000..c0c6e9f --- /dev/null +++ b/PRIVACY.md @@ -0,0 +1,67 @@ +# Privacy Policy — Silent Send + +**Last updated:** March 26, 2026 + +## Summary + +Silent Send does not collect, transmit, or store any data externally. All processing happens 100% locally in your browser. There are no servers, no analytics, no tracking, and no telemetry of any kind. + +## What data Silent Send accesses + +Silent Send accesses the following data **only within your browser** to perform its core function (substituting personal information before it reaches AI services): + +- **Text you type** in AI chat interfaces (Claude, ChatGPT, Grok, Gemini, etc.) — scanned for configured personal information and substituted before sending +- **Files you upload** to AI services — text is extracted and scanned for personal information before upload +- **Your identity configuration** — names, emails, usernames, hostnames, phones, and their substitute values, stored in browser local storage +- **Your substitution mappings** — real-to-substitute value pairs you configure +- **Activity log** — a local record of substitutions performed (never sent anywhere) +- **Settings and preferences** — extension configuration + +## Where data is stored + +All data is stored in your browser's `storage.local` (the extension's private storage area). When at-rest encryption is enabled, all sensitive data is AES-256-GCM encrypted before being written to storage. + +Data is **never** sent to any server operated by Silent Send or any third party. The only network requests Silent Send makes are: + +- **To the AI service you are already using** (e.g., claude.ai, chatgpt.com) — this is the substituted/sanitized version of your text, not the original +- **GitHub Gist sync** (optional, user-initiated) — if you configure Gist sync, your encrypted settings are stored in a private Gist on your own GitHub account +- **Custom URL sync** (optional, user-initiated) — if you configure a custom sync endpoint, encrypted settings are sent to the URL you specify +- **Org policy URL** (optional) — if you join an organization, the extension fetches the policy JSON from the URL your admin provides + +## Data sharing + +Silent Send does not share any data with anyone. There are no analytics providers, no crash reporting services, no advertising networks, and no data brokers involved. + +## Data retention + +All data persists in your browser until you delete it. You can: +- Clear all data via Options → Danger Zone → Reset Everything +- Uninstall the extension (removes all stored data) +- Export your data before clearing + +## Permissions explained + +| Permission | Why it's needed | +|---|---| +| `storage` | Store your identity, mappings, settings, and activity log locally | +| `activeTab` | Access the current tab to inject the substitution script | +| `scripting` | Inject content scripts on custom domains you configure | +| `notifications` | Show desktop notifications for sync status updates | +| `alarms` | Background polling for auto-sync and org policy updates | +| Host permissions (claude.ai, etc.) | Intercept API requests to substitute personal information before sending | + +## Children's privacy + +Silent Send does not knowingly collect data from children under 13. The extension does not collect data from anyone — it processes everything locally. + +## Changes to this policy + +If this privacy policy changes, the updated version will be posted at this URL and in the extension's GitHub repository. + +## Contact + +For questions about this privacy policy, open an issue at: https://github.com/outis1one/silent-send/issues + +## Open source + +Silent Send's source code is publicly available at https://github.com/outis1one/silent-send — you can verify every claim in this policy by reading the code. From ad065f42cd0b385949e0bb9925e8768d60b5bbb5 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 26 Mar 2026 23:45:35 +0000 Subject: [PATCH 3/3] fix: replace all innerHTML assignments with safeHTML for AMO review Replaced all 31 innerHTML assignments across popup.js (12), options.js (17), and content.js (3) to pass Mozilla AMO linter. Each file gets a safeHTML(el, html) helper: - popup.js/options.js: DOMParser-based (extension page context) - content.js: