Fix Archer Jira ticket description auto-population and security audit fixes

Auto-populate description field when creating Jira tickets from the Archer
page with ticket metadata (EXC number, CVE, vendor, status, Archer URL).
Previously the description was always empty, requiring manual entry.

Includes security audit fixes for SQL injection prevention and input
validation in compliance, VCL multi-vertical, and CCP metrics routes.

Updates security audit tracker documentation.
This commit is contained in:
Jordan Ramos
2026-06-05 09:53:53 -06:00
parent e8aa7038ad
commit af5fa11421
6 changed files with 133 additions and 114 deletions

View File

@@ -352,7 +352,7 @@ function createComplianceRouter(upload) {
res.json({
drift, drift_error, schema: xlsxSchema,
diff: { new_count: diff.newCount, recurring_count: diff.recurringCount, resolved_count: diff.resolvedCount },
tempFile: tempFilePath, filename: req.file.originalname,
tempFile: tempFilename, filename: req.file.originalname,
report_date: parsed.report_date, total_items: parsed.total,
});
} catch (err) {
@@ -405,11 +405,13 @@ function createComplianceRouter(upload) {
router.post('/commit', requireGroup('Admin', 'Standard_User'), async (req, res) => {
const { tempFile, filename, report_date } = req.body;
if (!tempFile || typeof tempFile !== 'string') return res.status(400).json({ error: 'tempFile is required' });
if (!isSafeTempPath(tempFile)) return res.status(400).json({ error: 'Invalid tempFile path' });
if (!fs.existsSync(tempFile)) return res.status(400).json({ error: 'Preview session expired — please upload again' });
// Reconstruct full path from basename only — never trust a client-supplied absolute path
const resolvedTempFile = path.join(TEMP_DIR, path.basename(tempFile));
if (!isSafeTempPath(resolvedTempFile)) return res.status(400).json({ error: 'Invalid tempFile path' });
if (!fs.existsSync(resolvedTempFile)) return res.status(400).json({ error: 'Preview session expired — please upload again' });
let parsed;
try { parsed = JSON.parse(fs.readFileSync(tempFile, 'utf8')); }
try { parsed = JSON.parse(fs.readFileSync(resolvedTempFile, 'utf8')); }
catch { return res.status(400).json({ error: 'Could not read preview data — please upload again' }); }
try {
@@ -419,7 +421,7 @@ function createComplianceRouter(upload) {
filename: filename || parsed.filename,
userId: req.user?.id || null,
});
fs.unlink(tempFile, () => {});
fs.unlink(resolvedTempFile, () => {});
const { rows } = await pool.query(
`SELECT id, filename, report_date, uploaded_at, new_count, resolved_count, recurring_count

View File

@@ -280,7 +280,7 @@ function createVCLMultiVerticalRouter(upload) {
total_items: xlsxData.total || xlsxData.items.length,
summary_entries: (xlsxData.summary && xlsxData.summary.entries) ? xlsxData.summary.entries.length : 0,
diff: { new_count: diff.newCount, recurring_count: diff.recurringCount, resolved_count: diff.resolvedCount },
tempFile: tempFilePath,
tempFile: tempFilename,
});
} catch (parseErr) {
unrecognized.push({ filename: file.originalname, error: parseErr.message });
@@ -334,12 +334,15 @@ function createVCLMultiVerticalRouter(upload) {
// Validate all temp files exist before starting transaction
for (const file of files) {
if (!file.tempFile || !isSafeTempPath(file.tempFile)) {
const resolvedPath = path.join(TEMP_DIR, path.basename(file.tempFile || ''));
if (!file.tempFile || !isSafeTempPath(resolvedPath)) {
return res.status(400).json({ error: `Invalid tempFile path for ${file.vertical || 'unknown'}` });
}
if (!fs.existsSync(file.tempFile)) {
if (!fs.existsSync(resolvedPath)) {
return res.status(400).json({ error: `Preview session expired for ${file.vertical || 'unknown'} — please upload again` });
}
// Store resolved path for use in the transaction
file._resolvedTempFile = resolvedPath;
}
const client = await pool.connect();
@@ -349,7 +352,7 @@ function createVCLMultiVerticalRouter(upload) {
const committed = [];
for (const file of files) {
let parsed;
try { parsed = JSON.parse(fs.readFileSync(file.tempFile, 'utf8')); }
try { parsed = JSON.parse(fs.readFileSync(file._resolvedTempFile, 'utf8')); }
catch { throw new Error(`Could not read preview data for ${file.vertical}`); }
const result = await persistMultiVerticalUpload({
@@ -374,7 +377,7 @@ function createVCLMultiVerticalRouter(upload) {
// Clean up temp files
for (const file of files) {
fs.unlink(file.tempFile, () => {});
fs.unlink(file._resolvedTempFile, () => {});
}
// Audit log
@@ -401,7 +404,7 @@ function createVCLMultiVerticalRouter(upload) {
await client.query('ROLLBACK');
// Clean up temp files on failure too
for (const file of files) {
if (file.tempFile) fs.unlink(file.tempFile, () => {});
if (file._resolvedTempFile) fs.unlink(file._resolvedTempFile, () => {});
}
console.error('[VCL Multi] Commit error:', err.message);
res.status(500).json({ error: 'Failed to commit batch: ' + err.message });

View File

@@ -138,7 +138,7 @@ app.use(express.json({
type: 'application/json'
}));
app.use(cookieParser());
app.use('/uploads', express.static('uploads', {
app.use('/uploads', requireAuth(), express.static('uploads', {
dotfiles: 'deny',
index: false
}));