Dark Mode

Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat: SFTP access control, enforcement mode, file transfer logging, and user role expiry#1681

Open
mrmm wants to merge 38 commits intowarp-tech:mainfrom
mrmm:mrmm/feat-scp-sftp-access-control
Open

feat: SFTP access control, enforcement mode, file transfer logging, and user role expiry#1681
mrmm wants to merge 38 commits intowarp-tech:mainfrom
mrmm:mrmm/feat-scp-sftp-access-control

Conversation

Copy link
Contributor

mrmm commented Jan 22, 2026 *
edited
Loading

Closes #1679

What this does

Adds SFTP file transfer access control, an instance-wide enforcement mode, audit logging, and user role expiry management.

Key changes

SFTP Access Control

  • Role defaults -- upload/download toggles, allowed paths, blocked extensions, max file size
  • Target-role overrides -- inherit from role OR explicitly allow/deny per target
  • Hierarchy: Target override - Role default - System default (allow)

API: GET/PUT /role/{id}/file-transfer, GET/PUT /targets/{id}/roles/{role_id}/file-transfer

Enforcement Mode (strict / permissive)

  • Strict: Shell, exec, port forwarding blocked when SFTP restrictions active
  • Permissive: SFTP restrictions enforced, shell still works
  • SFTP EXTENDED packets handled via allowlist (safe/write/unknown categories)

API: GET/PUT /parameters (field: sftp_permission_mode)

SCP Removal

Removed the legacy SCP module per maintainer feedback -- legacy scp commands now pass through as regular exec (blocked in strict mode).

Audit Logging

All SFTP operations logged: user, target, direction, path, size, SHA256 hash, status, denial reason. Stored in FileTransferLog table.

User Role Expiry

  • Optional expires_at on role assignments with automatic revocation
  • Re-activation via expiry update/removal
  • Full history tracking (granted - expiry_changed - revoked - re-enabled)

API: POST/GET/PUT/DELETE /users/{id}/roles/{role_id}/expiry, GET /users/{id}/roles/{role_id}/history

Admin UI

  • Global enforcement mode setting with contextual help
  • Role-level file transfer defaults with collapsible advanced restrictions
  • Target-level per-role overrides with 3-state controls (inherit/allow/deny)
  • User role expiry with countdown display and quick presets (4h-7d)
  • Role assignment history timeline

Design Decisions

  • Inheritance model over explicit booleans -- Target-role permissions default to NULL (inherit from role) rather than explicit true/false. This avoids permission drift when role defaults change and reduces per-target configuration burden.
  • Strict mode as default -- When restrictions exist, strict mode blocks shell/exec/forwarding to prevent SFTP bypass. Permissive mode exists for gradual rollout or audit-only use.
  • SCP removed, not restricted -- SCP's protocol makes reliable interception fragile. Rather than half-working restrictions, SCP was removed entirely. SFTP covers all the same use cases with better protocol support.
  • EXTENDED packet allowlist -- Unknown SFTP extensions are blocked when restrictions are active. Known-safe extensions (statvfs, fsync, limits) always pass through. Write extensions (posix-rename, hardlink) check upload permission.
  • Cumulative max_file_size -- Tracked per Write operation across the stream, not per-packet. Denial happens mid-transfer when the limit is exceeded.
  • Session-scoped permission caching -- Permissions resolved once at session start, not per-packet. Trades real-time revocation for performance.
  • Soft-delete for role revocation -- revoked_at timestamp instead of row deletion. Allows re-activation and preserves audit trail integrity.

Database Migrations

  • m00031 -- target_roles: add allow_file_upload, allow_file_download (bool, default true), allowed_paths, blocked_extensions (json), max_file_size (bigint)
  • m00032 -- parameters: add file_transfer_hash_threshold_bytes (bigint, default 10MB) -- controls when SHA256 hashing kicks in for audit logs
  • m00033 -- user_roles: add granted_at, granted_by, expires_at, revoked_at, revoked_by. Creates user_role_history table with indexes on user_id, role_id, occurred_at
  • m00034 -- roles: add allow_file_upload, allow_file_download (bool, default true), allowed_paths, blocked_extensions (json), max_file_size (bigint) -- role-level defaults
  • m00035 -- target_roles: convert allow_file_upload / allow_file_download from non-nullable bool to nullable bool. Migrates existing true - NULL (inherit), preserves false (explicit deny)
  • m00036 -- parameters: add sftp_permission_mode (string, default "strict")

