Skip to content
Merged
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
55 changes: 28 additions & 27 deletions .github/workflows/shieldci.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
name: ShieldCI Security Scan

on:
push:
branches: [main, master]
pull_request:
branches: [main, master]
workflow_dispatch:

jobs:
shieldci-scan:
Expand All @@ -13,81 +16,79 @@ jobs:
- name: Checkout target repository
uses: actions/checkout@v4

- name: Get PR metadata
- name: Gather metadata
id: meta
run: |
echo "repo=${{ github.repository }}" >> "$GITHUB_OUTPUT"
echo "branch=${{ github.head_ref }}" >> "$GITHUB_OUTPUT"
echo "commit=${{ github.event.pull_request.head.sha }}" >> "$GITHUB_OUTPUT"
echo "commit_msg=$(git log -1 --pretty=%s 2>/dev/null || echo 'PR scan')" >> "$GITHUB_OUTPUT"
if [ "${{ github.event_name }}" = "pull_request" ]; then
echo "branch=${{ github.head_ref }}" >> "$GITHUB_OUTPUT"
echo "commit=${{ github.event.pull_request.head.sha }}" >> "$GITHUB_OUTPUT"
echo "trigger=PR" >> "$GITHUB_OUTPUT"
else
echo "branch=${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
echo "commit=${{ github.sha }}" >> "$GITHUB_OUTPUT"
echo "trigger=${{ github.event_name }}" >> "$GITHUB_OUTPUT"
fi
echo "commit_msg=$(git log -1 --pretty=%s 2>/dev/null || echo 'scan')" >> "$GITHUB_OUTPUT"

- name: Check ShieldCI engine is available
run: |
if [ ! -f "$HOME/Desktop/ShieldCI/target/release/shield-ci" ]; then
echo "ERROR: ShieldCI engine not found at ~/Desktop/ShieldCI/target/release/shield-ci"
echo "Please build the engine first: cd ~/Desktop/ShieldCI && cargo build --release"
echo "ERROR: ShieldCI engine not found"
exit 1
fi

- name: Copy shieldci.yml to engine tests directory
- name: Copy shieldci.yml config
run: |
if [ -f "shieldci.yml" ]; then
cp shieldci.yml "$HOME/Desktop/ShieldCI/tests/shieldci.yml"
echo "Copied shieldci.yml config"
else
echo "No shieldci.yml found in repo root, engine will auto-detect"
fi

- name: Copy target repo to engine tests directory
- name: Copy target repo to engine
run: |
rm -rf "$HOME/Desktop/ShieldCI/tests/repo"
cp -r "$GITHUB_WORKSPACE" "$HOME/Desktop/ShieldCI/tests/repo"

- name: Run ShieldCI engine
id: scan
working-directory: ${{ env.HOME }}/Desktop/ShieldCI/tests
run: |
START_TIME=$(date +%s)
cd "$HOME/Desktop/ShieldCI/tests"
"$HOME/Desktop/ShieldCI/target/release/shield-ci" 2>&1 | tee scan_output.log || true
END_TIME=$(date +%s)
DURATION=$((END_TIME - START_TIME))
echo "duration=${DURATION}s" >> "$GITHUB_OUTPUT"
echo "duration=$((END_TIME - START_TIME))s" >> "$GITHUB_OUTPUT"

- name: Push results to ShieldCI dashboard
if: always()
env:
SHIELDCI_API_URL: ${{ secrets.SHIELDCI_API_URL }}
SHIELDCI_API_KEY: ${{ secrets.SHIELDCI_API_KEY }}
SHIELDCI_API_URL: http://localhost:3000
SHIELDCI_API_KEY: fc09420a3737855a3094ff7831a6219565cee6777a0fbeec
Comment on lines +64 to +65
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The workflow hardcodes an API key (and an API URL) directly in the repository. This exposes credentials to anyone with repo access and to any workflow log/runner that can read the YAML. Move these values back to GitHub Secrets (e.g., secrets.SHIELDCI_API_URL / secrets.SHIELDCI_API_KEY) and avoid committing real keys to source control.

