Last active
December 10, 2025 19:11
-
-
Save Gerg/6051804e04ccd01f4e429b0c5e911168 to your computer and use it in GitHub Desktop.
🤖 Script to inflate UAA access token by adding user to long-named groups
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| Script to create a large number of UAA groups and assign them to a user | |
| to inflate their JWT access token size beyond 8KB. | |
| Note that clients can filter what scopes are included in the token. You | |
| may need to add "poetic-token-bloat-*" as a wildcard scope to the client | |
| requesting the token. | |
| 🤖 This script was generated with Gemini 3 Pro, with some light modification | |
| by Greg Cobb to make it actually work""" | |
| import sys | |
| import subprocess | |
| import argparse | |
| # Prefix to identify groups created by this script | |
| PREFIX = "poetic-token-bloat-" | |
| # A selection of verses (Public Domain) - "The Raven" by Edgar Allan Poe | |
| # Used to generate deterministic, artistic group names | |
| VERSES = [ | |
| "once-upon-a-midnight-dreary-while-i-pondered-weak-and-weary", | |
| "over-many-a-quaint-and-curious-volume-of-forgotten-lore", | |
| "while-i-nodded-nearly-napping-suddenly-there-came-a-tapping", | |
| "as-of-some-one-gently-rapping-rapping-at-my-chamber-door", | |
| "tis-some-visitor-i-muttered-tapping-at-my-chamber-door", | |
| "only-this-and-nothing-more", | |
| "ah-distinctly-i-remember-it-was-in-the-bleak-december", | |
| "and-each-separate-dying-ember-wrought-its-ghost-upon-the-floor", | |
| "eagerly-i-wished-the-morrow-vainly-i-had-sought-to-borrow", | |
| "from-my-books-surcease-of-sorrow-sorrow-for-the-lost-lenore", | |
| "for-the-rare-and-radiant-maiden-whom-the-angels-name-lenore", | |
| "nameless-here-for-evermore", | |
| "and-the-silken-sad-uncertain-rustling-of-each-purple-curtain", | |
| "thrilled-me-filled-me-with-fantastic-terrors-never-felt-before", | |
| "so-that-now-to-still-the-beating-of-my-heart-i-stood-repeating", | |
| "tis-some-visitor-entreating-entrance-at-my-chamber-door", | |
| "some-late-visitor-entreating-entrance-at-my-chamber-door", | |
| "this-it-is-and-nothing-more", | |
| "presently-my-soul-grew-stronger-hesitating-then-no-longer", | |
| "sir-said-i-or-madam-truly-your-forgiveness-i-implore", | |
| "but-the-fact-is-i-was-napping-and-so-gently-you-came-rapping", | |
| "and-so-faintly-you-came-tapping-tapping-at-my-chamber-door", | |
| "that-i-scarce-was-sure-i-heard-you-here-i-opened-wide-the-door", | |
| "darkness-there-and-nothing-more", | |
| "deep-into-that-darkness-peering-long-i-stood-there-wondering-fearing", | |
| "doubting-dreaming-dreams-no-mortal-ever-dared-to-dream-before", | |
| "but-the-silence-was-unbroken-and-the-stillness-gave-no-token", | |
| "and-the-only-word-there-spoken-was-the-whispered-word-lenore", | |
| "this-i-whispered-and-an-echo-murmured-back-the-word-lenore", | |
| "merely-this-and-nothing-more", | |
| "back-into-the-chamber-turning-all-my-soul-within-me-burning", | |
| "soon-again-i-heard-a-tapping-somewhat-louder-than-before", | |
| "surely-said-i-surely-that-is-something-at-my-window-lattice", | |
| "let-me-see-then-what-thereat-is-and-this-mystery-explore", | |
| "let-my-heart-be-still-a-moment-and-this-mystery-explore", | |
| "tis-the-wind-and-nothing-more", | |
| "open-here-i-flung-the-shutter-when-with-many-a-flirt-and-flutter", | |
| "in-there-stepped-a-stately-raven-of-the-saintly-days-of-yore", | |
| "not-the-least-obeisance-made-he-not-a-minute-stopped-or-stayed-he", | |
| "but-with-mien-of-lord-or-lady-perched-above-my-chamber-door", | |
| "perched-upon-a-bust-of-pallas-just-above-my-chamber-door", | |
| "perched-and-sat-and-nothing-more", | |
| "then-this-ebony-bird-beguiling-my-sad-fancy-into-smiling", | |
| "by-the-grave-and-stern-decorum-of-the-countenance-it-wore", | |
| "though-thy-crest-be-shorn-and-shaven-thou-i-said-art-sure-no-craven", | |
| "ghastly-grim-and-ancient-raven-wandering-from-the-nightly-shore", | |
| "tell-me-what-thy-lordly-name-is-on-the-night-s-plutonian-shore", | |
| "quoth-the-raven-nevermore", | |
| "much-i-marvelled-this-ungainly-fowl-to-hear-discourse-so-plainly", | |
| "though-its-answer-little-meaning-little-relevancy-bore", | |
| "for-we-cannot-help-agreeing-that-no-living-human-being", | |
| "ever-yet-was-blessed-with-seeing-bird-above-his-chamber-door", | |
| "bird-or-beast-upon-the-sculptured-bust-above-his-chamber-door", | |
| "with-such-name-as-nevermore" | |
| ] | |
| def run_uaac(args): | |
| """Executes a uaac command and suppresses expected errors.""" | |
| try: | |
| # Check if the group/member already exists by capturing stderr | |
| result = subprocess.run( | |
| ["uaac"] + args, | |
| capture_output=True, | |
| text=True, | |
| check=False | |
| ) | |
| if result.returncode != 0: | |
| # We ignore specific errors that indicate idempotency | |
| err = result.stderr.lower() | |
| if "already exists" in err or \ | |
| "already a member" in err or \ | |
| "scim resource already exists" in err: | |
| return True # Treated as success (idempotent) | |
| print(f"Error running 'uaac {' '.join(args)}': { | |
| result.stderr.strip()}") | |
| return False | |
| return True | |
| except FileNotFoundError: | |
| print("Error: 'uaac' command not found. Is it installed and on your PATH?") | |
| sys.exit(1) | |
| def generate_groups(target_kb=9): | |
| """Generates a list of group names until the target size is reached.""" | |
| groups = [] | |
| current_bytes = 0 | |
| target_bytes = target_kb * 1024 | |
| max_group_length = 250 # UAA limit is 255, leaving room for unique suffix | |
| # Repeat verses if we run out | |
| i = 0 | |
| while current_bytes < target_bytes: | |
| verse = VERSES[i % len(VERSES)] | |
| # Calculate how many times to repeat the verse to fill the group name | |
| # Account for prefix length | |
| base_name = f"{PREFIX}{verse}" | |
| # If verse is too short, repeat it | |
| while len(base_name) < max_group_length - 10: # buffer for suffix | |
| base_name += f"-{verse}" | |
| # Trim to max length if needed | |
| base_name = base_name[:max_group_length] | |
| # Add a counter to ensure uniqueness if we loop | |
| suffix = f"-{i}" if i >= len(VERSES) else "" | |
| # Ensure suffix fits | |
| if len(base_name) + len(suffix) > 255: | |
| base_name = base_name[:255-len(suffix)] | |
| group_name = f"{base_name}{suffix}" | |
| groups.append(group_name) | |
| current_bytes += len(group_name) | |
| i += 1 | |
| return groups | |
| def get_existing_poetic_groups(): | |
| """Fetches all existing groups that match the poetic prefix.""" | |
| try: | |
| result = subprocess.run( | |
| ["uaac", "groups"], | |
| capture_output=True, | |
| text=True, | |
| check=False | |
| ) | |
| if result.returncode != 0: | |
| print(f"Error listing groups: {result.stderr.strip()}") | |
| return [] | |
| groups = [] | |
| for line in result.stdout.splitlines(): | |
| # In 'uaac groups' output, group names are keys (less indented) | |
| # e.g. " my-group-name" | |
| # followed by indented properties " id: ..." | |
| stripped = line.strip() | |
| if stripped.startswith(PREFIX): | |
| # Ensure it's a group name line and not a property value | |
| # Simple check: does it start with the prefix? | |
| groups.append(stripped) | |
| return list(set(groups)) | |
| except FileNotFoundError: | |
| print("Error: 'uaac' command not found.") | |
| sys.exit(1) | |
| def cleanup(): | |
| """Removes all groups created by this script.""" | |
| print("🧹 Searching for existing poetic groups to clean up...") | |
| # Find existing groups using 'uaac groups' | |
| groups = get_existing_poetic_groups() | |
| if not groups: | |
| print("No poetic groups found.") | |
| return | |
| print(f"Found {len(groups)} groups to delete.") | |
| count = 0 | |
| for group in groups: | |
| if run_uaac(["group", "delete", group]): | |
| pass | |
| count += 1 | |
| if count % 10 == 0: | |
| print(f" Processed {count}/{len(groups)} deletion attempts...") | |
| print("Cleanup complete.") | |
| def main(): | |
| """Main function to parse args and run group creation or cleanup.""" | |
| parser = argparse.ArgumentParser( | |
| description="Inflate UAA token size with poetic groups." | |
| ) | |
| parser.add_argument( | |
| "username", | |
| nargs="?", | |
| help="The UAA username to assign groups to" | |
| ) | |
| parser.add_argument( | |
| "--cleanup", | |
| action="store_true", | |
| help="Remove the groups created by this script" | |
| ) | |
| args = parser.parse_args() | |
| if args.cleanup: | |
| cleanup() | |
| return | |
| username = args.username | |
| if not username: | |
| if sys.stdin.isatty(): | |
| username = input("Enter username: ").strip() | |
| else: | |
| # Try reading from pipe | |
| username = sys.stdin.readline().strip() | |
| if not username: | |
| print("Error: Username is required.") | |
| sys.exit(1) | |
| print(f"generating groups for user '{username}'...") | |
| groups = generate_groups(target_kb=9) | |
| print(f"Plan: Create {len(groups)} groups.") | |
| success_count = 0 | |
| for i, group in enumerate(groups): | |
| if run_uaac(["group", "add", group]): | |
| if run_uaac(["member", "add", group, username]): | |
| success_count += 1 | |
| if (i + 1) % 10 == 0: | |
| print(f" Processed {i + 1}/{len(groups)} groups...") | |
| print(f"\nDone! User '{username}' assigned to { | |
| success_count} poetic groups.") | |
| print("Token size should now exceed 8KB limits.") | |
| if __name__ == "__main__": | |
| main() | |
| # ============================================================================== | |
| # MIT License | |
| # | |
| # Copyright (c) 2025 Greg Cobb | |
| # | |
| # Permission is hereby granted, free of charge, to any person obtaining a copy | |
| # of this software and associated documentation files (the "Software"), to deal | |
| # in the Software without restriction, including without limitation the rights | |
| # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| # copies of the Software, and to permit persons to whom the Software is | |
| # furnished to do so, subject to the following conditions: | |
| # | |
| # The above copyright notice and this permission notice shall be included in all | |
| # copies or substantial portions of the Software. | |
| # | |
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
| # SOFTWARE. | |
| # ============================================================================== | |
| # ============================================================================== |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment