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
1 change: 1 addition & 0 deletions .codacy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ exclude_paths:
- "hooks/**"
- "src/main/java/gg/effront/anarcry/scanner/**"
- "src/main/java/gg/effront/anarcry/exploit/**"
- "src/main/java/gg/effront/anarcry/apiclient/auth/**"
- "src/main/java/gg/effront/anarcry/EnvLoader.java"
- "SECURITY.md"
87 changes: 72 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
| (15 providers) + circuit breaker. OAST blind detection infra. |
| TUI + Swing GUI + AI Assistant. |
| Built-in API Client (Bruno-inspired) with collections, environments, |
| auth injectors, variable interpolation, runner + assertions. |
| auth injectors, variable interpolation, scripting, runner + assertions. |
+===========================================================================+
```

Expand Down Expand Up @@ -123,7 +123,7 @@

```bash
./gradlew shadowJar
java -jar build/libs/anarcry-2.2.0.jar
java -jar build/libs/anarcry-2.2.1.jar
```

Or use the launcher (auto-builds if source changed):
Expand All @@ -141,7 +141,7 @@ With `.env.local` for pre-configured targets:
Launch in GUI mode (Swing + FlatLaf):

```bash
java -jar build/libs/anarcry-2.2.0.jar --gui
java -jar build/libs/anarcry-2.2.1.jar --gui
```

---
Expand All @@ -163,7 +163,8 @@ java -jar build/libs/anarcry-2.2.0.jar --gui
| Knowledge Base | knowledge_base.json (34 entries, CWE+CVSS+VRT) |
| | knowledge-base/*.md (27 enriched entries) |
| AI Assistant | Multi-provider (Claude, OpenAI, Gemini, Ollama) |
| GUI | Swing + FlatLaf + RSyntaxTextArea + JFreeChart |
| GUI | Swing + FlatLaf (dark/light) + RSyntaxTextArea |
| | + JFreeChart |
| Test | JUnit 5.11.3 |
+======================+====================================================+
```
Expand Down Expand Up @@ -222,7 +223,7 @@ without knowing their credentials.

## 05 - Interface

Two interfaces: **TUI** (Lanterna, terminal-native) and **GUI** (Swing + FlatLaf, desktop). Both offer the same core functionality: Target scan, Repeater, Findings, SiteMap, Export. The GUI adds a Dashboard with severity chart (JFreeChart), Preferences dialog, and the AI Assistant chat view.
Two interfaces: **TUI** (Lanterna, terminal-native) and **GUI** (Swing + FlatLaf, desktop). Both offer the same core functionality: Target scan, Repeater, Findings, SiteMap, Export. The GUI adds a Dashboard with severity chart (JFreeChart), Preferences dialog with persistent settings (`~/.anarcry/settings.json`), dark/light theme, and the AI Assistant chat view.

### TUI

