Skip to content

Compliance#19

Open
Scetrov wants to merge 18 commits intomainfrom
feat/compliance
Open

Compliance#19
Scetrov wants to merge 18 commits intomainfrom
feat/compliance

Conversation

@Scetrov
Copy link
Owner

@Scetrov Scetrov commented Feb 13, 2026

1. Title

feat: implement user profile deletion and compliance updates

2. Description

What?

This PR introduces the ability for users to delete their profiles, adds essential legal documentation (Privacy Policy, Terms of Service, etc.), and updates the codebase to use more inclusive language ("denylist" instead of "blacklist").

Why?

  • Compliance: To adhere to data privacy regulations (GDPR/CCPA), users must have a way to delete their accounts and be informed of their rights through legal documentation.
  • Inclusivity: Standardizing inclusive language across the project.
  • Security: Ensuring that deleted users' identifiers (Discord ID, Wallets) are denylisted (hashed) to prevent re-registration or misuse of deleted accounts.

How?

  • Backend:
    • Implemented delete_profile endpoint in auth.rs.
    • Added hashing logic to safely store denylisted identifiers.
    • Updated database schemas and queries to use denylisted terminology.
  • Frontend:
    • Added ConfirmationModal for critical actions like account deletion.
    • Implemented deleted.tsx route for the post-deletion state.
    • Added static markdown files for legal policies and a generic $page.tsx route to render them.
    • Updated DashboardLayout and AuthProvider to handle the new routes and states.

3. Type of Change

  • feat: A new feature
  • fix: A bug fix
  • docs: Documentation only changes
  • style: Changes that do not affect the meaning of the code (white-space, formatting, etc.)
  • refactor: A code change that neither fixes a bug nor adds a feature
  • perf: A code change that improves performance
  • test: Adding missing tests or correcting existing tests
  • chore: Changes to the build process or auxiliary tools and libraries

4. Development Checklist

  • Rust: Ran cargo fmt and cargo clippy.
  • TypeScript: Ran bun run lint in the frontend directory.
  • Migrations: If the database schema was modified, ran sqlx migrate run and updated sqlx-data.json via cargo sqlx prepare.
  • Testing: Verified changes locally with appropriate tests.
  • Release: Confirmed that any necessary version metadata patching is compatible with the automated release workflow.

5. Security Assessment

Evaluating against OWASP Top 10:2025 guidelines.

  • Access Control: Verified that AuthenticatedUser extractors properly validate session state and that deletion is restricted to the owner.
  • Injection: Verified that all new SQLx queries use parameterized inputs for denylist checks.
  • Cryptographic: Implemented salted hashing for denylisted identifiers to ensure privacy while maintaining security.
  • CORS/Infrastructure: No changes to infrastructure or overly permissive policies.
OWASP Mapping MITRE ATT&CK ID Impact Description
A01:2025 T1078 Verified session boundaries for profile deletion.
A03:2025 T1059 Used parameterized SQLx queries.

6. Frontend (if applicable)

  • Components adhere to the "Design System".
  • Screenshots or screen recordings of the UI changes are attached below. (To be added manually).

7. Reviewer Feedback & Resolution

This PR follows the "Fix & Reply" loop for all unresolved comments.

  • I have replied directly to all reviewer comment threads with a resolution summary.
  • All fixes are committed atomically with references to the specific feedback.

# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
@Scetrov Scetrov self-assigned this Feb 13, 2026
@Scetrov Scetrov added the dependencies Pull requests that update a dependency file label Feb 13, 2026
Copilot AI review requested due to automatic review settings February 13, 2026 21:31

This comment was marked as resolved.

Copilot AI review requested due to automatic review settings February 13, 2026 22:09

This comment was marked as resolved.

Copilot AI review requested due to automatic review settings February 13, 2026 22:45
@Scetrov
Copy link
Owner Author

Scetrov commented Feb 13, 2026

I have addressed the second round of review comments.

Key changes:

  • Enforced IDENTITY_HASH_PEPPER to prevent insecure fallbacks.
  • Fixed a potential SQLite deadlock by moving log_audit after transaction commit.
  • Expanded account deletion scrubbing to include user_tribes, mumble_accounts, and notes.
  • Improved ConfirmationModal accessibility (ARIA roles, keyboard support) and user flow (redirecting to /deleted).

Verified with pre-commit run --all. Commit: 5730d5e

This comment was marked as resolved.

@Scetrov Scetrov enabled auto-merge (squash) February 13, 2026 23:11
@Scetrov Scetrov disabled auto-merge February 13, 2026 23:13
Copilot AI review requested due to automatic review settings February 13, 2026 23:24
Copy link

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

Copilot reviewed 24 out of 28 changed files in this pull request and generated 5 comments.

