From 4ccd5158d6b507b2542897e36ae8537bf8e61f79 Mon Sep 17 00:00:00 2001 From: dev-kishor Date: Thu, 4 Dec 2025 19:57:54 +0530 Subject: [PATCH 01/26] Clarify SAS token terminology in Azure upload documentation - Because we are using shared key not short lived token --- apps/docs/docs/decisions/0022-existing-azure-upload.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/docs/docs/decisions/0022-existing-azure-upload.md b/apps/docs/docs/decisions/0022-existing-azure-upload.md index fa1d8d83e..700fc9d4e 100644 --- a/apps/docs/docs/decisions/0022-existing-azure-upload.md +++ b/apps/docs/docs/decisions/0022-existing-azure-upload.md @@ -15,14 +15,14 @@ informed: Our application requires a secure and scalable mechanism for handling file uploads. From the start, the design approach was to leverage Azure Blob Storage as the primary file storage service due to its reliability, scalability, and seamless integration with the Azure ecosystem. -Users need to upload various file types (PDFs, images) along with metadata and tags for tracking. The system implements a direct upload flow where the client requests authorization from the backend, which issues a short-lived SAS (Shared Access Signature) token allowing the file to be uploaded directly to Azure Blob Storage. +Users need to upload various file types (PDFs, images) along with metadata and tags for tracking. The system implements a direct upload flow where the client requests authorization from the backend, which issues a Shared Key SAS (Shared Access Signature) token allowing the file to be uploaded directly to Azure Blob Storage. ## Decision Drivers - **Scalability**: The system must efficiently handle large file uploads and multiple concurrent requests without overloading backend services. - **Performance**: Direct client-to-Azure Blob uploads reduce backend latency and improve user upload speed. - **Cost Optimization**: Offloading upload bandwidth from backend servers to Azure Blob Storage minimizes infrastructure and data transfer costs. -- **Security**: Uploads must be secure and authenticated. Short-lived SAS tokens (valet keys) provide controlled, time-bound access to the storage account. +- **Security**: Uploads must be secure and authenticated. Shared Key SAS tokens (valet keys) provide controlled, time-bound access to the storage account. - **Malware Scanning**: Uploaded files must undergo malware scanning. Any malicious files must be identified, quarantined, and deleted immediately to maintain data integrity and user safety. ## Considered Options @@ -31,11 +31,11 @@ Users need to upload various file types (PDFs, images) along with metadata and t - The client uploads files to the backend, which then transfers them to Azure Blob Storage. - **Option 2: Direct client uploads using Azure Blob SAS tokens (current approach)** - - The backend generates a short-lived SAS token authorizing the client to upload directly to Blob Storage. + - The backend generates a Shared Key SAS token authorizing the client to upload directly to Blob Storage. ## Decision Outcome -Chosen option: **Option 2: Direct client uploads using Azure Blob SAS tokens (current approach)**, because it offloads upload traffic from the backend, reduces latency, lower cost, maintains security via short-lived tokens. +Chosen option: **Option 2: Direct client uploads using Azure Blob SAS tokens (current approach)**, because it offloads upload traffic from the backend, reduces latency, lower cost, maintains security via Shared Key tokens. ## Consequences @@ -53,7 +53,7 @@ Chosen option: **Option 2: Direct client uploads using Azure Blob SAS tokens (cu **Backend Services:** - SAS Token Generation: - - The backend handles SAS token generation and validation for Azure Blob Storage uploads, ensuring secure and controlled access for file uploads. There are different mutations for PDF and image files. The backend service encapsulates all business logic enforcing file upload restrictions and security requirements before enabling clients to upload files directly to Azure Blob Storage with short-lived and carefully permissioned SAS tokens. + - The backend handles SAS token generation and validation for Azure Blob Storage uploads, ensuring secure and controlled access for file uploads. There are different mutations for PDF and image files. The backend service encapsulates all business logic enforcing file upload restrictions and security requirements before enabling clients to upload files directly to Azure Blob Storage with Shared Key and carefully permissioned SAS tokens. - Post-Upload Malware Handling: - The backend polls the blob for the Microsoft Defender for Cloud scan result tag: `No threats found` or `Malicious`. - `No threats found` → retain the blob. From ab97e93ef14370e536ea5aedb9e267af976718f0 Mon Sep 17 00:00:00 2001 From: dev-kishor Date: Thu, 4 Dec 2025 20:00:39 +0530 Subject: [PATCH 02/26] Fix grammatical error in decision outcome for Azure upload documentation --- apps/docs/docs/decisions/0022-existing-azure-upload.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/docs/decisions/0022-existing-azure-upload.md b/apps/docs/docs/decisions/0022-existing-azure-upload.md index 700fc9d4e..c939238d0 100644 --- a/apps/docs/docs/decisions/0022-existing-azure-upload.md +++ b/apps/docs/docs/decisions/0022-existing-azure-upload.md @@ -35,7 +35,7 @@ Users need to upload various file types (PDFs, images) along with metadata and t ## Decision Outcome -Chosen option: **Option 2: Direct client uploads using Azure Blob SAS tokens (current approach)**, because it offloads upload traffic from the backend, reduces latency, lower cost, maintains security via Shared Key tokens. +Chosen option: **Option 2: Direct client uploads using Azure Blob SAS tokens (current approach)**, because it offloads upload traffic from the backend, reduces latency, lowers cost, and maintains security via Shared Key tokens. ## Consequences From 70c857d5964956a4361740bc1bf1bafbb258003e Mon Sep 17 00:00:00 2001 From: dev-kishor Date: Thu, 4 Dec 2025 20:47:29 +0530 Subject: [PATCH 03/26] Clarify authorization strategies for Azure Blob uploads and emphasize the use of Shared Key SAS for enhanced security --- .../docs/decisions/0022-existing-azure-upload.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/docs/docs/decisions/0022-existing-azure-upload.md b/apps/docs/docs/decisions/0022-existing-azure-upload.md index c939238d0..508be50c7 100644 --- a/apps/docs/docs/decisions/0022-existing-azure-upload.md +++ b/apps/docs/docs/decisions/0022-existing-azure-upload.md @@ -15,7 +15,16 @@ informed: Our application requires a secure and scalable mechanism for handling file uploads. From the start, the design approach was to leverage Azure Blob Storage as the primary file storage service due to its reliability, scalability, and seamless integration with the Azure ecosystem. -Users need to upload various file types (PDFs, images) along with metadata and tags for tracking. The system implements a direct upload flow where the client requests authorization from the backend, which issues a Shared Key SAS (Shared Access Signature) token allowing the file to be uploaded directly to Azure Blob Storage. +Users need to upload various file types (PDFs, images) along with metadata and tags for tracking. The system implements a direct upload flow where the client requests authorization from the backend, which issues a SAS (Shared Access Signature) token allowing the file to be uploaded directly to Azure Blob Storage. + +we considered two authorization strategies: +- Short-lived SAS : A short-lived SAS (Shared Access Signature) in Azure Blob Storage is a token with a limited time validity that grants specific permissions to access a blob or container. And this can be breach by someone and can misuse this short lived token. +- Shared Key SAS : A Shared Key–signed Shared Access Signature (SAS) is a time‑limited and permission‑scoped token generated using the storage account’s shared key. It allows clients temporary access to upload or modify blobs without exposing the actual account key. + +We Chosen **Shared Key SAS** +Because: +- The backend generates SAS tokens along with all relevant metadata for each specific upload request, ensuring contextual and controlled access. +- Tokens are cryptographically signed using the storage account key, making them tamper‑proof and preventing third‑party manipulation. ## Decision Drivers @@ -97,4 +106,5 @@ Frontend-->>User: Show success / preview / error if malicious - Malware scanning occurs after upload using Azure Blob Storage’s capabilities. Files flagged as malicious are deleted or reverted to ensure data integrity. The system uses Microsoft Defender for Storage to automatically scan uploaded blobs for malware. Defender checks for known malware signatures, embedded scripts, and other suspicious file patterns. This introduces a small window where a malicious file may exist in storage before removal. - At present, backend permission enforcement for blob upload is minimal. The frontend restricts upload actions according to application state, but users could potentially bypass this if they possess valid credentials. -- Future improvements will focus on implementing domain-driven permission checks before SAS tokens are issued and exploring pre-upload scanning alternatives to further reduce risk. \ No newline at end of file +- Future improvements will focus on implementing domain-driven permission checks before SAS tokens are issued and exploring pre-upload scanning alternatives to further reduce risk. +- Shared Key \ No newline at end of file From e5934a25051e14c2cc388546845d8568f6136352 Mon Sep 17 00:00:00 2001 From: dev-kishor Date: Thu, 4 Dec 2025 20:49:50 +0530 Subject: [PATCH 04/26] Clarify terminology for Shared Key SAS in Azure upload documentation --- apps/docs/docs/decisions/0022-existing-azure-upload.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/docs/decisions/0022-existing-azure-upload.md b/apps/docs/docs/decisions/0022-existing-azure-upload.md index 508be50c7..d55750a09 100644 --- a/apps/docs/docs/decisions/0022-existing-azure-upload.md +++ b/apps/docs/docs/decisions/0022-existing-azure-upload.md @@ -19,7 +19,7 @@ Users need to upload various file types (PDFs, images) along with metadata and t we considered two authorization strategies: - Short-lived SAS : A short-lived SAS (Shared Access Signature) in Azure Blob Storage is a token with a limited time validity that grants specific permissions to access a blob or container. And this can be breach by someone and can misuse this short lived token. -- Shared Key SAS : A Shared Key–signed Shared Access Signature (SAS) is a time‑limited and permission‑scoped token generated using the storage account’s shared key. It allows clients temporary access to upload or modify blobs without exposing the actual account key. +- Shared Key SAS : A Shared Key–signed SAS (Shared Access Signature) is a time‑limited and permission‑scoped token generated using the storage account’s shared key. It allows clients temporary access to upload or modify blobs without exposing the actual account key. We Chosen **Shared Key SAS** Because: From cde3bf1948fcc7fa82a52e51f8930f5c5e7bf316 Mon Sep 17 00:00:00 2001 From: dev-kishor Date: Thu, 4 Dec 2025 21:16:52 +0530 Subject: [PATCH 05/26] Fix grammatical errors and improve clarity in Azure upload documentation --- apps/docs/docs/decisions/0022-existing-azure-upload.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/docs/docs/decisions/0022-existing-azure-upload.md b/apps/docs/docs/decisions/0022-existing-azure-upload.md index d55750a09..7cd10c27c 100644 --- a/apps/docs/docs/decisions/0022-existing-azure-upload.md +++ b/apps/docs/docs/decisions/0022-existing-azure-upload.md @@ -17,11 +17,11 @@ Our application requires a secure and scalable mechanism for handling file uploa Users need to upload various file types (PDFs, images) along with metadata and tags for tracking. The system implements a direct upload flow where the client requests authorization from the backend, which issues a SAS (Shared Access Signature) token allowing the file to be uploaded directly to Azure Blob Storage. -we considered two authorization strategies: -- Short-lived SAS : A short-lived SAS (Shared Access Signature) in Azure Blob Storage is a token with a limited time validity that grants specific permissions to access a blob or container. And this can be breach by someone and can misuse this short lived token. +We considered two authorization strategies: +- Short-lived SAS: A short-lived SAS (Shared Access Signature) in Azure Blob Storage is a token with limited validity that grants specific permissions to access a blob or container. It can be breached by someone, who could then misuse this short-lived token. - Shared Key SAS : A Shared Key–signed SAS (Shared Access Signature) is a time‑limited and permission‑scoped token generated using the storage account’s shared key. It allows clients temporary access to upload or modify blobs without exposing the actual account key. -We Chosen **Shared Key SAS** +We chose **Shared Key SAS** Because: - The backend generates SAS tokens along with all relevant metadata for each specific upload request, ensuring contextual and controlled access. - Tokens are cryptographically signed using the storage account key, making them tamper‑proof and preventing third‑party manipulation. From 1159e59397cd0e3e3b24e5ee046d9622c8ce3580 Mon Sep 17 00:00:00 2001 From: dev-kishor Date: Thu, 4 Dec 2025 21:27:44 +0530 Subject: [PATCH 06/26] Refine authorization strategies for Azure Blob uploads, emphasizing Shared Key SAS tokens and clarifying decision outcomes for enhanced security and management. --- .../decisions/0022-existing-azure-upload.md | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/apps/docs/docs/decisions/0022-existing-azure-upload.md b/apps/docs/docs/decisions/0022-existing-azure-upload.md index 7cd10c27c..77252d351 100644 --- a/apps/docs/docs/decisions/0022-existing-azure-upload.md +++ b/apps/docs/docs/decisions/0022-existing-azure-upload.md @@ -17,15 +17,6 @@ Our application requires a secure and scalable mechanism for handling file uploa Users need to upload various file types (PDFs, images) along with metadata and tags for tracking. The system implements a direct upload flow where the client requests authorization from the backend, which issues a SAS (Shared Access Signature) token allowing the file to be uploaded directly to Azure Blob Storage. -We considered two authorization strategies: -- Short-lived SAS: A short-lived SAS (Shared Access Signature) in Azure Blob Storage is a token with limited validity that grants specific permissions to access a blob or container. It can be breached by someone, who could then misuse this short-lived token. -- Shared Key SAS : A Shared Key–signed SAS (Shared Access Signature) is a time‑limited and permission‑scoped token generated using the storage account’s shared key. It allows clients temporary access to upload or modify blobs without exposing the actual account key. - -We chose **Shared Key SAS** -Because: -- The backend generates SAS tokens along with all relevant metadata for each specific upload request, ensuring contextual and controlled access. -- Tokens are cryptographically signed using the storage account key, making them tamper‑proof and preventing third‑party manipulation. - ## Decision Drivers - **Scalability**: The system must efficiently handle large file uploads and multiple concurrent requests without overloading backend services. @@ -34,7 +25,19 @@ Because: - **Security**: Uploads must be secure and authenticated. Shared Key SAS tokens (valet keys) provide controlled, time-bound access to the storage account. - **Malware Scanning**: Uploaded files must undergo malware scanning. Any malicious files must be identified, quarantined, and deleted immediately to maintain data integrity and user safety. -## Considered Options +## Considered Authorization Options + +- **Option 1: Short‑lived SAS tokens** + - A short‑lived Shared Access Signature (SAS) is a token with a very limited validity period that grants specific permissions to access a blob or container. If such a token is intercepted, it can still be misused for the duration of its lifetime, so it must be kept extremely short and carefully managed. + +- **Option B: Shared Key SAS tokens (current approach)** + - A Shared Key–signed Shared Access Signature (SAS) is a time‑limited, permission‑scoped URL generated using the storage account’s access key. It allows the client to upload or modify blobs temporarily without exposing the actual account key, and the backend can control exactly which permissions, metadata, and expiry apply for each individual upload request. + +## Authorization Decision Outcome + +Chosen option: **Option 2: Option B: Shared Key SAS tokens (current approach)**, because SAS URLs are generated by the backend per request with the correct blob path, metadata, and tags, and are cryptographically signed so that clients or third parties cannot tamper with or escalate their permissions. + +## Considered Upload Options - **Option 1: Backend-mediated uploads (server uploads to Blob Storage)** - The client uploads files to the backend, which then transfers them to Azure Blob Storage. @@ -42,7 +45,7 @@ Because: - **Option 2: Direct client uploads using Azure Blob SAS tokens (current approach)** - The backend generates a Shared Key SAS token authorizing the client to upload directly to Blob Storage. -## Decision Outcome +## Uploads Decision Outcome Chosen option: **Option 2: Direct client uploads using Azure Blob SAS tokens (current approach)**, because it offloads upload traffic from the backend, reduces latency, lowers cost, and maintains security via Shared Key tokens. From d233d9d236fb1feebf1e8bc122eaf7ad81b575d7 Mon Sep 17 00:00:00 2001 From: dev-kishor Date: Thu, 4 Dec 2025 21:30:45 +0530 Subject: [PATCH 07/26] Correct option labeling for Shared Key SAS tokens in Azure upload documentation and clarify chosen option in decision outcome. --- apps/docs/docs/decisions/0022-existing-azure-upload.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/docs/docs/decisions/0022-existing-azure-upload.md b/apps/docs/docs/decisions/0022-existing-azure-upload.md index 77252d351..1362d875c 100644 --- a/apps/docs/docs/decisions/0022-existing-azure-upload.md +++ b/apps/docs/docs/decisions/0022-existing-azure-upload.md @@ -30,12 +30,12 @@ Users need to upload various file types (PDFs, images) along with metadata and t - **Option 1: Short‑lived SAS tokens** - A short‑lived Shared Access Signature (SAS) is a token with a very limited validity period that grants specific permissions to access a blob or container. If such a token is intercepted, it can still be misused for the duration of its lifetime, so it must be kept extremely short and carefully managed. -- **Option B: Shared Key SAS tokens (current approach)** +- **Option 2: Shared Key SAS tokens (current approach)** - A Shared Key–signed Shared Access Signature (SAS) is a time‑limited, permission‑scoped URL generated using the storage account’s access key. It allows the client to upload or modify blobs temporarily without exposing the actual account key, and the backend can control exactly which permissions, metadata, and expiry apply for each individual upload request. ## Authorization Decision Outcome -Chosen option: **Option 2: Option B: Shared Key SAS tokens (current approach)**, because SAS URLs are generated by the backend per request with the correct blob path, metadata, and tags, and are cryptographically signed so that clients or third parties cannot tamper with or escalate their permissions. +Chosen option: **Option 2: Shared Key SAS tokens (current approach)**, because SAS URLs are generated by the backend per request with the correct blob path, metadata, and tags, and are cryptographically signed so that clients or third parties cannot tamper with or escalate their permissions. ## Considered Upload Options From 25c5d7b671aaac72a12a41cdb7fac99e67ec008e Mon Sep 17 00:00:00 2001 From: dev-kishor Date: Thu, 4 Dec 2025 21:35:47 +0530 Subject: [PATCH 08/26] Refine wording for clarity in SAS token generation description for Azure Blob uploads --- apps/docs/docs/decisions/0022-existing-azure-upload.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/docs/decisions/0022-existing-azure-upload.md b/apps/docs/docs/decisions/0022-existing-azure-upload.md index 1362d875c..13dc3b455 100644 --- a/apps/docs/docs/decisions/0022-existing-azure-upload.md +++ b/apps/docs/docs/decisions/0022-existing-azure-upload.md @@ -65,7 +65,7 @@ Chosen option: **Option 2: Direct client uploads using Azure Blob SAS tokens (cu **Backend Services:** - SAS Token Generation: - - The backend handles SAS token generation and validation for Azure Blob Storage uploads, ensuring secure and controlled access for file uploads. There are different mutations for PDF and image files. The backend service encapsulates all business logic enforcing file upload restrictions and security requirements before enabling clients to upload files directly to Azure Blob Storage with Shared Key and carefully permissioned SAS tokens. + - The backend handles SAS token generation and validation for Azure Blob Storage uploads, ensuring secure and controlled access for file uploads. There are different mutations for PDF and image files. The backend service encapsulates all business logic enforcing file upload restrictions and security requirements before enabling clients to upload files directly to Azure Blob Storage using carefully permissioned, Shared Key–signed SAS tokens. - Post-Upload Malware Handling: - The backend polls the blob for the Microsoft Defender for Cloud scan result tag: `No threats found` or `Malicious`. - `No threats found` → retain the blob. From c58ea4326aeeb96cd84a61a3a53e446e43c2c48f Mon Sep 17 00:00:00 2001 From: dev-kishor Date: Wed, 17 Dec 2025 20:57:40 +0530 Subject: [PATCH 09/26] Refine documentation to clarify the use of Valet Key pattern (Shared Key Authorization) for Azure Blob uploads --- .../decisions/0022-existing-azure-upload.md | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/apps/docs/docs/decisions/0022-existing-azure-upload.md b/apps/docs/docs/decisions/0022-existing-azure-upload.md index 13dc3b455..12aa40a9e 100644 --- a/apps/docs/docs/decisions/0022-existing-azure-upload.md +++ b/apps/docs/docs/decisions/0022-existing-azure-upload.md @@ -1,7 +1,7 @@ --- sidebar_position: 22 sidebar_label: 0022 Existing Azure Upload -description: "Existing Azure Upload with Direct client uploads using Azure Blob SAS tokens." +description: "Existing Azure Upload with Direct client uploads using Azure Valet Key pattern (Shared Key Authorization)." status: proposed date: 2025-10-21 deciders: gidich @@ -15,14 +15,14 @@ informed: Our application requires a secure and scalable mechanism for handling file uploads. From the start, the design approach was to leverage Azure Blob Storage as the primary file storage service due to its reliability, scalability, and seamless integration with the Azure ecosystem. -Users need to upload various file types (PDFs, images) along with metadata and tags for tracking. The system implements a direct upload flow where the client requests authorization from the backend, which issues a SAS (Shared Access Signature) token allowing the file to be uploaded directly to Azure Blob Storage. +Users need to upload various file types (PDFs, images) along with metadata and tags for tracking. The system implements a direct upload flow where the client requests authorization from the backend, which issues a Shared Key Authorization token allowing the file to be uploaded directly to Azure Blob Storage. ## Decision Drivers - **Scalability**: The system must efficiently handle large file uploads and multiple concurrent requests without overloading backend services. - **Performance**: Direct client-to-Azure Blob uploads reduce backend latency and improve user upload speed. - **Cost Optimization**: Offloading upload bandwidth from backend servers to Azure Blob Storage minimizes infrastructure and data transfer costs. -- **Security**: Uploads must be secure and authenticated. Shared Key SAS tokens (valet keys) provide controlled, time-bound access to the storage account. +- **Security**: Uploads must be secure and authenticated. Valet Key pattern provides controlled, time-bound access to the storage account. - **Malware Scanning**: Uploaded files must undergo malware scanning. Any malicious files must be identified, quarantined, and deleted immediately to maintain data integrity and user safety. ## Considered Authorization Options @@ -30,24 +30,24 @@ Users need to upload various file types (PDFs, images) along with metadata and t - **Option 1: Short‑lived SAS tokens** - A short‑lived Shared Access Signature (SAS) is a token with a very limited validity period that grants specific permissions to access a blob or container. If such a token is intercepted, it can still be misused for the duration of its lifetime, so it must be kept extremely short and carefully managed. -- **Option 2: Shared Key SAS tokens (current approach)** - - A Shared Key–signed Shared Access Signature (SAS) is a time‑limited, permission‑scoped URL generated using the storage account’s access key. It allows the client to upload or modify blobs temporarily without exposing the actual account key, and the backend can control exactly which permissions, metadata, and expiry apply for each individual upload request. +- **Option 2: Valet Key pattern (current approach)** + - A Valet Key pattern (Shared Key Authorization) is a time‑limited, permission‑scoped URL generated using the storage account’s access key. It allows the client to upload or modify blobs temporarily without exposing the actual account key, and the backend can control exactly which permissions, metadata, and expiry apply for each individual upload request. ## Authorization Decision Outcome -Chosen option: **Option 2: Shared Key SAS tokens (current approach)**, because SAS URLs are generated by the backend per request with the correct blob path, metadata, and tags, and are cryptographically signed so that clients or third parties cannot tamper with or escalate their permissions. +Chosen option: **Option 2: Valet Key pattern (current approach)**, because Shared Key Authorization tokens are generated by the backend per request with the correct blob path, metadata, and tags, and are cryptographically signed so that clients or third parties cannot tamper with or escalate their permissions. ## Considered Upload Options - **Option 1: Backend-mediated uploads (server uploads to Blob Storage)** - The client uploads files to the backend, which then transfers them to Azure Blob Storage. -- **Option 2: Direct client uploads using Azure Blob SAS tokens (current approach)** - - The backend generates a Shared Key SAS token authorizing the client to upload directly to Blob Storage. +- **Option 2: Direct client uploads using Azure Blob Valet Key pattern (Shared Key Authorization) tokens (current approach)** + - The backend generates a Shared Key Authorization token authorizing the client to upload directly to Blob Storage. ## Uploads Decision Outcome -Chosen option: **Option 2: Direct client uploads using Azure Blob SAS tokens (current approach)**, because it offloads upload traffic from the backend, reduces latency, lowers cost, and maintains security via Shared Key tokens. +Chosen option: **Option 2: Direct client uploads using Azure Valet Key pattern (Shared Key Authorization) tokens (current approach)**, because it offloads upload traffic from the backend, reduces latency, lowers cost, and maintains security via Shared Key tokens. ## Consequences @@ -60,12 +60,12 @@ Chosen option: **Option 2: Direct client uploads using Azure Blob SAS tokens (cu **Frontend Components:** - Handles client-side file validation (type, size, dimensions). - Requests authorization from the backend to upload a specific file. -- Uses the received SAS token to upload the file directly to Azure Blob Storage. +- Uses the received Shared Key Authorization token to upload the file directly to Azure Blob Storage. - After upload, notifies the backend to trigger malware scanning and persist upload metadata. **Backend Services:** -- SAS Token Generation: - - The backend handles SAS token generation and validation for Azure Blob Storage uploads, ensuring secure and controlled access for file uploads. There are different mutations for PDF and image files. The backend service encapsulates all business logic enforcing file upload restrictions and security requirements before enabling clients to upload files directly to Azure Blob Storage using carefully permissioned, Shared Key–signed SAS tokens. +- Shared Key Authorization Token Generation: + - The backend handles Shared Key Authorization token generation and validation for Azure Blob Storage uploads, ensuring secure and controlled access for file uploads. There are different mutations for PDF and image files. The backend service encapsulates all business logic enforcing file upload restrictions and security requirements before enabling clients to upload files directly to Azure Blob Storage using carefully permissioned, Shared Key–signed Authorization tokens. - Post-Upload Malware Handling: - The backend polls the blob for the Microsoft Defender for Cloud scan result tag: `No threats found` or `Malicious`. - `No threats found` → retain the blob. @@ -88,9 +88,9 @@ participant Blob User->>Frontend: Click upload and select file Frontend->>Frontend: Sanitize & validate (type, size, dimensions) -Frontend->>Backend: Request SAS token (name, type, size) +Frontend->>Backend: Request Shared Key Authorization token (name, type, size) Backend->>Backend: Build blob path + tags + metadata, validate upload rules -Backend-->>Frontend: AuthResult (blob URL + SAS token + x-ms-date + tags + metadata) +Backend-->>Frontend: AuthResult (blob URL + Shared Key Authorization token + x-ms-date + tags + metadata) Frontend->>Blob: PUT file bytes (headers + auth + tags + metadata) Blob-->>Frontend: 201 Created (x-ms-version-id) Frontend->>Backend: Persist blob reference/version ID @@ -109,5 +109,4 @@ Frontend-->>User: Show success / preview / error if malicious - Malware scanning occurs after upload using Azure Blob Storage’s capabilities. Files flagged as malicious are deleted or reverted to ensure data integrity. The system uses Microsoft Defender for Storage to automatically scan uploaded blobs for malware. Defender checks for known malware signatures, embedded scripts, and other suspicious file patterns. This introduces a small window where a malicious file may exist in storage before removal. - At present, backend permission enforcement for blob upload is minimal. The frontend restricts upload actions according to application state, but users could potentially bypass this if they possess valid credentials. -- Future improvements will focus on implementing domain-driven permission checks before SAS tokens are issued and exploring pre-upload scanning alternatives to further reduce risk. -- Shared Key \ No newline at end of file +- Future improvements will focus on implementing domain-driven permission checks before Shared Key Authauthorization tokens are issued and exploring pre-upload scanning alternatives to further reduce risk. \ No newline at end of file From 454cb0de52d69f3bab9315cec87029f61ad5ecad Mon Sep 17 00:00:00 2001 From: dev-kishor Date: Wed, 17 Dec 2025 22:31:57 +0530 Subject: [PATCH 10/26] Enhance documentation for Azure upload by clarifying security measures and adding a visual representation of the Valet Key pattern. --- .../decisions/0022-existing-azure-upload.md | 4 +++- .../docs/decisions/img/valet-key-pattern.png | Bin 0 -> 30839 bytes 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 apps/docs/docs/decisions/img/valet-key-pattern.png diff --git a/apps/docs/docs/decisions/0022-existing-azure-upload.md b/apps/docs/docs/decisions/0022-existing-azure-upload.md index 12aa40a9e..b73bb9e67 100644 --- a/apps/docs/docs/decisions/0022-existing-azure-upload.md +++ b/apps/docs/docs/decisions/0022-existing-azure-upload.md @@ -22,7 +22,7 @@ Users need to upload various file types (PDFs, images) along with metadata and t - **Scalability**: The system must efficiently handle large file uploads and multiple concurrent requests without overloading backend services. - **Performance**: Direct client-to-Azure Blob uploads reduce backend latency and improve user upload speed. - **Cost Optimization**: Offloading upload bandwidth from backend servers to Azure Blob Storage minimizes infrastructure and data transfer costs. -- **Security**: Uploads must be secure and authenticated. Valet Key pattern provides controlled, time-bound access to the storage account. +- **Security**: Uploads must be secure and authenticated. The Valet Key pattern provides controlled, time-bound access to the storage account. - **Malware Scanning**: Uploaded files must undergo malware scanning. Any malicious files must be identified, quarantined, and deleted immediately to maintain data integrity and user safety. ## Considered Authorization Options @@ -32,6 +32,8 @@ Users need to upload various file types (PDFs, images) along with metadata and t - **Option 2: Valet Key pattern (current approach)** - A Valet Key pattern (Shared Key Authorization) is a time‑limited, permission‑scoped URL generated using the storage account’s access key. It allows the client to upload or modify blobs temporarily without exposing the actual account key, and the backend can control exactly which permissions, metadata, and expiry apply for each individual upload request. +

