mcpField
S
security-audit

security-audit

github.com/decebalsセキュリティ761322 回インストール8 週間前に更新
リポジトリ

security-audit について

OWASP Top 10 とセキュアコーディングの実践に基づく、Java アプリケーション開発者向けのセキュリティレビューガイドです。入力検証、認証、依存関係、設定の確認を扱います。

主な機能

  1. 01OWASP Top 10 のリスク一覧
  2. 02入力検証とインジェクション対策
  3. 03認証と認可の確認
  4. 04シークレット、ログ、デシリアライズの指針
  5. 05依存関係とセキュリティ設定の確認

活用シーン

  1. 01リリース前に Java アプリの安全性を確認する
  2. 02認証・認可コードをレビューする
  3. 03SQL インジェクションや XSS のリスクを探す
  4. 04依存関係の脆弱性と設定を確認する

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/decebals/claude-code-java --skill security-audit

インストール後、Claude Code で/security-auditを入力して読み込まれたか確認します。

同梱ファイルREADME.md

詳細

セキュリティ監査スキル

OWASP Top 10 とセキュアコーディングの実践に基づく、Java アプリケーション向けセキュリティチェックリストです。

使用する場面

  • セキュリティコードレビュー
  • 本番リリース前
  • ユーザーが「セキュリティ」「脆弱性」「OWASP」について尋ねたとき
  • 認証/認可コードのレビュー
  • インジェクション脆弱性の確認

OWASP Top 10 クイックリファレンス

#リスクJava での対策
A01アクセス制御の不備ロールベースのチェック、デフォルトで拒否
A02暗号化の失敗強力なアルゴリズムを使用し、秘密情報をハードコードしない
A03インジェクションパラメーター化クエリ、入力検証
A04安全でない設計脅威モデリング、安全なデフォルト設定
A05セキュリティ設定のミスデバッグを無効にし、安全なヘッダーを設定
A06脆弱なコンポーネント依存関係のスキャン、更新
A07認証の失敗強力なパスワード、多要素認証、セッション管理
A08データの完全性の失敗署名の検証、安全なデシリアライズ
A09ログ記録の失敗セキュリティイベントを記録し、機密データを記録しない
A10SSRFURL の検証、ドメインの許可リスト

入力検証(すべてのフレームワーク)

Bean Validation(JSR 380)

Spring、Quarkus、Jakarta EE、およびスタンドアロンで動作します。

// ✅ GOOD: Validate at boundary
public class CreateUserRequest {

    @NotNull(message = "Username is required")
    @Size(min = 3, max = 50, message = "Username must be 3-50 characters")
    @Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "Username can only contain letters, numbers, underscore")
    private String username;

    @NotNull
    @Email(message = "Invalid email format")
    private String email;

    @NotNull
    @Size(min = 8, max = 100)
    @Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).*$",
             message = "Password must contain uppercase, lowercase, and number")
    private String password;

    @Min(value = 0, message = "Age cannot be negative")
    @Max(value = 150, message = "Invalid age")
    private Integer age;
}

// Controller/Resource - trigger validation
public Response createUser(@Valid CreateUserRequest request) {
    // request is already validated
}
カスタムバリデーター
// Custom annotation
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = SafeHtmlValidator.class)
public @interface SafeHtml {
    String message() default "Contains unsafe HTML";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

// Validator implementation
public class SafeHtmlValidator implements ConstraintValidator<SafeHtml, String> {

    private static final Pattern DANGEROUS_PATTERN = Pattern.compile(
        "<script|javascript:|on\\w+\\s*=", Pattern.CASE_INSENSITIVE
    );

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null) return true;
        return !DANGEROUS_PATTERN.matcher(value).find();
    }
}
許可リストと拒否リスト
// ❌ BAD: Blocklist (attackers find bypasses)
if (input.contains("<script>")) {
    throw new ValidationException("Invalid input");
}

// ✅ GOOD: Allowlist (only permit known-good)
private static final Pattern SAFE_NAME = Pattern.compile("^[a-zA-Z\\s'-]{1,100}$");

if (!SAFE_NAME.matcher(input).matches()) {
    throw new ValidationException("Invalid name format");
}

SQL インジェクションの防止

JPA/Hibernate(すべてのフレームワーク)
// ✅ GOOD: Parameterized queries
@Query("SELECT u FROM User u WHERE u.email = :email")
Optional<User> findByEmail(@Param("email") String email);

// ✅ GOOD: Criteria API
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
Root<User> user = query.from(User.class);
query.where(cb.equal(user.get("email"), email));  // Safe

// ✅ GOOD: Named parameters
TypedQuery<User> query = entityManager.createQuery(
    "SELECT u FROM User u WHERE u.status = :status", User.class);
query.setParameter("status", status);  // Safe