Suggested change
SHIELDCI_API_URL: http://localhost:3000
SHIELDCI_API_KEY: fc09420a3737855a3094ff7831a6219565cee6777a0fbeec
SHIELDCI_API_URL: ${{ secrets.SHIELDCI_API_URL }}
SHIELDCI_API_KEY: ${{ secrets.SHIELDCI_API_KEY }}

Copilot uses AI. Check for mistakes.
SHIELDCI_REPO: ${{ steps.meta.outputs.repo }}
SHIELDCI_BRANCH: ${{ steps.meta.outputs.branch }}
SHIELDCI_COMMIT: ${{ steps.meta.outputs.commit }}
SHIELDCI_COMMIT_MSG: ${{ steps.meta.outputs.commit_msg }}
SHIELDCI_DURATION: ${{ steps.scan.outputs.duration }}
SHIELDCI_TRIGGERED_BY: PR
SHIELDCI_RESULTS_FILE: ${{ env.HOME }}/Desktop/ShieldCI/tests/shield_results.json
SHIELDCI_TRIGGERED_BY: ${{ steps.meta.outputs.trigger }}
SHIELDCI_RESULTS_FILE: ${{ runner.temp }}/../../../Desktop/ShieldCI/tests/shield_results.json
run: |
export SHIELDCI_RESULTS_FILE="$HOME/Desktop/ShieldCI/tests/shield_results.json"
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SHIELDCI_RESULTS_FILE is set to two different values: one via the job env: (with a brittle runner.temp + ../../../ traversal), then overridden in the step with export. This can lead to confusing behavior depending on which value push_results.py reads and makes the path resolution fragile. Use a single, consistent absolute path for SHIELDCI_RESULTS_FILE (preferably set once in env) and remove the conflicting override.

Suggested change
export SHIELDCI_RESULTS_FILE="$HOME/Desktop/ShieldCI/tests/shield_results.json"

Copilot uses AI. Check for mistakes.
python3 "$HOME/Desktop/ShieldCI/push_results.py"

- name: Post scan summary as PR comment
if: always()
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const reportPath = `${process.env.HOME}/Desktop/ShieldCI/tests/SHIELD_REPORT.md`;
const reportPath = process.env.HOME + '/Desktop/ShieldCI/tests/SHIELD_REPORT.md';
let report = 'Scan completed but no report was generated.';
try {
report = fs.readFileSync(reportPath, 'utf8');
if (report.length > 60000) {
report = report.substring(0, 60000) + '\n\n... (truncated)';
}
} catch (e) {
report = 'Could not read scan report.';
}
if (report.length > 60000) report = report.substring(0, 60000) + '\n\n... (truncated)';
} catch (e) { report = 'Could not read scan report.'; }
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## 🛡️ ShieldCI Security Scan Results\n\n${report}`
body: '## 🛡️ ShieldCI Security Scan Results\n\n' + report
});
Empty file modified run.sh
100644 → 100755
Empty file.
1 change: 1 addition & 0 deletions tests/repo
Submodule repo added at 7d5bc8
64 changes: 64 additions & 0 deletions tests/scan_output.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
🛡️ Booting ShieldCI Orchestrator...
📋 Loaded shieldci.yml configuration
⚙️ Running build: npm install

up to date, audited 192 packages in 685ms

26 packages are looking for funding
run `npm fund` for details

7 vulnerabilities (2 low, 5 high)

To address all issues (including breaking changes), run:
npm audit fix --force

Run `npm audit` for details.
🚀 Launching Node.js server on http://127.0.0.1:3000...
⏳ Waiting for target http://127.0.0.1:3000 to come online...
✅ Target is up and responding!
Recursively flattening codebase for full context...

