Skip to content
This repository was archived by the owner on Jul 3, 2026. It is now read-only.

Security: devsper-com/registry

Security

SECURITY.md

Security Policy — Devsper Registry

Reporting Vulnerabilities

If you discover a security vulnerability, please report it responsibly:

  1. Do NOT open a public GitHub issue
  2. Email security@devsper.com with:
    • Description of the vulnerability
    • Steps to reproduce
    • Potential impact assessment
  3. You will receive an acknowledgment within 48 hours
  4. We aim to release a fix within 7 days for critical issues

Security Architecture

Authentication

The registry supports three authentication methods:

MethodUse CaseToken Format
Better Auth JWT (JWKS)Web frontend sessionsRS256/ES256 signed JWT
Legacy JWTFallback when JWKS not configuredHS256 signed JWT
API KeyCLI/Twine publishinghm_ prefixed, SHA-256 hashed

JWT algorithm pinning: JWKS verification is pinned to RS256 and ES256 only, preventing algorithm confusion attacks.

API key security: Keys are hashed with SHA-256 before storage. Comparison uses crypto/subtle.ConstantTimeCompare to prevent timing attacks.

Authorization

  • Scope-based: API keys carry explicit scopes (read, publish, admin)
  • JWT sessions bypass scope checks (full access assumed for authenticated web users)
  • API key expiry is enforced at middleware level after database lookup

Input Validation

InputValidation
Package namesRegex: ^[a-z0-9]([a-z0-9._-]*[a-z0-9])?$, max 128 chars
Version stringsRegex: ^[0-9]+\.[0-9]+(\.[0-9]+)?([a-zA-Z0-9.+_-]*)$, max 64 chars
File uploadsExtensions restricted to .whl and .tar.gz only
.whl filesValidated as valid ZIP archives before storage
Upload sizeEnforced via http.MaxBytesReader (default: 100 MB)
SQL queriesAll parameterized via sqlc-generated code; search uses plainto_tsquery

CORS

CORS is restricted to an explicit origin allowlist derived from BASE_URL, FRONTEND_URL, HOMEPAGE_URL, and optional CORS_ALLOWED_ORIGINS configuration. Credentials are only sent for matching origins.

Rate Limiting

EndpointLimit
GlobalConfigurable via RATE_LIMIT_RPS (default: 10/sec)
Device auth (/api/v1/auth/device/*)5/min per IP
Upload (/api/v1/packages/*/upload)10/min per IP
Search (/api/v1/search)60/min per IP

The device flow store is capped at 10,000 pending requests to prevent memory exhaustion.

Storage Security

  • Server-side encryption: S3 PutObject uses AES-256 server-side encryption in production (disabled for MinIO/LocalStack in development)
  • Presigned URL TTL: Download URLs expire after 15 minutes
  • Upload timeout: S3 uploads have a 30-second context deadline

Transport Security (Caddy)

  • HSTS with preload directive (max-age 2 years)
  • X-Frame-Options: DENY
  • X-Content-Type-Options: nosniff
  • Permissions-Policy restricts camera, microphone, geolocation, payment
  • Content-Security-Policy with strict connect-src
  • Server header removed
  • /internal/* endpoints blocked from external access

Secrets Management

SecretSourceNotes
INTERNAL_SECRETEnvironment variable>= 32 chars enforced in production
JWT_SECRETEnvironment variableUsed only for legacy JWT
JWKS_URLEnvironment variableMust use HTTPS in production
OAuth client secretsEnvironment variableFlag: rotate regularly

Known Limitations

  1. Verification pipeline (verify.go): Currently a stub — no automated package verification is performed. Packages are marked "passed" immediately on upload.

  2. User code entropy (device flow): The 4-letter + 4-digit user code has ~29 bits of entropy with non-uniform distribution. Acceptable per RFC 8628 but could be improved.

  3. Docker Compose sslmode=disable: The production Docker Compose uses sslmode=disable for the PostgreSQL connection. This is acceptable for container-to-container networking on the same Docker bridge, but should NOT be used if the database is on a separate host.

  4. OAuth secrets in .env: The working tree contains real GitHub and Google OAuth client secrets in registry/.env. While gitignored, these should be rotated if the repository is ever made public or the secrets are compromised.

Security Audit Log

DateFindingSeverityStatus
2026-03-13Timing attack in VerifyAPIKeyCriticalFixed
2026-03-13Timing attack in RequireInternalSecretCriticalFixed
2026-03-13CORS reflects any origin with credentialsCriticalFixed
2026-03-13Debug fmt.Printf leaks auth contextCriticalFixed
2026-03-13Expired API keys not checkedCriticalFixed
2026-03-13OAuth secrets in working treeCriticalFlagged for rotation
2026-03-13No package name validationHighFixed
2026-03-13No version string validationHighFixed
2026-03-13No upload body size enforcementHighFixed
2026-03-13No file extension allowlistHighFixed
2026-03-13Presigned URL TTL too long (1hr)HighFixed (15min)
2026-03-13No S3 server-side encryptionHighFixed (conditional)
2026-03-13No JWT algorithm pinningHighFixed (RS256/ES256)
2026-03-13No JWKS HTTPS enforcementHighFixed (production)
2026-03-13Missing .dockerignore filesHighFixed
2026-03-13Caddyfile missing security headersHighFixed
2026-03-13Unbounded device flow storeHighFixed (10K cap)
2026-03-13INTERNAL_SECRET no min lengthMediumFixed (32 chars prod)
2026-03-13S3 upload no timeoutMediumFixed (30s)
2026-03-13No per-endpoint rate limitingMediumFixed
2026-03-13Caddyfile allows /internal/*MediumFixed
2026-03-13No .whl zip validationMediumFixed
2026-03-13Unconditional XFF trustMediumFixed
2026-03-13hm_ prefix not checked earlyLowFixed

There aren't any published security advisories