Expand Down Expand Up @@ -318,6 +319,11 @@ Test suite covers: JWT probes (alg:none, weak secret, RS256->HS256, JKU, null si

## 07 - Architecture



<details>
<summary><kbd> wanna see? (click to expand)</kbd></summary>

```
src/main/java/gg/effront/anarcry/
+-- Main.java Entry point
Expand Down Expand Up @@ -388,6 +394,7 @@ src/main/java/gg/effront/anarcry/
| +-- SaveDocTool Save markdown documents to ~/.anarcry/reports/
+-- gui/
| +-- AnarCryGui.java Main Swing window (FlatLaf theme)
| +-- AppSettings.java Singleton: ~/.anarcry/settings.json persistence (Gson)
| +-- GuiController.java View orchestration and scan lifecycle
| +-- PrivacyFilter.java URL/JWT/IP redaction (Ctrl+Shift+P)
| +-- views/
Expand All @@ -399,10 +406,12 @@ src/main/java/gg/effront/anarcry/
| | +-- ReportView Report generation and export
| | +-- ApiClientView API Client with collection tree + request tabs
| | +-- AssistantView AI Assistant chat with streaming + tools
| | +-- PreferencesDialog Settings dialog with persistence (10 categories)
| | +-- EnvironmentEditorDialog API Client environment variable editor
| | +-- GlobalSearchDialog Cross-tab search (Ctrl+F)
| | +-- GlobalSearchDialog Cross-tab search (Ctrl+K)
| | +-- ExportDialog Multi-format export (MD, JSON, SARIF, H1, BC)
| +-- components/ Reusable GUI components
| | +-- StatusBarIcons.java Tabler Icons loader (PNG, 16x16)
| +-- theme/ FlatLaf theme customization
+-- tui/
+-- AnarCryTui.java Main TUI window (Lanterna)
Expand Down Expand Up @@ -433,6 +442,8 @@ ScanRunner.run(ScanContext)
-> fire onFinding callback for each result
-> fire onScanFinish when all modules complete
```
</details>


---

Expand Down Expand Up @@ -499,23 +510,73 @@ Built-in API client inspired by [Bruno](https://www.usebruno.com/) for manual AP
| (built-in > env > collection > folder > request > |
| runtime) + circular reference detection |
| Request Builder -- method, URL, headers, body (JSON/form/multipart/ |
| XML/GraphQL/file), query/path params |
| XML/GraphQL/file), query/path params, descriptions |
| Scripting -- pre-request + post-response + tests (GraalJS) |
| Runner -- sequential execution with variable chaining |
| Assertions -- declarative validation (status, JSON path, header, |
| regex, timing, size) |
| Import -- Postman v2.1, OpenAPI 3.0, Bruno .bru, cURL |
| Export -- Markdown + JSON collection run reports |
| Import -- Postman v2.1, OpenAPI 3.0, Bruno .bru, Insomnia, |
| cURL |
| Export -- cURL command, Markdown + JSON collection run reports |
| Proxy -- HTTP/HTTPS/SOCKS5 with auth (via Preferences) |
+===========================================================================+
```

**GUI (F8):** Collection tree, request tabs (color-coded by method), environment editor dialog, right-click context menu (new/duplicate/delete/rename), Send (Ctrl+Enter), Run All.
**GUI (F8):** Collection tree, request tabs (color-coded by method), environment editor dialog, right-click context menu (new/duplicate/delete/rename), pre-request/post-response/test scripts (GraalJS), cURL export, keyboard shortcuts (Ctrl+Enter send, Ctrl+N new request), tab content badges, query/path param split, global search integration. Proxy and SSL configured via Preferences.

**TUI (F8):** Simplified collection list, request builder, environment selector, Send/Run All, response viewer.

---

## 11 - Changelog

### v2.2.1

**GUI + API Client bugfixes, Preferences persistence, light theme**

Preferences:
- **AppSettings persistence**: singleton saves all preferences to `~/.anarcry/settings.json` via Gson -- loads on startup, saves on Apply, survives restarts
- **Light theme**: AnarCryLaf.installLight() with theme-aware color helpers (fg(), bg(), border(), isDark()), saved theme applied on app launch
- **Preferences rework**: tab reorder (General, Themes, Display, Proxy, Certificates, Notifications, Keybindings, License, Support, About -- info tabs grouped at end)
- **Check for Updates**: dedicated sidebar button queries GitHub Releases API, compares with Version.get(), offers to open release page
- **Proxy centralized**: proxy config moved from API Client sidebar to Preferences -- single source of truth, ApiClientView receives ProxySelector/SSLContext via setNetworkConfig()
- **Certificates panel**: client cert table (Enable, Domain, Type PEM/PKCS12, Cert/PFX File, Key File, Passphrase) with Browse buttons, custom CA certificate with keep-default-CA toggle
- **SSL context builder**: PEM private key loading (RSA/EC/Ed25519), PKCS12 keystore, custom CA merge with JVM defaults, trust-all toggle for SSL verification off
- **Support panel**: support-art.png image, documentation and repository hyperlinks with Desktop.browse()
- **Display defaults**: resolution combo defaults to 1920x1080, saved resolution applied on app launch (including Maximized state)

API Client enhancements:
- **Auth interpolation**: `{{var}}` resolved in all 12 AuthConfig sealed interface variants (Bearer, Basic, Digest, API Key, OAuth2 CC/PKCE/Password/Implicit, WSSE, AWS SigV4, NTLM, custom)
- **GraalJS scripting**: pre-request, post-response, and test scripts with console output panel
- **cURL export**: generate cURL command from any request (CurlGenerator)
- **Insomnia import**: InsomniaImporter for Insomnia v4 JSON collections
- **Tab content badges**: "Headers 1", "Body *", "Script *" indicators on editor tabs
- **Query/Path params split**: separate KvTableEditors for query and path parameters
- **Keyboard shortcuts**: Ctrl+Enter send, Ctrl+N new request, Ctrl+E edit environment, empty state panel with shortcut hints
- **Description column**: KvTableEditor now has 4 columns (checkbox, Key, Value, Description)
- **Tab state cache**: per-tab editor state and response preserved when switching tabs
- **Auto-select editor tab**: first tab with content is auto-selected on request load
- **Sidebar width**: increased from 250px to 320px for longer request names
- **Method label fix**: full method names shown in tree (POST, DELETE, PATCH, OPTIONS) instead of 3-char truncation

GUI fixes:
- **Status bar icons**: Tabler Icons (MIT) replace Unicode/emoji text -- settings, theme, GitHub, search, cookie, license, proxy icons as 16x16 PNGs
- **Status bar actions**: Theme toggle, GitHub repo link, License info buttons with icon-only labels
- **Global search rewrite**: empty query shows all items (API requests, findings, sitemap), endpoint matching, wrap-around arrow navigation, single-click select, focus-loss dismissal
- **Global keyboard shortcuts**: Ctrl+K opens global search (works in text fields), Ctrl+, opens Preferences
- **Cookies status bar click**: opens PreferencesDialog
- **Search popup fix**: Enter no longer closes empty search dialog
- **API requests in search**: API Client request names wired into GlobalSearchDialog
- **Status reason phrase**: "401 Unauthorized" instead of "401 401"
- **Response status badges**: color-coded HTTP status in response panel

Other:
- **RequestExecutor**: auth variable interpolation in all auth config fields
- Version bump to 2.2.1

<details>
<summary><kbd>Previous releases (click to expand)</kbd></summary>

### v2.2.0

**Scanner Expansion -- 13 new modules, OAST, SARIF, FP reduction**
Expand Down Expand Up @@ -553,10 +614,6 @@ Other:
- **Knowledge base**: expanded from 25 to 34 entries with new module coverage
- Version bump to 2.2.0


<details>
<summary><kbd>Previous releases (click to expand)</kbd></summary>

### v2.1.0

**API Client Module - Bruno-inspired collections, environments, runner**
Expand All @@ -573,7 +630,7 @@ Other:
**GUI/TUI Integration**
- **API Client tab (F8)**: request tabs color-coded by method (GET=green, POST=yellow, DELETE=red), collection tree with right-click menu (new request/folder, duplicate, delete, rename), environment selector with "..." configure button
- **Environment Editor**: modal dialog for managing environments and variables (name, value, secret toggle, enabled toggle)
- **PreferencesDialog**: Bruno-style categorized settings (General, Display, Proxy, Certificates, Keybindings, About) - SSL/TLS toggle, proxy config for Burp/ZAP, client certificates table
- **PreferencesDialog**: Bruno-style categorized settings (General, Themes, Display, Proxy, Certificates, Notifications, Keybindings, License, Support, About) - SSL/TLS toggle, proxy config for Burp/ZAP, client certificates table
- **StatusBar shortcuts**: Search (jumps to Findings search), Cookies count, Proxy status, Privacy toggle, version display
- **F-key shift**: API Client on F8, Assistant moved to F9

Expand Down
6 changes: 3 additions & 3 deletions anarchrypto
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,15 @@ if [ "$TUI_MODE" = true ]; then
if command -v kitty &>/dev/null; then
export _ANARCRY_READY=1
exec kitty \
--override initial_window_width=180c \
--override initial_window_height=82c \
--override initial_window_width=200c \
--override initial_window_height=92c \
--title "AnarCry v${VERSION}" \
bash "$SELF" "$@"
fi
printf '\e[8;54;220t'
if command -v xdotool &>/dev/null; then
WID=$(xdotool getactivewindow 2>/dev/null)
[ -n "$WID" ] && xdotool windowsize "$WID" 1660 1080 2>/dev/null
[ -n "$WID" ] && xdotool windowsize "$WID" 1920 1200 2>/dev/null
fi
sleep 0.4
stty rows 54 cols 220 2>/dev/null
Expand Down
6 changes: 5 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ plugins {
}

group = 'gg.effront'
version = '2.2.0'
version = '2.2.1'

java {
toolchain {
Expand All @@ -27,6 +27,10 @@ dependencies {
implementation 'com.microsoft.playwright:playwright:1.49.0'
implementation 'com.google.code.gson:gson:2.11.0'

// Scripting (GraalJS)
implementation 'org.graalvm.polyglot:polyglot:24.1.1'
implementation 'org.graalvm.polyglot:js-community:24.1.1'

// GUI (Swing + FlatLaf)
implementation 'com.formdev:flatlaf:3.5.4'
implementation 'com.formdev:flatlaf-intellij-themes:3.5.4'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ public AuthInjectorRegistry() {
register(new DigestAuthInjector());
register(new OAuth2Injector());
register(new WsseInjector());
register(new NtlmInjector());
register(new OAuth1Injector());
register(new AwsV4Injector());
register(new EdgeGridInjector());
}

public void register(AuthInjector injector) {
Expand Down
114 changes: 114 additions & 0 deletions src/main/java/gg/effront/anarcry/apiclient/auth/AwsV4Injector.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package gg.effront.anarcry.apiclient.auth;

import gg.effront.anarcry.apiclient.model.AuthConfig;
import gg.effront.anarcry.apiclient.model.HttpRequestDef;
import gg.effront.anarcry.apiclient.vars.VariableStore;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.HexFormat;
import java.util.TreeMap;
import java.util.stream.Collectors;

public class AwsV4Injector implements AuthInjector {

private static final DateTimeFormatter AMZ_DATE_FMT = DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'")
.withZone(ZoneOffset.UTC);
private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyyMMdd").withZone(ZoneOffset.UTC);

@Override
public String mode() {
return "awsv4";
}

@Override
public HttpRequestDef inject(HttpRequestDef request, AuthConfig auth, VariableStore vars) {
if (!(auth instanceof AuthConfig.AwsV4 aws)) {
return request;
}

Instant now = Instant.now();
String amzDate = AMZ_DATE_FMT.format(now);
String dateStamp = DATE_FMT.format(now);

URI uri = URI.create(request.url());
String host = uri.getHost();
if (uri.getPort() > 0 && uri.getPort() != 443 && uri.getPort() != 80) {
host = host + ":" + uri.getPort();
}
String path = uri.getRawPath() != null && !uri.getRawPath().isEmpty() ? uri.getRawPath() : "/";
String query = uri.getRawQuery() != null ? uri.getRawQuery() : "";

String service = aws.service() != null && !aws.service().isEmpty() ? aws.service() : "execute-api";
String region = aws.region() != null && !aws.region().isEmpty() ? aws.region() : "us-east-1";

TreeMap<String, String> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
headers.put("host", host);
headers.put("x-amz-date", amzDate);
if (aws.sessionToken() != null && !aws.sessionToken().isEmpty()) {
headers.put("x-amz-security-token", aws.sessionToken());
}

String signedHeaders = headers.keySet().stream().map(String::toLowerCase).sorted()
.collect(Collectors.joining(";"));
String canonicalHeaders = headers.entrySet().stream().sorted(java.util.Map.Entry.comparingByKey())
.map(e -> e.getKey().toLowerCase() + ":" + e.getValue().trim()).collect(Collectors.joining("\n"))
+ "\n";

String payloadHash = sha256Hex("");
String canonicalRequest = request.method().toUpperCase() + "\n" + path + "\n" + query + "\n" + canonicalHeaders
+ "\n" + signedHeaders + "\n" + payloadHash;

String credentialScope = dateStamp + "/" + region + "/" + service + "/aws4_request";
String stringToSign = "AWS4-HMAC-SHA256\n" + amzDate + "\n" + credentialScope + "\n"
+ sha256Hex(canonicalRequest);

byte[] signingKey = getSignatureKey(aws.secretAccessKey(), dateStamp, region, service);
String signature = HexFormat.of().formatHex(hmacSha256(signingKey, stringToSign));

String authorization = "AWS4-HMAC-SHA256 Credential=" + aws.accessKeyId() + "/" + credentialScope
+ ", SignedHeaders=" + signedHeaders + ", Signature=" + signature;

HttpRequestDef result = AuthInjectorRegistry.addHeader(request, "Authorization", authorization);
result = AuthInjectorRegistry.addHeader(result, "X-Amz-Date", amzDate);
if (aws.sessionToken() != null && !aws.sessionToken().isEmpty()) {
result = AuthInjectorRegistry.addHeader(result, "X-Amz-Security-Token", aws.sessionToken());
}
return result;
}

private byte[] getSignatureKey(String key, String dateStamp, String region, String service) {
byte[] kDate = hmacSha256(("AWS4" + key).getBytes(StandardCharsets.UTF_8), dateStamp);
byte[] kRegion = hmacSha256(kDate, region);
byte[] kService = hmacSha256(kRegion, service);
return hmacSha256(kService, "aws4_request");
}

private static byte[] hmacSha256(byte[] key, String data) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(key, "HmacSHA256"));
return mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
throw new RuntimeException("HMAC-SHA256 failed", e);
}
}

private static String sha256Hex(String data) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(data.getBytes(StandardCharsets.UTF_8));
return HexFormat.of().formatHex(hash);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("SHA-256 not available", e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ private static String extractUri(String url) {
@SuppressWarnings("java:S4790")
private static String md5(String input) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
MessageDigest md = MessageDigest.getInstance("MD5"); // nosemgrep: use-of-md5 -- RFC 7616 Digest Auth
// requires MD5
byte[] digest = md.digest(input.getBytes(StandardCharsets.UTF_8));
return HexFormat.of().formatHex(digest);
} catch (NoSuchAlgorithmException e) {
Expand Down
Loading
Loading