Security model

Threat model, encryption and keys, authorization, isolation and abuse limits.

Secure File Transfer is designed on the assumption that links get forwarded, credentials leak and files are hostile. This page summarises the threat model, encryption and keys, authentication, tenant isolation, abuse limits and the supply chain.

Threat model

The threat model applies STRIDE (spoofing, tampering, repudiation, information disclosure, denial of service, elevation of privilege) to each element of the data-flow diagram and to each key flow: send, verify and download, request, revoke and expire. It covers the application, every data store, the edge, the Osaka standby, the delivery pipeline, the AWS accounts and their operators, in dev and staging as well as prod.

  • Stable IDs. Threats are numbered T-01 to T-107 and never renumbered. Each names its mitigation and the requirement or ADR it relies on.
  • Rated twice. Likelihood and impact are rated without mitigation, then the residual risk with it. An element with a High residual risk doesn't ship.
  • Accepted risks in writing. Eighteen residual risks (R-01 to R-18) are accepted, each with an owner, a review date and a trigger to revisit early. Examples: mailbox control is the only factor for external recipients (R-02), signature scanning misses new malware (R-04), and one maintainer means no separation of duties (R-08).
  • A test per mitigation. Each mitigation group maps to an automated test, a CI check or a drill. The model is reviewed in every pull request that changes a trust boundary and in full every six months.

Encryption and keys

All traffic uses TLS 1.2 or later, including from the load balancer to the API pods. At rest, each environment has eight customer managed KMS keys, one per data class (ADR-0017). Each is a multi-Region key with its primary in Tokyo and, in staging and prod, a replica in Osaka, rotated every year.

KeyProtects
filesS3 files buckets; scan node volumes
fieldsPer-tenant data keys for file names, messages and DLP finding details
databaseAurora clusters, backups and snapshots
auditThe audit archive, which outlives teardown
logsLog buckets, CloudWatch Logs, log and trace stores
secretsSecrets Manager and Kubernetes Secrets
platformSQS, SNS, Valkey and EBS
tfstateTerraform state buckets

A ninth key, in the shared account, encrypts container images. Load balancer access logs are the one exception NFR-13 allows: they support only S3-managed encryption, and they never contain link tokens.

Field encryption. File names and messages are encrypted in PostgreSQL with AES-256-GCM under a data key per tenant. The service unwraps the key of the tenant in the caller's scope, never the tenant on the row, so a row read across tenants fails to decrypt and raises an alert.

Why not a KMS key per tenant. The API must serve every tenant, so its role could use every tenant's key anyway; per-tenant keys wouldn't protect against a compromised service. For 500 tenants in two regions they would cost about $1,000 to $3,000 a month as keys rotate, and add a privileged path to provisioning. A dedicated key, ours or held in the tenant's own AWS account, is the opt-in path for a tenant whose contract requires one. Service control policies stop anyone but the deploy roles, the bootstrap admin role and a break-glass key-admin role from disabling or deleting a key or changing its policy.

Authentication and authorization

Each caller type has one credential, and a request that carries two is refused.

CallerProves identity withSession
Member or tenant adminCognito with mandatory MFA (TOTP or a passkey), or the tenant's identity provider by SAML or OIDCServer-side session in a __Host- cookie: 12 hours, 30 minutes idle. No Cognito token reaches the browser
Recipient or uploaderThe emailed link token, then a 6-digit code sent to the invited addressLink session: 24 hours, 1 hour idle, never past the transfer's expiry
Tenant automationA tenant API key with fixed scopes, as a Bearer tokenExpires after 90 days by default, at most 365
Platform operatorIAM Identity Center with MFA by security key or passkeyA 15-minute operator token, through a port-forward

Admin rights for a local admin are designed to require a passkey session, and sensitive actions such as creating an API key need a sign-in within the last 15 minutes. API keys never download file contents, decide DLP approvals or manage users. Operators can't read tenant data or file contents; break-glass access is time-limited, audited and raises an alert. See Recipient verification and Administration.

Tenant isolation

NFR-12 is absolute: no API call, link or token gives access to another tenant's data. It is enforced in layers, so one bug is not a breach:

  • scoped data access in the application, which refuses a query without a scope;
  • composite foreign keys that include tenant_id;
  • PostgreSQL row-level security on every tenant-owned table, which no workload role can bypass (ADR-0020);
  • IAM conditions on S3 object prefixes and scan tags;
  • per-tenant data keys for names and messages.

An item in another tenant returns 404, never 403, so its existence is never revealed. The isolation suite tries every endpoint and data-access function with a second tenant's IDs, tokens and keys. It runs on every pull request, and is planned to run nightly against staging.

Abuse limits

NFR-14 sets the starting limits. Counters live in Valkey, with WAF rate rules in front for floods. The state that decides access, such as code attempts, lockouts and download counts, stays in PostgreSQL.

LimitStarting value
Failed member sign-ins10 per account per 15 minutes
Verification codes5 per address per hour and 50 a day; 20 per link a day
Code validity10 minutes, 5 attempts
LockoutAfter 20 failed attempts, until the member unlocks
Link-page requests60 per IP address per minute
API requests1,000 per tenant per minute
Downloads granted500 GB per tenant and 25 GB per recipient a day
Uploads200 GB declared per tenant a day; 2 TB stored per tenant
API key audit exportOnce an hour, at most 100,000 events

A verified session lasts at most 24 hours, and link tokens carry 256 bits of randomness. If the counter store is down, verification and sign-in fail closed.

Secrets and supply chain

Runtime secrets live in AWS Secrets Manager, never in git. Workloads use IAM roles and IAM database authentication, so there are no static database passwords and no long-lived AWS keys. GitHub Actions reaches AWS through OIDC roles pinned to this repository.

ControlWhere
Secret scanning on every commit and pull requestgitleaks in pre-commit and CI
Vulnerability and misconfiguration scans of code, images and TerraformTrivy in CI; no critical or high finding in a released image
Pinned dependenciesHashed lockfiles; Actions pinned to commit SHAs; weekly Dependabot with a cooldown
Signed images with SBOM and provenancecosign keyless signing through GitHub OIDC
Only signed images runKyverno admits an image only by digest, with our signature or an allow-listed vendor's
Hardened containersNon-root, read-only filesystem, no capabilities

Scanning, pinning and signing run in CI today, and each release publishes a signed image with an SBOM to GitHub Container Registry. Admission control arrives with the clusters, and Amazon ECR in the shared account replaces GitHub's registry once that account exists (ADR-0007).

Verification

The baseline (NFR-13) targets OWASP ASVS 5.0 Level 2 for the web app and API; the threat model holds the checklist. An ASVS review and a penetration test of the public surface are planned before prod opens, then yearly. Report vulnerabilities privately through GitHub's private vulnerability reporting.

Source: Threat model, Encryption and keys, Authentication and authorization, Abuse and rate limits, SECURITY.md, PRD