Breaking Changes

None -- defaults match current behavior. Existing targets/roles work as before.

Related

Screenshots

Role UI - Upload/Download Toggles

Show Screenshot

Target UI - Role Attribution for Upload/Download Override

Show Screenshot

User UI - Role Assignment History

Show Screenshot

User UI - Role Expiry Setting

Show Screenshot

User UI - Role Expiry Countdown

Show Screenshot

User UI - Make Role Permanent

Show Screenshot

User UI - Role History

Show Screenshot

Copy link
Contributor Author

mrmm commented Jan 22, 2026

Hello @Eugeny, I am not sure why the test aer failing in the Github Action as I have ran them locally and they passed without an issue. Looks like the issue is 2 different test timeout in CI each time due to Docker resource exhaustion after ~160 tests (maybe).
I am open to any proposal!

Copy link
Member

Eugeny commented Jan 24, 2026

Thank you for the PR! After a cursory glance, it doesn't seem to actually prevent the user from writing/reading data from the FS as there's still nothing stopping them from doing so from within their shell session?

Copy link
Contributor Author

mrmm commented Jan 24, 2026

@Eugeny Thanks fro the feedback, this PR design was mainly around the SCP/SFTP subsystem of SSH, It is possible to intercept the SSH session commands as it is the same-ish mechanism used for recording the SSH session.
I am planning to look into having a feature that allow the administrator to control/prevent some commands to be ran on the Target server.

This PR also does not cover these use-cases too:

  • Allow only the user to upload and/or download file from the server ONLY without SSH session
  • Inline file scanning to prevent any malicious file to be uploaded (only file extensions prevention, which was quick-win as a all the metadata were accessible)

If you think it is mandatory feature to be able to have this in this PR, I will work on it but I will need sometime before being able to deliver this

Thanks again fro the review

Copy link
Member

Eugeny commented Jan 24, 2026

I see - unfortunately there's no security gained by this PR as-is unless shell and exec sessions are also forbidden, because there's still (a very easy) way to read/write arbitrary files. So while it might tick the boxes for a specific security audit, it doesn't actually prevent unauthorized access.

It's fundamentally impossible to selectively restrict command execution in shell sessions too, since the SSH server only sees user keystrokes and display output and has no concept of "commands" per se (except exec requests, but again the user can bypass that by simply running commands in the shell).

It's not possible to "recognize" and parse shell input either because that's trivially bypassable by encrypting the commands/inputs, saving it remotely and decrypting it there, so it's never visible on the SSH protocol.

As it is, I'm currently tending towards not accepting this as a feature, however I'm still interested in some parts of this PR, if you could separate them out:

  • Time limited role assignments
  • SFTP audit logging - please see if it's possible to reuse the russh-sftp crate instead of manually parsing the messages
mrmm reacted with thumbs up emoji

Copy link
Contributor Author

mrmm commented Jan 24, 2026

@Eugeny
I totally agree with you that this technically does not block file transfer as I can open SSH session and copy/paste the content of a file.

I will separate the TTL role assignment feature, and look into the usage of rssh-sftp for the SFTP audit logging, for audit log purposes only !

While I am at it I added some screenshot to this PR, even if it will not move forward I really appreciate the review

Copy link
Contributor Author

mrmm commented Jan 24, 2026

@Eugeny
Quick update on the russh-sftp usage (and thanks for pointing that out), it would replace:

  • the parser parser.rs
  • response.rs
  • protocol types in types.rs

Follow-up question, would it be acceptable to update this feature to:

  • State that this only blocks, SCP, SFTP and RSYNC protocols from uploading but user has SSH this access can bypassed this ?
    Which gets me to the next point :
  • Is adding the possibility to block interactive SSH sessions which can make the target usable only for File transfer
  • This PR supports also the legacy SCP (that does scp -t and scp -f exec) -> if we move forward with the proposal of blocking interactive sessions

