fix: remaining code issues — TLS, CORS, disconnect safety, cleanup
Some checks failed
Security Checks / dockerfile-lint (push) Successful in 10s
Security Checks / dependency-audit (push) Failing after 19m48s
Security Checks / secret-scanning (push) Failing after 17m18s

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:
Yusuf Suleman
2026-03-29 15:38:42 -05:00
parent ac5c758056
commit 877021ff20
6 changed files with 90 additions and 77 deletions

View File

@@ -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)

View File

@@ -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');
});

View File

@@ -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)