Skip to content

Feature/workspaces api services - #31

Open
philvarner-snyk wants to merge 9 commits into
mainfrom
feature/workspaces-api-services
Open

philvarner-snyk wants to merge 9 commits into
mainfrom
feature/workspaces-api-services

Conversation

@philvarner-snyk

Copy link
Copy Markdown
Collaborator

No description provided.

Introduce multi-workspace support with role-based access control,
audit logging, webhook delivery, rule engine with scheduled execution,
CSV todo import, and notes CRUD. Adds new Mongoose models (Workspace,
WorkspaceMember, AuditEvent, Webhook, WebhookDelivery, Rule, Note),
API middleware, and supporting documentation.

Made-with: Cursor
@philvarner-snyk

philvarner-snyk commented Feb 27, 2026

Copy link
Copy Markdown
Collaborator Author

Snyk checks have failed. 19 issues have been found so far.

Status Scan Engine Critical High Medium Low Total (19)
Open Source Security 0 8 1 0 9 issues
Licenses 0 0 0 0 0 issues
Code Security 0 10 0 0 10 issues
Secrets 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

Comment thread routes/audit.js
if (req.query.from) q.createdAt.$gte = new Date(req.query.from);
if (req.query.to) q.createdAt.$lte = new Date(req.query.to);
}
AuditEvent.countDocuments(q).exec(function (err, total) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

  NoSQL Injection

Unsanitized input from an HTTP parameter flows into countDocuments, where it is used in an NoSQL query. This may result in an NoSQL Injection vulnerability.

Line 70 | CWE-943 | Priority score 821 | Learn more about this vulnerability
Data flow: 11 steps

Step 1 - 9

if (typeof req.query.action === 'string' && req.query.action.trim()) q.action = req.query.action.trim();

Step 10 - 11

AuditEvent.countDocuments(q).exec(function (err, total) {

Comment thread routes/workspace-todos.js
}
var workspaceObjId = new mongoose.Types.ObjectId(workspaceId);
var qSafe = Object.assign({}, q, { workspace: workspaceObjId });
Todo.countDocuments(qSafe).exec(function (err, total) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

  NoSQL Injection

Unsanitized input from an HTTP parameter flows into countDocuments, where it is used in an NoSQL query. This may result in an NoSQL Injection vulnerability.

Line 105 | CWE-943 | Priority score 821 | Learn more about this vulnerability
Data flow: 12 steps

Step 1 - 7

if (typeof req.query.priority === 'string' && VALID_PRIORITIES.indexOf(req.query.priority) !== -1) q.priority = req.query.priority;

Step 8 - 10 routes/workspace-todos.js#L104

Step 11 - 12

Todo.countDocuments(qSafe).exec(function (err, total) {

Comment thread routes/audit.js
}
AuditEvent.countDocuments(q).exec(function (err, total) {
if (err) return next(err);
AuditEvent.find(q)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

  NoSQL Injection

Unsanitized input from an HTTP parameter flows into find, where it is used in an NoSQL query. This may result in an NoSQL Injection vulnerability.

Line 72 | CWE-943 | Priority score 821 | Learn more about this vulnerability
Data flow: 11 steps

Step 1 - 9

if (typeof req.query.action === 'string' && req.query.action.trim()) q.action = req.query.action.trim();

Step 10 - 11

AuditEvent.find(q)

Comment thread routes/workspace-todos.js
var qSafe = Object.assign({}, q, { workspace: workspaceObjId });
Todo.countDocuments(qSafe).exec(function (err, total) {
if (err) return next(err);
Todo.find(qSafe)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

  NoSQL Injection

Unsanitized input from an HTTP parameter flows into find, where it is used in an NoSQL query. This may result in an NoSQL Injection vulnerability.

Line 107 | CWE-943 | Priority score 821 | Learn more about this vulnerability
Data flow: 12 steps

Step 1 - 7

if (typeof req.query.priority === 'string' && VALID_PRIORITIES.indexOf(req.query.priority) !== -1) q.priority = req.query.priority;

Step 8 - 10 routes/workspace-todos.js#L104

Step 11 - 12

Todo.find(qSafe)

Comment thread routes/workspaces.js
details: { user: newUser, role: role },
ip: req.ip,
});
webhookDelivery.notifyWebhooks(workspaceId, 'member.added', { resourceId: String(member._id), data: { user: newUser, role } });

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

  NoSQL Injection

Unsanitized input from an HTTP parameter flows into find, where it is used in an NoSQL query. This may result in an NoSQL Injection vulnerability.

Line 219 | CWE-943 | Priority score 821 | Learn more about this vulnerability
Data flow: 10 steps

Step 1 - 5

var workspaceId = req.params.id;

Step 6 routes/workspaces.js#L219

Step 7 services/webhook-delivery.js#L182

Step 8 - 10

Webhook.find({ workspace: workspaceId, active: true, events: event }).lean().exec(function (err, webhooks) {

Comment thread routes/rules.js
if (actErr) return res.status(400).json({ error: actErr });
var workspaceId = req.workspaceId;
var id = req.params.id;
Rule.findOne({ _id: id, workspace: workspaceId }).exec(function (err, rule) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

  NoSQL Injection

Unsanitized input from an HTTP parameter flows into findOne, where it is used in an NoSQL query. This may result in an NoSQL Injection vulnerability.

Line 179 | CWE-943 | Priority score 821 | Learn more about this vulnerability
Data flow: 9 steps

Step 1 - 5

var id = req.params.id;

Step 6 - 9

Rule.findOne({ _id: id, workspace: workspaceId }).exec(function (err, rule) {

Comment thread routes/workspaces.js
if (!workspaceAuth.ROLES_WITH_ADMIN.includes(membership.role)) {
return res.status(403).json({ error: 'Only owner or admin can add members' });
}
return WorkspaceMember.findOne({ workspace: workspaceId, user: newUser }).exec();

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

  NoSQL Injection

Unsanitized input from an HTTP parameter flows into findOne, where it is used in an NoSQL query. This may result in an NoSQL Injection vulnerability.

Line 204 | CWE-943 | Priority score 821 | Learn more about this vulnerability
Data flow: 8 steps

Step 1 - 5

var workspaceId = req.params.id;

Step 6 - 8

return WorkspaceMember.findOne({ workspace: workspaceId, user: newUser }).exec();

Comment thread routes/workspaces.js
if (targetUserId === actorId && membership.role === 'owner') {
return res.status(400).json({ error: 'Owner cannot remove themselves; transfer ownership first' });
}
return WorkspaceMember.findOneAndDelete({ workspace: workspaceId, user: targetUserId }).exec();

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

  NoSQL Injection

Unsanitized input from an HTTP parameter flows into findOneAndDelete, where it is used in an NoSQL query. This may result in an NoSQL Injection vulnerability.

Line 242 | CWE-943 | Priority score 821 | Learn more about this vulnerability
Data flow: 8 steps

Step 1 - 5

var workspaceId = req.params.id;

Step 6 - 8

return WorkspaceMember.findOneAndDelete({ workspace: workspaceId, user: targetUserId }).exec();

Comment thread routes/rules.js
function validateActions(actions) {
if (!Array.isArray(actions)) return 'actions must be an array';
if (actions.length > MAX_ACTIONS) return 'At most ' + MAX_ACTIONS + ' actions per rule';
for (var i = 0; i < actions.length; i++) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

  Unchecked Input for Loop Condition

The 0 object may not be an array, and its length property may be directly set to huge values by an attacker, which would make this loop iterate too many times. This may lead to a Denial-of-service vulnerability.

Line 44 | CWE-400 | CWE-606 | Priority score 803 | Learn more about this vulnerability
Data flow: 3 steps

Step 1 - 3

for (var i = 0; i < actions.length; i++) {

philvarner-snyk and others added 5 commits February 27, 2026 08:32
Comment out the pull_request trigger so the pipeline only runs
on pushes to main/master and manual dispatch.

Made-with: Cursor
Only the npm:adm-zip:20180415 (Zip Slip) vulnerability is ignored,
to test visibility in the Snyk Dashboard UI.

Made-with: Cursor
- Serve profile images from public/images/profiles with extension allowlist,
  numeric user ID validation, and path traversal checks
- Add express-rate-limit dependency
- Enable Snyk Code global exclude for tests/** in .snyk

Pre-commit: Snyk Code scan and Snyk Open Source (SCA) scan were run on the repo.
Made-with: Cursor
Adds the scaffolding to demo Snyk's per-release-version monitoring story:
- scripts/snyk-monitor-release.sh: pushes a monitor snapshot keyed on
  package.json version via --target-reference, so each release becomes
  its own indefinitely-monitored target in the Snyk dashboard.
- scripts/snyk-compliance-report.sh: generates auditor-friendly per-version
  vuln reports (JSON + markdown) for compliance evidence.
- scripts/setup-demo-release-branches.sh: one-shot helper that creates
  release/v1.0..v2.0 branches from historical commits with pinned versions.
- .github/workflows/snyk-release-monitor.yml: release-tag-driven monitor
  job plus weekly heartbeat for legacy versions to defeat stale-project
  auto-deactivation.
- docs/proconex-demo.md: runbook for the customer meeting.

Co-authored-by: Cursor <cursoragent@cursor.com>
Trigger fresh security scan on PR #31

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread routes/xss-vulnerable.js
res.setHeader('X-Content-Type-Options', 'nosniff');
const userInput = req.query.input;

res.send(`

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

  Cross-site Scripting (XSS)

Unsanitized input from an HTTP parameter flows into send, where it is used to render an HTML page returned to the user. This may result in a Cross-Site Scripting attack (XSS).

Line 11 | CWE-79 | Priority score 553 | Learn more about this vulnerability
Data flow: 8 steps

Step 1 - 5

const userInput = req.query.input;

Step 6 routes/xss-vulnerable.js#L32

Step 7 - 8


Refresh the page to see if a fix suggestion is available 🔄

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

⚡ Snyk Agent Fix suggestion 1 of 1

The Cross-site Scripting (XSS) vulnerability was fixed by sanitizing the user input with sanitizeHtml before embedding it into the HTML response.

Code changes
--- routes/xss-vulnerable.js
+++ routes/xss-vulnerable.js
@@ -6,7 +6,7 @@
 // SECURE: Safe endpoint with proper HTML escaping
 router.get('/secure', (req, res) => {
     // Get user input from query parameter
-    const userInput = req.query.input;
+    const userInput = sanitizeHtml(req.query.input);
     
     res.send(`
         <!DOCTYPE html>
Content generated by AI, expires on 2026-09-02 17:56:05 UTC. Refresh the page after running Snyk commands.
Commands
  • ✅ To apply this fix and create a commit - reply with @snyk /apply 1

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.

1 participant