Copy link
Member

Eugeny commented Jan 26, 2026

Could you please reformulate the second half of your reply? I couldn't parse it

Copy link
Contributor Author

mrmm commented Jan 27, 2026 *
edited
Loading

Sorry @Eugeny for the not clear comment, It was done little bit too lat in the night

To address this, I suggest adding a Restricted Shell mode to make targets truly "File Transfer Only." Regarding this proposal:

  • Interactive Sessions: Should we allow blocking interactive SSH sessions to enforce this file-transfer-only access?
  • UI Clarity: For cases where the role blocks file transfer protocols but not the interactive session, I can add a warning in the UI to let admins know the restriction can be bypassed via the shell.
  • Legacy SCP: I have already implemented support for legacy SCP (scp -t and -f). Should we keep this to ensure full backward compatibility, or would you prefer I drop it to keep this PR more focused and simplified?

Copy link
Contributor Author

mrmm commented Jan 29, 2026

@Eugeny Hello, did you have the time tou check my last comment please ?

Copy link
Member

Eugeny commented Feb 1, 2026

Thanks for getting back to me and sorry for the delay!

Interactive Sessions: Should we allow blocking interactive SSH sessions to enforce this file-transfer-only access?

Yes - blocking session and exec channels should be a requirement for using SFTP permissions.

UI Clarity: For cases where the role blocks file transfer protocols but not the interactive session, I can add a warning in the UI to let admins know the restriction can be bypassed via the shell.

See above - enabling SFTP permissions should not be possible when interactive sessions are allowed.

Legacy SCP: I have already implemented support for legacy SCP (scp -t and -f). Should we keep this to ensure full backward compatibility, or would you prefer I drop it to keep this PR more focused and simplified?

We can leave these out - no reason to use legacy SCP in 2026.

Copy link
Contributor Author

mrmm commented Feb 7, 2026

@Eugeny Thanks a lot for the clarification I will add those requirements then !
I will update the PR to match the specs, byt the end of the next week

mrmm force-pushed the mrmm/feat-scp-sftp-access-control branch from 3209fca to 0dec921 Compare February 7, 2026 19:36
mrmm changed the title feat: SCP/SFTP access control, file transfer logging, and user role expiry feat: SFTP access control, enforcement mode, file transfer logging, and user role expiry Feb 9, 2026
mrmm added 15 commits February 10, 2026 09:11
Implement granular file transfer permissions allowing administrators to
control upload/download access on a per-role-per-target basis.

- Add database migrations for permission columns on target_roles table
- Add SFTP protocol parser with fine-grained operation blocking
- Add SCP command parser for upload/download detection
- Integrate permission checks into SSH session handling
- Add Admin API endpoints for managing file transfer permissions
- Add Admin UI toggles in Target configuration page
- Add E2E tests for permission enforcement
- Add SFTP packet parsing for Open, Read, Write, Close operations
- Add SCP command parsing for upload/download detection
- Implement permission enforcement at packet level in SSH session
- Track file transfers with size and SHA256 hash calculation
- Log file_transfer events with status, direction, protocol, and metadata
- Add pending_reads tracking for accurate SFTP download byte counting
- Update LogViewer UI to display file transfer events with direction arrows
- Add ssh-server container with test user for SCP/SFTP testing
- Update docker-compose with warpgate and ssh-server services
- Ignore docker/data/ directory for persistent test data
- 10 permission tests covering SFTP/SCP upload/download allow/deny scenarios
- 2 logging tests verifying file_transfer events in JSON logs
- Tests use wait_port for reliable warpgate restart handling
- Changed SFTP subsystem to always allow connection for SSH targets
- Permission enforcement now happens at operation level (Open, Read, Write)
- Added detailed error messages including action and target name
- Example: 'Permission denied: file upload is not allowed on target prod-server'

This improves UX when users have restricted file transfer permissions,
replacing cryptic 'subsystem request failed' errors with actionable messages.
Implements time-limited user role assignments with automatic tracking of all role changes in a history table. Roles can have an optional expiry timestamp and all modifications (granted, revoked, expiry changes) are recorded with actor information and timestamps.

