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:
@@ -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
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user