Skip to content

[SECURITY] Credential rotation scheduler + API permission validation - closes #58 #59#80

Merged
sjackson0109 merged 12 commits into
sjackson0109:mainfrom
Ibrahim-3d:feat/58-59-credential-rotation-permission-validation
May 24, 2026
Merged

[SECURITY] Credential rotation scheduler + API permission validation - closes #58 #59#80
sjackson0109 merged 12 commits into
sjackson0109:mainfrom
Ibrahim-3d:feat/58-59-credential-rotation-permission-validation

Conversation

@Ibrahim-3d

Copy link
Copy Markdown
Collaborator

Summary

  • [SECURITY] Implement credential rotation mechanism #58 — Adds CredentialRotationScheduler daemon thread that checks rotation status every N hours and fires a notification callback (GUI alert, log, etc.)
  • [SECURITY] Add API key permission validation #59 — Adds PermissionValidator that validates exchange API permissions on startup against required/trading permission sets, with full JSONL audit trail
  • validate_credentials_on_startup() convenience function handles both checks in one call
  • Rotation uses backup/restore pattern — zero data loss if new credentials fail to encrypt

Changes

  • Modified: app/pt_credentials.pyCredentialMetadata, rotation methods, PermissionValidator, CredentialRotationScheduler, validate_credentials_on_startup()
  • New: app/test_credentials_rotation.py — 22 tests covering all scenarios

Usage

from pt_credentials import validate_credentials_on_startup, CredentialRotationScheduler

# Startup check (permissions + rotation warning)
ok, msg = validate_credentials_on_startup(
    permission_fetcher=lambda: exchange.get_permissions(),
    require_trading=True,
    notify_rotation=lambda w: show_alert(w),
)

# Background rotation scheduler
scheduler = CredentialRotationScheduler(
    notification_callback=lambda msg: logger.warning(msg),
    check_interval_hours=24,
)
scheduler.start()

Test plan

  • 22 unit tests pass
  • Rotation backup/restore tested
  • Overdue and near-due warnings verified
  • Permission audit JSONL log verified
  • Fetcher exception handling tested

Closes #58
Closes #59

Copilot AI review requested due to automatic review settings May 17, 2026 20:31
@Ibrahim-3d Ibrahim-3d requested a review from sjackson0109 as a code owner May 17, 2026 20:31

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds credential rotation scheduling, metadata tracking, and API permission validation to the credential management subsystem, plus unit tests.

Changes:

  • Introduces CredentialMetadata, PermissionAuditResult, PermissionValidator, and CredentialRotationScheduler along with rotation/backup logic in SecureCredentialManager.
  • Adds validate_credentials_on_startup convenience entry point and structured logging throughout.
  • Adds unit tests covering metadata, encryption roundtrip, rotation, permission validation, and scheduler lifecycle.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 14 comments.

File Description
app/pt_credentials.py New rotation/permission classes + refactored encrypt/decrypt/migrate flow.
app/test_credentials_rotation.py New tests for metadata, manager, validator, and scheduler.

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

Comment thread app/pt_credentials.py Outdated
Comment thread app/pt_credentials.py Outdated
Comment thread app/pt_credentials.py Outdated
Comment thread app/pt_credentials.py Outdated
Comment thread app/pt_credentials.py Outdated
Comment thread app/test_credentials_rotation.py
Comment thread app/test_credentials_rotation.py Outdated
Comment thread app/pt_credentials.py
Comment thread app/pt_credentials.py
Comment thread app/pt_credentials.py Outdated
@sjackson0109 sjackson0109 self-assigned this May 17, 2026
@sjackson0109 sjackson0109 added enhancement New feature or request architecture component-security Security components component-api API integration component-architecture System architecture and design components labels May 17, 2026
@sjackson0109

Copy link
Copy Markdown
Owner

Hello @Ibrahim-3d, thank you for your great efforts here. I can see what you are striving to achieve, I would like to pull this branch and test it, just no time lately.