// ❌ BAD: String concatenation
String jpql = "SELECT u FROM User u WHERE u.email = '" + email + "'";  // VULNERABLE!
ネイティブクエリ
// ✅ GOOD: Parameterized native query
@Query(value = "SELECT * FROM users WHERE email = ?1", nativeQuery = true)
User findByEmailNative(String email);

// ❌ BAD: Concatenated native query
String sql = "SELECT * FROM users WHERE email = '" + email + "'";  // VULNERABLE!
JDBC(プレーン Java)
// ✅ GOOD: PreparedStatement
String sql = "SELECT * FROM users WHERE email = ? AND status = ?";
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
    stmt.setString(1, email);
    stmt.setString(2, status);
    ResultSet rs = stmt.executeQuery();
}

// ❌ BAD: Statement with concatenation
String sql = "SELECT * FROM users WHERE email = '" + email + "'";  // VULNERABLE!
Statement stmt = connection.createStatement();
stmt.executeQuery(sql);

XSS の防止

出力エンコーディング
// ✅ GOOD: Use templating engine's auto-escaping

// Thymeleaf - auto-escapes by default
<p th:text="${userInput}">...</p>  // Safe

// To display HTML (dangerous, use carefully):
<p th:utext="${trustedHtml}">...</p>  // Only for trusted content!

// ✅ GOOD: Manual encoding when needed
import org.owasp.encoder.Encode;

String safe = Encode.forHtml(userInput);
String safeJs = Encode.forJavaScript(userInput);
String safeUrl = Encode.forUriComponent(userInput);

OWASP Encoder の Maven 依存関係:

<dependency>
    <groupId>org.owasp.encoder</groupId>
    <artifactId>encoder</artifactId>
    <version>1.2.3</version>
</dependency>
コンテンツセキュリティポリシー
// Add CSP header to prevent inline scripts

// Spring Boot
@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.headers(headers -> headers
            .contentSecurityPolicy(csp -> csp
                .policyDirectives("default-src 'self'; script-src 'self'; style-src 'self'")
            )
        );
        return http.build();
    }
}

// Servlet Filter (works everywhere)
@WebFilter("/*")
public class SecurityHeadersFilter implements Filter {
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) res;
        response.setHeader("Content-Security-Policy", "default-src 'self'");
        response.setHeader("X-Content-Type-Options", "nosniff");
        response.setHeader("X-Frame-Options", "DENY");
        response.setHeader("X-XSS-Protection", "1; mode=block");
        chain.doFilter(req, res);
    }
}

CSRF 対策

Spring Security
// CSRF enabled by default for browser clients
@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            // For REST APIs with JWT (stateless) - can disable CSRF
            .csrf(csrf -> csrf.disable())

            // For browser apps with sessions - keep CSRF enabled
            .csrf(csrf -> csrf
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
            );
        return http.build();
    }
}
Quarkus
# application.properties
quarkus.http.csrf.enabled=true
quarkus.http.csrf.cookie-name=XSRF-TOKEN

認証と認可

パスワードの保存
// ✅ GOOD: Use BCrypt or Argon2
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;

// BCrypt (widely supported)
PasswordEncoder encoder = new BCryptPasswordEncoder(12);  // strength 12
String hash = encoder.encode(rawPassword);
boolean matches = encoder.matches(rawPassword, hash);

// Argon2 (recommended for new projects)
PasswordEncoder encoder = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8();
String hash = encoder.encode(rawPassword);

// ❌ BAD: MD5, SHA1, SHA256 without salt
String hash = DigestUtils.md5Hex(password);  // NEVER for passwords!
認可チェック
// ✅ GOOD: Check authorization at service layer
@Service
public class DocumentService {

    public Document getDocument(Long documentId, User currentUser) {
        Document doc = documentRepository.findById(documentId)
            .orElseThrow(() -> new NotFoundException("Document not found"));

        // Authorization check
        if (!doc.getOwnerId().equals(currentUser.getId()) &&
            !currentUser.hasRole("ADMIN")) {
            throw new AccessDeniedException("Not authorized to access this document");
        }

        return doc;
    }
}

// ❌ BAD: Only check at controller level, trust user input
@GetMapping("/documents/{id}")
public Document getDocument(@PathVariable Long id) {
    return documentRepository.findById(id).orElseThrow();  // No auth check!
}
Spring Security アノテーション
@PreAuthorize("hasRole('ADMIN')")
public void adminOnly() { }

@PreAuthorize("hasRole('USER') and #userId == authentication.principal.id")
public void ownDataOnly(Long userId) { }

@PreAuthorize("@authService.canAccess(#documentId, authentication)")
public Document getDocument(Long documentId) { }

シークレットの管理

シークレットをハードコードしない
// ❌ BAD: Hardcoded secrets
private static final String API_KEY = "sk-1234567890abcdef";
private static final String DB_PASSWORD = "admin123";

