diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
new file mode 100644
index 0000000..0f4376d
--- /dev/null
+++ b/.github/copilot-instructions.md
@@ -0,0 +1,40 @@
+# GitHub Copilot Instructions
+
+This repository implements a NIP-42 authentication proxy for Nostr relays. It adds client authentication support to upstream relays (like strfry) that don't natively support it.
+
+## Guidelines
+
+- Use Conventional Commits for titles and commit messages (e.g., `feat(scope): message`).
+- Ensure pull requests include a clear description and test results.
+- Reference related issues using `Closes #123` when applicable.
+- Run `mvn -q verify` before committing code.
+- Document new features in the README or related docs.
+- Maintain Java 21 compatibility and update `pom.xml` for new dependencies.
+- Remove unused imports.
+
+## Relevant Nostr NIPs
+
+When implementing features, consult the relevant NIP specifications:
+
+- [NIP-01](https://github.com/nostr-protocol/nips/blob/master/01.md) - Basic protocol flow (events, subscriptions, messages)
+- [NIP-42](https://github.com/nostr-protocol/nips/blob/master/42.md) - Client authentication (core to this proxy)
+- [NIP-04](https://github.com/nostr-protocol/nips/blob/master/04.md) - Encrypted direct messages (protected kind)
+- [NIP-17](https://github.com/nostr-protocol/nips/blob/master/17.md) - Private direct messages (protected kinds 14, 15)
+- [NIP-46](https://github.com/nostr-protocol/nips/blob/master/46.md) - Nostr Connect / remote signing (protected kind 24133)
+- [NIP-47](https://github.com/nostr-protocol/nips/blob/master/47.md) - Wallet Connect (protected kinds 23194-23197)
+- [NIP-59](https://github.com/nostr-protocol/nips/blob/master/59.md) - Gift wraps (protected kinds 13, 1059)
+
+## Architecture
+
+```
+Client (port 7777) ←→ NIP-42 Auth Proxy ←→ Upstream Relay (strfry @ 7778)
+```
+
+Key components:
+- `auth/` - NIP-42 authentication and BIP-340 signature verification
+- `session/` - WebSocket session management and state tracking
+- `access/` - Access control (open, allowlist, blocklist modes)
+- `handler/` - WebSocket message handling and upstream routing
+- `upstream/` - Upstream relay client connections
+
+These instructions help GitHub Copilot produce code that respects the repository's conventions and protocol requirements.
\ No newline at end of file
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..5de893e
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,28 @@
+version: 2
+
+# Private registries used by Dependabot
+registries:
+ maven-releases:
+ type: maven-repository
+ url: https://maven.398ja.xyz/releases
+ username: ${{secrets.MVN_USER}}
+ password: ${{secrets.MVN_PASSWORD}}
+ maven-snapshots:
+ type: maven-repository
+ url: https://maven.398ja.xyz/snapshots
+ username: ${{secrets.MVN_USER}}
+ password: ${{secrets.MVN_PASSWORD}}
+
+updates:
+ - package-ecosystem: "maven"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ open-pull-requests-limit: 5
+ target-branch: "develop"
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ open-pull-requests-limit: 5
+ target-branch: "develop"
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000..2fb9275
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,17 @@
+## Summary
+
+
+## What changed?
+
+
+## Breaking changes
+- [ ] BREAKING: this change introduces breaking API or behavior
+
+## Review focus
+
+
+## Checklist
+- [ ] Tests added or updated
+- [ ] `mvn -q verify` passes
+- [ ] Documentation updated (README, docs, etc.)
+- [ ] No unused imports
\ No newline at end of file
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..dad8912
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,46 @@
+name: CI
+
+on:
+ push:
+ branches: [main, develop]
+ pull_request:
+ branches: [main, develop]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v5
+ - uses: actions/setup-java@v5
+ with:
+ java-version: '21'
+ distribution: 'temurin'
+ cache: 'maven'
+ - name: Build with Maven
+ run: mvn -q verify
+ - name: Upload surefire reports
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: surefire-reports
+ path: '**/target/surefire-reports'
+ if-no-files-found: ignore
+ - name: Upload JaCoCo coverage
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: jacoco-exec
+ path: '**/target/jacoco.exec'
+ if-no-files-found: ignore
+ - name: Upload coverage to Codecov
+ uses: codecov/codecov-action@v5
+ with:
+ files: '**/target/site/jacoco/jacoco.xml'
+ token: ${{ secrets.CODECOV_TOKEN }}
+ - name: Upload test results to Codecov
+ if: ${{ !cancelled() }}
+ uses: codecov/test-results-action@v1
+ with:
+ token: ${{ secrets.CODECOV_TOKEN }}
+ files: '**/target/surefire-reports/*.xml'
+
diff --git a/.github/workflows/enforce_conventional_commits.yml b/.github/workflows/enforce_conventional_commits.yml
new file mode 100644
index 0000000..89dc3bd
--- /dev/null
+++ b/.github/workflows/enforce_conventional_commits.yml
@@ -0,0 +1,29 @@
+name: Conventional Commits
+
+permissions:
+ contents: read
+ pull-requests: read
+
+on:
+ pull_request:
+ branches:
+ - main
+ - develop
+
+jobs:
+ commit-lint:
+ name: Verify Conventional Commits
+ if: (github.event_name == 'pull_request' && !startsWith(github.event.pull_request.head.ref, 'release-please--'))
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v5
+ with:
+ fetch-depth: 0
+
+ - name: Check Commit Messages
+ uses: wagoid/commitlint-github-action@v6
+ with:
+ configFile: .commitlintrc.yml
+ token: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
diff --git a/.github/workflows/google-java-format.yml b/.github/workflows/google-java-format.yml
new file mode 100644
index 0000000..536a472
--- /dev/null
+++ b/.github/workflows/google-java-format.yml
@@ -0,0 +1,26 @@
+name: Format
+
+on:
+ pull_request:
+ branches:
+ - main
+
+permissions:
+ contents: write
+
+jobs:
+
+ formatting:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v5 # v2 minimum required
+ - name: Set up JDK 21
+ uses: actions/setup-java@v5
+ with:
+ distribution: 'temurin'
+ java-version: '21'
+ - uses: axel-op/googlejavaformat-action@v4
+ with:
+ args: "--replace"
+ # Recommended if you use MacOS:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/qodana.yml b/.github/workflows/qodana.yml
new file mode 100644
index 0000000..82fee59
--- /dev/null
+++ b/.github/workflows/qodana.yml
@@ -0,0 +1,28 @@
+name: Qodana
+on:
+ workflow_dispatch:
+ pull_request:
+ push:
+ branches:
+ - main
+ - develop
+
+jobs:
+ qodana:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ pull-requests: write
+ checks: write
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ ref: ${{ github.event.pull_request.head.sha }} # to check out the actual pull request commit, not the merge commit
+ fetch-depth: 0 # a full history is required for pull request analysis
+ - name: 'Qodana Scan'
+ uses: JetBrains/qodana-action@v2025.2
+ with:
+ pr-mode: false
+ env:
+ QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
+ QODANA_ENDPOINT: 'https://qodana.cloud'
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 95a79dc..d5781da 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,3 +32,4 @@ logs/
*.swp
*.swo
*~
+/.claude/
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..b5fb607
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,46 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+
+### Fixed
+
+- Add null checks for upstream response body in NIP-11 handlers
+- Use system Maven instead of wrapper in CI workflow
+
+### Changed
+
+- Update `actions/setup-java` to v5 in google-java-format workflow
+
+### Security
+
+- Upgrade Spring Boot 3.5.5 → 3.5.8 (fixes CVE-2025-41249, CVE-2025-41254, CVE-2025-55754, CVE-2025-11226)
+- Upgrade commons-lang3 3.17.0 → 3.18.0 (fixes CVE-2025-48924)
+- Upgrade commons-compress 1.24.0 → 1.27.1 (fixes CVE-2024-25710, CVE-2024-26308)
+
+## [0.1.1] - 2026-01-06
+
+### Fixed
+
+- Enforce auth requirement for REQ messages when `require-auth=true`
+
+## [0.1.0] - 2026-01-06
+
+### Added
+
+- Initial standalone nostr-auth-proxy project
+- NIP-42 client authentication with BIP-340 signature verification
+- WebSocket proxy to upstream Nostr relays (strfry)
+- Session management with per-pubkey connection limits
+- Access control modes: open, allowlist, blocklist
+- Protection for privacy-sensitive event kinds (DMs, wallet data, etc.)
+- Spring Boot Actuator health checks and Prometheus metrics
+- Docker image support via Jib
+
+[Unreleased]: https://github.com/tcheeric/nostr-auth-proxy/compare/v0.1.1...HEAD
+[0.1.1]: https://github.com/tcheeric/nostr-auth-proxy/compare/v0.1.0...v0.1.1
+[0.1.0]: https://github.com/tcheeric/nostr-auth-proxy/releases/tag/v0.1.0
diff --git a/pom.xml b/pom.xml
index 2461547..f817011 100644
--- a/pom.xml
+++ b/pom.xml
@@ -8,13 +8,13 @@
org.springframework.boot
spring-boot-starter-parent
- 3.5.5
+ 3.5.8
xyz.tcheeric
nostr-auth-proxy
- 0.1.0
+ 0.1.1
jar
Nostr Auth Proxy
@@ -26,8 +26,21 @@
1.81
1.20.4
4.3.0
+
+ 3.18.0
+
+
+
+
+ org.apache.commons
+ commons-compress
+ 1.27.1
+
+
+
+
reposilite-releases
diff --git a/src/main/java/xyz/tcheeric/nostr/authproxy/filter/Nip11FilterConfig.java b/src/main/java/xyz/tcheeric/nostr/authproxy/filter/Nip11FilterConfig.java
index 66c61c2..12cb6b4 100644
--- a/src/main/java/xyz/tcheeric/nostr/authproxy/filter/Nip11FilterConfig.java
+++ b/src/main/java/xyz/tcheeric/nostr/authproxy/filter/Nip11FilterConfig.java
@@ -111,9 +111,10 @@ private void handleNip11Request(HttpServletResponse response) throws IOException
request,
String.class);
+ String body = upstreamResponse.getBody();
response.setContentType("application/nostr+json");
response.setStatus(HttpServletResponse.SC_OK);
- response.getWriter().write(upstreamResponse.getBody());
+ response.getWriter().write(body != null ? body : "{}");
response.getWriter().flush();
log.info("nip11_response_sent");
diff --git a/src/main/java/xyz/tcheeric/nostr/authproxy/handler/Nip11HandshakeInterceptor.java b/src/main/java/xyz/tcheeric/nostr/authproxy/handler/Nip11HandshakeInterceptor.java
index 4363622..9ac1e33 100644
--- a/src/main/java/xyz/tcheeric/nostr/authproxy/handler/Nip11HandshakeInterceptor.java
+++ b/src/main/java/xyz/tcheeric/nostr/authproxy/handler/Nip11HandshakeInterceptor.java
@@ -83,8 +83,9 @@ private void handleNip11Request(ServerHttpResponse response) throws IOException
request,
String.class);
+ String body = upstreamResponse.getBody();
response.getHeaders().set("Content-Type", "application/nostr+json");
- response.getBody().write(upstreamResponse.getBody().getBytes());
+ response.getBody().write(body != null ? body.getBytes() : "{}".getBytes());
response.getBody().flush();
log.info("nip11_response_sent");