Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/codeql/codeql-config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
name: ConvNetJS Security Scanning
rules:
- query: security-and-quality
- query: security-extended
- query: security-audit
- query: experimental
- query: performance

exclude:
- path: test/**
- path: demo/**
- path: build/**
- path: compile/**
158 changes: 158 additions & 0 deletions .github/scripts/security-review.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
const fs = require('fs');
const path = require('path');
const https = require('https');

const sarifPath = process.argv[2] || 'results.sarif';
const reportPath = process.argv[3] || 'security-report.md';
const openAiKey = process.env.OPENAI_API_KEY;
const openAiModel = process.env.OPENAI_MODEL || 'gpt-4o-mini';

function loadSarif(filePath) {
if (!fs.existsSync(filePath)) {
throw new Error(`SARIF file not found: ${filePath}`);
}
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
}

function summarizeFindings(results) {
const severityOrder = ['error', 'warning', 'note', 'none'];
const severityCounts = results.reduce((acc, item) => {
const severity = (item.level || item.kind || 'warning').toLowerCase();
acc[severity] = (acc[severity] || 0) + 1;
return acc;
}, {});

const byRule = results.reduce((acc, item) => {
const ruleId = item.ruleId || 'unknown';
acc[ruleId] = acc[ruleId] || { count: 0, message: [] };
acc[ruleId].count += 1;
if (item.message && item.message.text) acc[ruleId].message.push(item.message.text);
return acc;
}, {});

return { severityCounts, byRule };
}

function formatReport(sarif, summary, topFindings) {
const totalFindings = topFindings.length;
const severityLines = Object.entries(summary.severityCounts)
.sort(([a], [b]) => severityOrder.indexOf(a) - severityOrder.indexOf(b))
.map(([level, count]) => `- **${level}**: ${count}`)
.join('\n');

const findingLines = topFindings.map((item, index) => {
const location = item.locations && item.locations[0] && item.locations[0].physicalLocation;
const file = location && location.artifactLocation && location.artifactLocation.uri;
const region = location && location.region;
const line = region ? region.startLine : 'N/A';
return [`### Finding ${index + 1}`,
`- **Rule**: ${item.ruleId || 'unknown'}`,
`- **Severity**: ${item.level || item.kind || 'warning'}`,
`- **Message**: ${item.message && item.message.text ? item.message.text : 'N/A'}`,
`- **Location**: ${file || 'unknown'}:${line}`,
''].join('\n');
}).join('\n');

return [`# Security Report`,
'',
`## Summary`,
'',
`- Total findings: **${totalFindings}**`,
severityLines ? `\n${severityLines}` : '',
'',
`## Top Findings`,
'',
findingLines || 'No findings detected.',
'',
`## Review Notes`,
'',
`The report was generated automatically by the GitHub Actions DevSecOps pipeline.`,
openAiKey ? `An LLM review was requested and its analysis is included below.` : `LLM review was skipped because OPENAI_API_KEY is not configured.`,
'',
`---`,
'',
`*Generated by the DevSecOps pipeline.*`].join('\n');
}

function extractTopFindings(results, max = 8) {
return results.slice(0, max);
}

async function callOpenAI(prompt) {
const body = JSON.stringify({
model: openAiModel,
messages: [{ role: 'system', content: 'Você é um assistente de segurança de software. Revise os resultados SARIF e gere um resumo conciso com risco, gravidade e recomendações.' },
{ role: 'user', content: prompt }],
max_tokens: 500,
temperature: 0.2
});

const options = {
hostname: 'api.openai.com',
path: '/v1/chat/completions',
method: 'POST',
headers: {
'Authorization': `Bearer ${openAiKey}`,
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(body)
}
};

return new Promise((resolve, reject) => {
const req = https.request(options, (res) => {
let data = '';
res.on('data', (chunk) => data += chunk);
res.on('end', () => {
if (res.statusCode >= 200 && res.statusCode < 300) {
try {
const json = JSON.parse(data);
const content = json.choices && json.choices[0] && json.choices[0].message && json.choices[0].message.content;
resolve(content || 'LLM review completed but no content was returned.');
} catch (error) {
reject(new Error(`Falha ao analisar retorno do OpenAI: ${error.message}`));
}
} else {
reject(new Error(`OpenAI API error ${res.statusCode}: ${data}`));
}
});
});

req.on('error', reject);
req.write(body);
req.end();
});
}

async function main() {
const sarif = loadSarif(sarifPath);
const results = (sarif.runs || []).flatMap(run => run.results || []);
const summary = summarizeFindings(results);
const topFindings = extractTopFindings(results);
let report = formatReport(sarif, summary, topFindings);

if (openAiKey && results.length > 0) {
const prompt = `Aqui estão os ${results.length} resultados SARIF mais importantes. Forneça um resumo de risco, explique os impactos e indique ações corretivas relevantes. Exiba como listas claras e concisas. \n\n` +
topFindings.map((item, index) => {
const location = item.locations && item.locations[0] && item.locations[0].physicalLocation;
const file = location && location.artifactLocation && location.artifactLocation.uri;
const region = location && location.region;
const line = region ? region.startLine : 'N/A';
return `${index + 1}. Rule: ${item.ruleId || 'unknown'}; Severity: ${item.level || item.kind || 'warning'}; File: ${file || 'unknown'}:${line}; Message: ${item.message && item.message.text ? item.message.text : 'N/A'}`;
}).join('\n');

try {
const llmReview = await callOpenAI(prompt);
report += '\n\n## LLM Review\n\n' + llmReview + '\n';
} catch (error) {
report += `\n\n## LLM Review\n\nFalha ao obter análise LLM: ${error.message}\n`;
}
}

fs.writeFileSync(reportPath, report, 'utf8');
console.log(`Security report written to ${reportPath}`);
}

main().catch((error) => {
console.error(error);
process.exit(1);
});
95 changes: 95 additions & 0 deletions .github/workflows/devsecops-security.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
name: DevSecOps Security Pipeline

on:
push:
branches:
- main
- master
- develop
pull_request:
types: [opened, synchronize, reopened]
workflow_dispatch:
schedule:
- cron: '0 3 * * *'

jobs:
codeql:
name: CodeQL SAST Scan
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
#analysis: write
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: javascript
config-file: .github/codeql/codeql-config.yml

- name: Perform CodeQL analysis
uses: github/codeql-action/analyze@v3
with:
output: results.sarif

- name: Upload SARIF artifact
uses: actions/upload-artifact@v4
with:
name: codeql-sarif
path: results.sarif

security_review:
name: LLM Security Review & PR Comment
runs-on: ubuntu-latest
needs: codeql
if: always()
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Download SARIF artifact
uses: actions/download-artifact@v4
with:
name: codeql-sarif

- name: Generate security report
run: |
node .github/scripts/security-review.js results.sarif security-report.md

- name: Upload security report artifact
uses: actions/upload-artifact@v4
with:
name: security-report
path: security-report.md

- name: Create automatic security report pull request
if: github.event_name != 'pull_request'
uses: peter-evans/create-pull-request@v7
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "docs: update automated security report"
title: "docs: update automated security report"
body: |
This pull request was generated automatically by the DevSecOps Security Pipeline.

It updates the generated `security-report.md` file from the latest CodeQL SARIF results.
branch: automated/security-report
base: ${{ github.ref_name }}
add-paths: security-report.md
labels: security, automated-pr

- name: Comment on pull request
if: github.event_name == 'pull_request'
uses: peter-evans/create-or-update-comment@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
issue-number: ${{ github.event.pull_request.number }}
body-file: security-report.md
edit-mode: replace
1 change: 1 addition & 0 deletions demo/autoencoder.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<script src="../build/vis.js"></script>
<script src="../build/util.js"></script>
<script src="../build/convnet.js"></script>
<script src="js/safe-config.js"></script>


<script src="mnist/mnist_labels.js"></script>
Expand Down
1 change: 1 addition & 0 deletions demo/cifar10.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<script src="../build/vis.js"></script>
<script src="../build/util.js"></script>
<script src="../build/convnet.js"></script>
<script src="js/safe-config.js"></script>

<script src="js/image-helpers.js"></script>
<script src="js/pica.js"></script>
Expand Down
3 changes: 2 additions & 1 deletion demo/classify2d.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<script src="js/npgmain.js"></script>
<script src="../build/convnet.js"></script>
<script src="../build/util.js"></script>
<script src="js/safe-config.js"></script>
<script src="js/classify2d.js"></script>

<style type="text/css">
Expand Down Expand Up @@ -50,7 +51,7 @@ <h1><a href="http://cs.stanford.edu/people/karpathy/convnetjs">ConvnetJS</a> dem
<br>
<input id="buttontp" type="submit" value="change network" onclick="reload();" style="width: 300px; height: 50px;"/>

<p>Feel free to change this, the text area above gets eval()'d when you hit the button and the network gets reloaded. Every 10th of a second, all points are fed to the network multiple times through the trainer class to train the network. The resulting predictions of the network are then "painted" under the data points to show you the generalization.</p>
<p>Feel free to change this, the text area above is parsed as a restricted ConvNetJS configuration when you hit the button and the network gets reloaded. Every 10th of a second, all points are fed to the network multiple times through the trainer class to train the network. The resulting predictions of the network are then "painted" under the data points to show you the generalization.</p>

<p>On the right we visualize the transformed representation of all grid points in the original space and the data, for a given layer and only for 2 neurons at a time. The number in the bracket shows the total number of neurons at that level of representation. If the number is more than 2, you will only see the two visualized but you can cycle through all of them with the cycle button.</p>

Expand Down
3 changes: 2 additions & 1 deletion demo/image_regression.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<script src="js/jquery-ui.min.js"></script>

<script src="../build/convnet.js"></script>
<script src="js/safe-config.js"></script>
<script src="js/image_regression.js"></script>

<style type="text/css">
Expand Down Expand Up @@ -38,7 +39,7 @@ <h1><a href="http://cs.stanford.edu/people/karpathy/convnetjs/">ConvnetJS</a> de

<p>This demo that treats the pixels of an image as a learning problem: it takes the (x,y) position on a grid and learns to predict the color at that point using regression to (r,g,b). It's a bit like compression, since the image information is encoded in the weights of the network, but almost certainly not of practical kind :)</p>
<p>
Note that the entire ConvNetJS definition is shown in textbox below and it gets eval()'d to create the network, so feel free to fiddle with the parameters and hit "reload". I found that, empirically and interestingly, deeper networks tend to work much better on this task given a fixed parameter budget.
Note that the entire ConvNetJS definition is shown in textbox below and it is parsed as a restricted configuration to create the network, so feel free to fiddle with the parameters and hit "reload". I found that, empirically and interestingly, deeper networks tend to work much better on this task given a fixed parameter budget.
</p>

<p>Report questions/bugs/suggestions to <a href="https://twitter.com/karpathy">@karpathy</a>.</p>
Expand Down
5 changes: 4 additions & 1 deletion demo/js/autoencoder.js
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,9 @@ var load_from_json = function() {
reset_all();
}
var change_net = function() {
eval($("#newnet").val());
var config = convnetjs.demoConfig.parseNetwork($("#newnet").val());
layer_defs = config.layer_defs;
net = config.net;
trainer = config.trainer;
reset_all();
}
7 changes: 5 additions & 2 deletions demo/js/classify2d.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ trainer = new convnetjs.SGDTrainer(net, {learning_rate:0.01, momentum:0.1, batch
";

function reload() {
eval($("#layerdef").val());
var config = convnetjs.demoConfig.parseNetwork($("#layerdef").val());
layer_defs = config.layer_defs;
net = config.net;
trainer = config.trainer;

// enter buttons for layers
var t = '';
Expand Down Expand Up @@ -318,4 +321,4 @@ $(function() {
reload();
NPGinit(20);

});
});
5 changes: 4 additions & 1 deletion demo/js/image_regression.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,10 @@ function tick() {

function reload() {
counter = 0;
eval($("#layerdef").val());
var config = convnetjs.demoConfig.parseNetwork($("#layerdef").val());
layer_defs = config.layer_defs;
net = config.net;
trainer = config.trainer;
//$("#slider").slider("value", Math.log(trainer.learning_rate) / Math.LN10);
//$("#lr").html('Learning rate: ' + trainer.learning_rate);
}
Expand Down
10 changes: 8 additions & 2 deletions demo/js/images-demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,10 @@ var loaded_train_batches = [];
$(window).load(function() {

$("#newnet").val(t);
eval($("#newnet").val());
var config = convnetjs.demoConfig.parseNetwork($("#newnet").val());
layer_defs = config.layer_defs;
net = config.net;
trainer = config.trainer;

update_net_param_display();

Expand Down Expand Up @@ -646,6 +649,9 @@ var load_pretrained = function() {
}

var change_net = function() {
eval($("#newnet").val());
var config = convnetjs.demoConfig.parseNetwork($("#newnet").val());
layer_defs = config.layer_defs;
net = config.net;
trainer = config.trainer;
reset_all();
}
Loading