Can I ask you to review the copilot comments here #80 (review) and respond to each with either justification to resolve the dispute, or counter/dismiss it with a valid reason.

I’ll try and get some time towards the end of the week to look at your copy of this code. Looks comprehensive from my initial view.

@Ibrahim-3d

Ibrahim-3d commented May 18, 2026

Copy link
Copy Markdown
Collaborator Author

All 14 Copilot review comments addressed in commit bf8a6bf. Inline replies posted directly on each thread. All threads resolved.

Note: Threads are resolved and collapsed. To view any thread, open the Files changed tab and click "Show resolved" or the collapsed thread indicator.

# File Topic Status
1 pt_credentials.py all_ok expression always evaluates False - dead code Resolved
2 pt_credentials.py Rotation rollback does not restore metadata file Resolved
3 pt_credentials.py Partial write leaves mismatched key/secret pair on disk Resolved
4 pt_credentials.py rotation_interval_days not updated in existing metadata on re-encrypt Resolved
5 pt_credentials.py _get_machine_password uses Windows-only env vars Resolved
6 pt_credentials.py Rotation scheduler fires callback every tick while overdue Resolved
7 pt_credentials.py timedelta imported but never used Resolved
8 pt_credentials.py Audit log has no size cap or rotation Resolved
9 pt_credentials.py Plaintext fallback removed, silently locking out users on vault failure Resolved
10 test_credentials_rotation.py tearDown missing, scheduler callback not asserted Resolved
11 test_credentials_rotation.py import shutil local inside tearDown Resolved
12 pt_credentials.py import shutil local inside rotate_credentials Resolved
13 pt_credentials.py from_dict silently drops unknown keys but raises TypeError on missing required fields Resolved
14 pt_credentials.py %d format truncates float check_interval_hours in log output Resolved

@Ibrahim-3d

Copy link
Copy Markdown
Collaborator Author

Local CI Simulation Results

Ran all workflow steps locally against this branch prior to owner approval. Results:

Code Quality & Testing

Check Tool Result
Code formatting black --check ✅ Pass — 0 files need reformatting
Import ordering isort --check ✅ Pass
Linting (hard errors) flake8 --select=E9,F63,F7,F82 ✅ Pass — 0 syntax/undefined-name errors
Linting (extended) flake8 --exit-zero ✅ Pass — 0 warnings in new files
Unit tests pytest ✅ All tests pass (see table below)

Unit Test Results

File Tests Result
test_circuit_breaker.py 20 ✅ All pass
test_credentials_rotation.py 30 ✅ All pass
test_error_handler.py 22 ✅ All pass
test_backup_validation.py 35 ✅ All pass
test_database_manager.py 29 ✅ All pass
test_security_logger.py 25 ✅ All pass

CI/CD Pipeline

Workflow Status Reason
Code Quality & Testing ⏳ Awaiting owner approval Fork PR — first-time contributor gate
PowerTrader AI+ CI/CD ⏳ Awaiting owner approval Fork PR — first-time contributor gate
Project Management ⏳ Awaiting owner approval Fork PR — first-time contributor gate

All three workflows are queued with action_required status. This is GitHub's security gate for PRs from fork contributors. Once approved by @sjackson0109, they will execute against the same code that was verified locally above.

Extends SecureCredentialManager with rotation status tracking and graceful
rotation with backup/restore on failure. Adds CredentialRotationScheduler
daemon thread for automated rotation warnings. Adds PermissionValidator with
JSONL audit trail and validate_credentials_on_startup() helper.

- 22 unit tests, all passing
…-platform fixes

- Atomic writes via temp->rename prevent mismatched key/secret on partial failure
- rotate_credentials now backs up and restores metadata alongside ciphertext
- rotation_interval_days updated in existing metadata on re-encrypt
- _get_machine_password uses socket.gethostname()+getpass.getuser() (cross-platform)
- CredentialRotationScheduler deduplicates warnings (only fires on message change)
- Minimum scheduler interval enforced (60s) to prevent tight-loop misconfiguration
- PermissionValidator audit log has MAX_AUDIT_LINES cap + secure file permissions
- get_credentials restores plaintext fallback to prevent user lockout on vault failure
- _load_metadata catches TypeError for corrupt/partial JSON
- shutil moved to module-level import
- timedelta import removed (unused)
- Tests: shutil at top, proper tearDown in all classes, dedup and overdue callback tests
Copilot AI review requested due to automatic review settings May 18, 2026 18:38
@Ibrahim-3d Ibrahim-3d force-pushed the feat/58-59-credential-rotation-permission-validation branch from 296124a to d999b4b Compare May 18, 2026 18:39

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 19 comments.

Comment thread app/pt_credentials.py
Comment thread app/pt_credentials.py
Comment thread app/pt_credentials.py Outdated
Comment thread app/pt_credentials.py Outdated
Comment thread app/pt_credentials.py Outdated
Comment thread app/pt_credentials.py Outdated
Comment thread app/pt_credentials.py Outdated
Comment thread app/pt_credentials.py
Comment thread app/pt_credentials.py
Comment thread app/pt_credentials.py Outdated
@sjackson0109

Copy link
Copy Markdown
Owner

@Ibrahim-3d we have another large series of copilot reviewer comments. Any chance you can read and respond to them? #80 (review)

- rotate_credentials rollback: replace shutil.copy2 with os.replace for
  atomic restore (POSIX rename, no partial-restore window on interruption)
- rotate_credentials: snapshot created_at before calling encrypt_credentials;
  restore it if encrypt_credentials constructed fresh metadata (which resets
  created_at when metadata file is missing or corrupt at rotation time)
@Ibrahim-3d

Copy link
Copy Markdown
Collaborator Author

Manual Testing Guide — PR #80: Credential Rotation + Permission Validation

Prerequisites

git fetch fork && git checkout feat/58-59-credential-rotation-permission-validation
cd app
pip install cryptography

1. Run unit tests

python -m pytest test_credentials_rotation.py -v

Expected: All 30 tests pass.

2. Smoke test — full rotation with rollback protection

python -c "
import tempfile
from pt_credentials import SecureCredentialManager
with tempfile.TemporaryDirectory() as d:
    m = SecureCredentialManager(d)
    # Initial encrypt
    m.encrypt_credentials('key_v1', 'secret_v1')
    print('v1 encrypted:', m.decrypt_credentials())
    # Rotate to v2
    ok = m.rotate_credentials('key_v2', 'secret_v2')
    print('rotation ok:', ok)
    print('v2 decrypts:', m.decrypt_credentials())
    # Verify created_at preserved
    meta1 = m._load_metadata()
    m.rotate_credentials('key_v3', 'secret_v3')
    meta2 = m._load_metadata()
    assert meta1.created_at == meta2.created_at, 'created_at changed!'
    print('PASS: created_at preserved across rotation')
"

3. Verify atomic rollback (key change)

python -c "
import tempfile, unittest.mock
from pt_credentials import SecureCredentialManager
with tempfile.TemporaryDirectory() as d:
    m = SecureCredentialManager(d)
    m.encrypt_credentials('orig_key', 'orig_secret')
    # Force encrypt_credentials to fail mid-way
    with unittest.mock.patch.object(m, 'encrypt_credentials', return_value=False):
        result = m.rotate_credentials('new_key', 'new_secret')
    print('rotation result (should be False):', result)
    # Original creds must still be intact
    creds = m.decrypt_credentials()
    print('creds after failed rotation:', creds)
    assert creds == ('orig_key', 'orig_secret'), f'FAIL: got {creds}'
    print('PASS: rollback preserved original credentials')
"

4. Verify rotation warning fires once (not every tick)

python -c "
import tempfile, time
from pt_credentials import SecureCredentialManager, CredentialRotationScheduler
fires = []
with tempfile.TemporaryDirectory() as d:
    m = SecureCredentialManager(d)
    m.encrypt_credentials('k', 's', rotation_interval_days=0)  # immediately overdue
    sched = CredentialRotationScheduler(m, check_interval_hours=0.0001,
                                        callback=lambda w: fires.append(w))
    sched.start()
    time.sleep(0.1)
    sched.stop()
    print('callback fired', len(fires), 'time(s)')
    # Should fire once (warning text unchanged = no re-fire)
    assert len(fires) >= 1, 'callback never fired'
    print('PASS')
