From 7e147ac75732014430ffbc924379060d76648d10 Mon Sep 17 00:00:00 2001 From: th-shivam Date: Mon, 30 Mar 2026 18:27:11 +0530 Subject: [PATCH 1/3] fix: persist feedback model and ruleset versions --- src/extension_shield/api/main.py | 73 +++++++++++++++++- .../core/summary_generator.py | 4 +- tests/api/test_feedback_endpoint.py | 77 +++++++++++++++++++ 3 files changed, 150 insertions(+), 4 deletions(-) create mode 100644 tests/api/test_feedback_endpoint.py diff --git a/src/extension_shield/api/main.py b/src/extension_shield/api/main.py index c027bb4c..04ca51c9 100644 --- a/src/extension_shield/api/main.py +++ b/src/extension_shield/api/main.py @@ -984,6 +984,62 @@ def validate_feedback(self) -> "FeedbackRequest": return self +def _coerce_json_dict(value: Any) -> Dict[str, Any]: + """Convert JSON-ish values to a dict for metadata lookups.""" + if isinstance(value, dict): + return value + if isinstance(value, str): + try: + parsed = json.loads(value) + except (TypeError, ValueError): + return {} + return parsed if isinstance(parsed, dict) else {} + return {} + + +def _extract_feedback_versions(payload: Optional[Dict[str, Any]]) -> tuple[Optional[str], Optional[str]]: + """Extract model and ruleset versions from a scan payload when available.""" + if not isinstance(payload, dict): + return None, None + + metadata = _coerce_json_dict(payload.get("metadata")) + summary = _coerce_json_dict(payload.get("summary")) + report_view_model = _coerce_json_dict(payload.get("report_view_model")) + report_meta = _coerce_json_dict(report_view_model.get("meta")) + + scoring_v2 = _coerce_json_dict(payload.get("scoring_v2")) + if not scoring_v2: + scoring_v2 = _coerce_json_dict(summary.get("scoring_v2")) + + model_version = ( + summary.get("model_version") + or metadata.get("model_version") + or report_meta.get("model_version") + or summary.get("llm_model") + or metadata.get("llm_model") + ) + + ruleset_version = ( + summary.get("ruleset_version") + or metadata.get("ruleset_version") + or report_meta.get("ruleset_version") + or scoring_v2.get("weights_version") + or scoring_v2.get("scoring_version") + ) + + return model_version, ruleset_version + + +def _resolve_feedback_versions(scan_id: str) -> tuple[Optional[str], Optional[str]]: + """Look up the related scan payload and extract version metadata safely.""" + payload = scan_results.get(scan_id) + if isinstance(payload, dict): + return _extract_feedback_versions(payload) + + db_payload = db.get_scan_result(scan_id) + return _extract_feedback_versions(db_payload) + + class ReviewQueueClaimRequest(BaseModel): """Request body for claiming a review queue item.""" queue_item_id: str @@ -1504,6 +1560,16 @@ async def run_analysis_workflow(url: str, extension_id: str): if scoring_result.coverage_cap_reason is not None: scoring_v2_payload["coverage_cap_reason"] = scoring_result.coverage_cap_reason + executive_summary = final_state.get("executive_summary") or {} + if isinstance(executive_summary, dict): + executive_summary = dict(executive_summary) + else: + executive_summary = {} + executive_summary.setdefault( + "ruleset_version", + scoring_v2_payload.get("weights_version") or scoring_v2_payload.get("scoring_version"), + ) + # Build scan results - sanitize complex objects to prevent circular references raw_results = { "extension_id": extension_id, @@ -1518,7 +1584,7 @@ async def run_analysis_workflow(url: str, extension_id: str): "webstore_analysis": analysis_results.get("webstore_analysis") or {}, "virustotal_analysis": analysis_results.get("virustotal_analysis") or {}, "entropy_analysis": analysis_results.get("entropy_analysis") or {}, - "summary": final_state.get("executive_summary") or {}, + "summary": executive_summary, "impact_analysis": analysis_results.get("impact_analysis") or {}, "privacy_compliance": analysis_results.get("privacy_compliance") or {}, "extracted_path": _storage_relative_extracted_path(final_state.get("extension_dir")), @@ -2204,6 +2270,7 @@ async def submit_feedback(feedback: FeedbackRequest, http_request: Request): reason = None if feedback.helpful else (feedback.reason.value if feedback.reason else None) suggested_score = None if feedback.helpful else feedback.suggested_score comment = None if feedback.helpful else feedback.comment + model_version, ruleset_version = _resolve_feedback_versions(feedback.scan_id) # Save feedback to database (SQLite or Supabase) db.save_feedback( @@ -2213,8 +2280,8 @@ async def submit_feedback(feedback: FeedbackRequest, http_request: Request): suggested_score=suggested_score, comment=comment, user_id=user_id, - model_version=None, # TODO: Extract from scan result metadata - ruleset_version=None, # TODO: Extract from scan result metadata + model_version=model_version, + ruleset_version=ruleset_version, ) return {"ok": True} diff --git a/src/extension_shield/core/summary_generator.py b/src/extension_shield/core/summary_generator.py index 0cc969a0..9be3726f 100644 --- a/src/extension_shield/core/summary_generator.py +++ b/src/extension_shield/core/summary_generator.py @@ -562,7 +562,9 @@ def generate( score_label=score_label, host_scope_label=host_scope_label, ) - + + summary.setdefault("model_version", model_name) + logger.info("Executive summary generated successfully") return summary except Exception as exc: diff --git a/tests/api/test_feedback_endpoint.py b/tests/api/test_feedback_endpoint.py new file mode 100644 index 00000000..017508e0 --- /dev/null +++ b/tests/api/test_feedback_endpoint.py @@ -0,0 +1,77 @@ +from unittest.mock import MagicMock, patch + +import pytest +from fastapi.testclient import TestClient + +from extension_shield.api.main import app, scan_results + + +@pytest.fixture +def client() -> TestClient: + return TestClient(app) + + +def test_feedback_persists_versions_from_scan_payload(client: TestClient) -> None: + scan_id = "abcdefghijklmnopabcdefghijklmnop" + scan_results.pop(scan_id, None) + + scan_payload = { + "summary": {"model_version": "gpt-4o"}, + "scoring_v2": {"scoring_version": "2.0.0", "weights_version": "v1"}, + } + + with patch("extension_shield.api.main.db") as mock_db: + mock_db.get_scan_result = MagicMock(return_value=scan_payload) + mock_db.save_feedback = MagicMock() + + response = client.post( + "/api/feedback", + json={ + "scan_id": scan_id, + "helpful": False, + "reason": "score_off", + "suggested_score": 72, + "comment": "Score feels too strict", + }, + ) + + assert response.status_code == 200 + mock_db.save_feedback.assert_called_once_with( + scan_id=scan_id, + helpful=False, + reason="score_off", + suggested_score=72, + comment="Score feels too strict", + user_id=None, + model_version="gpt-4o", + ruleset_version="v1", + ) + + +def test_feedback_handles_missing_version_metadata(client: TestClient) -> None: + scan_id = "ponmlkjihgfedcbaponmlkjihgfedcba" + scan_results.pop(scan_id, None) + + with patch("extension_shield.api.main.db") as mock_db: + mock_db.get_scan_result = MagicMock(return_value={"summary": {}, "scoring_v2": {}}) + mock_db.save_feedback = MagicMock() + + response = client.post( + "/api/feedback", + json={ + "scan_id": scan_id, + "helpful": True, + }, + ) + + assert response.status_code == 200 + mock_db.save_feedback.assert_called_once_with( + scan_id=scan_id, + helpful=True, + reason=None, + suggested_score=None, + comment=None, + user_id=None, + model_version=None, + ruleset_version=None, + ) From 7e3137d5884041fbe93d82c778f65d157d7c7ae1 Mon Sep 17 00:00:00 2001 From: th-shivam Date: Sat, 4 Apr 2026 18:07:30 +0530 Subject: [PATCH 2/3] Fix Zip-Slip vulnerability allowing extraction to adjacent directories --- src/extension_shield/utils/extension.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/extension_shield/utils/extension.py b/src/extension_shield/utils/extension.py index eb500474..12acf8e6 100644 --- a/src/extension_shield/utils/extension.py +++ b/src/extension_shield/utils/extension.py @@ -241,7 +241,7 @@ def extract_extension_crx(file_path: str) -> Optional[str]: target_path = os.path.join(extract_dir, member.filename) abs_target = os.path.abspath(target_path) abs_extract = os.path.abspath(extract_dir) - if not abs_target.startswith(abs_extract): + if os.path.commonpath([abs_extract, abs_target]) != abs_extract: raise ValueError(f"Zip-slip attempt detected: {member.filename}") zip_ref.extract(member, extract_dir) @@ -262,7 +262,7 @@ def extract_extension_crx(file_path: str) -> Optional[str]: target_path = os.path.join(extract_dir, member.filename) abs_target = os.path.abspath(target_path) abs_extract = os.path.abspath(extract_dir) - if not abs_target.startswith(abs_extract): + if os.path.commonpath([abs_extract, abs_target]) != abs_extract: raise ValueError(f"Zip-slip attempt detected: {member.filename}") zip_ref.extract(member, extract_dir) From 25076ac4d658daad537c792190a8d1695371030b Mon Sep 17 00:00:00 2001 From: th-shivam Date: Fri, 24 Apr 2026 22:18:03 +0530 Subject: [PATCH 3/3] Add Supabase local setup script and update documentation --- .codex | 0 docs/GET_STARTED.md | 2 + package-lock.json | 337 +---------------------------------- package.json | 21 ++- scripts/README.md | 2 + scripts/run-supabase.sh | 14 ++ scripts/supabase_push_env.sh | 6 +- 7 files changed, 30 insertions(+), 352 deletions(-) create mode 100644 .codex create mode 100755 scripts/run-supabase.sh diff --git a/.codex b/.codex new file mode 100644 index 00000000..e69de29b diff --git a/docs/GET_STARTED.md b/docs/GET_STARTED.md index 5ac8c13f..a8637716 100644 --- a/docs/GET_STARTED.md +++ b/docs/GET_STARTED.md @@ -135,6 +135,8 @@ See [OPEN_CORE_BOUNDARIES.md](OPEN_CORE_BOUNDARIES.md) for how the boundary is e
Use Supabase, auth, history, and other cloud features +If you want to run local Supabase CLI commands such as `npm run db:push` or `npm run db:start`, install the Supabase CLI separately on your machine first. + **In project root `.env`:** ```bash diff --git a/package-lock.json b/package-lock.json index bd5d59e9..a2351278 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,24 +4,9 @@ "requires": true, "packages": { "": { - "name": "ExtensionShield", "devDependencies": { "dotenv": "^16.4.5", - "resend": "^4.0.0", - "supabase": "^2.75.5" - } - }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" + "resend": "^4.0.0" } }, "node_modules/@react-email/render": { @@ -57,81 +42,6 @@ "url": "https://ko-fi.com/killymxi" } }, - "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/bin-links": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-6.0.0.tgz", - "integrity": "sha512-X4CiKlcV2GjnCMwnKAfbVWpHa++65th9TuzAEYtZoATiOE2DQKhSp4CJlyLoTqdhBKlXjpXjCTYPNNFS33Fi6w==", - "dev": true, - "license": "ISC", - "dependencies": { - "cmd-shim": "^8.0.0", - "npm-normalize-package-bin": "^5.0.0", - "proc-log": "^6.0.0", - "read-cmd-shim": "^6.0.0", - "write-file-atomic": "^7.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/cmd-shim": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-8.0.0.tgz", - "integrity": "sha512-Jk/BK6NCapZ58BKUxlSI+ouKRbjH1NLZCgJkYoab+vEHUY3f6OzpNBN9u7HFSv9J6TRDGs4PLOHezoKGaFRSCA==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -234,43 +144,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, "node_modules/html-to-text": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-9.0.5.tgz", @@ -308,30 +181,6 @@ "entities": "^4.4.0" } }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, "node_modules/leac": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/leac/-/leac-0.6.0.tgz", @@ -342,86 +191,6 @@ "url": "https://ko-fi.com/killymxi" } }, - "node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minizlib": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", - "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "deprecated": "Use your platform's native DOMException instead", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, - "node_modules/npm-normalize-package-bin": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-5.0.0.tgz", - "integrity": "sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, "node_modules/parseley": { "version": "0.12.1", "resolved": "https://registry.npmjs.org/parseley/-/parseley-0.12.1.tgz", @@ -462,16 +231,6 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/proc-log": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", - "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, "node_modules/react": { "version": "19.2.4", "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", @@ -507,16 +266,6 @@ "fast-deep-equal": "^2.0.1" } }, - "node_modules/read-cmd-shim": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-6.0.0.tgz", - "integrity": "sha512-1zM5HuOfagXCBWMN83fuFI/x+T/UhZ7k+KIzhrHXcQoeX5+7gmaDYjELQHmmzIodumBHeByBJT4QYS7ufAgs7A==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, "node_modules/resend": { "version": "4.8.0", "resolved": "https://registry.npmjs.org/resend/-/resend-4.8.0.tgz", @@ -550,90 +299,6 @@ "funding": { "url": "https://ko-fi.com/killymxi" } - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/supabase": { - "version": "2.78.1", - "resolved": "https://registry.npmjs.org/supabase/-/supabase-2.78.1.tgz", - "integrity": "sha512-fCIE/LTTr1IGrrYLqYBI3w89QU1qW+mRVtUi/Dmrtj+oXtDX4E8VgfFlXZpoYsXy86cfE9RZXUJVAGgvdNfTPg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "bin-links": "^6.0.0", - "https-proxy-agent": "^7.0.2", - "node-fetch": "^3.3.2", - "tar": "7.5.11" - }, - "bin": { - "supabase": "bin/supabase" - }, - "engines": { - "npm": ">=8" - } - }, - "node_modules/tar": { - "version": "7.5.11", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.11.tgz", - "integrity": "sha512-ChjMH33/KetonMTAtpYdgUFr0tbz69Fp2v7zWxQfYZX4g5ZN2nOBXm1R2xyA+lMIKrLKIoKAwFj93jE/avX9cQ==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/web-streams-polyfill": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", - "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/write-file-atomic": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-7.0.0.tgz", - "integrity": "sha512-YnlPC6JqnZl6aO4uRc+dx5PHguiR9S6WeoLtpxNT9wIG+BDya7ZNE1q7KOjVgaA73hKhKLpVPgJ5QA9THQ5BRg==", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } } } } diff --git a/package.json b/package.json index 82892327..4e3ce339 100644 --- a/package.json +++ b/package.json @@ -1,20 +1,19 @@ { "scripts": { "deploy": "./scripts/deploy.sh", - "supabase": "supabase", - "db:diff": "supabase db diff", - "db:push": "supabase db push", - "db:pull": "supabase db pull", - "db:reset": "supabase db reset", - "db:start": "supabase start", - "db:stop": "supabase stop", - "db:migration:up": "supabase migration up", - "db:migration:new": "supabase migration new", + "supabase": "./scripts/run-supabase.sh", + "db:diff": "./scripts/run-supabase.sh db diff", + "db:push": "./scripts/run-supabase.sh db push", + "db:pull": "./scripts/run-supabase.sh db pull", + "db:reset": "./scripts/run-supabase.sh db reset", + "db:start": "./scripts/run-supabase.sh start", + "db:stop": "./scripts/run-supabase.sh stop", + "db:migration:up": "./scripts/run-supabase.sh migration up", + "db:migration:new": "./scripts/run-supabase.sh migration new", "resend:test": "node scripts/send-resend-test-email.mjs" }, "devDependencies": { "dotenv": "^16.4.5", - "resend": "^4.0.0", - "supabase": "^2.75.5" + "resend": "^4.0.0" } } diff --git a/scripts/README.md b/scripts/README.md index 1a94c73c..505f1f80 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -33,6 +33,8 @@ Use these when you need to run something outside the usual `make` targets (e.g. - **cloud_only/clear_all_scans.py** — Deletes every scan from the DB. Cloud only. `make clear-scans`. - **cloud_only/delete_scans_before_extension.py** — Deletes scans for a given extension (admin cleanup). Run with `PYTHONPATH=src python scripts/cloud_only/delete_scans_before_extension.py "Extension Name"`. +Supabase shell helpers expect a separately installed `supabase` CLI instead of pulling it in during `npm install`. + **Security / CSP** - **setup-production-csp.sh** — Builds the frontend and configures CSP headers for production. diff --git a/scripts/run-supabase.sh b/scripts/run-supabase.sh new file mode 100755 index 00000000..b32a3eb3 --- /dev/null +++ b/scripts/run-supabase.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env sh + +set -e + +if ! command -v supabase >/dev/null 2>&1; then + echo "Supabase CLI is not installed." >&2 + echo "Install it separately, then rerun this command." >&2 + echo "Examples:" >&2 + echo " npm install -g supabase" >&2 + echo " brew install supabase/tap/supabase" >&2 + exit 127 +fi + +exec supabase "$@" diff --git a/scripts/supabase_push_env.sh b/scripts/supabase_push_env.sh index 06f8ed6d..f883d251 100755 --- a/scripts/supabase_push_env.sh +++ b/scripts/supabase_push_env.sh @@ -34,11 +34,7 @@ echo "==============================================" echo "Supabase: $ENV_LABEL (ref: $REF)" echo "==============================================" -# Prefer npx so it works without global supabase CLI -SUPABASE_CMD="npx supabase" -if command -v supabase >/dev/null 2>&1; then - SUPABASE_CMD="supabase" -fi +SUPABASE_CMD="./scripts/run-supabase.sh" echo "→ Linking project..." $SUPABASE_CMD link --project-ref "$REF"