Skip to content

Commit 96e8209

Browse files
committed
feat: add agent skills and security templates
Signed-off-by: zebbern <185730623+zebbern@users.noreply.github.com>
1 parent 771d68d commit 96e8209

227 files changed

Lines changed: 23372 additions & 3575 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/skills/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Project Agent Skills
2+
3+
Place skill folders here for automatic discovery by Sentris Agent Skills.
4+
5+
Each skill is a **directory** containing at least `SKILL.md`, plus optional supporting files (scripts, references, assets as text):
6+
7+
```
8+
.agents/skills/my-skill/
9+
SKILL.md
10+
scripts/
11+
references/
12+
```
13+
14+
Discovered folders also include `.claude/skills/`, `.github/skills/`, `.codex/skills/`, `.kimi/skills/`, and `.opencode/skills/` in this repository.
15+
16+
Import them from **Manage → Agent Skills → Workspace skills** in the UI, or zip a folder and use **Import zip**.

AGENTS.md

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,23 +30,27 @@ bun install && cp backend/.env.example backend/.env && cp worker/.env.example wo
3030
# Start dev environment (Docker infra + PM2 apps)
3131
just dev # Recommended (Linux/macOS/WSL)
3232
# OR:
33-
bun run dev # Cross-platform (requires bash)
33+
bun run dev # Cross-platform; respects SENTRIS_INSTANCE/.sentris-instance
3434
# OR (manual):
3535
docker compose -f docker/docker-compose.infra.yml -f docker/docker-compose.dev-ports.yml up -d
36-
pm2 startOrReload pm2.config.cjs --only sentris-frontend-0,sentris-backend-0,sentris-worker-0
36+
bun run pm2 -- startOrReload pm2.config.cjs --only sentris-frontend-0,sentris-backend-0,sentris-worker-0
3737

3838
# Status & logs
39-
just dev status # PM2 + Docker status
39+
just dev status # PM2 + Docker + runtime health status
4040
just dev logs # Tail app logs
41-
pm2 status # PM2 only
41+
bun run dev status # Cross-platform PM2 + Docker + runtime health status
42+
bun run dev logs # Cross-platform PM2 app logs
43+
bun run pm2 -- status # PM2 only via repo-local binary
4244
docker ps --filter name=sentris # Docker only
4345

4446
# Stop
4547
just dev stop # Stop PM2 + Docker
4648
# OR:
49+
bun run dev stop
50+
# OR:
4751
bun run dev:stop
4852
# OR (manual):
49-
pm2 delete sentris-frontend-0 sentris-backend-0 sentris-worker-0 && docker compose -f docker/docker-compose.infra.yml -f docker/docker-compose.dev-ports.yml down
53+
bun run pm2 -- delete sentris-frontend-0 sentris-backend-0 sentris-worker-0 && docker compose -f docker/docker-compose.infra.yml -f docker/docker-compose.dev-ports.yml down
5054

5155
# Health checks
5256
curl -sf http://localhost:3211/health # Backend liveness
@@ -61,7 +65,9 @@ just help # All commands
6165

6266
```bash
6367
just instance show # Print active instance number
68+
bun run instance show # Cross-platform fallback; initializes .sentris-instance to 0 if missing
6469
just instance use 5 # Set active instance for this workspace
70+
bun run instance use 5 # Cross-platform fallback
6571
```
6672

6773
**Instance env files**:
@@ -86,20 +92,23 @@ Full details: `docs/MULTI-INSTANCE-DEV.mdx`
8692

8793
Local development runs as **multiple app instances** (PM2) on top of **one shared Docker infra stack**.
8894

89-
- Shared infra (Docker Compose project `sentris-infra`): Postgres/Temporal/Redpanda/Redis/MinIO/Loki on fixed ports.
95+
- Shared infra (Docker Compose project `sentris`): Postgres/Temporal/Redpanda/Redis/MinIO/Loki on fixed ports.
9096
- Per-instance apps: `sentris-{frontend,backend,worker}-N`.
9197
- Isolation is via per-instance DB + Temporal namespace/task queue + Kafka topic suffixing + instance-scoped Kafka consumer groups/client IDs (not per-instance infra containers).
92-
- The workspace can have an **active instance** (stored in `.sentris-instance`, gitignored).
98+
- The workspace has an **active instance** (stored in `.sentris-instance`, gitignored). If neither `SENTRIS_INSTANCE` nor `.sentris-instance` exists, cross-platform tooling initializes `.sentris-instance` to `0` instead of silently guessing.
9399
- Instance env files are stored at `.instances/instance-N/{backend,worker,frontend}.env` and can be managed with `just instance-env ...`.
100+
- `bun run dev` initializes or repairs the active instance env files before PM2 starts, so PowerShell and Bash dev startup use the same instance-scoped env behavior.
101+
- `just dev`, `bun run dev`, `just dev clean`, and `bun run dev clean` prune oversized PM2 logs for the selected instance. The default cap is 64MB per app log file; override with `SENTRIS_PM2_LOG_MAX_BYTES`.
102+
- Backend PM2 dev watch includes `backend/src`, `backend/scripts/seed-templates`, and `packages/shared/src`; worker PM2 dev watch includes worker source plus shared runtime package sources. Keep this in sync when template validation inputs, seed catalogs, or shared execution contracts move.
94103

95104
**Agent rule:** before running any dev commands, ensure you’re targeting the intended instance.
96105

97-
- Always check: `just instance show`
106+
- Always check: `just instance show`; if `just` is unavailable, run `bun run instance show`. Either command initializes `.sentris-instance` to `0` when no active instance has been selected.
98107
- If the task is ambiguous (logs, curl, E2E, “run locally”, etc.), ask the user which instance to use.
99108
- If the user says “use instance N”, prefer either:
100109
- `just instance use N` then run `just dev` / `bun run test:e2e`, or
101-
- explicit env override (`SENTRIS_INSTANCE=N just dev ...`) for one-off commands.
102-
- Local maintenance scripts that mutate or inspect local Postgres data must use the shared script runtime (`scripts/lib/local-script-runtime.ts`) instead of reading `DATABASE_URL` directly. `DATABASE_URL` is for the running app process, Drizzle CLI, and explicit app env files; local scripts should target `SENTRIS_INSTANCE` / `.sentris-instance` by default and only use script-specific overrides such as `TEMPLATE_SEED_DATABASE_URL` or the generic `SENTRIS_SCRIPT_DATABASE_URL`.
110+
- explicit env override (`SENTRIS_INSTANCE=N just dev ...` or `SENTRIS_INSTANCE=N bun run dev`) for one-off commands.
111+
- Local maintenance scripts that mutate or inspect local Postgres data must use the shared script runtime (`scripts/lib/local-script-runtime.ts`) instead of reading `DATABASE_URL` directly. `DATABASE_URL` is for the running app process, production Drizzle runs, and explicit app env files; local scripts should target `SENTRIS_INSTANCE` / `.sentris-instance` by default and only use script-specific overrides such as `TEMPLATE_SEED_DATABASE_URL`, `DRIZZLE_DATABASE_URL`, or the generic `SENTRIS_SCRIPT_DATABASE_URL`.
103112
- Local scripts that start or inspect Temporal workflows must also use `getScriptTemporalTarget()` from the shared script runtime instead of reading `TEMPORAL_NAMESPACE` / `TEMPORAL_TASK_QUEUE` directly. Use script-specific `*_TEMPORAL_NAMESPACE` + `*_TEMPORAL_TASK_QUEUE` variables or `SENTRIS_SCRIPT_TEMPORAL_NAMESPACE` + `SENTRIS_SCRIPT_TEMPORAL_TASK_QUEUE` when intentionally targeting another namespace.
104113
- Maintenance scripts must print the target database and/or Temporal target before mutating data or starting workflows.
105114

@@ -111,8 +120,9 @@ Local development runs as **multiple app instances** (PM2) on top of **one share
111120

112121
#### E2E tests
113122

114-
- E2E targets the backend for `SENTRIS_INSTANCE` (or the active instance).
115-
- When asked to run E2E, confirm the instance and ensure that instance is running: `SENTRIS_INSTANCE=N just dev start` (or `just instance use N` then `just dev start`).
123+
- E2E targets the backend for `SENTRIS_INSTANCE`, then legacy `E2E_INSTANCE`, then the active instance.
124+
- `bun run test:e2e*` uses the cross-platform Node runner at `scripts/e2e-test.js`; do not reintroduce Bash-only active instance lookup in package scripts.
125+
- When asked to run E2E, confirm the instance and ensure that instance is running: `SENTRIS_INSTANCE=N just dev start`, `SENTRIS_INSTANCE=N bun run dev`, or `just instance use N` then `just dev start`.
116126

117127
#### Keep docs in sync
118128

@@ -136,7 +146,7 @@ bun run lint # Lint
136146
### Database
137147

138148
```bash
139-
just db-reset # Reset database
149+
just db-reset # Reset active instance database
140150
bun --cwd backend run migration:push # Push schema
141151
bun --cwd backend run db:studio # View data
142152
```

README.md

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ Get the dev environment running in 3 steps:
5454
git clone https://github.com/zebbern/Sentris.git
5555
cd Sentris
5656
bun run setup # Install deps + create .env files
57-
bun run dev # Start Docker infra + all apps
57+
bun run dev # Start Docker infra + all apps for the active instance
5858
```
5959

6060
Once running:
@@ -66,33 +66,34 @@ Once running:
6666
| Temporal UI | <http://localhost:8081> |
6767

6868
```bash
69-
bun run dev:stop # Stop everything (PM2 + Docker)
70-
bun run dev:fe # Frontend-only dev (no Docker needed)
71-
pm2 logs # View application logs
72-
pm2 status # Check process status
69+
bun run dev status # PM2 + Docker + runtime health for the active instance
70+
bun run dev logs # View application logs for the active instance
71+
bun run dev stop # Stop the active instance apps; instance 0 also stops Docker infra
72+
bun run dev:stop # Compatibility alias for bun run dev stop
73+
bun run dev:fe # Frontend-only dev (no Docker needed)
7374
```
7475

75-
> **Advanced:** `just dev` remains available for multi-instance development, Clerk auth auto-detection, and TLS certificate generation. See the [Multi-Instance Guide](docs/MULTI-INSTANCE-DEV.mdx) for details.
76+
> **Advanced:** `bun run dev` and `just dev` both support multi-instance development and active instance env files. `just dev` also handles Clerk auth auto-detection and TLS certificate generation. See the [Multi-Instance Guide](docs/MULTI-INSTANCE-DEV.mdx) for details.
7677
7778
---
7879

7980
## Capabilities
8081

8182
### Integrated Tooling
8283

83-
25 security components wrapping industry-standard open-source tools:
84+
29 security components wrapping industry-standard open-source tools:
8485

8586
- **Discovery & Recon**: `Subfinder`, `Amass`, `DNSX`, `Naabu`, `HTTPx`, `Katana`, `theHarvester`, `ShuffleDNS`
8687
- **Vulnerability Scanning**: `Nuclei`, `Trivy`, `Semgrep`, `Checkov`, `TestSSL`
8788
- **Secret Detection**: `TruffleHog`
88-
- **Threat Intelligence**: `AbuseIPDB`, `VirusTotal`, `YARA`
89+
- **Threat Intelligence**: `AbuseIPDB`, `VirusTotal`, `YARA`, `NPM Registry Intel`
8990
- **Web Security**: `Ffuf`, `Wafw00f`, `Prowler`, `Supabase Scanner`
9091
- **Notifications**: `Notify` (Slack, Discord, Telegram, Email)
9192
- **Utility**: `JSON Transform`, `Logic Scripts`, `HTTP Requests`
9293

9394
### Template Library
9495

95-
- **10 live-validated security workflow templates** focused on bug bounty recon, takeover triage, exposed services, repository risk, secrets, and CVE/dependency research.
96+
- **30 live-validated security workflow templates** focused on bug bounty recon, takeover triage, exposed services, repository risk, secrets, and CVE/dependency research.
9697
- **One-click deployment**: Browse, preview, and create workflows from templates instantly.
9798
- **Community publishing**: Share your workflows as templates via GitHub PR with automatic secret sanitization.
9899
- **Maintainer preflight**: Run `bun run template-library:check` before changing templates, then `bun run template-library:verify` before handoff. These use the validation ledger and focused template tests, so unchanged templates are not rerun.
@@ -133,11 +134,16 @@ Run multiple isolated dev instances on one machine for parallel feature work:
133134

134135
```bash
135136
# Instance 0 (default)
137+
just instance show
138+
bun run instance show
136139
just dev
140+
bun run dev
137141

138142
# Switch active workspace instance
139143
just instance use 1
144+
bun run instance use 1
140145
just dev
146+
bun run dev
141147

142148
# Manage per-instance env files
143149
just instance-env init 1

backend/drizzle.config.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
import { defineConfig } from 'drizzle-kit';
22
import 'dotenv/config';
3+
import { getDrizzleDatabaseTarget } from '../scripts/lib/local-script-runtime';
34

4-
const connectionString = process.env.DATABASE_URL;
5-
6-
if (!connectionString) {
7-
throw new Error('DATABASE_URL is not set');
8-
}
5+
const databaseTarget = getDrizzleDatabaseTarget({
6+
overrideEnvVar: 'DRIZZLE_DATABASE_URL',
7+
});
98

109
export default defineConfig({
1110
dialect: 'postgresql',
1211
schema: './src/database/schema',
1312
out: './drizzle',
1413
dbCredentials: {
15-
url: connectionString,
14+
url: databaseTarget.connectionString,
1615
},
1716
});

backend/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"build": "bun build src/main.ts --outdir build --target bun --external @nestjs/websockets/socket-module --external nats --external @nestjs/mongoose --external @mikro-orm/core --external @nestjs/typeorm/dist/common/typeorm.utils --external @nestjs/sequelize/dist/common/sequelize.utils --external class-transformer/storage",
1111
"test": "bun test",
1212
"lint": "eslint .",
13-
"typecheck": "tsc --noEmit",
13+
"typecheck": "tsc --build",
1414
"generate:openapi": "bun scripts/generate-openapi.ts",
1515
"migration:push": "bun x drizzle-kit push",
1616
"migration:smoke": "bun scripts/migration-smoke.ts",
@@ -41,6 +41,7 @@
4141
"@temporalio/client": "^1.14.1",
4242
"@temporalio/worker": "^1.14.1",
4343
"@temporalio/workflow": "^1.14.1",
44+
"adm-zip": "^0.5.17",
4445
"ai": "^6.0.49",
4546
"amqp-connection-manager": "^5.0.0",
4647
"amqplib": "^0.10.9",
@@ -69,6 +70,7 @@
6970
"devDependencies": {
7071
"@eslint/js": "^9.39.2",
7172
"@nestjs/testing": "^10.4.22",
73+
"@types/adm-zip": "^0.5.8",
7274
"@types/bcryptjs": "^3.0.0",
7375
"@types/cookie-parser": "^1.4.10",
7476
"@types/express": "^5.0.6",

backend/scripts/__tests__/script-database-target.test.ts

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { dirname, join, resolve } from 'path';
44
import { fileURLToPath } from 'url';
55
import { afterEach, describe, expect, it } from 'bun:test';
66
import {
7+
getDrizzleDatabaseTarget,
78
formatTemporalTarget,
89
getScriptDatabaseTarget,
910
getScriptTemporalTarget,
@@ -22,6 +23,12 @@ function createRootWithInstance(instance: string): string {
2223
return root;
2324
}
2425

26+
function createRootWithoutInstance(): string {
27+
const root = mkdtempSync(join(tmpdir(), 'sentris-script-db-target-'));
28+
cleanupDirs.push(root);
29+
return root;
30+
}
31+
2532
describe('script database target resolver', () => {
2633
afterEach(() => {
2734
for (const dir of cleanupDirs) {
@@ -78,14 +85,64 @@ describe('script database target resolver', () => {
7885
expect(target.source).toBe('env:TEMPLATE_SEED_DATABASE_URL');
7986
});
8087

81-
it('falls back to instance 0 when no instance is configured', () => {
82-
const target = getScriptDatabaseTarget({ env: {}, repoRoot: createRootWithInstance('') });
88+
it('initializes the active instance marker to 0 when no instance is configured', () => {
89+
const root = createRootWithoutInstance();
90+
const target = getScriptDatabaseTarget({ env: {}, repoRoot: root });
8391

8492
expect(target.connectionString).toBe(
8593
'postgresql://sentris:sentris@localhost:5433/sentris_instance_0',
8694
);
8795
expect(target.databaseName).toBe('sentris_instance_0');
88-
expect(target.source).toBe('default:instance-0');
96+
expect(target.source).toBe('file:.sentris-instance');
97+
expect(readFileSync(join(root, '.sentris-instance'), 'utf8')).toBe('0\n');
98+
});
99+
100+
it('uses the active instance for local Drizzle migrations instead of stale DATABASE_URL', () => {
101+
const target = getDrizzleDatabaseTarget({
102+
env: {
103+
DATABASE_URL: 'postgresql://sentris:sentris@localhost:5433/sentris',
104+
},
105+
repoRoot: createRootWithInstance('6'),
106+
});
107+
108+
expect(target.connectionString).toBe(
109+
'postgresql://sentris:sentris@localhost:5433/sentris_instance_6',
110+
);
111+
expect(target.databaseName).toBe('sentris_instance_6');
112+
expect(target.source).toBe('file:.sentris-instance');
113+
expect(target.ignoredDatabaseUrl).toBe(true);
114+
});
115+
116+
it('allows an explicit Drizzle database override for intentional local targeting', () => {
117+
const target = getDrizzleDatabaseTarget({
118+
env: {
119+
DATABASE_URL: 'postgresql://sentris:sentris@localhost:5433/sentris',
120+
DRIZZLE_DATABASE_URL: 'postgresql://sentris:sentris@localhost:5433/manual_drizzle',
121+
},
122+
repoRoot: createRootWithInstance('6'),
123+
});
124+
125+
expect(target.connectionString).toBe(
126+
'postgresql://sentris:sentris@localhost:5433/manual_drizzle',
127+
);
128+
expect(target.databaseName).toBe('manual_drizzle');
129+
expect(target.source).toBe('env:DRIZZLE_DATABASE_URL');
130+
expect(target.ignoredDatabaseUrl).toBe(true);
131+
});
132+
133+
it('uses DATABASE_URL for production Drizzle migrations', () => {
134+
const target = getDrizzleDatabaseTarget({
135+
env: {
136+
NODE_ENV: 'production',
137+
DATABASE_URL: 'postgresql://sentris:sentris@db:5432/prod_sentris',
138+
},
139+
repoRoot: createRootWithInstance('6'),
140+
});
141+
142+
expect(target.connectionString).toBe('postgresql://sentris:sentris@db:5432/prod_sentris');
143+
expect(target.databaseName).toBe('prod_sentris');
144+
expect(target.source).toBe('env:DATABASE_URL');
145+
expect(target.ignoredDatabaseUrl).toBe(false);
89146
});
90147

91148
it('rejects invalid instance values before building a database URL', () => {
@@ -156,6 +213,14 @@ describe('script database target resolver', () => {
156213
}
157214
});
158215

216+
it('keeps Drizzle CLI database targeting on the shared resolver', () => {
217+
const source = readFileSync(join(repoRoot, 'backend', 'drizzle.config.ts'), 'utf-8');
218+
219+
expect(source).toContain('getDrizzleDatabaseTarget');
220+
expect(source).toContain('DRIZZLE_DATABASE_URL');
221+
expect(source).not.toContain('process.env.DATABASE_URL');
222+
});
223+
159224
it('prevents all local database maintenance scripts from reading DATABASE_URL directly', () => {
160225
const databaseScriptFiles = [
161226
join(scriptsDir, 'delete-all-workflow-runs.ts'),
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { readFileSync } from 'node:fs';
2+
import { join, resolve } from 'node:path';
3+
import { describe, expect, it } from 'bun:test';
4+
5+
const backendRoot = resolve(import.meta.dir, '..', '..');
6+
7+
describe('setup-opensearch script', () => {
8+
it('uses the OpenSearch client DynamicMapping string literal for object mappings', () => {
9+
const source = readFileSync(join(backendRoot, 'scripts', 'setup-opensearch.ts'), 'utf8');
10+
11+
expect(source).toContain("dynamic: 'true'");
12+
expect(source).not.toContain('dynamic: true');
13+
});
14+
});

0 commit comments

Comments
 (0)