2fb067ecbf
checklocks / checklocks (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
natlab-integrationtest / natlab-integrationtest (push) Has been cancelled
CI / gomod-cache (push) Has been cancelled
CI / race-root-integration (1/4) (push) Has been cancelled
CI / race-root-integration (2/4) (push) Has been cancelled
CI / race-root-integration (3/4) (push) Has been cancelled
CI / race-root-integration (4/4) (push) Has been cancelled
CI / test (-race, amd64, 1/3) (push) Has been cancelled
CI / test (-race, amd64, 2/3) (push) Has been cancelled
CI / test (-race, amd64, 3/3) (push) Has been cancelled
CI / test (386) (push) Has been cancelled
CI / test (amd64) (push) Has been cancelled
CI / Windows (benchmarks) (push) Has been cancelled
CI / Windows (1/2) (push) Has been cancelled
CI / Windows (2/2) (push) Has been cancelled
CI / macos (push) Has been cancelled
CI / privileged (push) Has been cancelled
CI / vm (push) Has been cancelled
CI / cross (386, linux) (push) Has been cancelled
CI / cross (amd64, darwin) (push) Has been cancelled
CI / cross (amd64, freebsd) (push) Has been cancelled
CI / cross (amd64, openbsd) (push) Has been cancelled
CI / cross (amd64, windows) (push) Has been cancelled
CI / cross (arm, 5, linux) (push) Has been cancelled
CI / cross (arm, 7, linux) (push) Has been cancelled
CI / cross (arm64, darwin) (push) Has been cancelled
CI / cross (arm64, linux) (push) Has been cancelled
CI / cross (arm64, windows) (push) Has been cancelled
CI / cross (loong64, linux) (push) Has been cancelled
CI / ios (push) Has been cancelled
CI / crossmin (amd64, illumos) (push) Has been cancelled
CI / crossmin (amd64, plan9) (push) Has been cancelled
CI / crossmin (amd64, solaris) (push) Has been cancelled
CI / crossmin (ppc64, aix) (push) Has been cancelled
CI / android (push) Has been cancelled
CI / wasm (push) Has been cancelled
CI / tailscale_go (push) Has been cancelled
CI / fuzz (push) Has been cancelled
CI / depaware (push) Has been cancelled
CI / go_generate (push) Has been cancelled
CI / make_tidy (push) Has been cancelled
CI / licenses (push) Has been cancelled
CI / staticcheck (macOS) (push) Has been cancelled
CI / staticcheck (Linux) (push) Has been cancelled
CI / staticcheck (Windows) (push) Has been cancelled
CI / staticcheck (Portable (1/4)) (push) Has been cancelled
CI / staticcheck (Portable (2/4)) (push) Has been cancelled
CI / staticcheck (Portable (3/4)) (push) Has been cancelled
CI / staticcheck (Portable (4/4)) (push) Has been cancelled
CI / notify_slack (push) Has been cancelled
CI / merge_blocker (push) Has been cancelled
CI / check_mergeability_strict (push) Has been cancelled
CI / check_mergeability (push) Has been cancelled
Dockerfile build / deploy (push) Has been cancelled
test installer.sh / test (curl, alpine:3.21) (push) Has been cancelled
test installer.sh / test (curl, alpine:edge) (push) Has been cancelled
test installer.sh / test (curl, alpine:latest) (push) Has been cancelled
test installer.sh / test (curl, amazonlinux:latest) (push) Has been cancelled
test installer.sh / test (curl, archlinux:latest) (push) Has been cancelled
test installer.sh / test (curl, debian:oldstable-slim) (push) Has been cancelled
test installer.sh / test (curl, debian:sid-slim) (push) Has been cancelled
test installer.sh / test (curl, debian:stable-slim, 1.80.0) (push) Has been cancelled
test installer.sh / test (curl, debian:testing-slim) (push) Has been cancelled
test installer.sh / test (curl, elementary/docker:stable) (push) Has been cancelled
test installer.sh / test (curl, elementary/docker:unstable) (push) Has been cancelled
test installer.sh / test (curl, fedora:latest, 1.80.0) (push) Has been cancelled
test installer.sh / test (curl, kalilinux/kali-dev) (push) Has been cancelled
test installer.sh / test (curl, kalilinux/kali-rolling) (push) Has been cancelled
test installer.sh / test (curl, opensuse/leap:latest) (push) Has been cancelled
test installer.sh / test (curl, opensuse/tumbleweed:latest) (push) Has been cancelled
test installer.sh / test (curl, oraclelinux:8) (push) Has been cancelled
test installer.sh / test (curl, oraclelinux:9) (push) Has been cancelled
test installer.sh / test (curl, parrotsec/core:latest) (push) Has been cancelled
test installer.sh / test (curl, rockylinux:8.7) (push) Has been cancelled
test installer.sh / test (curl, rockylinux:9) (push) Has been cancelled
test installer.sh / test (curl, ubuntu:20.04) (push) Has been cancelled
test installer.sh / test (curl, ubuntu:22.04) (push) Has been cancelled
test installer.sh / test (curl, ubuntu:24.04, 1.80.0) (push) Has been cancelled
test installer.sh / test (wget, debian:oldstable-slim) (push) Has been cancelled
test installer.sh / test (wget, debian:sid-slim) (push) Has been cancelled
update-flake / update-flake (push) Has been cancelled
tailscale.com/cmd/vet / vet (push) Has been cancelled
test installer.sh / notify-slack (push) Has been cancelled
Client security fixes (cmd/tailscale-tray/main.go): - SSRF protection in Add Server dialog (validateControlURL): reject private/loopback/link-local/cloud-metadata IPs via DNS resolution - RCE gate on AuthURL/BrowseToURL exec paths (validateAuthURL) - Sanitized URL logging (sanitizeURLForLog drops query auth tokens) - Error handling on exec.Command with user-facing showError() Admin panel security (web-admin): - Bcrypt password hashing (replaces SHA256) - Rate limiting: 5 failed logins → 15-min lockout - Session + login attempt cleanup goroutine (hourly) - url.QueryEscape / encodeURIComponent for all API params - Fail-hard startup when no TLS and non-loopback bind - ADMIN_PASSWORD required (no default), password min 12 chars - Username regex whitelist Installer hardening (Setup.wxs): - util:PermissionEx restricts SCM access: only Administrators + SYSTEM can start/stop/reconfigure service. Authenticated Users limited to QueryStatus/QueryConfig/Interrogate - Vital="yes" on ServiceInstall Docs & roadmap: - PRODUCTION_ROADMAP.md: 5-milestone plan (security + features + distribution + ops) with granular tasks, effort, done-when - CLIENT_SECURITY_AUDIT.md, SECURITY_FIXES.md, DEPLOYMENT.md - AI assistant rules (.cursorrules, .antigravityrules, etc.) Build & distribution: - build-msi.ps1, deploy-and-sign.ps1, sign-release.ps1 - redeploy.ps1, tray-deploy.ps1, test-msi.ps1 - installer/msi/ alternative WXS setup - Restored .github/workflows/ removed in mirror cleanup .gitignore hardened: *.pfx, *.p12, *.key, *.pem, .env*
212 lines
7.4 KiB
Python
212 lines
7.4 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
API Validator - Checks API endpoints for best practices.
|
|
Validates OpenAPI specs, response formats, and common issues.
|
|
"""
|
|
import sys
|
|
import json
|
|
import re
|
|
from pathlib import Path
|
|
|
|
# Fix Windows console encoding for Unicode output
|
|
try:
|
|
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
|
sys.stderr.reconfigure(encoding='utf-8', errors='replace')
|
|
except AttributeError:
|
|
pass # Python < 3.7
|
|
|
|
def find_api_files(project_path: Path) -> list:
|
|
"""Find API-related files."""
|
|
patterns = [
|
|
"**/*api*.ts", "**/*api*.js", "**/*api*.py",
|
|
"**/routes/*.ts", "**/routes/*.js", "**/routes/*.py",
|
|
"**/controllers/*.ts", "**/controllers/*.js",
|
|
"**/endpoints/*.ts", "**/endpoints/*.py",
|
|
"**/*.openapi.json", "**/*.openapi.yaml",
|
|
"**/swagger.json", "**/swagger.yaml",
|
|
"**/openapi.json", "**/openapi.yaml"
|
|
]
|
|
|
|
files = []
|
|
for pattern in patterns:
|
|
files.extend(project_path.glob(pattern))
|
|
|
|
# Exclude node_modules, etc.
|
|
return [f for f in files if not any(x in str(f) for x in ['node_modules', '.git', 'dist', 'build', '__pycache__'])]
|
|
|
|
def check_openapi_spec(file_path: Path) -> dict:
|
|
"""Check OpenAPI/Swagger specification."""
|
|
issues = []
|
|
passed = []
|
|
|
|
try:
|
|
content = file_path.read_text(encoding='utf-8')
|
|
|
|
if file_path.suffix == '.json':
|
|
spec = json.loads(content)
|
|
else:
|
|
# Basic YAML check
|
|
if 'openapi:' in content or 'swagger:' in content:
|
|
passed.append("[OK] OpenAPI/Swagger version defined")
|
|
else:
|
|
issues.append("[X] No OpenAPI version found")
|
|
|
|
if 'paths:' in content:
|
|
passed.append("[OK] Paths section exists")
|
|
else:
|
|
issues.append("[X] No paths defined")
|
|
|
|
if 'components:' in content or 'definitions:' in content:
|
|
passed.append("[OK] Schema components defined")
|
|
|
|
return {'file': str(file_path), 'passed': passed, 'issues': issues, 'type': 'openapi'}
|
|
|
|
# JSON OpenAPI checks
|
|
if 'openapi' in spec or 'swagger' in spec:
|
|
passed.append("[OK] OpenAPI version defined")
|
|
|
|
if 'info' in spec:
|
|
if 'title' in spec['info']:
|
|
passed.append("[OK] API title defined")
|
|
if 'version' in spec['info']:
|
|
passed.append("[OK] API version defined")
|
|
if 'description' not in spec['info']:
|
|
issues.append("[!] API description missing")
|
|
|
|
if 'paths' in spec:
|
|
path_count = len(spec['paths'])
|
|
passed.append(f"[OK] {path_count} endpoints defined")
|
|
|
|
# Check each path
|
|
for path, methods in spec['paths'].items():
|
|
for method, details in methods.items():
|
|
if method in ['get', 'post', 'put', 'patch', 'delete']:
|
|
if 'responses' not in details:
|
|
issues.append(f"[X] {method.upper()} {path}: No responses defined")
|
|
if 'summary' not in details and 'description' not in details:
|
|
issues.append(f"[!] {method.upper()} {path}: No description")
|
|
|
|
except Exception as e:
|
|
issues.append(f"[X] Parse error: {e}")
|
|
|
|
return {'file': str(file_path), 'passed': passed, 'issues': issues, 'type': 'openapi'}
|
|
|
|
def check_api_code(file_path: Path) -> dict:
|
|
"""Check API code for common issues."""
|
|
issues = []
|
|
passed = []
|
|
|
|
try:
|
|
content = file_path.read_text(encoding='utf-8')
|
|
|
|
# Check for error handling
|
|
error_patterns = [
|
|
r'try\s*{', r'try:', r'\.catch\(',
|
|
r'except\s+', r'catch\s*\('
|
|
]
|
|
has_error_handling = any(re.search(p, content) for p in error_patterns)
|
|
if has_error_handling:
|
|
passed.append("[OK] Error handling present")
|
|
else:
|
|
issues.append("[X] No error handling found")
|
|
|
|
# Check for status codes
|
|
status_patterns = [
|
|
r'status\s*\(\s*\d{3}\s*\)', r'statusCode\s*[=:]\s*\d{3}',
|
|
r'HttpStatus\.', r'status_code\s*=\s*\d{3}',
|
|
r'\.status\(\d{3}\)', r'res\.status\('
|
|
]
|
|
has_status = any(re.search(p, content) for p in status_patterns)
|
|
if has_status:
|
|
passed.append("[OK] HTTP status codes used")
|
|
else:
|
|
issues.append("[!] No explicit HTTP status codes")
|
|
|
|
# Check for validation
|
|
validation_patterns = [
|
|
r'validate', r'schema', r'zod', r'joi', r'yup',
|
|
r'pydantic', r'@Body\(', r'@Query\('
|
|
]
|
|
has_validation = any(re.search(p, content, re.I) for p in validation_patterns)
|
|
if has_validation:
|
|
passed.append("[OK] Input validation present")
|
|
else:
|
|
issues.append("[!] No input validation detected")
|
|
|
|
# Check for auth middleware
|
|
auth_patterns = [
|
|
r'auth', r'jwt', r'bearer', r'token',
|
|
r'middleware', r'guard', r'@Authenticated'
|
|
]
|
|
has_auth = any(re.search(p, content, re.I) for p in auth_patterns)
|
|
if has_auth:
|
|
passed.append("[OK] Authentication/authorization detected")
|
|
|
|
# Check for rate limiting
|
|
rate_patterns = [r'rateLimit', r'throttle', r'rate.?limit']
|
|
has_rate = any(re.search(p, content, re.I) for p in rate_patterns)
|
|
if has_rate:
|
|
passed.append("[OK] Rate limiting present")
|
|
|
|
# Check for logging
|
|
log_patterns = [r'console\.log', r'logger\.', r'logging\.', r'log\.']
|
|
has_logging = any(re.search(p, content) for p in log_patterns)
|
|
if has_logging:
|
|
passed.append("[OK] Logging present")
|
|
|
|
except Exception as e:
|
|
issues.append(f"[X] Read error: {e}")
|
|
|
|
return {'file': str(file_path), 'passed': passed, 'issues': issues, 'type': 'code'}
|
|
|
|
def main():
|
|
target = sys.argv[1] if len(sys.argv) > 1 else "."
|
|
project_path = Path(target)
|
|
|
|
print("\n" + "=" * 60)
|
|
print(" API VALIDATOR - Endpoint Best Practices Check")
|
|
print("=" * 60 + "\n")
|
|
|
|
api_files = find_api_files(project_path)
|
|
|
|
if not api_files:
|
|
print("[!] No API files found.")
|
|
print(" Looking for: routes/, controllers/, api/, openapi.json/yaml")
|
|
sys.exit(0)
|
|
|
|
results = []
|
|
for file_path in api_files[:15]: # Limit
|
|
if 'openapi' in file_path.name.lower() or 'swagger' in file_path.name.lower():
|
|
result = check_openapi_spec(file_path)
|
|
else:
|
|
result = check_api_code(file_path)
|
|
results.append(result)
|
|
|
|
# Print results
|
|
total_issues = 0
|
|
total_passed = 0
|
|
|
|
for result in results:
|
|
print(f"\n[FILE] {result['file']} [{result['type']}]")
|
|
for item in result['passed']:
|
|
print(f" {item}")
|
|
total_passed += 1
|
|
for item in result['issues']:
|
|
print(f" {item}")
|
|
if item.startswith("[X]"):
|
|
total_issues += 1
|
|
|
|
print("\n" + "=" * 60)
|
|
print(f"[RESULTS] {total_passed} passed, {total_issues} critical issues")
|
|
print("=" * 60)
|
|
|
|
if total_issues == 0:
|
|
print("[OK] API validation passed")
|
|
sys.exit(0)
|
|
else:
|
|
print("[X] Fix critical issues before deployment")
|
|
sys.exit(1)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|