// ✅ GOOD: Environment variables
String apiKey = System.getenv("API_KEY");

// ✅ GOOD: External configuration
@Value("${api.key}")
private String apiKey;

// ✅ GOOD: Secrets manager
@Autowired
private SecretsManager secretsManager;
String apiKey = secretsManager.getSecret("api-key");
設定ファイル
# ✅ GOOD: Reference environment variables
spring:
  datasource:
    password: ${DB_PASSWORD}

api:
  key: ${API_KEY}

# ❌ BAD: Hardcoded in application.yml
spring:
  datasource:
    password: admin123  # NEVER!
.gitignore
# Never commit these
.env
*.pem
*.key
*credentials*
*secret*
application-local.yml

安全なデシリアライズ

Java シリアライズを避ける
// ❌ DANGEROUS: Java ObjectInputStream
ObjectInputStream ois = new ObjectInputStream(untrustedInput);
Object obj = ois.readObject();  // Remote Code Execution risk!

// ✅ GOOD: Use JSON with Jackson
ObjectMapper mapper = new ObjectMapper();
// Disable dangerous features
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.activateDefaultTyping(
    LaissezFaireSubTypeValidator.instance,
    ObjectMapper.DefaultTyping.NON_FINAL
);  // Be careful with polymorphic types!

User user = mapper.readValue(json, User.class);
Jackson のセキュリティ
// ✅ Configure Jackson safely
@Configuration
public class JacksonConfig {

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();

        // Prevent unknown properties exploitation
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

        // Don't allow class type in JSON (prevents gadget attacks)
        mapper.deactivateDefaultTyping();

        return mapper;
    }
}

依存関係のセキュリティ

OWASP Dependency Check

Maven:

<plugin>
    <groupId>org.owasp</groupId>
    <artifactId>dependency-check-maven</artifactId>
    <version>9.0.7</version>
    <executions>
        <execution>
            <goals>
                <goal>check</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <failBuildOnCVSS>7</failBuildOnCVSS>  
    </configuration>
</plugin>

実行:

mvn dependency-check:check
# Report: target/dependency-check-report.html
依存関係を最新の状態に保つ
# Check for updates
mvn versions:display-dependency-updates

# Update to latest
mvn versions:use-latest-releases

セキュリティヘッダー

推奨ヘッダー
ヘッダー値目的
Content-Security-Policydefault-src 'self'XSS を防止
X-Content-Type-OptionsnosniffMIME スニッフィングを防止
X-Frame-OptionsDENYクリックジャッキングを防止
Strict-Transport-Securitymax-age=31536000HTTPS を強制
X-XSS-Protection1; mode=blockレガシー XSS フィルター
Spring Boot の設定
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.headers(headers -> headers
        .contentSecurityPolicy(csp -> csp.policyDirectives("default-src 'self'"))
        .frameOptions(frame -> frame.deny())
        .httpStrictTransportSecurity(hsts -> hsts.maxAgeInSeconds(31536000))
        .contentTypeOptions(Customizer.withDefaults())
    );
    return http.build();
}

セキュリティイベントのログ記録

// ✅ Log security-relevant events
log.info("User login successful", kv("userId", userId), kv("ip", clientIp));
log.warn("Failed login attempt", kv("username", username), kv("ip", clientIp), kv("attempt", attemptCount));
log.warn("Access denied", kv("userId", userId), kv("resource", resourceId), kv("action", action));
log.error("Authentication failure", kv("reason", reason), kv("ip", clientIp));

// ❌ NEVER log sensitive data
log.info("Login: user={}, password={}", username, password);  // NEVER!
log.debug("Request body: {}", requestWithCreditCard);  // NEVER!

セキュリティチェックリスト

コードレビュー
  • 許可リストパターンで入力を検証している
  • SQL クエリでパラメーターを使用している(文字列連結なし)
  • コンテキスト(HTML、JS、URL)に応じて出力をエンコードしている
  • サービス層で認可をチェックしている
  • シークレットをハードコードしていない
  • BCrypt/Argon2 でパスワードをハッシュ化している
  • 機密データをログに記録していない
  • CSRF 対策を有効にしている(ブラウザーアプリの場合)
設定
  • HTTPS を強制している
  • セキュリティヘッダーを設定している
  • 本番環境でデバッグ/開発機能を無効にしている
  • デフォルトの認証情報を変更している
  • エラーメッセージから内部の詳細が漏えいしない
依存関係
  • 既知の脆弱性がない(OWASP チェック)
  • 依存関係を最新の状態に保っている
  • 不要な依存関係を削除している

関連スキル

  • java-code-review - 一般的なコードレビュー
  • maven-dependency-audit - 依存関係の脆弱性スキャン
  • logging-patterns - 安全なログ記録の実践

コメント