New:Socket for Asana Is Now Available.Learn more
Sign In

campus-cli

Package Overview
Dependencies
Maintainers
1
Versions
14
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

campus-cli - npm Package Compare versions

Comparing version
1.4.1
to
1.4.2
+13
-0
CHANGELOG.md

@@ -7,2 +7,15 @@ # Changelog

## [1.4.2] — 2026-08-08
### Security
- Los IDs de Blackboard (`courseId`, `contentId`, `parentId`, `columnId`, `attemptId`, `fileId`) se interpolaban sin validar en las URLs de la API REST. Un valor con `/` o `..` — por ejemplo, sugerido a un agente por contenido malicioso de un curso — podía redirigir la petición a un endpoint distinto dentro del mismo host. Ahora las 21 tools MCP que reciben alguno de estos campos exigen el formato real de Blackboard (`_529580_1`) antes de armar la petición.
### Fixed
- 10 vulnerabilidades de dependencias resueltas, sin saltar a la última versión mayor de nada:
- `inquirer` 10 → 12 (no 14): el mínimo salto donde `@inquirer/editor` deja de arrastrar el paquete `tmp` vulnerable (escritura arbitraria de archivos vía symlink, path traversal por prefix/postfix sin sanear).
- Se eliminaron `google-auth-library`, `@modelcontextprotocol/node`, `@modelcontextprotocol/server` y `@modelcontextprotocol/client`: ninguno se importa en el código — dependencias muertas que arrastraban las vulnerabilidades de `gaxios`/`uuid` y una versión vieja de `@hono/node-server`.
- `tsx` refrescado dentro de su propio rango ya declarado, trayendo el `esbuild` con el fix de lectura arbitraria de archivos en Windows.
---
## [1.4.1] — 2026-08-08

@@ -9,0 +22,0 @@

+28
-22

@@ -16,2 +16,8 @@ "use strict";