Features:
- User role assignments can expire at a specified timestamp
- Expired roles are filtered out during authorization
- Full history tracking with UserRoleHistory entity
- API endpoints for managing and viewing role expiry
- Re-enable expired roles by updating/removing expiry
Adds default file upload/download permissions at the role level that can be inherited or overridden by target-role assignments. Roles now define baseline file transfer policies that apply to all SSH targets unless explicitly overridden.

Features:
- Role-level allowFileUpload and allowFileDownload flags
- API endpoints to get and update role file transfer defaults
- Defaults are inherited by target-role assignments when not overridden
Allows targets to override role file transfer defaults with three-way options (inherit/allow/deny) for upload and download permissions separately. This enables fine-grained control where specific targets can restrict or permit file transfers regardless of role defaults.

Features:
- Nullable allow_file_upload and allow_file_download columns
- null = inherit from role, true = force allow, false = force deny
- API endpoints to get and update target-role file transfer permissions
- Updated authorization logic to respect override hierarchy
- E2E tests for file transfer permission scenarios
Implements comprehensive UI for managing user role assignments with time-based expiry and viewing historical changes.

Features:
- Quick expiry presets (4h, 8h, 12h, 1d, 3d, 7d) for common durations
- Custom datetime picker for precise expiry control
- Real-time expiry status display (e.g., "Expires in 3h 59min")
- Expired roles treated as unassigned and can be re-enabled
- Re-enabling expired role removes expiry (makes permanent)
- Inline role history section with load more pagination
- History shows all role changes with actor and timestamp information
- Modal with improved layout and horizontal button arrangement
Adds UI for managing role-level file transfer defaults and target-level permission overrides with clean, compact controls.

Features:
- Role page: Simple checkboxes for default upload/download permissions
- Target page: Collapsible file transfer section per role
- Three-way select dropdowns (Inherit/Allow/Deny) for upload and download
- Collapsed state shows current permission summary
- Compact horizontal layout with input groups
- Shows inherited role defaults in select options for clarity
Add 100ms delay after SSH server startup to allow full initialization
in resource-constrained CI environments. This may help prevent timeout
issues when many Docker containers are running concurrently.

