Skip to content

fix(auth): enforce readonly scopes by revoking stale tokens and adding client-side guard#520

Open
gerfalcon wants to merge 1 commit intogoogleworkspace:mainfrom
gerfalcon:fix/issue-168-readonly-scope-enforcement
Open

fix(auth): enforce readonly scopes by revoking stale tokens and adding client-side guard#520
gerfalcon wants to merge 1 commit intogoogleworkspace:mainfrom
gerfalcon:fix/issue-168-readonly-scope-enforcement

Conversation

@gerfalcon
Copy link

@gerfalcon gerfalcon commented Mar 17, 2026

Summary

Fixes #168.

gws auth login --readonly doesn'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 the scope param on refresh — so the token silently has write access.

What this PR does

  1. Saves configured scopes to scopes.json on login so we can detect scope changes later
  2. Revokes the old refresh token when scopes change, clearing local creds before re-authenticating — this removes the prior consent grant so Google only shows the new scopes
  3. Blocks write operations client-side when in a readonly session (defense-in-depth, enforced in auth::get_token so helpers like +send are also covered)
  4. Shows scope_mode in gws auth status for transparency

Why just prompt=consent isn't enough

Google'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

  • 2 new unit tests + full suite passes (776 tests)
  • cargo clippy -- -D warnings clean
  • gws gmail +send in readonly session → blocked with clear error: "This operation requires scope '...' (write access), but the current session uses read-only scopes."
  • gws calendar +agenda in readonly session → works (uses .readonly scope)
  • gws drive files list in readonly session → blocked (Discovery doc's first scope is broad drive)
  • gws auth status → shows scope_mode: readonly and configured_scopes
  • Switch scopes.json to default (write) scopes → gws gmail +send --dry-run passes scope guard
  • gws auth login --readonlygws auth login (full) → prints "Scopes changed — revoked previous credentials." and re-authenticates with full scopes
  • gws auth logoutscopes.json listed in removed and confirmed deleted

@gerfalcon gerfalcon requested a review from jpoehnelt as a code owner March 17, 2026 16:42
@changeset-bot
Copy link

changeset-bot bot commented Mar 17, 2026

🦋 Changeset detected

Latest commit: 505a52a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@googleworkspace/cli Patch

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

@googleworkspace-bot googleworkspace-bot added area: auth area: core Core CLI parsing, commands, error handling, utilities labels Mar 17, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, 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 gws auth login --readonly failed to enforce read-only access if a user had previously authenticated with broader permissions. The changes introduce a robust, multi-layered approach to ensure that requested scopes are strictly adhered to, preventing unintended write access and providing clearer feedback to the user about their current authentication state.

Highlights

  • Scope persistence: Configured scopes are now saved to scopes.json to detect changes across sessions.
  • Token revocation on scope change: Old refresh tokens are revoked via Google's endpoint and local credentials cleared if requested scopes differ from previously saved ones, ensuring new consent grants are clean.
  • Client-side scope guard: Before API calls, the system verifies scope compatibility, rejecting write-scope requests in read-only sessions with an informative error.
  • Status display: gws auth status now shows scope_mode and configured_scopes for improved transparency.
Changelog
  • .changeset/fix-readonly-scope-enforcement.md
    • Added a new changeset entry for the readonly scope enforcement fix.
  • src/auth_commands.rs
    • Introduced scopes_path, save_scopes, load_saved_scopes, is_readonly_session, and check_scope_allowed functions to manage and enforce authentication scopes.
    • Implemented logic within handle_login to detect scope changes, revoke previous refresh tokens, and clear local credentials.
    • Enhanced handle_status to output scope_mode and configured_scopes for better transparency.
    • Modified handle_logout to ensure the scopes.json file is removed upon logout.
    • Added comprehensive unit tests for scope persistence, readonly session detection, and client-side scope guarding.
  • src/main.rs
    • Integrated auth_commands::check_scope_allowed to prevent write operations in read-only sessions.
Activity
  • Unit tests for scope persistence, readonly detection, and scope guard (6 new tests) have been implemented and passed.
  • cargo clippy -- -D warnings passed without warnings.
  • The full test suite (445 tests) passed.
  • Manual test cases for gws auth login (full to readonly), gws gmail +send in readonly, gws auth status display, gws auth login (full) for write operations, and gws auth logout cleanup are outlined in the PR description.
Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Generative AI Prohibited Use Policy, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

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.

@googleworkspace-bot
Copy link
Collaborator

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

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.

@googleworkspace-bot
Copy link
Collaborator

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

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.

@googleworkspace-bot
Copy link
Collaborator

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

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.

@googleworkspace-bot
Copy link
Collaborator

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

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.

@googleworkspace-bot
Copy link
Collaborator

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request effectively addresses the issue of enforcing read-only scopes by revoking stale tokens on scope changes and adding a client-side guard. The implementation is solid, introducing mechanisms to save, load, and compare scopes across sessions, and correctly handling token revocation and local credential cleanup. I've identified one high-severity issue related to security best practices that should be addressed.

@googleworkspace-bot
Copy link
Collaborator

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

The pull request introduces new functionality to enforce read-only scopes by revoking stale tokens upon scope changes and adding a client-side guard. This involves saving and loading configured scopes, checking if a session is read-only, and validating requested scopes. The changes also update handle_login, handle_logout, and handle_status to incorporate scope management and add new unit tests. Review feedback highlights a critical vulnerability regarding terminal escape sequence injection when printing error messages, and several high-severity issues related to blocking the async runtime with synchronous file I/O operations, requiring conversion of file system operations and related functions to their asynchronous counterparts and proper awaiting.

@googleworkspace-bot
Copy link
Collaborator

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a robust mechanism for enforcing read-only scopes, a critical security and user experience improvement. The changes include saving scopes on login, revoking old tokens when scopes changes, and adding a client-side guard to prevent write operations in a read-only session.

My review focuses on potential performance improvements. I've identified two areas where file I/O operations are performed repeatedly, and I've provided suggestions to optimize these by caching the results of file reads. These changes will make the authentication and status-checking flows more efficient.

@googleworkspace-bot
Copy link
Collaborator

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a critical fix to enforce read-only scopes by revoking stale tokens and adding a client-side guard. The changes are well-structured, touching upon login, status, and logout functionalities to ensure consistent behavior. The addition of tests for the new scope logic is also a great improvement. My review includes one high-severity comment regarding a performance issue in the new token revocation logic.

@googleworkspace-bot
Copy link
Collaborator

/gemini review

1 similar comment
@googleworkspace-bot
Copy link
Collaborator

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a robust mechanism to enforce read-only sessions by revoking stale tokens on scope changes and adding a client-side guard to block write operations. The implementation is well-structured and includes necessary changes to login, logout, and status commands, along with corresponding tests. My review identifies a minor omission in the scope classification logic that could lead to incorrect behavior in an edge case. Overall, this is a high-quality contribution that significantly improves the authentication flow's security.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a robust mechanism for enforcing read-only scopes, which is a great security and usability improvement. The implementation correctly identifies scope changes, revokes old tokens, and adds a client-side guard. My review focuses on improving the error handling in the new logic to make it even more robust. Specifically, I've identified two places where file loading/parsing errors are silently ignored, which could undermine the new security features under certain failure conditions. Addressing these will ensure the read-only enforcement is reliable even when configuration files are corrupted.

@googleworkspace-bot
Copy link
Collaborator

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request implements robust enforcement of read-only scopes for the Google Workspace CLI. Key changes include adding a client-side guard in get_token to reject write-scope requests if the session is read-only, persisting configured scopes to a scopes.json file, and revoking stale refresh tokens with Google when scopes change during login. Additionally, the gws auth status command now displays the current scope configuration, and the scopes.json file is properly cleaned up during logout. New unit tests validate the scope persistence and read-only detection logic.

@googleworkspace-bot
Copy link
Collaborator

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request effectively addresses a critical authentication bug by enforcing read-only scopes. The implementation correctly revokes stale tokens when scopes change and adds a client-side guard to prevent unintended write operations. The changes are well-tested and improve the tool's security and reliability. I have one suggestion to improve the maintainability of the new token revocation logic.

@googleworkspace-bot
Copy link
Collaborator

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request effectively addresses the issue of enforcing read-only scopes by revoking stale tokens and adding a client-side guard. The changes are well-structured and include necessary additions like persisting scopes, handling token revocation, and updating status/logout commands. My review includes one suggestion to improve error reporting for file removal failures, which will enhance usability in error scenarios.

Comment on lines +420 to +433
if let Err(e) = tokio::fs::remove_file(&enc_path).await {
if e.kind() != std::io::ErrorKind::NotFound {
return Err(GwsError::Auth(format!(
"Failed to remove old credentials file: {e}. Please remove it manually."
)));
}
}
if let Err(e) = tokio::fs::remove_file(token_cache_path()).await {
if e.kind() != std::io::ErrorKind::NotFound {
return Err(GwsError::Auth(format!(
"Failed to remove old token cache: {e}. Please remove it manually."
)));
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The error messages for failing to remove the old credentials and token cache files should include the file paths. This will help users with manual cleanup if the automatic removal fails by telling them exactly which file to remove.

            if let Err(e) = tokio::fs::remove_file(&enc_path).await {
                if e.kind() != std::io::ErrorKind::NotFound {
                    return Err(GwsError::Auth(format!(
                        "Failed to remove old credentials file '{}': {e}. Please remove it manually.",
                        enc_path.display()
                    )));
                }
            }
            let token_path = token_cache_path();
            if let Err(e) = tokio::fs::remove_file(&token_path).await {
                if e.kind() != std::io::ErrorKind::NotFound {
                    return Err(GwsError::Auth(format!(
                        "Failed to remove old token cache '{}': {e}. Please remove it manually.",
                        token_path.display()
                    )));
                }
            }

@jpoehnelt
Copy link
Member

⚠️ Rebase requiredsrc/ has moved to crates/google-workspace-cli/src/ as part of a workspace refactor (#613). Please rebase on main to resolve path conflicts.

@gerfalcon gerfalcon force-pushed the fix/issue-168-readonly-scope-enforcement branch from e5ffef7 to b1b3e53 Compare March 24, 2026 21:18
…g client-side guard

Rebased on main after workspace refactor (googleworkspace#613). All changes now target
crates/google-workspace-cli/src/.

- Persist configured scopes to scopes.json on login
- Revoke old refresh token when scopes change (extracted into
  attempt_token_revocation helper)
- Client-side scope guard in auth::get_token blocks write ops in
  readonly sessions (covers helpers like +send)
- load_saved_scopes returns Result to surface corrupt scopes.json
- Show scope_mode in auth status
- Clean up scopes.json on logout
- Sanitize error output via sanitize_for_terminal
- Add 'profile' as non-write scope alias

Fixes googleworkspace#168
@gerfalcon gerfalcon force-pushed the fix/issue-168-readonly-scope-enforcement branch from b1b3e53 to 505a52a Compare March 24, 2026 21:23
@gerfalcon
Copy link
Author

gerfalcon commented Mar 24, 2026

Thank you for raising it @jpoehnelt! Resolved the conflicts and all the unit and manual tests passed.

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

Labels

area: auth area: core Core CLI parsing, commands, error handling, utilities

Projects

None yet

Development

Successfully merging this pull request may close these issues.

gws auth login --readonly + auth export --unmasked appears to allow full access on external machine

3 participants