+ github_create_task ## Authorization Decision Outcome diff --git a/apps/docs/docs/decisions/img/valet-key-pattern.png b/apps/docs/docs/decisions/img/valet-key-pattern.png new file mode 100644 index 0000000000000000000000000000000000000000..36fb65c9fa80f943ac072154eb4bf565f796be44 GIT binary patch literal 30839 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 x>bE?Dzb@*8uSc2 Date: Wed, 17 Dec 2025 23:36:02 +0530 Subject: [PATCH 11/26] Refine Valet Key pattern documentation by updating HTML syntax for image styling and improving formatting for clarity. --- apps/docs/docs/decisions/0022-existing-azure-upload.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/docs/docs/decisions/0022-existing-azure-upload.md b/apps/docs/docs/decisions/0022-existing-azure-upload.md index b73bb9e67..f5e7a679d 100644 --- a/apps/docs/docs/decisions/0022-existing-azure-upload.md +++ b/apps/docs/docs/decisions/0022-existing-azure-upload.md @@ -32,8 +32,8 @@ Users need to upload various file types (PDFs, images) along with metadata and t - **Option 2: Valet Key pattern (current approach)** - A Valet Key pattern (Shared Key Authorization) is a time‑limited, permission‑scoped URL generated using the storage account’s access key. It allows the client to upload or modify blobs temporarily without exposing the actual account key, and the backend can control exactly which permissions, metadata, and expiry apply for each individual upload request. -

