Skip to content

Conversation

@AdnaneKhan
Copy link
Owner

@AdnaneKhan AdnaneKhan commented Jan 1, 2026

Pull Request Overview

Inform user of reason for empty secrets if repo shows accessible org secrets but the repository is private and attached to an organization with a free plan.

What does this PR add/solve?

Clarify behavior.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactoring (no functional changes)
  • Performance improvement
  • Security enhancement
  • CI/CD improvement

Areas affected

  • Enumeration functionality
  • Attack/Exploitation features
  • GitHub API integration
  • Workflow analysis
  • Runner detection
  • MCP Server
  • CLI/User interface
  • Documentation
  • Tests
  • Configuration

Testing Steps

Manual Testing

Automated Testing

  • All existing unit tests pass
  • New unit tests added for new functionality
  • Integration tests updated/added if applicable
  • Code coverage maintained or improved

Testing Commands

# Add specific commands to test this PR
# Example:
# python -m pytest unit_test/test_new_feature.py
# python -m gatox enumerate --help

Security Considerations

  • This change does not introduce new security risks
  • This change has been reviewed for potential security vulnerabilities
  • This change improves existing security measures
  • N/A - No security implications

Documentation

  • Code changes are documented with appropriate comments
  • Public API changes are documented
  • README.md updated if needed
  • Documentation site updated if needed (docs/ folder)
  • CHANGELOG.md updated if applicable

Checklist

  • My code follows the project's coding style and conventions
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published

Additional Notes

Screenshots (if applicable)

Performance Impact

Breaking Changes

Related Issues/PRs

Closes #
Related to #

else:
Output.error("Unexpected run artifact structure!")
for k, v in extracted.items():
print(f"{k}={v}")

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information High

This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.

Copilot Autofix

AI about 1 month ago

In general, to fix clear-text logging of secrets, you must avoid printing or logging the actual secret values. Options include: omitting them entirely, logging only metadata (like key names), or logging redacted forms (such as lengths or masked strings). The goal is that any logs or captured stdout do not contain usable secret material, while still providing helpful information to the user.

For this specific code in gatox/attack/secrets/secrets_attack.py, the problematic behavior is the print(f"{k}={v}") inside the loop over extracted.items(). We can keep informing the user which secrets were exfiltrated and that secret values are available, without printing their contents. A minimal, behavior-preserving change is to print the secret name and a placeholder/redacted indication instead of the raw value, e.g. print(f"{k}=<redacted>") or include the length of v if useful. This change alters only line 262 and keeps the rest of the flow intact.

Concretely:

  • In the else block that currently does for k, v in extracted.items(): print(f"{k}={v}"), replace the print statement with a version that:
    • Does not include v in clear text.
    • Optionally includes len(str(v)) or some basic info that isn’t itself secret.
  • No new imports are needed; we just change the format string.
  • All related alert variants are fixed simultaneously because they all refer to the same sink.

Suggested changeset 1
gatox/attack/secrets/secrets_attack.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/gatox/attack/secrets/secrets_attack.py b/gatox/attack/secrets/secrets_attack.py
--- a/gatox/attack/secrets/secrets_attack.py
+++ b/gatox/attack/secrets/secrets_attack.py
@@ -259,7 +259,7 @@
                     )
                 else:
                     for k, v in extracted.items():
-                        print(f"{k}={v}")
+                        print(f"{k}=<redacted>")
             if delete_action and (
                 not finegrain_scopes or "actions:write" in finegrain_scopes
             ):
EOF
@@ -259,7 +259,7 @@
)
else:
for k, v in extracted.items():
print(f"{k}={v}")
print(f"{k}=<redacted>")
if delete_action and (
not finegrain_scopes or "actions:write" in finegrain_scopes
):
Copilot is powered by AI and may make mistakes. Always verify output.
@AdnaneKhan AdnaneKhan marked this pull request as ready for review January 5, 2026 02:20
Copilot AI review requested due to automatic review settings January 5, 2026 02:20
Copy link
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 enhances error handling for GitHub Actions secrets exfiltration by addressing edge cases where workflow artifacts may not be immediately available and when no extractable secrets are present despite enumeration showing accessible secrets.

Key Changes:

  • Implements retry logic with 30-second timeout for artifact retrieval to handle asynchronous availability
  • Adds informative warning when only the default github_token is extracted, indicating org secrets limitations
  • Refactors conditional logic to improve code clarity by eliminating nested if-else statements

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

if not extracted:
Output.warn(
"No secrets were extracted. Org secrets are not "
"accessible to public repos on the free plan."
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

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

The error message states that "Org secrets are not accessible to public repos on the free plan" but according to the PR description, this issue occurs with private repositories attached to organizations with a free plan. The message should be updated to accurately reflect that this limitation applies to private repos on free organization plans, not just public repos.

Suggested change
"accessible to public repos on the free plan."
"accessible to private repos on free organization plans."

Copilot uses AI. Check for mistakes.
Comment on lines +227 to +233
for _ in range(30):
res = await self.api.retrieve_workflow_artifact(
target_repo, workflow_id
)
if res and "output_updated.json" in res and "lookup.txt" in res:
break
await asyncio.sleep(1)
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

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

The retry loop uses a hard-coded 30 second timeout (30 iterations × 1 second sleep). Consider making this configurable or adding a comment explaining why 30 seconds is appropriate. This could lead to a poor user experience if artifacts consistently fail to upload or if they typically become available much sooner.

Copilot uses AI. Check for mistakes.
Comment on lines +255 to +259
if not extracted:
Output.warn(
"No secrets were extracted. Org secrets are not "
"accessible to public repos on the free plan."
)
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

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

The new warning message for empty secrets (lines 255-259) lacks test coverage. Consider adding a test case that verifies this warning is displayed when only the github_token is present in the extracted secrets, to ensure the filtering and warning logic works as expected.

Copilot uses AI. Check for mistakes.
Comment on lines +227 to +233
for _ in range(30):
res = await self.api.retrieve_workflow_artifact(
target_repo, workflow_id
)
if res and "output_updated.json" in res and "lookup.txt" in res:
break
await asyncio.sleep(1)
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

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

The artifact retrieval retry logic (lines 227-233) lacks test coverage. Consider adding test cases that verify: 1) successful retrieval after retries, 2) timeout after all retries are exhausted, and 3) immediate success on first attempt. This ensures the retry mechanism works correctly in different scenarios.

Copilot uses AI. Check for mistakes.
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.

2 participants