11 Commits

Author SHA1 Message Date
hikari 4437762002 chore: replace .npmrc with pnpm-workspace.yaml
Node.js CI / CI (push) Failing after 17s
Security Scan and Upload / Security & DefectDojo Upload (push) Failing after 14m33s
2026-03-02 16:28:54 -08:00
hikari f9761d19e1 docs: update feedback section to use support forum
Node.js CI / CI (push) Failing after 9s
Security Scan and Upload / Security & DefectDojo Upload (push) Successful in 1m32s
2026-01-26 12:37:45 -08:00
naomi 0178959397 feat: automated upload of .gitea/workflows/ci.yml
Node.js CI / CI (push) Failing after 10s
Security Scan and Upload / Security & DefectDojo Upload (push) Successful in 1m1s
2025-12-22 19:43:27 +01:00
naomi ec9fe32c88 feat: automated upload of .gitea/workflows/ci.yml
Node.js CI / CI (push) Has been cancelled
Security Scan and Upload / Security & DefectDojo Upload (push) Has been cancelled
2025-12-22 19:36:57 +01:00
naomi a0d814dbb1 feat: automated upload of .gitea/workflows/ci.yml
Node.js CI / Lint and Test (push) Has been cancelled
Security Scan and Upload / Security & DefectDojo Upload (push) Has been cancelled
2025-12-22 19:26:36 +01:00
naomi a06e61b48f feat: automated upload of .npmrc
Node.js CI / Lint and Test (push) Has been cancelled
Security Scan and Upload / Security & DefectDojo Upload (push) Has been cancelled
2025-12-22 19:17:27 +01:00
naomi 13567644ff feat: automated upload of .gitea/workflows/security.yml
Node.js CI / Lint and Test (push) Successful in 19s
Security Scan and Upload / Security & DefectDojo Upload (push) Successful in 1m39s
2025-12-18 03:09:53 +01:00
naomi 276cbb845a release: v0.0.6
Node.js CI / Lint and Test (push) Successful in 29s
2025-10-08 15:06:14 -07:00
naomi 9afe0d55c2 fix: cron every day not hour 2025-10-08 15:05:56 -07:00
naomi a6ab06eac4 release: v0.0.5
Node.js CI / Lint and Test (push) Successful in 34s
2025-10-07 18:10:45 -07:00
naomi db4d125613 feat: no flatten at all, let the analytic server handle that 2025-10-07 18:10:26 -07:00
7 changed files with 222 additions and 23 deletions
+14 -5
View File
@@ -8,22 +8,31 @@ on:
- main - main
jobs: jobs:
lint: ci:
name: Lint and Test name: CI
runs-on: ubuntu-latest
steps: steps:
- name: Checkout Source Files - name: Checkout Source Files
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Use Node.js v22 - name: Use Node.js v24
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 22 node-version: 24
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@v2 uses: pnpm/action-setup@v2
with: with:
version: 9 version: 10
- name: Ensure Dependencies are Pinned
uses: naomi-lgbt/dependency-pin-check@main
with:
language: javascript
dev-dependencies: true
peer-dependencies: true
optional-dependencies: true
- name: Install Dependencies - name: Install Dependencies
run: pnpm install run: pnpm install
+177
View File
@@ -0,0 +1,177 @@
name: Security Scan and Upload
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
schedule:
- cron: '0 0 * * 1'
workflow_dispatch:
jobs:
security-audit:
name: Security & DefectDojo Upload
runs-on: ubuntu-latest
continue-on-error: true
steps:
- name: Checkout code
uses: actions/checkout@v4
# --- AUTO-SETUP PROJECT ---
- name: Ensure DefectDojo Product Exists
env:
DD_URL: ${{ secrets.DD_URL }}
DD_TOKEN: ${{ secrets.DD_TOKEN }}
PRODUCT_NAME: ${{ github.repository }}
PRODUCT_TYPE_ID: 1
run: |
sudo apt-get install jq -y > /dev/null
echo "Checking connection to $DD_URL..."
# Check if product exists - capture HTTP code to debug connection issues
RESPONSE=$(curl --write-out "%{http_code}" --silent --output /tmp/response.json \
-H "Authorization: Token $DD_TOKEN" \
"$DD_URL/api/v2/products/?name=$PRODUCT_NAME")
# If response is not 200, print error
if [ "$RESPONSE" != "200" ]; then
echo "::error::Failed to query DefectDojo. HTTP Code: $RESPONSE"
cat /tmp/response.json
exit 1
fi
COUNT=$(cat /tmp/response.json | jq -r '.count')
if [ "$COUNT" = "0" ]; then
echo "Creating product '$PRODUCT_NAME'..."
curl -s -X POST "$DD_URL/api/v2/products/" \
-H "Authorization: Token $DD_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "name": "'"$PRODUCT_NAME"'", "description": "Auto-created by Gitea Actions", "prod_type": '$PRODUCT_TYPE_ID' }'
else
echo "Product '$PRODUCT_NAME' already exists."
fi
# --- 1. TRIVY (Dependencies & Misconfig) ---
- name: Install Trivy
run: |
sudo apt-get install wget apt-transport-https gnupg lsb-release -y
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add -
echo "deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | sudo tee -a /etc/apt/sources.list.d/trivy.list
sudo apt-get update && sudo apt-get install trivy -y
- name: Run Trivy (FS Scan)
run: |
trivy fs . --scanners vuln,misconfig --format json --output trivy-results.json --exit-code 0
- name: Upload Trivy to DefectDojo
env:
DD_URL: ${{ secrets.DD_URL }}
DD_TOKEN: ${{ secrets.DD_TOKEN }}
run: |
echo "Uploading Trivy results..."
# Generate today's date in YYYY-MM-DD format
TODAY=$(date +%Y-%m-%d)
HTTP_CODE=$(curl --write-out "%{http_code}" --output response.txt --silent -X POST "$DD_URL/api/v2/import-scan/" \
-H "Authorization: Token $DD_TOKEN" \
-F "active=true" \
-F "verified=true" \
-F "scan_type=Trivy Scan" \
-F "engagement_name=CI/CD Pipeline" \
-F "product_name=${{ github.repository }}" \
-F "scan_date=$TODAY" \
-F "auto_create_context=true" \
-F "file=@trivy-results.json")
if [[ "$HTTP_CODE" != "200" && "$HTTP_CODE" != "201" ]]; then
echo "::error::Upload Failed with HTTP $HTTP_CODE"
echo "--- SERVER RESPONSE ---"
cat response.txt
echo "-----------------------"
exit 1
else
echo "Upload Success!"
fi
# --- 2. GITLEAKS (Secrets) ---
- name: Install Gitleaks
run: |
wget -qO gitleaks.tar.gz https://github.com/gitleaks/gitleaks/releases/download/v8.18.0/gitleaks_8.18.0_linux_x64.tar.gz
tar -xzf gitleaks.tar.gz
sudo mv gitleaks /usr/local/bin/ && chmod +x /usr/local/bin/gitleaks
- name: Run Gitleaks
run: gitleaks detect --source . -v --report-path gitleaks-results.json --report-format json --no-git || true
- name: Upload Gitleaks to DefectDojo
env:
DD_URL: ${{ secrets.DD_URL }}
DD_TOKEN: ${{ secrets.DD_TOKEN }}
run: |
echo "Uploading Gitleaks results..."
TODAY=$(date +%Y-%m-%d)
HTTP_CODE=$(curl --write-out "%{http_code}" --output response.txt --silent -X POST "$DD_URL/api/v2/import-scan/" \
-H "Authorization: Token $DD_TOKEN" \
-F "active=true" \
-F "verified=true" \
-F "scan_type=Gitleaks Scan" \
-F "engagement_name=CI/CD Pipeline" \
-F "product_name=${{ github.repository }}" \
-F "scan_date=$TODAY" \
-F "auto_create_context=true" \
-F "file=@gitleaks-results.json")
if [[ "$HTTP_CODE" != "200" && "$HTTP_CODE" != "201" ]]; then
echo "::error::Upload Failed with HTTP $HTTP_CODE"
echo "--- SERVER RESPONSE ---"
cat response.txt
echo "-----------------------"
exit 1
else
echo "Upload Success!"
fi
# --- 3. SEMGREP (SAST) ---
- name: Install Semgrep (via pipx)
run: |
sudo apt-get install pipx -y
pipx install semgrep
# Add pipx binary path to GITHUB_PATH so next steps can see 'semgrep'
echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: Run Semgrep
run: semgrep scan --config=p/security-audit --config=p/owasp-top-ten --json --output semgrep-results.json . || true
- name: Upload Semgrep to DefectDojo
env:
DD_URL: ${{ secrets.DD_URL }}
DD_TOKEN: ${{ secrets.DD_TOKEN }}
run: |
echo "Uploading Semgrep results..."
TODAY=$(date +%Y-%m-%d)
HTTP_CODE=$(curl --write-out "%{http_code}" --output response.txt --silent -X POST "$DD_URL/api/v2/import-scan/" \
-H "Authorization: Token $DD_TOKEN" \
-F "active=true" \
-F "verified=true" \
-F "scan_type=Semgrep JSON Report" \
-F "engagement_name=CI/CD Pipeline" \
-F "product_name=${{ github.repository }}" \
-F "scan_date=$TODAY" \
-F "auto_create_context=true" \
-F "file=@semgrep-results.json")
if [[ "$HTTP_CODE" != "200" && "$HTTP_CODE" != "201" ]]; then
echo "::error::Upload Failed with HTTP $HTTP_CODE"
echo "--- SERVER RESPONSE ---"
cat response.txt
echo "-----------------------"
exit 1
else
echo "Upload Success!"
fi
+1 -1
View File
@@ -8,7 +8,7 @@ This page is currently deployed. [View the live website.](https://www.npmjs.com/
## Feedback and Bugs ## Feedback and Bugs
If you have feedback or a bug report, please feel free to open a GitHub issue! If you have feedback or a bug report, please [log a ticket on our forum](https://support.nhcarrigan.com).
## Contributing ## Contributing
+1 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@nhcarrigan/discord-analytics", "name": "@nhcarrigan/discord-analytics",
"version": "0.0.4", "version": "0.0.6",
"description": "Package that pairs with our logging tool to provide analytics for our Discord bots.", "description": "Package that pairs with our logging tool to provide analytics for our Discord bots.",
"type": "module", "type": "module",
"private": false, "private": false,
@@ -28,7 +28,6 @@
"discord.js": "^14.0.0" "discord.js": "^14.0.0"
}, },
"dependencies": { "dependencies": {
"flat": "6.0.1",
"node-schedule": "2.1.1" "node-schedule": "2.1.1"
} }
} }
-10
View File
@@ -14,9 +14,6 @@ importers:
discord.js: discord.js:
specifier: ^14.0.0 specifier: ^14.0.0
version: 14.22.1 version: 14.22.1
flat:
specifier: 6.0.1
version: 6.0.1
node-schedule: node-schedule:
specifier: 2.1.1 specifier: 2.1.1
version: 2.1.1 version: 2.1.1
@@ -1130,11 +1127,6 @@ packages:
resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
engines: {node: '>=16'} engines: {node: '>=16'}
flat@6.0.1:
resolution: {integrity: sha512-/3FfIa8mbrg3xE7+wAhWeV+bd7L2Mof+xtZb5dRDKZ+wDvYJK4WDYeIOuOhre5Yv5aQObZrlbRmk3RTSiuQBtw==}
engines: {node: '>=18'}
hasBin: true
flatted@3.3.3: flatted@3.3.3:
resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
@@ -3376,8 +3368,6 @@ snapshots:
flatted: 3.3.3 flatted: 3.3.3
keyv: 4.5.4 keyv: 4.5.4
flat@6.0.1: {}
flatted@3.3.3: {} flatted@3.3.3: {}
for-each@0.3.5: for-each@0.3.5:
+21
View File
@@ -0,0 +1,21 @@
# Security
# Do not execute any scripts of installed packages (project scripts still run)
ignoreDepScripts: true
# Do not automatically run pre/post scripts (e.g. preinstall, postbuild)
enablePrePostScripts: false
# Only allow packages published at least 10 days ago (reduces risk of compromised packages)
minimumReleaseAge: 14400
# Fail if a package's trust level has decreased compared to previous releases
trustPolicy: no-downgrade
# Ignore trust policy for packages published more than 1 year ago (predates provenance signing)
trustPolicyIgnoreAfter: 525960
# Fail if there are missing or invalid peer dependencies
strictPeerDependencies: true
# Prevent transitive dependencies from using exotic sources (git repos, direct tarball URLs)
blockExoticSubdeps: true
# Lockfile
# Allow the lockfile to be updated during install (set to true in CI for stricter reproducibility)
preferFrozenLockfile: false
+8 -5
View File
@@ -4,7 +4,6 @@
* @author Naomi Carrigan * @author Naomi Carrigan
*/ */
import { flatten } from "flat";
import { scheduleJob, type Job } from "node-schedule"; import { scheduleJob, type Job } from "node-schedule";
import type { Logger } from "@nhcarrigan/logger"; import type { Logger } from "@nhcarrigan/logger";
import type { Events, Client } from "discord.js"; import type { Events, Client } from "discord.js";
@@ -23,8 +22,7 @@ export class DiscordAnalytics {
public constructor( public constructor(
private readonly client: Client, private readonly client: Client,
private readonly logger: Logger, private readonly logger: Logger,
) { ) {}
}
/** /**
* Starts a CRON job to run at midnight (system time) daily. * Starts a CRON job to run at midnight (system time) daily.
@@ -36,7 +34,7 @@ export class DiscordAnalytics {
if (this.job) { if (this.job) {
return; return;
} }
this.job = scheduleJob("metrics", "0 * * * *", async() => { this.job = scheduleJob("metrics", "0 0 * * *", async() => {
try { try {
const fakeGuilds = await this.client.guilds.fetch(); const fakeGuilds = await this.client.guilds.fetch();
const guilds = await Promise.all( const guilds = await Promise.all(
@@ -88,6 +86,11 @@ export class DiscordAnalytics {
event: Events, event: Events,
payload: Record<string, unknown>, payload: Record<string, unknown>,
): Promise<void> { ): Promise<void> {
await this.logger.metric(event, 1, flatten(payload, { delimiter: "_" })); await this.logger.metric(
event,
1,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- We want to cast this to a specific type.
payload as Record<string, string | number>,
);
} }
} }