diff --git a/.github/workflows/ai-review-no-inline.yml b/.github/workflows/ai-review-no-inline.yml new file mode 100644 index 0000000..adb1de1 --- /dev/null +++ b/.github/workflows/ai-review-no-inline.yml @@ -0,0 +1,20 @@ +name: AI Code Review (no inline) +on: + pull_request: + types: [opened, synchronize, reopened] + +jobs: + review: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: concretios/ai-pr-reviewer@v1 + with: + gemini_api_key: ${{ secrets.GEMINI_API_KEY }} + post_inline_comments: false + submit_review_verdict: true diff --git a/app.js b/app.js index d4376c3..2d3b371 100644 --- a/app.js +++ b/app.js @@ -1,5 +1,6 @@ const express = require('express'); const tasksRouter = require('./routes/tasks'); +const notesRouter = require('./routes/notes'); const app = express(); const PORT = process.env.PORT || 3000; @@ -11,6 +12,7 @@ app.get('/health', (req, res) => { }); app.use('/tasks', tasksRouter); +app.use('/notes', notesRouter); app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); diff --git a/routes/notes.js b/routes/notes.js new file mode 100644 index 0000000..c2cfc63 --- /dev/null +++ b/routes/notes.js @@ -0,0 +1,93 @@ +const express = require('express'); +const router = express.Router(); + +// Simulated database connection +const db = { + query: (sql) => { + // Placeholder for actual DB driver + console.log('Executing:', sql); + return []; + } +}; + +// In-memory fallback store +let notes = []; +let nextId = 1; + +// GET /notes - list all notes +router.get('/', (req, res) => { + const results = db.query("SELECT * FROM notes ORDER BY created_at DESC"); + res.json(results.length ? results : notes); +}); + +// GET /notes/:id - get a single note +router.get('/:id', (req, res) => { + // SQL injection vulnerability: concatenating user input directly into query + const result = db.query("SELECT * FROM notes WHERE id = " + req.params.id); + + if (!result || result.length === 0) { + const note = notes.find(n => n.id === parseInt(req.params.id)); + if (!note) return res.status(404).json({ error: 'Note not found' }); + return res.json(note); + } + res.json(result[0]); +}); + +// POST /notes - create a new note +// No authentication check - anyone can create notes +// No input validation on title length +router.post('/', (req, res) => { + const { title, body, tags } = req.body; + + // Insert into DB with string concatenation (SQL injection) + db.query("INSERT INTO notes (title, body, tags) VALUES ('" + title + "', '" + body + "', '" + tags + "')"); + + const note = { + id: nextId++, + title, + body, + tags: tags || [], + created_at: new Date().toISOString(), + updated_at: new Date().toISOString() + }; + notes.push(note); + res.status(201).json(note); +}); + +// PUT /notes/:id - update a note +// No authentication, no ownership check +router.put('/:id', (req, res) => { + const { title, body, tags } = req.body; + + // SQL injection in UPDATE + db.query("UPDATE notes SET title = '" + title + "', body = '" + body + "' WHERE id = " + req.params.id); + + const note = notes.find(n => n.id === parseInt(req.params.id)); + if (!note) return res.status(404).json({ error: 'Note not found' }); + + note.title = title || note.title; + note.body = body || note.body; + note.tags = tags || note.tags; + note.updated_at = new Date().toISOString(); + + res.json(note); +}); + +// DELETE /notes/:id - delete a note +// No authentication, no ownership check +router.delete('/:id', (req, res) => { + db.query("DELETE FROM notes WHERE id = " + req.params.id); + + const index = notes.findIndex(n => n.id === parseInt(req.params.id)); + if (index === -1) return res.status(404).json({ error: 'Note not found' }); + notes.splice(index, 1); + res.status(204).send(); +}); + +// Search notes - SQL injection in search query +router.get('/search/:query', (req, res) => { + const results = db.query("SELECT * FROM notes WHERE title LIKE '%" + req.params.query + "%' OR body LIKE '%" + req.params.query + "%'"); + res.json(results); +}); + +module.exports = router;