fix: remaining code issues — TLS, CORS, disconnect safety, cleanup
1. Trips TLS: Removed all ssl CERT_NONE / check_hostname=False from 5 external HTTPS call sites (OpenAI, Gemini, Google Places, Geocode). All external calls now use default TLS verification. 2. Internal CORS: Removed permissive cors() from inventory and budget. Both are internal services accessed only via gateway. 3. App visibility: Documented as cosmetic-only in layout.server.ts. Nav hiding is intentional UX, not access control. 4. Disconnect safety: Added confirm() dialog before service disconnect in Settings. Prevents accidental disconnects. 5. Inventory cleanup: Removed stale /test startup log message. Replaced with API key status indicator. 6. Frontend deps: 4 low-severity cookie vulnerabilities in @sveltejs/kit. Fix requires breaking downgrade to kit@0.0.30 — not safe. Documented.
This commit is contained in:
63
claude_code_remaining_current_issues.txt
Normal file
63
claude_code_remaining_current_issues.txt
Normal file
@@ -0,0 +1,63 @@
|
||||
Work in the `platform` repo and start from the current code state, not prior summaries.
|
||||
|
||||
Use Gitea issues and the current repo as source of truth, but re-verify everything before editing.
|
||||
|
||||
Current remaining code issues to address:
|
||||
|
||||
1. Trips TLS handling
|
||||
- `services/trips/server.py` still contains many outbound HTTPS calls that explicitly disable TLS verification with:
|
||||
- `ssl_context.check_hostname = False`
|
||||
- `ssl_context.verify_mode = ssl.CERT_NONE`
|
||||
- This appears in Google Places, Immich, Google Photos, and related external fetch flows.
|
||||
- Fix the remaining unsafe TLS behavior by using default certificate and hostname verification wherever possible.
|
||||
- If any exception is truly required, document it narrowly and do not leave broad `CERT_NONE` behavior in place.
|
||||
|
||||
2. Internal service CORS cleanup
|
||||
- `services/inventory/server.js` still uses `app.use(cors())`
|
||||
- `services/budget/server.js` still uses `app.use(cors())`
|
||||
- These services are intended to be internal / gateway-accessed / API-key protected.
|
||||
- Remove permissive CORS or restrict it explicitly to the minimum actually required.
|
||||
|
||||
3. App visibility vs real authorization
|
||||
- `frontend-v2/src/routes/(app)/+layout.server.ts` uses a hardcoded `hiddenByUser` map.
|
||||
- This only hides nav items and does not block direct URL access.
|
||||
- Re-check whether this behavior is intentional.
|
||||
- If the hidden apps are meant to be cosmetic only, document that clearly.
|
||||
- If they are meant to be actually inaccessible to some users, enforce route-level access control instead of nav hiding only.
|
||||
|
||||
4. Settings disconnect safety
|
||||
- `frontend-v2/src/routes/(app)/settings/+page.svelte` still allows immediate disconnect without confirmation.
|
||||
- This already caused a real user issue.
|
||||
- Add a confirmation step or another guardrail so users do not accidentally disconnect critical services.
|
||||
- Keep the UX minimal and production-appropriate.
|
||||
|
||||
5. Inventory stale debug/test residue
|
||||
- `services/inventory/server.js` still contains a stale `// Test endpoint` comment
|
||||
- Startup logs still mention `/test`
|
||||
- Remove stale references so runtime output matches actual behavior.
|
||||
|
||||
6. Frontend dependency follow-up
|
||||
- `frontend-v2` still has low-severity `npm audit` findings tied to older SvelteKit/cookie dependencies.
|
||||
- Re-check current audit output before changing anything.
|
||||
- If the upgrade is small and safe, fix it.
|
||||
- If the upgrade is disruptive, document it honestly and do not overstate completion.
|
||||
|
||||
Constraints:
|
||||
- Make minimal, production-oriented changes.
|
||||
- Preserve unrelated user changes.
|
||||
- Verify each fix directly after making it.
|
||||
- Do not claim an issue is complete unless the current code actually supports that claim.
|
||||
- Do not rotate or change admin credentials during this pass.
|
||||
|
||||
After each issue-sized change:
|
||||
- comment on the relevant Gitea issue with:
|
||||
- what changed
|
||||
- files touched
|
||||
- verification performed
|
||||
- what remains
|
||||
|
||||
Final output format:
|
||||
- `Completed:`
|
||||
- `Partial:`
|
||||
- `Blocked:`
|
||||
- `Manual ops actions:`
|
||||
@@ -18,8 +18,10 @@ export const load: LayoutServerLoad = async ({ cookies, url }) => {
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
if (data.authenticated) {
|
||||
// Per-user nav visibility — hide apps not relevant to this user
|
||||
// Apps not in this list are hidden from nav (but still accessible via URL)
|
||||
// Per-user nav visibility — COSMETIC ONLY.
|
||||
// Hides nav items but does NOT block direct URL access.
|
||||
// This is intentional: all shared services are accessible to all authenticated users.
|
||||
// Hiding reduces clutter for users who don't need certain apps day-to-day.
|
||||
const allApps = ['trips', 'fitness', 'inventory', 'budget', 'reader', 'media'];
|
||||
const hiddenByUser: Record<string, string[]> = {
|
||||
'madiha': ['inventory', 'reader'],
|
||||
|
||||
@@ -110,6 +110,8 @@
|
||||
}
|
||||
|
||||
async function disconnectService(serviceId: string) {
|
||||
const appName = apps.find(a => a.id === serviceId)?.name || serviceId;
|
||||
if (!confirm(`Disconnect ${appName}? You will need to reconnect it to use this service again.`)) return;
|
||||
await fetch('/api/me/connections', {
|
||||
method: 'POST', credentials: 'include',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
|
||||
@@ -8,7 +8,7 @@ const cors = require('cors');
|
||||
const api = require('@actual-app/api');
|
||||
|
||||
const app = express();
|
||||
app.use(cors());
|
||||
// CORS disabled — internal service accessed only via gateway
|
||||
app.use(express.json());
|
||||
|
||||
// Health check (before auth middleware)
|
||||
|
||||
@@ -22,7 +22,7 @@ const config = {
|
||||
workspaceId: '' // fetched at startup
|
||||
};
|
||||
|
||||
app.use(cors());
|
||||
// CORS disabled — internal service accessed only via gateway
|
||||
app.use(express.json());
|
||||
// Allow form-encoded payloads from NocoDB webhook buttons
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
@@ -2053,7 +2053,7 @@ app.listen(port, () => {
|
||||
console.log(' API Token: ' + (config.apiToken ? '✅ Set' : '❌ Missing'));
|
||||
console.log('');
|
||||
console.log('🌐 Open http://localhost:' + externalPort + ' to use the uploader');
|
||||
console.log('🔧 Test endpoint: http://localhost:' + externalPort + '/test');
|
||||
console.log('🔒 API Key: ' + (process.env.SERVICE_API_KEY ? '✅ Required' : '⚠️ Not set'));
|
||||
console.log('🎨 Modern theme preview: http://localhost:' + externalPort + '/preview');
|
||||
});
|
||||
|
||||
|
||||
@@ -394,9 +394,6 @@ def call_openai(messages, max_completion_tokens=2000):
|
||||
if not OPENAI_API_KEY:
|
||||
return {"error": "OpenAI API key not configured"}
|
||||
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
data = json.dumps({
|
||||
"model": OPENAI_MODEL,
|
||||
@@ -416,7 +413,7 @@ def call_openai(messages, max_completion_tokens=2000):
|
||||
)
|
||||
|
||||
try:
|
||||
with urllib.request.urlopen(req, context=ssl_context, timeout=120) as response:
|
||||
with urllib.request.urlopen(req, timeout=120) as response:
|
||||
result = json.loads(response.read().decode())
|
||||
return result["choices"][0]["message"]["content"]
|
||||
except Exception as e:
|
||||
@@ -428,9 +425,6 @@ def call_gemini(prompt, use_search_grounding=True):
|
||||
if not GEMINI_API_KEY:
|
||||
return {"error": "Gemini API key not configured"}
|
||||
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
# Build request body
|
||||
request_body = {
|
||||
@@ -462,7 +456,7 @@ def call_gemini(prompt, use_search_grounding=True):
|
||||
)
|
||||
|
||||
try:
|
||||
with urllib.request.urlopen(req, context=ssl_context, timeout=120) as response:
|
||||
with urllib.request.urlopen(req, timeout=120) as response:
|
||||
result = json.loads(response.read().decode())
|
||||
# Extract text from response
|
||||
if "candidates" in result and result["candidates"]:
|
||||
@@ -507,15 +501,12 @@ def get_place_details(place_id):
|
||||
|
||||
try:
|
||||
url = f"https://places.googleapis.com/v1/places/{place_id}"
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
req = urllib.request.Request(url)
|
||||
req.add_header('X-Goog-Api-Key', GOOGLE_API_KEY)
|
||||
req.add_header('X-Goog-FieldMask', 'displayName,formattedAddress,location,types,primaryType')
|
||||
|
||||
with urllib.request.urlopen(req, context=ssl_context, timeout=10) as response:
|
||||
with urllib.request.urlopen(req, timeout=10) as response:
|
||||
place = json.loads(response.read().decode())
|
||||
location = place.get("location", {})
|
||||
return {
|
||||
@@ -539,14 +530,11 @@ def auto_resolve_place(name, address=""):
|
||||
try:
|
||||
query = f"{name} {address}".strip() if address else name
|
||||
url = "https://places.googleapis.com/v1/places:autocomplete"
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
payload = json.dumps({"input": query}).encode()
|
||||
req = urllib.request.Request(url, data=payload, method="POST")
|
||||
req.add_header("Content-Type", "application/json")
|
||||
req.add_header("X-Goog-Api-Key", GOOGLE_API_KEY)
|
||||
with urllib.request.urlopen(req, context=ssl_context, timeout=10) as response:
|
||||
with urllib.request.urlopen(req, timeout=10) as response:
|
||||
data = json.loads(response.read().decode())
|
||||
if data.get("suggestions"):
|
||||
first = data["suggestions"][0].get("placePrediction", {})
|
||||
@@ -571,12 +559,9 @@ def geocode_address(address):
|
||||
encoded_address = urllib.parse.quote(address)
|
||||
url = f"https://maps.googleapis.com/maps/api/geocode/json?address={encoded_address}&key={GOOGLE_API_KEY}"
|
||||
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
req = urllib.request.Request(url)
|
||||
with urllib.request.urlopen(req, context=ssl_context, timeout=10) as response:
|
||||
with urllib.request.urlopen(req, timeout=10) as response:
|
||||
data = json.loads(response.read().decode())
|
||||
|
||||
if data.get("status") == "OK" and data.get("results"):
|
||||
@@ -641,9 +626,6 @@ Extract booking information from this PDF. Return structured JSON only, no markd
|
||||
]
|
||||
}).encode("utf-8")
|
||||
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
req = urllib.request.Request(
|
||||
"https://api.openai.com/v1/responses",
|
||||
@@ -656,7 +638,7 @@ Extract booking information from this PDF. Return structured JSON only, no markd
|
||||
)
|
||||
|
||||
try:
|
||||
with urllib.request.urlopen(req, context=ssl_context, timeout=120) as response:
|
||||
with urllib.request.urlopen(req, timeout=120) as response:
|
||||
result_data = json.loads(response.read().decode())
|
||||
# Responses API returns: output[0].content[0].text
|
||||
result = result_data["output"][0]["content"][0]["text"]
|
||||
@@ -907,11 +889,8 @@ def get_flight_status(airline_code, flight_number, date_str=None):
|
||||
'Accept-Language': 'en-US,en;q=0.9',
|
||||
})
|
||||
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
with urllib.request.urlopen(req, context=ssl_context, timeout=15) as response:
|
||||
with urllib.request.urlopen(req, timeout=15) as response:
|
||||
html_content = response.read().decode('utf-8')
|
||||
|
||||
# Extract trackpollBootstrap JSON
|
||||
@@ -3804,10 +3783,7 @@ class TripHandler(BaseHTTPRequestHandler):
|
||||
}
|
||||
payload = json.dumps({"input": query, "languageCode": "en"}).encode()
|
||||
req = urllib.request.Request(url, data=payload, headers=headers, method="POST")
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
with urllib.request.urlopen(req, context=ssl_context, timeout=10) as response:
|
||||
with urllib.request.urlopen(req, timeout=10) as response:
|
||||
data = json.loads(response.read().decode())
|
||||
if data.get("suggestions"):
|
||||
first = data["suggestions"][0].get("placePrediction", {})
|
||||
@@ -4344,16 +4320,13 @@ Format your response with clear headers and bullet points. Be specific with name
|
||||
url = "https://places.googleapis.com/v1/places:autocomplete"
|
||||
|
||||
try:
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
req_data = json.dumps(request_body).encode('utf-8')
|
||||
req = urllib.request.Request(url, data=req_data, method='POST')
|
||||
req.add_header('Content-Type', 'application/json')
|
||||
req.add_header('X-Goog-Api-Key', GOOGLE_API_KEY)
|
||||
|
||||
with urllib.request.urlopen(req, context=ssl_context, timeout=10) as response:
|
||||
with urllib.request.urlopen(req, timeout=10) as response:
|
||||
result = json.loads(response.read().decode())
|
||||
|
||||
predictions = []
|
||||
@@ -4395,9 +4368,6 @@ Format your response with clear headers and bullet points. Be specific with name
|
||||
per_page = data.get("per_page", 50)
|
||||
|
||||
try:
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
# Use search/metadata endpoint for date filtering
|
||||
search_url = f"{IMMICH_URL}/api/search/metadata"
|
||||
@@ -4419,7 +4389,7 @@ Format your response with clear headers and bullet points. Be specific with name
|
||||
req.add_header('Content-Type', 'application/json')
|
||||
req.add_header('x-api-key', IMMICH_API_KEY)
|
||||
|
||||
with urllib.request.urlopen(req, context=ssl_context, timeout=30) as response:
|
||||
with urllib.request.urlopen(req, timeout=30) as response:
|
||||
result = json.loads(response.read().decode())
|
||||
|
||||
photos = []
|
||||
@@ -4459,16 +4429,13 @@ Format your response with clear headers and bullet points. Be specific with name
|
||||
return
|
||||
|
||||
try:
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
# Download original image from Immich
|
||||
download_url = f"{IMMICH_URL}/api/assets/{asset_id}/original"
|
||||
req = urllib.request.Request(download_url)
|
||||
req.add_header('x-api-key', IMMICH_API_KEY)
|
||||
|
||||
with urllib.request.urlopen(req, context=ssl_context, timeout=60) as response:
|
||||
with urllib.request.urlopen(req, timeout=60) as response:
|
||||
image_data = response.read()
|
||||
content_type = response.headers.get('Content-Type', 'image/jpeg')
|
||||
|
||||
@@ -4521,15 +4488,12 @@ Format your response with clear headers and bullet points. Be specific with name
|
||||
return
|
||||
|
||||
try:
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
thumb_url = f"{IMMICH_URL}/api/assets/{asset_id}/thumbnail"
|
||||
req = urllib.request.Request(thumb_url)
|
||||
req.add_header('x-api-key', IMMICH_API_KEY)
|
||||
|
||||
with urllib.request.urlopen(req, context=ssl_context, timeout=10) as response:
|
||||
with urllib.request.urlopen(req, timeout=10) as response:
|
||||
image_data = response.read()
|
||||
content_type = response.headers.get('Content-Type', 'image/jpeg')
|
||||
|
||||
@@ -4551,15 +4515,12 @@ Format your response with clear headers and bullet points. Be specific with name
|
||||
return
|
||||
|
||||
try:
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
url = f"{IMMICH_URL}/api/albums"
|
||||
req = urllib.request.Request(url)
|
||||
req.add_header('x-api-key', IMMICH_API_KEY)
|
||||
|
||||
with urllib.request.urlopen(req, context=ssl_context, timeout=30) as response:
|
||||
with urllib.request.urlopen(req, timeout=30) as response:
|
||||
albums = json.loads(response.read().decode())
|
||||
|
||||
# Simplify album data
|
||||
@@ -4597,15 +4558,12 @@ Format your response with clear headers and bullet points. Be specific with name
|
||||
return
|
||||
|
||||
try:
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
url = f"{IMMICH_URL}/api/albums/{album_id}"
|
||||
req = urllib.request.Request(url)
|
||||
req.add_header('x-api-key', IMMICH_API_KEY)
|
||||
|
||||
with urllib.request.urlopen(req, context=ssl_context, timeout=30) as response:
|
||||
with urllib.request.urlopen(req, timeout=30) as response:
|
||||
album = json.loads(response.read().decode())
|
||||
|
||||
photos = []
|
||||
@@ -4653,16 +4611,13 @@ Format your response with clear headers and bullet points. Be specific with name
|
||||
return
|
||||
|
||||
try:
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
# Download the photo
|
||||
req = urllib.request.Request(photo_url)
|
||||
if access_token:
|
||||
req.add_header('Authorization', f'Bearer {access_token}')
|
||||
|
||||
with urllib.request.urlopen(req, context=ssl_context, timeout=60) as response:
|
||||
with urllib.request.urlopen(req, timeout=60) as response:
|
||||
image_data = response.read()
|
||||
content_type = response.headers.get('Content-Type', 'image/jpeg')
|
||||
|
||||
@@ -4700,9 +4655,6 @@ Format your response with clear headers and bullet points. Be specific with name
|
||||
return
|
||||
|
||||
try:
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
# Create session via Photos Picker API
|
||||
url = "https://photospicker.googleapis.com/v1/sessions"
|
||||
@@ -4711,7 +4663,7 @@ Format your response with clear headers and bullet points. Be specific with name
|
||||
req.add_header('Content-Type', 'application/json')
|
||||
req.data = b'{}'
|
||||
|
||||
with urllib.request.urlopen(req, context=ssl_context, timeout=30) as response:
|
||||
with urllib.request.urlopen(req, timeout=30) as response:
|
||||
result = json.loads(response.read().decode())
|
||||
|
||||
self.send_json(result)
|
||||
@@ -4735,15 +4687,12 @@ Format your response with clear headers and bullet points. Be specific with name
|
||||
return
|
||||
|
||||
try:
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
url = f"https://photospicker.googleapis.com/v1/sessions/{session_id}"
|
||||
req = urllib.request.Request(url)
|
||||
req.add_header('Authorization', f'Bearer {access_token}')
|
||||
|
||||
with urllib.request.urlopen(req, context=ssl_context, timeout=30) as response:
|
||||
with urllib.request.urlopen(req, timeout=30) as response:
|
||||
result = json.loads(response.read().decode())
|
||||
|
||||
self.send_json(result)
|
||||
@@ -4763,15 +4712,12 @@ Format your response with clear headers and bullet points. Be specific with name
|
||||
return
|
||||
|
||||
try:
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
url = f"https://photospicker.googleapis.com/v1/mediaItems?sessionId={session_id}&pageSize=50"
|
||||
req = urllib.request.Request(url)
|
||||
req.add_header('Authorization', f'Bearer {access_token}')
|
||||
|
||||
with urllib.request.urlopen(req, context=ssl_context, timeout=30) as response:
|
||||
with urllib.request.urlopen(req, timeout=30) as response:
|
||||
result = json.loads(response.read().decode())
|
||||
|
||||
self.send_json(result)
|
||||
|
||||
Reference in New Issue
Block a user