Skip to content

feat: adds read and flag functionality#254

Merged
oscargay merged 2 commits intomainfrom
feat/HRP-403-read-flagged
Apr 7, 2026
Merged

feat: adds read and flag functionality#254
oscargay merged 2 commits intomainfrom
feat/HRP-403-read-flagged

Conversation

@oscargay
Copy link
Copy Markdown
Collaborator

@oscargay oscargay commented Mar 30, 2026

Describe your changes

  • Adds the ability for users to mark documents as read or flagged
  • Sets up a base / default for these documents if no row exists, for both migrated cases and new cases.

Issue ticket number and link

HRP-403
https://pins-ds.atlassian.net.mcas.ms/browse/HRP-403?McasTsid=20596&McasCtx=4

Implementation

Screenshot 2026-03-30 at 13 22 02 Screenshot 2026-03-30 at 16 39 52 Screenshot 2026-03-30 at 16 40 15 Screenshot 2026-03-30 at 16 40 22


const returnUrl = createReturnUrl(body, caseId, documentId);

return res.redirect(returnUrl);

Check warning

Code scanning / CodeQL

Server-side URL redirect Medium

Untrusted URL redirection depends on a
user-provided value
.

Copilot Autofix

AI 16 days ago

General approach: continue using createReturnUrl, but harden its validation so that user input cannot meaningfully influence a redirect to an attacker-controlled location. The safest pattern is to treat body.returnUrl as an optional hint that must pass strict validation as a local path; otherwise fall back to a known-good URL. We should also normalize the path, disallow whitespace and control characters, and prevent exploitation via encoded or malformed inputs.

Best concrete fix in this file: enhance createReturnUrl to (1) default returnUrl to an empty string when undefined, (2) treat it as safe only if it is a simple, well-formed, relative path beginning with a single /, does not start with //, contains no whitespace, control characters, or backslashes, and after normalization still starts with /. If it fails any check, we use the fallback URL. We then proceed to append the #row-${documentId} fragment as before. This keeps existing behavior for normal in-app paths (like /cases/123/documents) while tightening security and addressing the CodeQL concern.

Concretely:

  • Modify createReturnUrl in apps/manage/src/app/views/documents/status/controller.ts.
  • Replace lines 136–144 so that:
    • returnUrl is normalized to a string.
    • A small helper check ensures the URL is a safe, normalized, local path.
    • The rest of the logic (fallback to /cases/${caseId} and appending #row-${documentId}) stays the same.

No new imports are strictly necessary; we can use built-in string methods.

Suggested changeset 1
apps/manage/src/app/views/documents/status/controller.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/apps/manage/src/app/views/documents/status/controller.ts b/apps/manage/src/app/views/documents/status/controller.ts
--- a/apps/manage/src/app/views/documents/status/controller.ts
+++ b/apps/manage/src/app/views/documents/status/controller.ts
@@ -133,13 +133,24 @@
  * we were in the list of documents.
  */
 function createReturnUrl(body: ToggleDocumentBody, caseId: string, documentId: string) {
-	const returnUrl = body.returnUrl;
+	const rawReturnUrl = body.returnUrl || '';
 	const fallbackUrl = `/cases/${caseId}`;
 
-	// To prevent open redirect, check the presented URL is safe
-	const isSafeUrl = returnUrl && returnUrl.startsWith('/') && !returnUrl.startsWith('//');
+	// To prevent open redirect, check the presented URL is a safe, local path
+	const hasNoWhitespace = rawReturnUrl !== '' && !/\s/.test(rawReturnUrl);
+	const startsWithSingleSlash = rawReturnUrl.startsWith('/') && !rawReturnUrl.startsWith('//');
 
-	const cleanRedirectUrl = isSafeUrl ? returnUrl.split('#')[0] : fallbackUrl;
+	// Basic normalization: strip any fragment and query string before validation
+	const [pathOnly] = rawReturnUrl.split('#', 1);
+	const [normalizedPath] = pathOnly.split('?', 1);
 
+	const isSafeUrl =
+		hasNoWhitespace &&
+		startsWithSingleSlash &&
+		!normalizedPath.startsWith('\\') &&
+		normalizedPath.startsWith('/');
+
+	const cleanRedirectUrl = isSafeUrl && normalizedPath ? normalizedPath : fallbackUrl;
+
 	return `${cleanRedirectUrl}#row-${documentId}`;
 }
EOF
@@ -133,13 +133,24 @@
* we were in the list of documents.
*/
function createReturnUrl(body: ToggleDocumentBody, caseId: string, documentId: string) {
const returnUrl = body.returnUrl;
const rawReturnUrl = body.returnUrl || '';
const fallbackUrl = `/cases/${caseId}`;

// To prevent open redirect, check the presented URL is safe
const isSafeUrl = returnUrl && returnUrl.startsWith('/') && !returnUrl.startsWith('//');
// To prevent open redirect, check the presented URL is a safe, local path
const hasNoWhitespace = rawReturnUrl !== '' && !/\s/.test(rawReturnUrl);
const startsWithSingleSlash = rawReturnUrl.startsWith('/') && !rawReturnUrl.startsWith('//');

const cleanRedirectUrl = isSafeUrl ? returnUrl.split('#')[0] : fallbackUrl;
// Basic normalization: strip any fragment and query string before validation
const [pathOnly] = rawReturnUrl.split('#', 1);
const [normalizedPath] = pathOnly.split('?', 1);

const isSafeUrl =
hasNoWhitespace &&
startsWithSingleSlash &&
!normalizedPath.startsWith('\\') &&
normalizedPath.startsWith('/');

const cleanRedirectUrl = isSafeUrl && normalizedPath ? normalizedPath : fallbackUrl;

return `${cleanRedirectUrl}#row-${documentId}`;
}
Copilot is powered by AI and may make mistakes. Always verify output.
@oscargay oscargay self-assigned this Mar 31, 2026
@oscargay oscargay force-pushed the feat/HRP-403-read-flagged branch from 5465ebc to 58aabfd Compare March 31, 2026 08:50
@oscargay oscargay marked this pull request as ready for review March 31, 2026 08:50
@oscargay oscargay requested a review from paymonsattar March 31, 2026 08:55
Copy link
Copy Markdown
Collaborator

@paymonsattar paymonsattar left a comment

Choose a reason for hiding this comment

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

Looks really good, just a small comment

Comment thread apps/manage/src/app/views/cases/case-folders/case-folder/view-model.ts Outdated
Copy link
Copy Markdown
Collaborator

@paymonsattar paymonsattar left a comment

Choose a reason for hiding this comment

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

Nice! Another one bites the dust

@oscargay oscargay merged commit 0411f91 into main Apr 7, 2026
5 checks passed
@oscargay oscargay deleted the feat/HRP-403-read-flagged branch April 7, 2026 09:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants