Skip to content

feat: support Authentication and Authorization for the proxy#2698

Open
jiashengguo wants to merge 6 commits into
devfrom
jiasheng-proxy
Open

feat: support Authentication and Authorization for the proxy#2698
jiashengguo wants to merge 6 commits into
devfrom
jiasheng-proxy

Conversation

@jiashengguo
Copy link
Copy Markdown
Member

@jiashengguo jiashengguo commented Jun 4, 2026

Summary by CodeRabbit

  • New Features

    • Added Studio API authentication to the proxy/studio command with Ed25519 request signature verification
    • New CLI options to provide the Studio auth key and configure signature age tolerance (defaults to 60s)
    • Per-request authorization enforcing user-scoped policy rules
  • Tests

    • Expanded test suite to cover signed-request handling, timestamp tolerance, public-key formats, and policy-based access scenarios
  • Chores

    • Declared policy plugin as a workspace dependency

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 4, 2026

PR changed again? Review this PR in Change Stack to compare snapshots and stay oriented.

Review Change Stack

📝 Walkthrough

Walkthrough

The CLI proxy adds optional Studio-signed request handling: Ed25519 signature verification with timestamp tolerance, CLI/env-configured public key and tolerance, and policy-backed per-request client selection that scopes DB access for authenticated users while remaining backwards-compatible when disabled.

Changes

Studio API Authentication and Authorization

Layer / File(s) Summary
Dependencies and CLI options
packages/cli/package.json, packages/cli/src/index.ts
Add @zenstackhq/plugin-policy dependency; extend proxy command with --studioAuthKey and --signatureToleranceSecs CLI options.
Proxy authentication and authorization logic
packages/cli/src/actions/proxy.ts
Import PolicyPlugin and Ed25519 verification; extend Options with studioAuthKey and signatureToleranceSecs; resolve studioAuthKey from CLI or ZENSTACK_STUDIO_AUTH_KEY; create authDb with PolicyPlugin when key is provided; extend createProxyApp() to capture raw JSON bodies and install createSignatureMiddleware() for /api/model and /api/schema; implement createSignatureMiddleware() parsing x-zenstack-signature with timestamp tolerance and Ed25519 v1 verification; implement resolveClient() to choose base or policy-scoped client (with optional $setAuth(claim.data)).
Authentication and authorization test coverage
packages/cli/test/proxy.test.ts
Add Ed25519 test key material and signature/bearer token helpers; add tests for required/optional signature modes, accepted public key formats, timestamp/replay tolerance, signature binding to Authorization, and policy-plugin authorization (anonymous, user-scoped, superUser, cross-user denial, and transaction behavior).

🎯 4 (Complex) | ⏱️ ~45 minutes


🐰 A tiny key, a timestamp bright,
I sign the payload through the night,
Policies whisper what to show,
Bound to tokens, seeds will grow,
Hopping tests pass—secure and light. 🥕✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately captures the main feature: adding authentication and authorization support to the proxy, which is reflected across all modified files including policy plugin integration, signature verification, auth options, and comprehensive tests.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jiasheng-proxy

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Jun 4, 2026

Claude finished @jiashengguo's task —— View job


PR Review: Authentication and Authorization for Proxy

Review Summary

This PR implements comprehensive authentication and authorization for the ZenStack proxy server. The implementation includes Ed25519 signature verification, user impersonation, and policy enforcement. Here's my detailed analysis:

Strengths

Security Architecture:

  • Well-designed Ed25519 signature verification system with proper timestamp-based replay protection
  • Clean separation between authentication (signature verification) and authorization (policy enforcement)
  • Secure handling of public keys with support for both PEM and base64 DER formats
  • Super-user bypass mechanism for administrative access
  • Proper error handling with generic 401 responses to avoid information leakage

Code Quality:

  • Excellent test coverage (74 comprehensive test cases covering all scenarios)
  • Clean, readable code with good separation of concerns
  • Proper TypeScript typing throughout
  • Good documentation and comments explaining complex logic

⚠️ Security Concerns

1. Key Validation (packages/cli/src/actions/proxy.ts:51-59)