"

Rollback

git checkout main -- app/pt_credentials.py app/test_credentials_rotation.py

- from_dict: raise ValueError on missing required fields (clearer than TypeError)
- atomic writes use tempfile.mkstemp to avoid `.tmp` name collisions
- decrypt: legacy COMPUTERNAME/USERNAME derivation fallback + auto re-encrypt
- get_rotation_status: consistent dict shape (rotation_due_at always present)
- audit log: O(1) size-based rotation to .1 instead of per-call O(n) rewrite
- PermissionValidator: documented default base_dir caveat
- scheduler: extracted _tick() so tests exercise real dedup; clamp warning;
  stop() reports if join times out
- get_credentials: plaintext fallback logged at error level with explicit warning
- validate_credentials_on_startup: accepts base_dir for testability
- tests: addCleanup for tmpdirs, audit log permission assertion, metadata
  rollback assertions, real _tick() in dedup tests
Copilot AI review requested due to automatic review settings May 20, 2026 13:40
@Ibrahim-3d

Copy link
Copy Markdown
Collaborator Author

All 19 round-2 Copilot threads addressed in commit cd45203 and resolved. Inline replies posted on each thread with the specific fix or rationale. Tests passing locally (32 tests, all green).

Highlights:

  • [FEATURE] Strategy: Bollinger mean reversion with regime filter #124 (machine-password compat): decrypt now falls back to legacy COMPUTERNAME/USERNAME derivation and re-encrypts with new key — existing Windows vaults migrate transparently.
  • #633 (plaintext fallback): log level raised to error with explicit SECURITY DEGRADATION message.
  • #137 (tmp collision): atomic writes use tempfile.mkstemp for unique per-writer names.
  • #514 (audit log O(n)): replaced read-rewrite with O(1) size-based rotation to .1.
  • Scheduler dedup tests: extracted _tick() method; tests now exercise real code path.

Ready for re-review.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@Ibrahim-3d

Copy link
Copy Markdown
Collaborator Author

@sjackson0109 all 19 round-2 Copilot threads addressed in cd45203, replied inline, resolved. 32 tests pass. Ready for re-review.

Copilot AI review requested due to automatic review settings May 20, 2026 20:40

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

Comment thread app/pt_credentials.py Outdated
Comment thread app/pt_credentials.py Outdated
Comment thread app/pt_credentials.py
Comment thread app/pt_credentials.py Outdated
@sjackson0109

Copy link
Copy Markdown
Owner

@Ibrahim-3d This PR review #80 (review) has left us with 4x open comments/concerns of varying risk-levels. Can you review and repy to each (inline, or all at once).
Aside from this, the PR looks clean and ready to merge.

- encrypt_credentials: two-phase commit for key/secret ciphertexts.
  Both ciphertexts are now staged to temp files before either rename
  happens. If the second rename fails after the first commits, the
  previously-saved key ciphertext is restored from an in-memory snapshot,
  so the vault never ends up with a new key paired with the old secret.
- _stage_temp_binary: new helper that writes a temp file and returns its
  path without renaming. _atomic_write_binary refactored to use it.
- PermissionValidator.validate: missing_required / missing_trading lists
  now sorted() for stable, diff-friendly audit log entries.
- _rotate_audit_if_needed: AUDIT_ROTATION_KEEP is now actually wired in.
  Older generations shift down (.N-1 -> .N) and anything past the keep
  window is dropped, instead of the previous single-.1 rotation.
- CredentialRotationScheduler._tick: update _last_warning BEFORE calling
  the user callback so a broken callback cannot re-fire the same warning
  on every subsequent tick. Callback exceptions are caught and logged
  with traceback, isolated from the scheduler's dedup state.
- test_rotate_restores_metadata_on_failure: patch _stage_temp_binary
  (new internal seam) instead of _atomic_write_binary.
@Ibrahim-3d

Copy link
Copy Markdown
Collaborator Author

@sjackson0109 round-3 done — addressed in 4c5c927:

# Issue Fix
1 encrypt_credentials non-atomic key+secret writes — partial-failure could yield new-key/old-secret pair Two-phase commit via new _stage_temp_binary: both ciphertexts staged first, then both os.replaced. If second rename fails, previous key ciphertext restored from in-memory snapshot
2 missing_required / missing_trading had set-ordered output — noisy audit log All four list constructions use sorted(...) now
3 AUDIT_ROTATION_KEEP defined but unused _rotate_audit_if_needed now shifts .N-1 → .N and drops anything past the keep window
4 _tick updated _last_warning after callback — broken callback caused spam Dedup state set before callback; callback exceptions caught + logged with exc_info=True, isolated from dedup state

32 unit tests pass locally. Threads resolved.

@Ibrahim-3d

Ibrahim-3d commented May 21, 2026

Copy link
Copy Markdown
Collaborator Author

Hey - please hold off on merging this one for a moment. I want to add a round-trip migration test for the new vault derivation before this lands, since changing how credentials are scrambled could lock users out if we get it wrong. Will follow up here as soon as the test is in.

Adds regression test for the legacy-derivation fallback added in this
PR. Encrypts a vault under the legacy COMPUTERNAME/USERNAME password,
upgrades to the new gethostname()/getuser() derivation, and asserts
that decrypt_credentials() transparently:

1. Falls back to the legacy derivation and returns the original creds.
2. Auto-rewrites the vault under the new derivation in the same call.
3. Subsequent decrypt with only the new derivation available (legacy
   fallback returning None) still succeeds — proving the rewrite
   happened on disk, not just in memory.

Closes the migration-risk review feedback on this PR.
Copilot AI review requested due to automatic review settings May 23, 2026 13:29
@Ibrahim-3d

Ibrahim-3d commented May 23, 2026

Copy link
Copy Markdown
Collaborator Author

Test is in. The new test (test_legacy_vault_auto_migrates_to_new_derivation) walks the whole upgrade path: encrypt a vault with the old Windows-only password, upgrade to the new cross-platform derivation, decrypt successfully via the legacy fallback, and confirm the vault gets quietly rewritten under the new key on disk so the next decrypt does not need the fallback. 33 tests passing. Good to review when you have a minute.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@Ibrahim-3d

Copy link
Copy Markdown
Collaborator Author

@copilot review

sjackson0109
sjackson0109 previously approved these changes May 23, 2026

@sjackson0109 sjackson0109 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Really happy to see this change go in...

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

Comment thread app/pt_credentials.py
Comment thread app/test_credentials_rotation.py
…er tick callback)

- pt_credentials.py: legacy derivation migration in decrypt_credentials()
  now snapshots the existing rotation metadata before calling
  encrypt_credentials() and restores last_rotated_at / rotation_due_at /
  created_at / rotation_interval_days afterwards. Without this, a derivation
  upgrade silently looked like a real credential rotation and pushed the
  next rotation warning out by a full interval, defeating the scheduler for
  any vault that triggered the fallback path.

- test_credentials_rotation.py:
  * test_callback_fires_when_overdue now also exercises the real _tick()
    dispatch path and asserts the callback is invoked with the OVERDUE
    warning, so a regression in tick→callback wiring would actually fail
    the test (matching what the docstring already promised).
  * New test_legacy_migration_preserves_rotation_metadata pins the
    fix above: backdated metadata must survive the derivation upgrade.
Copilot AI review requested due to automatic review settings May 24, 2026 10:54

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@sjackson0109 sjackson0109 merged commit 7a74a60 into sjackson0109:main May 24, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

architecture component-api API integration component-architecture System architecture and design components component-security Security components enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[SECURITY] Add API key permission validation [SECURITY] Implement credential rotation mechanism

3 participants