fix(auth): enforce readonly scopes by revoking stale tokens and adding client-side guard#520
Conversation
…g client-side guard When a user previously authenticated with full scopes, `gws auth login --readonly` did not actually enforce read-only access. The underlying refresh token retained the original consent grant, and Google's token endpoint ignores the scope parameter on refresh requests — so the cached token silently carried write access. This commit fixes the issue with a layered approach: - Persist the configured scope set to scopes.json on login so scope changes can be detected across sessions. - When scopes change, revoke the old refresh token via Google's revocation endpoint and clear local credential/cache files before starting the new OAuth flow. - Add a client-side scope guard that rejects write-scope API requests when the session is readonly. - Show scope_mode and configured_scopes in `gws auth status`. - Clean up scopes.json on logout. Fixes googleworkspace#168
🦋 Changeset detectedLatest commit: 14a2dfe The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request addresses a critical security vulnerability where Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces a robust mechanism for enforcing read-only scopes by persisting scope information, revoking stale tokens on scope changes, and adding a client-side guard. The implementation is well-structured and addresses the reported issue effectively.
I have one main suggestion for improvement:
Add error handling to the save_scopes function to prevent silent failures and ensure application state consistency.
This change will make the new authentication flow more robust.
Keep both upstream's new tests (find_unmatched_services, extract_scopes_from_doc) and our scope persistence/guard tests.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces a robust mechanism for enforcing read-only scopes, which is a critical security improvement. The layered approach of persisting scopes, revoking tokens on scope change, and adding a client-side guard is well-thought-out. My review includes a suggestion to improve error handling when saving scopes to ensure the system's state is always consistent. Overall, this is a solid contribution to improving the tool's security and reliability.
Address review feedback: save_scopes now returns Result<(), GwsError> and the call site uses ? to propagate failures. This ensures scopes.json is always consistent with the actual login state.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request effectively addresses the issue of enforcing read-only scopes by revoking stale tokens upon scope change and adding a client-side guard. The implementation is robust and includes new unit tests. I've identified a couple of areas for improvement regarding code duplication and test structure that would enhance the maintainability and reliability of the new logic. My comments focus on refactoring to remove duplicated code and making the tests less brittle.
…ation DRY up the scope classification logic that was duplicated between is_readonly_session and check_scope_allowed. Rewrite tests to call the extracted helper directly instead of re-implementing the logic.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces a mechanism to enforce read-only scopes by revoking old tokens and adding a client-side guard. The implementation is a solid step towards fixing the security issue. I've identified a critical issue where the failure to delete old credential files is ignored, which could lead to an inconsistent state and bypass the intended scope enforcement. I've also pointed out a high-severity issue where the failure of the token revocation API call is silently ignored. Addressing these points will make the implementation more robust and secure.
Warn the user when token revocation fails (network error or non-200 response) so they know the old token may still be valid server-side. Return errors when credential/cache file removal fails (ignoring NotFound) instead of silently continuing with stale files.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request effectively addresses a security flaw in read-only scope enforcement by revoking stale tokens when scopes change. The implementation is robust, including a client-side guard as a defense-in-depth measure. My review includes one high-severity security recommendation to prevent terminal escape sequence injection by sanitizing error output, in line with the repository's general rules.
| eprintln!( | ||
| "Warning: could not revoke old token ({e}). \ | ||
| The old token may still be valid on Google's side." | ||
| ); |
There was a problem hiding this comment.
Printing the reqwest::Error directly to the terminal using the default Display trait can be a security risk. If the error is related to DNS resolution or other network issues where an attacker can control parts of the error message, it could lead to terminal escape sequence injection. This could be used to trick the user or hide other malicious activity on the terminal.
To mitigate this, you should use the Debug format specifier ({:?}), which is designed for developer output and will safely escape control characters.
eprintln!(
"Warning: could not revoke old token ({:?}). \
The old token may still be valid on Google's side.",
e
);References
- Sanitize error strings printed to the terminal to prevent escape sequence injection.
Summary
Fixes #168.
gws auth login --readonlydoesn't actually enforce read-only access when the user previously logged in with broader scopes. The refresh token keeps its original grants, and Google ignores thescopeparam on refresh — so the token silently has write access.What this PR does
scopes.jsonon login so we can detect scope changes laterscope_modeingws auth statusfor transparencyWhy just
prompt=consentisn't enoughGoogle's consent screen shows previously-granted scopes pre-checked. Users click "Allow" and unknowingly re-grant broad access. Revoking the token first removes the prior grant entirely.
Test plan
cargo clippy -- -D warningscleangws auth login→gws auth login --readonly→ write op blocked →gws auth login→ write op works