- github_create_task +

+ github_create_task ## Authorization Decision Outcome From fc5ce349e68f87eeb10232fcf0934277bab5c17b Mon Sep 17 00:00:00 2001 From: dev-kishor Date: Thu, 18 Dec 2025 01:25:25 +0530 Subject: [PATCH 12/26] Refactor Azure upload documentation for clarity and consistency; update description and remove outdated image. --- .../decisions/0022-existing-azure-upload.md | 69 +++++++++++------- .../docs/decisions/img/valet-key-pattern.png | Bin 30839 -> 0 bytes 2 files changed, 44 insertions(+), 25 deletions(-) delete mode 100644 apps/docs/docs/decisions/img/valet-key-pattern.png diff --git a/apps/docs/docs/decisions/0022-existing-azure-upload.md b/apps/docs/docs/decisions/0022-existing-azure-upload.md index f5e7a679d..ae0a5aa51 100644 --- a/apps/docs/docs/decisions/0022-existing-azure-upload.md +++ b/apps/docs/docs/decisions/0022-existing-azure-upload.md @@ -1,7 +1,7 @@ --- sidebar_position: 22 sidebar_label: 0022 Existing Azure Upload -description: "Existing Azure Upload with Direct client uploads using Azure Valet Key pattern (Shared Key Authorization)." +description: "Existing Azure Upload using the Valet Key architectural pattern with direct client uploads with Shared Key authorization." status: proposed date: 2025-10-21 deciders: gidich @@ -15,7 +15,7 @@ informed: Our application requires a secure and scalable mechanism for handling file uploads. From the start, the design approach was to leverage Azure Blob Storage as the primary file storage service due to its reliability, scalability, and seamless integration with the Azure ecosystem. -Users need to upload various file types (PDFs, images) along with metadata and tags for tracking. The system implements a direct upload flow where the client requests authorization from the backend, which issues a Shared Key Authorization token allowing the file to be uploaded directly to Azure Blob Storage. +Users need to upload various file types (PDFs, images) along with metadata and tags for tracking. The system implements a direct upload flow where the client requests authorization from the backend, which issues Shared Key–signed request details allowing the file to be uploaded directly to Azure Blob Storage. ## Decision Drivers @@ -25,35 +25,54 @@ Users need to upload various file types (PDFs, images) along with metadata and t - **Security**: Uploads must be secure and authenticated. The Valet Key pattern provides controlled, time-bound access to the storage account. - **Malware Scanning**: Uploaded files must undergo malware scanning. Any malicious files must be identified, quarantined, and deleted immediately to maintain data integrity and user safety. -## Considered Authorization Options +## Considered Architectural Upload Options -- **Option 1: Short‑lived SAS tokens** - - A short‑lived Shared Access Signature (SAS) is a token with a very limited validity period that grants specific permissions to access a blob or container. If such a token is intercepted, it can still be misused for the duration of its lifetime, so it must be kept extremely short and carefully managed. +- **Option 1: Backend-mediated uploads (server uploads to Blob Storage)** + - The client uploads files to the backend, which then transfers them to Azure Blob Storage. -- **Option 2: Valet Key pattern (current approach)** - - A Valet Key pattern (Shared Key Authorization) is a time‑limited, permission‑scoped URL generated using the storage account’s access key. It allows the client to upload or modify blobs temporarily without exposing the actual account key, and the backend can control exactly which permissions, metadata, and expiry apply for each individual upload request. -