function normalizePublicKey(key: string): string {
    key = key.trim();
    if (key.startsWith('-----BEGIN PUBLIC KEY-----')) {
        return key;
    }
    const b64 = key.replace(/-/g, '+').replace(/_/g, '/');
    return `-----BEGIN PUBLIC KEY-----\n${b64}\n-----END PUBLIC KEY-----`;
}
  • Issue: No validation that the input is actually a valid Ed25519 public key
  • Risk: Malformed keys could cause runtime errors or unexpected behavior
  • Recommendation: Add key validation using crypto.createPublicKey() to verify format

2. Signature Error Handling (packages/cli/src/actions/proxy.ts:355-364)

try {
    const isValid = verify(null, Buffer.from(message, 'utf8'), publicKey, Buffer.from(sig, 'base64url'));
    if (!isValid) {
        warnInvalidSignature();
        return res.status(401).json({ message: 'Invalid signature' });
    }
} catch {
    warnInvalidSignature();
    return res.status(401).json({ message: 'Invalid signature' });
}
  • Issue: Broad catch block may hide important errors (e.g., key format issues)
  • Recommendation: Add specific error handling for different failure modes

3. Authorization Token Parsing (packages/cli/src/actions/proxy.ts:393-398)

try {
    claim = JSON.parse(Buffer.from(token, 'base64').toString('utf8')) as UserClaim;
} catch {
    return client;
}
  • Issue: Silent failure on malformed tokens
  • Risk: Could mask issues in token generation/transmission
  • Recommendation: Consider logging malformed token attempts for debugging

🚀 Performance Considerations

Positive:

  • Efficient signature verification using native Node.js crypto
  • Smart throttling of invalid signature warnings (60-second intervals)
  • Minimal overhead when authentication is disabled
  • Proper connection pooling for databases

Potential Optimizations:

  • Consider caching normalized public keys if the same key is used frequently
  • The timestamp tolerance check is efficient and prevents DoS via old requests

🧪 Test Coverage Assessment

Excellent coverage including:

  • Basic functionality (schema serving, computed field handling, transactions)
  • Authentication edge cases (missing headers, invalid signatures, timestamp validation)
  • Authorization scenarios (super-user vs regular user access)
  • Key format handling (PEM vs base64 DER)
  • Policy enforcement with realistic access control scenarios

Test quality is particularly strong - tests are well-structured, cover error cases, and use realistic scenarios.

📝 Minor Code Quality Issues

1. Package.json (packages/cli/package.json:66)

  • Adding @zenstackhq/plugin-policy as a dependency is appropriate for the authorization features

2. CLI Integration (packages/cli/src/index.ts:268-276)

  • Good addition of CLI options for authentication configuration
  • Clear help text explaining the studioAuthKey parameter

🔒 Recommended Security Improvements

  1. Add key validation:
function normalizePublicKey(key: string): string {
    key = key.trim();
    if (key.startsWith('-----BEGIN PUBLIC KEY-----')) {
        // Validate PEM format
        try {
            crypto.createPublicKey(key);
        } catch {
            throw new Error('Invalid public key format');
        }
        return key;
    }
    const b64 = key.replace(/-/g, '+').replace(/_/g, '/');
    const pem = `-----BEGIN PUBLIC KEY-----\n${b64}\n-----END PUBLIC KEY-----`;
    // Validate converted key
    try {
        crypto.createPublicKey(pem);
    } catch {
        throw new Error('Invalid public key format');
    }
    return pem;
}
  1. Add rate limiting for failed authentication attempts to prevent brute force attacks

  2. Consider adding audit logging for authentication events (successful/failed attempts)

Overall Assessment

This is a high-quality implementation that adds essential security features to the ZenStack proxy. The code is well-architected, thoroughly tested, and follows security best practices. The few issues identified are minor and can be addressed in follow-up improvements.

Recommendation: ✅ Approve with the suggestion to address the key validation improvement in a future iteration.

The implementation successfully provides:

  • ✅ Strong authentication via Ed25519 signatures
  • ✅ Flexible authorization via policy plugin integration
  • ✅ Backward compatibility (works without authentication)
  • ✅ Comprehensive testing
  • ✅ Clear security boundaries between super-users and regular users