📋 Test Plan (5 tests):
1. [RECON: Port Scan] nmap_scan → http://host.docker.internal:3000
2. [RECON: Security Headers] check_headers → http://host.docker.internal:3000
3. [VULN SCAN: Web Server] nikto_scan → http://host.docker.internal:3000
4. [DISCOVERY: Hidden Paths] gobuster_scan → http://host.docker.internal:3000
5. [SQLi: GET /login] sqlmap_scan → http://host.docker.internal:3000/login?username=test

--- Test 1/5: RECON: Port Scan ---
🤝 Initiating MCP Handshake & Strike: nmap_scan on http://host.docker.internal:3000
[03/07/26 02:07:37] INFO Processing request of type server.py:720
CallToolRequest

--- Test 2/5: RECON: Security Headers ---
🤝 Initiating MCP Handshake & Strike: check_headers on http://host.docker.internal:3000
[03/07/26 02:07:38] INFO Processing request of type server.py:720
CallToolRequest

--- Test 3/5: VULN SCAN: Web Server ---
🤝 Initiating MCP Handshake & Strike: nikto_scan on http://host.docker.internal:3000
[03/07/26 02:07:39] INFO Processing request of type server.py:720
CallToolRequest

--- Test 4/5: DISCOVERY: Hidden Paths ---
🤝 Initiating MCP Handshake & Strike: gobuster_scan on http://host.docker.internal:3000
[03/07/26 02:07:53] INFO Processing request of type server.py:720
CallToolRequest

--- Test 5/5: SQLi: GET /login ---
🤝 Initiating MCP Handshake & Strike: sqlmap_scan on http://host.docker.internal:3000/login?username=test
[03/07/26 02:07:54] INFO Processing request of type server.py:720
CallToolRequest

--- Adaptive Strike 1 ---
🧠 Invoking local model via Ollama API...
🤝 Initiating MCP Handshake & Strike: sqlmap_scan on http://host.docker.internal:3000/login?username=admin
[03/07/26 02:08:01] INFO Processing request of type server.py:720
CallToolRequest

--- Adaptive Strike 2 ---
🧠 Invoking local model via Ollama API...
🤝 Initiating MCP Handshake & Strike: sqlmap_scan on http://host.docker.internal:3000/login?username=test
[03/07/26 02:08:09] INFO Processing request of type server.py:720
CallToolRequest
📝 Compiling final security assessment...
5 changes: 5 additions & 0 deletions tests/shield_results.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"status": "Clean",
"vulnerabilities": [],
Comment on lines +2 to +3
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fixture is internally inconsistent: it marks the scan status as Clean and vulnerabilities as an empty list, while report_markdown describes multiple critical vulnerabilities. If downstream logic/tests rely on status/vulnerabilities to match the report, this will produce incorrect behavior. Align the JSON fields with the report content (e.g., non-clean status and populated vulnerabilities) or adjust the report_markdown to match a clean scan.

Suggested change
"status": "Clean",
"vulnerabilities": [],
"status": "Vulnerable",
"vulnerabilities": [
{
"id": "SQL_INJECTION_LOGIN",
"title": "SQL Injection in /login route",
"severity": "critical",
"description": "The GET /login route builds SQL queries via string concatenation using unsanitized user input, allowing SQL injection."
},
{
"id": "INSECURE_PASSWORD_STORAGE",
"title": "Insecure password storage",
"severity": "high",
"description": "Passwords are stored in plaintext instead of being hashed with a secure algorithm such as bcrypt."
},
{
"id": "IDOR_LOGIN",
"title": "Insecure Direct Object Reference in /login",
"severity": "high",
"description": "The /login route does not properly validate or sanitize the username parameter, potentially exposing sensitive user data."
}
],

