Skip to content

fix: extend offline secret caching to token-based auth and fix connection check#272

Open
devin-ai-integration[bot] wants to merge 2 commits into
mainfrom
devin/1781886077-fix-offline-fallback
Open

fix: extend offline secret caching to token-based auth and fix connection check#272
devin-ai-integration[bot] wants to merge 2 commits into
mainfrom
devin/1781886077-fix-offline-fallback

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Description 📣

Fixes two issues that prevent the CLI's offline secret caching from working in common scenarios:

1. ValidateInfisicalAPIConnection() only checked for transport errors, not HTTP status codes

When a load balancer or reverse proxy returns HTTP 503 (server down), the Go http.Get succeeds without error — so the CLI treated the server as "connected" and never fell back to cached secrets. Now checks for a 2xx response:

// Before: 503 from a proxy → err == nil → isConnected = true → no fallback
_, err := http.Get(...)
return err == nil

// After: 503 → resp.StatusCode == 503 → isConnected = false → fallback triggers
resp, err := http.Get(...)
defer resp.Body.Close()
return resp.StatusCode >= 200 && resp.StatusCode < 300

2. Token-based auth (service tokens, Universal Auth / machine identity) had zero offline caching

GetAllEnvironmentVariables() only wrote/read secret backups in the infisical login user-auth code path. The else branch for InfisicalToken and UniversalAuthAccessToken had no caching logic at all — any fetch failure returned an error immediately with no fallback.

Added the same backup write-on-success / read-on-failure pattern to the token-based auth branch, using the existing WriteBackupSecrets/ReadBackupSecrets/GetBackupEncryptionKey helpers. The fallback only triggers on connection errors or server errors (5xx) — client errors like 401/403 (revoked token, permission denied) propagate correctly so users see actionable auth failure messages.

Type ✨

  • Bug fix
  • New feature
  • Improvement
  • Breaking change
  • Documentation

Tests 🛠️

Scenario to verify:

  1. Authenticate with a machine identity (Universal Auth) and run infisical run --env=prod --path=/test --projectId=<id> -- env — secrets should be fetched and injected as before
  2. Verify ~/.infisical/secrets-backup/ contains an encrypted backup file
  3. Make the Infisical server unreachable (stop it, or have a proxy return 503)
  4. Re-run the same command — should print a warning and serve cached secrets instead of failing
  5. Revoke/invalidate the token and re-run — should show an auth error (NOT fall back to cache)

Link to Devin session: https://app.devin.ai/sessions/2dfab5dd573b484f84cce72d5f38c757

…tion check

- ValidateInfisicalAPIConnection now checks HTTP status code, not just
  transport errors. A 503 from a load balancer/proxy no longer counts as
  'connected', allowing the cache fallback to trigger correctly.

- Added secret backup/restore for service token and Universal Auth
  (machine identity) code paths. Previously, offline caching only worked
  for infisical-login user auth. Now all auth methods write secrets to
  the encrypted backup on success and fall back to cached secrets on
  fetch failure.

Co-Authored-By: jake <jake@infisical.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@greptile-apps

greptile-apps Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR extends offline secret caching to token-based auth (service tokens and Universal Auth machine identities) and fixes ValidateInfisicalAPIConnection to require a 2xx response rather than just a successful TCP handshake.

  • common.go: The connection check now reads the HTTP status code, so a proxy returning 503 correctly triggers the offline fallback; resp.Body is now properly closed.
  • secrets.go: Write-on-success and read-on-failure caching is added to the else branch covering InfisicalToken and UniversalAuthAccessToken; a shouldFallback guard using errors.As(*api.APIError) is meant to prevent 4xx auth errors from being masked by cache, but this guard does not fire for service-token-detail failures because that error is wrapped with %v rather than %w.

Confidence Score: 4/5

Safe to merge for Universal Auth users; service token users who also pass --projectId can have a revoked token silently masked by cached secrets.

The 4xx guard introduced to prevent stale-cache masking of auth errors works correctly for Universal Auth because the typed error propagates directly, but breaks for service tokens whose CallGetServiceTokenDetailsV2 failure is re-wrapped with %v (not %w), losing the error chain that errors.As relies on. A revoked service token combined with a cached result for the same workspace/environment would surface wrong secrets with no auth error.

packages/util/secrets.go — specifically the service-token error-wrapping in GetPlainTextSecretsViaServiceToken and how it interacts with the new errors.As guard.

Important Files Changed

Filename Overview
packages/util/common.go Extends ValidateInfisicalAPIConnection to check for 2xx HTTP status codes rather than just transport-level success; body is now properly closed with defer.
packages/util/secrets.go Adds offline caching (write-on-success / read-on-failure) to the token-based auth branch; caching is silently skipped for service tokens unless --projectId is passed, and when --projectId IS passed, auth errors from CallGetServiceTokenDetailsV2 are wrapped with %v (not %w), breaking the errors.As 4xx-detection that is supposed to prevent stale cache from masking revoked-token errors.

Reviews (2): Last reviewed commit: "fix: close resp.Body in connection check..." | Re-trigger Greptile

Comment thread packages/util/common.go
Comment thread packages/util/secrets.go
Comment thread packages/util/common.go
…to non-auth errors

Address review feedback:
- Close resp.Body in ValidateInfisicalAPIConnection to prevent connection leak
- Only fall back to cached secrets on connection/server errors (5xx), not on
  client errors (4xx) like 401/403 which indicate auth issues (revoked token,
  permission denied). This prevents silently serving stale secrets when the
  real problem is invalid credentials.

Co-Authored-By: jake <jake@infisical.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Addressed all Greptile feedback in d7e51f3:

  1. resp.Body leak — Fixed with defer resp.Body.Close()
  2. Fallback on all errors — Fixed. Now only falls back to cache on connection errors (GenericRequestError) or server errors (5xx). Client errors (4xx like 401/403) propagate correctly so users see actionable auth failure messages.
  3. Service token + WorkspaceId — By design. WorkspaceId is set by the caller via --projectId flag or INFISICAL_PROJECT_ID env var (see run.go:80). The community user's command includes --projectId=xxxxx. For Universal Auth, WorkspaceId is already enforced (line 367). For service tokens without --projectId, caching gracefully degrades to a no-op.
  4. SSRF — Pre-existing pattern, out of scope for this bugfix.

@jakehulberg

Copy link
Copy Markdown

@greptile re-review please

Comment thread packages/util/secrets.go
Comment on lines +386 to +390
shouldFallback := true
var apiErr *api.APIError
if errors.As(errorToReturn, &apiErr) && apiErr.StatusCode >= 400 && apiErr.StatusCode < 500 {
shouldFallback = false
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 4xx masking still active for service tokens when --projectId is passed

GetPlainTextSecretsViaServiceToken wraps any error from CallGetServiceTokenDetailsV2 with fmt.Errorf("unable to get service token details. [err=%v]", err) — using %v, not %w. That strips the error chain, so errors.As(errorToReturn, &apiErr) always returns false for this error path, leaving shouldFallback = true. If a user runs infisical run --projectId=<id> --token=<service-token> ... and the token is revoked (the API returns 401), the 4xx guard is bypassed and the fallback silently serves stale cached secrets instead of surfacing the auth error. The fix that was applied to the Universal Auth path (which directly propagates the typed *api.APIError) does not cover service tokens due to this wrapping.

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.

1 participant