// Apply signature-verification middleware to all authenticated endpoints.
const toleranceSecs = options.signatureToleranceSecs ?? 60;
const normalizedKey = normalizePublicKey(options.studioAuthKey);
app.use(['/api/model', '/api/schema'], createSignatureMiddleware(normalizedKey, toleranceSecs));
@gitguardian
Copy link
Copy Markdown

gitguardian Bot commented Jun 4, 2026

⚠️ GitGuardian has uncovered 1 secret following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secret in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
33485392 Triggered Generic Private Key 12bf3f4 packages/cli/test/proxy.test.ts View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secret safely. Learn here the best practices.
  3. Revoke and rotate this secret.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds an authenticated mode for the CLI proxy where incoming requests must be Ed25519-signed by ZenStack Studio, and introduces Authorization-based user impersonation via the PolicyPlugin to enable access-policy enforcement.

Changes:

  • Add request-signature verification middleware (timestamp tolerance + optional inclusion of bearer token in the signed message).
  • Add proxy authorization support by selecting a policy-enforced client (PolicyPlugin + $setAuth) based on a base64-encoded JSON bearer claim.
  • Add CLI flags/env support (--studioAuthKey, ZENSTACK_STUDIO_AUTH_KEY, --signatureToleranceSecs) and extensive tests for the new behaviors.

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
pnpm-lock.yaml Adds workspace link for @zenstackhq/plugin-policy.
packages/cli/package.json Adds @zenstackhq/plugin-policy dependency to the CLI package.
packages/cli/src/index.ts Adds CLI options for Studio auth key and signature tolerance.
packages/cli/src/actions/proxy.ts Implements signature verification and Authorization-based client resolution using PolicyPlugin.
packages/cli/test/proxy.test.ts Adds tests covering signature verification, key formats, timestamp tolerance, and policy-based authorization scenarios.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/cli/src/actions/proxy.ts
Comment thread packages/cli/src/actions/proxy.ts
Comment thread packages/cli/src/actions/proxy.ts Outdated
Comment thread packages/cli/src/actions/proxy.ts Outdated
Comment thread packages/cli/src/actions/proxy.ts Outdated
Comment thread packages/cli/src/actions/proxy.ts
Comment thread packages/cli/src/index.ts
Comment thread packages/cli/test/proxy.test.ts
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Jun 4, 2026

Claude finished @jiashengguo's task —— View job


PR Review: Authentication and Authorization for Proxy

This PR implements comprehensive authentication and authorization features for the ZenStack proxy server. I've conducted a thorough review of the implementation, security aspects, and test coverage.

Strengths

Security Architecture:

  • Well-designed Ed25519 signature verification system with proper timestamp-based replay protection
  • Clean separation between authentication (signature verification) and authorization (policy enforcement)
  • Secure handling of public keys with support for both PEM and base64 DER formats
  • Super-user bypass mechanism for administrative access
  • Proper error handling with generic 401 responses to avoid information leakage

Code Quality:

  • Excellent test coverage (29 comprehensive test cases covering all scenarios)
  • Clean, readable code with good separation of concerns
  • Proper TypeScript typing throughout
  • Good documentation and comments explaining complex logic

⚠️ Security Concerns & Bugs

1. Authorization Bypass Issues (Critical - packages/cli/src/actions/proxy.ts:389, 398, 409)

// ISSUE: When authDb is configured, these scenarios bypass policy enforcement:
if (!authHeader?.startsWith('Bearer ')) {
    return client; // ❌ Should return authDb for anonymous policy evaluation
}
// ...
} catch {
    return client; // ❌ Should return authDb for malformed tokens
}
// ...
return client; // ❌ Unknown claim types bypass policies

Risk: Anonymous or malformed token requests get full access instead of being evaluated against policies (e.g., @@allow(...)).

Recommendation: Default to authDb for policy enforcement, only return client for explicit super-user claims.

2. Input Validation Issues

signatureToleranceSecs validation (packages/cli/src/actions/proxy.ts:268, packages/cli/src/index.ts:276):