Copilot uses AI. Check for mistakes.
"report_markdown": "The provided code snippets are quite extensive, and I'll focus on the most critical vulnerabilities and provide corrected versions.\n\n**1. SQL Injection Vulnerability in `/login` Route**\n\nThe `GET /login` route is vulnerable to SQL injection. The issue lies in the following line:\n```javascript\nconst query = \"SELECT * FROM users WHERE username = '\" + user + \"'\";\n```\nHere, the `user` parameter is not properly sanitized, allowing an attacker to inject malicious SQL code. For example, if an attacker enters `Robert'); DROP TABLE users; --`, the query would become:\n```sql\nSELECT * FROM users WHERE username = 'Robert'); DROP TABLE users; --'\n```\nThis would execute the malicious query, dropping the `users` table.\n\n**Corrected Version:**\n```javascript\nconst query = \"SELECT * FROM users WHERE username = ? \";\ndb.get(query, [user], (err, row) => {\n // ...\n});\n```\nIn this corrected version, we use a parameterized query with a parameter `?`, which is replaced with the actual `user` value. This prevents SQL injection attacks.\n\n**2. Code Injection Vulnerability in `app.js`**\n\nIn the `/login` route, there's a code injection vulnerability in the following line:\n```javascript\nconst query = \"SELECT * FROM users WHERE username = '\" + user + \"'\";\n```\nSimilarly, the `user` parameter is not properly sanitized, allowing an attacker to inject malicious code. However, this vulnerability is more related to the fact that the query is being constructed as a string, making it vulnerable to code injection.\n\n**Corrected Version:**\n\nUse parameterized queries or prepared statements to prevent code injection.\n\n**3. Path Traversal Vulnerability in `app.js`**\n\nIn the `/login` route, there's a path traversal vulnerability in the following line:\n```javascript\nconst user = req.query.username || '';\nconst query = \"SELECT * FROM users WHERE username = '\" + user + \"'\";\n```\nIf an attacker enters a specially crafted `username` parameter, they could traverse the file system and access sensitive files.\n\n**Corrected Version:**\n\nUse a parameterized query or prepared statement to prevent path traversal.\n\n**4. Security Misconfiguration in `app.js`**\n\nThe `/login` route uses the `sqlite3` library, which is not secure for production environments. The `serialize()` method is used, which can lead to unexpected behavior and security issues.\n\n**Corrected Version:**\n\nUse a more secure database library, such as `pg` or `mysql2`, and ensure proper error handling and security configurations.\n\n**5. Command Injection Vulnerability in `app.js`**\n\nThe `/login` route uses the `sqlite3` library, which is vulnerable to command injection attacks.\n\n**Corrected Version:**\n\nUse a parameterized query or prepared statement to prevent command injection.\n\n**6. Insecure Direct Object Reference (IDOR) in `app.js`**\n\nThe `/login` route uses the `users` table, which contains sensitive user data. However, the route does not properly validate or sanitize the `username` parameter, allowing an attacker to access sensitive user data.\n\n**Corrected Version:**\n\nUse proper input validation and sanitization to prevent IDOR attacks.\n\n**7. Insecure Password Storage in `app.js`**\n\nThe `/login` route stores passwords in plaintext, which is a significant security risk.\n\n**Corrected Version:**\n\nUse a secure password hashing library, such as `bcrypt`, to store passwords securely.\n\n**8. Security Misconfiguration in `app.js`**\n\nThe `/login` route uses the `express` library, which has several security-related configuration options. However, the route does not properly configure these options, leading to potential security issues.\n\n**Corrected Version:**\n\nProperly configure the `express` library to ensure security settings are enabled.\n\n**9. Insecure Deserialization in `app.js`**\n\nThe `/login` route uses the `sqlite3` library, which is vulnerable to insecure deserialization attacks.\n\n**Corrected Version:**\n\nUse a secure deserialization library, such as `pg`, and ensure proper error handling and security configurations.\n\n**10. Security Misconfiguration in `app.js`**\n\nThe `/login` route uses the `sqlite3` library, which is not secure for production environments. The `serialize()` method is used, which can lead to unexpected behavior and security issues.\n\n**Corrected Version:**\n\nUse a more secure database library, such as `pg` or `mysql2`, and ensure proper error handling and security configurations.\n\nThese vulnerabilities are significant, and it's essential to address them to ensure the security of your application."
}
Loading