Tests continue to pass locally (12/12 in 14s).
Ignore development environment files:
- AGENTS.md (OpenCode agent instructions)
- mise.toml (personal mise configuration)
- .github/workflows/opencode.yml (local workflow)
- *.log files (session logs)
- thoughts/ directory (documentation drafts)
- docker/*.sample.yaml (personal config samples)
Migrate from custom SFTP parser (~590 lines) to russh-sftp 2.1 codec wrapper
(~150 lines), achieving 49% code reduction in the SFTP module.

Changes:
- Add russh-sftp 2.1 dependency
- Create new codec.rs with packet_to_operation(), packet_to_response(),
and build_denial_response() wrappers
- Remove parser.rs (465 lines) and response.rs (126 lines)
- Update session.rs to use codec functions instead of custom parser
- Simplify types.rs by removing redundant type definitions

The russh-sftp library provides proper SFTP protocol parsing while
Warpgate's codec.rs handles access control and metadata extraction.

Refs: thoughts/plans/russh-sftp_migration.md
mrmm added 12 commits February 10, 2026 09:14
- CredentialEditor: fix curly braces, use SvelteSet instead of Set
- RoleHistoryModal: handle PaginatedResponse type, fix Date formatting
- CreateApiTokenModal: remove semicolons from toLocalISO function
- User.svelte: fix lint warnings
- Rust API files: minor fixes for consistency
Add 28 E2E tests covering:
- File transfer permissions (default, download, upload, API, multi-role)
- Transfer logging and denied transfer logging
- Strict mode (shell blocked, shell allowed, SFTP works, port forwarding)
- Permissive mode (shell allowed, SFTP enforced)
- Advanced restrictions (allowed_paths, blocked_extensions, case sensitivity)
- Role-level defaults and target-role overrides
- Exec and remote forwarding blocking in strict mode

Fix test_ssh_client_auth_config.py: add missing AddUserRoleRequest
argument required by updated SDK from user role expiry feature.
Update bytes 1.11.0->1.11.1, time 0.3.44->0.3.47, thiserror 2.0.17->2.0.18
to resolve cargo-deny vulnerability warnings in CI.
...ssignment

- Show expired roles with strikethrough, reduced opacity, and red Expired badge
- Add Re-enable button for expired roles that opens expiry modal
- Inline expiry modal on new role assignment (permanent default, presets)
- Add labeled Expiry button (clock icon + text) replacing icon-only
- Past-date validation on datetime-local input with min constraint
- Live countdown timer (60s interval) showing time remaining
- Confirmation dialog before revoking a role
- Auto-load role history on first expand
- Add 30-day preset to expiry options
- 14 role expiry tests: grant with TTL, expired denied, re-enable,
revoke, reactivate, history audit trail, API state listing
- 18 SFTP operation tests: remove, rename, mkdir, rmdir, setstat,
symlink, max_file_size enforcement, extended packets (statvfs),
metadata ops, and strict mode streamlocal blocking
mrmm force-pushed the mrmm/feat-scp-sftp-access-control branch from 8330df8 to 040f7d2 Compare February 10, 2026 09:12
Copy link
Contributor Author

mrmm commented Feb 10, 2026 *
edited
Loading

@Eugeny Here's an update addressing all your feedback points:

What changed

Enforcement mode (addresses session/exec blocking requirement)

  • Added instance-wide sftp_permission_mode setting: strict (default) / permissive
  • Strict mode: When SFTP restrictions are active on a target, session, exec, direct-tcpip, direct-streamlocal, tcpip-forward, and streamlocal-forward channels are all blocked
  • Permissive mode: SFTP restrictions enforced, but shell/exec/forwarding remain open (for gradual rollout)
  • File transfer only: The toggle file_transfer_only on role, we can enable this to enforce for that specific role to block the interactive, exec channels and port-forward channels.

SCP removed

  • Deleted the entire SCP module per your guidance
  • Legacy scp commands now pass through as regular exec (blocked in strict mode and if the role with "FIle transfer only" activated)

SFTP codec replaced

  • Swapped custom SFTP parser with russh-sftp codec as you suggested
  • Added allowlist-based SFTP EXTENDED packet handling (safe/write/unknown categories)

Permission inheritance model

  • Role-level defaults: upload/download toggles + advanced restrictions (allowed paths, blocked extensions, max file size)
  • Target-role overrides: null = inherit from role, explicit true/false = override
  • Hierarchy: Target override - Role default - System default (allow)

User role expiry

  • Optional expires_at on role assignments, automatic revocation, full history tracking

PR description updated

Rewrote to be more concise with a design decisions section explaining the rationale behind each architectural choice.

Terraform provider

Companion PR: https://github.com/warp-tech/terraform-provider-warpgate/pull/18``

mrmm force-pushed the mrmm/feat-scp-sftp-access-control branch from be762fc to 286ed7d Compare February 11, 2026 12:21
Align with Warpgate's additive RBAC model where roles only grant
permissions, never deny. Previously if ANY role had file_transfer_only=true,
shell was blocked. Now only if ALL matching roles agree is it enforced,
so a single role without the flag restores shell access.
Copy link
Contributor Author

mrmm commented Feb 11, 2026 *
edited
Loading

@Eugeny Please let me know if you think this needs more changed or updated to match the expected behavior.

Edit: I am planning to rewrite git history to make it easier to grasp as there was some changes and fixes that could have been merge. Let me know if you want me to keep it as is !

Copy link
Contributor Author

mrmm commented Feb 27, 2026

Hello @Eugeny sorry for the multiple pings, can you please check this PR ?

Copy link
Member

Eugeny commented Mar 3, 2026

Sorry for taking so long - I have a large backlog and can spend very little time on it right now - it might take up to 2 weeks for me to finish a review so please hold tight

mrmm reacted with heart emoji

Copy link
Contributor Author

mrmm commented Mar 3, 2026

@Eugeny that is no problem at all. I just wanted to make sure that there is no blocker on mu side.
Let me know if there is any issue that I can help with !

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

Reviewers

No reviews

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

feat: SFTP access control, file transfer logging, and role expiry

2 participants