const toleranceSecs = options.signatureToleranceSecs ?? 60; // Can be NaN
// ...
if (isNaN(requestTime) || Math.abs(now - requestTime) > toleranceSeconds) {
    // Math.abs(...) > NaN is always false, disabling replay protection
}

Risk: Invalid input can disable replay protection entirely.

3. Header Parsing Robustness (packages/cli/src/actions/proxy.ts:324, 351)

const parts = signatureHeader.split(',');
// ❌ Fails if header has spaces: "t=..., v1=..." 

const authorizationToken = authHeader && authHeader.startsWith('Bearer ') ? ...
// ❌ Case-sensitive, should handle "bearer" per RFC 9110

4. Public Key Validation (packages/cli/src/actions/proxy.ts:51-59)
No validation that the input is actually a valid Ed25519 public key - malformed keys could cause runtime errors.

🚀 Performance & Architecture

Positive:

  • Efficient signature verification using native Node.js crypto
  • Smart throttling of invalid signature warnings (60-second intervals)
  • Minimal overhead when authentication is disabled
  • Proper connection pooling for databases

CodeQL Alert: Missing rate limiting on route handler - consider adding rate limiting for failed authentication attempts.

🧪 Test Coverage Assessment

Excellent coverage including:

  • Basic functionality (schema serving, computed field handling, transactions)
  • Authentication edge cases (missing headers, invalid signatures, timestamp validation)
  • Authorization scenarios (super-user vs regular user access)
  • Key format handling (PEM vs base64 DER)
  • Policy enforcement with realistic access control scenarios

Missing Test Case: Anonymous/invalid-token scenarios when policies are configured (per Copilot review comment on line 647).

🔒 GitGuardian Alert - Test Keys

The hardcoded private key in packages/cli/test/proxy.test.ts:11 is flagged by GitGuardian. This appears to be a test-only Ed25519 keypair, which is acceptable for testing purposes, but consider:

  • Adding a comment clearly marking it as test-only
  • Ensuring it's never used in production

📝 Recommended Fixes

Priority 1 - Security:

// Fix authorization bypass
function resolveClient(client, authDb, req) {
    if (!authDb) return client;
    
    const authHeader = req.headers['authorization'];
    if (!authHeader?.toLowerCase().startsWith('bearer ')) {
        return authDb; // Policy enforcement for anonymous users
    }
    
    const token = authHeader.substring(7);
    let claim: UserClaim;
    try {
        claim = JSON.parse(Buffer.from(token, 'base64').toString('utf8'));
    } catch {
        return authDb; // Policy enforcement for malformed tokens
    }
    
    if (claim.type === 'superUser') return client;
    if (claim.type === 'user') {
        try {
            return authDb.$setAuth(claim.data);
        } catch {
            return authDb; // Handle invalid auth shape
        }
    }
    
    return authDb; // Policy enforcement for unknown claim types
}

Priority 2 - Input Validation:

  • Add finite number validation for signatureToleranceSecs
  • Trim header parts in signature parsing
  • Add public key format validation

Overall Assessment

This is a high-quality implementation that adds essential security features. The authorization bypass issues are significant but easily fixable. The code is well-architected and thoroughly tested.

Recommendation: ✅ Conditional Approval - Fix the authorization bypass issues before merging.

The implementation successfully provides:

  • ✅ Strong authentication via Ed25519 signatures
  • ⚠️ Authorization via policy plugin (needs bypass fix)
  • ✅ Backward compatibility
  • ✅ Comprehensive testing
  • ✅ Clear security boundaries

Todo List:

  • Read repository guidelines (CLAUDE.md)
  • Analyze changed files and implementation
  • Review security concerns and vulnerabilities
  • Assess test coverage and quality
  • Check performance implications
  • Provide comprehensive review feedback

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
packages/cli/src/actions/proxy.ts (2)

51-59: ⚡ Quick win

Consider adding base64url padding restoration.

The base64url-to-base64 conversion replaces - with + and _ with /, but doesn't restore padding (= characters) that may have been stripped from the base64url string per RFC 4648.

While many decoders tolerate missing padding, explicitly restoring it would ensure maximum compatibility:

