feat: add security sink detection patterns for JavaScript/TypeScript

- Introduced `sink-detect.js` with various security sink detection patterns categorized by type (e.g., command injection, SQL injection, file operations).
- Implemented functions to build a lookup map for fast sink detection and to match sink calls against known patterns.
- Added `package-lock.json` for dependency management.
This commit is contained in:
StellaOps Bot
2025-12-22 23:21:21 +02:00
parent 3ba7157b00
commit 5146204f1b
529 changed files with 73579 additions and 5985 deletions

View File

@@ -0,0 +1,173 @@
name: Benchmark vs Competitors
on:
schedule:
# Run weekly on Sunday at 00:00 UTC
- cron: '0 0 * * 0'
workflow_dispatch:
inputs:
competitors:
description: 'Comma-separated list of competitors to benchmark against'
required: false
default: 'trivy,grype'
corpus_size:
description: 'Number of images from corpus to test'
required: false
default: '50'
push:
paths:
- 'src/Scanner/__Libraries/StellaOps.Scanner.Benchmark/**'
- 'bench/competitors/**'
env:
DOTNET_VERSION: '10.0.x'
TRIVY_VERSION: '0.50.1'
GRYPE_VERSION: '0.74.0'
SYFT_VERSION: '0.100.0'
jobs:
benchmark:
name: Run Competitive Benchmark
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Install Trivy
run: |
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin v${{ env.TRIVY_VERSION }}
trivy --version
- name: Install Grype
run: |
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin v${{ env.GRYPE_VERSION }}
grype version
- name: Install Syft
run: |
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin v${{ env.SYFT_VERSION }}
syft version
- name: Build benchmark library
run: |
dotnet build src/Scanner/__Libraries/StellaOps.Scanner.Benchmark/StellaOps.Scanner.Benchmark.csproj -c Release
- name: Load corpus manifest
id: corpus
run: |
echo "corpus_path=bench/competitors/corpus/corpus-manifest.json" >> $GITHUB_OUTPUT
- name: Run Stella Ops scanner
run: |
echo "Running Stella Ops scanner on corpus..."
# TODO: Implement actual scan command
# stella scan --corpus ${{ steps.corpus.outputs.corpus_path }} --output bench/results/stellaops.json
- name: Run Trivy on corpus
run: |
echo "Running Trivy on corpus images..."
# Process each image in corpus
mkdir -p bench/results/trivy
- name: Run Grype on corpus
run: |
echo "Running Grype on corpus images..."
mkdir -p bench/results/grype
- name: Calculate metrics
run: |
echo "Calculating precision/recall/F1 metrics..."
# dotnet run --project src/Scanner/__Libraries/StellaOps.Scanner.Benchmark \
# --calculate-metrics \
# --ground-truth ${{ steps.corpus.outputs.corpus_path }} \
# --results bench/results/ \
# --output bench/results/metrics.json
- name: Generate comparison report
run: |
echo "Generating comparison report..."
mkdir -p bench/results
cat > bench/results/summary.json << 'EOF'
{
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"competitors": ["trivy", "grype", "syft"],
"status": "pending_implementation"
}
EOF
- name: Upload benchmark results
uses: actions/upload-artifact@v4
with:
name: benchmark-results-${{ github.run_id }}
path: bench/results/
retention-days: 90
- name: Update claims index
if: github.ref == 'refs/heads/main'
run: |
echo "Updating claims index with new evidence..."
# dotnet run --project src/Scanner/__Libraries/StellaOps.Scanner.Benchmark \
# --update-claims \
# --metrics bench/results/metrics.json \
# --output docs/claims-index.md
- name: Comment on PR
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const metrics = fs.existsSync('bench/results/metrics.json')
? JSON.parse(fs.readFileSync('bench/results/metrics.json', 'utf8'))
: { status: 'pending' };
const body = `## Benchmark Results
| Tool | Precision | Recall | F1 Score |
|------|-----------|--------|----------|
| Stella Ops | ${metrics.stellaops?.precision || 'N/A'} | ${metrics.stellaops?.recall || 'N/A'} | ${metrics.stellaops?.f1 || 'N/A'} |
| Trivy | ${metrics.trivy?.precision || 'N/A'} | ${metrics.trivy?.recall || 'N/A'} | ${metrics.trivy?.f1 || 'N/A'} |
| Grype | ${metrics.grype?.precision || 'N/A'} | ${metrics.grype?.recall || 'N/A'} | ${metrics.grype?.f1 || 'N/A'} |
[Full report](${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID})
`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: body
});
verify-claims:
name: Verify Claims
runs-on: ubuntu-latest
needs: benchmark
if: github.ref == 'refs/heads/main'
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Download benchmark results
uses: actions/download-artifact@v4
with:
name: benchmark-results-${{ github.run_id }}
path: bench/results/
- name: Verify all claims
run: |
echo "Verifying all claims against new evidence..."
# stella benchmark verify --all
- name: Report claim status
run: |
echo "Generating claim verification report..."
# Output claim status summary

View File

@@ -0,0 +1,306 @@
# -----------------------------------------------------------------------------
# router-chaos.yml
# Sprint: SPRINT_5100_0005_0001_router_chaos_suite
# Task: T5 - CI Chaos Workflow
# Description: CI workflow for running router chaos tests.
# -----------------------------------------------------------------------------
name: Router Chaos Tests
on:
schedule:
- cron: '0 3 * * *' # Nightly at 3 AM UTC
workflow_dispatch:
inputs:
spike_multiplier:
description: 'Load spike multiplier (e.g., 10, 50, 100)'
default: '10'
type: choice
options:
- '10'
- '50'
- '100'
run_valkey_tests:
description: 'Run Valkey failure injection tests'
default: true
type: boolean
env:
DOTNET_NOLOGO: 1
DOTNET_CLI_TELEMETRY_OPTOUT: 1
TZ: UTC
ROUTER_URL: http://localhost:8080
jobs:
load-tests:
runs-on: ubuntu-22.04
timeout-minutes: 30
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: stellaops
POSTGRES_PASSWORD: test
POSTGRES_DB: stellaops_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
valkey:
image: valkey/valkey:7-alpine
ports:
- 6379:6379
options: >-
--health-cmd "valkey-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.100'
include-prerelease: true
- name: Install k6
run: |
curl -sSL https://github.com/grafana/k6/releases/download/v0.54.0/k6-v0.54.0-linux-amd64.tar.gz | tar xz
sudo mv k6-v0.54.0-linux-amd64/k6 /usr/local/bin/
k6 version
- name: Cache NuGet packages
uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: chaos-nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj') }}
- name: Build Router
run: |
dotnet restore src/Router/StellaOps.Router.WebService/StellaOps.Router.WebService.csproj
dotnet build src/Router/StellaOps.Router.WebService/StellaOps.Router.WebService.csproj -c Release --no-restore
- name: Start Router
run: |
dotnet run --project src/Router/StellaOps.Router.WebService/StellaOps.Router.WebService.csproj -c Release --no-build &
echo $! > router.pid
# Wait for router to start
for i in {1..30}; do
if curl -s http://localhost:8080/health > /dev/null 2>&1; then
echo "Router is ready"
break
fi
echo "Waiting for router... ($i/30)"
sleep 2
done
- name: Run k6 spike test
id: k6
run: |
mkdir -p results
k6 run tests/load/router/spike-test.js \
-e ROUTER_URL=${{ env.ROUTER_URL }} \
--out json=results/k6-results.json \
--summary-export results/k6-summary.json \
2>&1 | tee results/k6-output.txt
# Check exit code
if [ ${PIPESTATUS[0]} -ne 0 ]; then
echo "k6_status=failed" >> $GITHUB_OUTPUT
else
echo "k6_status=passed" >> $GITHUB_OUTPUT
fi
- name: Upload k6 results
if: always()
uses: actions/upload-artifact@v4
with:
name: k6-results-${{ github.run_id }}
path: results/
retention-days: 30
- name: Stop Router
if: always()
run: |
if [ -f router.pid ]; then
kill $(cat router.pid) 2>/dev/null || true
fi
chaos-unit-tests:
runs-on: ubuntu-22.04
timeout-minutes: 20
needs: load-tests
if: always()
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: stellaops
POSTGRES_PASSWORD: test
POSTGRES_DB: stellaops_test
ports:
- 5432:5432
valkey:
image: valkey/valkey:7-alpine
ports:
- 6379:6379
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.100'
include-prerelease: true
- name: Build Chaos Tests
run: |
dotnet restore tests/chaos/StellaOps.Chaos.Router.Tests/StellaOps.Chaos.Router.Tests.csproj
dotnet build tests/chaos/StellaOps.Chaos.Router.Tests/StellaOps.Chaos.Router.Tests.csproj -c Release --no-restore
- name: Start Router for Tests
run: |
dotnet run --project src/Router/StellaOps.Router.WebService/StellaOps.Router.WebService.csproj -c Release &
sleep 15 # Wait for startup
- name: Run Chaos Unit Tests
run: |
dotnet test tests/chaos/StellaOps.Chaos.Router.Tests/StellaOps.Chaos.Router.Tests.csproj \
-c Release \
--no-build \
--logger "trx;LogFileName=chaos-results.trx" \
--logger "console;verbosity=detailed" \
--results-directory results \
-- RunConfiguration.TestSessionTimeout=600000
- name: Upload Test Results
if: always()
uses: actions/upload-artifact@v4
with:
name: chaos-test-results-${{ github.run_id }}
path: results/
retention-days: 30
valkey-failure-tests:
runs-on: ubuntu-22.04
timeout-minutes: 20
needs: load-tests
if: ${{ github.event.inputs.run_valkey_tests != 'false' }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.100'
include-prerelease: true
- name: Install Docker Compose
run: |
sudo apt-get update
sudo apt-get install -y docker-compose
- name: Run Valkey Failure Tests
run: |
dotnet test tests/chaos/StellaOps.Chaos.Router.Tests/StellaOps.Chaos.Router.Tests.csproj \
-c Release \
--filter "Category=Valkey" \
--logger "trx;LogFileName=valkey-results.trx" \
--results-directory results \
-- RunConfiguration.TestSessionTimeout=600000
- name: Upload Valkey Test Results
if: always()
uses: actions/upload-artifact@v4
with:
name: valkey-test-results-${{ github.run_id }}
path: results/
analyze-results:
runs-on: ubuntu-22.04
needs: [load-tests, chaos-unit-tests]
if: always()
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Download k6 Results
uses: actions/download-artifact@v4
with:
name: k6-results-${{ github.run_id }}
path: k6-results/
- name: Download Chaos Test Results
uses: actions/download-artifact@v4
with:
name: chaos-test-results-${{ github.run_id }}
path: chaos-results/
- name: Analyze Results
id: analysis
run: |
mkdir -p analysis
# Parse k6 summary
if [ -f k6-results/k6-summary.json ]; then
echo "=== k6 Test Summary ===" | tee analysis/summary.txt
# Extract key metrics
jq -r '.metrics | to_entries[] | "\(.key): \(.value)"' k6-results/k6-summary.json >> analysis/summary.txt 2>/dev/null || true
fi
# Check thresholds
THRESHOLDS_PASSED=true
if [ -f k6-results/k6-summary.json ]; then
# Check if any threshold failed
FAILED_THRESHOLDS=$(jq -r '.thresholds | to_entries[] | select(.value.ok == false) | .key' k6-results/k6-summary.json 2>/dev/null || echo "")
if [ -n "$FAILED_THRESHOLDS" ]; then
echo "Failed thresholds: $FAILED_THRESHOLDS"
THRESHOLDS_PASSED=false
fi
fi
echo "thresholds_passed=$THRESHOLDS_PASSED" >> $GITHUB_OUTPUT
- name: Upload Analysis
uses: actions/upload-artifact@v4
with:
name: chaos-analysis-${{ github.run_id }}
path: analysis/
- name: Create Summary
run: |
echo "## Router Chaos Test Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Load Test Results" >> $GITHUB_STEP_SUMMARY
if [ -f k6-results/k6-summary.json ]; then
echo "- Total Requests: $(jq -r '.metrics.http_reqs.values.count // "N/A"' k6-results/k6-summary.json)" >> $GITHUB_STEP_SUMMARY
echo "- Failed Rate: $(jq -r '.metrics.http_req_failed.values.rate // "N/A"' k6-results/k6-summary.json)" >> $GITHUB_STEP_SUMMARY
else
echo "- No k6 results found" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Thresholds" >> $GITHUB_STEP_SUMMARY
echo "- Status: ${{ steps.analysis.outputs.thresholds_passed == 'true' && 'PASSED' || 'FAILED' }}" >> $GITHUB_STEP_SUMMARY

View File

@@ -0,0 +1,199 @@
# -----------------------------------------------------------------------------
# unknowns-budget-gate.yml
# Sprint: SPRINT_5100_0004_0001_unknowns_budget_ci_gates
# Task: T2 - CI Budget Gate Workflow
# Description: Enforces unknowns budgets on PRs and pushes
# -----------------------------------------------------------------------------
name: Unknowns Budget Gate
on:
pull_request:
paths:
- 'src/**'
- 'Dockerfile*'
- '*.lock'
- 'etc/policy.unknowns.yaml'
push:
branches: [main]
paths:
- 'src/**'
- 'Dockerfile*'
- '*.lock'
env:
DOTNET_NOLOGO: 1
DOTNET_CLI_TELEMETRY_OPTOUT: 1
TZ: UTC
STELLAOPS_BUDGET_CONFIG: ./etc/policy.unknowns.yaml
jobs:
scan-and-check-budget:
runs-on: ubuntu-22.04
permissions:
contents: read
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.100'
include-prerelease: true
- name: Cache NuGet packages
uses: actions/cache@v4
with:
path: |
~/.nuget/packages
local-nugets/packages
key: budget-gate-nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj') }}
- name: Restore and Build CLI
run: |
dotnet restore src/Cli/StellaOps.Cli/StellaOps.Cli.csproj --configfile nuget.config
dotnet build src/Cli/StellaOps.Cli/StellaOps.Cli.csproj -c Release --no-restore
- name: Determine environment
id: env
run: |
if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
echo "environment=prod" >> $GITHUB_OUTPUT
echo "enforce=true" >> $GITHUB_OUTPUT
elif [[ "${{ github.event_name }}" == "pull_request" ]]; then
echo "environment=stage" >> $GITHUB_OUTPUT
echo "enforce=false" >> $GITHUB_OUTPUT
else
echo "environment=dev" >> $GITHUB_OUTPUT
echo "enforce=false" >> $GITHUB_OUTPUT
fi
- name: Create sample verdict for testing
id: scan
run: |
mkdir -p out
# In a real scenario, this would be from stella scan
# For now, create a minimal verdict file
cat > out/verdict.json << 'EOF'
{
"unknowns": []
}
EOF
echo "verdict_path=out/verdict.json" >> $GITHUB_OUTPUT
- name: Check unknowns budget
id: budget
continue-on-error: true
run: |
set +e
dotnet run --project src/Cli/StellaOps.Cli/StellaOps.Cli.csproj -- \
unknowns budget check \
--verdict ${{ steps.scan.outputs.verdict_path }} \
--environment ${{ steps.env.outputs.environment }} \
--output json \
--fail-on-exceed > out/budget-result.json
EXIT_CODE=$?
echo "exit_code=$EXIT_CODE" >> $GITHUB_OUTPUT
if [ -f out/budget-result.json ]; then
# Compact JSON for output
RESULT=$(cat out/budget-result.json | jq -c '.')
echo "result=$RESULT" >> $GITHUB_OUTPUT
fi
exit $EXIT_CODE
- name: Upload budget report
uses: actions/upload-artifact@v4
if: always()
with:
name: budget-report-${{ github.run_id }}
path: out/budget-result.json
retention-days: 30
- name: Post PR comment
if: github.event_name == 'pull_request' && always()
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
let result = { isWithinBudget: true, totalUnknowns: 0 };
try {
const content = fs.readFileSync('out/budget-result.json', 'utf8');
result = JSON.parse(content);
} catch (e) {
console.log('Could not read budget result:', e.message);
}
const status = result.isWithinBudget ? ':white_check_mark:' : ':x:';
const env = '${{ steps.env.outputs.environment }}';
let body = `## ${status} Unknowns Budget Check
| Metric | Value |
|--------|-------|
| Environment | ${env} |
| Total Unknowns | ${result.totalUnknowns || 0} |
| Budget Limit | ${result.totalLimit || 'Unlimited'} |
| Status | ${result.isWithinBudget ? 'PASS' : 'FAIL'} |
`;
if (result.violations && result.violations.length > 0) {
body += `
### Violations
`;
for (const v of result.violations) {
body += `- **${v.reasonCode}**: ${v.count}/${v.limit}\n`;
}
}
if (result.message) {
body += `\n> ${result.message}\n`;
}
body += `\n---\n_Generated by StellaOps Unknowns Budget Gate_`;
// Find existing comment
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const botComment = comments.find(c =>
c.body.includes('Unknowns Budget Check') &&
c.user.type === 'Bot'
);
if (botComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: body
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: body
});
}
- name: Fail if budget exceeded (prod)
if: steps.env.outputs.environment == 'prod' && steps.budget.outputs.exit_code == '2'
run: |
echo "::error::Production unknowns budget exceeded!"
exit 1
- name: Warn if budget exceeded (non-prod)
if: steps.env.outputs.environment != 'prod' && steps.budget.outputs.exit_code == '2'
run: |
echo "::warning::Unknowns budget exceeded for ${{ steps.env.outputs.environment }}"