diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml
new file mode 100644
index 00000000..2717f1e5
--- /dev/null
+++ b/.github/codeql/codeql-config.yml
@@ -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/**
diff --git a/.github/scripts/security-review.js b/.github/scripts/security-review.js
new file mode 100644
index 00000000..674f479f
--- /dev/null
+++ b/.github/scripts/security-review.js
@@ -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);
+});
diff --git a/.github/workflows/devsecops-security.yml b/.github/workflows/devsecops-security.yml
new file mode 100644
index 00000000..37c4f34b
--- /dev/null
+++ b/.github/workflows/devsecops-security.yml
@@ -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
diff --git a/demo/autoencoder.html b/demo/autoencoder.html
index e2d37f18..234c8ca1 100644
--- a/demo/autoencoder.html
+++ b/demo/autoencoder.html
@@ -13,6 +13,7 @@
+
diff --git a/demo/cifar10.html b/demo/cifar10.html
index a8a1594a..80ac0b5b 100644
--- a/demo/cifar10.html
+++ b/demo/cifar10.html
@@ -12,6 +12,7 @@
+
diff --git a/demo/classify2d.html b/demo/classify2d.html
index c933ae6c..cf601aef 100644
--- a/demo/classify2d.html
+++ b/demo/classify2d.html
@@ -7,6 +7,7 @@
+