- github_create_task +- **Option 2: Valet Key architectural pattern (direct client uploads) (current approach)** + - Client requests permission from the backend; backend validates and grants time‑bound, scope‑limited permission for a specific upload; client uploads directly to Azure Blob Storage. + - The Valet Key pattern is an architectural approach where the backend acts as a gatekeeper that grants narrowly scoped, time‑bound permission for a specific operation against storage. The client never has broad storage credentials and only performs the single intended operation within a short window using parameters the server defines (path, headers, metadata, tags, and expiry). -## Authorization Decision Outcome +## Pros and Cons of the Options -Chosen option: **Option 2: Valet Key pattern (current approach)**, because Shared Key Authorization tokens are generated by the backend per request with the correct blob path, metadata, and tags, and are cryptographically signed so that clients or third parties cannot tamper with or escalate their permissions. +### Backend-mediated uploads +- Good, because simpler client logic; backend can stream/transform data inline. +- Bad, because higher backend load and egress, potential bottlenecks, increased infrastructure cost. -## Considered Upload Options +### Valet Key architectural pattern +- Good, because excellent scalability, reduced backend bandwidth/cost, improved upload performance and latency, precise server control over per‑upload intent (path, metadata, tags). +- Bad, because validation and security checks occur after upload, adding complexity to post-upload workflows (e.g., malware scanning) and requiring careful orchestration of server-issued parameters and client behavior. -- **Option 1: Backend-mediated uploads (server uploads to Blob Storage)** - - The client uploads files to the backend, which then transfers them to Azure Blob Storage. +## Architectural Upload Decision Outcome + +Chosen option: **Valet Key architectural pattern (direct client uploads) (current approach)**, because it offloads upload traffic from the backend, reduces latency, lowers cost, and maintains security. + +## Considered Authorization Mechanism (within Valet Key) + +- **Option 1: Short‑lived SAS token** + - A short‑lived Shared Access Signature (SAS) is a token with a very limited validity period that grants specific permissions to access a blob or container. If such a token is intercepted, it can still be misused for the duration of its lifetime, so it must be kept extremely short and carefully managed. + +- **Option 2: Shared Key (current approach)** + - The server signs the request using the storage account’s key (request signing). The client sends the exact signed headers as provided by the backend to perform the upload. This is not a token; it is a Shared Key–signed request. It allows the client to perform a single, narrowly scoped upload operation using server-signed request headers, without exposing the account key, and the backend can control exactly which permissions, metadata, and expiry apply for each individual upload request. + +### Pros/Cons + +### Short‑lived SAS token +- Good, because it's simple client integration; permission and expiry embedded in a single token; easy link‑style distribution when appropriate. +- Bad, because token becomes a reusable artifact during its lifetime; needs strict expirations and storage discipline; leakage risk exists for the token window. -- **Option 2: Direct client uploads using Azure Blob Valet Key pattern (Shared Key Authorization) tokens (current approach)** - - The backend generates a Shared Key Authorization token authorizing the client to upload directly to Blob Storage. +### Shared Key (current approach) +- Good, because no reusable token artifact; tighter server‑side control over the exact method, resource path, headers, metadata, tags, and narrow time window; matches current implementation. +- Bad, because backend must construct canonicalized request details; stronger coupling between backend‑issued headers and client upload code. -## Uploads Decision Outcome +## Authorization Mechanism (within Valet Key) Decision Outcome -Chosen option: **Option 2: Direct client uploads using Azure Valet Key pattern (Shared Key Authorization) tokens (current approach)**, because it offloads upload traffic from the backend, reduces latency, lowers cost, and maintains security via Shared Key tokens. +Chosen option: **Option 2: Shared Key (current approach)**, because Shared Key–signed requests are generated by the backend per request with the correct blob path, metadata, and tags, and are cryptographically signed so that clients or third parties cannot tamper with or escalate their permissions. ## Consequences -- Good, because uploading directly from the client to Azure Blob Storage significantly reduces backend bandwidth usage and infrastructure costs. +- Good, because uploading directly from the client to Azure Blob Storage using Shared-key significantly reduces backend bandwidth usage and infrastructure costs. - Good, because versioning support allows easy rollback in case of corruption or malicious file detection. - Bad, because malware scanning occurs after upload, introducing a brief exposure window before a file is fully validated. @@ -62,12 +81,12 @@ Chosen option: **Option 2: Direct client uploads using Azure Valet Key pattern ( **Frontend Components:** - Handles client-side file validation (type, size, dimensions). - Requests authorization from the backend to upload a specific file. -- Uses the received Shared Key Authorization token to upload the file directly to Azure Blob Storage. +- Uses the server-provided Shared Key–signed headers to upload the file directly to Azure Blob Storage. - After upload, notifies the backend to trigger malware scanning and persist upload metadata. **Backend Services:** -- Shared Key Authorization Token Generation: - - The backend handles Shared Key Authorization token generation and validation for Azure Blob Storage uploads, ensuring secure and controlled access for file uploads. There are different mutations for PDF and image files. The backend service encapsulates all business logic enforcing file upload restrictions and security requirements before enabling clients to upload files directly to Azure Blob Storage using carefully permissioned, Shared Key–signed Authorization tokens. +- Shared Key–Signed Header Generation: + - The backend handles Shared Key–Signed Header generation and validation for Azure Blob Storage uploads, ensuring secure and controlled access for file uploads. There are different mutations for PDF and image files. The backend service encapsulates all business logic enforcing file upload restrictions and security requirements before enabling clients to upload files directly to Azure Blob Storage using carefully permissioned, Shared Key–signed request headers. - Post-Upload Malware Handling: - The backend polls the blob for the Microsoft Defender for Cloud scan result tag: `No threats found` or `Malicious`. - `No threats found` → retain the blob. @@ -90,9 +109,9 @@ participant Blob User->>Frontend: Click upload and select file Frontend->>Frontend: Sanitize & validate (type, size, dimensions) -Frontend->>Backend: Request Shared Key Authorization token (name, type, size) +Frontend->>Backend: Request Shared Key–signed headers (name, type, size) Backend->>Backend: Build blob path + tags + metadata, validate upload rules -Backend-->>Frontend: AuthResult (blob URL + Shared Key Authorization token + x-ms-date + tags + metadata) +Backend-->>Frontend: AuthResult (blob URL + Shared Key–signed headers + x-ms-date + tags + metadata) Frontend->>Blob: PUT file bytes (headers + auth + tags + metadata) Blob-->>Frontend: 201 Created (x-ms-version-id) Frontend->>Backend: Persist blob reference/version ID @@ -111,4 +130,4 @@ Frontend-->>User: Show success / preview / error if malicious - Malware scanning occurs after upload using Azure Blob Storage’s capabilities. Files flagged as malicious are deleted or reverted to ensure data integrity. The system uses Microsoft Defender for Storage to automatically scan uploaded blobs for malware. Defender checks for known malware signatures, embedded scripts, and other suspicious file patterns. This introduces a small window where a malicious file may exist in storage before removal. - At present, backend permission enforcement for blob upload is minimal. The frontend restricts upload actions according to application state, but users could potentially bypass this if they possess valid credentials. -- Future improvements will focus on implementing domain-driven permission checks before Shared Key Authauthorization tokens are issued and exploring pre-upload scanning alternatives to further reduce risk. \ No newline at end of file +- Future improvements will focus on implementing domain-driven permission checks before Shared Key–signed request details are issued. \ No newline at end of file diff --git a/apps/docs/docs/decisions/img/valet-key-pattern.png b/apps/docs/docs/decisions/img/valet-key-pattern.png deleted file mode 100644 index 36fb65c9fa80f943ac072154eb4bf565f796be44..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30839 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 x>bE?Dzb@*8uSc2 Date: Thu, 18 Dec 2025 01:34:30 +0530 Subject: [PATCH 13/26] Fix description wording for clarity and consistency in Azure upload documentation; update Shared Key reference. --- apps/docs/docs/decisions/0022-existing-azure-upload.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/docs/docs/decisions/0022-existing-azure-upload.md b/apps/docs/docs/decisions/0022-existing-azure-upload.md index ae0a5aa51..35f19f5ec 100644 --- a/apps/docs/docs/decisions/0022-existing-azure-upload.md +++ b/apps/docs/docs/decisions/0022-existing-azure-upload.md @@ -1,7 +1,7 @@ --- sidebar_position: 22 sidebar_label: 0022 Existing Azure Upload -description: "Existing Azure Upload using the Valet Key architectural pattern with direct client uploads with Shared Key authorization." +description: "Existing Azure Upload using the Valet Key architectural pattern with direct client uploads and Shared Key authorization." status: proposed date: 2025-10-21 deciders: gidich @@ -72,7 +72,7 @@ Chosen option: **Option 2: Shared Key (current approach)**, because Shared Key ## Consequences -- Good, because uploading directly from the client to Azure Blob Storage using Shared-key significantly reduces backend bandwidth usage and infrastructure costs. +- Good, because uploading directly from the client to Azure Blob Storage using Shared key significantly reduces backend bandwidth usage and infrastructure costs. - Good, because versioning support allows easy rollback in case of corruption or malicious file detection. - Bad, because malware scanning occurs after upload, introducing a brief exposure window before a file is fully validated. From 4b20164c248fdd9e7a71f207f9ce10262d99b8af Mon Sep 17 00:00:00 2001 From: dev-kishor Date: Thu, 18 Dec 2025 01:38:08 +0530 Subject: [PATCH 14/26] Refine documentation language for clarity in Azure upload consequences and backend services section. --- apps/docs/docs/decisions/0022-existing-azure-upload.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/docs/docs/decisions/0022-existing-azure-upload.md b/apps/docs/docs/decisions/0022-existing-azure-upload.md index 35f19f5ec..2f2c0eb32 100644 --- a/apps/docs/docs/decisions/0022-existing-azure-upload.md +++ b/apps/docs/docs/decisions/0022-existing-azure-upload.md @@ -72,7 +72,7 @@ Chosen option: **Option 2: Shared Key (current approach)**, because Shared Key ## Consequences -- Good, because uploading directly from the client to Azure Blob Storage using Shared key significantly reduces backend bandwidth usage and infrastructure costs. +- Good, because uploading directly from the client to Azure Blob Storage using Shared Key significantly reduces backend bandwidth usage and infrastructure costs. - Good, because versioning support allows easy rollback in case of corruption or malicious file detection. - Bad, because malware scanning occurs after upload, introducing a brief exposure window before a file is fully validated. @@ -85,8 +85,8 @@ Chosen option: **Option 2: Shared Key (current approach)**, because Shared Key - After upload, notifies the backend to trigger malware scanning and persist upload metadata. **Backend Services:** -- Shared Key–Signed Header Generation: - - The backend handles Shared Key–Signed Header generation and validation for Azure Blob Storage uploads, ensuring secure and controlled access for file uploads. There are different mutations for PDF and image files. The backend service encapsulates all business logic enforcing file upload restrictions and security requirements before enabling clients to upload files directly to Azure Blob Storage using carefully permissioned, Shared Key–signed request headers. +- Shared Key–signed headers generation: + - The backend handles Shared Key–signed headers generation and validation for Azure Blob Storage uploads, ensuring secure and controlled access for file uploads. There are different mutations for PDF and image files. The backend service encapsulates all business logic enforcing file upload restrictions and security requirements before enabling clients to upload files directly to Azure Blob Storage using carefully permissioned, Shared Key–signed request headers. - Post-Upload Malware Handling: - The backend polls the blob for the Microsoft Defender for Cloud scan result tag: `No threats found` or `Malicious`. - `No threats found` → retain the blob. From 2e5770ad555313d6f9ca954a6878e80f3d3ca082 Mon Sep 17 00:00:00 2001 From: dev-kishor Date: Thu, 18 Dec 2025 20:13:19 +0530 Subject: [PATCH 15/26] Clarify Shared Key authorization details in Azure upload documentation, emphasizing time-bounded request validity and backend header issuance. --- apps/docs/docs/decisions/0022-existing-azure-upload.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/docs/docs/decisions/0022-existing-azure-upload.md b/apps/docs/docs/decisions/0022-existing-azure-upload.md index 2f2c0eb32..0cb608098 100644 --- a/apps/docs/docs/decisions/0022-existing-azure-upload.md +++ b/apps/docs/docs/decisions/0022-existing-azure-upload.md @@ -68,7 +68,7 @@ Chosen option: **Valet Key architectural pattern (direct client uploads) (curren ## Authorization Mechanism (within Valet Key) Decision Outcome -Chosen option: **Option 2: Shared Key (current approach)**, because Shared Key–signed requests are generated by the backend per request with the correct blob path, metadata, and tags, and are cryptographically signed so that clients or third parties cannot tamper with or escalate their permissions. +Chosen option: **Option 2: Shared Key (current approach)**, because Shared Key–signed requests are generated by the backend per request with the correct blob path, metadata, and tags, and are cryptographically signed so that clients or third parties cannot tamper with or escalate their permissions. Although Shared Key authorization does not include an explicit expiry field like SAS, request validity is effectively time-bounded by Azure Blob Storage through the x-ms-date header and strict clock-skew enforcement, with the backend issuing these signed headers just-in-time per upload. ## Consequences @@ -130,4 +130,4 @@ Frontend-->>User: Show success / preview / error if malicious - Malware scanning occurs after upload using Azure Blob Storage’s capabilities. Files flagged as malicious are deleted or reverted to ensure data integrity. The system uses Microsoft Defender for Storage to automatically scan uploaded blobs for malware. Defender checks for known malware signatures, embedded scripts, and other suspicious file patterns. This introduces a small window where a malicious file may exist in storage before removal. - At present, backend permission enforcement for blob upload is minimal. The frontend restricts upload actions according to application state, but users could potentially bypass this if they possess valid credentials. -- Future improvements will focus on implementing domain-driven permission checks before Shared Key–signed request details are issued. \ No newline at end of file +- Future improvements will focus on implementing domain-driven permission checks before upload authorizations/Shared Key–signed headers are issued. \ No newline at end of file From 6f58b3b1f9fcc112a41f87c4a422f4d3c775b599 Mon Sep 17 00:00:00 2001 From: dev-kishor Date: Tue, 23 Dec 2025 20:50:22 +0530 Subject: [PATCH 16/26] Expand documentation on Shared Key authorization, detailing reasons for not using SAS tokens and Entra ID, along with mitigations for storage account key exposure. --- .../decisions/0022-existing-azure-upload.md | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/apps/docs/docs/decisions/0022-existing-azure-upload.md b/apps/docs/docs/decisions/0022-existing-azure-upload.md index 0cb608098..a3262ed35 100644 --- a/apps/docs/docs/decisions/0022-existing-azure-upload.md +++ b/apps/docs/docs/decisions/0022-existing-azure-upload.md @@ -70,6 +70,34 @@ Chosen option: **Valet Key architectural pattern (direct client uploads) (curren Chosen option: **Option 2: Shared Key (current approach)**, because Shared Key–signed requests are generated by the backend per request with the correct blob path, metadata, and tags, and are cryptographically signed so that clients or third parties cannot tamper with or escalate their permissions. Although Shared Key authorization does not include an explicit expiry field like SAS, request validity is effectively time-bounded by Azure Blob Storage through the x-ms-date header and strict clock-skew enforcement, with the backend issuing these signed headers just-in-time per upload. +### Why we did not choose SAS tokens + +- **Insufficient granularity for headers/metadata/tags**: SAS scopes permissions (read/write/list/set permissions) and time, but it does not bind the request to specific header values like `x-ms-meta-*` or blob index tags. A SAS with `w` (write) or `c` (create) permission allows any actor possessing the token to upload any content and arbitrary metadata/tags to the scoped resource during the token lifetime. +- **Reusable artifact risk**: A leaked SAS can be used by unauthorized parties for the duration of its validity window. While short expirations help, they do not eliminate the risk of misuse within that window. +- **No content-level binding**: SAS does not bind the authorization to the server-approved payload (file digest, exact headers, or preapproved metadata/tags). With Shared Key–signed canonical requests, we include the precise headers the client must send; any deviation invalidates the signature. +- **Operational blast radius**: Using SAS for direct client uploads would require wide distribution of short-lived tokens and introduce complex token lifecycle management without improving our fine-grained control over what is uploaded. + +We considered placing a proxy in front of uploads (client → Function App → Blob) to perform server-side validation with SAS or Entra ID, but this approach reintroduces backend bandwidth/latency bottlenecks and increases exposure to DDoS and malicious payload processing on the server. Our current design avoids those risks by keeping uploads direct while maintaining strong, per-request constraints through Shared Key–signed headers. + +### Why we have not switched to Entra ID (Azure AD) + +- **Control model mismatch**: Entra ID enables RBAC and token-based auth for storage, and can mint user delegation SAS, but it does not provide the same mechanism to cryptographically bind uploads to exact header values, metadata, and tags for a single pre-authorized request. Our design relies on signing the canonical request (including headers) so the client cannot alter approved parameters. +- **Managed Identity does not expose account keys**: Shared Key requires the storage account key to sign the canonical request. Managed Identity (MI) authenticates the Function App to Azure but does not grant access to the raw account key value. Without the account key, we cannot perform Shared Key signing as implemented. +- **User delegation SAS still inherits SAS limitations**: Even with Entra ID, user delegation SAS remains a SAS model—time/permission scoped, but not bound to specific header values or content—and therefore lacks our required fine-grained constraints. + +We prefer Entra ID for service-to-service auth wherever possible and continue to revisit it. For uploads requiring per-request binding to exact headers/metadata/tags, Shared Key signing remains the only option that satisfies our constraints. + +### Mitigations for storage account key exposure + +Although Shared Key authorization requires access to the storage account key, the associated risk is mitigated through the following controls: + +- **Key Vault only, no inline secrets**: The storage account key is never stored in code or configuration. It resides solely in Azure Key Vault. +- **Access via Managed Identity**: The Function App accesses the Key Vault using its Managed Identity, with least-privilege RBAC and no direct exposure of the key outside the process memory of the signing operation. +- **Network and access controls**: Key Vault firewall, private endpoints, and access policies restrict retrieval to the Function App identity. Storage account network rules further limit access. +- **Key rotation**: Regular rotation of storage account keys reduces exposure window if compromise were ever to occur. +- **Monitoring and scanning**: Defender for Storage malware scanning, audit logs, and alerts monitor anomalous activity; malicious uploads are quarantined and rolled back using blob versioning. +- **Time-bounded signed requests**: Each upload is signed just-in-time with strict `x-ms-date` constraints; signatures expire quickly and cannot be reused or altered by clients. + ## Consequences - Good, because uploading directly from the client to Azure Blob Storage using Shared Key significantly reduces backend bandwidth usage and infrastructure costs. From 4051767f3d3abeb09fb991ed935f21b4a3c151fc Mon Sep 17 00:00:00 2001 From: dev-kishor Date: Tue, 23 Dec 2025 22:08:23 +0530 Subject: [PATCH 17/26] Update documentation to correct 'Microsoft Defender for Cloud' to 'Microsoft Defender for Storage' in post-upload malware handling section. --- apps/docs/docs/decisions/0022-existing-azure-upload.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/docs/decisions/0022-existing-azure-upload.md b/apps/docs/docs/decisions/0022-existing-azure-upload.md index a3262ed35..5a34b99b4 100644 --- a/apps/docs/docs/decisions/0022-existing-azure-upload.md +++ b/apps/docs/docs/decisions/0022-existing-azure-upload.md @@ -116,7 +116,7 @@ Although Shared Key authorization requires access to the storage account key, th - Shared Key–signed headers generation: - The backend handles Shared Key–signed headers generation and validation for Azure Blob Storage uploads, ensuring secure and controlled access for file uploads. There are different mutations for PDF and image files. The backend service encapsulates all business logic enforcing file upload restrictions and security requirements before enabling clients to upload files directly to Azure Blob Storage using carefully permissioned, Shared Key–signed request headers. - Post-Upload Malware Handling: - - The backend polls the blob for the Microsoft Defender for Cloud scan result tag: `No threats found` or `Malicious`. + - The backend polls the blob for the Microsoft Defender for Storage scan result tag: `No threats found` or `Malicious`. - `No threats found` → retain the blob. - `Malicious` → delete the current blob version and restore the previous non-malicious version. - The previous version is promoted to current using copyBlob. From 743c391fa7ef85301d9ab7f40ef99b2f05622b3e Mon Sep 17 00:00:00 2001 From: dev-kishor Date: Tue, 23 Dec 2025 23:40:42 +0530 Subject: [PATCH 18/26] Clarify documentation on Shared Key authorization, specifying server-signed request headers and enhancing descriptions for upload mechanisms. --- .../decisions/0022-existing-azure-upload.md | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/apps/docs/docs/decisions/0022-existing-azure-upload.md b/apps/docs/docs/decisions/0022-existing-azure-upload.md index 5a34b99b4..7da67cbfa 100644 --- a/apps/docs/docs/decisions/0022-existing-azure-upload.md +++ b/apps/docs/docs/decisions/0022-existing-azure-upload.md @@ -15,7 +15,7 @@ informed: Our application requires a secure and scalable mechanism for handling file uploads. From the start, the design approach was to leverage Azure Blob Storage as the primary file storage service due to its reliability, scalability, and seamless integration with the Azure ecosystem. -Users need to upload various file types (PDFs, images) along with metadata and tags for tracking. The system implements a direct upload flow where the client requests authorization from the backend, which issues Shared Key–signed request details allowing the file to be uploaded directly to Azure Blob Storage. +Users need to upload various file types (PDFs, images) along with metadata and tags for tracking. The system implements a direct upload flow where the client requests authorization from the backend, which issues server-signed request headers (using Shared Key authorization) allowing the file to be uploaded directly to Azure Blob Storage. ## Decision Drivers @@ -53,8 +53,8 @@ Chosen option: **Valet Key architectural pattern (direct client uploads) (curren - **Option 1: Short‑lived SAS token** - A short‑lived Shared Access Signature (SAS) is a token with a very limited validity period that grants specific permissions to access a blob or container. If such a token is intercepted, it can still be misused for the duration of its lifetime, so it must be kept extremely short and carefully managed. -- **Option 2: Shared Key (current approach)** - - The server signs the request using the storage account’s key (request signing). The client sends the exact signed headers as provided by the backend to perform the upload. This is not a token; it is a Shared Key–signed request. It allows the client to perform a single, narrowly scoped upload operation using server-signed request headers, without exposing the account key, and the backend can control exactly which permissions, metadata, and expiry apply for each individual upload request. +- **Option 2: Shared Key authorization with server-signed request headers (current approach)** + - The server signs the request using the storage account’s key (request signing). The client sends the exact signed headers as provided by the backend to perform the upload. This is not a token; it is a request authenticated via Shared Key authorization. It allows the client to perform a single, narrowly scoped upload operation using server-signed request headers, without exposing the account key, and the backend can control exactly which permissions, metadata, and expiry apply for each individual upload request. ### Pros/Cons @@ -68,24 +68,24 @@ Chosen option: **Valet Key architectural pattern (direct client uploads) (curren ## Authorization Mechanism (within Valet Key) Decision Outcome -Chosen option: **Option 2: Shared Key (current approach)**, because Shared Key–signed requests are generated by the backend per request with the correct blob path, metadata, and tags, and are cryptographically signed so that clients or third parties cannot tamper with or escalate their permissions. Although Shared Key authorization does not include an explicit expiry field like SAS, request validity is effectively time-bounded by Azure Blob Storage through the x-ms-date header and strict clock-skew enforcement, with the backend issuing these signed headers just-in-time per upload. +Chosen option: **Option 2: Shared Key authorization with server-signed request headers (current approach)**, because server-signed request headers are generated by the backend per request with the correct blob path, metadata, and tags, and are cryptographically signed so that clients or third parties cannot tamper with or escalate their permissions. Although Shared Key authorization does not include an explicit expiry field like SAS, request validity is effectively time-bounded by Azure Blob Storage through the x-ms-date header and strict clock-skew enforcement, with the backend issuing these signed headers just-in-time per upload. ### Why we did not choose SAS tokens - **Insufficient granularity for headers/metadata/tags**: SAS scopes permissions (read/write/list/set permissions) and time, but it does not bind the request to specific header values like `x-ms-meta-*` or blob index tags. A SAS with `w` (write) or `c` (create) permission allows any actor possessing the token to upload any content and arbitrary metadata/tags to the scoped resource during the token lifetime. - **Reusable artifact risk**: A leaked SAS can be used by unauthorized parties for the duration of its validity window. While short expirations help, they do not eliminate the risk of misuse within that window. -- **No content-level binding**: SAS does not bind the authorization to the server-approved payload (file digest, exact headers, or preapproved metadata/tags). With Shared Key–signed canonical requests, we include the precise headers the client must send; any deviation invalidates the signature. +- **No content-level binding**: SAS does not bind the authorization to the server-approved payload (file digest, exact headers, or preapproved metadata/tags). With server-signed request headers, we include the precise headers the client must send; any deviation invalidates the signature. - **Operational blast radius**: Using SAS for direct client uploads would require wide distribution of short-lived tokens and introduce complex token lifecycle management without improving our fine-grained control over what is uploaded. -We considered placing a proxy in front of uploads (client → Function App → Blob) to perform server-side validation with SAS or Entra ID, but this approach reintroduces backend bandwidth/latency bottlenecks and increases exposure to DDoS and malicious payload processing on the server. Our current design avoids those risks by keeping uploads direct while maintaining strong, per-request constraints through Shared Key–signed headers. +We considered placing a proxy in front of uploads (client → Function App → Blob) to perform server-side validation with SAS or Entra ID, but this approach reintroduces backend bandwidth/latency bottlenecks and increases exposure to DDoS and malicious payload processing on the server. Our current design avoids those risks by keeping uploads direct while maintaining strong, per-request constraints through server-signed request headers. ### Why we have not switched to Entra ID (Azure AD) - **Control model mismatch**: Entra ID enables RBAC and token-based auth for storage, and can mint user delegation SAS, but it does not provide the same mechanism to cryptographically bind uploads to exact header values, metadata, and tags for a single pre-authorized request. Our design relies on signing the canonical request (including headers) so the client cannot alter approved parameters. -- **Managed Identity does not expose account keys**: Shared Key requires the storage account key to sign the canonical request. Managed Identity (MI) authenticates the Function App to Azure but does not grant access to the raw account key value. Without the account key, we cannot perform Shared Key signing as implemented. +- **Managed Identity does not expose account keys**: Shared Key authorization requires the storage account key to sign the canonical request. Managed Identity (MI) authenticates the Function App to Azure but does not grant access to the raw account key value. Without the account key, we cannot perform Shared Key authorization as implemented. - **User delegation SAS still inherits SAS limitations**: Even with Entra ID, user delegation SAS remains a SAS model—time/permission scoped, but not bound to specific header values or content—and therefore lacks our required fine-grained constraints. -We prefer Entra ID for service-to-service auth wherever possible and continue to revisit it. For uploads requiring per-request binding to exact headers/metadata/tags, Shared Key signing remains the only option that satisfies our constraints. +We prefer Entra ID for service-to-service auth wherever possible and continue to revisit it. For uploads requiring per-request binding to exact headers/metadata/tags, Shared Key authorization remains the only option that satisfies our constraints. ### Mitigations for storage account key exposure @@ -109,12 +109,12 @@ Although Shared Key authorization requires access to the storage account key, th **Frontend Components:** - Handles client-side file validation (type, size, dimensions). - Requests authorization from the backend to upload a specific file. -- Uses the server-provided Shared Key–signed headers to upload the file directly to Azure Blob Storage. +- Uses the server-provided server-signed request headers to upload the file directly to Azure Blob Storage. - After upload, notifies the backend to trigger malware scanning and persist upload metadata. **Backend Services:** -- Shared Key–signed headers generation: - - The backend handles Shared Key–signed headers generation and validation for Azure Blob Storage uploads, ensuring secure and controlled access for file uploads. There are different mutations for PDF and image files. The backend service encapsulates all business logic enforcing file upload restrictions and security requirements before enabling clients to upload files directly to Azure Blob Storage using carefully permissioned, Shared Key–signed request headers. +- Server-signed request headers generation: + - The backend handles server-signed request headers generation and validation for Azure Blob Storage uploads, ensuring secure and controlled access for file uploads. There are different mutations for PDF and image files. The backend service encapsulates all business logic enforcing file upload restrictions and security requirements before enabling clients to upload files directly to Azure Blob Storage using carefully permissioned, server-signed request headers. - Post-Upload Malware Handling: - The backend polls the blob for the Microsoft Defender for Storage scan result tag: `No threats found` or `Malicious`. - `No threats found` → retain the blob. @@ -137,9 +137,9 @@ participant Blob User->>Frontend: Click upload and select file Frontend->>Frontend: Sanitize & validate (type, size, dimensions) -Frontend->>Backend: Request Shared Key–signed headers (name, type, size) +Frontend->>Backend: Request server-signed request headers (name, type, size) Backend->>Backend: Build blob path + tags + metadata, validate upload rules -Backend-->>Frontend: AuthResult (blob URL + Shared Key–signed headers + x-ms-date + tags + metadata) +Backend-->>Frontend: AuthResult (blob URL + server-signed request headers + x-ms-date + tags + metadata) Frontend->>Blob: PUT file bytes (headers + auth + tags + metadata) Blob-->>Frontend: 201 Created (x-ms-version-id) Frontend->>Backend: Persist blob reference/version ID @@ -158,4 +158,4 @@ Frontend-->>User: Show success / preview / error if malicious - Malware scanning occurs after upload using Azure Blob Storage’s capabilities. Files flagged as malicious are deleted or reverted to ensure data integrity. The system uses Microsoft Defender for Storage to automatically scan uploaded blobs for malware. Defender checks for known malware signatures, embedded scripts, and other suspicious file patterns. This introduces a small window where a malicious file may exist in storage before removal. - At present, backend permission enforcement for blob upload is minimal. The frontend restricts upload actions according to application state, but users could potentially bypass this if they possess valid credentials. -- Future improvements will focus on implementing domain-driven permission checks before upload authorizations/Shared Key–signed headers are issued. \ No newline at end of file +- Future improvements will focus on implementing domain-driven permission checks before upload authorizations/server-signed request headers are issued. \ No newline at end of file From 791be7f7918b0f40f9fc4c9632958ad98234cb68 Mon Sep 17 00:00:00 2001 From: dev-kishor Date: Wed, 24 Dec 2025 00:02:57 +0530 Subject: [PATCH 19/26] Clarify documentation on the Valet Key pattern in the consequences section of the Azure upload decision document. --- apps/docs/docs/decisions/0022-existing-azure-upload.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/docs/decisions/0022-existing-azure-upload.md b/apps/docs/docs/decisions/0022-existing-azure-upload.md index 7da67cbfa..3647ed306 100644 --- a/apps/docs/docs/decisions/0022-existing-azure-upload.md +++ b/apps/docs/docs/decisions/0022-existing-azure-upload.md @@ -100,7 +100,7 @@ Although Shared Key authorization requires access to the storage account key, th ## Consequences -- Good, because uploading directly from the client to Azure Blob Storage using Shared Key significantly reduces backend bandwidth usage and infrastructure costs. +- Good, because uploading directly from the client to Azure Blob Storage using the Valet Key pattern significantly reduces backend bandwidth usage and infrastructure costs. - Good, because versioning support allows easy rollback in case of corruption or malicious file detection. - Bad, because malware scanning occurs after upload, introducing a brief exposure window before a file is fully validated. From eb8d8b767e825bf4ddd43ffce033fbb3aaa4346a Mon Sep 17 00:00:00 2001 From: dev-kishor Date: Wed, 24 Dec 2025 20:35:39 +0530 Subject: [PATCH 20/26] Clarify documentation on the Valet Key pattern in the consequences section of the Azure upload decision document. --- apps/docs/docs/decisions/0022-existing-azure-upload.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/docs/decisions/0022-existing-azure-upload.md b/apps/docs/docs/decisions/0022-existing-azure-upload.md index 3647ed306..02cc36bc1 100644 --- a/apps/docs/docs/decisions/0022-existing-azure-upload.md +++ b/apps/docs/docs/decisions/0022-existing-azure-upload.md @@ -100,7 +100,7 @@ Although Shared Key authorization requires access to the storage account key, th ## Consequences -- Good, because uploading directly from the client to Azure Blob Storage using the Valet Key pattern significantly reduces backend bandwidth usage and infrastructure costs. +- Good, because uploading directly from the client to Azure Blob Storage using the Valet Key/direct upload pattern significantly reduces backend bandwidth usage and infrastructure costs. - Good, because versioning support allows easy rollback in case of corruption or malicious file detection. - Bad, because malware scanning occurs after upload, introducing a brief exposure window before a file is fully validated. From 3f4608dcc03d6a40fabbf35172755456cf82c4e5 Mon Sep 17 00:00:00 2001 From: dev-kishor Date: Tue, 6 Jan 2026 19:51:12 +0530 Subject: [PATCH 21/26] Refine documentation on server-signed request header generation for Azure Blob Storage uploads --- apps/docs/docs/decisions/0022-existing-azure-upload.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/docs/docs/decisions/0022-existing-azure-upload.md b/apps/docs/docs/decisions/0022-existing-azure-upload.md index 02cc36bc1..a620acb12 100644 --- a/apps/docs/docs/decisions/0022-existing-azure-upload.md +++ b/apps/docs/docs/decisions/0022-existing-azure-upload.md @@ -113,8 +113,8 @@ Although Shared Key authorization requires access to the storage account key, th - After upload, notifies the backend to trigger malware scanning and persist upload metadata. **Backend Services:** -- Server-signed request headers generation: - - The backend handles server-signed request headers generation and validation for Azure Blob Storage uploads, ensuring secure and controlled access for file uploads. There are different mutations for PDF and image files. The backend service encapsulates all business logic enforcing file upload restrictions and security requirements before enabling clients to upload files directly to Azure Blob Storage using carefully permissioned, server-signed request headers. +- Server-signed request header generation: + - The backend handles generation and validation of server-signed request headers for Azure Blob Storage uploads, ensuring secure and controlled access for file uploads. There are different mutations for PDF and image files. The backend service encapsulates all business logic enforcing file upload restrictions and security requirements before enabling clients to upload files directly to Azure Blob Storage using carefully permissioned, server-signed request headers. - Post-Upload Malware Handling: - The backend polls the blob for the Microsoft Defender for Storage scan result tag: `No threats found` or `Malicious`. - `No threats found` → retain the blob. From ba1a59a17902154e83dde2618ead977992968ff5 Mon Sep 17 00:00:00 2001 From: dev-kishor Date: Tue, 6 Jan 2026 20:29:21 +0530 Subject: [PATCH 22/26] Revised || Clarify reasons for not adopting Entra ID for Azure Blob Storage uploads, emphasizing header-binding requirements and limitations of Managed Identity and user delegation SAS. --- .../docs/decisions/0022-existing-azure-upload.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/apps/docs/docs/decisions/0022-existing-azure-upload.md b/apps/docs/docs/decisions/0022-existing-azure-upload.md index a620acb12..bcc333196 100644 --- a/apps/docs/docs/decisions/0022-existing-azure-upload.md +++ b/apps/docs/docs/decisions/0022-existing-azure-upload.md @@ -81,11 +81,16 @@ We considered placing a proxy in front of uploads (client → Function App → B ### Why we have not switched to Entra ID (Azure AD) -- **Control model mismatch**: Entra ID enables RBAC and token-based auth for storage, and can mint user delegation SAS, but it does not provide the same mechanism to cryptographically bind uploads to exact header values, metadata, and tags for a single pre-authorized request. Our design relies on signing the canonical request (including headers) so the client cannot alter approved parameters. -- **Managed Identity does not expose account keys**: Shared Key authorization requires the storage account key to sign the canonical request. Managed Identity (MI) authenticates the Function App to Azure but does not grant access to the raw account key value. Without the account key, we cannot perform Shared Key authorization as implemented. -- **User delegation SAS still inherits SAS limitations**: Even with Entra ID, user delegation SAS remains a SAS model—time/permission scoped, but not bound to specific header values or content—and therefore lacks our required fine-grained constraints. +We evaluated Entra ID–based authorization patterns (including Managed Identity and user delegation SAS) as part of the design. While Entra ID is our preferred approach for service-to-service authentication in many areas of the platform, it does not meet the specific requirements of this upload flow. + +- **Header-binding requirement**: Our design relies on cryptographically binding the upload authorization to the exact canonical request, including HTTP method, blob path, headers, metadata, index tags, and timing constraints. Entra ID–based RBAC and OAuth tokens authorize *who* can access storage, but they do not provide a mechanism to bind authorization to exact per-request header values in the way Shared Key request signing does. + +- **Managed Identity design choice**: Although Managed Identity can authenticate the backend to Azure Resource Manager and the storage data plane, it intentionally does not expose raw storage account keys to workloads. We deliberately chose not to retrieve or manage account keys via the control plane or alternative mechanisms, as doing so would undermine our narrowly scoped, request-level signing model. + +- **User delegation SAS limitations**: Entra ID can mint user delegation SAS tokens, but these still follow the SAS model—time- and permission-scoped access that is reusable within its validity window. User delegation SAS does not bind uploads to exact server-approved headers, metadata, or tags, which is a core requirement of this system. + +For uploads requiring strict, per-request binding to exact headers, metadata, and tags, Shared Key–based request signing remains the only mechanism that satisfies our constraints. We continue to prefer Entra ID where applicable and periodically re-evaluate whether future platform capabilities can meet these requirements without compromising security or control. -We prefer Entra ID for service-to-service auth wherever possible and continue to revisit it. For uploads requiring per-request binding to exact headers/metadata/tags, Shared Key authorization remains the only option that satisfies our constraints. ### Mitigations for storage account key exposure From 30dabedffb2e9f0b7a3d0c2c3eb2d017ab5e48b7 Mon Sep 17 00:00:00 2001 From: dev-kishor Date: Tue, 6 Jan 2026 21:03:21 +0530 Subject: [PATCH 23/26] Quick Summary || Enhance documentation on Azure Blob Storage uploads, clarifying decision rationale and terminology for server-signed request headers. --- .../decisions/0022-existing-azure-upload.md | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/apps/docs/docs/decisions/0022-existing-azure-upload.md b/apps/docs/docs/decisions/0022-existing-azure-upload.md index bcc333196..1bfb40433 100644 --- a/apps/docs/docs/decisions/0022-existing-azure-upload.md +++ b/apps/docs/docs/decisions/0022-existing-azure-upload.md @@ -11,6 +11,24 @@ informed: # Existing Azure Upload Implementation +# tl;dr (Summary) + +**Decision:** Use Azure Blob Storage uploads with Shared Key authorization and server-signed request headers (sometimes called "Valet Key"). + +**Key reasons:** +- Enables secure, time-limited, and permission-scoped uploads without exposing storage account keys to clients. +- Avoids the complexity and risks of SAS tokens and direct key distribution. +- Aligns with our DDD security model and allows for future domain-driven permission checks. + + +## Terminology + +- **Server-signed request headers**: The mechanism where the backend signs specific HTTP headers for a client upload request using the Azure Storage Shared Key. This is sometimes called the "Valet Key" pattern. +- **Shared Key authorization**: Azure’s primary authentication method for Blob Storage, using the account key to sign requests. In this ADR, it refers to the backend signing upload requests on behalf of the client. +- **Valet Key**: An informal term for the pattern where the server signs upload requests for clients, granting scoped, time-limited access. In this document, we use "server-signed request headers" as the primary term, with "Valet Key" and "Shared Key authorization" as synonyms. + +For clarity, this ADR will use **server-signed request headers** as the main term, with the others in parentheses as needed. + ## Context and Problem Statement Our application requires a secure and scalable mechanism for handling file uploads. From the start, the design approach was to leverage Azure Blob Storage as the primary file storage service due to its reliability, scalability, and seamless integration with the Azure ecosystem. @@ -163,4 +181,4 @@ Frontend-->>User: Show success / preview / error if malicious - Malware scanning occurs after upload using Azure Blob Storage’s capabilities. Files flagged as malicious are deleted or reverted to ensure data integrity. The system uses Microsoft Defender for Storage to automatically scan uploaded blobs for malware. Defender checks for known malware signatures, embedded scripts, and other suspicious file patterns. This introduces a small window where a malicious file may exist in storage before removal. - At present, backend permission enforcement for blob upload is minimal. The frontend restricts upload actions according to application state, but users could potentially bypass this if they possess valid credentials. -- Future improvements will focus on implementing domain-driven permission checks before upload authorizations/server-signed request headers are issued. \ No newline at end of file +Future improvements will focus on implementing domain-driven permission checks before server-signed request headers (upload authorizations) are issued and exploring pre-upload scanning alternatives to further reduce risk. \ No newline at end of file From 746248636abc3a1d87d563e0d98d0d66e2461bf7 Mon Sep 17 00:00:00 2001 From: dev-kishor Date: Tue, 6 Jan 2026 22:19:05 +0530 Subject: [PATCH 24/26] Refine documentation on frontend components, removing redundancy in the description of server-signed request headers for Azure Blob Storage uploads. --- apps/docs/docs/decisions/0022-existing-azure-upload.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/docs/decisions/0022-existing-azure-upload.md b/apps/docs/docs/decisions/0022-existing-azure-upload.md index 1bfb40433..0faa1eb3c 100644 --- a/apps/docs/docs/decisions/0022-existing-azure-upload.md +++ b/apps/docs/docs/decisions/0022-existing-azure-upload.md @@ -132,7 +132,7 @@ Although Shared Key authorization requires access to the storage account key, th **Frontend Components:** - Handles client-side file validation (type, size, dimensions). - Requests authorization from the backend to upload a specific file. -- Uses the server-provided server-signed request headers to upload the file directly to Azure Blob Storage. +- Uses the server-signed request headers to upload the file directly to Azure Blob Storage. - After upload, notifies the backend to trigger malware scanning and persist upload metadata. **Backend Services:** From 40885c35f928b405ed2e8f2374c4146d914b4de3 Mon Sep 17 00:00:00 2001 From: dev-kishor Date: Tue, 6 Jan 2026 22:22:38 +0530 Subject: [PATCH 25/26] Enhance security description in Azure upload documentation, detailing the Valet Key pattern's role in granting scoped upload permissions and controlling access. --- apps/docs/docs/decisions/0022-existing-azure-upload.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/docs/decisions/0022-existing-azure-upload.md b/apps/docs/docs/decisions/0022-existing-azure-upload.md index 0faa1eb3c..b550e8cb4 100644 --- a/apps/docs/docs/decisions/0022-existing-azure-upload.md +++ b/apps/docs/docs/decisions/0022-existing-azure-upload.md @@ -40,7 +40,7 @@ Users need to upload various file types (PDFs, images) along with metadata and t - **Scalability**: The system must efficiently handle large file uploads and multiple concurrent requests without overloading backend services. - **Performance**: Direct client-to-Azure Blob uploads reduce backend latency and improve user upload speed. - **Cost Optimization**: Offloading upload bandwidth from backend servers to Azure Blob Storage minimizes infrastructure and data transfer costs. -- **Security**: Uploads must be secure and authenticated. The Valet Key pattern provides controlled, time-bound access to the storage account. +- **Security**: Uploads must be secure and authenticated. The Valet Key pattern enables the backend to grant narrowly scoped upload permissions with strong server-side control over the blob path, headers, metadata, and index tags, preventing clients from altering or escalating the approved upload intent. - **Malware Scanning**: Uploaded files must undergo malware scanning. Any malicious files must be identified, quarantined, and deleted immediately to maintain data integrity and user safety. ## Considered Architectural Upload Options From 09175fc406505409bc962d7785b42723af02c0a9 Mon Sep 17 00:00:00 2001 From: dev-kishor Date: Tue, 6 Jan 2026 22:30:40 +0530 Subject: [PATCH 26/26] Clarify terminology in Azure upload documentation, defining the Valet Key architectural pattern and its relationship with server-signed request headers and Shared Key authorization. --- apps/docs/docs/decisions/0022-existing-azure-upload.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/docs/docs/decisions/0022-existing-azure-upload.md b/apps/docs/docs/decisions/0022-existing-azure-upload.md index b550e8cb4..ca096e4ea 100644 --- a/apps/docs/docs/decisions/0022-existing-azure-upload.md +++ b/apps/docs/docs/decisions/0022-existing-azure-upload.md @@ -23,11 +23,13 @@ informed: ## Terminology -- **Server-signed request headers**: The mechanism where the backend signs specific HTTP headers for a client upload request using the Azure Storage Shared Key. This is sometimes called the "Valet Key" pattern. -- **Shared Key authorization**: Azure’s primary authentication method for Blob Storage, using the account key to sign requests. In this ADR, it refers to the backend signing upload requests on behalf of the client. -- **Valet Key**: An informal term for the pattern where the server signs upload requests for clients, granting scoped, time-limited access. In this document, we use "server-signed request headers" as the primary term, with "Valet Key" and "Shared Key authorization" as synonyms. +- **Valet Key architectural pattern**: A Microsoft-defined architectural pattern in which a backend service acts as a gatekeeper and grants a client narrowly scoped, limited permission to access a storage resource directly. The backend validates intent and issues temporary or constrained access, while the client performs the data transfer directly to the storage service. The Valet Key pattern is independent of the specific authorization mechanism used. -For clarity, this ADR will use **server-signed request headers** as the main term, with the others in parentheses as needed. +- **Shared Key authorization**: An Azure Blob Storage authentication mechanism where requests are authenticated by signing a canonical representation of the HTTP request using the storage account access key. This mechanism allows the backend to cryptographically bind authorization to exact request details such as HTTP method, resource path, headers, metadata, and index tags. + +- **Server-signed request headers**: The specific implementation used in this system to apply the Valet Key pattern. The backend generates and signs the required HTTP request headers using Shared Key authorization and provides those headers to the client, which must send them unmodified when uploading directly to Azure Blob Storage. + +For clarity, this ADR uses **Valet Key architectural pattern** to describe the overall design approach and **server-signed request headers (Shared Key authorization)** to describe the specific authorization mechanism chosen for direct client uploads. ## Context and Problem Statement