Skip to content
Back to skills

File Upload Storage Architect

ASecurity

Design file/object storage and upload flows for a multi-tenant SaaS — direct-to-storage vs proxied upload, short-lived signed URLs scoped to one object + verb + expiry, tenancy by bucket/path-prefix, size/type/CONTENT validation (magic-byte, not just extension), malware scanning, image/derivative processing off the request path, retention/lifecycle, CDN + cache-control, and storage-cost posture. Produces the upload-flow design, the storage-tenancy + signed-URL contract, the validation/scan pi...

  • 4 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 11, 2026
businessrustgodatabasebackendsecurity

Works with

  • cli

Security analysis

A100/100

Pro scans all 3 files and shows the line behind each finding

Scanned September 28, 2026

npx -y skills add ModernNomad-98/Project-Aegis --skill file-upload-storage-architect --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of File Upload Storage Architect?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for File Upload Storage Architect
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/modernnomad-98-file-upload-storage-architect/badge)](https://www.skillsdirectory.com/skills/modernnomad-98-file-upload-storage-architect)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: file-upload-storage-architect
description: Design file/object storage and upload flows for a multi-tenant SaaS — direct-to-storage vs proxied upload, short-lived signed URLs scoped to one object + verb + expiry, tenancy by bucket/path-prefix, size/type/CONTENT validation (magic-byte, not just extension), malware scanning, image/derivative processing off the request path, retention/lifecycle, CDN + cache-control, and storage-cost posture. Produces the upload-flow design, the storage-tenancy + signed-URL contract, the validation/scan pipeline, and the retention plan. Use when adding uploads, attachments, or media, when files must be tenant-isolated, or when uploads are slow, unsafe, or unbounded. Do NOT use for the personal-data lifecycle of file CONTENTS — consent, subject-access, deletion SLAs (pii-lifecycle-designer) — or for auditing existing storage RLS/bucket policies (rls-policy-auditor); this designs the storage + upload architecture and defers those.
---

# File Upload Storage Architect

Terms: **SaaS** means software as a service; **URL** means uniform resource
locator; **PII** means personally identifiable information; **RLS** means
row-level security; **CDN** means content delivery network; **MIME** means
Multipurpose Internet Mail Extensions; **XSS** means cross-site scripting;
**DB** means database; **HTML** means Hypertext Markup Language; **SVG**
means Scalable Vector Graphics. **HTTP** means Hypertext Transfer Protocol;
its **PUT** method uploads and **GET** method retrieves. **PDF** means
Portable Document Format; **SLA** means service-level agreement.

## Purpose

File uploads are a security and cost surface disguised as a convenience:
proxying every byte through the app server wastes compute and caps file
size, a signed URL scoped too broadly hands a client a key to the whole
bucket, trusting the file extension lets an attacker upload an executable as
a `.png`, and unbounded retention quietly turns storage into the biggest
line on the bill. This skill designs the upload and object-storage
architecture so none of that happens: the upload flow (direct vs proxied),
narrowly-scoped short-lived signed URLs, tenancy by bucket/path prefix,
content validation that inspects bytes rather than names, malware scanning,
derivative processing off the request path, and a retention/lifecycle plan.
The deliverable is the upload-flow design, the storage-tenancy and signed-URL
contract, the validation/scan pipeline, and the retention plan. The personal-
data lifecycle of what the files CONTAIN belongs to `pii-lifecycle-designer`;
this owns the storage architecture.

## Use When

- Use when: adding file uploads, attachments, avatars, document/media
  storage, or import/export of files to/from object storage.
- Use when: files must be tenant-isolated and you need the bucket/path and
  access-control design that guarantees one tenant cannot read another's
  objects.
- Use when: uploads are slow (proxied through the app), unsafe (extension-
  trusted, unscanned), or unbounded (no size cap, no retention), or signed
  URLs are over-scoped or long-lived.
- Use when: choosing direct-to-storage vs proxied upload, or designing
  image/thumbnail/transcode derivative processing.
- Do NOT use when: the concern is the PERSONAL-DATA lifecycle of file
  contents — lawful basis/consent, data-subject access and export, deletion
  SLAs, PII classification of what's inside the file — that is
  `pii-lifecycle-designer`; this skill designs where and how files are
  stored, not the privacy obligations on their contents.
- Do NOT use when: the task is auditing EXISTING storage RLS / bucket
  policies for holes — that is `rls-policy-auditor`; this skill DESIGNS the
  storage access model, and hands policy auditing there.
- Do NOT use when: the "file" is really structured data that belongs in the
  database, not object storage — say so rather than designing storage for it.
- Do NOT use when: the risk is instruction injection hidden in file content a
  model later reads (text or instructions in images, audio, PDF layers,
  metadata) — that is `prompt-injection-defender` (MANUAL-ONLY: recommend the
  user invoke it explicitly); this skill keeps file safety — type checks, malware scanning,
  quarantine, storage, and serving.

## Inputs to Inspect

1. What is uploaded: file types, expected and maximum sizes, volume per
   tenant, and whether files are user-facing (avatars, attachments) or
   pipeline inputs (imports, media to transcode).
2. Who may upload and who may read each object: the authorization model
   (`authorization-matrix-designer`) mapping to object access, and whether
   any objects are public (and must be, deliberately).
3. Current upload path: proxied through the app vs direct to storage, how
   URLs are signed today (scope, expiry), and any incident history (oversized
   uploads, malicious files, cross-tenant access).
4. The tenant model (`multi-tenant-data-architect`): pooled vs siloed, so
   storage tenancy (shared bucket + path prefix vs per-tenant bucket) matches.
5. Storage backend and its features: signed-URL support, object-level access
   control, lifecycle rules, versioning, and CDN integration available.
6. Downstream processing: derivatives needed (thumbnails, transcodes, text
   extraction), and whether scanning/processing must complete before the
   file is usable.

## Workflow

1. **Choose the upload flow.** Direct-to-storage (client uploads straight to
   object storage via a signed URL; the app never touches the bytes) is the
   default for size and cost — it removes the app server from the data path.
   Proxied (through the app) only when the app must inspect/transform every
   byte inline, accepting the size cap and compute cost. Before asking the
   user to choose, explain both flows in plain terms: why each fits this
   workload, its benefits and drawbacks, and money, setup, and ongoing
   upkeep costs or unknowns. Recommend the flow supported by file sizes,
   inspection needs, and existing infrastructure; explain why and what fact
   would change it. If decisive facts are missing, ask only for one missing
   fact per turn until all are known; only then recommend and ask one clear
   decision question. The choice does
   not authorize provisioning or spending. For direct uploads, the app
   issues a signed URL and RECORDS the intended object. Before marking it complete, the server checks object
   existence and expected metadata at storage; it does not trust a client
   success report.
2. **Scope signed URLs narrowly and briefly.** A signed URL grants exactly
   one verb (PUT for upload, GET for download) on exactly one object key, with
   a short expiry (minutes, not days), and — for uploads — a content-length
   and content-type constraint where the backend supports it. Never issue a
   URL scoped to a prefix or bucket. The object key is server-derived from
   the tenant + a random id, never a client-supplied path.
3. **Design storage tenancy.** Shared bucket with a mandatory tenant path
   prefix (`<tenant-id>/<random-object-id>`) or per-tenant buckets, chosen to
   match the data-tenancy model. Explain any user-facing tenancy choice with
   the same case-specific tradeoffs, costs, recommendation and single question
   before selecting it. The access rule: a signed URL or policy for
   tenant A can only ever name objects in A's bucket or under A's prefix,
   according to the chosen model; a client cannot craft a key that escapes
   its tenant. Hand the resulting bucket/RLS policy to
   `rls-policy-auditor` for verification.
4. **Validate on CONTENT, not name.** Enforce the size cap at the storage
   layer (not just client-side), and validate type by magic bytes / content
   sniffing, not the extension or the client-sent MIME type. Reject or
   quarantine mismatches. State that a `.png` whose bytes are an executable is
   rejected.
5. **Scan before use.** Route uploaded files through malware/content scanning
   BEFORE they are served or processed; hold them in a quarantine/pending
   state until scanned clean. Define the scan-failure path (delete + alert)
   and the not-yet-scanned state (not downloadable). Do not serve user-uploaded
   files from the app's own origin/domain in a way that enables stored-XSS —
   serve from an isolated domain / with safe content-disposition.
6. **Process derivatives off the request path.** Thumbnails, transcodes, and
   text extraction run as background jobs (compose
   `background-job-orchestration-architect`), not inline in the upload
   request — with the original immutable and derivatives regenerable.
7. **Design retention and lifecycle.** Per object class: retention period,
   lifecycle transitions (hot → cold/archive), orphan cleanup (objects whose
   DB record was deleted), and deletion propagation (deleting the record
   deletes the object). Storage without a retention rule grows forever — name
   the cost. Actual PII deletion SLAs defer to `pii-lifecycle-designer`.
8. **Design serving.** CDN + cache-control for public/cacheable objects;
   signed short-lived GET URLs for private objects (never a public URL for
   private data); and cache-invalidation on replacement. State the private-vs-
   public decision per object class explicitly.
9. **Deliver** the upload-flow design, tenancy + signed-URL contract,
   validation/scan pipeline, and retention plan in the Output Format.

## Output Format

```
FILE UPLOAD & STORAGE DESIGN — <system/domain>
Upload flow:    <direct-to-storage | proxied> + why; app records intent and
  verifies object existence/metadata at storage before completion
Signed URLs:    <one verb + one object key + short expiry + size/type constraint;
  never prefix/bucket-scoped; server-derived key in tenant bucket or prefix>
Storage tenancy: <shared bucket + tenant prefix | per-tenant bucket>; a signed
  URL/policy for A names only A's bucket/prefix keys → policy audit to rls-policy-auditor
Validation:     <storage-enforced size cap; magic-byte/content-type check, not
  extension; mismatch → reject/quarantine>
Scanning:       <malware/content scan before serve/process; quarantine/pending
  state; scan-fail path; safe serving domain / content-disposition (no stored XSS)>
Derivatives:    <thumbnails/transcodes/extraction as background jobs (→
  background-job-orchestration-architect); original immutable, derivatives regenerable>
Retention:      <per class: retention, lifecycle transitions, orphan cleanup,
  delete-record→delete-object>; PII deletion SLAs → pii-lifecycle-designer
Serving:        <CDN + cache-control for public; signed short-lived GET for
  private; per-class public-vs-private decision>
Cost posture:   <growth per tenant, archive tiering, orphan cost>
Choice, if needed: <plain-language options; why each fits; case-specific pros/cons;
  money/setup/upkeep costs or unknowns; recommendation + why + what could change it;
  one clear decision question>
Open questions / risks: <each with risk-if-wrong / who answers>
```

## Validation Checklist

- [ ] Upload flow (direct vs proxied) is chosen with a stated reason; direct
      uploads record intent and verify storage existence/metadata before
      completion, without trusting a client success report.
- [ ] User-facing upload or tenancy choices explain each viable option's fit,
      tradeoffs, and money/setup/upkeep costs or unknowns in plain terms;
      give a contextual recommendation and one clear question; while
      decisive facts are missing, ask only one discovery question per turn
      and recommend only once all are known.
- [ ] Signed URLs grant one verb on one object key with a short expiry and
      size/type constraints — never prefix- or bucket-scoped.
- [ ] Object keys are server-derived; a client cannot craft a key outside
      its tenant's bucket or prefix, according to the selected tenancy model.
- [ ] Size cap is enforced at the storage layer; type is validated by content/
      magic bytes, not extension or client MIME.
- [ ] Files are scanned before serve/process and held in quarantine until
      clean; private files are never served via public URLs; serving avoids
      stored-XSS (isolated domain / safe content-disposition).
- [ ] Derivatives run off the request path; the original is immutable.
- [ ] Every object class has retention, orphan cleanup, and delete-propagation;
      unbounded growth is named as cost.
- [ ] Storage access-policy verification is handed to `rls-policy-auditor`;
      PII lifecycle of contents is handed to `pii-lifecycle-designer`.

## Gotchas

- Trusting the file extension or the client-sent MIME type is the malicious-
  upload bug: validate the actual bytes, or a `.png` that is really a script
  or executable sails through.
- A signed URL scoped to a prefix or bucket, or with a multi-day expiry, is a
  bearer key to more than intended — and it can be shared/leaked. One object,
  one verb, minutes of validity.
- A client-supplied object path (`key = req.body.path`) is directory traversal
  into another tenant's data; the key must be server-derived from the tenant.
- Serving user-uploaded HTML/SVG from your app's own domain is stored XSS in
  the making; SVGs carry scripts. Serve from an isolated domain with a safe
  content-disposition, or sanitize.
- Proxying every upload through the app caps file size at the request limit
  and burns app compute on bytes it only forwards — direct-to-storage exists
  for exactly this.
- No retention rule means storage grows forever and orphaned objects (record
  deleted, object left) accumulate silently as cost with no owner.
- "Scanned later" that still serves the file now is "unscanned": the pending
  state must be non-servable, or you serve malware for the scan window.
- A public bucket for "just the avatars" becomes a public bucket for whatever
  gets written there next; default private, make public explicit per class.

## Stop Conditions

- The object-access authorization (who may read/write each object) is
  undefined → obtain it from `authorization-matrix-designer` /
  `multi-tenant-data-architect` before designing signed-URL scope; a signed
  URL enforcing an undefined policy is a false assurance.
- Regulated or highly sensitive file CONTENTS drive real retention/deletion/
  residency obligations → surface them and route the personal-data lifecycle
  to `pii-lifecycle-designer`; this skill designs storage, not the privacy
  obligations on the payload.
- The "file" is structured data better modeled in the database (queried,
  joined, partially updated) → say so; object storage is the wrong home for it.
- Asked to run a bulk migration / lifecycle purge / bucket-policy change
  against live storage → this skill DESIGNS the flow and migration; executing
  destructive storage operations follows the repo's approval path.

## Supporting Files

- `evals/evals.json` — behavior cases: the add-uploads design, the direct-vs-
  proxied + signed-URL-scope edge, the extension-trust rejection, and the PII-
  lifecycle non-trigger.
- `evals/trigger-evals.json` — discrimination against `pii-lifecycle-designer`
  (personal-data lifecycle of contents) and `rls-policy-auditor` (auditing
  existing storage policies).
- No `references/` — the upload/tenancy/validation/retention procedure above
  is complete; detail lives in the produced artifacts.

Files in this skill

  • SKILL.md12.7 KB
  • evals/evals.json4.8 KB
  • evals/trigger-evals.json2.7 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.