♻️ Proposed fix to restore padding
 function normalizePublicKey(key: string): string {
     key = key.trim();
     if (key.startsWith('-----BEGIN PUBLIC KEY-----')) {
         return key;
     }
     // Convert base64url → standard base64, then wrap in PEM markers.
     const b64 = key.replace(/-/g, '+').replace(/_/g, '/');
-    return `-----BEGIN PUBLIC KEY-----\n${b64}\n-----END PUBLIC KEY-----`;
+    // Restore padding if necessary (base64url may omit trailing '=')
+    const paddedB64 = b64 + '='.repeat((4 - (b64.length % 4)) % 4);
+    return `-----BEGIN PUBLIC KEY-----\n${paddedB64}\n-----END PUBLIC KEY-----`;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/src/actions/proxy.ts` around lines 51 - 59, The
normalizePublicKey function currently converts base64url characters but doesn't
restore '=' padding which can be stripped; update normalizePublicKey to, after
replacing '-'→'+' and '_'→'/', compute remainder = b64.length % 4 and if
remainder !== 0 append '=' characters (4 - remainder) to b64 before wrapping
with PEM markers so the resulting base64 is properly padded and decodable.

266-271: ⚖️ Poor tradeoff

Consider adding rate limiting for signature-verified endpoints.

While signature verification prevents unauthorized access, it doesn't protect against DoS attacks where an attacker floods the server with requests that force expensive signature verification operations.

Consider implementing rate limiting using a package like express-rate-limit:

  • Limit requests per IP address (e.g., 100 requests/minute)
  • Apply stricter limits to failed authentication attempts
  • Return 429 (Too Many Requests) when limits are exceeded

This can be deferred to a follow-up PR if you want to ship the core auth functionality first.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/src/actions/proxy.ts` around lines 266 - 271, Add IP-based rate
limiting to the signature-protected endpoints by applying an express-rate-limit
middleware to the same routes you currently call
app.use(['/api/model','/api/schema'], ...). Install and configure
express-rate-limit with a sensible default (e.g., 100 requests per minute per
IP) and attach it before createSignatureMiddleware so expensive signature
verification is protected; for stricter limits on failed authentication, either
create a second limiter (e.g., 20 req/min) that you trigger from inside
createSignatureMiddleware on verification failures or use a shared store
(memory/Redis) to increment failure counts and return 429 when thresholds are
exceeded; reference createSignatureMiddleware, normalizePublicKey,
options.studioAuthKey, and options.signatureToleranceSecs to locate where to add
and configure the limiter.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/cli/package.json`:
- Line 48: The CI is failing because package.json was updated to add the
dependency "`@zenstackhq/plugin-policy`" but the pnpm-lock.yaml is not
regenerated; run pnpm install at the repo root to update pnpm-lock.yaml, verify
the lockfile changes include the new "`@zenstackhq/plugin-policy`" entry, and
commit the updated pnpm-lock.yaml alongside the package.json change so the
pipeline will pass.

In `@packages/cli/src/index.ts`:
- Around line 266-277: The --signatureToleranceSecs Option currently uses
.argParser((v) => parseInt(v, 10)) without validating the parsed value and also
lacks a declared default; update the Option for '--signatureToleranceSecs' to
call .default(60) and change the argParser to validate the result of parseInt
(e.g., if isNaN(parsed) throw an appropriate commander InvalidArgumentError or
Error) so non-numeric input is rejected immediately; reference the Option
instance, the '--signatureToleranceSecs' flag, the current argParser using
parseInt, and ensure the default aligns with the default used in proxy.ts.

---

Nitpick comments:
In `@packages/cli/src/actions/proxy.ts`:
- Around line 51-59: The normalizePublicKey function currently converts
base64url characters but doesn't restore '=' padding which can be stripped;
update normalizePublicKey to, after replacing '-'→'+' and '_'→'/', compute
remainder = b64.length % 4 and if remainder !== 0 append '=' characters (4 -
remainder) to b64 before wrapping with PEM markers so the resulting base64 is
properly padded and decodable.
- Around line 266-271: Add IP-based rate limiting to the signature-protected
endpoints by applying an express-rate-limit middleware to the same routes you
currently call app.use(['/api/model','/api/schema'], ...). Install and configure
express-rate-limit with a sensible default (e.g., 100 requests per minute per
IP) and attach it before createSignatureMiddleware so expensive signature
verification is protected; for stricter limits on failed authentication, either
create a second limiter (e.g., 20 req/min) that you trigger from inside
createSignatureMiddleware on verification failures or use a shared store
(memory/Redis) to increment failure counts and return 429 when thresholds are
exceeded; reference createSignatureMiddleware, normalizePublicKey,
options.studioAuthKey, and options.signatureToleranceSecs to locate where to add
and configure the limiter.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 746ce95d-d4e0-4772-b655-eb099f2ff622

📥 Commits

Reviewing files that changed from the base of the PR and between 2a9689e and d8268cd.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • packages/cli/package.json
  • packages/cli/src/actions/proxy.ts
  • packages/cli/src/index.ts
  • packages/cli/test/proxy.test.ts

Comment thread packages/cli/package.json
Comment thread packages/cli/src/index.ts
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Jun 6, 2026

Claude finished @jiashengguo's task —— View job


PR Review: Authentication and Authorization for Proxy

I've completed a thorough analysis of this PR that adds authentication and authorization to the ZenStack proxy server. Here's my comprehensive review:

Todo List:

  • Read and analyze all changed files
  • Review authentication implementation and security architecture
  • Check for potential bugs and vulnerabilities
  • Assess performance implications
  • Evaluate test coverage and quality
  • Provide comprehensive feedback

Strengths

Security Architecture:

  • Well-designed Ed25519 signature verification with proper timestamp-based replay protection
  • Clean separation between authentication (signature verification) and authorization (policy enforcement)
  • Secure handling of public keys with support for both PEM and base64 DER formats
  • Super-user bypass mechanism for administrative access
  • Generic 401 responses to avoid information leakage

Code Quality:

  • Excellent test coverage (29+ comprehensive test cases covering all scenarios)
  • Clean, readable code with good separation of concerns
  • Proper TypeScript typing throughout
  • Good documentation explaining complex logic

⚠️ Critical Security Issues

1. Authorization Bypass Vulnerability (packages/cli/src/actions/proxy.ts:406, 417, 424)

The most critical issue is in the resolveClient function where several scenarios bypass policy enforcement:

// ❌ CRITICAL: Anonymous requests bypass policies
if (!authHeader?.startsWith('Bearer ')) {
    return authDb; // Should return authDb, not client
}

// ❌ CRITICAL: Malformed tokens bypass policies  
} catch (err) {
    return authDb; // Should return authDb, not client
}

// ❌ CRITICAL: Unknown claim types bypass policies
// Falls through to implicit client return

Risk: When authDb is configured, anonymous requests and malformed tokens get full database access instead of being evaluated against access policies (e.g., @@allow('all', auth() != null && ...)).

Fix: Always default to authDb for policy enforcement unless explicitly super-user:

function resolveClient(client, authDb, req) {
    if (!authDb) return client;
    
    const authHeader = req.headers['authorization'];
    if (!authHeader?.toLowerCase().startsWith('bearer ')) {
        return authDb; // Policy enforcement for anonymous
    }
    
    let claim;
    try {
        claim = UserClaimSchema.parse(JSON.parse(Buffer.from(authHeader.substring(7), 'base64').toString('utf8')));
    } catch {
        return authDb; // Policy enforcement for malformed tokens
    }
    
    if (claim.type === 'superUser') return client;
    if (claim.type === 'user') return authDb.$setAuth(claim.data);
    
    return authDb; // Policy enforcement for unknown types
}

2. Input Validation Issues

  • signatureToleranceSecs: Fixed in latest commit - now properly validates positive integers
  • Header parsing: signatureHeader.split(',') fails with spaces (e.g., t=..., v1=...)
  • Case sensitivity: Authorization scheme should be case-insensitive per RFC 9110

🚀 Performance & Architecture

Positive:

  • Efficient Ed25519 signature verification using native Node.js crypto
  • Smart throttling of invalid signature warnings (60-second intervals)
  • Minimal overhead when authentication is disabled
  • Proper database connection pooling

Recommendations:

  • Consider rate limiting for signature verification endpoints (as noted by CodeQL)
  • Current implementation is CPU-efficient for signature operations

🧪 Test Coverage Assessment

Excellent Coverage:

  • Basic functionality (schema serving, computed fields, transactions)
  • Authentication edge cases (missing headers, invalid signatures, timestamp validation)
  • Authorization scenarios (super-user vs regular user access)
  • Key format handling (PEM vs base64 DER formats)
  • Policy enforcement with realistic access control

Missing Test Coverage:

  • Anonymous requests when policies are configured (per authorization bypass issue)
  • Case-insensitive Authorization header handling
  • Malformed signature header parsing with spaces

🔒 Additional Security Concerns

3. Public Key Validation (packages/cli/src/actions/proxy.ts:68-76)

function normalizePublicKey(key: string): string {
    // ❌ No validation that input is actually a valid Ed25519 key
    const b64 = key.replace(/-/g, '+').replace(/_/g, '/');
    return `-----BEGIN PUBLIC KEY-----\n${b64}\n-----END PUBLIC KEY-----`;
}

Risk: Malformed keys could cause runtime errors.

4. Base64URL Padding (Minor)
The base64url conversion doesn't restore padding, though most decoders tolerate this.

5. GitGuardian Alert
The hardcoded test keys in proxy.test.ts:11 are flagged but acceptable for testing (consider adding test-only comment).


📋 Recommended Priority Fixes

Priority 1 (Critical):

  1. Fix authorization bypass in resolveClient function
  2. Add test coverage for anonymous policy scenarios

Priority 2 (High):

  1. Add public key format validation
  2. Fix header parsing to handle spaces
  3. Make Authorization scheme case-insensitive

Priority 3 (Medium):

  1. Add rate limiting for signature verification endpoints
  2. Consider base64url padding restoration
  3. Add audit logging for authentication events

Overall Assessment

This is a high-quality implementation that adds essential security features to ZenStack proxy. The authorization bypass issue is critical but easily fixable. The authentication mechanism is solid, test coverage is comprehensive, and the overall architecture is sound.

Recommendation: ⚠️ Conditional Approval - Fix the authorization bypass before merging.

Security Summary:

  • ✅ Strong Ed25519 signature verification
  • ⚠️ Authorization bypass needs immediate fix
  • ✅ Proper replay protection
  • ✅ Secure key handling
  • ✅ Comprehensive testing

The implementation successfully provides robust authentication with a critical authorization fix needed for complete security.


Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/cli/src/actions/proxy.ts (1)

393-406: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Docstring is inconsistent with implementation.

The docstring states "No / invalid token: return the base client" but the implementation correctly returns authDb (policy-enforced client) when the token is missing or invalid. The implementation is the secure behavior; update the docstring to match.

 /**
  * Resolves the appropriate client for a request based on the Authorization header.
  *
  * - No studioAuthKey configured (authDb is undefined): always return the base client.
  * - SuperUser claim: return the base client (full access, no policy enforcement).
  * - Regular user claim: return authDb with the user identity set via $setAuth.
- * - No / invalid token: return the base client.
+ * - No / invalid token: return authDb (policy-enforced, typically denying access).
  */
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/src/actions/proxy.ts` around lines 393 - 406, The docstring for
resolveClient is incorrect: update its description to state that when authDb is
provided and the Authorization header is missing or invalid, resolveClient
returns the policy-enforced client (authDb), while if authDb is undefined it
returns the base client (client). Edit the docstring to clearly document these
behaviors and mention that a valid Bearer token leads to token-based user
resolution (and returning the base client scoped to that user) so the comment
matches the implementation in resolveClient.
🧹 Nitpick comments (2)
packages/cli/src/actions/proxy.ts (1)

287-287: ⚡ Quick win

Consider adding rate limiting for production deployments.

The route handler performs authorization but lacks rate limiting, which could allow brute-force attacks on signature verification. While this may be acceptable for local development use, production deployments would benefit from rate limiting to prevent abuse.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/src/actions/proxy.ts` at line 287, The middleware chain mounting
signature checks (app.use(['/api/model', '/api/schema'],
createSignatureMiddleware(normalizedKey, toleranceSecs))) lacks rate limiting;
add a rate-limiter middleware (e.g. express-rate-limit or a lightweight
in-memory/token-bucket implementation) and insert it before
createSignatureMiddleware so requests to '/api/model' and '/api/schema' are
throttled; make the limiter configurable by env (NODE_ENV or a dedicated
RATE_LIMIT_* vars) so it is enabled in production but can be relaxed/disabled in
local/dev, and apply it to the same route registrations to prevent brute-force
signature attempts.

Source: Linters/SAST tools

packages/cli/test/proxy.test.ts (1)

10-20: 💤 Low value

Add comment clarifying test-only keys to suppress false positive alerts.

GitGuardian flagged these as hardcoded secrets. While this is a false positive (these are intentionally generated test-only keys with no production value), adding a clear comment will help automated scanners and future reviewers understand the intent.

📝 Suggested documentation
-// ─── Ed25519 key pair for tests ───────────────────────────────────────────────
+// ─── Ed25519 key pair for tests ───────────────────────────────────────────────
+// IMPORTANT: These are TEST-ONLY keys generated specifically for unit tests.
+// They have no production value and are safe to commit.
+// nosec: GitGuardian false positive - intentional test fixtures
 const TEST_PRIVATE_KEY = `-----BEGIN PRIVATE KEY-----
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/test/proxy.test.ts` around lines 10 - 20, Add a clear
top-of-file comment above the TEST_PRIVATE_KEY, TEST_PUBLIC_KEY, and
TEST_PUBLIC_KEY_DER constants stating these keys are intentionally generated
test-only keys with no production value, should not be used outside tests, and
are safe to be committed (e.g., "TEST-ONLY: non-secret, generated keys for unit
tests; do not use in production; safe to commit"). This will signal automated
scanners and reviewers (and can be referenced by whitelisting rules) that these
are false-positive secrets.

Source: Pipeline failures

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/cli/src/actions/proxy.ts`:
- Around line 393-406: The docstring for resolveClient is incorrect: update its
description to state that when authDb is provided and the Authorization header
is missing or invalid, resolveClient returns the policy-enforced client
(authDb), while if authDb is undefined it returns the base client (client). Edit
the docstring to clearly document these behaviors and mention that a valid
Bearer token leads to token-based user resolution (and returning the base client
scoped to that user) so the comment matches the implementation in resolveClient.

---

Nitpick comments:
In `@packages/cli/src/actions/proxy.ts`:
- Line 287: The middleware chain mounting signature checks
(app.use(['/api/model', '/api/schema'], createSignatureMiddleware(normalizedKey,
toleranceSecs))) lacks rate limiting; add a rate-limiter middleware (e.g.
express-rate-limit or a lightweight in-memory/token-bucket implementation) and
insert it before createSignatureMiddleware so requests to '/api/model' and
'/api/schema' are throttled; make the limiter configurable by env (NODE_ENV or a
dedicated RATE_LIMIT_* vars) so it is enabled in production but can be
relaxed/disabled in local/dev, and apply it to the same route registrations to
prevent brute-force signature attempts.

In `@packages/cli/test/proxy.test.ts`:
- Around line 10-20: Add a clear top-of-file comment above the TEST_PRIVATE_KEY,
TEST_PUBLIC_KEY, and TEST_PUBLIC_KEY_DER constants stating these keys are
intentionally generated test-only keys with no production value, should not be
used outside tests, and are safe to be committed (e.g., "TEST-ONLY: non-secret,
generated keys for unit tests; do not use in production; safe to commit"). This
will signal automated scanners and reviewers (and can be referenced by
whitelisting rules) that these are false-positive secrets.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 62f8e9dc-ee45-4176-a699-ec26745652b7

📥 Commits

Reviewing files that changed from the base of the PR and between d8268cd and ef4984b.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (3)
  • packages/cli/src/actions/proxy.ts
  • packages/cli/src/index.ts
  • packages/cli/test/proxy.test.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants