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
4 changes: 1 addition & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ This is a GraphQL API server for OSWatcher that uses Neo4j as the database and p
- GraphQL API with WebSocket subscriptions
- REST API for blob downloads (authorization check disabled by default -- see below)
- S3/MinIO integration for blob storage
- PostHog analytics proxy (production only)
- Windows registry value filtering for sensitive data (disabled by default -- see below)

**Open-source release note (2026-08-11):** blob download authorization and
Expand Down Expand Up @@ -60,7 +59,6 @@ The main server file that orchestrates all components:
- Sets up three endpoints:
- `/graphql` - GraphQL API with WebSocket subscriptions
- `/blob/:hash` - REST API for blob downloads
- `/events` - PostHog analytics proxy (production only)
- Implements security features:
- Rate limiting (100 requests/minute, 5 WebSocket connections per IP)
- Query complexity validation (max 100 fields)
Expand Down Expand Up @@ -126,7 +124,7 @@ See README.md for complete `.env` setup. Key variables:
- **Neo4j**: `NEO4J_URI`, `NEO4J_USER`, `NEO4J_PASSWORD`
- **Storage**: `OBJECT_STORAGE_URI`, `MINIO_ACCESS_KEY`, `MINIO_SECRET_KEY`, `MINIO_OBJECTS_BUCKET_NAME`
- **Authorization**: `RESTRICTED_BRANCH_NAME` - branch containing restricted blobs
- **Optional**: `POSTHOG_HOST`, `POSTHOG_PROJECT_API_KEY`, `NODE_ENV`, `SENSITIVE_REGISTRY_VALUES`
- **Optional**: `NODE_ENV`, `SENSITIVE_REGISTRY_VALUES`

## Testing Strategy
- Jest 30 with ts-jest for TypeScript support
Expand Down
4 changes: 0 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,6 @@ MINIO_OBJECTS_BUCKET_NAME=objects
# re-enabled (see docs/reference/access-restrictions.md)
RESTRICTED_BRANCH_NAME=

# Optional - PostHog Analytics (production only)
POSTHOG_HOST=https://us.i.posthog.com
POSTHOG_PROJECT_API_KEY=

# Optional - Environment
NODE_ENV=development

Expand Down
95 changes: 2 additions & 93 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
"@graphql-tools/schema": "^10.0.29",
"@neo4j/graphql": "^5.12.10",
"@neo4j/graphql-ogm": "5.11.4",
"axios": "1.12.2",
"cors": "^2.8.5",
"debug": "^4.4.3",
"dotenv": "17.2.2",
Expand Down
60 changes: 0 additions & 60 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import { expressMiddleware } from "@as-integrations/express5";
// that does matter is enforced server-side: rate limiting and restricted-branch
// filtering.
import cors from "cors";
import axios from "axios";
import { createServer } from "http";
import { WebSocketServer } from "ws";
import { useServer } from "graphql-ws/use/ws";
Expand Down Expand Up @@ -55,13 +54,6 @@ const env = cleanEnv(process.env, {
desc: "MinIO bucket name for blob storage",
}),
// Optional environment variables
POSTHOG_HOST: url({
default: "https://us.i.posthog.com",
desc: "PostHog analytics host",
}),
POSTHOG_PROJECT_API_KEY: str({
desc: "PostHog project API key",
}),
NODE_ENV: str({
choices: ["development", "production", "test"],
default: "development",
Expand Down Expand Up @@ -327,53 +319,6 @@ async function main() {
// Start Apollo Server
await server.start();

// PostHog events endpoint - only in production
if (isProduction && env.POSTHOG_PROJECT_API_KEY) {
app.use(
"/events",
cors(),
express.raw({ type: "*/*", limit: "10mb" }),
async (req: Request, res: Response) => {
try {
const posthogPath = req.originalUrl.replace(
"/events",
"",
);
const fullUrl = `${env.POSTHOG_HOST}${posthogPath}`;

// Forward the request exactly as received
const response = await axios({
method: req.method,
url: fullUrl,
data: req.body,
headers: {
...req.headers,
host: new URL(env.POSTHOG_HOST).host,
Authorization: `Bearer ${env.POSTHOG_PROJECT_API_KEY}`,
},
decompress: false,
});

// Forward the response exactly as received
res.status(response.status);
Object.entries(response.headers).forEach(
([key, value]) => {
res.setHeader(key, value);
},
);
res.send(response.data);
} catch (error: unknown) {
// Log full error for debugging but don't expose details
console.error("Error proxying PostHog event:", error);
res.status(502).json({
error: "Service temporarily unavailable",
});
}
},
);
console.log(`📊 PostHog events endpoint enabled in production`);
}

// Blob REST API - mounted before GraphQL
app.use(
"/blob",
Expand Down Expand Up @@ -406,11 +351,6 @@ async function main() {
console.log(`🚀 Server ready at http://localhost:4000/graphql`);
console.log(`🔌 WebSocket ready at ws://localhost:4000/graphql`);
console.log(`📦 Blob API ready at http://localhost:4000/blob`);
if (isProduction) {
console.log(
`📊 PostHog events endpoint ready at http://localhost:4000/events`,
);
}
});
} catch (error) {
console.error("❌ Server startup failed:", error);
Expand Down
Loading