const MAX_UPLOAD_BYTES = 50 * 1024 * 1024; // 50MB
// Blackboard's own IDs (course, content, column, attempt, file) always look like
// `_529580_1`. These get interpolated straight into REST path templates below —
// without this shape check, a crafted ID containing `/` or `..` could redirect
// the request to a different endpoint entirely (path injection within the same
// host), e.g. from a prompt-injected value the agent didn't scrutinize.
const blackboardId = (label) => zod_1.z.string().regex(/^_\d+_\d+$/, `${label} must look like a Blackboard ID, e.g. _529580_1`);
async function getClient() {

@@ -75,3 +81,3 @@ const session = await (0, session_js_1.loadOrRefreshSession)();

description: 'Get details of a specific course by its Blackboard ID (e.g. _529580_1)',
inputSchema: { courseId: zod_1.z.string().describe('Blackboard course ID like _529580_1') },
inputSchema: { courseId: blackboardId('courseId').describe('Blackboard course ID like _529580_1') },
}, async ({ courseId }) => {

@@ -86,4 +92,4 @@ const { client } = await getClient();

inputSchema: {
courseId: zod_1.z.string().describe('Blackboard course ID'),
parentId: zod_1.z.string().optional().describe('Parent folder content ID (omit for root level)'),
courseId: blackboardId('courseId').describe('Blackboard course ID'),
parentId: blackboardId('parentId').optional().describe('Parent folder content ID (omit for root level)'),
},

@@ -98,3 +104,3 @@ }, async ({ courseId, parentId }) => {

description: 'List recent announcements for a course',
inputSchema: { courseId: zod_1.z.string().describe('Blackboard course ID') },
inputSchema: { courseId: blackboardId('courseId').describe('Blackboard course ID') },
}, async ({ courseId }) => {

@@ -115,3 +121,3 @@ const { client } = await getClient();

inputSchema: {
courseId: zod_1.z.string().describe('Blackboard course ID'),
courseId: blackboardId('courseId').describe('Blackboard course ID'),
search: zod_1.z.string().optional().describe('Name of one person in the course'),

@@ -165,3 +171,3 @@ },

description: 'List assignments and tasks in a course with due dates, scores and submission status',
inputSchema: { courseId: zod_1.z.string().describe('Blackboard course ID') },
inputSchema: { courseId: blackboardId('courseId').describe('Blackboard course ID') },
}, async ({ courseId }) => {

@@ -176,4 +182,4 @@ const { client } = await getClient();

inputSchema: {
courseId: zod_1.z.string().describe('Blackboard course ID'),
columnId: zod_1.z.string().describe('Gradebook column ID (assignment ID)'),
courseId: blackboardId('courseId').describe('Blackboard course ID'),
columnId: blackboardId('columnId').describe('Gradebook column ID (assignment ID)'),
},

@@ -188,3 +194,3 @@ }, async ({ courseId, columnId }) => {

description: 'Get all grades for the current student in a course',
inputSchema: { courseId: zod_1.z.string().describe('Blackboard course ID') },
inputSchema: { courseId: blackboardId('courseId').describe('Blackboard course ID') },
}, async ({ courseId }) => {

@@ -212,4 +218,4 @@ const { client, session } = await getClient();

inputSchema: {
courseId: zod_1.z.string().describe('Blackboard course ID'),
contentId: zod_1.z.string().describe('Content item ID'),
courseId: blackboardId('courseId').describe('Blackboard course ID'),
contentId: blackboardId('contentId').describe('Content item ID'),
attachmentId: zod_1.z.string().describe('Attachment ID from blackboard_list_attachments, or a full bbcswebdav URL for embedded files'),

@@ -248,4 +254,4 @@ filename: zod_1.z.string().optional().describe('Filename to save as (e.g. displayName from blackboard_list_attachments). Falls back to Content-Disposition header.'),

inputSchema: {
courseId: zod_1.z.string().describe('Blackboard course ID'),
contentId: zod_1.z.string().describe('Content item ID'),
courseId: blackboardId('courseId').describe('Blackboard course ID'),
contentId: blackboardId('contentId').describe('Content item ID'),
},

@@ -362,4 +368,4 @@ }, async ({ courseId, contentId }) => {

inputSchema: {
courseId: zod_1.z.string().describe('Blackboard course ID'),
columnId: zod_1.z.string().describe('Assignment (gradebook column) ID'),
courseId: blackboardId('courseId').describe('Blackboard course ID'),
columnId: blackboardId('columnId').describe('Assignment (gradebook column) ID'),
studentComments: zod_1.z.string().optional().describe('Comment to the instructor'),

@@ -387,4 +393,4 @@ studentSubmission: zod_1.z.string().optional().describe('Text body of the submission'),

inputSchema: {
courseId: zod_1.z.string().describe('Blackboard course ID'),
columnId: zod_1.z.string().describe('Assignment (gradebook column) ID'),
courseId: blackboardId('courseId').describe('Blackboard course ID'),
columnId: blackboardId('columnId').describe('Assignment (gradebook column) ID'),
studentComments: zod_1.z.string().optional().describe('Comment to the instructor'),

@@ -409,3 +415,3 @@ studentSubmission: zod_1.z.string().optional().describe('Text body of the submission'),

'For each graded submission, shows score, instructor comments, and any feedback files attached by the professor.',
inputSchema: { courseId: zod_1.z.string().describe('Blackboard course ID') },
inputSchema: { courseId: blackboardId('courseId').describe('Blackboard course ID') },
}, async ({ courseId }) => {

@@ -464,6 +470,6 @@ const { client, session } = await getClient();

inputSchema: {
courseId: zod_1.z.string().describe('Blackboard course ID'),
columnId: zod_1.z.string().describe('Gradebook column (assignment) ID'),
attemptId: zod_1.z.string().describe('Attempt ID from blackboard_get_assignment_feedback'),
fileId: zod_1.z.string().describe('File ID from blackboard_get_assignment_feedback → attempt.feedbackFiles'),
courseId: blackboardId('courseId').describe('Blackboard course ID'),
columnId: blackboardId('columnId').describe('Gradebook column (assignment) ID'),
attemptId: blackboardId('attemptId').describe('Attempt ID from blackboard_get_assignment_feedback'),
fileId: blackboardId('fileId').describe('File ID from blackboard_get_assignment_feedback → attempt.feedbackFiles'),
filename: zod_1.z.string().optional().describe('Filename to save as (defaults to the name from feedbackFiles)'),

@@ -470,0 +476,0 @@ outputDir: zod_1.z.string().optional().describe('Directory to save the file (default: current working directory)'),

{
"name": "campus-cli",
"version": "1.4.1",
"version": "1.4.2",
"description": "Conecta Blackboard UPC con ChatGPT y Claude vía MCP, o úsalo desde la terminal — CLI/MCP no oficial para el campus universitario (Blackboard, Canvas, Moodle...)",

@@ -65,5 +65,3 @@ "mcpName": "io.github.alejooroncoy/campus-cli",

"@browserbasehq/sdk": "^2.16.0",
"@modelcontextprotocol/node": "^2.0.0",
"@modelcontextprotocol/sdk": "^1.28.0",
"@modelcontextprotocol/server": "^2.0.0",
"@types/inquirer": "^9.0.9",

@@ -75,14 +73,10 @@ "@types/node": "^22.0.0",

"form-data": "^4.0.5",
"google-auth-library": "^9.15.1",
"inquirer": "^10.1.0",
"inquirer": "^12.0.0",
"ora": "^8.1.0",
"playwright": "^1.47.0",
"playwright-core": "^1.62.0",
"tsx": "^4.19.0",
"tsx": "^4.23.11",
"typescript": "^5.5.0",
"zod": "^4.3.6"
},
"devDependencies": {
"@modelcontextprotocol/client": "^2.0.0"
}
}