let lastUpdated = ''
try {
// Use git log to get the last commit date
lastUpdated = execSync(`git log -1 --format=%cd --date=iso-strict "${filePath}"`).toString().trim()
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

The execSync call uses string interpolation to embed filePath directly into a shell command without sanitization. Although filePath is derived from fs.readdirSync, a malicious filename (e.g., "; rm -rf / #.md) in the static/ directory could lead to command injection. Use execFileSync instead of execSync to pass arguments safely, or validate that filenames contain only expected characters before running the command.

Copilot uses AI. Check for mistakes.

* **Access:** Request a copy of the data we hold about you (Discord ID and linked Wallet), you can review this information in the service homepage (`/home`).
* **Rectification:** Update your data (typically done by re-syncing your Discord profile).
* **Erasure ("Right to be Forgotten"):** You may unlink your wallet and request deletion of your account. We support "soft-deletes" and unlinking capabilities.
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

The privacy policy states "We support 'soft-deletes' and unlinking capabilities" (line 56), but the actual implementation in delete_me performs hard deletion of wallets (DELETE FROM wallets WHERE user_id = ?) and anonymizes the user row rather than soft-deleting. Update this statement to accurately reflect the implementation, e.g., "You may delete your account, which will permanently remove your wallets and anonymize your user profile."

Suggested change
* **Erasure ("Right to be Forgotten"):** You may unlink your wallet and request deletion of your account. We support "soft-deletes" and unlinking capabilities.
* **Erasure ("Right to be Forgotten"):** You may delete your account, which will permanently remove your wallets and anonymize your user profile. You may also unlink your wallet without deleting your account.

Copilot uses AI. Check for mistakes.
Comment on lines +399 to +401
setIsDeleteModalOpen(false);
} catch (err) {
console.error('Failed to delete account:', err);
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

If deleteAccount() throws an error (network failure, non-200 response, etc.), the modal is left open because setIsDeleteModalOpen(false) is only called on success. The error message is logged to console and set via setError, but the user is stuck in the modal with no clear recovery path. Consider calling setIsDeleteModalOpen(false) in a finally block, or adding UI feedback (e.g., displaying the error inside the modal) to improve the user experience.

Suggested change
setIsDeleteModalOpen(false);
} catch (err) {
console.error('Failed to delete account:', err);
} catch (err) {
console.error('Failed to delete account:', err);
alert('Failed to delete your profile. Please try again in a moment, or contact support if the problem persists.');
} finally {
setIsDeleteModalOpen(false);

Copilot uses AI. Check for mistakes.
Your account has been permanently deleted at your request to fulfill your <a href="https://ico.org.uk/for-organisations/uk-gdpr-guidance-and-resources/individual-rights/individual-rights/right-to-erasure/" style={{ color: '#ef4444', textDecoration: 'underline', textUnderlineOffset: '4px', transition: 'opacity 0.2s' }} onMouseOver={(e) => e.currentTarget.style.opacity = '0.8'} onMouseOut={(e) => e.currentTarget.style.opacity = '1'}><strong>Right to be Forgotten / Right to erasure</strong></a> under GDPR.
</p>
<p>
As part of our commitment to your privacy, your Discord ID and associated Wallet Addresses have been completely delisted and barred from future use to ensure that this data cannot be re-processed or used to recreate a profile on this platform.
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

Inconsistent terminology: the deleted page uses "delisted" (line 49), but the rest of the codebase uses "denylisted" (e.g., identity_hashes table, error messages like "This wallet has been denylisted due to account deletion"). For consistency and clarity, consider updating "delisted" to "denylisted" or use a more user-friendly term like "blocked" that aligns with the inclusive language goals of this PR.

Suggested change
As part of our commitment to your privacy, your Discord ID and associated Wallet Addresses have been completely delisted and barred from future use to ensure that this data cannot be re-processed or used to recreate a profile on this platform.
As part of our commitment to your privacy, your Discord ID and associated Wallet Addresses have been permanently blocked from future use to ensure that this data cannot be re-processed or used to recreate a profile on this platform.

Copilot uses AI. Check for mistakes.
Comment on lines +22 to +23
let identity_hash_pepper = std::env::var("IDENTITY_HASH_PEPPER")
.expect("IDENTITY_HASH_PEPPER must be set for security and deterministic hashing");
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

The new required environment variable IDENTITY_HASH_PEPPER is not documented in .env.example. Since state.rs uses .expect() to require this variable at startup, developers will experience cryptic errors if they don't set it. Add an entry to .env.example with a comment explaining its purpose (e.g., "Secret pepper for hashing denylisted identifiers. Generate a strong random string.") to guide developers and ensure smooth setup.

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

dependencies Pull requests that update a dependency file

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant